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

460 lines
13 KiB
Vue
Raw Normal View History

<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;
2026-04-03 11:00:33 +08:00
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;
}
2026-03-19 10:39:24 +08:00
:deep(.people-table th) {
2026-04-03 11:00:33 +08:00
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 {
2026-04-03 11:00:33 +08:00
color: var(--ccdi-text-muted);
}
</style>