feat: 添加API服务层和请求封装

- 新增authApi.ts实现用户认证相关接口
- 新增commentApi.ts实现评论功能接口
- 新增eventApi.ts实现活动管理接口
- 新增favoriteApi.ts实现收藏功能接口
- 新增heritageApi.ts实现非遗项目接口
- 新增inheritorApi.ts实现传承人接口
- 新增likeApi.ts实现点赞功能接口
- 新增newsApi.ts实现资讯接口
- 新增userApi.ts实现用户中心接口
- 优化request.ts请求拦截器,统一处理认证和错误
This commit is contained in:
Leo 2025-10-13 21:40:21 +08:00
parent 8a9c9e9d6e
commit 093cb7c1fd
10 changed files with 688 additions and 0 deletions

39
src/services/authApi.ts Normal file
View File

@ -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<LoginResult> => {
return http.post<LoginResult>('/api/auth/login', data)
}
/**
*
* POST /api/auth/register
*/
export const registerApi = async (data: RegisterRequest): Promise<string> => {
return http.post<string>('/api/auth/register', data)
}
/**
*
* POST /api/auth/logout
*/
export const logoutApi = async (): Promise<string> => {
return http.post<string>('/api/auth/logout')
}
/**
*
* GET /api/auth/userInfo
*/
export const getUserInfoApi = async (): Promise<UserInfo> => {
return http.get<UserInfo>('/api/auth/userInfo')
}

View File

@ -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<string> => {
return request.post('/api/comment/add', params)
}
/**
*
* @param commentId ID
* @returns
*/
export const deleteComment = (commentId: number): Promise<string> => {
return request.delete(`/api/comment/delete/${commentId}`)
}
/**
*
* @param params
* @returns
*/
export const getCommentList = (params: CommentQueryParams): Promise<IPage<CommentItem>> => {
return request.get('/api/comment/list', { params })
}
/**
*
* @param params
* @returns
*/
export const getMyCommentList = (params: {
pageNum?: number
pageSize?: number
targetType?: string
}): Promise<IPage<CommentItem>> => {
return request.get('/api/comment/myList', { params })
}

62
src/services/eventApi.ts Normal file
View File

@ -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<IPage<EventListItem>> => {
return request.get('/api/event/list', { params })
}
/**
*
* @param eventId ID
* @returns
*/
export const getEventDetail = (eventId: number | string): Promise<EventDetail> => {
return request.get(`/api/event/detail/${eventId}`)
}
/**
*
* @param params
* @returns
*/
export const registerEvent = (params: EventRegistrationParams): Promise<string> => {
return request.post('/api/event/register', params)
}
/**
*
* @param eventId ID
* @returns
*/
export const cancelEventRegistration = (eventId: number): Promise<string> => {
return request.post(`/api/event/cancel/${eventId}`)
}
/**
*
* @param params
* @returns
*/
export const getMyEventRegistrations = (params?: {
pageNum?: number
pageSize?: number
status?: string
}): Promise<IPage<EventListItem>> => {
return request.get('/api/event/myRegistrations', { params })
}

View File

@ -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<string> => {
return request.post('/api/favorite/add', params)
}
/**
*
* @param params
* @returns
*/
export const cancelFavorite = (params: FavoriteOperateParams): Promise<string> => {
return request.post('/api/favorite/cancel', params)
}
/**
*
* @param params
* @returns
*/
export const getMyFavoriteList = (params?: FavoriteQueryParams): Promise<IPage<FavoriteItem>> => {
return request.get('/api/favorite/myList', { params })
}
/**
*
* @param targetType
* @param targetId ID
* @returns
*/
export const checkFavorite = (targetType: string, targetId: number): Promise<boolean> => {
return request.get('/api/favorite/check', {
params: { targetType, targetId }
})
}
/**
*
* @param params
* @param isFavorited
* @returns
*/
export const toggleFavorite = async (
params: FavoriteOperateParams,
isFavorited: boolean
): Promise<string> => {
if (isFavorited) {
return await cancelFavorite(params)
} else {
return await addFavorite(params)
}
}

View File

