一、文件夹结构说明
- views 页面文件
- router 路由文件
- api 请求
- styles 样式
- components 公共组件
- store 存储
- lauout 布局
- utils 公共方法
- assets 静态资源
二、添加组件
2.1 路由
pnpm install vue-router
创建vue文件
- views/home/index.vue 首页
- views/systom/login/index.vue 登陆
- views/dashboard/index.vue 驾驶舱
定义路由组件 router/index.ts
TypeScript
import { createRouter, createWebHashHistory, Router } from 'vue-router'
const routes = [
{ path: '/', component: import('~views/home/index.vue') },
{ path: "/login", component: import("~views/systom/login/index.vue") },
{ path: '/dashboard', component: import('~views/dashboard/index.vue') },
]
export const router: Router = createRouter({
history: createWebHashHistory(),
routes: routes
})
main.ts使用
TypeScript
import App from "./App.vue";
import { createApp } from "vue";
/* 基础组件 */
import { router } from "~router/index";
async function bootstrap() {
const app = createApp(App);
app.use(router)
app.mount("#app");
}
bootstrap();
启动项目,页面显示home.vue页面的内容,通过路由(login.vue、dashboard)可切换页面
2.2 添加ant-design-vue
安装 Ant Design的Vue实现、以及图标组件包 pnpm i --save ant-design-vue@4.x @ant-design/icons-vue
引入组件方式有两种具体参考官网
- 全局引入
- 全局部分引入
- 局部注册
解放双手,按需自动导入
unplugin-vue-components
和全局引入组件的主要区别在于,使用 unplugin-vue-components
插件可以实现按需引入组件,即在代码中当你实际使用某个组件时,它会被自动引入,而不需要手动在某个文件中全局引入。这样可以减少打包体积,并可能提高构建速度。
全局引入组件通常是通过在 main.js
或类似的入口文件中使用 Vue.component
方法来注册组件的。而使用 unplugin-vue-components
插件,你只需要在你的代码中直接使用组件,无需手动注册。
pnpm install unplugin-vue-components -D
修改vite配置
TypeScript
import components from "unplugin-vue-components/vite";
import { AntDesignVueResolver } from "unplugin-vue-components/resolvers";
plugins: [
components({
resolvers: [AntDesignVueResolver({ importStyle: false })],
dts: false,
}),
]
配置完之后 main.ts 就不用在引入Antd了
2.3 写一个简单的登陆,正常的登陆都做了什么
现在的程序会自动跳转到home页,实际程序当中要判断当前是否有登录。
- 已经登录过,那么跳转目标路由(home或其他)
- 没有登录,跳转登录界面,登录成功后将信息存储(token,和患者信息)。
- 存储信息使用pinia
- 然后路由跳转。
2.4 引入axios
pnpm i axios
utils/http/axios/index.ts
TypeScript
import axios from "axios";
export const MYAxios = axios.create({
baseURL: "/api/",
timeout: 10000,
headers: {},
});
api/systom/user.ts
TypeScript
import { MYAxios } from "~utils/http/axios";
enum Api {
Login = "/login"
}
/**
* @description: 登录
*/
export function loginApi(data: any) {
return MYAxios.post(Api.Login, data);
}
使用: 登录页面
javascript
import { loginApi } from "~api/systom/user";
/* 登录 */
const login = async () => {
try {
const data = await loginApi(formData);
console.log(data);
} catch (error) {
formData;
}
};
2.5 mock模拟后端
mock插件:pnpm install vite-plugin-mock -D
TypeScript
plugins: [
viteMockServe({
// default
mockPath: "mock",
enable: command === "serve",
}),
],
在根目录下创建 mock文件夹,添加index.ts文件
mock:pnpm install mockjs
用于:生成随机数据,拦截 Ajax 请求
mock完数据就可以请求到数据了
TypeScript
import { MockMethod } from "vite-plugin-mock";
import Mock from "mockjs";
export default [
{
url: "/api/login",
method: "post",
timeout: 500,
response: (RespThisType, opt) => {
return {
code: 200,
data: {
user: { name: "", roles: [], permission: [] },
token: Mock.mock("@guid"),
},
message: "成功!"
};
},
},
] as MockMethod[];
2.6 引入状态管理库 pinia
pnpm install pinia pinia-plugin-persistedstate
pinia.vuejs.org/zh/introduc...
prazdevs.github.io/pinia-plugi...
src/store/index.ts
TypeScript
import type { App } from 'vue';
import { createPinia } from 'pinia';
import { createPersistedState } from "pinia-plugin-persistedstate";
const store = createPinia();
store.use(createPersistedState())
export function setupStore(app: App<Element>) {
app.use(store);
}
export { store };
main.ts中使用这个setupStore方法
2.7 实现检测token成功放行,失败调登陆页逻辑
添加路由守卫,检测到没有token就跳转到login页面
TypeScript
import { PageEnum } from "~/enums/pageEnum";
import { useUserStoreWithOut } from "~store/modules/user";
router.beforeEach((to, from) => {
const userStore = useUserStoreWithOut();
// ...
// 返回 false 以取消导航
// console.log("beforeEach", to.path, from.path);
if (whiteList.findIndex((i) => i === to.path) == -1) {
if (
// 检查用户是否已登录
!userStore.getToken
) {
console.log("beforeEach", to.path, from.path);
// // 将用户重定向到登录页面
return { path: PageEnum.BASE_LOGIN };
}
}
});
登陆页面
TypeScript
<template>
<div>
<a-form
ref="formLoginRef"
:model="formData"
:label-col="{ span: 8 }"
:wrapper-col="{ span: 16 }"
autocomplete="off"
:rules="getFormRules"
@finishFailed="onFinishFailed"
>
<a-form-item label="账号" name="username">
<a-input v-model:value="formData.username" />
</a-form-item>
<a-form-item label="密码" name="password">
<a-input-password
autocomplete="off"
v-model:value="formData.password"
/>
</a-form-item>
<a-form-item>
<a-form-item name="remember" no-style>
<a-checkbox v-model:checked="formData.remember"
>Remember me</a-checkbox
>
</a-form-item>
<a class="login-form-forgot" href="">Forgot password</a>
</a-form-item>
<a-form-item :wrapper-col="{ offset: 8, span: 16 }">
<a-button type="primary" @click="onSubmit">登录</a-button>
Or
<a href="">register now!</a>
</a-form-item>
</a-form>
</div>
</template>
<script setup lang="ts">
//api
import { loginApi } from "~api/systom/user";
/* UI comp */
import { reactive, ref } from "vue";
import { useFormRules } from "./useLogin";
interface FormState {
username: string;
password: string;
remember: boolean;
}
const { getFormRules } = useFormRules();
import { useUserStore } from "~store/modules/user";
const userStore = useUserStore();
const formData = reactive<FormState>({
username: "",
password: "",
remember: true,
});
const formLoginRef = ref();
const onSubmit = () => {
formLoginRef.value
.validate()
.then(() => {
login();
})
.catch((error: any) => {
console.log(error);
});
};
/* 登录 */
const login = async () => {
try {
const data = await userStore.login(formData);
console.log("userStore.login", data);
} catch (error) {
formData;
}
};
// 提交表单且数据验证失败后回调事件;
const onFinishFailed = (errorInfo: any) => {
console.log("Failed:", errorInfo);
};
</script>
<style scoped lang="scss"></style>
封装用户相关状态管理: store/modules/user.ts
TypeScript
import { defineStore } from "pinia";
import { PageEnum } from "~/enums/pageEnum";
import { UserInfo } from "~api/systom/model/user";
import { loginApi } from "~api/systom/user";
import { router } from "~router/index";
import { store } from "~store/index";
/* 将来移动到types */
// TODO
interface UserState {
userInfo: Nullable<UserInfo>;
token?: string;
roleList: any[];
sessionTimeout?: boolean;
lastUpdateTime: number;
}
export const useUserStore = defineStore("app-user", {
state: (): UserState => ({
userInfo: null,
token: undefined,
roleList: [],
// Whether the login expired
sessionTimeout: false,
lastUpdateTime: 0,
}),
getters: {
getUserInfo(state) {
return state.userInfo;
},
// getToken(state): string { // TODO
getToken(state) {
return state.token;
},
getRoleList(state) {
return state.roleList.length > 0 ? state.roleList : [];
},
getSessionTimeout(state): boolean {
return !!state.sessionTimeout;
},
getLastUpdateTime(state): number {
return state.lastUpdateTime;
},
},
actions: {
setToken(info: string | undefined) {
this.token = info ? info : ""; // for null or undefined value
},
setRoleList(roleList: []) {
this.roleList = roleList;
},
setUserInfo(info: UserInfo | null) {
this.userInfo = info;
this.lastUpdateTime = new Date().getTime();
},
setSessionTimeout(flag: boolean) {
this.sessionTimeout = flag;
},
resetState() {
this.userInfo = null;
this.token = "";
this.roleList = [];
this.sessionTimeout = false;
},
/**
* @description: login
*/
async login(params: any) {
try {
const response = await loginApi(params);
const { token, user } = response.data.data;
// save token
this.setToken(token);
this.setUserInfo({
userId: user.name,
username: user.name,
});
return this.afterLoginAction();
} catch (error) {
return Promise.reject(error);
}
},
async afterLoginAction() {
router.push(PageEnum.BASE_HOME);
},
},
persist: true, //现在,你的整个 Store 将使用默认持久化配置保存。
});
// Need to be used outside the setup
export function useUserStoreWithOut() {
return useUserStore(store);
}
2.8 引入css处理器sass
pnpm i sass -D
2.9 异常处理
浏览器警告问题
浏览器警告:Input elements should have autocomplete attributes (suggested: "current-password"):
在 imput-passward 添加属性autocomplete="off"
, autocomplete规定输入字段是否启用自动完成功能。
html
<a-input-password
autocomplete="off"
v-model:value="formData.password"
/>
Pinia使用问题
"getActivePinia()" was called but there was no active Pinia. Are you trying to use a store before calling "app.use(pinia)"?
原因:在main.ts中,是先引用了router,再调用的setupStore方法将pinia挂载在app上,此时不能使用pinia。
TypeScript
import App from "./App.vue";
import { createApp } from "vue";
/* 基础组件 */
import { router } from "~router/index";
import { setupStore } from "~store/index";
import "ant-design-vue/dist/reset.css";
async function bootstrap() {
const app = createApp(App);
setupStore(app);
app.use(router);
app.mount("#app");
}
bootstrap();
解决:取消全局调用,在用的地方调用。
TypeScript
export function useUserStoreWithOut() {
return useUserStore(store);
}
碎碎
插件
css
pnpm i vite-aliases -D
启动项目,ts配置文件中自动添加如下代码,但这些配置需要 "baseUrl": "./",(指定用于解析非相对模块名称的基本目录),配置完就不报警告了。
javascript
"paths": {
"~api/*": [
"src/api/*"
],
"~assets/*": [
"src/assets/*"
],
"~components/*": [
"src/components/*"
],
"~router/*": [
"src/router/*"
],
"~store/*": [
"src/store/*"
],
"~styles/*": [
"src/styles/*"
],
"~utils/*": [
"src/utils/*"
],
"~views/*": [
"src/views/*"
],
"~/*": [
"src/*"
]
}
终断提示changed tsconfig file detected: G:\study\muyang-admin\tsconfig.json - Clearing cache and forcing full-reload to ensure TypeScript is compiled with updated config values.