diff --git a/apps/web-antd/src/views/ops/cleaning/work-order/dashboard/index.vue b/apps/web-antd/src/views/ops/cleaning/work-order/dashboard/index.vue index 51947cd3c..5273c205b 100644 --- a/apps/web-antd/src/views/ops/cleaning/work-order/dashboard/index.vue +++ b/apps/web-antd/src/views/ops/cleaning/work-order/dashboard/index.vue @@ -113,9 +113,7 @@ const { renderEcharts: renderStatusChart } = useEcharts(statusChartRef); const { renderEcharts: renderFunctionTypeRankingChart } = useEcharts( functionTypeRankingChartRef, ); -const { renderEcharts: renderBadgeQueueChart } = useEcharts( - badgeQueueChartRef, -); +const { renderEcharts: renderBadgeQueueChart } = useEcharts(badgeQueueChartRef); const statsData = ref({ pendingCount: 0, @@ -175,11 +173,62 @@ function getTrendChartOptions(): ECOption { const { trendData } = statsData.value; return { tooltip: { trigger: 'axis' }, - legend: { data: ['新增工单'], top: '5%', textStyle: { color: '#595959' } }, - grid: { left: '3%', right: '4%', bottom: '3%', top: '15%', containLabel: true }, - xAxis: [{ type: 'category', boundaryGap: false, data: trendData.dates, axisLine: { lineStyle: { color: '#d9d9d9' } }, axisLabel: { color: '#8c8c8c' } }], - yAxis: [{ type: 'value', name: '工单数量', nameTextStyle: { color: '#8c8c8c' }, axisLine: { show: false }, axisLabel: { color: '#8c8c8c' }, splitLine: { lineStyle: { color: '#f0f0f0', type: 'dashed' } } }], - series: [{ name: '新增工单', type: 'line', smooth: true, symbol: 'circle', symbolSize: 6, data: trendData.createdData, itemStyle: { color: '#1677ff' }, areaStyle: { opacity: 0.15, color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [{ offset: 0, color: 'rgba(22, 119, 255, 0.4)' }, { offset: 1, color: 'rgba(22, 119, 255, 0.05)' }] } } }], + legend: { + data: ['新增工单'], + top: '5%', + textStyle: { color: '#595959' }, + }, + grid: { + left: '3%', + right: '4%', + bottom: '3%', + top: '15%', + containLabel: true, + }, + xAxis: [ + { + type: 'category', + boundaryGap: false, + data: trendData.dates, + axisLine: { lineStyle: { color: '#d9d9d9' } }, + axisLabel: { color: '#8c8c8c' }, + }, + ], + yAxis: [ + { + type: 'value', + name: '工单数量', + nameTextStyle: { color: '#8c8c8c' }, + axisLine: { show: false }, + axisLabel: { color: '#8c8c8c' }, + splitLine: { lineStyle: { color: '#f0f0f0', type: 'dashed' } }, + }, + ], + series: [ + { + name: '新增工单', + type: 'line', + smooth: true, + symbol: 'circle', + symbolSize: 6, + data: trendData.createdData, + itemStyle: { color: '#1677ff' }, + areaStyle: { + opacity: 0.15, + color: { + type: 'linear', + x: 0, + y: 0, + x2: 0, + y2: 1, + colorStops: [ + { offset: 0, color: 'rgba(22, 119, 255, 0.4)' }, + { offset: 1, color: 'rgba(22, 119, 255, 0.05)' }, + ], + }, + }, + }, + ], }; } @@ -193,7 +242,8 @@ function getTrendChartOptions(): ECOption { return parts[2] || d; }); // 取最长的日期轴 - const xData = currentDates.length >= lastDates.length ? currentDates : lastDates; + const xData = + currentDates.length >= lastDates.length ? currentDates : lastDates; return { tooltip: { @@ -205,22 +255,32 @@ function getTrendChartOptions(): ECOption { top: '5%', textStyle: { color: '#595959' }, }, - grid: { left: '3%', right: '4%', bottom: '3%', top: '15%', containLabel: true }, - xAxis: [{ - type: 'category', - boundaryGap: false, - data: xData, - axisLine: { lineStyle: { color: '#d9d9d9' } }, - axisLabel: { color: '#8c8c8c', formatter: (val: string) => `${val}日` }, - }], - yAxis: [{ - type: 'value', - name: '工单数量', - nameTextStyle: { color: '#8c8c8c' }, - axisLine: { show: false }, - axisLabel: { color: '#8c8c8c' }, - splitLine: { lineStyle: { color: '#f0f0f0', type: 'dashed' } }, - }], + grid: { + left: '3%', + right: '4%', + bottom: '3%', + top: '15%', + containLabel: true, + }, + xAxis: [ + { + type: 'category', + boundaryGap: false, + data: xData, + axisLine: { lineStyle: { color: '#d9d9d9' } }, + axisLabel: { color: '#8c8c8c', formatter: (val: string) => `${val}日` }, + }, + ], + yAxis: [ + { + type: 'value', + name: '工单数量', + nameTextStyle: { color: '#8c8c8c' }, + axisLine: { show: false }, + axisLabel: { color: '#8c8c8c' }, + splitLine: { lineStyle: { color: '#f0f0f0', type: 'dashed' } }, + }, + ], series: [ { name: '当月新增', @@ -231,37 +291,46 @@ function getTrendChartOptions(): ECOption { areaStyle: { opacity: 0.15, color: { - type: 'linear', x: 0, y: 0, x2: 0, y2: 1, + type: 'linear', + x: 0, + y: 0, + x2: 0, + y2: 1, colorStops: [ - { offset: 0, color: 'rgba(22, 119, 255, 0.4)' }, - { offset: 1, color: 'rgba(22, 119, 255, 0.05)' }, + { offset: 0, color: 'rgba(22, 119, 255, 0.35)' }, + { offset: 1, color: 'rgba(22, 119, 255, 0.03)' }, ], }, }, emphasis: { focus: 'series' }, data: monthly.currentMonth.createdData, itemStyle: { color: '#1677ff' }, + lineStyle: { width: 2.5 }, }, { name: '上月新增', type: 'line', smooth: true, symbol: 'circle', - symbolSize: 6, - lineStyle: { type: 'dashed', opacity: 0.6 }, + symbolSize: 5, + lineStyle: { type: 'dashed', width: 1.5 }, areaStyle: { opacity: 0.06, color: { - type: 'linear', x: 0, y: 0, x2: 0, y2: 1, + type: 'linear', + x: 0, + y: 0, + x2: 0, + y2: 1, colorStops: [ - { offset: 0, color: 'rgba(82, 196, 26, 0.3)' }, - { offset: 1, color: 'rgba(82, 196, 26, 0.05)' }, + { offset: 0, color: 'rgba(147, 196, 255, 0.25)' }, + { offset: 1, color: 'rgba(147, 196, 255, 0.03)' }, ], }, }, emphasis: { focus: 'series' }, data: monthly.lastMonth.createdData, - itemStyle: { color: '#52c41a', opacity: 0.6 }, + itemStyle: { color: '#93c4fd' }, }, ], }; @@ -488,13 +557,13 @@ function getStatusDistributionChartOptions(): ECOption { /** * 时段热力图图表配置(7天x24小时) + * 对数变换 + RGB线性插值:低量区间色彩差异大,高量区间自动压缩 */ function getHeatmapChartOptions(): ECOption { const { heatmapData } = statsData.value; - const xAxisData = heatmapData.hours.map((h, i) => i); - const yAxisData = heatmapData.days.map((d, i) => i); + const xAxisData = heatmapData.hours.map((_h, i) => i); + const yAxisData = heatmapData.days.map((_d, i) => i); - // 将二维数据转换为ECharts热力图格式 [x, y, value] const data: number[][] = []; for (let day = 0; day < heatmapData.days.length; day++) { for (let hour = 0; hour < 24; hour++) { @@ -503,6 +572,36 @@ function getHeatmapChartOptions(): ECOption { } } + const maxVal = Math.max(1, ...data.map((d: number[]) => d[2] || 0)); + + // RGB线性插值生成颜色:0单灰色 → 最大值主蓝,基于对数比例 + const COLOR_ZERO: [number, number, number] = [245, 245, 245]; // #f5f5f5 + const COLOR_MAX: [number, number, number] = [22, 119, 255]; // #1677ff + const logMax = Math.log1p(maxVal); + + function valueToColor(val: number): string { + if (val <= 0) return `rgb(${COLOR_ZERO.join(',')})`; + const t = Math.log1p(val) / logMax; // 0~1 对数比例 + const r = Math.round(COLOR_ZERO[0] + (COLOR_MAX[0] - COLOR_ZERO[0]) * t); + const g = Math.round(COLOR_ZERO[1] + (COLOR_MAX[1] - COLOR_ZERO[1]) * t); + const b = Math.round(COLOR_ZERO[2] + (COLOR_MAX[2] - COLOR_ZERO[2]) * t); + return `rgb(${r},${g},${b})`; + } + + // 为每个数据点计算精确颜色 + const seriesData = data.map((d) => ({ + value: d, + itemStyle: { color: valueToColor(d[2] || 0) }, + })); + + // 图例色带:采样生成渐变色阶 + const legendSteps = 10; + const legendColors: string[] = []; + for (let i = 0; i <= legendSteps; i++) { + const val = (maxVal * i) / legendSteps; + legendColors.push(valueToColor(val)); + } + return { tooltip: { position: 'top', @@ -514,85 +613,56 @@ function getHeatmapChartOptions(): ECOption { }, }, grid: { - height: '75%', + height: '70%', top: '5%', left: '8%', right: '3%', - bottom: '15%', + bottom: '20%', }, xAxis: { type: 'category', data: xAxisData, - splitArea: { - show: true, - }, + splitArea: { show: true }, axisLabel: { fontSize: 9, color: '#8c8c8c', formatter: (i: number) => (i % 3 === 0 ? `${i}:00` : ''), }, - axisLine: { - show: false, - }, - axisTick: { - show: false, - }, + axisLine: { show: false }, + axisTick: { show: false }, }, yAxis: { type: 'category', data: yAxisData, - splitArea: { - show: true, - }, + splitArea: { show: true }, axisLabel: { fontSize: 11, color: '#595959', formatter: (i: number) => heatmapData.days[i] || '', }, - axisLine: { - show: false, - }, - axisTick: { - show: false, - }, + axisLine: { show: false }, + axisTick: { show: false }, }, visualMap: { min: 0, - max: Math.max(10, ...data.map((d: number[]) => d[2] || 0)), + max: maxVal, calculable: false, orient: 'horizontal', left: 'center', bottom: '2%', - textStyle: { - fontSize: 10, - color: '#8c8c8c', - }, - itemWidth: 8, - itemHeight: 50, - inRange: { - color: [ - '#f0f5ff', - '#d6e4ff', - '#adc6ff', - '#85a5ff', - '#597ef7', - '#2f54eb', - '#1d39c4', - '#10239e', - '#061178', - ], - }, - splitNumber: 8, - show: false, + text: [`${maxVal}`, '0'], + textStyle: { fontSize: 10, color: '#8c8c8c' }, + itemWidth: 10, + itemHeight: 80, + inRange: { color: legendColors }, + show: true, }, series: [ { name: '工单数量', type: 'heatmap', - data, - label: { - show: false, - }, + data: seriesData, + label: { show: false }, emphasis: { itemStyle: { shadowBlur: 10, @@ -605,10 +675,13 @@ function getHeatmapChartOptions(): ECOption { } /** - * 功能类型排行图表配置 + * 区域类型排行图表配置 */ function getFunctionTypeRankingChartOptions(): ECOption { - const { functionTypeRanking } = statsData.value; + // 按工单数从低到高排序(ECharts水平柱状图yAxis从下往上,所以升序=视觉上从高到低) + const sorted = [...statsData.value.functionTypeRanking].toSorted( + (a, b) => a.count - b.count, + ); return { grid: { left: '3%', @@ -629,7 +702,7 @@ function getFunctionTypeRankingChartOptions(): ECOption { }, yAxis: { type: 'category', - data: functionTypeRanking.map((item) => item.functionType), + data: sorted.map((item) => item.functionType), axisLabel: { fontSize: 13, color: '#595959', @@ -644,7 +717,7 @@ function getFunctionTypeRankingChartOptions(): ECOption { series: [ { type: 'bar', - data: functionTypeRanking.map((item) => ({ + data: sorted.map((item) => ({ value: item.count, itemStyle: { color: getCompletionRateColor(item.rate), @@ -701,61 +774,73 @@ function getBadgeQueueChartOptions(): ECOption { top: '15%', containLabel: true, }, - xAxis: [{ - type: 'category', - data: queue.dates, - axisLine: { lineStyle: { color: '#f0f0f0' } }, - axisTick: { show: false }, - axisLabel: { color: '#8c8c8c', fontSize: 11 }, - }], - yAxis: [{ - type: 'value', - name: '队列数', - nameTextStyle: { color: '#8c8c8c', fontSize: 12 }, - axisLine: { show: false }, - axisTick: { show: false }, - axisLabel: { color: '#8c8c8c' }, - splitLine: { lineStyle: { color: '#f5f5f5', type: 'dashed' } }, - }], - series: [{ - name: '队列数量', - type: 'bar', - barWidth: '40%', - data: queue.queueData.map((val) => ({ - value: val, - itemStyle: { - color: { - type: 'linear', - x: 0, y: 0, x2: 0, y2: 1, - colorStops: [ - { offset: 0, color: val === maxVal ? '#0958d9' : '#1677ff' }, - { offset: 1, color: val === maxVal ? '#4096ff' : '#91caff' }, - ], - }, - borderRadius: [6, 6, 0, 0], - }, - })), - label: { - show: true, - position: 'top', - fontSize: 11, - fontWeight: '600', - color: '#1677ff', - formatter: (params: any) => params.value > 0 ? params.value : '', + xAxis: [ + { + type: 'category', + data: queue.dates, + axisLine: { lineStyle: { color: '#f0f0f0' } }, + axisTick: { show: false }, + axisLabel: { color: '#8c8c8c', fontSize: 11 }, }, - emphasis: { - itemStyle: { - color: { - type: 'linear', - x: 0, y: 0, x2: 0, y2: 1, - colorStops: [ - { offset: 0, color: '#003eb3' }, - { offset: 1, color: '#0958d9' }, - ], + ], + yAxis: [ + { + type: 'value', + name: '队列数', + nameTextStyle: { color: '#8c8c8c', fontSize: 12 }, + axisLine: { show: false }, + axisTick: { show: false }, + axisLabel: { color: '#8c8c8c' }, + splitLine: { lineStyle: { color: '#f5f5f5', type: 'dashed' } }, + }, + ], + series: [ + { + name: '队列数量', + type: 'bar', + barWidth: '40%', + data: queue.queueData.map((val) => ({ + value: val, + itemStyle: { + color: { + type: 'linear', + x: 0, + y: 0, + x2: 0, + y2: 1, + colorStops: [ + { offset: 0, color: val === maxVal ? '#0958d9' : '#1677ff' }, + { offset: 1, color: val === maxVal ? '#4096ff' : '#91caff' }, + ], + }, + borderRadius: [6, 6, 0, 0], + }, + })), + label: { + show: true, + position: 'top', + fontSize: 11, + fontWeight: '600', + color: '#1677ff', + formatter: (params: any) => (params.value > 0 ? params.value : ''), + }, + emphasis: { + itemStyle: { + color: { + type: 'linear', + x: 0, + y: 0, + x2: 0, + y2: 1, + colorStops: [ + { offset: 0, color: '#003eb3' }, + { offset: 1, color: '#0958d9' }, + ], + }, }, }, }, - }], + ], }; } @@ -991,30 +1076,40 @@ onUnmounted(stopPolling); - + - + - -
- 工牌队列统计(近7天) -
+ + - + - + - -
- 功能类型排行 -
+ +