实战 9 权限菜单管理

目录

1、权限菜单后端接口

2、查询权限菜单列表

2.1 设计效果图

2.2 menuList.vue

3、 新增权限菜单

3.1 新增权限菜单窗口代码

3.2 选择所属菜单代码

3.3 封装图标选择器

3.4 新增、编辑和删除权限菜单


1、权限菜单后端接口

package com.cizhu.service;

import com.cizhu.entity.Permission;
import com.baomidou.mybatisplus.extension.service.IService;
import com.cizhu.vo.query.PermissionQueryVo;

import java.util.List;

/**
 * <p>
 *  服务类
 * </p>
 *
 * @author cizhu
 * @since 2023-12-14
 */
public interface IPermissionService extends IService<Permission> {
    /**
     * 根据用户ID查询菜单列表
     * @param userId
     * @return
     */
    List<Permission> findPermissionListByUserId(Long userId);


    List<Permission> findPermissionList(PermissionQueryVo permissionQueryVo);

    boolean hasChildrenOfPermission(Long id);

    List<Permission> findParentPermissionList();
}
package com.cizhu.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.cizhu.entity.Permission;
import com.cizhu.mapper.PermissionMapper;
import com.cizhu.service.IPermissionService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cizhu.utils.MenuTree;
import com.cizhu.vo.query.PermissionQueryVo;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Arrays;
import java.util.List;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author cizhu
 * @since 2023-12-14
 */
@Service
@Transactional
public class PermissionServiceImpl extends ServiceImpl<PermissionMapper, Permission> implements IPermissionService {

    @Override
    public List<Permission> findPermissionListByUserId(Long userId) {
        return baseMapper.findPermissionListByUserId(userId);
    }

    @Override
    public List<Permission> findPermissionList(PermissionQueryVo permissionQueryVo) {
        //创建条件构造器对象
        QueryWrapper<Permission> queryWrapper = new QueryWrapper<Permission>();
        //排序
        queryWrapper.orderByAsc("order_num");
        //调用查询菜单列表的方法
        List<Permission> permissionList = baseMapper.selectList(queryWrapper);
        //生成菜单树
        List<Permission> menuTree = MenuTree.makeMenuTree(permissionList, 0L);
        //返回数据
        return menuTree;
    }

    @Override
    public boolean hasChildrenOfPermission(Long id) {
        return false;
    }

    @Override
    public List<Permission> findParentPermissionList() {
        QueryWrapper<Permission> queryWrapper = new QueryWrapper<Permission>();
        //只查询type为目录和菜单的数据(type=0或type=1)
        queryWrapper.in("type", Arrays.asList(0,1));
        //排序
        queryWrapper.orderByAsc("order_num");
        //查询菜单数据
        List<Permission> permissionList = baseMapper.selectList(queryWrapper);
        //构造顶级菜单信息,如果数据库中的菜单表没有数据,选择上级菜单时则显示顶级菜单
        Permission permission = new Permission();
        permission.setId(0L);
        permission.setParentId(-1L);
        permission.setLabel("顶级菜单");
        permissionList.add(permission);//将顶级菜单添加到集合
        //生成菜单数据
        List<Permission> menuTree = MenuTree.makeMenuTree(permissionList, -1L);
        //返回数据
        return menuTree;
    }

}
package com.cizhu.controller;


import com.cizhu.entity.Permission;
import com.cizhu.service.IPermissionService;
import com.cizhu.utils.Result;
import com.cizhu.vo.query.PermissionQueryVo;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.List;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author cizhu
 * @since 2023-12-14
 */
@RestController
@RequestMapping("/api/permission")
public class PermissionController {
    @Resource
    private IPermissionService permissionService;

    /**
     * 查询菜单列表
     * @param permissionQueryVo
     * @return
     */
    @GetMapping("/getPermissionList")
    @PreAuthorize("hasAuthority('sys:menu:select')")
    public Result getPermissionList(PermissionQueryVo permissionQueryVo){
        List<Permission> resultPermissionList = permissionService.findPermissionList(permissionQueryVo);
        return Result.ok(resultPermissionList);
    }

    /**
     * 查询上级菜单列表
     * @return
     */
    @GetMapping("/parent/list")
    public Result getParentPermissionList(){
        List<Permission> parentPermissionList = permissionService.findParentPermissionList();
        return Result.ok(parentPermissionList);
    }

    /**
     * 根据id查询菜单信息
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    public Result getMenuById(@PathVariable Long id){
        return Result.ok(permissionService.getById(id));
    }

    /**
     * 添加菜单
     * @param permission
     * @return
     */
    @PostMapping("/addPermission")
    @PreAuthorize("hasAuthority('sys:menu:add')")
    public Result addPermission(@RequestBody Permission permission){
        if (permissionService.save(permission)) return Result.ok().message("菜单新增成功!");
        return Result.error().message("菜单新增失败!");
    }

    /**
     * 修改菜单
     * @param permission
     * @return
     */
    @PutMapping("/editPermission")
    @PreAuthorize("hasAuthority('sys:menu:edit')")
    public Result editPermission(@RequestBody Permission permission){
        if(permissionService.updateById(permission)) return Result.ok().message("修改菜单成功!");
        return Result.error().message("修改菜单失败!");
    }

