fix(analytics): 趋势图可读性——摘要行/轴刻度稀疏与大数万化/实线细网格/峰值标注/均值参考线

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-07 11:56:48 +08:00
parent 487f45b074
commit 9f2dff08d9
+97 -13
View File
@@ -55,11 +55,15 @@
<el-card v-loading="loading" shadow="never" class="trend-card">
<template #header>
<el-radio-group v-model="trendMetric" size="small">
<el-radio-button value="msg">消息量</el-radio-button>
<el-radio-button value="active_customers">活跃客户</el-radio-button>
<el-radio-button value="call_cnt">通话量</el-radio-button>
</el-radio-group>
<div class="trend-header">
<el-radio-group v-model="trendMetric" size="small">
<el-radio-button value="msg">消息量</el-radio-button>
<el-radio-button value="active_customers">活跃客户</el-radio-button>
<el-radio-button value="call_cnt">通话量</el-radio-button>
</el-radio-group>
<!-- 数字摘要行:图之前先给一眼可读的合计/日均/峰值 -->
<span v-if="trendSummary" class="trend-summary">近30天 {{ trendSummary }}</span>
</div>
</template>
<div ref="chartEl" class="trend-chart"></div>
</el-card>
@@ -208,6 +212,10 @@ const anomalyView = computed<AnomalyView | null>(() => {
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<CardItem[]>(() => {
]
})
// 大数压缩:轴刻度/标注统一 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;
}