fix(analytics): 看板异常明细诊断优先排序+角标复用formatTimeShort+health解耦

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-04 22:02:41 +08:00
parent 4cec162f53
commit 4ac73fff49
+34 -16
View File
@@ -57,9 +57,10 @@
// 数据来自 /analytics/overview + /analytics/health(数据截至角标)。
import { computed, onMounted, ref, watch } from 'vue'
import {
getAnalyticsHealth, getOverview, type Channel, type OverviewResp, type Scope,
getAnalyticsHealth, getOverview, type AnomalyStaff, type Channel, type OverviewResp, type Scope,
} from '@/api/analytics'
import { useChart } from '@/composables/useChart'
import { formatTimeShort } from '@/utils/format'
interface CardItem {
label: string
@@ -101,10 +102,9 @@ async function load() {
loading.value = true
error.value = ''
try {
const [ov, h] = await Promise.all([getOverview(channel.value, scope.value), getAnalyticsHealth()])
const ov = await getOverview(channel.value, scope.value)
if (my !== seq) return
data.value = ov
asOf.value = h.data_as_of
} catch (e) {
if (my === seq) error.value = e instanceof Error ? e.message : String(e)
} finally {
@@ -113,12 +113,15 @@ async function load() {
}
}
const asOfText = computed(() => {
if (!asOf.value) return ''
const d = new Date(asOf.value)
const pad = (n: number) => String(n).padStart(2, '0')
return `${d.getMonth() + 1}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`
})
const asOfText = computed(() => formatTimeShort(asOf.value))
// 异常员工偏离分:相对前 7 日均值的变化幅度;无均值但昨日有量/新增 → 最高优先
const anomalyDeviation = (s: AnomalyStaff) =>
s.prev7_mean > 0
? Math.abs(s.yesterday - s.prev7_mean) / s.prev7_mean
: s.yesterday > 0 || s.new
? Infinity
: 0
// 异常警示展示模型:判空/格式化收在 computed,模板只读定型字段(slot 闭包里
// 无法沿用 v-if 的空值窄化,预拍平最稳)
@@ -131,12 +134,18 @@ const anomalyView = computed<AnomalyView | null>(() => {
yesterday: a.yesterday_total,
mean: a.prev7_mean,
staffCount: a.staff.length,
topStaff: a.staff.slice(0, 5).map((s) => ({
user_id: s.user_id,
yesterday: s.yesterday,
prev7_mean: s.prev7_mean,
isNew: !!s.new,
})),
// 截前 5 条前按「诊断价值」重排,不能沿用后端 yesterday 降序——归零
// (掉同步/离职,spec §3.2 核心用例)恰在降序尾部,掉线事故(>5 人被
// 标记)时最先被裁掉。归零/新增置顶,其余按偏离幅度降序。
topStaff: [...a.staff]
.sort((x, y) => anomalyDeviation(y) - anomalyDeviation(x))
.slice(0, 5)
.map((s) => ({
user_id: s.user_id,
yesterday: s.yesterday,
prev7_mean: s.prev7_mean,
isNew: !!s.new,
})),
}
})
@@ -189,7 +198,16 @@ function draw() {
// post:等 DOM 更新后再画,保证首帧 chartEl 已挂载(趋势图恒 30 天,随 data 重画)
watch([data, trendMetric], draw, { flush: 'post' })
onMounted(load)
onMounted(() => {
load()
// health(数据截至)与筛选无关,挂载拉一次即可;失败只影响角标(静默隐藏),
// 不进页面 error alert
getAnalyticsHealth()
.then((h) => {
asOf.value = h.data_as_of
})
.catch(() => {})
})
</script>
<style scoped>