# 月度任务系统详细实现文档 ## 📊 功能概述 月度任务系统是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
进度与一键补齐
补齐规则:让"当前天数比例"和"完成比例"一致; 若无剩余天数则按满额({{FISH_TARGET}}/{{ARENA_TARGET}})计算。