feat: 新增等级与奖章系统
Some checks failed
continuous-integration/drone/push Build is failing

- 后端: 新增 user_levels, exp_history, badge_definitions, user_badges, level_configs 表
- 后端: 新增 LevelService 和 BadgeService 服务
- 后端: 新增等级/奖章/签到/排行榜 API 端点
- 后端: 考试/练习/陪练完成时触发经验值和奖章检查
- 前端: 新增 LevelBadge, ExpProgress, BadgeCard, LevelUpDialog 组件
- 前端: 新增排行榜页面
- 前端: 成长路径页面集成真实等级数据
- 数据库: 包含迁移脚本和初始数据
This commit is contained in:
yuliang_guo
2026-01-29 16:19:22 +08:00
parent 5dfe23831d
commit 0933b936f9
19 changed files with 3207 additions and 65 deletions

182
frontend/src/api/level.ts Normal file
View File

@@ -0,0 +1,182 @@
/**
* 等级与奖章 API
*/
import request from '@/api/request'
// 类型定义
export interface LevelInfo {
user_id: number
level: number
exp: number
total_exp: number
title: string
color: string
login_streak: number
max_login_streak: number
last_checkin_at: string | null
next_level_exp: number
exp_to_next_level: number
is_max_level: boolean
}
export interface ExpHistoryItem {
id: number
exp_change: number
exp_type: string
description: string
level_before: number | null
level_after: number | null
created_at: string
}
export interface LeaderboardItem {
rank: number
user_id: number
username: string
full_name: string | null
avatar_url: string | null
level: number
title: string
color: string
total_exp: number
login_streak: number
}
export interface Badge {
id: number
code: string
name: string
description: string
icon: string
category: string
condition_type?: string
condition_value?: number
exp_reward: number
unlocked?: boolean
unlocked_at?: string | null
}
export interface CheckinResult {
success: boolean
message: string
exp_gained: number
base_exp?: number
bonus_exp?: number
login_streak: number
leveled_up?: boolean
new_level?: number | null
already_checked_in?: boolean
new_badges?: Badge[]
}
// API 函数
/**
* 获取当前用户等级信息
*/
export function getMyLevel() {
return request.get<LevelInfo>('/level/me')
}
/**
* 获取指定用户等级信息
*/
export function getUserLevel(userId: number) {
return request.get<LevelInfo>(`/level/user/${userId}`)
}
/**
* 每日签到
*/
export function dailyCheckin() {
return request.post<CheckinResult>('/level/checkin')
}
/**
* 获取经验值历史
*/
export function getExpHistory(params?: {
limit?: number
offset?: number
exp_type?: string
}) {
return request.get<{
items: ExpHistoryItem[]
total: number
limit: number
offset: number
}>('/level/exp-history', { params })
}
/**
* 获取等级排行榜
*/
export function getLeaderboard(params?: {
limit?: number
offset?: number
}) {
return request.get<{
items: LeaderboardItem[]
total: number
limit: number
offset: number
my_rank: number
my_level_info: LevelInfo
}>('/level/leaderboard', { params })
}
/**
* 获取所有奖章定义
*/
export function getAllBadges() {
return request.get<Badge[]>('/level/badges/all')
}
/**
* 获取用户奖章(含解锁状态)
*/
export function getMyBadges() {
return request.get<{
badges: Badge[]
total: number
unlocked_count: number
}>('/level/badges/me')
}
/**
* 获取未通知的新奖章
*/
export function getUnnotifiedBadges() {
return request.get<Badge[]>('/level/badges/unnotified')
}
/**
* 标记奖章为已通知
*/
export function markBadgesNotified(badgeIds?: number[]) {
return request.post('/level/badges/mark-notified', badgeIds)
}
/**
* 手动检查并授予奖章
*/
export function checkAndAwardBadges() {
return request.post<{
new_badges: Badge[]
count: number
}>('/level/check-badges')
}
export default {
getMyLevel,
getUserLevel,
dailyCheckin,
getExpHistory,
getLeaderboard,
getAllBadges,
getMyBadges,
getUnnotifiedBadges,
markBadgesNotified,
checkAndAwardBadges
}