保存更改
当前位置:   article > 正文

element ul+VUE+Spring boot + MybatisPlus实现三级菜单分类_vue3 el-tree drag 三级分类

vue3 el-tree drag 三级分类

效果图:

 

前端代码:

<template>
   <div>
     <el-switch
       v-model="value1"
       active-text="开启拖拽"
       inactive-text="关闭拖拽">
     </el-switch>

     <el-button type="primary" @click="SaveChanges">保存更改</el-button>
     <el-button type="danger" @click="batchDelete">批量删除</el-button>

    <el-tree ref="menuTree"   @node-drop="handleDrop"  :allow-drop="allowDrop" :draggable="value1" :default-expanded-keys="expandedKey" :data="data" :props="defaultProps"  :expand-on-click-node="false" :show-checkbox ="true" node-key="catId">
        <span class="custom-tree-node" slot-scope="{ node, data }">
            <span>{{ node.label }}</span>
            <span>

                <el-button  v-if="data.catLevel == 1 || data.catLevel == 2" type="text" size="mini" @click="() => append(data)">
                    添加
                </el-button>

                <el-button  v-if="node.childNodes.length == 0" type="text" size="mini" @click="() => remove(node, data)">
                    删除
                </el-button>

                <el-button  type="text" size="mini" @click="() => exit(node, data)">
                    编辑
                </el-button>
            </span>
        </span>
    </el-tree>

    <el-dialog :title="title" :visible.sync="dialogFormVisible" :close-on-click-modal="false">
        <el-form :model="category">
          <el-form-item label="分类名称"  >
            <el-input v-model="category.name" autocomplete="off"></el-input>
          </el-form-item>

          <el-form-item label="图标"  >
            <el-input v-model="category.icon" autocomplete="off"></el-input>
          </el-form-item>

          <el-form-item label="排序"  >
            <el-input v-model="category.sort" autocomplete="off"></el-input>
          </el-form-item>

<!--          <el-upload-->
<!--          :label-width="formLabelWidth"-->
<!--          action=""-->
<!--          list-type="picture-card"-->
<!--          :on-preview="handlePictureCardPreview"-->
<!--          :on-remove="handleRemove">-->
<!--          <i class="el-icon-plus"></i>-->
<!--          </el-upload>-->
<!--          <el-dialog  :label-width="formLabelWidth" :visible.sync="dialogVisible">-->
<!--          <img width="100%" :src="dialogImageUrl" alt="">-->
<!--          </el-dialog>-->
        </el-form>
        <div slot="footer" class="dialog-footer">
          <el-button @click="dialogFormVisible = false">取 消</el-button>
          <el-button v-if="title =='添加菜单'" type="primary" @click="setMenu">确 定</el-button>
          <el-button v-if="title =='编辑菜单'" type="primary" @click="exitMenu">确 定</el-button>
        </div>
      </el-dialog>



    </div>

