axios(前端Http调用组件)

一般会在重新统一封装下axios的调用组件,放在项目src的api目录下,里面会自定义axios的配置信息,实例化axios对象,添加请求和响应拦截器功能.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#前端请求后台的组件
import axios, { AxiosInstance, AxiosError, AxiosRequestConfig, InternalAxiosRequestConfig, AxiosResponse } from "axios";
import { showFullScreenLoading, tryHideFullScreenLoading } from "@/config/serviceLoading";
import { ResultData } from "@/api/interface";
import { ResultEnum } from "@/enums/httpEnum";
import { checkStatus } from "./helper/checkStatus";
import { ElMessage } from "element-plus";
import { GlobalStore } from "@/stores";
import { LOGIN_URL } from "@/config/config";
import router from "@/routers";

const config = {
// 默认地址请求地址,可在 .env.*** 文件中修改
baseURL: import.meta.env.VITE_API_URL as string,
// 设置超时时间(60s)
timeout: ResultEnum.TIMEOUT as number,
// 跨域时候允许携带凭证
withCredentials: true
};

class RequestHttp {
service: AxiosInstance;
public constructor(config: AxiosRequestConfig) {
// 实例化axios
this.service = axios.create(config);

/**
* @description 请求拦截器
* 客户端发送请求 -> [请求拦截器] -> 服务器
* token校验(JWT) : 接受服务器返回的token,存储到vuex/pinia/本地储存当中
*/
this.service.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
const globalStore = GlobalStore();
// * 如果当前请求不需要显示 loading,在 api 服务中通过指定的第三个参数: { headers: { noLoading: true } }来控制不显示loading,参见loginApi
config.headers!.noLoading || showFullScreenLoading();
const token = globalStore.token;
if (config.headers && typeof config.headers?.set === "function") config.headers.set("token", token);
return config;
},
(error: AxiosError) => {
return Promise.reject(error);
}
);

/**
* @description 响应拦截器
* 服务器换返回信息 -> [拦截统一处理] -> 客户端JS获取到信息
*/
this.service.interceptors.response.use(
(response: AxiosResponse) => {
const { data } = response;
const globalStore = GlobalStore();
// * 在请求结束后,并关闭请求 loading
tryHideFullScreenLoading();
// * 登陆失效(code == 401)
if (data.code == ResultEnum.OVERDUE) {
ElMessage.error(data.msg);
globalStore.setToken("");
router.replace(LOGIN_URL);
return Promise.reject(data);
}
// * 全局错误信息拦截(防止下载文件得时候返回数据流,没有code,直接报错)
if (data.code && data.code !== ResultEnum.SUCCESS) {
ElMessage.error(data.msg);
return Promise.reject(data);
}
// * 成功请求(在页面上除非特殊情况,否则不用在页面处理失败逻辑)
return data;
},
async (error: AxiosError) => {
const { response } = error;
tryHideFullScreenLoading();
// 请求超时 && 网络错误单独判断,没有 response
if (error.message.indexOf("timeout") !== -1) ElMessage.error("请求超时!请您稍后重试");
if (error.message.indexOf("Network Error") !== -1) ElMessage.error("网络错误!请您稍后重试");
// 根据响应的错误状态码,做不同的处理
if (response) checkStatus(response.status);
// 服务器结果都没有返回(可能服务器错误可能客户端断网),断网处理:可以跳转到断网页面
if (!window.navigator.onLine) router.replace("/500");
return Promise.reject(error);
}
);
}

// * 常用请求方法封装
get<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
return this.service.get(url, { params, ..._object });
}
post<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
return this.service.post(url, params, _object);
}
put<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
return this.service.put(url, params, _object);
}
delete<T>(url: string, params?: any, _object = {}): Promise<ResultData<T>> {
return this.service.delete(url, { params, ..._object });
}
download(url: string, params?: object, _object = {}): Promise<BlobPart> {
return this.service.post(url, params, { ..._object, responseType: "blob" });
}
}

export default new RequestHttp(config);

使用示例

对应模块组件需要请求,可在api的modules目录下定义对应的ts文件路径

login.ts

