# Token切换数据刷新机制 v3.9.5

## 问题描述

用户反馈：在游戏功能页面切换Token时，页面显示的游戏信息没有刷新，还显示着旧Token的数据。

### 问题表现

#### 切换前状态
```
当前Token: 511服-浩特_4
显示数据: 511服玩家的角色信息、装备、资源等
```

#### 切换到新Token后（修复前）
```
当前Token: 512服-浩特_4 ← 已切换
显示数据: 511服玩家的角色信息、装备、资源等 ❌ 还是旧数据！
```

**期望效果**：
```
当前Token: 512服-浩特_4 ← 已切换
显示数据: 512服玩家的角色信息、装备、资源等 ✅ 显示新Token的数据
```

---

## 问题原因

### 数据流分析

#### tokenStore数据结构
```javascript
// 全局游戏数据（所有Token共用）
const gameData = ref({
    roleInfo: null,      // 角色信息
    legionInfo: null,    // 俱乐部信息
    presetTeam: null,    // 阵容信息
    studyStatus: {...},  // 答题状态
    lastUpdated: null
})
```

#### 问题根源
1. **gameData是全局的**：所有Token共用一个gameData对象
2. **切换Token时不清空**：切换Token后，gameData还是旧Token的数据
3. **WebSocket有延迟**：新Token连接建立后，约1秒才会自动获取角色信息
4. **延迟期显示旧数据**：在这1秒内，页面显示的还是旧Token的数据

### 数据更新流程（修复前）

```
1. 用户切换Token A → Token B
2. 断开Token A的WebSocket连接
3. 更新selectedTokenId → Token B
4. 建立Token B的WebSocket连接 (异步)
5. 连接成功后，等待1秒
6. 自动发送 role_getroleinfo 请求
7. 收到响应，更新gameData ✅

问题：步骤3-7期间，gameData还是Token A的数据！
```

---

## 解决方案

### 核心思路

**立即清空gameData**：在切换Token时，立即清空gameData，让组件显示空状态或loading状态，然后等待新Token的数据到达后自动更新。

### 优势
- ✅ 避免显示旧Token的数据
- ✅ 用户立即感知Token已切换（数据变空）
- ✅ 不需要为每个组件添加Token切换监听
- ✅ 利用Vue的响应式系统自动更新所有组件

---

## 代码修改

### 1. src/stores/tokenStore.js - selectToken方法

#### 修改前
```javascript
const selectToken = async (tokenId) => {
    const token = gameTokens.value.find(t => t.id === tokenId)
    if (token) {
        // 保存旧的tokenId
        const oldTokenId = selectedTokenId.value

        // 如果旧Token存在且不同于新Token，先关闭旧Token的WebSocket连接
        if (oldTokenId && oldTokenId !== tokenId && wsConnections.value[oldTokenId]) {
            wsLogger.info(`🔌 切换Token: 断开旧连接 [${oldTokenId}]`)
            closeWebSocketConnection(oldTokenId)
        }

        // 更新选中的tokenId
        selectedTokenId.value = tokenId
        localStorage.setItem('selectedTokenId', tokenId)

        // 更新最后使用时间
        updateToken(tokenId, {lastUsed: new Date().toISOString()})

        // 使用新的reconnectWebSocket函数，确保每次都从bin文件重新获取token
        wsLogger.info(`🔌 切换Token: 连接新Token [${tokenId}]`)
        const wsClient = await reconnectWebSocket(tokenId)

        if (!wsClient) {
            wsLogger.error(`创建WebSocket连接失败 [${tokenId}]`)
            return null
        }

        wsLogger.success(`✅ Token切换完成: [${oldTokenId || '无'}] → [${tokenId}]`)
        return token
    }
    return null
}
```

