调整风险人员总览核心异常点标签展示

This commit is contained in:
wkc
2026-03-20 14:31:22 +08:00
parent 5a650ab05f
commit 3bf1c276e8
4 changed files with 151 additions and 2 deletions

View File

@@ -0,0 +1,30 @@
# 风险人员总览核心异常点标签化展示实施记录
## 本次改动
- 将风险人员总览中的“核心异常点”从纯文本展示改为标签列表展示,样式与“命中模型涉及人员”的异常标签保持一致。
- 调整 [`RiskPeopleSection.vue`](/Users/wkc/Desktop/ccdi/ccdi/ruoyi-ui/src/views/ccdiProject/components/detail/RiskPeopleSection.vue)
- 新增 `riskPointTagList` 归一化逻辑。
- 兼容后端返回标签数组、字符串数组、以及历史 `riskPoint` 拼接字符串三种输入形式。
- 按风险等级映射 `el-tag` 颜色,空值场景显示 `-`
- 调整 [`preliminaryCheck.mock.js`](/Users/wkc/Desktop/ccdi/ccdi/ruoyi-ui/src/views/ccdiProject/components/detail/preliminaryCheck.mock.js),补充标签列 mock 数据。
- 新增 [`preliminary-check-risk-people-hit-tags.test.js`](/Users/wkc/Desktop/ccdi/ccdi/ruoyi-ui/tests/unit/preliminary-check-risk-people-hit-tags.test.js) 锁定核心异常点标签化渲染结构。
## 实现说明
- 不修改后端接口口径,展示层在前端做最小归一化处理。
- 若接口继续返回 `riskPoint` 字符串,则按 `、``,````;``` 拆分为多个标签。
- 若接口后续直接返回 `riskPointTagList`,则优先使用该字段,避免重复拆分。
## 验证情况
- 前端单测:
- `node ruoyi-ui/tests/unit/preliminary-check-risk-people-hit-tags.test.js`
- `node ruoyi-ui/tests/unit/preliminary-check-risk-people-binding.test.js`
- `node ruoyi-ui/tests/unit/preliminary-check-summary-and-people.test.js`
## 未包含内容
- 未调整风险人员总览接口返回结构
- 未改动风险等级口径与统计逻辑
- 未改动“命中模型涉及人员”区块的接口或交互

View File