    /**
     * 根据id删除菜单
     * @param id
     * @return
     */
    @DeleteMapping("/delete/{id}")
    @PreAuthorize("hasAuthority('sys:menu:delete')")
    public Result deletePermissionById(@PathVariable Long id){
        if (permissionService.removeById(id)) return Result.ok().message("删除菜单成功!");
        return Result.ok().message("删除菜单失败!");
    }

    /**
     * 检查菜单是否有子菜单
     * @param id
     * @return
     */
    @GetMapping("/check/{id}")
    public Result hasChildrenOfPermission(@PathVariable Long id){
        if (permissionService.hasChildrenOfPermission(id)) {
            return Result.exist().message("该菜单下有子菜单,不能删除!");
        }
        return Result.ok();
    }

}

package com.cizhu.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;

/**
 * <p>
 * 
 * </p>
 *
 * @author cizhu
 * @since 2023-12-14
 */
@Getter
@Setter
@TableName("sys_role_permission")
@ApiModel(value = "RolePermission对象", description = "")
public class RolePermission implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty("角色ID")
    private Long roleId;

    @ApiModelProperty("权限ID")
    private Long permissionId;


}
package com.cizhu.vo.query;

import com.cizhu.entity.Permission;
import lombok.Data;

@Data
public class PermissionQueryVo extends Permission {
}
package com.cizhu.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;

/**
 * <p>
 * 
 * </p>
 *
 * @author cizhu
 * @since 2023-12-14
 */
@Getter
@Setter
@TableName("sys_permission")
@ApiModel(value = "Permission对象", description = "")
public class Permission implements Serializable {

    /**
     * 子菜单列表
     */
    @JsonInclude(JsonInclude.Include.NON_NULL) //属性值为null不进行序列化操作
    @TableField(exist = false)
    private List<Permission> children = new ArrayList<Permission>();
    /**
     * 用于前端判断是菜单、目录或按钮
     */
    @TableField(exist = false)
    private String value;
    /**
     * 是否展开
     */
    @TableField(exist = false)
    private Boolean open;


    private static final long serialVersionUID = 1L;


    @ApiModelProperty("权限编号")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    @ApiModelProperty("权限名称")
    private String label;

    @ApiModelProperty("父权限ID")
    private Long parentId;

    @ApiModelProperty("父权限名称")
    private String parentName;

    @ApiModelProperty("授权标识符")
    private String code;

    @ApiModelProperty("路由地址")
    private String path;

    @ApiModelProperty("路由名称")
    private String name;

    @ApiModelProperty("授权路径")
    private String url;

    @ApiModelProperty("权限类型(0-目录 1-菜单 2-按钮)")
    private Integer type;

    @ApiModelProperty("图标")
    private String icon;

    @ApiModelProperty("创建时间")
    private Date createTime;

    @ApiModelProperty("修改时间")
    private Date updateTime;

    @ApiModelProperty("备注")
    private String remark;

    @ApiModelProperty("排序")
    private Integer orderNum;

    @ApiModelProperty("是否删除(0-未删除,1-已删除)")
    private Integer isDelete;

}

2、查询权限菜单列表

2.1 设计效果图

2.2 menuList.vue

列表页面原型代码,编写前端api脚本和页面组件代码

<template>
  <el-main>
    <!-- 新增按钮 -->
    <el-button type="success" size="small" @click="openAddWindow()" icon="el-icon-plus">新增</el-button>
    
    <!-- 数据表格 -->
    <el-table style="margin-top: 10px" :height="tableHeight" :data="menuList" row-key="id"
      default-expand-all :tree-props="{ children: 'children' }" :border="true" stripe>
      <el-table-column prop="label" label="菜单名称"></el-table-column>
      <el-table-column prop="type" label="菜单类型" align="center">
        <template slot-scope="scope">
          <el-tag v-if="scope.row.type == '0'" size="normal">目录</el-tag>
          <el-tag type="success" v-else-if="scope.row.type == '1'" size="normal">菜单</el-tag>
          <el-tag type="warning" v-else-if="scope.row.type == '2'" size="normal">按钮</el-tag>
        </template>
      </el-table-column>
      <el-table-column prop="icon" label="图标" align="center">
        <template slot-scope="scope">
          <i :class="scope.row.icon" v-if="scope.row.icon.includes('el-icon')" ></i>
          <svg-icon v-else :icon-class="scope.row.icon"></svg-icon>
        </template>
      </el-table-column>
      <el-table-column prop="name" label="路由名称"></el-table-column>
      <el-table-column prop="path" label="路由地址"></el-table-column>
      <el-table-column prop="url" label="组件路径"></el-table-column>
      <el-table-column label="操作" align="center">
        <template slot-scope="scope">
          <el-button type="primary" icon="el-icon-edit" size="small" @click="editMenu(scope.row)">编辑</el-button>
          <el-button type="danger" size="small" icon="el-icon-delete" @click="deleteMenu(scope.row)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
    </el-main>
