# 月度任务系统详细实现文档

## 📊 功能概述

月度任务系统是v2.1.1版本的核心新功能，提供钓鱼和竞技场的月度进度跟踪和自动补齐功能。

---

## 🎯 核心参数

```javascript
// 月度目标
const FISH_TARGET = 320      // 钓鱼月度目标：320次
const ARENA_TARGET = 240     // 竞技场月度目标：240次

// 状态变量
const monthLoading = ref(false)      // 刷新进度中
const fishToppingUp = ref(false)     // 钓鱼补齐中
const arenaToppingUp = ref(false)    // 竞技场补齐中
const monthActivity = ref(null)      // 月度任务数据
```

---

## 📈 进度计算逻辑

### 1. 时间相关计算

```javascript
// 获取当前日期信息
const now = new Date()
const daysInMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate() // 本月总天数
const dayOfMonth = now.getDate()  // 当前是本月第几天

// 剩余天数
const remainingDays = computed(() => Math.max(0, daysInMonth - dayOfMonth))

// 月度进度（当前天数 / 总天数）
const monthProgress = computed(() => Math.min(1, Math.max(0, dayOfMonth / daysInMonth)))

// 月度进度百分比（显示用）
const monthPercent = computed(() => Math.min(100, Math.round((dayOfMonth / daysInMonth) * 100)))
```

### 2. 任务进度计算

```javascript
// 从API获取的数据
const myMonthInfo = computed(() => monthActivity.value?.myMonthInfo || {})
const myArenaInfo = computed(() => monthActivity.value?.myArenaInfo || {})

// 当前完成数量
const fishNum = computed(() => Number(myMonthInfo.value?.['2']?.num || 0))
const arenaNum = computed(() => Number(myArenaInfo.value?.num || 0))

// 完成百分比
const fishPercent = computed(() => Math.min(100, Math.round((fishNum.value / FISH_TARGET) * 100)))
const arenaPercent = computed(() => Math.min(100, Math.round((arenaNum.value / ARENA_TARGET) * 100)))
```

### 3. 应该完成的数量（补齐目标）

```javascript
// 钓鱼应该完成的数量
const fishShouldBe = computed(() => {
  if (remainingDays.value === 0) {
    return FISH_TARGET  // 最后一天，按满额计算
  }
  return Math.min(FISH_TARGET, Math.ceil(monthProgress.value * FISH_TARGET))
})

// 竞技场应该完成的数量
const arenaShouldBe = computed(() => {
  if (remainingDays.value === 0) {
    return ARENA_TARGET
  }
  return Math.min(ARENA_TARGET, Math.ceil(monthProgress.value * ARENA_TARGET))
})

// 需要补齐的数量
const fishNeeded = computed(() => Math.max(0, fishShouldBe.value - fishNum.value))
const arenaNeeded = computed(() => Math.max(0, arenaShouldBe.value - arenaNum.value))
```

**计算示例**：
- 假设今天是10月15日，本月共31天
- 月度进度 = 15 / 31 = 48.39%
- 钓鱼应该完成 = 320 × 48.39% = 155次（向上取整）
- 如果当前完成100次，需要补齐 = 155 - 100 = 55次

---

## 🎣 钓鱼补齐功能

### 实现逻辑

```javascript
const topUpMonthly = (type) => {
  const isFish = type === 'fish'
  const target = isFish ? FISH_TARGET : ARENA_TARGET
  const current = isFish ? fishNum.value : arenaNum.value
  const shouldBe = isFish ? fishShouldBe.value : arenaShouldBe.value
  const needed = Math.max(0, shouldBe - current)
  
  if (needed === 0) {
    message.info(`${isFish ? '钓鱼' : '竞技场'}已达标，无需补齐`)
    return
  }
  
  if (isFish) {
    topUpFish(needed)
  } else {
    topUpArena(needed)
  }
}
```

### 钓鱼补齐详细流程

