问题: - 导入成功条数显示为负数 - 原因:成功数量计算使用 validRecords.size() - failures.size() - 但没有使用实际的数据库操作返回值 修复: - saveBatchWithUpsert 和 saveBatch 方法现在返回 int - 累加实际的数据库影响行数 - 使用 actualSuccessCount 变量跟踪真实成功数量 影响范围: - CcdiIntermediaryPersonImportServiceImpl - CcdiIntermediaryEntityImportServiceImpl
13 KiB
13 KiB
中介库导入失败记录查看功能设计
1. 需求背景
当前中介库导入功能在导入失败后,只显示通知消息,但没有提供查看失败记录的入口,用户无法了解具体哪些数据导入失败以及失败原因。
2. 功能描述
为中介库管理页面添加导入失败记录查看功能,支持个人中介和实体中介两种类型的失败记录查看。
2.1 核心功能
-
双按钮独立管理
- "查看个人导入失败记录"按钮 - 仅在个人中介导入存在失败记录时显示
- "查看实体导入失败记录"按钮 - 仅在实体中介导入存在失败记录时显示
- 按钮带tooltip提示上次导入时间
-
localStorage持久化存储
- 分别存储个人中介和实体中介的导入任务信息
- 存储期限:7天,过期自动清除
- 存储内容:任务ID、导入时间、成功数、失败数、hasFailures标志
-
失败记录对话框
- 显示导入统计摘要(总数/成功/失败)
- 表格展示所有失败记录,支持分页(每页10条)
- 提供清除历史记录按钮
- 记录过期时自动提示并清除
3. 技术设计
3.1 组件结构
index.vue (中介库管理页面)
├── 工具栏按钮区域
│ ├── 新增按钮
│ ├── 导入按钮
│ ├── 查看个人导入失败记录按钮 (条件显示)
│ └── 查看实体导入失败记录按钮 (条件显示)
├── 数据表格
├── 个人中介导入失败记录对话框
└── 实体中介导入失败记录对话框
3.2 数据流程
用户选择文件上传
↓
ImportDialog 组件提交导入
↓
后端返回 taskId (异步处理)
↓
前端开始轮询导入状态
↓
导入完成,ImportDialog 触发 @import-complete 事件
↓
index.vue 接收事件,根据 importType 判断类型
↓
保存任务信息到 localStorage (person 或 entity)
↓
更新对应的失败记录按钮显示状态
↓
用户点击"查看失败记录"按钮
↓
调用后端接口获取失败记录列表 (支持分页)
↓
在对话框中展示失败记录和错误原因
3.3 localStorage存储设计
3.3.1 个人中介导入任务
Key: intermediary_person_import_last_task
数据结构:
{
taskId: "uuid", // 任务ID
saveTime: 1234567890, // 保存时间戳
hasFailures: true, // 是否有失败记录
totalCount: 100, // 总数
successCount: 95, // 成功数
failureCount: 5 // 失败数
}
3.3.2 实体中介导入任务
Key: intermediary_entity_import_last_task
数据结构: 同个人中介
3.4 页面状态管理
data() {
return {
// 按钮显示状态
showPersonFailureButton: false,
showEntityFailureButton: false,
// 当前任务ID
currentPersonTaskId: null,
currentEntityTaskId: null,
// 个人失败记录对话框
personFailureDialogVisible: false,
personFailureList: [],
personFailureLoading: false,
personFailureTotal: 0,
personFailureQueryParams: {
pageNum: 1,
pageSize: 10
},
// 实体失败记录对话框
entityFailureDialogVisible: false,
entityFailureList: [],
entityFailureLoading: false,
entityFailureTotal: 0,
entityFailureQueryParams: {
pageNum: 1,
pageSize: 10
}
}
}
4. 接口依赖
4.1 已有后端接口
4.1.1 查询个人中介导入失败记录
接口: GET /ccdi/intermediary/importPersonFailures/{taskId}
参数:
taskId: 任务ID (路径参数)pageNum: 页码 (默认1)pageSize: 每页大小 (默认10)
返回: IntermediaryPersonImportFailureVO[]
字段:
name: 姓名personId: 证件号码personType: 人员类型gender: 性别mobile: 手机号码company: 所在公司errorMessage: 错误信息
4.1.2 查询实体中介导入失败记录
接口: GET /ccdi/intermediary/importEntityFailures/{taskId}
参数:
taskId: 任务ID (路径参数)pageNum: 页码 (默认1)pageSize: 每页大小 (默认10)
返回: IntermediaryEntityImportFailureVO[]
字段:
enterpriseName: 机构名称socialCreditCode: 统一社会信用代码enterpriseType: 主体类型enterpriseNature: 企业性质legalRepresentative: 法定代表人establishDate: 成立日期errorMessage: 错误信息
4.2 前端API方法
已有API方法 (位于 @/api/ccdiIntermediary.js):
getPersonImportFailures(taskId, pageNum, pageSize)- 查询个人导入失败记录getEntityImportFailures(taskId, pageNum, pageSize)- 查询实体导入失败记录
5. UI设计
5.1 工具栏按钮
<el-col :span="1.5" v-if="showPersonFailureButton">
<el-tooltip :content="getPersonImportTooltip()" placement="top">
<el-button
type="warning"
plain
icon="el-icon-warning"
size="mini"
@click="viewPersonImportFailures"
>查看个人导入失败记录</el-button>
</el-tooltip>
</el-col>
<el-col :span="1.5" v-if="showEntityFailureButton">
<el-tooltip :content="getEntityImportTooltip()" placement="top">
<el-button
type="warning"
plain
icon="el-icon-warning"
size="mini"
@click="viewEntityImportFailures"
>查看实体导入失败记录</el-button>
</el-tooltip>
</el-col>
5.2 失败记录对话框
个人中介失败记录对话框:
- 标题: "个人中介导入失败记录"
- 顶部提示: 显示导入统计信息
- 表格列: 姓名、证件号码、人员类型、性别、手机号码、所在公司、失败原因(最小宽度200px,溢出显示tooltip)
- 分页组件: 支持翻页
- 底部按钮: "关闭"、"清除历史记录"
实体中介失败记录对话框:
- 标题: "实体中介导入失败记录"
- 顶部提示: 显示导入统计信息
- 表格列: 机构名称、统一社会信用代码、主体类型、企业性质、法定代表人、成立日期、失败原因(最小宽度200px,溢出显示tooltip)
- 分页组件: 支持翻页
- 底部按钮: "关闭"、"清除历史记录"
6. 核心方法设计
6.1 localStorage管理方法
6.1.1 个人中介导入任务
/** 保存个人导入任务到localStorage */
savePersonImportTaskToStorage(taskData) {
const data = {
...taskData,
saveTime: Date.now()
}
localStorage.setItem('intermediary_person_import_last_task', JSON.stringify(data))
}
/** 从localStorage读取个人导入任务 */
getPersonImportTaskFromStorage() {
try {
const data = localStorage.getItem('intermediary_person_import_last_task')
if (!data) return null
const task = JSON.parse(data)
// 7天过期检查
const sevenDays = 7 * 24 * 60 * 60 * 1000
if (Date.now() - task.saveTime > sevenDays) {
this.clearPersonImportTaskFromStorage()
return null
}
return task
} catch (error) {
console.error('读取个人导入任务失败:', error)
this.clearPersonImportTaskFromStorage()
return null
}
}
/** 清除个人导入任务 */
clearPersonImportTaskFromStorage() {
localStorage.removeItem('intermediary_person_import_last_task')
}
6.1.2 实体中介导入任务
结构同个人中介,方法名为:
saveEntityImportTaskToStorage(taskData)getEntityImportTaskFromStorage()clearEntityImportTaskFromStorage()
6.2 导入完成处理
/** 处理导入完成 */
handleImportComplete(importData) {
const { taskId, hasFailures, importType, totalCount, successCount, failureCount } = importData
if (importType === 'person') {
// 保存个人导入任务
this.savePersonImportTaskToStorage({
taskId,
hasFailures,
totalCount,
successCount,
failureCount
})
// 更新按钮显示
this.showPersonFailureButton = hasFailures
this.currentPersonTaskId = taskId
} else if (importType === 'entity') {
// 保存实体导入任务
this.saveEntityImportTaskToStorage({
taskId,
hasFailures,
totalCount,
successCount,
failureCount
})
// 更新按钮显示
this.showEntityFailureButton = hasFailures
this.currentEntityTaskId = taskId
}
// 刷新列表
this.getList()
}
6.3 查看失败记录
/** 查看个人导入失败记录 */
viewPersonImportFailures() {
this.personFailureDialogVisible = true
this.getPersonFailureList()
}
/** 查询个人失败记录列表 */
getPersonFailureList() {
this.personFailureLoading = true
getPersonImportFailures(
this.currentPersonTaskId,
this.personFailureQueryParams.pageNum,
this.personFailureQueryParams.pageSize
).then(response => {
this.personFailureList = response.rows
this.personFailureTotal = response.total
this.personFailureLoading = false
}).catch(error => {
this.personFailureLoading = false
// 错误处理: 404表示记录已过期
if (error.response?.status === 404) {
this.$modal.msgWarning('导入记录已过期,无法查看失败记录')
this.clearPersonImportTaskFromStorage()
this.showPersonFailureButton = false
this.personFailureDialogVisible = false
} else {
this.$modal.msgError('查询失败记录失败')
}
})
}
6.4 清除历史记录
/** 清除个人导入历史记录 */
clearPersonImportHistory() {
this.$confirm('确认清除上次导入记录?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.clearPersonImportTaskFromStorage()
this.showPersonFailureButton = false
this.currentPersonTaskId = null
this.personFailureDialogVisible = false
this.$message.success('已清除')
}).catch(() => {})
}
7. 生命周期管理
7.1 created钩子
created() {
this.getList()
this.loadEnumOptions()
this.restoreImportState() // 恢复导入状态
}
7.2 恢复导入状态
/** 恢复导入状态 */
restoreImportState() {
// 恢复个人中介导入状态
const personTask = this.getPersonImportTaskFromStorage()
if (personTask && personTask.hasFailures && personTask.taskId) {
this.currentPersonTaskId = personTask.taskId
this.showPersonFailureButton = true
}
// 恢复实体中介导入状态
const entityTask = this.getEntityImportTaskFromStorage()
if (entityTask && entityTask.hasFailures && entityTask.taskId) {
this.currentEntityTaskId = entityTask.taskId
this.showEntityFailureButton = true
}
}
8. 边界情况处理
8.1 记录过期
- localStorage中存储的记录超过7天,自动清除
- 后端接口返回404时,提示用户"导入记录已过期",并清除本地存储
- 清除后隐藏对应的"查看失败记录"按钮
8.2 并发导入
- 每次新导入开始前,清除旧的导入记录
- 同一类型的导入进行时,取消之前的轮询
- 只保留最近一次的导入任务信息
8.3 网络错误
- 查询失败记录时网络错误,显示友好的错误提示
- 不影响页面其他功能的正常使用
9. 测试要点
9.1 功能测试
-
个人中介导入失败场景
- 导入包含错误数据的Excel文件
- 验证失败记录按钮是否显示
- 点击按钮查看失败记录
- 验证失败原因是否正确显示
-
实体中介导入失败场景
- 导入包含错误数据的Excel文件
- 验证失败记录按钮是否显示
- 点击按钮查看失败记录
- 验证失败原因是否正确显示
-
localStorage持久化
- 导入失败后刷新页面
- 验证"查看失败记录"按钮是否仍然显示
- 验证点击后能否正常查看失败记录
-
分页功能
- 失败记录超过10条时
- 验证分页组件是否正常工作
- 验证翻页后数据是否正确
-
清除历史记录
- 点击"清除历史记录"按钮
- 验证localStorage是否清除
- 验证按钮是否隐藏
- 再次点击导入,验证新记录是否正常
-
记录过期处理
- 手动修改localStorage中的saveTime模拟过期
- 刷新页面,验证按钮是否隐藏
- 或点击查看,验证是否提示"记录已过期"
9.2 兼容性测试
-
浏览器兼容性
- Chrome
- Firefox
- Edge
- Safari
-
数据量大时性能测试
- 导入1000条数据,其中100条失败
- 验证查询速度和渲染性能
10. 参考实现
本设计参考了员工管理页面 (ccdiEmployee/index.vue) 的导入失败记录查看功能的实现,主要参考点:
- localStorage存储模式
- 失败记录对话框布局
- 分页查询逻辑
- 错误处理机制
- 过期记录清理逻辑
11. 变更历史
| 日期 | 版本 | 变更内容 | 作者 |
|---|---|---|---|
| 2026-02-08 | 1.0 | 初始设计 | Claude |