429 lines
12 KiB
TypeScript
429 lines
12 KiB
TypeScript
import { ref } from 'vue'
|
||
import { useToast } from './useToast'
|
||
import {
|
||
GetAccounts,
|
||
QueryXiaodianAssets,
|
||
QueryXiaodianRoles,
|
||
GetXiaodianBindQRCode,
|
||
BindXiaodianRole,
|
||
DeleteAccounts,
|
||
QueryScore,
|
||
ReloadAccounts,
|
||
VerifyAllExchange,
|
||
VerifyExchange
|
||
} from '@wails/go/app/App'
|
||
|
||
export interface Account {
|
||
index: number
|
||
name: string
|
||
cookie: string
|
||
}
|
||
|
||
export interface AccountStatus {
|
||
ok?: boolean
|
||
pending?: boolean
|
||
message?: string
|
||
}
|
||
|
||
const accounts = ref<Account[]>([])
|
||
const selectedIndices = ref<Set<number>>(new Set())
|
||
const statusMap = ref<Record<string, AccountStatus | boolean>>({})
|
||
const scoreMap = ref<Record<string, string>>({})
|
||
const accountProductMap = ref<Record<string, string>>({})
|
||
export interface XiaodianAssets {
|
||
success: boolean
|
||
coupons: number
|
||
fragments: number
|
||
error?: string
|
||
}
|
||
const xiaodianAssetsMap = ref<Record<string, XiaodianAssets>>({})
|
||
export interface XiaodianRole {
|
||
success: boolean
|
||
roleId?: string
|
||
roleName?: string
|
||
openId?: string
|
||
type?: string
|
||
platId?: number
|
||
areaId?: number
|
||
error?: string
|
||
}
|
||
export interface XiaodianQRCode {
|
||
success: boolean
|
||
accountName?: string
|
||
url?: string
|
||
gameRoleBindUrl?: string
|
||
error?: string
|
||
}
|
||
const xiaodianRolesMap = ref<Record<string, XiaodianRole>>({})
|
||
const xiaodianQRCode = ref<XiaodianQRCode | null>(null)
|
||
|
||
export function useAccounts() {
|
||
const { showToast } = useToast()
|
||
|
||
const loadAccounts = async () => {
|
||
const data = await GetAccounts()
|
||
accounts.value = data || []
|
||
|
||
// 清理无效选择
|
||
selectedIndices.value = new Set(
|
||
[...selectedIndices.value].filter(i => i >= 0 && i < accounts.value.length)
|
||
)
|
||
saveSelection()
|
||
}
|
||
|
||
const reloadAccounts = async () => {
|
||
const data = await ReloadAccounts()
|
||
await loadAccounts()
|
||
showToast('账号已刷新', `共 ${data.count} 个账号`, 'success')
|
||
}
|
||
|
||
const deleteAccount = async (accountName: string) => {
|
||
if (!accountName) return false
|
||
if (!window.confirm(`确定删除账号「${accountName}」及其 CK 吗?`)) return false
|
||
try {
|
||
const result = await DeleteAccounts([accountName])
|
||
if (!result.success) {
|
||
showToast('删除失败', result.error || '账号文件未更新', 'error')
|
||
return false
|
||
}
|
||
selectedIndices.value = new Set()
|
||
saveSelection()
|
||
delete statusMap.value[accountName]
|
||
delete accountProductMap.value[accountName]
|
||
delete xiaodianAssetsMap.value[accountName]
|
||
delete xiaodianRolesMap.value[accountName]
|
||
localStorage.setItem('statusMap', JSON.stringify(statusMap.value))
|
||
localStorage.setItem('accountProducts', JSON.stringify(accountProductMap.value))
|
||
await loadAccounts()
|
||
showToast('删除成功', `已删除 ${accountName} 及其 CK`, 'success')
|
||
return true
|
||
} catch (e: any) {
|
||
showToast('删除失败', e?.message || '', 'error')
|
||
return false
|
||
}
|
||
}
|
||
|
||
const toggleAccount = (index: number) => {
|
||
if (selectedIndices.value.has(index)) {
|
||
selectedIndices.value.delete(index)
|
||
} else {
|
||
selectedIndices.value.add(index)
|
||
}
|
||
saveSelection()
|
||
}
|
||
|
||
const toggleAll = () => {
|
||
if (selectedIndices.value.size === accounts.value.length) {
|
||
selectedIndices.value.clear()
|
||
} else {
|
||
accounts.value.forEach((_, i) => selectedIndices.value.add(i))
|
||
}
|
||
saveSelection()
|
||
}
|
||
|
||
const clearSelection = () => {
|
||
selectedIndices.value = new Set()
|
||
saveSelection()
|
||
}
|
||
|
||
const saveSelection = () => {
|
||
localStorage.setItem('selAccs', JSON.stringify([...selectedIndices.value]))
|
||
}
|
||
|
||
const restoreSelection = () => {
|
||
const saved = localStorage.getItem('selAccs')
|
||
if (saved) {
|
||
try {
|
||
selectedIndices.value = new Set(JSON.parse(saved))
|
||
} catch {}
|
||
}
|
||
const savedStatus = localStorage.getItem('statusMap')
|
||
if (savedStatus) {
|
||
try {
|
||
statusMap.value = JSON.parse(savedStatus)
|
||
} catch {}
|
||
}
|
||
const savedProducts = localStorage.getItem('accountProducts')
|
||
if (savedProducts) {
|
||
try {
|
||
accountProductMap.value = JSON.parse(savedProducts)
|
||
} catch {}
|
||
}
|
||
}
|
||
|
||
const queryAllScore = async () => {
|
||
showToast('查询积分', '正在并发查询...', 'info')
|
||
const tasks = accounts.value.map(async (a) => {
|
||
try {
|
||
const d = await QueryScore(a.name)
|
||
const score = d.score ?? 'N/A'
|
||
scoreMap.value[a.name] = String(score)
|
||
localStorage.setItem('accScore_' + a.name, String(score))
|
||
} catch {}
|
||
})
|
||
await Promise.all(tasks)
|
||
showToast('积分查询完成', '', 'success')
|
||
}
|
||
|
||
const queryXiaodianAssets = async (accountNames: string[], automatic = false) => {
|
||
if (accountNames.length === 0) {
|
||
if (!automatic) showToast('查询点券和碎片', '请先勾选要查询的账号', 'warn')
|
||
return false
|
||
}
|
||
showToast(automatic ? '抢购完成' : '查询点券和碎片', automatic ? '正在刷新本次抢购账号的资产...' : '正在查询账号资产...', 'info')
|
||
try {
|
||
const data = await QueryXiaodianAssets(accountNames)
|
||
const queriedResults = data?.results || {}
|
||
xiaodianAssetsMap.value = {
|
||
...xiaodianAssetsMap.value,
|
||
...queriedResults
|
||
}
|
||
|
||
const successCount = Object.values(queriedResults).filter(item => item.success).length
|
||
showToast(automatic ? '抢购后资产已刷新' : '资产查询完成', `成功 ${successCount}/${accountNames.length} 个账号`, successCount > 0 ? 'success' : 'warn')
|
||
return successCount > 0
|
||
} catch (e: any) {
|
||
showToast('资产查询失败', e?.message || '', 'error')
|
||
return false
|
||
}
|
||
}
|
||
|
||
const queryAllXiaodianAssets = async () => queryXiaodianAssets(selectedAccountNames())
|
||
|
||
const queryAllXiaodianRoles = async () => {
|
||
const accountNames = selectedAccountNames()
|
||
if (accountNames.length === 0) {
|
||
showToast('查询角色信息', '请先勾选要查询的账号', 'warn')
|
||
return
|
||
}
|
||
showToast('查询角色信息', '正在查询小店绑定角色...', 'info')
|
||
try {
|
||
const data = await QueryXiaodianRoles(accountNames)
|
||
const queriedResults = data?.results || {}
|
||
xiaodianRolesMap.value = {
|
||
...xiaodianRolesMap.value,
|
||
...queriedResults
|
||
}
|
||
const successCount = Object.values(queriedResults).filter(item => item.success).length
|
||
showToast('角色查询完成', `成功 ${successCount}/${accountNames.length} 个账号`, successCount > 0 ? 'success' : 'warn')
|
||
} catch (e: any) {
|
||
showToast('角色查询失败', e?.message || '', 'error')
|
||
}
|
||
}
|
||
|
||
const getXiaodianRole = (name: string): XiaodianRole | undefined => xiaodianRolesMap.value[name]
|
||
|
||
const getXiaodianBindQRCode = async (accountName: string) => {
|
||
if (!accountName) {
|
||
showToast('获取二维码', '请先选择一个账号', 'warn')
|
||
return false
|
||
}
|
||
try {
|
||
const result = await GetXiaodianBindQRCode(accountName)
|
||
if (!result?.success || !result.url) {
|
||
xiaodianQRCode.value = null
|
||
showToast('获取二维码失败', result?.error || '接口未返回二维码链接', 'error')
|
||
return false
|
||
}
|
||
xiaodianQRCode.value = result
|
||
return true
|
||
} catch (e: any) {
|
||
showToast('获取二维码失败', e?.message || '', 'error')
|
||
return false
|
||
}
|
||
}
|
||
|
||
const bindXiaodianRole = async () => {
|
||
const accountName = xiaodianQRCode.value?.accountName || ''
|
||
if (!accountName) return false
|
||
try {
|
||
const result = await BindXiaodianRole(accountName)
|
||
xiaodianRolesMap.value = { ...xiaodianRolesMap.value, [accountName]: result }
|
||
if (!result?.success || !result.roleName) {
|
||
showToast('绑定失败', result?.error || '尚未检测到绑定角色,请先扫码', 'warn')
|
||
return false
|
||
}
|
||
showToast('绑定成功', `${accountName}:${result.roleName}`, 'success')
|
||
return true
|
||
} catch (e: any) {
|
||
showToast('绑定失败', e?.message || '', 'error')
|
||
return false
|
||
}
|
||
}
|
||
|
||
const closeXiaodianQRCode = () => {
|
||
xiaodianQRCode.value = null
|
||
}
|
||
|
||
const getScore = (name: string): string => {
|
||
return scoreMap.value[name] || localStorage.getItem('accScore_' + name) || '-'
|
||
}
|
||
|
||
const getXiaodianAssets = (name: string): XiaodianAssets | undefined => xiaodianAssetsMap.value[name]
|
||
|
||
const resetStatus = () => {
|
||
statusMap.value = {}
|
||
localStorage.removeItem('statusMap')
|
||
}
|
||
|
||
const setStatuses = (statuses: Record<string, AccountStatus | boolean>) => {
|
||
statusMap.value = statuses
|
||
if (Object.keys(statusMap.value).length > 0) {
|
||
localStorage.setItem('statusMap', JSON.stringify(statusMap.value))
|
||
} else {
|
||
localStorage.removeItem('statusMap')
|
||
}
|
||
}
|
||
|
||
const mergeStatuses = (statuses: Record<string, AccountStatus | boolean>) => {
|
||
setStatuses({
|
||
...statusMap.value,
|
||
...statuses
|
||
})
|
||
}
|
||
|
||
const setStatus = (name: string, status: AccountStatus | boolean) => {
|
||
mergeStatuses({ [name]: status })
|
||
}
|
||
|
||
const selectedAccountNames = (): string[] => {
|
||
return [...selectedIndices.value]
|
||
.map(i => accounts.value[i]?.name)
|
||
.filter(Boolean) as string[]
|
||
}
|
||
|
||
const markSelectedStatus = (status: AccountStatus | boolean) => {
|
||
const next: Record<string, AccountStatus | boolean> = {}
|
||
selectedAccountNames().forEach(name => {
|
||
next[name] = typeof status === 'object' ? { ...status } : status
|
||
})
|
||
mergeStatuses(next)
|
||
}
|
||
|
||
const markSelectedPending = (message = '抢购中,等待后端结果') => {
|
||
markSelectedStatus({ pending: true, message })
|
||
}
|
||
|
||
const setAccountProduct = (accountName: string, productId: string) => {
|
||
if (productId) {
|
||
accountProductMap.value[accountName] = productId
|
||
} else {
|
||
delete accountProductMap.value[accountName]
|
||
}
|
||
localStorage.setItem('accountProducts', JSON.stringify(accountProductMap.value))
|
||
}
|
||
|
||
const getAccountProduct = (accountName: string): string => {
|
||
return accountProductMap.value[accountName] || ''
|
||
}
|
||
|
||
const buildAccountProducts = (productId: string, respectOverrides = true): Record<string, string> | null => {
|
||
if (selectedIndices.value.size === 0) {
|
||
showToast('提示', '请勾选要抢购的账号', 'warn')
|
||
return null
|
||
}
|
||
|
||
const ap: Record<string, string> = {}
|
||
selectedIndices.value.forEach(i => {
|
||
const account = accounts.value[i]
|
||
if (account) {
|
||
const selectedProduct = respectOverrides ? getAccountProduct(account.name) : ''
|
||
const assignedProduct = selectedProduct || productId
|
||
if (assignedProduct) {
|
||
ap[account.name] = assignedProduct
|
||
}
|
||
}
|
||
})
|
||
|
||
if (Object.keys(ap).length === 0) {
|
||
showToast('提示', '请先选择商品,或为每个账号分配商品', 'warn')
|
||
return null
|
||
}
|
||
if (Object.keys(ap).length !== selectedIndices.value.size) {
|
||
showToast('提示', '还有已选账号没有商品', 'warn')
|
||
return null
|
||
}
|
||
return ap
|
||
}
|
||
|
||
return {
|
||
accounts,
|
||
selectedIndices,
|
||
statusMap,
|
||
scoreMap,
|
||
xiaodianAssetsMap,
|
||
xiaodianRolesMap,
|
||
xiaodianQRCode,
|
||
accountProductMap,
|
||
loadAccounts,
|
||
reloadAccounts,
|
||
deleteAccount,
|
||
toggleAccount,
|
||
toggleAll,
|
||
clearSelection,
|
||
restoreSelection,
|
||
queryAllScore,
|
||
queryXiaodianAssets,
|
||
queryAllXiaodianAssets,
|
||
queryAllXiaodianRoles,
|
||
getXiaodianRole,
|
||
getXiaodianBindQRCode,
|
||
bindXiaodianRole,
|
||
closeXiaodianQRCode,
|
||
getScore,
|
||
getXiaodianAssets,
|
||
resetStatus,
|
||
setStatuses,
|
||
mergeStatuses,
|
||
setStatus,
|
||
markSelectedStatus,
|
||
markSelectedPending,
|
||
setAccountProduct,
|
||
getAccountProduct,
|
||
selectedAccountNames,
|
||
buildAccountProducts
|
||
}
|
||
}
|
||
|
||
export function useVerifyExchange() {
|
||
const { accounts } = useAccounts()
|
||
const verifyResults = ref<Record<string, any>>({})
|
||
const verifying = ref(false)
|
||
|
||
const verifyAccount = async (accountName: string) => {
|
||
try {
|
||
const result = await VerifyExchange(accountName)
|
||
verifyResults.value[accountName] = result
|
||
return result
|
||
} catch (e: any) {
|
||
verifyResults.value[accountName] = { success: false, error: e.message }
|
||
return { success: false, error: e.message }
|
||
}
|
||
}
|
||
|
||
const verifyAll = async () => {
|
||
verifying.value = true
|
||
try {
|
||
// 使用后端批量查询接口,自带间隔
|
||
const results = await VerifyAllExchange()
|
||
verifyResults.value = results?.results || {}
|
||
} catch (e: any) {
|
||
console.error('批量复验失败:', e)
|
||
} finally {
|
||
verifying.value = false
|
||
}
|
||
}
|
||
|
||
const clearResults = () => {
|
||
verifyResults.value = {}
|
||
}
|
||
|
||
return {
|
||
verifyResults,
|
||
verifying,
|
||
verifyAccount,
|
||
verifyAll,
|
||
clearResults
|
||
}
|
||
}
|