完成专项核查家庭资产负债实现

This commit is contained in:
wkc
2026-03-24 20:42:56 +08:00
parent 979b905682
commit c1b4514806
38 changed files with 2514 additions and 19 deletions

View File

@@ -0,0 +1,20 @@
import request from "@/utils/request";
export function getFamilyAssetLiabilityList(projectId) {
return request({
url: "/ccdi/project/special-check/family-asset-liability/list",
method: "get",
params: { projectId },
});
}
export function getFamilyAssetLiabilityDetail(projectId, staffIdCard) {
return request({
url: "/ccdi/project/special-check/family-asset-liability/detail",
method: "get",
params: {
projectId,
staffIdCard,
},
});
}

View File

@@ -0,0 +1,225 @@
<template>
<div class="family-asset-liability-detail" v-loading="loading">
<div class="detail-grid">
<section class="detail-block">
<div class="block-header">
<div>
<div class="block-title">收入明细</div>
<div class="block-subtitle">展示本人配偶与家庭收入汇总</div>
</div>
</div>
<div class="detail-metric-list">
<div class="detail-metric-item">
<span class="metric-label">本人收入</span>
<span class="metric-value">{{ formatAmount(incomeDetail.selfIncome) }}</span>
</div>
<div class="detail-metric-item">
<span class="metric-label">配偶收入</span>
<span class="metric-value">{{ formatAmount(incomeDetail.spouseIncome) }}</span>
</div>
<div class="detail-metric-item">
<span class="metric-label">家庭总收入</span>
<span class="metric-value">{{ formatAmount(incomeDetail.totalIncome) }}</span>
</div>
</div>
</section>
<section class="detail-block">
<div class="block-header">
<div>
<div class="block-title">资产明细</div>
<div class="block-subtitle">展示本人及配偶资产小计与明细列表</div>
</div>
</div>
<div v-if="!assetDetail.missingSelfAssetInfo" class="detail-summary">
<span>本人资产小计{{ formatAmount(assetDetail.selfTotalAsset) }}</span>
<span>配偶资产小计{{ formatAmount(assetDetail.spouseTotalAsset) }}</span>
</div>
<el-table v-if="assetItems.length" :data="assetItems" size="mini" class="detail-table">
<el-table-column prop="assetName" label="资产名称" min-width="140" />
<el-table-column prop="assetMainType" label="资产大类" min-width="100" />
<el-table-column prop="assetSubType" label="资产小类" min-width="120" />
<el-table-column prop="holderName" label="持有人" min-width="100" />
<el-table-column label="当前估值" min-width="120">
<template slot-scope="scope">
<span>{{ formatAmount(scope.row.currentValue) }}</span>
</template>
</el-table-column>
<el-table-column prop="valuationDate" label="估值日期" min-width="120" />
</el-table>
<el-empty v-else :image-size="64" description="暂无资产明细" />
</section>
<section class="detail-block">
<div class="block-header">
<div>
<div class="block-title">负债明细</div>
<div class="block-subtitle">展示本人及配偶负债小计与明细列表</div>
</div>
</div>
<div v-if="!debtDetail.missingSelfDebtInfo" class="detail-summary">
<span>本人负债小计{{ formatAmount(debtDetail.selfTotalDebt) }}</span>
<span>配偶负债小计{{ formatAmount(debtDetail.spouseTotalDebt) }}</span>
</div>
<el-table v-if="debtItems.length" :data="debtItems" size="mini" class="detail-table">
<el-table-column prop="debtName" label="负债名称" min-width="140" />
<el-table-column prop="debtMainType" label="负债大类" min-width="100" />
<el-table-column prop="debtSubType" label="负债小类" min-width="120" />
<el-table-column prop="creditorType" label="债权人类型" min-width="120" />
<el-table-column prop="ownerName" label="归属人" min-width="100" />
<el-table-column label="本金余额" min-width="120">
<template slot-scope="scope">
<span>{{ formatAmount(scope.row.principalBalance) }}</span>
</template>
</el-table-column>
<el-table-column prop="queryDate" label="查询日期" min-width="120" />
</el-table>
<el-empty v-else :image-size="64" description="暂无负债明细" />
</section>
</div>
</div>
</template>
<script>
export default {
name: "FamilyAssetLiabilityDetail",
props: {
detail: {
type: Object,
default: () => ({}),
},
loading: {
type: Boolean,
default: false,
},
},
computed: {
incomeDetail() {
return this.detail && this.detail.incomeDetail
? this.detail.incomeDetail
: {
selfIncome: 0,
spouseIncome: 0,
totalIncome: 0,
};
},
assetDetail() {
return this.detail && this.detail.assetDetail
? this.detail.assetDetail
: {
missingSelfAssetInfo: false,
selfTotalAsset: 0,
spouseTotalAsset: 0,
totalAsset: 0,
items: [],
};
},
debtDetail() {
return this.detail && this.detail.debtDetail
? this.detail.debtDetail
: {
missingSelfDebtInfo: false,
selfTotalDebt: 0,
spouseTotalDebt: 0,
totalDebt: 0,
items: [],
};
},
assetItems() {
return Array.isArray(this.assetDetail.items) ? this.assetDetail.items : [];
},
debtItems() {
return Array.isArray(this.debtDetail.items) ? this.debtDetail.items : [];
},
},
methods: {
formatAmount(value) {
const amount = Number(value || 0);
return `${amount.toLocaleString("zh-CN", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})} 元`;
},
},
};
</script>
<style lang="scss" scoped>
.family-asset-liability-detail {
padding: 8px 0 4px;
}
.detail-grid {
display: grid;
gap: 12px;
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.detail-block {
padding: 16px;
background: #f8fafc;
border: 1px solid #e2e8f0;
min-width: 0;
}
.block-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.block-title {
font-size: 14px;
font-weight: 600;
color: #1f2937;
}
.block-subtitle {
margin-top: 4px;
font-size: 12px;
color: #94a3b8;
}
.detail-metric-list {
display: grid;
gap: 8px;
}
.detail-metric-item {
display: flex;
justify-content: space-between;
gap: 16px;
font-size: 13px;
color: #475569;
}
.metric-value {
font-weight: 600;
color: #111827;
}
.detail-summary {
display: flex;
flex-wrap: wrap;
gap: 16px;
margin-bottom: 12px;
font-size: 13px;
color: #475569;
}
.detail-table {
border-radius: 12px;
overflow: hidden;
}
:deep(.detail-table .el-table__body-wrapper) {
overflow-x: auto;
}
@media (max-width: 1200px) {
.detail-grid {
grid-template-columns: 1fr;
}
}
</style>

View File

@@ -0,0 +1,211 @@
<template>
<section class="family-asset-liability-section">
<div class="section-card">
<div class="block-header">
<div>
<div class="block-title">{{ title }}</div>
<div class="block-subtitle">{{ subtitle }}</div>
</div>
</div>
<el-table
ref="familyTable"
v-loading="loading"
:data="rows"
class="family-table"
row-key="staffIdCard"
:expand-row-keys="expandedRowKeys"
>
<template slot="empty">
<el-empty :image-size="80" description="暂无员工家庭资产负债核查数据" />
</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])"
/>
</template>
</el-table-column>
<el-table-column type="index" label="序号" width="60" />
<el-table-column prop="staffName" label="姓名" min-width="100" />
<el-table-column prop="staffIdCard" label="身份证号" min-width="180" />
<el-table-column prop="deptName" label="所属部门" min-width="140" />
<el-table-column label="家庭总年收入" min-width="140">
<template slot-scope="scope">
<span>{{ formatAmount(scope.row.totalIncome) }}</span>
</template>
</el-table-column>
<el-table-column label="家庭总资产" min-width="140">
<template slot-scope="scope">
<span>{{ formatAmount(scope.row.totalAsset) }}</span>
</template>
</el-table-column>
<el-table-column label="家庭总负债" min-width="140">
<template slot-scope="scope">
<span>{{ formatAmount(scope.row.totalDebt) }}</span>
</template>
</el-table-column>
<el-table-column label="风险情况" min-width="120">
<template slot-scope="scope">
<el-tag size="mini" effect="plain" :type="resolveRiskTagType(scope.row.riskLevelCode)">
{{ scope.row.riskLevelName || "-" }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="100" align="right">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="handleToggleDetail(scope.row)">
查看详情
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</section>
</template>
<script>
import { getFamilyAssetLiabilityDetail } from "@/api/ccdi/projectSpecialCheck";
import FamilyAssetLiabilityDetail from "./FamilyAssetLiabilityDetail";
export default {
name: "FamilyAssetLiabilitySection",
components: {
FamilyAssetLiabilityDetail,
},
props: {
rows: {
type: Array,
default: () => [],
},
loading: {
type: Boolean,
default: false,
},
projectId: {
type: [String, Number],
default: null,
},
title: {
type: String,
default: "员工家庭资产负债专项核查",
},
subtitle: {
type: String,
default: "展示项目内员工家庭收入、资产、负债与风险情况",
},
},
data() {
return {
expandedStaffIdCard: "",
detailCache: {},
detailLoadingMap: {},
};
},
computed: {
expandedRowKeys() {
return this.expandedStaffIdCard ? [this.expandedStaffIdCard] : [];
},
},
watch: {
projectId() {
this.resetDetailState();
},
},
methods: {
resolveRiskTagType(riskLevelCode) {
const riskTagTypeMap = {
NORMAL: "success",
RISK: "warning",
HIGH: "danger",
MISSING_INFO: "info",
};
return riskTagTypeMap[riskLevelCode] || "info";
},
formatAmount(value) {
const amount = Number(value || 0);
return `${amount.toLocaleString("zh-CN", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})} 元`;
},
async handleToggleDetail(row) {
if (!row || !row.staffIdCard) {
return;
}
if (this.expandedStaffIdCard === row.staffIdCard) {
this.expandedStaffIdCard = "";
return;
}
this.expandedStaffIdCard = row.staffIdCard;
if (!this.detailCache[row.staffIdCard]) {
await this.loadFamilyDetail(row);
}
},
async loadFamilyDetail(row) {
const staffIdCard = row.staffIdCard;
this.$set(this.detailLoadingMap, staffIdCard, true);
try {
const response = await getFamilyAssetLiabilityDetail(this.projectId, staffIdCard);
const detail = (response && response.data) || {};
this.$set(this.detailCache, staffIdCard, detail);
} catch (error) {
this.$set(this.detailCache, staffIdCard, null);
console.error("加载员工家庭资产负债详情失败", error);
} finally {
this.$set(this.detailLoadingMap, staffIdCard, false);
}
},
resetDetailState() {
this.expandedStaffIdCard = "";
this.detailCache = {};
this.detailLoadingMap = {};
},
},
};
</script>
<style lang="scss" scoped>
.family-asset-liability-section {
margin-bottom: 16px;
}
.section-card {
padding: 20px;
border-radius: 0;
background: #fff;
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.06);
}
.block-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 14px;
}
.block-title {
font-size: 16px;
font-weight: 600;
color: #1f2937;
}
.block-subtitle {
margin-top: 4px;
font-size: 12px;
color: #94a3b8;
}
.family-table {
border-radius: 12px;
overflow: hidden;
}
:deep(.family-table th) {
background: #f8fafc;
color: #64748b;
}
</style>

