|
|
|
|
@@ -25,6 +25,7 @@
|
|
|
|
|
class="suspicious-filter-select"
|
|
|
|
|
:placeholder="allModelOptionLabel"
|
|
|
|
|
filterable
|
|
|
|
|
@change="handleSuspiciousModelSelectChange"
|
|
|
|
|
>
|
|
|
|
|
<el-option :label="allModelOptionLabel" value="" />
|
|
|
|
|
<el-option
|
|
|
|
|
@@ -513,6 +514,15 @@ const normalizeDetailData = (detail) => ({
|
|
|
|
|
hitTags: normalizeHitTags(detail && detail.hitTags),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const normalizeSuspiciousRows = (rows) => (
|
|
|
|
|
Array.isArray(rows)
|
|
|
|
|
? rows.map((row) => ({
|
|
|
|
|
...row,
|
|
|
|
|
hitTags: normalizeHitTags(row && row.hitTags, row),
|
|
|
|
|
}))
|
|
|
|
|
: []
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
name: "RiskDetailSection",
|
|
|
|
|
props: {
|
|
|
|
|
@@ -546,6 +556,8 @@ export default {
|
|
|
|
|
abnormalAccountList: [],
|
|
|
|
|
projectId: null,
|
|
|
|
|
statementDetailCache: {},
|
|
|
|
|
suspiciousRequestSeq: 0,
|
|
|
|
|
detailRequestSeq: 0,
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
computed: {
|
|
|
|
|
@@ -574,15 +586,11 @@ export default {
|
|
|
|
|
watch: {
|
|
|
|
|
sectionData: {
|
|
|
|
|
immediate: true,
|
|
|
|
|
deep: true,
|
|
|
|
|
handler(value) {
|
|
|
|
|
this.projectId = value && value.projectId ? value.projectId : null;
|
|
|
|
|
this.currentSuspiciousType = normalizeSuspiciousType(value && value.suspiciousType);
|
|
|
|
|
this.selectedModelCode = "";
|
|
|
|
|
const nextProjectId = value && value.projectId ? value.projectId : null;
|
|
|
|
|
const projectChanged = String(nextProjectId || "") !== String(this.projectId || "");
|
|
|
|
|
this.projectId = nextProjectId;
|
|
|
|
|
this.riskModelOptions = normalizeRiskModelOptions(value && value.riskModelOptions);
|
|
|
|
|
this.suspiciousPageNum = 1;
|
|
|
|
|
this.suspiciousPageSize = DEFAULT_RISK_DETAIL_PAGE_SIZE;
|
|
|
|
|
this.suspiciousTotal = Number(value && value.total) || 0;
|
|
|
|
|
this.employeeCreditNegativePageNum = 1;
|
|
|
|
|
this.employeeCreditNegativePageSize = DEFAULT_RISK_DETAIL_PAGE_SIZE;
|
|
|
|
|
this.employeeCreditNegativeTotal = Number(value && value.employeeCreditNegativeTotal) || 0;
|
|
|
|
|
@@ -590,19 +598,33 @@ export default {
|
|
|
|
|
this.abnormalAccountPageSize = DEFAULT_RISK_DETAIL_PAGE_SIZE;
|
|
|
|
|
this.abnormalAccountTotal = 0;
|
|
|
|
|
this.abnormalAccountList = [];
|
|
|
|
|
const rows = Array.isArray(value && value.suspiciousTransactionList)
|
|
|
|
|
? value.suspiciousTransactionList
|
|
|
|
|
: [];
|
|
|
|
|
this.employeeCreditNegativeList = Array.isArray(value && value.employeeCreditNegativeList)
|
|
|
|
|
? value.employeeCreditNegativeList
|
|
|
|
|
: [];
|
|
|
|
|
this.hydrateSuspiciousRows(rows);
|
|
|
|
|
if (projectChanged) {
|
|
|
|
|
this.suspiciousRequestSeq += 1;
|
|
|
|
|
this.detailRequestSeq += 1;
|
|
|
|
|
this.currentSuspiciousType = normalizeSuspiciousType(value && value.suspiciousType);
|
|
|
|
|
this.selectedModelCode = "";
|
|
|
|
|
this.suspiciousPageNum = 1;
|
|
|
|
|
this.suspiciousPageSize = DEFAULT_RISK_DETAIL_PAGE_SIZE;
|
|
|
|
|
this.suspiciousTotal = Number(value && value.total) || 0;
|
|
|
|
|
this.suspiciousTransactionList = normalizeSuspiciousRows(
|
|
|
|
|
value && value.suspiciousTransactionList
|
|
|
|
|
);
|
|
|
|
|
this.suspiciousLoading = false;
|
|
|
|
|
this.statementDetailCache = {};
|
|
|
|
|
this.closeDetailDialog();
|
|
|
|
|
}
|
|
|
|
|
this.loadAbnormalAccountPeople();
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
methods: {
|
|
|
|
|
buildFlowEvidenceFingerprint,
|
|
|
|
|
handleSuspiciousModelSelectChange() {
|
|
|
|
|
this.resetSuspiciousFilterResults();
|
|
|
|
|
},
|
|
|
|
|
handleSuspiciousTypeSelectChange(value) {
|
|
|
|
|
if (value === "EXTERNAL_PERSON") {
|
|
|
|
|
const hasExternalModel = EXTERNAL_RISK_MODEL_OPTIONS
|
|
|
|
|
@@ -610,11 +632,20 @@ export default {
|
|
|
|
|
if (!hasExternalModel) {
|
|
|
|
|
this.selectedModelCode = "";
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (value === "MODEL_RULE" && isExternalModelCode(this.selectedModelCode)) {
|
|
|
|
|
} else if (value === "MODEL_RULE" && isExternalModelCode(this.selectedModelCode)) {
|
|
|
|
|
this.selectedModelCode = "";
|
|
|
|
|
}
|
|
|
|
|
this.resetSuspiciousFilterResults();
|
|
|
|
|
},
|
|
|
|
|
resetSuspiciousFilterResults() {
|
|
|
|
|
this.suspiciousRequestSeq += 1;
|
|
|
|
|
this.detailRequestSeq += 1;
|
|
|
|
|
this.suspiciousPageNum = 1;
|
|
|
|
|
this.suspiciousTotal = 0;
|
|
|
|
|
this.suspiciousTransactionList = [];
|
|
|
|
|
this.suspiciousLoading = false;
|
|
|
|
|
this.statementDetailCache = {};
|
|
|
|
|
this.closeDetailDialog();
|
|
|
|
|
},
|
|
|
|
|
async handleSuspiciousQuery() {
|
|
|
|
|
this.suspiciousPageNum = 1;
|
|
|
|
|
@@ -663,6 +694,7 @@ export default {
|
|
|
|
|
await this.loadAbnormalAccountPeople();
|
|
|
|
|
},
|
|
|
|
|
async loadSuspiciousTransactions() {
|
|
|
|
|
const requestSeq = ++this.suspiciousRequestSeq;
|
|
|
|
|
if (!this.projectId) {
|
|
|
|
|
this.suspiciousTransactionList = [];
|
|
|
|
|
this.suspiciousTotal = 0;
|
|
|
|
|
@@ -670,24 +702,34 @@ export default {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const query = {
|
|
|
|
|
projectId: this.projectId,
|
|
|
|
|
modelCode: this.selectedModelCode,
|
|
|
|
|
suspiciousType: this.currentSuspiciousType,
|
|
|
|
|
pageNum: this.suspiciousPageNum,
|
|
|
|
|
pageSize: this.suspiciousPageSize,
|
|
|
|
|
};
|
|
|
|
|
this.suspiciousLoading = true;
|
|
|
|
|
try {
|
|
|
|
|
const response = await getOverviewSuspiciousTransactions({
|
|
|
|
|
projectId: this.projectId,
|
|
|
|
|
modelCode: this.selectedModelCode,
|
|
|
|
|
suspiciousType: this.currentSuspiciousType,
|
|
|
|
|
pageNum: this.suspiciousPageNum,
|
|
|
|
|
pageSize: this.suspiciousPageSize,
|
|
|
|
|
});
|
|
|
|
|
const response = await getOverviewSuspiciousTransactions(query);
|
|
|
|
|
if (requestSeq !== this.suspiciousRequestSeq) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const data = (response && response.data) || {};
|
|
|
|
|
this.suspiciousTotal = Number(data.total) || 0;
|
|
|
|
|
await this.hydrateSuspiciousRows(Array.isArray(data.rows) ? data.rows : []);
|
|
|
|
|
this.suspiciousTransactionList = normalizeSuspiciousRows(data.rows);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if (requestSeq !== this.suspiciousRequestSeq) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this.suspiciousTransactionList = [];
|
|
|
|
|
this.suspiciousTotal = 0;
|
|
|
|
|
this.$message.error("加载涉疑交易明细失败");
|
|
|
|
|
console.error("加载涉疑交易明细失败", error);
|
|
|
|
|
this.suspiciousLoading = false;
|
|
|
|
|
} finally {
|
|
|
|
|
if (requestSeq === this.suspiciousRequestSeq) {
|
|
|
|
|
this.suspiciousLoading = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
async loadEmployeeCreditNegative() {
|
|
|
|
|
@@ -744,57 +786,25 @@ export default {
|
|
|
|
|
this.abnormalAccountLoading = false;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
async hydrateSuspiciousRows(rows) {
|
|
|
|
|
const safeRows = Array.isArray(rows) ? rows : [];
|
|
|
|
|
if (!safeRows.length) {
|
|
|
|
|
this.suspiciousTransactionList = [];
|
|
|
|
|
this.suspiciousLoading = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.suspiciousLoading = true;
|
|
|
|
|
try {
|
|
|
|
|
const enrichedRows = await Promise.all(
|
|
|
|
|
safeRows.map(async (row) => {
|
|
|
|
|
if (!row || !row.bankStatementId) {
|
|
|
|
|
return {
|
|
|
|
|
...row,
|
|
|
|
|
hitTags: [],
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
const detail = await this.fetchStatementDetail(row.bankStatementId, true);
|
|
|
|
|
return {
|
|
|
|
|
...detail,
|
|
|
|
|
...row,
|
|
|
|
|
hitTags: normalizeHitTags(detail.hitTags, row),
|
|
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
return {
|
|
|
|
|
...row,
|
|
|
|
|
hitTags: [],
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
this.suspiciousTransactionList = enrichedRows;
|
|
|
|
|
} finally {
|
|
|
|
|
this.suspiciousLoading = false;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
async fetchStatementDetail(bankStatementId, silent) {
|
|
|
|
|
async fetchStatementDetail(bankStatementId, silent, scope = {}) {
|
|
|
|
|
if (!bankStatementId) {
|
|
|
|
|
return createEmptyDetailData();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.statementDetailCache[bankStatementId]) {
|
|
|
|
|
return this.statementDetailCache[bankStatementId];
|
|
|
|
|
const modelCode = scope.modelCode !== undefined ? scope.modelCode : this.selectedModelCode;
|
|
|
|
|
const suspiciousType = scope.suspiciousType || this.currentSuspiciousType;
|
|
|
|
|
const cacheKey = `${bankStatementId}|${suspiciousType || "ALL"}|${modelCode || "ALL"}`;
|
|
|
|
|
if (this.statementDetailCache[cacheKey]) {
|
|
|
|
|
return this.statementDetailCache[cacheKey];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const response = await getBankStatementDetail(bankStatementId);
|
|
|
|
|
const response = await getBankStatementDetail(bankStatementId, {
|
|
|
|
|
modelCode,
|
|
|
|
|
suspiciousType,
|
|
|
|
|
});
|
|
|
|
|
const detail = normalizeDetailData(response && response.data);
|
|
|
|
|
this.$set(this.statementDetailCache, bankStatementId, detail);
|
|
|
|
|
this.$set(this.statementDetailCache, cacheKey, detail);
|
|
|
|
|
return detail;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if (!silent) {
|
|
|
|
|
@@ -808,10 +818,18 @@ export default {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const requestSeq = ++this.detailRequestSeq;
|
|
|
|
|
const scope = {
|
|
|
|
|
modelCode: this.selectedModelCode,
|
|
|
|
|
suspiciousType: this.currentSuspiciousType,
|
|
|
|
|
};
|
|
|
|
|
this.detailVisible = true;
|
|
|
|
|
this.detailLoading = true;
|
|
|
|
|
try {
|
|
|
|
|
const detail = await this.fetchStatementDetail(row.bankStatementId, false);
|
|
|
|
|
const detail = await this.fetchStatementDetail(row.bankStatementId, false, scope);
|
|
|
|
|
if (requestSeq !== this.detailRequestSeq) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this.detailData = {
|
|
|
|
|
...detail,
|
|
|
|
|
...row,
|
|
|
|
|
@@ -820,11 +838,16 @@ export default {
|
|
|
|
|
: normalizeHitTags(detail.hitTags, row),
|
|
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if (requestSeq !== this.detailRequestSeq) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this.detailData = createEmptyDetailData();
|
|
|
|
|
this.$message.error("加载流水详情失败");
|
|
|
|
|
console.error("加载流水详情失败", error);
|
|
|
|
|
} finally {
|
|
|
|
|
this.detailLoading = false;
|
|
|
|
|
if (requestSeq === this.detailRequestSeq) {
|
|
|
|
|
this.detailLoading = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
closeDetailDialog() {
|
|
|
|
|
@@ -865,6 +888,8 @@ export default {
|
|
|
|
|
"ccdi/project/overview/risk-details/export",
|
|
|
|
|
{
|
|
|
|
|
projectId: this.projectId,
|
|
|
|
|
modelCode: this.selectedModelCode,
|
|
|
|
|
suspiciousType: this.currentSuspiciousType,
|
|
|
|
|
},
|
|
|
|
|
`风险明细_${this.projectId}_${new Date().getTime()}.xlsx`
|
|
|
|
|
);
|
|
|
|
|
|