新增项目证据库一期功能
This commit is contained in:
27
ruoyi-ui/src/api/ccdiEvidence.js
Normal file
27
ruoyi-ui/src/api/ccdiEvidence.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 保存证据
|
||||
export function saveEvidence(data) {
|
||||
return request({
|
||||
url: '/ccdi/evidence',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 查询项目证据列表
|
||||
export function listEvidence(params) {
|
||||
return request({
|
||||
url: '/ccdi/evidence/list',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
// 查询证据详情
|
||||
export function getEvidence(evidenceId) {
|
||||
return request({
|
||||
url: '/ccdi/evidence/' + evidenceId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
78
ruoyi-ui/src/utils/ccdiEvidence.js
Normal file
78
ruoyi-ui/src/utils/ccdiEvidence.js
Normal file
@@ -0,0 +1,78 @@
|
||||
import md5 from "@/utils/md5";
|
||||
|
||||
export const FLOW_EVIDENCE_FINGERPRINT_RULE =
|
||||
"md5(leAccountNo+leAccountName+customerAccountNo+customerAccountName+trxDate+displayAmount+userMemo)";
|
||||
|
||||
export const MODEL_EVIDENCE_FINGERPRINT_RULE = "md5(personIdCard+modelCode)";
|
||||
|
||||
export const ASSET_EVIDENCE_FINGERPRINT_RULE =
|
||||
"md5(staffIdCard+totalIncome+totalDebt+totalAsset+riskLevelCode)";
|
||||
|
||||
function normalizeFingerprintValue(value) {
|
||||
if (value === null || value === undefined) {
|
||||
return "";
|
||||
}
|
||||
return String(value).trim();
|
||||
}
|
||||
|
||||
function resolveCounterpartyName(detail) {
|
||||
return detail.customerAccountName || detail.customerName || detail.counterpartyName || "";
|
||||
}
|
||||
|
||||
export function buildFlowEvidenceFingerprintSource(detail = {}) {
|
||||
return [
|
||||
detail.leAccountNo,
|
||||
detail.leAccountName,
|
||||
detail.customerAccountNo,
|
||||
resolveCounterpartyName(detail),
|
||||
detail.trxDate,
|
||||
detail.displayAmount,
|
||||
detail.userMemo,
|
||||
]
|
||||
.map(normalizeFingerprintValue)
|
||||
.join("");
|
||||
}
|
||||
|
||||
export function buildFlowEvidenceFingerprint(detail = {}) {
|
||||
const source = buildFlowEvidenceFingerprintSource(detail);
|
||||
return source ? md5(source) : "";
|
||||
}
|
||||
|
||||
export function buildFlowEvidenceSnapshot(detail = {}) {
|
||||
const evidenceFingerprint = buildFlowEvidenceFingerprint(detail);
|
||||
return {
|
||||
...detail,
|
||||
evidenceFingerprint,
|
||||
evidenceFingerprintRule: FLOW_EVIDENCE_FINGERPRINT_RULE,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildModelEvidenceFingerprint(personIdCard, modelCode) {
|
||||
const idCard = normalizeFingerprintValue(personIdCard);
|
||||
const code = normalizeFingerprintValue(modelCode);
|
||||
return idCard && code ? md5(idCard + code) : "";
|
||||
}
|
||||
|
||||
export function buildAssetEvidenceFingerprint(row = {}) {
|
||||
const idCard = normalizeFingerprintValue(row.staffIdCard);
|
||||
const assetSource = [
|
||||
row.totalIncome,
|
||||
row.totalDebt,
|
||||
row.totalAsset,
|
||||
row.riskLevelCode,
|
||||
]
|
||||
.map(normalizeFingerprintValue)
|
||||
.join("");
|
||||
return idCard && assetSource ? md5(idCard + assetSource) : "";
|
||||
}
|
||||
|
||||
export function buildAssetEvidenceSnapshot(row = {}, detail = {}, summary = {}) {
|
||||
const evidenceFingerprint = buildAssetEvidenceFingerprint(row);
|
||||
return {
|
||||
row,
|
||||
detail,
|
||||
summary,
|
||||
evidenceFingerprint,
|
||||
evidenceFingerprintRule: ASSET_EVIDENCE_FINGERPRINT_RULE,
|
||||
};
|
||||
}
|
||||
161
ruoyi-ui/src/utils/md5.js
Normal file
161
ruoyi-ui/src/utils/md5.js
Normal file
@@ -0,0 +1,161 @@
|
||||
function safeAdd(x, y) {
|
||||
const lsw = (x & 0xffff) + (y & 0xffff);
|
||||
const msw = (x >> 16) + (y >> 16) + (lsw >> 16);
|
||||
return (msw << 16) | (lsw & 0xffff);
|
||||
}
|
||||
|
||||
function rotateLeft(num, cnt) {
|
||||
return (num << cnt) | (num >>> (32 - cnt));
|
||||
}
|
||||
|
||||
function cmn(q, a, b, x, s, t) {
|
||||
return safeAdd(rotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b);
|
||||
}
|
||||
|
||||
function ff(a, b, c, d, x, s, t) {
|
||||
return cmn((b & c) | (~b & d), a, b, x, s, t);
|
||||
}
|
||||
|
||||
function gg(a, b, c, d, x, s, t) {
|
||||
return cmn((b & d) | (c & ~d), a, b, x, s, t);
|
||||
}
|
||||
|
||||
function hh(a, b, c, d, x, s, t) {
|
||||
return cmn(b ^ c ^ d, a, b, x, s, t);
|
||||
}
|
||||
|
||||
function ii(a, b, c, d, x, s, t) {
|
||||
return cmn(c ^ (b | ~d), a, b, x, s, t);
|
||||
}
|
||||
|
||||
function wordsToRaw(input) {
|
||||
let output = "";
|
||||
for (let i = 0; i < input.length * 32; i += 8) {
|
||||
output += String.fromCharCode((input[i >> 5] >>> (i % 32)) & 0xff);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
function rawToWords(input) {
|
||||
const output = [];
|
||||
output[(input.length >> 2) - 1] = undefined;
|
||||
for (let i = 0; i < output.length; i++) {
|
||||
output[i] = 0;
|
||||
}
|
||||
for (let i = 0; i < input.length * 8; i += 8) {
|
||||
output[i >> 5] |= (input.charCodeAt(i / 8) & 0xff) << (i % 32);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
function calculate(words, len) {
|
||||
words[len >> 5] |= 0x80 << (len % 32);
|
||||
words[(((len + 64) >>> 9) << 4) + 14] = len;
|
||||
|
||||
let a = 1732584193;
|
||||
let b = -271733879;
|
||||
let c = -1732584194;
|
||||
let d = 271733878;
|
||||
|
||||
for (let i = 0; i < words.length; i += 16) {
|
||||
const olda = a;
|
||||
const oldb = b;
|
||||
const oldc = c;
|
||||
const oldd = d;
|
||||
|
||||
a = ff(a, b, c, d, words[i], 7, -680876936);
|
||||
d = ff(d, a, b, c, words[i + 1], 12, -389564586);
|
||||
c = ff(c, d, a, b, words[i + 2], 17, 606105819);
|
||||
b = ff(b, c, d, a, words[i + 3], 22, -1044525330);
|
||||
a = ff(a, b, c, d, words[i + 4], 7, -176418897);
|
||||
d = ff(d, a, b, c, words[i + 5], 12, 1200080426);
|
||||
c = ff(c, d, a, b, words[i + 6], 17, -1473231341);
|
||||
b = ff(b, c, d, a, words[i + 7], 22, -45705983);
|
||||
a = ff(a, b, c, d, words[i + 8], 7, 1770035416);
|
||||
d = ff(d, a, b, c, words[i + 9], 12, -1958414417);
|
||||
c = ff(c, d, a, b, words[i + 10], 17, -42063);
|
||||
b = ff(b, c, d, a, words[i + 11], 22, -1990404162);
|
||||
a = ff(a, b, c, d, words[i + 12], 7, 1804603682);
|
||||
d = ff(d, a, b, c, words[i + 13], 12, -40341101);
|
||||
c = ff(c, d, a, b, words[i + 14], 17, -1502002290);
|
||||
b = ff(b, c, d, a, words[i + 15], 22, 1236535329);
|
||||
|
||||
a = gg(a, b, c, d, words[i + 1], 5, -165796510);
|
||||
d = gg(d, a, b, c, words[i + 6], 9, -1069501632);
|
||||
c = gg(c, d, a, b, words[i + 11], 14, 643717713);
|
||||
b = gg(b, c, d, a, words[i], 20, -373897302);
|
||||
a = gg(a, b, c, d, words[i + 5], 5, -701558691);
|
||||
d = gg(d, a, b, c, words[i + 10], 9, 38016083);
|
||||
c = gg(c, d, a, b, words[i + 15], 14, -660478335);
|
||||
b = gg(b, c, d, a, words[i + 4], 20, -405537848);
|
||||
a = gg(a, b, c, d, words[i + 9], 5, 568446438);
|
||||
d = gg(d, a, b, c, words[i + 14], 9, -1019803690);
|
||||
c = gg(c, d, a, b, words[i + 3], 14, -187363961);
|
||||
b = gg(b, c, d, a, words[i + 8], 20, 1163531501);
|
||||
a = gg(a, b, c, d, words[i + 13], 5, -1444681467);
|
||||
d = gg(d, a, b, c, words[i + 2], 9, -51403784);
|
||||
c = gg(c, d, a, b, words[i + 7], 14, 1735328473);
|
||||
b = gg(b, c, d, a, words[i + 12], 20, -1926607734);
|
||||
|
||||
a = hh(a, b, c, d, words[i + 5], 4, -378558);
|
||||
d = hh(d, a, b, c, words[i + 8], 11, -2022574463);
|
||||
c = hh(c, d, a, b, words[i + 11], 16, 1839030562);
|
||||
b = hh(b, c, d, a, words[i + 14], 23, -35309556);
|
||||
a = hh(a, b, c, d, words[i + 1], 4, -1530992060);
|
||||
d = hh(d, a, b, c, words[i + 4], 11, 1272893353);
|
||||
c = hh(c, d, a, b, words[i + 7], 16, -155497632);
|
||||
b = hh(b, c, d, a, words[i + 10], 23, -1094730640);
|
||||
a = hh(a, b, c, d, words[i + 13], 4, 681279174);
|
||||
d = hh(d, a, b, c, words[i], 11, -358537222);
|
||||
c = hh(c, d, a, b, words[i + 3], 16, -722521979);
|
||||
b = hh(b, c, d, a, words[i + 6], 23, 76029189);
|
||||
a = hh(a, b, c, d, words[i + 9], 4, -640364487);
|
||||
d = hh(d, a, b, c, words[i + 12], 11, -421815835);
|
||||
c = hh(c, d, a, b, words[i + 15], 16, 530742520);
|
||||
b = hh(b, c, d, a, words[i + 2], 23, -995338651);
|
||||
|
||||
a = ii(a, b, c, d, words[i], 6, -198630844);
|
||||
d = ii(d, a, b, c, words[i + 7], 10, 1126891415);
|
||||
c = ii(c, d, a, b, words[i + 14], 15, -1416354905);
|
||||
b = ii(b, c, d, a, words[i + 5], 21, -57434055);
|
||||
a = ii(a, b, c, d, words[i + 12], 6, 1700485571);
|
||||
d = ii(d, a, b, c, words[i + 3], 10, -1894986606);
|
||||
c = ii(c, d, a, b, words[i + 10], 15, -1051523);
|
||||
b = ii(b, c, d, a, words[i + 1], 21, -2054922799);
|
||||
a = ii(a, b, c, d, words[i + 8], 6, 1873313359);
|
||||
d = ii(d, a, b, c, words[i + 15], 10, -30611744);
|
||||
c = ii(c, d, a, b, words[i + 6], 15, -1560198380);
|
||||
b = ii(b, c, d, a, words[i + 13], 21, 1309151649);
|
||||
a = ii(a, b, c, d, words[i + 4], 6, -145523070);
|
||||
d = ii(d, a, b, c, words[i + 11], 10, -1120210379);
|
||||
c = ii(c, d, a, b, words[i + 2], 15, 718787259);
|
||||
b = ii(b, c, d, a, words[i + 9], 21, -343485551);
|
||||
|
||||
a = safeAdd(a, olda);
|
||||
b = safeAdd(b, oldb);
|
||||
c = safeAdd(c, oldc);
|
||||
d = safeAdd(d, oldd);
|
||||
}
|
||||
|
||||
return [a, b, c, d];
|
||||
}
|
||||
|
||||
function rawToHex(input) {
|
||||
const hex = "0123456789abcdef";
|
||||
let output = "";
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
const x = input.charCodeAt(i);
|
||||
output += hex.charAt((x >>> 4) & 0x0f) + hex.charAt(x & 0x0f);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
function toUtf8Raw(input) {
|
||||
return unescape(encodeURIComponent(input));
|
||||
}
|
||||
|
||||
export default function md5(input) {
|
||||
const value = input === null || input === undefined ? "" : String(input);
|
||||
const raw = toUtf8Raw(value);
|
||||
return rawToHex(wordsToRaw(calculate(rawToWords(raw), raw.length * 8)));
|
||||
}
|
||||
@@ -283,10 +283,23 @@
|
||||
:visible.sync="detailVisible"
|
||||
append-to-body
|
||||
custom-class="detail-dialog"
|
||||
title="流水详情"
|
||||
width="980px"
|
||||
@close="closeDetailDialog"
|
||||
>
|
||||
<template slot="title">
|
||||
<div class="detail-dialog-title">
|
||||
<span>流水详情</span>
|
||||
<el-button
|
||||
class="evidence-corner-btn"
|
||||
size="mini"
|
||||
plain
|
||||
:disabled="detailLoading || !buildFlowEvidenceFingerprint(detailData)"
|
||||
@click="handleAddEvidence"
|
||||
>
|
||||
加入证据库
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<div v-loading="detailLoading" class="detail-dialog-body">
|
||||
<div class="detail-overview-grid">
|
||||
<div class="detail-field">
|
||||
@@ -394,6 +407,7 @@ import {
|
||||
getBankStatementOptions,
|
||||
getBankStatementDetail,
|
||||
} from "@/api/ccdiProjectBankStatement";
|
||||
import { buildFlowEvidenceFingerprint, buildFlowEvidenceSnapshot } from "@/utils/ccdiEvidence";
|
||||
|
||||
const TAB_MAP = {
|
||||
all: "all",
|
||||
@@ -518,6 +532,7 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
buildFlowEvidenceFingerprint,
|
||||
async getList() {
|
||||
this.syncProjectId();
|
||||
if (!this.queryParams.projectId) {
|
||||
@@ -638,6 +653,29 @@ export default {
|
||||
this.detailLoading = false;
|
||||
this.detailData = createEmptyDetailData();
|
||||
},
|
||||
handleAddEvidence() {
|
||||
const detail = this.detailData || {};
|
||||
const sourceRecordId = buildFlowEvidenceFingerprint(detail);
|
||||
const amountText = this.formatSignedAmount(detail.displayAmount);
|
||||
const counterparty = this.formatCounterpartyName(detail);
|
||||
const hitTagText = Array.isArray(detail.hitTags) && detail.hitTags.length
|
||||
? `,命中${detail.hitTags.map((tag) => tag.ruleName).filter(Boolean).join("、")}标签`
|
||||
: "";
|
||||
this.$emit("evidence-confirm", {
|
||||
evidenceType: "FLOW",
|
||||
relatedPersonName: this.resolveFlowRelatedPerson(detail),
|
||||
relatedPersonId: detail.cretNo || "",
|
||||
evidenceTitle: `${this.resolveFlowRelatedPerson(detail)} / ${this.formatField(detail.leAccountNo)}`,
|
||||
evidenceSummary: `${this.formatField(detail.trxDate)},${this.resolveFlowRelatedPerson(detail)}账户与${counterparty}发生交易,金额${amountText}${hitTagText}。`,
|
||||
sourceType: "BANK_STATEMENT",
|
||||
sourceRecordId,
|
||||
sourcePage: "流水详情",
|
||||
snapshotJson: JSON.stringify(buildFlowEvidenceSnapshot(detail)),
|
||||
});
|
||||
},
|
||||
resolveFlowRelatedPerson(detail) {
|
||||
return this.formatField(detail.leAccountName) === "-" ? "关联人员" : this.formatField(detail.leAccountName);
|
||||
},
|
||||
handleExport() {
|
||||
if (this.total === 0) {
|
||||
return;
|
||||
@@ -751,6 +789,26 @@ export default {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.detail-dialog-title {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.evidence-corner-btn {
|
||||
padding: 4px 9px;
|
||||
font-size: 12px;
|
||||
color: #5b7fb8;
|
||||
border-color: #d6e4f7;
|
||||
background: #f8fbff;
|
||||
|
||||
&:hover {
|
||||
color: #2474e8;
|
||||
border-color: #9fc3ff;
|
||||
background: #edf5ff;
|
||||
}
|
||||
}
|
||||
|
||||
.shell-sidebar,
|
||||
.shell-main {
|
||||
border: 1px solid #ebeef5;
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:visible.sync="dialogVisible"
|
||||
append-to-body
|
||||
title="确认为证据"
|
||||
width="560px"
|
||||
:close-on-click-modal="false"
|
||||
@close="handleClose"
|
||||
>
|
||||
<el-form label-position="top" class="evidence-confirm-form">
|
||||
<el-form-item label="证据类型">
|
||||
<el-input :value="typeLabel" readonly />
|
||||
</el-form-item>
|
||||
<el-form-item label="关联人员">
|
||||
<el-input :value="payload.relatedPersonName || '-'" readonly />
|
||||
</el-form-item>
|
||||
<el-form-item label="证据摘要">
|
||||
<el-input
|
||||
:value="payload.evidenceSummary || '-'"
|
||||
readonly
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="确认理由/备注" required>
|
||||
<el-input
|
||||
v-model.trim="confirmReason"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
placeholder="请填写为什么将该详情确认为证据"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div slot="footer">
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="handleSubmit">
|
||||
确认入库
|
||||
</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { saveEvidence } from "@/api/ccdiEvidence";
|
||||
|
||||
const TYPE_LABEL_MAP = {
|
||||
FLOW: "流水证据",
|
||||
MODEL: "模型证据",
|
||||
ASSET: "资产证据",
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "EvidenceConfirmDialog",
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
payload: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
confirmReason: "",
|
||||
submitting: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
dialogVisible: {
|
||||
get() {
|
||||
return this.visible;
|
||||
},
|
||||
set(value) {
|
||||
if (!value) {
|
||||
this.handleClose();
|
||||
}
|
||||
},
|
||||
},
|
||||
typeLabel() {
|
||||
return TYPE_LABEL_MAP[this.payload.evidenceType] || this.payload.evidenceType || "-";
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
visible(value) {
|
||||
if (value) {
|
||||
this.confirmReason = "";
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleClose() {
|
||||
if (this.submitting) {
|
||||
return;
|
||||
}
|
||||
this.$emit("update:visible", false);
|
||||
},
|
||||
async handleSubmit() {
|
||||
if (!this.confirmReason) {
|
||||
this.$message.warning("请填写确认理由/备注");
|
||||
return;
|
||||
}
|
||||
this.submitting = true;
|
||||
try {
|
||||
const data = {
|
||||
...this.payload,
|
||||
confirmReason: this.confirmReason,
|
||||
};
|
||||
const response = await saveEvidence(data);
|
||||
this.$message.success("证据入库成功");
|
||||
this.$emit("saved", response.data);
|
||||
this.$emit("update:visible", false);
|
||||
} catch (error) {
|
||||
this.$message.error("证据入库失败");
|
||||
console.error("证据入库失败", error);
|
||||
} finally {
|
||||
this.submitting = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.evidence-confirm-form {
|
||||
:deep(.el-form-item__label) {
|
||||
padding-bottom: 6px;
|
||||
font-weight: 600;
|
||||
color: #606266;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<el-drawer
|
||||
:visible.sync="drawerVisible"
|
||||
append-to-body
|
||||
title="证据线索"
|
||||
size="420px"
|
||||
custom-class="evidence-drawer"
|
||||
@open="loadEvidence"
|
||||
>
|
||||
<div class="evidence-drawer-body">
|
||||
<el-input
|
||||
v-model.trim="keyword"
|
||||
clearable
|
||||
size="small"
|
||||
prefix-icon="el-icon-search"
|
||||
placeholder="搜索姓名、账号、证据编号"
|
||||
@keyup.enter.native="loadEvidence"
|
||||
@clear="loadEvidence"
|
||||
>
|
||||
<el-button slot="append" @click="loadEvidence">查询</el-button>
|
||||
</el-input>
|
||||
|
||||
<div class="evidence-list" v-loading="loading">
|
||||
<el-empty
|
||||
v-if="!loading && evidenceList.length === 0"
|
||||
:image-size="80"
|
||||
description="暂无证据线索"
|
||||
/>
|
||||
<article
|
||||
v-for="item in evidenceList"
|
||||
:key="item.evidenceId"
|
||||
class="evidence-card"
|
||||
>
|
||||
<div class="evidence-card__header">
|
||||
<span class="evidence-code">EV-{{ formatEvidenceId(item.evidenceId) }} {{ formatType(item.evidenceType) }}</span>
|
||||
<el-tag size="mini" type="danger" effect="plain">确认可疑</el-tag>
|
||||
</div>
|
||||
<div class="evidence-title">{{ item.evidenceTitle }}</div>
|
||||
<div class="evidence-summary">{{ item.evidenceSummary }}</div>
|
||||
<div class="evidence-meta">
|
||||
来源:{{ item.sourcePage || formatSource(item.sourceType) }};确认人:{{ item.confirmBy || "-" }}
|
||||
</div>
|
||||
<div v-if="item.confirmReason" class="evidence-meta">
|
||||
备注:{{ item.confirmReason }}
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listEvidence } from "@/api/ccdiEvidence";
|
||||
|
||||
const TYPE_LABEL_MAP = {
|
||||
FLOW: "流水证据",
|
||||
MODEL: "模型证据",
|
||||
ASSET: "资产证据",
|
||||
};
|
||||
|
||||
const SOURCE_LABEL_MAP = {
|
||||
BANK_STATEMENT: "流水详情",
|
||||
MODEL_DETAIL: "模型详情",
|
||||
ASSET_DETAIL: "资产详情",
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "EvidenceDrawer",
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
projectId: {
|
||||
type: [String, Number],
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
keyword: "",
|
||||
loading: false,
|
||||
evidenceList: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
drawerVisible: {
|
||||
get() {
|
||||
return this.visible;
|
||||
},
|
||||
set(value) {
|
||||
this.$emit("update:visible", value);
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async loadEvidence() {
|
||||
if (!this.projectId) {
|
||||
this.evidenceList = [];
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
try {
|
||||
const response = await listEvidence({
|
||||
projectId: this.projectId,
|
||||
keyword: this.keyword,
|
||||
});
|
||||
this.evidenceList = response.data || [];
|
||||
} catch (error) {
|
||||
this.evidenceList = [];
|
||||
this.$message.error("加载证据线索失败");
|
||||
console.error("加载证据线索失败", error);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
formatEvidenceId(value) {
|
||||
return String(value || "").padStart(3, "0");
|
||||
},
|
||||
formatType(type) {
|
||||
return TYPE_LABEL_MAP[type] || type || "证据";
|
||||
},
|
||||
formatSource(sourceType) {
|
||||
return SOURCE_LABEL_MAP[sourceType] || sourceType || "-";
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.evidence-drawer-body {
|
||||
padding: 0 16px 16px;
|
||||
}
|
||||
|
||||
.evidence-list {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.evidence-card {
|
||||
padding: 14px;
|
||||
margin-bottom: 12px;
|
||||
border: 1px solid #e5eaf2;
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.evidence-card__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.evidence-code {
|
||||
color: #2474e8;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.evidence-title {
|
||||
margin-top: 8px;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.evidence-summary {
|
||||
margin-top: 8px;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
color: #475467;
|
||||
}
|
||||
|
||||
.evidence-meta {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: #8a96a8;
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
@@ -21,10 +21,24 @@
|
||||
</template>
|
||||
<el-table-column type="expand" width="1">
|
||||
<template slot-scope="scope">
|
||||
<family-asset-liability-detail
|
||||
:detail="detailCache[scope.row.staffIdCard]"
|
||||
:loading="Boolean(detailLoadingMap[scope.row.staffIdCard])"
|
||||
/>
|
||||
<div class="family-detail-wrapper">
|
||||
<div class="family-detail-toolbar">
|
||||
<span class="family-detail-title">资产详情</span>
|
||||
<el-button
|
||||
class="evidence-corner-btn"
|
||||
size="mini"
|
||||
plain
|
||||
:disabled="Boolean(detailLoadingMap[scope.row.staffIdCard]) || !buildAssetEvidenceFingerprint(scope.row)"
|
||||
@click="handleAddEvidence(scope.row)"
|
||||
>
|
||||
加入证据库
|
||||
</el-button>
|
||||
</div>
|
||||
<family-asset-liability-detail
|
||||
:detail="detailCache[scope.row.staffIdCard]"
|
||||
:loading="Boolean(detailLoadingMap[scope.row.staffIdCard])"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column type="index" label="序号" width="60" />
|
||||
@@ -67,6 +81,7 @@
|
||||
|
||||
<script>
|
||||
import { getFamilyAssetLiabilityDetail } from "@/api/ccdi/projectSpecialCheck";
|
||||
import { buildAssetEvidenceFingerprint, buildAssetEvidenceSnapshot } from "@/utils/ccdiEvidence";
|
||||
import FamilyAssetLiabilityDetail from "./FamilyAssetLiabilityDetail";
|
||||
|
||||
export default {
|
||||
@@ -114,6 +129,7 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
buildAssetEvidenceFingerprint,
|
||||
resolveRiskTagType(riskLevelCode) {
|
||||
const riskTagTypeMap = {
|
||||
NORMAL: "success",
|
||||
@@ -164,6 +180,30 @@ export default {
|
||||
this.detailCache = {};
|
||||
this.detailLoadingMap = {};
|
||||
},
|
||||
handleAddEvidence(row) {
|
||||
if (!row || !row.staffIdCard) {
|
||||
return;
|
||||
}
|
||||
const sourceRecordId = buildAssetEvidenceFingerprint(row);
|
||||
if (!sourceRecordId) {
|
||||
this.$message.warning("缺少人员身份证或资产字段,暂不能加入证据库");
|
||||
return;
|
||||
}
|
||||
const detail = this.detailCache[row.staffIdCard] || {};
|
||||
const summary = detail.summary || {};
|
||||
const evidenceSummary = `${row.staffName}家庭资产负债核查:家庭总年收入${this.formatAmount(row.totalIncome)},家庭总负债${this.formatAmount(row.totalDebt)},家庭总资产${this.formatAmount(row.totalAsset)},风险情况${row.riskLevelName || "-" }。`;
|
||||
this.$emit("evidence-confirm", {
|
||||
evidenceType: "ASSET",
|
||||
relatedPersonName: row.staffName || "关联人员",
|
||||
relatedPersonId: row.staffIdCard || "",
|
||||
evidenceTitle: `${row.staffName || "关联人员"} / 家庭资产负债核查`,
|
||||
evidenceSummary,
|
||||
sourceType: "ASSET_DETAIL",
|
||||
sourceRecordId,
|
||||
sourcePage: "资产详情",
|
||||
snapshotJson: JSON.stringify(buildAssetEvidenceSnapshot(row, detail, summary)),
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -204,6 +244,39 @@ export default {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.family-detail-wrapper {
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.family-detail-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.family-detail-title {
|
||||
margin-right: auto;
|
||||
color: #64748b;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.evidence-corner-btn {
|
||||
padding: 4px 9px;
|
||||
font-size: 12px;
|
||||
color: #5b7fb8;
|
||||
border-color: #d6e4f7;
|
||||
background: #f8fbff;
|
||||
|
||||
&:hover {
|
||||
color: #2474e8;
|
||||
border-color: #9fc3ff;
|
||||
background: #edf5ff;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.family-table th) {
|
||||
background: #f8fafc;
|
||||
color: #64748b;
|
||||
|
||||
@@ -33,7 +33,10 @@
|
||||
@selection-change="handleRiskModelSelectionChange"
|
||||
@view-project-analysis="handleRiskModelProjectAnalysis"
|
||||
/>
|
||||
<risk-detail-section :section-data="currentData.riskDetails" />
|
||||
<risk-detail-section
|
||||
:section-data="currentData.riskDetails"
|
||||
@evidence-confirm="$emit('evidence-confirm', $event)"
|
||||
/>
|
||||
</div>
|
||||
<project-analysis-dialog
|
||||
:visible.sync="projectAnalysisDialogVisible"
|
||||
@@ -43,6 +46,7 @@
|
||||
:model-summary="projectAnalysisModelSummary"
|
||||
:project-name="projectInfo.projectName"
|
||||
@close="handleProjectAnalysisDialogClose"
|
||||
@evidence-confirm="$emit('evidence-confirm', $event)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -75,15 +75,28 @@
|
||||
<div v-else-if='group.groupType === "OBJECT"' class="object-card-grid">
|
||||
<article
|
||||
v-for="(item, index) in group.records || []"
|
||||
:key="`${item.title || index}-object`"
|
||||
:key="`${resolveGroupKey(group, groupIndex)}-${item.title || 'object'}-${index}`"
|
||||
class="object-card"
|
||||
>
|
||||
<div class="object-card__title">{{ item.title || "-" }}</div>
|
||||
<div class="object-card__subtitle">{{ item.subtitle || "-" }}</div>
|
||||
<div class="object-card__header">
|
||||
<div>
|
||||
<div class="object-card__title">{{ item.title || "-" }}</div>
|
||||
<div class="object-card__subtitle">{{ item.subtitle || "-" }}</div>
|
||||
</div>
|
||||
<el-button
|
||||
class="evidence-corner-btn"
|
||||
size="mini"
|
||||
plain
|
||||
:disabled="!buildModelEvidenceFingerprint(resolvePersonIdCard(), item.modelCode)"
|
||||
@click="handleAddModelEvidence(item, group)"
|
||||
>
|
||||
加入证据库
|
||||
</el-button>
|
||||
</div>
|
||||
<div v-if="item.riskTags && item.riskTags.length" class="tag-list">
|
||||
<el-tag
|
||||
v-for="(tag, tagIndex) in item.riskTags"
|
||||
:key="`${item.title || index}-risk-${tagIndex}`"
|
||||
:key="`${resolveGroupKey(group, groupIndex)}-${item.title || index}-risk-${tagIndex}`"
|
||||
size="mini"
|
||||
effect="plain"
|
||||
>
|
||||
@@ -97,7 +110,7 @@
|
||||
<p class="object-card__summary">{{ item.summary || "-" }}</p>
|
||||
<div
|
||||
v-for="(field, fieldIndex) in item.extraFields || []"
|
||||
:key="`${item.title || index}-field-${fieldIndex}`"
|
||||
:key="`${resolveGroupKey(group, groupIndex)}-${item.title || index}-field-${fieldIndex}`"
|
||||
class="summary-row"
|
||||
>
|
||||
<span class="summary-row__label">{{ field.label }}</span>
|
||||
@@ -113,6 +126,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { buildModelEvidenceFingerprint, MODEL_EVIDENCE_FINGERPRINT_RULE } from "@/utils/ccdiEvidence";
|
||||
|
||||
export default {
|
||||
name: "ProjectAnalysisAbnormalTab",
|
||||
props: {
|
||||
@@ -122,6 +137,14 @@ export default {
|
||||
groups: [],
|
||||
}),
|
||||
},
|
||||
person: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
projectId: {
|
||||
type: [String, Number],
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -150,6 +173,7 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
buildModelEvidenceFingerprint,
|
||||
resolveGroupKey(group, index = 0) {
|
||||
return group.groupCode || group.groupName || `BANK_STATEMENT_${index}`;
|
||||
},
|
||||
@@ -170,6 +194,48 @@ export default {
|
||||
const groupKey = this.resolveGroupKey(group);
|
||||
this.$set(this.statementPageMap, groupKey, page);
|
||||
},
|
||||
handleAddModelEvidence(item, group) {
|
||||
const safeItem = item || {};
|
||||
const safeGroup = group || {};
|
||||
const relatedPersonName = this.resolveRelatedPersonName(safeItem);
|
||||
const personIdCard = this.resolvePersonIdCard();
|
||||
const sourceRecordId = buildModelEvidenceFingerprint(personIdCard, safeItem.modelCode);
|
||||
if (!sourceRecordId) {
|
||||
this.$message.warning("缺少人员身份证或模型编码,暂不能加入证据库");
|
||||
return;
|
||||
}
|
||||
const riskTags = Array.isArray(safeItem.riskTags) ? safeItem.riskTags.join("、") : "";
|
||||
const reason = safeItem.reasonDetail || safeItem.summary || "-";
|
||||
const payload = {
|
||||
evidenceType: "MODEL",
|
||||
relatedPersonName,
|
||||
relatedPersonId: personIdCard,
|
||||
evidenceTitle: `${relatedPersonName} / ${safeItem.title || safeGroup.groupName || "模型异常"}`,
|
||||
evidenceSummary: `${safeItem.title || safeGroup.groupName || "模型异常"}:${reason}`,
|
||||
sourceType: "MODEL_DETAIL",
|
||||
sourceRecordId,
|
||||
sourcePage: "模型详情",
|
||||
snapshotJson: JSON.stringify({
|
||||
group: safeGroup,
|
||||
item: safeItem,
|
||||
person: this.person,
|
||||
riskTags,
|
||||
evidenceFingerprint: sourceRecordId,
|
||||
evidenceFingerprintRule: MODEL_EVIDENCE_FINGERPRINT_RULE,
|
||||
}),
|
||||
};
|
||||
this.$emit("evidence-confirm", payload);
|
||||
this.$root.$emit("ccdi-evidence-confirm", payload);
|
||||
},
|
||||
resolvePersonIdCard() {
|
||||
return (this.person && (this.person.idNo || this.person.staffIdCard)) || "";
|
||||
},
|
||||
resolveRelatedPersonName(item) {
|
||||
if (this.person && (this.person.name || this.person.staffName)) {
|
||||
return this.person.name || this.person.staffName;
|
||||
}
|
||||
return item.title || "关联人员";
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -252,12 +318,35 @@ export default {
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.object-card__header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.object-card__title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.evidence-corner-btn {
|
||||
flex: 0 0 auto;
|
||||
padding: 4px 9px;
|
||||
font-size: 12px;
|
||||
color: #5b7fb8;
|
||||
border-color: #d6e4f7;
|
||||
background: #f8fbff;
|
||||
|
||||
&:hover {
|
||||
color: #2474e8;
|
||||
border-color: #9fc3ff;
|
||||
background: #edf5ff;
|
||||
}
|
||||
}
|
||||
|
||||
.object-card__subtitle {
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
|
||||
@@ -46,7 +46,12 @@
|
||||
</el-alert>
|
||||
<el-tabs v-model="activeTab" class="project-analysis-tabs" stretch>
|
||||
<el-tab-pane label="异常明细" name="abnormalDetail">
|
||||
<project-analysis-abnormal-tab :detail-data="dialogData.abnormalDetail" />
|
||||
<project-analysis-abnormal-tab
|
||||
:detail-data="dialogData.abnormalDetail"
|
||||
:person="person"
|
||||
:project-id="projectId"
|
||||
@evidence-confirm="$emit('evidence-confirm', $event)"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="资产分析" name="assetAnalysis">
|
||||
<project-analysis-placeholder-tab :tab-data="getTabData('assetAnalysis')" />
|
||||
|
||||
@@ -207,10 +207,23 @@
|
||||
:visible.sync="detailVisible"
|
||||
append-to-body
|
||||
custom-class="detail-dialog"
|
||||
title="流水详情"
|
||||
width="980px"
|
||||
@close="closeDetailDialog"
|
||||
>
|
||||
<template slot="title">
|
||||
<div class="detail-dialog-title">
|
||||
<span>流水详情</span>
|
||||
<el-button
|
||||
class="evidence-corner-btn"
|
||||
size="mini"
|
||||
plain
|
||||
:disabled="detailLoading || !buildFlowEvidenceFingerprint(detailData)"
|
||||
@click="handleAddEvidence"
|
||||
>
|
||||
加入证据库
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<div v-loading="detailLoading" class="detail-dialog-body">
|
||||
<div class="detail-overview-grid">
|
||||
<div class="detail-field">
|
||||
@@ -318,6 +331,7 @@ import {
|
||||
getOverviewSuspiciousTransactions,
|
||||
} from "@/api/ccdi/projectOverview";
|
||||
import { getBankStatementDetail } from "@/api/ccdiProjectBankStatement";
|
||||
import { buildFlowEvidenceFingerprint, buildFlowEvidenceSnapshot } from "@/utils/ccdiEvidence";
|
||||
|
||||
const SUSPICIOUS_TYPE_OPTIONS = [
|
||||
{ value: "ALL", label: "全部可疑人员类型" },
|
||||
@@ -428,6 +442,7 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
buildFlowEvidenceFingerprint,
|
||||
async handleSuspiciousTypeChange(command) {
|
||||
this.currentSuspiciousType = command;
|
||||
this.suspiciousPageNum = 1;
|
||||
@@ -586,6 +601,31 @@ export default {
|
||||
this.detailLoading = false;
|
||||
this.detailData = createEmptyDetailData();
|
||||
},
|
||||
handleAddEvidence() {
|
||||
const detail = this.detailData || {};
|
||||
const sourceRecordId = buildFlowEvidenceFingerprint(detail);
|
||||
const amountText = this.formatSignedAmount(detail.displayAmount);
|
||||
const counterparty = this.formatCounterpartyName(detail);
|
||||
const relatedPersonName = this.resolveFlowRelatedPerson(detail);
|
||||
const hitTagText = Array.isArray(detail.hitTags) && detail.hitTags.length
|
||||
? `,命中${detail.hitTags.map((tag) => tag.ruleName).filter(Boolean).join("、")}标签`
|
||||
: "";
|
||||
this.$emit("evidence-confirm", {
|
||||
evidenceType: "FLOW",
|
||||
relatedPersonName,
|
||||
relatedPersonId: detail.cretNo || "",
|
||||
evidenceTitle: `${relatedPersonName} / ${this.formatField(detail.leAccountNo)}`,
|
||||
evidenceSummary: `${this.formatField(detail.trxDate)},${relatedPersonName}账户与${counterparty}发生交易,金额${amountText}${hitTagText}。`,
|
||||
sourceType: "BANK_STATEMENT",
|
||||
sourceRecordId,
|
||||
sourcePage: "流水详情",
|
||||
snapshotJson: JSON.stringify(buildFlowEvidenceSnapshot(detail)),
|
||||
});
|
||||
},
|
||||
resolveFlowRelatedPerson(detail) {
|
||||
const value = this.formatField(detail.leAccountName);
|
||||
return value === "-" ? "关联人员" : value;
|
||||
},
|
||||
handleRiskDetailExport() {
|
||||
if (!this.projectId) {
|
||||
return;
|
||||
@@ -960,6 +1000,26 @@ export default {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.detail-dialog-title {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.evidence-corner-btn {
|
||||
padding: 4px 9px;
|
||||
font-size: 12px;
|
||||
color: #5b7fb8;
|
||||
border-color: #d6e4f7;
|
||||
background: #f8fbff;
|
||||
|
||||
&:hover {
|
||||
color: #2474e8;
|
||||
border-color: #9fc3ff;
|
||||
background: #edf5ff;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.detail-dialog) {
|
||||
border-radius: 8px;
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
:project-id="projectId"
|
||||
:title="sectionTitle"
|
||||
:subtitle="sectionSubtitle"
|
||||
@evidence-confirm="$emit('evidence-confirm', $event)"
|
||||
/>
|
||||
|
||||
<section class="graph-placeholder-card">
|
||||
|
||||
@@ -33,6 +33,15 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<el-button
|
||||
class="evidence-entry-btn"
|
||||
size="mini"
|
||||
plain
|
||||
icon="el-icon-collection-tag"
|
||||
@click="evidenceDrawerVisible = true"
|
||||
>
|
||||
证据线索
|
||||
</el-button>
|
||||
<el-menu
|
||||
:default-active="activeTab"
|
||||
mode="horizontal"
|
||||
@@ -59,6 +68,18 @@
|
||||
@name-selected="handleNameSelected"
|
||||
@generate-report="handleGenerateReport"
|
||||
@fetch-bank-info="handleFetchBankInfo"
|
||||
@evidence-confirm="handleEvidenceConfirm"
|
||||
/>
|
||||
|
||||
<evidence-confirm-dialog
|
||||
:visible.sync="evidenceConfirmVisible"
|
||||
:payload="evidencePayload"
|
||||
@saved="handleEvidenceSaved"
|
||||
/>
|
||||
<evidence-drawer
|
||||
ref="evidenceDrawer"
|
||||
:visible.sync="evidenceDrawerVisible"
|
||||
:project-id="projectId"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -69,6 +90,8 @@ import ParamConfig from "./components/detail/ParamConfig";
|
||||
import PreliminaryCheck from "./components/detail/PreliminaryCheck";
|
||||
import SpecialCheck from "./components/detail/SpecialCheck";
|
||||
import DetailQuery from "./components/detail/DetailQuery";
|
||||
import EvidenceConfirmDialog from "./components/detail/EvidenceConfirmDialog";
|
||||
import EvidenceDrawer from "./components/detail/EvidenceDrawer";
|
||||
import { getProject } from "@/api/ccdiProject";
|
||||
|
||||
export default {
|
||||
@@ -79,6 +102,8 @@ export default {
|
||||
PreliminaryCheck,
|
||||
SpecialCheck,
|
||||
DetailQuery,
|
||||
EvidenceConfirmDialog,
|
||||
EvidenceDrawer,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -102,6 +127,9 @@ export default {
|
||||
warningThreshold: 60,
|
||||
projectStatus: "0",
|
||||
},
|
||||
evidenceConfirmVisible: false,
|
||||
evidenceDrawerVisible: false,
|
||||
evidencePayload: {},
|
||||
projectStatusPollingTimer: null,
|
||||
projectStatusPollingInterval: 1000,
|
||||
projectStatusPollingLoading: false,
|
||||
@@ -139,8 +167,10 @@ export default {
|
||||
// 初始化页面数据
|
||||
this.initActiveTabFromRoute();
|
||||
this.initPageData();
|
||||
this.$root.$on("ccdi-evidence-confirm", this.handleEvidenceConfirm);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$root.$off("ccdi-evidence-confirm", this.handleEvidenceConfirm);
|
||||
this.stopProjectStatusPolling();
|
||||
},
|
||||
methods: {
|
||||
@@ -400,6 +430,21 @@ export default {
|
||||
handleRefreshProject() {
|
||||
this.initPageData();
|
||||
},
|
||||
handleEvidenceConfirm(payload) {
|
||||
this.evidencePayload = {
|
||||
projectId: this.projectId,
|
||||
...(payload || {}),
|
||||
};
|
||||
this.evidenceConfirmVisible = true;
|
||||
},
|
||||
handleEvidenceSaved() {
|
||||
this.evidenceDrawerVisible = true;
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.evidenceDrawer) {
|
||||
this.$refs.evidenceDrawer.loadEvidence();
|
||||
}
|
||||
});
|
||||
},
|
||||
/** 导出报告 */
|
||||
handleExport() {
|
||||
console.log("导出报告");
|
||||
@@ -496,6 +541,21 @@ export default {
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
.evidence-entry-btn {
|
||||
padding: 6px 10px;
|
||||
font-size: 12px;
|
||||
color: #5b7fb8;
|
||||
border-color: #d6e4f7;
|
||||
background: #f8fbff;
|
||||
|
||||
&:hover {
|
||||
color: var(--ccdi-primary);
|
||||
border-color: #9fc3ff;
|
||||
background: #edf5ff;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
// 移除默认背景色和边框
|
||||
|
||||
Reference in New Issue
Block a user