优化项目分析详情与异常明细展示

This commit is contained in:
wjj
2026-07-01 11:26:01 +08:00
parent 066d850389
commit 04ede64767
17 changed files with 724 additions and 131 deletions

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -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;

View File

@@ -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;