# 功能更新 - 批量任务进度保存和恢复 v3.12.0 **版本**: v3.12.0 **日期**: 2025-10-08 **类型**: 功能更新 ## 功能概述 新增批量任务执行进度的自动保存和恢复功能。当批量任务执行过程中刷新页面或浏览器意外关闭后,可以从上次中断的位置继续执行,无需重新开始。 ### 用户需求 > "突然发现批量自动化没有记录上次跑到哪一个token了,我需要记录一下这次跑到第几个token,刷新网页之后,还可以继续下面进行做任务" ## 功能特性 ### 1. 自动保存进度 - **实时保存**:每完成一个Token的任务后,自动保存进度到 `localStorage` - **保存内容**: - 已完成的Token ID列表 - 所有Token ID列表 - 任务列表 - 执行统计(成功、失败、跳过数量) - 保存时间戳 ### 2. 智能进度恢复 - **刷新检测**:页面刷新后自动检测是否有未完成的任务 - **用户选择**: - ✅ **继续上次进度** - 只执行剩余未完成的Token - 🔄 **重新开始** - 清除进度,从头开始执行所有Token - **进度过期**:超过24小时的进度自动清除 ### 3. 进度信息展示 弹窗显示详细进度信息: - 总计Token数量 - 已完成Token数量 - 剩余Token数量(高亮显示) ## 技术实现 ### 1. 数据结构 #### 保存的进度数据 ```javascript { completedTokenIds: ['token1', 'token2', ...], // 已完成的Token ID列表 allTokenIds: ['token1', 'token2', 'token3', ...], // 所有Token ID列表 tasks: ['dailyFix', 'legionSignIn', ...], // 任务列表 timestamp: 1728374400000, // 保存时间戳 stats: { total: 100, // 总数 success: 45, // 成功数 failed: 3, // 失败数 skipped: 0 // 跳过数 } } ``` ### 2. 核心函数 #### src/stores/batchTaskStore.js **保存进度**: ```javascript const saveExecutionProgress = (completedTokenIds, allTokenIds, tasks) => { const progress = { completedTokenIds: completedTokenIds, allTokenIds: allTokenIds, tasks: tasks, timestamp: Date.now(), stats: { total: executionStats.value.total, success: executionStats.value.success, failed: executionStats.value.failed, skipped: executionStats.value.skipped } } localStorage.setItem('batchTaskProgress', JSON.stringify(progress)) savedProgress.value = progress } ``` **清除进度**: ```javascript const clearSavedProgress = () => { localStorage.removeItem('batchTaskProgress') savedProgress.value = null } ``` **检查进度**: ```javascript const hasSavedProgress = computed(() => { if (!savedProgress.value) return false // 检查进度是否过期(超过24小时) const elapsed = Date.now() - savedProgress.value.timestamp const isExpired = elapsed > 24 * 60 * 60 * 1000 if (isExpired) { clearSavedProgress() return false } // 检查是否还有未完成的token const remaining = savedProgress.value.allTokenIds.length - savedProgress.value.completedTokenIds.length return remaining > 0 }) ``` **修改启动函数**: ```javascript const startBatchExecution = async (tokenIds = null, tasks = null, isRetry = false, continueFromSaved = false) => { // ... 省略其他代码 let targetTokens = tokenIds || tokenStore.gameTokens.map(t => t.id) let targetTasks = tasks || currentTemplateTasks.value let completedTokenIds = [] // 检查是否要从保存的进度继续 if (continueFromSaved && savedProgress.value && hasSavedProgress.value) { console.log('📂 从上次保存的进度继续执行') targetTokens = savedProgress.value.allTokenIds targetTasks = savedProgress.value.tasks completedTokenIds = savedProgress.value.completedTokenIds // 恢复统计数据 executionStats.value = { ...executionStats.value, total: savedProgress.value.stats.total, success: savedProgress.value.stats.success, failed: savedProgress.value.stats.failed, skipped: savedProgress.value.stats.skipped } console.log(`✅ 已完成: ${completedTokenIds.length}/${targetTokens.length} 个Token`) console.log(`🔄 继续执行剩余 ${targetTokens.length - completedTokenIds.length} 个Token`) } // ... 省略其他代码 // 过滤出需要执行的token(排除已完成的) const tokensToExecute = targetTokens.filter(id => !completedTokenIds.includes(id)) // 执行批量任务 await executeBatchWithConcurrency(tokensToExecute, targetTasks, targetTokens, completedTokenIds) } ``` **修改并发执行函数**: ```javascript const executeBatchWithConcurrency = async (tokenIds, tasks, allTokenIds = null, completedTokenIds = []) => { const queue = [...tokenIds] const executing = [] const completed = [...completedTokenIds] // 已完成的token列表 const all = allTokenIds || tokenIds // 所有token列表 while (queue.length > 0 || executing.length > 0) { // ... 省略其他代码 const promise = (async () => { // 执行任务 return executeTokenTasks(tokenId, tasks) })() .then(() => { // 从执行队列中移除 const index = executing.indexOf(promise) if (index > -1) { executing.splice(index, 1) } executingTokens.value.delete(tokenId) // 保存进度:记录此token已完成 completed.push(tokenId) saveExecutionProgress(completed, all, tasks) }) .catch(error => { // ... 省略错误处理 // 保存进度:即使失败也记录为已完成(避免下次重复执行) completed.push(tokenId) saveExecutionProgress(completed, all, tasks) }) } } ``` **完成时清除进度**: ```javascript const finishBatchExecution = async () => { // ... 省略其他代码 // 清除保存的进度(任务已全部完成) clearSavedProgress() // ... 省略其他代码 } ``` #### src/components/BatchTaskPanel.vue **修改开始执行函数**: ```javascript const handleStart = () => { // 检查是否有保存的进度 if (batchStore.hasSavedProgress && batchStore.savedProgress) { const completed = batchStore.savedProgress.completedTokenIds.length const total = batchStore.savedProgress.allTokenIds.length const remaining = total - completed dialog.warning({ title: '发现未完成的任务', content: () => { return h('div', [ h('p', `检测到上次有未完成的批量任务:`), h('ul', { style: 'margin: 12px 0; padding-left: 24px;' }, [ h('li', `总计:${total} 个Token`), h('li', `已完成:${completed} 个`), h('li', { style: 'color: #ff6b6b; font-weight: bold;' }, `剩余:${remaining} 个`) ]), h('p', { style: 'margin-top: 16px; font-weight: bold;' }, '您想要:') ]) }, positiveText: '继续上次进度', negativeText: '重新开始', onPositiveClick: () => { batchStore.startBatchExecution(null, null, false, true) message.success(`继续执行剩余 ${remaining} 个Token`) }, onNegativeClick: () => { dialog.warning({ title: '确认重新开始', content: `确定要重新开始执行所有 ${tokenStore.gameTokens.length} 个角色的任务吗?\n\n已完成的 ${completed} 个Token将会重新执行。`, positiveText: '确定重新开始', negativeText: '取消', onPositiveClick: () => { batchStore.clearSavedProgress() batchStore.startBatchExecution() message.success('批量任务已启动(重新开始)') } }) } }) } else { // 没有保存的进度,正常开始 dialog.warning({ title: '确认执行', content: `即将对 ${tokenStore.gameTokens.length} 个角色执行批量任务,是否继续?`, positiveText: '确定', negativeText: '取消', onPositiveClick: () => { batchStore.startBatchExecution() message.success('批量任务已启动') } }) } } ``` ### 3. 新增导出 #### src/stores/batchTaskStore.js ```javascript return { // 状态 savedProgress, // 新增:保存的进度数据 // 计算属性 hasSavedProgress, // 新增:是否有保存的进度 // 方法 clearSavedProgress, // 新增:清除保存的进度 // ... 其他已有的导出 } ``` ## 使用场景 ### 场景1:正常完成 ``` 用户操作: 1. 开始执行批量任务(100个Token) 2. 任务全部执行完成 3. 系统自动清除保存的进度 结果: ✅ 所有任务完成 ✅ 进度已清除 ``` ### 场景2:中途刷新(继续执行) ``` 用户操作: 1. 开始执行批量任务(100个Token) 2. 执行到第50个时,刷新页面 3. 点击"开始执行"按钮 4. 弹窗显示:已完成50个,剩余50个 5. 选择"继续上次进度" 结果: ✅ 从第51个Token继续执行 ✅ 已完成的50个Token不会重复执行 ✅ 统计数据累加(成功、失败数量保持) ``` ### 场景3:中途刷新(重新开始) ``` 用户操作: 1. 开始执行批量任务(100个Token) 2. 执行到第50个时,刷新页面 3. 点击"开始执行"按钮 4. 弹窗显示:已完成50个,剩余50个 5. 选择"重新开始" 6. 二次确认弹窗 7. 确认"确定重新开始" 结果: ✅ 清除保存的进度 ✅ 从第1个Token重新执行所有100个 ✅ 统计数据重置 ``` ### 场景4:进度过期 ``` 用户操作: 1. 开始执行批量任务(100个Token) 2. 执行到第50个时,关闭浏览器 3. 24小时后重新打开页面 4. 点击"开始执行"按钮 结果: ✅ 进度已自动过期清除 ✅ 直接弹出正常开始确认框 ✅ 从头开始执行所有Token ``` ## 用户界面 ### 弹窗1:发现未完成的任务 ``` ┌─────────────────────────────────────┐ │ ⚠ 发现未完成的任务 │ ├─────────────────────────────────────┤ │ 检测到上次有未完成的批量任务: │ │ │ │ • 总计:100 个Token │ │ • 已完成:50 个 │ │ • 剩余:50 个 ← 红色高亮 │ │ │ │ 您想要: │ │ │ │ [继续上次进度] [重新开始] │ └─────────────────────────────────────┘ ``` ### 弹窗2:确认重新开始 ``` ┌─────────────────────────────────────┐ │ ⚠ 确认重新开始 │ ├─────────────────────────────────────┤ │ 确定要重新开始执行所有 100 个角色的 │ │ 任务吗? │ │ │ │ 已完成的 50 个Token将会重新执行。 │ │ │ │ [确定重新开始] [取消] │ └─────────────────────────────────────┘ ``` ### 控制台日志 **继续执行时**: ``` 📂 从上次保存的进度继续执行 ✅ 已完成: 50/100 个Token 🔄 继续执行剩余 50 个Token 🚀 开始批量执行任务 📋 Token数量: 100 📋 任务列表: ['dailyFix', 'legionSignIn', ...] ``` **每完成一个Token**: ``` 💾 已保存进度: 51/100 个Token已完成 💾 已保存进度: 52/100 个Token已完成 ... ``` **全部完成时**: ``` 🎉 批量任务执行完成 🗑️ 已清除保存的进度 ✅ 所有任务成功完成! ``` ## 数据存储 ### localStorage 键名 ``` batchTaskProgress ``` ### 数据示例 ```json { "completedTokenIds": [ "10601服-0-7145...", "10601服-1-7146...", "10602服-0-7147..." ], "allTokenIds": [ "10601服-0-7145...", "10601服-1-7146...", "10602服-0-7147...", "10603服-0-7148...", "10604服-0-7149..." ], "tasks": [ "dailyFix", "legionSignIn", "autoStudy", "claimHangupReward", "addClock", "sendCar", "climbTower" ], "timestamp": 1728374400000, "stats": { "total": 5, "success": 3, "failed": 0, "skipped": 0 } } ``` ## 技术要点 ### 1. 进度保存时机 - ✅ **成功完成**:Token任务执行成功后保存 - ✅ **失败**:Token任务执行失败后也保存(避免重复执行失败的任务) - ❌ **执行中**:不保存(避免状态不一致) ### 2. 进度恢复逻辑 ```javascript // 初始化每个Token的进度 targetTokens.forEach(tokenId => { if (completedTokenIds.includes(tokenId)) { // 已完成的token标记为completed状态 taskProgress.value[tokenId] = { status: 'completed', progress: 100, // ... } } else { // 未完成的token初始化为pending状态 taskProgress.value[tokenId] = { status: 'pending', progress: 0, // ... } } }) // 过滤出需要执行的token const tokensToExecute = targetTokens.filter(id => !completedTokenIds.includes(id)) // 只执行未完成的token await executeBatchWithConcurrency(tokensToExecute, targetTasks, targetTokens, completedTokenIds) ``` ### 3. 统计数据累加 ```javascript if (continueFromSaved && savedProgress.value) { // 恢复之前的统计数据 executionStats.value = { ...executionStats.value, total: savedProgress.value.stats.total, success: savedProgress.value.stats.success, // 保持之前的成功数 failed: savedProgress.value.stats.failed, // 保持之前的失败数 skipped: savedProgress.value.stats.skipped // 保持之前的跳过数 } } ``` ### 4. 进度过期检查 ```javascript const hasSavedProgress = computed(() => { if (!savedProgress.value) return false const elapsed = Date.now() - savedProgress.value.timestamp const isExpired = elapsed > 24 * 60 * 60 * 1000 // 24小时 if (isExpired) { clearSavedProgress() return false } const remaining = savedProgress.value.allTokenIds.length - savedProgress.value.completedTokenIds.length return remaining > 0 }) ``` ## 优势与特性 ### 1. 可靠性 - ✅ 实时保存到 localStorage,不依赖网络 - ✅ 每完成一个Token就保存,最小化数据丢失 - ✅ 自动过期机制,避免过时数据干扰 ### 2. 灵活性 - ✅ 用户可选择继续或重新开始 - ✅ 支持部分完成的恢复 - ✅ 失败的Token也记录为已完成(避免无限重试) ### 3. 用户体验 - ✅ 详细的进度信息展示 - ✅ 明确的操作选项 - ✅ 二次确认机制(重新开始时) - ✅ 友好的提示消息 ### 4. 性能优化 - ✅ 只执行未完成的Token,节省时间 - ✅ 避免重复执行,减少服务器压力 - ✅ localStorage 存储,无网络开销 ## 注意事项 ### 1. Token列表变化 如果Token列表发生变化(添加/删除Token),保存的进度可能不准确: - **建议**:重新开始执行 - **未来改进**:可以添加Token指纹验证 ### 2. 任务列表变化 如果任务模板发生变化,保存的进度仍使用旧的任务列表: - **当前行为**:继续使用保存的任务列表 - **未来改进**:可以提示任务列表已变化 ### 3. 数据大小 对于大量Token(如1000+),进度数据可能较大: - **当前限制**:localStorage 通常 5-10MB - **评估**:1000个Token ID × 20字节 ≈ 20KB(完全可接受) ### 4. 浏览器隐私模式 在隐私/无痕模式下,localStorage 可能不持久化: - **影响**:刷新后进度丢失 - **解决**:建议使用正常模式执行长时间任务 ## 测试用例 ### 测试1:正常完成 1. 开始执行10个Token 2. 等待全部完成 3. 检查localStorage:`batchTaskProgress` 应该被删除 4. 再次点击"开始执行":应该是正常的确认框 ### 测试2:中途刷新继续 1. 开始执行10个Token 2. 完成5个后刷新页面 3. 点击"开始执行":应该弹出"发现未完成的任务" 4. 选择"继续上次进度" 5. 应该从第6个Token开始执行 6. 统计数据应该累加 ### 测试3:中途刷新重新开始 1. 开始执行10个Token 2. 完成5个后刷新页面 3. 点击"开始执行":应该弹出"发现未完成的任务" 4. 选择"重新开始" 5. 二次确认后,应该清除进度 6. 从第1个Token重新执行所有10个 ### 测试4:进度过期 1. 开始执行10个Token 2. 完成5个后关闭浏览器 3. 手动修改localStorage中的timestamp为25小时前 4. 重新打开页面,点击"开始执行" 5. 应该弹出正常的确认框(进度已自动清除) ### 测试5:失败Token处理 1. 开始执行10个Token,其中3个会失败 2. 完成5个(包括2个失败)后刷新 3. 继续执行:失败的2个不应该再次执行 4. 统计数据中的失败数应该保持 ## 修改文件 - ✅ src/stores/batchTaskStore.js - 新增 `savedProgress` 状态 - 新增 `saveExecutionProgress` 函数 - 新增 `clearSavedProgress` 函数 - 新增 `hasSavedProgress` 计算属性 - 修改 `startBatchExecution` 函数支持 `continueFromSaved` 参数 - 修改 `executeBatchWithConcurrency` 函数支持进度保存 - 修改 `finishBatchExecution` 函数清除进度 - ✅ src/components/BatchTaskPanel.vue - 导入 `h` 函数 - 修改 `handleStart` 函数支持进度恢复提示 ## 相关版本 - **v3.11.x**: 批量任务优化和错误处理 - **v3.12.0**: 新增进度保存和恢复功能(本版本) ## 总结 **核心功能**: - 💾 自动保存批量任务执行进度 - 🔄 刷新后可继续执行剩余任务 - 🎯 用户可选择继续或重新开始 - ⏰ 24小时自动过期机制 **用户获益**: - ✅ 不怕浏览器意外关闭 - ✅ 不怕页面刷新中断 - ✅ 节省时间(继续执行而非重新开始) - ✅ 灵活控制(可选择重新开始) **技术优势**: - ✅ 实时保存,可靠性高 - ✅ 自动过期,避免脏数据 - ✅ 累加统计,数据准确 - ✅ 用户友好,操作简单 --- **状态**: ✅ 已完成 **版本**: v3.12.0