diff --git a/src/store/useUserStore.ts b/src/store/useUserStore.ts index 5458afe..d3b8ba8 100644 --- a/src/store/useUserStore.ts +++ b/src/store/useUserStore.ts @@ -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 - register: (username: string, email: string, password: string, phone?: string) => Promise - logout: () => void + login: (account: string, password: string, rememberMe?: boolean) => Promise + register: (username: string, password: string, confirmPassword: string, nickname: string, email?: string, phone?: string) => Promise + logout: () => Promise + getUserInfo: () => Promise updateUser: (userData: Partial) => 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()( persist( (set, get) => ({ @@ -31,42 +53,91 @@ export const useUserStore = create()( 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) => { diff --git a/src/types/index.ts b/src/types/index.ts index e95c7bc..04d1b39 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -267,11 +267,21 @@ export interface FilterParams { sortOrder?: 'asc' | 'desc' } +// ===== 后端MyBatis-Plus分页返回类型 ===== +export interface IPage { + records: T[] // 数据列表 + total: number // 总记录数 + size: number // 每页大小 + current: number // 当前页码 + pages: number // 总页数 +} + +// 后端实际返回格式 export interface ApiResponse { - 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 +}