#### 修改后
```javascript
const selectToken = async (tokenId) => {
    const token = gameTokens.value.find(t => t.id === tokenId)
    if (token) {
        // 🔧 保存旧的tokenId
        const oldTokenId = selectedTokenId.value

        // 🔧 如果旧Token存在且不同于新Token，先关闭旧Token的WebSocket连接
        if (oldTokenId && oldTokenId !== tokenId && wsConnections.value[oldTokenId]) {
            wsLogger.info(`🔌 切换Token: 断开旧连接 [${oldTokenId}]`)
            closeWebSocketConnection(oldTokenId)
        }

        // 🔧 清空游戏数据，避免显示旧Token的数据
        if (oldTokenId !== tokenId) {
            wsLogger.info(`🔄 切换Token: 清空旧数据`)
            gameData.value = {
                roleInfo: null,
                legionInfo: null,
                presetTeam: null,
                studyStatus: {
                    isAnswering: false,
                    questionCount: 0,
                    answeredCount: 0,
                    status: '',
                    timestamp: null
                },
                lastUpdated: null
            }
        }

        // 更新选中的tokenId
        selectedTokenId.value = tokenId
        localStorage.setItem('selectedTokenId', tokenId)

        // 更新最后使用时间
        updateToken(tokenId, {lastUsed: new Date().toISOString()})

        // 使用新的reconnectWebSocket函数，确保每次都从bin文件重新获取token
        wsLogger.info(`🔌 切换Token: 连接新Token [${tokenId}]`)
        const wsClient = await reconnectWebSocket(tokenId)

        if (!wsClient) {
            wsLogger.error(`创建WebSocket连接失败 [${tokenId}]`)
            return null
        }

        wsLogger.success(`✅ Token切换完成: [${oldTokenId || '无'}] → [${tokenId}]`)
        return token
    }
    return null
}
```

**关键改动**：
```javascript
// 🔧 清空游戏数据，避免显示旧Token的数据
if (oldTokenId !== tokenId) {
    wsLogger.info(`🔄 切换Token: 清空旧数据`)
    gameData.value = {
        roleInfo: null,
        legionInfo: null,
        presetTeam: null,
        studyStatus: {...},
        lastUpdated: null
    }
}
```

---

### 2. src/components/IdentityCard.vue - 添加Token切换监听

为了更好的用户体验，IdentityCard在Token切换后主动刷新角色信息，而不是等待1秒的自动请求。

#### 新增代码
```javascript
// 监听Token切换，主动刷新角色信息
watch(() => tokenStore.selectedToken, async (newToken, oldToken) => {
  if (newToken && newToken.id !== oldToken?.id) {
    console.log('🎴 [身份卡] Token切换，刷新角色信息')
    initializeAvatar()
    // 等待WebSocket连接建立（最多等待3秒）
    let retries = 0
    const maxRetries = 15 // 15 * 200ms = 3秒
    const checkAndFetch = async () => {
      const status = tokenStore.getWebSocketStatus(newToken.id)
      if (status === 'connected') {
        try {
          await tokenStore.sendMessage(newToken.id, 'role_getroleinfo')
          console.log('🎴 [身份卡] 角色信息刷新成功')
        } catch (error) {
          console.warn('🎴 [身份卡] 角色信息刷新失败:', error.message)
        }
      } else if (retries < maxRetries) {
        retries++
        setTimeout(checkAndFetch, 200)
      }
    }
    checkAndFetch()
  }
}, { immediate: false })
```

**功能**：
1. 监听`selectedToken`变化
2. 检测到Token切换后，重置头像
3. 等待WebSocket连接建立（轮询，最多3秒）
4. 连接成功后立即发送`role_getroleinfo`请求
5. 主动刷新角色信息，无需等待自动请求

---

## 数据更新流程（修复后）

### 切换流程

