重构后端API和配置,新增仪表板数据接口并优化本地开发环境配置

This commit is contained in:
ylweng
2025-09-21 23:18:08 +08:00
parent 14aca938de
commit 5fc1a4fcb9
33 changed files with 2990 additions and 321 deletions

View File

@@ -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
# 功能开关

View File

@@ -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
# 功能开关

View File

@@ -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: () =>

View File

@@ -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

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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')
}