code refactor

This commit is contained in:
MaysWind
2025-01-23 21:57:53 +08:00
parent a9805b8fff
commit 28322bad5e
4 changed files with 167 additions and 189 deletions
+96
View File
@@ -0,0 +1,96 @@
import { ref, computed, watch } from 'vue';
import { useI18n } from '@/locales/helpers.ts';
import { DEFAULT_CHART_COLORS } from '@/consts/color.ts';
import { isNumber } from '@/lib/common.ts';
import { formatPercent } from '@/lib/numeral.ts';
export interface CommonPieChartDataItem {
id: string;
name: string;
displayName: string;
value: number;
percent: number;
actualPercent: number;
color: string;
sourceItem: Record<string, unknown>;
displayPercent?: string;
displayValue?: string;
}
export interface CommonPieChartProps {
skeleton?: boolean;
items: Record<string, unknown>[];
idField?: string;
nameField: string;
valueField: string;
percentField?: string;
colorField?: string;
hiddenField?: string;
minValidPercent?: number;
defaultCurrency?: string;
showValue?: boolean;
enableClickItem?: boolean;
}
export function usePieChartBase(props: CommonPieChartProps) {
const { formatAmountWithCurrency } = useI18n();
const selectedIndex = ref<number>(0);
const validItems = computed<CommonPieChartDataItem[]>(() => {
let totalValidValue = 0;
for (let i = 0; i < props.items.length; i++) {
const item = props.items[i];
const value = item[props.valueField];
if (isNumber(value) && value > 0 && (!props.hiddenField || !item[props.hiddenField])) {
totalValidValue += value;
}
}
const validItems: CommonPieChartDataItem[] = [];
for (let i = 0; i < props.items.length; i++) {
const item = props.items[i];
const value = item[props.valueField];
const percent = props.percentField ? item[props.percentField] : -1;
if (isNumber(value) && value > 0 &&
(!props.hiddenField || !item[props.hiddenField]) &&
(!props.minValidPercent || value / totalValidValue > props.minValidPercent)) {
const finalItem: CommonPieChartDataItem = {
id: (props.idField && item[props.idField]) ? item[props.idField] as string : item[props.nameField] as string,
name: (props.idField && item[props.idField]) ? item[props.idField] as string : item[props.nameField] as string,
displayName: item[props.nameField] as string,
value: value,
percent: (isNumber(percent) && percent >= 0) ? percent : (value / totalValidValue * 100),
actualPercent: value / totalValidValue,
color: (props.colorField && item[props.colorField]) ? item[props.colorField] as string : DEFAULT_CHART_COLORS[validItems.length % DEFAULT_CHART_COLORS.length],
sourceItem: item
};
finalItem.displayPercent = formatPercent(finalItem.percent, 2, '&lt;0.01');
finalItem.displayValue = formatAmountWithCurrency(finalItem.value, props.defaultCurrency) as string;
validItems.push(finalItem);
}
}
return validItems;
});
watch(() => props.items, () => {
selectedIndex.value = 0;
});
return {
// states
selectedIndex,
// computed states
validItems
};
}
+21 -76
View File
@@ -4,51 +4,29 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watch } from 'vue'; import { ref, computed } from 'vue';
import { useTheme } from 'vuetify'; import { useTheme } from 'vuetify';
import type { ECElementEvent } from 'echarts/core'; import type { ECElementEvent } from 'echarts/core';
import type { CallbackDataParams } from 'echarts/types/dist/shared'; import type { CallbackDataParams } from 'echarts/types/dist/shared';
import { useI18n } from '@/locales/helpers.ts'; import { useI18n } from '@/locales/helpers.ts';
import { type CommonPieChartDataItem, type CommonPieChartProps, usePieChartBase } from '@/components/base/PieChartBase.ts'
import type { ColorValue } from '@/core/color.ts'; import type { ColorValue } from '@/core/color.ts';
import { ThemeType } from '@/core/theme.ts'; import { ThemeType } from '@/core/theme.ts';
import { DEFAULT_ICON_COLOR, DEFAULT_CHART_COLORS } from '@/consts/color.ts'; import { DEFAULT_ICON_COLOR } from '@/consts/color.ts';
import { isNumber } from '@/lib/common.ts'; interface DesktopPieChartDataItem extends CommonPieChartDataItem {
import { formatPercent } from '@/lib/numeral.ts';
interface DesktopPieChartDataItem {
id: string;
name: string;
displayName: string;
value: number;
percent: number;
actualPercent: number;
itemStyle: { itemStyle: {
color: ColorValue; color: ColorValue;
}; };
selected: boolean; selected: boolean;
sourceItem: Record<string, unknown>;
displayPercent?: string;
displayValue?: string;
} }
const props = defineProps<{ interface DesktopPieChartProps extends CommonPieChartProps {}
skeleton?: boolean;
items: Record<string, unknown>[]; const props = defineProps<DesktopPieChartProps>();
idField?: string;
nameField: string;
valueField: string;
percentField?: string;
colorField?: string;
hiddenField?: string;
minValidPercent?: number;
defaultCurrency?: string;
showValue?: boolean;
enableClickItem?: boolean;
}>();
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'click', value: Record<string, unknown>): void; (e: 'click', value: Record<string, unknown>): void;
@@ -57,9 +35,9 @@ const emit = defineEmits<{
const theme = useTheme(); const theme = useTheme();
const { formatAmountWithCurrency } = useI18n(); const { formatAmountWithCurrency } = useI18n();
const { selectedIndex, validItems } = usePieChartBase(props);
const selectedLegends = ref<Record<string, boolean> | null>(null); const selectedLegends = ref<Record<string, boolean> | null>(null);
const selectedIndex = ref<number>(0);
const isDarkMode = computed<boolean>(() => theme.global.name.value === ThemeType.Dark); const isDarkMode = computed<boolean>(() => theme.global.name.value === ThemeType.Dark);
@@ -82,50 +60,21 @@ const itemsMap = computed<Record<string, Record<string, unknown>>>(() => {
return map; return map;
}); });
const validItems = computed<DesktopPieChartDataItem[]>(() => { const seriesData = computed<DesktopPieChartDataItem[]>(() => {
let totalValidValue = 0; const ret: DesktopPieChartDataItem[] = [];
for (let i = 0; i < props.items.length; i++) { for (let i = 0; i < validItems.value.length; i++) {
const item = props.items[i]; const item = validItems.value[i];
const value = item[props.valueField]; ret.push({
...item,
if (isNumber(value) && value > 0 && (!props.hiddenField || !item[props.hiddenField])) { itemStyle: {
totalValidValue += value; color: getColor(item.color),
} },
selected: true
});
} }
const validItems: DesktopPieChartDataItem[] = []; return ret;
for (let i = 0; i < props.items.length; i++) {
const item = props.items[i];
const value = item[props.valueField];
const percent = props.percentField ? item[props.percentField] : -1;
if (isNumber(value) && value > 0 &&
(!props.hiddenField || !item[props.hiddenField]) &&
(!props.minValidPercent || value / totalValidValue > props.minValidPercent)) {
const finalItem: DesktopPieChartDataItem = {
id: (props.idField && item[props.idField]) ? item[props.idField] as string : item[props.nameField] as string,
name: (props.idField && item[props.idField]) ? item[props.idField] as string : item[props.nameField] as string,
displayName: item[props.nameField] as string,
value: value,
percent: (isNumber(percent) && percent >= 0) ? percent : (value / totalValidValue * 100),
actualPercent: value / totalValidValue,
itemStyle: {
color: getColor((props.colorField && item[props.colorField]) ? item[props.colorField] as ColorValue : DEFAULT_CHART_COLORS[validItems.length % DEFAULT_CHART_COLORS.length]),
},
selected: true,
sourceItem: item
};
finalItem.displayPercent = formatPercent(finalItem.percent, 2, '&lt;0.01');
finalItem.displayValue = formatAmountWithCurrency(finalItem.value, props.defaultCurrency) as string;
validItems.push(finalItem);
}
}
return validItems;
}); });
const hasUnselectedItem = computed<boolean>(() => { const hasUnselectedItem = computed<boolean>(() => {
@@ -221,7 +170,7 @@ const chartOptions = computed(() => {
series: [ series: [
{ {
type: 'pie', type: 'pie',
data: validItems.value, data: seriesData.value,
top: 50, top: 50,
startAngle: -90 + firstItemAndHalfCurrentItemTotalPercent.value * 360, startAngle: -90 + firstItemAndHalfCurrentItemTotalPercent.value * 360,
emphasis: { emphasis: {
@@ -311,10 +260,6 @@ function onLegendSelectChanged(e: { selected: Record<string, boolean> }): void {
selectedIndex.value = newSelectedIndex; selectedIndex.value = newSelectedIndex;
} }
} }
watch(() => props.items, () => {
selectedIndex.value = 0;
});
</script> </script>
<style scoped> <style scoped>
+49 -113
View File
@@ -58,7 +58,7 @@
</p> </p>
<f7-link class="pie-chart-selected-item-info" :no-link-class="!enableClickItem" v-if="selectedItem" @click="clickItem(selectedItem)"> <f7-link class="pie-chart-selected-item-info" :no-link-class="!enableClickItem" v-if="selectedItem" @click="clickItem(selectedItem)">
<span class="skeleton-text" v-if="skeleton">Name</span> <span class="skeleton-text" v-if="skeleton">Name</span>
<span v-else-if="!skeleton && selectedItem.name">{{ selectedItem.name }}</span> <span v-else-if="!skeleton && selectedItem.displayName">{{ selectedItem.displayName }}</span>
<span class="skeleton-text" v-if="skeleton">Value</span> <span class="skeleton-text" v-if="skeleton">Value</span>
<span v-else-if="!skeleton && showValue" :style="getColorStyle(selectedItem ? selectedItem.color : '')">{{ selectedItem.displayValue }}</span> <span v-else-if="!skeleton && showValue" :style="getColorStyle(selectedItem ? selectedItem.color : '')">{{ selectedItem.displayValue }}</span>
<f7-icon class="item-navigate-icon" f7="chevron_right" v-if="enableClickItem"></f7-icon> <f7-icon class="item-navigate-icon" f7="chevron_right" v-if="enableClickItem"></f7-icon>
@@ -77,96 +77,34 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watch } from 'vue'; import { computed } from 'vue';
import { useI18n } from '@/locales/helpers.ts'; import { useI18n } from '@/locales/helpers.ts';
import { type CommonPieChartDataItem, type CommonPieChartProps, usePieChartBase } from '@/components/base/PieChartBase.ts'
import type { ColorValue } from '@/core/color.ts'; import type { ColorValue } from '@/core/color.ts';
import { DEFAULT_ICON_COLOR, DEFAULT_CHART_COLORS } from '@/consts/color.ts'; import { DEFAULT_ICON_COLOR } from '@/consts/color.ts';
import { isNumber } from '@/lib/common.ts'; interface MobilePieChartDataItem extends CommonPieChartDataItem {}
import { formatPercent } from '@/lib/numeral.ts';
interface MobilePieChartDataItem { interface MobilePieChartProps extends CommonPieChartProps {
name: string;
value: number;
percent: number;
actualPercent: number;
color: ColorValue;
sourceItem: Record<string, unknown>;
displayPercent?: string;
displayValue?: string;
}
const props = defineProps<{
skeleton?: boolean;
items: Record<string, unknown>[];
nameField: string;
valueField: string;
percentField?: string;
colorField?: string;
hiddenField?: string;
minValidPercent?: number;
defaultCurrency?: string;
showValue?: boolean;
showCenterText?: boolean; showCenterText?: boolean;
showSelectedItemInfo?: boolean; showSelectedItemInfo?: boolean;
enableClickItem?: boolean;
centerTextBackground?: ColorValue; centerTextBackground?: ColorValue;
}>(); }
const props = defineProps<MobilePieChartProps>();
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'click', value: Record<string, unknown>): void; (e: 'click', value: Record<string, unknown>): void;
}>(); }>();
const { tt, formatAmountWithCurrency } = useI18n(); const { tt } = useI18n();
const { selectedIndex, validItems } = usePieChartBase(props);
const diameter: number = 100; const diameter: number = 100;
const circumference: number = diameter * Math.PI; const circumference: number = diameter * Math.PI;
const selectedIndex = ref<number>(0);
const validItems = computed<MobilePieChartDataItem[]>(() => {
let totalValidValue = 0;
for (let i = 0; i < props.items.length; i++) {
const item = props.items[i];
const value = item[props.valueField];
if (isNumber(value) && value > 0 && (!props.hiddenField || !item[props.hiddenField])) {
totalValidValue += value;
}
}
const validItems: MobilePieChartDataItem[] = [];
for (let i = 0; i < props.items.length; i++) {
const item = props.items[i];
const value = item[props.valueField];
const percent = props.percentField ? item[props.percentField] : -1;
if (isNumber(value) && value > 0 &&
(!props.hiddenField || !item[props.hiddenField]) &&
(!props.minValidPercent || value / totalValidValue > props.minValidPercent)) {
const finalItem: MobilePieChartDataItem = {
name: item[props.nameField] as string,
value: value,
percent: (isNumber(percent) && percent >= 0) ? percent : (value / totalValidValue * 100),
actualPercent: value / totalValidValue,
color: (props.colorField && item[props.colorField]) ? item[props.colorField] as ColorValue : DEFAULT_CHART_COLORS[validItems.length % DEFAULT_CHART_COLORS.length],
sourceItem: item
};
finalItem.displayPercent = formatPercent(finalItem.percent, 2, '&lt;0.01');
finalItem.displayValue = formatAmountWithCurrency(finalItem.value, props.defaultCurrency) as string;
validItems.push(finalItem);
}
}
return validItems;
});
const totalValidValue = computed<number>(() => { const totalValidValue = computed<number>(() => {
let totalValidValue = 0; let totalValidValue = 0;
@@ -199,44 +137,6 @@ const itemCommonDashOffset = computed<number>(() => {
return offset; return offset;
}); });
const selectedItem = computed<MobilePieChartDataItem | null>(() => {
if (!validItems.value || !validItems.value.length) {
return null;
}
let index = selectedIndex.value;
if (index < 0 || index >= validItems.value.length) {
index = 0;
}
return validItems.value[index];
});
watch(() => props.items, () => {
selectedIndex.value = 0;
});
function switchSelectedIndex(index: number): void {
selectedIndex.value = index;
}
function switchSelectedItem(offset: number): void {
let newSelectedIndex = selectedIndex.value + offset;
while (newSelectedIndex < 0) {
newSelectedIndex += validItems.value.length;
}
selectedIndex.value = newSelectedIndex % validItems.value.length;
}
function clickItem(item: MobilePieChartDataItem): void {
if (props.enableClickItem) {
emit('click', item.sourceItem);
}
}
function getColor(color: ColorValue): ColorValue { function getColor(color: ColorValue): ColorValue {
if (color && color !== DEFAULT_ICON_COLOR) { if (color && color !== DEFAULT_ICON_COLOR) {
color = '#' + color; color = '#' + color;
@@ -248,12 +148,14 @@ function getColor(color: ColorValue): ColorValue {
} }
function getColorStyle(color: ColorValue, additionalFieldName?: string): Record<string, string> { function getColorStyle(color: ColorValue, additionalFieldName?: string): Record<string, string> {
const finalColor = getColor(color);
const ret: Record<string, string> = { const ret: Record<string, string> = {
color: getColor(color) color: finalColor
}; };
if (additionalFieldName) { if (additionalFieldName) {
ret[additionalFieldName] = ret.color; ret[additionalFieldName] = finalColor;
} }
return ret; return ret;
@@ -290,6 +192,40 @@ function getItemDashOffset(item: MobilePieChartDataItem, items: MobilePieChartDa
const allPreviousLength = allPreviousPercent * circumference; const allPreviousLength = allPreviousPercent * circumference;
return circumference - allPreviousLength + offset; return circumference - allPreviousLength + offset;
} }
const selectedItem = computed<MobilePieChartDataItem | null>(() => {
if (!validItems.value || !validItems.value.length) {
return null;
}
let index = selectedIndex.value;
if (index < 0 || index >= validItems.value.length) {
index = 0;
}
return validItems.value[index];
});
function switchSelectedIndex(index: number): void {
selectedIndex.value = index;
}
function switchSelectedItem(offset: number): void {
let newSelectedIndex = selectedIndex.value + offset;
while (newSelectedIndex < 0) {
newSelectedIndex += validItems.value.length;
}
selectedIndex.value = newSelectedIndex % validItems.value.length;
}
function clickItem(item: MobilePieChartDataItem): void {
if (props.enableClickItem) {
emit('click', item.sourceItem);
}
}
</script> </script>
<style scoped> <style scoped>
@@ -60,6 +60,7 @@
:show-center-text="true" :show-center-text="true"
:show-selected-item-info="true" :show-selected-item-info="true"
class="statistics-pie-chart" class="statistics-pie-chart"
name-field="name"
value-field="value" value-field="value"
color-field="color" color-field="color"
center-text-background="#cccccc" center-text-background="#cccccc"