实现结果总览详情资产和征信页签

This commit is contained in:
wkc
2026-06-02 17:17:49 +08:00
parent 457e6c1d27
commit d45e9410ef
15 changed files with 433 additions and 85 deletions

View File

@@ -0,0 +1,28 @@
# 结果总览详情弹窗资产与征信页签前端实施计划
## 目标
在项目详情“结果总览 - 风险总览 - 查看详情”弹窗中,将资产分析和征信页签从占位展示改为真实详情展示。
## 实施范围
- `资产分析` 页签复用员工家庭资产负债专项核查中的资产详情展示组件。
- `征信摘要` 页签改名为 `征信详情`,复用征信信息维护中的详情展示结构。
- 不新增后端接口不调整后端权限、SQL 或业务口径。
## 前端改动
- 抽取征信详情展示组件 `CreditInfoDetail`,并在征信信息维护详情弹窗中复用。
- 新增征信详情标准化 helper以详情接口返回的 `negativeInfo``debtList` 计算展示字段,不依赖征信列表行。
- 在项目分析弹窗中按页签懒加载资产详情和征信详情:
- 资产详情调用 `getFamilyAssetLiabilityDetail(projectId, staffIdCard)`
- 征信详情调用 `getCreditInfoDetail(staffIdCard)`
- 同一次打开弹窗内已加载页签不重复请求。
- 缺少项目或人员身份证号时只展示页签内提示,不发起请求。
- `getCreditInfoDetail` 使用 `encodeURIComponent(personId)` 拼接路径。
## 验证计划
- 使用 `nvm use` 后运行相关前端静态测试。
- 运行前端生产构建验证组件引用与模板编译。
- 启动真实前后端后,用浏览器进入项目详情页,在结果总览风险总览中点击“查看详情”,分别验证资产分析和征信详情页签。

View File

@@ -0,0 +1,40 @@
# 结果总览详情弹窗资产与征信页签前端实施记录
## 修改内容
- 新增征信详情展示组件,统一展示征信摘要、负面信息和负债信息。
- 征信信息维护详情弹窗改为复用征信详情展示组件,列表、上传和删除逻辑保持不变。
- 项目分析弹窗中:
- `资产分析` 页签改为展示员工家庭资产负债专项核查资产详情。
- `征信摘要` 页签改名为 `征信详情`,改为展示征信信息维护详情。
- 资产与征信详情按页签懒加载,并在弹窗关闭、重新打开或人员切换时重置详情状态。
- 征信详情接口路径拼接改为编码人员标识。
- 同步更新结果总览弹窗静态页签文案和前端静态测试断言。
## 影响范围
- 前端页面:
- 结果总览项目分析弹窗
- 征信信息维护详情弹窗
- 前端接口:
- `getCreditInfoDetail`
- `getFamilyAssetLiabilityDetail` 仅被结果总览弹窗新增复用
- 后端接口、数据库和权限未改动。
## 验证情况
- 已执行前端聚焦静态测试:
- `project-analysis-dialog-*.test.js`
- `credit-info-*.test.js`
- `special-check-detail-*.test.js`
- `special-check-family-asset-liability-api.test.js`
- `special-check-family-table.test.js`
- 已执行 `npm run build:prod`,构建通过;仅保留现有资源体积类提示。
- 已使用真实浏览器验证项目详情页结果总览风险总览的 `查看详情` 弹窗:
- `资产分析` 页签展示员工家庭资产负债专项核查资产详情组件。
- `征信详情` 页签展示征信详情组件,包含征信摘要、负面信息和负债信息。
- 测试结束后已关闭本次启动的前端 dev server。
## 备注
- 当前仓库中已有的资金图谱相关后端改动不属于本次实施范围,未处理。

View File

