refactor(aixos): 完善axios错误处理流程

This commit is contained in:
chen.home 2023-01-12 00:25:48 +08:00
parent 204cb7ad9f
commit ef6392615b
5 changed files with 442 additions and 323 deletions

View File

@ -10,13 +10,13 @@ import {
} from '@/config';
type ErrorStatus = keyof typeof ERROR_STATUS;
/**
* @description: axios返回的http错误
* @description: axioshttp错误
* @param {AxiosError} err
* @return {*}
*/
export function handleHttpError(err: AxiosError) {
export function handleAxiosError(err: AxiosError) {
const error = {
type: 'axios',
type: 'Axios',
code: DEFAULT_REQUEST_ERROR_CODE,
msg: DEFAULT_REQUEST_ERROR_MSG,
};
@ -43,9 +43,42 @@ export function handleHttpError(err: AxiosError) {
/**
* @description: axios请求成功
* @param {AxiosResponse} err
* @param {AxiosResponse} response
* @return {*}
*/
// export function handleResponseError(err: AxiosResponse) {}
export function handleResponseError(response: AxiosResponse) {
const error = {
type: 'Axios',
code: DEFAULT_REQUEST_ERROR_CODE,
msg: DEFAULT_REQUEST_ERROR_MSG,
};
// export function handleBusinessError() {}
if (!window.navigator.onLine) {
// 网路错误
Object.assign(error, { code: NETWORK_ERROR_CODE, msg: NETWORK_ERROR_MSG });
} else {
// 请求成功的状态码非200的错误
const errorCode: ErrorStatus = response.status as ErrorStatus;
const msg = ERROR_STATUS[errorCode] || DEFAULT_REQUEST_ERROR_MSG;
Object.assign(error, { type: 'Response', code: errorCode, msg });
}
return error;
}
/**
* @description:
* @param {Record} apiData
* @param {Service} config axios字段配置
* @return {*}
*/
export function handleBusinessError(apiData: Record<string, any>, config: Service.BackendResultConfig) {
const { codeKey, msgKey } = config;
const error = {
type: 'Business',
code: apiData[codeKey],
msg: apiData[msgKey],
};
return error;
}

View File

@ -1,8 +1,7 @@
import axios from 'axios';
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios';
import { getToken } from '@/utils';
// import { handleHttpError, handleResponseError, handleBusinessError } from './help';
import { handleHttpError } from './help';
import { handleAxiosError, handleResponseError, handleBusinessError } from './handle';
/**
* @description: axios请求类
@ -34,34 +33,44 @@ export default class createAxiosInstance {
// 设置类拦截器的函数
setInterceptor() {
this.instance.interceptors.request.use(
(config: AxiosRequestConfig) => {
// 一般会请求拦截里面加token
config.headers!.Authorization = getToken();
return config;
async (config) => {
const handleConfig = { ...config };
if (handleConfig.headers) {
// 设置token
typeof handleConfig.headers.set === 'function' &&
handleConfig.headers.set('Authorization', `Bearer ${getToken() || ''}`);
}
return handleConfig;
},
(err: any) => Promise.reject(err)
(axiosError: AxiosError) => {
const error = handleAxiosError(axiosError);
Promise.reject(error);
}
);
this.instance.interceptors.response.use(
// 因为接口的数据都在res.data下所以直接返回res.data
// 系统如果有自定义code也可以在这里处理
(res: AxiosResponse) => {
// apiData 是 API 返回的数据
const apiData = res.data;
// 这个 Code 是和后端约定的业务 Code
const code = String(res.data[this.backendConfig.codeKey]);
switch (code) {
case this.backendConfig.successCode:
// code === 200 代表没有错误,直接返回约定的数据内容
async (response) => {
const { status } = response;
if (status === 200) {
// 获取返回的数据
const apiData = response.data;
const { codeKey, successCode } = this.backendConfig;
// 请求成功
if (apiData[codeKey] == successCode) {
// return apiData[dataKey];
return apiData;
default:
// 不是正确的 Code,返回错误提示信息
return Promise.reject(new Error(`Error:${this.backendConfig.dataKey}`));
}
//TODO 添加刷新token的操作
// 业务请求失败
const error = handleBusinessError(apiData, this.backendConfig);
return Promise.reject(error);
}
// 接口请求失败
const error = handleResponseError(response);
return Promise.reject(error);
},
(err: AxiosError) => {
// 这里用来处理http常见错误进行全局提示等
const error = handleHttpError(err);
// 这里是AxiosError类型所以一般我们只reject我们需要的响应即可
(axiosError: AxiosError) => {
// 处理http常见错误进行全局提示等
const error = handleAxiosError(axiosError);
return Promise.reject(error);
}
);

View File

@ -7,130 +7,130 @@ import { fetchUserRoutes } from '@/service';
import { staticRoutes } from '@/router/modules';
interface RoutesStatus {
isInitAuthRoute: boolean;
menus: any;
userRoutes: AppRoute.Route[];
activeMenu: string | null;
authRouteMode: ImportMetaEnv['VITE_AUTH_ROUTE_MODE'];
cacheRoutes: string[];
isInitAuthRoute: boolean;
menus: any;
userRoutes: AppRoute.Route[];
activeMenu: string | null;
authRouteMode: ImportMetaEnv['VITE_AUTH_ROUTE_MODE'];
cacheRoutes: string[];
}
export const useRouteStore = defineStore('route-store', {
state: (): RoutesStatus => {
return {
userRoutes: [],
isInitAuthRoute: false,
menus: [],
activeMenu: null,
authRouteMode: import.meta.env.VITE_AUTH_ROUTE_MODE,
cacheRoutes: [],
};
},
actions: {
resetRouteStore() {
this.resetRoutes();
this.$reset();
},
resetRoutes() {
/* 删除后面添加的路由 */
router.removeRoute('appRoot');
},
/* 根据当前路由的name生成面包屑数据 */
createBreadcrumbFromRoutes(routeName = '/', userRoutes: AppRoute.Route[]) {
const path: AppRoute.Route[] = [];
// 筛选所有包含目标的各级路由组合成一维数组
const getPathfromRoutes = (routeName: string, userRoutes: AppRoute.Route[]) => {
userRoutes.forEach((item) => {
if (this.hasPathinAllPath(routeName, item)) {
path.push(item);
if (item.children && item.children.length !== 0) {
getPathfromRoutes(routeName, item.children);
}
}
});
};
getPathfromRoutes(routeName, userRoutes);
return path;
},
/* 判断当前路由和子路由中是否存在为routeName的路由 */
hasPathinAllPath(routeName: string, userRoutes: AppRoute.Route) {
if (userRoutes.name === routeName) {
return true;
}
if (userRoutes.children && userRoutes.children.length !== 0) {
const arr: boolean[] = [];
userRoutes.children.forEach((item) => {
arr.push(this.hasPathinAllPath(routeName, item));
});
return arr.some((item) => {
return item;
});
}
return false;
},
/* 设置当前高亮的菜单key */
setActiveMenu(key: string) {
this.activeMenu = key;
},
/* 生成侧边菜单的数据 */
createMenus(userRoutes: AppRoute.Route[]) {
this.userRoutes = userRoutes;
this.menus = this.transformAuthRoutesToMenus(userRoutes);
},
/* 初始化动态路由 */
async initDynamicRoute() {
// 根据用户id来获取用户的路由
const { userId } = getUserInfo();
const { data } = await fetchUserRoutes(userId);
// 根据用户返回的路由表来生成真实路由
const appRoutes = await createDynamicRoutes(data);
// 生成侧边菜单
await this.createMenus(data);
// 插入路由表
router.addRoute(appRoutes);
},
/* 初始化静态路由 */
async initStaticRoute() {
// 根据静态路由表来生成真实路由
const appRoutes = await createDynamicRoutes(staticRoutes);
// 生成侧边菜单
await this.createMenus(staticRoutes);
// 插入路由表
router.addRoute(appRoutes);
},
//* 将返回的路由表渲染成侧边栏 */
transformAuthRoutesToMenus(userRoutes: AppRoute.Route[]): MenuOption[] {
return userRoutes
.filter((item) => {
return !item.meta.hide;
})
.map((item) => {
const target: MenuOption = {
label: item.meta.title,
key: item.path,
};
// 判断有无图标
if (item.meta.icon) {
target.icon = renderIcon(item.meta.icon);
}
// 判断子元素
if (item.children) {
const children = this.transformAuthRoutesToMenus(item.children);
// 只有子元素有且不为空时才添加
if (children.length !== 0) {
target.children = children;
}
}
return target;
});
},
async initAuthRoute() {
this.isInitAuthRoute = false;
if (this.authRouteMode === 'dynamic') {
await this.initDynamicRoute();
} else {
await this.initStaticRoute();
}
this.isInitAuthRoute = true;
},
},
state: (): RoutesStatus => {
return {
userRoutes: [],
isInitAuthRoute: false,
menus: [],
activeMenu: null,
authRouteMode: import.meta.env.VITE_AUTH_ROUTE_MODE,
cacheRoutes: [],
};
},
actions: {
resetRouteStore() {
this.resetRoutes();
this.$reset();
},
resetRoutes() {
/* 删除后面添加的路由 */
router.removeRoute('appRoot');
},
/* 根据当前路由的name生成面包屑数据 */
createBreadcrumbFromRoutes(routeName = '/', userRoutes: AppRoute.Route[]) {
const path: AppRoute.Route[] = [];
// 筛选所有包含目标的各级路由组合成一维数组
const getPathfromRoutes = (routeName: string, userRoutes: AppRoute.Route[]) => {
userRoutes.forEach((item) => {
if (this.hasPathinAllPath(routeName, item)) {
path.push(item);
if (item.children && item.children.length !== 0) {
getPathfromRoutes(routeName, item.children);
}
}
});
};
getPathfromRoutes(routeName, userRoutes);
return path;
},
/* 判断当前路由和子路由中是否存在为routeName的路由 */
hasPathinAllPath(routeName: string, userRoutes: AppRoute.Route) {
if (userRoutes.name === routeName) {
return true;
}
if (userRoutes.children && userRoutes.children.length !== 0) {
const arr: boolean[] = [];
userRoutes.children.forEach((item) => {
arr.push(this.hasPathinAllPath(routeName, item));
});
return arr.some((item) => {
return item;
});
}
return false;
},
/* 设置当前高亮的菜单key */
setActiveMenu(key: string) {
this.activeMenu = key;
},
/* 生成侧边菜单的数据 */
createMenus(userRoutes: AppRoute.Route[]) {
this.userRoutes = userRoutes;
this.menus = this.transformAuthRoutesToMenus(userRoutes);
},
/* 初始化动态路由 */
async initDynamicRoute() {
// 根据用户id来获取用户的路由
const { userId } = getUserInfo();
const { data: routes } = await fetchUserRoutes(userId);
// 根据用户返回的路由表来生成真实路由
const appRoutes = await createDynamicRoutes(routes);
// 生成侧边菜单
await this.createMenus(routes);
// 插入路由表
router.addRoute(appRoutes);
},
/* 初始化静态路由 */
async initStaticRoute() {
// 根据静态路由表来生成真实路由
const appRoutes = await createDynamicRoutes(staticRoutes);
// 生成侧边菜单
await this.createMenus(staticRoutes);
// 插入路由表
router.addRoute(appRoutes);
},
//* 将返回的路由表渲染成侧边栏 */
transformAuthRoutesToMenus(userRoutes: AppRoute.Route[]): MenuOption[] {
return userRoutes
.filter((item) => {
return !item.meta.hide;
})
.map((item) => {
const target: MenuOption = {
label: item.meta.title,
key: item.path,
};
// 判断有无图标
if (item.meta.icon) {
target.icon = renderIcon(item.meta.icon);
}
// 判断子元素
if (item.children) {
const children = this.transformAuthRoutesToMenus(item.children);
// 只有子元素有且不为空时才添加
if (children.length !== 0) {
target.children = children;
}
}
return target;
});
},
async initAuthRoute() {
this.isInitAuthRoute = false;
if (this.authRouteMode === 'dynamic') {
await this.initDynamicRoute();
} else {
await this.initStaticRoute();
}
this.isInitAuthRoute = true;
},
},
});

