Showing
4 changed files
with
146 additions
and
148 deletions
1 | -import { ApplicationRecordPageParams, CallStatisticsItemType } from './model/record'; | |
1 | +import { | |
2 | + ApplicationRecordPageParams, | |
3 | + CallStatisticsItemType, | |
4 | + ClassifyItemType, | |
5 | +} from './model/record'; | |
2 | 6 | import { PaginationResult } from '/#/axios'; |
3 | 7 | import { defHttp } from '/@/utils/http/axios'; |
4 | 8 | |
... | ... | @@ -40,7 +44,7 @@ export const getApplicationRecordTop = () => { |
40 | 44 | |
41 | 45 | export const getApplicationRecordClassify = (type?: string) => { |
42 | 46 | const joinUrlParams = type ? `?type=${type}` : ''; |
43 | - return defHttp.get({ | |
47 | + return defHttp.get<ClassifyItemType[]>({ | |
44 | 48 | url: `${ApplicationRecordManageApi.OPEN_API_RECORD}/getClassify${joinUrlParams}`, |
45 | 49 | }); |
46 | 50 | }; | ... | ... |
... | ... | @@ -4,23 +4,20 @@ |
4 | 4 | import { useAppStore } from '/@/store/modules/app'; |
5 | 5 | import { useI18n } from '/@/hooks/web/useI18n'; |
6 | 6 | import { Empty } from 'ant-design-vue'; |
7 | + import { EChartsOption, SeriesOption } from 'echarts'; | |
7 | 8 | |
8 | 9 | const { t } = useI18n(); |
9 | 10 | |
10 | - const props = defineProps({ | |
11 | - seriesData: { | |
12 | - type: Array, | |
13 | - default: () => [], | |
14 | - }, | |
15 | - timeLineXAxisData: { | |
16 | - type: Array, | |
17 | - default: () => [], | |
18 | - }, | |
19 | - timeType: { | |
20 | - type: String, | |
21 | - default: 'week', | |
22 | - }, | |
23 | - }); | |
11 | + const props = withDefaults( | |
12 | + defineProps<{ | |
13 | + timeType: string; | |
14 | + chartsData?: Partial<EChartsOption>; | |
15 | + }>(), | |
16 | + { | |
17 | + timeType: 'week', | |
18 | + chartsData: () => ({}), | |
19 | + } | |
20 | + ); | |
24 | 21 | |
25 | 22 | const emits = defineEmits(['emitTimeRange']); |
26 | 23 | |
... | ... | @@ -51,7 +48,8 @@ |
51 | 48 | |
52 | 49 | const { setOptions, resize } = useECharts(chartRef as Ref<HTMLDivElement>); |
53 | 50 | |
54 | - const getOptions: any = () => { | |
51 | + const getOptions = (): EChartsOption => { | |
52 | + const { xAxis, series } = props.chartsData || {}; | |
55 | 53 | return { |
56 | 54 | backgroundColor: skinName.value, |
57 | 55 | tooltip: { |
... | ... | @@ -63,31 +61,12 @@ |
63 | 61 | bottom: '1%', |
64 | 62 | containLabel: true, |
65 | 63 | }, |
66 | - xAxis: { | |
67 | - type: 'time', | |
68 | - splitLine: { show: false }, | |
69 | - lineStyle: { | |
70 | - width: 2, | |
71 | - }, | |
72 | - axisTick: { | |
73 | - show: false, | |
74 | - }, | |
75 | - axisLabel: { | |
76 | - formatter: | |
77 | - props.timeType === 'hour' | |
78 | - ? '{yyyy}-{MM}-{dd} {HH}:{mm}:{ss}' | |
79 | - : props.timeType === 'day' | |
80 | - ? '{yyyy}-{MM}-{dd} {HH}:{mm}:{ss}' | |
81 | - : '{yyyy}-{MM}-{dd}', | |
82 | - showMinLabel: true, | |
83 | - showMaxLabel: true, // 固定显示X轴的最后一条数据 | |
84 | - }, | |
85 | - }, | |
64 | + xAxis, | |
86 | 65 | yAxis: { |
87 | 66 | type: 'value', |
88 | 67 | axisLabel: { formatter: '{value}' }, |
89 | 68 | }, |
90 | - series: props.seriesData, | |
69 | + series, | |
91 | 70 | }; |
92 | 71 | }; |
93 | 72 | |
... | ... | @@ -104,7 +83,7 @@ |
104 | 83 | ); |
105 | 84 | |
106 | 85 | watch( |
107 | - () => props.seriesData, | |
86 | + () => props.chartsData, | |
108 | 87 | () => { |
109 | 88 | setOptions(getOptions()); |
110 | 89 | }, |
... | ... | @@ -141,10 +120,17 @@ |
141 | 120 | </template> |
142 | 121 | </a-radio-group> |
143 | 122 | </template> |
144 | - <div v-show="seriesData.length" ref="chartRef" class="w-full h-80"></div> | |
145 | - <div v-show="!seriesData.length" class="w-full h-72 flex justify-center items-center" | |
146 | - ><Empty :image="Empty.PRESENTED_IMAGE_SIMPLE" | |
147 | - /></div> | |
123 | + <div | |
124 | + v-show="(chartsData?.series as SeriesOption[])?.length" | |
125 | + ref="chartRef" | |
126 | + class="w-full h-80" | |
127 | + ></div> | |
128 | + <div | |
129 | + v-show="!(chartsData?.series as SeriesOption[])?.length" | |
130 | + class="w-full h-72 flex justify-center items-center" | |
131 | + > | |
132 | + <Empty :image="Empty.PRESENTED_IMAGE_SIMPLE" /> | |
133 | + </div> | |
148 | 134 | </a-card> |
149 | 135 | </template> |
150 | 136 | ... | ... |
1 | +import { EChartsOption } from 'echarts'; | |
2 | +import { formatClassifyText } from '../../api/config'; | |
3 | +import { ClassifyDtsItemType, ClassifyItemType } from '/@/api/application/model/record'; | |
1 | 4 | import { DescItem } from '/@/components/Description'; |
2 | 5 | import { BasicColumn } from '/@/components/Table'; |
3 | 6 | import { useI18n } from '/@/hooks/web/useI18n'; |
7 | +import { dateUtil } from '/@/utils/dateUtil'; | |
8 | + | |
9 | +export type DateInterval = 'hour' | 'day' | 'week' | 'month'; | |
4 | 10 | |
5 | 11 | const { t } = useI18n(); |
6 | 12 | |
... | ... | @@ -45,3 +51,85 @@ export const formSchema: DescItem[] = [ |
45 | 51 | label: t('application.record.text.numberOfFailedAttempts'), |
46 | 52 | }, |
47 | 53 | ]; |
54 | + | |
55 | +function generateFullXAxis(type: DateInterval) { | |
56 | + if (type === 'hour') { | |
57 | + return Array.from({ length: 60 / 5 }, (_, index) => | |
58 | + dateUtil() | |
59 | + .subtract(index * 5, 'minute') | |
60 | + .startOf('minute') | |
61 | + .valueOf() | |
62 | + ); | |
63 | + } else if (type === 'day') { | |
64 | + return Array.from({ length: 24 / 2 }, (_, index) => | |
65 | + dateUtil() | |
66 | + .subtract(index * 2, 'hour') | |
67 | + .startOf('hour') | |
68 | + .valueOf() | |
69 | + ); | |
70 | + } else if (type === 'week') { | |
71 | + return Array.from({ length: 7 }, (_, index) => | |
72 | + dateUtil().subtract(index, 'day').startOf('day').valueOf() | |
73 | + ); | |
74 | + } else { | |
75 | + return Array.from({ length: 30 }, (_, index) => | |
76 | + dateUtil().subtract(index, 'day').startOf('day').valueOf() | |
77 | + ); | |
78 | + } | |
79 | +} | |
80 | + | |
81 | +export function formatDateFromType(date: number, type: DateInterval) { | |
82 | + if (type === 'hour') { | |
83 | + return dateUtil(date).format('YYYY-MM-DD HH:mm'); | |
84 | + } else if (type === 'day') { | |
85 | + return dateUtil(date).format('YYYY-MM-DD HH:mm'); | |
86 | + } else { | |
87 | + return dateUtil(date).format('YYYY-MM-DD'); | |
88 | + } | |
89 | +} | |
90 | + | |
91 | +export function transformClassifyItem(data: ClassifyItemType[], type: DateInterval) { | |
92 | + const res: ClassifyItemType[] = []; | |
93 | + const fullXAxis = generateFullXAxis(type); | |
94 | + | |
95 | + for (const { dts, classify } of data) { | |
96 | + const valueMap = dts.reduce((prev, next) => { | |
97 | + const key = dateUtil(next.time).valueOf(); | |
98 | + | |
99 | + return { ...prev, [key]: next }; | |
100 | + }, {} as Record<number, ClassifyDtsItemType>); | |
101 | + | |
102 | + const transformDts: ClassifyDtsItemType[] = fullXAxis.map((timeSpan) => { | |
103 | + const exist = valueMap[timeSpan]; | |
104 | + | |
105 | + return exist || { time: formatDateFromType(timeSpan, type), count: 0, classify }; | |
106 | + }); | |
107 | + | |
108 | + res.push({ classify, dts: transformDts }); | |
109 | + } | |
110 | + | |
111 | + return res; | |
112 | +} | |
113 | + | |
114 | +export function transformChartsOptionsByClassifyRecord( | |
115 | + data: ClassifyItemType[], | |
116 | + type: DateInterval | |
117 | +): Partial<EChartsOption> { | |
118 | + const series: EChartsOption['series'] = data.map(({ classify, dts }) => { | |
119 | + return { | |
120 | + type: 'line', | |
121 | + name: formatClassifyText[classify], | |
122 | + data: dts.map(({ count }) => count), | |
123 | + }; | |
124 | + }); | |
125 | + | |
126 | + const xAxisData = generateFullXAxis(type).map((timeSpan) => formatDateFromType(timeSpan, type)); | |
127 | + | |
128 | + return { | |
129 | + series: series.reverse(), | |
130 | + xAxis: { | |
131 | + type: 'category', | |
132 | + data: xAxisData.reverse(), | |
133 | + }, | |
134 | + }; | |
135 | +} | ... | ... |
... | ... | @@ -8,35 +8,32 @@ |
8 | 8 | import { useI18n } from '/@/hooks/web/useI18n'; |
9 | 9 | import { onMounted, reactive } from 'vue'; |
10 | 10 | import { getApplicationRecordClassify, getApplicationRecordTop } from '/@/api/application/record'; |
11 | - import { | |
12 | - ApplicationRecordTopItemType, | |
13 | - ClassifyDtsItemType, | |
14 | - ClassifyItemType, | |
15 | - TopItemType, | |
16 | - } from '/@/api/application/model/record'; | |
11 | + import { ApplicationRecordTopItemType, TopItemType } from '/@/api/application/model/record'; | |
17 | 12 | import { Empty } from 'ant-design-vue'; |
18 | - import moment from 'moment'; | |
19 | - import { formatClassifyText } from '../api/config'; | |
20 | 13 | import total1 from '/@/assets/images/total1.png'; |
21 | 14 | import total2 from '/@/assets/images/total2.png'; |
22 | 15 | import total3 from '/@/assets/images/total3.png'; |
16 | + import { | |
17 | + DateInterval, | |
18 | + transformClassifyItem, | |
19 | + transformChartsOptionsByClassifyRecord, | |
20 | + } from './config'; | |
21 | + import { EChartsOption } from 'echarts'; | |
23 | 22 | |
24 | 23 | const { t } = useI18n(); |
25 | 24 | |
26 | 25 | interface ChartData { |
27 | 26 | lineChartData: Recordable[]; |
28 | 27 | pieChartData: Recordable[]; |
29 | - timeLineChartData: Recordable[]; | |
30 | 28 | totalData: Recordable[]; |
31 | - timeLineXAxisData?: string[]; | |
29 | + timeLineChartOptions?: Partial<EChartsOption>; | |
32 | 30 | } |
33 | 31 | |
34 | 32 | const chartData = reactive<ChartData>({ |
35 | 33 | lineChartData: [], |
36 | 34 | pieChartData: [], |
37 | - timeLineChartData: [], | |
38 | 35 | totalData: [], |
39 | - timeLineXAxisData: [], | |
36 | + timeLineChartOptions: {}, | |
40 | 37 | }); |
41 | 38 | |
42 | 39 | // API统计 |
... | ... | @@ -88,96 +85,20 @@ |
88 | 85 | } |
89 | 86 | }; |
90 | 87 | |
91 | - //1小时->5分钟间隔 1天->2小时间隔 7天->1天间隔 30天->1天间隔 | |
92 | - const timeFormat = (type, config) => { | |
93 | - let startT: any = null; | |
94 | - let endT: any = null; | |
95 | - let interval: any = null; //分割间隔 | |
96 | - if (type == 'hour') { | |
97 | - interval = 5; | |
98 | - startT = moment().subtract(1, config.text); | |
99 | - endT = moment().format('HH:mm'); | |
100 | - } else if (type == 'day') { | |
101 | - interval = 2; | |
102 | - startT = moment().subtract(1, config.text); | |
103 | - endT = moment().format('HH:mm'); | |
104 | - } else if (type == 'week') { | |
105 | - interval = 1; | |
106 | - startT = moment().subtract(1, config.text); | |
107 | - endT = moment().format('HH:mm'); | |
108 | - } else if (type == 'month') { | |
109 | - interval = 1; | |
110 | - startT = moment().subtract(1, config.text); | |
111 | - endT = moment().format('HH:mm'); | |
112 | - } | |
113 | - let starTime = moment(startT, 'HH:mm'); | |
114 | - let endTime = moment(endT, 'HH:mm'); | |
115 | - let diff = endTime.diff(starTime, config.diffText); | |
116 | - let num = Math.ceil(diff / interval); | |
117 | - let times: any = []; | |
118 | - for (let i = 1; i <= num; i++) { | |
119 | - let timeFrom = starTime.clone().add((i - 1) * interval, config.timeText) as any; | |
120 | - times.push(timeFrom.format('YYYY-MM-DD HH:mm')); | |
121 | - } | |
122 | - return times; | |
123 | - }; | |
124 | - | |
125 | - const configTime = [ | |
126 | - { | |
127 | - name: 'hour', | |
128 | - value: { | |
129 | - text: 'hours', | |
130 | - diffText: 'minutes', | |
131 | - timeText: 'minutes', | |
132 | - }, | |
133 | - }, | |
134 | - { | |
135 | - name: 'day', | |
136 | - value: { | |
137 | - text: 'days', | |
138 | - diffText: 'hours', | |
139 | - timeText: 'hours', | |
140 | - }, | |
141 | - }, | |
142 | - { | |
143 | - name: 'week', | |
144 | - value: { | |
145 | - text: 'weeks', | |
146 | - diffText: 'days', | |
147 | - timeText: 'days', | |
148 | - }, | |
149 | - }, | |
150 | - { | |
151 | - name: 'month', | |
152 | - value: { | |
153 | - text: 'months', | |
154 | - diffText: 'days', | |
155 | - timeText: 'days', | |
156 | - }, | |
157 | - }, | |
158 | - ]; | |
159 | - //代码保留 | |
160 | - | |
161 | 88 | const timeType = ref(''); |
162 | 89 | |
163 | 90 | // 接口调用历史 |
164 | - const getClassify = async (type?: string) => { | |
165 | - timeType.value = type as string; | |
166 | - const findValue = configTime.find((configItem) => configItem.name === type)?.value; | |
167 | - chartData.timeLineXAxisData = timeFormat(type, findValue); | |
168 | - const res = (await getApplicationRecordClassify(type)) as any as ClassifyItemType[]; | |
169 | - chartData.timeLineChartData = res?.map((classifyItem: ClassifyItemType) => ({ | |
170 | - type: 'line', | |
171 | - name: formatClassifyText[classifyItem.classify], | |
172 | - data: classifyItem?.dts?.map((dtsItem: ClassifyDtsItemType) => [ | |
173 | - moment(dtsItem.time).format('YYYY-MM-DD HH:mm:ss'), | |
174 | - dtsItem.count, | |
175 | - ]), | |
176 | - connectNulls: true, | |
177 | - })); | |
91 | + const getClassify = async (type: DateInterval) => { | |
92 | + timeType.value = type; | |
93 | + // const findValue = configTime.find((configItem) => configItem.name === type)?.value; | |
94 | + // chartData.timeLineXAxisData = timeFormat(type, findValue); | |
95 | + const res = await getApplicationRecordClassify(type); | |
96 | + | |
97 | + const transformData = transformClassifyItem(res, type); | |
98 | + chartData.timeLineChartOptions = transformChartsOptionsByClassifyRecord(transformData, type); | |
178 | 99 | }; |
179 | 100 | |
180 | - const handleReceiveTimeRange = (value: string) => { | |
101 | + const handleReceiveTimeRange = (value: DateInterval) => { | |
181 | 102 | getClassify(value); |
182 | 103 | }; |
183 | 104 | |
... | ... | @@ -192,22 +113,21 @@ |
192 | 113 | <Total :seriesData="chartData.totalData" /> |
193 | 114 | <a-card :title="t('application.record.text.pieTitle')" class="h-99" style="width: 40%"> |
194 | 115 | <PieChart v-if="chartData.pieChartData.length" :seriesData="chartData.pieChartData" /> |
195 | - <div class="w-full h-72 flex justify-center items-center" v-else | |
196 | - ><Empty :image="Empty.PRESENTED_IMAGE_SIMPLE" | |
197 | - /></div> | |
116 | + <div class="w-full h-72 flex justify-center items-center" v-else> | |
117 | + <Empty :image="Empty.PRESENTED_IMAGE_SIMPLE" /> | |
118 | + </div> | |
198 | 119 | </a-card> |
199 | 120 | <a-card :title="t('application.record.text.lineTitle')" class="h-99" style="width: 45%"> |
200 | 121 | <LineChart v-if="chartData.lineChartData.length" :seriesData="chartData.lineChartData" /> |
201 | - <div class="w-full h-72 flex justify-center items-center" v-else | |
202 | - ><Empty :image="Empty.PRESENTED_IMAGE_SIMPLE" | |
203 | - /></div> | |
122 | + <div class="w-full h-72 flex justify-center items-center" v-else> | |
123 | + <Empty :image="Empty.PRESENTED_IMAGE_SIMPLE" /> | |
124 | + </div> | |
204 | 125 | </a-card> |
205 | 126 | </div> |
206 | 127 | <div> |
207 | 128 | <TimeLineChart |
208 | 129 | @emitTimeRange="handleReceiveTimeRange" |
209 | - :seriesData="chartData.timeLineChartData" | |
210 | - :timeLineXAxisData="chartData.timeLineXAxisData" | |
130 | + :charts-data="(chartData.timeLineChartOptions as EChartsOption)" | |
211 | 131 | :timeType="timeType" |
212 | 132 | /> |
213 | 133 | </div> | ... | ... |