feat: 完善用户状态管理和类型定义

- 优化useUserStore的登录注册逻辑
- 完善用户信息类型定义
- 新增评论、点赞、收藏等功能相关类型
- 改进状态管理的数据流
This commit is contained in:
Leo 2025-10-13 21:41:35 +08:00
parent 668864a736
commit 0a63a30a23
2 changed files with 596 additions and 28 deletions

View File

@ -4,8 +4,9 @@
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
import type { User } from '@types/index'
import { login as apiLogin, register as apiRegister, getUserById } from '@services/api'
import type { User, UserInfo } from '@/types'
import { loginApi, registerApi, logoutApi, getUserInfoApi } from '@/services/authApi'
import { setToken, removeToken } from '@/utils/request'
interface UserState {
user: User | null
@ -13,9 +14,10 @@ interface UserState {
isLoading: boolean
// Actions
login: (username: string, password: string) => Promise<boolean>
register: (username: string, email: string, password: string, phone?: string) => Promise<boolean>
logout: () => void
login: (account: string, password: string, rememberMe?: boolean) => Promise<boolean>
register: (username: string, password: string, confirmPassword: string, nickname: string, email?: string, phone?: string) => Promise<boolean>
logout: () => Promise<void>
getUserInfo: () => Promise<void>
updateUser: (userData: Partial<User>) => void
addFavorite: (heritageId: string) => void
removeFavorite: (heritageId: string) => void
@ -24,6 +26,26 @@ interface UserState {
enrollCourse: (courseId: string) => void
}
// 将UserInfo转换为User类型
const convertUserInfoToUser = (userInfo: UserInfo): User => {
return {
id: String(userInfo.id),
username: userInfo.username,
nickname: userInfo.nickname || userInfo.username,
avatar: userInfo.avatar || `https://api.dicebear.com/7.x/avataaars/svg?seed=${userInfo.username}`,
email: userInfo.email,
phone: userInfo.phone,
bio: userInfo.remark,
favorites: [],
followedInheritors: [],
enrolledCourses: [],
registeredEvents: [],
points: 0,
level: 1,
createdAt: userInfo.birthday || new Date().toISOString().split('T')[0],
}
}
export const useUserStore = create<UserState>()(
persist(
(set, get) => ({
@ -31,42 +53,91 @@ export const useUserStore = create<UserState>()(
isAuthenticated: false,
isLoading: false,
login: async (username: string, password: string) => {
login: async (account: string, password: string, rememberMe?: boolean) => {
set({ isLoading: true })
try {
const user = await apiLogin(username, password)
if (user) {
set({ user, isAuthenticated: true, isLoading: false })
return true
const result = await loginApi({ account, password, rememberMe })
// 检查返回结果
if (!result || !result.userInfo) {
set({ isLoading: false })
throw new Error('登录失败,返回数据格式错误')
}
set({ isLoading: false })
return false
// 保存token
setToken(result.token)
// 转换用户信息并保存
const user = convertUserInfoToUser(result.userInfo)
set({ user, isAuthenticated: true, isLoading: false })
return true
} catch (error) {
console.error('Login failed:', error)
console.error('登录失败:', error)
set({ isLoading: false })
return false
// 重新抛出错误,让调用方处理具体的错误信息
throw error
}
},
register: async (username: string, email: string, password: string, phone?: string) => {
register: async (username: string, password: string, confirmPassword: string, nickname: string, email?: string, phone?: string) => {
set({ isLoading: true })
try {
const user = await apiRegister({ username, email, password, phone })
if (user) {
set({ user, isAuthenticated: true, isLoading: false })
return true
// 调用注册接口
await registerApi({ username, password, confirmPassword, nickname, email, phone })
// 注册成功后自动登录使用username作为account
const loginResult = await loginApi({ account: username, password })
// 检查返回结果
if (!loginResult || !loginResult.userInfo) {
throw new Error('注册后自动登录失败')
}
set({ isLoading: false })
return false
// 保存token
setToken(loginResult.token)
// 转换用户信息并保存
const user = convertUserInfoToUser(loginResult.userInfo)
set({ user, isAuthenticated: true, isLoading: false })
return true
} catch (error) {
console.error('Register failed:', error)
console.error('注册失败:', error)
set({ isLoading: false })
return false
throw error
}
},
logout: () => {
set({ user: null, isAuthenticated: false })
logout: async () => {
try {
// 调用后端登出接口
await logoutApi()
// 清除token
removeToken()
// 清除用户状态
set({ user: null, isAuthenticated: false })
} catch (error) {
console.error('登出失败:', error)
// 即使后端接口失败,也清除本地状态
removeToken()
set({ user: null, isAuthenticated: false })
}
},
getUserInfo: async () => {
try {
const userInfo = await getUserInfoApi()
const user = convertUserInfoToUser(userInfo)
set({ user, isAuthenticated: true })
} catch (error) {
console.error('获取用户信息失败:', error)
// 获取失败则清除登录状态
removeToken()
set({ user: null, isAuthenticated: false })
}
},
updateUser: (userData: Partial<User>) => {

View File

@ -267,11 +267,21 @@ export interface FilterParams {
sortOrder?: 'asc' | 'desc'
}
// ===== 后端MyBatis-Plus分页返回类型 =====
export interface IPage<T> {
records: T[] // 数据列表
total: number // 总记录数
size: number // 每页大小
current: number // 当前页码
pages: number // 总页数
}
// 后端实际返回格式
export interface ApiResponse<T = any> {
code: number
message: string
status: number // 后端使用 status 而不是 code
msg: string // 后端使用 msg 而不是 message
data: T
timestamp: number
traceId?: string // 后端返回的追踪ID
}
// ===== 评论类型 =====
@ -315,3 +325,490 @@ export interface SearchHistory {
keyword: string
timestamp: string
}
// ===== 后端接口相关类型 =====
// 登录请求参数(对应后端 HrtLoginVo
export interface LoginRequest {
account: string // 账号(用户名/邮箱/手机号)
password: string // 密码
rememberMe?: boolean // 记住登录默认false
}
// 登录返回结果(对应后端 HrtLoginResultVo
export interface LoginResult {
token: string
tokenName: string
tokenTimeout: number
userInfo: UserInfo
}
// 注册请求参数(对应后端 HrtRegisterVo
export interface RegisterRequest {
username: string // 用户名3-16位数字/字母/下划线)
password: string // 密码6-20位
confirmPassword: string // 确认密码(必填)
nickname: string // 昵称(必填)
email?: string // 邮箱(可选)
phone?: string // 手机号(可选)
}
// 用户信息(对应后端 HrtUserInfoVo
export interface UserInfo {
id: number
username: string
nickname?: string
avatar?: string
email: string
phone?: string
gender?: number // 0-未知1-男2-女
birthday?: string // yyyy-MM-dd
province?: string
city?: string
status?: number // 0-禁用1-正常
loginIp?: string
loginTime?: string // yyyy-MM-dd HH:mm:ss
remark?: string
}
// ===== 非遗项目相关后端类型 =====
// 非遗项目查询参数(对应后端 HrtHeritageQueryVo
export interface HeritageQueryParams {
pageNum?: number // 页码默认1
pageSize?: number // 每页数量默认10
name?: string // 项目名称(模糊查询)
category?: string // 分类traditional-craft、traditional-art等
level?: string // 级别world、national、provincial、municipal、county
province?: string // 省份
city?: string // 城市
status?: string // 状态active-正常传承endangered-濒危
tag?: string // 标签(模糊匹配)
keyword?: string // 关键词(搜索名称、描述、历史等字段)
sortField?: string // 排序字段view_count、like_count、favorite_count、create_time
sortOrder?: string // 排序方式asc、desc
}
// ===== 传承人相关后端类型 =====
// 传承人查询参数(对应后端 HrtInheritorQueryVo
export interface InheritorQueryParams {
pageNum?: number // 页码默认1
pageSize?: number // 每页数量默认10
name?: string // 姓名(模糊查询)
level?: string // 传承人级别national、provincial等
province?: string // 省份
city?: string // 城市
heritageId?: number // 关联非遗项目ID
keyword?: string // 关键词(搜索姓名、简介、传承故事等字段)
sortField?: string // 排序字段view_count、like_count、create_time
sortOrder?: string // 排序方式asc、desc
}
// 传承人列表项(对应后端 HrtInheritorListVo
export interface InheritorListItem {
id: number
name: string
nameEn?: string
gender: number // 1-男2-女
birthYear: number
avatar: string
heritageId: number
heritageName: string
level: string // national、provincial等
province: string
city?: string
introduction: string
viewCount: number
likeCount: number
isFeatured: number // 0-否1-是
createTime: string // yyyy-MM-dd HH:mm:ss
}
// 传承人详情(对应后端 HrtInheritorDetailVo
export interface InheritorDetail {
id: number
name: string
nameEn?: string
gender: number
birthYear: number
avatar: string
heritageId: number
heritageName: string
level: string
province: string
city?: string
introduction: string
story: string
achievements: string
works: string // JSON数组字符串
images: string // JSON数组字符串
videoUrl?: string
viewCount: number
likeCount: number
isFeatured: number
createTime: string
updateTime: string
}
// 非遗项目列表项(对应后端 HrtHeritageListVo
export interface HeritageListItem {
id: number
name: string
nameEn?: string
category: string
level: string
province: string
city?: string
description: string
coverImage: string
tags: string // 逗号分隔的标签
status: string
viewCount: number
likeCount: number
favoriteCount: number
commentCount: number
isFeatured: number // 0-否1-是
createTime: string // yyyy-MM-dd HH:mm:ss
}
// 非遗项目详情(对应后端 HrtHeritageDetailVo
export interface HeritageDetail {
id: number
name: string
nameEn?: string
category: string
level: string
province: string
city?: string
description: string
history: string
skills: string
significance: string
coverImage: string
images: string // JSON数组字符串
videoUrl?: string
tags: string // 逗号分隔的标签
status: string
viewCount: number
likeCount: number
favoriteCount: number
commentCount: number
isFeatured: number
createTime: string
updateTime: string
}
// ===== 收藏功能类型 =====
/**
*
*/
export type FavoriteTargetType = 'heritage' | 'inheritor' | 'news'
/**
*
*/
export interface FavoriteOperateParams {
targetType: FavoriteTargetType
targetId: number
}
/**
*
*/
export interface FavoriteQueryParams {
pageNum?: number
pageSize?: number
targetType?: FavoriteTargetType
}
/**
*
*/
export interface FavoriteItem {
id: number
userId: number
targetType: FavoriteTargetType
targetId: number
targetName: string
targetCover?: string
targetDescription?: string
createTime: string
}
// ===== 点赞功能类型 =====
/**
*
*/
export type LikeTargetType = 'heritage' | 'inheritor' | 'news' | 'comment'
/**
*
*/
export interface LikeOperateParams {
targetType: LikeTargetType
targetId: number
}
// ===== 评论功能类型 =====
/**
*
*/
export type CommentTargetType = 'heritage' | 'inheritor' | 'news' | 'event'
/**
*
*/
export interface CommentAddParams {
targetType: CommentTargetType
targetId: number
content: string
rating?: number // 评分1-5星可选
parentId?: number // 父评论ID0或null表示一级评论
}
/**
*
*/
export interface CommentQueryParams {
pageNum?: number
pageSize?: number
targetType: CommentTargetType
targetId: number
sortType?: 'latest' | 'hottest' // 排序方式latest-最新、hottest-最热
}
/**
*
*/
export interface CommentItem {
id: number
userId: number
userName: string
userAvatar: string
targetType: CommentTargetType
targetId: number
content: string
rating?: number
parentId: number
likeCount: number
isLiked: boolean // 当前用户是否已点赞
createTime: string
replies?: CommentItem[] // 回复列表(仅一级评论包含)
replyCount: number
}
// ===== 新闻资讯功能类型 =====
/**
*
*/
export type NewsCategory = 'news' | 'activity' | 'notice'
/**
*
*/
export interface NewsQueryParams {
pageNum?: number
pageSize?: number
category?: NewsCategory
keyword?: string
}
/**
*
*/
export interface NewsListItem {
id: number
title: string
summary: string
coverImage: string
author: string
source: string
category: NewsCategory
tags: string // 逗号分隔
viewCount: number
likeCount: number
isTop: number // 0-否1-是
publishTime: string
}
/**
*
*/
export interface NewsDetail {
id: number
title: string
summary: string
content: string
coverImage: string
author: string
source: string
category: NewsCategory
tags: string
viewCount: number
likeCount: number
isTop: number
isLiked: boolean
isFavorited: boolean
publishTime: string
}
// ===== 活动管理功能类型 =====
/**
*
*/
export type EventStatus = 'upcoming' | 'ongoing' | 'finished' | 'cancelled'
/**
*
*/
export interface EventQueryParams {
pageNum?: number
pageSize?: number
status?: EventStatus
keyword?: string
}
/**
*
*/
export interface EventListItem {
id: number
title: string
summary: string
coverImage: string
location: string
startTime: string
endTime: string
maxParticipants: number
currentParticipants: number
registrationStart: string
registrationEnd: string
status: EventStatus
viewCount: number
isRegistered: boolean
}
/**
*
*/
export interface EventDetail {
id: number
title: string
summary: string
content: string
coverImage: string
location: string
startTime: string
endTime: string
maxParticipants: number
currentParticipants: number
registrationStart: string
registrationEnd: string
status: EventStatus
viewCount: number
isRegistered: boolean
canRegister: boolean
}
/**
*
*/
export interface EventRegistrationParams {
eventId: number
phone: string
remark?: string
}
// ===== 用户中心类型 =====
/**
*
*/
export type UserGender = 0 | 1 | 2 // 0-未知1-男2-女
/**
*
*/
export type ViewHistoryTargetType = 'heritage' | 'inheritor' | 'news' | 'event'
/**
*
*/
export interface UserProfile {
id: number
username: string
nickname: string
avatar: string
email: string
phone: string
gender: UserGender
birthday: string // yyyy-MM-dd
province: string
city: string
createTime: string // yyyy-MM-dd HH:mm:ss
}
/**
*
*/
export interface UserProfileUpdateParams {
nickname?: string
email?: string
phone?: string
gender?: UserGender
birthday?: string // yyyy-MM-dd
province?: string
city?: string
}
/**
*
*/
export interface UserAvatarUpdateParams {
avatar: string
}
/**
*
*/
export interface UserPasswordUpdateParams {
oldPassword: string
newPassword: string
confirmPassword: string
}
/**
*
*/
export interface UserStats {
viewHistoryCount: number // 浏览历史数量
commentCount: number // 评论数量
likeCount: number // 点赞数量
favoriteCount: number // 收藏数量
eventRegistrationCount: number // 活动报名数量
}
/**
*
*/
export interface ViewHistoryQueryParams {
pageNum?: number
pageSize?: number
targetType?: ViewHistoryTargetType
}
/**
*
*/
export interface ViewHistoryItem {
id: number
targetType: ViewHistoryTargetType
targetId: number
targetTitle: string
targetCover: string
targetDescription: string
viewTime: string // yyyy-MM-dd HH:mm:ss
}