merge: 修复弹窗队列的 id 空间冲突
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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-04 07:23:42 -06:00
3 changed files with 22 additions and 7 deletions

View File

@@ -22,7 +22,7 @@ function playNext() {
visible.value = true visible.value = true
timer = setTimeout(async () => { timer = setTimeout(async () => {
visible.value = false visible.value = false
await store.markRead(item.id) await store.markRead(item)
gapTimer = setTimeout(playNext, 400) gapTimer = setTimeout(playNext, 400)
}, 3000) }, 3000)
} }
@@ -49,7 +49,9 @@ onUnmounted(() => {
> >
<div class="icon">{{ current.icon }}</div> <div class="icon">{{ current.icon }}</div>
<div class="body"> <div class="body">
<div class="label">成就解锁</div> <div class="label">
{{ current.kind === "badge" ? "获得奖章" : "成就解锁" }}
</div>
<div class="name">{{ current.name }}</div> <div class="name">{{ current.name }}</div>
<div class="desc">{{ current.description }}</div> <div class="desc">{{ current.description }}</div>
</div> </div>

View File

@@ -18,14 +18,20 @@ export const useAchievementStore = defineStore("achievement", () => {
const queue = ref<PendingAchievement[]>([]) const queue = ref<PendingAchievement[]>([])
const current = ref<PendingAchievement | null>(null) const current = ref<PendingAchievement | null>(null)
// 成就和题单奖章的 id 来自两张不同的表,数值会重叠,
// 只按 id 去重会让奖章 5 把成就 5 挤掉
function keyOf(item: PendingAchievement) {
return `${item.kind ?? "achievement"}:${item.id}`
}
function enqueue(items: PendingAchievement[]) { function enqueue(items: PendingAchievement[]) {
if (!items?.length) return if (!items?.length) return
// 去重WebSocket 推来的和 pending 拉来的可能是同一批 // 去重WebSocket 推来的和 pending 拉来的可能是同一批
const known = new Set([ const known = new Set([
...queue.value.map((i) => i.id), ...queue.value.map(keyOf),
...(current.value ? [current.value.id] : []), ...(current.value ? [keyOf(current.value)] : []),
]) ])
queue.value.push(...items.filter((i) => !known.has(i.id))) queue.value.push(...items.filter((i) => !known.has(keyOf(i))))
} }
async function fetchPending() { async function fetchPending() {
@@ -43,9 +49,12 @@ export const useAchievementStore = defineStore("achievement", () => {
return current.value return current.value
} }
async function markRead(id: number) { async function markRead(item: PendingAchievement) {
// 奖章不在 UserAchievement 表里,它的 id 传给标记接口会被当成成就 id
// 把一个恰好同号、还没弹过的成就静默标记为已弹——那个奖杯就再也不会出现
if (item.kind === "badge") return
try { try {
await markAchievementsRead([id]) await markAchievementsRead([item.id])
} catch { } catch {
// 标记失败下次会重复弹一次,可接受 // 标记失败下次会重复弹一次,可接受
} }

View File

@@ -759,6 +759,10 @@ export interface PendingAchievement {
description: string description: string
icon: string icon: string
rarity: AchievementRarity rarity: AchievementRarity
// 弹窗队列里混着两种东西:全站成就和题单奖章。它们的 id 来自两张不同的表,
// 数值会重叠,所以去重和标记已读都必须带上 kind 一起判断。
// pending 接口只返回成就,不带这个字段,缺省按 achievement 处理。
kind?: "achievement" | "badge"
} }
export interface AchievementSummary { export interface AchievementSummary {