前端权限

前端权限管理涵盖权限资源、菜单、按钮、列表及表单等多维度,确保用户仅能访问和操作其被授权的内容。

权限资源

本框架维护菜单、按钮资源有两种方式,一种是在菜单管理模块下增、删、改、查,另一种则是通过维护前端路由并同步到菜单,这里主要演示前端路由同步的方式

资源维护

下面以系统管理模块路由为例:

import { AppRouteRecordRaw } from '/@/router/types';
import { LAYOUT } from '/@/router/constant';
const menu: AppRouteRecordRaw = {
  name: 'sys:manager',
  path: '/sys/manager',
  component: LAYOUT,
  meta: {
    title: '系统设置',
    icon: 'ant-design:setting-outlined',
    perms: ['admin', 'sys:manager'],
    orderNo: 100,
    component: 'LAYOUT',
  },
  children: [
    {
      name: 'sys:user',
      path: '/sys/user/index',
      component: () => import('/@/views/sys/user/index.vue'),
      meta: {
        title: '用户管理',
        icon: 'ant-design:user-outlined',
        orderNo: 210,
        perms: ['admin', 'sys:user'],
        component: '/sys/user/index',
        btns: {
          'sys:user:page': '分页查询用户',
          'sys:user:detail': '查看用户详情',
          'sys:user:save': '添加用户',
          'sys:user:update': '修改用户',
          'sys:user:remove': '删除用户',
          'sys:playUser': '扮演用户',
          'sys:user:grantRole': '授权角色',
        },
      },
    },
  ],
};

export default menu;

一级菜单(目录)

一般为目录,没有具体的路由页

import { AppRouteRecordRaw } from '/@/router/types';
import { LAYOUT } from '/@/router/constant';
const menu: AppRouteRecordRaw = {
  name: 'sys:manager',
  path: '/sys/manager',
  component: LAYOUT,
  meta: {
    title: '系统设置',
    icon: 'ant-design:setting-outlined',
    perms: ['admin', 'sys:manager'],
    orderNo: 100,
    component: 'LAYOUT',
  },
  children: [],
}
  • name 组件名称,一般和权限码对应,格式如:a:b:c,用:号分割
  • path 路由地址,由name进行转换,如:a:b:c==>/a/b/c
  • component,组件对象,一级菜单一般为布局组件LAYOUT
  • meta 组件的扩展属性
    • title 标题,中文名称
    • icon 图标,ant-design的图标则以ant-design开头加冒号拼接,可选值可看文件,一般ant-design的图标都支持:src/components/Icon/data/icons.data.ts
    • perms 权限码,用户拥有该权限码则会显示,注:admin是必须配置,方便超管拥有所有权限;
    • orderNo 排序,路由按升序排序
    • component 组件相对地址,为字符串,用于同步给后端的component的,如后续有动态菜单需求,可以次来改造,这里不先不展开。

二级菜单

可为目录,也可为路由(菜单)页 对比一下

  • name: 'sys:user'
  • path: '/sys/user/index'
  • component : () => import('/@/views/sys/user/index.vue')
  • perms: ['admin', 'sys:user']
  • component: '/sys/user/index'

其实就是有一定的规律,也算是一种路由规则。

三级接口/按钮

在上面的例子中,我们还会看到有btn的配置 按钮/接口标识配置

这个其实就是对应这个菜单(路由页)的接口、按钮权限。本着只有前端开发人员才知道该页面用到哪些接口、按钮权限为原则,该信息理应由前端维护,所以就统一放在路由中进行配置。

菜单权限

菜单权限的定义

菜单权限是指定义在管理系统中的左侧菜单(或其他导航结构)中哪些菜单项可以被特定角色或用户访问和操作。通过菜单权限的设置,可以确保不同角色或用户只能看到和操作他们被授权的部分。

新增功能模块与菜单的生成

当在管理系统中新增一个功能模块时,通常需要为这个模块生成对应的菜单项。这个过程可能涉及到前端和后端的协作。前端负责展示菜单项,后端负责处理与这个模块相关的数据和逻辑。本框架主要是通过同步路由清单的方式对菜单进行维护的。

角色与菜单权限的分配

在生成了对应的菜单项之后,下一步是将这些菜单项与角色进行关联,即给角色分配相应的菜单权限。这通常是通过权限管理系统来实现的。管理员可以在权限管理系统中,为不同的角色选择他们可以访问的菜单项,从而控制他们在系统中的操作范围。

按钮权限

在前端实现按钮级权限通常涉及以下几个步骤:

  1. 定义权限数据:首先,你需要定义系统中所有可能的权限,并将它们与特定的角色或用户关联起来。这通常是通过后端服务来完成的,前端通过API接口获取这些数据。
  2. 获取用户权限:当用户登录到系统时,前端需要获取用户的身份信息和相关的权限数据。这通常是通过API调用实现的,本系统通过调用/sys/user/permCode接口来获取用户当前的角色和对应的权限列表。
  3. 渲染按钮:在前端渲染按钮时,根据用户权限数据来决定是否显示某个按钮。这可以通过使用指令(Directives)或组件(Components)来实现。

