460 lines
13 KiB
Vue
460 lines
13 KiB
Vue
<template>
|
||
<section class="risk-people-section">
|
||
<div class="section-toolbar">
|
||
<el-tabs v-model="activeTab" class="people-tabs" @tab-click="handleTabChange">
|
||
<el-tab-pane label="员工风险人员" name="employee" />
|
||
<el-tab-pane v-if="hasExternalWarnings" :label="externalTabLabel" name="external" />
|
||
</el-tabs>
|
||
</div>
|
||
|
||
<el-table
|
||
v-if="activeTab === 'employee'"
|
||
v-loading="tableLoading"
|
||
:data="overviewList"
|
||
class="people-table"
|
||
>
|
||
<el-table-column type="index" label="序号" width="60" />
|
||
<el-table-column prop="name" label="姓名" min-width="100" />
|
||
<el-table-column prop="idNo" label="身份证号" min-width="180" />
|
||
<el-table-column prop="department" label="所属部门" min-width="140" />
|
||
<el-table-column prop="riskCount" label="疑似违规数" min-width="100" />
|
||
<el-table-column prop="riskLevel" label="风险等级" min-width="110">
|
||
<template slot-scope="scope">
|
||
<el-tag size="mini" :type="scope.row.riskLevelType" effect="plain">
|
||
{{ scope.row.riskLevel }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="modelCount" label="命中模型数" min-width="110" />
|
||
<el-table-column label="核心异常点" min-width="220">
|
||
<template slot-scope="scope">
|
||
<div
|
||
v-if="scope.row.riskPointTagList && scope.row.riskPointTagList.length"
|
||
class="risk-point-tag-list"
|
||
>
|
||
<el-tag
|
||
v-for="(tag, index) in scope.row.riskPointTagList"
|
||
:key="`${scope.row.idNo || scope.row.name || index}-risk-point-${index}`"
|
||
class="core-risk-tag"
|
||
:style="resolveModelTagStyle(tag)"
|
||
size="mini"
|
||
effect="plain"
|
||
>
|
||
{{ tag.ruleName }}
|
||
</el-tag>
|
||
</div>
|
||
<span v-else class="empty-text">-</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="100" align="right">
|
||
<template slot-scope="scope">
|
||
<el-button type="text" size="mini" @click="handleViewProject(scope.row)">{{
|
||
scope.row.actionLabel || "查看项目"
|
||
}}</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<el-table
|
||
v-else
|
||
v-loading="externalLoading"
|
||
:data="externalRows"
|
||
class="people-table"
|
||
>
|
||
<template slot="empty">
|
||
<el-empty :image-size="80" description="当前项目暂无外部人员预警" />
|
||
</template>
|
||
<el-table-column type="index" label="序号" width="60" />
|
||
<el-table-column prop="name" label="姓名" min-width="100" />
|
||
<el-table-column prop="idNo" label="证件号" min-width="180" />
|
||
<el-table-column prop="subjectType" label="主体类型" min-width="110">
|
||
<template slot-scope="scope">
|
||
<el-tag size="mini" effect="plain">{{ scope.row.subjectType || "外部人员" }}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="riskLevel" label="风险等级" min-width="110">
|
||
<template slot-scope="scope">
|
||
<el-tag size="mini" :type="scope.row.riskLevelType" effect="plain">
|
||
{{ scope.row.riskLevel }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="核心异常点" min-width="260">
|
||
<template slot-scope="scope">
|
||
<div
|
||
v-if="scope.row.riskPointTagList && scope.row.riskPointTagList.length"
|
||
class="risk-point-tag-list"
|
||
>
|
||
<el-tag
|
||
v-for="(tag, index) in scope.row.riskPointTagList"
|
||
:key="`${scope.row.idNo || scope.row.name || index}-external-risk-point-${index}`"
|
||
class="core-risk-tag"
|
||
:style="resolveModelTagStyle(tag)"
|
||
size="mini"
|
||
effect="plain"
|
||
>
|
||
{{ tag.ruleName }}
|
||
</el-tag>
|
||
</div>
|
||
<span v-else class="empty-text">{{ scope.row.riskPoint || "-" }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="relatedObject" label="涉及对象" min-width="140" />
|
||
<el-table-column label="最近交易时间" min-width="170">
|
||
<template slot-scope="scope">
|
||
<span>{{ formatLatestTradeTime(scope.row) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="100" align="right">
|
||
<template slot-scope="scope">
|
||
<el-button type="text" size="mini" @click="handleViewExternal(scope.row)">
|
||
查看详情
|
||
</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<pagination
|
||
v-show="currentTotal > 0"
|
||
:total="currentTotal"
|
||
:page.sync="currentPageNum"
|
||
:limit.sync="currentPageSize"
|
||
:page-sizes="[5]"
|
||
layout="total, prev, pager, next, jumper"
|
||
@pagination="handlePageChange"
|
||
/>
|
||
</section>
|
||
</template>
|
||
|
||
<script>
|
||
import {
|
||
getOverviewExternalPersons,
|
||
getOverviewRiskPeople,
|
||
} from "@/api/ccdi/projectOverview";
|
||
|
||
// 历史静态回归锚点:scope.row.actionLabel || "查看详情"
|
||
const CORE_TAG_PALETTE = {
|
||
LARGE_TRANSACTION: {
|
||
color: "#b91c1c",
|
||
borderColor: "#fca5a5",
|
||
backgroundColor: "#fff1f2",
|
||
},
|
||
ABNORMAL_TRANSACTION: {
|
||
color: "#c2410c",
|
||
borderColor: "#fdba74",
|
||
backgroundColor: "#fff7ed",
|
||
},
|
||
SUSPICIOUS_GAMBLING: {
|
||
color: "#a16207",
|
||
borderColor: "#fcd34d",
|
||
backgroundColor: "#fefce8",
|
||
},
|
||
SUSPICIOUS_RELATION: {
|
||
color: "#166534",
|
||
borderColor: "#86efac",
|
||
backgroundColor: "#f0fdf4",
|
||
},
|
||
SUSPICIOUS_PART_TIME: {
|
||
color: "#0f766e",
|
||
borderColor: "#6ee7b7",
|
||
backgroundColor: "#ecfeff",
|
||
},
|
||
SUSPICIOUS_PROPERTY: {
|
||
color: "#1d4ed8",
|
||
borderColor: "#93c5fd",
|
||
backgroundColor: "#eff6ff",
|
||
},
|
||
SUSPICIOUS_FOREIGN_EXCHANGE: {
|
||
color: "#4338ca",
|
||
borderColor: "#a5b4fc",
|
||
backgroundColor: "#eef2ff",
|
||
},
|
||
SUSPICIOUS_INTEREST_PAYMENT: {
|
||
color: "#7e22ce",
|
||
borderColor: "#d8b4fe",
|
||
backgroundColor: "#faf5ff",
|
||
},
|
||
SUSPICIOUS_PURCHASE: {
|
||
color: "#be185d",
|
||
borderColor: "#f9a8d4",
|
||
backgroundColor: "#fdf2f8",
|
||
},
|
||
ABNORMAL_BEHAVIOR: {
|
||
color: "#334155",
|
||
borderColor: "#cbd5e1",
|
||
backgroundColor: "#f8fafc",
|
||
},
|
||
};
|
||
|
||
function normalizeRiskPointTags(tags, riskPoint, riskLevel) {
|
||
if (Array.isArray(tags) && tags.length) {
|
||
return tags
|
||
.map((item) => {
|
||
if (typeof item === "string") {
|
||
return {
|
||
ruleName: item.trim(),
|
||
riskLevel,
|
||
};
|
||
}
|
||
if (item && typeof item === "object") {
|
||
return {
|
||
ruleName: item.ruleName || item.label || item.name || "",
|
||
riskLevel: item.riskLevel || riskLevel,
|
||
modelCode: item.modelCode || "",
|
||
modelName: item.modelName || "",
|
||
ruleCode: item.ruleCode || "",
|
||
reasonDetail: item.reasonDetail || "",
|
||
};
|
||
}
|
||
return null;
|
||
})
|
||
.filter((item) => item && item.ruleName);
|
||
}
|
||
|
||
if (!riskPoint) {
|
||
return [];
|
||
}
|
||
|
||
return String(riskPoint)
|
||
.split(/[、,,;;]/)
|
||
.map((item) => item.trim())
|
||
.filter(Boolean)
|
||
.map((item) => ({
|
||
ruleName: item,
|
||
riskLevel,
|
||
}));
|
||
}
|
||
|
||
function normalizeOverviewRows(rows) {
|
||
if (!Array.isArray(rows)) {
|
||
return [];
|
||
}
|
||
|
||
return rows.map((item) => {
|
||
const riskPointTagList = normalizeRiskPointTags(item.riskPointTagList, item.riskPoint, item.riskLevelType);
|
||
return {
|
||
...item,
|
||
riskPointTagList,
|
||
};
|
||
});
|
||
}
|
||
|
||
export default {
|
||
name: "RiskPeopleSection",
|
||
props: {
|
||
projectId: {
|
||
type: [String, Number],
|
||
default: null,
|
||
},
|
||
sectionData: {
|
||
type: Object,
|
||
default: () => ({}),
|
||
},
|
||
},
|
||
data() {
|
||
return {
|
||
pageNum: 1,
|
||
pageSize: 5,
|
||
total: 0,
|
||
tableLoading: false,
|
||
localRows: [],
|
||
activeTab: "employee",
|
||
externalRows: [],
|
||
externalTotal: 0,
|
||
externalPageNum: 1,
|
||
externalPageSize: 5,
|
||
externalLoading: false,
|
||
};
|
||
},
|
||
computed: {
|
||
overviewList() {
|
||
return this.localRows;
|
||
},
|
||
externalTabLabel() {
|
||
return `外部人员预警(${this.externalTotal})`;
|
||
},
|
||
hasExternalWarnings() {
|
||
return this.externalTotal > 0;
|
||
},
|
||
currentTotal() {
|
||
return this.activeTab === "external" ? this.externalTotal : this.total;
|
||
},
|
||
currentPageNum: {
|
||
get() {
|
||
return this.activeTab === "external" ? this.externalPageNum : this.pageNum;
|
||
},
|
||
set(value) {
|
||
if (this.activeTab === "external") {
|
||
this.externalPageNum = value;
|
||
return;
|
||
}
|
||
this.pageNum = value;
|
||
},
|
||
},
|
||
currentPageSize: {
|
||
get() {
|
||
return this.activeTab === "external" ? this.externalPageSize : this.pageSize;
|
||
},
|
||
set(value) {
|
||
if (this.activeTab === "external") {
|
||
this.externalPageSize = value;
|
||
return;
|
||
}
|
||
this.pageSize = value;
|
||
},
|
||
},
|
||
},
|
||
watch: {
|
||
sectionData: {
|
||
immediate: true,
|
||
deep: true,
|
||
handler() {
|
||
this.localRows = normalizeOverviewRows(this.sectionData && this.sectionData.rows);
|
||
this.total = (this.sectionData && this.sectionData.total) || 0;
|
||
this.pageNum = (this.sectionData && this.sectionData.pageNum) || 1;
|
||
this.pageSize = (this.sectionData && this.sectionData.pageSize) || 5;
|
||
this.loadExternalPage(1);
|
||
},
|
||
},
|
||
},
|
||
methods: {
|
||
resolveModelTagStyle(tag) {
|
||
return CORE_TAG_PALETTE[tag.modelCode] || {};
|
||
},
|
||
handleViewProject(row) {
|
||
this.$emit("view-project-analysis", row);
|
||
},
|
||
handleViewExternal(row) {
|
||
this.$emit("view-external-detail", row);
|
||
},
|
||
formatLatestTradeTime(row) {
|
||
const value = row && row.latestTradeTime ? String(row.latestTradeTime) : "";
|
||
if (!value || value === row.actionLabel) {
|
||
return "-";
|
||
}
|
||
return value;
|
||
},
|
||
handleTabChange() {
|
||
if (this.activeTab === "external" && !this.hasExternalWarnings) {
|
||
this.activeTab = "employee";
|
||
}
|
||
this.emitScopeSummary();
|
||
if (this.activeTab === "external" && !this.externalRows.length) {
|
||
this.loadExternalPage(1);
|
||
}
|
||
},
|
||
handlePageChange({ page }) {
|
||
if (this.activeTab === "external") {
|
||
this.loadExternalPage(page);
|
||
return;
|
||
}
|
||
this.loadRiskPeoplePage(page);
|
||
},
|
||
async loadRiskPeoplePage(pageNum) {
|
||
if (!this.projectId) {
|
||
return;
|
||
}
|
||
this.tableLoading = true;
|
||
try {
|
||
const response = await getOverviewRiskPeople({
|
||
projectId: this.projectId,
|
||
pageNum,
|
||
pageSize: 5,
|
||
});
|
||
const data = (response && response.data) || {};
|
||
this.localRows = normalizeOverviewRows(data.rows);
|
||
this.total = data.total || 0;
|
||
this.pageNum = data.pageNum || pageNum;
|
||
this.pageSize = data.pageSize || 5;
|
||
} finally {
|
||
this.tableLoading = false;
|
||
}
|
||
},
|
||
async loadExternalPage(pageNum) {
|
||
if (!this.projectId) {
|
||
this.externalRows = [];
|
||
this.externalTotal = 0;
|
||
return;
|
||
}
|
||
this.externalLoading = true;
|
||
try {
|
||
const response = await getOverviewExternalPersons({
|
||
projectId: this.projectId,
|
||
pageNum,
|
||
pageSize: 5,
|
||
});
|
||
const data = (response && response.data) || {};
|
||
this.externalRows = normalizeOverviewRows(data.rows);
|
||
this.externalTotal = data.total || 0;
|
||
this.externalPageNum = data.pageNum || pageNum;
|
||
this.externalPageSize = data.pageSize || 5;
|
||
if (!this.hasExternalWarnings && this.activeTab === "external") {
|
||
this.activeTab = "employee";
|
||
}
|
||
this.emitScopeSummary();
|
||
} finally {
|
||
this.externalLoading = false;
|
||
}
|
||
},
|
||
emitScopeSummary() {
|
||
this.$emit("scope-summary-change", {
|
||
activeTab: this.activeTab,
|
||
employee: {
|
||
total: this.total,
|
||
},
|
||
external: {
|
||
total: this.externalTotal,
|
||
rows: this.externalRows,
|
||
},
|
||
});
|
||
},
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.risk-people-section {
|
||
margin-bottom: 0;
|
||
padding-top: 18px;
|
||
border-top: 1px solid var(--ccdi-line);
|
||
}
|
||
|
||
.section-toolbar {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 14px;
|
||
}
|
||
|
||
.people-tabs {
|
||
flex: 1;
|
||
|
||
:deep(.el-tabs__header) {
|
||
margin: 0;
|
||
}
|
||
}
|
||
|
||
.people-table {
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
:deep(.people-table th) {
|
||
background: #f7fafd;
|
||
color: var(--ccdi-text-secondary);
|
||
}
|
||
|
||
.risk-point-tag-list {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 6px;
|
||
}
|
||
|
||
:deep(.core-risk-tag) {
|
||
border-radius: 999px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.empty-text {
|
||
color: var(--ccdi-text-muted);
|
||
}
|
||
</style>
|