feat: improve xiaodian seckill flow

This commit is contained in:
yml2213
2026-08-08 23:04:40 +08:00
parent 16972b41cd
commit dde2b43d82
19 changed files with 871 additions and 64 deletions
+34 -7
View File
@@ -25,6 +25,7 @@ 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
@@ -93,6 +94,12 @@ export function useAccounts() {
statusMap.value = JSON.parse(savedStatus)
} catch {}
}
const savedProducts = localStorage.getItem('accountProducts')
if (savedProducts) {
try {
accountProductMap.value = JSON.parse(savedProducts)
} catch {}
}
}
const queryAllScore = async () => {
@@ -170,26 +177,43 @@ export function useAccounts() {
markSelectedStatus({ pending: true, message })
}
const buildAccountProducts = (productId: string): Record<string, string> | null => {
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
}
if (!productId) {
showToast('提示', '请先选择商品', 'warn')
return null
}
const ap: Record<string, string> = {}
selectedIndices.value.forEach(i => {
const account = accounts.value[i]
if (account) {
ap[account.name] = productId
const selectedProduct = respectOverrides ? getAccountProduct(account.name) : ''
const assignedProduct = selectedProduct || productId
if (assignedProduct) {
ap[account.name] = assignedProduct
}
}
})
if (Object.keys(ap).length === 0) {
showToast('提示', '没有有效账号可提交', 'warn')
showToast('提示', '请先选择商品,或为每个账号分配商品', 'warn')
return null
}
if (Object.keys(ap).length !== selectedIndices.value.size) {
showToast('提示', '还有已选账号没有商品', 'warn')
return null
}
return ap
@@ -201,6 +225,7 @@ export function useAccounts() {
statusMap,
scoreMap,
xiaodianAssetsMap,
accountProductMap,
loadAccounts,
reloadAccounts,
toggleAccount,
@@ -217,6 +242,8 @@ export function useAccounts() {
setStatus,
markSelectedStatus,
markSelectedPending,
setAccountProduct,
getAccountProduct,
selectedAccountNames,
buildAccountProducts
}
+2
View File
@@ -8,6 +8,8 @@ export interface Product {
commodityName: string
category: string
score: number
payType?: number
payLabel?: string
count: number
pic: string
status: number
+71 -6
View File
@@ -3,24 +3,63 @@ import { useToast } from './useToast'
import { useAccounts } from './useAccounts'
import { useProducts } from './useProducts'
import { useLogs } from './useLogs'
import { CancelSchedule, SetSchedule, StartFixedSeckill, StartSeckill } from '@wails/go/app/App'
import { useActivity } from './useActivity'
import { CancelSchedule, GetSeckillStatus, SetSchedule, StartFixedSeckill, StartSeckill, StartXiaodianSeckill, StopSeckill } from '@wails/go/app/App'
import { EventsOn } from '@wails/runtime/runtime'
const scheduleActive = ref(false)
const seckillRunning = ref(false)
const countdownSeconds = ref(0)
const countdownTimer = ref<number | null>(null)
const runState = ref('等待操作')
let unsubscribeSeckillState: (() => void) | null = null
export function useScheduler() {
const { showToast } = useToast()
const { resetStatus, buildAccountProducts, markSelectedPending, markSelectedStatus } = useAccounts()
const { selectedProductId, selectedProductName } = useProducts()
const { currentActivity } = useActivity()
const { addLocalLog, clearLocalResults } = useLogs()
const setRunState = (text: string) => {
runState.value = text || '等待操作'
}
const startNow = async () => {
const refreshRunningState = async () => {
try {
const d = await GetSeckillStatus()
seckillRunning.value = !!d?.success
} catch {
seckillRunning.value = false
}
}
const applySeckillState = (state: string) => {
seckillRunning.value = state === 'running' || state === 'stopping'
if (state === 'stopping') {
setRunState('正在停止抢购')
}
}
const subscribeSeckillState = () => {
if (unsubscribeSeckillState) {
return unsubscribeSeckillState
}
unsubscribeSeckillState = EventsOn('seckill:state', (state: string) => {
applySeckillState(state)
})
void refreshRunningState()
return unsubscribeSeckillState
}
const unsubscribeSchedulerEvents = () => {
if (unsubscribeSeckillState) {
unsubscribeSeckillState()
unsubscribeSeckillState = null
}
}
const startNow = async (attempts = 0, delayMs = 0) => {
const ap = buildAccountProducts(selectedProductId.value || '')
if (!ap) return
@@ -32,10 +71,13 @@ export function useScheduler() {
showToast('抢购中', `${Object.keys(ap).length} 个账号正在抢购`, 'info', 3000)
try {
const d = await StartSeckill(ap)
const d = currentActivity.value?.id === 'xiaodian'
? await StartXiaodianSeckill({ accountProducts: ap, attempts, delayMs })
: await StartSeckill(ap)
if (d?.success === false) {
throw new Error(d.error || '提交失败')
}
seckillRunning.value = true
setRunState(d.msg || '已提交')
addLocalLog(d.msg || '抢购已提交', 'i')
} catch (e: any) {
@@ -46,7 +88,7 @@ export function useScheduler() {
}
}
const startSchedule = async (targetTime: string) => {
const startSchedule = async (targetTime: string, attempts = 0, delayMs = 0) => {
const ap = buildAccountProducts(selectedProductId.value || '')
if (!ap) return
@@ -76,7 +118,9 @@ export function useScheduler() {
try {
const d = await SetSchedule({
accountProducts: ap,
targetTime: fullTime
targetTime: fullTime,
attempts,
delayMs
})
if (d?.success === false) {
throw new Error('定时设置失败')
@@ -104,7 +148,7 @@ export function useScheduler() {
}
const startFixed = async (attempts: number, delayMs: number) => {
const ap = buildAccountProducts(selectedProductId.value || '')
const ap = buildAccountProducts(selectedProductId.value || '', false)
if (!ap) return false
resetStatus()
clearLocalResults()
@@ -113,6 +157,7 @@ export function useScheduler() {
try {
const d = await StartFixedSeckill({ accountProducts: ap, attempts, delayMs })
if (d?.success === false) throw new Error(d.error || '提交失败')
seckillRunning.value = true
setRunState(d.msg || '固定抢购已启动')
addLocalLog(`固定抢购: ${Object.keys(ap).length} 个账号,${attempts} 次,间隔 ${delayMs}ms`, 'i')
showToast('固定抢购已启动', `${Object.keys(ap).length} 个账号`, 'info', 3000)
@@ -138,6 +183,22 @@ export function useScheduler() {
showToast('定时已停止', '抢购任务已取消', 'warn')
}
const stopSeckill = async () => {
try {
const d = await StopSeckill()
if (d?.success === false) {
throw new Error(d.error || '停止失败')
}
setRunState(d.msg || '已停止抢购')
showToast('已停止抢购', d.msg || '', 'warn')
} catch (e: any) {
setRunState('停止失败')
showToast('停止失败', e?.message || '', 'error')
} finally {
seckillRunning.value = false
}
}
const startCountdown = (sec: number) => {
stopCountdown()
const endTime = Date.now() + sec * 1000
@@ -177,10 +238,14 @@ export function useScheduler() {
return {
scheduleActive,
seckillRunning,
countdownSeconds,
runState,
setRunState,
subscribeSeckillState,
unsubscribeSchedulerEvents,
startNow,
stopSeckill,
startFixed,
startSchedule,
cancelSchedule,