</template>
<script>
    export default {
        data() {
            return {
              catIds:[],
              ExpandID:[],
              value1: true,
              updateNodes: [],
              maxLevel:0,
              title:'',
              category:{
                name: '',
                parentCid: 0,
                catLevel: 0,
                sort: 0,
                icon: '',
                showStatus: 1
                },
               exitcategory:{
                 catId:'',
                 name:'',
                 icon:'',
                 sort:''
                },
                data: [],
                defaultProps: {
                    children: 'children',
                    label: 'name'
                },
                dialogFormVisible: false,
                form: {
                    name: '',
                    region: '',
                    date1: '',
                    date2: '',
                    delivery: false,
                    type: [],
                    resource: '',
                    desc: ''
                },
                formLabelWidth: '120px',
                dialogImageUrl: '',
                dialogVisible: false,
                expandedKey: []


            }
        },
        //监控data中的数据变化
        watch: {
          //监控开关按钮的状态变化 oldName之前的值  newName改变后的值
          value1(newName,oldName){
             if(newName == false){
                this.ExpandID=[];
                this.updateNodes=[];
                this.maxLevel=0;
                console.log("结果:==",this.ExpandID,this.updateNodes,this.maxLevel);
             }
          }

        },
        //方法集合
        methods: {
            //批量删除选中的菜单
            batchDelete(){
              console.log(this.$refs.menuTree.getCheckedNodes());
              let checkedNodes = this.$refs.menuTree.getCheckedNodes();
              console.log(checkedNodes);

              if(checkedNodes.length > 0){


              for(let i=0;i<checkedNodes.length;i++){
                this.catIds.push(checkedNodes[i].catId)
              }

              console.log("统计出来的id串:",this.catIds);

                this.$confirm('是否确定删除选中菜单?', '提示', {
                  confirmButtonText: '确定删除',
                  cancelButtonText: '取消',
                  type: 'warning'
                }).then(() => {

                  this.$http({
                    url: this.$http.adornUrl('/product/category/delete'),
                    method: 'post',
                    data: this.$http.adornData(this.catIds,false)
                  }).then(({data}) => {
                    console.log(data)
                    if (data && data.code === 0) {
                      this.$message({
                        message: '删除成功',
                        type: 'success',
                        duration: 1500,
                        onClose: () => {
                          this.getMenus()
                          this.expandedKey = [checkedNodes[0].parentCid]
                        }
                      })
                    } else {
                      this.$message.error(data.msg)
                    }
                  });

                }).catch(() => {
                  this.$message({
                    type: 'info',
                    message: '已取消删除'
                  });
                });



              }



            },
            //保存更改
            SaveChanges(){
              this.$http({
                url: this.$http.adornUrl(`/product/category/update/sort`),
                method: 'post',
                data: this.$http.adornData(this.updateNodes,false)
              }).then(({ data }) => {

                if (data && data.code === 0) {

                  this.$message({
                    message: '拖拽成功',
                    type: 'success',
                    onClose: () => {
                      this.getMenus()
                      this.expandedKey = this.ExpandID;
                    }
                  })

                } else {
                  this.$message.error(data.msg)
                }
                this.updateNodes = [];
                // this.ExpandID = [];


              }).catch(err=>{

                console.log(err)

              })

            },
            //拖拽完成时触发的事件
            handleDrop(draggingNode, dropNode, dropType, ev) {
              //清空
              // this.ExpandID = [];
              console.log('当前被拖动的节点:====',draggingNode,'tree drop: ===', dropNode, dropType);
              //展开id
              this.ExpandID.push(draggingNode.data.catId);
              // 1、 改变当前被拖动的父ID
              var updateCatId = 0;
              var childNodes = null;
              var level = 0;
              if(dropType == "inner"){

                updateCatId = dropNode.data.catId;
                childNodes = dropNode.childNodes;
                level = dropNode.level + 1;

              }else{

                updateCatId = dropNode.parent.data.catId == null?0:dropNode.parent.data.catId ;
                childNodes = dropNode.parent.childNodes;
                level = dropNode.level;

              }

              console.log(updateCatId)
              console.log("childNodes:=========",childNodes)
              // 2、 排序 当前拖拽节点的最新顺序
              for(let i = 0;i < childNodes.length;i++){

                if(childNodes[i].data.catId == draggingNode.data.catId){

                  //当前节点的默认层级
                  let catLevel = draggingNode.level;
                  if(childNodes[i].level != draggingNode.level){
                      //如果能够进来 就表示当前节点的层级发生了变化
                      catLevel = childNodes[i].level;
                      //修改子节点的层级
                      this.updateChildNodeLevel(childNodes[i]);



                  }
                  console.log("层级:",catLevel);
                  //如果遍历的是当前正在拖拽的节点 当前拖动的父ID和层级 也要修改
                  this.updateNodes.push({catId:childNodes[i].data.catId,sort: i,parentCid: updateCatId,level: level});

                }else{
                  this.updateNodes.push({catId:childNodes[i].data.catId,sort: i});

                }

              }

              // 3、 拖动完成



              console.log("updateNodes",this.updateNodes);

            },
            updateChildNodeLevel(node){
               if(node.childNodes.length >0){
                 for(let i=0;i<node.childNodes.length ;i++){
                   var cNode = node.childNodes[i].data;
                   this.updateNodes.push({catId:cNode.catId,catLevel:node.childNodes[i].level});
                   if(node.childNodes[i].childNodes.length > 0){
                     this.updateChildNodeLevel(node.childNodes[i]);
                   }

                 }
               }
            },
            //菜单拖拽
            allowDrop(draggingNode, dropNode, type) {
              //1、 被拖动的当前节点以及所在的父节点总层数不能大于3

              //获取最大子节点的深度
              this.maxLevel = 0;
              this.countNodeLevel(draggingNode.data);
              console.log("拖动===============================",draggingNode,dropNode,type)
              let deep = (this.maxLevel - draggingNode.data.catLevel)+1;
              console.log("深度:",deep)
              // this.maxLevel
              if(type == "inner"){

                return (deep+ dropNode.level) <=3

              }else{

                return (deep + dropNode.parent.level)<=3;

              }
              // 被拖动的当前节点总层数 draggingNode 当前拖动的菜单信息     dropNode 被拖动到的菜单信息

            },
            countNodeLevel(node){
              this.maxLevel = 0;
              //找到所有子节点,求出最大深度 maxLevel
              if(this.maxLevel < node.catLevel){
                    this.maxLevel = node.catLevel
              }
              if(node.children != null && node.children.length >0){

                    for(let i=0;i<node.children.length;i++){
                           if(node.children[i].catLevel > this.maxLevel){
                             this.maxLevel = node.children[i].catLevel;
                           }
                      this.countNodeLevel(node.children[i]);
                    }

              }

            },
            //编辑菜单方法:↓
            exitMenu(){
              this.exitcategory.icon = this.category.icon
              this.exitcategory.name = this.category.name
              this.exitcategory.sort = this.category.sort

              this.$http({
                url: this.$http.adornUrl(`/product/category/update`),
                method: 'post',
                data: this.$http.adornData(this.exitcategory,false)
              }).then(({ data }) => {

                if (data && data.code === 0) {

                  this.$message({
                    message: '编辑成功',
                    type: 'success',
                    onClose: () => {
                      this.getMenus()
                      this.expandedKey = [ this.exitcategory.catId]
                    }
                  })
                  this.dialogFormVisible = false
                } else {
                  this.$message.error(data.msg)
                }


              }).catch(err=>{

                console.log(err)

              })

              console.log("这是编辑")


            },
            //编辑菜单:↓↓↓
            exit(node,data){

              this.exitcategory.catId = data.catId

              this.$http({
                url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
                method: 'post'
              }).then(({ data }) => {

                console.log("成功获取到菜单数据....", data.data)
                this.category.name = data.data.name
                this.category.icon = data.data.icon
                this.category.sort = data.data.sort
                this.category.parentCid = data.data.parentCid
              }).catch(err=>{

                console.log(err)

              })

              this.title = "编辑菜单"
              this.dialogFormVisible = true
              console.log("data=======",data,"node",node)




            },
            //添加菜单:↓↓↓
            setMenu(){

              this.$http({
                url: this.$http.adornUrl('/product/category/save'),
                method: 'post',
                data: this.$http.adornData(this.category,false)
              }).then(({data}) => {
                // console.log(data)
                // console.log("添加成功!")

                if (data && data.code === 0) {
                  this.dialogFormVisible = false
                  this.$message({
                    message: '添加成功',
                    type: 'success',
                    onClose: () => {
                      this.getMenus()
                      this.expandedKey = [this.category.parentCid]
                    }
                  })
                } else {
                  this.$message.error(data.msg)
                }
              }).catch(err => {
                console.log(err)
              })
            },
            handleRemove(file, fileList) {
            console.log(file, fileList);
              },
            handlePictureCardPreview(file) {
            this.dialogImageUrl = file.url;
            console.log(this.dialogImageUrl)
            this.dialogVisible = true;

            },
            getMenus() {
                this.$http({
                    url: this.$http.adornUrl('/product/category/list/tree'),
                    method: 'get'
                }).then(({ data }) => {
                    console.log("成功获取到菜单数据....", data.data)
                    this.data = data.data
                })
            },
            remove(node, data) {

                this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
                    confirmButtonText: '确定',
                    cancelButtonText: '取消',
                    type: 'warning'
                    }).then(() => {
                        var ids = [data.catId]
                        console.log("remove=======data:",data,"node:",node)
                        this.$http({
                            url: this.$http.adornUrl('/product/category/delete'),
                            method: 'post',
                            data: this.$http.adornData(ids,false)
                        }).then(({data}) => {
                            console.log(data)
                            if (data && data.code === 0) {
                            this.$message({
                                message: '删除成功',
                                type: 'success',
                                duration: 1500,
                                onClose: () => {
                                this.getMenus()
                                this.expandedKey = [node.parent.data.catId]
                                }
                            })
                            } else {
                            this.$message.error(data.msg)
                            }
                        })
                    }).catch(() => {
                    this.$message({
                        type: 'info',
                        message: '已取消删除'
                    });
                    });



            },
            append(data) {
                this.category.name = ""
                this.category.sort = 0
                this.category.icon = ""
                this.title = "添加菜单"
                //字符串*1 可以将字符串转换成数值
                this.category.catLevel = (data.catLevel * 1) + 1
                this.category.parentCid = data.catId
                this.dialogFormVisible = true
                console.log("append=======",data)
            }

        },
        //生命周期 创建完成 可以访问当前this实例
        created() {
            this.getMenus();
        }
    }
