前言
这章主要是清除前端多余的代码,移除不用的模块和包,完善用户管理,角色管理,菜单管理,机构管理!
一、清除多余的模块和代码
1、所有跟mock相关的不需要
2、跟国际化相关的不需要(用的上国际化的可以留着)
3、多余的路由配置不需要
4、其他用不上的组件库
二、Vue请求库
我用的是vue-request,不了解的可以先学习下,后面用到地方很多,像接口请求、手动触发请求、loading、分页、刷新、轮询等,都可以用到。
三、分页配置
用法可以参考vue-request的官方文档。
import { usePagination } from 'vue-request';
分页的默认配置我们自己写一个pagination.ts,放到util目录下
import { PaginationOptions } from 'vue-request';
/**
* 后台的默认分页查询信息
*/
const defaultPageOptions: PaginationOptions<any, any> = {
// 默认大小改成20
// @ts-ignore
defaultParams: [{ current: 1, size: 10 }] as any,
// 默认 10毫秒
loadingDelay: 10,
// 默认有个10毫秒的判定 阻止连续触发问题 拖到最后
debounceInterval: 10,
pagination: {
/**
* 后端的分页数据格式
*/
// 当前页面
currentKey: 'current',
// 分页大小的key
pageSizeKey: 'size',
// 所有页面
totalPageKey: 'pages',
// 总条数
totalKey: 'total',
},
};
export default defaultPageOptions;
四、显隐配置
对应模态框的显示和隐藏我们也需要一个公共的方法来统一处理,这个钩子方法我放到hooks目录下。
visible.ts
/**
* 定义公用模态框的类型信息
* 方便对模态框弹出等处理
*
*/
import { computed, watch } from 'vue';
/**
* 显示隐藏对应的props
* 和类型信息
*/
export interface Visible {
visible: boolean;
}
/**
* 相关的emits对应类型
* 参数等定义
*/
export interface VisibleEmits {
(e: 'update:visible', visible: boolean): void;
}
/**
* composable 对应的显示隐藏属性
* @param props 属性
* @param emits 事件
* @returns
*/
export const useVisible = (props: Visible, emits: VisibleEmits) => {
// 间接托管的可见状态
const formVisible = computed({
get: () => props.visible,
set: (v) => emits('update:visible', v),
});
return {
formVisible,
/**
* 当状态变为打开的时候
* @param callback 回调执行
* @returns
*/
open: (callback: () => any) => watch(formVisible, (v) => v && callback()),
/**
* 关闭方法
*/
close: () => {
formVisible.value = false;
},
};
};
五、动态加载菜单
官方文档都是把路由菜单写死到文件里面的,但是实际项目中都是从后台动态加载的,我们修改一下,改成从后端读取菜单数据。
代码逻辑也很简单,就是在用户登录完成后请求菜单数据,然后组装菜单,添加到路由就可以了,核心代码如下:
MenuStore
import { defineStore } from 'pinia';
import { getMenuList } from '@/api/upms/menu';
import { RouteRecordRaw } from 'vue-router';
import { MenuState, Menu } from './types';
const components = import.meta.glob('@/views/**/index.vue');
const modules = Object.fromEntries(
Object.entries(components).map(([k, v]) => [k.slice(5, -4), v])
);
const useMenuStore = defineStore('menu', {
state: (): MenuState => ({
serverMenu: [],
}),
getters: {
asyncMenus(state: MenuState): RouteRecordRaw[] {
return state.serverMenu as RouteRecordRaw[];
},
},
actions: {
generatorDynamicRouter(menuList: Menu[], parent: string): RouteRecordRaw[] {
return menuList?.map((item) => {
const { component, name, icon, path, sort, title } = item;
// ??为NULL判断运算符,运算符左侧的值为null或undefined时,才会返回右侧的值
const children = item.children ?? [];
const meta = {
icon,
title,
sort,
};
const isChild = children != null && children.length !== 0;
const currentRouter: RouteRecordRaw = {
// 路由地址动态拼接生成如 /dashboard/workplace
path: parent ? `${parent}/${path}` : `${path || ''}`,
name,
meta,
children: [],
// 动态导入页面
component:
!component || component === 'Layout'
? () => import('@/layout/default-layout.vue')
: modules?.[component],
};
// 为了防止出现后端返回结果不规范,处理有可能出现拼接出两个 反斜杠
if (!currentRouter.path.startsWith('http')) {
currentRouter.path = currentRouter.path.replace('//', '/');
}
// 是否有子菜单,并递归处理
if (isChild) {
currentRouter.children = this.generatorDynamicRouter(
children,
currentRouter.path
);
}
return currentRouter;
});
},
async fetchServerMenuConfig() {
const data = await getMenuList();
this.serverMenu = this.generatorDynamicRouter(data, '');
},
clearServerMenu() {
this.serverMenu = [];
},
},
});
export default useMenuStore;
路由守卫:
在这里插入图片描述
六、管理页面
管理页面就很简单,参考官方的文档写起来就没啥难度,这里我就不再啰嗦了。前端页面大部分都是相通的,写好一个其他的都可以复用,看看我效果:
在这里插入图片描述
当前版本tag:1.0.7
代码仓库
七、 体验地址
后台数据库只给了部分权限,报错属于正常!
想学的老铁给点点关注吧!!!
不清楚明白的地方欢迎留言,空了我会给大家解答的!!!
我是阿咕噜,一个从互联网慢慢上岸的程序员,如果喜欢我的文章,记得帮忙点个赞哟,谢谢!