重构后端服务架构并优化前端错误处理
This commit is contained in:
@@ -5,19 +5,19 @@ NODE_ENV=development
|
||||
VITE_APP_TITLE=活牛采购智能数字化系统 - 管理后台
|
||||
|
||||
# API接口地址
|
||||
VITE_API_BASE_URL=http://localhost:3001/api
|
||||
VITE_API_BASE_URL=http://localhost:3002/api
|
||||
|
||||
# WebSocket地址
|
||||
VITE_WS_BASE_URL=ws://localhost:3001
|
||||
VITE_WS_BASE_URL=ws://localhost:3002
|
||||
|
||||
# 上传文件地址
|
||||
VITE_UPLOAD_URL=http://localhost:3001/api/upload
|
||||
VITE_UPLOAD_URL=http://localhost:3002/api/upload
|
||||
|
||||
# 静态资源地址
|
||||
VITE_STATIC_URL=http://localhost:3001/static
|
||||
VITE_STATIC_URL=http://localhost:3002/static
|
||||
|
||||
# 是否启用Mock数据
|
||||
VITE_USE_MOCK=true
|
||||
VITE_USE_MOCK=false
|
||||
|
||||
# 是否启用开发工具
|
||||
VITE_DEV_TOOLS=true
|
||||
|
||||
91
admin-system/.eslintrc-auto-import.json
Normal file
91
admin-system/.eslintrc-auto-import.json
Normal file
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"globals": {
|
||||
"Component": true,
|
||||
"ComponentPublicInstance": true,
|
||||
"ComputedRef": true,
|
||||
"DirectiveBinding": true,
|
||||
"EffectScope": true,
|
||||
"ExtractDefaultPropTypes": true,
|
||||
"ExtractPropTypes": true,
|
||||
"ExtractPublicPropTypes": true,
|
||||
"InjectionKey": true,
|
||||
"MaybeRef": true,
|
||||
"MaybeRefOrGetter": true,
|
||||
"PropType": true,
|
||||
"Ref": true,
|
||||
"VNode": true,
|
||||
"WritableComputedRef": true,
|
||||
"acceptHMRUpdate": true,
|
||||
"computed": true,
|
||||
"createApp": true,
|
||||
"createPinia": true,
|
||||
"customRef": true,
|
||||
"defineAsyncComponent": true,
|
||||
"defineComponent": true,
|
||||
"defineStore": true,
|
||||
"effectScope": true,
|
||||
"getActivePinia": true,
|
||||
"getCurrentInstance": true,
|
||||
"getCurrentScope": true,
|
||||
"h": true,
|
||||
"inject": true,
|
||||
"isProxy": true,
|
||||
"isReactive": true,
|
||||
"isReadonly": true,
|
||||
"isRef": true,
|
||||
"mapActions": true,
|
||||
"mapGetters": true,
|
||||
"mapState": true,
|
||||
"mapStores": true,
|
||||
"mapWritableState": true,
|
||||
"markRaw": true,
|
||||
"nextTick": true,
|
||||
"onActivated": true,
|
||||
"onBeforeMount": true,
|
||||
"onBeforeRouteLeave": true,
|
||||
"onBeforeRouteUpdate": true,
|
||||
"onBeforeUnmount": true,
|
||||
"onBeforeUpdate": true,
|
||||
"onDeactivated": true,
|
||||
"onErrorCaptured": true,
|
||||
"onMounted": true,
|
||||
"onRenderTracked": true,
|
||||
"onRenderTriggered": true,
|
||||
"onScopeDispose": true,
|
||||
"onServerPrefetch": true,
|
||||
"onUnmounted": true,
|
||||
"onUpdated": true,
|
||||
"onWatcherCleanup": true,
|
||||
"provide": true,
|
||||
"reactive": true,
|
||||
"readonly": true,
|
||||
"ref": true,
|
||||
"resolveComponent": true,
|
||||
"setActivePinia": true,
|
||||
"setMapStoreSuffix": true,
|
||||
"shallowReactive": true,
|
||||
"shallowReadonly": true,
|
||||
"shallowRef": true,
|
||||
"storeToRefs": true,
|
||||
"toRaw": true,
|
||||
"toRef": true,
|
||||
"toRefs": true,
|
||||
"toValue": true,
|
||||
"triggerRef": true,
|
||||
"unref": true,
|
||||
"useAttrs": true,
|
||||
"useCssModule": true,
|
||||
"useCssVars": true,
|
||||
"useId": true,
|
||||
"useLink": true,
|
||||
"useModel": true,
|
||||
"useRoute": true,
|
||||
"useRouter": true,
|
||||
"useSlots": true,
|
||||
"useTemplateRef": true,
|
||||
"watch": true,
|
||||
"watchEffect": true,
|
||||
"watchPostEffect": true,
|
||||
"watchSyncEffect": true
|
||||
}
|
||||
}
|
||||
6
admin-system/package-lock.json
generated
6
admin-system/package-lock.json
generated
@@ -1287,9 +1287,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.19.12",
|
||||
"resolved": "https://registry.npmmirror.com/@types/node/-/node-20.19.12.tgz",
|
||||
"integrity": "sha512-lSOjyS6vdO2G2g2CWrETTV3Jz2zlCXHpu1rcubLKpz9oj+z/1CceHlj+yq53W+9zgb98nSov/wjEKYDNauD+Hw==",
|
||||
"version": "20.19.13",
|
||||
"resolved": "https://registry.npmmirror.com/@types/node/-/node-20.19.13.tgz",
|
||||
"integrity": "sha512-yCAeZl7a0DxgNVteXFHt9+uyFbqXGy/ShC4BlcHkoE0AfGXYv/BUiplV72DjMYXHDBXFjhvr6DD1NiRVfB4j8g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
4
admin-system/public/logo.svg
Normal file
4
admin-system/public/logo.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">
|
||||
<rect width="100" height="100" rx="20" fill="#4CAF50" />
|
||||
<text x="50" y="65" font-family="Arial, sans-serif" font-size="50" font-weight="bold" text-anchor="middle" fill="white">N</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 284 B |
@@ -3,9 +3,9 @@
|
||||
<!-- 侧边栏 -->
|
||||
<el-aside :width="isCollapse ? '64px' : '240px'" class="layout-aside">
|
||||
<div class="logo-container">
|
||||
<img v-if="!isCollapse" src="/logo.png" alt="Logo" class="logo" />
|
||||
<img v-if="!isCollapse" src="/logo.svg" alt="Logo" class="logo" />
|
||||
<span v-if="!isCollapse" class="logo-text">NiuMall</span>
|
||||
<img v-else src="/logo.png" alt="Logo" class="logo-mini" />
|
||||
<img v-else src="/logo.svg" alt="Logo" class="logo-mini" />
|
||||
</div>
|
||||
|
||||
<el-menu
|
||||
|
||||
@@ -29,10 +29,14 @@ export const useUserStore = defineStore('user', () => {
|
||||
localStorage.setItem('token', access_token)
|
||||
localStorage.setItem('userInfo', JSON.stringify(user))
|
||||
|
||||
ElMessage.success('登录成功')
|
||||
ElMessage.success({
|
||||
message: '登录成功',
|
||||
grouping: true,
|
||||
duration: 3000
|
||||
})
|
||||
return Promise.resolve()
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error.message || '登录失败')
|
||||
// 错误信息已在request拦截器中统一处理
|
||||
return Promise.reject(error)
|
||||
}
|
||||
}
|
||||
@@ -57,8 +61,13 @@ export const useUserStore = defineStore('user', () => {
|
||||
const logoutAction = async () => {
|
||||
try {
|
||||
await logout()
|
||||
} catch (error) {
|
||||
console.error('登出接口调用失败:', error)
|
||||
ElMessage.success({
|
||||
message: '已退出登录',
|
||||
grouping: true,
|
||||
duration: 3000
|
||||
})
|
||||
} catch (error: any) {
|
||||
// 错误信息已在request拦截器中统一处理
|
||||
} finally {
|
||||
// 清除状态和本地存储
|
||||
token.value = ''
|
||||
|
||||
@@ -3,6 +3,19 @@ import type { AxiosResponse, AxiosError } from 'axios'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
// 错误代码映射表
|
||||
const ERROR_CODES: Record<string, string> = {
|
||||
'AUTH_INVALID_CREDENTIALS': '用户名或密码错误',
|
||||
'AUTH_ACCOUNT_LOCKED': '账户已被锁定,请联系管理员',
|
||||
'AUTH_ACCOUNT_DISABLED': '账户已被禁用',
|
||||
'AUTH_TOKEN_EXPIRED': '登录已过期,请重新登录',
|
||||
'AUTH_INVALID_TOKEN': '无效的登录凭证',
|
||||
'NETWORK_ERROR': '网络错误,请检查网络连接',
|
||||
'TIMEOUT_ERROR': '请求超时,请稍后重试',
|
||||
'SERVER_ERROR': '服务器内部错误',
|
||||
'UNKNOWN_ERROR': '未知错误,请联系管理员'
|
||||
}
|
||||
|
||||
// 创建axios实例
|
||||
const request = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000/api',
|
||||
@@ -36,8 +49,14 @@ request.interceptors.response.use(
|
||||
|
||||
// 检查业务状态码
|
||||
if (data.success === false) {
|
||||
ElMessage.error(data.message || '请求失败')
|
||||
return Promise.reject(new Error(data.message || '请求失败'))
|
||||
const errorCode = data.code || 'UNKNOWN_ERROR'
|
||||
const errorMsg = ERROR_CODES[errorCode] || data.message || '请求失败'
|
||||
ElMessage.error({
|
||||
message: errorMsg,
|
||||
grouping: true,
|
||||
duration: 5000
|
||||
})
|
||||
return Promise.reject(new Error(errorMsg))
|
||||
}
|
||||
|
||||
return data
|
||||
@@ -47,30 +66,61 @@ request.interceptors.response.use(
|
||||
const { response } = error
|
||||
|
||||
if (response) {
|
||||
const errorCode = (response.data as any)?.code
|
||||
const errorMsg = errorCode ? ERROR_CODES[errorCode] : undefined
|
||||
|
||||
switch (response.status) {
|
||||
case 401:
|
||||
ElMessage.error('未授权,请重新登录')
|
||||
ElMessage.error({
|
||||
message: errorMsg || '未授权,请重新登录',
|
||||
grouping: true,
|
||||
duration: 5000
|
||||
})
|
||||
// 清除登录状态并跳转到登录页
|
||||
const userStore = useUserStore()
|
||||
userStore.logoutAction()
|
||||
window.location.href = '/login'
|
||||
break
|
||||
case 403:
|
||||
ElMessage.error('访问被拒绝,权限不足')
|
||||
ElMessage.error({
|
||||
message: errorMsg || '访问被拒绝,权限不足',
|
||||
grouping: true,
|
||||
duration: 5000
|
||||
})
|
||||
break
|
||||
case 404:
|
||||
ElMessage.error('请求的资源不存在')
|
||||
ElMessage.error({
|
||||
message: errorMsg || '请求的资源不存在',
|
||||
grouping: true,
|
||||
duration: 5000
|
||||
})
|
||||
break
|
||||
case 500:
|
||||
ElMessage.error('服务器内部错误')
|
||||
ElMessage.error({
|
||||
message: errorMsg || '服务器内部错误',
|
||||
grouping: true,
|
||||
duration: 5000
|
||||
})
|
||||
break
|
||||
default:
|
||||
ElMessage.error(`请求失败: ${response.status}`)
|
||||
ElMessage.error({
|
||||
message: errorMsg || `请求失败: ${response.status}`,
|
||||
grouping: true,
|
||||
duration: 5000
|
||||
})
|
||||
}
|
||||
} else if (error.code === 'ECONNABORTED') {
|
||||
ElMessage.error('请求超时,请稍后重试')
|
||||
ElMessage.error({
|
||||
message: '请求超时,请稍后重试',
|
||||
grouping: true,
|
||||
duration: 5000
|
||||
})
|
||||
} else {
|
||||
ElMessage.error('网络错误,请检查网络连接')
|
||||
ElMessage.error({
|
||||
message: '网络错误,请检查网络连接',
|
||||
grouping: true,
|
||||
duration: 5000
|
||||
})
|
||||
}
|
||||
|
||||
return Promise.reject(error)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="login-box">
|
||||
<div class="login-header">
|
||||
<div class="logo">
|
||||
<img src="/logo.png" alt="Logo" class="logo-img" />
|
||||
<img src="/logo.svg" alt="Logo" class="logo-img" />
|
||||
<h1 class="title">活牛采购智能数字化系统</h1>
|
||||
</div>
|
||||
<p class="subtitle">管理后台</p>
|
||||
|
||||
@@ -95,7 +95,7 @@ export default defineConfig(({ mode }) => {
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
additionalData: `@import "@/assets/styles/variables.scss"; @import "@/assets/styles/mixins.scss";`
|
||||
additionalData: `@import "@/style/variables.scss"; @import "@/style/mixins.scss";`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user