Files
ccdi/ruoyi-ui/src/views/ccdiProject/components/detail/RiskPeopleSection.vue

460 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>