Vue脚手架模板
Reverse Lv4

:::tip 有啥用?
浅浅记录一下在使用 Vue 的轮子时,各个支持库的模板,方便在新起项目时可以快速 CV 上去 :)
:::

Router Template

这是路由组件的 ts 代码模板,可以直接 CV 到/src/router/index.ts使用,删除一些自己不需要的东西就行.

:::details 点我查看代码

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
import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";
import { useUserStore } from "@/stores/user"; // 若使用 Pinia
import { ElMessage } from "element-plus"; // 可换成任意 UI 库
import NProgress from "nprogress"; // 替代 loadingBar

// 页面组件引入
import ViewLogin from "@/views/Login.vue";
import ViewHome from "@/views/Home.vue";
import View404 from "@/views/404.vue";

// 路由配置
const routes: RouteRecordRaw[] = [
{
path: "/",
redirect: "/home",
},
{
path: "/login",
name: "Login",
component: ViewLogin,
meta: { public: true },
},
{
path: "/home",
name: "Home",
component: ViewHome,
meta: { requiresAuth: true },
},
{
path: "/:pathMatch(.*)*",
name: "NotFound",
component: View404,
meta: { public: true },
},
];

const router = createRouter({
history: createWebHashHistory(),
routes,
});

// 路由守卫
router.beforeEach((to, from, next) => {
NProgress.start();

const userStore = useUserStore();
const isAuthenticated = !!userStore.token;

if (to.meta.requiresAuth && !isAuthenticated) {
ElMessage.warning("请先登录");
return next({ name: "Login" });
}

if (to.path === "/login" && isAuthenticated) {
return next({ name: "Home" });
}

next();
});

router.afterEach(() => {
NProgress.done();
});

export default router;

:::

Pinia [TypeScript] Template

这是 pinia 状态管理插件的 TS 模板,一般是放在/src/stores/xxxx.ts

:::details 点我查看代码

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
import { defineStore } from "pinia";

interface UserInfo {
id: string;
name: string;
email?: string;
[key: string]: any;
}

export const useUserStore = defineStore("user", {
state: (): {
token: string;
userInfo: UserInfo | null;
} => ({
token: "",
userInfo: null,
}),

getters: {
isLogin: (state) => !!state.token,
username: (state) => state.userInfo?.name || "",
},

actions: {
setToken(token: string) {
this.token = token;
},

setUserInfo(info: UserInfo) {
this.userInfo = info;
},

logout() {
this.token = "";
this.userInfo = null;
},
},

// 持久化(可选,需要配合 pinia-plugin-persistedstate 插件)
persist: {
key: "user-store",
paths: ["token", "userInfo"],
storage: sessionStorage,
},
});

:::

Pinia [JavaScript] Template

这是 pinia 状态管理插件的 JS 模板,一般是放在/src/stores/xxxx.js

:::details 点我查看代码

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
import { defineStore } from "pinia";

export const useUserStore = defineStore("user", {
state: () => ({
token: "",
userInfo: null,
}),

getters: {
isLogin: (state) => !!state.token,
username: (state) => state.userInfo?.name || "",
},

actions: {
setToken(token) {
this.token = token;
},

setUserInfo(info) {
this.userInfo = info;
},

logout() {
this.token = "";
this.userInfo = null;
},
},

// 开启持久化(需要 pinia-plugin-persistedstate 插件)
persist: {
key: "user-store",
paths: ["token", "userInfo"],
storage: sessionStorage,
},
});

:::

axios 二次封装

可以直接写到/src/api/request.ts

:::details 点我查看代码

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
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
import { ElMessage } from "element-plus";
import { useUserStore } from "@/stores/user";

const BASE_URL = import.meta.env.VITE_API_BASE_URL || "/api";

const service: AxiosInstance = axios.create({
baseURL: BASE_URL,
timeout: 10000,
headers: {
"Content-Type": "application/json",
},
});

// 请求拦截器
service.interceptors.request.use(
(config: AxiosRequestConfig) => {
const userStore = useUserStore();
const token = userStore.token;

if (token) {
config.headers = {
...config.headers,
Authorization: `Bearer ${token}`,
};
}

return config;
},
(error) => {
return Promise.reject(error);
}
);

// 响应拦截器
service.interceptors.response.use(
(response: AxiosResponse) => {
const { data } = response;

// 根据业务约定处理
if (data.code !== 0) {
ElMessage.error(data.message || "请求失败");
return Promise.reject(data);
}

return data.data; // 默认返回 data 下的 data
},
(error) => {
const status = error?.response?.status;

if (status === 401) {
const userStore = useUserStore();
userStore.logout();
ElMessage.error("登录已过期,请重新登录");
// 可跳转登录页
} else if (status === 500) {
ElMessage.error("服务器错误");
} else {
ElMessage.error(error?.response?.data?.message || "请求异常");
}

return Promise.reject(error);
}
);

export default service;

怎么使用? 参考下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import request from "@/api/request";

interface LoginParams {
username: string;
password: string;
}

interface LoginResponse {
token: string;
userInfo: {
id: string;
name: string;
};
}

export function login(data: LoginParams) {
return request.post<LoginResponse>("/auth/login", data);
}

:::