权限管理:菜单管理

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

功能说明

权限管理内部划分为:

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

菜单权限

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

资源权限

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

角色

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

功能关系:

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



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

菜单管理

添加菜单,整体布局

使用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,所以提交操作就不需要处理了

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

推荐阅读更多精彩内容