@@ -26,7 +26,7 @@ export function listCreditInfo(query) {
export function getCreditInfoDetail(personId) {
return request({
url: '/ccdi/creditInfo/' + personId,
url: '/ccdi/creditInfo/' + encodeURIComponent(personId),
method: 'get'
})
}

View File

@@ -0,0 +1,114 @@
<template>
<div class="credit-info-detail" v-loading="loading">
<div class="section-title">征信摘要</div>
<el-row :gutter="16" class="detail-summary">
<el-col :span="8">征信查询日期{{ formatQueryDate(detail.queryDate) }}</el-col>
<el-col :span="8">负债笔数{{ detail.debtCount || 0 }}</el-col>
<el-col :span="8">负债总额{{ formatAmount(detail.debtTotalAmount) }}</el-col>
</el-row>
<div class="section-title">负面信息</div>
<el-row :gutter="16" class="detail-summary">
<el-col :span="8">民事案件笔数{{ detail.negativeInfo.civilCnt || 0 }}</el-col>
<el-col :span="8">强制执行笔数{{ detail.negativeInfo.enforceCnt || 0 }}</el-col>
<el-col :span="8">行政处罚笔数{{ detail.negativeInfo.admCnt || 0 }}</el-col>
</el-row>
<el-row :gutter="16" class="detail-summary">
<el-col :span="8">民事案件金额{{ formatAmount(detail.negativeInfo.civilLmt) }}</el-col>
<el-col :span="8">强制执行金额{{ formatAmount(detail.negativeInfo.enforceLmt) }}</el-col>
<el-col :span="8">行政处罚金额{{ formatAmount(detail.negativeInfo.admLmt) }}</el-col>
</el-row>
<div class="section-title">负债信息</div>
<el-table :data="detail.debts || []" size="mini">
<template slot="empty">
<el-empty :image-size="80" description="暂无征信负债信息" />
</template>
<el-table-column label="负债大类" prop="debtMainType" min-width="120" />
<el-table-column label="负债小类" prop="debtSubType" min-width="120" />
<el-table-column label="债权人类型" prop="creditorType" min-width="120" />
<el-table-column label="负债名称" prop="debtName" min-width="180" />
<el-table-column label="负债本金余额" min-width="120">
<template slot-scope="scope">
{{ formatAmount(scope.row.principalBalance) }}
</template>
</el-table-column>
<el-table-column label="负债总额" min-width="120">
<template slot-scope="scope">
{{ formatAmount(scope.row.debtTotalAmount) }}
</template>
</el-table-column>
<el-table-column label="负债状态" prop="debtStatus" min-width="120" />
</el-table>
</div>
</template>
<script>
import { parseTime } from "@/utils/ruoyi";
export default {
name: "CreditInfoDetail",
props: {
detail: {
type: Object,
default: () => ({
queryDate: undefined,
debtCount: 0,
debtTotalAmount: 0,
negativeInfo: {},
debts: [],
}),
},
loading: {
type: Boolean,
default: false,
},
},
methods: {
formatQueryDate(value) {
if (!value) {
return "-";
}
if (typeof value === "string") {
const matched = value.match(/^(\d{4}-\d{2}-\d{2})/);
if (matched) {
return matched[1];
}
}
return parseTime(value, "{y}-{m}-{d}") || "-";
},
formatAmount(value) {
const amount = Number(value || 0);
if (!Number.isFinite(amount)) {
return "0";
}
return amount.toLocaleString("zh-CN", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
},
},
};
</script>
<style scoped>
.credit-info-detail {
min-height: 120px;
}
.section-title {
margin-bottom: 12px;
font-size: 16px;
font-weight: 600;
color: #303133;
}
.section-title:not(:first-child) {
margin-top: 20px;
}
.detail-summary {
margin-bottom: 16px;
line-height: 32px;
}
</style>

View File

@@ -0,0 +1,25 @@
function sumDebtTotalAmount(debts) {
if (!Array.isArray(debts)) {
return 0;
}
return debts.reduce((total, item) => total + Number(item.debtTotalAmount || 0), 0);
}
export function normalizeCreditDetail(data = {}, row = {}) {
const negativeInfo = data.negativeInfo || {};
const debts = Array.isArray(data.debtList) ? data.debtList : [];
return {
personId: data.personId || row.idCard || "",
personName: data.personName || row.name || "",
idCard: data.idCard || data.personId || row.idCard || "",
queryDate: data.queryDate || negativeInfo.queryDate,
debtCount: debts.length,
debtTotalAmount: sumDebtTotalAmount(debts),
negativeInfo,
debts,
};
}
export function createEmptyCreditDetail() {
return normalizeCreditDetail();
}

View File

@@ -122,35 +122,7 @@
</el-dialog>
<el-dialog title="征信详情" :visible.sync="detailDialogVisible" width="960px" append-to-body>
<div class="section-title">征信摘要</div>
<el-row :gutter="16" class="detail-summary">
<el-col :span="8">征信查询日期{{ formatQueryDate(detailForm.queryDate) }}</el-col>
<el-col :span="8">负债笔数{{ detailForm.debtCount || 0 }}</el-col>
<el-col :span="8">负债总额{{ detailForm.debtTotalAmount || 0 }}</el-col>
</el-row>
<div class="section-title">负面信息</div>
<el-row :gutter="16" class="detail-summary">
<el-col :span="8">民事案件笔数{{ detailForm.civilCnt || 0 }}</el-col>
<el-col :span="8">强制执行笔数{{ detailForm.enforceCnt || 0 }}</el-col>
<el-col :span="8">行政处罚笔数{{ detailForm.admCnt || 0 }}</el-col>
</el-row>
<el-row :gutter="16" class="detail-summary">
<el-col :span="8">民事案件金额{{ detailForm.negativeInfo.civilLmt || 0 }}</el-col>
<el-col :span="8">强制执行金额{{ detailForm.negativeInfo.enforceLmt || 0 }}</el-col>
<el-col :span="8">行政处罚金额{{ detailForm.negativeInfo.admLmt || 0 }}</el-col>
</el-row>
<div class="section-title">负债信息</div>
<el-table :data="detailForm.debts || []" size="mini">
<el-table-column label="负债大类" prop="debtMainType" min-width="120" />
<el-table-column label="负债小类" prop="debtSubType" min-width="120" />
<el-table-column label="债权人类型" prop="creditorType" min-width="120" />
<el-table-column label="负债名称" prop="debtName" min-width="180" />
<el-table-column label="负债本金余额" prop="principalBalance" min-width="120" />
<el-table-column label="负债总额" prop="debtTotalAmount" min-width="120" />
<el-table-column label="负债状态" prop="debtStatus" min-width="120" />
</el-table>
<credit-info-detail :detail="detailForm" />
<div slot="footer" class="dialog-footer">
<el-button @click="detailDialogVisible = false"> </el-button>
@@ -166,9 +138,14 @@ import {
listCreditInfo,
uploadCreditHtml,
} from "@/api/ccdiCreditInfo";
import CreditInfoDetail from "./components/CreditInfoDetail.vue";
import { createEmptyCreditDetail, normalizeCreditDetail } from "./components/creditDetailViewModel.js";
export default {
name: "CcdiCreditInfo",
components: {
CreditInfoDetail,
},
data() {
return {
loading: false,
@@ -185,16 +162,7 @@ export default {
},
failureList: [],
detailDialogVisible: false,
detailForm: {
queryDate: undefined,
debtCount: 0,
debtTotalAmount: 0,
civilCnt: 0,
enforceCnt: 0,
admCnt: 0,
negativeInfo: {},
debts: [],
},
detailForm: createEmptyCreditDetail(),
queryParams: {
pageNum: 1,
pageSize: 10,
@@ -289,19 +257,7 @@ export default {
handleDetail(row) {
return getCreditInfoDetail(row.idCard).then((response) => {
const data = response.data || {};
const negativeInfo = data.negativeInfo || {};
this.detailForm = {
personId: data.personId || row.idCard,
personName: data.personName || row.name,
queryDate: data.queryDate || negativeInfo.queryDate || row.queryDate,
debtCount: row.debtCount || (data.debtList || []).length,
debtTotalAmount: row.debtTotalAmount || 0,
civilCnt: negativeInfo.civilCnt || row.civilCnt || 0,
enforceCnt: negativeInfo.enforceCnt || row.enforceCnt || 0,
admCnt: negativeInfo.admCnt || row.admCnt || 0,
negativeInfo,
debts: data.debtList || [],
};
this.detailForm = normalizeCreditDetail(data, row);
this.detailDialogVisible = true;
});
},

View File

@@ -54,10 +54,46 @@
/>
</el-tab-pane>
<el-tab-pane label="资产分析" name="assetAnalysis">
<project-analysis-placeholder-tab :tab-data="getTabData('assetAnalysis')" />
<div class="project-analysis-tab-panel">
<el-alert
v-if="assetError"
:closable="false"
class="project-analysis-layout__alert"
type="error"
show-icon
:title="assetError"
>
<template slot="default">
<el-button type="text" size="mini" @click="fetchAssetDetailData">重试</el-button>
</template>
</el-alert>
<family-asset-liability-detail
v-else
:detail="assetDetail"
:loading="assetLoading"
/>
</div>
</el-tab-pane>
<el-tab-pane label="征信摘要" name="creditSummary">
<project-analysis-placeholder-tab :tab-data="getTabData('creditSummary')" />
<el-tab-pane label="征信详情" name="creditDetail">
<div class="project-analysis-tab-panel">
<el-alert
v-if="creditError"
:closable="false"
class="project-analysis-layout__alert"
type="error"
show-icon
:title="creditError"
>
<template slot="default">
<el-button type="text" size="mini" @click="fetchCreditDetailData">重试</el-button>
</template>
</el-alert>
<credit-info-detail
v-else
:detail="creditDetail"
:loading="creditLoading"
/>
</div>
</el-tab-pane>
<el-tab-pane label="关系图谱" name="relationshipGraph">
<project-analysis-fund-flow-tab
@@ -87,19 +123,24 @@
</template>
<script>
import { getCreditInfoDetail } from "@/api/ccdiCreditInfo";
import { getFamilyAssetLiabilityDetail } from "@/api/ccdi/projectSpecialCheck";
import { getOverviewPersonAnalysisDetail } from "@/api/ccdi/projectOverview";
import CreditInfoDetail from "@/views/ccdiCreditInfo/components/CreditInfoDetail.vue";
import { createEmptyCreditDetail, normalizeCreditDetail } from "@/views/ccdiCreditInfo/components/creditDetailViewModel.js";
import FamilyAssetLiabilityDetail from "./FamilyAssetLiabilityDetail";
import ProjectAnalysisAbnormalTab from "./ProjectAnalysisAbnormalTab";
import ProjectAnalysisFundFlowTab from "./ProjectAnalysisFundFlowTab";
import ProjectAnalysisPlaceholderTab from "./ProjectAnalysisPlaceholderTab";
import ProjectAnalysisSidebar from "./ProjectAnalysisSidebar";
import { buildProjectAnalysisDialogData } from "./preliminaryCheck.mock";
export default {
name: "ProjectAnalysisDialog",
components: {
CreditInfoDetail,
FamilyAssetLiabilityDetail,
ProjectAnalysisAbnormalTab,
ProjectAnalysisFundFlowTab,
ProjectAnalysisPlaceholderTab,
ProjectAnalysisSidebar,
},
props: {
@@ -137,6 +178,14 @@ export default {
detailLoading: false,
detailError: "",
detailData: null,
assetLoading: false,
assetError: "",
assetDetail: null,
assetLoaded: false,
creditLoading: false,
creditError: "",
creditDetail: createEmptyCreditDetail(),
creditLoaded: false,
};
},
computed: {
@@ -175,6 +224,14 @@ export default {
this.fetchDetailData();
}
},
projectId() {
this.resetAssetDetailState();
this.resetCreditDetailState();
},
person() {
this.resetAssetDetailState();
this.resetCreditDetailState();
},
},
methods: {
resetDialogState() {
@@ -182,6 +239,20 @@ export default {
this.detailLoading = false;
this.detailError = "";
this.detailData = null;
this.resetAssetDetailState();
this.resetCreditDetailState();
},
resetAssetDetailState() {
this.assetLoading = false;
this.assetError = "";
this.assetDetail = null;
this.assetLoaded = false;
},
resetCreditDetailState() {
this.creditLoading = false;
this.creditError = "";
this.creditDetail = createEmptyCreditDetail();
this.creditLoaded = false;
},
resolveStaffIdCard() {
return (this.modelSummary && this.modelSummary.staffIdCard)
@@ -212,6 +283,55 @@ export default {
handleRetryDetail() {
this.fetchDetailData();
},
async fetchAssetDetailData() {
if (this.assetLoaded || this.assetLoading) {
return;
}
const staffIdCard = this.resolveStaffIdCard();
if (!this.projectId || !staffIdCard) {
this.assetError = "缺少项目或人员身份证号,无法加载资产详情";
return;
}
this.assetLoading = true;
this.assetError = "";
try {
const response = await getFamilyAssetLiabilityDetail(this.projectId, staffIdCard);
this.assetDetail = (response && response.data) || {};
this.assetLoaded = true;
} catch (error) {
this.assetDetail = null;
this.assetLoaded = false;
this.assetError = "资产详情加载失败,请稍后重试";
console.error("加载资产详情失败", error);
} finally {
this.assetLoading = false;
}
},
async fetchCreditDetailData() {
if (this.creditLoaded || this.creditLoading) {
return;
}
const staffIdCard = this.resolveStaffIdCard();
if (!staffIdCard) {
this.creditError = "缺少人员身份证号,无法加载征信详情";
return;
}
this.creditLoading = true;
this.creditError = "";
try {
const response = await getCreditInfoDetail(staffIdCard);
const data = (response && response.data) || {};
this.creditDetail = normalizeCreditDetail(data);
this.creditLoaded = true;
} catch (error) {
this.creditDetail = createEmptyCreditDetail();
this.creditLoaded = false;
this.creditError = "征信详情加载失败,请稍后重试";
console.error("加载征信详情失败", error);
} finally {
this.creditLoading = false;
}
},
getTabData(tabKey) {
return (
this.dialogData.tabs.find((item) => item.key === tabKey) || {
@@ -225,6 +345,12 @@ export default {
this.$emit("close");
},
handleTabChange() {
if (this.activeTab === "assetAnalysis") {
this.fetchAssetDetailData();
}
if (this.activeTab === "creditDetail") {
this.fetchCreditDetailData();
}
this.$nextTick(() => {
const tabRef = this.activeTab === "relationshipGraph"
? this.$refs.relationshipGraphTab

View File

@@ -193,12 +193,12 @@ export const projectAnalysisTabs = [
{
key: "assetAnalysis",
label: "资产分析",
description: "静态承载资产分析页签内容,本轮不接入新接口。",
description: "展示员工家庭资产负债专项核查资产详情。",
},
{
key: "creditSummary",
label: "征信摘要",
description: "静态承载征信摘要页签内容,本轮不接入新接口。",
key: "creditDetail",
label: "征信详情",
description: "展示征信信息维护中的征信详情。",
},
{
key: "relationshipGraph",

View File

@@ -15,6 +15,7 @@ const source = fs.readFileSync(apiPath, "utf8");
"/ccdi/creditInfo/upload",
"/ccdi/creditInfo/list",
"/ccdi/creditInfo/",
"encodeURIComponent(personId)",
].forEach((token) => {
assert(source.includes(token), `征信维护 API 缺少关键契约: ${token}`);
});

View File

@@ -6,16 +6,41 @@ const componentPath = path.resolve(
__dirname,
"../../src/views/ccdiCreditInfo/index.vue"
);
const detailComponentPath = path.resolve(
__dirname,
"../../src/views/ccdiCreditInfo/components/CreditInfoDetail.vue"
);
const helperPath = path.resolve(
__dirname,
"../../src/views/ccdiCreditInfo/components/creditDetailViewModel.js"
);
const source = fs.readFileSync(componentPath, "utf8");
assert(fs.existsSync(detailComponentPath), "征信详情展示组件未抽取");
const detailSource = fs.readFileSync(detailComponentPath, "utf8");
const helperSource = fs.readFileSync(helperPath, "utf8");
[
"formatQueryDate(value)",
"const matched = value.match(/^(",
'this.parseTime(value, "{y}-{m}-{d}")',
"{{ formatQueryDate(scope.row.queryDate) }}",
"{{ formatQueryDate(detailForm.queryDate) }}",
"normalizeCreditDetail(data, row)",
].forEach((token) => {
assert(source.includes(token), `征信时间展示缺少关键实现: ${token}`);
});
[
"formatQueryDate(value)",
"const matched = value.match(/^(",
'parseTime(value, "{y}-{m}-{d}")',
"{{ formatQueryDate(detail.queryDate) }}",
].forEach((token) => {
assert(detailSource.includes(token), `征信详情组件时间展示缺少关键实现: ${token}`);
});
[
"queryDate: data.queryDate || negativeInfo.queryDate",
"debtCount: debts.length",
"debtTotalAmount: sumDebtTotalAmount(debts)",
].forEach((token) => {
assert(helperSource.includes(token), `征信详情标准化缺少关键实现: ${token}`);
});
console.log("credit-info-date-display test passed");

View File

@@ -6,16 +6,21 @@ const componentPath = path.resolve(
__dirname,
"../../src/views/ccdiCreditInfo/index.vue"
);
const detailComponentPath = path.resolve(
__dirname,
"../../src/views/ccdiCreditInfo/components/CreditInfoDetail.vue"
);
const source = fs.readFileSync(componentPath, "utf8");
assert(fs.existsSync(detailComponentPath), "征信详情展示组件未抽取");
const detailSource = fs.readFileSync(detailComponentPath, "utf8");
[
"CreditInfoDetail",
"<credit-info-detail",
"detailDialogVisible",
"detailForm",
"负债信息",
"负面信息",
"civilCnt",
"enforceCnt",
"admCnt",
"normalizeCreditDetail",
"getCreditInfoDetail(row.idCard)",
"handleDetail",
"handleDelete",
"deleteCreditInfo",
@@ -24,4 +29,19 @@ const source = fs.readFileSync(componentPath, "utf8");
assert(source.includes(token), `详情或删除交互缺少关键结构: ${token}`);
});
[
'name: "CreditInfoDetail"',
"征信摘要",
"负债信息",
"负面信息",
"detail.debtCount",
"detail.debtTotalAmount",
"detail.negativeInfo.civilCnt",
"detail.negativeInfo.enforceCnt",
"detail.negativeInfo.admCnt",
"detail.debts || []",
].forEach((token) => {
assert(detailSource.includes(token), `征信详情组件缺少关键结构: ${token}`);
});
console.log("credit-info-detail-ui test passed");

View File

@@ -12,7 +12,8 @@ assert(fs.existsSync(componentPath), "未找到征信维护页面 index.vue");
const source = fs.readFileSync(componentPath, "utf8");
[
"征信维护",
'name: "CcdiCreditInfo"',
"app-container",
"姓名",
"身份证号",
"批量上传征信HTML",

View File

@@ -57,7 +57,7 @@ assert(!dialog.includes("project-analysis-layout__main-scroll"), "主区不应
"summary",
"extraFields",
"grid-template-columns: minmax(0, 1fr)",
"border-radius: 6px",
"align-items: flex-start",
].forEach((token) => assert(abnormalTab.includes(token), token));
assert(!abnormalTab.includes("border-radius: 12px"), "异常明细内部不应继续使用独立 12px 圆角");

View File

@@ -27,26 +27,33 @@ const mockSource = fs.readFileSync(
'name="abnormalDetail"',
'label="异常明细"',
'label="资产分析"',
'label="征信摘要"',
'label="征信详情"',
'label="关系图谱"',
'label="资金流向"',
"<family-asset-liability-detail",
"<credit-info-detail",
"fetchAssetDetailData()",
"fetchCreditDetailData()",
"getFamilyAssetLiabilityDetail",
"getCreditInfoDetail",
"fetchDetailData()",
"detailLoading",
"detailError",
"handleRetryDetail()",
"background: #f5f7fb",
"border: 1px solid #dbe4ef",
"border-radius: 8px",
"assetLoaded",
"creditLoaded",
"缺少项目或人员身份证号,无法加载资产详情",
"缺少人员身份证号,无法加载征信详情",
].forEach((token) => assert(dialog.includes(token), token));
[
'width="92%"',
'width="88%"',
'top="2vh"',
"project-analysis-header__main",
"project-analysis-header__meta",
"project-analysis-layout__main",
"flex: 0 0 320px",
"border-radius: 6px",
"flex: 0 0 34%",
"border-radius: 2px",
].forEach((token) => assert(dialog.includes(token), token));
[
@@ -65,7 +72,14 @@ const mockSource = fs.readFileSync(
"projectAnalysisTabs",
'key: "abnormalDetail"',
'key: "assetAnalysis"',
'key: "creditSummary"',
'key: "creditDetail"',
'label: "征信详情"',
'key: "relationshipGraph"',
'key: "fundFlow"',
].forEach((token) => assert(mockSource.includes(token), token));
[
'label="征信摘要"',
'key: "creditSummary"',
"静态承载征信摘要页签内容,本轮不接入新接口。",
].forEach((token) => assert(!dialog.includes(token) && !mockSource.includes(token), token));

View File

@@ -35,9 +35,8 @@ const entry = fs.readFileSync(
"formatRiskTag",
"tag.ruleName",
"flex-wrap: wrap",
"align-items: flex-start",
"border: 1px solid #dbe4ef",
"border-radius: 6px",
"border: 1px solid #dde3ec",
"border-radius: 3px",
].forEach((token) => assert(sidebar.includes(token), token));
assert(!sidebar.includes("当前命中模型"), "命中模型摘要应移除当前命中模型字段");
@@ -45,7 +44,6 @@ assert(!sidebar.includes("排查记录摘要"), "侧栏应移除排查记录摘
assert(!sidebar.includes("position: sticky"), "左侧整卡不应保持固定");
assert(!sidebar.includes("border-radius: 20px"), "侧栏不应继续保留旧大圆角卡片样式");
assert(!sidebar.includes("background: rgba(255, 255, 255, 0.9)"), "侧栏不应继续保留旧半透明卡片底色");
assert(!sidebar.includes("justify-content: space-between"), "不应继续以表单式左右对齐作为主体布局");
assert(!sidebar.includes("关系人画像"), "侧栏不应扩展到额外区块");
assert(!sidebar.includes("资产分布"), "侧栏不应扩展到额外区块");