</template>

<style lang="scss" scoped>

</style>

 menu.js

import http from '@/utils/request'

export default {

  /**
  * 查询权限菜单列表
  * @param params
  */
  async getMenuList(params){
    return await http.get("/api/permission/list",params);
  },

  /**
  * 获取上级菜单
  * @returns
  */
  async getParentMenuList(params) {
    return await http.get("/api/permission/parent/list", params)
  },

  /**
  * 添加菜单
  * @returns
  */
  async addMenu(params){
    return await http.post("/api/permission/add",params)
  },

  /**
  * 修改菜单
  * @returns
  */
  async updateMenu(params) {
    return await http.put("/api/permission/update", params);
  },

  /**
  * 检查菜单下是否存在子菜单
  */
  async checkPermission(param){
    return await http.getRestApi("/api/permission/check",param);
  },

  /**
  * 删除菜单
  * @returns
  */
  async deleteById(params) {
    return await http.delete("/api/permission/delete", params);
  }
}

1) 菜单类型,图标

2)滚动条样式

public/index.html

 ::-webkit-scrollbar{
  width: 5px;
  height: 5px;
  background-color: #F5F5F5;
  }
  ::-webkit-scrollbar-track {
  box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
  -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
  border-radius: 8px;
  background-color: #F5F5F5;
  }
  ::-webkit-scrollbar-thumb{
  border-radius: 8px;
  box-shadow: inset 0 0 6px rgba(0, 0, 0, .1);
  -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .1);
  background-color: #c8c8c8;
  }

3、 新增权限菜单

3.1 新增权限菜单窗口代码

 <!-- 新增或编辑弹框 -->
    <system-dialog
    :title="menuDialog.title"
    :width="menuDialog.width"
    :height="menuDialog.height"
    :visible="menuDialog.visible"
    @onClose="onClose"
    @onConfirm="onConfirm">
      <div slot="content">
        <el-form :model="menu" ref="menuForm" :rules="rules" label-width="80px" :inline="true" size="small">
          <el-col :span="24">
            <el-form-item label="菜单类型" prop="type">
              <el-radio-group v-model="menu.type">
                <el-radio :label="0">目录</el-radio>
                <el-radio :label="1">菜单</el-radio>
                <el-radio :label="2">按钮</el-radio>
              </el-radio-group>
            </el-form-item>
          </el-col>
          <el-form-item label="所属菜单" size="small" prop="parentName">
            <el-input v-model="menu.parentName" :readonly="true" @click.native="selectParentMenu()" />
          </el-form-item>
          <el-form-item label="菜单名称" size="small" prop="label">
            <el-input v-model="menu.label"></el-input>
          </el-form-item>
          <el-form-item label="路由名称" size="small" prop="name" v-if="menu.type == 1">
            <el-input v-model="menu.name"></el-input>
          </el-form-item>
          <el-form-item label="路由地址" size="small" prop="path" v-if="menu.type != 2">
            <el-input v-model="menu.path"></el-input>
          </el-form-item>
          <el-form-item label="组件路径" size="small" prop="url" v-if="menu.type == 1">
            <el-input v-model="menu.url"></el-input>
          </el-form-item>
          <el-form-item label="权限字段" size="small" prop="code">
            <el-input v-model="menu.code"></el-input>
          </el-form-item>
          <el-form-item label="菜单图标" size="small" >
            <my-icon @selecticon="setIcon" ref="child"></my-icon>
          </el-form-item>
          <el-form-item label="菜单序号" size="small">
            <el-input v-model="menu.orderNum"></el-input>
          </el-form-item>
        </el-form>
      </div>
    </system-dialog>

3.2 选择所属菜单代码


    <!-- 选择所属菜单弹框 -->
    <system-dialog
    :title="parentDialog.title"
    :width="parentDialog.width"
    :height="parentDialog.height"
    :visible="parentDialog.visible"
    @onClose="onParentClose"
    @onConfirm="onParentConfirm">
      <div slot="content">
        <el-tree
          style="font-size: 14px"
          ref="parentTree"
          :data="parentMenuList"
          node-key="id"
          :props="defaultProps"
          empty-text="暂无数据"
          :show-checkbox="false"
          default-expand-all
          :highlight-current="true"
          :expand-on-click-node="false"
          @node-click="handleNodeClick">
            <div class="customer-tree-node" slot-scope="{ node, data }">
              <!-- 长度为0说明没有下级 -->
              <span v-if="data.children.length == 0">
                <i class="el-icon-document" style="color: #8c8c8c; font-size: 18px"/>
              </span>
              <span v-else @click.stop="changeIcon(data)">
                <svg-icon v-if="data.open" icon-class="add-s"/>
                <svg-icon v-else icon-class="sub-s"/>
              </span>
              <span style="margin-left: 3px">{{ node.label }}</span>
            </div>
        </el-tree>
      </div>
    </system-dialog>

3.3 封装图标选择器

utils/icons.js

