From 7ff776b9d8a3f917a1f57617ad0da4feebae0160 Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Mon, 26 Oct 2026 15:21:29 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E5=81=9A=E5=AF=B9=E4=BA=86):=20=E7=82=B9?= =?UTF-8?q?=E8=AF=84=E8=BD=AE=E7=9B=98=E6=94=B9=E6=88=90=E7=83=9F=E8=8A=B1?= =?UTF-8?q?=E5=BC=80=E5=A7=8B=201.5=20=E7=A7=92=E5=B0=B1=E5=BC=B9=EF=BC=8C?= =?UTF-8?q?=E4=B8=8D=E5=86=8D=E7=AD=89=E7=83=9F=E8=8A=B1=E8=90=BD=E5=AE=8C?= =?UTF-8?q?=E5=B9=B2=E7=AD=89=E4=B8=89=E5=9B=9B=E7=A7=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../problem/components/SubmissionEffects.vue | 27 ++++--------- .../oj/problem/composables/useFireworks.ts | 38 +++---------------- 2 files changed, 12 insertions(+), 53 deletions(-) diff --git a/apps/web/src/oj/problem/components/SubmissionEffects.vue b/apps/web/src/oj/problem/components/SubmissionEffects.vue index 15784af..e4b0a86 100644 --- a/apps/web/src/oj/problem/components/SubmissionEffects.vue +++ b/apps/web/src/oj/problem/components/SubmissionEffects.vue @@ -36,7 +36,7 @@ const { celebrate } = useFireworks() // 只对已经能评价、且还没评过的人弹:后端要求先有 AC 才收评价,这里刚 AC 完正好; // mine 非 null 说明早就评过了,别再打扰。 // -// 点评是强制的,但弹窗要等烟花放完才出来(两三秒,加上查一次「评过没有」),而换题会把 +// 点评是强制的,但弹窗要等烟花放一会儿才出来(1.5 秒,加上查一次「评过没有」),而换题会把 // 定时器取消(见下面换题的 watch)—— 「下一题」在通过的那一刻就出现了,学生手快点 // 下去,点评就跳过了。所以这段时间里欠着一次点评(reviewOwed),离开这道题的导航 // 先拦下来:立刻弹点评,选完再去原来要去的地方。 @@ -130,23 +130,10 @@ function continueAfterReview() { } /** - * 烟花放完再弹点评,两边一个时间(十种烟花长短不一,两到三秒)。 - * 每次通过记一个号,换题、被导航拦下提前弹了,就把号作废,放完也不再弹。 + * 烟花开始后固定 1.5 秒弹点评,不等它放完 —— 十种烟花长短不一,等放完的话慢的那几种 + * 要干等三四秒。没放完的半场盖在弹窗上面接着放(useFireworks 的 Z_INDEX)。 */ -let reviewAfterFireworks = 0 - -function showCommentPanelAfter(fireworks: Promise) { - const run = ++reviewAfterFireworks - fireworks.then(() => { - if (run === reviewAfterFireworks) openReview() - }) -} - -function cancelCommentPanel() { - reviewAfterFireworks++ -} -// 离开题目页时烟花那边当作放完了,不能接着去查、去弹 -onScopeDispose(cancelCommentPanel) +const { start: showCommentPanelDelayed, stop: cancelCommentPanel } = useTimeoutFn(openReview, 1500, { immediate: false }) /** * 只拦「离开这道题」:换 query(左栏切页签会改 `?tab=`)不算。弹窗已经开着的时候 @@ -241,7 +228,7 @@ watch( if (result !== SubmissionStatus.accepted) return - // 3. 烟花放完弹出评价框。比赛里不打扰;题单里 1.5 秒后要跳回题单页,弹了也会被冲掉 + // 3. 烟花放一会儿弹出评价框。比赛里不打扰;题单里 1.5 秒后要跳回题单页,弹了也会被冲掉 // (评价是欠着的这件事马上记下:方块还在亮的时候离开这道题,也要先评) if (ctx.value.reviewAfterAccepted) reviewOwed.value = true @@ -249,8 +236,8 @@ watch( const id = submission.value?.id void afterCasesRevealed(id).then(() => { if (submission.value?.id !== id) return - const fireworks = celebrate() - if (ctx.value.reviewAfterAccepted) showCommentPanelAfter(fireworks) + celebrate() + if (ctx.value.reviewAfterAccepted) showCommentPanelDelayed() }) if (ctx.value.backToProblemSet) { diff --git a/apps/web/src/oj/problem/composables/useFireworks.ts b/apps/web/src/oj/problem/composables/useFireworks.ts index 05797e4..0460066 100644 --- a/apps/web/src/oj/problem/composables/useFireworks.ts +++ b/apps/web/src/oj/problem/composables/useFireworks.ts @@ -74,8 +74,8 @@ const PALETTES = [ const RAINBOW = [COLORS.pink, COLORS.orange, COLORS.gold, COLORS.green, COLORS.blue, COLORS.purple] /** - * 盖在弹层上面放。点评弹窗平时等烟花放完才出来(见 celebrate 的返回值),但学生在放的时候 - * 点「下一题」,点评会被提前叫出来 —— 默认的 zIndex 100 在 Naive 弹层(2000 起)底下, + * 盖在弹层上面放。点评弹窗在烟花开始后固定一段时间就出来(见 SubmissionEffects), + * 那时烟花多半还没放完 —— 默认的 zIndex 100 在 Naive 弹层(2000 起)底下, * 剩下的半场就被遮罩挡住了。画布本身 pointer-events: none,不挡点评的点击。 */ const Z_INDEX = 3000 @@ -113,11 +113,6 @@ export function useFireworks() { // 动画期间创建的定时器,组件卸载时统一清理,避免离开页面后动画继续 const timers = new Set>() let disposed = false - // 这一场最后一次发射的 promise:canvas-confetti 同一块画布上的粒子是一场动画, - // 后发的接进去,promise 在所有粒子都落完的时候才 resolve - let lastShot: Promise | null = null - // 还没发出去的异步准备(表情包位图在解码),算「还在放」 - let preparing = 0 function runLater(fn: () => void, ms: number) { const id = setTimeout(() => { @@ -151,22 +146,7 @@ export function useFireworks() { } function fire(options: confetti.Options) { - lastShot = confetti({ zIndex: Z_INDEX, ...options }) ?? lastShot - } - - /** 定时器都走完、最后一把粒子也落完。离开页面(定时器被清掉)就当放完了 */ - function finished(): Promise { - return new Promise((resolve) => { - const check = () => { - if (disposed) return resolve() - if (timers.size > 0 || preparing > 0) { - setTimeout(check, 50) - return - } - Promise.resolve(lastShot).then(() => resolve()) - } - check() - }) + void confetti({ zIndex: Z_INDEX, ...options }) } /** 没有真火箭:沿上升路线一路撒往下掉的小火星,当作尾焰。x0 → x、从屏幕底到 y */ @@ -429,7 +409,6 @@ export function useFireworks() { // 效果10: 表情包烟花 —— 一左一右炸出两团表情 () => { - preparing++ loadEmojiShapes() .then((emojiShapes) => { if (disposed) return @@ -447,19 +426,12 @@ export function useFireworks() { runLater(() => fire({ ...options, origin: { x: 0.68, y: 0.42 } }), 300) }) .catch(() => {}) - .finally(() => preparing--) }, ] - /** 最多等这么久:粒子落完的回调万一没来,别让点评弹窗一直不出来 */ - const MAX_WAIT_MS = 4000 - - /** - * 触发随机烟花效果,返回的 promise 在这一场放完时 resolve(点评弹窗等它) - */ - function celebrate(): Promise { + /** 触发随机烟花效果 */ + function celebrate() { pick(fireworkTypes)() - return Promise.race([finished(), new Promise((resolve) => setTimeout(resolve, MAX_WAIT_MS))]) } return {