修正征信维护列表筛选与上传展示逻辑
This commit is contained in:
BIN
assets/征信解析/ccdi_debts_info.xlsx
Normal file
BIN
assets/征信解析/ccdi_debts_info.xlsx
Normal file
Binary file not shown.
@@ -29,6 +29,7 @@
|
|||||||
) debt_agg ON debt_agg.person_id = s.id_card
|
) debt_agg ON debt_agg.person_id = s.id_card
|
||||||
LEFT JOIN ccdi_credit_negative_info neg ON neg.person_id = s.id_card
|
LEFT JOIN ccdi_credit_negative_info neg ON neg.person_id = s.id_card
|
||||||
<where>
|
<where>
|
||||||
|
AND (debt_agg.person_id IS NOT NULL OR neg.person_id IS NOT NULL)
|
||||||
<if test="query != null and query.name != null and query.name != ''">
|
<if test="query != null and query.name != null and query.name != ''">
|
||||||
AND s.name LIKE CONCAT('%', #{query.name}, '%')
|
AND s.name LIKE CONCAT('%', #{query.name}, '%')
|
||||||
</if>
|
</if>
|
||||||
@@ -38,13 +39,6 @@
|
|||||||
<if test="query != null and query.idCard != null and query.idCard != ''">
|
<if test="query != null and query.idCard != null and query.idCard != ''">
|
||||||
AND s.id_card LIKE CONCAT('%', #{query.idCard}, '%')
|
AND s.id_card LIKE CONCAT('%', #{query.idCard}, '%')
|
||||||
</if>
|
</if>
|
||||||
<if test="query != null and query.maintained == '1'">
|
|
||||||
AND (debt_agg.person_id IS NOT NULL OR neg.person_id IS NOT NULL)
|
|
||||||
</if>
|
|
||||||
<if test="query != null and query.maintained == '0'">
|
|
||||||
AND debt_agg.person_id IS NULL
|
|
||||||
AND neg.person_id IS NULL
|
|
||||||
</if>
|
|
||||||
</where>
|
</where>
|
||||||
ORDER BY debt_agg.query_date DESC, s.staff_id DESC
|
ORDER BY debt_agg.query_date DESC, s.staff_id DESC
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@@ -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'"),
|
||||||
|
"征信维护列表不应再支持未维护员工列表");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,9 +10,14 @@
|
|||||||
|
|
||||||
- `ruoyi-ui/src/api/ccdiCreditInfo.js`
|
- `ruoyi-ui/src/api/ccdiCreditInfo.js`
|
||||||
- `ruoyi-ui/src/views/ccdiCreditInfo/index.vue`
|
- `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-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-page-layout.test.js`
|
||||||
- `ruoyi-ui/tests/unit/credit-info-upload-ui.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`
|
- `ruoyi-ui/tests/unit/credit-info-detail-ui.test.js`
|
||||||
|
|
||||||
## 3. 功能落地说明
|
## 3. 功能落地说明
|
||||||
@@ -21,6 +26,7 @@
|
|||||||
|
|
||||||
- 新增征信上传、列表、详情、删除四个接口封装
|
- 新增征信上传、列表、详情、删除四个接口封装
|
||||||
- 上传接口补充 `multipart/form-data` 请求头,字段名按后端约定使用 `files`
|
- 上传接口补充 `multipart/form-data` 请求头,字段名按后端约定使用 `files`
|
||||||
|
- 列表查询后端固定仅返回已维护征信的员工数据,不再返回未维护员工空记录
|
||||||
|
|
||||||
### 3.2 页面结构
|
### 3.2 页面结构
|
||||||
|
|
||||||
@@ -28,6 +34,8 @@
|
|||||||
- 页面顶部包含姓名、柜员号、身份证号、是否已维护查询条件
|
- 页面顶部包含姓名、柜员号、身份证号、是否已维护查询条件
|
||||||
- 页面中部按员工维度展示征信摘要列表
|
- 页面中部按员工维度展示征信摘要列表
|
||||||
- 操作列提供“详情”“删除”入口
|
- 操作列提供“详情”“删除”入口
|
||||||
|
- 列表默认仅展示已维护征信的员工,不再展示未维护员工空记录
|
||||||
|
- 列表与详情中的征信查询日期统一按 `yyyy-MM-dd` 展示,避免直接渲染原始时间串导致展示不一致
|
||||||
|
|
||||||
### 3.3 上传交互
|
### 3.3 上传交互
|
||||||
|
|
||||||
@@ -35,6 +43,9 @@
|
|||||||
- 支持一次选择多个 `.html/.htm` 文件
|
- 支持一次选择多个 `.html/.htm` 文件
|
||||||
- 选择与提交时均校验文件后缀
|
- 选择与提交时均校验文件后缀
|
||||||
- 上传结果展示总文件数、成功数、失败数与失败清单
|
- 上传结果展示总文件数、成功数、失败数与失败清单
|
||||||
|
- 上传请求调整为由 API 层统一构造 `FormData`,页面层仅传递原始文件数组
|
||||||
|
- 上传接口显式声明 `multipart/form-data` 请求头,保证若依前端请求封装下后端能按 multipart 请求识别
|
||||||
|
- 上传成功后主动清空已选文件,避免同批次文件再次选择时表现异常
|
||||||
|
|
||||||
### 3.4 详情与删除
|
### 3.4 详情与删除
|
||||||
|
|
||||||
@@ -49,15 +60,20 @@
|
|||||||
执行命令:
|
执行命令:
|
||||||
|
|
||||||
```bash
|
```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-api-contract.test.js
|
||||||
node ruoyi-ui/tests/unit/credit-info-page-layout.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-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
|
node ruoyi-ui/tests/unit/credit-info-detail-ui.test.js
|
||||||
|
mvn -pl ccdi-info-collection -Dtest=CcdiCreditInfoQueryMapperXmlTest test
|
||||||
```
|
```
|
||||||
|
|
||||||
执行结果:
|
执行结果:
|
||||||
|
|
||||||
- 四个脚本均通过
|
- 前端七个源码契约脚本均通过
|
||||||
|
- 后端 `CcdiCreditInfoQueryMapperXmlTest` 通过
|
||||||
|
|
||||||
### 4.2 前端构建
|
### 4.2 前端构建
|
||||||
|
|
||||||
@@ -82,8 +98,8 @@ npm run build:prod
|
|||||||
|
|
||||||
## 6. 进程与环境说明
|
## 6. 进程与环境说明
|
||||||
|
|
||||||
- 本次仅执行源码契约测试与生产构建,未启动本地前端 dev server
|
- 本次执行了源码契约测试、后端 XML 契约测试与前端生产构建
|
||||||
- 未启动额外后端或 Mock 进程,因此无残留测试进程需要关闭
|
- 调试过程中启动过本地前端 dev server,验证结束后已主动关闭,无残留测试进程
|
||||||
|
|
||||||
## 7. 提交记录
|
## 7. 提交记录
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
import request from '@/utils/request'
|
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({
|
return request({
|
||||||
url: '/ccdi/creditInfo/upload',
|
url: '/ccdi/creditInfo/upload',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
|
data: formData,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'multipart/form-data'
|
'Content-Type': 'multipart/form-data'
|
||||||
},
|
}
|
||||||
data
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,9 +31,8 @@
|
|||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="是否已维护">
|
<el-form-item label="是否已维护">
|
||||||
<el-select v-model="queryParams.maintained" clearable placeholder="请选择是否已维护" style="width: 240px">
|
<el-select v-model="queryParams.maintained" placeholder="仅展示已维护数据" style="width: 240px">
|
||||||
<el-option label="已维护" value="1" />
|
<el-option label="已维护" value="1" />
|
||||||
<el-option label="未维护" value="0" />
|
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
@@ -53,7 +52,11 @@
|
|||||||
<el-table-column label="姓名" prop="name" align="center" />
|
<el-table-column label="姓名" prop="name" align="center" />
|
||||||
<el-table-column label="柜员号" prop="staffId" align="center" />
|
<el-table-column label="柜员号" prop="staffId" align="center" />
|
||||||
<el-table-column label="身份证号" prop="idCard" align="center" />
|
<el-table-column label="身份证号" prop="idCard" align="center" />
|
||||||
<el-table-column label="最近征信查询日期" prop="queryDate" align="center" width="160" />
|
<el-table-column label="最近征信查询日期" align="center" width="160">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
{{ formatQueryDate(scope.row.queryDate) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="负债笔数" prop="debtCount" align="center" width="100" />
|
<el-table-column label="负债笔数" prop="debtCount" align="center" width="100" />
|
||||||
<el-table-column label="负债总额" prop="debtTotalAmount" align="center" width="140" />
|
<el-table-column label="负债总额" prop="debtTotalAmount" align="center" width="140" />
|
||||||
<el-table-column label="民事案件笔数" prop="civilCnt" align="center" width="120" />
|
<el-table-column label="民事案件笔数" prop="civilCnt" align="center" width="120" />
|
||||||
@@ -130,7 +133,7 @@
|
|||||||
<el-dialog title="征信详情" :visible.sync="detailDialogVisible" width="960px" append-to-body>
|
<el-dialog title="征信详情" :visible.sync="detailDialogVisible" width="960px" append-to-body>
|
||||||
<div class="section-title">征信摘要</div>
|
<div class="section-title">征信摘要</div>
|
||||||
<el-row :gutter="16" class="detail-summary">
|
<el-row :gutter="16" class="detail-summary">
|
||||||
<el-col :span="8">征信查询日期:{{ detailForm.queryDate || "-" }}</el-col>
|
<el-col :span="8">征信查询日期:{{ formatQueryDate(detailForm.queryDate) }}</el-col>
|
||||||
<el-col :span="8">负债笔数:{{ detailForm.debtCount || 0 }}</el-col>
|
<el-col :span="8">负债笔数:{{ detailForm.debtCount || 0 }}</el-col>
|
||||||
<el-col :span="8">负债总额:{{ detailForm.debtTotalAmount || 0 }}</el-col>
|
<el-col :span="8">负债总额:{{ detailForm.debtTotalAmount || 0 }}</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
@@ -207,7 +210,7 @@ export default {
|
|||||||
name: undefined,
|
name: undefined,
|
||||||
staffId: undefined,
|
staffId: undefined,
|
||||||
idCard: undefined,
|
idCard: undefined,
|
||||||
maintained: undefined,
|
maintained: "1",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -239,7 +242,7 @@ export default {
|
|||||||
name: undefined,
|
name: undefined,
|
||||||
staffId: undefined,
|
staffId: undefined,
|
||||||
idCard: undefined,
|
idCard: undefined,
|
||||||
maintained: undefined,
|
maintained: "1",
|
||||||
};
|
};
|
||||||
this.resetForm("queryForm");
|
this.resetForm("queryForm");
|
||||||
this.handleQuery();
|
this.handleQuery();
|
||||||
@@ -274,12 +277,9 @@ export default {
|
|||||||
this.$modal.msgError("请先选择征信 HTML 文件");
|
this.$modal.msgError("请先选择征信 HTML 文件");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const formData = new FormData();
|
const files = this.uploadFileList.map((file) => file.raw || file);
|
||||||
this.uploadFileList.forEach((file) => {
|
|
||||||
formData.append("files", file.raw || file);
|
|
||||||
});
|
|
||||||
this.uploadSubmitting = true;
|
this.uploadSubmitting = true;
|
||||||
return uploadCreditHtml(formData)
|
return uploadCreditHtml(files)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
const data = response.data || {};
|
const data = response.data || {};
|
||||||
this.uploadResult = {
|
this.uploadResult = {
|
||||||
@@ -289,6 +289,10 @@ export default {
|
|||||||
};
|
};
|
||||||
this.failureList = data.failures || [];
|
this.failureList = data.failures || [];
|
||||||
this.$modal.msgSuccess(response.msg || "上传成功");
|
this.$modal.msgSuccess(response.msg || "上传成功");
|
||||||
|
this.uploadFileList = [];
|
||||||
|
if (this.$refs.upload) {
|
||||||
|
this.$refs.upload.clearFiles();
|
||||||
|
}
|
||||||
this.getList();
|
this.getList();
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
@@ -302,7 +306,7 @@ export default {
|
|||||||
this.detailForm = {
|
this.detailForm = {
|
||||||
personId: data.personId || row.idCard,
|
personId: data.personId || row.idCard,
|
||||||
personName: data.personName || row.name,
|
personName: data.personName || row.name,
|
||||||
queryDate: negativeInfo.queryDate || row.queryDate,
|
queryDate: data.queryDate || negativeInfo.queryDate || row.queryDate,
|
||||||
debtCount: row.debtCount || (data.debtList || []).length,
|
debtCount: row.debtCount || (data.debtList || []).length,
|
||||||
debtTotalAmount: row.debtTotalAmount || 0,
|
debtTotalAmount: row.debtTotalAmount || 0,
|
||||||
civilCnt: negativeInfo.civilCnt || row.civilCnt || 0,
|
civilCnt: negativeInfo.civilCnt || row.civilCnt || 0,
|
||||||
@@ -327,6 +331,18 @@ export default {
|
|||||||
this.getList();
|
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}") || "-";
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ assert(fs.existsSync(apiPath), "未找到征信维护 API 文件 ccdiCreditInfo.
|
|||||||
const source = fs.readFileSync(apiPath, "utf8");
|
const source = fs.readFileSync(apiPath, "utf8");
|
||||||
|
|
||||||
[
|
[
|
||||||
"export function uploadCreditHtml(data)",
|
"export function uploadCreditHtml(files)",
|
||||||
"export function listCreditInfo(query)",
|
"export function listCreditInfo(query)",
|
||||||
"export function getCreditInfoDetail(personId)",
|
"export function getCreditInfoDetail(personId)",
|
||||||
"export function deleteCreditInfo(personId)",
|
"export function deleteCreditInfo(personId)",
|
||||||
|
|||||||
21
ruoyi-ui/tests/unit/credit-info-date-display.test.js
Normal file
21
ruoyi-ui/tests/unit/credit-info-date-display.test.js
Normal file
@@ -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");
|
||||||
21
ruoyi-ui/tests/unit/credit-info-maintained-filter.test.js
Normal file
21
ruoyi-ui/tests/unit/credit-info-maintained-filter.test.js
Normal file
@@ -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");
|
||||||
37
ruoyi-ui/tests/unit/credit-info-upload-api-behavior.test.js
Normal file
37
ruoyi-ui/tests/unit/credit-info-upload-api-behavior.test.js
Normal file
@@ -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");
|
||||||
Reference in New Issue
Block a user