美文网首页前端开发那些事儿
权限管理:菜单管理

权限管理:菜单管理

作者: amanohina | 来源:发表于2021-03-05 13:53 被阅读0次

    权限管理是后台管理系统的核心功能,要给不同工作岗位的用户分配不同的操作权限,就需要进行权限管理

    功能说明

    权限管理内部划分为:

    • 菜单权限
    • 资源权限
    • 角色

    菜单权限

    控制登录到后台的用户能够访问到哪些后台菜单页面,比如负责广告的人员只能看到广告管理,课程人员只能看到课程管理,就需要进行不同的菜单权限分配

    资源权限

    资源对应的是接口,资源权限用于控制用户能够操作哪些接口功能,比如分配资源权限的时候没有禁用用户权限,指的是没有操作这个接口的权限。资源权限与菜单权限不冲突,如果有的用户能够看到用户管理页面,也可以添加用户(有权限操作新增用户接口),但是没法进行禁用用户操作(无禁用用户的接口权限)

    角色

    代表了菜单权限和资源权限的一种组合方式,比如我设置了多个用户需要相同的菜单权限和资源权限, 就可以将这些权限组合起来,设置为角色,再将角色分配给用户简化操作
    在项目中,不会直接对某个用户进行菜单权限或者资源权限的分配,而是提前根据岗位清空设定不同的角色,再将角色分配给用户就可以了

    功能关系:

    用户需要分配角色,角色需要分配菜单权限和资源权限



    由于功能之间存在依赖,我们先从菜单权限和资源权限功能开始制作,最后再依次完成角色和用户的功能

    菜单管理

    添加菜单,整体布局

    使用Element的Card卡片
    添加到views/menu/index.vue,将标题区域更改为添加菜单按钮,添加后跳转到菜单组件

    <template>
      <div class="menu">
        <el-card class="box-card">
          <div slot="header" class="clearfix">
            <el-button
            @click="$router.push({ name: 'menu-create' })"
            >添加菜单</el-button
            >
          </div>
          <div v-for="o in 4" :key="o" class="text item">
            {{ "列表内容 " + o }}
          </div>
        </el-card>
      </div>
    </template>
    
    <script>
    export default {
      name: 'MenuIndex'
    }
    </script>
    
    <style lang="scss" scoped>
    
    </style>
    
    

    在menu目录下创建menuCreate.vue,并且创建初始结构

    <template>
      <div class="menu-create">添加菜单</div>
    </template>
    
    <script>
    export default {
      name: 'MenuCreate'
    }
    </script>
    
    <style lang="scss" scoped>
    
    </style>
    
    

    在路由表中添加上去

    // router/index.js
        ...
      {
        path: '/menu/menuCreate',
        name: 'menu-create',
        component: () => import(/* webpackChunkName: 'menu-create' */'@/views/menu/menuCreate')
      }
    ]
    

    下面通过Element的Card套Form的方式给menu-create布局

    • 将Card头部设置为标题
    • 将Card内容替换为Form
      数据绑定的名称可以通过接口来设置,请求的接口为:菜单管理->保存或者新增菜单
      由于接口的请求参数需要为application/json,使用postman测试的时候要进行对应的选择

      将数据声明在data中,并且绑定到视图上
    <template>
      <div class="menu-create">
        <el-card class="box-card">
          <div slot="header" class="clearfix">
            <span>添加菜单</span>
          </div>
          <el-form ref="form" :model="form" label-width="80px">
            <el-form-item label="菜单名称">
              <el-input v-model="form.name"></el-input>
            </el-form-item>
            <el-form-item label="菜单路径">
              <el-input v-model="form.href"></el-input>
            </el-form-item>
            <el-form-item label="上级菜单">
              <el-select v-model="form.parentId" placeholder='请选择上级菜单'>
                <el-option label="区域一" value="shanghai"></el-option>
                <el-option label="区域二" value="beijing"></el-option>
              </el-select>
            </el-form-item>
            <el-form-item label="描述">
              <el-input v-model="form.description"></el-input>
            </el-form-item>
            <el-form-item label="前段图标">
              <el-input v-model="form.icon"></el-input>
            </el-form-item>
            <el-form-item label="是否显示">
              <el-radio-group v-model="form.shown">
    <!-- label的数据会在选择后设置给v-model的shown -->
                <el-radio :label="true">是</el-radio>
                <el-radio :label="false">否</el-radio>
              </el-radio-group>
            </el-form-item>
            <el-form-item label="排序">
              <el-input-number
                v-model="form.orderNum"
                aria-label="显示信息"
              ></el-input-number>
            </el-form-item>
            <el-form-item>
              <el-button type="primary" @click="onSubmit">提交</el-button>
              <el-button>取消</el-button>
            </el-form-item>
          </el-form>
        </el-card>
      </div>
    </template>
    
    <script>
    export default {
      name: 'MenuCreate',
      data () {
        return {
          form: {
            parentId: null,
            name: '',
            href: '',
            icon: '',
            orderNum: null,
            description: '',
            shown: true
          }
        }
      },
      methods: {
        onSubmit () {
          console.log('submit!')
        }
      }
    }
    </script>
    
    <style lang="scss" scoped>
    </style>
    
    

    完成

    上级菜单的处理

    上级菜单数据需要请求接口才能得到,并且要渲染到模板中
    用于获取菜单的接口有两个

    • 获取所有的菜单
    • 获取编辑菜单页面信息
      通过功能实例观察可以得知,菜单分为一级和二级,二级菜单无法作用“上一级菜单”来使用,所以我们应该使用列举的第二个菜单来操作
      接口使用时需要传请求参数id,添加传入id=-1,编辑传入相对应id,区别为添加操作的菜单信息menuInfo为null
      仅添加用id=-1
      有id
      我们需要使用的是parentMenuList字段信息,其中的每个元素都为一级菜单,元素如果有subMenuList就说明他存在有子菜单(二级)

    接下来是设置接口的请求方法

    import request from '@/utils/request'
    
    // 获取编辑菜单页面信息
    export const getEditMenuInfo = (id = -1) => {
      return request({
        method: 'GET',
        // url: `/boss/menu/getEditMenuInfo?id=${id}`
        // 上面是模板字符串写法
        // 下面是GET请求时,使用params,如果请求方式是POST,那就是data
        url: '/boss/menu/getEditMenuInfo',
        params: {
          id
        }
      })
    }
    
    

    menuCreate.vue中请求数据

    添加一个专门用户存储上级菜单数据的数组,导入menu.js中的模块
    如若是请求成功了,就将响应数据的parentMenuList保存到data中进行列表渲染
    // menu-create.vue
    ...
    <el-form-item label="上级菜单">
      <el-select v-model="form.region" placeholder="请选择上级菜单">
        <el-option
          :label="item.name"
          :value="item.id"
          v-for="item in parentMenuList"
          :key="item.id"
        ></el-option>
      </el-select>
    </el-form-item>
    ...
    

    添加无上级菜单选项(如果你添加的是一级菜单的话就这么选)

    // menu-create.vue
    ...
    <!-- 将下拉菜单数据绑定为 parentId,默认为-1 -->
    <el-select v-model="form.parentId" placeholder="请选择上级菜单">
      <!-- 无上级菜单选项 -->
      <el-option
        :value="-1"
        label="无上级菜单"
      ></el-option>
      <!-- 动态选项 -->
      <el-option
        :label="item.name"
        :value="item.id"
        v-for="item in parentMenuList"
        :key="item.id"
      ></el-option>
    </el-select>
    ...
    

    添加菜单提交

    首先要将用于添加菜单的请求功能封装到services/menu.js模块中,由于文件不存在,先进行创建

    // services/menu.js
    import request from '@/utils/request'
    
    // 添加菜单请求功能
    export const createOrUpdateMenu = data => {
      return request({
        method: 'POST',
        url: '/boss/menu/saveOrUpdate',
        // 当前请求参数为 application/json,无需通过 qs 处理
        data
      })
    }
    

    在createMenu.vue中引入并在在点击提交按钮时发送请求,顺便检验一下是否成功了,成功了要记得提示和跳转

    import { getEditMenuInfo, creatOrUpdateMenu } from '@/services/menu'
    ...
    async onSubmit () {
          // 1.先进行一个表单校验
          // 2.发送请求
          const { data } = await creatOrUpdateMenu(this.form)
          if (data.code === '000000') {
            this.$message.success('提交成功')
            this.$router.push({
              name: 'menu'
            })
          }
        },
    ...
    

    菜单列表展示

    展示数据相关列表,使用Element的Table表格组件进行处理,并且根据我们的项目功能,修改模板表格的内容

    // menu/index.vue
    <!-- 菜单列表展示区域 -->
    <el-table
      :data="tableData"
      style="width: 100%">
      <el-table-column
        prop="date"
        label="编号">
      </el-table-column>
      <el-table-column
        prop="name"
        label="菜单名称">
      </el-table-column>
      <el-table-column
        prop="address"
        label="菜单级数">
      </el-table-column>
      <el-table-column
        prop="address"
        label="前端图标">
      </el-table-column>
      <el-table-column
        prop="address"
        label="排序">
      </el-table-column>
      <el-table-column
        prop="address"
        label="操作">
      </el-table-column>
    </el-table>
    

    封装接口请求功能

    // services/menu.js
    ...
    // 获取所有菜单
    export const getAllmenu = () => {
      return request({
        method: 'GET',
        url: '/boss/menu/getAll'
      })
    }
    

    引入并且请求数据,请求成功保存到data中



    数据展示

    // menu/index.vue
    <!-- 将 menus 绑定给 el-table 组件的 data 属性 -->
    <el-table
      :data="menus"
      style="width: 100%">
      <!-- 编号通过组件提供的 type="index" 设置 -->
      <el-table-column
        label="编号"
        type="index">
      </el-table-column>
      <!-- 后续的数据通过 prop 设置为 menus 中对象的对应属性名 -->
      <el-table-column
        prop="name"
        label="菜单名称">
      </el-table-column>
      <el-table-column
        prop="level"
        label="菜单级数">
      </el-table-column>
      <el-table-column
        prop="icon"
        label="前端图标">
      </el-table-column>
      <el-table-column
        prop="orderNum"
        label="排序">
      </el-table-column>
      <!-- 操作中不是内容,而是操作按钮,无需设置 prop,结构单独处理,宽度 150 可选 -->
      
      <el-table-column
        label="操作"
        width=""150>
      </el-table-column>
    </el-table>
    

    操作部分的内容需要对Table进行自定义
    注意:

    • Element的Table组件使用的slot-scope="scope"是Vue.js在2.6版本之前的作用域插槽语法现在已经被废弃,现行版本语法中应该使用v-slot指令进行作用域插槽的设置
    • scope是作用域插槽中接收的,由组件内部提供的数据,可以自行命名并且在template中使用
      • $index代表的是索引
      • row代表当前行信息(数据)
        是否使用取决于我们的需求
    <!-- 操作中不是内容,而是按钮,单独处理 -->
    <el-table-column
      label="操作"
      width="180">
      <!-- 自定义列模板 -->
      <template slot-scope="scope">
        <el-button
          size="mini"
          @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
        <el-button
          size="mini"
          type="danger"
          @click="handleDelete(scope.$index, scope.row)">删除</el-button>
      </template>
    </el-table-column>
    ...
    <script>
    ...
      methods: {
        ...
        handleEdit (index, rowData) {
          // 输出观察数据,后续根据需要可选择删除或保留
          console.log('编辑', index, rowData)
        },
        handleDelete () {
          console.log('删除')
        }
      }
    ...
    </script>
    

    删除菜单

    点击删除的时候,提示用户确认,并且使用当前行数据信息进行删除请求

    // menu/index.vue
    ...
    handleDelete () {
      // 确认提示(参数3的具体配置不需要可省略)
      this.$confirm('确认删除吗?', '删除提示')
        .then(() => {
          // 发送删除请求
          
        })
        .catch(() => {
          // 取消提示
          this.$message.info('已取消删除')
        })
    }
    ...
    

    删除接口:地址,使用Postman测试没问题之后投入使用

    // services/menu.js
    ...
    // 删除指定菜单
    export const deleteMenu = id => {
      return request({
        method: 'DELETE',
        url: `/boss/menu/${id}`
      })
    }
    
    // menu/index.vue
    <template>
        ...
            <el-button
          size="mini"
          type="danger"
          @click="handleDelete(scope.row)">删除</el-button>
        ...
    </template>
    <script>
    import { getAllMenus, deleteMenu } from '@/services/menu'
    ...
    handleDelete (rowData) {
      this.$confirm('确认删除吗?', '删除提示')
        .then(async () => {
          // 发送删除请求
          const { data } = await deleteMenu(rowData.id)
          if (data.code === '000000') {
            this.$message.success('删除成功')
            // 更新数据列表
            this.loadAllMenus()
          }
        })
        ...
    }
    </script>
    

    编辑菜单

    布局处理

    观察项目的时候发现,添加菜单和编辑菜单的组件结构几乎是一模一样的,可以封装为组件进行复用

    • 创建menu/components/CreateOrEdit.vue
    • 将menu/menuCreate.vue内容复制到CreateOrEdit.vue中
      • 更改name
      • create-or-edit组件通过props接收父组件的数据isEdit来判断展示哪种结构
      • 标题判断处理
    // menu/components/create-or-edit.vue
    <template>
    ...
        <div slot="header" class="clearfix">
        <!-- 设置标题 -->
        <span>{{ isEdit ? '编辑菜单' : '添加菜单' }}</span>
      </div>
    ...
    </template>
    <script>
    ...
        // 更改 name
        name: 'CreateOrEdit',
        // 接收父组件传值,判断是添加还是编辑功能
      props: {
        isEdit: {
          // 要求类型为布尔,默认值 false
          type: Boolean,
          default: false
        }
      },
    ...
    </script>
    

    去除menuCreate.vue中的多余内容

    • 根元素内的所有结构
    • data,created,methods中的内容
    • 引入组件CreateOrEdit
    <template>
      <div class="menu-create">
        <!-- 将添加功能封装到了单独组件 -->
        <create-or-edit></create-or-edit>
      </div>
    </template>
    
    <script>
    import CreateOrEdit from './components/CreateOreEdit'
    export default {
      name: 'MenuCreate',
      components: {
        CreateOrEdit
      }
    }
    </script>
    
    <style lang="scss" scoped>
    </style>
    
    

    创建menuEdit.vue组件,设置内容

    <template>
      <div class="menu-edit">
          <!-- 引入并通过is-edit -->
          <create-or-edit :is-edit="true"></create-or-edit>
      </div>
    </template>
    
    <script>
    import CreateOrEdit from './components/CreateOreEdit'
    export default {
      name: 'MenuEdit',
      components: {
        CreateOrEdit
      }
    }
    </script>
    
    <style lang="scss" scoped>
    
    </style>
    
    

    将菜单编辑添加到路由表中

    • 由于编辑为某个菜单的编辑,应设置动态路由展示菜单项id
    // router/index.js
        ...
        {
        path: '/menu/:id/edit',
        name: 'menu-edit',
        component: () => import(/* webpackChunkName: 'menu-edit' */'@/views/menu/edit')
      }
    ]
    ...
    

    给menu/index.vue中的编辑按钮设置点击后的路由跳转

    // menu/index.vue
    <template>
    ...
        <el-button
        size="mini"
        @click="handleEdit(scope.row)">编辑</el-button>
    ...
    </template>
    <script>
    ...
    handleEdit (rowData) {
      // 导航到菜单编辑页
      this.$router.push({
        name: 'menu-edit',
        // 传递动态路由参数
        params: {
          id: rowData.id
        }
      })
    },
    ...
    </script>
    

    逻辑处理

    开始之前补充一点,组件CreateOrEdit.vue中的重置按钮,应当设置一个全部清空重置的点击事件



    设置完毕,让我们开始逻辑处理的部分吧:
    编辑功能中,CreateOrEdit的表单不需要重置按钮,通过v-if判断
    <!-- 编辑功能中无需渲染重置按钮 -->
    <el-button
      v-if="!isEdit"
    >重置</el-button>
    

    编辑时,将要编辑的菜单项信息展示在表单中

    • 之前操作中分析过,getEditMenuInfo接口在编辑功能时可以获取到菜单信息,添加时为空
    • 这里需要将动态路由的参数传入,并给添加功能设置默认值-1
    • 将响应数据的menuInfo赋值给data中的data.form就可以了(属性名是对应的,没有差错)
    async loadMenuInfo () {
          // 检测是否存在路由参数id,并且进行对应处理
          const id = this.$route.params.id || -1
          // 请求菜单数据,上级菜单数据(一级)
          const { data } = await getEditMenuInfo(id)
          if (data.code === '000000') {
            // 上级菜单数据保存,进行数据绑定
            this.parentMenuList = data.data.parentMenuList
            // 检测是否存在菜单数据menuinfo,有的话就保存到form
            if (data.data.menuInfo) {
              this.form = data.data.menuInfo
            }
          }
        },
    

    由于添加和编辑时同一个接口,区别在于编辑时是否多了参数id,由于提交时传入为form数据,编辑提交时就会自动包含id,所以提交操作就不需要处理了

    相关文章

      网友评论

        本文标题:权限管理:菜单管理

        本文链接:https://www.haomeiwen.com/subject/pcxkqltx.html