2026-09-10 10:15:10 +08:00
|
|
|
<template>
|
|
|
|
|
<el-table
|
|
|
|
|
v-loading="loading"
|
|
|
|
|
:data="anomalies"
|
|
|
|
|
:row-class-name="rowClass"
|
|
|
|
|
@row-click="onRowClick"
|
|
|
|
|
>
|
|
|
|
|
<el-table-column label="级别" width="70" align="center">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-tag v-if="row.severity === 'critical'" type="danger" size="small">严重</el-tag>
|
|
|
|
|
<el-tag v-else type="warning" size="small">关注</el-tag>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="异常" min-width="240">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<div class="cell-title">{{ row.title }}</div>
|
2026-09-10 12:20:28 +08:00
|
|
|
<div v-if="showEmployee" class="cell-emp">
|
|
|
|
|
<el-link
|
|
|
|
|
type="primary"
|
|
|
|
|
:underline="false"
|
|
|
|
|
@click.stop="goEmployee(row)"
|
|
|
|
|
>{{ row.employee_name || row.employee_user_id.slice(0, 8) }}</el-link>
|
|
|
|
|
</div>
|
2026-09-10 10:15:10 +08:00
|
|
|
<div class="cell-sub">{{ row.reason }}</div>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="当前值" width="150">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<div>{{ formatValue(row.current_value, row.unit) }}</div>
|
|
|
|
|
<div class="cell-sub">
|
|
|
|
|
{{ row.baseline_value === null ? '无基线' : `基线 ${formatValue(row.baseline_value, row.unit)}` }}
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="样本" prop="sample_size" align="right" width="80" />
|
|
|
|
|
<el-table-column label="影响" align="right" width="90">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<span :class="{ 'affected-hot': row.affected_count >= 50 }">{{ row.affected_count }}</span>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="建议动作" min-width="180" show-overflow-tooltip>
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<span class="cell-sub">{{ row.suggested_action }}</span>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<template #empty>
|
|
|
|
|
<el-empty description="暂无异常" :image-size="60" />
|
|
|
|
|
</template>
|
|
|
|
|
</el-table>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2026-09-10 12:20:28 +08:00
|
|
|
// 异常榜(批次C 驾驶舱):后端 AnomalyCard[] 平铺表——级别 tag/标题+员工行
|
|
|
|
|
// (showEmployee,批次D:employee_name 链接→员工详情,.stop 防触发行下钻)
|
|
|
|
|
// +原因/单位感知当前值+基线/样本/影响(≥50 红显)/建议动作。行 click 打开
|
2026-09-10 10:15:10 +08:00
|
|
|
// 下钻抽屉(emit drill,抽屉自治加载);critical 整行淡红底提示优先处置。
|
|
|
|
|
// 纯展示+事件,不取数。
|
2026-09-10 12:20:28 +08:00
|
|
|
import { useRoute, useRouter } from 'vue-router'
|
2026-09-10 10:15:10 +08:00
|
|
|
import { formatMsDuration, formatRatio } from '@/utils/format'
|
|
|
|
|
import type { AnomalyCard } from '@/api/management'
|
|
|
|
|
|
2026-09-10 12:20:28 +08:00
|
|
|
// showEmployee(默认 true):驾驶舱异常榜显示员工行;员工详情页复用本组件
|
|
|
|
|
// 传 false(自指链接无意义)
|
2026-09-10 12:08:35 +08:00
|
|
|
withDefaults(
|
|
|
|
|
defineProps<{
|
|
|
|
|
anomalies: AnomalyCard[]
|
|
|
|
|
loading?: boolean
|
|
|
|
|
showEmployee?: boolean
|
|
|
|
|
}>(),
|
|
|
|
|
{ showEmployee: true },
|
|
|
|
|
)
|
2026-09-10 10:15:10 +08:00
|
|
|
|
|
|
|
|
const emit = defineEmits<{
|
|
|
|
|
(e: 'drill', card: AnomalyCard): void
|
|
|
|
|
}>()
|
|
|
|
|
|
2026-09-10 12:20:28 +08:00
|
|
|
const route = useRoute()
|
|
|
|
|
const router = useRouter()
|
|
|
|
|
|
|
|
|
|
// 员工链接:带当前 URL query(channel/start/end/compare 随行,与列表
|
|
|
|
|
// goDetail 带窗进详情同语义);detail 页对白名单外 query 键不敏感
|
|
|
|
|
function goEmployee(row: AnomalyCard): void {
|
|
|
|
|
router.push({
|
|
|
|
|
path: `/management/employees/${row.employee_user_id}`,
|
|
|
|
|
query: route.query,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-10 10:15:10 +08:00
|
|
|
// 单位感知格式化:ms→时长,0-1→比率,其余千分位(与指标口径字典 unit 对齐)
|
|
|
|
|
function formatValue(v: number, unit: string): string {
|
|
|
|
|
if (unit === 'ms') return formatMsDuration(v)
|
|
|
|
|
if (unit === '0-1') return formatRatio(v)
|
|
|
|
|
return v.toLocaleString()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function rowClass({ row }: { row: AnomalyCard }): string {
|
|
|
|
|
return row.severity === 'critical'
|
|
|
|
|
? 'anomaly-critical-row yk-clickable-row'
|
|
|
|
|
: 'yk-clickable-row'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onRowClick(row: AnomalyCard): void {
|
|
|
|
|
emit('drill', row)
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
/* critical 行整行淡红底(优先处置);hover 深一档由 EP 行 hover 自带 */
|
|
|
|
|
:deep(.el-table .anomaly-critical-row) {
|
|
|
|
|
background-color: var(--el-color-danger-light-9);
|
|
|
|
|
}
|
|
|
|
|
:deep(.el-table .anomaly-critical-row:hover > td.el-table__cell) {
|
|
|
|
|
background-color: var(--el-color-danger-light-8);
|
|
|
|
|
}
|
|
|
|
|
.cell-title {
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
color: var(--el-text-color-primary);
|
|
|
|
|
}
|
2026-09-10 12:20:28 +08:00
|
|
|
.cell-emp {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
margin-top: 2px;
|
|
|
|
|
line-height: 1.4;
|
|
|
|
|
}
|
|
|
|
|
.cell-emp .el-link {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
}
|
2026-09-10 10:15:10 +08:00
|
|
|
.cell-sub {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: var(--el-text-color-secondary);
|
|
|
|
|
margin-top: 2px;
|
|
|
|
|
}
|
|
|
|
|
.affected-hot {
|
|
|
|
|
color: var(--el-color-danger);
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
</style>
|