修复抢购后 UI 状态不刷新

This commit is contained in:
yml2213
2026-06-26 20:23:06 +08:00
parent 385a6d882c
commit 818e1ff11b
7 changed files with 143 additions and 13 deletions
@@ -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;
+6 -2
View File
@@ -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++
}
}
+34 -2
View File
@@ -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
}
+39 -5
View File
@@ -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
}
}
+23 -1
View File
@@ -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()
}