Files
ccdi/doc/plans/2026-02-06-employee-import-result-persistence.md
wkc 9e9733cf52 feat: 完成员工导入结果跨页面持久化功能
功能概述:
- 使用localStorage存储最近一次导入任务信息
- 支持切换菜单后查看上一次的导入失败记录
- 自动过期处理(7天)
- 完整的错误处理和用户友好的提示信息
- 新增清除历史记录功能

核心实现:
- saveImportTaskToStorage: 保存导入状态到localStorage
- getImportTaskFromStorage: 读取并验证导入状态
- clearImportTaskFromStorage: 清除localStorage数据
- restoreImportState: 页面加载时恢复导入状态
- getLastImportTooltip: 获取导入时间提示
- clearImportHistory: 用户手动清除历史记录

导入流程增强:
- handleFileSuccess: 保存初始状态,清除旧数据
- handleImportComplete: 保存完整状态,更新UI
- startImportStatusPolling: 添加5分钟超时机制

错误处理增强:
- getFailureList: 分类处理404/500/网络错误
- 404错误时自动清除localStorage并隐藏按钮
- 友好的用户提示信息

UI优化:
- lastImportInfo计算属性显示导入统计
- 失败记录按钮tooltip显示导入时间
- 失败记录对话框显示完整统计信息
- 对话框添加清除历史记录按钮

测试场景:
- 导入成功无失败后刷新页面
- 导入有失败后刷新页面
- 导入有失败后切换菜单
- 新导入覆盖旧记录
- 手动清除历史记录
- localStorage过期处理

相关提交:
- b932a7d docs: 添加员工导入结果跨页面持久化设计文档

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-06 13:40:39 +08:00

24 KiB

员工导入结果跨页面持久化实施计划

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

目标: 实现员工导入结果的跨页面持久化,使用户在切换菜单后仍能查看上一次的导入失败记录

架构: 使用浏览器localStorage存储最近一次导入的任务信息,在页面加载时恢复状态,实现导入状态的持久化保存

技术栈:

  • Vue 2.6.12
  • localStorage API
  • Element UI 2.15.14

前置准备

Task 0: 验证环境

Files:

  • 检查: ruoyi-ui/src/views/ccdiEmployee/index.vue

Step 1: 阅读现有代码

读取 ruoyi-ui/src/views/ccdiEmployee/index.vue 文件,特别关注:

  • data() 中的 showFailureButtoncurrentTaskIdpollingTimer 等状态变量
  • handleFileSuccess() 方法 - 导入上传成功处理
  • handleImportComplete() 方法 - 导入完成处理
  • getFailureList() 方法 - 查询失败记录
  • created()beforeDestroy() 生命周期钩子

确认当前实现确实存在状态丢失问题。

Step 2: 理解localStorage使用场景

理解需要持久化的数据:

{
  taskId: 'uuid',
  status: 'SUCCESS' | 'PARTIAL_SUCCESS' | 'FAILED',
  timestamp: 1707225900000,
  saveTime: 1707225900000,
  hasFailures: true,
  totalCount: 100,
  successCount: 95,
  failureCount: 5
}

Step 3: 无需提交

这只是验证步骤,无需提交代码。


核心功能实现

Task 1: 新增localStorage工具方法

Files:

  • Modify: ruoyi-ui/src/views/ccdiEmployee/index.vue (在 methods 对象中添加)

Step 1: 添加 saveImportTaskToStorage 方法

methods 对象中添加以下方法(放在 methods 的开头部分):

/**
 * 保存导入任务到localStorage
 * @param {Object} taskData - 任务数据
 */
saveImportTaskToStorage(taskData) {
  try {
    const data = {
      ...taskData,
      saveTime: Date.now()
    };
    localStorage.setItem('employee_import_last_task', JSON.stringify(data));
  } catch (error) {
    console.error('保存导入任务状态失败:', error);
  }
},