按钮权限标识维护

对应的是路由配置项的meta.btns 按钮权限标识维护

使用指令

在Vue.js中,你可以创建自定义指令来实现按钮级权限。例如,创建一个名为v-auth的指令,该指令会根据用户权限决定是否渲染某个元素(按钮)。 本框架自定义指令代码,用于实现按钮级权限,具体看:src/directives/index.ts 然后,在你的模板中,你可以使用v-auth指令来控制按钮的显示:

<a-button v-auth="['admin','sys:user:save']">添加用户</a-button>  
<a-button v-auth="['admin','sys:user:update']">修改用户</a-button>  

在这个例子中,'admin'、'sys:user:save'、'sys:user:update'是权限标识符,它们应该与后端定义的权限相匹配。如果用户具有'admin'或'sys:user:save'权限,则第一个按钮将被渲染;如果用户具有'admin'或'sys:user:update'权限,则第二个按钮将被渲染。

使用组件

src/components/Authority/src/Authority.vue

<template>
  <PageWrapper>
    <Authority :value="['admin', 'sys:user:save']">
      <a-button type="primary"> 添加用户 </a-button>
    </Authority>
    <Authority :value="['sys:user:update']">
      <a-button type="primary"> 修改用户 </a-button>
    </Authority>
  </PageWrapper>
</template>
<script setup>
  import Authority from '/@/components/Authority/src/Authority.vue';
</script>

请注意,这种方法仅在前端进行权限检查,后端也应该进行相应的权限验证,因为前端验证可以被绕过。后端验证是确保系统安全性的关键。

列表权限

前面的课程我们也提到过,列表的列配置中是有auth和ifShow配置权限的

auth权限码样例

<template>
  <PageWrapper>
    <BasicTable @register="register" />
  </PageWrapper>
</template>
<script setup>
  import { BasicTable, useTable } from '/@/components/Table';
  import { sysUserPage } from '/@/api/sys/user';
  const columns = [
    {
      title: '用户名',
      dataIndex: 'userName',
    },
    {
      title: '姓名',
      dataIndex: 'realName',
    },
    {
      title: '手机号',
      dataIndex: 'mobilePhone',
    },
    {
      title: '性别',
      dataIndex: 'sex',
      component: 'ApiDict',
      componentProps: {
        code: 'sex',
      },
      auth: 'admin', // 显示
      // auth: 'adminxxxx', // 隐藏
    },
  ];
  const [register] = useTable({
    columns: columns,
    api: sysUserPage,
  });
</script>

ifShow权限码样例

<template>
  <PageWrapper>
    <BasicTable @register="register">
      <template #mm-sex="{ text }">
        <a-tag v-if="text == 1" color="blue"></a-tag>
        <a-tag v-else-if="text == 2" color="red"></a-tag>
        <a-tag v-else>未知</a-tag>
      </template>
    </BasicTable>
  </PageWrapper>
</template>
<script setup>
  import { BasicTable, useTable } from '/@/components/Table';
  import { sysUserPage } from '/@/api/sys/user';
  import { usePermission } from '/@/hooks/web/usePermission';
  const { hasPermission } = usePermission();
  const columns = [
    {
      title: '用户名',
      dataIndex: 'userName',
    },
    {
      title: '姓名',
      dataIndex: 'realName',
    },
    {
      title: '手机号',
      dataIndex: 'mobilePhone',
    },
    {
      title: '管理员类型',
      dataIndex: 'adminType',
      component: 'ApiDict',
      componentProps: {
        code: 'sys_user_admin_type',
      },
      auth: ['admin', 'adminxxx'], // 拥有admin或adminxxx权限码的用户才能看到
    },
    {
      title: '性别',
      dataIndex: 'sex',
      slots: { customRender: 'mm-sex' },
      ifShow() {
        return hasPermission('adminxx');
      },
    },
  ];
  const [register] = useTable({
    showIndexColumn: false, // 是否显示序号
    columns: columns, // 表格列配置
    showTableSetting: true, // 是否显示表格设置
    api: sysUserPage, // 表格请求数据接口
  });
</script>

表单权限

本框架表单字段并没有权限码配置,只能通过ifShow来实现

<template>
  <PageWrapper>
    <BasicForm @register="register" />
  </PageWrapper>
</template>
<script setup>
  import { BasicForm, useForm } from '/@/components/Form';
  import { usePermission } from '/@/hooks/web/usePermission';
  const { hasPermission } = usePermission();
  const schemas = [
    {
      field: 'name',
      label: '名称',
      component: 'Input',
      componentProps: {
        placeholder: '请输入名称',
      },
      ifShow() {
        return hasPermission(['adminxx']);
      },
    },
    {
      field: 'remark',
      label: '备注',
      component: 'InputTextArea',
      componentProps: {
        placeholder: '请输入备注',
      },
    },
  ];
  const [register] = useForm({
    schemas: schemas,
    submitButtonOptions: {
      text: '提交',
    },
  });
</script>