@ -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<IPage<HeritageListItem>> => {
return request.get('/api/heritage/list', { params })
}
/**
*
* @param id ID
* @returns
*/
export const getHeritageDetail = (id: number | string): Promise<HeritageDetail> => {
return request.get(`/api/heritage/detail/${id}`)
}
/**
*
* @param limit 10
* @returns
*/
export const getHotHeritageList = (limit: number = 10): Promise<HeritageListItem[]> => {
return request.get('/api/heritage/hot', { params: { limit } })
}
/**
*
* @param limit 10
* @returns
*/
export const getFeaturedHeritageList = (limit: number = 10): Promise<HeritageListItem[]> => {
return request.get('/api/heritage/featured', { params: { limit } })
}

View File

@ -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<IPage<InheritorListItem>> => {
return request.get('/api/inheritor/list', { params })
}
/**
*
* @param id ID
* @returns
*/
export const getInheritorDetail = (id: number | string): Promise<InheritorDetail> => {
return request.get(`/api/inheritor/detail/${id}`)
}
/**
*
* @param heritageId ID
* @returns
*/
export const getInheritorsByHeritage = (heritageId: number | string): Promise<InheritorListItem[]> => {
return request.get(`/api/inheritor/listByHeritage/${heritageId}`)
}
/**
*
* @param limit 10
* @returns
*/
export const getFeaturedInheritorList = (limit: number = 10): Promise<InheritorListItem[]> => {
return request.get('/api/inheritor/featured', { params: { limit } })
}

54
src/services/likeApi.ts Normal file
View File

@ -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<string> => {
return request.post('/api/like/add', params)
}
/**
*
* @param params
* @returns
*/
export const cancelLike = (params: LikeOperateParams): Promise<string> => {
return request.post('/api/like/cancel', params)
}
/**
*
* @param targetType
* @param targetId ID
* @returns
*/
export const checkLike = (targetType: string, targetId: number): Promise<boolean> => {
return request.get('/api/like/check', {
params: { targetType, targetId }
})
}
/**
*
* @param params
* @param isLiked
* @returns
*/
export const toggleLike = async (
params: LikeOperateParams,
isLiked: boolean
): Promise<string> => {
if (isLiked) {
return await cancelLike(params)
} else {
return await addLike(params)
}
}

47
src/services/newsApi.ts Normal file
View File

@ -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<IPage<NewsListItem>> => {
return request.get('/api/news/list', { params })
}
/**
*
* @param newsId ID
* @returns
*/
export const getNewsDetail = (newsId: number | string): Promise<NewsDetail> => {
return request.get(`/api/news/detail/${newsId}`)
}
/**
*
* @param limit 10
* @returns
*/
export const getHotNews = (limit: number = 10): Promise<NewsListItem[]> => {
return request.get('/api/news/hot', { params: { limit } })
}
/**
*
* @returns
*/
export const getTopNews = (): Promise<NewsListItem[]> => {
return request.get('/api/news/top')
}

84
src/services/userApi.ts Normal file
View File

@ -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<UserProfile> => {
return request.get('/api/user/profile')
}
/**
*
* @param params
* @returns
*/
export const updateUserProfile = (params: UserProfileUpdateParams): Promise<string> => {
return request.put('/api/user/profile', params)
}
/**
*
* @param params URL
* @returns
*/
export const updateUserAvatar = (params: UserAvatarUpdateParams): Promise<string> => {
return request.put('/api/user/avatar', params)
}
/**
*
* @param file
* @returns URL
*/
export const uploadAvatar = (file: File): Promise<string> => {
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<string> => {
return request.put('/api/user/password', params)
}
/**
*
* @returns
*/
export const getUserStats = (): Promise<UserStats> => {
return request.get('/api/user/stats')
}
/**
*
* @param params
* @returns
*/
export const getViewHistory = (params?: ViewHistoryQueryParams): Promise<IPage<ViewHistoryItem>> => {
return request.get('/api/user/viewHistory', { params })
}

196
src/utils/request.ts Normal file
View File

@ -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<any>) => {
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 === 200HTTP标准
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<any>) => {
// 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<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
return request.get(url, config)
},
post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
return request.post(url, data, config)
},
put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
return request.put(url, data, config)
},
delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
return request.delete(url, config)
},
patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
return request.patch(url, data, config)
},
}