前言:
从搭建一个后台管理系统开始,通过这个过程来学习和熟悉 vue 3.2 + TypeScript 的使用。后续会持续更新内容,包括如何使用 Node.js 提供接口,以及如何选择服务器并进行部署上线。
项目部分功能与插件:
- Vue3.2
- vite
- vue-router
- TypeScript
- Pinia
- Axios
pinia-plugin-persistedstate
(状态的持久化)mitt
(兄弟组件传值)unplugin-vue-components
(Vue 组件自动导入插件)unplugin-auto-import
(官方API 自动导入)plugin-vue-jsx
(JSX 插件)
一:封装loading
地址:src > config > loadingInstance.ts
作用:封装element-plus loading,使其可以全局调用
代码
import { ElLoading } from 'element-plus'
let loadingInstance: ReturnType<typeof ElLoading.service>;
const startLoading = () => {
loadingInstance = ElLoading.service({
lock: true,
text: "Loading",
fullscreen: true,
background: "rgba(0, 0, 0, 0.7)"
});
};
let count = 0;
export const showFullScreenLoading = () => {
if (count === 0) startLoading();
++count;
};
export const hideFullScreenLoading = () => {
if (count <= 0) return;
--count;
if (count === 0) loadingInstance.close();
};
解析
let loadingInstance: ReturnType<typeof ElLoading.service>;
通过 typeof ElLoading.service 获取到它的类型。然后使用 ReturnType 获取这个函数的返回值类型,也就是 ElLoading.service 函数的返回类型。
二:封装Axios
地址:src > request > http.ts
作用:封装使用 Axios发送HTTP请求的功能,并暴露了一些常用的请求方法
代码
import Cookie from 'js-cookie'
import { showFullScreenLoading, hideFullScreenLoading } from '@/config/loadingInstance'
import axios, { AxiosInstance, AxiosError, AxiosRequestConfig, InternalAxiosRequestConfig, AxiosResponse } from "axios";
// 接口:扩展了一个可选的 noLoading 属性,用于控制请求是否显示加载状态。
export interface CustomAxiosRequestConfig extends InternalAxiosRequestConfig {
noLoading?: boolean;
}
// 接口:响应的常规数据类型(响应码与响应提示)
interface Result {
code: string;
msg: string;
}
// 接口:返回的非常规数据类型,如数据/字符串/对象类型的数据,并继承常规数据类型。
interface ResultData<T = any> extends Result {
data: T;
}
// 声明常量config,用于配置Axios实例的选项
const config = {
timeout: 10000, // 设置请求超时时间,以毫秒为单位。默认值为 3000,表示请求将在 3000 毫秒后超时。
};
class RequestHttp {
service: AxiosInstance;
public constructor(config: AxiosRequestConfig) {
this.service = axios.create(config);
this.service.interceptors.request.use(
(config: CustomAxiosRequestConfig) => {
config.noLoading && showFullScreenLoading();
if (config.headers && typeof config.headers.set === "function") {
config.headers.set("x-access-token", Cookie.get('token'));
}
return config;
},
(error: AxiosError) => {
return Promise.reject(error);
}
);
/**
* @description 响应拦截器
*/
this.service.interceptors.response.use(
(response: AxiosResponse) => {
const { data } = response;
hideFullScreenLoading();
if (data.code && data.code !== 200) {
return Promise.reject(data);
}
return data;
},
async (error: AxiosError) => {
const { response } = error;
hideFullScreenLoading();
return Promise.reject(error);
}
);
}
/**
* @description 常用请求方法封装
*/
get<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
return this.service.get(url, { params, ..._object });
}
post<T>(url: string, params?: object | string, _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);
逐行解析:
1.import Cookie from 'js-cookie'
导入 Cookie 存储,用于获取登陆时保存的token
2.import axios, {...} from "axios"
引入了 axios 库,并导入了一些特定的类型和接口。
axios
: 这是 axios 库的默认导出,它是一个用于发送 HTTP 请求的库。AxiosInstance
: 这是 axios 库中定义的一个接口,表示一个 Axios 实例,它包含了可以发送请求的各种方法。AxiosError
: 这是 axios 库中定义的一个接口,表示一个 Axios 错误对象,它包含了发送请求过程中产生的错误信息。AxiosRequestConfig
: 这是 axios 库中定义的一个接口,表示一个 Axios 请求的配置对象,它包含了请求的各种配置选项,例如 URL、参数、请求头等。InternalAxiosRequestConfig
: 这是 axios 库中定义的一个内部接口,表示 Axios 请求的内部配置对象,包含了一些额外的配置选项。AxiosResponse
: 这是 axios 库中定义的一个接口,表示一个 Axios 响应对象,它包含了请求的响应数据。
3.export interface CustomAxiosRequestConfig extends InternalAxiosRequestConfig
接口:扩展了一个可选的 noLoading 属性,用于控制请求是否显示加载状态。
4.interface Result
接口:响应的常规数据类型(响应码与响应提示)
5.interface ResultData<T = any> extends Result
接口:返回的非常规数据类型,如数据/字符串/对象类型的数据,并继承常规数据类型。
6.const config,timeout
声明常量config,用于配置Axios实例的选项。 timeout 设置请求超时时间,以毫秒为单位。默认值为
3000
,表示请求将在 3000 毫秒后超时。
7.class RequestHttp
定义了RequestHttp类,它封装了使用Axios发送HTTP请求的功能,并暴露了一些常用的请求方法。
8.service: AxiosInstance
声明了一个名为service的成员变量,类型为 AxiosInstance。AxiosInstance是Axios库创建的实例类型,它具有发送 HTTP 请求的能力。
9.this.service.interceptors.request.use
注册了一个请求拦截器,对请求进行预处理。在这个拦截器中,可以进行一些公共的请求配置,比如在请求头中添加 token 等。检查
config.headers
是否存在且具有set
方法。
10.this.service.interceptors.response.use
对响应数据进行处理。在这个拦截器中,首先获取响应数据
response.data
,然后判断返回的数据是否符合预期的格式。不同的状态码,进行不同的处理。
11.``get
、post
、put
、delete
和 download
(url: string, params?: object | string, _object = {})
分别对应了不同的 HTTP 请求方法,并使用 Axios 实例
service
来发送请求。这些方法都返回了一个 Promise 对象,用于处理异步操作,并将响应数据进行包装。url
是请求的URL地址,params
是一个可选参数,表示请求的查询参数,_object
是一个可选参数,用于传递其他的配置项。
三:封装接口
地址:src > request > api.ts
作用:对HTTP请求进行处理
代码
import RequestHttp from "@/request/http";
namespace Login {
export interface ReqLoginForm {
username: string;
password: string;
}
export interface ResLogin {
token: string,
customer_id: string
}
}
/** @name 登录模块*/
export const loginApi = (params: Login.ReqLoginForm) => {
return RequestHttp.post<Login.ResLogin>(`/api/admin_login.php?storeId=13`, params, { noLoading: true });
};
逐行解析:
1.namespace Login
定义了一个命名空间 Login,用于封装登录模块相关的接口和类型。
2.export const loginApi = (params: Login.ReqLoginForm)
这行代码导出了一个名为 loginApi 的函数,它接收一个名为 params 的参数,类型为 Login.ReqLoginForm。这个函数用于发送登录请求
3.RequestHttp.post
这行代码调用了 RequestHttp 模块中的 post 方法来发送 POST 请求。它传递了三个参数:请求的 URL、请求的参数(params),以及一个配置对象控制是否显示加载状态 { noLoading: true }。
四:登录页
地址:src > views > login > index.vue
代码
<template>
<el-form ref="loginFormRef" :model="loginForm" :rules="rules" size="large">
<div class="title">
<h1>登陆</h1>
<p>欢迎回来!请输入您的账户信息</p>
</div>
<el-form-item prop="username">
<span>账号</span>
<el-input v-model="loginForm.username" placeholder="用户名" />
</el-form-item>
<el-form-item prop="password">
<span>密码</span>
<el-input type="password" v-model="loginForm.password" placeholder="密码" show-password
autocomplete="new-password" />
</el-form-item>
<div class="login_btn">
<el-button color="#3438cd" @click="submitForm(loginFormRef)" size="large" :loading="loading">登录</el-button>
</div>
</el-form>
</template>
<script setup lang="ts">
import Cookies from "js-cookie";
import { store } from '@/store/index'
import { loginApi } from "@/request/api"
import { initDynamic } from '@/router/modules/dynamic'
import type { FormRules, FormInstance } from 'element-plus'
const loading = ref(false) // 登陆加载
const Store = store();
const loginFormRef = ref<FormInstance>() // 用于表单的引用
interface ReqLoginForm {
username: string;
password: string;
}
const loginForm = reactive<ReqLoginForm>({
username: "admin",
password: "6666",
})
const rules = reactive<FormRules>({
username: [{ required: true, message: "请输入用户名", trigger: "blur" }],
password: [{ required: true, message: "请输入密码", trigger: "blur" }]
})
// formEl是在submitForm方法中作为参数传递的表单实例,调用表单实例的方法和属性。
const submitForm = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.validate(async (valid) => {
if (!valid) return
loading.value = true
try {
const { token, customer_id } = await loginApi(loginForm).then(res => res.data)
Store.setID(customer_id); // 配置大菠萝
Cookies.set('token', token);
await initDynamic(); // 动态路由
} finally {
loading.value = false
}
})
}
</script>
五:配置大菠萝
安装:
npm i pinia
npm i pinia-plugin-persistedstate
export const store = defineStore
代码:
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
interface GlobalState {
customer_id: string;
}
export const store = defineStore({
id: 'global-store',
state: (): GlobalState => ({
customer_id: '', // 角色ID
}),
getters: {},
actions: {
setID(id: string) {
this.customer_id = id
}
}
})
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate)
export default pinia;
逐行解析:
创建全局状态存储对象
const pinia = createPinia
创建了一个Pinia实例
使用了插件来实现状态的持久化,确保状态在刷新页面或重新加载应用后仍然保持
使用了插件来实现状态的持久化,确保状态在刷新页面或重新加载应用后仍然保持