Template
1
0
mirror of https://github.com/un-pany/v3-admin-vite.git synced 2025-04-22 03:49:19 +08:00

refactor:增加统一错误处理

This commit is contained in:
moke 2023-12-21 18:46:54 +08:00
parent 03928dfce5
commit 6bb345e558
12 changed files with 123 additions and 29 deletions

4
.gitignore vendored
View File

@ -33,3 +33,7 @@ lerna-debug.log*
# Use the PNPM
package-lock.json
yarn.lock
# history
.history

View File

@ -1,7 +1,7 @@
{
"editor.tabSize": 2,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
"source.fixAll.eslint": "explicit"
},
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
@ -26,5 +26,10 @@
},
"[scss]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
},
"cSpell.words": [
"endregion",
"nprogress",
"pinia"
]
}

View File

@ -1,3 +1,5 @@
import { handleApiError } from "@/utils/error-handler"
/** 模拟接口响应数据 */
const SELECT_RESPONSE_DATA = {
code: 0,
@ -22,15 +24,12 @@ const SELECT_RESPONSE_DATA = {
/** 模拟接口 */
export function getSelectDataApi() {
return new Promise<typeof SELECT_RESPONSE_DATA>((resolve, reject) => {
// 模拟接口响应时间 2s
setTimeout(() => {
// 模拟接口调用成功
if (Math.random() < 0.8) {
resolve(SELECT_RESPONSE_DATA)
} else {
// 模拟接口调用出错
reject(new Error("接口发生错误"))
}
}, 2000)
})
}).catch(handleApiError)
}

View File

@ -1,12 +1,13 @@
import { request } from "@/utils/service"
import type * as Login from "./types/login"
import { handleApiError } from "@/utils/error-handler"
/** 获取登录验证码 */
export function getLoginCodeApi() {
return request<Login.LoginCodeResponseData>({
url: "login/code",
method: "get"
})
}).catch(handleApiError)
}
/** 登录并返回 Token */
@ -15,7 +16,7 @@ export function loginApi(data: Login.LoginRequestData) {
url: "users/login",
method: "post",
data
})
}).catch(handleApiError)
}
/** 获取用户详情 */
@ -23,5 +24,5 @@ export function getUserInfoApi() {
return request<Login.UserInfoResponseData>({
url: "users/info",
method: "get"
})
}).catch(handleApiError)
}

View File

@ -1,5 +1,6 @@
import { request } from "@/utils/service"
import type * as Table from "./types/table"
import { handleApiError } from "@/utils/error-handler"
/** 增 */
export function createTableDataApi(data: Table.CreateTableRequestData) {
@ -7,7 +8,7 @@ export function createTableDataApi(data: Table.CreateTableRequestData) {
url: "table",
method: "post",
data
})
}).catch(handleApiError)
}
/** 删 */
@ -15,7 +16,7 @@ export function deleteTableDataApi(id: string) {
return request({
url: `table/${id}`,
method: "delete"
})
}).catch(handleApiError)
}
/** 改 */
@ -24,7 +25,7 @@ export function updateTableDataApi(data: Table.UpdateTableRequestData) {
url: "table",
method: "put",
data
})
}).catch(handleApiError)
}
/** 查 */
@ -33,5 +34,5 @@ export function getTableDataApi(params: Table.GetTableRequestData) {
url: "table",
method: "get",
params
})
}).catch(handleApiError)
}

View File