export const elementIcons = ["platform-eleme", "eleme", "delete-solid", "delete",
"s-tools", "setting", "user-solid", "user", "phone", "phone-outline", "more",
"more-outline", "star-on", "star-off", "s-goods", "goods", "warning", "warningoutline", "question", "info", "remove", "circle-plus", "success", "error", "zoomin", "zoom-out", "remove-outline", "circle-plus-outline", "circle-check", "circleclose", "s-help", "help", "minus", "plus", "check", "close", "picture", "pictureoutline", "picture-outline-round", "upload", "upload2", "download", "camerasolid", "camera", "video-camera-solid", "video-camera", "message-solid", "bell",
"s-cooperation", "s-order", "s-platform", "s-fold", "s-unfold", "s-operation", "spromotion", "s-home", "s-release", "s-ticket", "s-management", "s-open", "s-shop",
"s-marketing", "s-flag", "s-comment", "s-finance", "s-claim", "s-custom", "sopportunity", "s-data", "s-check", "s-grid", "menu", "share", "d-caret", "caretleft", "caret-right", "caret-bottom", "caret-top", "bottom-left", "bottom-right",
"back", "right", "bottom", "top", "top-left", "top-right", "arrow-left", "arrowright", "arrow-down", "arrow-up", "d-arrow-left", "d-arrow-right", "video-pause",
"video-play", "refresh", "refresh-right", "refresh-left", "finished", "sort",
"sort-up", "sort-down", "rank", "loading", "view", "c-scale-to-original", "date",
"edit", "edit-outline", "folder", "folder-opened", "folder-add", "folder-remove",
"folder-delete", "folder-checked", "tickets", "document-remove", "documentdelete", "document-copy", "document-checked", "document", "document-add",
"printer", "paperclip", "takeaway-box", "search", "monitor", "attract", "mobile",
"scissors", "umbrella", "headset", "brush", "mouse", "coordinate", "magic-stick",
"reading", "data-line", "data-board", "pie-chart", "data-analysis", "collectiontag", "film", "suitcase", "suitcase-1", "receiving", "collection", "files",
"notebook-1", "notebook-2", "toilet-paper", "office-building", "school", "tablelamp", "house", "no-smoking", "smoking", "shopping-cart-full", "shopping-cart-1",
"shopping-cart-2", "shopping-bag-1", "shopping-bag-2", "sold-out", "sell",
"present", "box", "bank-card", "money", "coin", "wallet", "discount", "price-tag",
"news", "guide", "male", "female", "thumb", "cpu", "link", "connection", "open",
"turn-off", "set-up", "chat-round", "chat-line-round", "chat-square", "chat-dotround", "chat-dot-square", "chat-line-square", "message", "postcard", "position",
"turn-off-microphone", "microphone", "close-notification", "bangzhu", "time",
"odometer", "crop", "aim", "switch-button", "full-screen", "copy-document", "mic",
"stopwatch", "medal-1", "medal", "trophy", "trophy-1", "first-aid-kit",
"discover", "place", "location", "location-outline", "location-information", "addlocation", "delete-location", "map-location", "alarm-clock", "timer", "watch-1",
"watch", "lock", "unlock", "key", "service", "mobile-phone", "bicycle", "truck",
"ship", "basketball", "football", "soccer", "baseball", "wind-power", "lightrain", "lightning", "heavy-rain", "sunrise", "sunrise-1", "sunset", "sunny",
"cloudy", "partly-cloudy", "cloudy-and-sunny", "moon", "moon-night", "dish",
"dish-1", "food", "chicken", "fork-spoon", "knife-fork", "burger", "tableware",
"sugar", "dessert", "ice-cream", "hot-water", "water-cup", "coffee-cup", "colddrink", "goblet", "goblet-full", "goblet-square", "goblet-square-full",
"refrigerator", "grape", "watermelon", "cherry", "apple", "pear", "orange",
"coffee", "ice-tea", "ice-drink", "milk-tea", "potato-strips", "lollipop", "icecream-square", "ice-cream-round"].map(s => "el-icon-" + s);
    /**
    * 切换图标
    * @param data
    */
    changeIcon(data) {
      data.ope = !data.open
      this.$refs.parentTree.store.nodesMap[data.id].expanded = !data.open
    },
    //给icon绑定的点击事件
    setIcon(icon) {
      this.menu.icon = icon;
    },

 

<template>
  <div class="chooseIcons">
    <el-popover placement="bottom" width="450" trigger="click">
      <span slot="reference" style="display: inline-block;width: 200px;height: 33px;line-height: 33px;">
        <i :class="userChooseIcon"></i>
        {{ userChooseIcon }}
      </span>
      <div class="iconList">
        <i v-for="item in iconList" :key="item" :class="item" @click="setIcon(item)" style="font-size: 20px"></i>
      </div>
    </el-popover>
  </div>
</template>
<script>
//导入自定义的icon图标库
import { elementIcons } from "@/utils/icons";

