一、去除登录页浏览器的输入框提示
- 修改src/views/Login/index.vue
<!-- 输入框加入属性autocomplete="off" -->
<template>
<a-row class="layout" type="flex" justify="center" align="middle">
<a-card class="login_card" :bodyStyle="{ height: '100%', padding: 'unset' }" hoverable>
<div class="card_body">
......
<div class="login_form">
<a-form class="form" ref="loginFormRef" layout="vertical" :rules="loginRules" :model="loginForm">
<a-form-item label="账号:" name="username">
<a-input v-model:value="loginForm.username" size="large" autocomplete="off" />
</a-form-item>
<a-form-item label="密码:" name="password">
<a-input-password v-model:value="loginForm.password" size="large" autocomplete="off" />
</a-form-item>
<a-form-item>
<a-button type="primary" size="large" block @click="onSubmit">提交</a-button>
</a-form-item>
</a-form>
</div>
</div>
</a-card>
</a-row>
</template>
<script lang="ts" setup>......</script>
<style lang="less" scoped>......</style>
二、新建需要的几个页面
新建404页面
- 在src/views新建目录404
- 在src/views/404目录新建文件index.vue
<template>
<div class="page">
<h1 data-t="404">404</h1>
<p>当前页面不存在,<router-link to="/">点击回到首页</router-link></p>
</div>
</template>
<script lang="ts" setup></script>
<style lang="less" scoped>
.page {
position: absolute;
width: 100%;
height: 100%;
top: 0;
bottom: 0;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
h1 {
text-align: center;
width: 100%;
font-size: 6rem;
animation: shake 0.6s ease-in-out infinite alternate;
}
@keyframes shake {
0% {
transform: translate(-1px);
}
10% {
transform: translate(2px, 1px);
}
30% {
transform: translate(-3px, 2px);
}
35% {
transform: translate(2px, -3px);
filter: blur(4px);
}
45% {
transform: translate(2px, 2px) skewY(-8deg) scaleX(0.96);
filter: blur(0);
}
50% {
transform: translate(-3px, 1px);
}
}
h1:before {
content: attr(data-t);
position: absolute;
left: 50%;
transform: translate(-50%, 0.34em);
height: 0.1em;
line-height: 0.5em;
width: 100%;
animation: scan 0.5s ease-in-out 275ms infinite alternate, glitch-anim 0.3s ease-in-out infinite alternate;
overflow: hidden;
opacity: 0.7;
}
@keyframes glitch-anim {
0% {
clip: rect(32px, 9999px, 28px, 0);
}
10% {
clip: rect(13px, 9999px, 37px, 0);
}
20% {
clip: rect(45px, 9999px, 33px, 0);
}
30% {
clip: rect(31px, 9999px, 94px, 0);
}
40% {
clip: rect(88px, 9999px, 98px, 0);
}
50% {
clip: rect(9px, 9999px, 98px, 0);
}
60% {
clip: rect(37px, 9999px, 17px, 0);
}
70% {
clip: rect(77px, 9999px, 34px, 0);
}
80% {
clip: rect(55px, 9999px, 49px, 0);
}
90% {
clip: rect(10px, 9999px, 2px, 0);
}
to {
clip: rect(35px, 9999px, 53px, 0);
}
}
@keyframes scan {
0%,
20%,
to {
height: 0;
transform: translate(-50%, 0.44em);
}
10%,
15% {
height: 1em;
line-height: 0.2em;
transform: translate(-55%, 0.09em);
}
}
h1:after {
content: attr(data-t);
position: absolute;
top: -8px;
left: 50%;
transform: translate(-50%, 0.34em);
height: 0.5em;
line-height: 0.1em;
width: 100%;
animation: scan 665ms ease-in-out 0.59s infinite alternate, glitch-anim 0.3s ease-in-out infinite alternate;
overflow: hidden;
opacity: 0.8;
}
</style>
新建Root页面
- 在src/views新建目录Root
- 在src/views/Root目录新建文件index.vue
<template>
<h1>Root 权限才能看到的页面!</h1>
</template>
<script lang="ts" setup></script>
新建Admin页面
- 在src/views新建目录Admin
- 在src/views/Admin目录新建文件index.vue
<template>
<h1>Admin 权限才能看到的页面!</h1>
</template>
<script lang="ts" setup></script>
新建Menu页面
- 在src/views新建目录Menu
- 在src/views/Menu目录新建文件index.vue
<template>
<router-view />
</template>
新建Menu/Menu1页面
- 在src/views/Menu新建目录Menu1
- 在src/views/Menu/Menu1目录新建文件index.vue
<template>
<h1>Menu1页面!</h1>
</template>
<script lang="ts" setup></script>
新建Menu/Menu2页面
- 在src/views/Menu新建目录Menu2
- 在src/views/Menu/Menu2目录新建文件index.vue
<template>
<h1>Menu2页面!</h1>
</template>
<script lang="ts" setup></script>
三、修改mock中的login接口
- mock目录下新建data目录
- mock/data目录下新建文件routes.ts
- mock/data/routes.ts
interface routeConfig {
path: string;
name: string;
component?: string;
redirect?: string;
meta: {
title: string;
layout: boolean;
};
children?: routeConfig[];
}
const rootRoutes: routeConfig[] = [
{
path: '/root',
name: 'root',
component: 'views/Root/index.vue',
meta: {
title: 'Root权限页',
layout: true,
},
},
{
path: '/menu',
name: 'menu',
component: 'views/Menu/index.vue',
redirect: '/menu/menu1',
meta: {
title: '多级菜单页',
layout: true,
},
children: [
{
path: 'menu1',
name: 'menu1',
component: 'views/Menu/Menu1/index.vue',
meta: {
title: 'Menu1页',
layout: true,
},
},
{
path: 'menu2',
name: 'menu2',
component: 'views/Menu/Menu2/index.vue',
meta: {
title: 'Menu2页',
layout: true,
},
},
],
},
];
const adminRoutes: routeConfig[] = [
{
path: '/admin',
name: 'admin',
component: 'views/Admin/index.vue',
meta: {
title: 'Admin权限页',
layout: true,
},
},
];
const userRoutes: routeConfig[] = [];
export { rootRoutes, adminRoutes, userRoutes, routeConfig };
- 修改mock/login.ts
import Mock from 'mockjs';
import { rootRoutes, adminRoutes, userRoutes, routeConfig } from './data/routes';
export default [
{
// http://mockjs.com/examples.html
url: '/mock/api/login',
method: 'post',
timeout: 500,
// statusCode: 500,
response: ({ body }) => {
let role: string;
let routes: routeConfig[] = [];
if (body.username === 'root') {
role = 'Root';
routes = rootRoutes;
} else if (body.username === 'admin') {
role = 'Admin';
routes = adminRoutes;
} else {
role = 'User';
routes = userRoutes;
}
return {
code: 200,
success: true,
message: 'ok',
data: {
token: Mock.Random.string('lower', 200),
userInfo: {
id: Mock.Random.id(),
name: Mock.Random.cname(),
email: Mock.Random.email(),
gender: Mock.Random.natural(1, 2),
age: Mock.Random.natural(18, 30),
avatar: Mock.Random.image('800x800'),
role,
},
routes,
},
};
},
},
];
四、修改src/store/modules/user.ts
import { defineStore } from 'pinia';
const useUserStore = defineStore('user', {
state: () => {
return {
token: '',
userInfo: {},
routes: [],
};
},
actions: {
......
setRoutes(routes) {
this.routes = routes;
},
......
clearRoutes() {
this.routes = [];
},
},
persist: {
enabled: true,
strategies: [
{ key: 'token', storage: localStorage, paths: ['token'] },
{ key: 'userInfo', storage: localStorage, paths: ['userInfo'] },
{ key: 'routes', storage: localStorage, paths: ['routes'] },
],
},
});
export default useUserStore;
五、在登录成功后存储动态路由数据
- 修改src/views/Login/index.vue
<template>
......
</template>
<script lang="ts" setup>
import { reactive, ref, toRaw } from 'vue';
import { useRouter } from 'vue-router';
import { useUserStore } from '@/store';
import LoginAPI from '@/request/api/loginAPI';
interface loginFormConfig {
username: string;
password: string;
}
interface loginResConfig {
token: string;
userInfo: any;
}
const router = useRouter();
const userStore = useUserStore();
const loginFormRef = ref();
const loginForm: loginFormConfig = reactive({
username: '',
password: '',
});
const loginRules = {
username: [
{
required: true,
message: '请输入用户名',
trigger: 'blur',
},
],
password: [
{
required: true,
message: '请输入密码',
trigger: 'blur',
},
],
};
const onSubmit = () => {
loginFormRef.value.validate().then(async () => {
const res = (await LoginAPI.setLogin(toRaw(loginForm))) as loginResConfig;
if (res) {
console.log(res);
userStore.setToken(res.token);
userStore.setUserInfo(res.userInfo);
userStore.setRoutes(res.routes);
router.push({ path: '/' });
}
});
};
</script>
<style lang="less" scoped>
......
</style>
六、新建动态增删路由的公共方法
- src目录下新建util
- src/util目录下新建文件anyncRoutes.ts
// 递归遍历路由数据
const recursiveRoutes = (tree: any[], views) => {
return tree.map((node) => {
const tempNode = node;
if (tempNode.component) {
tempNode.component = views[`../${tempNode.component}`];
}
if (tempNode.children && tempNode.children.length > 0) {
recursiveRoutes(tempNode.children, views);
}
return tempNode;
});
};
// 添加动态路由
const addRoutes = (userStore, router) => {
if (userStore.routes && userStore.routes.length > 0) {
const routesData = JSON.parse(JSON.stringify(userStore.routes));
const views = import.meta.glob('../views/**/*.vue');
recursiveRoutes(routesData, views);
routesData.forEach((item: any) => {
router.addRoute(item);
});
}
router.addRoute({
path: '/:pathMatch(.*)*',
name: '404',
component: () => import('@/views/404/index.vue'),
meta: {
title: '无法找到该页面',
layout: false,
},
});
};
// 清除动态路由
const clerRoutes = (userStore, router) => {
if (userStore.routes && userStore.routes.length > 0) {
userStore.routes.forEach((item: any) => {
router.removeRoute(item.name);
});
userStore.clearRoutes();
}
};
export { addRoutes, clerRoutes };
七、在登录成功后动态添加路由
- 修改src/views/Login/index.vue
<template>
......
</template>
<script lang="ts" setup>
import { reactive, ref, toRaw } from 'vue';
import { useRouter } from 'vue-router';
import { useUserStore } from '@/store';
import LoginAPI from '@/request/api/loginAPI';
import { addRoutes } from '@/util/anyncRoutes';
interface loginFormConfig {
username: string;
password: string;
}
interface loginResConfig {
token: string;
userInfo: any;
routes: [];
}
const router = useRouter();
const userStore = useUserStore();
const loginFormRef = ref();
const loginForm: loginFormConfig = reactive({
username: '',
password: '',
});
const loginRules = {
username: [
{
required: true,
message: '请输入用户名',
trigger: 'blur',
},
],
password: [
{
required: true,
message: '请输入密码',
trigger: 'blur',
},
],
};
const onSubmit = () => {
loginFormRef.value.validate().then(async () => {
const res = (await LoginAPI.setLogin(toRaw(loginForm))) as loginResConfig;
if (res) {
console.log(res);
userStore.setToken(res.token);
userStore.setUserInfo(res.userInfo);
userStore.setRoutes(res.routes);
addRoutes(userStore, router);
router.push({ path: '/' });
}
});
};
</script>
<style lang="less" scoped>
......
</style>
八、页面刷新保持动态路由,退出登录清除动态路由
- 修改src/router/index.ts
// @/router/index.ts
import { createRouter, createWebHashHistory } from 'vue-router';
import { useUserStore } from '@/store';
import { addRoutes, clerRoutes } from '@/util/anyncRoutes';
const routes = [
{
path: '/',
name: 'home',
component: () => import('@/views/Home/index.vue'),
meta: {
title: '首页',
layout: true,
},
},
{
path: '/login',
name: 'Login',
component: () => import('@/views/Login/index.vue'),
meta: {
title: '登录',
layout: false,
},
},
];
const router = createRouter({
history: createWebHashHistory(),
routes,
});
let registerRouteFresh = true;
router.beforeEach((to, from, next) => {
const userStore = useUserStore();
if (to.name !== 'Login' && !userStore.token) {
next({ name: 'Login' });
} else {
if (to.name === 'Login') {
userStore.clearToken();
userStore.clearUser();
clerRoutes(userStore, router);
}
if (!from.name && registerRouteFresh) {
addRoutes(userStore, router);
next({ ...to, replace: true });
registerRouteFresh = false;
} else {
next();
}
}
});
export default router;
九、源代码地址
https://github.com/jiangzetian/vue3-admin-template
十、视频演示及源码
本文演示视频:点击浏览
更多前端内容欢迎关注公众号:天小天个人网