diff --git a/src/services/authApi.ts b/src/services/authApi.ts new file mode 100644 index 0000000..d43dd3e --- /dev/null +++ b/src/services/authApi.ts @@ -0,0 +1,39 @@ +/** + * 用户认证相关API接口 + * 对接后端 /api/auth 接口 + */ + +import { http } from '@/utils/request' +import type { LoginRequest, LoginResult, RegisterRequest, UserInfo } from '@/types' + +/** + * 用户登录 + * POST /api/auth/login + */ +export const loginApi = async (data: LoginRequest): Promise => { + return http.post('/api/auth/login', data) +} + +/** + * 用户注册 + * POST /api/auth/register + */ +export const registerApi = async (data: RegisterRequest): Promise => { + return http.post('/api/auth/register', data) +} + +/** + * 用户登出 + * POST /api/auth/logout + */ +export const logoutApi = async (): Promise => { + return http.post('/api/auth/logout') +} + +/** + * 获取当前登录用户信息 + * GET /api/auth/userInfo + */ +export const getUserInfoApi = async (): Promise => { + return http.get('/api/auth/userInfo') +} diff --git a/src/services/commentApi.ts b/src/services/commentApi.ts new file mode 100644 index 0000000..ac42cae --- /dev/null +++ b/src/services/commentApi.ts @@ -0,0 +1,52 @@ +/** + * 评论功能API服务 + * 对接后端 /api/comment 接口 + */ + +import request from '@/utils/request' +import type { + CommentAddParams, + CommentQueryParams, + CommentItem, + IPage +} from '@/types' + +/** + * 发表评论 + * @param params 评论参数 + * @returns 操作消息 + */ +export const addComment = (params: CommentAddParams): Promise => { + return request.post('/api/comment/add', params) +} + +/** + * 删除评论 + * @param commentId 评论ID + * @returns 操作消息 + */ +export const deleteComment = (commentId: number): Promise => { + return request.delete(`/api/comment/delete/${commentId}`) +} + +/** + * 查看评论列表 + * @param params 查询参数 + * @returns 分页评论列表 + */ +export const getCommentList = (params: CommentQueryParams): Promise> => { + return request.get('/api/comment/list', { params }) +} + +/** + * 我的评论列表 + * @param params 查询参数 + * @returns 分页评论列表 + */ +export const getMyCommentList = (params: { + pageNum?: number + pageSize?: number + targetType?: string +}): Promise> => { + return request.get('/api/comment/myList', { params }) +} diff --git a/src/services/eventApi.ts b/src/services/eventApi.ts new file mode 100644 index 0000000..6781af8 --- /dev/null +++ b/src/services/eventApi.ts @@ -0,0 +1,62 @@ +/** + * 活动管理API服务 + * 对接后端 /api/event 接口 + */ + +import request from '@/utils/request' +import type { + EventQueryParams, + EventListItem, + EventDetail, + EventRegistrationParams, + IPage +} from '@/types' + +/** + * 分页查询活动列表 + * @param params 查询参数 + * @returns 分页活动列表 + */ +export const getEventList = (params?: EventQueryParams): Promise> => { + return request.get('/api/event/list', { params }) +} + +/** + * 查看活动详情 + * @param eventId 活动ID + * @returns 活动详情 + */ +export const getEventDetail = (eventId: number | string): Promise => { + return request.get(`/api/event/detail/${eventId}`) +} + +/** + * 报名参加活动 + * @param params 报名参数 + * @returns 操作消息 + */ +export const registerEvent = (params: EventRegistrationParams): Promise => { + return request.post('/api/event/register', params) +} + +/** + * 取消报名 + * @param eventId 活动ID + * @returns 操作消息 + */ +export const cancelEventRegistration = (eventId: number): Promise => { + return request.post(`/api/event/cancel/${eventId}`) +} + +/** + * 我报名的活动列表 + * @param params 查询参数 + * @returns 分页报名列表 + */ +export const getMyEventRegistrations = (params?: { + pageNum?: number + pageSize?: number + status?: string +}): Promise> => { + return request.get('/api/event/myRegistrations', { params }) +} diff --git a/src/services/favoriteApi.ts b/src/services/favoriteApi.ts new file mode 100644 index 0000000..201c032 --- /dev/null +++ b/src/services/favoriteApi.ts @@ -0,0 +1,68 @@ +/** + * 收藏功能API服务 + * 对接后端 /api/favorite 接口 + */ + +import request from '@/utils/request' +import type { + FavoriteOperateParams, + FavoriteQueryParams, + FavoriteItem, + IPage +} from '@/types' + +/** + * 添加收藏 + * @param params 收藏操作参数 + * @returns 操作消息 + */ +export const addFavorite = (params: FavoriteOperateParams): Promise => { + return request.post('/api/favorite/add', params) +} + +/** + * 取消收藏 + * @param params 收藏操作参数 + * @returns 操作消息 + */ +export const cancelFavorite = (params: FavoriteOperateParams): Promise => { + return request.post('/api/favorite/cancel', params) +} + +/** + * 我的收藏列表 + * @param params 查询参数 + * @returns 分页收藏列表 + */ +export const getMyFavoriteList = (params?: FavoriteQueryParams): Promise> => { + return request.get('/api/favorite/myList', { params }) +} + +/** + * 检查是否已收藏 + * @param targetType 目标类型 + * @param targetId 目标ID + * @returns 是否已收藏 + */ +export const checkFavorite = (targetType: string, targetId: number): Promise => { + return request.get('/api/favorite/check', { + params: { targetType, targetId } + }) +} + +/** + * 切换收藏状态(添加或取消) + * @param params 收藏操作参数 + * @param isFavorited 当前是否已收藏 + * @returns 操作消息 + */ +export const toggleFavorite = async ( + params: FavoriteOperateParams, + isFavorited: boolean +): Promise => { + if (isFavorited) { + return await cancelFavorite(params) + } else { + return await addFavorite(params) + } +} diff --git a/src/services/heritageApi.ts b/src/services/heritageApi.ts new file mode 100644 index 0000000..d80ab2b --- /dev/null +++ b/src/services/heritageApi.ts @@ -0,0 +1,43 @@ +/** + * 非遗项目API服务 + * 对接后端 /api/heritage 接口 + */ + +import request from '@/utils/request' +import type { IPage, HeritageQueryParams, HeritageListItem, HeritageDetail } from '@/types' + +/** + * 分页查询非遗项目列表 + * @param params 查询参数 + * @returns 分页数据 + */ +export const getHeritageList = (params?: HeritageQueryParams): Promise> => { + return request.get('/api/heritage/list', { params }) +} + +/** + * 查看非遗项目详情 + * @param id 非遗项目ID + * @returns 详情数据 + */ +export const getHeritageDetail = (id: number | string): Promise => { + return request.get(`/api/heritage/detail/${id}`) +} + +/** + * 获取热门非遗项目 + * @param limit 查询数量,默认10条 + * @returns 热门项目列表 + */ +export const getHotHeritageList = (limit: number = 10): Promise => { + return request.get('/api/heritage/hot', { params: { limit } }) +} + +/** + * 获取精选非遗项目 + * @param limit 查询数量,默认10条 + * @returns 精选项目列表 + */ +export const getFeaturedHeritageList = (limit: number = 10): Promise => { + return request.get('/api/heritage/featured', { params: { limit } }) +} diff --git a/src/services/inheritorApi.ts b/src/services/inheritorApi.ts new file mode 100644 index 0000000..665331a --- /dev/null +++ b/src/services/inheritorApi.ts @@ -0,0 +1,43 @@ +/** + * 传承人API服务 + * 对接后端 /api/inheritor 接口 + */ + +import request from '@/utils/request' +import type { IPage, InheritorQueryParams, InheritorListItem, InheritorDetail } from '@/types' + +/** + * 分页查询传承人列表 + * @param params 查询参数 + * @returns 分页数据 + */ +export const getInheritorList = (params?: InheritorQueryParams): Promise> => { + return request.get('/api/inheritor/list', { params }) +} + +/** + * 查看传承人详情 + * @param id 传承人ID + * @returns 详情数据 + */ +export const getInheritorDetail = (id: number | string): Promise => { + return request.get(`/api/inheritor/detail/${id}`) +} + +/** + * 根据非遗项目查询传承人 + * @param heritageId 非遗项目ID + * @returns 传承人列表 + */ +export const getInheritorsByHeritage = (heritageId: number | string): Promise => { + return request.get(`/api/inheritor/listByHeritage/${heritageId}`) +} + +/** + * 获取精选传承人 + * @param limit 查询数量,默认10条 + * @returns 精选传承人列表 + */ +export const getFeaturedInheritorList = (limit: number = 10): Promise => { + return request.get('/api/inheritor/featured', { params: { limit } }) +} diff --git a/src/services/likeApi.ts b/src/services/likeApi.ts new file mode 100644 index 0000000..90dd820 --- /dev/null +++ b/src/services/likeApi.ts @@ -0,0 +1,54 @@ +/** + * 点赞功能API服务 + * 对接后端 /api/like 接口 + */ + +import request from '@/utils/request' +import type { LikeOperateParams } from '@/types' + +/** + * 点赞 + * @param params 点赞操作参数 + * @returns 操作消息 + */ +export const addLike = (params: LikeOperateParams): Promise => { + return request.post('/api/like/add', params) +} + +/** + * 取消点赞 + * @param params 点赞操作参数 + * @returns 操作消息 + */ +export const cancelLike = (params: LikeOperateParams): Promise => { + return request.post('/api/like/cancel', params) +} + +/** + * 检查是否已点赞 + * @param targetType 目标类型 + * @param targetId 目标ID + * @returns 是否已点赞 + */ +export const checkLike = (targetType: string, targetId: number): Promise => { + return request.get('/api/like/check', { + params: { targetType, targetId } + }) +} + +/** + * 切换点赞状态(点赞或取消) + * @param params 点赞操作参数 + * @param isLiked 当前是否已点赞 + * @returns 操作消息 + */ +export const toggleLike = async ( + params: LikeOperateParams, + isLiked: boolean +): Promise => { + if (isLiked) { + return await cancelLike(params) + } else { + return await addLike(params) + } +} diff --git a/src/services/newsApi.ts b/src/services/newsApi.ts new file mode 100644 index 0000000..71190a3 --- /dev/null +++ b/src/services/newsApi.ts @@ -0,0 +1,47 @@ +/** + * 新闻资讯API服务 + * 对接后端 /api/news 接口 + */ + +import request from '@/utils/request' +import type { + NewsQueryParams, + NewsListItem, + NewsDetail, + IPage +} from '@/types' + +/** + * 分页查询新闻列表 + * @param params 查询参数 + * @returns 分页新闻列表 + */ +export const getNewsList = (params?: NewsQueryParams): Promise> => { + return request.get('/api/news/list', { params }) +} + +/** + * 查看新闻详情 + * @param newsId 新闻ID + * @returns 新闻详情 + */ +export const getNewsDetail = (newsId: number | string): Promise => { + return request.get(`/api/news/detail/${newsId}`) +} + +/** + * 获取热门新闻 + * @param limit 数量,默认10 + * @returns 热门新闻列表 + */ +export const getHotNews = (limit: number = 10): Promise => { + return request.get('/api/news/hot', { params: { limit } }) +} + +/** + * 获取置顶新闻 + * @returns 置顶新闻列表 + */ +export const getTopNews = (): Promise => { + return request.get('/api/news/top') +} diff --git a/src/services/userApi.ts b/src/services/userApi.ts new file mode 100644 index 0000000..346431b --- /dev/null +++ b/src/services/userApi.ts @@ -0,0 +1,84 @@ +/** + * 用户中心API服务 + * 对接后端 /api/user 接口 + */ + +import request from '@/utils/request' +import type { + UserProfile, + UserProfileUpdateParams, + UserAvatarUpdateParams, + UserPasswordUpdateParams, + UserStats, + ViewHistoryQueryParams, + ViewHistoryItem, + IPage +} from '@/types' + +/** + * 获取个人资料 + * @returns 个人资料信息 + */ +export const getUserProfile = (): Promise => { + return request.get('/api/user/profile') +} + +/** + * 修改个人资料 + * @param params 修改参数 + * @returns 操作消息 + */ +export const updateUserProfile = (params: UserProfileUpdateParams): Promise => { + return request.put('/api/user/profile', params) +} + +/** + * 更新头像 + * @param params 头像URL + * @returns 操作消息 + */ +export const updateUserAvatar = (params: UserAvatarUpdateParams): Promise => { + return request.put('/api/user/avatar', params) +} + +/** + * 上传并更新头像 + * @param file 头像文件 + * @returns 新的头像URL + */ +export const uploadAvatar = (file: File): Promise => { + const formData = new FormData() + formData.append('file', file) + + return request.post('/api/user/avatar/upload', formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }) +} + +/** + * 修改密码 + * @param params 密码修改参数 + * @returns 操作消息 + */ +export const updateUserPassword = (params: UserPasswordUpdateParams): Promise => { + return request.put('/api/user/password', params) +} + +/** + * 获取用户统计信息 + * @returns 统计信息 + */ +export const getUserStats = (): Promise => { + return request.get('/api/user/stats') +} + +/** + * 获取浏览历史 + * @param params 查询参数 + * @returns 分页浏览历史 + */ +export const getViewHistory = (params?: ViewHistoryQueryParams): Promise> => { + return request.get('/api/user/viewHistory', { params }) +} diff --git a/src/utils/request.ts b/src/utils/request.ts new file mode 100644 index 0000000..d3f1201 --- /dev/null +++ b/src/utils/request.ts @@ -0,0 +1,196 @@ +/** + * Axios HTTP 请求封装 + * 统一处理请求、响应、错误和Token + */ + +import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios' +import { message } from 'antd' +import type { ApiResponse } from '@/types' + +// 创建axios实例 +const request: AxiosInstance = axios.create({ + baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:18099', + timeout: 30000, + headers: { + 'Content-Type': 'application/json', + }, +}) + +// Token存储键(统一使用 hrt-token,与后端约定一致) +const TOKEN_KEY = 'hrt-token' + +// 获取Token +export const getToken = (): string | null => { + return localStorage.getItem(TOKEN_KEY) +} + +// 设置Token +export const setToken = (token: string): void => { + localStorage.setItem(TOKEN_KEY, token) +} + +// 移除Token +export const removeToken = (): void => { + localStorage.removeItem(TOKEN_KEY) +} + +// 请求拦截器 +request.interceptors.request.use( + (config) => { + // 自动添加Token到请求头(前台用户使用hrt-token) + const token = getToken() + if (token && config.headers) { + config.headers['hrt-token'] = token + } + return config + }, + (error: AxiosError) => { + console.error('请求错误:', error) + return Promise.reject(error) + } +) + +// 响应拦截器 +request.interceptors.response.use( + (response: AxiosResponse) => { + const { data } = response + + // 后端使用 mica 框架的 R 类包装响应 + // R.success() 格式:{ code: 1, success: true, data: ..., msg: "操作成功" } + // R.error() 格式:{ code: xxx, success: false, data: null, msg: "错误信息" } + if (data && typeof data === 'object') { + // 检查 code 字段(mica 框架使用 code) + if ('code' in data) { + // 成功响应判断(支持多种成功标识) + // 1. success === true(最可靠的成功标志) + // 2. code === 1(后端实际使用 - 重要!) + // 3. code === 0(部分框架默认) + // 4. code === 200(HTTP标准) + const isSuccess = + data.success === true || + data.success === 'true' || + data.code === 1 || // ✅ 后端实际使用 code: 1 表示成功 + data.code === 0 || + data.code === 200 + + if (isSuccess) { + return data.data + } + + // 业务错误 + const errorMessage = data.msg || data.message || '请求失败' + + // 🔥 识别未登录相关的错误,自动跳转到登录页 + const needLoginKeywords = [ + '未提供token', + '未提供Token', + 'token无效', + 'Token无效', + '未登录', + '请登录', + '登录已过期', + '身份验证失败', + '未授权' + ] + + const needLogin = needLoginKeywords.some(keyword => + errorMessage.toLowerCase().includes(keyword.toLowerCase()) + ) + + if (needLogin) { + message.warning('请先登录') + removeToken() + // 延迟跳转,让用户看到提示 + setTimeout(() => { + window.location.href = '/login' + }, 500) + } + + return Promise.reject(new Error(errorMessage)) + } + + // 兼容其他格式:{ status, msg, data } + if ('status' in data) { + if (data.status === 200 || data.status === 0) { + return data.data + } + const errorMessage = data.msg || data.message || '请求失败' + return Promise.reject(new Error(errorMessage)) + } + } + + // 如果后端直接返回数据(非标准格式) + return data + }, + (error: AxiosError) => { + // HTTP错误处理 + if (error.response) { + const { status, data } = error.response + let errorMessage = '请求失败' + + // 优先从 data.msg 或 data.message 提取错误信息 + if (data && typeof data === 'object') { + errorMessage = data.msg || data.message || errorMessage + } + + // 针对特定 HTTP 状态码的处理 + switch (status) { + case 400: + errorMessage = errorMessage || '请求参数错误' + break + case 401: + errorMessage = '未授权,请重新登录' + removeToken() + window.location.href = '/login' + break + case 403: + errorMessage = errorMessage || '没有权限访问' + break + case 404: + errorMessage = '请求的资源不存在' + break + case 500: + errorMessage = errorMessage || '服务器错误' + break + } + + // 将错误信息附加到error对象上 + const enhancedError = new Error(errorMessage) as any + enhancedError.status = status + enhancedError.response = error.response + return Promise.reject(enhancedError) + } else if (error.request) { + // 请求已发出但没有收到响应 + return Promise.reject(new Error('网络连接失败,请检查网络')) + } else { + // 请求配置出错 + return Promise.reject(new Error('请求配置错误')) + } + } +) + +// 导出请求方法 +export default request + +// 常用请求方法封装 +export const http = { + get(url: string, config?: AxiosRequestConfig): Promise { + return request.get(url, config) + }, + + post(url: string, data?: any, config?: AxiosRequestConfig): Promise { + return request.post(url, data, config) + }, + + put(url: string, data?: any, config?: AxiosRequestConfig): Promise { + return request.put(url, data, config) + }, + + delete(url: string, config?: AxiosRequestConfig): Promise { + return request.delete(url, config) + }, + + patch(url: string, data?: any, config?: AxiosRequestConfig): Promise { + return request.patch(url, data, config) + }, +}