</script>

 

 

 

后端代码

 

控制层

package com.atguigu.gulimall.product.controller;

import com.atguigu.common.utils.R;
import com.atguigu.gulimall.product.entity.CategoryEntity;
import com.atguigu.gulimall.product.service.CategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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


/**
 * 商品三级分类
 *
 * @author leifengyang
 * @email leifengyang@gmail.com
 * @date 2019-10-01 22:50:32
 */
@RestController
@RequestMapping("product/category")
public class CategoryController {
    @Autowired
    private CategoryService categoryService;

    /**
     * 查出所有分类以及子分类,以树形结构组装起来
     */
    @RequestMapping("/list/tree")
    public R list(){

        List<CategoryEntity> entities = categoryService.listWithTree();


        return R.ok().put("data", entities);
    }


    /**
     * 信息
     */
    @RequestMapping("/info/{catId}")
    //@RequiresPermissions("product:category:info")
    public R info(@PathVariable("catId") Long catId){
      CategoryEntity category = categoryService.getById(catId);

        return R.ok().put("data", category);
    }

    /**
     * 保存
     */
    @RequestMapping("/save")
    //@RequiresPermissions("product:category:save")
    public R save(@RequestBody CategoryEntity category){
      categoryService.save(category);

        return R.ok();
    }

