- 当前来源:{{ source === "riskModelPeople" ? "命中模型涉及人员" : "风险人员总览" }}
+
+
+
+
+
+
+
异常明细
+
{{ getTabDescription("abnormalDetail") }}
+
+
+
+
+
资产分析
+
{{ getTabDescription("assetAnalysis") }}
+
+
+
+
+
征信摘要
+
{{ getTabDescription("creditSummary") }}
+
+
+
+
+
关系图谱
+
{{ getTabDescription("relationshipGraph") }}
+
+
+
+
+
资金流向
+
{{ getTabDescription("fundFlow") }}
+
+
+
diff --git a/ruoyi-ui/src/views/ccdiProject/components/detail/ProjectAnalysisSidebar.vue b/ruoyi-ui/src/views/ccdiProject/components/detail/ProjectAnalysisSidebar.vue
new file mode 100644
index 00000000..6564a1cb
--- /dev/null
+++ b/ruoyi-ui/src/views/ccdiProject/components/detail/ProjectAnalysisSidebar.vue
@@ -0,0 +1,144 @@
+
+
+
+
+
+
+
diff --git a/ruoyi-ui/src/views/ccdiProject/components/detail/preliminaryCheck.mock.js b/ruoyi-ui/src/views/ccdiProject/components/detail/preliminaryCheck.mock.js
index 5392e058..5c5bebb8 100644
--- a/ruoyi-ui/src/views/ccdiProject/components/detail/preliminaryCheck.mock.js
+++ b/ruoyi-ui/src/views/ccdiProject/components/detail/preliminaryCheck.mock.js
@@ -128,6 +128,93 @@ export const mockOverviewData = {
},
};
+export const projectAnalysisTabs = [
+ {
+ key: "abnormalDetail",
+ label: "异常明细",
+ description: "展示异常交易、频繁转账账户与关联交易的分析摘要。",
+ },
+ {
+ key: "assetAnalysis",
+ label: "资产分析",
+ description: "静态承载资产分析页签内容,本轮不接入新接口。",
+ },
+ {
+ key: "creditSummary",
+ label: "征信摘要",
+ description: "静态承载征信摘要页签内容,本轮不接入新接口。",
+ },
+ {
+ key: "relationshipGraph",
+ label: "关系图谱",
+ description: "静态承载关系图谱页签内容,本轮不接入新接口。",
+ },
+ {
+ key: "fundFlow",
+ label: "资金流向",
+ description: "静态承载资金流向页签内容,本轮不接入新接口。",
+ },
+];
+
+function normalizeProjectAnalysisTags(person) {
+ const sourceTags = Array.isArray(person && person.riskPointTagList)
+ ? person.riskPointTagList
+ : Array.isArray(person && person.hitTagList)
+ ? person.hitTagList
+ : [];
+
+ return sourceTags
+ .map((item) => {
+ if (typeof item === "string") {
+ return item;
+ }
+ if (item && typeof item === "object") {
+ return item.ruleName || item.label || item.name || "";
+ }
+ return "";
+ })
+ .filter(Boolean);
+}
+
+function resolveCurrentModel(person, source) {
+ if (source === "riskModelPeople") {
+ if (Array.isArray(person && person.modelNames) && person.modelNames.length) {
+ return person.modelNames.join("、");
+ }
+ return person && person.modelName ? person.modelName : "-";
+ }
+
+ const firstTag = Array.isArray(person && person.riskPointTagList) ? person.riskPointTagList[0] : null;
+ if (firstTag && firstTag.modelName) {
+ return firstTag.modelName;
+ }
+ return "-";
+}
+
+export function buildProjectAnalysisDialogData({ person, source = "riskPeople", projectName = "" } = {}) {
+ const safePerson = person || {};
+ const riskTags = normalizeProjectAnalysisTags(safePerson);
+
+ return {
+ tabs: projectAnalysisTabs,
+ sidebar: {
+ basicInfo: {
+ name: safePerson.name || safePerson.staffName || "-",
+ staffCode: safePerson.staffCode || "-",
+ department: safePerson.department || "-",
+ riskLevel: safePerson.riskLevel || safePerson.warningType || "-",
+ projectName: projectName || safePerson.projectName || "-",
+ },
+ modelSummary: {
+ modelCount: safePerson.modelCount || (Array.isArray(safePerson.modelNames) ? safePerson.modelNames.length : "-"),
+ currentModel: resolveCurrentModel(safePerson, source),
+ riskTags,
+ },
+ recordSummary: "排查记录摘要为静态承载内容,本轮用于展示统一工作台侧栏结构。",
+ },
+ };
+}
+
const summaryStatMetaMap = mockOverviewData.summary.stats.reduce((acc, item) => {
acc[item.key] = {
icon: item.icon,
diff --git a/ruoyi-ui/tests/unit/project-analysis-dialog-default-tab.test.js b/ruoyi-ui/tests/unit/project-analysis-dialog-default-tab.test.js
new file mode 100644
index 00000000..94c93f70
--- /dev/null
+++ b/ruoyi-ui/tests/unit/project-analysis-dialog-default-tab.test.js
@@ -0,0 +1,20 @@
+const assert = require("assert");
+const fs = require("fs");
+const path = require("path");
+
+const dialog = fs.readFileSync(
+ path.resolve(
+ __dirname,
+ "../../src/views/ccdiProject/components/detail/ProjectAnalysisDialog.vue"
+ ),
+ "utf8"
+);
+
+[
+ 'activeTab: "abnormalDetail"',
+ "resetDialogState()",
+ 'this.activeTab = "abnormalDetail"',
+ "handleDialogClosed()",
+ "if (value) {",
+ "this.resetDialogState()",
+].forEach((token) => assert(dialog.includes(token), token));
diff --git a/ruoyi-ui/tests/unit/project-analysis-dialog-layout.test.js b/ruoyi-ui/tests/unit/project-analysis-dialog-layout.test.js
new file mode 100644
index 00000000..28ceae1c
--- /dev/null
+++ b/ruoyi-ui/tests/unit/project-analysis-dialog-layout.test.js
@@ -0,0 +1,39 @@
+const assert = require("assert");
+const fs = require("fs");
+const path = require("path");
+
+const dialog = fs.readFileSync(
+ path.resolve(
+ __dirname,
+ "../../src/views/ccdiProject/components/detail/ProjectAnalysisDialog.vue"
+ ),
+ "utf8"
+);
+const mockSource = fs.readFileSync(
+ path.resolve(
+ __dirname,
+ "../../src/views/ccdiProject/components/detail/preliminaryCheck.mock.js"
+ ),
+ "utf8"
+);
+
+[
+ 'title="项目分析"',
+ "
assert(dialog.includes(token), token));
+
+[
+ "projectAnalysisTabs",
+ 'key: "abnormalDetail"',
+ 'key: "assetAnalysis"',
+ 'key: "creditSummary"',
+ 'key: "relationshipGraph"',
+ 'key: "fundFlow"',
+].forEach((token) => assert(mockSource.includes(token), token));
diff --git a/ruoyi-ui/tests/unit/project-analysis-dialog-sidebar.test.js b/ruoyi-ui/tests/unit/project-analysis-dialog-sidebar.test.js
new file mode 100644
index 00000000..7324b8b9
--- /dev/null
+++ b/ruoyi-ui/tests/unit/project-analysis-dialog-sidebar.test.js
@@ -0,0 +1,29 @@
+const assert = require("assert");
+const fs = require("fs");
+const path = require("path");
+
+const sidebar = fs.readFileSync(
+ path.resolve(
+ __dirname,
+ "../../src/views/ccdiProject/components/detail/ProjectAnalysisSidebar.vue"
+ ),
+ "utf8"
+);
+
+[
+ "人员基础信息",
+ "命中模型摘要",
+ "排查记录摘要",
+ "姓名",
+ "工号",
+ "部门",
+ "风险等级",
+ "所属项目",
+ "命中模型数",
+ "当前命中模型",
+ "核心异常标签",
+ "暂无异常标签",
+].forEach((token) => assert(sidebar.includes(token), token));
+
+assert(!sidebar.includes("关系人画像"), "侧栏不应扩展到额外区块");
+assert(!sidebar.includes("资产分布"), "侧栏不应扩展到额外区块");