Step 2: 添加 getImportTaskFromStorage 方法

saveImportTaskToStorage 方法后添加:

/**
 * 从localStorage读取导入任务
 * @returns {Object|null} 任务数据或null
 */
getImportTaskFromStorage() {
  try {
    const data = localStorage.getItem('employee_import_last_task');
    if (!data) return null;

    const task = JSON.parse(data);

    // 数据格式校验
    if (!task || !task.taskId) {
      this.clearImportTaskFromStorage();
      return null;
    }

    // 时间戳校验
    if (task.saveTime && typeof task.saveTime !== 'number') {
      this.clearImportTaskFromStorage();
      return null;
    }

    // 过期检查(7天)
    const sevenDays = 7 * 24 * 60 * 60 * 1000;
    if (Date.now() - task.saveTime > sevenDays) {
      this.clearImportTaskFromStorage();
      return null;
    }

    return task;
  } catch (error) {
    console.error('读取导入任务状态失败:', error);
    this.clearImportTaskFromStorage();
    return null;
  }
},

Step 3: 添加 clearImportTaskFromStorage 方法

getImportTaskFromStorage 方法后添加:

/**
 * 清除localStorage中的导入任务
 */
clearImportTaskFromStorage() {
  try {
    localStorage.removeItem('employee_import_last_task');
  } catch (error) {
    console.error('清除导入任务状态失败:', error);
  }
},

Step 4: 手动测试 - 打开浏览器控制台验证

  1. 启动前端开发服务器: npm run dev (在 ruoyi-ui 目录)
  2. 打开浏览器,访问员工管理页面
  3. 打开浏览器开发者工具(F12),切换到 Console 标签
  4. 在控制台输入:
// 测试保存
localStorage.setItem('employee_import_last_task', JSON.stringify({
  taskId: 'test-123',
  status: 'SUCCESS',
  timestamp: Date.now(),
  saveTime: Date.now(),
  hasFailures: true,
  totalCount: 100,
  successCount: 95,
  failureCount: 5
}))

// 测试读取
JSON.parse(localStorage.getItem('employee_import_last_task'))

// 测试清除
localStorage.removeItem('employee_import_last_task')
  1. 确认每个操作都正常工作

Step 5: 提交

git add ruoyi-ui/src/views/ccdiEmployee/index.vue
git commit -m "feat: 添加localStorage工具方法用于导入状态持久化

- saveImportTaskToStorage: 保存导入任务到localStorage
- getImportTaskFromStorage: 读取并校验导入任务数据
- clearImportTaskFromStorage: 清除localStorage数据

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"

Task 2: 添加状态恢复和用户交互方法

Files:

  • Modify: ruoyi-ui/src/views/ccdiEmployee/index.vue

Step 1: 添加 restoreImportState 方法

clearImportTaskFromStorage 方法后添加:

/**
 * 恢复导入状态
 * 在created()钩子中调用
 */
async restoreImportState() {
  const savedTask = this.getImportTaskFromStorage();

  if (!savedTask) {
    this.showFailureButton = false;
    this.currentTaskId = null;
    return;
  }

  // 如果有失败记录,恢复按钮显示
  if (savedTask.hasFailures && savedTask.taskId) {
    this.currentTaskId = savedTask.taskId;
    this.showFailureButton = true;
  }
},

Step 2: 添加 getLastImportTooltip 方法

restoreImportState 方法后添加:

/**
 * 获取上次导入的提示信息
 * @returns {String} 提示文本
 */
getLastImportTooltip() {
  const savedTask = this.getImportTaskFromStorage();
  if (savedTask && savedTask.timestamp) {
    const date = new Date(savedTask.timestamp);
    const timeStr = this.parseTime(date, '{y}-{m}-{d} {h}:{i}');
    return `上次导入: ${timeStr}`;
  }
  return '';
},

Step 3: 添加 clearImportHistory 方法

