272 lines
8.8 KiB
TypeScript
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>) => {
|
|
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 })
|
|
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
|
|
}
|
|
}
|