fix
Some checks failed
Deploy / deploy (build, debian, 22, /root/OJDeploy/data/clientnext) (push) Has been cancelled
Deploy / deploy (build:staging, school, 8822, /root/OJ/data/dist) (push) Has been cancelled

This commit is contained in:
2026-07-03 10:27:58 -06:00
parent 9144dbb4fc
commit ebe2ab762f

View File

@@ -1,4 +1,5 @@
import confetti from "canvas-confetti" import confetti from "canvas-confetti"
import { getCurrentScope, onScopeDispose } from "vue"
// 表情图标取自 @iconify 的 streamline-emojis 图标集,与项目内 <Icon icon="streamline-emojis:..."> 用法保持一致 // 表情图标取自 @iconify 的 streamline-emojis 图标集,与项目内 <Icon icon="streamline-emojis:..."> 用法保持一致
const EMOJI_SVGS = [ const EMOJI_SVGS = [
@@ -67,6 +68,31 @@ export function useFireworks() {
// 提前预加载表情包位图避免首次触发效果8时出现解码延迟 // 提前预加载表情包位图避免首次触发效果8时出现解码延迟
loadEmojiShapes() loadEmojiShapes()
// 动画期间创建的定时器,组件卸载时统一清理,避免离开页面后动画继续
const timers = new Set<ReturnType<typeof setTimeout>>()
let disposed = false
function stopTimer(id: ReturnType<typeof setTimeout>) {
clearInterval(id)
timers.delete(id)
}
function runLater(fn: () => void, ms: number) {
const id = setTimeout(() => {
timers.delete(id)
fn()
}, ms)
timers.add(id)
}
if (getCurrentScope()) {
onScopeDispose(() => {
disposed = true
timers.forEach((id) => clearInterval(id))
timers.clear()
})
}
/** /**
* 触发随机烟花效果 * 触发随机烟花效果
*/ */
@@ -80,12 +106,11 @@ export function useFireworks() {
startVelocity: 30, startVelocity: 30,
spread: 360, spread: 360,
ticks: 60, ticks: 60,
zIndex: 0,
} }
const interval: any = setInterval(() => { const interval = setInterval(() => {
const timeLeft = animationEnd - Date.now() const timeLeft = animationEnd - Date.now()
if (timeLeft <= 0) return clearInterval(interval) if (timeLeft <= 0) return stopTimer(interval)
const particleCount = 50 * (timeLeft / duration) const particleCount = 50 * (timeLeft / duration)
confetti({ confetti({
@@ -101,30 +126,30 @@ export function useFireworks() {
colors: ["#ff6b6b", "#ffd93d", "#6bcf7f", "#4ecdc4", "#a29bfe"], colors: ["#ff6b6b", "#ffd93d", "#6bcf7f", "#4ecdc4", "#a29bfe"],
}) })
}, 250) }, 250)
timers.add(interval)
}, },
// 效果2: 星星雨 // 效果2: 星星雨
() => { () => {
const count = 10 const animationEnd = Date.now() + 2000
const defaults = {
origin: { y: 0.7 }, const interval = setInterval(() => {
shapes: ["star"], if (Date.now() >= animationEnd) return stopTimer(interval)
colors: ["#FFD700", "#FFA500", "#FFFF00", "#FF69B4", "#00CED1"],
}
function fire(particleRatio: number, opts: any) {
confetti({ confetti({
...defaults, particleCount: 12,
...opts, spread: 100,
particleCount: Math.floor(200 * particleRatio), startVelocity: 12,
gravity: 0.6,
decay: 0.96,
ticks: 200,
scalar: 1.2,
shapes: ["star"],
origin: { x: Math.random(), y: -0.1 },
colors: ["#FFD700", "#FFA500", "#FFFF00", "#FF69B4", "#00CED1"],
}) })
} }, 150)
timers.add(interval)
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: 爆炸波浪 // 效果3: 爆炸波浪
@@ -134,7 +159,7 @@ export function useFireworks() {
} }
for (let i = 0; i < 5; i++) { for (let i = 0; i < 5; i++) {
setTimeout(() => { runLater(() => {
confetti({ confetti({
angle: randomInRange(55, 125), angle: randomInRange(55, 125),
spread: randomInRange(50, 70), spread: randomInRange(50, 70),
@@ -150,7 +175,14 @@ export function useFireworks() {
() => { () => {
const end = Date.now() + 2000 const end = Date.now() + 2000
const colors = ["#bb0000", "#ffffff"] const colors = [
"#ff595e",
"#ff924c",
"#ffca3a",
"#8ac926",
"#1982c4",
"#6a4c93",
]
const frame = () => { const frame = () => {
confetti({ confetti({
@@ -168,7 +200,7 @@ export function useFireworks() {
colors: colors, colors: colors,
}) })
if (Date.now() < end) { if (Date.now() < end && !disposed) {
requestAnimationFrame(frame) requestAnimationFrame(frame)
} }
} }
@@ -181,9 +213,9 @@ export function useFireworks() {
const duration = 2500 const duration = 2500
const animationEnd = Date.now() + duration const animationEnd = Date.now() + duration
const interval: any = setInterval(() => { const interval = setInterval(() => {
const timeLeft = animationEnd - Date.now() const timeLeft = animationEnd - Date.now()
if (timeLeft <= 0) return clearInterval(interval) if (timeLeft <= 0) return stopTimer(interval)
const particleCount = 50 const particleCount = 50
confetti({ confetti({
@@ -205,6 +237,7 @@ export function useFireworks() {
], ],
}) })
}, 200) }, 200)
timers.add(interval)
}, },
// 效果6: 炮竹齐鸣 // 效果6: 炮竹齐鸣
@@ -270,11 +303,11 @@ export function useFireworks() {
}) })
} }
setTimeout(shoot, 0) runLater(shoot, 0)
setTimeout(shoot, 100) runLater(shoot, 100)
setTimeout(shoot, 200) runLater(shoot, 200)
setTimeout(shoot, 300) runLater(shoot, 300)
setTimeout(shoot, 400) runLater(shoot, 400)
}, },
// 效果8: 表情包烟花 // 效果8: 表情包烟花
@@ -298,7 +331,7 @@ export function useFireworks() {
const colors = ["#ff9a9e", "#fad0c4", "#fbc2eb", "#a18cd1", "#fad961"] const colors = ["#ff9a9e", "#fad0c4", "#fbc2eb", "#a18cd1", "#fad961"]
for (let angle = 0; angle < 360; angle += 30) { for (let angle = 0; angle < 360; angle += 30) {
setTimeout( runLater(
() => { () => {
confetti({ confetti({
particleCount: 15, particleCount: 15,