Files
dygou_go/frontend/src/composables/useScheduler.ts
T

272 lines
8.8 KiB
TypeScript

import { ref } from 'vue'
import { useToast } from './useToast'
import { useAccounts } from './useAccounts'
import { useProducts } from './useProducts'
import { useLogs } from './useLogs'
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('等待操作')
const pendingXiaodianAssetAccounts = ref<string[]>([])
let unsubscribeSeckillState: (() => void) | null = null
export function useScheduler() {
const { showToast } = useToast()
const { resetStatus, buildAccountProducts, markSelectedPending, markAccountNamesPending, markSelectedStatus, queryXiaodianAssets } = useAccounts()
const { selectedProductId, selectedProductName } = useProducts()
const { currentActivity } = useActivity()
const { addLocalLog, clearLocalResults } = useLogs()
const setRunState = (text: string) => {
runState.value = text || '等待操作'
}
const refreshRunningState = async () => {
try {
const d = await GetSeckillStatus()
seckillRunning.value = !!d?.success
} catch {
seckillRunning.value = false
}
}
const refreshXiaodianAssetsAfterRun = async () => {
const accountNames = pendingXiaodianAssetAccounts.value
pendingXiaodianAssetAccounts.value = []
if (accountNames.length === 0) return
await queryXiaodianAssets(accountNames, true)
}
const applySeckillState = (state: string) => {
seckillRunning.value = state === 'running' || state === 'stopping'
if (state === 'stopping') {
setRunState('正在停止抢购')
}
if (state === 'idle') {
void refreshXiaodianAssetsAfterRun()
}
}
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
resetStatus()
clearLocalResults()
markSelectedPending('抢购中,等待后端结果')
setRunState('立即抢购已提交')
addLocalLog(`立即抢购: ${Object.keys(ap).length} 个账号 -> ${selectedProductName.value || selectedProductId.value}`, 'i')
showToast('抢购中', `${Object.keys(ap).length} 个账号正在抢购`, 'info', 3000)
try {
const isXiaodianRun = currentActivity.value?.id === 'xiaodian'
const d = isXiaodianRun
? await StartXiaodianSeckill({ accountProducts: ap, attempts, delayMs })
: await StartSeckill(ap)
if (d?.success === false) {
throw new Error(d.error || '提交失败')
}
seckillRunning.value = true
pendingXiaodianAssetAccounts.value = isXiaodianRun ? Object.keys(ap) : []
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')
}
}
const startSchedule = async (targetTime: string, attempts = 0, delayMs = 0) => {
const ap = buildAccountProducts(selectedProductId.value || '')
if (!ap) return
if (!targetTime) {
showToast('提示', '请选择目标时间', 'warn')
return
}
// 格式化时间
let timeStr = targetTime
if (timeStr.split(':').length === 2) timeStr += ':00'
const today = new Intl.DateTimeFormat('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
}).format(new Date()).replace(/\//g, '-')
const fullTime = today + ' ' + timeStr
resetStatus()
clearLocalResults()
markSelectedPending('定时配置中,等待后端确认')
setRunState('定时配置中')
addLocalLog(`设置定时: ${fullTime}`, 'i')
try {
const d = await SetSchedule({
accountProducts: ap,
targetTime: fullTime,
attempts,
delayMs
})
if (d?.success === false) {
throw new Error('定时设置失败')
}
scheduleActive.value = true
pendingXiaodianAssetAccounts.value = currentActivity.value?.id === 'xiaodian' ? Object.keys(ap) : []
localStorage.setItem('targetTime', timeStr)
markSelectedPending(`定时等待触发: ${d.triggerTime || fullTime}`)
setRunState(`定时已开启,触发 ${d.triggerTime}`)
addLocalLog(`定时已开启: 触发 ${d.triggerTime},剩余 ${d.remainSecond} 秒`, 's')
showToast(
'定时已开启',
`触发时刻: ${d.triggerTime}<br>剩余时间: ${formatRemain(d.remainSecond)}`,
'success',
5000
)
startCountdown(d.remainSecond)
} catch (e: any) {
setRunState('定时设置失败')
markSelectedStatus({ ok: false, message: `定时设置失败: ${e.message}` })
addLocalLog('设置失败: ' + e.message, 'e')
showToast('定时失败', e.message, 'error')
}
}
const startFixed = async (attempts: number, delayMs: number, accountProducts?: Record<string, string>, reportSessionID = '') => {
const ap = accountProducts || buildAccountProducts(selectedProductId.value || '', false)
if (!ap) return false
resetStatus()
clearLocalResults()
markAccountNamesPending(Object.keys(ap), '固定抢购中,等待后端结果')
setRunState('固定抢购已提交')
try {
const d = await StartFixedSeckill({ accountProducts: ap, attempts, delayMs, reportSessionId: reportSessionID })
if (d?.success === false) throw new Error(d.error || '提交失败')
seckillRunning.value = true
pendingXiaodianAssetAccounts.value = Object.keys(ap)
setRunState(d.msg || '固定抢购已启动')
addLocalLog(`固定抢购: ${Object.keys(ap).length} 个账号,${attempts} 次,间隔 ${delayMs}ms`, 'i')
showToast('固定抢购已启动', `${Object.keys(ap).length} 个账号`, 'info', 3000)
return true
} catch (e: any) {
setRunState('固定抢购提交失败')
markSelectedStatus({ ok: false, message: `提交失败: ${e.message}` })
addLocalLog('固定抢购提交失败: ' + e.message, 'e')
showToast('提交失败', e.message, 'error')
return false
}
}
const cancelSchedule = async () => {
try {
await CancelSchedule()
} catch {}
scheduleActive.value = false
pendingXiaodianAssetAccounts.value = []
localStorage.removeItem('targetTime')
stopCountdown()
setRunState('定时已停止')
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
countdownTimer.value = window.setInterval(() => {
const remain = Math.max(0, Math.ceil((endTime - Date.now()) / 1000))
countdownSeconds.value = remain
if (remain <= 0) {
scheduleActive.value = false
markSelectedPending('抢购中,等待后端结果')
setRunState('定时已触发')
stopCountdown()
}
}, 500)
}
const stopCountdown = () => {
if (countdownTimer.value) {
clearInterval(countdownTimer.value)
countdownTimer.value = null
}
countdownSeconds.value = 0
}
const formatRemain = (sec: number): string => {
const h = Math.floor(sec / 3600)
const m = Math.floor((sec % 3600) / 60)
const s = sec % 60
return `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`
}
const restoreSchedule = () => {
const savedTime = localStorage.getItem('targetTime')
return { savedTime }
}
return {
scheduleActive,
seckillRunning,
countdownSeconds,
runState,
setRunState,
subscribeSeckillState,
unsubscribeSchedulerEvents,
startNow,
stopSeckill,
startFixed,
startSchedule,
cancelSchedule,
formatRemain,
restoreSchedule
}
}