fix:修复若干bug
This commit is contained in:
@@ -22,12 +22,18 @@
|
|||||||
{{ teamId }}
|
{{ teamId }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="team-button refresh-button"
|
class="refresh-button"
|
||||||
:disabled="loading"
|
:disabled="loading"
|
||||||
title="刷新队伍数据"
|
title="刷新队伍数据"
|
||||||
@click="refreshTeamData(true)"
|
@click="refreshTeamData(true)"
|
||||||
>
|
>
|
||||||
🔄
|
<svg class="refresh-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/>
|
||||||
|
<path d="M21 3v5h-5"/>
|
||||||
|
<path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/>
|
||||||
|
<path d="M3 21v-5h5"/>
|
||||||
|
</svg>
|
||||||
|
<span class="refresh-text">刷新</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -84,7 +90,7 @@ import { useMessage, NTag } from 'naive-ui'
|
|||||||
* 集成英雄字典(游戏ID -> { name, type })
|
* 集成英雄字典(游戏ID -> { name, type })
|
||||||
* 你也可以独立出一个 heroDict.ts 后 import;按你的要求,这里整合到同一文件。
|
* 你也可以独立出一个 heroDict.ts 后 import;按你的要求,这里整合到同一文件。
|
||||||
*/
|
*/
|
||||||
const HERO_DICT: Record<number, { name: string; type: string }> = {
|
const HERO_DICT = {
|
||||||
101: { name: '司马懿', type: '魏国' }, 102: { name: '郭嘉', type: '魏国' }, 103: { name: '关羽', type: '蜀国' },
|
101: { name: '司马懿', type: '魏国' }, 102: { name: '郭嘉', type: '魏国' }, 103: { name: '关羽', type: '蜀国' },
|
||||||
104: { name: '诸葛亮', type: '蜀国' }, 105: { name: '周瑜', type: '吴国' }, 106: { name: '太史慈', type: '吴国' },
|
104: { name: '诸葛亮', type: '蜀国' }, 105: { name: '周瑜', type: '吴国' }, 106: { name: '太史慈', type: '吴国' },
|
||||||
107: { name: '吕布', type: '群雄' }, 108: { name: '华佗', type: '群雄' }, 109: { name: '甄姬', type: '魏国' },
|
107: { name: '吕布', type: '群雄' }, 108: { name: '华佗', type: '群雄' }, 109: { name: '甄姬', type: '魏国' },
|
||||||
@@ -115,16 +121,22 @@ const message = useMessage()
|
|||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const switching = ref(false)
|
const switching = ref(false)
|
||||||
const currentTeam = ref(1)
|
const currentTeam = ref(1)
|
||||||
const availableTeams = ref<number[]>([1, 2, 3, 4])
|
const availableTeams = ref([1, 2, 3, 4])
|
||||||
|
|
||||||
|
// WebSocket连接状态
|
||||||
|
const wsStatus = computed(() => {
|
||||||
|
if (!tokenStore.selectedToken) return 'disconnected'
|
||||||
|
return tokenStore.getWebSocketStatus(tokenStore.selectedToken.id)
|
||||||
|
})
|
||||||
|
|
||||||
// —— 缓存优先的 presetTeam 原始数据 ——
|
// —— 缓存优先的 presetTeam 原始数据 ——
|
||||||
const presetTeamRaw = computed(() => tokenStore.gameData?.presetTeam ?? null)
|
const presetTeamRaw = computed(() => tokenStore.gameData?.presetTeam ?? null)
|
||||||
|
|
||||||
// 统一结构:输出 { useTeamId, teams }
|
// 统一结构:输出 { useTeamId, teams }
|
||||||
function normalizePresetTeam(raw: any): { useTeamId: number; teams: Record<number, { teamInfo: Record<string, any> }> } {
|
function normalizePresetTeam(raw) {
|
||||||
if (!raw) return { useTeamId: 1, teams: {} }
|
if (!raw) return { useTeamId: 1, teams: {} }
|
||||||
const root = raw.presetTeamInfo ?? raw
|
const root = raw.presetTeamInfo ?? raw
|
||||||
const findUseIdRec = (obj: any): number | null => {
|
const findUseIdRec = (obj) => {
|
||||||
if (!obj || typeof obj !== 'object') return null
|
if (!obj || typeof obj !== 'object') return null
|
||||||
if (typeof obj.useTeamId === 'number') return obj.useTeamId
|
if (typeof obj.useTeamId === 'number') return obj.useTeamId
|
||||||
for (const k of Object.keys(obj)) {
|
for (const k of Object.keys(obj)) {
|
||||||
@@ -136,7 +148,7 @@ function normalizePresetTeam(raw: any): { useTeamId: number; teams: Record<numbe
|
|||||||
const useTeamId = root.useTeamId ?? root.presetTeamInfo?.useTeamId ?? findUseIdRec(root) ?? 1
|
const useTeamId = root.useTeamId ?? root.presetTeamInfo?.useTeamId ?? findUseIdRec(root) ?? 1
|
||||||
|
|
||||||
const dict = root.presetTeamInfo ?? root
|
const dict = root.presetTeamInfo ?? root
|
||||||
const teams: Record<number, { teamInfo: Record<string, any> }> = {}
|
const teams = {}
|
||||||
const ids = Object.keys(dict || {}).filter(k => /^\d+$/.test(k))
|
const ids = Object.keys(dict || {}).filter(k => /^\d+$/.test(k))
|
||||||
for (const idStr of ids) {
|
for (const idStr of ids) {
|
||||||
const id = Number(idStr)
|
const id = Number(idStr)
|
||||||
@@ -146,10 +158,10 @@ function normalizePresetTeam(raw: any): { useTeamId: number; teams: Record<numbe
|
|||||||
teams[id] = { teamInfo: node.teamInfo }
|
teams[id] = { teamInfo: node.teamInfo }
|
||||||
} else if (node.heroes) {
|
} else if (node.heroes) {
|
||||||
const ti: Record<string, any> = {}
|
const ti: Record<string, any> = {}
|
||||||
node.heroes.forEach((h: any, idx: number) => { ti[String(idx + 1)] = h })
|
node.heroes.forEach((h, idx) => { ti[String(idx + 1)] = h })
|
||||||
teams[id] = { teamInfo: ti }
|
teams[id] = { teamInfo: ti }
|
||||||
} else if (typeof node === 'object') {
|
} else if (typeof node === 'object') {
|
||||||
const hasHero = Object.values(node).some((v: any) => v && typeof v === 'object' && 'heroId' in v)
|
const hasHero = Object.values(node).some((v) => v && typeof v === 'object' && 'heroId' in v)
|
||||||
teams[id] = { teamInfo: hasHero ? node : {} }
|
teams[id] = { teamInfo: hasHero ? node : {} }
|
||||||
} else {
|
} else {
|
||||||
teams[id] = { teamInfo: {} }
|
teams[id] = { teamInfo: {} }
|
||||||
@@ -164,7 +176,7 @@ const presetTeam = computed(() => normalizePresetTeam(presetTeamRaw.value))
|
|||||||
const currentTeamHeroes = computed(() => {
|
const currentTeamHeroes = computed(() => {
|
||||||
const team = presetTeam.value.teams[currentTeam.value]?.teamInfo
|
const team = presetTeam.value.teams[currentTeam.value]?.teamInfo
|
||||||
if (!team) return []
|
if (!team) return []
|
||||||
const heroes: Array<{ id: number; name: string; type: string; position: number; level?: number; avatar?: string }> = []
|
const heroes = []
|
||||||
for (const [pos, hero] of Object.entries(team)) {
|
for (const [pos, hero] of Object.entries(team)) {
|
||||||
const hid = (hero as any)?.heroId ?? (hero as any)?.id
|
const hid = (hero as any)?.heroId ?? (hero as any)?.id
|
||||||
if (!hid) continue
|
if (!hid) continue
|
||||||
@@ -183,10 +195,10 @@ const currentTeamHeroes = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// —— 命令封装 ——
|
// —— 命令封装 ——
|
||||||
const executeGameCommand = async (tokenId: string | number, cmd: string, params: any = {}, description = '', timeout = 8000) => {
|
const executeGameCommand = async (tokenId, cmd, params = {}, description = '', timeout = 8000) => {
|
||||||
try {
|
try {
|
||||||
return await tokenStore.sendMessageWithPromise(tokenId, cmd, params, timeout)
|
return await tokenStore.sendMessageWithPromise(tokenId, cmd, params, timeout)
|
||||||
} catch (error: any) {
|
} catch (error) {
|
||||||
const msg = error?.message ?? String(error)
|
const msg = error?.message ?? String(error)
|
||||||
if (description) message.error(`${description}失败:${msg}`)
|
if (description) message.error(`${description}失败:${msg}`)
|
||||||
throw error
|
throw error
|
||||||
@@ -213,6 +225,9 @@ const getTeamInfoWithCache = async (force = false) => {
|
|||||||
state.gameData = { ...(state.gameData ?? {}), presetTeam: result }
|
state.gameData = { ...(state.gameData ?? {}), presetTeam: result }
|
||||||
})
|
})
|
||||||
return result?.presetTeamInfo ?? null
|
return result?.presetTeamInfo ?? null
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取阵容信息失败:', error)
|
||||||
|
return null
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
@@ -226,7 +241,7 @@ const updateAvailableTeams = () => {
|
|||||||
const updateCurrentTeam = () => { currentTeam.value = presetTeam.value.useTeamId || 1 }
|
const updateCurrentTeam = () => { currentTeam.value = presetTeam.value.useTeamId || 1 }
|
||||||
|
|
||||||
// —— 交互 ——
|
// —— 交互 ——
|
||||||
const selectTeam = async (teamId: number) => {
|
const selectTeam = async (teamId) => {
|
||||||
if (switching.value || loading.value) return
|
if (switching.value || loading.value) return
|
||||||
if (!tokenStore.selectedToken) { message.warning('请先选择Token'); return }
|
if (!tokenStore.selectedToken) { message.warning('请先选择Token'); return }
|
||||||
const prev = currentTeam.value
|
const prev = currentTeam.value
|
||||||
@@ -245,13 +260,52 @@ const selectTeam = async (teamId: number) => {
|
|||||||
|
|
||||||
const refreshTeamData = async (force = false) => { await getTeamInfoWithCache(force) }
|
const refreshTeamData = async (force = false) => { await getTeamInfoWithCache(force) }
|
||||||
|
|
||||||
// —— 首次挂载:先查缓存,再兜底拉接口 ——
|
// —— 首次挂载:检查连接状态后获取数据 ——
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await refreshTeamData(false)
|
// 组件挂载时获取队伍信息
|
||||||
updateAvailableTeams(); updateCurrentTeam()
|
if (tokenStore.selectedToken && wsStatus.value === 'connected') {
|
||||||
if (!presetTeamRaw.value) {
|
await refreshTeamData(false)
|
||||||
await refreshTeamData(true)
|
|
||||||
updateAvailableTeams(); updateCurrentTeam()
|
updateAvailableTeams(); updateCurrentTeam()
|
||||||
|
if (!presetTeamRaw.value) {
|
||||||
|
await refreshTeamData(true)
|
||||||
|
updateAvailableTeams(); updateCurrentTeam()
|
||||||
|
}
|
||||||
|
} else if (!tokenStore.selectedToken) {
|
||||||
|
console.log('🛡️ 没有选中的Token,无法获取队伍信息')
|
||||||
|
} else {
|
||||||
|
console.log('🛡️ WebSocket未连接,等待连接后自动获取队伍信息')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// —— 监听WebSocket连接状态变化 ——
|
||||||
|
watch(wsStatus, (newStatus, oldStatus) => {
|
||||||
|
console.log(`🛡️ WebSocket状态变化: ${oldStatus} -> ${newStatus}`)
|
||||||
|
|
||||||
|
if (newStatus === 'connected' && oldStatus !== 'connected' && tokenStore.selectedToken) {
|
||||||
|
console.log('🛡️ WebSocket已连接,自动获取队伍信息')
|
||||||
|
// 延迟一点时间让WebSocket完全就绪
|
||||||
|
setTimeout(async () => {
|
||||||
|
await refreshTeamData(false)
|
||||||
|
updateAvailableTeams(); updateCurrentTeam()
|
||||||
|
if (!presetTeamRaw.value) {
|
||||||
|
await refreshTeamData(true)
|
||||||
|
updateAvailableTeams(); updateCurrentTeam()
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// —— 监听Token变化 ——
|
||||||
|
watch(() => tokenStore.selectedToken, async (newToken, oldToken) => {
|
||||||
|
if (newToken && newToken.id !== oldToken?.id) {
|
||||||
|
console.log('🛡️ Token已切换,重新获取队伍信息')
|
||||||
|
|
||||||
|
// 检查WebSocket是否已连接
|
||||||
|
const status = tokenStore.getWebSocketStatus(newToken.id)
|
||||||
|
if (status === 'connected') {
|
||||||
|
await refreshTeamData(true) // 切换Token时强制刷新
|
||||||
|
updateAvailableTeams(); updateCurrentTeam()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -282,9 +336,76 @@ watch(() => presetTeamRaw.value, () => { updateAvailableTeams(); updateCurrentTe
|
|||||||
cursor: pointer; transition: all var(--transition-fast);
|
cursor: pointer; transition: all var(--transition-fast);
|
||||||
&:hover { background: var(--bg-secondary); }
|
&:hover { background: var(--bg-secondary); }
|
||||||
&.active { background: var(--primary-color); color: white; }
|
&.active { background: var(--primary-color); color: white; }
|
||||||
&.refresh-button { background: var(--success-color, #10b981); color: white; &:hover { background: var(--success-color-dark, #059669); } }
|
|
||||||
&:disabled { opacity: .6; cursor: not-allowed; }
|
&:disabled { opacity: .6; cursor: not-allowed; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.refresh-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0 12px;
|
||||||
|
border: 1px solid var(--border-color, #e5e7eb);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--bg-primary, #ffffff);
|
||||||
|
color: var(--text-secondary, #6b7280);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-fast, 0.15s ease);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--bg-secondary, #f9fafb);
|
||||||
|
border-color: var(--border-hover, #d1d5db);
|
||||||
|
color: var(--text-primary, #374151);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
|
box-shadow: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--bg-primary, #ffffff);
|
||||||
|
border-color: var(--border-color, #e5e7eb);
|
||||||
|
color: var(--text-secondary, #6b7280);
|
||||||
|
transform: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-icon {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
transition: transform var(--transition-fast, 0.15s ease);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:disabled):hover .refresh-icon {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled .refresh-icon {
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-text {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
from { transform: rotate(0deg); }
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
.card-content .current-team-info {
|
.card-content .current-team-info {
|
||||||
display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--spacing-lg);
|
display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--spacing-lg);
|
||||||
.label { font-size: var(--font-size-sm); color: var(--text-secondary); }
|
.label { font-size: var(--font-size-sm); color: var(--text-secondary); }
|
||||||
|
|||||||
@@ -89,20 +89,18 @@
|
|||||||
>
|
>
|
||||||
{{ getWSStatus(roleId) === 'connected' ? '断开WS' : '连接WS' }}
|
{{ getWSStatus(roleId) === 'connected' ? '断开WS' : '连接WS' }}
|
||||||
</n-button>
|
</n-button>
|
||||||
<n-button
|
|
||||||
size="tiny"
|
<n-dropdown
|
||||||
type="warning"
|
:options="getTokenMenuOptions(tokenData)"
|
||||||
@click="regenerateToken(roleId)"
|
@select="handleTokenAction($event, roleId, tokenData)"
|
||||||
|
trigger="click"
|
||||||
>
|
>
|
||||||
刷新Token
|
<n-button size="tiny" type="tertiary">
|
||||||
</n-button>
|
<template #icon>
|
||||||
<n-button
|
<n-icon><EllipsisHorizontal /></n-icon>
|
||||||
size="tiny"
|
</template>
|
||||||
type="error"
|
</n-button>
|
||||||
@click="removeToken(roleId)"
|
</n-dropdown>
|
||||||
>
|
|
||||||
删除
|
|
||||||
</n-button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -156,14 +154,19 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { ref, h } from 'vue'
|
||||||
import { useMessage, useDialog } from 'naive-ui'
|
import { useMessage, useDialog, NIcon } from 'naive-ui'
|
||||||
import { useLocalTokenStore } from '@/stores/localTokenManager'
|
import { useLocalTokenStore } from '@/stores/localTokenManager'
|
||||||
import { useGameRolesStore } from '@/stores/gameRoles'
|
import { useGameRolesStore } from '@/stores/gameRoles'
|
||||||
import {
|
import {
|
||||||
Refresh,
|
Refresh,
|
||||||
Download,
|
Download,
|
||||||
CloudUpload
|
CloudUpload,
|
||||||
|
EllipsisHorizontal,
|
||||||
|
Create,
|
||||||
|
TrashBin,
|
||||||
|
SyncCircle,
|
||||||
|
CopyOutline
|
||||||
} from '@vicons/ionicons5'
|
} from '@vicons/ionicons5'
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
@@ -205,6 +208,70 @@ const getWSStatusText = (status) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取Token菜单选项
|
||||||
|
const getTokenMenuOptions = (tokenData) => {
|
||||||
|
const options = [
|
||||||
|
{
|
||||||
|
label: '编辑',
|
||||||
|
key: 'edit',
|
||||||
|
icon: () => h(NIcon, null, { default: () => h(Create) })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '复制Token',
|
||||||
|
key: 'copy',
|
||||||
|
icon: () => h(NIcon, null, { default: () => h(CopyOutline) })
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 如果是URL获取的Token,显示刷新选项
|
||||||
|
if (tokenData.importMethod === 'url' && tokenData.sourceUrl) {
|
||||||
|
options.unshift({
|
||||||
|
label: '从URL刷新',
|
||||||
|
key: 'refresh-url',
|
||||||
|
icon: () => h(NIcon, null, { default: () => h(SyncCircle) })
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// 手动添加的Token显示重新生成选项
|
||||||
|
options.unshift({
|
||||||
|
label: '刷新Token',
|
||||||
|
key: 'refresh',
|
||||||
|
icon: () => h(NIcon, null, { default: () => h(Refresh) })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
options.push(
|
||||||
|
{ type: 'divider' },
|
||||||
|
{
|
||||||
|
label: '删除',
|
||||||
|
key: 'delete',
|
||||||
|
icon: () => h(NIcon, null, { default: () => h(TrashBin) })
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理Token菜单操作
|
||||||
|
const handleTokenAction = (action, roleId, tokenData) => {
|
||||||
|
switch (action) {
|
||||||
|
case 'edit':
|
||||||
|
editToken(roleId, tokenData)
|
||||||
|
break
|
||||||
|
case 'copy':
|
||||||
|
copyToken(tokenData.token)
|
||||||
|
break
|
||||||
|
case 'refresh':
|
||||||
|
regenerateToken(roleId)
|
||||||
|
break
|
||||||
|
case 'refresh-url':
|
||||||
|
refreshTokenFromUrl(roleId, tokenData)
|
||||||
|
break
|
||||||
|
case 'delete':
|
||||||
|
removeToken(roleId)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const refreshTokens = () => {
|
const refreshTokens = () => {
|
||||||
localTokenStore.initTokenManager()
|
localTokenStore.initTokenManager()
|
||||||
message.success('Token数据已刷新')
|
message.success('Token数据已刷新')
|
||||||
@@ -337,6 +404,84 @@ const removeToken = (roleId) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 编辑Token(暂时显示提示信息,后续可以实现编辑功能)
|
||||||
|
const editToken = (roleId, tokenData) => {
|
||||||
|
message.info('编辑功能正在开发中')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制Token到剪贴板
|
||||||
|
const copyToken = async (token) => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(token)
|
||||||
|
message.success('Token已复制到剪贴板')
|
||||||
|
} catch (error) {
|
||||||
|
// 降级方案
|
||||||
|
const textArea = document.createElement('textarea')
|
||||||
|
textArea.value = token
|
||||||
|
document.body.appendChild(textArea)
|
||||||
|
textArea.select()
|
||||||
|
document.execCommand('copy')
|
||||||
|
document.body.removeChild(textArea)
|
||||||
|
message.success('Token已复制到剪贴板')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从URL刷新Token
|
||||||
|
const refreshTokenFromUrl = async (roleId, tokenData) => {
|
||||||
|
if (!tokenData.sourceUrl) {
|
||||||
|
message.warning('该Token没有配置源URL')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.info({
|
||||||
|
title: '从URL刷新Token',
|
||||||
|
content: `确定要从源URL重新获取Token吗?\n源地址:${tokenData.sourceUrl}`,
|
||||||
|
positiveText: '确定',
|
||||||
|
negativeText: '取消',
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
try {
|
||||||
|
const loadingMsg = message.loading('正在从URL获取新Token...', { duration: 0 })
|
||||||
|
|
||||||
|
// 使用与TokenImport相同的逻辑获取Token
|
||||||
|
let response
|
||||||
|
const isLocalUrl = tokenData.sourceUrl.startsWith(window.location.origin) ||
|
||||||
|
tokenData.sourceUrl.startsWith('/') ||
|
||||||
|
tokenData.sourceUrl.startsWith('http://localhost') ||
|
||||||
|
tokenData.sourceUrl.startsWith('http://127.0.0.1')
|
||||||
|
|
||||||
|
if (isLocalUrl) {
|
||||||
|
response = await fetch(tokenData.sourceUrl)
|
||||||
|
} else {
|
||||||
|
// 跨域请求,使用代理
|
||||||
|
const proxyUrl = `/api/proxy?url=${encodeURIComponent(tokenData.sourceUrl)}`
|
||||||
|
response = await fetch(proxyUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
if (!data.token) {
|
||||||
|
throw new Error('返回数据中未找到token字段')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新Token
|
||||||
|
localTokenStore.updateGameToken(roleId, {
|
||||||
|
token: data.token,
|
||||||
|
lastUsed: new Date().toISOString()
|
||||||
|
})
|
||||||
|
|
||||||
|
loadingMsg.destroy()
|
||||||
|
message.success('Token刷新成功')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('URL刷新Token失败:', error)
|
||||||
|
message.error('刷新失败: ' + error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const exportTokens = () => {
|
const exportTokens = () => {
|
||||||
try {
|
try {
|
||||||
const tokenData = localTokenStore.exportTokens()
|
const tokenData = localTokenStore.exportTokens()
|
||||||
|
|||||||
@@ -44,7 +44,10 @@ export const useTokenStore = defineStore('tokens', () => {
|
|||||||
profession: tokenData.profession || '',
|
profession: tokenData.profession || '',
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
lastUsed: new Date().toISOString(),
|
lastUsed: new Date().toISOString(),
|
||||||
isActive: true
|
isActive: true,
|
||||||
|
// URL获取相关信息
|
||||||
|
sourceUrl: tokenData.sourceUrl || null, // Token来源URL(用于刷新)
|
||||||
|
importMethod: tokenData.importMethod || 'manual' // 导入方式:manual 或 url
|
||||||
}
|
}
|
||||||
|
|
||||||
gameTokens.value.push(newToken)
|
gameTokens.value.push(newToken)
|
||||||
@@ -221,7 +224,7 @@ export const useTokenStore = defineStore('tokens', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 处理队伍信息 - 支持多种队伍相关响应
|
// 处理队伍信息 - 支持多种队伍相关响应
|
||||||
else if (cmd === 'presetteam_getteam' || cmd === 'presetteam_getteamresp' ||
|
else if (cmd === 'presetteam_getinfo' || cmd === 'presetteam_getinforesp' ||
|
||||||
cmd === 'presetteam_setteam' || cmd === 'presetteam_setteamresp' ||
|
cmd === 'presetteam_setteam' || cmd === 'presetteam_setteamresp' ||
|
||||||
cmd === 'presetteam_saveteam' || cmd === 'presetteam_saveteamresp' ||
|
cmd === 'presetteam_saveteam' || cmd === 'presetteam_saveteamresp' ||
|
||||||
cmd === 'role_gettargetteam' || cmd === 'role_gettargetteamresp' ||
|
cmd === 'role_gettargetteam' || cmd === 'role_gettargetteamresp' ||
|
||||||
@@ -669,7 +672,7 @@ export const useTokenStore = defineStore('tokens', () => {
|
|||||||
|
|
||||||
// 发送消息到WebSocket
|
// 发送消息到WebSocket
|
||||||
const sendMessage = (tokenId, cmd, params = {}, options = {}) => {
|
const sendMessage = (tokenId, cmd, params = {}, options = {}) => {
|
||||||
const connection = wsConnections.value[tokenId]
|
const connection = wsConnections.value[tokenId]
|
||||||
if (!connection || connection.status !== 'connected') {
|
if (!connection || connection.status !== 'connected') {
|
||||||
console.error(`❌ WebSocket未连接,无法发送消息 [${tokenId}]`)
|
console.error(`❌ WebSocket未连接,无法发送消息 [${tokenId}]`)
|
||||||
return false
|
return false
|
||||||
@@ -696,15 +699,19 @@ export const useTokenStore = defineStore('tokens', () => {
|
|||||||
const sendMessageWithPromise = async (tokenId, cmd, params = {}, timeout = 5000) => {
|
const sendMessageWithPromise = async (tokenId, cmd, params = {}, timeout = 5000) => {
|
||||||
const connection = wsConnections.value[tokenId]
|
const connection = wsConnections.value[tokenId]
|
||||||
if (!connection || connection.status !== 'connected') {
|
if (!connection || connection.status !== 'connected') {
|
||||||
throw new Error(`WebSocket未连接 [${tokenId}]`)
|
return Promise.reject(new Error(`WebSocket未连接 [${tokenId}]`))
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = connection.client
|
const client = connection.client
|
||||||
if (!client) {
|
if (!client) {
|
||||||
throw new Error(`WebSocket客户端不存在 [${tokenId}]`)
|
return Promise.reject(new Error(`WebSocket客户端不存在 [${tokenId}]`))
|
||||||
}
|
}
|
||||||
|
|
||||||
return await client.sendWithPromise(cmd, params, timeout)
|
try {
|
||||||
|
return await client.sendWithPromise(cmd, params, timeout)
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送心跳消息
|
// 发送心跳消息
|
||||||
@@ -748,7 +755,7 @@ export const useTokenStore = defineStore('tokens', () => {
|
|||||||
|
|
||||||
// 发送获取队伍信息
|
// 发送获取队伍信息
|
||||||
const sendGetTeamInfo = (tokenId, params = {}) => {
|
const sendGetTeamInfo = (tokenId, params = {}) => {
|
||||||
return sendMessageWithPromise(tokenId, 'presetteam_getteam', params)
|
return sendMessageWithPromise(tokenId, 'presetteam_getinfo', params)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送自定义游戏消息
|
// 发送自定义游戏消息
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ export function registerDefaultCommands(reg) {
|
|||||||
|
|
||||||
// 队伍相关
|
// 队伍相关
|
||||||
.register("presetteam_getinfo")
|
.register("presetteam_getinfo")
|
||||||
.register("presetteam_getteam")
|
.register("presetteam_getinfo")
|
||||||
.register("presetteam_setteam")
|
.register("presetteam_setteam")
|
||||||
.register("presetteam_saveteam", { teamId: 1 })
|
.register("presetteam_saveteam", { teamId: 1 })
|
||||||
.register("role_gettargetteam")
|
.register("role_gettargetteam")
|
||||||
@@ -532,7 +532,7 @@ 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',
|
'presetteam_getinforesp': 'presetteam_getinfo',
|
||||||
'mail_claimallattachmentresp': 'mail_claimallattachment',
|
'mail_claimallattachmentresp': 'mail_claimallattachment',
|
||||||
'store_buyresp': 'store_purchase',
|
'store_buyresp': 'store_purchase',
|
||||||
'system_getdatabundleverresp': 'system_getdatabundlever',
|
'system_getdatabundleverresp': 'system_getdatabundlever',
|
||||||
|
|||||||
@@ -111,6 +111,10 @@ const connectionClass = computed(() => {
|
|||||||
return connectionStatus.value === 'connected' ? 'status-connected' : 'status-disconnected'
|
return connectionStatus.value === 'connected' ? 'status-connected' : 'status-disconnected'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const isConnected = computed(() => {
|
||||||
|
return connectionStatus.value === 'connected'
|
||||||
|
})
|
||||||
|
|
||||||
// 方法
|
// 方法
|
||||||
const handleFeatureAction = (featureType) => {
|
const handleFeatureAction = (featureType) => {
|
||||||
if (!tokenStore.selectedToken) {
|
if (!tokenStore.selectedToken) {
|
||||||
@@ -255,7 +259,7 @@ const initializeGameData = async () => {
|
|||||||
|
|
||||||
// 获取队伍信息
|
// 获取队伍信息
|
||||||
console.log('🎮 正在获取队伍信息...')
|
console.log('🎮 正在获取队伍信息...')
|
||||||
const teamResult = tokenStore.sendMessage(tokenId, 'presetteam_getteam')
|
const teamResult = tokenStore.sendMessage(tokenId, 'presetteam_getinfo')
|
||||||
console.log('🎮 队伍信息请求结果:', teamResult)
|
console.log('🎮 队伍信息请求结果:', teamResult)
|
||||||
|
|
||||||
console.log('🎮 游戏数据初始化请求已发送')
|
console.log('🎮 游戏数据初始化请求已发送')
|
||||||
|
|||||||
@@ -451,9 +451,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, onMounted, computed } from 'vue'
|
import { ref, reactive, onMounted, computed, h } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useMessage, useDialog } from 'naive-ui'
|
import { useMessage, useDialog, NIcon } from 'naive-ui'
|
||||||
import { useTokenStore } from '@/stores/tokenStore'
|
import { useTokenStore } from '@/stores/tokenStore'
|
||||||
import {
|
import {
|
||||||
Add,
|
Add,
|
||||||
@@ -464,7 +464,12 @@ import {
|
|||||||
Refresh,
|
Refresh,
|
||||||
Sunny,
|
Sunny,
|
||||||
Moon,
|
Moon,
|
||||||
Home
|
Home,
|
||||||
|
Create,
|
||||||
|
Copy,
|
||||||
|
SyncCircle,
|
||||||
|
Link,
|
||||||
|
TrashBin
|
||||||
} from '@vicons/ionicons5'
|
} from '@vicons/ionicons5'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -573,7 +578,8 @@ const handleImport = async () => {
|
|||||||
importForm.base64Token,
|
importForm.base64Token,
|
||||||
{
|
{
|
||||||
server: importForm.server,
|
server: importForm.server,
|
||||||
wsUrl: importForm.wsUrl
|
wsUrl: importForm.wsUrl,
|
||||||
|
importMethod: 'manual'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -654,7 +660,8 @@ const handleUrlImport = async () => {
|
|||||||
{
|
{
|
||||||
server: urlForm.server || data.server,
|
server: urlForm.server || data.server,
|
||||||
wsUrl: urlForm.wsUrl,
|
wsUrl: urlForm.wsUrl,
|
||||||
sourceUrl: urlForm.url // 保存源URL用于刷新
|
sourceUrl: urlForm.url, // 保存源URL用于刷新
|
||||||
|
importMethod: 'url'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -795,13 +802,52 @@ const toggleConnection = (token) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTokenActions = (token) => [
|
const getTokenActions = (token) => {
|
||||||
{ label: '编辑', key: 'edit' },
|
const actions = [
|
||||||
{ label: '复制Token', key: 'copy' },
|
{
|
||||||
{ label: '重新连接', key: 'reconnect' },
|
label: '编辑',
|
||||||
{ type: 'divider' },
|
key: 'edit',
|
||||||
{ label: '删除', key: 'delete' }
|
icon: () => h(NIcon, null, { default: () => h(Create) })
|
||||||
]
|
},
|
||||||
|
{
|
||||||
|
label: '复制Token',
|
||||||
|
key: 'copy',
|
||||||
|
icon: () => h(NIcon, null, { default: () => h(Copy) })
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 根据Token类型添加不同的刷新选项
|
||||||
|
if (token.importMethod === 'url' && token.sourceUrl) {
|
||||||
|
actions.push({
|
||||||
|
label: '从URL刷新',
|
||||||
|
key: 'refresh-url',
|
||||||
|
icon: () => h(NIcon, null, { default: () => h(SyncCircle) })
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
actions.push({
|
||||||
|
label: '刷新Token',
|
||||||
|
key: 'refresh',
|
||||||
|
icon: () => h(NIcon, null, { default: () => h(Refresh) })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
actions.push(
|
||||||
|
{
|
||||||
|
label: '重新连接',
|
||||||
|
key: 'reconnect',
|
||||||
|
icon: () => h(NIcon, null, { default: () => h(Link) })
|
||||||
|
},
|
||||||
|
{ type: 'divider' },
|
||||||
|
{
|
||||||
|
label: '删除',
|
||||||
|
key: 'delete',
|
||||||
|
icon: () => h(NIcon, null, { default: () => h(TrashBin) }),
|
||||||
|
props: { style: { color: '#e74c3c' } }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return actions
|
||||||
|
}
|
||||||
|
|
||||||
const handleTokenAction = async (key, token) => {
|
const handleTokenAction = async (key, token) => {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
@@ -811,6 +857,14 @@ const handleTokenAction = async (key, token) => {
|
|||||||
case 'copy':
|
case 'copy':
|
||||||
copyToken(token)
|
copyToken(token)
|
||||||
break
|
break
|
||||||
|
case 'refresh':
|
||||||
|
// 手动添加的Token的刷新逻辑(暂时提示)
|
||||||
|
message.info('手动添加的Token暂不支持刷新,请重新导入')
|
||||||
|
break
|
||||||
|
case 'refresh-url':
|
||||||
|
// URL获取的Token刷新
|
||||||
|
refreshToken(token)
|
||||||
|
break
|
||||||
case 'reconnect':
|
case 'reconnect':
|
||||||
reconnectToken(token)
|
reconnectToken(token)
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -18,13 +18,6 @@ export default defineConfig({
|
|||||||
server: {
|
server: {
|
||||||
port: 3000,
|
port: 3000,
|
||||||
open: true,
|
open: true,
|
||||||
proxy: {
|
|
||||||
'/api': {
|
|
||||||
target: 'http://xyzw.my',
|
|
||||||
changeOrigin: true,
|
|
||||||
rewrite: (path) => path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
css: {
|
css: {
|
||||||
preprocessorOptions: {
|
preprocessorOptions: {
|
||||||
|
|||||||
Reference in New Issue
Block a user