feat(achievement): 添加成就 API 层与类型,修复 WebSocket 消息分流
This commit is contained in:
27
src/oj/achievement/api.ts
Normal file
27
src/oj/achievement/api.ts
Normal file
@@ -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<AchievementSummary>("achievements/summary", {
|
||||||
|
params: name ? { name } : {},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPendingAchievements() {
|
||||||
|
return http.get<PendingAchievement[]>("achievements/pending")
|
||||||
|
}
|
||||||
|
|
||||||
|
export function markAchievementsRead(ids: number[]) {
|
||||||
|
return http.post("achievements/pending", { ids })
|
||||||
|
}
|
||||||
@@ -45,6 +45,12 @@ export function useSubmissionMonitor() {
|
|||||||
|
|
||||||
// ==================== WebSocket 处理 ====================
|
// ==================== WebSocket 处理 ====================
|
||||||
const handleSubmissionUpdate = (data: SubmissionUpdate) => {
|
const handleSubmissionUpdate = (data: SubmissionUpdate) => {
|
||||||
|
// push_to_user 复用了 submission_update 这个 channel handler,
|
||||||
|
// 其他类型的消息(如成就通知)会走同一条 WebSocket 帧进来,必须先挡掉
|
||||||
|
if (data.type !== "submission_update") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
console.log("[SubmissionMonitor] 收到WebSocket更新:", data)
|
console.log("[SubmissionMonitor] 收到WebSocket更新:", data)
|
||||||
|
|
||||||
if (data.submission_id !== submissionId.value) {
|
if (data.submission_id !== submissionId.value) {
|
||||||
|
|||||||
@@ -723,3 +723,49 @@ export interface DetailsData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type Grade = "S" | "A" | "B" | "C"
|
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[]
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user