```javascript
const topUpFish = async (needed) => {
  fishToppingUp.value = true
  try {
    // 1. 获取当前资源
    const roleInfo = tokenStore.gameData?.roleInfo?.role
    const items = roleInfo?.items || {}
    
    // 2. 解析鱼竿数量
    const normalRod = getItemCount(items, 1011) || 0  // 普通鱼竿ID: 1011
    const goldRod = getItemCount(items, 1012) || 0    // 金鱼竿ID: 1012
    
    // 3. 计算使用策略（优先使用免费次数）
    let useFree = 0
    let useGold = 0
    
    if (normalRod >= needed) {
      // 免费次数足够
      useFree = needed
    } else {
      // 免费次数不足，使用金鱼竿
      useFree = normalRod
      useGold = needed - normalRod
      
      if (goldRod < useGold) {
        message.warning(`金鱼竿不足！需要${useGold}个，仅有${goldRod}个`)
        // 根据可用金鱼竿调整补齐数量
        useGold = goldRod
      }
    }
    
    const total = useFree + useGold
    if (total === 0) {
      message.error('没有可用的鱼竿')
      return
    }
    
    // 4. 执行钓鱼
    message.info(`开始钓鱼：使用普通鱼竿${useFree}次，金鱼竿${useGold}次`)
    
    // 先使用普通鱼竿
    for (let i = 0; i < useFree; i++) {
      await tokenStore.sendMessageWithPromise(
        tokenStore.selectedToken.id,
        'fishing_fish',
        { fishingType: 1 },  // 1=普通鱼竿
        5000
      )
      await sleep(500)  // 间隔500ms
    }
    
    // 再使用金鱼竿
    for (let i = 0; i < useGold; i++) {
      await tokenStore.sendMessageWithPromise(
        tokenStore.selectedToken.id,
        'fishing_fish',
        { fishingType: 2 },  // 2=金鱼竿
        5000
      )
      await sleep(500)
    }
    
    // 5. 完成后刷新进度
    await sleep(1000)
    await fetchMonthlyActivity()
    
    message.success(`钓鱼补齐完成！共完成${total}次钓鱼`)
    
  } catch (error) {
    message.error(`钓鱼补齐失败：${error.message}`)
  } finally {
    fishToppingUp.value = false
  }
}
```

### 鱼竿数量解析

```javascript
const getItemCount = (items, itemId) => {
  if (!items) return 0
  
  // 支持多种数据结构
  
  // 1. 数组结构：[{id: 1011, num: 10}, ...]
  if (Array.isArray(items)) {
    const found = items.find(it => Number(it.id ?? it.itemId) === itemId)
    if (!found) return 0
    return Number(found.num ?? found.count ?? found.quantity ?? 0)
  }
  
  // 2. 对象结构：{ '1011': 10 } 或 { '1011': { num: 10 } }
  const node = items[String(itemId)] ?? items[itemId]
  if (node == null) return 0
  
  if (typeof node === 'number') return Number(node)
  if (typeof node === 'object') {
    return Number(node.num ?? node.count ?? node.quantity ?? 0)
  }
  
  return Number(node) || 0
}
```

---

## ⚔️ 竞技场补齐功能

### 实现逻辑

```javascript
const topUpArena = async (needed) => {
  arenaToppingUp.value = true
  try {
    // 1. 获取当前体力
    const roleInfo = tokenStore.gameData?.roleInfo?.role
    const energy = roleInfo?.energy || 0
    const maxEnergy = roleInfo?.maxEnergy || 100
    
    // 2. 计算战斗次数（贪心策略）
    // 假设每次战斗消耗5点体力
    const ENERGY_PER_BATTLE = 5
    const possibleBattles = Math.floor(energy / ENERGY_PER_BATTLE)
    
    if (possibleBattles < needed) {
      message.warning(`体力不足！需要${needed}次战斗（${needed * ENERGY_PER_BATTLE}体力），当前仅有${energy}体力`)
      // 可以选择：1) 只打可以打的次数  2) 取消操作
      // 这里选择打可以打的次数
      needed = possibleBattles
    }
    
    if (needed === 0) {
      message.error('体力不足，无法进行战斗')
      return
    }
    
    // 3. 执行战斗
    message.info(`开始竞技场战斗：共${needed}次`)
    
    let successCount = 0
    for (let i = 0; i < needed; i++) {
      try {
        // 先匹配对手
        const matchResult = await tokenStore.sendMessageWithPromise(
          tokenStore.selectedToken.id,
          'arena_matchopponent',
          {},
          5000
        )
        
        await sleep(300)
        
        // 发起战斗
        const battleResult = await tokenStore.sendMessageWithPromise(
          tokenStore.selectedToken.id,
          'arena_battle',
          { 
            targetRoleId: matchResult?.opponent?.roleId,
            battleType: 1  // 普通战斗
          },
          5000
        )
        
        successCount++
        await sleep(1000)  // 战斗间隔
        
      } catch (error) {
        console.error(`第${i+1}次战斗失败:`, error)
        // 继续下一次
      }
    }
    
    // 4. 刷新进度
    await sleep(1000)
    await fetchMonthlyActivity()
    
    message.success(`竞技场补齐完成！成功进行${successCount}次战斗`)
    
  } catch (error) {
    message.error(`竞技场补齐失败：${error.message}`)
  } finally {
    arenaToppingUp.value = false
  }
}
```