View File

@ -5,72 +5,72 @@ const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7;
const prefix = import.meta.env.VITE_STORAGE_PREFIX as string;
interface StorageData {
value: any;
expire: number | null;
value: any;
expire: number | null;
}
/**
* LocalStorage部分操作
*/
export const setLocal = (key: string, value: unknown, expire: number | null = DEFAULT_CACHE_TIME): void => {
const storageData: StorageData = {
value,
expire: expire !== null ? new Date().getTime() + expire * 1000 : null,
};
const json = JSON.stringify(storageData);
localStorage.setItem(prefix + key, json);
const storageData: StorageData = {
value,
expire: expire !== null ? new Date().getTime() + expire * 1000 : null,
};
const json = JSON.stringify(storageData);
localStorage.setItem(prefix + key, json);
};
export const getLocal = (key: string) => {
const json = localStorage.getItem(prefix + key);
if (!json) return null;
const json = localStorage.getItem(prefix + key);
if (!json) return null;
let storageData: StorageData | null = null;
storageData = JSON.parse(json as string);
let storageData: StorageData | null = null;
storageData = JSON.parse(json as string);
if (storageData) {
const { value, expire } = storageData;
if (expire === null || expire >= Date.now()) {
return value;
}
}
removeLocal(key);
return null;
if (storageData) {
const { value, expire } = storageData;
if (expire === null || expire >= Date.now()) {
return value;
}
}
removeLocal(key);
return null;
};
export const removeLocal = (key: string): void => {
localStorage.removeItem(prefix + key);
localStorage.removeItem(prefix + key);
};
export const clearLocal = (): void => {
localStorage.clear();
localStorage.clear();
};
/**
* sessionStorage部分操作
*/
export function setSession(key: string, value: unknown) {
const json = JSON.stringify(value);
sessionStorage.setItem(prefix + key, json);
const json = JSON.stringify(value);
sessionStorage.setItem(prefix + key, json);
}
export function getSession<T>(key: string) {
const json = sessionStorage.getItem(prefix + key);
let data: T | null = null;
if (json) {
try {
data = JSON.parse(json);
} catch {
// 防止解析失败
}
}
return data;
const json = sessionStorage.getItem(prefix + key);
let data: T | null = null;
if (json) {
try {
data = JSON.parse(json);
} catch {
// 防止解析失败
}
}
return data;
}
export function removeSession(key: string) {
window.sessionStorage.removeItem(prefix + key);
window.sessionStorage.removeItem(prefix + key);
}
export function clearSession() {
window.sessionStorage.clear();
window.sessionStorage.clear();
}

