开发记录
从零开始开发后台管理系统,还是有很多值得记录的地方。构建工具Vite、使用Vue3。
1.批量导入指定目录的组件
代码语言:javascript复制/*
* @author 友人a丶
* @date 2022-07-11
* @app Vue应用对象
* */
export default function (app) {
/*
* 指定要导入的文件目录
* 直接加载用{eager:true},懒加载用glob
* */
const modules = import.meta.glob(['@/layouts/*/index.js', '@/components/*/index.js']);
for (let i in modules) {
let name = /(.*)?/(.*)/index.js/.exec(i);
/*直接引入组件*/
app.component(name[2], modules[i].default);
/*异步组件*/
app.component(name[2], defineAsyncComponent(modules[i]));
}
}
直接加载的时候modules就是元素为组件对象的数组,懒加载的时候是元素为import方法的函数数组 。
2.应用初始化
代码语言:javascript复制/*
* @author 友人a丶
* @date 2022-07-11
*
* 引导系统初始化
* 初始化全局响应拦截器
* 初始化路由守卫
* 初始化用户登录
* */
import loadGuard from "./loadGuard"
import loadInterceptor from "./loadInterceptor"
import watchRem from "./watchRem";
export default async function (){
/*
* 自动调整rem
* */
watchRem();
/*
* 加载拦截器
* */
loadInterceptor();
/*
* 加载路由守卫
* */
loadGuard();
}
3.自动调整rem
代码语言:javascript复制/*
* @author 友人a丶
* @date 2022-07-11
* 自动调整rem的大小
* */
export default function () {
let l = () => {
let r = document.documentElement, o = r.offsetWidth / 100;
o < 16 && (o = 16), r.style.fontSize = o "px", window.rem = o
};
l();
window.addEventListener("resize",()=>l());
}
4.导航守卫
代码语言:javascript复制/*
* @author 友人a丶
* @date 2022-07-11
*
* 加载全局路由守卫
* */
import {router} from "@/router";
import userStore from "@/stores/user";
import load from "@/common/load";
import systems from "@/stores/system";
import NProgress from 'nprogress'
import loadUser from "@/service/loadUser";
/*进度条*/
NProgress.configure({showSpinner: false})
// 不需要拦截的路由配置
const ignoreRoute = {
names: ['404', '403'], //根据路由名称匹配
paths: ['/login'], //根据路由fullPath匹配
/**
* 判断路由是否包含在该配置中
* @param route vue-router 的 route 对象
* @returns {boolean}
*/
includes(route) {
return ignoreRoute.names.includes(route.name) || ignoreRoute.paths.includes(route.path)
}
}
/*
* 加载路由守卫
* */
export default function () {
console.log("加载路由守卫...");
let user = userStore();//全局状态
/*
* 加载进度条
* */
router.beforeEach((to, from, next) => {
// start progress bar
if (!NProgress.isStarted()) {
NProgress.start()
}
next()
});
/*
* 判断系统是否初始化
* */
router.beforeEach(async (to, from, next) => {
if(!systems().loaded){
await loadUser();//加载用户信息初始化系统
}
next(); //下一个
});
/*
* 已登录时,访问登录页面,让它走
* */
router.beforeEach( (to, from, next) => {
if (user.role != 0 && (to.path == "/login")) {
next('/');
}else{
next(); //下一个
}
});
/*
* 判断是否登录
* */
router.beforeEach((to, from, next) => {
/*
* 判断是否需要拦截
* */
console.log("登录判断守卫激活....")
if (ignoreRoute.includes(to)) {
next();
} else {
/*
* 角色为0,代表未登录
* */
if (user.role == 0) {
next({path: '/login'});
} else {
next();
}
}
})
/*
* 判断用户权限
* */
router.beforeEach((to, from, next) => {
console.log("权限判断守卫激活....")
/*
* 判断是否需要拦截
* */
if (ignoreRoute.includes(to)) {
next();
} else {
/*
* 判断用户权限
* */
if (user.role < to.meta.role) {
load.error("您无权限访问该页面....");
next({path: '/403'});
} else {
next();
}
}
})
/*
* 切换页面标题
* */
router.beforeEach((to, from, next) => {
document.title = to.name
next();
})
/*
* 结束进度条
* */
router.afterEach(() => {
// finish progress bar
NProgress.done()
});
}
5.axios拦截器
代码语言:javascript复制/*
* @author 友人a丶
* @date 2022-07-11
*
* 加载axio拦截器
* */
import axios from "axios";
import load from "@/common/load";
import {router} from "@/router";
import apis from '@/service/api';
// 不需要拦截的接口
const ignoreApi = {
api: [
apis.login
],
includes(api) {
/*
* 判断当前请求的接口是否在忽略的列表
* */
for(let item of ignoreApi.api){
let reg=new RegExp(`.*${item}.*`);
if(reg.test(api)){
return true;
}
}
return false;
}
}
/*
* 注册响应拦截器
* */
export default function (){
console.log("加载拦截器...");
axios.interceptors.response.use(function (res){
console.log("请求接口:" res.config.url)
console.log(res);
/*
* 判断是否需要拦截
* */
if(ignoreApi.includes(res.config.url)){
return res;
}
/*
* 判断用户登录是否失效
* */
if(res.code == -1){
load.confirm("当前登录状态已失效,请您重新登录!",()=>{
router.replace('/login')
})
}
return res;
});
}
6.获取某个路由的子路由(用于生成菜单)
代码语言:javascript复制/*
* 操作路由的相关方法
* */
import {router} from "@/router/index";
/*
* 获取某个路由项的子项
* */
export function getChildren(path) {
let routes=router.getRoutes();
for (let i of routes) {
if (i.path == path) {
return i.children;
}
}
}
7.简单的弹出封装(antd design vue)
代码语言:javascript复制import {
message,
Modal
} from "ant-design-vue";
let hide = [];
export default {
loading(text = '加载中...') {
hide.push(message.loading(text, 0))
},
loaded() {
if (hide.length > 0) {
let timer=setTimeout(()=>{
hide[hide.length - 1]()
hide.splice(hide.length - 1, 1)
},500);
}
},
error(text = '加载异常') {
message.error(text);
},
success(text = 'ok!') {
message.success(text);
},
confirm(text, callback = null) {
Modal.confirm({
title: '提示',
centered: true,
content: text,
maskClosable: false,
onOk: (close) => {
close(); //关闭
if (callback) {
callback()
}
}
})
}
}
8.退出登录
代码语言:javascript复制/*
* @author 友人a丶
* @date 2022-07-11
* 用户退出登录
* */
import user from "@/stores/user";
import Cookies from "js-cookie";
import {router} from "@/router";
import load from "@/common/load";
export default function () {
load.confirm("确认退出登录吗?",()=>{
user().$reset(); //重置用户数据的状态管理器
/*
* 清空cookie
* */
Object.keys(Cookies.get()).forEach((item)=>{
Cookies.remove(item);
})
/*
* 跳转登录界面
* */
router.replace('/login');
})
}
9.获取需要缓存的组件列表
代码语言:javascript复制/*
* @author 友人a丶
* @date 2022-07-11
* name代表组件名
* 获取需要缓存的组件
* */
import routes from '@/router'
export function getChached(path='') {
let routes=router.getRoutes();
let cahced=[]; //是否开启缓存
/*
* 为空代表获取所有一级组件
* */
if(path == ""){
routes.forEach((item)=>{
if(item.meta.cache){
cahced.push(item.meta.cache);
}
});
}else{
/*遍历目标子组件*/
for (let i of routes) {
if (i.path == path) {
i.children.forEach((item)=>{
if(item.meta.cache){
cahced.push(item.meta.cache);
}
});
}
}
}
return cahced;
}
需要考虑
1.如何让显示的菜单响应路由的变化(跳转到某个页面,自动选中某个菜单)?
本身菜单被点击了,自己会变化被选中的状态,需要考虑的是从其他页面跳转过来的时候,如何正常匹配显示被选的菜单;
路由包括静态的路由和有变化的参数路由,某些情况下还会具有参数。
router.matched,与给定路由地址匹配的标准化的路由记录数组。
正则匹配,搭配计算属性;假设业务场景:【顶部是一级菜单,用于打开一个新页面,每个页面都有自身的菜单(二级菜单),菜单下面加包括子菜单】,首先就需要根据上方一级菜单的变化匹配二级菜单,还需要根据当前路由判断哪个子菜单被选中;所有需要条件有:代表当前路由的响应式变量、代表当前一级路由的子路由的响应式变量、代表被选中的菜单的响应式变量,最终如下:
代码语言:javascript复制let selectedKeys = computed({
get() {
let current = route.fullPath;
for (let i = 0; i < items.length; i ) {
//断言右边是空或者?或者/
// 完整匹配或者带参数匹配
let regexp=new RegExp(`(?:.*${items[i].path}$)|(?:.*${items[i].path}[?/].*)`);
if(regexp.test(current)){
return [i];
}
}
},
set(value) {
return;
}
}
);
考虑到参数路由和带有?相关参数的路由,所以正则匹配的是 当前路由与对应的路由完全相等
以及部分相等的同时右边为/或者?
提示
由此还需考虑父路由存在相似的路由片段时,匹配的优先级的问题
2.如何组织目录?
- 代表页面的组件一般以文件夹的形式通过index.js导出组件,方便观察层次结构,并且页面组件一般都会拆分JS模块,通过文件夹也更加方便文件的分类,保持目录的简洁。
- 其他的组件,如果设计到大量的逻辑,需要拆分JS模块,可以用文件夹,如何很简单的直接用.vue文件即可。
- 如何让父子组件的层级更加清晰?首先名字可以按层级写;parent-children.vue。
- 名字较长的组件用“-”分割,更加友好。
3.结构型的组件划分?
- 将布局看组架子(布局组件)、视图看做需要的内容(视图组件),布局承载内容;
- 通过全局状态的设置来动态调整布局组件的显示和隐藏。
4.如何组织无限层级的子路由作为菜单?
模板方式实现起来非常的麻烦,JSX的方式更加适合这种需求;
- 首先需要根据当前路由获取一个可以作为祖先的父级路由对象
5.运行中的router
getRoutes();
获取一个包括所有路由项的数组;不同层次的路由path属性都是从根节点开始的;路由的children属性则是内的子路由是相对路径
不管是push、redirect、route-link,都可以进行相对路径(dynamic)或者绝对路径(/dynamic)跳转;
代码语言:javascript复制[
{
"path": "/spread/tencent",
"name": "腾讯广告",
"meta": {
"role": 0,
"icon": "icon-guangdiantong"
},
"props": {
"default": false
},
"children": [],
"instances": {},
"leaveGuards": {},
"updateGuards": {},
"enterCallbacks": {},
"components": {}
}
]