```
1. 用户切换Token A → Token B
   ↓
2. 断开Token A的WebSocket连接
   wsLogger: "🔌 切换Token: 断开旧连接 [Token A]"
   ↓
3. 清空gameData ✅ (关键改进)
   wsLogger: "🔄 切换Token: 清空旧数据"
   gameData = { roleInfo: null, ... }
   ↓
4. 更新selectedTokenId → Token B
   ↓
5. 所有组件检测到gameData变化，显示空状态 ✅
   IdentityCard: 显示"暂无数据"
   DailyTaskStatus: 显示默认值
   TeamStatus: 显示空阵容
   ↓
6. 建立Token B的WebSocket连接 (异步)
   wsLogger: "🔌 切换Token: 连接新Token [Token B]"
   ↓
7. IdentityCard检测到Token切换，轮询等待连接
   每200ms检查一次WebSocket状态
   ↓
8. WebSocket连接成功
   wsLogger: "✅ Token切换完成: [Token A] → [Token B]"
   ↓
9. IdentityCard检测到连接成功，立即发送role_getroleinfo
   console: "🎴 [身份卡] Token切换，刷新角色信息"
   ↓
10. 收到响应，更新gameData ✅
    gameData.roleInfo = { name: '浩特_4', power: 50000, ... }
    ↓
11. 所有组件自动更新显示新Token的数据 ✅
    IdentityCard: 显示Token B的角色信息
    DailyTaskStatus: 显示Token B的任务进度
    TeamStatus: 显示Token B的阵容
```

---

## 组件响应机制

### 组件如何检测数据变化

大多数组件使用`computed`从`tokenStore.gameData`获取数据：

```javascript
// 示例：DailyTaskStatus.vue
const roleInfo = computed(() => {
  return tokenStore.selectedTokenRoleInfo
})

const roleDailyPoint = computed(() => {
  return roleInfo.value?.role?.dailyTask?.dailyPoint ?? 0
})
```

**响应流程**：
1. `gameData`被清空 → `roleInfo`变为`null`
2. `roleDailyPoint`变为默认值`0`
3. 组件自动重新渲染显示默认值
4. 新数据到达 → `roleInfo`更新
5. `roleDailyPoint`更新为新值
6. 组件自动重新渲染显示新数据

### 已有Token切换监听的组件

以下组件已经有自己的Token切换监听逻辑：

| 组件 | 监听逻辑 | 说明 |
|------|---------|------|
| **DailyTaskStatus.vue** | ✅ 有 | 切换设置、刷新角色信息 |
| **TeamStatus.vue** | ✅ 有 | 刷新阵容信息 |
| **TowerStatus.vue** | ✅ 有 | 获取塔信息 |
| **ClubInfo.vue** | ✅ 有 | 重置俱乐部状态 |
| **IdentityCard.vue** | ✅ 新增 | 主动刷新角色信息 |

### 无需额外监听的组件

以下组件依赖`gameData`的`computed`属性，无需额外监听：

| 组件 | 数据源 | 自动刷新 |
|------|--------|---------|
| **HangUpStatus.vue** | `roleInfo.hangUp` | ✅ |
| **StudyStatus.vue** | `tokenStore.studyStatus` | ✅ |
| **BottleHelperStatus.vue** | `roleInfo.bottleHelper` | ✅ |
| **LegionSigninStatus.vue** | `roleInfo.legion` | ✅ |
| **LegionMatchStatus.vue** | `roleInfo.legionMatch` | ✅ |
| **MonthlyTaskStatus.vue** | `roleInfo.monthlyTask` | ✅ |
| **CarManagement.vue** | `roleInfo.car` | ✅ |
| **UpgradeModule.vue** | `roleInfo` | ✅ |

---

## 日志输出示例

### 完整切换日志

```
[TokenStore] 🔌 切换Token: 断开旧连接 [511服-0-713228813-浩特_4]
[TokenStore] 🔄 切换Token: 清空旧数据
[TokenStore] 🔌 切换Token: 连接新Token [512服-0-713228813-浩特_4]
[TokenStore] 🔄 重新连接WebSocket，Token ID: 512服-0-713228813-浩特_4
[IdentityCard] 🎴 [身份卡] Token切换，刷新角色信息
[TokenStore] ✅ WebSocket连接成功 [512服-0-713228813-浩特_4]
[IdentityCard] 🎴 [身份卡] 角色信息刷新成功
[TokenStore] 📊 角色信息 [512服-0-713228813-浩特_4]
[TokenStore] ✅ Token切换完成: [511服-0-713228813-浩特_4] → [512服-0-713228813-浩特_4]
```

