feat(DailyTasks): 进入日常页面时自动加载阵容数据- 新增 loadTeamDataWithConnection 函数,用于检查 WebSocket 连接状态并加载阵容数据
- 在页面进入时调用该函数,确保 WebSocket 连接成功后再加载阵容数据 - 增加重试机制,提高加载成功率 - 优化错误处理,提升用户体验
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -2,30 +2,30 @@
|
|||||||
<div class="team-status-card">
|
<div class="team-status-card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<img
|
<img
|
||||||
src="/icons/Ob7pyorzmHiJcbab2c25af264d0758b527bc1b61cc3b.png"
|
src="/icons/Ob7pyorzmHiJcbab2c25af264d0758b527bc1b61cc3b.png"
|
||||||
alt="队伍图标"
|
alt="队伍图标"
|
||||||
class="team-icon"
|
class="team-icon"
|
||||||
>
|
>
|
||||||
<div class="team-info">
|
<div class="team-info">
|
||||||
<h3>队伍阵容</h3>
|
<h3>队伍阵容</h3>
|
||||||
<p>当前使用的战斗阵容</p>
|
<p>当前使用的战斗阵容</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="team-selector">
|
<div class="team-selector">
|
||||||
<button
|
<button
|
||||||
v-for="teamId in availableTeams"
|
v-for="teamId in availableTeams"
|
||||||
:key="teamId"
|
:key="teamId"
|
||||||
:class="[
|
:disabled="loading || switching"
|
||||||
'team-button',
|
:class="['team-button', { active: currentTeam === teamId }]"
|
||||||
{ active: currentTeam === teamId }
|
@click="selectTeam(teamId)"
|
||||||
]"
|
|
||||||
@click="selectTeam(teamId)"
|
|
||||||
>
|
>
|
||||||
{{ teamId }}
|
{{ teamId }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="team-button refresh-button"
|
class="team-button refresh-button"
|
||||||
title="刷新队伍数据"
|
:disabled="loading"
|
||||||
@click="refreshTeamData"
|
title="刷新队伍数据"
|
||||||
|
@click="refreshTeamData(true)"
|
||||||
>
|
>
|
||||||
🔄
|
🔄
|
||||||
</button>
|
</button>
|
||||||
@@ -36,236 +36,227 @@
|
|||||||
<div class="team-display">
|
<div class="team-display">
|
||||||
<div class="current-team-info">
|
<div class="current-team-info">
|
||||||
<span class="label">当前阵容</span>
|
<span class="label">当前阵容</span>
|
||||||
<span class="team-number">阵容 {{ currentTeam }}</span>
|
<span class="team-number">
|
||||||
|
<template v-if="!loading">阵容 {{ currentTeam }}</template>
|
||||||
|
<template v-else>加载中…</template>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="heroes-container">
|
<div class="heroes-container">
|
||||||
<div class="heroes-grid">
|
<div v-if="!loading" class="heroes-inline">
|
||||||
<div
|
<div
|
||||||
v-for="hero in currentTeamHeroes"
|
v-for="hero in currentTeamHeroes"
|
||||||
:key="hero.id || hero.name"
|
:key="hero.id || hero.name"
|
||||||
class="hero-card"
|
class="hero-item"
|
||||||
>
|
>
|
||||||
<img
|
<div class="hero-circle">
|
||||||
v-if="hero.avatar"
|
<img
|
||||||
:src="hero.avatar"
|
v-if="hero.avatar"
|
||||||
:alt="hero.name"
|
:src="hero.avatar"
|
||||||
class="hero-avatar"
|
:alt="hero.name"
|
||||||
>
|
class="hero-avatar"
|
||||||
<div
|
>
|
||||||
v-else
|
<div v-else class="hero-placeholder">
|
||||||
class="hero-placeholder"
|
{{ hero.name?.substring(0, 2) || '?' }}
|
||||||
>
|
</div>
|
||||||
{{ hero.name?.substring(0, 2) || '?' }}
|
|
||||||
</div>
|
</div>
|
||||||
<span class="hero-name">{{ hero.name || '未知' }}</span>
|
<span class="hero-name">{{ hero.name || '未知' }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div v-if="!loading && !currentTeamHeroes.length" class="empty-team">
|
||||||
v-if="!currentTeamHeroes.length"
|
|
||||||
class="empty-team"
|
|
||||||
>
|
|
||||||
<p>暂无队伍信息</p>
|
<p>暂无队伍信息</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="loading" class="empty-team"><p>正在加载队伍信息…</p></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch, onMounted } from 'vue'
|
import { ref, computed, watch, onMounted } from 'vue'
|
||||||
import { useTokenStore } from '@/stores/tokenStore'
|
import { useTokenStore } from '@/stores/tokenStore'
|
||||||
import { useMessage } from 'naive-ui'
|
import { useMessage, NTag } from 'naive-ui'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 集成英雄字典(游戏ID -> { name, type })
|
||||||
|
* 你也可以独立出一个 heroDict.ts 后 import;按你的要求,这里整合到同一文件。
|
||||||
|
*/
|
||||||
|
const HERO_DICT: Record<number, { name: string; type: string }> = {
|
||||||
|
101: { name: '司马懿', type: '魏国' }, 102: { name: '郭嘉', type: '魏国' }, 103: { name: '关羽', type: '蜀国' },
|
||||||
|
104: { name: '诸葛亮', type: '蜀国' }, 105: { name: '周瑜', type: '吴国' }, 106: { name: '太史慈', type: '吴国' },
|
||||||
|
107: { name: '吕布', type: '群雄' }, 108: { name: '华佗', type: '群雄' }, 109: { name: '甄姬', type: '魏国' },
|
||||||
|
110: { name: '黄月英', type: '蜀国' }, 111: { name: '孙策', type: '吴国' }, 112: { name: '贾诩', type: '群雄' },
|
||||||
|
113: { name: '曹仁', type: '魏国' }, 114: { name: '姜维', type: '蜀国' }, 115: { name: '孙坚', type: '吴国' },
|
||||||
|
116: { name: '公孙瓒', type: '群雄' }, 117: { name: '典韦', type: '魏国' }, 118: { name: '赵云', type: '蜀国' },
|
||||||
|
119: { name: '大乔', type: '吴国' }, 120: { name: '张角', type: '群雄' }, 201: { name: '徐晃', type: '魏国' },
|
||||||
|
202: { name: '荀彧', type: '魏国' }, 203: { name: '典韦', type: '魏国' }, 204: { name: '张飞', type: '蜀国' },
|
||||||
|
205: { name: '赵云', type: '蜀国' }, 206: { name: '庞统', type: '蜀国' }, 207: { name: '鲁肃', type: '吴国' },
|
||||||
|
208: { name: '陆逊', type: '吴国' }, 209: { name: '甘宁', type: '吴国' }, 210: { name: '貂蝉', type: '群雄' },
|
||||||
|
211: { name: '董卓', type: '群雄' }, 212: { name: '张角', type: '群雄' }, 213: { name: '张辽', type: '魏国' },
|
||||||
|
214: { name: '夏侯惇', type: '魏国' }, 215: { name: '许褚', type: '魏国' }, 216: { name: '夏侯渊', type: '魏国' },
|
||||||
|
217: { name: '魏延', type: '蜀国' }, 218: { name: '黄忠', type: '蜀国' }, 219: { name: '马超', type: '蜀国' },
|
||||||
|
220: { name: '马岱', type: '蜀国' }, 221: { name: '吕蒙', type: '吴国' }, 222: { name: '黄盖', type: '吴国' },
|
||||||
|
223: { name: '蔡文姬', type: '魏国' }, 224: { name: '小乔', type: '吴国' }, 225: { name: '袁绍', type: '群雄' },
|
||||||
|
226: { name: '华雄', type: '群雄' }, 227: { name: '颜良', type: '群雄' }, 228: { name: '文丑', type: '群雄' },
|
||||||
|
301: { name: '周泰', type: '吴国' }, 302: { name: '许攸', type: '魏国' }, 303: { name: '于禁', type: '魏国' },
|
||||||
|
304: { name: '张星彩', type: '蜀国' }, 305: { name: '关银屏', type: '蜀国' }, 306: { name: '关平', type: '蜀国' },
|
||||||
|
307: { name: '程普', type: '吴国' }, 308: { name: '张昭', type: '吴国' }, 309: { name: '陆绩', type: '吴国' },
|
||||||
|
310: { name: '吕玲绮', type: '群雄' }, 311: { name: '潘凤', type: '群雄' }, 312: { name: '邢道荣', type: '群雄' },
|
||||||
|
313: { name: '祝融夫人', type: '群雄' }, 314: { name: '孟获', type: '群雄' }
|
||||||
|
}
|
||||||
|
|
||||||
const tokenStore = useTokenStore()
|
const tokenStore = useTokenStore()
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
|
||||||
// 响应式数据
|
// 状态
|
||||||
|
const loading = ref(false)
|
||||||
|
const switching = ref(false)
|
||||||
const currentTeam = ref(1)
|
const currentTeam = ref(1)
|
||||||
const availableTeams = ref([1, 2, 3, 4])
|
const availableTeams = ref<number[]>([1, 2, 3, 4])
|
||||||
|
|
||||||
// 计算属性
|
// —— 缓存优先的 presetTeam 原始数据 ——
|
||||||
const presetTeamInfo = computed(() => {
|
const presetTeamRaw = computed(() => tokenStore.gameData?.presetTeam ?? null)
|
||||||
return tokenStore.gameData?.presetTeam || null
|
|
||||||
})
|
|
||||||
|
|
||||||
|
// 统一结构:输出 { useTeamId, teams }
|
||||||
|
function normalizePresetTeam(raw: any): { useTeamId: number; teams: Record<number, { teamInfo: Record<string, any> }> } {
|
||||||
|
if (!raw) return { useTeamId: 1, teams: {} }
|
||||||
|
const root = raw.presetTeamInfo ?? raw
|
||||||
|
const findUseIdRec = (obj: any): number | null => {
|
||||||
|
if (!obj || typeof obj !== 'object') return null
|
||||||
|
if (typeof obj.useTeamId === 'number') return obj.useTeamId
|
||||||
|
for (const k of Object.keys(obj)) {
|
||||||
|
const v = findUseIdRec(obj[k])
|
||||||
|
if (v) return v
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const useTeamId = root.useTeamId ?? root.presetTeamInfo?.useTeamId ?? findUseIdRec(root) ?? 1
|
||||||
|
|
||||||
|
const dict = root.presetTeamInfo ?? root
|
||||||
|
const teams: Record<number, { teamInfo: Record<string, any> }> = {}
|
||||||
|
const ids = Object.keys(dict || {}).filter(k => /^\d+$/.test(k))
|
||||||
|
for (const idStr of ids) {
|
||||||
|
const id = Number(idStr)
|
||||||
|
const node = dict[idStr]
|
||||||
|
if (!node) { teams[id] = { teamInfo: {} }; continue }
|
||||||
|
if (node.teamInfo) {
|
||||||
|
teams[id] = { teamInfo: node.teamInfo }
|
||||||
|
} else if (node.heroes) {
|
||||||
|
const ti: Record<string, any> = {}
|
||||||
|
node.heroes.forEach((h: any, idx: number) => { ti[String(idx + 1)] = h })
|
||||||
|
teams[id] = { teamInfo: ti }
|
||||||
|
} else if (typeof node === 'object') {
|
||||||
|
const hasHero = Object.values(node).some((v: any) => v && typeof v === 'object' && 'heroId' in v)
|
||||||
|
teams[id] = { teamInfo: hasHero ? node : {} }
|
||||||
|
} else {
|
||||||
|
teams[id] = { teamInfo: {} }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { useTeamId: Number(useTeamId) || 1, teams }
|
||||||
|
}
|
||||||
|
|
||||||
|
const presetTeam = computed(() => normalizePresetTeam(presetTeamRaw.value))
|
||||||
|
|
||||||
|
// —— 英雄列表 ——
|
||||||
const currentTeamHeroes = computed(() => {
|
const currentTeamHeroes = computed(() => {
|
||||||
if (!presetTeamInfo.value) {
|
const team = presetTeam.value.teams[currentTeam.value]?.teamInfo
|
||||||
console.log('👥 TeamStatus: presetTeamInfo 为空')
|
if (!team) return []
|
||||||
return []
|
const heroes: Array<{ id: number; name: string; type: string; position: number; level?: number; avatar?: string }> = []
|
||||||
}
|
for (const [pos, hero] of Object.entries(team)) {
|
||||||
|
const hid = (hero as any)?.heroId ?? (hero as any)?.id
|
||||||
console.log('👥 TeamStatus: 当前队伍信息结构:', {
|
if (!hid) continue
|
||||||
presetTeamInfo: presetTeamInfo.value,
|
const meta = HERO_DICT[Number(hid)]
|
||||||
currentTeam: currentTeam.value,
|
heroes.push({
|
||||||
hasPresetTeamInfo: !!presetTeamInfo.value.presetTeamInfo,
|
id: Number(hid),
|
||||||
presetTeamInfoKeys: presetTeamInfo.value.presetTeamInfo ? Object.keys(presetTeamInfo.value.presetTeamInfo) : []
|
name: meta?.name ?? `英雄${hid}`,
|
||||||
})
|
type: meta?.type ?? '',
|
||||||
|
position: Number(pos),
|
||||||
// 尝试多种可能的数据结构
|
level: (hero as any)?.level ?? 1,
|
||||||
let teamData = null
|
avatar: (hero as any)?.avatar
|
||||||
|
|
||||||
// 方式1: 标准结构 presetTeamInfo[teamId].teamInfo
|
|
||||||
if (presetTeamInfo.value.presetTeamInfo?.[currentTeam.value]?.teamInfo) {
|
|
||||||
teamData = presetTeamInfo.value.presetTeamInfo[currentTeam.value].teamInfo
|
|
||||||
console.log('👥 TeamStatus: 使用标准结构获取队伍数据')
|
|
||||||
}
|
|
||||||
// 方式2: 直接在presetTeamInfo[teamId]下
|
|
||||||
else if (presetTeamInfo.value.presetTeamInfo?.[currentTeam.value]) {
|
|
||||||
const teamInfo = presetTeamInfo.value.presetTeamInfo[currentTeam.value]
|
|
||||||
if (typeof teamInfo === 'object' && !Array.isArray(teamInfo)) {
|
|
||||||
teamData = teamInfo
|
|
||||||
console.log('👥 TeamStatus: 使用直接结构获取队伍数据')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 方式3: 查找任何包含英雄数据的结构
|
|
||||||
else if (presetTeamInfo.value.presetTeamInfo) {
|
|
||||||
for (const [key, value] of Object.entries(presetTeamInfo.value.presetTeamInfo)) {
|
|
||||||
if (value && typeof value === 'object') {
|
|
||||||
// 查找包含heroId或类似字段的数据
|
|
||||||
if (value.teamInfo || value.heroes || value.formation ||
|
|
||||||
Object.values(value).some(v => v && v.heroId)) {
|
|
||||||
teamData = value.teamInfo || value.heroes || value.formation || value
|
|
||||||
console.log(`👥 TeamStatus: 在 ${key} 中找到队伍数据`)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!teamData) {
|
|
||||||
console.log('👥 TeamStatus: 未找到队伍数据')
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('👥 TeamStatus: 解析队伍数据:', teamData)
|
|
||||||
|
|
||||||
// 转换队伍信息为英雄数组
|
|
||||||
const heroes = []
|
|
||||||
|
|
||||||
// 处理不同的数据格式
|
|
||||||
if (Array.isArray(teamData)) {
|
|
||||||
// 数组格式
|
|
||||||
teamData.forEach((hero, index) => {
|
|
||||||
if (hero && (hero.heroId || hero.id)) {
|
|
||||||
heroes.push({
|
|
||||||
id: hero.heroId || hero.id,
|
|
||||||
name: getHeroName(hero.heroId || hero.id),
|
|
||||||
position: index + 1,
|
|
||||||
level: hero.level || 1
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
} else if (typeof teamData === 'object') {
|
|
||||||
// 对象格式(position => hero)
|
|
||||||
for (const [position, hero] of Object.entries(teamData)) {
|
|
||||||
if (hero && (hero.heroId || hero.id)) {
|
|
||||||
heroes.push({
|
|
||||||
id: hero.heroId || hero.id,
|
|
||||||
name: getHeroName(hero.heroId || hero.id),
|
|
||||||
position: position,
|
|
||||||
level: hero.level || 1
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
heroes.sort((a, b) => a.position - b.position)
|
||||||
console.log('👥 TeamStatus: 解析出的英雄列表:', heroes)
|
|
||||||
return heroes
|
return heroes
|
||||||
})
|
})
|
||||||
|
|
||||||
// 从presetTeamInfo获取可用队伍数量
|
// —— 命令封装 ——
|
||||||
|
const executeGameCommand = async (tokenId: string | number, cmd: string, params: any = {}, description = '', timeout = 8000) => {
|
||||||
|
try {
|
||||||
|
return await tokenStore.sendMessageWithPromise(tokenId, cmd, params, timeout)
|
||||||
|
} catch (error: any) {
|
||||||
|
const msg = error?.message ?? String(error)
|
||||||
|
if (description) message.error(`${description}失败:${msg}`)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// —— 数据加载:缓存优先,可强制刷新 ——
|
||||||
|
const getTeamInfoWithCache = async (force = false) => {
|
||||||
|
if (!tokenStore.selectedToken) {
|
||||||
|
message.warning('请先选择Token')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const tokenId = tokenStore.selectedToken.id
|
||||||
|
|
||||||
|
if (!force) {
|
||||||
|
const cached = tokenStore.gameData?.presetTeam?.presetTeamInfo
|
||||||
|
if (cached) return cached
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const result = await executeGameCommand(tokenId, 'presetteam_getinfo', {}, '获取阵容信息')
|
||||||
|
tokenStore.$patch((state) => {
|
||||||
|
state.gameData = { ...(state.gameData ?? {}), presetTeam: result }
|
||||||
|
})
|
||||||
|
return result?.presetTeamInfo ?? null
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// —— UI 同步 ——
|
||||||
const updateAvailableTeams = () => {
|
const updateAvailableTeams = () => {
|
||||||
if (!presetTeamInfo.value?.presetTeamInfo) return
|
const ids = Object.keys(presetTeam.value.teams).map(Number).filter(n => !Number.isNaN(n)).sort((a, b) => a - b)
|
||||||
|
availableTeams.value = ids.length ? ids : [1, 2, 3, 4]
|
||||||
const teams = Object.keys(presetTeamInfo.value.presetTeamInfo)
|
}
|
||||||
.map(Number)
|
const updateCurrentTeam = () => { currentTeam.value = presetTeam.value.useTeamId || 1 }
|
||||||
.filter(num => !isNaN(num))
|
|
||||||
.sort((a, b) => a - b)
|
// —— 交互 ——
|
||||||
|
const selectTeam = async (teamId: number) => {
|
||||||
if (teams.length > 0) {
|
if (switching.value || loading.value) return
|
||||||
availableTeams.value = teams
|
if (!tokenStore.selectedToken) { message.warning('请先选择Token'); return }
|
||||||
|
const prev = currentTeam.value
|
||||||
|
switching.value = true
|
||||||
|
try {
|
||||||
|
await executeGameCommand(tokenStore.selectedToken.id, 'presetteam_saveteam', { teamId }, `切换到阵容 ${teamId}`)
|
||||||
|
currentTeam.value = teamId
|
||||||
|
message.success(`已切换到阵容 ${teamId}`)
|
||||||
|
await refreshTeamData(true)
|
||||||
|
} catch (e) {
|
||||||
|
currentTeam.value = prev
|
||||||
|
} finally {
|
||||||
|
switching.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新当前队伍
|
const refreshTeamData = async (force = false) => { await getTeamInfoWithCache(force) }
|
||||||
const updateCurrentTeam = () => {
|
|
||||||
if (presetTeamInfo.value?.presetTeamInfo?.useTeamId) {
|
|
||||||
currentTeam.value = presetTeamInfo.value.presetTeamInfo.useTeamId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取英雄名称(这里需要英雄数据字典)
|
// —— 首次挂载:先查缓存,再兜底拉接口 ——
|
||||||
const getHeroName = (heroId) => {
|
onMounted(async () => {
|
||||||
// 暂时返回英雄ID,后续可以添加英雄名称映射
|
await refreshTeamData(false)
|
||||||
const heroNames = {
|
updateAvailableTeams(); updateCurrentTeam()
|
||||||
1: '剑士',
|
if (!presetTeamRaw.value) {
|
||||||
2: '法师',
|
await refreshTeamData(true)
|
||||||
3: '弓手',
|
updateAvailableTeams(); updateCurrentTeam()
|
||||||
4: '盗贼',
|
|
||||||
5: '牧师'
|
|
||||||
// 更多英雄映射...
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return heroNames[heroId] || `英雄${heroId}`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 选择队伍
|
|
||||||
const selectTeam = (teamId) => {
|
|
||||||
if (!tokenStore.selectedToken) {
|
|
||||||
message.warning('请先选择Token')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
currentTeam.value = teamId
|
|
||||||
|
|
||||||
// 发送切换队伍的消息
|
|
||||||
const tokenId = tokenStore.selectedToken.id
|
|
||||||
tokenStore.sendMessage(tokenId, 'presetteam_saveteam', { teamId })
|
|
||||||
|
|
||||||
message.info(`切换到阵容 ${teamId}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 监听预设队伍信息变化
|
|
||||||
watch(presetTeamInfo, (newValue) => {
|
|
||||||
if (newValue) {
|
|
||||||
updateAvailableTeams()
|
|
||||||
updateCurrentTeam()
|
|
||||||
}
|
|
||||||
}, { deep: true, immediate: true })
|
|
||||||
|
|
||||||
// 刷新队伍数据
|
|
||||||
const refreshTeamData = () => {
|
|
||||||
if (!tokenStore.selectedToken) {
|
|
||||||
message.warning('请先选择Token')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const tokenId = tokenStore.selectedToken.id
|
|
||||||
console.log('👥 手动刷新队伍数据')
|
|
||||||
|
|
||||||
// 发送多个可能的队伍相关命令
|
|
||||||
const commands = [
|
|
||||||
'presetteam_getteam',
|
|
||||||
'role_gettargetteam',
|
|
||||||
'role_getroleinfo' // 角色信息中可能包含队伍数据
|
|
||||||
]
|
|
||||||
|
|
||||||
commands.forEach(cmd => {
|
|
||||||
tokenStore.sendMessage(tokenId, cmd, {})
|
|
||||||
console.log(`👥 发送命令: ${cmd}`)
|
|
||||||
})
|
|
||||||
|
|
||||||
message.info('正在刷新队伍数据...')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生命周期
|
|
||||||
onMounted(() => {
|
|
||||||
// 获取队伍信息
|
|
||||||
refreshTeamData()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// —— 监听缓存变化(其他地方写入也能联动) ——
|
||||||
|
watch(() => presetTeamRaw.value, () => { updateAvailableTeams(); updateCurrentTeam() }, { deep: true })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@@ -275,187 +266,121 @@ onMounted(() => {
|
|||||||
padding: var(--spacing-lg);
|
padding: var(--spacing-lg);
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
transition: all var(--transition-normal);
|
transition: all var(--transition-normal);
|
||||||
|
&:hover { box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); transform: translateY(-2px); }
|
||||||
&:hover {
|
|
||||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
.card-header { display: flex; align-items: flex-start; gap: var(--spacing-md); margin-bottom: var(--spacing-lg); }
|
||||||
.card-header {
|
.team-icon { width: 32px; height: 32px; object-fit: contain; flex-shrink: 0; }
|
||||||
display: flex;
|
.team-info { flex: 1;
|
||||||
align-items: flex-start;
|
h3 { font-size: var(--font-size-md); font-weight: var(--font-weight-semibold); color: var(--text-primary); margin: 0 0 var(--spacing-xs) 0; }
|
||||||
gap: var(--spacing-md);
|
p { font-size: var(--font-size-sm); color: var(--text-secondary); margin: 0; }
|
||||||
margin-bottom: var(--spacing-lg);
|
|
||||||
}
|
}
|
||||||
|
.team-selector { display: flex; gap: var(--spacing-xs); }
|
||||||
.team-icon {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
object-fit: contain;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.team-info {
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: var(--font-size-md);
|
|
||||||
font-weight: var(--font-weight-semibold);
|
|
||||||
color: var(--text-primary);
|
|
||||||
margin: 0 0 var(--spacing-xs) 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: var(--font-size-sm);
|
|
||||||
color: var(--text-secondary);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.team-selector {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--spacing-xs);
|
|
||||||
}
|
|
||||||
|
|
||||||
.team-button {
|
.team-button {
|
||||||
width: 32px;
|
width: 32px; height: 32px; border: none; border-radius: 50%;
|
||||||
height: 32px;
|
background: var(--bg-tertiary); color: var(--text-secondary);
|
||||||
border: none;
|
font-size: var(--font-size-sm); font-weight: var(--font-weight-medium);
|
||||||
border-radius: 50%;
|
cursor: pointer; transition: all var(--transition-fast);
|
||||||
background: var(--bg-tertiary);
|
&:hover { background: var(--bg-secondary); }
|
||||||
color: var(--text-secondary);
|
&.active { background: var(--primary-color); color: white; }
|
||||||
font-size: var(--font-size-sm);
|
&.refresh-button { background: var(--success-color, #10b981); color: white; &:hover { background: var(--success-color-dark, #059669); } }
|
||||||
font-weight: var(--font-weight-medium);
|
&:disabled { opacity: .6; cursor: not-allowed; }
|
||||||
cursor: pointer;
|
|
||||||
transition: all var(--transition-fast);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background: var(--primary-color);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.refresh-button {
|
|
||||||
background: var(--success-color, #10b981);
|
|
||||||
color: white;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--success-color-dark, #059669);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
.card-content .current-team-info {
|
||||||
.card-content {
|
display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--spacing-lg);
|
||||||
.current-team-info {
|
.label { font-size: var(--font-size-sm); color: var(--text-secondary); }
|
||||||
display: flex;
|
.team-number { font-size: var(--font-size-lg); font-weight: var(--font-weight-bold); color: var(--text-primary); }
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: var(--spacing-lg);
|
|
||||||
|
|
||||||
.label {
|
|
||||||
font-size: var(--font-size-sm);
|
|
||||||
color: var(--text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.team-number {
|
|
||||||
font-size: var(--font-size-lg);
|
|
||||||
font-weight: var(--font-weight-bold);
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.heroes-container {
|
.heroes-container {
|
||||||
background: var(--bg-tertiary);
|
background: var(--bg-tertiary);
|
||||||
border-radius: var(--border-radius-medium);
|
border-radius: var(--border-radius-medium);
|
||||||
padding: var(--spacing-md);
|
padding: var(--spacing-md);
|
||||||
min-height: 120px;
|
min-height: 60px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.heroes-grid {
|
.heroes-inline {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: var(--spacing-md);
|
|
||||||
justify-content: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-card {
|
.hero-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--spacing-xs);
|
gap: 4px;
|
||||||
padding: var(--spacing-sm);
|
min-width: 50px;
|
||||||
border-radius: var(--border-radius-medium);
|
|
||||||
background: white;
|
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
||||||
transition: all var(--transition-fast);
|
|
||||||
min-width: 80px;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-avatar {
|
.hero-circle {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 2px solid var(--border-color, #e5e5e5);
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-avatar {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-placeholder {
|
.hero-placeholder {
|
||||||
width: 40px;
|
width: 100%;
|
||||||
height: 40px;
|
height: 100%;
|
||||||
border-radius: 50%;
|
|
||||||
background: var(--primary-color);
|
background: var(--primary-color);
|
||||||
color: white;
|
color: white;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: var(--font-size-sm);
|
font-size: 12px;
|
||||||
font-weight: var(--font-weight-bold);
|
font-weight: var(--font-weight-bold);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-name {
|
.hero-name {
|
||||||
font-size: var(--font-size-xs);
|
font-size: 11px;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-weight: var(--font-weight-medium);
|
font-weight: var(--font-weight-medium);
|
||||||
|
max-width: 50px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
.empty-team { text-align: center; color: var(--text-secondary); p { margin: 0; font-size: var(--font-size-sm); }
|
||||||
.empty-team {
|
|
||||||
text-align: center;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
font-size: var(--font-size-sm);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 响应式设计
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.card-header {
|
.card-header {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--spacing-sm);
|
gap: var(--spacing-sm);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
.team-selector {
|
||||||
.team-selector {
|
justify-content: center;
|
||||||
justify-content: center;
|
|
||||||
}
|
}
|
||||||
|
.heroes-inline {
|
||||||
.heroes-grid {
|
justify-content: center;
|
||||||
flex-direction: column;
|
gap: var(--spacing-xs);
|
||||||
gap: var(--spacing-sm);
|
}
|
||||||
|
.hero-item {
|
||||||
|
min-width: 45px;
|
||||||
|
}
|
||||||
|
.hero-circle {
|
||||||
|
width: 35px;
|
||||||
|
height: 35px;
|
||||||
|
}
|
||||||
|
.hero-name {
|
||||||
|
font-size: 10px;
|
||||||
|
max-width: 45px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
>
|
>
|
||||||
{{ isClimbing.value ? '爬塔中...' : '开始爬塔' }}
|
{{ isClimbing.value ? '爬塔中...' : '开始爬塔' }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- 调试用的重置按钮,只在开发环境显示 -->
|
<!-- 调试用的重置按钮,只在开发环境显示 -->
|
||||||
<button
|
<button
|
||||||
v-if="isClimbing.value"
|
v-if="isClimbing.value"
|
||||||
@@ -57,9 +57,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted, watch } from 'vue'
|
import {computed, onMounted, ref, watch} from 'vue'
|
||||||
import { useTokenStore } from '@/stores/tokenStore'
|
import {useTokenStore} from '@/stores/tokenStore'
|
||||||
import { useMessage } from 'naive-ui'
|
import {useMessage} from 'naive-ui'
|
||||||
|
|
||||||
const tokenStore = useTokenStore()
|
const tokenStore = useTokenStore()
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
@@ -72,66 +72,43 @@ const lastClimbResult = ref(null) // 最后一次爬塔结果
|
|||||||
// 计算属性 - 从gameData中获取塔相关信息
|
// 计算属性 - 从gameData中获取塔相关信息
|
||||||
const roleInfo = computed(() => {
|
const roleInfo = computed(() => {
|
||||||
const data = tokenStore.gameData?.roleInfo || null
|
const data = tokenStore.gameData?.roleInfo || null
|
||||||
console.log('🗼 TowerStatus roleInfo 计算属性更新:', data)
|
|
||||||
if (data?.role?.tower) {
|
|
||||||
console.log('🗼 TowerStatus 发现tower数据:', data.role.tower)
|
|
||||||
} else {
|
|
||||||
console.log('🗼 TowerStatus 没有找到tower数据, gameData:', tokenStore.gameData)
|
|
||||||
}
|
|
||||||
return data
|
return data
|
||||||
})
|
})
|
||||||
|
|
||||||
const currentFloor = computed(() => {
|
const currentFloor = computed(() => {
|
||||||
const tower = roleInfo.value?.role?.tower
|
const tower = roleInfo.value?.role?.tower
|
||||||
console.log('🗼 TowerStatus currentFloor 计算属性更新')
|
|
||||||
console.log('🗼 TowerStatus 输入的tower数据:', tower)
|
|
||||||
console.log('🗼 TowerStatus 完整的roleInfo:', roleInfo.value)
|
|
||||||
|
|
||||||
if (!tower) {
|
if (!tower) {
|
||||||
console.log('🗼 没有tower对象,显示默认值')
|
|
||||||
return "0 - 0"
|
return "0 - 0"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tower.id && tower.id !== 0) {
|
if (!tower.id && tower.id !== 0) {
|
||||||
console.log('🗼 没有塔ID或ID无效,显示默认值, tower.id:', tower.id)
|
|
||||||
return "0 - 0"
|
return "0 - 0"
|
||||||
}
|
}
|
||||||
|
|
||||||
const towerId = tower.id
|
const towerId = tower.id
|
||||||
const floor = Math.floor(towerId / 10) + 1
|
const floor = Math.floor(towerId / 10) + 1
|
||||||
const layer = towerId % 10 + 1
|
const layer = towerId % 10 + 1
|
||||||
const result = `${floor} - ${layer}`
|
return `${floor} - ${layer}`
|
||||||
console.log(`🗼 计算层数: towerId=${towerId} -> floor=${floor}, layer=${layer} -> ${result}`)
|
|
||||||
return result
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const towerEnergy = computed(() => {
|
const towerEnergy = computed(() => {
|
||||||
const tower = roleInfo.value?.role?.tower
|
const tower = roleInfo.value?.role?.tower
|
||||||
console.log('🗼 TowerStatus towerEnergy 计算属性更新')
|
|
||||||
console.log('🗼 TowerStatus tower对象:', tower)
|
|
||||||
|
|
||||||
const energy = tower?.energy || 0
|
const energy = tower?.energy || 0
|
||||||
console.log('🗼 TowerStatus 计算出的energy:', energy)
|
|
||||||
return energy
|
return energy
|
||||||
})
|
})
|
||||||
|
|
||||||
const canClimb = computed(() => {
|
const canClimb = computed(() => {
|
||||||
const hasEnergy = towerEnergy.value > 0
|
const hasEnergy = towerEnergy.value > 0
|
||||||
const notClimbing = !isClimbing.value
|
const notClimbing = !isClimbing.value
|
||||||
console.log(`🗼 canClimb 计算: hasEnergy=${hasEnergy}, notClimbing=${notClimbing}, result=${hasEnergy && notClimbing}`)
|
|
||||||
return hasEnergy && notClimbing
|
return hasEnergy && notClimbing
|
||||||
})
|
})
|
||||||
|
|
||||||
// 方法
|
// 方法
|
||||||
const startTowerClimb = async () => {
|
const startTowerClimb = async () => {
|
||||||
console.log('🗼 开始爬塔按钮被点击')
|
|
||||||
console.log('🗼 当前状态:', {
|
|
||||||
canClimb: canClimb.value,
|
|
||||||
isClimbing: isClimbing.value,
|
|
||||||
towerEnergy: towerEnergy.value,
|
|
||||||
hasSelectedToken: !!tokenStore.selectedToken
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!tokenStore.selectedToken) {
|
if (!tokenStore.selectedToken) {
|
||||||
message.warning('请先选择Token')
|
message.warning('请先选择Token')
|
||||||
return
|
return
|
||||||
@@ -149,57 +126,45 @@ const startTowerClimb = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 确保在操作开始前设置状态
|
// 确保在操作开始前设置状态
|
||||||
console.log('🗼 设置爬塔状态为true')
|
|
||||||
isClimbing.value = true
|
isClimbing.value = true
|
||||||
|
|
||||||
// 设置超时保护,15秒后自动重置状态
|
// 设置超时保护,15秒后自动重置状态
|
||||||
climbTimeout.value = setTimeout(() => {
|
climbTimeout.value = setTimeout(() => {
|
||||||
console.log('🗼 超时保护触发,自动重置爬塔状态')
|
|
||||||
isClimbing.value = false
|
isClimbing.value = false
|
||||||
climbTimeout.value = null
|
climbTimeout.value = null
|
||||||
}, 15000)
|
}, 15000)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const tokenId = tokenStore.selectedToken.id
|
const tokenId = tokenStore.selectedToken.id
|
||||||
console.log('🗼 使用Token ID:', tokenId)
|
|
||||||
|
|
||||||
message.info('开始爬塔挑战...')
|
|
||||||
|
|
||||||
// 发送爬塔命令
|
// 发送爬塔命令
|
||||||
console.log('🗼 发送爬塔命令...')
|
|
||||||
await tokenStore.sendMessageWithPromise(tokenId, 'fight_starttower', {}, 10000)
|
await tokenStore.sendMessageWithPromise(tokenId, 'fight_starttower', {}, 10000)
|
||||||
|
|
||||||
console.log('🗼 爬塔命令发送成功')
|
|
||||||
message.success('爬塔命令已发送')
|
message.success('爬塔命令已发送')
|
||||||
|
|
||||||
// 立即查询塔信息以获取最新状态
|
// 立即查询塔信息以获取最新状态
|
||||||
console.log('🗼 爬塔完成,立即查询塔信息')
|
|
||||||
await getTowerInfo()
|
await getTowerInfo()
|
||||||
|
|
||||||
// 再延迟查询一次确保数据同步
|
// 再延迟查询一次确保数据同步
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
console.log('🗼 延迟查询塔信息')
|
|
||||||
await getTowerInfo()
|
await getTowerInfo()
|
||||||
|
|
||||||
// 清除超时并重置状态
|
// 清除超时并重置状态
|
||||||
if (climbTimeout.value) {
|
if (climbTimeout.value) {
|
||||||
clearTimeout(climbTimeout.value)
|
clearTimeout(climbTimeout.value)
|
||||||
climbTimeout.value = null
|
climbTimeout.value = null
|
||||||
}
|
}
|
||||||
console.log('🗼 延迟查询完成,重置爬塔状态')
|
|
||||||
isClimbing.value = false
|
isClimbing.value = false
|
||||||
}, 3000)
|
}, 2000)
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('🗼 爬塔失败:', error)
|
|
||||||
message.error('爬塔失败: ' + (error.message || '未知错误'))
|
message.error('爬塔失败: ' + (error.message || '未知错误'))
|
||||||
|
|
||||||
// 发生错误时立即重置状态
|
// 发生错误时立即重置状态
|
||||||
if (climbTimeout.value) {
|
if (climbTimeout.value) {
|
||||||
clearTimeout(climbTimeout.value)
|
clearTimeout(climbTimeout.value)
|
||||||
climbTimeout.value = null
|
climbTimeout.value = null
|
||||||
}
|
}
|
||||||
console.log('🗼 发生错误,立即重置爬塔状态')
|
|
||||||
isClimbing.value = false
|
isClimbing.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,12 +174,11 @@ const startTowerClimb = async () => {
|
|||||||
|
|
||||||
// 重置爬塔状态的方法
|
// 重置爬塔状态的方法
|
||||||
const resetClimbingState = () => {
|
const resetClimbingState = () => {
|
||||||
console.log('🗼 用户手动重置爬塔状态')
|
|
||||||
if (climbTimeout.value) {
|
if (climbTimeout.value) {
|
||||||
clearTimeout(climbTimeout.value)
|
clearTimeout(climbTimeout.value)
|
||||||
climbTimeout.value = null
|
climbTimeout.value = null
|
||||||
}
|
}
|
||||||
isClimbing.value = falsexian1xian
|
isClimbing.value = false
|
||||||
message.info('爬塔状态已重置')
|
message.info('爬塔状态已重置')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,36 +190,19 @@ const getTowerInfo = async () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const tokenId = tokenStore.selectedToken.id
|
const tokenId = tokenStore.selectedToken.id
|
||||||
console.log('🗼 getTowerInfo: 开始获取塔信息, tokenId:', tokenId)
|
|
||||||
|
|
||||||
// 检查WebSocket连接状态
|
// 检查WebSocket连接状态
|
||||||
const wsStatus = tokenStore.getWebSocketStatus(tokenId)
|
const wsStatus = tokenStore.getWebSocketStatus(tokenId)
|
||||||
console.log('🗼 getTowerInfo: WebSocket状态:', wsStatus)
|
|
||||||
|
|
||||||
if (wsStatus !== 'connected') {
|
if (wsStatus !== 'connected') {
|
||||||
console.warn('🗼 getTowerInfo: WebSocket未连接,无法获取数据')
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 首先获取角色信息,这包含了塔的数据
|
// 首先获取角色信息,这包含了塔的数据
|
||||||
console.log('🗼 getTowerInfo: 正在请求角色信息...')
|
|
||||||
const roleResult = tokenStore.sendMessage(tokenId, 'role_getroleinfo')
|
const roleResult = tokenStore.sendMessage(tokenId, 'role_getroleinfo')
|
||||||
console.log('🗼 getTowerInfo: 角色信息请求结果:', roleResult)
|
|
||||||
|
|
||||||
// 直接请求塔信息
|
// 直接请求塔信息
|
||||||
console.log('🗼 getTowerInfo: 正在请求塔信息...')
|
|
||||||
const towerResult = tokenStore.sendMessage(tokenId, 'tower_getinfo')
|
const towerResult = tokenStore.sendMessage(tokenId, 'tower_getinfo')
|
||||||
console.log('🗼 getTowerInfo: 塔信息请求结果:', towerResult)
|
|
||||||
|
|
||||||
// 检查当前gameData状态
|
|
||||||
console.log('🗼 getTowerInfo: 当前gameData:', tokenStore.gameData)
|
|
||||||
console.log('🗼 getTowerInfo: 当前roleInfo:', tokenStore.gameData?.roleInfo)
|
|
||||||
console.log('🗼 getTowerInfo: 当前tower数据:', tokenStore.gameData?.roleInfo?.role?.tower)
|
|
||||||
|
|
||||||
if (!roleResult && !towerResult) {
|
if (!roleResult && !towerResult) {
|
||||||
console.error('🗼 getTowerInfo: 所有请求都失败了')
|
console.error('🗼 getTowerInfo: 所有请求都失败了')
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('🗼 getTowerInfo: 获取塔信息失败:', error)
|
console.error('🗼 getTowerInfo: 获取塔信息失败:', error)
|
||||||
}
|
}
|
||||||
@@ -299,11 +246,11 @@ watch(() => tokenStore.selectedToken, (newToken, oldToken) => {
|
|||||||
watch(() => tokenStore.gameData.towerResult, (newResult, oldResult) => {
|
watch(() => tokenStore.gameData.towerResult, (newResult, oldResult) => {
|
||||||
if (newResult && newResult.timestamp !== oldResult?.timestamp) {
|
if (newResult && newResult.timestamp !== oldResult?.timestamp) {
|
||||||
console.log('🗼 收到新的爬塔结果:', newResult)
|
console.log('🗼 收到新的爬塔结果:', newResult)
|
||||||
|
|
||||||
// 显示爬塔结果消息
|
// 显示爬塔结果消息
|
||||||
if (newResult.success) {
|
if (newResult.success) {
|
||||||
message.success('咸将塔挑战成功!')
|
message.success('咸将塔挑战成功!')
|
||||||
|
|
||||||
if (newResult.autoReward) {
|
if (newResult.autoReward) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
message.success(`自动领取第${newResult.rewardFloor}层奖励`)
|
message.success(`自动领取第${newResult.rewardFloor}层奖励`)
|
||||||
@@ -312,7 +259,7 @@ watch(() => tokenStore.gameData.towerResult, (newResult, oldResult) => {
|
|||||||
} else {
|
} else {
|
||||||
message.error('咸将塔挑战失败')
|
message.error('咸将塔挑战失败')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重置爬塔状态
|
// 重置爬塔状态
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
console.log('🗼 爬塔结果处理完成,重置状态')
|
console.log('🗼 爬塔结果处理完成,重置状态')
|
||||||
@@ -327,24 +274,15 @@ watch(() => tokenStore.gameData.towerResult, (newResult, oldResult) => {
|
|||||||
|
|
||||||
// 生命周期
|
// 生命周期
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
console.log('🗼 TowerStatus 组件已挂载')
|
|
||||||
console.log('🗼 当前选中Token:', tokenStore.selectedToken?.name)
|
|
||||||
console.log('🗼 当前选中Token ID:', tokenStore.selectedToken?.id)
|
|
||||||
console.log('🗼 当前WebSocket状态:', wsStatus.value)
|
|
||||||
console.log('🗼 当前游戏数据:', tokenStore.gameData)
|
|
||||||
console.log('🗼 当前roleInfo:', tokenStore.gameData?.roleInfo)
|
|
||||||
console.log('🗼 当前tower数据:', tokenStore.gameData?.roleInfo?.role?.tower)
|
|
||||||
|
|
||||||
// 检查WebSocket客户端
|
// 检查WebSocket客户端
|
||||||
if (tokenStore.selectedToken) {
|
if (tokenStore.selectedToken) {
|
||||||
const client = tokenStore.getWebSocketClient(tokenStore.selectedToken.id)
|
const client = tokenStore.getWebSocketClient(tokenStore.selectedToken.id)
|
||||||
console.log('🗼 WebSocket客户端:', client)
|
|
||||||
console.log('🗼 WebSocket客户端状态:', client ? 'exists' : 'null')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 组件挂载时获取塔信息
|
// 组件挂载时获取塔信息
|
||||||
if (tokenStore.selectedToken && wsStatus.value === 'connected') {
|
if (tokenStore.selectedToken && wsStatus.value === 'connected') {
|
||||||
console.log('🗼 条件满足,开始获取塔信息')
|
|
||||||
getTowerInfo()
|
getTowerInfo()
|
||||||
} else if (!tokenStore.selectedToken) {
|
} else if (!tokenStore.selectedToken) {
|
||||||
console.log('🗼 没有选中的Token,无法获取塔信息')
|
console.log('🗼 没有选中的Token,无法获取塔信息')
|
||||||
|
|||||||
@@ -712,9 +712,23 @@ export const useTokenStore = defineStore('tokens', () => {
|
|||||||
return sendMessage(tokenId, 'heart_beat')
|
return sendMessage(tokenId, 'heart_beat')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送获取角色信息请求
|
// 发送获取角色信息请求(异步处理)
|
||||||
const sendGetRoleInfo = (tokenId, params = {}) => {
|
const sendGetRoleInfo = async (tokenId, params = {}) => {
|
||||||
return sendMessageWithPromise(tokenId, 'role_getroleinfo', params)
|
try {
|
||||||
|
const roleInfo = await sendMessageWithPromise(tokenId, 'role_getroleinfo', params, 10000)
|
||||||
|
|
||||||
|
// 手动更新游戏数据(因为响应可能不会自动触发消息处理)
|
||||||
|
if (roleInfo) {
|
||||||
|
gameData.value.roleInfo = roleInfo
|
||||||
|
gameData.value.lastUpdated = new Date().toISOString()
|
||||||
|
console.log('📊 角色信息已通过 Promise 更新')
|
||||||
|
}
|
||||||
|
|
||||||
|
return roleInfo
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ 获取角色信息失败 [${tokenId}]:`, error.message)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送获取数据版本请求
|
// 发送获取数据版本请求
|
||||||
|
|||||||
@@ -143,11 +143,11 @@ export function registerDefaultCommands(reg) {
|
|||||||
|
|
||||||
// 神器抽奖
|
// 神器抽奖
|
||||||
.register("artifact_lottery", { lotteryNumber: 1, newFree: true, type: 1 })
|
.register("artifact_lottery", { lotteryNumber: 1, newFree: true, type: 1 })
|
||||||
|
|
||||||
// 灯神相关
|
// 灯神相关
|
||||||
.register("genie_sweep", { genieId: 1 })
|
.register("genie_sweep", { genieId: 1 })
|
||||||
.register("genie_buysweep")
|
.register("genie_buysweep")
|
||||||
|
|
||||||
// 礼包相关
|
// 礼包相关
|
||||||
.register("discount_claimreward", { discountId: 1 })
|
.register("discount_claimreward", { discountId: 1 })
|
||||||
.register("card_claimreward", { cardId: 1 })
|
.register("card_claimreward", { cardId: 1 })
|
||||||
@@ -165,7 +165,7 @@ export function registerDefaultCommands(reg) {
|
|||||||
|
|
||||||
// 排名相关
|
// 排名相关
|
||||||
.register("rank_getroleinfo")
|
.register("rank_getroleinfo")
|
||||||
|
|
||||||
// 梦魇相关
|
// 梦魇相关
|
||||||
.register("nightmare_getroleinfo")
|
.register("nightmare_getroleinfo")
|
||||||
}
|
}
|
||||||
@@ -234,7 +234,7 @@ export class XyzwWebSocketClient {
|
|||||||
try {
|
try {
|
||||||
packet = this.utils?.parse ? this.utils.parse(buffer, "auto") : buffer
|
packet = this.utils?.parse ? this.utils.parse(buffer, "auto") : buffer
|
||||||
// Blob解析完成
|
// Blob解析完成
|
||||||
|
|
||||||
// 处理消息体解码(ProtoMsg会自动解码)
|
// 处理消息体解码(ProtoMsg会自动解码)
|
||||||
if (packet instanceof Object && packet.rawData !== undefined) {
|
if (packet instanceof Object && packet.rawData !== undefined) {
|
||||||
// ProtoMsg消息
|
// ProtoMsg消息
|
||||||
@@ -252,19 +252,19 @@ export class XyzwWebSocketClient {
|
|||||||
// 消息体解码失败
|
// 消息体解码失败
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.showMsg) {
|
if (this.showMsg) {
|
||||||
// 收到Blob消息
|
// 收到Blob消息
|
||||||
}
|
}
|
||||||
|
|
||||||
// 回调处理
|
// 回调处理
|
||||||
if (this.messageListener) {
|
if (this.messageListener) {
|
||||||
this.messageListener(packet)
|
this.messageListener(packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Promise 响应处理
|
// Promise 响应处理
|
||||||
this._handlePromiseResponse(packet)
|
this._handlePromiseResponse(packet)
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Blob解析失败:', error.message)
|
console.error('Blob解析失败:', error.message)
|
||||||
}
|
}
|
||||||
@@ -382,27 +382,29 @@ export class XyzwWebSocketClient {
|
|||||||
|
|
||||||
/** Promise 版发送 */
|
/** Promise 版发送 */
|
||||||
sendWithPromise(cmd, params = {}, timeoutMs = 5000) {
|
sendWithPromise(cmd, params = {}, timeoutMs = 5000) {
|
||||||
const respKey = `${cmd}_${this.seq + 1}`
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!this.connected && !this.socket) {
|
if (!this.connected && !this.socket) {
|
||||||
return reject(new Error("WebSocket 连接已关闭"))
|
return reject(new Error("WebSocket 连接已关闭"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 生成唯一的请求ID
|
||||||
|
const requestId = `${cmd}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
||||||
|
|
||||||
// 设置 Promise 状态
|
// 设置 Promise 状态
|
||||||
this.promises[respKey] = { resolve, reject }
|
this.promises[requestId] = { resolve, reject, originalCmd: cmd }
|
||||||
|
|
||||||
// 超时处理
|
// 超时处理
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
delete this.promises[respKey]
|
delete this.promises[requestId]
|
||||||
reject(new Error(`请求超时: ${cmd} (${timeoutMs}ms)`))
|
reject(new Error(`请求超时: ${cmd} (${timeoutMs}ms)`))
|
||||||
}, timeoutMs)
|
}, timeoutMs)
|
||||||
|
|
||||||
// 发送消息
|
// 发送消息
|
||||||
this.send(cmd, params, {
|
this.send(cmd, params, {
|
||||||
respKey,
|
respKey: requestId,
|
||||||
onSent: () => {
|
onSent: () => {
|
||||||
clearTimeout(timer)
|
// 消息发送成功后,不要清除超时器,让它继续等待响应
|
||||||
|
// 只有在收到响应或超时时才清除
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -445,7 +447,7 @@ export class XyzwWebSocketClient {
|
|||||||
this.sendHeartbeat()
|
this.sendHeartbeat()
|
||||||
}
|
}
|
||||||
}, 3000)
|
}, 3000)
|
||||||
|
|
||||||
// 设置定期心跳
|
// 设置定期心跳
|
||||||
this.heartbeatTimer = setInterval(() => {
|
this.heartbeatTimer = setInterval(() => {
|
||||||
if (this.connected && this.socket?.readyState === WebSocket.OPEN) {
|
if (this.connected && this.socket?.readyState === WebSocket.OPEN) {
|
||||||
@@ -513,13 +515,11 @@ export class XyzwWebSocketClient {
|
|||||||
|
|
||||||
// 命令到响应的映射 - 处理响应命令与原始命令不匹配的情况
|
// 命令到响应的映射 - 处理响应命令与原始命令不匹配的情况
|
||||||
const responseToCommandMap = {
|
const responseToCommandMap = {
|
||||||
|
// 1:1 响应映射(优先级高)
|
||||||
'role_getroleinforesp': 'role_getroleinfo',
|
'role_getroleinforesp': 'role_getroleinfo',
|
||||||
'system_signinrewardresp': 'system_signinreward',
|
|
||||||
'hero_recruitresp': 'hero_recruit',
|
'hero_recruitresp': 'hero_recruit',
|
||||||
'friend_batchresp': 'friend_batch',
|
'friend_batchresp': 'friend_batch',
|
||||||
'system_claimhanguprewardresp': 'system_claimhangupreward',
|
'system_claimhanguprewardresp': 'system_claimhangupreward',
|
||||||
'task_claimdailyrewardresp': 'task_claimdailyreward',
|
|
||||||
'task_claimweekrewardresp': 'task_claimweekreward',
|
|
||||||
'item_openboxresp': 'item_openbox',
|
'item_openboxresp': 'item_openbox',
|
||||||
'bottlehelper_claimresp': 'bottlehelper_claim',
|
'bottlehelper_claimresp': 'bottlehelper_claim',
|
||||||
'bottlehelper_startresp': 'bottlehelper_start',
|
'bottlehelper_startresp': 'bottlehelper_start',
|
||||||
@@ -532,23 +532,46 @@ export class XyzwWebSocketClient {
|
|||||||
'arena_getareatargetresp': 'arena_getareatarget',
|
'arena_getareatargetresp': 'arena_getareatarget',
|
||||||
'presetteam_getinforesp': 'presetteam_getinfo',
|
'presetteam_getinforesp': 'presetteam_getinfo',
|
||||||
'presetteam_saveteamresp': 'presetteam_saveteam',
|
'presetteam_saveteamresp': 'presetteam_saveteam',
|
||||||
|
'presetteam_getteamresp': 'presetteam_getteam',
|
||||||
'mail_claimallattachmentresp': 'mail_claimallattachment',
|
'mail_claimallattachmentresp': 'mail_claimallattachment',
|
||||||
'store_buyresp': 'store_purchase'
|
'store_buyresp': 'store_purchase',
|
||||||
|
'system_getdatabundleverresp': 'system_getdatabundlever',
|
||||||
|
'tower_claimrewardresp': 'tower_claimreward',
|
||||||
|
'fight_starttowerresp': 'fight_starttower',
|
||||||
|
|
||||||
|
// 特殊响应映射 - 有些命令有独立响应,有些用同步响应
|
||||||
|
'task_claimdailyrewardresp': 'task_claimdailyreward',
|
||||||
|
'task_claimweekrewardresp': 'task_claimweekreward',
|
||||||
|
|
||||||
|
// 同步响应映射(优先级低)
|
||||||
|
'syncresp': ['system_mysharecallback', 'task_claimdailypoint'],
|
||||||
|
'syncrewardresp': ['system_buygold', 'discount_claimreward', 'card_claimreward',
|
||||||
|
'artifact_lottery', 'genie_sweep', 'genie_buysweep','system_signinreward']
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取原始命令名
|
// 获取原始命令名(支持一对一和一对多映射)
|
||||||
const originalCmd = responseToCommandMap[cmd] || cmd
|
let originalCmds = responseToCommandMap[cmd]
|
||||||
|
if (!originalCmds) {
|
||||||
|
originalCmds = [cmd] // 如果没有映射,使用响应命令本身
|
||||||
|
} else if (typeof originalCmds === 'string') {
|
||||||
|
originalCmds = [originalCmds] // 转换为数组
|
||||||
|
}
|
||||||
|
|
||||||
// 查找对应的 Promise
|
// 查找对应的 Promise - 遍历所有等待中的 Promise
|
||||||
for (const [key, promise] of Object.entries(this.promises)) {
|
for (const [requestId, promiseData] of Object.entries(this.promises)) {
|
||||||
// 检查是否匹配原始命令或响应命令
|
// 检查 Promise 是否匹配当前响应的任一原始命令
|
||||||
if (key.startsWith(originalCmd) || key.startsWith(cmd) || cmd === key) {
|
if (originalCmds.includes(promiseData.originalCmd)) {
|
||||||
delete this.promises[key]
|
delete this.promises[requestId]
|
||||||
|
|
||||||
|
// 获取响应数据,优先使用 rawData(ProtoMsg 自动解码),然后 decodedBody(手动解码),最后 body
|
||||||
|
const responseBody = packet.rawData !== undefined ? packet.rawData :
|
||||||
|
packet.decodedBody !== undefined ? packet.decodedBody :
|
||||||
|
packet.body
|
||||||
|
|
||||||
if (packet.code === 0 || packet.code === undefined) {
|
if (packet.code === 0 || packet.code === undefined) {
|
||||||
promise.resolve(packet.body || packet)
|
promiseData.resolve(responseBody || packet)
|
||||||
} else {
|
} else {
|
||||||
promise.reject(new Error(`服务器错误: ${packet.code} - ${packet.hint || '未知错误'}`))
|
promiseData.reject(new Error(`服务器错误: ${packet.code} - ${packet.hint || '未知错误'}`))
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -267,6 +267,77 @@ const bulkActionOptions = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// 等待WebSocket连接并加载阵容数据
|
||||||
|
const loadTeamDataWithConnection = async (tokenId, maxRetries = 3, retryDelay = 2000) => {
|
||||||
|
console.log('每日页面进入,开始检查WebSocket连接状态...')
|
||||||
|
|
||||||
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||||
|
try {
|
||||||
|
// 检查WebSocket连接状态
|
||||||
|
const wsStatus = tokenStore.getWebSocketStatus(tokenId)
|
||||||
|
console.log(`第${attempt}次检查,WebSocket状态:`, wsStatus)
|
||||||
|
|
||||||
|
if (wsStatus !== 'connected') {
|
||||||
|
console.log('WebSocket未连接,尝试建立连接...')
|
||||||
|
|
||||||
|
// 尝试建立WebSocket连接
|
||||||
|
const tokenData = tokenStore.gameTokens.find(t => t.id === tokenId)
|
||||||
|
if (tokenData && tokenData.token) {
|
||||||
|
// 触发WebSocket连接
|
||||||
|
tokenStore.createWebSocketConnection(tokenId, tokenData.token, tokenData.wsUrl)
|
||||||
|
|
||||||
|
// 等待连接建立
|
||||||
|
await new Promise(resolve => setTimeout(resolve, retryDelay))
|
||||||
|
|
||||||
|
// 再次检查连接状态
|
||||||
|
const newStatus = tokenStore.getWebSocketStatus(tokenId)
|
||||||
|
if (newStatus !== 'connected') {
|
||||||
|
if (attempt < maxRetries) {
|
||||||
|
console.log(`连接未建立,${retryDelay / 1000}秒后重试...`)
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
throw new Error('WebSocket连接超时')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('未找到有效的Token数据或WebSocket URL')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebSocket已连接,开始加载阵容数据
|
||||||
|
console.log('WebSocket已连接,开始加载阵容数据...')
|
||||||
|
const result = await tokenStore.sendMessageWithPromise(
|
||||||
|
tokenId,
|
||||||
|
'presetteam_getinfo',
|
||||||
|
{},
|
||||||
|
8000
|
||||||
|
)
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
// 更新到游戏数据缓存中
|
||||||
|
tokenStore.$patch((state) => {
|
||||||
|
state.gameData = { ...(state.gameData ?? {}), presetTeam: result }
|
||||||
|
})
|
||||||
|
console.log('阵容数据加载成功:', result)
|
||||||
|
message.success('阵容数据已更新')
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`第${attempt}次尝试失败:`, error)
|
||||||
|
|
||||||
|
if (attempt < maxRetries) {
|
||||||
|
console.log(`${retryDelay / 1000}秒后进行第${attempt + 1}次重试...`)
|
||||||
|
await new Promise(resolve => setTimeout(resolve, retryDelay))
|
||||||
|
} else {
|
||||||
|
console.error('所有重试均失败,阵容数据加载失败')
|
||||||
|
message.warning(`阵容数据加载失败: ${error.message || '未知错误'}`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 方法
|
// 方法
|
||||||
const refreshTasks = async () => {
|
const refreshTasks = async () => {
|
||||||
if (!selectedRoleId.value) {
|
if (!selectedRoleId.value) {
|
||||||
@@ -567,6 +638,11 @@ onMounted(async () => {
|
|||||||
await gameRolesStore.fetchGameRoles()
|
await gameRolesStore.fetchGameRoles()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 页面进入时手动调用阵容加载接口,确保WebSocket连接后再调用
|
||||||
|
if (tokenStore.selectedToken) {
|
||||||
|
await loadTeamDataWithConnection(tokenStore.selectedToken.id)
|
||||||
|
}
|
||||||
|
|
||||||
// 设置默认选中的角色
|
// 设置默认选中的角色
|
||||||
if (gameRolesStore.selectedRole) {
|
if (gameRolesStore.selectedRole) {
|
||||||
selectedRoleId.value = gameRolesStore.selectedRole.id
|
selectedRoleId.value = gameRolesStore.selectedRole.id
|
||||||
|
|||||||
Reference in New Issue
Block a user