This commit is contained in:
@@ -1,211 +1,228 @@
|
||||
import confetti from "canvas-confetti"
|
||||
|
||||
/**
|
||||
* 随机烟花效果 Composable
|
||||
* 提供7种不同风格的烟花庆祝效果
|
||||
*/
|
||||
export function useFireworks() {
|
||||
/**
|
||||
* 触发随机烟花效果
|
||||
*/
|
||||
function celebrate() {
|
||||
const fireworkTypes = [
|
||||
// 效果1: 经典烟花秀
|
||||
() => {
|
||||
const duration = 3000
|
||||
const animationEnd = Date.now() + duration
|
||||
const defaults = { startVelocity: 30, spread: 360, ticks: 60, zIndex: 0 }
|
||||
|
||||
const interval: any = setInterval(() => {
|
||||
const timeLeft = animationEnd - Date.now()
|
||||
if (timeLeft <= 0) return clearInterval(interval)
|
||||
|
||||
const particleCount = 50 * (timeLeft / duration)
|
||||
confetti({
|
||||
...defaults,
|
||||
particleCount,
|
||||
origin: { x: Math.random() * 0.3 + 0.1, y: Math.random() - 0.2 },
|
||||
colors: ["#ff6b6b", "#ffd93d", "#6bcf7f", "#4ecdc4", "#a29bfe"],
|
||||
})
|
||||
confetti({
|
||||
...defaults,
|
||||
particleCount,
|
||||
origin: { x: Math.random() * 0.3 + 0.7, y: Math.random() - 0.2 },
|
||||
colors: ["#ff6b6b", "#ffd93d", "#6bcf7f", "#4ecdc4", "#a29bfe"],
|
||||
})
|
||||
}, 250)
|
||||
},
|
||||
|
||||
// 效果2: 星星雨
|
||||
() => {
|
||||
const count = 10
|
||||
const defaults = {
|
||||
origin: { y: 0.7 },
|
||||
shapes: ["star"],
|
||||
colors: ["#FFD700", "#FFA500", "#FFFF00", "#FF69B4", "#00CED1"],
|
||||
}
|
||||
|
||||
function fire(particleRatio: number, opts: any) {
|
||||
confetti({ ...defaults, ...opts, particleCount: Math.floor(200 * particleRatio) })
|
||||
}
|
||||
|
||||
fire(0.25, { spread: 26, startVelocity: 55 })
|
||||
fire(0.2, { spread: 60 })
|
||||
fire(0.35, { spread: 100, decay: 0.91, scalar: 0.8 })
|
||||
fire(0.1, { spread: 120, startVelocity: 25, decay: 0.92, scalar: 1.2 })
|
||||
fire(0.1, { spread: 120, startVelocity: 45 })
|
||||
},
|
||||
|
||||
// 效果3: 爆炸波浪
|
||||
() => {
|
||||
function randomInRange(min: number, max: number) {
|
||||
return Math.random() * (max - min) + min
|
||||
}
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
setTimeout(() => {
|
||||
confetti({
|
||||
angle: randomInRange(55, 125),
|
||||
spread: randomInRange(50, 70),
|
||||
particleCount: randomInRange(50, 100),
|
||||
origin: { y: 0.6 },
|
||||
colors: ["#26ccff", "#a25afd", "#ff5e7e", "#88ff5a", "#fcff42"],
|
||||
})
|
||||
}, i * 200)
|
||||
}
|
||||
},
|
||||
|
||||
// 效果4: 彩虹喷泉
|
||||
() => {
|
||||
const end = Date.now() + 2000
|
||||
|
||||
const colors = ["#bb0000", "#ffffff"]
|
||||
|
||||
const frame = () => {
|
||||
confetti({
|
||||
particleCount: 2,
|
||||
angle: 60,
|
||||
spread: 55,
|
||||
origin: { x: 0 },
|
||||
colors: colors,
|
||||
})
|
||||
confetti({
|
||||
particleCount: 2,
|
||||
angle: 120,
|
||||
spread: 55,
|
||||
origin: { x: 1 },
|
||||
colors: colors,
|
||||
})
|
||||
|
||||
if (Date.now() < end) {
|
||||
requestAnimationFrame(frame)
|
||||
}
|
||||
}
|
||||
|
||||
frame()
|
||||
},
|
||||
|
||||
// 效果5: 烟花雨
|
||||
() => {
|
||||
const duration = 2500
|
||||
const animationEnd = Date.now() + duration
|
||||
|
||||
const interval: any = setInterval(() => {
|
||||
const timeLeft = animationEnd - Date.now()
|
||||
if (timeLeft <= 0) return clearInterval(interval)
|
||||
|
||||
const particleCount = 50
|
||||
confetti({
|
||||
particleCount,
|
||||
startVelocity: 30,
|
||||
spread: 360,
|
||||
ticks: 60,
|
||||
origin: {
|
||||
x: Math.random(),
|
||||
y: Math.random() - 0.2,
|
||||
},
|
||||
colors: ["#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff", "#00ffff"],
|
||||
})
|
||||
}, 200)
|
||||
},
|
||||
|
||||
// 效果6: 炮竹齐鸣
|
||||
() => {
|
||||
const count = 200
|
||||
const defaults = {
|
||||
origin: { y: 0.7 },
|
||||
}
|
||||
|
||||
function fire(particleRatio: number, opts: any) {
|
||||
confetti({
|
||||
...defaults,
|
||||
...opts,
|
||||
particleCount: Math.floor(count * particleRatio),
|
||||
})
|
||||
}
|
||||
|
||||
fire(0.25, {
|
||||
spread: 26,
|
||||
startVelocity: 55,
|
||||
})
|
||||
|
||||
fire(0.2, {
|
||||
spread: 60,
|
||||
})
|
||||
|
||||
fire(0.35, {
|
||||
spread: 100,
|
||||
decay: 0.91,
|
||||
scalar: 0.8,
|
||||
})
|
||||
|
||||
fire(0.1, {
|
||||
spread: 120,
|
||||
startVelocity: 25,
|
||||
decay: 0.92,
|
||||
scalar: 1.2,
|
||||
})
|
||||
|
||||
fire(0.1, {
|
||||
spread: 120,
|
||||
startVelocity: 45,
|
||||
})
|
||||
},
|
||||
|
||||
// 效果7: 螺旋上升
|
||||
() => {
|
||||
const defaults = {
|
||||
spread: 360,
|
||||
ticks: 100,
|
||||
gravity: 0,
|
||||
decay: 0.94,
|
||||
startVelocity: 30,
|
||||
}
|
||||
|
||||
function shoot() {
|
||||
confetti({
|
||||
...defaults,
|
||||
particleCount: 50,
|
||||
scalar: 1.2,
|
||||
shapes: ["circle", "square"],
|
||||
colors: ["#a864fd", "#29cdff", "#78ff44", "#ff718d", "#fdff6a"],
|
||||
})
|
||||
}
|
||||
|
||||
setTimeout(shoot, 0)
|
||||
setTimeout(shoot, 100)
|
||||
setTimeout(shoot, 200)
|
||||
setTimeout(shoot, 300)
|
||||
setTimeout(shoot, 400)
|
||||
},
|
||||
]
|
||||
|
||||
// 随机选择一种效果
|
||||
const randomEffect = fireworkTypes[Math.floor(Math.random() * fireworkTypes.length)]
|
||||
randomEffect()
|
||||
}
|
||||
|
||||
return {
|
||||
celebrate,
|
||||
}
|
||||
}
|
||||
import confetti from "canvas-confetti"
|
||||
|
||||
/**
|
||||
* 随机烟花效果 Composable
|
||||
* 提供7种不同风格的烟花庆祝效果
|
||||
*/
|
||||
export function useFireworks() {
|
||||
/**
|
||||
* 触发随机烟花效果
|
||||
*/
|
||||
function celebrate() {
|
||||
const fireworkTypes = [
|
||||
// 效果1: 经典烟花秀
|
||||
() => {
|
||||
const duration = 3000
|
||||
const animationEnd = Date.now() + duration
|
||||
const defaults = {
|
||||
startVelocity: 30,
|
||||
spread: 360,
|
||||
ticks: 60,
|
||||
zIndex: 0,
|
||||
}
|
||||
|
||||
const interval: any = setInterval(() => {
|
||||
const timeLeft = animationEnd - Date.now()
|
||||
if (timeLeft <= 0) return clearInterval(interval)
|
||||
|
||||
const particleCount = 50 * (timeLeft / duration)
|
||||
confetti({
|
||||
...defaults,
|
||||
particleCount,
|
||||
origin: { x: Math.random() * 0.3 + 0.1, y: Math.random() - 0.2 },
|
||||
colors: ["#ff6b6b", "#ffd93d", "#6bcf7f", "#4ecdc4", "#a29bfe"],
|
||||
})
|
||||
confetti({
|
||||
...defaults,
|
||||
particleCount,
|
||||
origin: { x: Math.random() * 0.3 + 0.7, y: Math.random() - 0.2 },
|
||||
colors: ["#ff6b6b", "#ffd93d", "#6bcf7f", "#4ecdc4", "#a29bfe"],
|
||||
})
|
||||
}, 250)
|
||||
},
|
||||
|
||||
// 效果2: 星星雨
|
||||
() => {
|
||||
const count = 10
|
||||
const defaults = {
|
||||
origin: { y: 0.7 },
|
||||
shapes: ["star"],
|
||||
colors: ["#FFD700", "#FFA500", "#FFFF00", "#FF69B4", "#00CED1"],
|
||||
}
|
||||
|
||||
function fire(particleRatio: number, opts: any) {
|
||||
confetti({
|
||||
...defaults,
|
||||
...opts,
|
||||
particleCount: Math.floor(200 * particleRatio),
|
||||
})
|
||||
}
|
||||
|
||||
fire(0.25, { spread: 26, startVelocity: 55 })
|
||||
fire(0.2, { spread: 60 })
|
||||
fire(0.35, { spread: 100, decay: 0.91, scalar: 0.8 })
|
||||
fire(0.1, { spread: 120, startVelocity: 25, decay: 0.92, scalar: 1.2 })
|
||||
fire(0.1, { spread: 120, startVelocity: 45 })
|
||||
},
|
||||
|
||||
// 效果3: 爆炸波浪
|
||||
() => {
|
||||
function randomInRange(min: number, max: number) {
|
||||
return Math.random() * (max - min) + min
|
||||
}
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
setTimeout(() => {
|
||||
confetti({
|
||||
angle: randomInRange(55, 125),
|
||||
spread: randomInRange(50, 70),
|
||||
particleCount: randomInRange(50, 100),
|
||||
origin: { y: 0.6 },
|
||||
colors: ["#26ccff", "#a25afd", "#ff5e7e", "#88ff5a", "#fcff42"],
|
||||
})
|
||||
}, i * 200)
|
||||
}
|
||||
},
|
||||
|
||||
// 效果4: 彩虹喷泉
|
||||
() => {
|
||||
const end = Date.now() + 2000
|
||||
|
||||
const colors = ["#bb0000", "#ffffff"]
|
||||
|
||||
const frame = () => {
|
||||
confetti({
|
||||
particleCount: 2,
|
||||
angle: 60,
|
||||
spread: 55,
|
||||
origin: { x: 0 },
|
||||
colors: colors,
|
||||
})
|
||||
confetti({
|
||||
particleCount: 2,
|
||||
angle: 120,
|
||||
spread: 55,
|
||||
origin: { x: 1 },
|
||||
colors: colors,
|
||||
})
|
||||
|
||||
if (Date.now() < end) {
|
||||
requestAnimationFrame(frame)
|
||||
}
|
||||
}
|
||||
|
||||
frame()
|
||||
},
|
||||
|
||||
// 效果5: 烟花雨
|
||||
() => {
|
||||
const duration = 2500
|
||||
const animationEnd = Date.now() + duration
|
||||
|
||||
const interval: any = setInterval(() => {
|
||||
const timeLeft = animationEnd - Date.now()
|
||||
if (timeLeft <= 0) return clearInterval(interval)
|
||||
|
||||
const particleCount = 50
|
||||
confetti({
|
||||
particleCount,
|
||||
startVelocity: 30,
|
||||
spread: 360,
|
||||
ticks: 60,
|
||||
origin: {
|
||||
x: Math.random(),
|
||||
y: Math.random() - 0.2,
|
||||
},
|
||||
colors: [
|
||||
"#ff0000",
|
||||
"#00ff00",
|
||||
"#0000ff",
|
||||
"#ffff00",
|
||||
"#ff00ff",
|
||||
"#00ffff",
|
||||
],
|
||||
})
|
||||
}, 200)
|
||||
},
|
||||
|
||||
// 效果6: 炮竹齐鸣
|
||||
() => {
|
||||
const count = 200
|
||||
const defaults = {
|
||||
origin: { y: 0.7 },
|
||||
}
|
||||
|
||||
function fire(particleRatio: number, opts: any) {
|
||||
confetti({
|
||||
...defaults,
|
||||
...opts,
|
||||
particleCount: Math.floor(count * particleRatio),
|
||||
})
|
||||
}
|
||||
|
||||
fire(0.25, {
|
||||
spread: 26,
|
||||
startVelocity: 55,
|
||||
})
|
||||
|
||||
fire(0.2, {
|
||||
spread: 60,
|
||||
})
|
||||
|
||||
fire(0.35, {
|
||||
spread: 100,
|
||||
decay: 0.91,
|
||||
scalar: 0.8,
|
||||
})
|
||||
|
||||
fire(0.1, {
|
||||
spread: 120,
|
||||
startVelocity: 25,
|
||||
decay: 0.92,
|
||||
scalar: 1.2,
|
||||
})
|
||||
|
||||
fire(0.1, {
|
||||
spread: 120,
|
||||
startVelocity: 45,
|
||||
})
|
||||
},
|
||||
|
||||
// 效果7: 螺旋上升
|
||||
() => {
|
||||
const defaults = {
|
||||
spread: 360,
|
||||
ticks: 100,
|
||||
gravity: 0,
|
||||
decay: 0.94,
|
||||
startVelocity: 30,
|
||||
}
|
||||
|
||||
function shoot() {
|
||||
confetti({
|
||||
...defaults,
|
||||
particleCount: 50,
|
||||
scalar: 1.2,
|
||||
shapes: ["circle", "square"],
|
||||
colors: ["#a864fd", "#29cdff", "#78ff44", "#ff718d", "#fdff6a"],
|
||||
})
|
||||
}
|
||||
|
||||
setTimeout(shoot, 0)
|
||||
setTimeout(shoot, 100)
|
||||
setTimeout(shoot, 200)
|
||||
setTimeout(shoot, 300)
|
||||
setTimeout(shoot, 400)
|
||||
},
|
||||
]
|
||||
|
||||
// 随机选择一种效果
|
||||
const randomEffect =
|
||||
fireworkTypes[Math.floor(Math.random() * fireworkTypes.length)]
|
||||
randomEffect()
|
||||
}
|
||||
|
||||
return {
|
||||
celebrate,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,172 +1,172 @@
|
||||
import { ref } from "vue"
|
||||
import { getSubmission } from "oj/api"
|
||||
import { SubmissionStatus } from "utils/constants"
|
||||
import type { Submission } from "utils/types"
|
||||
import {
|
||||
useSubmissionWebSocket,
|
||||
type SubmissionUpdate,
|
||||
} from "shared/composables/websocket"
|
||||
|
||||
/**
|
||||
* 判题监控 Composable
|
||||
* 负责通过 WebSocket + 轮询双保险机制监控判题结果
|
||||
*/
|
||||
export function useSubmissionMonitor() {
|
||||
// ==================== 状态 ====================
|
||||
const submissionId = ref("")
|
||||
const submission = ref<Submission>()
|
||||
|
||||
// ==================== 轮询机制 ====================
|
||||
const { pause: pausePolling, resume: resumePolling } = useIntervalFn(
|
||||
async () => {
|
||||
if (!submissionId.value) return
|
||||
|
||||
try {
|
||||
const res = await getSubmission(submissionId.value)
|
||||
submission.value = res.data
|
||||
|
||||
const result = res.data.result
|
||||
// 判题完成,停止轮询
|
||||
if (
|
||||
result !== SubmissionStatus.judging &&
|
||||
result !== SubmissionStatus.pending
|
||||
) {
|
||||
pausePolling()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[SubmissionMonitor] 轮询失败:", error)
|
||||
pausePolling()
|
||||
}
|
||||
},
|
||||
2000,
|
||||
{ immediate: false }
|
||||
)
|
||||
|
||||
// ==================== WebSocket 处理 ====================
|
||||
const handleSubmissionUpdate = (data: SubmissionUpdate) => {
|
||||
console.log("[SubmissionMonitor] 收到WebSocket更新:", data)
|
||||
|
||||
if (data.submission_id !== submissionId.value) {
|
||||
console.log("[SubmissionMonitor] 提交ID不匹配,忽略")
|
||||
return
|
||||
}
|
||||
|
||||
if (!submission.value) {
|
||||
submission.value = {} as Submission
|
||||
}
|
||||
|
||||
submission.value.result = data.result as Submission["result"]
|
||||
|
||||
// 判题完成或出错,获取完整详情
|
||||
if (data.status === "finished" || data.status === "error") {
|
||||
console.log(
|
||||
`[SubmissionMonitor] 判题${data.status === "finished" ? "完成" : "出错"}`
|
||||
)
|
||||
|
||||
// 停止轮询(WebSocket已成功)
|
||||
pausePolling()
|
||||
|
||||
getSubmission(submissionId.value).then((res) => {
|
||||
submission.value = res.data
|
||||
// 15分钟无新提交则断开WebSocket(节省资源)
|
||||
scheduleDisconnect(15 * 60 * 1000)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化 WebSocket
|
||||
const {
|
||||
connect,
|
||||
subscribe,
|
||||
scheduleDisconnect,
|
||||
cancelScheduledDisconnect,
|
||||
status: wsStatus,
|
||||
} = useSubmissionWebSocket(handleSubmissionUpdate)
|
||||
|
||||
// ==================== 轮询保底启动 ====================
|
||||
const { start: startPollingFallback } = useTimeoutFn(
|
||||
() => {
|
||||
if (
|
||||
submission.value &&
|
||||
(submission.value.result === SubmissionStatus.judging ||
|
||||
submission.value.result === SubmissionStatus.pending ||
|
||||
submission.value.result === 9) // 9 = submitting
|
||||
) {
|
||||
console.log("[SubmissionMonitor] WebSocket未及时响应,启动轮询保底")
|
||||
resumePolling()
|
||||
}
|
||||
},
|
||||
5000,
|
||||
{ immediate: false }
|
||||
)
|
||||
|
||||
// ==================== 启动监控 ====================
|
||||
const startMonitoring = (id: string) => {
|
||||
submissionId.value = id
|
||||
submission.value = { result: 9 } as Submission // 9 = submitting
|
||||
|
||||
// 取消之前的断开计划
|
||||
cancelScheduledDisconnect()
|
||||
|
||||
// 如果WebSocket未连接,先连接
|
||||
if (wsStatus.value !== "connected") {
|
||||
console.log("[SubmissionMonitor] 启动WebSocket连接...")
|
||||
connect()
|
||||
}
|
||||
|
||||
// 等待WebSocket连接并订阅
|
||||
const unwatch = watch(
|
||||
wsStatus,
|
||||
(status) => {
|
||||
if (status === "connected") {
|
||||
console.log("[SubmissionMonitor] WebSocket已连接,订阅提交:", id)
|
||||
subscribe(id)
|
||||
unwatch() // 订阅成功后停止监听
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
// 5秒后启动轮询保底(防止WebSocket失败)
|
||||
startPollingFallback()
|
||||
}
|
||||
|
||||
// ==================== 计算属性 ====================
|
||||
const judging = computed(
|
||||
() => submission.value?.result === SubmissionStatus.judging
|
||||
)
|
||||
|
||||
const pending = computed(
|
||||
() => submission.value?.result === SubmissionStatus.pending
|
||||
)
|
||||
|
||||
const submitting = computed(
|
||||
() => submission.value?.result === SubmissionStatus.submitting
|
||||
)
|
||||
|
||||
const isProcessing = computed(() => {
|
||||
return judging.value || pending.value || submitting.value
|
||||
})
|
||||
|
||||
// ==================== 清理 ====================
|
||||
onUnmounted(() => {
|
||||
pausePolling()
|
||||
})
|
||||
|
||||
return {
|
||||
// 状态
|
||||
submissionId,
|
||||
submission,
|
||||
|
||||
// 计算属性
|
||||
judging,
|
||||
pending,
|
||||
submitting,
|
||||
isProcessing,
|
||||
|
||||
// 方法
|
||||
startMonitoring,
|
||||
pausePolling,
|
||||
}
|
||||
}
|
||||
|
||||
import { ref, computed, watch, onUnmounted } from "vue"
|
||||
import { useIntervalFn, useTimeoutFn } from "@vueuse/core"
|
||||
import { getSubmission } from "oj/api"
|
||||
import { SubmissionStatus } from "utils/constants"
|
||||
import type { Submission } from "utils/types"
|
||||
import {
|
||||
useSubmissionWebSocket,
|
||||
type SubmissionUpdate,
|
||||
} from "shared/composables/websocket"
|
||||
|
||||
/**
|
||||
* 判题监控 Composable
|
||||
* 负责通过 WebSocket + 轮询双保险机制监控判题结果
|
||||
*/
|
||||
export function useSubmissionMonitor() {
|
||||
// ==================== 状态 ====================
|
||||
const submissionId = ref("")
|
||||
const submission = ref<Submission>()
|
||||
|
||||
// ==================== 轮询机制 ====================
|
||||
const { pause: pausePolling, resume: resumePolling } = useIntervalFn(
|
||||
async () => {
|
||||
if (!submissionId.value) return
|
||||
|
||||
try {
|
||||
const res = await getSubmission(submissionId.value)
|
||||
submission.value = res.data
|
||||
|
||||
const result = res.data.result
|
||||
// 判题完成,停止轮询
|
||||
if (
|
||||
result !== SubmissionStatus.judging &&
|
||||
result !== SubmissionStatus.pending
|
||||
) {
|
||||
pausePolling()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[SubmissionMonitor] 轮询失败:", error)
|
||||
pausePolling()
|
||||
}
|
||||
},
|
||||
2000,
|
||||
{ immediate: false },
|
||||
)
|
||||
|
||||
// ==================== WebSocket 处理 ====================
|
||||
const handleSubmissionUpdate = (data: SubmissionUpdate) => {
|
||||
console.log("[SubmissionMonitor] 收到WebSocket更新:", data)
|
||||
|
||||
if (data.submission_id !== submissionId.value) {
|
||||
console.log("[SubmissionMonitor] 提交ID不匹配,忽略")
|
||||
return
|
||||
}
|
||||
|
||||
if (!submission.value) {
|
||||
submission.value = {} as Submission
|
||||
}
|
||||
|
||||
submission.value.result = data.result as Submission["result"]
|
||||
|
||||
// 判题完成或出错,获取完整详情
|
||||
if (data.status === "finished" || data.status === "error") {
|
||||
console.log(
|
||||
`[SubmissionMonitor] 判题${data.status === "finished" ? "完成" : "出错"}`,
|
||||
)
|
||||
|
||||
// 停止轮询(WebSocket已成功)
|
||||
pausePolling()
|
||||
|
||||
getSubmission(submissionId.value).then((res) => {
|
||||
submission.value = res.data
|
||||
// 15分钟无新提交则断开WebSocket(节省资源)
|
||||
scheduleDisconnect(15 * 60 * 1000)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化 WebSocket
|
||||
const {
|
||||
connect,
|
||||
subscribe,
|
||||
scheduleDisconnect,
|
||||
cancelScheduledDisconnect,
|
||||
status: wsStatus,
|
||||
} = useSubmissionWebSocket(handleSubmissionUpdate)
|
||||
|
||||
// ==================== 轮询保底启动 ====================
|
||||
const { start: startPollingFallback } = useTimeoutFn(
|
||||
() => {
|
||||
if (
|
||||
submission.value &&
|
||||
(submission.value.result === SubmissionStatus.judging ||
|
||||
submission.value.result === SubmissionStatus.pending ||
|
||||
submission.value.result === 9) // 9 = submitting
|
||||
) {
|
||||
console.log("[SubmissionMonitor] WebSocket未及时响应,启动轮询保底")
|
||||
resumePolling()
|
||||
}
|
||||
},
|
||||
5000,
|
||||
{ immediate: false },
|
||||
)
|
||||
|
||||
// ==================== 启动监控 ====================
|
||||
const startMonitoring = (id: string) => {
|
||||
submissionId.value = id
|
||||
submission.value = { result: 9 } as Submission // 9 = submitting
|
||||
|
||||
// 取消之前的断开计划
|
||||
cancelScheduledDisconnect()
|
||||
|
||||
// 如果WebSocket未连接,先连接
|
||||
if (wsStatus.value !== "connected") {
|
||||
console.log("[SubmissionMonitor] 启动WebSocket连接...")
|
||||
connect()
|
||||
}
|
||||
|
||||
// 等待WebSocket连接并订阅
|
||||
const unwatch = watch(
|
||||
wsStatus,
|
||||
(status) => {
|
||||
if (status === "connected") {
|
||||
console.log("[SubmissionMonitor] WebSocket已连接,订阅提交:", id)
|
||||
subscribe(id)
|
||||
unwatch() // 订阅成功后停止监听
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
// 5秒后启动轮询保底(防止WebSocket失败)
|
||||
startPollingFallback()
|
||||
}
|
||||
|
||||
// ==================== 计算属性 ====================
|
||||
const judging = computed(
|
||||
() => submission.value?.result === SubmissionStatus.judging,
|
||||
)
|
||||
|
||||
const pending = computed(
|
||||
() => submission.value?.result === SubmissionStatus.pending,
|
||||
)
|
||||
|
||||
const submitting = computed(
|
||||
() => submission.value?.result === SubmissionStatus.submitting,
|
||||
)
|
||||
|
||||
const isProcessing = computed(() => {
|
||||
return judging.value || pending.value || submitting.value
|
||||
})
|
||||
|
||||
// ==================== 清理 ====================
|
||||
onUnmounted(() => {
|
||||
pausePolling()
|
||||
})
|
||||
|
||||
return {
|
||||
// 状态
|
||||
submissionId,
|
||||
submission,
|
||||
|
||||
// 计算属性
|
||||
judging,
|
||||
pending,
|
||||
submitting,
|
||||
isProcessing,
|
||||
|
||||
// 方法
|
||||
startMonitoring,
|
||||
pausePolling,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user