diff --git a/src/oj/achievement/api.ts b/src/oj/achievement/api.ts new file mode 100644 index 0000000..8d682f5 --- /dev/null +++ b/src/oj/achievement/api.ts @@ -0,0 +1,27 @@ +import http from "utils/http" +import type { + Achievement, + AchievementSummary, + PendingAchievement, +} from "utils/types" + +export function getAchievements(name?: string) { + return http.get<{ username: string; achievements: Achievement[] }>( + "achievements", + { params: name ? { name } : {} }, + ) +} + +export function getAchievementSummary(name?: string) { + return http.get("achievements/summary", { + params: name ? { name } : {}, + }) +} + +export function getPendingAchievements() { + return http.get("achievements/pending") +} + +export function markAchievementsRead(ids: number[]) { + return http.post("achievements/pending", { ids }) +} diff --git a/src/oj/problem/composables/useSubmissionMonitor.ts b/src/oj/problem/composables/useSubmissionMonitor.ts index e9822f1..d76de47 100644 --- a/src/oj/problem/composables/useSubmissionMonitor.ts +++ b/src/oj/problem/composables/useSubmissionMonitor.ts @@ -45,6 +45,12 @@ export function useSubmissionMonitor() { // ==================== WebSocket 处理 ==================== const handleSubmissionUpdate = (data: SubmissionUpdate) => { + // push_to_user 复用了 submission_update 这个 channel handler, + // 其他类型的消息(如成就通知)会走同一条 WebSocket 帧进来,必须先挡掉 + if (data.type !== "submission_update") { + return + } + console.log("[SubmissionMonitor] 收到WebSocket更新:", data) if (data.submission_id !== submissionId.value) { diff --git a/src/utils/types.ts b/src/utils/types.ts index db11c1f..32d87da 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -723,3 +723,49 @@ export interface DetailsData { } export type Grade = "S" | "A" | "B" | "C" + +// ==================== 成就相关类型 ==================== + +export type AchievementRarity = "bronze" | "silver" | "gold" | "platinum" + +export interface Achievement { + id: number + name: string + description: string + icon: string + rarity: AchievementRarity + hidden: boolean + // 隐藏成就未解锁时,后端已做掩码处理,以下四个字段为 null + metric: string | null + operator: "gte" | "lte" | null + threshold: number | null + unlocked: boolean + unlock_time: string | null + backfilled: boolean + progress: number | null + unlock_rate: number +} + +export interface AchievementRarityStat { + rarity: AchievementRarity + label: string + total: number + unlocked: number +} + +export interface PendingAchievement { + id: number + name: string + description: string + icon: string + rarity: AchievementRarity +} + +export interface AchievementSummary { + username: string + total: number + unlocked: number + percent: number + rarity: AchievementRarityStat[] + recent: PendingAchievement[] +}