export default {
  name: 'MyIcon',
  data(){
    return{
      userChooseIcon:"",//用户选中的图标
      iconList:[],//图标列表
    }
  },
  created() {
    //获取图标列表
    this.getIconList();
  },
  methods:{
    /**
    * 获取图标列表
    */
    getIconList(){
      this.iconList = elementIcons;
    },
    //给icon绑定的点击事件
    setIcon(icon) {
      //将i的样式设为选中的样式el-icon-xxx
      this.userChooseIcon = icon;
      //将选中的图标传递给父组件
      this.$emit("selecticon",icon)
    },
  }
}
</script>

<style scoped lang="scss">
.iconList {
  width: 400px;
  height: 300px;
  overflow-y: scroll;
  overflow-x: hidden;
  display: flex;
  justify-content: space-around;
  flex-wrap: wrap;
  i {
    display: inline-block;
    width: 60px;
    height: 45px;
    color: #000000;
    font-size: 20px;
    border: 1px solid #e6e6e6;
    border-radius: 4px;
    cursor: pointer;
    text-align: center;
    line-height: 45px;
    margin: 5px;
    &:hover {
      color: orange;
      border-color:orange;
    }
  }
}

.chooseIcons{
  width: 175px;
  background-color: #FFFFFF;
  background-image: none;
  border-radius: 4px;
  border: 1px solid #DCDFE6;
  box-sizing: border-box;
  color: #606266;
  display: inline-block;
  font-size: inherit;
  height: 33px;
  line-height: 25px;
  outline: none;
  padding: 0 15px;
  transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
}
</style>

3.4 新增、编辑和删除权限菜单

<template>
  <el-main>
    <!-- 新增按钮 -->
    <el-button type="success" size="small" @click="openAddWindow()" icon="el-icon-plus">新增</el-button>

    <!-- 数据表格 -->
    <el-table style="margin-top: 10px" :height="tableHeight" :data="menuList" row-key="id"
      default-expand-all :tree-props="{ children: 'children' }" :border="true" stripe>
      <el-table-column prop="label" label="菜单名称"></el-table-column>
      <el-table-column prop="type" label="菜单类型" align="center">
        <template slot-scope="scope">
          <el-tag v-if="scope.row.type == '0'" size="normal">目录</el-tag>
          <el-tag type="success" v-else-if="scope.row.type == '1'" size="normal">菜单</el-tag>
          <el-tag type="warning" v-else-if="scope.row.type == '2'" size="normal">按钮</el-tag>
        </template>
      </el-table-column>
      <el-table-column prop="icon" label="图标" align="center">
        <template slot-scope="scope">
          <i :class="scope.row.icon" v-if="scope.row.icon.includes('el-icon')" ></i>
          <svg-icon v-else :icon-class="scope.row.icon"></svg-icon>
        </template>
      </el-table-column>
      <el-table-column prop="name" label="路由名称"></el-table-column>
      <el-table-column prop="path" label="路由地址"></el-table-column>
      <el-table-column prop="url" label="组件路径"></el-table-column>
      <el-table-column label="操作" align="center">
        <template slot-scope="scope">
          <el-button type="primary" icon="el-icon-edit" size="small" @click="editMenu(scope.row)">编辑</el-button>
          <el-button type="danger" size="small" icon="el-icon-delete" @click="deleteMenu(scope.row)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
    <!-- 新增或编辑弹框 -->
    <system-dialog
    :title="menuDialog.title"
    :width="menuDialog.width"
    :height="menuDialog.height"
    :visible="menuDialog.visible"
    @onClose="onClose"
    @onConfirm="onConfirm">
      <div slot="content">
        <el-form :model="menu" ref="menuForm" :rules="rules" label-width="80px" :inline="true" size="small">
          <el-col :span="24">
            <el-form-item label="菜单类型" prop="type">
              <el-radio-group v-model="menu.type">
                <el-radio :label="0">目录</el-radio>
                <el-radio :label="1">菜单</el-radio>
                <el-radio :label="2">按钮</el-radio>
              </el-radio-group>
            </el-form-item>
          </el-col>
          <el-form-item label="所属菜单" size="small" prop="parentName">
            <el-input v-model="menu.parentName" :readonly="true" @click.native="selectParentMenu()" />
          </el-form-item>
          <el-form-item label="菜单名称" size="small" prop="label">
            <el-input v-model="menu.label"></el-input>
          </el-form-item>
          <el-form-item label="路由名称" size="small" prop="name" v-if="menu.type == 1">
            <el-input v-model="menu.name"></el-input>
          </el-form-item>
          <el-form-item label="路由地址" size="small" prop="path" v-if="menu.type != 2">
            <el-input v-model="menu.path"></el-input>
          </el-form-item>
          <el-form-item label="组件路径" size="small" prop="url" v-if="menu.type == 1">
            <el-input v-model="menu.url"></el-input>
          </el-form-item>
          <el-form-item label="权限字段" size="small" prop="code">
            <el-input v-model="menu.code"></el-input>
          </el-form-item>
          <el-form-item label="菜单图标" size="small" >
            <my-icon @selecticon="setIcon" ref="child"></my-icon>
          </el-form-item>
          <el-form-item label="菜单序号" size="small">
            <el-input v-model="menu.orderNum"></el-input>
          </el-form-item>
        </el-form>
      </div>
    </system-dialog>

    <!-- 选择所属菜单弹框 -->
    <system-dialog
    :title="parentDialog.title"
    :width="parentDialog.width"
    :height="parentDialog.height"
    :visible="parentDialog.visible"
    @onClose="onParentClose"
    @onConfirm="onParentConfirm">
      <div slot="content">
        <el-tree
          style="font-size: 14px"
          ref="parentTree"
          :data="parentMenuList"
          node-key="id"
          :props="defaultProps"
          empty-text="暂无数据"
          :show-checkbox="false"
          default-expand-all
          :highlight-current="true"
          :expand-on-click-node="false"
          @node-click="handleNodeClick">
            <div class="customer-tree-node" slot-scope="{ node, data }">
              <!-- 长度为0说明没有下级 -->
              <span v-if="data.children.length == 0">
                <i class="el-icon-document" style="color: #8c8c8c; font-size: 18px"/>
              </span>
              <span v-else @click.stop="changeIcon(data)">
                <svg-icon v-if="data.open" icon-class="add-s"/>
                <svg-icon v-else icon-class="sub-s"/>
              </span>
              <span style="margin-left: 3px">{{ node.label }}</span>
            </div>
        </el-tree>
      </div>
    </system-dialog>
  </el-main>
