优化项目分析详情与异常明细展示
This commit is contained in:
@@ -12,7 +12,6 @@
|
||||
<div class="project-analysis-header">
|
||||
<div class="project-analysis-header__main">
|
||||
<div class="project-analysis-header__title-group">
|
||||
<div class="project-analysis-header__eyebrow">结果总览</div>
|
||||
<div class="project-analysis-header__title">外部人员详情</div>
|
||||
</div>
|
||||
<button class="project-analysis-header__close" type="button" aria-label="关闭" @click="closeDialog">
|
||||
@@ -21,12 +20,19 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="project-analysis-workspace">
|
||||
<project-analysis-sidebar
|
||||
class="project-analysis-layout__sidebar"
|
||||
:sidebar-data="sidebarData"
|
||||
:field-labels="sidebarFieldLabels"
|
||||
/>
|
||||
<div
|
||||
class="project-analysis-workspace"
|
||||
:class="{ 'project-analysis-workspace--sidebar-collapsed': sidebarCollapsed }"
|
||||
>
|
||||
<div class="project-analysis-layout__sidebar">
|
||||
<project-analysis-sidebar
|
||||
:sidebar-data="sidebarData"
|
||||
:field-labels="sidebarFieldLabels"
|
||||
collapsible
|
||||
:collapsed="sidebarCollapsed"
|
||||
@toggle="toggleSidebar"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="project-analysis-layout__main">
|
||||
<el-alert
|
||||
@@ -100,6 +106,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
activeTab: "abnormalDetail",
|
||||
sidebarCollapsed: false,
|
||||
detailLoading: false,
|
||||
detailError: "",
|
||||
statementRows: [],
|
||||
@@ -213,6 +220,7 @@ export default {
|
||||
visible(value) {
|
||||
if (value) {
|
||||
this.activeTab = "abnormalDetail";
|
||||
this.sidebarCollapsed = false;
|
||||
this.loadStatementRows();
|
||||
}
|
||||
},
|
||||
@@ -295,22 +303,91 @@ export default {
|
||||
...tag,
|
||||
modelCode: tag.modelCode || (matched && matched.modelCode),
|
||||
modelName: tag.modelName || (matched && matched.modelName),
|
||||
reasonDetail: tag.reasonDetail || (matched && matched.reasonDetail),
|
||||
};
|
||||
},
|
||||
buildObjectAbnormalRecord(tag, index) {
|
||||
const safeTag = typeof tag === "object" && tag ? tag : {};
|
||||
const modelName = safeTag.modelName || "外部人员模型";
|
||||
const ruleName = safeTag.ruleName || this.formatRiskTag(tag) || "外部人员预警";
|
||||
const matchedRows = this.findMatchedStatementRows(safeTag);
|
||||
const statementReason = this.buildStatementReasonDetail(ruleName, matchedRows);
|
||||
return {
|
||||
modelCode: safeTag.modelCode || `EXTERNAL_MODEL_${index}`,
|
||||
title: ruleName,
|
||||
subtitle: modelName,
|
||||
riskTags: [modelName, this.formatRiskLevel(safeTag.riskLevel)].filter(Boolean),
|
||||
reasonDetail: safeTag.reasonDetail || this.buildReasonDetail(ruleName, modelName),
|
||||
reasonDetail: safeTag.reasonDetail || statementReason || this.buildReasonDetail(ruleName, modelName),
|
||||
summary: ruleName,
|
||||
extraFields: [],
|
||||
extraFields: this.buildObjectExtraFields(matchedRows),
|
||||
};
|
||||
},
|
||||
findMatchedStatementRows(riskTag) {
|
||||
const riskKeys = this.buildTagKeys(riskTag);
|
||||
if (!riskKeys.length) {
|
||||
return [];
|
||||
}
|
||||
return this.statementRows.filter((row) => {
|
||||
const hitTags = Array.isArray(row && row.hitTags) ? row.hitTags : [];
|
||||
return hitTags.some((tag) => this.buildTagKeys(tag).some((key) => riskKeys.includes(key)));
|
||||
});
|
||||
},
|
||||
buildStatementReasonDetail(ruleName, rows) {
|
||||
if (!Array.isArray(rows) || !rows.length) {
|
||||
return "";
|
||||
}
|
||||
const amounts = rows.map((row) => Math.abs(Number(row && row.displayAmount) || 0));
|
||||
const totalAmount = amounts.reduce((sum, amount) => sum + amount, 0);
|
||||
const maxAmount = amounts.reduce((max, amount) => Math.max(max, amount), 0);
|
||||
const latestTime = rows
|
||||
.map((row) => row && row.trxDate)
|
||||
.filter(Boolean)
|
||||
.sort()
|
||||
.pop();
|
||||
const counterparties = this.collectCounterparties(rows);
|
||||
const parts = [
|
||||
`命中${ruleName}`,
|
||||
`关联流水${rows.length}笔`,
|
||||
`累计交易金额${this.formatAmount(totalAmount)}元`,
|
||||
`单笔最大金额${this.formatAmount(maxAmount)}元`,
|
||||
latestTime ? `最近交易时间${latestTime}` : "",
|
||||
counterparties ? `主要对手方:${counterparties}` : "",
|
||||
].filter(Boolean);
|
||||
return parts.join(",");
|
||||
},
|
||||
buildObjectExtraFields(rows) {
|
||||
if (!Array.isArray(rows) || !rows.length) {
|
||||
return [];
|
||||
}
|
||||
const amounts = rows.map((row) => Math.abs(Number(row && row.displayAmount) || 0));
|
||||
const totalAmount = amounts.reduce((sum, amount) => sum + amount, 0);
|
||||
const maxAmount = amounts.reduce((max, amount) => Math.max(max, amount), 0);
|
||||
const latestTime = rows
|
||||
.map((row) => row && row.trxDate)
|
||||
.filter(Boolean)
|
||||
.sort()
|
||||
.pop();
|
||||
return [
|
||||
{ label: "关联流水", value: `${rows.length}笔` },
|
||||
{ label: "累计金额", value: `${this.formatAmount(totalAmount)}元` },
|
||||
{ label: "最大金额", value: `${this.formatAmount(maxAmount)}元` },
|
||||
latestTime ? { label: "最近交易", value: latestTime } : null,
|
||||
{ label: "主要对手方", value: this.collectCounterparties(rows) || "-" },
|
||||
].filter(Boolean);
|
||||
},
|
||||
collectCounterparties(rows) {
|
||||
const names = [];
|
||||
rows.forEach((row) => {
|
||||
const value = row && (row.customerAccountName || row.customerAccountNo);
|
||||
if (value && !names.includes(value)) {
|
||||
names.push(value);
|
||||
}
|
||||
});
|
||||
return names.slice(0, 3).join("、");
|
||||
},
|
||||
formatAmount(value) {
|
||||
return (Number(value) || 0).toFixed(2);
|
||||
},
|
||||
buildReasonDetail(ruleName, modelName) {
|
||||
const parts = [
|
||||
this.displayName,
|
||||
@@ -343,8 +420,18 @@ export default {
|
||||
closeDialog() {
|
||||
this.visibleProxy = false;
|
||||
},
|
||||
toggleSidebar() {
|
||||
this.sidebarCollapsed = !this.sidebarCollapsed;
|
||||
this.$nextTick(() => {
|
||||
const fundFlowTab = this.$refs.fundFlowTab;
|
||||
if (fundFlowTab && fundFlowTab.ensureGraphReady) {
|
||||
fundFlowTab.ensureGraphReady();
|
||||
}
|
||||
});
|
||||
},
|
||||
handleClosed() {
|
||||
this.activeTab = "abnormalDetail";
|
||||
this.sidebarCollapsed = false;
|
||||
this.detailLoading = false;
|
||||
this.detailError = "";
|
||||
this.statementRows = [];
|
||||
@@ -372,35 +459,27 @@ export default {
|
||||
}
|
||||
|
||||
.project-analysis-header {
|
||||
padding: 28px 40px 24px;
|
||||
padding: 14px 24px;
|
||||
border-bottom: 1px solid #dde3ec;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.project-analysis-header__main {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.project-analysis-header__title-group {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.project-analysis-header__eyebrow {
|
||||
color: #65758d;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.project-analysis-header__title {
|
||||
margin-top: 18px;
|
||||
color: #101a2b;
|
||||
font-size: 28px;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.project-analysis-header__close {
|
||||
@@ -425,24 +504,41 @@ export default {
|
||||
.project-analysis-workspace {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 36px;
|
||||
gap: 0;
|
||||
min-height: 640px;
|
||||
max-height: calc(92vh - 150px);
|
||||
padding: 20px 40px 28px;
|
||||
max-height: calc(92vh - 96px);
|
||||
padding: 20px 28px 28px;
|
||||
overflow: auto;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.project-analysis-workspace--sidebar-collapsed {
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.project-analysis-layout__sidebar {
|
||||
flex: 0 0 34%;
|
||||
max-width: 460px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
flex: 0 0 286px;
|
||||
max-width: 286px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.project-analysis-workspace--sidebar-collapsed .project-analysis-layout__sidebar {
|
||||
flex-basis: 46px;
|
||||
max-width: 46px;
|
||||
}
|
||||
|
||||
.project-analysis-layout__main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
border-left: 1px solid #dde3ec;
|
||||
padding-left: 36px;
|
||||
border-left: 0;
|
||||
padding-left: 24px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.project-analysis-workspace--sidebar-collapsed .project-analysis-layout__main {
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
||||
.project-analysis-tabs {
|
||||
|
||||
@@ -8,6 +8,16 @@
|
||||
>
|
||||
<div class="abnormal-card__header">
|
||||
<div class="abnormal-card__title">{{ group.groupName || "异常明细" }}</div>
|
||||
<el-button
|
||||
v-if="shouldShowExportButton(group)"
|
||||
size="mini"
|
||||
type="primary"
|
||||
plain
|
||||
icon="el-icon-download"
|
||||
@click="handleExportAbnormalDetail"
|
||||
>
|
||||
导出
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div class="abnormal-card__content">
|
||||
@@ -17,15 +27,15 @@
|
||||
class="abnormal-table"
|
||||
>
|
||||
<el-table-column prop="trxDate" label="交易时间" min-width="160" />
|
||||
<el-table-column label="本方账户" min-width="220">
|
||||
<el-table-column label="本方信息" min-width="220">
|
||||
<template slot-scope="scope">
|
||||
<div class="multi-line-cell">
|
||||
<div class="primary-text">{{ scope.row.leAccountNo || "-" }}</div>
|
||||
<div class="secondary-text">{{ scope.row.leAccountName || "-" }}</div>
|
||||
<div class="primary-text">{{ scope.row.leAccountName || "-" }}</div>
|
||||
<div class="secondary-text">{{ scope.row.leAccountNo || "-" }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="对方账户" min-width="220">
|
||||
<el-table-column label="对方信息" min-width="220">
|
||||
<template slot-scope="scope">
|
||||
<div class="multi-line-cell">
|
||||
<div class="primary-text">{{ scope.row.customerAccountName || "-" }}</div>
|
||||
@@ -126,6 +136,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { saveAs } from "file-saver";
|
||||
import { buildModelEvidenceFingerprint, MODEL_EVIDENCE_FINGERPRINT_RULE } from "@/utils/ccdiEvidence";
|
||||
|
||||
export default {
|
||||
@@ -194,6 +205,82 @@ export default {
|
||||
const groupKey = this.resolveGroupKey(group);
|
||||
this.$set(this.statementPageMap, groupKey, page);
|
||||
},
|
||||
shouldShowExportButton(group) {
|
||||
return group && group.groupType === "BANK_STATEMENT";
|
||||
},
|
||||
handleExportAbnormalDetail() {
|
||||
const rows = this.buildAbnormalExportRows();
|
||||
if (!rows.length) {
|
||||
this.$message.warning("暂无可导出的异常流水明细");
|
||||
return;
|
||||
}
|
||||
const headers = [
|
||||
"分组",
|
||||
"交易时间",
|
||||
"本方名称",
|
||||
"本方账号",
|
||||
"对手方名称",
|
||||
"对手方账号",
|
||||
"摘要",
|
||||
"交易类型",
|
||||
"异常标签",
|
||||
"交易金额",
|
||||
];
|
||||
const csvContent = [headers, ...rows]
|
||||
.map((row) => row.map((cell) => this.escapeCsvCell(cell)).join(","))
|
||||
.join("\r\n");
|
||||
const blob = new Blob([`\uFEFF${csvContent}`], { type: "text/csv;charset=utf-8;" });
|
||||
const fileName = `${this.sanitizeFileName(`异常流水明细_${this.resolvePersonName()}`)}.csv`;
|
||||
saveAs(blob, fileName);
|
||||
},
|
||||
buildAbnormalExportRows() {
|
||||
const rows = [];
|
||||
this.detailGroups.forEach((group) => {
|
||||
if (!group || group.groupType !== "BANK_STATEMENT") {
|
||||
return;
|
||||
}
|
||||
const records = Array.isArray(group && group.records) ? group.records : [];
|
||||
records.forEach((record) => {
|
||||
rows.push([
|
||||
group.groupName || "",
|
||||
record.trxDate,
|
||||
record.leAccountName,
|
||||
record.leAccountNo,
|
||||
record.customerAccountName,
|
||||
record.customerAccountNo,
|
||||
record.userMemo,
|
||||
record.cashType,
|
||||
this.formatRiskTags(record.hitTags),
|
||||
record.displayAmount,
|
||||
]);
|
||||
});
|
||||
});
|
||||
return rows;
|
||||
},
|
||||
formatRiskTags(tags) {
|
||||
if (!Array.isArray(tags)) {
|
||||
return "";
|
||||
}
|
||||
return tags
|
||||
.map((tag) => {
|
||||
if (tag && typeof tag === "object") {
|
||||
return tag.ruleName || tag.name || tag.label || tag.title || "";
|
||||
}
|
||||
return tag;
|
||||
})
|
||||
.filter((tag) => tag !== null && tag !== undefined && `${tag}`.trim() !== "")
|
||||
.join("、");
|
||||
},
|
||||
escapeCsvCell(value) {
|
||||
const text = value === null || value === undefined ? "" : `${value}`;
|
||||
return `"${text.replace(/"/g, '""')}"`;
|
||||
},
|
||||
sanitizeFileName(value) {
|
||||
return `${value || "异常明细"}`.replace(/[\\/:*?"<>|]/g, "_");
|
||||
},
|
||||
resolvePersonName() {
|
||||
return (this.person && (this.person.name || this.person.staffName || this.person.personName)) || "项目分析";
|
||||
},
|
||||
handleAddModelEvidence(item, group) {
|
||||
const safeItem = item || {};
|
||||
const safeGroup = group || {};
|
||||
@@ -254,6 +341,10 @@ export default {
|
||||
}
|
||||
|
||||
.abnormal-card__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 0;
|
||||
padding: 22px 30px;
|
||||
border-bottom: 1px solid #e3eaf3;
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
<div class="project-analysis-header">
|
||||
<div class="project-analysis-header__main">
|
||||
<div class="project-analysis-header__title-group">
|
||||
<div class="project-analysis-header__eyebrow">结果总览</div>
|
||||
<div class="project-analysis-header__title">项目分析详情</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -29,11 +28,19 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-loading="detailLoading" class="project-analysis-workspace">
|
||||
<project-analysis-sidebar
|
||||
class="project-analysis-layout__sidebar"
|
||||
:sidebar-data="dialogData.sidebar"
|
||||
/>
|
||||
<div
|
||||
v-loading="detailLoading"
|
||||
class="project-analysis-workspace"
|
||||
:class="{ 'project-analysis-workspace--sidebar-collapsed': sidebarCollapsed }"
|
||||
>
|
||||
<div class="project-analysis-layout__sidebar">
|
||||
<project-analysis-sidebar
|
||||
:sidebar-data="dialogData.sidebar"
|
||||
collapsible
|
||||
:collapsed="sidebarCollapsed"
|
||||
@toggle="toggleSidebar"
|
||||
/>
|
||||
</div>
|
||||
<div class="project-analysis-layout__main">
|
||||
<el-alert
|
||||
v-if="detailError"
|
||||
@@ -137,6 +144,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
activeTab: "abnormalDetail",
|
||||
sidebarCollapsed: false,
|
||||
detailLoading: false,
|
||||
detailError: "",
|
||||
detailData: null,
|
||||
@@ -182,6 +190,7 @@ export default {
|
||||
methods: {
|
||||
resetDialogState() {
|
||||
this.activeTab = "abnormalDetail";
|
||||
this.sidebarCollapsed = false;
|
||||
this.detailLoading = false;
|
||||
this.detailError = "";
|
||||
this.detailData = null;
|
||||
@@ -230,6 +239,17 @@ export default {
|
||||
closeDialog() {
|
||||
this.visibleProxy = false;
|
||||
},
|
||||
toggleSidebar() {
|
||||
this.sidebarCollapsed = !this.sidebarCollapsed;
|
||||
this.$nextTick(() => {
|
||||
const tabRef = this.activeTab === "relationshipGraph"
|
||||
? this.$refs.relationshipGraphTab
|
||||
: this.$refs.fundFlowTab;
|
||||
if (tabRef && tabRef.ensureGraphReady) {
|
||||
tabRef.ensureGraphReady();
|
||||
}
|
||||
});
|
||||
},
|
||||
handleTabChange() {
|
||||
this.$nextTick(() => {
|
||||
const tabRef = this.activeTab === "relationshipGraph"
|
||||
@@ -255,42 +275,34 @@ export default {
|
||||
}
|
||||
|
||||
.project-analysis-header {
|
||||
padding: 28px 40px 24px;
|
||||
padding: 14px 24px;
|
||||
border-bottom: 1px solid #dde3ec;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.project-analysis-header__main {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.project-analysis-header__title-group {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.project-analysis-header__eyebrow {
|
||||
color: #65758d;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.project-analysis-header__title {
|
||||
margin-top: 18px;
|
||||
color: #101a2b;
|
||||
font-size: 28px;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.project-analysis-header__meta {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-height: 34px;
|
||||
min-height: 30px;
|
||||
padding: 0 12px;
|
||||
border: 1px solid #bfd0e2;
|
||||
border-radius: 2px;
|
||||
@@ -331,14 +343,18 @@ export default {
|
||||
.project-analysis-workspace {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 36px;
|
||||
gap: 0;
|
||||
min-height: 640px;
|
||||
max-height: calc(92vh - 150px);
|
||||
padding: 20px 40px 28px;
|
||||
max-height: calc(92vh - 96px);
|
||||
padding: 20px 28px 28px;
|
||||
overflow: auto;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.project-analysis-workspace--sidebar-collapsed {
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.project-analysis-layout {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
@@ -346,15 +362,28 @@ export default {
|
||||
}
|
||||
|
||||
.project-analysis-layout__sidebar {
|
||||
flex: 0 0 34%;
|
||||
max-width: 460px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
flex: 0 0 286px;
|
||||
max-width: 286px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.project-analysis-workspace--sidebar-collapsed .project-analysis-layout__sidebar {
|
||||
flex-basis: 46px;
|
||||
max-width: 46px;
|
||||
}
|
||||
|
||||
.project-analysis-layout__main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
border-left: 1px solid #dde3ec;
|
||||
padding-left: 36px;
|
||||
border-left: 0;
|
||||
padding-left: 24px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.project-analysis-workspace--sidebar-collapsed .project-analysis-layout__main {
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
||||
.project-analysis-layout__alert {
|
||||
@@ -370,7 +399,7 @@ export default {
|
||||
.project-analysis-dialog {
|
||||
margin-top: 2vh !important;
|
||||
border-radius: 0;
|
||||
background: #f5f6f8;
|
||||
background: #ffffff;
|
||||
overflow: hidden;
|
||||
|
||||
.el-dialog__header {
|
||||
@@ -379,7 +408,7 @@ export default {
|
||||
|
||||
.el-dialog__body {
|
||||
padding: 0;
|
||||
background: #f5f6f8;
|
||||
background: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,30 @@
|
||||
<template>
|
||||
<aside class="project-analysis-sidebar">
|
||||
<div class="sidebar-profile-card">
|
||||
<aside class="project-analysis-sidebar" :class="{ 'is-collapsed': collapsed }">
|
||||
<div v-if="collapsed" class="sidebar-collapsed-card">
|
||||
<button
|
||||
v-if="collapsible"
|
||||
class="sidebar-card-toggle"
|
||||
type="button"
|
||||
title="展开"
|
||||
aria-label="展开"
|
||||
@click="$emit('toggle')"
|
||||
>
|
||||
<i class="el-icon-arrow-right" />
|
||||
</button>
|
||||
<span>人物档案</span>
|
||||
<strong>{{ sidebarData.basicInfo.name || "-" }}</strong>
|
||||
</div>
|
||||
<div v-else class="sidebar-profile-card">
|
||||
<button
|
||||
v-if="collapsible"
|
||||
class="sidebar-card-toggle"
|
||||
type="button"
|
||||
title="收起"
|
||||
aria-label="收起"
|
||||
@click="$emit('toggle')"
|
||||
>
|
||||
<i class="el-icon-arrow-left" />
|
||||
</button>
|
||||
<section class="sidebar-profile">
|
||||
<div class="sidebar-profile__identity">
|
||||
<div class="sidebar-profile__identity-label">人物档案</div>
|
||||
@@ -26,13 +50,12 @@
|
||||
</section>
|
||||
|
||||
<section class="sidebar-summary">
|
||||
<div class="sidebar-summary__title">命中模型摘要</div>
|
||||
<div class="sidebar-summary__count">
|
||||
<span class="sidebar-summary__count-label">命中模型数</span>
|
||||
<span class="sidebar-summary__count-label">命中模型</span>
|
||||
<span class="sidebar-summary__count-value">{{ sidebarData.modelSummary.modelCount || "-" }}</span>
|
||||
</div>
|
||||
<div class="sidebar-summary__tags">
|
||||
<span class="sidebar-profile__label">核心异常标签</span>
|
||||
<span class="sidebar-profile__label">异常标签</span>
|
||||
<div v-if="sidebarData.modelSummary.riskTags.length" class="sidebar-tag-list">
|
||||
<el-tag
|
||||
v-for="(tag, index) in sidebarData.modelSummary.riskTags"
|
||||
@@ -71,6 +94,14 @@ export default {
|
||||
projectName: "所属项目",
|
||||
}),
|
||||
},
|
||||
collapsible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
collapsed: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
normalizedFieldLabels() {
|
||||
@@ -97,32 +128,67 @@ export default {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.project-analysis-sidebar {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.sidebar-profile-card {
|
||||
border: 1px solid #dde3ec;
|
||||
border-radius: 3px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.project-analysis-sidebar.is-collapsed {
|
||||
width: 46px;
|
||||
}
|
||||
|
||||
.sidebar-profile-card {
|
||||
position: relative;
|
||||
border: 1px solid #dfe6ef;
|
||||
border-radius: 3px;
|
||||
background: #ffffff;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.sidebar-card-toggle {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
z-index: 2;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 0;
|
||||
border: 1px solid #d5dfeb;
|
||||
border-radius: 2px;
|
||||
background: #ffffff;
|
||||
color: #4f6077;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sidebar-card-toggle:hover {
|
||||
border-color: #9db7d4;
|
||||
color: #245b8f;
|
||||
background: #eef5fb;
|
||||
}
|
||||
|
||||
.sidebar-profile {
|
||||
padding: 34px 42px 28px;
|
||||
border-bottom: 1px solid #edf1f5;
|
||||
padding: 22px 22px 6px;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.sidebar-summary {
|
||||
padding: 30px 42px 34px;
|
||||
border-top: 1px solid #dde3ec;
|
||||
background: #fcfdfe;
|
||||
padding: 0 22px 22px;
|
||||
border-top: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.sidebar-profile__identity-label {
|
||||
padding-right: 32px;
|
||||
color: #637187;
|
||||
font-size: 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 28px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.sidebar-profile__name-row {
|
||||
@@ -134,39 +200,41 @@ export default {
|
||||
|
||||
.sidebar-profile__name {
|
||||
color: #111827;
|
||||
font-size: 34px;
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
min-width: 0;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.sidebar-risk-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 102px;
|
||||
height: 44px;
|
||||
padding: 0 16px;
|
||||
min-width: 74px;
|
||||
height: 32px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid #f0c6c1;
|
||||
border-radius: 2px;
|
||||
background: #fff3f2;
|
||||
color: #c43d33;
|
||||
font-size: 17px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 42px;
|
||||
line-height: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sidebar-profile__meta {
|
||||
display: grid;
|
||||
gap: 0;
|
||||
margin-top: 30px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.sidebar-profile__item {
|
||||
display: grid;
|
||||
grid-template-columns: 116px minmax(0, 1fr);
|
||||
gap: 16px;
|
||||
padding: 18px 0;
|
||||
grid-template-columns: 72px minmax(0, 1fr);
|
||||
gap: 10px;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #edf1f5;
|
||||
}
|
||||
|
||||
@@ -177,46 +245,38 @@ export default {
|
||||
.sidebar-profile__label,
|
||||
.sidebar-summary__count-label {
|
||||
color: #637187;
|
||||
font-size: 16px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.sidebar-profile__value,
|
||||
.sidebar-summary__count-value {
|
||||
color: #172033;
|
||||
font-size: 17px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 1.4;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.sidebar-summary__title {
|
||||
margin: 0 0 24px;
|
||||
color: #223047;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.sidebar-summary__count {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 16px;
|
||||
margin-top: 0;
|
||||
margin-bottom: 28px;
|
||||
gap: 10px;
|
||||
margin: 4px 0 14px;
|
||||
}
|
||||
|
||||
.sidebar-summary__tags {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
gap: 10px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.sidebar-summary__count-label {
|
||||
font-size: 17px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.sidebar-summary__count-value {
|
||||
color: #172033;
|
||||
font-size: 40px;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
@@ -229,14 +289,52 @@ export default {
|
||||
}
|
||||
|
||||
.sidebar-tag-list ::v-deep(.el-tag) {
|
||||
height: 38px;
|
||||
padding: 0 16px;
|
||||
height: 28px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid #cad8eb;
|
||||
border-radius: 2px;
|
||||
background: #ffffff;
|
||||
color: #245b8f;
|
||||
font-size: 15px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 36px;
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
.sidebar-collapsed-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-height: 220px;
|
||||
width: 46px;
|
||||
padding: 44px 0 14px;
|
||||
border: 1px solid #dfe6ef;
|
||||
border-radius: 3px;
|
||||
background: #ffffff;
|
||||
color: #637187;
|
||||
writing-mode: vertical-rl;
|
||||
text-orientation: mixed;
|
||||
}
|
||||
|
||||
.sidebar-collapsed-card .sidebar-card-toggle {
|
||||
top: 10px;
|
||||
left: 50%;
|
||||
right: auto;
|
||||
transform: translateX(-50%);
|
||||
writing-mode: horizontal-tb;
|
||||
}
|
||||
|
||||
.sidebar-collapsed-card span {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.sidebar-collapsed-card strong {
|
||||
max-height: 136px;
|
||||
color: #172033;
|
||||
font-size: 16px;
|
||||
line-height: 1.2;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -200,6 +200,8 @@ function normalizeRiskPointTags(tags, riskPoint, riskLevel) {
|
||||
riskLevel: item.riskLevel || riskLevel,
|
||||
modelCode: item.modelCode || "",
|
||||
modelName: item.modelName || "",
|
||||
ruleCode: item.ruleCode || "",
|
||||
reasonDetail: item.reasonDetail || "",
|
||||
};
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -148,15 +148,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="detail-panel edge-panel">
|
||||
<div class="detail-head">
|
||||
<div>
|
||||
<div class="detail-title">{{ selectedEdgeTitle }}</div>
|
||||
<div class="detail-subtitle">
|
||||
{{ isManualEdge(selectedEdge) ? "手工录入汇总边,不提供逐笔流水下钻" : "主体汇总边,下钻展示账户层逐笔流水" }}
|
||||
<div v-else class="detail-panel edge-panel">
|
||||
<div class="detail-head">
|
||||
<div>
|
||||
<div class="detail-title">{{ selectedEdgeTitle }}</div>
|
||||
<div class="detail-subtitle">
|
||||
{{ isManualEdge(selectedEdge) ? "手工录入汇总边,不提供逐笔流水下钻" : "主体汇总边,下钻展示账户层逐笔流水" }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="showEdgeMetrics" class="edge-metrics">
|
||||
<div>
|
||||
@@ -209,10 +209,16 @@
|
||||
<el-empty :image-size="72" description="暂无流水明细" />
|
||||
</template>
|
||||
<el-table-column label="交易日期" prop="trxDate" width="132" />
|
||||
<el-table-column label="本方名称" prop="leAccountName" min-width="92" show-overflow-tooltip />
|
||||
<el-table-column label="对手方名称" min-width="98" show-overflow-tooltip>
|
||||
<el-table-column label="本方信息" min-width="132" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.customerAccountName || "-" }}
|
||||
<div class="account-main">{{ scope.row.leAccountName || "-" }}</div>
|
||||
<div class="account-sub">{{ scope.row.leAccountNo || "-" }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="对手方信息" min-width="142" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
<div class="account-main">{{ scope.row.customerAccountName || "-" }}</div>
|
||||
<div class="account-sub">{{ scope.row.customerAccountNo || "-" }}</div>
|
||||
<el-tag
|
||||
v-if="scope.row.familyRelationType"
|
||||
size="mini"
|
||||
@@ -483,6 +489,13 @@ const COMPANY_SYMBOL = svgSymbol(`
|
||||
<path d="M20 24 H44 M20 34 H44 M20 44 H35" stroke="#b88745" stroke-width="4" stroke-linecap="round" />
|
||||
</svg>`);
|
||||
|
||||
const RELATION_SYMBOL = svgSymbol(`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
|
||||
<path d="M32 6 L55 19 V45 L32 58 L9 45 V19 Z" fill="#fff6ec" stroke="#d98b3c" stroke-width="3.5" />
|
||||
<circle cx="32" cy="27" r="8" fill="#d98b3c" />
|
||||
<path d="M18 47 C20 37 25 33 32 33 C39 33 44 37 46 47 Z" fill="#d98b3c" />
|
||||
</svg>`);
|
||||
|
||||
export default {
|
||||
name: "FundGraphSection",
|
||||
props: {
|
||||
@@ -1380,7 +1393,7 @@ export default {
|
||||
return `${edge.fromName} → ${edge.toName}<br/>金额:${this.formatMoney(edge.totalAmount)}<br/>笔数:${edge.transactionCount || 0}`;
|
||||
}
|
||||
const node = params.data.raw;
|
||||
return `${node.nodeName}<br/>object_key:${node.objectKey || "-"}`;
|
||||
return `${node.nodeName}<br/>类型:${this.getFundNodeCategoryName(params.data.category)}<br/>object_key:${node.objectKey || "-"}`;
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
@@ -1389,7 +1402,7 @@ export default {
|
||||
itemWidth: 10,
|
||||
itemHeight: 10,
|
||||
textStyle: { color: "#606f82" },
|
||||
data: ["人员", "家庭关系", "对手方", "已展开"],
|
||||
data: ["本人", "关系人", "其他方", "已展开"],
|
||||
},
|
||||
series: [{
|
||||
type: "graph",
|
||||
@@ -1400,9 +1413,9 @@ export default {
|
||||
right: 56,
|
||||
bottom: 34,
|
||||
categories: [
|
||||
{ name: "人员", itemStyle: { color: "#48a57f" } },
|
||||
{ name: "家庭关系", itemStyle: { color: "#7fc56d" } },
|
||||
{ name: "对手方", itemStyle: { color: "#4f7fbd" } },
|
||||
{ name: "本人", itemStyle: { color: "#3f9f78" } },
|
||||
{ name: "关系人", itemStyle: { color: "#d98b3c" } },
|
||||
{ name: "其他方", itemStyle: { color: "#4f7fbd" } },
|
||||
{ name: "已展开", itemStyle: { color: "#95a3b6" } },
|
||||
],
|
||||
edgeSymbol: ["none", "arrow"],
|
||||
@@ -1679,14 +1692,29 @@ export default {
|
||||
};
|
||||
},
|
||||
getNodeSymbol(category) {
|
||||
if (category === 0 || category === 1) {
|
||||
if (category === 0) {
|
||||
return PERSON_SYMBOL;
|
||||
}
|
||||
if (category === 1) {
|
||||
return RELATION_SYMBOL;
|
||||
}
|
||||
if (category === 3) {
|
||||
return EXPANDED_SYMBOL;
|
||||
}
|
||||
return PROXY_SYMBOL;
|
||||
},
|
||||
getFundNodeCategoryName(category) {
|
||||
if (category === 0) {
|
||||
return "本人";
|
||||
}
|
||||
if (category === 1) {
|
||||
return "关系人";
|
||||
}
|
||||
if (category === 3) {
|
||||
return "已展开";
|
||||
}
|
||||
return "其他方";
|
||||
},
|
||||
getNodeStyle(category, selected, dimmed = false) {
|
||||
const isCenter = category === 0;
|
||||
return {
|
||||
@@ -2256,12 +2284,17 @@ export default {
|
||||
|
||||
.detail-head {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid #e5edf5;
|
||||
}
|
||||
|
||||
.detail-head > div {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
@@ -2274,6 +2307,21 @@ export default {
|
||||
color: #8a99ad;
|
||||
}
|
||||
|
||||
.account-main,
|
||||
.memo-main {
|
||||
color: #24364f;
|
||||
font-weight: 600;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.account-sub,
|
||||
.memo-sub {
|
||||
margin-top: 2px;
|
||||
color: #7a8aa0;
|
||||
font-size: 12px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.node-field-list {
|
||||
margin: 14px 0;
|
||||
border: 1px solid #dfeaf5;
|
||||
|
||||
Reference in New Issue
Block a user