diff --git a/frontend/src/components/account/AccountTable.vue b/frontend/src/components/account/AccountTable.vue
index a7b8e9b..fb6c3dd 100644
--- a/frontend/src/components/account/AccountTable.vue
+++ b/frontend/src/components/account/AccountTable.vue
@@ -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' }
diff --git a/frontend/src/components/console/StatusGrid.vue b/frontend/src/components/console/StatusGrid.vue
index 2034b68..be92268 100644
--- a/frontend/src/components/console/StatusGrid.vue
+++ b/frontend/src/components/console/StatusGrid.vue
@@ -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++
}
}
diff --git a/frontend/src/components/layout/RightConsole.vue b/frontend/src/components/layout/RightConsole.vue
index 3568591..e72123b 100644
--- a/frontend/src/components/layout/RightConsole.vue
+++ b/frontend/src/components/layout/RightConsole.vue
@@ -45,6 +45,14 @@ const openSettings = () => {
CK 管理
+
+ {{ runState }}
+
{{ formatRemain(countdownSeconds) }}
@@ -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;
diff --git a/frontend/src/components/log/LogPanel.vue b/frontend/src/components/log/LogPanel.vue
index 4344fc5..8035bcb 100644
--- a/frontend/src/components/log/LogPanel.vue
+++ b/frontend/src/components/log/LogPanel.vue
@@ -12,6 +12,8 @@ const { showToast } = useToast()
const activeTab = ref<'log' | 'result' | 'stats' | 'verify'>('log')
const logBodyRef = ref(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++
}
}
diff --git a/frontend/src/composables/useAccounts.ts b/frontend/src/composables/useAccounts.ts
index e26c19b..acd12a8 100644
--- a/frontend/src/composables/useAccounts.ts
+++ b/frontend/src/composables/useAccounts.ts
@@ -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) => {
+ 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) => {
+ 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 = {}
+ selectedAccountNames().forEach(name => {
+ next[name] = typeof status === 'object' ? { ...status } : status
+ })
+ mergeStatuses(next)
+ }
+
+ const markSelectedPending = (message = '抢购中,等待后端结果') => {
+ markSelectedStatus({ pending: true, message })
+ }
+
const buildAccountProducts = (productId: string): Record | 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
}
diff --git a/frontend/src/composables/useLogs.ts b/frontend/src/composables/useLogs.ts
index 6b89124..8183fff 100644
--- a/frontend/src/composables/useLogs.ts
+++ b/frontend/src/composables/useLogs.ts
@@ -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>({})
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): Record => {
+ const next: Record = {}
+
+ 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
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
}
}
diff --git a/frontend/src/composables/useScheduler.ts b/frontend/src/composables/useScheduler.ts
index dc54e26..71cefe9 100644
--- a/frontend/src/composables/useScheduler.ts
+++ b/frontend/src/composables/useScheduler.ts
@@ -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}
剩余时间: ${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()
}