---

## 🔄 刷新进度功能

```javascript
const fetchMonthlyActivity = async () => {
  if (!tokenStore.selectedToken) {
    message.warning('请先选择Token')
    return
  }
  
  const status = tokenStore.getWebSocketStatus(tokenStore.selectedToken.id)
  if (status !== 'connected') {
    return
  }
  
  monthLoading.value = true
  try {
    const tokenId = tokenStore.selectedToken.id
    const result = await tokenStore.sendMessageWithPromise(
      tokenId, 
      'activity_get',  // 获取月度任务信息的命令
      {}, 
      10000
    )
    
    // 解析响应数据（兼容多种格式）
    const act = result?.activity || result?.body?.activity || result
    monthActivity.value = act || null
    
    if (act) {
      message.success('月度任务进度已更新')
    }
  } catch (e) {
    message.error(`获取月度任务失败：${e.message}`)
  } finally {
    monthLoading.value = false
  }
}
```

---

## 🎬 一键完成功能

### 下拉菜单选项

```javascript
const fishMoreOptions = [
  { label: '一键完成', key: 'complete-fish' }
]

const arenaMoreOptions = [
  { label: '一键完成', key: 'complete-arena' }
]
```

### 一键完成处理

```javascript
const onFishMoreSelect = (key) => {
  if (key === 'complete-fish') {
    completeMonthly('fish')
  }
}

const onArenaMoreSelect = (key) => {
  if (key === 'complete-arena') {
    completeMonthly('arena')
  }
}

const completeMonthly = async (type) => {
  const isFish = type === 'fish'
  const target = isFish ? FISH_TARGET : ARENA_TARGET
  const current = isFish ? fishNum.value : arenaNum.value
  const remaining = target - current
  
  if (remaining <= 0) {
    message.info(`${isFish ? '钓鱼' : '竞技场'}已完成全部目标`)
    return
  }
  
  // 直接补齐到满额
  if (isFish) {
    await topUpFish(remaining)
  } else {
    await topUpArena(remaining)
  }
}
```

---

## 🎨 UI组件代码

### 月度任务面板

```vue
<div class="status-card monthly-tasks" v-show="activeSection === 'activity'">
  <div class="card-header">
    <img src="/icons/1736425783912140.png" alt="月度任务" class="status-icon">
    <div class="status-info">
      <h3>月度任务</h3>
      <p>进度与一键补齐</p>
    </div>
    <div class="status-badge" :class="{ active: monthHasData }">
      <div class="status-dot" />
      <span v-if="remainingDays > 0">剩余 {{ remainingDays }} 天</span>
      <span v-else>本月最后一天</span>
    </div>
  </div>
  
  <div class="card-content">
    <!-- 钓鱼进度 -->
    <div class="monthly-row">
      <div class="row-title">钓鱼进度</div>
      <div class="row-value">
        {{ fishNum }} / {{ FISH_TARGET }}（{{ fishPercent }}%）
      </div>
    </div>
    
    <!-- 竞技场进度 -->
    <div class="monthly-row">
      <div class="row-title">竞技场进度</div>
      <div class="row-value">
        {{ arenaNum }} / {{ ARENA_TARGET }}（{{ arenaPercent }}%）
      </div>
    </div>
    
    <!-- 操作按钮 -->
    <div class="action-row">
      <!-- 刷新按钮 -->
      <button 
        class="action-button secondary" 
        :disabled="monthLoading || fishToppingUp || arenaToppingUp" 
        @click="fetchMonthlyActivity"
      >
        {{ monthLoading ? '刷新中...' : '刷新进度' }}
      </button>

      <!-- 钓鱼补齐 -->
      <n-button-group>
        <n-button 
          class="action-button" 
          :disabled="monthLoading || fishToppingUp" 
          @click="topUpMonthly('fish')"
        >
          {{ fishToppingUp ? '补齐中...' : '钓鱼补齐' }}
        </n-button>
        <n-dropdown 
          :options="fishMoreOptions" 
          trigger="click" 
          @select="onFishMoreSelect"
        >
          <n-button :disabled="monthLoading || fishToppingUp">▾</n-button>
        </n-dropdown>
      </n-button-group>

      <!-- 竞技场补齐 -->
      <n-button-group>
        <n-button 
          class="action-button" 
          :disabled="monthLoading || arenaToppingUp" 
          @click="topUpMonthly('arena')"
        >
          {{ arenaToppingUp ? '补齐中...' : '竞技场补齐' }}
        </n-button>
        <n-dropdown 
          :options="arenaMoreOptions" 
          trigger="click" 
          @select="onArenaMoreSelect"
        >
          <n-button :disabled="monthLoading || arenaToppingUp">▾</n-button>
        </n-dropdown>
      </n-button-group>
    </div>
    
    <!-- 补齐说明 -->
    <p class="description muted">
      补齐规则：让"当前天数比例"和"完成比例"一致；
      若无剩余天数则按满额（{{FISH_TARGET}}/{{ARENA_TARGET}}）计算。
    </p>
  </div>
</div>
```