</template>

<script>
// 导入menu脚本文件
import menuApi from '@/api/menu'
//导入自定义图标组件
import MyIcon from '@/components/system/MyIcon.vue'
// 导入对话框组件
import SystemDialog from '@/components/system/SystemDialog.vue'

export default {
  name: 'menuList',
  //注册组件
  components:{ SystemDialog, MyIcon },
  data() {
    return {
      //新增或编辑弹框属性
      menuDialog: {
        title: "",
        width: 630,
        height: 270,
        visible: false,
      },
      //菜单属性
      menu: {
        id: "",
        type: "",
        parentId: "",
        parentName: "",
        label: "",
        icon: "",
        name: "",
        path: "",
        url: "",
        code: "",
        orderNum: "",
      },
      rules: {
        type: [{ required: true, trigger: "change", message: "请选择菜单类型" }],
        parentName: [{ required: true, trigger: "change", message: "请选择所属菜单"}],
        label: [{ required: true, trigger: "blur", message: "请输入菜单名称" }],
        name: [{ required: true, trigger: "blur", message: "请输入路由名称" }],
        path: [{ required: true, trigger: "blur", message: "请输入路由路径" }],
        url: [{ required: true, trigger: "blur", message: "请输入组件路径" }],
        code: [{ required: true, trigger: "blur", message: "请输入权限字段" }],
      },
      parentDialog: {
        title: '选择所属菜单',
        width: 280,
        height: 450,
        visible: false
      },
      //树属性定义
      defaultProps: {
        children: 'children',
        label: 'label'
      },
      menuList: [],       //菜单列表
      tableHeight: 0,     //表格高度
      parentMenuList: [], //所属菜单列表
    }
  },
  mounted() {
    this.$nextTick(() => {
      this.tableHeight = window.innerHeight - 180
    })
  },
  //初始化时调用
  created(){
    //调用查询菜单列表的方法
    this.search()
  },
  mounted() {
    this.$nextTick(() => {
      this.tableHeight = window.innerHeight - 180
    })
  },
  methods:{
    /**
    * 选择所属菜单
    */
    async selectParentMenu() {
      //显示窗口
      this.parentDialog.visible = true;
      //发送查询请求
      let res = await menuApi.getParentMenuList();
      //判断是否成功
      if (res.success) {
        //赋值
        this.parentMenuList = res.data;
      }
    },
    /**
    * 查询菜单列表
    */
    async search() {
      //发送查询请求
      let res = await menuApi.getMenuList();
      //判断是否成功
      if(res.success){
        //赋值
        this.menuList = res.data;
      }
    },
    /**
    * 编辑菜单
    * @param row
    */
    editMenu(row){
      //把当前要编辑的数据复制到数据域,给表单回显
      this.$objCopy(row, this.menu);
      //设置弹框属性
      this.menuDialog.title = "编辑菜单";
      this.menuDialog.visible = true;
      this.$nextTick(() => {
        this.$refs["child"].userChooseIcon = row.icon;//菜单图标回显
      })
    },
    /**
    * 删除菜单
    * @param row
    */
    async deleteMenu(row){
      //判断是否存在子菜单
      let result = await menuApi.checkPermission({ id: row.id });
      //判断是否可以删除
      if (!result.success) {
        //提示不能删除
        this.$message.warning(result.message);
      } else {
        //确认是否删除
        let confirm =await this.$myconfirm("确定要删除该数据吗?");
        if (confirm) {
          //发送删除请求
          let res = await menuApi.deleteById({ id: row.id });
          //判断是否成功
          if (res.success) {
            //成功提示
            this.$message.success(res.message);
            //刷新
            this.search();
          } else {
            //失败提示
            this.$message.error(res.message);
          }
        }
      }
    },
    /**
     * 打开新增窗口
     */
    openAddWindow(){
      this.$resetForm("menuForm", this.menu) //清空表单数据
      this.menuDialog.title = "新增菜单"      //设置窗口标题
      this.menuDialog.visible = true         //显示窗口
      this.$nextTick(() => {
        this.$refs['child'].userChooseIcon = '' // 清空菜单图标 
      })
    },
    /**
    * 选择所属菜单取消事件
    */
    onParentClose() {
      this.parentDialog.visible = false //关闭窗口
    },
    /**
    * 选择所属菜单确认事件
    */
    onParentConfirm() {
      this.parentDialog.visible = false //关闭窗口
    },
    /**
    * 添加和修改菜单窗口关闭事件
    */
    onClose() {
      this.menuDialog.visible = false; //关闭窗口
    },
    /**
    * 添加和修改菜单窗口确认事件
    */
    onConfirm() {
      //表单验证
      this.$refs.menuForm.validate(async (valid) => {
        if (valid) {
          let res = null;
          //判断菜单ID是否为空
          if (this.menu.id === "") {
            //发送添加请求
            res = await menuApi.addMenu(this.menu);
          } else {
            //发送修改请求
            res = await menuApi.updateMenu(this.menu);
          }
          //判断是否成功
          if (res.success) {
            this.$message.success(res.message);
            //刷新
            //this.search();
            window.location.reload();
            //关闭窗口
            this.menuDialog.visible = false;
          } else {
            this.$message.error(res.message);
          }
          this.menuDialog.visible = false; //关闭窗口
        }
      });
    },
    /**
    * 所属菜单节点点击事件
    */
    handleNodeClick(data) {
      //所属父级菜单ID
      this.menu.parentId = data.id;
      //所属父级菜单名称
      this.menu.parentName = data.label;
    },
    /**
    * 切换图标
    * @param data
    */
    changeIcon(data) {
      data.open = !data.open
      this.$refs.parentTree.store.nodesMap[data.id].expanded = !data.open
    },
    //给icon绑定的点击事件
    setIcon(icon) {
      this.menu.icon = icon;
    },
  }
}
</script>
<style lang="scss" scoped>
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/269860.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用 FFmpeg 清除文件夹下所有 .mp4 文件声音

