Files
ccdi/doc/intermediary-import-failure-view-design.md
wkc 5ec5913759 fix: 修复中介导入成功条数计算错误
问题:
- 导入成功条数显示为负数
- 原因:成功数量计算使用 validRecords.size() - failures.size()
- 但没有使用实际的数据库操作返回值

修复:
- saveBatchWithUpsert 和 saveBatch 方法现在返回 int
- 累加实际的数据库影响行数
- 使用 actualSuccessCount 变量跟踪真实成功数量

影响范围:
- CcdiIntermediaryPersonImportServiceImpl
- CcdiIntermediaryEntityImportServiceImpl
2026-02-08 17:18:18 +08:00

13 KiB

中介库导入失败记录查看功能设计

1. 需求背景

当前中介库导入功能在导入失败后,只显示通知消息,但没有提供查看失败记录的入口,用户无法了解具体哪些数据导入失败以及失败原因。

2. 功能描述

为中介库管理页面添加导入失败记录查看功能,支持个人中介和实体中介两种类型的失败记录查看。

2.1 核心功能

  1. 双按钮独立管理

    • "查看个人导入失败记录"按钮 - 仅在个人中介导入存在失败记录时显示
    • "查看实体导入失败记录"按钮 - 仅在实体中介导入存在失败记录时显示
    • 按钮带tooltip提示上次导入时间
  2. localStorage持久化存储

    • 分别存储个人中介和实体中介的导入任务信息
    • 存储期限:7天,过期自动清除
    • 存储内容:任务ID、导入时间、成功数、失败数、hasFailures标志
  3. 失败记录对话框

    • 显示导入统计摘要(总数/成功/失败)
    • 表格展示所有失败记录,支持分页(每页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 功能测试

  1. 个人中介导入失败场景

    • 导入包含错误数据的Excel文件
    • 验证失败记录按钮是否显示
    • 点击按钮查看失败记录
    • 验证失败原因是否正确显示
  2. 实体中介导入失败场景

    • 导入包含错误数据的Excel文件
    • 验证失败记录按钮是否显示
    • 点击按钮查看失败记录
    • 验证失败原因是否正确显示
  3. localStorage持久化

    • 导入失败后刷新页面
    • 验证"查看失败记录"按钮是否仍然显示
    • 验证点击后能否正常查看失败记录
  4. 分页功能

    • 失败记录超过10条时
    • 验证分页组件是否正常工作
    • 验证翻页后数据是否正确
  5. 清除历史记录

    • 点击"清除历史记录"按钮
    • 验证localStorage是否清除
    • 验证按钮是否隐藏
    • 再次点击导入,验证新记录是否正常
  6. 记录过期处理

    • 手动修改localStorage中的saveTime模拟过期
    • 刷新页面,验证按钮是否隐藏
    • 或点击查看,验证是否提示"记录已过期"

9.2 兼容性测试

  1. 浏览器兼容性

    • Chrome
    • Firefox
    • Edge
    • Safari
  2. 数据量大时性能测试

    • 导入1000条数据,其中100条失败
    • 验证查询速度和渲染性能

10. 参考实现

本设计参考了员工管理页面 (ccdiEmployee/index.vue) 的导入失败记录查看功能的实现,主要参考点:

  1. localStorage存储模式
  2. 失败记录对话框布局
  3. 分页查询逻辑
  4. 错误处理机制
  5. 过期记录清理逻辑

11. 变更历史

日期 版本 变更内容 作者
2026-02-08 1.0 初始设计 Claude