1
2
3
4
5
6
7
8
9
10
11
12
import { Login, ResultData } from "@/api/interface/index";
import { PORT1 } from "@/api/config/servicePort";
import http from "@/api";

/**
* @name 登录模块
*/
// * 用户登录
export const loginApi = (params: Login.ReqLoginForm) => {
// return Logininfo;
return http.post<Login.ResLogin>(PORT1 + `/login`, params, { headers: { noLoading: true } }); // 正常 post json 请求 ==> application/json
};

对应请求和响应的自定义对象描述需要进行type类型定义,一般放在对应组件目录下,定义type.ts文件

index.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
export namespace Login {
export interface ResCode {
needVerificationCode: string;
}
export interface ReqLoginForm {
username: string;
password: string | boolean;
code: string;
}
export interface ResLogin {
token: string;
isUpdatePwd: string;
}
export interface ResAuthButtons {
[key: string]: string[];
}
export interface ResChangePass {
oldPassword: string | boolean;
newPassword: string | boolean;
confirmPassword: string | boolean;
}
}

pinia(前端统一状态管理组件)

作用记录前端的全局状态管理,使用piniaPluginPersistedstate插件来进行持久化,默认保存在localStorage里面,目录结果通常在store包下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import { defineStore, createPinia } from "pinia";
import { GlobalState, ThemeConfigProps, AssemblySizeType } from "./interface";
import { DEFAULT_PRIMARY } from "@/config/config";
import piniaPersistConfig from "@/config/piniaPersist";
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";

// defineStore 调用后返回一个函数,调用该函数获得 Store 实体
export const GlobalStore = defineStore({
// id: 必须的,在所有 Store 中唯一
id: "GlobalState",
// state: 返回对象的函数
state: (): GlobalState => ({
//当前页title的key
title: "",
// token
token: "",
// userInfo
userInfo: "",
// element组件大小
assemblySize: "default",
// language
language: "",
// themeConfig
themeConfig: {
// 当前页面是否全屏
maximize: false,
// 布局切换 ==> 纵向:vertical | 经典:classic | 横向:transverse | 分栏:columns
layout: "vertical",
// 默认 primary 主题颜色
primary: DEFAULT_PRIMARY,
// 深色模式
isDark: false,
// 灰色模式
isGrey: false,
// 色弱模式
isWeak: false,
// 折叠菜单
isCollapse: false,
// 面包屑导航
breadcrumb: true,
// 面包屑导航图标
breadcrumbIcon: false,
// 标签页
tabs: true,
// 标签页图标
tabsIcon: false,
// 页脚
footer: true
}
}),
getters: {},
actions: {
// setToken
setToken(token: string) {
this.token = token;
},
// setUserInfo
setUserInfo(userInfo: any) {
this.userInfo = userInfo;
},
// setAssemblySizeSize
setAssemblySizeSize(assemblySize: AssemblySizeType) {
this.assemblySize = assemblySize;
},
// updateLanguage
updateLanguage(language: string) {
this.language = language;
},
// setThemeConfig
setThemeConfig(themeConfig: ThemeConfigProps) {
this.themeConfig = themeConfig;
},
// setTitle
setTitle(title: string) {
this.title = title;
}
},
persist: piniaPersistConfig("GlobalState")
});

// piniaPersist(持久化)
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);

export default pinia;

piniaPersistConfig配置文件说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { PersistedStateOptions } from "pinia-plugin-persistedstate";

/**
* @description pinia持久化参数配置
* @param {String} key 存储到持久化的 name
* @param {Array} paths 需要持久化的 state name
* @return persist
* */
const piniaPersistConfig = (key: string, paths?: string[]) => {
const persist: PersistedStateOptions = {
key,
storage: localStorage,
// storage: sessionStorage,
paths
};
return persist;
};

export default piniaPersistConfig;

需要在main.ts里面注册

1
createApp(App).use(pinia)

持久化道localStorage后,可通过以下方法来进行取值和设值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* @description 获取localStorage
* @param {String} key Storage名称
* @return string
*/
export function localGet(key: string) {
const value = window.localStorage.getItem(key);
try {
return JSON.parse(window.localStorage.getItem(key) as string);
} catch (error) {
return value;
}
}