运行以下命令来清除声音&#xff1a; ffmpeg -i input.mp4 -c copy -an output.mp4这个命令会将 “input.mp4” 替换为你要处理的 .mp4 文件名。它会生成一个新的文件名为 “output.mp4” 的文件&#xff0c;该文件是没有声音的副本。 如果你想要直接替换原始文件&#xff0c;…

【shell脚本实战学习笔记】#1

shell脚本实战学习笔记#1 脚本编写场景需求&#xff1a; 编写一个比较数据大小的shell脚本&#xff0c;要求判断用户只能输入两位数字&#xff0c;不能是字符或其他特殊字符&#xff1b;并且在shell脚本中需要用到函数来控制执行顺序。 知识点&#xff1a;shell函数&#xff…

数字乡村智慧农业云平台建设方案:PPT全文30页,附下载

关键词&#xff1a;数字乡村解决方那&#xff0c;智慧农业解决方案&#xff0c;智慧农业建设&#xff0c;数字乡村平台&#xff0c;智慧农业大数据平台&#xff0c;智慧农业项目建设规划 一、对“互联网农业”的理解 “互联网农业”是指将互联网技术与农业生产、加工、销售等…

Elasticsearch可视化平台Kibana [ES系列] - 第498篇

历史文章&#xff08;文章累计490&#xff09; 《国内最全的Spring Boot系列之一》 《国内最全的Spring Boot系列之二》 《国内最全的Spring Boot系列之三》 《国内最全的Spring Boot系列之四》 《国内最全的Spring Boot系列之五》 《国内最全的Spring Boot系列之六》 全…

串口服务器助力环境监测系统高效管理、远程监控

物联网的发展使得环境监测系统具备了更强大的数据采集和分析能力。传统的环境监测系统通常需要人工到现场采集数据&#xff0c;费时费力且容易受到外界干扰。而通过使用串口服务器&#xff0c;可以实现环境监测系统的远程数据采集和监控&#xff0c;从而提高监测效率和数据准确…

雷盛红酒分享葡萄酒在冬天如何运输和保存

这两天&#xff0c;强大的寒潮从西向东横扫我国大部地区。我国北方地区未来几天气温持续偏低&#xff0c;冷上加冷&#xff1b;南方的降温大幕今天也将开启&#xff0c;江南、华南部分地区将出现6~12℃的降温。如此寒冷的冬天&#xff0c;对葡萄酒来说&#xff0c;并不是什么好…

Vue实现响应式布局

前提准备&#xff1a;响应式布局有两种方法&#xff0c;看自己想要哪种。 方法一&#xff1a;百分比 用百分比去写元素的宽度&#xff0c;然后让子元素撑起父元素的高度 .parent {width: 50%; }.child {width:100%;height:100px; } 方法二&#xff1a;vh、vw vw、vh是基于视…

【excel密码】Excel工作表不能复制或移动