getLastImportTooltip 方法后添加:

/**
 * 清除导入历史记录
 * 用户手动触发
 */
clearImportHistory() {
  this.$confirm('确认清除上次导入记录?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    this.clearImportTaskFromStorage();
    this.showFailureButton = false;
    this.currentTaskId = null;
    this.failureDialogVisible = false;
    this.$message.success('已清除');
  }).catch(() => {});
},

Step 4: 修改 created() 生命周期钩子

找到 created() 方法,在 this.getList(); 后添加:

created() {
  this.getList();
  this.getDeptTree();
  this.restoreImportState(); // 新增:恢复导入状态
},

Step 5: 手动测试 - 状态恢复功能

  1. 在浏览器控制台手动设置测试数据:
localStorage.setItem('employee_import_last_task', JSON.stringify({
  taskId: 'test-restore-123',
  status: 'PARTIAL_SUCCESS',
  timestamp: Date.now(),
  saveTime: Date.now(),
  hasFailures: true,
  totalCount: 100,
  successCount: 95,
  failureCount: 5
}))
  1. 刷新员工管理页面
  2. 确认"查看上次导入失败记录"按钮显示出来
  3. 打开Vue DevTools(如果有的话),检查 showFailureButtontrue, currentTaskId'test-restore-123'

Step 6: 提交

git add ruoyi-ui/src/views/ccdiEmployee/index.vue
git commit -m "feat: 添加导入状态恢复和用户交互方法

- restoreImportState: 从localStorage恢复导入状态
- getLastImportTooltip: 获取导入时间提示信息
- clearImportHistory: 用户手动清除历史记录
- created(): 添加状态恢复调用

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"

Task 3: 修改导入成功处理逻辑

Files:

  • Modify: ruoyi-ui/src/views/ccdiEmployee/index.vue

Step 1: 修改 handleFileSuccess 方法

找到 handleFileSuccess 方法,替换为:

// 文件上传成功处理
handleFileSuccess(response, file, fileList) {
  this.upload.isUploading = false;
  this.upload.open = false;

  if (response.code === 200) {
    const taskId = response.data.taskId;

    // 清除旧的导入记录(防止并发)
    if (this.pollingTimer) {
      clearInterval(this.pollingTimer);
      this.pollingTimer = null;
    }

    this.clearImportTaskFromStorage();

    // 保存新任务的初始状态
    this.saveImportTaskToStorage({
      taskId: taskId,
      status: 'PROCESSING',
      timestamp: Date.now(),
      hasFailures: false
    });

    // 重置状态
    this.showFailureButton = false;
    this.currentTaskId = taskId;

    // 显示后台处理提示
    this.$notify({
      title: '导入任务已提交',
      message: '正在后台处理中,处理完成后将通知您',
      type: 'info',
      duration: 3000
    });

    // 开始轮询检查状态
    this.startImportStatusPolling(taskId);
  } else {
    this.$modal.msgError(response.msg);
  }
},

关键改动:

  • 添加清除旧轮询定时器的逻辑
  • 调用 clearImportTaskFromStorage() 清除旧数据
  • 调用 saveImportTaskToStorage() 保存新任务初始状态
  • 重置 showFailureButtoncurrentTaskId

Step 2: 修改 handleImportComplete 方法

找到 handleImportComplete 方法,替换为:

/** 处理导入完成 */
handleImportComplete(statusResult) {
  const hasFailures = statusResult.failureCount > 0;

  // 更新localStorage中的任务状态
  this.saveImportTaskToStorage({
    taskId: statusResult.taskId,
    status: statusResult.status,
    timestamp: Date.now(),
    hasFailures: hasFailures,
    totalCount: statusResult.totalCount,
    successCount: statusResult.successCount,
    failureCount: statusResult.failureCount
  });

  if (statusResult.status === 'SUCCESS') {
    this.$notify({
      title: '导入完成',
      message: `全部成功!共导入${statusResult.totalCount}条数据`,
      type: 'success',
      duration: 5000
    });
    this.getList();
  } else if (hasFailures) {
    this.$notify({
      title: '导入完成',
      message: `成功${statusResult.successCount}条,失败${statusResult.failureCount}条`,
      type: 'warning',
      duration: 5000
    });

    // 显示查看失败记录按钮
    this.showFailureButton = true;
    this.currentTaskId = statusResult.taskId;

    // 刷新列表
    this.getList();
  }
},