/**
* @description 存储localStorage
* @param {String} key Storage名称
* @param {Any} value Storage值
* @return void
*/
export function localSet(key: string, value: any) {
window.localStorage.setItem(key, JSON.stringify(value));
}

//设值可仅调整json里面的部分属性key,示例代码如下
//仅调整GlobalState.language的值,其他保持不变
localSet("GlobalState.language", lang);

i18n(页面多语言环境切换组件)

作用页面可进行多语言切换,通常放在src的languages包目录下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { createI18n } from "vue-i18n";
import zh from "./modules/zh";
import en from "./modules/en";
import { localGet } from "@/utils/util";

const i18n = createI18n({
legacy: false, // 如果要支持 compositionAPI,此项必须设置为 false
locale: localGet("GlobalState").language ? localGet("GlobalState").language : "zh", // 设置语言类型
globalInjection: true, // 全局注册$t方法
messages: {
zh,
en
}
});

export default i18n;

zh.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
export default {
home: {
welcome: "欢迎使用"
},
tabs: {
more: "更多",
refresh: "刷新",
maximize: "最大化",
closeCurrent: "关闭当前",
closeOther: "关闭其它",
closeAll: "关闭所有"
},
header: {
componentSize: "组件大小",
language: "国际化",
layoutConfig: "布局设置",
layoutSwitch: "布局切换",
layoutTheme: "全局主题",
themeColor: "主题颜色",
darkMode: "暗黑模式",
greyMode: "灰色模式",
weakMode: "色弱模式",
viewSetting: "界面设置",
collapseMenu: "折叠菜单",
breadcrumb: "面包屑",
breadcrumbIcon: "面包屑图标",
tabs: "标签栏",
tabsIcon: "标签栏图标",
pageFooter: "页脚",
fullScreen: "全屏",
exitFullScreen: "退出全屏",
personalData: "个人信息",
changePassword: "修改密码",
orgLabel: "所属机构:",
deptLabel: "所属部门:",
infoDialog: {
title: "用户基本信息:",
loginName: "登录名:",
org: "所属机构:",
dept: "所属部门:",
role: "角色:",
cnName: "中文名:",
tel: "电话:",
email: "邮箱:",
lastLoginTime: "上次登录时间:",
uptPwd: "修改密码"
},
pwdDialog: {
title: "修改密码",
oldPwd: "旧密码:",
newPwd: "新密码:",
confirmPwd: "确认新密码:",
oldPwdHolder: "请输入旧密码!",
newPwdHolder: "请输入新密码!",
cofPwdHolder: "请确认新密码!",
mesSuc: "修改密码成功!",
button: "保存"
},
logout: {
cofMes: "您是否确认退出登录?",
tipMes: "温馨提示",
mesSuc: "退出登录成功!",
name: "退出登录"
}
},
login: {
login: "登录",
userName: "账号",
userNameRequired: "请输入账号!",
password: "密码",
passwordRequired: "请输入密码!",
codeRequired: "请输入验证码!",
code: "验证码",
codeError: "验证码错误!",
success: "欢迎登录!",
firstLogin: "首次登录与密码过期时,需要重新修改密码!",
companyName: "剑阁信息",
systemName: "监管统一报送平台"
},
footer: {
copyright: "版权所有 © 2022-2023 剑阁信息 保留所有权利"
},
staticRouter: {
login: "登录",
403: "403 页面",
404: "404 页面",
500: "500 页面"
},
layout: {
shortCompany: "剑阁信息"
},
button: {
cancel: "取消",
confirm: "确认"
}
};

