重构后端API和配置,新增仪表板数据接口并优化本地开发环境配置
This commit is contained in:
@@ -2,8 +2,8 @@
|
||||
VITE_APP_NAME=结伴客后台管理系统
|
||||
VITE_APP_VERSION=1.0.0
|
||||
|
||||
# API配置
|
||||
VITE_API_BASE_URL=https://webapi.jiebanke.com/api
|
||||
# API配置 - 修改为本地测试地址
|
||||
VITE_API_BASE_URL=http://localhost:3200/api/v1
|
||||
VITE_API_TIMEOUT=10000
|
||||
|
||||
# 功能开关
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# 开发环境配置
|
||||
NODE_ENV=development
|
||||
|
||||
# API配置
|
||||
VITE_API_BASE_URL=https://webapi.jiebanke.com/api/v1
|
||||
# API配置 - 修改为本地测试地址
|
||||
VITE_API_BASE_URL=http://localhost:3200/api/v1
|
||||
VITE_API_TIMEOUT=30000
|
||||
|
||||
# 功能开关
|
||||
|
||||
@@ -4,8 +4,8 @@ import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
|
||||
import { mockAPI } from './mockData'
|
||||
import { createMockWrapper } from '@/config/mock'
|
||||
|
||||
// API基础配置
|
||||
const baseURL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3100/api'
|
||||
// API基础配置 - 修改为本地测试地址
|
||||
const baseURL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3200/api/v1'
|
||||
const timeout = parseInt(import.meta.env.VITE_API_TIMEOUT || '10000')
|
||||
|
||||
// 检查是否使用模拟数据(注释掉未使用的变量)
|
||||
@@ -42,6 +42,23 @@ api.interceptors.request.use(
|
||||
}
|
||||
)
|
||||
|
||||
// 用于防止重复刷新token的标志
|
||||
let isRefreshing = false
|
||||
let failedQueue: Array<{ resolve: Function; reject: Function }> = []
|
||||
|
||||
// 处理队列中的请求
|
||||
const processQueue = (error: any, token: string | null = null) => {
|
||||
failedQueue.forEach(({ resolve, reject }) => {
|
||||
if (error) {
|
||||
reject(error)
|
||||
} else {
|
||||
resolve(token)
|
||||
}
|
||||
})
|
||||
|
||||
failedQueue = []
|
||||
}
|
||||
|
||||
// 响应拦截器
|
||||
api.interceptors.response.use(
|
||||
(response: AxiosResponse) => {
|
||||
@@ -62,19 +79,71 @@ api.interceptors.response.use(
|
||||
return Promise.reject(new Error(errorMsg))
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
async (error) => {
|
||||
// 处理错误响应
|
||||
console.error('❌ API错误:', error)
|
||||
|
||||
const originalRequest = error.config
|
||||
|
||||
if (error.response) {
|
||||
const { status, data } = error.response
|
||||
|
||||
switch (status) {
|
||||
case 401:
|
||||
// 未授权,跳转到登录页
|
||||
message.error('登录已过期,请重新登录')
|
||||
localStorage.removeItem('admin_token')
|
||||
window.location.href = '/login'
|
||||
// 如果是登录接口或者已经在刷新token,直接返回错误
|
||||
if (originalRequest.url?.includes('/login') || originalRequest._retry) {
|
||||
message.error('登录已过期,请重新登录')
|
||||
localStorage.removeItem('admin_token')
|
||||
window.location.href = '/login'
|
||||
return Promise.reject(error)
|
||||
}
|
||||
|
||||
// 标记请求已重试
|
||||
originalRequest._retry = true
|
||||
|
||||
if (isRefreshing) {
|
||||
// 如果正在刷新token,将请求加入队列
|
||||
return new Promise((resolve, reject) => {
|
||||
failedQueue.push({ resolve, reject })
|
||||
}).then(token => {
|
||||
originalRequest.headers.Authorization = `Bearer ${token}`
|
||||
return api(originalRequest)
|
||||
}).catch(err => {
|
||||
return Promise.reject(err)
|
||||
})
|
||||
}
|
||||
|
||||
isRefreshing = true
|
||||
|
||||
try {
|
||||
// 尝试刷新token
|
||||
const refreshToken = localStorage.getItem('admin_refresh_token')
|
||||
const response = await authAPI.refreshToken(refreshToken)
|
||||
const newToken = response.data.token
|
||||
|
||||
// 更新localStorage中的token
|
||||
localStorage.setItem('admin_token', newToken)
|
||||
|
||||
// 更新默认请求头
|
||||
api.defaults.headers.common['Authorization'] = `Bearer ${newToken}`
|
||||
|
||||
// 处理队列中的请求
|
||||
processQueue(null, newToken)
|
||||
|
||||
// 重试原始请求
|
||||
originalRequest.headers['Authorization'] = `Bearer ${newToken}`
|
||||
return api(originalRequest)
|
||||
} catch (refreshError) {
|
||||
// 刷新失败,清除所有token并跳转登录页
|
||||
processQueue(refreshError, null)
|
||||
message.error('登录已过期,请重新登录')
|
||||
localStorage.removeItem('admin_token')
|
||||
localStorage.removeItem('admin_refresh_token')
|
||||
window.location.href = '/login'
|
||||
return Promise.reject(refreshError)
|
||||
} finally {
|
||||
isRefreshing = false
|
||||
}
|
||||
break
|
||||
case 403:
|
||||
message.error('权限不足,无法访问')
|
||||
@@ -143,13 +212,13 @@ export const authAPI = createMockWrapper({
|
||||
}>('/admin/profile'),
|
||||
|
||||
// 刷新token
|
||||
refreshToken: () =>
|
||||
refreshToken: (refreshToken: string) =>
|
||||
request.post<{
|
||||
success: boolean
|
||||
data: {
|
||||
token: string
|
||||
}
|
||||
}>('/auth/refresh'),
|
||||
}>('/auth/refresh', { refreshToken }),
|
||||
|
||||
// 退出登录
|
||||
logout: () =>
|
||||
|
||||
@@ -102,15 +102,34 @@ export const batchUpdateUserStatus = (userIds: number[], status: string) =>
|
||||
export const updateUserStatus = (id: number, status: string) =>
|
||||
request.put<ApiResponse<User>>(`/users/${id}/status`, { status })
|
||||
|
||||
// 启用用户
|
||||
export const enableUser = (id: number) =>
|
||||
request.put<ApiResponse<User>>(`/users/${id}/enable`)
|
||||
|
||||
// 开发环境使用模拟数据
|
||||
// 禁用用户
|
||||
export const disableUser = (id: number) =>
|
||||
request.put<ApiResponse<User>>(`/users/${id}/disable`)
|
||||
|
||||
// 封禁用户
|
||||
export const banUser = (id: number) =>
|
||||
request.put<ApiResponse<User>>(`/users/${id}/ban`)
|
||||
|
||||
// 解封用户
|
||||
export const unbanUser = (id: number) =>
|
||||
request.put<ApiResponse<User>>(`/users/${id}/unban`)
|
||||
|
||||
// 使用 mock 包装器
|
||||
const userAPI = createMockWrapper({
|
||||
getUsers,
|
||||
getUser,
|
||||
createUser,
|
||||
updateUser,
|
||||
deleteUser,
|
||||
batchUpdateUserStatus
|
||||
batchUpdateUserStatus,
|
||||
enableUser,
|
||||
disableUser,
|
||||
banUser,
|
||||
unbanUser
|
||||
}, mockUserAPI)
|
||||
|
||||
export default userAPI
|
||||
@@ -312,7 +312,8 @@ import {
|
||||
DownOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { getUsers, getUser, createUser, updateUser, disableUser, enableUser, banUser, unbanUser } from '@/api/user'
|
||||
import { getUsers, getUser, createUser, updateUser, updateUserStatus } from '@/api/user'
|
||||
import userAPI from '@/api/user'
|
||||
import type { User, UserQueryParams } from '@/types/user'
|
||||
|
||||
interface SearchForm {
|
||||
@@ -651,7 +652,7 @@ const handleDisable = async (record: User) => {
|
||||
content: `确定要禁用用户 "${record.username}" 吗?`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
await disableUser(record.id)
|
||||
await userAPI.disableUser(record.id)
|
||||
message.success('用户已禁用')
|
||||
loadUsers()
|
||||
} catch (error) {
|
||||
@@ -667,7 +668,7 @@ const handleEnable = async (record: User) => {
|
||||
content: `确定要启用用户 "${record.username}" 吗?`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
await enableUser(record.id)
|
||||
await userAPI.enableUser(record.id)
|
||||
message.success('用户已启用')
|
||||
loadUsers()
|
||||
} catch (error) {
|
||||
@@ -683,7 +684,7 @@ const handleBan = async (record: User) => {
|
||||
content: `确定要封禁用户 "${record.username}" 吗?`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
await banUser(record.id)
|
||||
await userAPI.banUser(record.id)
|
||||
message.success('用户已封禁')
|
||||
loadUsers()
|
||||
} catch (error) {
|
||||
@@ -699,7 +700,7 @@ const handleUnban = async (record: User) => {
|
||||
content: `确定要解封用户 "${record.username}" 吗?`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
await unbanUser(record.id)
|
||||
await userAPI.unbanUser(record.id)
|
||||
message.success('用户已解封')
|
||||
loadUsers()
|
||||
} catch (error) {
|
||||
|
||||
@@ -60,30 +60,37 @@ export const useAppStore = defineStore('app', () => {
|
||||
try {
|
||||
const token = localStorage.getItem('admin_token')
|
||||
if (token) {
|
||||
// 获取用户信息
|
||||
const response = await authAPI.getCurrentUser()
|
||||
|
||||
// 统一处理接口响应格式
|
||||
if (!response || typeof response !== 'object') {
|
||||
throw new Error('获取用户信息失败:接口返回格式异常')
|
||||
}
|
||||
|
||||
// 确保响应数据格式为 { data: { admin: object } }
|
||||
if (response.data && typeof response.data === 'object' && response.data.admin) {
|
||||
// 模拟权限数据 - 实际项目中应该从后端获取
|
||||
const mockPermissions = [
|
||||
'user:read', 'user:write',
|
||||
'merchant:read', 'merchant:write',
|
||||
'travel:read', 'travel:write',
|
||||
'animal:read', 'animal:write',
|
||||
'order:read', 'order:write',
|
||||
'promotion:read', 'promotion:write',
|
||||
'system:read', 'system:write'
|
||||
]
|
||||
state.user = response.data.admin
|
||||
state.permissions = mockPermissions
|
||||
} else {
|
||||
throw new Error('获取用户信息失败:响应数据格式不符合预期')
|
||||
try {
|
||||
// 获取用户信息
|
||||
const response = await authAPI.getCurrentUser()
|
||||
|
||||
// 统一处理接口响应格式
|
||||
if (!response || typeof response !== 'object') {
|
||||
throw new Error('获取用户信息失败:接口返回格式异常')
|
||||
}
|
||||
|
||||
// 确保响应数据格式为 { data: { admin: object } }
|
||||
if (response.data && typeof response.data === 'object' && response.data.admin) {
|
||||
// 模拟权限数据 - 实际项目中应该从后端获取
|
||||
const mockPermissions = [
|
||||
'user:read', 'user:write',
|
||||
'merchant:read', 'merchant:write',
|
||||
'travel:read', 'travel:write',
|
||||
'animal:read', 'animal:write',
|
||||
'order:read', 'order:write',
|
||||
'promotion:read', 'promotion:write',
|
||||
'system:read', 'system:write'
|
||||
]
|
||||
state.user = response.data.admin
|
||||
state.permissions = mockPermissions
|
||||
} else {
|
||||
throw new Error('获取用户信息失败:响应数据格式不符合预期')
|
||||
}
|
||||
} catch (apiError) {
|
||||
// 如果获取用户信息失败(比如token过期),清除登录状态但不抛出错误
|
||||
console.warn('获取用户信息失败,可能是token过期:', apiError)
|
||||
clearUser()
|
||||
// 不抛出错误,让应用正常初始化,路由守卫会处理重定向到登录页
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -93,7 +100,7 @@ export const useAppStore = defineStore('app', () => {
|
||||
token: localStorage.getItem('admin_token')
|
||||
})
|
||||
clearUser()
|
||||
throw error // 抛出错误以便调用方处理
|
||||
// 不抛出错误,让应用正常初始化
|
||||
} finally {
|
||||
state.loading = false
|
||||
state.initialized = true
|
||||
@@ -106,11 +113,17 @@ export const useAppStore = defineStore('app', () => {
|
||||
try {
|
||||
const response = await authAPI.login(credentials)
|
||||
|
||||
// 保存token - 修复数据结构访问问题
|
||||
// 保存token和refreshToken - 修复数据结构访问问题
|
||||
if (response?.data?.token) {
|
||||
localStorage.setItem('admin_token', response.data.token)
|
||||
if (response.data.refreshToken) {
|
||||
localStorage.setItem('admin_refresh_token', response.data.refreshToken)
|
||||
}
|
||||
} else if (response?.token) {
|
||||
localStorage.setItem('admin_token', response.token)
|
||||
if (response.refreshToken) {
|
||||
localStorage.setItem('admin_refresh_token', response.refreshToken)
|
||||
}
|
||||
} else {
|
||||
throw new Error('登录响应中缺少token')
|
||||
}
|
||||
@@ -158,7 +171,13 @@ export const useAppStore = defineStore('app', () => {
|
||||
|
||||
// 退出登录
|
||||
const logout = () => {
|
||||
clearUser()
|
||||
// 清除localStorage中的token和refreshToken
|
||||
localStorage.removeItem('admin_token')
|
||||
localStorage.removeItem('admin_refresh_token')
|
||||
|
||||
// 清除状态
|
||||
state.user = null
|
||||
state.permissions = []
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -27,7 +27,7 @@ export default defineConfig({
|
||||
port: 3150,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'https://webapi.jiebanke.com',
|
||||
target: 'http://localhost:3200',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api/, '/api/v1')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user