View File

@ -1,29 +1,76 @@
<template>
<n-space vertical size="large">
<n-space
vertical
size="large"
>
<n-card>
<n-form ref="formRef" :model="model" label-placement="left" :show-feedback="false">
<n-grid :x-gap="30" :cols="18">
<n-form-item-gi :span="4" label="姓名" path="condition_1">
<n-input v-model:value="model.condition_1" placeholder="请输入" />
<n-form
ref="formRef"
:model="model"
label-placement="left"
:show-feedback="false"
>
<n-grid
:x-gap="30"
:cols="18"
>
<n-form-item-gi
:span="4"
label="姓名"
path="condition_1"
>
<n-input
v-model:value="model.condition_1"
placeholder="请输入"
/>
</n-form-item-gi>
<n-form-item-gi :span="4" label="年龄" path="condition_2">
<n-input v-model:value="model.condition_2" placeholder="请输入" />
<n-form-item-gi
:span="4"
label="年龄"
path="condition_2"
>
<n-input
v-model:value="model.condition_2"
placeholder="请输入"
/>
</n-form-item-gi>
<n-form-item-gi :span="4" label="性别" path="condition_3">
<n-input v-model:value="model.condition_3" placeholder="请输入" />
<n-form-item-gi
:span="4"
label="性别"
path="condition_3"
>
<n-input
v-model:value="model.condition_3"
placeholder="请输入"
/>
</n-form-item-gi>
<n-form-item-gi :span="4" label="地址" path="condition_4">
<n-input v-model:value="model.condition_4" placeholder="请输入" />
<n-form-item-gi
:span="4"
label="地址"
path="condition_4"
>
<n-input
v-model:value="model.condition_4"
placeholder="请输入"
/>
</n-form-item-gi>
<n-gi :span="1">
<n-button type="primary">
<template #icon><i-icon-park-outline-search /></template>
<template #icon>
<i-icon-park-outline-search />
</template>
搜索
</n-button>
</n-gi>
<n-gi :span="1">
<n-button strong secondary @click="handleResetSearch">
<template #icon><i-icon-park-outline-redo /></template>
<n-button
strong
secondary
@click="handleResetSearch"
>
<template #icon>
<i-icon-park-outline-redo />
</template>
重置
</n-button>
</n-gi>
@ -31,24 +78,54 @@
</n-form>
</n-card>
<n-card>
<n-space vertical size="large">
<n-space
vertical
size="large"
>
<div class="flex gap-4">
<n-button type="primary" @click="handleAddTable">
<template #icon><i-icon-park-outline-add-one /></template>
<n-button
type="primary"
@click="handleAddTable"
>
<template #icon>
<i-icon-park-outline-add-one />
</template>
新建
</n-button>
<n-button strong secondary>
<template #icon><i-icon-park-outline-afferent /></template>
<n-button
strong
secondary
>
<template #icon>
<i-icon-park-outline-afferent />
</template>
批量导入
</n-button>
<n-button strong secondary class="ml-a">
<template #icon><i-icon-park-outline-download /></template>
<n-button
strong
secondary
class="ml-a"
>
<template #icon>
<i-icon-park-outline-download />
</template>
下载
</n-button>
</div>
<n-data-table :columns="columns" :data="listData" :loading="loading" />
<Pagination :count="100" @change="changePage" />
<TableModal v-model:visible="visible" :type="modalType" :modal-data="editData" />
<n-data-table
:columns="columns"
:data="listData"
:loading="loading"
/>
<Pagination
:count="100"
@change="changePage"
/>
<TableModal
v-model:visible="visible"
:type="modalType"
:modal-data="editData"
/>
</n-space>
</n-card>
</n-space>
@ -70,145 +147,145 @@ const model = ref({ ...initialModel });
const formRef = ref<FormInst | null>();
const columns: DataTableColumns = [
{
title: '姓名',
align: 'center',
key: 'name',
},
{
title: '年龄',
align: 'center',
key: 'age',
},
{
title: '性别',
align: 'center',
key: 'gender',
render: (row) => {
const rowData = row as unknown as CommonList.UserList;
const tagType = {
'0': {
label: '女',
type: 'primary',
},
'1': {
label: '男',
type: 'success',
},
} as const;
if (rowData.gender) {
return <NTag type={tagType[rowData.gender].type}>{tagType[rowData.gender].label}</NTag>;
}
},
},
{
title: '邮箱',
align: 'center',
key: 'email',
},
{
title: '地址',
align: 'center',
key: 'address',
},
{
title: '角色',
align: 'center',
key: 'role',
render: (row) => {
const rowData = row as unknown as CommonList.UserList;
const tagType = {
super: 'primary',
admin: 'warning',
user: 'success',
} as const;
return <NTag type={tagType[rowData.role]}>{rowData.role}</NTag>;
},
},
{
title: '状态',
align: 'center',
key: 'disabled',
render: (row) => {
const rowData = row as unknown as CommonList.UserList;
{
title: '姓名',
align: 'center',
key: 'name',
},
{
title: '年龄',
align: 'center',
key: 'age',
},
{
title: '性别',
align: 'center',
key: 'gender',
render: (row) => {
const rowData = row as unknown as CommonList.UserList;
const tagType = {
'0': {
label: '女',
type: 'primary',
},
'1': {
label: '男',
type: 'success',
},
} as const;
if (rowData.gender) {
return <NTag type={tagType[rowData.gender].type}>{tagType[rowData.gender].label}</NTag>;
}
},
},
{
title: '邮箱',
align: 'center',
key: 'email',
},
{
title: '地址',
align: 'center',
key: 'address',
},
{
title: '角色',
align: 'center',
key: 'role',
render: (row) => {
const rowData = row as unknown as CommonList.UserList;
const tagType = {
super: 'primary',
admin: 'warning',
user: 'success',
} as const;
return <NTag type={tagType[rowData.role]}>{rowData.role}</NTag>;
},
},
{
title: '状态',
align: 'center',
key: 'disabled',
render: (row) => {
const rowData = row as unknown as CommonList.UserList;
return (
<NSwitch value={rowData.disabled} onUpdateValue={(disabled) => handleUpdateDisabled(disabled, rowData.id)}>
{{ checked: () => '启用', unchecked: () => '禁用' }}
</NSwitch>
);
},
},
{
title: '操作',
align: 'center',
key: 'actions',
render: (row) => {
const rowData = row as unknown as CommonList.UserList;
return (
<NSpace justify={'center'}>
<NButton size={'small'} onClick={() => handleEditTable(rowData)}>
编辑
</NButton>
<NPopconfirm onPositiveClick={() => sendMail(rowData.id)}>
{{
default: () => '确认删除',
trigger: () => <NButton size={'small'}>删除</NButton>,
}}
</NPopconfirm>
</NSpace>
);
},
},
return (
<NSwitch value={rowData.disabled} onUpdateValue={(disabled) => handleUpdateDisabled(disabled, rowData.id)}>
{{ checked: () => '启用', unchecked: () => '禁用' }}
</NSwitch>
);
},
},
{
title: '操作',
align: 'center',
key: 'actions',
render: (row) => {
const rowData = row as unknown as CommonList.UserList;
return (
<NSpace justify={'center'}>
<NButton size={'small'} onClick={() => handleEditTable(rowData)}>
编辑
</NButton>
<NPopconfirm onPositiveClick={() => sendMail(rowData.id)}>
{{
default: () => '确认删除',
trigger: () => <NButton size={'small'}>删除</NButton>,
}}
</NPopconfirm>
</NSpace>
);
},
},
];
const sendMail = (id: number) => {
window.$message.success(`用户id:${id}`);
window.$message.success(`用户id:${id}`);
};
function handleUpdateDisabled(disabled: boolean, id: number) {
const index = listData.value.findIndex((item) => item.id === id);
if (index > -1) {
listData.value[index].disabled = disabled;
}
const index = listData.value.findIndex((item) => item.id === id);
if (index > -1) {
listData.value[index].disabled = disabled;
}
}
const listData = ref<CommonList.UserList[]>([]);
onMounted(() => {
getUserList();
getUserList();
});
async function getUserList() {
startLoading();
await fetchUserList().then((res) => {
listData.value = res.data;
endLoading();
});
startLoading();
await fetchUserList().then((res) => {
listData.value = res.data;
endLoading();
});
}
function changePage(page: number, size: number) {
window.$message.success(`分页器:${page},${size}`);
window.$message.success(`分页器:${page},${size}`);
}
function handleResetSearch() {
model.value = { ...initialModel };
model.value = { ...initialModel };
}
type ModalType = 'add' | 'edit';
const modalType = ref<ModalType>('add');
function setModalType(type: ModalType) {
modalType.value = type;
modalType.value = type;
}
const editData = ref<CommonList.UserList | null>(null);
function setEditData(data: CommonList.UserList | null) {
editData.value = data;
editData.value = data;
}
function handleEditTable(rowData: CommonList.UserList) {
setEditData(rowData);
setModalType('edit');
openModal();
setEditData(rowData);
setModalType('edit');
openModal();
}
function handleAddTable() {
openModal();
setModalType('add');
openModal();
setModalType('add');
}
</script>