@ -10,6 +10,7 @@ import routeSettings from "@/config/route"
import isWhiteList from "@/config/white-list"
import NProgress from "nprogress"
import "nprogress/nprogress.css"
import { handleApiError } from "@/utils/error-handler"
const { setTitle } = useTitle()
NProgress.configure({ showSpinner: false })
@ -61,10 +62,11 @@ router.beforeEach(async (to, _from, next) => {
// 确保添加路由已完成
// 设置 replace: true, 因此导航将不会留下历史记录
next({ ...to, replace: true })
} catch (err: any) {
} catch (error: any) {
// 过程中发生任何错误,都直接重置 Token并重定向到登录页面
handleApiError(error)
userStore.resetToken()
ElMessage.error(err.message || "路由守卫过程发生错误")
ElMessage.error(error.message || "路由守卫过程发生错误")
NProgress.done()
next("/login")
}

View File

@ -5,6 +5,7 @@ import { type RouteRecordRaw } from "vue-router"
import { constantRoutes, asyncRoutes } from "@/router"
import { flatMultiLevelRoutes } from "@/router/helper"
import routeSettings from "@/config/route"
import { handleApiError } from "@/utils/error-handler"
const hasPermission = (roles: string[], route: RouteRecordRaw) => {
const routeRoles = route.meta?.roles
@ -30,9 +31,15 @@ export const usePermissionStore = defineStore("permission", () => {
const dynamicRoutes = ref<RouteRecordRaw[]>([])
const setRoutes = (roles: string[]) => {
const accessedRoutes = routeSettings.async ? filterAsyncRoutes(asyncRoutes, roles) : asyncRoutes
routes.value = constantRoutes.concat(accessedRoutes)
dynamicRoutes.value = routeSettings.thirdLevelRouteCache ? flatMultiLevelRoutes(accessedRoutes) : accessedRoutes
try {
const accessedRoutes = routeSettings.async ? filterAsyncRoutes(asyncRoutes, roles) : asyncRoutes
routes.value = constantRoutes.concat(accessedRoutes)
dynamicRoutes.value = routeSettings.thirdLevelRouteCache ? flatMultiLevelRoutes(accessedRoutes) : accessedRoutes
} catch (error) {
if (error instanceof Error) {
handleApiError(error)
}
}
}
return { routes, dynamicRoutes, setRoutes }

View File

@ -10,6 +10,7 @@ import { loginApi, getUserInfoApi } from "@/api/login"
import { type LoginRequestData } from "@/api/login/types/login"
import { type RouteRecordRaw } from "vue-router"
import routeSettings from "@/config/route"
import { handleApiError } from "@/utils/error-handler"
export const useUserStore = defineStore("user", () => {
const token = ref<string>(getToken() || "")
@ -24,19 +25,34 @@ export const useUserStore = defineStore("user", () => {
const setRoles = (value: string[]) => {
roles.value = value
}
/** 登录 */
const login = async ({ username, password, code }: LoginRequestData) => {
const { data } = await loginApi({ username, password, code })
setToken(data.token)
token.value = data.token
try {
const { data } = await loginApi({ username, password, code })
setToken(data.token)
token.value = data.token
} catch (error) {
if (error instanceof Error) {
handleApiError(error)
}
}
}
/** 获取用户详情 */
const getInfo = async () => {
const { data } = await getUserInfoApi()
username.value = data.username
// 验证返回的 roles 是否为一个非空数组,否则塞入一个没有任何作用的默认角色,防止路由守卫逻辑进入无限循环
roles.value = data.roles?.length > 0 ? data.roles : routeSettings.defaultRoles
try {
const { data } = await getUserInfoApi()
username.value = data.username
// 验证返回的 roles 是否为一个非空数组,否则塞入一个没有任何作用的默认角色,防止路由守卫逻辑进入无限循环
roles.value = data.roles?.length > 0 ? data.roles : routeSettings.defaultRoles
} catch (error) {
if (error instanceof Error) {
handleApiError(error)
}
}
}
/** 切换角色 */
const changeRoles = async (role: string) => {
const newToken = "token-" + role

View File

@ -0,0 +1,46 @@
import { AxiosError } from "./types/axios-error"
// API 错误处理
export function handleApiError(error: Error) {
const axiosError = error as AxiosError
if (axiosError.response) {
// 服务器响应了请求,但状态码不在 2xx 范围内
console.error("API 错误,状态码:", axiosError.response.status)
console.error("错误详情:", axiosError.response.data)
// 根据错误码进行分类处理
switch (axiosError.response.status) {
case 400:
console.error("请求参数错误")
break
case 401:
console.error("未授权访问")
break
case 403:
console.error("禁止访问")
break
case 404:
console.error("资源未找到")
break
case 500:
console.error("服务器内部错误")
break
default:
console.error("未知 API 错误")
break
}
} else if (axiosError.request) {
// 请求已发出,但未收到响应
console.error("服务器无响应")
} else {
// 在设置请求时发生了一些问题
console.error("请求错误:", axiosError.message)
}
return Promise.reject(error)
}
// 通用错误处理
export function handleGeneralError(error: Error) {
console.error("发生错误:", error)
return Promise.reject(error)
}

View File

@ -0,0 +1,8 @@
export interface AxiosError {
response?: {
status: number
data: any
}
request?: any
message: string
}

View File

@ -7,6 +7,7 @@ import { User, Lock, Key, Picture, Loading } from "@element-plus/icons-vue"
import { getLoginCodeApi } from "@/api/login"
import { type LoginRequestData } from "@/api/login/types/login"
import ThemeSwitch from "@/components/ThemeSwitch/index.vue"
import { handleApiError } from "@/utils/error-handler"
const router = useRouter()
@ -60,9 +61,11 @@ const createCode = () => {
loginFormData.code = ""
//
codeUrl.value = ""
getLoginCodeApi().then((res) => {
codeUrl.value = res.data
})
getLoginCodeApi()
.then((res) => {
codeUrl.value = res.data
})
.catch(handleApiError)
}
/** 初始化验证码 */

View File

@ -5,6 +5,7 @@ import { type GetTableData } from "@/api/table/types/table"
import { type FormInstance, type FormRules, ElMessage, ElMessageBox } from "element-plus"
import { Search, Refresh, CirclePlus, Delete, Download, RefreshRight } from "@element-plus/icons-vue"
import { usePagination } from "@/hooks/usePagination"
import { handleApiError } from "@/utils/error-handler"
defineOptions({
//
@ -105,7 +106,8 @@ const getTableData = () => {
paginationData.total = res.data.total
tableData.value = res.data.list
})
.catch(() => {
.catch((error) => {
handleApiError(error)
tableData.value = []
})
.finally(() => {