Files
xyzw_web_helper/MD说明文件夹/月度任务系统详细实现.md
2025-10-17 20:56:50 +08:00

684 lines
16 KiB
Markdown
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.

# 月度任务系统详细实现文档
## 📊 功能概述
月度任务系统是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正常工作
---
## 🎉 总结
月度任务系统是一个完整的自动化功能模块,包含:
- ✅ 智能进度计算
- ✅ 资源优先级策略
- ✅ 自动补齐执行
- ✅ 友好的用户界面
- ✅ 完善的错误处理
这个系统大大提升了玩家完成月度任务的效率!