diff --git a/yunkes/src/views/analytics/DashboardView.vue b/yunkes/src/views/analytics/DashboardView.vue index ed694d9..033e64c 100644 --- a/yunkes/src/views/analytics/DashboardView.vue +++ b/yunkes/src/views/analytics/DashboardView.vue @@ -55,11 +55,15 @@
@@ -208,6 +212,10 @@ const anomalyView = computed(() => { const fmt = (n: number | undefined | null) => (n ?? 0).toLocaleString() +// 趋势摘要:先给数字再看图(合计/日均/峰值及日期) +const trendVals = (): number[] => + (data.value?.trend ?? []).map((p) => p[trendMetric.value] as number) + // 预警行动入口:计数来自 overview 载荷 actions(0 灰显仍可点,直达处置页) const actionCards = computed(() => { const a = data.value?.actions @@ -239,24 +247,90 @@ const cardList = computed(() => { ] }) +// 大数压缩:轴刻度/标注统一 1.1万 口径(裸 11254 难读) +const compact = (n: number) => (n >= 10000 ? `${(n / 10000).toFixed(1)}万` : n.toLocaleString()) + +// 趋势摘要:先给数字再看图(合计/日均/峰值及日期) +const trendSummary = computed(() => { + const t = data.value?.trend + if (!t || !t.length) return '' + const vals = trendVals() + const total = vals.reduce((a, b) => a + b, 0) + const max = Math.max(...vals) + const maxDay = t[vals.indexOf(max)].day + const dstr = `${String(maxDay).slice(4, 6)}-${String(maxDay).slice(6)}` + return `合计 ${total.toLocaleString()} · 日均 ${Math.round(total / vals.length).toLocaleString()} · 峰值 ${max.toLocaleString()}(${dstr})` +}) + function draw() { const t = data.value?.trend - if (!t) return + if (!t || !t.length) return + const labels = t.map((p) => `${String(p.day).slice(4, 6)}-${String(p.day).slice(6)}`) + const vals = trendVals() + // X 轴 ~6 个刻度(30 个日期全排会挤成一串不可读) + const interval = Math.max(0, Math.ceil(labels.length / 6) - 1) + const maxIdx = vals.indexOf(Math.max(...vals)) + const avg = Math.round(vals.reduce((a, b) => a + b, 0) / vals.length) render({ - grid: { left: 48, right: 16, top: 24, bottom: 28 }, - tooltip: { trigger: 'axis' }, + grid: { left: 56, right: 56, top: 28, bottom: 28 }, + tooltip: { + trigger: 'axis', + valueFormatter: (v: unknown) => compact(Number(v)), + }, xAxis: { type: 'category', - // day 是 yyyymmdd int(20260904 → "09-04") - data: t.map((p) => `${String(p.day).slice(4, 6)}-${String(p.day).slice(6)}`), + data: labels, + axisLabel: { interval, hideOverlap: true, color: '#909399' }, + axisLine: { lineStyle: { color: '#dcdfe6' } }, + axisTick: { show: false }, + }, + yAxis: { + type: 'value', + axisLabel: { formatter: (v: number) => compact(v), color: '#909399' }, + // 实线细网格(虚线网格是可视化反模式——虚线该留给参考线) + splitLine: { lineStyle: { type: 'solid', color: '#f0f1f3' } }, }, - yAxis: { type: 'value' }, series: [ { type: 'line', smooth: true, - areaStyle: { opacity: 0.12 }, - data: t.map((p) => p[trendMetric.value]), + lineStyle: { width: 2 }, + areaStyle: { opacity: 0.1 }, + data: vals, + showSymbol: false, + // 选择性标注:终点一个数(其余靠轴+tooltip,逐点标数是反模式) + endLabel: { + show: true, + formatter: () => compact(vals[vals.length - 1]), + color: '#606266', + fontSize: 12, + }, + // 峰值:小圆点+2px 表面描边环(默认大水滴太响);标注峰值日的数 + markPoint: { + symbol: 'circle', + symbolSize: 9, + itemStyle: { borderColor: '#fff', borderWidth: 2 }, + label: { + formatter: () => compact(vals[maxIdx]), + fontSize: 11, + color: '#606266', + offset: [0, -12], + }, + data: [{ coord: [maxIdx, vals[maxIdx]] }], + }, + // 均值参考线:虚线在此语义正确(参考阈值) + markLine: { + silent: true, + symbol: 'none', + lineStyle: { type: 'dashed', color: '#c0c4cc', width: 1 }, + label: { + formatter: `均值 ${compact(avg)}`, + position: 'insideEndTop', + fontSize: 11, + color: '#909399', + }, + data: [{ type: 'average', name: '均值' }], + }, }, ], }) @@ -324,6 +398,16 @@ onMounted(() => { font-size: 11px; color: var(--el-text-color-secondary); } +.trend-header { + display: flex; + align-items: center; + gap: 12px; +} +.trend-summary { + margin-left: auto; + font-size: 12px; + color: var(--el-text-color-secondary); +} .trend-chart { height: 320px; }