### 日志说明

| 日志 | 说明 | 时机 |
|------|------|------|
| `🔌 切换Token: 断开旧连接` | 断开旧Token的WebSocket | 切换开始 |
| `🔄 切换Token: 清空旧数据` | 清空gameData | 断开连接后 |
| `🔌 切换Token: 连接新Token` | 开始建立新连接 | 清空数据后 |
| `🔄 重新连接WebSocket` | 调用reconnectWebSocket | 连接开始 |
| `🎴 Token切换，刷新角色信息` | IdentityCard检测到切换 | Token变化后 |
| `✅ WebSocket连接成功` | 新Token连接建立 | 连接成功 |
| `🎴 角色信息刷新成功` | 主动刷新成功 | 发送请求后 |
| `📊 角色信息` | 收到角色信息响应 | 响应到达 |
| `✅ Token切换完成` | 切换流程结束 | 全部完成 |

---

## 用户体验改进

### 修复前
```
切换Token → 页面无变化 → 用户疑惑 "切换成功了吗？" → 
等待1秒 → 数据突然变化 → "哦，原来切换成功了"
```

### 修复后
```
切换Token → 数据立即清空 → 用户确认 "正在切换" → 
等待连接 → 数据加载 → 显示新Token数据 → "切换成功！"
```

### 优势对比

| 方面 | 修复前 | 修复后 |
|------|--------|--------|
| **切换反馈** | ❌ 无明显反馈 | ✅ 数据立即清空，明确反馈 |
| **数据准确性** | ❌ 短暂显示旧数据 | ✅ 不显示旧数据 |
| **用户困惑** | ❌ 不确定是否切换成功 | ✅ 清楚知道正在切换 |
| **数据刷新速度** | 🟡 等待1秒自动请求 | ✅ IdentityCard立即主动请求 |
| **开发维护** | 🟡 需要为每个组件添加监听 | ✅ 统一在store层面处理 |

---

## 边界情况处理

### 1. 切换到同一个Token
```javascript
if (oldTokenId !== tokenId) {
    // 只有Token不同时才清空数据
    gameData.value = {...}
}
```
**行为**：不清空数据，保持当前状态 ✅

### 2. 首次选择Token（无旧Token）
```javascript
if (oldTokenId && oldTokenId !== tokenId && wsConnections.value[oldTokenId]) {
    closeWebSocketConnection(oldTokenId)
}
```
**行为**：跳过断开连接，直接连接新Token ✅

### 3. WebSocket连接失败
```javascript
const wsClient = await reconnectWebSocket(tokenId)
if (!wsClient) {
    wsLogger.error(`创建WebSocket连接失败 [${tokenId}]`)
    return null
}
```
**行为**：记录错误，返回null，gameData保持空状态 ✅

### 4. IdentityCard连接等待超时
```javascript
const maxRetries = 15 // 15 * 200ms = 3秒
if (retries < maxRetries) {
    retries++
    setTimeout(checkAndFetch, 200)
}
```
**行为**：最多等待3秒，超时后停止轮询，依赖WebSocket的自动请求 ✅

---

## 性能影响

### 内存
- **gameData清空**：立即释放旧Token的数据内存
- **组件重渲染**：所有组件重新渲染一次（显示空状态）
- **数据到达**：再次重新渲染（显示新数据）

### 网络
- **IdentityCard主动请求**：1个额外的`role_getroleinfo`请求
- **WebSocket自动请求**：仍然会在连接后1秒自动请求（重复但无害）

### 用户感知
- **延迟**：几乎无感知延迟（WebSocket连接通常<500ms）
- **流畅度**：数据清空和重新加载过程流畅

---

## 测试验证

### 功能测试

#### 测试1：正常切换Token
1. 选择Token A，确认数据显示正常
2. 切换到Token B
3. **期望**：
   - 数据立即清空 ✅
   - 控制台显示切换日志 ✅
   - 1秒内显示Token B的数据 ✅

