重构后端服务架构并优化前端错误处理

This commit is contained in:
ylweng
2025-09-05 01:18:40 +08:00
parent 86322c6f50
commit 5853953f79
20 changed files with 608 additions and 772 deletions

View File

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

View 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
}
}

View File

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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";`
}
}
}