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 @@ - + + + @@ -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");