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;
}