en.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
export default {
home: {
welcome: "Welcome"
},
tabs: {
more: "More",
refresh: "Refresh",
maximize: "Maximize",
closeCurrent: "Close current",
closeOther: "Close other",
closeAll: "Close All"
},
header: {
componentSize: "Component size",
language: "Language",
layoutConfig: "Layout Config",
layoutSwitch: "Layout Switch",
layoutTheme: "Layout Theme",
themeColor: "Theme Color",
darkMode: "Dark Mode",
greyMode: "Grey mode",
weakMode: "Weak mode",
viewSetting: "View Setting",
collapseMenu: "Collapse Menu",
breadcrumb: "Breadcrumb",
breadcrumbIcon: "Breadcrumb Icon",
tabs: "Tabs",
tabsIcon: "Tabs Icon",
pageFooter: "Page Footer",
fullScreen: "Full Screen",
exitFullScreen: "Exit Full Screen",
personalData: "Personal Data",
changePassword: "Change Password",
orgLabel: "Organization:",
deptLabel: "Department:",
infoDialog: {
title: "User BaseInfo",
loginName: "Login Name:",
org: "Organization:",
dept: "Department:",
role: "Role:",
cnName: "Chinese Name:",
tel: "Tel:",
email: "Email:",
lastLoginTime: "Last Login Time:",
uptPwd: "UpdatePwd"
},
pwdDialog: {
title: "Update Password",
oldPwd: "Old Password:",
newPwd: "New Password:",
confirmPwd: "Confirm Password:",
oldPwdHolder: "please input old password!",
newPwdHolder: "please input new password!",
cofPwdHolder: "please confirm new password!",
mesSuc: "update password success!",
button: "Save"
},
logout: {
cofMes: "Are you sure to log out?",
tipMes: "Kind Tips",
mesSuc: "Logout Success!",
name: "logout"
}
},
login: {
login: "Login",
userName: "userName",
userNameRequired: "Please enter userName!",
password: "password",
passwordRequired: "Please enter password!",
code: "code",
codeRequired: "Please enter code!",
codeError: "code error!",
success: "Welcome to login!",
firstLogin: "When you log in for the first time and the password expires, you need to change the password again!",
companyName: "SwordGate Information",
systemName: "Unified Regulatory Reporting Platform"
},
footer: {
copyright: "Copyright © 2022-2023 SwordGate Information All Rights Reserved"
},
staticRouter: {
login: "login",
403: "403 page",
404: "404 page",
500: "500 page"
},
layout: {
shortCompany: "SwordGate Info"
},
button: {
cancel: "Cancel",
confirm: "Confirm"
}
};

不同作用域中使用说明:

vue template标签和script标签说明

1
2
3
4
5
6
7
8
9
10
11
12
13
//vue下template使用示例
<div class="login-footer">
<span>{{ $t("footer.copyright") }}</span>
</div>

<el-input v-model="loginForm.username" :placeholder="$t('login.userName')">

//vue下script使用示例
import { useI18n } from "vue-i18n";

const { t } = useI18n();

const userNameRequired = computed(() => t("login.userNameRequired"));

ts文件中的使用说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import i18n from "@/languages";

const title = i18n.global.t("login.systemName");

//取i18n属性值
//判断语言环境
if (i18n.global.locale.value === "zh") {
// 判断当前时间段
if (hours >= 6 && hours <= 10) return `早上好 ⛅`;
if (hours >= 10 && hours <= 14) return `中午好 🌞`;
if (hours >= 14 && hours <= 18) return `下午好 🌞`;
if (hours >= 18 && hours <= 24) return `晚上好 🌛`;
if (hours >= 0 && hours <= 6) return `凌晨好 🌛`;
} else {
// 判断当前时间段
if (hours >= 6 && hours <= 10) return `Good Morning ⛅`;
if (hours >= 10 && hours <= 14) return `Good Noon 🌞`;
if (hours >= 14 && hours <= 18) return `Good Afternoon 🌞`;
if (hours >= 18 && hours <= 24) return `Good Evening 🌛`;
if (hours >= 0 && hours <= 6) return `Good Night 🌛`;
}

4.相关通用方法记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Vue3父组件调用子组件的的方法,子组件需要用记录
defineExpose({ method })
Map对象定义
let map = new Map();
map.set(item.path, item.meta.title);
map.delete(item.path);
map.get(item.path);
创建store的方法
export const AuthStore = defineStore({
id: "AuthState",
state: (): AuthState => ({
...
}),
getters: {
...
},
actions: {
...
}
});