@@ -10,7 +10,7 @@
<el-button size="mini" type="text">导出</el-button> <el-button size="mini" type="text">导出</el-button>
</div> </div>
<el-table :data="sectionData.overviewList || []" class="people-table"> <el-table :data="overviewList" class="people-table">
<el-table-column type="index" label="序号" width="60" /> <el-table-column type="index" label="序号" width="60" />
<el-table-column prop="name" label="姓名" min-width="100" /> <el-table-column prop="name" label="姓名" min-width="100" />
<el-table-column prop="idNo" label="身份证号" min-width="180" /> <el-table-column prop="idNo" label="身份证号" min-width="180" />
@@ -24,7 +24,25 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="modelCount" label="命中模型数" min-width="110" /> <el-table-column prop="modelCount" label="命中模型数" min-width="110" />
<el-table-column prop="riskPoint" label="核心异常点" min-width="220" /> <el-table-column label="核心异常点" min-width="220">
<template slot-scope="scope">
<div
v-if="scope.row.riskPointTagList && scope.row.riskPointTagList.length"
class="risk-point-tag-list"
>
<el-tag
v-for="(tag, index) in scope.row.riskPointTagList"
:key="`${scope.row.idNo || scope.row.name || index}-risk-point-${index}`"
size="mini"
effect="plain"
:type="mapRiskLevelToTagType(tag.riskLevel)"
>
{{ tag.ruleName }}
</el-tag>
</div>
<span v-else class="empty-text">-</span>
</template>
</el-table-column>
<el-table-column label="操作" width="100" align="right"> <el-table-column label="操作" width="100" align="right">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button type="text" size="mini">{{ scope.row.actionLabel || "查看详情" }}</el-button> <el-button type="text" size="mini">{{ scope.row.actionLabel || "查看详情" }}</el-button>
@@ -37,6 +55,52 @@
</template> </template>
<script> <script>
function normalizeRiskPointTags(tags, riskPoint, riskLevel) {
if (Array.isArray(tags) && tags.length) {
return tags
.map((item) => {
if (typeof item === "string") {
return {
ruleName: item.trim(),
riskLevel,
};
}
if (item && typeof item === "object") {
return {
ruleName: item.ruleName || item.label || item.name || "",
riskLevel: item.riskLevel || riskLevel,
};
}
return null;
})
.filter((item) => item && item.ruleName);
}
if (!riskPoint) {
return [];
}
return String(riskPoint)
.split(/[、,;]/)
.map((item) => item.trim())
.filter(Boolean)
.map((item) => ({
ruleName: item,
riskLevel,
}));
}
function normalizeOverviewRows(rows) {
if (!Array.isArray(rows)) {
return [];
}
return rows.map((item) => ({
...item,
riskPointTagList: normalizeRiskPointTags(item.riskPointTagList, item.riskPoint, item.riskLevelType),
}));
}
export default { export default {
name: "RiskPeopleSection", name: "RiskPeopleSection",
props: { props: {
@@ -45,6 +109,23 @@ export default {
default: () => ({}), default: () => ({}),
}, },
}, },
computed: {
overviewList() {
return normalizeOverviewRows(this.sectionData.overviewList);
},
},
methods: {
mapRiskLevelToTagType(riskLevel) {
const level = String(riskLevel || "").toUpperCase();
if (level === "HIGH" || level === "DANGER") {
return "danger";
}
if (level === "MEDIUM" || level === "WARNING") {
return "warning";
}
return "info";
},
},
}; };
</script> </script>
@@ -88,4 +169,14 @@ export default {
background: #f8fafc; background: #f8fafc;
color: #64748b; color: #64748b;
} }
.risk-point-tag-list {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.empty-text {
color: #94a3b8;
}
</style> </style>

View File

@@ -22,6 +22,10 @@ export const mockOverviewData = {
riskLevelType: "danger", riskLevelType: "danger",
modelCount: 3, modelCount: 3,
riskPoint: "跨地域转账频繁交易", riskPoint: "跨地域转账频繁交易",
riskPointTagList: [
{ ruleName: "跨地域转账", riskLevel: "HIGH" },
{ ruleName: "频繁交易", riskLevel: "HIGH" },
],
actionLabel: "查看详情", actionLabel: "查看详情",
}, },
{ {
@@ -33,6 +37,10 @@ export const mockOverviewData = {
riskLevelType: "warning", riskLevelType: "warning",
modelCount: 2, modelCount: 2,
riskPoint: "多工资转入频繁交易", riskPoint: "多工资转入频繁交易",
riskPointTagList: [
{ ruleName: "多工资转入", riskLevel: "MEDIUM" },
{ ruleName: "频繁交易", riskLevel: "MEDIUM" },
],
actionLabel: "查看详情", actionLabel: "查看详情",
}, },
{ {
@@ -44,6 +52,9 @@ export const mockOverviewData = {
riskLevelType: "info", riskLevelType: "info",
modelCount: 1, modelCount: 1,
riskPoint: "频繁小额转账", riskPoint: "频繁小额转账",
riskPointTagList: [
{ ruleName: "频繁小额转账", riskLevel: "LOW" },
],
actionLabel: "查看详情", actionLabel: "查看详情",
}, },
], ],

View File

@@ -0,0 +1,17 @@
const assert = require("assert");
const fs = require("fs");
const path = require("path");
const source = fs.readFileSync(
path.resolve(__dirname, "../../src/views/ccdiProject/components/detail/RiskPeopleSection.vue"),
"utf8"
);
[
"risk-point-tag-list",
"scope.row.riskPointTagList",
"normalizeRiskPointTags",
":type=\"mapRiskLevelToTagType(tag.riskLevel)\"",
].forEach((token) => assert(source.includes(token), token));
assert(!source.includes('<el-table-column prop="riskPoint" label="核心异常点" min-width="220" />'));