662 lines
18 KiB
Markdown
662 lines
18 KiB
Markdown
|
|
# 功能更新 - 批量任务进度保存和恢复 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
|
|||
|
|
|