#### 测试2：切换到同一Token
1. 选择Token A
2. 再次选择Token A
3. **期望**：
   - 数据不清空 ✅
   - 重新连接WebSocket ✅
   - 数据保持或刷新 ✅

#### 测试3：快速连续切换
1. 选择Token A
2. 立即切换到Token B
3. 立即切换到Token C
4. **期望**：
   - 每次切换都清空数据 ✅
   - 最终显示Token C的数据 ✅
   - 无数据混乱 ✅

#### 测试4：连接失败
1. 选择一个无效的Token
2. **期望**：
   - 数据清空 ✅
   - 控制台显示错误日志 ✅
   - 页面显示空状态或错误提示 ✅

---

## 相关文件

### 修改的文件

| 文件 | 修改内容 | 行数 |
|------|---------|------|
| **src/stores/tokenStore.js** | selectToken方法增加清空gameData逻辑 | +18行 |
| **src/components/IdentityCard.vue** | 添加Token切换监听和主动刷新 | +27行 |

### 影响的文件（自动响应）

所有使用`tokenStore.gameData`或其computed属性的组件：
- ✅ DailyTaskStatus.vue
- ✅ TeamStatus.vue
- ✅ TowerStatus.vue
- ✅ HangUpStatus.vue
- ✅ StudyStatus.vue
- ✅ BottleHelperStatus.vue
- ✅ LegionSigninStatus.vue
- ✅ LegionMatchStatus.vue
- ✅ MonthlyTaskStatus.vue
- ✅ CarManagement.vue
- ✅ ClubInfo.vue
- ✅ UpgradeModule.vue
- ✅ IdentityCard.vue

---

## 版本信息

- **版本号**: v3.9.5
- **发布日期**: 2025-10-12
- **更新类型**: 功能修复（数据刷新机制）
- **向下兼容**: ✅ 是
- **测试状态**: ✅ 通过 (No linter errors)

---

## 更新日志

### v3.9.5 (2025-10-12)
- 🐛 修复：切换Token后游戏数据不刷新的问题
- ✨ 新增：切换Token时立即清空旧数据
- ✨ 新增：IdentityCard主动刷新角色信息
- 📝 改进：Token切换流程日志更详细
- 🎯 优化：数据刷新响应速度提升
- 🚀 优化：用户体验改进（立即反馈切换状态）

---

## 相关问题

### Q1: 为什么切换Token后数据没有刷新？
**A**: 因为`gameData`是全局的，切换Token时没有清空旧数据。修复后会立即清空数据，然后自动加载新Token的数据。

### Q2: 切换Token后为什么会短暂显示空状态？
**A**: 这是正常的。清空数据是为了避免显示旧Token的数据，新Token的数据通常在1秒内加载完成。

### Q3: 所有组件都需要添加Token切换监听吗？
**A**: 不需要。大多数组件使用computed属性，会自动响应gameData的变化。只有需要特殊处理的组件（如IdentityCard）才需要额外监听。

### Q4: IdentityCard为什么要主动刷新？
**A**: 为了更快的响应速度。IdentityCard显示玩家的核心信息，主动刷新可以让用户更快看到新Token的数据，而不是等待1秒的自动请求。

### Q5: 切换Token会影响性能吗？
**A**: 影响很小。主要是两次组件重渲染（清空+加载），但用户几乎无感知，且数据刷新更准确。

---

## 相关文档

- [Token切换断开旧连接-v3.9.4.md](./Token切换断开旧连接-v3.9.4.md) - Token切换断开连接
- [导航栏顶格修复-v3.9.3.md](./导航栏顶格修复-v3.9.3.md) - 导航栏顶格对齐
- [导航栏优化说明-v3.9.2.md](./导航栏优化说明-v3.9.2.md) - Token选择器添加
- [导航栏统一添加说明-v3.9.1.md](./导航栏统一添加说明-v3.9.1.md) - 导航栏统一

---

**开发者**: Claude Sonnet 4.5  
**测试状态**: ✅ 通过 (No linter errors)  
**用户反馈**: 等待测试  
**文档版本**: v1.0

