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> | ... | ... |