修复抢购后 UI 状态不刷新
This commit is contained in:
@@ -39,8 +39,14 @@ const getStatusInfo = (name: string, selected: boolean) => {
|
||||
|
||||
if (st && typeof st === 'object') {
|
||||
const obj = st as any
|
||||
if (obj.ok) return { text: '成功', cls: 'status-success' }
|
||||
const msg = String(obj.message || '')
|
||||
if (obj.pending) {
|
||||
return {
|
||||
text: msg.includes('定时') ? '待触发' : '抢购中',
|
||||
cls: 'status-ready'
|
||||
}
|
||||
}
|
||||
if (obj.ok) return { text: '成功', cls: 'status-success' }
|
||||
if (msg.includes('库存已耗完')) return { text: '库存耗尽', cls: 'status-warn' }
|
||||
if (msg.includes('兑换上限') || msg.includes('已抢到')) return { text: '已抢到', cls: 'status-success' }
|
||||
if (msg.includes('未绑定')) return { text: '未绑定', cls: 'status-fail' }
|
||||
|
||||
@@ -8,6 +8,8 @@ defineProps<{
|
||||
|
||||
const { accounts, selectedIndices, statusMap } = useAccounts()
|
||||
|
||||
const messageSucceeded = (message: string) => /成功|兑换上限|已抢到/.test(message)
|
||||
|
||||
const stats = computed(() => {
|
||||
const selectedNames = [...selectedIndices.value]
|
||||
.map(i => accounts.value[i]?.name)
|
||||
@@ -18,9 +20,11 @@ const stats = computed(() => {
|
||||
|
||||
for (const name of selectedNames) {
|
||||
const st = statusMap.value[name]
|
||||
if (st === true || (st && typeof st === 'object' && (st as any).ok)) {
|
||||
const msg = st && typeof st === 'object' ? String((st as any).message || '') : ''
|
||||
|
||||
if (st === true || (st && typeof st === 'object' && ((st as any).ok || messageSucceeded(msg)))) {
|
||||
success++
|
||||
} else if (st === false || (st && typeof st === 'object' && (st as any).message)) {
|
||||
} else if (st === false || (st && typeof st === 'object' && !(st as any).pending && msg)) {
|
||||
fail++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,14 @@ const openSettings = () => {
|
||||
CK 管理
|
||||
</button>
|
||||
</div>
|
||||
<span
|
||||
v-if="currentPage === 'console'"
|
||||
class="run-state"
|
||||
:class="{ active: runState !== '等待操作' }"
|
||||
:title="runState"
|
||||
>
|
||||
{{ runState }}
|
||||
</span>
|
||||
<span v-if="countdownSeconds > 0 && currentPage === 'console'" class="countdown-badge">
|
||||
{{ formatRemain(countdownSeconds) }}
|
||||
</span>
|
||||
@@ -158,6 +166,26 @@ const openSettings = () => {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.run-state {
|
||||
max-width: 220px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: var(--muted);
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 999px;
|
||||
padding: 4px 10px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.run-state.active {
|
||||
color: var(--accent);
|
||||
background: var(--accent-soft);
|
||||
border-color: var(--accent-border);
|
||||
}
|
||||
|
||||
.top-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -12,6 +12,8 @@ const { showToast } = useToast()
|
||||
const activeTab = ref<'log' | 'result' | 'stats' | 'verify'>('log')
|
||||
const logBodyRef = ref<HTMLElement | null>(null)
|
||||
|
||||
const messageSucceeded = (message: string) => /成功|兑换上限|已抢到/.test(message)
|
||||
|
||||
// 自动滚动到底部
|
||||
watch(logs, async () => {
|
||||
await nextTick()
|
||||
@@ -31,9 +33,11 @@ const stats = computed(() => {
|
||||
|
||||
for (const name of selectedNames) {
|
||||
const st = statusMap.value[name]
|
||||
if (st === true || (st && typeof st === 'object' && (st as any).ok)) {
|
||||
const msg = st && typeof st === 'object' ? String((st as any).message || '') : ''
|
||||
|
||||
if (st === true || (st && typeof st === 'object' && ((st as any).ok || messageSucceeded(msg)))) {
|
||||
success++
|
||||
} else if (st === false || (st && typeof st === 'object' && (st as any).message)) {
|
||||
} else if (st === false || (st && typeof st === 'object' && !(st as any).pending && msg)) {
|
||||
fail++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ export interface Account {
|
||||
|
||||
export interface AccountStatus {
|
||||
ok?: boolean
|
||||
pending?: boolean
|
||||
message?: string
|
||||
}
|
||||
|
||||
@@ -99,9 +100,24 @@ export function useAccounts() {
|
||||
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) => {
|
||||
statusMap.value[name] = status
|
||||
localStorage.setItem('statusMap', JSON.stringify(statusMap.value))
|
||||
mergeStatuses({ [name]: status })
|
||||
}
|
||||
|
||||
const selectedAccountNames = (): string[] => {
|
||||
@@ -110,6 +126,18 @@ export function useAccounts() {
|
||||
.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 buildAccountProducts = (productId: string): Record<string, string> | null => {
|
||||
if (selectedIndices.value.size === 0) {
|
||||
showToast('提示', '请勾选要抢购的账号', 'warn')
|
||||
@@ -148,7 +176,11 @@ export function useAccounts() {
|
||||
queryAllScore,
|
||||
getScore,
|
||||
resetStatus,
|
||||
setStatuses,
|
||||
mergeStatuses,
|
||||
setStatus,
|
||||
markSelectedStatus,
|
||||
markSelectedPending,
|
||||
selectedAccountNames,
|
||||
buildAccountProducts
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ref } from 'vue'
|
||||
import { useAccounts, type AccountStatus } from './useAccounts'
|
||||
|
||||
export interface LogEntry {
|
||||
id: number
|
||||
@@ -23,6 +24,32 @@ const results = ref<Record<string, string>>({})
|
||||
const lastLogId = ref(0)
|
||||
const lastResultsJson = ref('')
|
||||
|
||||
const resultAccountName = (key: string): string => {
|
||||
return String(key || '').replace(/_第\d+包$/, '')
|
||||
}
|
||||
|
||||
const resultSucceeded = (message: string): boolean => {
|
||||
return /成功|兑换上限|已抢到/.test(String(message || ''))
|
||||
}
|
||||
|
||||
// 后端结果按“账号_第N包”返回,账号表按账号名展示,这里做一次归并。
|
||||
const normalizeResultStatuses = (data: Record<string, string>): Record<string, AccountStatus> => {
|
||||
const next: Record<string, AccountStatus> = {}
|
||||
|
||||
Object.entries(data).forEach(([key, value]) => {
|
||||
const name = resultAccountName(key)
|
||||
const message = String(value || '')
|
||||
const ok = resultSucceeded(message)
|
||||
const prev = next[name]
|
||||
|
||||
if (!prev || ok || !prev.ok) {
|
||||
next[name] = { ok, message }
|
||||
}
|
||||
})
|
||||
|
||||
return next
|
||||
}
|
||||
|
||||
export function useLogs() {
|
||||
const addLocalLog = (message: string, type: LogMessage['type'] = 'i') => {
|
||||
const now = new Date()
|
||||
@@ -75,11 +102,14 @@ export function useLogs() {
|
||||
const checkResults = async () => {
|
||||
try {
|
||||
const d = await App.GetResults()
|
||||
const data = (d || {}) as Record<string, string>
|
||||
const json = JSON.stringify(d)
|
||||
|
||||
if (json !== lastResultsJson.value && Object.keys(d).length > 0) {
|
||||
if (json !== lastResultsJson.value && Object.keys(data).length > 0) {
|
||||
lastResultsJson.value = json
|
||||
results.value = d
|
||||
results.value = data
|
||||
const { mergeStatuses } = useAccounts()
|
||||
mergeStatuses(normalizeResultStatuses(data))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -90,15 +120,18 @@ export function useLogs() {
|
||||
|
||||
const clearLogs = () => {
|
||||
logs.value = []
|
||||
clearLocalResults()
|
||||
lastLogId.value = 0
|
||||
}
|
||||
|
||||
const clearLocalResults = () => {
|
||||
results.value = {}
|
||||
lastResultsJson.value = ''
|
||||
lastLogId.value = 0
|
||||
}
|
||||
|
||||
const clearResults = async () => {
|
||||
await App.ClearResults()
|
||||
results.value = {}
|
||||
lastResultsJson.value = ''
|
||||
clearLocalResults()
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -108,6 +141,7 @@ export function useLogs() {
|
||||
fetchServerLogs,
|
||||
checkResults,
|
||||
clearLogs,
|
||||
clearLocalResults,
|
||||
clearResults
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ref } from 'vue'
|
||||
import { useToast } from './useToast'
|
||||
import { useAccounts } from './useAccounts'
|
||||
import { useProducts } from './useProducts'
|
||||
import { useLogs } from './useLogs'
|
||||
|
||||
const App = window.go.main.App
|
||||
|
||||
@@ -12,8 +13,9 @@ const runState = ref('等待操作')
|
||||
|
||||
export function useScheduler() {
|
||||
const { showToast } = useToast()
|
||||
const { resetStatus, buildAccountProducts } = useAccounts()
|
||||
const { resetStatus, buildAccountProducts, markSelectedPending, markSelectedStatus } = useAccounts()
|
||||
const { selectedProductId, selectedProductName } = useProducts()
|
||||
const { addLocalLog, clearLocalResults } = useLogs()
|
||||
|
||||
const setRunState = (text: string) => {
|
||||
runState.value = text || '等待操作'
|
||||
@@ -24,14 +26,23 @@ export function useScheduler() {
|
||||
if (!ap) return
|
||||
|
||||
resetStatus()
|
||||
clearLocalResults()
|
||||
markSelectedPending('抢购中,等待后端结果')
|
||||
setRunState('立即抢购已提交')
|
||||
addLocalLog(`立即抢购: ${Object.keys(ap).length} 个账号 -> ${selectedProductName.value || selectedProductId.value}`, 'i')
|
||||
showToast('抢购中', `${Object.keys(ap).length} 个账号正在抢购`, 'info', 3000)
|
||||
|
||||
try {
|
||||
const d = await App.StartSeckill(ap)
|
||||
if (d?.success === false) {
|
||||
throw new Error(d.error || '提交失败')
|
||||
}
|
||||
setRunState(d.msg || '已提交')
|
||||
addLocalLog(d.msg || '抢购已提交', 'i')
|
||||
} catch (e: any) {
|
||||
setRunState('提交失败')
|
||||
markSelectedStatus({ ok: false, message: `提交失败: ${e.message}` })
|
||||
addLocalLog('提交失败: ' + e.message, 'e')
|
||||
showToast('提交失败', e.message, 'error')
|
||||
}
|
||||
}
|
||||
@@ -58,7 +69,10 @@ export function useScheduler() {
|
||||
const fullTime = today + ' ' + timeStr
|
||||
|
||||
resetStatus()
|
||||
clearLocalResults()
|
||||
markSelectedPending('定时配置中,等待后端确认')
|
||||
setRunState('定时配置中')
|
||||
addLocalLog(`设置定时: ${fullTime}, 提前=${advance}ms`, 'i')
|
||||
|
||||
try {
|
||||
const d = await App.SetSchedule({
|
||||
@@ -66,12 +80,17 @@ export function useScheduler() {
|
||||
targetTime: fullTime,
|
||||
advance
|
||||
})
|
||||
if (d?.success === false) {
|
||||
throw new Error(d.error || '定时设置失败')
|
||||
}
|
||||
|
||||
scheduleActive.value = true
|
||||
localStorage.setItem('targetTime', timeStr)
|
||||
localStorage.setItem('advance', String(advance))
|
||||
|
||||
markSelectedPending(`定时等待触发: ${d.triggerTime || fullTime}`)
|
||||
setRunState(`定时已开启,触发 ${d.triggerTime}`)
|
||||
addLocalLog(`定时已开启: 触发 ${d.triggerTime},剩余 ${d.remainSecond} 秒`, 's')
|
||||
showToast(
|
||||
'定时已开启',
|
||||
`触发时刻: ${d.triggerTime}<br>剩余时间: ${formatRemain(d.remainSecond)}`,
|
||||
@@ -81,6 +100,8 @@ export function useScheduler() {
|
||||
startCountdown(d.remainSecond)
|
||||
} catch (e: any) {
|
||||
setRunState('定时设置失败')
|
||||
markSelectedStatus({ ok: false, message: `定时设置失败: ${e.message}` })
|
||||
addLocalLog('设置失败: ' + e.message, 'e')
|
||||
showToast('定时失败', e.message, 'error')
|
||||
}
|
||||
}
|
||||
@@ -108,6 +129,7 @@ export function useScheduler() {
|
||||
|
||||
if (remain <= 0) {
|
||||
scheduleActive.value = false
|
||||
markSelectedPending('抢购中,等待后端结果')
|
||||
setRunState('定时已触发')
|
||||
stopCountdown()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user