diff --git a/assets/征信解析/ccdi_debts_info.xlsx b/assets/征信解析/ccdi_debts_info.xlsx
new file mode 100644
index 00000000..26457986
Binary files /dev/null and b/assets/征信解析/ccdi_debts_info.xlsx differ
diff --git a/ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiCreditInfoQueryMapper.xml b/ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiCreditInfoQueryMapper.xml
index a8eeb4e9..d19a1606 100644
--- a/ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiCreditInfoQueryMapper.xml
+++ b/ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiCreditInfoQueryMapper.xml
@@ -29,6 +29,7 @@
) debt_agg ON debt_agg.person_id = s.id_card
LEFT JOIN ccdi_credit_negative_info neg ON neg.person_id = s.id_card
+ AND (debt_agg.person_id IS NOT NULL OR neg.person_id IS NOT NULL)
AND s.name LIKE CONCAT('%', #{query.name}, '%')
@@ -38,13 +39,6 @@
AND s.id_card LIKE CONCAT('%', #{query.idCard}, '%')
-
- AND (debt_agg.person_id IS NOT NULL OR neg.person_id IS NOT NULL)
-
-
- AND debt_agg.person_id IS NULL
- AND neg.person_id IS NULL
-
ORDER BY debt_agg.query_date DESC, s.staff_id DESC
diff --git a/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/mapper/CcdiCreditInfoQueryMapperXmlTest.java b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/mapper/CcdiCreditInfoQueryMapperXmlTest.java
new file mode 100644
index 00000000..7e8a0bae
--- /dev/null
+++ b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/mapper/CcdiCreditInfoQueryMapperXmlTest.java
@@ -0,0 +1,24 @@
+package com.ruoyi.info.collection.mapper;
+
+import org.junit.jupiter.api.Test;
+
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class CcdiCreditInfoQueryMapperXmlTest {
+
+ @Test
+ void selectCreditInfoPage_shouldOnlyQueryMaintainedCreditInfo() throws Exception {
+ Path xmlPath = Path.of("src/main/resources/mapper/info/collection/CcdiCreditInfoQueryMapper.xml");
+ String source = Files.readString(xmlPath, StandardCharsets.UTF_8);
+
+ assertTrue(source.contains("AND (debt_agg.person_id IS NOT NULL OR neg.person_id IS NOT NULL)"),
+ "征信维护列表必须默认只返回已维护征信的员工");
+ assertFalse(source.contains("query.maintained == '0'"),
+ "征信维护列表不应再支持未维护员工列表");
+ }
+}
diff --git a/docs/reports/implementation/2026-03-23-credit-info-maintenance-frontend-implementation.md b/docs/reports/implementation/2026-03-23-credit-info-maintenance-frontend-implementation.md
index 28a32d36..776e0324 100644
--- a/docs/reports/implementation/2026-03-23-credit-info-maintenance-frontend-implementation.md
+++ b/docs/reports/implementation/2026-03-23-credit-info-maintenance-frontend-implementation.md
@@ -10,9 +10,14 @@
- `ruoyi-ui/src/api/ccdiCreditInfo.js`
- `ruoyi-ui/src/views/ccdiCreditInfo/index.vue`
+- `ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiCreditInfoQueryMapper.xml`
+- `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/mapper/CcdiCreditInfoQueryMapperXmlTest.java`
- `ruoyi-ui/tests/unit/credit-info-api-contract.test.js`
+- `ruoyi-ui/tests/unit/credit-info-date-display.test.js`
+- `ruoyi-ui/tests/unit/credit-info-maintained-filter.test.js`
- `ruoyi-ui/tests/unit/credit-info-page-layout.test.js`
- `ruoyi-ui/tests/unit/credit-info-upload-ui.test.js`
+- `ruoyi-ui/tests/unit/credit-info-upload-api-behavior.test.js`
- `ruoyi-ui/tests/unit/credit-info-detail-ui.test.js`
## 3. 功能落地说明
@@ -21,6 +26,7 @@
- 新增征信上传、列表、详情、删除四个接口封装
- 上传接口补充 `multipart/form-data` 请求头,字段名按后端约定使用 `files`
+- 列表查询后端固定仅返回已维护征信的员工数据,不再返回未维护员工空记录
### 3.2 页面结构
@@ -28,6 +34,8 @@
- 页面顶部包含姓名、柜员号、身份证号、是否已维护查询条件
- 页面中部按员工维度展示征信摘要列表
- 操作列提供“详情”“删除”入口
+- 列表默认仅展示已维护征信的员工,不再展示未维护员工空记录
+- 列表与详情中的征信查询日期统一按 `yyyy-MM-dd` 展示,避免直接渲染原始时间串导致展示不一致
### 3.3 上传交互
@@ -35,6 +43,9 @@
- 支持一次选择多个 `.html/.htm` 文件
- 选择与提交时均校验文件后缀
- 上传结果展示总文件数、成功数、失败数与失败清单
+- 上传请求调整为由 API 层统一构造 `FormData`,页面层仅传递原始文件数组
+- 上传接口显式声明 `multipart/form-data` 请求头,保证若依前端请求封装下后端能按 multipart 请求识别
+- 上传成功后主动清空已选文件,避免同批次文件再次选择时表现异常
### 3.4 详情与删除
@@ -49,15 +60,20 @@
执行命令:
```bash
+node ruoyi-ui/tests/unit/credit-info-date-display.test.js
+node ruoyi-ui/tests/unit/credit-info-maintained-filter.test.js
node ruoyi-ui/tests/unit/credit-info-api-contract.test.js
node ruoyi-ui/tests/unit/credit-info-page-layout.test.js
node ruoyi-ui/tests/unit/credit-info-upload-ui.test.js
+node ruoyi-ui/tests/unit/credit-info-upload-api-behavior.test.js
node ruoyi-ui/tests/unit/credit-info-detail-ui.test.js
+mvn -pl ccdi-info-collection -Dtest=CcdiCreditInfoQueryMapperXmlTest test
```
执行结果:
-- 四个脚本均通过
+- 前端七个源码契约脚本均通过
+- 后端 `CcdiCreditInfoQueryMapperXmlTest` 通过
### 4.2 前端构建
@@ -82,8 +98,8 @@ npm run build:prod
## 6. 进程与环境说明
-- 本次仅执行源码契约测试与生产构建,未启动本地前端 dev server
-- 未启动额外后端或 Mock 进程,因此无残留测试进程需要关闭
+- 本次执行了源码契约测试、后端 XML 契约测试与前端生产构建
+- 调试过程中启动过本地前端 dev server,验证结束后已主动关闭,无残留测试进程
## 7. 提交记录
diff --git a/ruoyi-ui/src/api/ccdiCreditInfo.js b/ruoyi-ui/src/api/ccdiCreditInfo.js
index d3fe2b42..0971c9c8 100644
--- a/ruoyi-ui/src/api/ccdiCreditInfo.js
+++ b/ruoyi-ui/src/api/ccdiCreditInfo.js
@@ -1,13 +1,18 @@
import request from '@/utils/request'
-export function uploadCreditHtml(data) {
+export function uploadCreditHtml(files) {
+ const formData = new FormData()
+ files.forEach((file) => {
+ formData.append('files', file)
+ })
+
return request({
url: '/ccdi/creditInfo/upload',
method: 'post',
+ data: formData,
headers: {
'Content-Type': 'multipart/form-data'
- },
- data
+ }
})
}
diff --git a/ruoyi-ui/src/views/ccdiCreditInfo/index.vue b/ruoyi-ui/src/views/ccdiCreditInfo/index.vue
index faf632fa..d7a9fc54 100644
--- a/ruoyi-ui/src/views/ccdiCreditInfo/index.vue
+++ b/ruoyi-ui/src/views/ccdiCreditInfo/index.vue
@@ -31,9 +31,8 @@
/>
-
+
-
@@ -53,7 +52,11 @@
-
+
+
+ {{ formatQueryDate(scope.row.queryDate) }}
+
+
@@ -130,7 +133,7 @@
征信摘要
- 征信查询日期:{{ detailForm.queryDate || "-" }}
+ 征信查询日期:{{ formatQueryDate(detailForm.queryDate) }}
负债笔数:{{ detailForm.debtCount || 0 }}
负债总额:{{ detailForm.debtTotalAmount || 0 }}
@@ -207,7 +210,7 @@ export default {
name: undefined,
staffId: undefined,
idCard: undefined,
- maintained: undefined,
+ maintained: "1",
},
};
},
@@ -239,7 +242,7 @@ export default {
name: undefined,
staffId: undefined,
idCard: undefined,
- maintained: undefined,
+ maintained: "1",
};
this.resetForm("queryForm");
this.handleQuery();
@@ -274,12 +277,9 @@ export default {
this.$modal.msgError("请先选择征信 HTML 文件");
return;
}
- const formData = new FormData();
- this.uploadFileList.forEach((file) => {
- formData.append("files", file.raw || file);
- });
+ const files = this.uploadFileList.map((file) => file.raw || file);
this.uploadSubmitting = true;
- return uploadCreditHtml(formData)
+ return uploadCreditHtml(files)
.then((response) => {
const data = response.data || {};
this.uploadResult = {
@@ -289,6 +289,10 @@ export default {
};
this.failureList = data.failures || [];
this.$modal.msgSuccess(response.msg || "上传成功");
+ this.uploadFileList = [];
+ if (this.$refs.upload) {
+ this.$refs.upload.clearFiles();
+ }
this.getList();
})
.finally(() => {
@@ -302,7 +306,7 @@ export default {
this.detailForm = {
personId: data.personId || row.idCard,
personName: data.personName || row.name,
- queryDate: negativeInfo.queryDate || row.queryDate,
+ queryDate: data.queryDate || negativeInfo.queryDate || row.queryDate,
debtCount: row.debtCount || (data.debtList || []).length,
debtTotalAmount: row.debtTotalAmount || 0,
civilCnt: negativeInfo.civilCnt || row.civilCnt || 0,
@@ -327,6 +331,18 @@ export default {
this.getList();
});
},
+ 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 this.parseTime(value, "{y}-{m}-{d}") || "-";
+ },
},
};
diff --git a/ruoyi-ui/tests/unit/credit-info-api-contract.test.js b/ruoyi-ui/tests/unit/credit-info-api-contract.test.js
index 727ee455..6cafe9e1 100644
--- a/ruoyi-ui/tests/unit/credit-info-api-contract.test.js
+++ b/ruoyi-ui/tests/unit/credit-info-api-contract.test.js
@@ -8,7 +8,7 @@ assert(fs.existsSync(apiPath), "未找到征信维护 API 文件 ccdiCreditInfo.
const source = fs.readFileSync(apiPath, "utf8");
[
- "export function uploadCreditHtml(data)",
+ "export function uploadCreditHtml(files)",
"export function listCreditInfo(query)",
"export function getCreditInfoDetail(personId)",
"export function deleteCreditInfo(personId)",
diff --git a/ruoyi-ui/tests/unit/credit-info-date-display.test.js b/ruoyi-ui/tests/unit/credit-info-date-display.test.js
new file mode 100644
index 00000000..0f4fc7dd
--- /dev/null
+++ b/ruoyi-ui/tests/unit/credit-info-date-display.test.js
@@ -0,0 +1,21 @@
+const assert = require("assert");
+const fs = require("fs");
+const path = require("path");
+
+const componentPath = path.resolve(
+ __dirname,
+ "../../src/views/ccdiCreditInfo/index.vue"
+);
+const source = fs.readFileSync(componentPath, "utf8");
+
+[
+ "formatQueryDate(value)",
+ "const matched = value.match(/^(",
+ 'this.parseTime(value, "{y}-{m}-{d}")',
+ "{{ formatQueryDate(scope.row.queryDate) }}",
+ "{{ formatQueryDate(detailForm.queryDate) }}",
+].forEach((token) => {
+ assert(source.includes(token), `征信时间展示缺少关键实现: ${token}`);
+});
+
+console.log("credit-info-date-display test passed");
diff --git a/ruoyi-ui/tests/unit/credit-info-maintained-filter.test.js b/ruoyi-ui/tests/unit/credit-info-maintained-filter.test.js
new file mode 100644
index 00000000..a0c83325
--- /dev/null
+++ b/ruoyi-ui/tests/unit/credit-info-maintained-filter.test.js
@@ -0,0 +1,21 @@
+const assert = require("assert");
+const fs = require("fs");
+const path = require("path");
+
+const componentPath = path.resolve(
+ __dirname,
+ "../../src/views/ccdiCreditInfo/index.vue"
+);
+const source = fs.readFileSync(componentPath, "utf8");
+
+assert(
+ source.includes('maintained: "1"'),
+ "征信维护页面应默认只查询已维护数据"
+);
+
+assert(
+ !source.includes('label="未维护"'),
+ "征信维护页面不应再提供未维护筛选项"
+);
+
+console.log("credit-info-maintained-filter test passed");
diff --git a/ruoyi-ui/tests/unit/credit-info-upload-api-behavior.test.js b/ruoyi-ui/tests/unit/credit-info-upload-api-behavior.test.js
new file mode 100644
index 00000000..c788428f
--- /dev/null
+++ b/ruoyi-ui/tests/unit/credit-info-upload-api-behavior.test.js
@@ -0,0 +1,37 @@
+const assert = require("assert");
+const fs = require("fs");
+const path = require("path");
+
+const apiPath = path.resolve(__dirname, "../../src/api/ccdiCreditInfo.js");
+const pagePath = path.resolve(__dirname, "../../src/views/ccdiCreditInfo/index.vue");
+
+const apiSource = fs.readFileSync(apiPath, "utf8");
+const pageSource = fs.readFileSync(pagePath, "utf8");
+
+assert(
+ apiSource.includes("const formData = new FormData()"),
+ "征信上传 API 应在接口层构造 FormData,避免页面层直接传裸 FormData"
+);
+assert(
+ apiSource.includes("files.forEach((file) => {"),
+ "征信上传 API 应逐个追加批量文件"
+);
+assert(
+ apiSource.includes("formData.append('files', file)"),
+ "征信上传 API 应按后端约定使用 files 字段名"
+);
+assert(
+ apiSource.includes("'Content-Type': 'multipart/form-data'"),
+ "征信上传 API 必须显式声明 multipart/form-data 请求头"
+);
+assert(
+ pageSource.includes("const files = this.uploadFileList.map((file) => file.raw || file);") &&
+ pageSource.includes("uploadCreditHtml(files)"),
+ "征信上传页面应把原始文件数组交给 API 层处理"
+);
+assert(
+ !pageSource.includes("const formData = new FormData();"),
+ "征信上传页面不应自行拼装 FormData"
+);
+
+console.log("credit-info-upload-api-behavior test passed");