新增未提交账户风险明细

This commit is contained in:
wjj
2026-07-20 14:26:29 +08:00
parent 17628486ae
commit 7fec8e21be
16 changed files with 513 additions and 35 deletions

View File

@@ -152,6 +152,18 @@ export function getOverviewUnsubmittedAccounts(projectId) {
})
}
export function getOverviewUnsubmittedAccountFlows(params) {
return request({
url: '/ccdi/project/overview/unsubmitted-accounts/flows',
method: 'get',
params: {
projectId: params.projectId,
subjectName: params.subjectName,
accountNo: params.accountNo
}
})
}
export function exportOverviewReport(projectId) {
return request({
url: '/ccdi/project/overview/report/export',

View File

@@ -6,31 +6,31 @@
<div class="section-title">风险明细</div>
<div class="section-subtitle">展示涉疑交易与异常账户关联人员信息</div>
</div>
<el-button size="mini" type="text" :disabled="!projectId" @click="handleRiskDetailExport">
导出
</el-button>
</div>
<div class="block">
<div class="block-header">
<div>
<div class="block-title">未提交账户</div>
<div class="block-subtitle">被调查主体未提交分析的同名账户已排除银行贷款类账号</div>
<div class="block-subtitle">被调查主体未提交分析的同名账户</div>
</div>
<el-button
size="mini"
type="text"
icon="el-icon-download"
:disabled="!projectId || unsubmittedAccountTotal <= 0"
@click="handleUnsubmittedAccountExport"
>
导出流水
导出
</el-button>
</div>
<div v-loading="unsubmittedAccountLoading" class="unsubmitted-panel">
<template v-if="unsubmittedAccountTotal > 0">
<div class="unsubmitted-metrics">
<div class="unsubmitted-metric">
<div class="metric-label">涉及主体数</div>
<div class="metric-value">{{ unsubmittedSubjectTotal }}</div>
</div>
<div class="unsubmitted-metric">
<div class="metric-label">未提交账户数</div>
<div class="metric-value">{{ unsubmittedAccountTotal }}</div>
@@ -65,6 +65,10 @@
v-for="account in subject.accounts"
:key="`${subject.subjectName}-${account.accountNo}`"
class="unsubmitted-account-card"
role="button"
tabindex="0"
@click="handleUnsubmittedAccountCardClick(subject, account)"
@keyup.enter="handleUnsubmittedAccountCardClick(subject, account)"
>
<div class="unsubmitted-account-head">
<div class="unsubmitted-account-no">
@@ -143,6 +147,11 @@
查询
</el-button>
</el-form-item>
<el-form-item class="suspicious-export-action">
<el-button size="mini" type="text" :disabled="!projectId" @click="handleSuspiciousTransactionExport">
导出
</el-button>
</el-form-item>
</el-form>
<el-table
@@ -329,6 +338,96 @@
</div>
</div>
<el-dialog
:visible.sync="unsubmittedFlowVisible"
append-to-body
custom-class="unsubmitted-flow-dialog"
width="88%"
top="4vh"
@close="closeUnsubmittedFlowDialog"
>
<template slot="title">
<div class="unsubmitted-flow-title">
<span>流水明细</span>
<div v-if="selectedUnsubmittedAccount" class="unsubmitted-flow-title-tags">
<span>{{ formatUnsubmittedFlowPeriod }}</span>
<span>{{ formatField(selectedUnsubmittedAccount.subjectName) }}</span>
<span>{{ formatField(selectedUnsubmittedAccount.accountNo) }}</span>
</div>
</div>
</template>
<div class="unsubmitted-flow-toolbar">
<el-radio-group v-model="unsubmittedFlowType" size="small">
<el-radio-button label="ALL">全部 · {{ unsubmittedFlowAllCount }}</el-radio-button>
<el-radio-button label="INFLOW">流入 · {{ unsubmittedFlowInCount }}</el-radio-button>
<el-radio-button label="OUTFLOW">流出 · {{ unsubmittedFlowOutCount }}</el-radio-button>
</el-radio-group>
<el-button
size="small"
type="text"
icon="el-icon-download"
:disabled="!selectedUnsubmittedAccount || unsubmittedFlowAllCount <= 0"
@click="handleUnsubmittedFlowExport"
>
导出流水
</el-button>
</div>
<el-table
v-loading="unsubmittedFlowLoading"
:data="unsubmittedFlowFilteredList"
class="result-table unsubmitted-flow-table"
height="640"
>
<template slot="empty">
<el-empty :image-size="96" description="当前账号暂无流水明细" />
</template>
<el-table-column label="交易时间" min-width="150">
<template slot-scope="scope">
<div class="multi-line-cell">
<div class="primary-text">{{ formatDatePart(scope.row.trxDate) }}</div>
<div class="secondary-text">{{ formatTimePart(scope.row.trxDate) }}</div>
</div>
</template>
</el-table-column>
<el-table-column label="本方账户/主体" min-width="240">
<template slot-scope="scope">
<div class="multi-line-cell">
<div class="primary-text">{{ formatField(scope.row.leAccountNo) }}</div>
<div class="secondary-text">{{ formatField(scope.row.leAccountName) }}</div>
</div>
</template>
</el-table-column>
<el-table-column label="对方名称/账户" min-width="260">
<template slot-scope="scope">
<div class="multi-line-cell">
<div class="primary-text link-text">{{ formatField(scope.row.customerAccountName) }}</div>
<div class="secondary-text">{{ formatField(scope.row.customerAccountNo) }}</div>
</div>
</template>
</el-table-column>
<el-table-column label="摘要/交易类型" min-width="260" show-overflow-tooltip>
<template slot-scope="scope">
<div class="multi-line-cell">
<div class="primary-text">{{ formatField(scope.row.userMemo) }}</div>
<div class="secondary-text">{{ formatField(scope.row.cashType) }}</div>
</div>
</template>
</el-table-column>
<el-table-column label="交易金额" min-width="170" align="right">
<template slot-scope="scope">
<span class="flow-amount" :class="getUnsubmittedFlowAmountClass(scope.row)">
{{ formatUnsubmittedFlowAmount(scope.row) }}
</span>
</template>
</el-table-column>
<el-table-column label="分类" min-width="130">
<template>
<span>未提交账户</span>
</template>
</el-table-column>
</el-table>
</el-dialog>
<el-dialog
:visible.sync="detailVisible"
append-to-body
@@ -456,6 +555,7 @@ import {
getOverviewAbnormalAccountPeople,
getOverviewEmployeeCreditNegative,
getOverviewSuspiciousTransactions,
getOverviewUnsubmittedAccountFlows,
getOverviewUnsubmittedAccounts,
} from "@/api/ccdi/projectOverview";
import { getBankStatementDetail } from "@/api/ccdiProjectBankStatement";
@@ -655,6 +755,11 @@ export default {
},
unsubmittedAccountTotal: 0,
unsubmittedAccountSubjects: [],
unsubmittedFlowVisible: false,
unsubmittedFlowLoading: false,
unsubmittedFlowType: "ALL",
selectedUnsubmittedAccount: null,
unsubmittedFlowList: [],
projectId: null,
statementDetailCache: {},
suspiciousRequestSeq: 0,
@@ -683,6 +788,33 @@ export default {
allModelOptionLabel() {
return this.isExternalSuspiciousType ? "全部外部模型" : "全部模型";
},
unsubmittedFlowFilteredList() {
if (this.unsubmittedFlowType === "INFLOW") {
return this.unsubmittedFlowList.filter((row) => this.isUnsubmittedFlowIn(row));
}
if (this.unsubmittedFlowType === "OUTFLOW") {
return this.unsubmittedFlowList.filter((row) => this.isUnsubmittedFlowOut(row));
}
return this.unsubmittedFlowList;
},
unsubmittedFlowAllCount() {
return this.unsubmittedFlowList.length;
},
unsubmittedSubjectTotal() {
return this.unsubmittedAccountSubjects.length;
},
unsubmittedFlowInCount() {
return this.unsubmittedFlowList.filter((row) => this.isUnsubmittedFlowIn(row)).length;
},
unsubmittedFlowOutCount() {
return this.unsubmittedFlowList.filter((row) => this.isUnsubmittedFlowOut(row)).length;
},
formatUnsubmittedFlowPeriod() {
if (!this.selectedUnsubmittedAccount) {
return "-";
}
return `${this.formatDatePart(this.selectedUnsubmittedAccount.startDate)} ~ ${this.formatDatePart(this.selectedUnsubmittedAccount.endDate)}`;
},
},
watch: {
sectionData: {
@@ -718,6 +850,7 @@ export default {
this.suspiciousLoading = false;
this.statementDetailCache = {};
this.closeDetailDialog();
this.closeUnsubmittedFlowDialog();
}
this.loadAbnormalAccountPeople();
this.loadUnsubmittedAccounts();
@@ -928,6 +1061,42 @@ export default {
this.unsubmittedAccountLoading = false;
}
},
async handleUnsubmittedAccountCardClick(subject, account) {
if (!this.projectId || !subject || !account || !account.accountNo) {
return;
}
const selectedAccount = {
...account,
subjectName: subject.subjectName,
};
this.selectedUnsubmittedAccount = selectedAccount;
this.unsubmittedFlowType = "ALL";
this.unsubmittedFlowList = [];
this.unsubmittedFlowVisible = true;
this.unsubmittedFlowLoading = true;
try {
const response = await getOverviewUnsubmittedAccountFlows({
projectId: this.projectId,
subjectName: selectedAccount.subjectName,
accountNo: selectedAccount.accountNo,
});
this.unsubmittedFlowList = Array.isArray(response && response.data) ? response.data : [];
} catch (error) {
this.unsubmittedFlowList = [];
this.$message.error("加载未提交账户流水失败");
console.error("加载未提交账户流水失败", error);
} finally {
this.unsubmittedFlowLoading = false;
}
},
closeUnsubmittedFlowDialog() {
this.unsubmittedFlowVisible = false;
this.unsubmittedFlowLoading = false;
this.unsubmittedFlowType = "ALL";
this.selectedUnsubmittedAccount = null;
this.unsubmittedFlowList = [];
},
async fetchStatementDetail(bankStatementId, silent, scope = {}) {
if (!bankStatementId) {
return createEmptyDetailData();
@@ -1022,18 +1191,18 @@ export default {
const value = this.formatField(detail.leAccountName);
return value === "-" ? "关联人员" : value;
},
handleRiskDetailExport() {
handleSuspiciousTransactionExport() {
if (!this.projectId) {
return;
}
this.download(
"ccdi/project/overview/risk-details/export",
"ccdi/project/overview/suspicious-transactions/export",
{
projectId: this.projectId,
modelCode: this.selectedModelCode,
suspiciousType: this.currentSuspiciousType,
},
`风险明细_${this.projectId}_${new Date().getTime()}.xlsx`
`涉疑交易明细_${this.projectId}_${new Date().getTime()}.xlsx`
);
},
handleUnsubmittedAccountExport() {
@@ -1046,6 +1215,20 @@ export default {
`未提交账户流水_${this.projectId}_${new Date().getTime()}.xlsx`
);
},
handleUnsubmittedFlowExport() {
if (!this.projectId || !this.selectedUnsubmittedAccount) {
return;
}
this.download(
"ccdi/project/overview/unsubmitted-accounts/export",
{
projectId: this.projectId,
subjectName: this.selectedUnsubmittedAccount.subjectName,
accountNo: this.selectedUnsubmittedAccount.accountNo,
},
`未提交账户流水_${this.projectId}_${this.selectedUnsubmittedAccount.accountNo}_${new Date().getTime()}.xlsx`
);
},
formatRelatedStaff(row) {
if (!row || !row.relatedStaffName) {
return "-";
@@ -1113,6 +1296,42 @@ export default {
}
return `${text.slice(0, 8)} ... ${text.slice(-6)}`;
},
formatDatePart(value) {
if (value === null || value === undefined || value === "") {
return "-";
}
return String(value).split(" ")[0] || "-";
},
formatTimePart(value) {
if (value === null || value === undefined || value === "") {
return "-";
}
const parts = String(value).split(" ");
return parts.length > 1 && parts[1] ? parts[1] : "-";
},
isUnsubmittedFlowIn(row) {
return Number(row && row.amountCr) > 0;
},
isUnsubmittedFlowOut(row) {
return Number(row && row.amountDr) > 0;
},
getUnsubmittedFlowAmountValue(row) {
if (this.isUnsubmittedFlowIn(row)) {
return Number(row.amountCr) || 0;
}
if (this.isUnsubmittedFlowOut(row)) {
return -Number(row.amountDr || 0);
}
return 0;
},
formatUnsubmittedFlowAmount(row) {
const amount = this.getUnsubmittedFlowAmountValue(row);
const prefix = amount < 0 ? "-¥" : "¥";
return `${prefix}${this.formatAmount(Math.abs(amount))}`;
},
getUnsubmittedFlowAmountClass(row) {
return this.getUnsubmittedFlowAmountValue(row) >= 0 ? "amount-in" : "amount-out";
},
formatSignedAmount(value) {
if (value === null || value === undefined || value === "") {
return "-";
@@ -1283,6 +1502,10 @@ export default {
min-width: 88px;
}
.suspicious-export-action {
margin-left: auto;
}
.suspicious-filter-select {
width: 240px;
}
@@ -1297,7 +1520,7 @@ export default {
.unsubmitted-metrics {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 18px;
margin-bottom: 24px;
}
@@ -1345,6 +1568,16 @@ export default {
padding: 18px 20px;
border: 1px solid #e5e7eb;
background: #fff;
cursor: pointer;
transition: border-color 0.16s ease, box-shadow 0.16s ease, transform 0.16s ease;
}
.unsubmitted-account-card:hover,
.unsubmitted-account-card:focus {
border-color: #93c5fd;
box-shadow: 0 10px 24px rgba(37, 99, 235, 0.1);
outline: none;
transform: translateY(-1px);
}
.unsubmitted-account-head {
@@ -1397,6 +1630,60 @@ export default {
overflow-wrap: anywhere;
}
.unsubmitted-flow-title {
display: flex;
align-items: center;
gap: 18px;
min-width: 0;
}
.unsubmitted-flow-title > span {
flex: 0 0 auto;
color: #111827;
font-size: 18px;
font-weight: 700;
}
.unsubmitted-flow-title-tags {
display: flex;
flex-wrap: wrap;
gap: 10px;
min-width: 0;
}
.unsubmitted-flow-title-tags span {
max-width: 260px;
padding: 4px 10px;
color: #409eff;
background: #ecf5ff;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.unsubmitted-flow-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
margin-bottom: 16px;
}
.unsubmitted-flow-table :deep(.el-table__header th) {
background: #fff;
color: #606266;
font-size: 14px;
}
.flow-amount {
font-size: 16px;
font-weight: 600;
}
.link-text {
color: #409eff;
}
.result-table {
width: 100%;
}