为什么excel文件打开之后&#xff0c;工作表里是可以编辑的&#xff0c;但是想要移动工作表或者复制、重命名等操作&#xff0c;这是什么原因&#xff1f;其实这是因为设置了工作簿保护&#xff0c;设置了保护的工作簿无法对整张工作表进行操作。 想要取消这种保护&#xff0c;…

MongoDB查询文档

3.5 MongoDB 查询文档 MongoDB 查询文档使用 find() 方法。 find() 方法以非结构化的方式来显示所有文档。find()查询数据的语法格式如下&#xff1a; db.collection.find(query, projection)[.pretty()] query &#xff1a;可选&#xff0c;使用查询操作符指定查询条件 pr…

AI Earth平台简介

AI Earth地球科学云平台由达摩院-视觉技术实验室打造&#xff0c;基于地球科学智能计算分析方面的创新研究&#xff0c;致力于解决地球科学领域基础性、前沿性、业务性问题&#xff0c;目标成为国内一流的地球科学云计算平台。&#xff08;摘自官网&#xff09; 下面&#xff…

C++ 比 C语言的新增的特性 1

1. C是C的增强 1.1 C是静态类型的语言&#xff0c;具有严格的数据类型检查 1.1.1 c 因为const修饰的变量不允许修改&#xff0c;但是只给了警告&#xff0c;不严谨 const int a10;a20; //报错int *p&a;*p20;//a的值&#xff1f; test1.c:6:9: warning: initialization dis…

教你如何开发并运营小程序商城或APP商城!

随着线下租金、仓储等成本的攀升&#xff0c;商家们面临着越来越大的压力。为了降低成本、提高效率&#xff0c;越来越多的商家开始转型做电商&#xff0c;甚至直接开发自己的电商商城小程序或APP。那么&#xff0c;商城小程序或APP该如何开发呢&#xff1f;又该如何运营呢&…

别再错过,100张BI报表,动动手指就能用

别再错过了&#xff01;覆盖财务、销售、库存、采购、应收、生产六大主题&#xff0c;百张BI报表&#xff0c;不需要开发&#xff0c;下了就能用。 简单注册、下载方案并执行&#xff0c;即可完成六大主题数据分析&#xff01; 在以前&#xff0c;即使有BI方案&#xff0c;要…

【MYSQL】MYSQL 的学习教程(八)之 12 种慢 SQL 查询原因

日常开发中&#xff0c;我们经常会遇到数据库慢查询。那么导致数据慢查询都有哪些常见的原因呢&#xff1f;今天就跟大家聊聊导致 MySQL 慢查询的 12 个常见原因&#xff0c;以及对应的解决方法&#xff1a; SQL 没加索引SQL 索引失效limit 深分页问题单表数据量太大join 或者…

《网络是怎样连接的》1.2、1.3节图表(自用)

图2.1&#xff1a;浏览器调用socket库中的解析器&#xff0c;向DNS服务器询问域名的ip地址 &#xff08;图中的gethostbyname是解析器的名称&#xff1b;协议栈是操作系统的网络控制软件&#xff0c;也称协议驱动、TCP/IP驱动&#xff09; 图2.2 DNS服务器根据客户端查询信息查…

云渲染ai加速怎么加速?云渲染ai加速平台推荐

在当下的视觉设计行业中&#xff0c;渲染速度成为设计师们亟需解决的难题。以往&#xff0c;高质量的效果图渲染需要耗费大量时间&#xff0c;特别是在处理复杂的视觉效果时&#xff0c;传统的渲染流程可能需要长时间等待&#xff0c;随着人工智能技术的快速进步&#xff0c;利…

基于AT89C51单片机的8位密码锁仿真与实物制作

点击链接获取Keil源码与Project Backups仿真图&#xff1a; https://download.csdn.net/download/qq_64505944/88657969?spm1001.2014.3001.5503 源码获取 C 源码仿真图毕业设计实物制作步骤01 摘要 在日常的生活和工作中, 住宅与部门的安全防范、单位的文件档案、财务报表…

阶段七-GitEE

Git&#xff1a;版本控制软件 Git的优点 1.1 协同修改 多人并行不悖的修改服务器端的同一个文件。 1.2 数据备份 不仅保存目录和文件的当前状态&#xff0c;还能够保存每一个提交过的历史状态。 1.3 版本管理 在保存每一个版本的文件信息的时候要做到不保存重复数据&…

搭建本地的pip镜像源

1. 创建文件夹&#xff1a;./pypi_mirror_test 2. 创建并进入conda虚拟环境&#xff0c;安装pip2pi包 pip install pip2pi 3. 下载pypi的packages 可以参考其他博客&#xff0c;或者我之前的博客. 偷懒&#xff0c;仅仅测试用的话&#xff1a; 1&#xff09;在文件夹下创…

Django之按钮(actions)

开篇就是道歉&#xff0c;哈哈哈哈&#xff0c;托更了好久好久&#xff0c;最近太忙了没啥时间更新&#xff0c;各位看官有催更的阔以给我私信哇&#xff0c;希望各位看官给个三连&#xff01;&#xff01;&#xff01;&#x1f60d;&#x1f60d;&#x1f60d;&#x1f60d; …