feat: 完善用户状态管理和类型定义
- 优化useUserStore的登录注册逻辑 - 完善用户信息类型定义 - 新增评论、点赞、收藏等功能相关类型 - 改进状态管理的数据流
This commit is contained in:
parent
668864a736
commit
0a63a30a23
@ -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>) => {
|
||||
|
||||
@ -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 // 父评论ID(0或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
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user