fix(achievement): 弹窗队列区分成就与题单奖章的 id 空间
两者的 id 来自不同的表、数值会重叠,却共用同一个队列: - 去重只按 id,队列里有奖章 5 时成就 5 会被当成重复项丢掉 - 弹完奖章后调 markAchievementsRead(奖章id),后端按成就 id 解释, 把同号且未弹过的成就静默标记为已弹,那个奖杯再也不会出现 载荷里本来就有 kind 字段,前端此前完全忽略。现在去重按 kind:id, 标记已读跳过奖章,弹窗标题也按类型区分。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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 {
|
||||||
// 标记失败下次会重复弹一次,可接受
|
// 标记失败下次会重复弹一次,可接受
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user