docs: 添加员工导入结果跨页面持久化设计文档
实现了导入状态的localStorage持久化方案: - 支持切换菜单后查看上一次导入结果 - 仅保留最后一次导入记录 - 依赖Redis TTL自动清理过期数据 - 完整的错误处理和边界情况处理 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,678 @@
|
||||
# 员工导入结果跨页面持久化设计文档
|
||||
|
||||
**创建日期**: 2026-02-06
|
||||
**设计者**: Claude Code
|
||||
**状态**: 已确认
|
||||
**关联文档**: [员工信息异步导入功能设计文档](./2026-02-06-employee-async-import-design.md)
|
||||
|
||||
---
|
||||
|
||||
## 一、需求概述
|
||||
|
||||
### 1.1 背景
|
||||
当前员工信息异步导入功能存在问题:
|
||||
- 导入开始后,切换到其他菜单再返回,无法查看上一次的导入结果
|
||||
- `showFailureButton`、`currentTaskId` 等状态变量存储在组件内存中,页面切换后丢失
|
||||
|
||||
### 1.2 目标
|
||||
- 实现导入结果的跨页面持久化
|
||||
- 用户可以在切换菜单后仍然查看上一次的导入失败记录
|
||||
- 仅保留最近一次导入记录,下次导入时自动清除旧数据
|
||||
- 依赖Redis的7天TTL机制自动清理过期数据
|
||||
|
||||
### 1.3 核心决策
|
||||
- **存储方案**: localStorage(前端持久化)
|
||||
- **保留范围**: 仅最后一次导入记录
|
||||
- **过期策略**: 依赖Redis TTL(7天),前端校验时间戳
|
||||
- **清除时机**: 下次导入开始时自动清除旧数据
|
||||
|
||||
---
|
||||
|
||||
## 二、技术方案
|
||||
|
||||
### 2.1 整体设计
|
||||
|
||||
采用 **前端localStorage持久化** 方案:
|
||||
|
||||
```
|
||||
用户上传Excel
|
||||
↓
|
||||
清除localStorage旧数据 → 保存新taskId
|
||||
↓
|
||||
开始轮询查询状态
|
||||
↓
|
||||
导入完成 → 更新localStorage状态
|
||||
↓
|
||||
用户切换菜单 → 组件销毁
|
||||
↓
|
||||
用户返回页面 → created()钩子
|
||||
↓
|
||||
从localStorage读取 → 恢复按钮显示状态
|
||||
↓
|
||||
用户点击查看失败记录 → 正常查询
|
||||
```
|
||||
|
||||
**核心优势**:
|
||||
- 无需后端改动,完全前端实现
|
||||
- 简单可靠,利用浏览器原生存储
|
||||
- 用户体验流畅,状态不丢失
|
||||
|
||||
### 2.2 数据结构设计
|
||||
|
||||
**localStorage存储格式**:
|
||||
|
||||
```javascript
|
||||
// key: 'employee_import_last_task'
|
||||
{
|
||||
taskId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
|
||||
status: 'SUCCESS' | 'PARTIAL_SUCCESS' | 'FAILED' | 'PROCESSING',
|
||||
timestamp: 1707225900000,
|
||||
saveTime: 1707225900000,
|
||||
hasFailures: true,
|
||||
totalCount: 100,
|
||||
successCount: 95,
|
||||
failureCount: 5
|
||||
}
|
||||
```
|
||||
|
||||
**字段说明**:
|
||||
- `taskId`: 导入任务唯一标识
|
||||
- `status`: 导入状态
|
||||
- `timestamp`: 导入完成时间戳
|
||||
- `saveTime`: 保存到localStorage的时间戳(用于过期校验)
|
||||
- `hasFailures`: 是否有失败记录
|
||||
- `totalCount/successCount/failureCount`: 导入统计信息
|
||||
|
||||
---
|
||||
|
||||
## 三、前端实现设计
|
||||
|
||||
### 3.1 新增工具方法
|
||||
|
||||
**文件**: `ruoyi-ui/src/views/ccdiEmployee/index.vue`
|
||||
|
||||
```javascript
|
||||
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);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 从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;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 清除localStorage中的导入任务
|
||||
*/
|
||||
clearImportTaskFromStorage() {
|
||||
try {
|
||||
localStorage.removeItem('employee_import_last_task');
|
||||
} catch (error) {
|
||||
console.error('清除导入任务状态失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 恢复导入状态
|
||||
* 在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;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取上次导入的提示信息
|
||||
* @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 '';
|
||||
},
|
||||
|
||||
/**
|
||||
* 清除导入历史记录
|
||||
* 用户手动触发
|
||||
*/
|
||||
clearImportHistory() {
|
||||
this.$confirm('确认清除上次导入记录?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.clearImportTaskFromStorage();
|
||||
this.showFailureButton = false;
|
||||
this.currentTaskId = null;
|
||||
this.failureDialogVisible = false;
|
||||
this.$message.success('已清除');
|
||||
}).catch(() => {});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 生命周期钩子修改
|
||||
|
||||
```javascript
|
||||
created() {
|
||||
this.getList();
|
||||
this.getDeptTree();
|
||||
this.restoreImportState(); // 新增:恢复导入状态
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 导入成功处理修改
|
||||
|
||||
```javascript
|
||||
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);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 导入完成处理修改
|
||||
|
||||
```javascript
|
||||
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();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.5 失败记录查询增强
|
||||
|
||||
```javascript
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 3.6 新增计算属性
|
||||
|
||||
```javascript
|
||||
computed: {
|
||||
/**
|
||||
* 上次导入信息摘要
|
||||
*/
|
||||
lastImportInfo() {
|
||||
const savedTask = this.getImportTaskFromStorage();
|
||||
if (savedTask && savedTask.totalCount) {
|
||||
return `导入时间: ${this.parseTime(savedTask.timestamp)} | 总数: ${savedTask.totalCount}条 | 成功: ${savedTask.successCount}条 | 失败: ${savedTask.failureCount}条`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.7 模板修改
|
||||
|
||||
**失败记录按钮**:
|
||||
```vue
|
||||
<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>
|
||||
```
|
||||
|
||||
**失败记录对话框**:
|
||||
```vue
|
||||
<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>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、用户体验流程
|
||||
|
||||
### 4.1 典型场景
|
||||
|
||||
**场景1: 导入成功无失败**
|
||||
1. 用户上传Excel文件
|
||||
2. 导入成功,显示通知"全部成功!共导入100条数据"
|
||||
3. 刷新页面或切换菜单后返回
|
||||
4. **预期**: 不显示"查看导入失败记录"按钮
|
||||
|
||||
**场景2: 导入有失败记录**
|
||||
1. 用户上传有错误数据的Excel文件
|
||||
2. 导入完成,显示通知"成功95条,失败5条"
|
||||
3. 显示"查看导入失败记录"按钮
|
||||
4. 用户切换到其他菜单
|
||||
5. 用户返回员工管理页面
|
||||
6. **预期**: 按钮仍然存在,点击可查看失败记录
|
||||
|
||||
**场景3: 导入中切换页面**
|
||||
1. 用户上传Excel文件
|
||||
2. 后台开始处理,用户立即切换菜单
|
||||
3. 用户返回员工管理页面
|
||||
4. **预期**: 如有失败,显示按钮并可查看
|
||||
|
||||
**场景4: Redis数据过期**
|
||||
1. 导入完成,有失败记录
|
||||
2. 7天后用户点击"查看导入失败记录"
|
||||
3. 后端返回404错误
|
||||
4. **预期**: 前端提示"导入记录已过期,无法查看失败记录",并清除localStorage数据,隐藏按钮
|
||||
|
||||
**场景5: 新导入覆盖旧记录**
|
||||
1. 已有上一次的导入失败记录
|
||||
2. 用户上传新的Excel文件
|
||||
3. **预期**: 旧记录被立即清除,新导入的结果覆盖localStorage
|
||||
|
||||
---
|
||||
|
||||
## 五、错误处理与边界情况
|
||||
|
||||
### 5.1 localStorage异常
|
||||
|
||||
| 异常情况 | 处理方式 |
|
||||
|---------|---------|
|
||||
| localStorage被禁用 | try-catch捕获,console.error记录,功能降级但不报错 |
|
||||
| 数据损坏(非JSON格式) | try-catch捕获,清除损坏数据,返回null |
|
||||
| 数据格式不完整 | 校验必要字段,清除无效数据 |
|
||||
| 时间戳异常 | 校验类型,清除无效数据 |
|
||||
|
||||
### 5.2 API请求失败
|
||||
|
||||
| 错误类型 | HTTP状态码 | 处理方式 |
|
||||
|---------|-----------|---------|
|
||||
| 记录不存在或已过期 | 404 | 提示用户"记录已过期",清除localStorage,隐藏按钮 |
|
||||
| 服务器内部错误 | 500 | 提示"服务器错误,请稍后重试" |
|
||||
| 网络连接失败 | 无响应 | 提示"网络连接失败,请检查网络" |
|
||||
| 其他错误 | 其他 | 显示具体错误信息 |
|
||||
|
||||
### 5.3 并发导入处理
|
||||
|
||||
- 新导入开始时,立即清除旧的localStorage数据
|
||||
- 清除旧的轮询定时器(如果有)
|
||||
- 防止状态混乱
|
||||
|
||||
### 5.4 浏览器兼容性
|
||||
|
||||
localStorage在所有现代浏览器中都得到支持:
|
||||
- Chrome 4+
|
||||
- Firefox 3.5+
|
||||
- Safari 4+
|
||||
- IE 8+
|
||||
- Edge(所有版本)
|
||||
|
||||
### 5.5 存储空间限制
|
||||
|
||||
- localStorage通常有5-10MB限制
|
||||
- 本功能仅存储一个JSON对象(约200字节),远低于限制
|
||||
- 不需要考虑存储空间问题
|
||||
|
||||
---
|
||||
|
||||
## 六、测试策略
|
||||
|
||||
### 6.1 功能测试
|
||||
|
||||
| 测试用例 | 步骤 | 预期结果 |
|
||||
|---------|------|---------|
|
||||
| 导入成功无失败-刷新 | 上传正确Excel → 等待完成 → 刷新页面 | 不显示失败记录按钮 |
|
||||
| 导入有失败-刷新 | 上传有错误Excel → 等待完成 → 刷新页面 | 显示按钮,可查看失败记录 |
|
||||
| 导入有失败-切换菜单 | 上传有错误Excel → 等待完成 → 切换菜单 → 返回 | 显示按钮,可查看失败记录 |
|
||||
| 导入中切换页面 | 上传Excel → 立即切换菜单 → 返回 | 状态正常,如有失败显示按钮 |
|
||||
| 新导入覆盖 | 有旧记录 → 上传新Excel → 等待完成 | 显示新导入的按钮,旧记录清除 |
|
||||
| 手动清除记录 | 有失败记录 → 点击"清除历史记录" | 按钮隐藏,localStorage清空 |
|
||||
| Redis过期模拟 | 修改localStorage时间戳为8天前 → 打开页面 | 自动清除数据,不显示按钮 |
|
||||
| API 404处理 | 有失败记录 → Mock后端返回404 | 提示过期,清除数据,隐藏按钮 |
|
||||
|
||||
### 6.2 边界测试
|
||||
|
||||
| 测试用例 | 预期结果 |
|
||||
|---------|---------|
|
||||
| localStorage被禁用 | 功能正常,不报错,仅不持久化 |
|
||||
| localStorage数据手动篡改 | 自动检测并清除,恢复正常 |
|
||||
| 连续快速多次导入 | 最后一次导入的状态为准 |
|
||||
| 浏览器关闭后重新打开 | localStorage数据保留,状态恢复 |
|
||||
|
||||
### 6.3 浏览器兼容性测试
|
||||
|
||||
测试目标浏览器:
|
||||
- Chrome(最新版)
|
||||
- Firefox(最新版)
|
||||
- Edge(最新版)
|
||||
- Safari(如适用)
|
||||
|
||||
### 6.4 性能测试
|
||||
|
||||
| 指标 | 目标 |
|
||||
|------|------|
|
||||
| localStorage读取时间 | < 10ms |
|
||||
| localStorage写入时间 | < 10ms |
|
||||
| 页面加载恢复时间 | < 50ms |
|
||||
| 内存占用增加 | 可忽略(约200字节) |
|
||||
|
||||
---
|
||||
|
||||
## 七、实施检查清单
|
||||
|
||||
### 7.1 代码实现
|
||||
|
||||
- [ ] 新增 `saveImportTaskToStorage()` 方法
|
||||
- [ ] 新增 `getImportTaskFromStorage()` 方法
|
||||
- [ ] 新增 `clearImportTaskFromStorage()` 方法
|
||||
- [ ] 新增 `restoreImportState()` 方法
|
||||
- [ ] 新增 `getLastImportTooltip()` 方法
|
||||
- [ ] 新增 `clearImportHistory()` 方法
|
||||
- [ ] 新增 `lastImportInfo` 计算属性
|
||||
- [ ] 修改 `created()` 钩子,调用 `restoreImportState()`
|
||||
- [ ] 修改 `handleFileSuccess()` 方法
|
||||
- [ ] 修改 `handleImportComplete()` 方法
|
||||
- [ ] 修改 `getFailureList()` 方法
|
||||
- [ ] 修改模板,添加tooltip和清除按钮
|
||||
|
||||
### 7.2 测试
|
||||
|
||||
- [ ] 导入成功无失败-刷新页面测试
|
||||
- [ ] 导入有失败-刷新页面测试
|
||||
- [ ] 导入有失败-切换菜单测试
|
||||
- [ ] 导入中切换页面测试
|
||||
- [ ] 新导入覆盖旧记录测试
|
||||
- [ ] 手动清除记录测试
|
||||
- [ ] Redis过期处理测试
|
||||
- [ ] API 404错误处理测试
|
||||
- [ ] localStorage异常处理测试
|
||||
- [ ] 浏览器兼容性测试
|
||||
|
||||
### 7.3 文档
|
||||
|
||||
- [ ] 更新 `doc/api/ccdi-employee-import-api.md` (如有需要)
|
||||
- [ ] 更新用户手册(如需要)
|
||||
|
||||
---
|
||||
|
||||
## 八、风险与限制
|
||||
|
||||
### 8.1 风险
|
||||
|
||||
| 风险 | 影响 | 缓解措施 |
|
||||
|------|------|---------|
|
||||
| localStorage被禁用 | 无法持久化 | 功能降级,不影响基本使用 |
|
||||
| 用户清除浏览器数据 | 记录丢失 | 符合预期,无负面影响 |
|
||||
| 多标签页并发导入 | 状态可能不一致 | 新导入会覆盖旧数据,可接受 |
|
||||
|
||||
### 8.2 限制
|
||||
|
||||
1. **仅保留最后一次导入记录**
|
||||
- 设计决策,符合用户需求
|
||||
- 需要查看历史记录可考虑后续扩展
|
||||
|
||||
2. **依赖Redis TTL**
|
||||
- 7天后Redis数据自动删除
|
||||
- 前端有7天时间戳校验,但以Redis为准
|
||||
|
||||
3. **单浏览器本地存储**
|
||||
- 不同浏览器不共享状态
|
||||
- 换设备后无法查看(符合预期)
|
||||
|
||||
---
|
||||
|
||||
## 九、未来扩展方向
|
||||
|
||||
### 9.1 可能的增强功能
|
||||
|
||||
1. **历史导入记录列表**
|
||||
- 后端新增导入记录表
|
||||
- 支持查询所有历史导入
|
||||
- 按时间倒序展示
|
||||
|
||||
2. **跨设备同步**
|
||||
- 使用后端存储导入记录
|
||||
- 用户登录后同步导入状态
|
||||
|
||||
3. **导入结果导出**
|
||||
- 支持导出失败记录为Excel
|
||||
- 便于用户修正后重新导入
|
||||
|
||||
4. **导入统计可视化**
|
||||
- 展示导入成功率趋势
|
||||
- 常见错误类型统计
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文件清单
|
||||
|
||||
### 10.1 修改文件
|
||||
|
||||
- `ruoyi-ui/src/views/ccdiEmployee/index.vue` - 员工管理页面
|
||||
|
||||
### 10.2 关联文档
|
||||
|
||||
- `doc/plans/2026-02-06-employee-async-import-design.md` - 员工信息异步导入功能设计文档
|
||||
- `doc/api/ccdi-employee-import-api.md` - 员工导入API文档
|
||||
|
||||
---
|
||||
|
||||
## 附录
|
||||
|
||||
### A. localStorage Key命名规范
|
||||
|
||||
```
|
||||
employee_import_last_task // 员工导入最后一次任务
|
||||
```
|
||||
|
||||
命名格式: `{模块}_{功能}_{用途}`
|
||||
|
||||
### B. 相关接口
|
||||
|
||||
| 接口 | 方法 | 说明 |
|
||||
|------|------|------|
|
||||
| /ccdi/employee/importData | POST | 提交导入任务 |
|
||||
| /ccdi/employee/importStatus/{taskId} | GET | 查询导入状态 |
|
||||
| /ccdi/employee/importFailures/{taskId} | GET | 查询失败记录 |
|
||||
|
||||
---
|
||||
|
||||
**文档版本**: 1.0
|
||||
**最后更新**: 2026-02-06
|
||||
Reference in New Issue
Block a user