feat: improve xiaodian seckill flow
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ export interface Product {
|
||||
commodityName: string
|
||||
category: string
|
||||
score: number
|
||||
payType?: number
|
||||
payLabel?: string
|
||||
count: number
|
||||
pic: string
|
||||
status: number
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user