    @RequestMapping("/update/sort")
    //@RequiresPermissions("product:category:update")
    public R updateSort(@RequestBody CategoryEntity[] category){
        //maibeitisi-pulasi 的updateBatchById 就是批量修改
        categoryService.updateBatchById(Arrays.asList(category));
        return R.ok();
    }

    /**
     * 修改
     */
    @RequestMapping("/update")
    //@RequiresPermissions("product:category:update")
    public R update(@RequestBody CategoryEntity category){
      categoryService.updateCascade(category);
        return R.ok();
    }


    /**
     * 删除
     * @RequestBody:获取请求体,必须发送POST请求
     * SpringMVC自动将请求体的数据(json),转为对应的对象
     */
    @RequestMapping("/delete")
    //@RequiresPermissions("product:category:delete")
    public R delete(@RequestBody Long[] catIds){


      //categoryService.removeByIds(Arrays.asList(catIds));

        categoryService.removeMenuByIds(Arrays.asList(catIds));

        return R.ok();
    }

}

 

服务层

 

服务层接口:

package com.atguigu.gulimall.product.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.gulimall.product.entity.CategoryEntity;

import java.util.List;
import java.util.Map;

/**
* 商品三级分类
*
* @author leifengyang
* @email leifengyang@gmail.com
* @date 2019-10-01 21:08:48
*/
public interface CategoryService extends IService<CategoryEntity> {

    PageUtils queryPage(Map<String, Object> params);

    List<CategoryEntity> listWithTree();

    void removeMenuByIds(List<Long> asList);

    /**
     * 找到catelogId的完整路径;
     * [父/子/孙]
     * @param catelogId
     * @return
     */
    Long[] findCatelogPath(Long catelogId);

    void updateCascade(CategoryEntity category);

}

服务层接口实现类:

package com.atguigu.gulimall.product.service.impl;

import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;
import com.atguigu.gulimall.product.dao.CategoryDao;
import com.atguigu.gulimall.product.entity.CategoryEntity;
import com.atguigu.gulimall.product.service.CategoryBrandRelationService;
import com.atguigu.gulimall.product.service.CategoryService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

