fix(analytics): 看板异常明细诊断优先排序+角标复用formatTimeShort+health解耦
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user