关键改动:

  • 在方法开头调用 saveImportTaskToStorage() 更新完整状态

Step 3: 手动测试 - 导入流程

  1. 准备一个包含错误数据的Excel文件
  2. 打开浏览器开发者工具 > Application > Local Storage
  3. 上传Excel文件,开始导入
  4. 观察 Local Storage 中是否有 employee_import_last_task
  5. 等待导入完成
  6. 检查 localStorage 中的数据是否包含完整的统计信息(totalCount, successCount, failureCount)
  7. 刷新页面,确认按钮仍然显示

Step 4: 提交

git add ruoyi-ui/src/views/ccdiEmployee/index.vue
git commit -m "feat: 修改导入处理逻辑以支持状态持久化

- handleFileSuccess: 清除旧数据,保存新任务初始状态
- handleImportComplete: 更新localStorage中的完整任务状态

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"

Task 4: 增强失败记录查询的错误处理

Files:

  • Modify: ruoyi-ui/src/views/ccdiEmployee/index.vue

Step 1: 修改 getFailureList 方法

找到 getFailureList 方法,替换为:

/** 查询失败记录列表 */
getFailureList() {
  this.failureLoading = true;
  getImportFailures(
    this.currentTaskId,
    this.failureQueryParams.pageNum,
    this.failureQueryParams.pageSize
  ).then(response => {
    this.failureList = response.rows;
    this.failureTotal = response.total;
    this.failureLoading = false;
  }).catch(error => {
    this.failureLoading = false;

    // 处理不同类型的错误
    if (error.response) {
      const status = error.response.status;

      if (status === 404) {
        // 记录不存在或已过期
        this.$modal.msgWarning('导入记录已过期,无法查看失败记录');
        this.clearImportTaskFromStorage();
        this.showFailureButton = false;
        this.currentTaskId = null;
        this.failureDialogVisible = false;
      } else if (status === 500) {
        this.$modal.msgError('服务器错误,请稍后重试');
      } else {
        this.$modal.msgError(`查询失败: ${error.response.data.msg || '未知错误'}`);
      }
    } else if (error.request) {
      // 请求发送了但没有收到响应
      this.$modal.msgError('网络连接失败,请检查网络');
    } else {
      this.$modal.msgError('查询失败记录失败: ' + error.message);
    }
  });
},

关键改动:

  • 添加详细的错误分类处理
  • 404错误时清除localStorage并隐藏按钮
  • 添加网络错误和服务器错误的友好提示

Step 2: 手动测试 - 错误处理

由于需要模拟后端404错误,这里提供两种测试方式:

方式1: 修改localStorage时间戳模拟过期

// 在控制台执行
const data = JSON.parse(localStorage.getItem('employee_import_last_task'));
data.saveTime = Date.now() - (8 * 24 * 60 * 60 * 1000); // 8天前
localStorage.setItem('employee_import_last_task', JSON.stringify(data));

然后刷新页面,虽然不会触发API 404,但可以验证localStorage的过期清除逻辑。

方式2: 使用无效的taskId测试

// 在控制台执行
localStorage.setItem('employee_import_last_task', JSON.stringify({
  taskId: 'invalid-task-id-12345',
  status: 'PARTIAL_SUCCESS',
  timestamp: Date.now(),
  saveTime: Date.now(),
  hasFailures: true,
  totalCount: 100,
  successCount: 95,
  failureCount: 5
}));

