Files
ccdi/doc/员工导入状态持久化功能测试.html
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

594 lines
19 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>员工导入状态持久化功能测试</title>
<style>
body {
font-family: 'Courier New', monospace;
max-width: 1200px;
margin: 20px auto;
padding: 20px;
background-color: #f5f5f5;
}
.test-container {
background: white;
border-radius: 8px;
padding: 30px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
h1 {
color: #333;
border-bottom: 3px solid #409eff;
padding-bottom: 10px;
}
h2 {
color: #666;
margin-top: 30px;
}
.test-section {
margin: 20px 0;
padding: 15px;
border-left: 4px solid #409eff;
background: #f9f9f9;
}
.status-pass {
color: #67c23a;
font-weight: bold;
}
.status-fail {
color: #f56c6c;
font-weight: bold;
}
.status-info {
color: #909399;
}
.code {
background: #2d2d2d;
color: #f8f8f2;
padding: 15px;
border-radius: 4px;
overflow-x: auto;
font-size: 13px;
line-height: 1.5;
}
.summary {
background: #e6f7ff;
border: 2px solid #1890ff;
border-radius: 8px;
padding: 20px;
margin: 30px 0;
}
.summary h3 {
margin-top: 0;
color: #1890ff;
}
button {
background: #409eff;
color: white;
border: none;
padding: 12px 24px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
margin: 5px;
}
button:hover {
background: #66b1ff;
}
button:disabled {
background: #ccc;
cursor: not-allowed;
}
.log {
background: #2d2d2d;
color: #f8f8f2;
padding: 15px;
border-radius: 4px;
max-height: 400px;
overflow-y: auto;
font-size: 12px;
line-height: 1.4;
}
.log-entry {
margin: 5px 0;
}
.log-success { color: #67c23a; }
.log-error { color: #f56c6c; }
.log-warning { color: #e6a23c; }
.log-info { color: #909399; }
</style>
</head>
<body>
<div class="test-container">
<h1>员工导入状态持久化功能 - 测试套件</h1>
<div style="margin: 20px 0;">
<button id="runAllTests" onclick="runAllTests()">运行所有测试</button>
<button onclick="clearResults()">清除结果</button>
<button onclick="clearLocalStorage()">清除localStorage</button>
</div>
<div id="log" class="log">
<div class="log-entry log-info">点击"运行所有测试"按钮开始测试...</div>
</div>
<div id="results"></div>
</div>
<script>
const BASE_URL = 'http://localhost:8080';
let authToken = '';
function log(message, type = 'info') {
const logDiv = document.getElementById('log');
const entry = document.createElement('div');
entry.className = `log-entry log-${type}`;
entry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
logDiv.appendChild(entry);
logDiv.scrollTop = logDiv.scrollHeight;
}
function clearResults() {
document.getElementById('results').innerHTML = '';
document.getElementById('log').innerHTML = '<div class="log-entry log-info">日志已清除</div>';
}
function clearLocalStorage() {
localStorage.removeItem('employee_import_last_task');
log('localStorage已清除', 'info');
}
function formatJSON(obj) {
return JSON.stringify(obj, null, 2);
}
// 模拟后端ImportStatusVO返回的数据
function simulateImportSuccess() {
log('=== 测试1: 模拟导入成功场景 ===', 'info');
const mockSuccessResult = {
taskId: 'task_' + Date.now(),
status: 'SUCCESS',
totalCount: 100,
successCount: 100,
failureCount: 0,
progress: 100,
message: '导入完成'
};
log('模拟后端返回数据: ' + formatJSON(mockSuccessResult), 'info');
// 模拟前端saveImportTaskToStorage方法
const taskData = {
taskId: mockSuccessResult.taskId,
status: mockSuccessResult.status,
hasFailures: mockSuccessResult.failureCount > 0,
totalCount: mockSuccessResult.totalCount,
successCount: mockSuccessResult.successCount,
failureCount: mockSuccessResult.failureCount,
saveTime: Date.now()
};
localStorage.setItem('employee_import_last_task', JSON.stringify(taskData));
log('✅ 已保存到localStorage', 'success');
log('保存的数据: ' + formatJSON(taskData), 'info');
return mockSuccessResult;
}
function simulateImportWithFailures() {
log('=== 测试2: 模拟导入部分失败场景 ===', 'info');
const mockFailureResult = {
taskId: 'task_' + Date.now(),
status: 'SUCCESS',
totalCount: 100,
successCount: 95,
failureCount: 5,
progress: 100,
message: '导入完成'
};
log('模拟后端返回数据: ' + formatJSON(mockFailureResult), 'info');
const taskData = {
taskId: mockFailureResult.taskId,
status: mockFailureResult.status,
hasFailures: mockFailureResult.failureCount > 0,
totalCount: mockFailureResult.totalCount,
successCount: mockFailureResult.successCount,
failureCount: mockFailureResult.failureCount,
saveTime: Date.now()
};
localStorage.setItem('employee_import_last_task', JSON.stringify(taskData));
log('✅ 已保存到localStorage包含失败记录', 'success');
log('保存的数据: ' + formatJSON(taskData), 'info');
return mockFailureResult;
}
function verifyStorageData() {
log('=== 测试3: 验证localStorage数据 ===', 'info');
try {
const data = localStorage.getItem('employee_import_last_task');
if (!data) {
log('❌ localStorage中没有找到导入任务数据', 'error');
return null;
}
const task = JSON.parse(data);
log('✅ 成功读取localStorage数据', 'success');
log('读取的数据: ' + formatJSON(task), 'info');
// 验证必要字段
const requiredFields = ['taskId', 'status', 'hasFailures', 'totalCount', 'successCount', 'failureCount', 'saveTime'];
const missingFields = requiredFields.filter(field => !(field in task));
if (missingFields.length > 0) {
log('❌ 缺少必要字段: ' + missingFields.join(', '), 'error');
return null;
}
log('✅ 所有必要字段都存在', 'success');
// 验证字段类型
const typeChecks = [
{ field: 'taskId', expected: 'string', actual: typeof task.taskId },
{ field: 'status', expected: 'string', actual: typeof task.status },
{ field: 'hasFailures', expected: 'boolean', actual: typeof task.hasFailures },
{ field: 'saveTime', expected: 'number', actual: typeof task.saveTime }
];
let allTypesCorrect = true;
typeChecks.forEach(check => {
if (check.actual !== check.expected) {
log(`${check.field}字段类型错误,期望${check.expected},实际${check.actual}`, 'error');
allTypesCorrect = false;
}
});
if (allTypesCorrect) {
log('✅ 所有字段类型正确', 'success');
}
// 验证时间戳合理性
const now = Date.now();
const timeDiff = now - task.saveTime;
if (timeDiff < 0 || timeDiff > 60000) {
log('⚠️ saveTime时间戳异常时间差: ' + timeDiff + 'ms', 'warning');
} else {
log('✅ saveTime时间戳正常', 'success');
}
return task;
} catch (error) {
log('❌ 解析localStorage数据失败: ' + error.message, 'error');
return null;
}
}
function testRestoreState() {
log('=== 测试4: 测试状态恢复逻辑 ===', 'info');
const task = verifyStorageData();
if (!task) {
log('❌ 无法恢复状态localStorage数据无效', 'error');
return false;
}
// 模拟restoreImportState()方法的逻辑
const restoredState = {
showFailureButton: false,
currentTaskId: null
};
if (task.hasFailures && task.taskId) {
restoredState.currentTaskId = task.taskId;
restoredState.showFailureButton = true;
log('✅ 检测到失败记录,应该显示"查看导入失败记录"按钮', 'success');
log(' - showFailureButton: ' + restoredState.showFailureButton, 'info');
log(' - currentTaskId: ' + restoredState.currentTaskId, 'info');
} else {
log('✅ 没有失败记录,不显示按钮', 'success');
log(' - showFailureButton: ' + restoredState.showFailureButton, 'info');
log(' - currentTaskId: ' + restoredState.currentTaskId, 'info');
}
return restoredState;
}
function testExpiredData() {
log('=== 测试5: 测试过期数据处理 ===', 'info');
const eightDaysAgo = Date.now() - (8 * 24 * 60 * 60 * 1000);
const expiredTask = {
taskId: 'expired_task',
status: 'SUCCESS',
hasFailures: true,
totalCount: 100,
successCount: 90,
failureCount: 10,
saveTime: eightDaysAgo
};
localStorage.setItem('employee_import_last_task', JSON.stringify(expiredTask));
log('已创建过期数据8天前', 'info');
// 模拟getImportTaskFromStorage()的过期检查逻辑
const sevenDays = 7 * 24 * 60 * 60 * 1000;
const isExpired = Date.now() - expiredTask.saveTime > sevenDays;
if (isExpired) {
localStorage.removeItem('employee_import_last_task');
log('✅ 检测到过期数据,已清除', 'success');
return true;
} else {
log('❌ 过期检查逻辑异常', 'error');
return false;
}
}
function testClearHistory() {
log('=== 测试6: 测试清除导入历史功能 ===', 'info');
const testTask = {
taskId: 'test_clear_task',
status: 'SUCCESS',
hasFailures: true,
totalCount: 50,
successCount: 45,
failureCount: 5,
saveTime: Date.now()
};
localStorage.setItem('employee_import_last_task', JSON.stringify(testTask));
log('已创建测试数据', 'info');
// 模拟clearImportHistory()方法
localStorage.removeItem('employee_import_last_task');
log('✅ 已清除导入历史', 'success');
const data = localStorage.getItem('employee_import_last_task');
if (data === null) {
log('✅ 验证成功:导入历史已完全清除', 'success');
return true;
} else {
log('❌ 清除失败localStorage中仍有数据', 'error');
return false;
}
}
function testFieldConsistency() {
log('=== 测试7: 测试字段名一致性 ===', 'info');
// 模拟后端ImportStatusVO返回的数据
const backendData = {
taskId: 'task_test',
status: 'SUCCESS',
totalCount: 100,
successCount: 95,
failureCount: 5,
progress: 100
};
log('后端ImportStatusVO返回: ' + formatJSON(backendData), 'info');
// 模拟前端saveImportTaskToStorage调用的数据
const frontendSaveData = {
taskId: backendData.taskId,
status: backendData.status,
hasFailures: backendData.failureCount > 0,
totalCount: backendData.totalCount,
successCount: backendData.successCount,
failureCount: backendData.failureCount
};
log('前端保存数据: ' + formatJSON(frontendSaveData), 'info');
// 验证字段映射
const fieldMappings = [
{ backend: 'taskId', frontend: 'taskId' },
{ backend: 'status', frontend: 'status' },
{ backend: 'totalCount', frontend: 'totalCount' },
{ backend: 'successCount', frontend: 'successCount' },
{ backend: 'failureCount', frontend: 'failureCount' }
];
let allMatch = true;
fieldMappings.forEach(mapping => {
const backendValue = backendData[mapping.backend];
const frontendValue = frontendSaveData[mapping.frontend];
if (backendValue === frontendValue) {
log(`${mapping.backend}${mapping.frontend}: 值一致 (${backendValue})`, 'success');
} else {
log(`${mapping.backend}${mapping.frontend}: 值不一致`, 'error');
allMatch = false;
}
});
// 验证saveTime字段会在saveImportTaskToStorage中自动添加
log('✅ saveTime字段在saveImportTaskToStorage方法中自动添加', 'info');
return allMatch;
}
function displayResults(results) {
const resultsDiv = document.getElementById('results');
let html = '<div class="summary">';
html += '<h3>测试结果汇总</h3>';
html += '<table style="width: 100%; border-collapse: collapse;">';
html += '<tr style="border-bottom: 1px solid #ddd;">';
html += '<th style="padding: 10px; text-align: left;">测试项目</th>';
html += '<th style="padding: 10px; text-align: left;">结果</th>';
html += '</tr>';
const testNames = {
importSuccess: '导入成功场景',
importWithFailures: '导入部分失败场景',
restoreState: '状态恢复逻辑',
expiredData: '过期数据处理',
clearHistory: '清除导入历史',
fieldConsistency: '字段名一致性'
};
let passCount = 0;
let failCount = 0;
Object.keys(results).forEach(key => {
const status = results[key] ? '✅ PASS' : '❌ FAIL';
const statusClass = results[key] ? 'status-pass' : 'status-fail';
const testName = testNames[key] || key;
html += '<tr style="border-bottom: 1px solid #eee;">';
html += `<td style="padding: 10px;">${testName}</td>`;
html += `<td style="padding: 10px;" class="${statusClass}">${status}</td>`;
html += '</tr>';
if (results[key]) {
passCount++;
} else {
failCount++;
}
});
html += '</table>';
html += '<p style="margin-top: 20px; font-size: 16px;">';
html += `<strong>总计:</strong> ${passCount + failCount} 个测试 | `;
html += `<span class="status-pass">通过: ${passCount} 个</span> | `;
html += `<span class="status-fail">失败: ${failCount} 个</span>`;
html += '</p>';
if (failCount === 0) {
html += '<p style="margin-top: 15px; font-size: 18px; color: #67c23a;">';
html += '🎉 <strong>所有测试通过!</strong> 导入状态持久化功能正常工作。';
html += '</p>';
} else {
html += '<p style="margin-top: 15px; font-size: 18px; color: #f56c6c;">';
html += '⚠️ <strong>部分测试失败</strong>,请检查相关功能。';
html += '</p>';
}
html += '</div>';
resultsDiv.innerHTML = html;
}
async function runAllTests() {
const btn = document.getElementById('runAllTests');
btn.disabled = true;
btn.textContent = '测试运行中...';
document.getElementById('log').innerHTML = '';
document.getElementById('results').innerHTML = '';
log('╔════════════════════════════════════════════════════════════╗', 'info');
log('║ 员工导入状态持久化功能 - 完整测试套件 ║', 'info');
log('╚════════════════════════════════════════════════════════════╝', 'info');
// 清理环境
localStorage.removeItem('employee_import_last_task');
log('✅ 测试环境已清理', 'success');
const results = {
importSuccess: false,
importWithFailures: false,
restoreState: false,
expiredData: false,
clearHistory: false,
fieldConsistency: false
};
// 测试1: 导入成功场景
try {
localStorage.removeItem('employee_import_last_task');
simulateImportSuccess();
const task = verifyStorageData();
results.importSuccess = (task !== null && !task.hasFailures);
} catch (error) {
log('❌ 导入成功场景测试失败: ' + error.message, 'error');
}
// 测试2: 导入部分失败场景
try {
localStorage.removeItem('employee_import_last_task');
simulateImportWithFailures();
const task = verifyStorageData();
results.importWithFailures = (task !== null && task.hasFailures);
} catch (error) {
log('❌ 导入部分失败场景测试失败: ' + error.message, 'error');
}
// 测试3: 状态恢复
try {
const state = testRestoreState();
results.restoreState = (state !== false && state.showFailureButton === true);
} catch (error) {
log('❌ 状态恢复测试失败: ' + error.message, 'error');
}
// 测试4: 过期数据处理
try {
localStorage.removeItem('employee_import_last_task');
results.expiredData = testExpiredData();
} catch (error) {
log('❌ 过期数据处理测试失败: ' + error.message, 'error');
}
// 测试5: 清除导入历史
try {
results.clearHistory = testClearHistory();
} catch (error) {
log('❌ 清除导入历史测试失败: ' + error.message, 'error');
}
// 测试6: 字段名一致性
try {
localStorage.removeItem('employee_import_last_task');
results.fieldConsistency = testFieldConsistency();
} catch (error) {
log('❌ 字段名一致性测试失败: ' + error.message, 'error');
}
log('╔════════════════════════════════════════════════════════════╗', 'info');
log('║ 测试完成 ║', 'info');
log('╚════════════════════════════════════════════════════════════╝', 'info');
displayResults(results);
// 清理测试数据
localStorage.removeItem('employee_import_last_task');
log('✅ 测试数据已清理', 'success');
btn.disabled = false;
btn.textContent = '运行所有测试';
}
</script>
</body>
</html>