View File

@@ -1,15 +1,39 @@
<template>
<div class="special-check-container">
<div class="placeholder-content">
<i class="el-icon-search"></i>
<p>专项排查功能开发中...</p>
<div v-if="pageState === 'loading'" class="special-check-state">
<div class="state-card">
<el-skeleton animated :rows="6" />
</div>
</div>
<div v-else-if="pageState === 'empty'" class="special-check-state">
<div class="state-card">
<el-empty description="暂无员工家庭资产负债核查数据" />
</div>
</div>
<div v-else class="special-check-page">
<family-asset-liability-section
:rows="currentData.rows"
:loading="false"
:project-id="projectId"
:title="sectionTitle"
:subtitle="sectionSubtitle"
/>
</div>
</div>
</template>
<script>
import { createSpecialCheckLoadedData, specialCheckStateData } from "./specialCheck.mock";
import { getFamilyAssetLiabilityList } from "@/api/ccdi/projectSpecialCheck";
import FamilyAssetLiabilitySection from "./FamilyAssetLiabilitySection";
export default {
name: "SpecialCheck",
components: {
FamilyAssetLiabilitySection,
},
props: {
projectId: {
type: [String, Number],
@@ -24,28 +48,85 @@ export default {
}),
},
},
data() {
return {
pageState: "loading",
realData: specialCheckStateData.loading,
sectionTitle: "员工家庭资产负债专项核查",
sectionSubtitle: "按项目员工范围聚合本人及配偶的收入、资产与负债情况",
};
},
computed: {
currentData() {
if (this.pageState === "loaded") {
return this.realData;
}
return specialCheckStateData[this.pageState] || this.realData;
},
},
watch: {
projectId(newVal) {
if (newVal) {
this.loadSpecialCheckData();
return;
}
this.realData = specialCheckStateData.empty;
this.pageState = "empty";
},
},
created() {
if (this.projectId) {
this.loadSpecialCheckData();
return;
}
this.realData = specialCheckStateData.empty;
this.pageState = "empty";
},
methods: {
async loadSpecialCheckData() {
if (!this.projectId) {
this.realData = specialCheckStateData.empty;
this.pageState = "empty";
return;
}
this.pageState = "loading";
try {
const response = await getFamilyAssetLiabilityList(this.projectId);
const listData = (response && response.data) || {};
this.realData = createSpecialCheckLoadedData({
projectId: this.projectId,
listData,
});
this.pageState = this.realData.rows.length ? "loaded" : "empty";
} catch (error) {
this.realData = specialCheckStateData.empty;
this.pageState = "empty";
console.error("加载专项核查数据失败", error);
}
},
},
};
</script>
<style lang="scss" scoped>
.special-check-container {
padding: 40px 20px;
background: #fff;
min-height: 400px;
padding: 0 0 24px;
}
.special-check-state {
min-height: 400px;
}
.placeholder-content {
text-align: center;
color: #909399;
.state-card {
padding: 32px 24px;
border-radius: 0;
background: #fff;
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.06);
}
i {
font-size: 48px;
margin-bottom: 16px;
}
p {
font-size: 14px;
margin: 0;
}
.special-check-page {
min-height: 400px;
}
</style>

View File

@@ -0,0 +1,24 @@
const baseLoadedData = {
projectId: null,
rows: [],
};
export function createSpecialCheckLoadedData({ projectId, listData } = {}) {
return {
...baseLoadedData,
projectId,
rows: Array.isArray(listData && listData.rows) ? listData.rows : [],
};
}
export const specialCheckStateData = {
loaded: baseLoadedData,
empty: {
...baseLoadedData,
rows: [],
},
loading: {
...baseLoadedData,
rows: [],
},
};