登录流程:
- login() 不再设置 userInfo,由 router guard 触发 getInfo() + 路由生成
- validate() 从 callback 改为 Promise 式,修复异步链断裂
- getInfo() 适配 /auth/me 扁平响应结构
动态路由:
- import.meta.glob 替代 Vite 不支持的多级动态 import
- 目录菜单检测: !component || 'Layout' 而非仅 'Layout'
- resolveComponent 支持多种 key 格式 + 后缀匹配兜底
侧边栏:
- menu.title → menu.name (匹配后端字段)
- 空 path 目录节点加 fallback index
门店管理:
- StoreController: region_id 改 nullable, 新增 contact/capacity/description 验证
- has('status') → filled('status') 修复空字符串导致列表为空
- capacity null → 0 兜底,避免 NOT NULL 约束报错
- 前端: capacity 初始值 '' → 1, handleSubmit 过滤冗余字段
- region 列渲染 region.name 而非 [object Object]
- 新增 stores 表迁移: contact/capacity/description 列
种子数据:
- DatabaseSeeder 改用 InitSeeder
- createMenus() 补全 52 个叶子页面菜单 (13 模块)
- 目录菜单添加 path 属性
其他:
- vite proxy 端口 8000 → 8001
- request.js 添加 Accept: application/json
- UserStatus 枚举值对齐 (Active=1, Disabled=0)
- 新增 positions/index.vue 页面
85 lines
2.4 KiB
JavaScript
85 lines
2.4 KiB
JavaScript
import axios from 'axios'
|
||
import { ElMessage } from 'element-plus'
|
||
|
||
const request = axios.create({
|
||
baseURL: '/api/v1',
|
||
timeout: 15000,
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Accept': 'application/json'
|
||
}
|
||
})
|
||
|
||
// Request interceptor — inject Authorization token
|
||
request.interceptors.request.use(
|
||
(config) => {
|
||
const token = localStorage.getItem('token')
|
||
if (token) {
|
||
config.headers['Authorization'] = `Bearer ${token}`
|
||
}
|
||
return config
|
||
},
|
||
(error) => {
|
||
return Promise.reject(error)
|
||
}
|
||
)
|
||
|
||
// Response interceptor — handle business codes and auth errors
|
||
request.interceptors.response.use(
|
||
(response) => {
|
||
const res = response.data
|
||
|
||
// Success: code === 0
|
||
if (res.code === 0) {
|
||
return res
|
||
}
|
||
|
||
// Auth error: 40100–40199 → clear token and redirect to login
|
||
if (res.code >= 40100 && res.code <= 40199) {
|
||
localStorage.removeItem('token')
|
||
localStorage.removeItem('userInfo')
|
||
ElMessage.error('登录已过期,请重新登录')
|
||
// Avoid circular import by using window.location
|
||
window.location.href = '/login'
|
||
return Promise.reject(new Error(res.message || '未授权'))
|
||
}
|
||
|
||
// Other business error
|
||
ElMessage.error(res.message || '请求失败')
|
||
return Promise.reject(new Error(res.message || '请求失败'))
|
||
},
|
||
(error) => {
|
||
// Network / HTTP errors
|
||
if (error.response) {
|
||
const status = error.response.status
|
||
if (status === 401) {
|
||
localStorage.removeItem('token')
|
||
localStorage.removeItem('userInfo')
|
||
ElMessage.error('登录已过期,请重新登录')
|
||
window.location.href = '/login'
|
||
return Promise.reject(error)
|
||
}
|
||
if (status === 403) {
|
||
ElMessage.error('无权限访问')
|
||
return Promise.reject(error)
|
||
}
|
||
if (status === 404) {
|
||
ElMessage.error('请求的资源不存在')
|
||
return Promise.reject(error)
|
||
}
|
||
if (status >= 500) {
|
||
ElMessage.error('服务器内部错误,请稍后重试')
|
||
return Promise.reject(error)
|
||
}
|
||
}
|
||
if (error.code === 'ECONNABORTED' || error.message.includes('timeout')) {
|
||
ElMessage.error('请求超时,请检查网络连接')
|
||
return Promise.reject(error)
|
||
}
|
||
ElMessage.error(error.message || '网络错误')
|
||
return Promise.reject(error)
|
||
}
|
||
)
|
||
|
||
export default request
|