//    @Autowired
//    CategoryDao categoryDao;

    @Autowired
    CategoryBrandRelationService categoryBrandRelationService;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> listWithTree() {
        //1、查出所有分类
        List<CategoryEntity> entities = baseMapper.selectList(null);

        //2、组装成父子的树形结构

        //2.1)、找到所有的一级分类
        List<CategoryEntity> level1Menus = entities.stream().filter((categoryEntity) ->   //filter 过滤掉不满足条件的所有数据
                categoryEntity.getParentCid() == 0
        ).map((menu)->{  //map 对当前filter过滤后的每个元素进行处理
            menu.setChildren(getChildrens(menu,entities));
            return menu;
        }).sorted((menu1,menu2)->{ //进行排序
            //2、菜单的排序
            return (menu1.getSort()==null?0:menu1.getSort()) - (menu2.getSort()==null?0:menu2.getSort());
        }).collect(Collectors.toList()); // collect 集合 返回


        //sorted排序
        //map 影响
        //stream api       collect 收集

//        List<CategoryEntity> level1Menus = entities.stream().filter(categoryEntity ->
//             categoryEntity.getParentCid() == 0
//        ).map((menu)->{
//            menu.setChildren(getChildrens(menu,entities));
//            return menu;
//        }).sorted((menu1,menu2)->{
//            return (menu1.getSort()==null?0:menu1.getSort()) - (menu2.getSort()==null?0:menu2.getSort());
//        }).collect(Collectors.toList());

//        List<CategoryEntity>  resultList = bulidPermission(entities);

        return level1Menus;
    }

    //把返回所有菜单list集合进行封装的方法
    public static List<CategoryEntity> bulidPermission(List<CategoryEntity> permissionList) {

        //创建list集合,用于数据最终封装
        List<CategoryEntity> finalNode = new ArrayList<>();
        //把所有菜单list集合遍历,得到顶层菜单 pid=0菜单,设置level是1
        for(CategoryEntity permissionNode : permissionList) {
            //得到顶层菜单 pid=0菜单
            if(permissionNode.getCatLevel() == 1) {
                //设置顶层菜单的level是1
                permissionNode.setCatLevel(1);
                //根据顶层菜单,向里面进行查询子菜单,封装到finalNode里面
                finalNode.add(selectChildren(permissionNode,permissionList));
            }
        }
        return finalNode;
    }

    private static CategoryEntity selectChildren(CategoryEntity permissionNode, List<CategoryEntity> permissionList) {
        //1 因为向一层菜单里面放二层菜单,二层里面还要放三层,把对象初始化
        permissionNode.setChildren(new ArrayList<CategoryEntity>());

        //2 遍历所有菜单list集合,进行判断比较,比较id和pid值是否相同
        for(CategoryEntity it : permissionList) {
            //判断 id和pid值是否相同
            if(permissionNode.getCatId().equals(it.getParentCid())) {
                //把父菜单的level值+1
                int level = permissionNode.getCatLevel()+1;
                it.setCatLevel(level);
                //如果children为空,进行初始化操作
                if(permissionNode.getChildren() == null) {
                    permissionNode.setChildren(new ArrayList<CategoryEntity>());
                }
                //把查询出来的子菜单放到父菜单里面
                permissionNode.getChildren().add(selectChildren(it,permissionList));
            }
        }
        return permissionNode;
    }

    @Override
    public void removeMenuByIds(List<Long> asList) {
        //TODO  1、检查当前删除的菜单,是否被别的地方引用

        //逻辑删除
        baseMapper.deleteBatchIds(asList);
    }

    //[2,25,225]
    @Override
    public Long[] findCatelogPath(Long catelogId) {
        List<Long> paths = new ArrayList<>();
        List<Long> parentPath = findParentPath(catelogId, paths);

        Collections.reverse(parentPath);


        return parentPath.toArray(new Long[parentPath.size()]);
    }

    /**
     * 级联更新所有关联的数据
     * @param category
     */
    @Transactional
    @Override
    public void updateCascade(CategoryEntity category) {
        this.updateById(category);
        categoryBrandRelationService.updateCategory(category.getCatId(),category.getName());
    }

    //225,25,2
    private List<Long> findParentPath(Long catelogId,List<Long> paths){
        //1、收集当前节点id
        paths.add(catelogId);
        CategoryEntity byId = this.getById(catelogId);
        if(byId.getParentCid()!=0){
            findParentPath(byId.getParentCid(),paths);
        }
        return paths;

    }


    //递归查找所有菜单的子菜单
    private List<CategoryEntity> getChildrens(CategoryEntity root,List<CategoryEntity> all){

        List<CategoryEntity> children = all.stream().filter(categoryEntity -> {
            return categoryEntity.getParentCid() == root.getCatId(); //如果不满足这个条件就过滤掉
        }).map(categoryEntity -> {  //对没有过滤掉的每个元素进行处理
            //1、找到子菜单
            categoryEntity.setChildren(getChildrens(categoryEntity,all));
            return categoryEntity;
        }).sorted((menu1,menu2)->{
            //2、菜单的排序
            return (menu1.getSort()==null?0:menu1.getSort()) - (menu2.getSort()==null?0:menu2.getSort());
        }).collect(Collectors.toList());

        return children;
    }



}




数据访问层

均使用MybatisPlus自带的baseMapper里面的功能来实现

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/579923
推荐阅读
相关标签