从零开始,手打一个权限管理系统(第八章 完善前端代码)

前言

这章主要是清除前端多余的代码,移除不用的模块和包,完善用户管理,角色管理,菜单管理,机构管理!


一、清除多余的模块和代码

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
代码仓库


七、 体验地址

后台数据库只给了部分权限,报错属于正常!
想学的老铁给点点关注吧!!!
不清楚明白的地方欢迎留言,空了我会给大家解答的!!!

我是阿咕噜,一个从互联网慢慢上岸的程序员,如果喜欢我的文章,记得帮忙点个赞哟,谢谢!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,607评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,239评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,960评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,750评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,764评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,604评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,347评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,253评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,702评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,893评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,015评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,734评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,352评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,934评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,052评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,216评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,969评论 2 355

推荐阅读更多精彩内容