刷新页面,点击"查看上次导入失败记录"按钮,应该会显示错误提示。

Step 3: 提交

git add ruoyi-ui/src/views/ccdiEmployee/index.vue
git commit -m "feat: 增强失败记录查询的错误处理

- 添加404错误处理(记录过期)
- 添加500错误和500错误的友好提示
- 错误时自动清除localStorage并隐藏按钮

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"

Task 5: 添加计算属性和模板优化

Files:

  • Modify: ruoyi-ui/src/views/ccdiEmployee/index.vue

Step 1: 添加 computed 计算属性

找到 export default { 中的 data() 方法,在 data() 后添加 computed:

computed: {
  /**
   * 上次导入信息摘要
   */
  lastImportInfo() {
    const savedTask = this.getImportTaskFromStorage();
    if (savedTask && savedTask.totalCount) {
      return `导入时间: ${this.parseTime(savedTask.timestamp)} | 总数: ${savedTask.totalCount}条 | 成功: ${savedTask.successCount}条 | 失败: ${savedTask.failureCount}条`;
    }
    return '';
  }
},

Step 2: 修改失败记录按钮 - 添加tooltip

找到"查看导入失败记录"按钮的代码(大约在第70-78行),替换为:

<el-col :span="1.5" v-if="showFailureButton">
  <el-tooltip
    :content="getLastImportTooltip()"
    placement="top"
  >
    <el-button
      type="warning"
      plain
      icon="el-icon-warning"
      size="mini"
      @click="viewImportFailures"
    >
      查看上次导入失败记录
    </el-button>
  </el-tooltip>
</el-col>

Step 3: 修改失败记录对话框 - 添加信息提示和清除按钮

找到导入失败记录对话框(大约在第269-294行),在 <el-table> 上方添加信息提示,在footer添加清除按钮:

<!-- 导入失败记录对话框 -->
<el-dialog
  title="导入失败记录"
  :visible.sync="failureDialogVisible"
  width="1200px"
  append-to-body
>
  <el-alert
    v-if="lastImportInfo"
    :title="lastImportInfo"
    type="info"
    :closable="false"
    style="margin-bottom: 15px"
  />

  <el-table :data="failureList" v-loading="failureLoading">
    <el-table-column label="姓名" prop="name" align="center" />
    <el-table-column label="柜员号" prop="employeeId" align="center" />
    <el-table-column label="身份证号" prop="idCard" align="center" />
    <el-table-column label="电话" prop="phone" align="center" />
    <el-table-column label="失败原因" prop="errorMessage" align="center" min-width="200" :show-overflow-tooltip="true" />
  </el-table>

  <pagination
    v-show="failureTotal > 0"
    :total="failureTotal"
    :page.sync="failureQueryParams.pageNum"
    :limit.sync="failureQueryParams.pageSize"
    @pagination="getFailureList"
  />

  <div slot="footer" class="dialog-footer">
    <el-button @click="failureDialogVisible = false">关闭</el-button>
    <el-button type="danger" plain @click="clearImportHistory">清除历史记录</el-button>
  </div>
</el-dialog>

Step 4: 手动测试 - UI优化验证

  1. 完成一次有失败记录的导入
  2. 鼠标悬停在"查看上次导入失败记录"按钮上
  3. 确认显示tooltip提示上次导入时间
  4. 点击按钮打开对话框
  5. 确认对话框顶部显示导入统计信息
  6. 点击"清除历史记录"按钮
  7. 确认弹出确认对话框
  8. 确认后对话框关闭,按钮消失

Step 5: 提交

git add ruoyi-ui/src/views/ccdiEmployee/index.vue
git commit -m "feat: 添加UI优化和用户体验增强

- 新增lastImportInfo计算属性显示导入统计
- 失败记录按钮添加tooltip显示导入时间
- 失败记录对话框添加统计信息展示
- 对话框添加清除历史记录按钮

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"

完整功能测试

Task 6: 端到端功能测试

Files:

  • 无修改,仅测试

Step 1: 测试场景1 - 导入成功无失败后刷新

  1. 准备一个正确的Excel文件(所有数据都有效)
  2. 上传文件并等待导入完成
  3. 确认不显示"查看上次导入失败记录"按钮
  4. 刷新页面(F5)
  5. 预期: 仍然不显示失败记录按钮
  6. 实际: 验证符合预期

Step 2: 测试场景2 - 导入有失败后刷新

  1. 准备一个包含错误数据的Excel文件
  2. 上传文件并等待导入完成
  3. 确认显示"查看上次导入失败记录"按钮
  4. 刷新页面(F5)
  5. 预期: 按钮仍然显示
  6. 实际: 验证符合预期
  7. 点击按钮,确认能正常查看失败记录

Step 3: 测试场景3 - 导入有失败后切换菜单

  1. 准备一个包含错误数据的Excel文件
  2. 上传文件并等待导入完成
  3. 确认显示"查看上次导入失败记录"按钮
  4. 点击左侧菜单,切换到其他页面(如"部门管理")
  5. 再点击菜单返回"员工管理"
  6. 预期: 按钮仍然显示
  7. 实际: 验证符合预期

Step 4: 测试场景4 - 新导入覆盖旧记录

  1. 完成一次有失败记录的导入
  2. 确认显示按钮
  3. 上传新的Excel文件(正确或错误都可以)
  4. 预期: 新导入开始时,旧记录被清除
  5. 实际: 验证localStorage中的数据被新的taskId覆盖

Step 5: 测试场景5 - 手动清除历史记录

  1. 完成一次有失败记录的导入
  2. 点击"查看上次导入失败记录"按钮
  3. 在对话框中点击"清除历史记录"按钮
  4. 预期: 弹出确认对话框,确认后对话框关闭,按钮消失
  5. 实际: 验证符合预期
  6. 刷新页面
  7. 预期: 按钮仍然不显示
  8. 实际: 验证符合预期

Step 6: 测试场景6 - localStorage过期处理

这个场景由于Redis TTL是7天,手动测试比较困难,可以通过修改localStorage数据模拟:

// 在浏览器控制台执行
const data = JSON.parse(localStorage.getItem('employee_import_last_task'));
if (data) {
  // 将saveTime改为8天前
  data.saveTime = Date.now() - (8 * 24 * 60 * 60 * 1000);
  localStorage.setItem('employee_import_last_task', JSON.stringify(data));
}

然后刷新页面,确认数据被自动清除,按钮不显示。

Step 7: 浏览器兼容性快速测试

在不同浏览器中重复上述测试场景:

  • Chrome (主要浏览器)
  • Edge (如果可用)
  • Firefox (如果可用)

确认功能在各个浏览器中正常工作。

Step 8: 无需提交

这是纯测试步骤,无需提交代码。


文档更新

Task 7: 更新API文档(可选)

Files:

  • Check: doc/api/ccdi-employee-import-api.md

Step 1: 检查API文档是否需要更新

由于这个改动是纯前端实现,不涉及后端API的变化,因此API文档理论上不需要更新。

检查 doc/api/ccdi-employee-import-api.md 文档中是否有关于前端行为或状态的说明,如果有的话,补充说明现在支持跨页面状态持久化。

Step 2: 如需要,在文档末尾添加说明

### 前端行为说明

#### 导入结果持久化

- 前端使用localStorage存储最近一次导入的任务信息
- 支持在切换菜单或刷新页面后继续查看上一次的导入失败记录
- 存储期限: 7天(与后端Redis TTL一致)
- 下次导入开始时,自动清除上一次的导入记录
- 用户可以手动清除导入历史记录

Step 3: 提交(如果进行了修改)

git add doc/api/ccdi-employee-import-api.md
git commit -m "docs: 补充导入结果持久化说明

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"

最终验证

Task 8: 代码审查和最终验证

Files:

  • Review: ruoyi-ui/src/views/ccdiEmployee/index.vue

Step 1: 代码审查清单

  • 所有新增方法都有适当的注释
  • localStorage操作都有try-catch保护
  • 错误处理覆盖了主要场景(404, 500, 网络错误)
  • 代码格式符合项目规范
  • 没有console.log等调试代码残留
  • 没有硬编码的测试数据

Step 2: 最终功能回归测试

按照 Task 6 的所有测试场景再执行一遍,确保所有功能正常。

Step 3: 浏览器控制台检查

打开浏览器控制台,执行以下操作,确认没有错误或警告:

  1. 刷新页面
  2. 完成一次导入
  3. 切换菜单
  4. 查看失败记录

Step 4: 性能检查

打开浏览器开发者工具 > Performance 或 Lighthouse(如果可用):

  1. 录制页面加载过程
  2. 确认localStorage读写操作不会明显影响页面加载性能
  3. 预期: 增加的开销 < 10ms

Step 5: 最终提交

所有代码已经在前面的任务中提交,这里只需确认所有提交都已完成:

# 查看最近的提交历史
git log --oneline -10

应该看到以下提交:

  1. feat: 添加localStorage工具方法用于导入状态持久化
  2. feat: 添加导入状态恢复和用户交互方法
  3. feat: 修改导入处理逻辑以支持状态持久化
  4. feat: 增强失败记录查询的错误处理
  5. feat: 添加UI优化和用户体验增强
  6. (可选) docs: 补充导入结果持久化说明

Step 6: 创建功能总结提交

git commit --allow-empty -m "feat: 完成员工导入结果跨页面持久化功能

功能概述:
- 使用localStorage存储最近一次导入任务信息
- 支持切换菜单后查看上一次的导入失败记录
- 自动过期处理(7天)
- 完整的错误处理和用户友好的提示信息
- 新增清除历史记录功能

测试场景:
- 导入成功无失败后刷新页面
- 导入有失败后刷新页面
- 导入有失败后切换菜单
- 新导入覆盖旧记录
- 手动清除历史记录
- localStorage过期处理

相关提交:
- b932a7d docs: 添加员工导入结果跨页面持久化设计文档

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"

附录

A. 相关设计文档

  • doc/plans/2026-02-06-employee-import-result-persistence-design.md - 详细设计文档
  • doc/plans/2026-02-06-employee-async-import-design.md - 异步导入功能设计文档

B. 测试数据准备

正确的Excel文件:

  • 柜员号: 7位数字,唯一
  • 姓名: 非空
  • 身份证号: 18位有效身份证号
  • 部门: 系统中存在的部门ID
  • 电话: 11位手机号
  • 状态: 0(在职)或1(离职)

包含错误数据的Excel文件:

  • 至少包含以下几种错误:
    • 重复的柜员号
    • 无效的身份证号(位数不对或校验位错误)
    • 不存在的部门ID
    • 无效的手机号格式

C. 常见问题排查

问题1: 按钮不显示

  • 检查localStorage是否有数据
  • 检查hasFailures是否为true
  • 检查taskId是否存在

问题2: 点击查询报错

  • 检查后端API是否正常
  • 检查taskId是否有效
  • 查看浏览器控制台的错误信息

问题3: 数据没有持久化

  • 检查浏览器是否支持localStorage
  • 检查是否在隐私模式/无痕模式
  • 查看控制台是否有异常

D. 回滚方案

如果需要回滚此功能:

# 查看提交历史
git log --oneline

# 回滚到功能之前的提交(假设功能前的提交是 abc1234)
git revert abc1234..HEAD

# 或者硬重置(慎用)
git reset --hard abc1234

计划版本: 1.0 创建日期: 2026-02-06 预计工时: 2-3小时