fix:修复若干bug

This commit is contained in:
steve
2025-09-03 04:18:19 +08:00
parent fe47c5257f
commit 767c4149b1
11 changed files with 1194 additions and 404 deletions

View File

@@ -2,7 +2,11 @@
<div class="daily-task-container">
<div class="task-header">
<div class="header-left">
<img src="/icons/174023274867420.png" alt="每日任务" class="task-icon">
<img
src="/icons/174023274867420.png"
alt="每日任务"
class="task-icon"
>
<div class="title-container">
<h3>每日任务</h3>
<p>当前进度</p>
@@ -12,14 +16,20 @@
<div class="header-right">
<div
class="status-indicator"
:class="{ completed: isAllCompleted }"
:class="{ completed: isFull }"
@click="showTaskDetails = true"
>
<div class="status-dot" :class="{ completed: isAllCompleted }"></div>
<div
class="status-dot"
:class="{ completed: isFull }"
/>
<span>任务详情</span>
</div>
<button class="settings-button" @click="showSettings = true">
<button
class="settings-button"
@click="showSettings = true"
>
<n-icon><Settings /></n-icon>
</button>
</div>
@@ -29,7 +39,7 @@
<div class="progress-container">
<n-progress
type="line"
:percentage="progressPercentage"
:percentage="dailyPoint"
:height="8"
:border-radius="4"
:color="progressColor"
@@ -45,12 +55,21 @@
<!-- 一键执行按钮 -->
<button
class="execute-button"
:disabled="isExecuting"
@click="executeAllTasks"
:disabled="busy"
@click="runDailyFix"
>
<span v-if="isExecuting" class="loading-text">
<svg class="loading-icon" viewBox="0 0 24 24">
<path fill="currentColor" d="M12 22c5.421 0 10-4.579 10-10h-2c0 4.337-3.663 8-8 8s-8-3.663-8-8c0-4.336 3.663-8 8-8V2C6.579 2 2 6.58 2 12c0 5.421 4.579 10 10 10z"/>
<span
v-if="busy"
class="loading-text"
>
<svg
class="loading-icon"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M12 22c5.421 0 10-4.579 10-10h-2c0 4.337-3.663 8-8 8s-8-3.663-8-8c0-4.336 3.663-8 8-8V2C6.579 2 2 6.58 2 12c0 5.421 4.579 10 10 10z"
/>
</svg>
执行中...
</span>
@@ -77,10 +96,9 @@
<div class="setting-item">
<label class="setting-label">竞技场阵容</label>
<n-select
v-model:value="taskSettings.arenaFormation"
v-model:value="settings.arenaFormation"
:options="formationOptions"
size="small"
@update:value="saveSettings"
/>
</div>
@@ -88,10 +106,9 @@
<div class="setting-item">
<label class="setting-label">BOSS阵容</label>
<n-select
v-model:value="taskSettings.bossFormation"
v-model:value="settings.bossFormation"
:options="formationOptions"
size="small"
@update:value="saveSettings"
/>
</div>
@@ -99,10 +116,9 @@
<div class="setting-item">
<label class="setting-label">BOSS次数</label>
<n-select
v-model:value="taskSettings.bossTimes"
v-model:value="settings.bossTimes"
:options="bossTimesOptions"
size="small"
@update:value="saveSettings"
/>
</div>
@@ -111,48 +127,42 @@
<div class="switch-row">
<span class="switch-label">领罐子</span>
<n-switch
v-model:value="taskSettings.claimBottle"
@update:value="saveSettings"
v-model:value="settings.claimBottle"
/>
</div>
<div class="switch-row">
<span class="switch-label">领挂机</span>
<n-switch
v-model:value="taskSettings.claimHangUp"
@update:value="saveSettings"
v-model:value="settings.claimHangUp"
/>
</div>
<div class="switch-row">
<span class="switch-label">竞技场</span>
<n-switch
v-model:value="taskSettings.arenaEnable"
@update:value="saveSettings"
v-model:value="settings.arenaEnable"
/>
</div>
<div class="switch-row">
<span class="switch-label">开宝箱</span>
<n-switch
v-model:value="taskSettings.openBox"
@update:value="saveSettings"
v-model:value="settings.openBox"
/>
</div>
<div class="switch-row">
<span class="switch-label">领取邮件奖励</span>
<n-switch
v-model:value="taskSettings.claimEmail"
@update:value="saveSettings"
v-model:value="settings.claimEmail"
/>
</div>
<div class="switch-row">
<span class="switch-label">付费招募</span>
<n-switch
v-model:value="taskSettings.payRecruit"
@update:value="saveSettings"
v-model:value="settings.payRecruit"
/>
</div>
</div>
@@ -176,7 +186,7 @@
<div class="task-list">
<div
v-for="task in dailyTasks"
v-for="task in tasks"
:key="task.id"
class="task-item"
>
@@ -214,21 +224,24 @@
</div>
</template>
<div class="log-container" ref="logContainer">
<div
ref="logContainer"
class="log-container"
>
<div
v-for="log in executionLogs"
:key="log.id"
v-for="logItem in logList"
:key="logItem.time + logItem.message"
class="log-item"
>
<span class="log-time">{{ formatTime(log.time) }}</span>
<span class="log-time">{{ logItem.time }}</span>
<span
class="log-message"
:class="{
error: log.type === 'error',
success: log.type === 'success'
error: logItem.type === 'error',
success: logItem.type === 'success'
}"
>
{{ log.message }}
{{ logItem.message }}
</span>
</div>
</div>
@@ -237,7 +250,7 @@
</template>
<script setup>
import { ref, computed, watch, nextTick, onMounted } from 'vue'
import { ref, reactive, computed, watch, onMounted, onBeforeUnmount, nextTick } from 'vue'
import { useTokenStore } from '@/stores/tokenStore'
import { useMessage } from 'naive-ui'
import {
@@ -255,12 +268,11 @@ const message = useMessage()
const showSettings = ref(false)
const showTaskDetails = ref(false)
const showLog = ref(false)
const isExecuting = ref(false)
const busy = ref(false)
const logContainer = ref(null)
const executionLogs = ref([])
// 任务设置
const taskSettings = ref({
// 任务设置 - 基于参考代码
const settings = reactive({
arenaFormation: 1,
bossFormation: 1,
bossTimes: 4,
@@ -272,8 +284,8 @@ const taskSettings = ref({
claimEmail: true
})
// 每日任务列表
const dailyTasks = ref([
// 每日任务列表 - 基于参考代码
const tasks = ref([
{ id: 1, name: '登录一次游戏', completed: false, loading: false },
{ id: 2, name: '分享一次游戏', completed: false, loading: false },
{ id: 3, name: '赠送好友3次金币', completed: false, loading: false },
@@ -287,65 +299,35 @@ const dailyTasks = ref([
])
// 选项配置
const formationOptions = [
{ label: '阵容1', value: 1 },
{ label: '阵容2', value: 2 },
{ label: '阵容3', value: 3 },
{ label: '阵容4', value: 4 }
]
const formationOptions = [1,2,3,4].map(v => ({ label: `阵容${v}`, value: v }))
const bossTimesOptions = [0,1,2,3,4].map(v => ({ label: `${v}`, value: v }))
const bossTimesOptions = [
{ label: '0次', value: 0 },
{ label: '1次', value: 1 },
{ label: '2次', value: 2 },
{ label: '3次', value: 3 },
{ label: '4次', value: 4 }
]
// 计算属性
// 计算属性 - 基于参考代码逻辑
const roleInfo = computed(() => {
return tokenStore.gameData?.roleInfo
return tokenStore.selectedTokenRoleInfo
})
const dailyTaskData = computed(() => {
return roleInfo.value?.role?.dailyTask
const roleDailyPoint = computed(() => {
return roleInfo.value?.role?.dailyTask?.dailyPoint ?? 0
})
const progressPercentage = computed(() => {
const current = dailyTaskData.value?.dailyPoint || 0
return current > 100 ? 100 : current
})
// 进度 - 基于参考代码
const dailyPoint = computed(() => Math.min(roleDailyPoint.value, 100))
const isFull = computed(() => dailyPoint.value >= 100)
const progressColor = computed(() => isFull.value ? '#10b981' : '#3b82f6')
const isAllCompleted = computed(() => {
return progressPercentage.value === 100
})
// 日志系统 - 基于参考代码的log函数
const logList = ref([])
const LOG_MAX = 500
const progressColor = computed(() => {
return progressPercentage.value === 100 ? '#10b981' : '#3b82f6'
})
// 更新任务完成状态
const updateTaskStatus = () => {
if (!dailyTaskData.value?.complete) return
const log = (message, type = 'info') => {
const time = new Date().toLocaleTimeString()
logList.value.push({ time, message, type })
const completed = dailyTaskData.value.complete
dailyTasks.value.forEach(task => {
const taskStatus = completed[task.id.toString()]
task.completed = taskStatus === -1 // -1 表示已完成
})
}
// 添加日志
const addLog = (message, type = 'info') => {
const log = {
id: Date.now() + Math.random(),
time: new Date().toLocaleTimeString(),
message,
type
if (logList.value.length > LOG_MAX) {
logList.value.splice(0, logList.value.length - LOG_MAX)
}
executionLogs.value.push(log)
// 自动滚动到底部
nextTick(() => {
if (logContainer.value) {
logContainer.value.scrollTop = logContainer.value.scrollHeight
@@ -353,113 +335,320 @@ const addLog = (message, type = 'info') => {
})
}
// 保存设置
const saveSettings = () => {
// 这里可以保存到 localStorage 或发送到服务器
localStorage.setItem('taskSettings', JSON.stringify(taskSettings.value))
// 超时执行器 - 基于参考代码的 callWithRetry 函数
const callWithRetry = async (fn, opt = {}) => {
const timeoutMs = opt?.timeoutMs ?? 30000
const retries = opt?.retries ?? 1
const delayMs = opt?.delayMs ?? 600
for (let attempt = 0; attempt <= retries; attempt++) {
try {
const r = await Promise.race([
fn(),
new Promise((_, rej) => setTimeout(() => rej(new Error(`请求超时(${timeoutMs}ms)`)), timeoutMs))
])
return r
} catch (err) {
if (attempt === retries) throw err
await new Promise(res => setTimeout(res, delayMs * (attempt + 1)))
}
}
throw new Error('unexpected')
}
// 格式化时间
const formatTime = (timeString) => {
return timeString
// 消息错误处理 - 基于参考代码的onRaw函数
const onRaw = (evt) => {
const err = evt?._raw?.error
if (err) log(String(err), 'error')
}
// 执行所有任务
const executeAllTasks = async () => {
if (!tokenStore.selectedToken || isExecuting.value) return
// 同步服务器任务完成状态 - 基于参考代码的 syncCompleteFromServer 函数
const syncCompleteFromServer = (resp) => {
if (!resp?.role?.dailyTask?.complete) return
const complete = resp.role.dailyTask.complete
const isDone = (v) => v === -1
isExecuting.value = true
Object.keys(complete).forEach(k => {
const id = Number(k)
const idx = tasks.value.findIndex(t => t.id === id)
if (idx >= 0) tasks.value[idx].completed = isDone(complete[k])
})
}
// 刷新角色信息 - 基于参考代码
const refreshRoleInfo = async () => {
if (!tokenStore.selectedToken) {
throw new Error('没有选中的Token')
}
const tokenId = tokenStore.selectedToken.id
const e = await callWithRetry(() =>
tokenStore.sendMessageWithPromise(tokenId, 'role_getroleinfo', {})
)
return e
}
// 一键补差 - 基于参考代码的runDailyFix函数
const runDailyFix = async () => {
if (!tokenStore.selectedToken || busy.value) return
// 设置消息监听和禁用显示
tokenStore.setMessageListener(onRaw)
tokenStore.setShowMsg(false)
busy.value = true
showLog.value = true
executionLogs.value = []
logList.value = []
addLog('开始执行任务...')
log('开始执行任务...')
try {
const tokenId = tokenStore.selectedToken.id
const e = await refreshRoleInfo()
log('获取角色信息成功')
// 获取角色信息
addLog('获取角色信息...')
await tokenStore.sendMessageWithPromise(tokenId, 'role_getroleinfo')
addLog('获取角色信息成功', 'success')
await U(e, log)
// 执行各种任务
if (taskSettings.value.claimHangUp) {
addLog('领取挂机奖励...')
await tokenStore.sendMessageWithPromise(tokenId, 'system_claimhangupreward')
}
if (taskSettings.value.claimBottle) {
addLog('领取盐罐奖励...')
await tokenStore.sendMessageWithPromise(tokenId, 'bottlehelper_claim')
}
if (taskSettings.value.payRecruit) {
addLog('进行招募...')
await tokenStore.sendMessageWithPromise(tokenId, 'hero_recruit', {
byClub: false,
recruitNumber: 1,
recruitType: 3
})
}
if (taskSettings.value.openBox) {
addLog('开启宝箱...')
await tokenStore.sendMessageWithPromise(tokenId, 'item_openbox', {
itemId: 2001,
number: 3
})
}
if (taskSettings.value.arenaEnable) {
addLog('进行竞技场战斗...')
await tokenStore.sendMessageWithPromise(tokenId, 'fight_startareaarena', {
targetId: 530479307
})
}
if (taskSettings.value.claimEmail) {
addLog('领取邮件奖励...')
await tokenStore.sendMessageWithPromise(tokenId, 'mail_claimallattachment', {
category: 0
})
}
// 最后再次获取角色信息更新状态
addLog('更新角色信息...')
await tokenStore.sendMessageWithPromise(tokenId, 'role_getroleinfo')
addLog('任务执行完成!', 'success')
log('任务执行完成', 'success')
message.success('任务处理完成')
} catch (error) {
addLog(`任务执行失败: ${error.message}`, 'error')
} catch (e) {
const msg = e?.message ?? (typeof e === 'string' ? e : JSON.stringify(e))
log(`任务执行失败: ${msg}`, 'error')
message.error('任务执行失败')
} finally {
isExecuting.value = false
tokenStore.setShowMsg(true)
busy.value = false
}
}
// 监听角色信息变化
watch(dailyTaskData, () => {
updateTaskStatus()
}, { deep: true, immediate: true })
// 执行器U函数 - 基于完整参考代码重写
const U = async (roleInfoResp, logFn, progress) => {
const tokenId = tokenStore.selectedToken.id
logFn?.('检查每日任务完成情况')
// 初始化设置
const initSettings = () => {
const saved = localStorage.getItem('taskSettings')
if (saved) {
try {
taskSettings.value = { ...taskSettings.value, ...JSON.parse(saved) }
} catch (error) {
console.error('加载设置失败:', error)
// 1) 已完成任务 → 生成要跳过的指令"指纹"
const officialList = [
{ id: 1, name: '登录一次游戏', cmds: [] },
{ id: 2, name: '分享一次游戏', cmds: ['system_mysharecallback'] },
{ id: 3, name: '赠送好友3次金币', cmds: ['friend_batch'] },
{ id: 4, name: '进行2次招募', cmds: ['hero_recruit'] },
{ id: 5, name: '领取5次挂机奖励', cmds: ['system_claimhangupreward'] },
{ id: 6, name: '进行3次点金', cmds: ['system_buygold'] },
{ id: 7, name: '开启3次宝箱', cmds: ['item_openbox'] },
{ id: 12, name: '黑市购买1次物品请设置采购清单', cmds: ['store_purchase'] },
{ id: 13, name: '进行1场竞技场战斗' },
{ id: 14, name: '收获1个任意盐罐', cmds: ['bottlehelper_stop', 'bottlehelper_start', 'bottlehelper_claim'] }
]
const completedMap = roleInfoResp.role?.dailyTask?.complete ?? {}
const doneIds = new Set()
const skipFp = new Set()
for (const k of Object.keys(completedMap)) {
const id = Number(k)
if (completedMap[k] === -1) {
doneIds.add(id)
const found = officialList.find(x => x.id === id)
if (found?.cmds) {
for (const c of found.cmds) skipFp.add(fpOf({ cmd: c, respKey: '' }))
}
logFn?.(`${officialList.find(x=>x.id===id)?.name ?? id} 完成`, 'success')
}
}
// 2) 组装动态命令段(依据统计与设置)
const cmds = []
// 2.1 切换 BOSS 阵容
cmds.push({ cmd: 'presetteam_getinfo', respKey: 'presetteam_getinforesp', sendMsg: '准备切换阵容' })
cmds.push({ cmd: 'presetteam_saveteam', respKey: 'presetteam_saveteamresp', params: { teamId: settings.bossFormation }, sendMsg: '切换攻打BOSS阵容中', successMsg: `成功切换阵容 ${settings.bossFormation}` })
// 2.2 军团 BOSS 次数
const alreadyLegion = Number(roleInfoResp.role?.statistics?.['legion:boss'] ?? 0)
const leftLegion = isTodayAvailable(roleInfoResp.role?.statisticsTime?.['legion:boss']) ? settings.bossTimes : Math.max(settings.bossTimes - alreadyLegion, 0)
for (let i = 0; i < leftLegion; i++) cmds.push({ cmd: 'fight_startlegionboss', respKey: 'fight_startlegionbossresp', sendMsg: '攻打每日俱乐部BOSS', successMsg: '攻打每日俱乐部BOSS成功' })
// 2.3 免费"钓鱼"
if (isTodayAvailable(roleInfoResp.role?.statisticsTime?.['artifact:normal:lottery:time'])) {
for (let i = 0; i < 3; i++) cmds.push({ cmd: 'artifact_lottery', respKey: 'syncrewardresp', sendMsg: '每日免费钓鱼', successMsg: '每日免费钓鱼成功' })
}
// 2.4 灯神免费扫荡1..4
const kingdoms = ['魏国','蜀国','吴国','群雄']
for (let gid = 1; gid <= 4; gid++) {
if (isTodayAvailable(roleInfoResp.role?.statisticsTime?.[`genie: daily: free: ${gid}`])) {
cmds.push({ cmd: 'genie_sweep', respKey: 'syncrewardresp', params: { genieId: gid }, sendMsg: `领取 ${kingdoms[gid-1]} 免费灯神扫荡`, successMsg: `领取 ${kingdoms[gid-1]} 免费灯神扫荡成功` })
}
}
// 2.5 用户开关
if (settings.claimBottle) cmds.push({ cmd: 'bottlehelper_claim', respKey: 'syncrewardresp', sendMsg: '领取盐罐奖励', successMsg: '领取盐罐奖励成功' })
if (settings.payRecruit) cmds.push({ cmd: 'hero_recruit', respKey: 'hero_recruitresp', params: { recruitType: 1 }, sendMsg: '招募付费1次', successMsg: '招募1次成功' })
if (settings.openBox) cmds.push({ cmd: 'item_openbox', respKey: 'item_openboxresp', params: { itemId: 2001, number: 10 }, sendMsg: '开启木质宝箱10个', successMsg: '开启木质宝箱10个成功' })
if (isTodayAvailable(roleInfoResp.role?.statisticsTime?.['buy:gold'])) {
for (let i = 0; i < 3; i++) cmds.push({ cmd: 'system_buygold', respKey: 'syncrewardresp', sendMsg: '免费点金', successMsg: '免费点金成功' })
}
if (settings.claimHangUp) {
for (let i = 0; i < 4; i++) cmds.push({ cmd: 'system_mysharecallback', respKey: 'syncresp', params: { isSkipShareCard: true, type: 2 }, sendMsg: '挂机加钟', successMsg: '加钟成功' })
cmds.push({ cmd: 'system_claimhangupreward', respKey: 'system_claimhanguprewardresp', sendMsg: '领取挂机奖励', successMsg: '领取挂机奖励成功' })
cmds.push({ cmd: 'system_mysharecallback', respKey: 'syncresp', params: { isSkipShareCard: true, type: 2 }, sendMsg: '挂机加钟', successMsg: '加钟成功' })
}
if (settings.claimEmail) cmds.push({ cmd: 'mail_claimallattachment', respKey: 'mail_claimallattachmentresp', sendMsg: '领取邮件奖励', successMsg: '邮件奖励领取成功' })
// 2.6 固定段
const dow = new Date().getDay()
const DAY_BOSS_MAP = [9904, 9905, 9901, 9902, 9903, 9904, 9905]
for (let i = 0; i < 3; i++) cmds.push({ cmd: 'fight_startboss', respKey: 'fight_startbossresp', params: { bossId: DAY_BOSS_MAP[dow] }, sendMsg: '攻打每日BOSS', successMsg: '攻打每日BOSS成功' })
for (let i = 0; i < 3; i++) cmds.push({ cmd: 'genie_buysweep', respKey: 'syncrewardresp', sendMsg: '领取每日免费扫荡卷', successMsg: '领取每日免费扫荡卷成功' })
cmds.push(
{ cmd: 'system_signinreward', respKey: 'syncrewardresp', sendMsg: '福利签到', successMsg: '同步奖励' },
{ cmd: 'discount_claimreward', respKey: 'syncrewardresp', sendMsg: '领取每日礼包', successMsg: '同步奖励' },
{ cmd: 'legion_signin', respKey: 'legion_signinresp', sendMsg: '俱乐部签到', successMsg: '同步奖励' },
{ cmd: 'card_claimreward', respKey: 'syncrewardresp', sendMsg: '领取每日免费礼包', successMsg: '同步奖励' },
{ cmd: 'card_claimreward', respKey: 'syncrewardresp', params: { cardId: 4003 }, sendMsg: '领取永久卡礼包', successMsg: '同步奖励' },
{ cmd: 'system_mysharecallback', respKey: 'syncresp', sendMsg: '分享一次游戏', successMsg: '分享一次游戏成功' },
{ cmd: 'friend_batch', respKey: 'friend_batchresp', sendMsg: '赠送好友金币', successMsg: '赠送好友金币成功' },
{ cmd: 'hero_recruit', respKey: 'hero_recruitresp', sendMsg: '免费招募1次', successMsg: '招募1次成功' }
)
// 领取积分taskId 1..10
for (let i = 1; i <= 10; i++) cmds.push({ cmd: 'task_claimdailypoint', respKey: 'syncresp', params: { taskId: i }, sendMsg: '领取任务奖励', successMsg: '领取任务奖励成功' })
// 领取日/周奖励
cmds.push({ cmd: 'task_claimdailyreward', respKey: 'syncrewardresp', sendMsg: '领取任务奖励', successMsg: '领取任务奖励成功' })
cmds.push({ cmd: 'task_claimweekreward', respKey: 'syncrewardresp', sendMsg: '领取任务奖励', successMsg: '领取任务奖励成功' })
// 3) 过滤掉"已完成任务对应的指令"与真正重复项(按指纹)
const uniq = new Map()
for (const it of cmds) {
const fp = fpOf(it)
if (skipFp.has(fpOf({ cmd: it.cmd, respKey: '', params: it.params }))) continue
if (!uniq.has(fp)) uniq.set(fp, it)
}
const queue = Array.from(uniq.values())
// 4) 竞技场(单独先跑)
if (settings.arenaEnable && completedMap[13] !== -1) {
logFn?.('竞技场自动战斗')
await callWithRetry(() => tokenStore.sendMessageWithPromise(tokenId, 'presetteam_getinfo', {}))
.then(async (resp) => {
if (resp?.presetTeamInfo?.useTeamId === settings.arenaFormation) {
logFn?.(`当前阵容为阵容 ${settings.arenaFormation}`)
} else {
await callWithRetry(() => tokenStore.sendMessageWithPromise(tokenId, 'presetteam_saveteam', { teamId: settings.arenaFormation }))
logFn?.(`切换竞技场阵容为阵容 ${settings.arenaFormation}`)
}
})
await callWithRetry(() => tokenStore.sendMessageWithPromise(tokenId, 'arena_startarea', {}))
for (let i = 0; i < 3; i++) {
const target = await callWithRetry(() => tokenStore.sendMessageWithPromise(tokenId, 'arena_getareatarget', {}))
const targetId = target?.roleList?.[0]?.roleId
if (targetId) await callWithRetry(() => tokenStore.sendMessageWithPromise(tokenId, 'fight_startareaarena', { targetId }))
}
} else {
logFn?.('今日竞技场任务已完成', 'success')
}
// 5) 依次执行队列 + 进度
const total = queue.length || 1
let done = 0
for (const it of queue) {
if (it.sendMsg) logFn?.(it.sendMsg)
try {
await callWithRetry(() => tokenStore.sendMessageWithPromise(tokenId, it.cmd, it.params || {}))
if (it.successMsg) logFn?.(it.successMsg, 'success')
} catch (err) {
logFn?.('任务执行失败', 'error')
} finally {
done += 1
const pct = Math.min(100, Math.floor(done * 100 / total))
if (progress && tokenId) progress(tokenId, pct)
}
}
// 收尾:确保 100%
if (progress && tokenId) progress(tokenId, 100)
}
// 生命周期
onMounted(() => {
initSettings()
updateTaskStatus()
// 辅助函数 - 基于参考代码
const fpOf = (item) => {
return `${item.cmd}|${JSON.stringify(item.params ?? {})}`
}
const getCurrentRole = () => {
return tokenStore.selectedToken ? { roleId: tokenStore.selectedToken.id } : null
}
const loadSettings = (roleId) => {
try {
const raw = localStorage.getItem(`daily-settings:${roleId}`)
return raw ? JSON.parse(raw) : null
} catch (error) {
console.error('Failed to load settings:', error)
return null
}
}
const saveSettings = (roleId, s) => {
try {
localStorage.setItem(`daily-settings:${roleId}`, JSON.stringify(s))
} catch (error) {
console.error('Failed to save settings:', error)
}
}
const isTodayAvailable = (v) => { return !!v }
// 监听设置变化 - 基于参考代码的watch逻辑
watch(settings, (cur) => {
const role = getCurrentRole()
if (role) saveSettings(role.roleId, cur)
}, { deep: true })
// 监听token选择变化
watch(() => tokenStore.selectedToken, async (newToken, oldToken) => {
if (newToken && newToken !== oldToken) {
// 加载新token的设置
const saved = loadSettings(newToken.id)
if (saved) Object.assign(settings, saved)
// 同步任务状态
syncCompleteFromServer(tokenStore.selectedTokenRoleInfo)
}
}, { immediate: true })
// 生命周期 - 基于参考代码的onMounted函数
onMounted(async () => {
// 首次拉取角色信息如果有选中的token
if (tokenStore.selectedToken) {
try {
await refreshRoleInfo()
} catch (error) {
console.warn('初始化时获取角色信息失败:', error.message)
// 如果获取失败,尝试发送普通消息(不等待响应)
try {
tokenStore.sendMessage(tokenStore.selectedToken.id, 'role_getroleinfo', {})
} catch (sendError) {
console.warn('发送角色信息请求失败:', sendError.message)
}
}
}
const role = getCurrentRole()
if (role) {
const saved = loadSettings(role.roleId)
if (saved) Object.assign(settings, saved)
}
// 同步完成态(使用现有的角色信息)
syncCompleteFromServer(tokenStore.selectedTokenRoleInfo)
})
// 清理监听 - 基于参考代码的onBeforeUnmount
onBeforeUnmount(() => {
tokenStore.setMessageListener(undefined)
})
</script>
@@ -589,8 +778,8 @@ onMounted(() => {
}
&:disabled {
background: var(--bg-tertiary);
color: var(--text-tertiary);
background: #e5e7eb;
color: #9ca3af;
cursor: not-allowed;
}
}

View File

@@ -13,13 +13,20 @@
<!-- 盐罐机器人状态 -->
<div class="status-card bottle-helper">
<div class="card-header">
<img src="/icons/173746572831736.png" alt="盐罐图标" class="status-icon">
<img
src="/icons/173746572831736.png"
alt="盐罐图标"
class="status-icon"
>
<div class="status-info">
<h3>盐罐机器人</h3>
<p>剩余时间</p>
</div>
<div class="status-badge" :class="{ active: bottleHelper.isRunning }">
<div class="status-dot"></div>
<div
class="status-badge"
:class="{ active: bottleHelper.isRunning }"
>
<div class="status-dot" />
<span>{{ bottleHelper.isRunning ? '运行中' : '已停止' }}</span>
</div>
</div>
@@ -40,13 +47,20 @@
<!-- 挂机状态 -->
<div class="status-card hang-up">
<div class="card-header">
<img src="/icons/174061875626614.png" alt="挂机图标" class="status-icon">
<img
src="/icons/174061875626614.png"
alt="挂机图标"
class="status-icon"
>
<div class="status-info">
<h3>挂机时间</h3>
<p>已挂机{{ formatTime(hangUp.elapsedTime) }}</p>
</div>
<div class="status-badge" :class="{ active: hangUp.isActive }">
<div class="status-dot"></div>
<div
class="status-badge"
:class="{ active: hangUp.isActive }"
>
<div class="status-dot" />
<span>{{ hangUp.isActive ? '挂机中' : '已完成' }}</span>
</div>
</div>
@@ -55,11 +69,49 @@
{{ formatTime(hangUp.remainingTime) }}
</div>
<div class="action-row">
<button class="action-button secondary" @click="extendHangUp">
加钟
<button
class="action-button secondary"
:disabled="hangUp.isExtending"
@click="extendHangUp"
>
<span
v-if="hangUp.isExtending"
class="loading-text"
>
<svg
class="loading-icon"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M12 22c5.421 0 10-4.579 10-10h-2c0 4.337-3.663 8-8 8s-8-3.663-8-8c0-4.336 3.663-8 8-8V2C6.579 2 2 6.58 2 12c0 5.421 4.579 10 10 10z"
/>
</svg>
加钟中...
</span>
<span v-else>加钟</span>
</button>
<button class="action-button primary" @click="claimHangUpReward">
领取奖励
<button
class="action-button primary"
:disabled="hangUp.isClaiming"
@click="claimHangUpReward"
>
<span
v-if="hangUp.isClaiming"
class="loading-text"
>
<svg
class="loading-icon"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M12 22c5.421 0 10-4.579 10-10h-2c0 4.337-3.663 8-8 8s-8-3.663-8-8c0-4.336 3.663-8 8-8V2C6.579 2 2 6.58 2 12c0 5.421 4.579 10 10 10z"
/>
</svg>
领取中...
</span>
<span v-else>领取奖励</span>
</button>
</div>
</div>
@@ -68,13 +120,20 @@
<!-- 俱乐部排位 -->
<div class="status-card legion-match">
<div class="card-header">
<img src="/icons/1733492491706152.png" alt="俱乐部图标" class="status-icon">
<img
src="/icons/1733492491706152.png"
alt="俱乐部图标"
class="status-icon"
>
<div class="status-info">
<h3>俱乐部排位</h3>
<p>赛事状态</p>
</div>
<div class="status-badge" :class="{ active: legionMatch.isRegistered }">
<div class="status-dot"></div>
<div
class="status-badge"
:class="{ active: legionMatch.isRegistered }"
>
<div class="status-dot" />
<span>{{ legionMatch.isRegistered ? '已报名' : '未报名' }}</span>
</div>
</div>
@@ -96,22 +155,35 @@
<!-- 俱乐部签到 -->
<div class="status-card legion-signin">
<div class="card-header">
<img src="/icons/1733492491706148.png" alt="签到图标" class="status-icon">
<img
src="/icons/1733492491706148.png"
alt="签到图标"
class="status-icon"
>
<div class="status-info">
<h3>俱乐部签到</h3>
<p>每日签到状态</p>
</div>
<div class="status-badge" :class="{ active: legionSignin.isSignedIn }">
<div class="status-dot"></div>
<div
class="status-badge"
:class="{ active: legionSignin.isSignedIn }"
>
<div class="status-dot" />
<span>{{ legionSignin.isSignedIn ? '已签到' : '待签到' }}</span>
</div>
</div>
<div class="card-content">
<p class="club-name" v-if="legionSignin.clubName">
<p
v-if="legionSignin.clubName"
class="club-name"
>
当前俱乐部<br>
<strong>{{ legionSignin.clubName }}</strong>
</p>
<p class="description" v-else>
<p
v-else
class="description"
>
尚未加入任何俱乐部
</p>
<button
@@ -127,13 +199,17 @@
<!-- 咸鱼大冲关 -->
<div class="status-card study">
<div class="card-header">
<img src="/icons/1736425783912140.png" alt="学习图标" class="status-icon">
<img
src="/icons/1736425783912140.png"
alt="学习图标"
class="status-icon"
>
<div class="status-info">
<h3>咸鱼大冲关</h3>
<p>每日知识挑战</p>
</div>
<div class="status-badge weekly">
<div class="status-dot"></div>
<div class="status-dot" />
<span>每周任务</span>
</div>
</div>
@@ -146,9 +222,18 @@
:disabled="study.isAnswering"
@click="startStudy"
>
<span v-if="study.isAnswering" class="loading-text">
<svg class="loading-icon" viewBox="0 0 24 24">
<path fill="currentColor" d="M12 22c5.421 0 10-4.579 10-10h-2c0 4.337-3.663 8-8 8s-8-3.663-8-8c0-4.336 3.663-8 8-8V2C6.579 2 2 6.58 2 12c0 5.421 4.579 10 10 10z"/>
<span
v-if="study.isAnswering"
class="loading-text"
>
<svg
class="loading-icon"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M12 22c5.421 0 10-4.579 10-10h-2c0 4.337-3.663 8-8 8s-8-3.663-8-8c0-4.336 3.663-8 8-8V2C6.579 2 2 6.58 2 12c0 5.421 4.579 10 10 10z"
/>
</svg>
答题中...
</span>
@@ -156,7 +241,6 @@
</button>
</div>
</div>
</div>
</template>
@@ -183,7 +267,9 @@ const hangUp = ref({
remainingTime: 0,
elapsedTime: 0,
lastTime: 0,
hangUpTime: 0
hangUpTime: 0,
isExtending: false, // 加钟状态
isClaiming: false // 领取奖励状态
})
const legionMatch = ref({
@@ -333,40 +419,108 @@ const handleBottleHelper = () => {
message.info(bottleHelper.value.isRunning ? '重启盐罐机器人' : '启动盐罐机器人')
}
// 挂机操作
const extendHangUp = () => {
if (!tokenStore.selectedToken) return
// 挂机操作 - 参考HangUpStatus逻辑优化
const extendHangUp = async () => {
if (!tokenStore.selectedToken) {
message.warning('请先选择Token')
return
}
const tokenId = tokenStore.selectedToken.id
// 发送4次分享回调请求来加钟
for (let i = 0; i < 4; i++) {
try {
console.log('🕐 开始加钟操作...')
hangUp.value.isExtending = true
message.info('正在加钟...')
// 按照参考代码的逻辑发送4次分享回调请求
const promises = []
for (let i = 0; i < 4; i++) {
const promise = new Promise((resolve) => {
setTimeout(() => {
console.log(`🕐 发送第${i+1}次加钟请求`)
const result = tokenStore.sendMessage(tokenId, 'system_mysharecallback', {
isSkipShareCard: true,
type: 2
})
resolve(result)
}, i * 300) // 增加间隔时间确保稳定性
})
promises.push(promise)
}
// 等待所有请求完成
await Promise.all(promises)
console.log('🕐 所有加钟请求已发送')
// 延迟获取最新角色信息
setTimeout(() => {
console.log('🕐 加钟后获取最新角色信息')
tokenStore.sendMessage(tokenId, 'role_getroleinfo')
}, 1500)
// 延迟显示完成消息和重置状态
setTimeout(() => {
message.success('加钟操作已完成,请查看挂机剩余时间')
hangUp.value.isExtending = false
}, 2500)
} catch (error) {
console.error('🕐 加钟操作失败:', error)
message.error('加钟操作失败: ' + (error.message || '未知错误'))
hangUp.value.isExtending = false
}
}
const claimHangUpReward = async () => {
if (!tokenStore.selectedToken) {
message.warning('请先选择Token')
return
}
const tokenId = tokenStore.selectedToken.id
try {
console.log('🎁 开始领取挂机奖励...')
hangUp.value.isClaiming = true
message.info('正在领取挂机奖励...')
// 参考HangUpStatus的S函数逻辑
// 1. 发送初始分享回调
tokenStore.sendMessage(tokenId, 'system_mysharecallback')
// 2. 领取挂机奖励
setTimeout(() => {
tokenStore.sendMessage(tokenId, 'system_claimhangupreward')
}, 200)
// 3. 发送跳过分享卡片的回调
setTimeout(() => {
tokenStore.sendMessage(tokenId, 'system_mysharecallback', {
isSkipShareCard: true,
type: 2
})
}, i * 200)
}, 400)
// 4. 获取最新角色信息
setTimeout(() => {
tokenStore.sendMessage(tokenId, 'role_getroleinfo')
}, 600)
// 5. 显示完成消息并重置状态
setTimeout(() => {
message.success('挂机奖励领取完成')
hangUp.value.isClaiming = false
}, 1200)
console.log('🎁 挂机奖励领取操作序列已启动')
} catch (error) {
console.error('🎁 领取挂机奖励失败:', error)
message.error('领取挂机奖励失败: ' + (error.message || '未知错误'))
hangUp.value.isClaiming = false
}
message.info('正在加钟...')
}
const claimHangUpReward = () => {
if (!tokenStore.selectedToken) return
const tokenId = tokenStore.selectedToken.id
// 领取挂机奖励
tokenStore.sendMessage(tokenId, 'system_mysharecallback')
tokenStore.sendMessage(tokenId, 'system_claimhangupreward')
tokenStore.sendMessage(tokenId, 'system_mysharecallback', {
isSkipShareCard: true,
type: 2
})
tokenStore.sendMessage(tokenId, 'role_getroleinfo')
message.info('领取挂机奖励')
}
// 俱乐部排位报名

View File

@@ -1,7 +1,11 @@
<template>
<div class="team-status-card">
<div class="card-header">
<img src="/icons/Ob7pyorzmHiJcbab2c25af264d0758b527bc1b61cc3b.png" alt="队伍图标" class="team-icon">
<img
src="/icons/Ob7pyorzmHiJcbab2c25af264d0758b527bc1b61cc3b.png"
alt="队伍图标"
class="team-icon"
>
<div class="team-info">
<h3>队伍阵容</h3>
<p>当前使用的战斗阵容</p>
@@ -10,18 +14,18 @@
<button
v-for="teamId in availableTeams"
:key="teamId"
@click="selectTeam(teamId)"
:class="[
'team-button',
{ active: currentTeam === teamId }
]"
@click="selectTeam(teamId)"
>
{{ teamId }}
</button>
<button
@click="refreshTeamData"
class="team-button refresh-button"
title="刷新队伍数据"
@click="refreshTeamData"
>
🔄
</button>
@@ -58,7 +62,10 @@
</div>
</div>
<div v-if="!currentTeamHeroes.length" class="empty-team">
<div
v-if="!currentTeamHeroes.length"
class="empty-team"
>
<p>暂无队伍信息</p>
</div>
</div>

View File

@@ -94,7 +94,7 @@
type="warning"
@click="regenerateToken(roleId)"
>
重生成
刷新Token
</n-button>
<n-button
size="tiny"
@@ -240,20 +240,85 @@ const toggleWebSocket = (roleId, tokenData) => {
}
const regenerateToken = (roleId) => {
const oldTokenData = localTokenStore.getGameToken(roleId)
if (!oldTokenData) {
message.error('找不到对应的Token数据')
return
}
// 检查是否有源URL可以重新获取
if (!oldTokenData.sourceUrl) {
message.warning('该Token没有配置源地址无法重新生成。请手动重新导入Token。')
return
}
dialog.info({
title: '重新生成Token',
content: '确定要为此角色重新生成游戏Token吗',
title: '重新获取Token',
content: '确定要从源地址重新获取此角色的Token吗',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
const oldTokenData = localTokenStore.getGameToken(roleId)
if (oldTokenData) {
const newToken = 'game_token_' + Date.now() + '_' + Math.random().toString(36).substr(2, 16)
onPositiveClick: async () => {
try {
// 显示加载状态
const loadingMsg = message.loading('正在重新获取Token...', { duration: 0 })
// 从源URL重新获取token
let response
const sourceUrl = oldTokenData.sourceUrl
// 使用与TokenImport相同的跨域处理逻辑
const isLocalUrl = sourceUrl.startsWith(window.location.origin) ||
sourceUrl.startsWith('/') ||
sourceUrl.startsWith('http://localhost') ||
sourceUrl.startsWith('http://127.0.0.1')
if (isLocalUrl) {
response = await fetch(sourceUrl)
} else {
try {
response = await fetch(sourceUrl, {
method: 'GET',
headers: {
'Accept': 'application/json',
},
mode: 'cors'
})
} catch (corsError) {
throw new Error(`跨域请求被阻止。请确保目标服务器支持CORS。错误详情: ${corsError.message}`)
}
}
if (!response.ok) {
throw new Error(`请求失败: ${response.status} ${response.statusText}`)
}
const data = await response.json()
if (!data.token) {
throw new Error('返回数据中未找到token字段')
}
// 更新token
localTokenStore.updateGameToken(roleId, {
token: newToken,
regeneratedAt: new Date().toISOString()
token: data.token,
server: data.server || oldTokenData.server,
regeneratedAt: new Date().toISOString(),
lastRefreshed: new Date().toISOString()
})
message.success('Token已重新生成')
// 如果当前token有连接需要重新连接
if (localTokenStore.getWebSocketStatus(roleId) === 'connected') {
localTokenStore.closeWebSocketConnection(roleId)
setTimeout(() => {
localTokenStore.createWebSocketConnection(roleId, data.token, oldTokenData.wsUrl)
}, 500)
}
loadingMsg.destroy()
message.success('Token已成功重新获取')
} catch (error) {
console.error('重新获取Token失败:', error)
message.error(error.message || 'Token重新获取失败')
}
}
})

View File

@@ -2,14 +2,22 @@
<div class="tower-status-card">
<div class="card-header">
<div class="header-info">
<img src="/icons/1733492491706148.png" alt="爬塔图标" class="tower-icon">
<img
src="/icons/1733492491706148.png"
alt="爬塔图标"
class="tower-icon"
>
<div class="tower-info">
<h3>咸将塔</h3>
<p>一个不小心就过了</p>
</div>
</div>
<div class="energy-display">
<img src="/icons/xiaoyugan.png" alt="小鱼干" class="energy-icon">
<img
src="/icons/xiaoyugan.png"
alt="小鱼干"
class="energy-icon"
>
<span class="energy-count">{{ towerEnergy }}</span>
</div>
</div>
@@ -33,7 +41,16 @@
:disabled="!canClimb"
@click="startTowerClimb"
>
开始爬塔
{{ isClimbing.value ? '爬塔中...' : '开始爬塔' }}
</button>
<!-- 调试用的重置按钮只在开发环境显示 -->
<button
v-if="isClimbing.value"
class="reset-button"
@click="resetClimbingState"
>
重置状态
</button>
</div>
</div>
@@ -49,6 +66,8 @@ const message = useMessage()
// 响应式数据
const isClimbing = ref(false)
const climbTimeout = ref(null) // 用于超时重置状态
const lastClimbResult = ref(null) // 最后一次爬塔结果
// 计算属性 - 从gameData中获取塔相关信息
const roleInfo = computed(() => {
@@ -97,11 +116,22 @@ const towerEnergy = computed(() => {
})
const canClimb = computed(() => {
return towerEnergy.value > 0 && !isClimbing.value
const hasEnergy = towerEnergy.value > 0
const notClimbing = !isClimbing.value
console.log(`🗼 canClimb 计算: hasEnergy=${hasEnergy}, notClimbing=${notClimbing}, result=${hasEnergy && notClimbing}`)
return hasEnergy && notClimbing
})
// 方法
const startTowerClimb = async () => {
console.log('🗼 开始爬塔按钮被点击')
console.log('🗼 当前状态:', {
canClimb: canClimb.value,
isClimbing: isClimbing.value,
towerEnergy: towerEnergy.value,
hasSelectedToken: !!tokenStore.selectedToken
})
if (!tokenStore.selectedToken) {
message.warning('请先选择Token')
return
@@ -112,15 +142,34 @@ const startTowerClimb = async () => {
return
}
// 清除之前的超时
if (climbTimeout.value) {
clearTimeout(climbTimeout.value)
climbTimeout.value = null
}
// 确保在操作开始前设置状态
console.log('🗼 设置爬塔状态为true')
isClimbing.value = true
// 设置超时保护15秒后自动重置状态
climbTimeout.value = setTimeout(() => {
console.log('🗼 超时保护触发,自动重置爬塔状态')
isClimbing.value = false
climbTimeout.value = null
}, 15000)
try {
isClimbing.value = true
const tokenId = tokenStore.selectedToken.id
console.log('🗼 使用Token ID:', tokenId)
message.info('开始爬塔挑战...')
// 发送爬塔命令
console.log('🗼 发送爬塔命令...')
await tokenStore.sendMessageWithPromise(tokenId, 'fight_starttower', {}, 10000)
console.log('🗼 爬塔命令发送成功')
message.success('爬塔命令已发送')
// 立即查询塔信息以获取最新状态
@@ -131,14 +180,42 @@ const startTowerClimb = async () => {
setTimeout(async () => {
console.log('🗼 延迟查询塔信息')
await getTowerInfo()
// 清除超时并重置状态
if (climbTimeout.value) {
clearTimeout(climbTimeout.value)
climbTimeout.value = null
}
console.log('🗼 延迟查询完成,重置爬塔状态')
isClimbing.value = false
}, 3000)
} catch (error) {
console.error('爬塔失败:', error)
console.error('🗼 爬塔失败:', error)
message.error('爬塔失败: ' + (error.message || '未知错误'))
} finally {
// 发生错误时立即重置状态
if (climbTimeout.value) {
clearTimeout(climbTimeout.value)
climbTimeout.value = null
}
console.log('🗼 发生错误,立即重置爬塔状态')
isClimbing.value = false
}
// 注意:不要在这里设置 isClimbing.value = false
// 因为我们要等待延迟查询完成后再重置状态
}
// 重置爬塔状态的方法
const resetClimbingState = () => {
console.log('🗼 用户手动重置爬塔状态')
if (climbTimeout.value) {
clearTimeout(climbTimeout.value)
climbTimeout.value = null
}
isClimbing.value = falsexian1xian
message.info('爬塔状态已重置')
}
const getTowerInfo = async () => {
@@ -218,6 +295,36 @@ watch(() => tokenStore.selectedToken, (newToken, oldToken) => {
}
})
// 监听爬塔结果
watch(() => tokenStore.gameData.towerResult, (newResult, oldResult) => {
if (newResult && newResult.timestamp !== oldResult?.timestamp) {
console.log('🗼 收到新的爬塔结果:', newResult)
// 显示爬塔结果消息
if (newResult.success) {
message.success('咸将塔挑战成功!')
if (newResult.autoReward) {
setTimeout(() => {
message.success(`自动领取第${newResult.rewardFloor}层奖励`)
}, 1000)
}
} else {
message.error('咸将塔挑战失败')
}
// 重置爬塔状态
setTimeout(() => {
console.log('🗼 爬塔结果处理完成,重置状态')
if (climbTimeout.value) {
clearTimeout(climbTimeout.value)
climbTimeout.value = null
}
isClimbing.value = false
}, 2000)
}
}, { deep: true })
// 生命周期
onMounted(() => {
console.log('🗼 TowerStatus 组件已挂载')
@@ -346,6 +453,9 @@ onMounted(() => {
.card-actions {
margin-top: var(--spacing-lg);
display: flex;
flex-direction: column;
gap: var(--spacing-sm);
}
@@ -375,6 +485,24 @@ onMounted(() => {
}
}
.reset-button {
width: 100%;
padding: var(--spacing-xs) var(--spacing-sm);
font-size: var(--font-size-xs);
font-weight: var(--font-weight-medium);
border: 1px solid var(--warning-color);
border-radius: var(--border-radius-small);
background: transparent;
color: var(--warning-color);
cursor: pointer;
transition: all var(--transition-fast);
&:hover {
background: var(--warning-color);
color: white;
}
}
.debug-info {
margin-top: var(--spacing-sm);
padding: var(--spacing-xs);

View File

@@ -119,9 +119,6 @@ router.beforeEach((to, from, next) => {
// 检查是否需要Token
if (to.meta.requiresToken && !tokenStore.hasTokens) {
next('/tokens')
} else if (to.name === 'TokenImport' && tokenStore.hasTokens && tokenStore.selectedToken) {
// 如果已有token且已选择重定向到控制台
next('/dashboard')
} else if (to.path === '/' && tokenStore.hasTokens) {
// 首页重定向逻辑
if (tokenStore.selectedToken) {

View File

@@ -26,6 +26,11 @@ export const useTokenStore = defineStore('tokens', () => {
const selectedToken = computed(() =>
gameTokens.value.find(token => token.id === selectedTokenId.value)
)
// 获取当前选中token的角色信息
const selectedTokenRoleInfo = computed(() => {
return gameData.value.roleInfo
})
// Token管理
const addToken = (tokenData) => {
@@ -118,7 +123,7 @@ export const useTokenStore = defineStore('tokens', () => {
// 辅助函数:尝试解析队伍数据
const tryParseTeamData = (data, cmd) => {
console.log(`👥 尝试解析队伍数据 [${cmd}]:`, data)
// 静默解析,不打印详细日志
// 查找队伍相关字段
const teamFields = []
@@ -164,7 +169,7 @@ export const useTokenStore = defineStore('tokens', () => {
}
})
} else {
console.log(`👥 未找到明显的队伍字段,完整数据结构:`, analyzeDataStructure(data))
// 未找到队伍数据
}
}
@@ -182,46 +187,28 @@ export const useTokenStore = defineStore('tokens', () => {
message.decodedBody !== undefined ? message.decodedBody :
message.body
console.log(`📋 处理消息 [${tokenId}] ${cmd}:`, {
hasRawData: message.rawData !== undefined,
hasDecodedBody: message.decodedBody !== undefined,
hasBody: message.body !== undefined,
bodyType: body ? typeof body : 'undefined',
bodyContent: body,
originalCmd: message.cmd,
fullMessage: message
})
// 记录所有消息的原始命令名
console.log(`📨 收到消息 [${tokenId}] 原始cmd: "${message.cmd}", 处理cmd: "${cmd}"`)
// 特别记录所有包含tower的消息
if (cmd && cmd.includes('tower')) {
console.log(`🗼 发现塔相关消息 [${tokenId}] ${cmd}:`, message)
// 简化消息处理日志(移除详细结构信息)
if (cmd !== '_sys/ack') { // 过滤心跳消息
console.log(`📋 处理 [${tokenId}] ${cmd}`, body ? '✓' : '✗')
}
// 过滤塔相关消息的详细打印
// 处理角色信息 - 支持多种可能的响应命令
if (cmd === 'role_getroleinfo' || cmd === 'role_getroleinforesp' || cmd.includes('role') && cmd.includes('info')) {
console.log(`📊 匹配到角色信息命令: ${cmd}`)
console.log(`📊 角色信息 [${tokenId}]`)
if (body) {
gameData.value.roleInfo = body
gameData.value.lastUpdated = new Date().toISOString()
console.log('📊 角色信息已更新:', body)
console.log('📊 角色信息类型:', typeof body)
console.log('📊 角色信息内容概览:', Object.keys(body || {}))
console.log('📊 角色信息已更新')
// 特别检查塔信息
// 检查塔信息
if (body.role?.tower) {
console.log('🗼 在角色信息中找到塔信息:', body.role.tower)
} else if (body.tower) {
console.log('🗼 在响应根级别找到塔信息:', body.tower)
} else {
console.log('🗼 未找到塔信息在角色数据中')
console.log('📊 角色数据结构:', body.role ? Object.keys(body.role) : '没有role对象')
// 塔信息已更新
}
} else {
console.log('📊 角色信息响应body为空')
console.log('📊 角色信息响应为空')
}
}
@@ -229,7 +216,7 @@ export const useTokenStore = defineStore('tokens', () => {
else if (cmd === 'legion_getinfo') {
if (body) {
gameData.value.legionInfo = body
console.log('🏛️ 军团信息已更新:', body)
console.log('🏛️ 军团信息已更新')
}
}
@@ -239,7 +226,7 @@ export const useTokenStore = defineStore('tokens', () => {
cmd === 'presetteam_saveteam' || cmd === 'presetteam_saveteamresp' ||
cmd === 'role_gettargetteam' || cmd === 'role_gettargetteamresp' ||
(cmd && cmd.includes('presetteam')) || (cmd && cmd.includes('team'))) {
console.log(`👥 匹配到队伍信息命令: ${cmd}`)
console.log(`👥 队伍信息 [${tokenId}] ${cmd}`)
if (body) {
// 更新队伍数据
@@ -268,67 +255,133 @@ export const useTokenStore = defineStore('tokens', () => {
}
gameData.value.lastUpdated = new Date().toISOString()
console.log('👥 队伍信息已更新:', {
cmd: cmd,
updatedData: gameData.value.presetTeam,
bodyKeys: Object.keys(body),
bodyContent: body
})
console.log('👥 队伍信息已更新')
// 详细日志队伍数据结构
// 简化队伍数据结构日志
if (gameData.value.presetTeam.presetTeamInfo) {
console.log('👥 队伍详细结构:', {
teamCount: Object.keys(gameData.value.presetTeam.presetTeamInfo).length,
teamIds: Object.keys(gameData.value.presetTeam.presetTeamInfo),
useTeamId: gameData.value.presetTeam.presetTeamInfo.useTeamId,
sampleTeam: gameData.value.presetTeam.presetTeamInfo[1] || gameData.value.presetTeam.presetTeamInfo[Object.keys(gameData.value.presetTeam.presetTeamInfo)[0]]
})
const teamCount = Object.keys(gameData.value.presetTeam.presetTeamInfo).length
console.log(`👥 队伍数量: ${teamCount}`)
}
} else {
console.log('👥 队伍信息响应body为空')
console.log('👥 队伍信息响应为空')
}
}
// 处理爬塔响应
// 处理爬塔响应(静默处理,保持功能)
else if (cmd === 'fight_starttower' || cmd === 'fight_starttowerresp') {
if (body) {
console.log('🗼 爬塔响应:', body)
// 判断爬塔结果
const battleData = body.battleData
if (battleData) {
const curHP = battleData.result?.sponsor?.ext?.curHP
const isSuccess = curHP > 0
// 保存爬塔结果到gameData中供组件使用
if (!gameData.value.towerResult) {
gameData.value.towerResult = {}
}
gameData.value.towerResult = {
success: isSuccess,
curHP: curHP,
towerId: battleData.options?.towerId,
timestamp: Date.now()
}
gameData.value.lastUpdated = new Date().toISOString()
if (isSuccess) {
// 检查是否需要自动领取奖励
const towerId = battleData.options?.towerId
if (towerId !== undefined) {
const layer = towerId % 10
const floor = Math.floor(towerId / 10)
// 如果是新层数的第一层(layer=0),检查是否有奖励可领取
if (layer === 0) {
setTimeout(() => {
const connection = wsConnections.value[tokenId]
if (connection && connection.status === 'connected' && connection.client) {
// 检查角色信息中的奖励状态
const roleInfo = gameData.value.roleInfo
const towerRewards = roleInfo?.role?.tower?.reward
if (towerRewards && !towerRewards[floor]) {
// 保存奖励信息
gameData.value.towerResult.autoReward = true
gameData.value.towerResult.rewardFloor = floor
connection.client.send('tower_claimreward', { rewardId: floor })
}
}
}, 1500)
}
}
}
}
// 爬塔后立即更新角色信息和塔信息
setTimeout(() => {
console.log('🗼 爬塔后自动更新数据')
try {
const connection = wsConnections.value[tokenId]
if (connection && connection.status === 'connected' && connection.client) {
// 获取最新角色信息
console.log('🗼 正在请求角色信息...')
connection.client.send('role_getroleinfo', {})
} else {
console.warn('🗼 WebSocket未连接无法更新数据')
}
} catch (error) {
console.warn('爬塔后更新数据失败:', error)
// 忽略更新数据错误
}
}, 1000)
}
}
// 处理心跳响应
// 处理奖励领取响应(静默处理)
else if (cmd === 'tower_claimreward' || cmd === 'tower_claimrewardresp') {
if (body) {
// 奖励领取成功后更新角色信息
setTimeout(() => {
const connection = wsConnections.value[tokenId]
if (connection && connection.status === 'connected' && connection.client) {
connection.client.send('role_getroleinfo', {})
}
}, 500)
}
}
// 处理加钟相关响应
else if (cmd === 'system_mysharecallback' || cmd === 'syncresp' || cmd === 'system_claimhangupreward' || cmd === 'system_claimhanguprewardresp') {
console.log(`🕐 加钟/挂机 [${tokenId}] ${cmd}`)
// 加钟操作完成后,延迟更新角色信息
if (cmd === 'syncresp' || cmd === 'system_mysharecallback') {
setTimeout(() => {
const connection = wsConnections.value[tokenId]
if (connection && connection.status === 'connected' && connection.client) {
connection.client.send('role_getroleinfo', {})
}
}, 800)
}
// 挂机奖励领取完成后更新角色信息
if (cmd === 'system_claimhanguprewardresp') {
setTimeout(() => {
const connection = wsConnections.value[tokenId]
if (connection && connection.status === 'connected' && connection.client) {
connection.client.send('role_getroleinfo', {})
}
}, 500)
}
}
// 处理心跳响应(静默处理,不打印日志)
else if (cmd === '_sys/ack') {
console.log(`💗 心跳响应 [${tokenId}]`)
// 心跳响应 - 静默处理
return
}
// 处理其他消息
else {
console.log(`📋 收到游戏消息 [${tokenId}] ${cmd}:`, body)
console.log(`📋 游戏消息 [${tokenId}] ${cmd}`)
// 特别关注队伍相关的未处理消息
if (cmd && (cmd.includes('team') || cmd.includes('preset') || cmd.includes('formation'))) {
console.log(`👥 未处理队伍相关消息 [${tokenId}] ${cmd}:`, {
originalMessage: message,
parsedBody: body,
messageKeys: Object.keys(message || {}),
bodyStructure: body ? analyzeDataStructure(body) : null
})
console.log(`👥 未处理队伍消息 [${tokenId}] ${cmd}`)
// 尝试自动解析队伍数据
if (body && typeof body === 'object') {
@@ -336,42 +389,74 @@ export const useTokenStore = defineStore('tokens', () => {
}
}
// 特别关注塔相关的未处理消息
// 特别关注塔相关的未处理消息(静默处理)
if (cmd && cmd.includes('tower')) {
console.log(`🗼 未处理的塔相关消息 [${tokenId}] ${cmd}:`, {
originalMessage: message,
parsedBody: body,
messageKeys: Object.keys(message || {})
})
// 未处理塔消息
}
}
} catch (error) {
console.error('处理游戏消息失败:', error)
console.error(`处理消息失败 [${tokenId}]:`, error.message)
}
}
// Base64解析功能
// 验证token有效性
const validateToken = (token) => {
if (!token) return false
if (typeof token !== 'string') return false
if (token.trim().length === 0) return false
// 简单检查token应该至少有一定长度
if (token.trim().length < 10) return false
return true
}
// Base64解析功能增强版
const parseBase64Token = (base64String) => {
try {
// 输入验证
if (!base64String || typeof base64String !== 'string') {
throw new Error('Token字符串无效')
}
// 移除可能的前缀和空格
const cleanBase64 = base64String.replace(/^data:.*base64,/, '').trim()
if (cleanBase64.length === 0) {
throw new Error('Token字符串为空')
}
// 解码base64
const decoded = atob(cleanBase64)
let decoded
try {
decoded = atob(cleanBase64)
} catch (decodeError) {
// 如果不是有效的Base64作为纯文本token处理
decoded = base64String.trim()
}
// 尝试解析为JSON
let tokenData
try {
tokenData = JSON.parse(decoded)
} catch {
// 如果不是JSON,当作纯token字符串处理
// 不是JSON格式,作为纯token处理
tokenData = { token: decoded }
}
// 提取实际token
const actualToken = tokenData.token || tokenData.gameToken || decoded
// 验证token有效性
if (!validateToken(actualToken)) {
throw new Error(`提取的token无效: "${actualToken}"`)
}
return {
success: true,
data: tokenData
data: {
...tokenData,
actualToken // 添加提取出的实际token
}
}
} catch (error) {
return {
@@ -385,22 +470,41 @@ export const useTokenStore = defineStore('tokens', () => {
const parseResult = parseBase64Token(base64String)
if (!parseResult.success) {
return parseResult
return {
success: false,
error: parseResult.error,
message: `Token "${name}" 导入失败: ${parseResult.error}`
}
}
const tokenData = {
name,
token: parseResult.data.token || parseResult.data.gameToken || base64String,
token: parseResult.data.actualToken, // 使用验证过的实际token
...additionalInfo,
...parseResult.data // 解析出的数据覆盖手动输入
}
const newToken = addToken(tokenData)
try {
const newToken = addToken(tokenData)
// 添加更多验证信息到成功消息
const tokenInfo = parseResult.data.actualToken
const displayToken = tokenInfo.length > 20 ?
`${tokenInfo.substring(0, 10)}...${tokenInfo.substring(tokenInfo.length - 6)}` :
tokenInfo
return {
success: true,
data: newToken,
message: `Token "${name}" 导入成功`
return {
success: true,
data: newToken,
message: `Token "${name}" 导入成功`,
details: `实际Token: ${displayToken}`
}
} catch (error) {
return {
success: false,
error: error.message,
message: `Token "${name}" 添加失败: ${error.message}`
}
}
}
@@ -411,25 +515,21 @@ export const useTokenStore = defineStore('tokens', () => {
}
try {
// 解析Base64获取实际Token
let actualToken = base64Token
// 尝试解析Base64获取实际token
try {
const cleanBase64 = base64Token.replace(/^data:.*base64,/, '').trim()
const decoded = atob(cleanBase64)
// 尝试解析为JSON获取token字段
try {
const tokenData = JSON.parse(decoded)
actualToken = tokenData.token || tokenData.gameToken || decoded
} catch {
// 如果不是JSON直接使用解码后的字符串
actualToken = decoded
// 使用统一的token解析逻辑
const parseResult = parseBase64Token(base64Token)
let actualToken
if (parseResult.success) {
actualToken = parseResult.data.actualToken
// Token解析成功
} else {
// Token解析失败使用原始token
// 如果解析失败尝试直接使用原始token
if (validateToken(base64Token)) {
actualToken = base64Token
} else {
throw new Error(`Token无效: ${parseResult.error}`)
}
} catch (error) {
console.warn('Base64解析失败使用原始token:', error.message)
actualToken = base64Token
}
// 使用固定的WebSocket基础地址将token带入占位符
@@ -498,13 +598,17 @@ export const useTokenStore = defineStore('tokens', () => {
// 设置消息监听
wsClient.setMessageListener((message) => {
console.log(`📨 收到消息 [${tokenId}]:`, message)
// 只打印消息命令,不打印完整结构
const cmd = message?.cmd || 'unknown'
if (cmd !== '_sys/ack') { // 过滤心跳消息
console.log(`📨 [${tokenId}] ${cmd}`)
}
// 更新连接状态中的最后接收消息
if (wsConnections.value[tokenId]) {
wsConnections.value[tokenId].lastMessage = {
timestamp: new Date().toISOString(),
data: message
data: { cmd: message?.cmd } // 只保存命令名
}
}
@@ -541,6 +645,26 @@ export const useTokenStore = defineStore('tokens', () => {
const getWebSocketClient = (tokenId) => {
return wsConnections.value[tokenId]?.client || null
}
// 设置消息监听器
const setMessageListener = (listener) => {
if (selectedToken.value) {
const connection = wsConnections.value[selectedToken.value.id]
if (connection && connection.client) {
connection.client.setMessageListener(listener)
}
}
}
// 设置是否显示消息
const setShowMsg = (show) => {
if (selectedToken.value) {
const connection = wsConnections.value[selectedToken.value.id]
if (connection && connection.client) {
connection.client.setShowMsg(show)
}
}
}
// 发送消息到WebSocket
@@ -559,11 +683,11 @@ export const useTokenStore = defineStore('tokens', () => {
}
client.send(cmd, params, options)
console.log(`📤 发送消息 [${tokenId}]: ${cmd}`, params)
console.log(`📤 [${tokenId}] ${cmd}`)
return true
} catch (error) {
console.error(`❌ 发送消息失败 [${tokenId}]:`, error)
console.error(`❌ 发送失败 [${tokenId}] ${cmd}:`, error.message)
return false
}
}
@@ -641,7 +765,7 @@ export const useTokenStore = defineStore('tokens', () => {
// 可能的塔层数字段(根据实际数据结构调整)
const level = tower.level || tower.currentLevel || tower.floor || tower.stage
console.log('🗼 当前塔层数:', level, '塔信息:', tower)
// 当前塔层数
return level
} catch (error) {
console.error('❌ 获取塔层数失败:', error)
@@ -729,7 +853,7 @@ export const useTokenStore = defineStore('tokens', () => {
try {
gameTokens.value = JSON.parse(savedTokens)
} catch (error) {
console.error('解析Token数据失败:', error)
console.error('解析Token数据失败:', error.message)
gameTokens.value = []
}
}
@@ -752,6 +876,7 @@ export const useTokenStore = defineStore('tokens', () => {
// 计算属性
hasTokens,
selectedToken,
selectedTokenRoleInfo,
// Token管理方法
addToken,
@@ -770,6 +895,8 @@ export const useTokenStore = defineStore('tokens', () => {
getWebSocketClient,
sendMessage,
sendMessageWithPromise,
setMessageListener,
setShowMsg,
sendHeartbeat,
sendGetRoleInfo,
sendGetDataBundleVersion,
@@ -787,6 +914,20 @@ export const useTokenStore = defineStore('tokens', () => {
// 塔信息方法
getCurrentTowerLevel,
getTowerInfo
getTowerInfo,
// 调试工具方法
validateToken,
debugToken: (tokenString) => {
console.log('🔍 Token调试信息:')
console.log('原始Token:', tokenString)
const parseResult = parseBase64Token(tokenString)
console.log('解析结果:', parseResult)
if (parseResult.success) {
console.log('实际Token:', parseResult.data.actualToken)
console.log('Token有效性:', validateToken(parseResult.data.actualToken))
}
return parseResult
}
}
})

View File

@@ -87,6 +87,7 @@ export function registerDefaultCommands(reg) {
.register("system_buygold", { buyNum: 1 })
.register("system_claimhangupreward")
.register("system_signinreward")
.register("system_mysharecallback", { isSkipShareCard: true, type: 2 })
// 任务相关
.register("task_claimdailypoint", { taskId: 1 })
@@ -107,6 +108,7 @@ export function registerDefaultCommands(reg) {
// 商店
.register("store_goodslist", { storeId: 1 })
.register("store_buy", { goodsId: 1 })
.register("store_purchase", { goodsId: 1 })
.register("store_refresh", { storeId: 1 })
// 军团
@@ -141,15 +143,24 @@ export function registerDefaultCommands(reg) {
// 神器抽奖
.register("artifact_lottery", { lotteryNumber: 1, newFree: true, type: 1 })
// 灯神相关
.register("genie_sweep", { genieId: 1 })
.register("genie_buysweep")
// 礼包相关
.register("discount_claimreward", { discountId: 1 })
.register("card_claimreward", { cardId: 1 })
// 爬塔相关
.register("tower_getinfo")
.register("tower_claimreward")
// 队伍相关
.register("presetteam_getinfo")
.register("presetteam_getteam")
.register("presetteam_setteam")
.register("presetteam_saveteam")
.register("presetteam_saveteam", { teamId: 1 })
.register("role_gettargetteam")
// 排名相关
@@ -184,12 +195,7 @@ export class XyzwWebSocketClient {
this.promises = Object.create(null)
this.registry = registerDefaultCommands(new CommandRegistry(this.utils, this.enc))
console.log('🔧 WebSocket客户端初始化:', {
url: this.url,
hasUtils: !!this.utils,
hasEnc: !!this.enc,
hasEncoder: !!this.utils?.encode
})
// WebSocket客户端初始化
// 状态回调
this.onConnect = null
@@ -199,16 +205,16 @@ export class XyzwWebSocketClient {
/** 初始化连接 */
init() {
console.log(`🔗 连接 WebSocket: ${this.url}`)
console.log(`🔗 连接: ${this.url.split('?')[0]}`)
this.socket = new WebSocket(this.url)
this.socket.onopen = () => {
console.log(` WebSocket 连接成功`)
console.log(`✅ 连接成功`)
this.connected = true
console.log(`🔄 启动心跳机制,间隔: ${this.heartbeatInterval}ms`)
// 启动心跳机制
this._setupHeartbeat()
console.log(`🔄 启动消息队列处理`)
// 启动消息队列处理
this._processQueueLoop()
if (this.onConnect) this.onConnect()
}
@@ -223,32 +229,32 @@ export class XyzwWebSocketClient {
packet = this.utils?.parse ? this.utils.parse(evt.data, "auto") : evt.data
} else if (evt.data instanceof Blob) {
// 处理Blob数据
console.log('📦 收到Blob数据, 大小:', evt.data.size)
// 收到Blob数据
evt.data.arrayBuffer().then(buffer => {
try {
packet = this.utils?.parse ? this.utils.parse(buffer, "auto") : buffer
console.log('📦 Blob解析结果:', packet)
// Blob解析完成
// 处理消息体解码ProtoMsg会自动解码
if (packet instanceof Object && packet.rawData !== undefined) {
console.log('✅ ProtoMsg消息使用rawData:', packet.rawData)
// ProtoMsg消息
} else if (packet.body && packet.body instanceof Uint8Array) {
try {
if (this.utils && this.utils.bon && this.utils.bon.decode) {
const decodedBody = this.utils.bon.decode(packet.body)
console.log('✅ 手动解码消息体成功:', decodedBody)
// 手动解码成功
// 不修改packet.body而是创建一个新的属性存储解码后的数据
packet.decodedBody = decodedBody
} else {
console.warn('⚠️ BON解码器不可用:', this.utils)
// BON解码器不可用
}
} catch (error) {
console.warn('❌ 消息体解码失败:', error)
// 消息体解码失败
}
}
if (this.showMsg) {
console.log(`📨 收到消息(Blob解析后):`, packet)
// 收到Blob消息
}
// 回调处理
@@ -260,7 +266,7 @@ export class XyzwWebSocketClient {
this._handlePromiseResponse(packet)
} catch (error) {
console.error('Blob解析失败:', error)
console.error('Blob解析失败:', error.message)
}
})
return // 异步处理,直接返回
@@ -280,14 +286,14 @@ export class XyzwWebSocketClient {
try {
if (this.utils && this.utils.bon && this.utils.bon.decode) {
const decodedBody = this.utils.bon.decode(packet.body)
console.log('✅ 手动解码消息体成功:', decodedBody)
// 手动解码成功
// 不修改packet.body而是创建一个新的属性存储解码后的数据
packet.decodedBody = decodedBody
} else {
console.warn('⚠️ BON解码器不可用:', this.utils)
// BON解码器不可用
}
} catch (error) {
console.warn('❌ 消息体解码失败:', error)
// 消息体解码失败
}
}
@@ -300,7 +306,7 @@ export class XyzwWebSocketClient {
this._handlePromiseResponse(packet)
} catch (error) {
console.error(`消息处理失败:`, error)
console.error(`消息处理失败:`, error.message)
}
}

View File

@@ -101,7 +101,7 @@
</n-button>
<n-button
size="large"
@click="router.push('/tokens')"
@click="handleManageTokens"
>
管理Token
</n-button>
@@ -366,13 +366,30 @@ const handleUserAction = (key) => {
}
}
const handleManageTokens = () => {
console.log('🔘 点击管理Token按钮')
console.log('📊 当前Token状态:', {
hasTokens: tokenStore.hasTokens,
selectedToken: tokenStore.selectedToken?.name,
tokenCount: tokenStore.gameTokens.length
})
try {
router.push('/tokens')
console.log('✅ 成功导航到 /tokens')
} catch (error) {
console.error('❌ 导航失败:', error)
message.error('导航到Token管理页面失败')
}
}
const handleQuickAction = (action) => {
switch (action.action) {
case 'game-features':
router.push('/game-features')
break
case 'add-token':
router.push('/tokens')
handleManageTokens()
break
case 'execute-tasks':
router.push('/game-features')

View File

@@ -30,8 +30,7 @@
<div
v-if="showFeedback"
class="feedback-section"
>
</div>
/>
<!-- 功能模块网格 -->
<div class="features-grid-section">

View File

@@ -14,8 +14,8 @@
<n-button
circle
size="small"
@click="toggleTheme"
class="theme-toggle"
@click="toggleTheme"
>
<template #icon>
<n-icon v-if="isDarkTheme">
@@ -56,7 +56,6 @@
URL获取
</n-radio-button>
</n-radio-group>
</div>
<!-- 手动输入表单 -->
@@ -175,9 +174,14 @@
clearable
/>
<template #feedback>
<span class="form-tip">
接口应返回包含token字段的JSON数据
</span>
<div class="form-tips">
<span class="form-tip">
接口应返回包含token字段的JSON数据
</span>
<span class="form-tip cors-tip">
注意如果是跨域URL服务器需要支持CORS否则会被浏览器阻止
</span>
</div>
</template>
</n-form-item>
@@ -240,6 +244,17 @@
<div class="section-header">
<h2>我的Token列表 ({{ tokenStore.gameTokens.length }})</h2>
<div class="header-actions">
<n-button
v-if="tokenStore.selectedToken"
type="success"
@click="goToDashboard"
>
<template #icon>
<n-icon><Home /></n-icon>
</template>
返回控制台
</n-button>
<n-button
v-if="!showImportForm"
type="primary"
@@ -386,15 +401,6 @@
<Key />
</n-icon>
</template>
<template #extra>
<n-button
type="primary"
size="large"
@click="showImportForm = true"
>
导入第一个Token
</n-button>
</template>
</n-empty>
</div>
</div>
@@ -457,7 +463,8 @@ import {
Key,
Refresh,
Sunny,
Moon
Moon,
Home
} from '@vicons/ionicons5'
const router = useRouter()
@@ -572,10 +579,20 @@ const handleImport = async () => {
if (result.success) {
message.success(result.message)
// 显示token详情信息如果有
if (result.details) {
console.log('Token导入详情:', result.details)
}
resetImportForm()
showImportForm.value = false
} else {
message.error(result.error || result.message)
const errorMsg = result.error || result.message || 'Token导入失败'
message.error(errorMsg)
console.error('Token导入错误详情:', {
error: result.error,
message: result.message,
originalToken: importForm.base64Token?.substring(0, 50) + '...'
})
}
} catch (error) {
// 表单验证失败
@@ -592,8 +609,33 @@ const handleUrlImport = async () => {
await urlFormRef.value.validate()
isImporting.value = true
// 获取Token数据
const response = await fetch(urlForm.url)
// 获取Token数据 - 处理跨域问题
let response
// 检查是否为本地或相同域名的URL
const isLocalUrl = urlForm.url.startsWith(window.location.origin) ||
urlForm.url.startsWith('/') ||
urlForm.url.startsWith('http://localhost') ||
urlForm.url.startsWith('http://127.0.0.1')
if (isLocalUrl) {
// 本地URL直接请求
response = await fetch(urlForm.url)
} else {
// 跨域URL - 尝试CORS请求
try {
response = await fetch(urlForm.url, {
method: 'GET',
headers: {
'Accept': 'application/json',
},
mode: 'cors'
})
} catch (corsError) {
throw new Error(`跨域请求被阻止。请确保目标服务器支持CORS或使用浏览器扩展/代理服务器。错误详情: ${corsError.message}`)
}
}
if (!response.ok) {
throw new Error(`请求失败: ${response.status} ${response.statusText}`)
}
@@ -618,10 +660,21 @@ const handleUrlImport = async () => {
if (result.success) {
message.success(result.message)
// 显示token详情信息如果有
if (result.details) {
console.log('URL Token导入详情:', result.details)
}
resetUrlForm()
showImportForm.value = false
} else {
message.error(result.error || result.message)
const errorMsg = result.error || result.message || 'URL Token导入失败'
message.error(errorMsg)
console.error('URL Token导入错误详情:', {
error: result.error,
message: result.message,
sourceUrl: urlForm.url,
receivedToken: data?.token?.substring(0, 50) + '...'
})
}
} catch (error) {
console.error('URL获取Token失败:', error)
@@ -641,7 +694,30 @@ const refreshToken = async (token) => {
refreshingTokens.value.add(token.id)
try {
const response = await fetch(token.sourceUrl)
// 使用与导入相同的跨域处理逻辑
let response
const isLocalUrl = token.sourceUrl.startsWith(window.location.origin) ||
token.sourceUrl.startsWith('/') ||
token.sourceUrl.startsWith('http://localhost') ||
token.sourceUrl.startsWith('http://127.0.0.1')
if (isLocalUrl) {
response = await fetch(token.sourceUrl)
} else {
try {
response = await fetch(token.sourceUrl, {
method: 'GET',
headers: {
'Accept': 'application/json',
},
mode: 'cors'
})
} catch (corsError) {
throw new Error(`跨域请求被阻止。请确保目标服务器支持CORS。错误详情: ${corsError.message}`)
}
}
if (!response.ok) {
throw new Error(`请求失败: ${response.status} ${response.statusText}`)
}
@@ -1031,11 +1107,22 @@ onMounted(() => {
}
}
.form-tips {
display: flex;
flex-direction: column;
gap: var(--spacing-xs);
}
.form-tip {
color: var(--text-tertiary);
font-size: var(--font-size-sm);
}
.cors-tip {
color: var(--warning-color);
font-weight: var(--font-weight-medium);
}
.connection-actions {
display: flex;
gap: var(--spacing-xs);