---

## 📡 相关游戏命令

### 1. 获取月度任务信息
```javascript
command: 'activity_get'
params: {}
response: {
  activity: {
    myMonthInfo: {
      '2': { num: 150 }  // 钓鱼完成次数
    },
    myArenaInfo: {
      num: 80  // 竞技场完成次数
    }
  }
}
```

### 2. 钓鱼
```javascript
command: 'fishing_fish'
params: {
  fishingType: 1  // 1=普通鱼竿, 2=金鱼竿
}
```

### 3. 竞技场匹配对手
```javascript
command: 'arena_matchopponent'
params: {}
response: {
  opponent: {
    roleId: 12345,
    name: '对手名称',
    power: 1000000
  }
}
```

### 4. 竞技场战斗
```javascript
command: 'arena_battle'
params: {
  targetRoleId: 12345,
  battleType: 1  // 1=普通战斗
}
```

### 5. 领取月度任务奖励
```javascript
command: 'monthlyactivity_receivereward'
params: {
  rewardId: 1  // 奖励ID
}
```

---

## ⚙️ 配置和优化

### 1. 错误处理

```javascript
try {
  // 执行任务
} catch (error) {
  // 1. 记录错误
  console.error('月度任务执行失败:', error)
  
  // 2. 用户提示
  message.error(`操作失败：${error.message}`)
  
  // 3. 状态恢复
  fishToppingUp.value = false
  arenaToppingUp.value = false
}
```

### 2. 请求间隔

```javascript
// 避免请求过快
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms))

// 钓鱼间隔：500ms
await sleep(500)

// 战斗间隔：1000ms
await sleep(1000)
```

### 3. 超时设置

```javascript
// 短请求：5秒超时
await tokenStore.sendMessageWithPromise(tokenId, cmd, params, 5000)

// 长请求：10秒超时
await tokenStore.sendMessageWithPromise(tokenId, cmd, params, 10000)
```

---

## 📊 数据流图

```
用户点击"刷新进度"
    ↓
fetchMonthlyActivity()
    ↓
发送 'activity_get' 命令
    ↓
接收响应并解析
    ↓
更新 monthActivity
    ↓
自动计算各项指标
    ↓
UI显示最新进度

---

用户点击"钓鱼补齐"
    ↓
topUpMonthly('fish')
    ↓
计算需要补齐的次数
    ↓
topUpFish(needed)
    ↓
获取当前鱼竿数量
    ↓
计算使用策略
    ↓
执行钓鱼操作（循环）
    ↓
刷新进度
    ↓
完成提示
```

---

## ✅ 实现检查清单

集成月度任务系统时，请确保：

- [ ] 添加月度任务状态变量
- [ ] 实现进度计算逻辑
- [ ] 实现钓鱼补齐功能
- [ ] 实现竞技场补齐功能
- [ ] 实现刷新进度功能
- [ ] 实现一键完成功能
- [ ] 添加物品数量解析函数
- [ ] 添加UI组件
- [ ] 添加相关游戏命令
- [ ] 错误处理和用户提示
- [ ] 测试各种边界情况
- [ ] 优化请求间隔

---

## 🐛 常见问题

### Q1: 补齐数量不准确
**A**: 检查monthProgress的计算，确保使用向上取整（Math.ceil）

### Q2: 鱼竿数量解析失败
**A**: 检查items数据结构，可能需要适配不同的服务端格式

### Q3: 竞技场体力不足
**A**: 在topUpArena中已经处理，会根据当前体力调整战斗次数

### Q4: 补齐操作卡住
**A**: 检查WebSocket连接状态，确保sendMessageWithPromise正常工作

---

## 🎉 总结

月度任务系统是一个完整的自动化功能模块，包含：
- ✅ 智能进度计算
- ✅ 资源优先级策略
- ✅ 自动补齐执行
- ✅ 友好的用户界面
- ✅ 完善的错误处理

这个系统大大提升了玩家完成月度任务的效率！

