谷粒商城-基础篇-Day02-实现三级分类

实现三级分类

后端实现:

在controller中

    @RequestMapping("/list/tree")
    public R list(){

List<CategoryEntity> entities=categoryService.listWithTree();
        return R.ok().put("data",entities);
    }

创建一个istWithTree()方法

查出所有的父子关系

 //查出所有分类,组装成父子的树形结构
    @Override
    public List<CategoryEntity> listWithTree() {

        //查询所有分类
        List<CategoryEntity> entities = categoryDao.selectList(null);

        //分类
          //查询所有一级分类
        List<CategoryEntity> level1Menus = entities.stream().filter((categoryEntity -> {
            return 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());

        return level1Menus;
    }


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

        List<CategoryEntity> collect = all.stream().filter((categoryEntity -> {
            return categoryEntity.getParentCid() == root.getCatId();

        })).map((childrenEntity) -> {
            childrenEntity.setChildren(getChildrens(childrenEntity, all));
            return childrenEntity;
        }).sorted((child1, child2) -> {
            return (child1.getSort()==null?0:child1.getSort())-(child2.getSort()==null?0:child2.getSort());//通过表中的排序字段进行排序
        }).collect(Collectors.toList());

        return collect;
    }

前端实现:

1、使用人人开发平台创建出来一个目录(商品系统),然后在商品系统的目录下创建一个分类维护(路径为propduct/catory)

2、在renren-fast-vue中的models中创建product文件夹,在该文件夹下创建category.vue

<template>
<div><el-tree :data="data" :props="defaultProps" @node-click="handleNodeClick"></el-tree></div>
</template>

<script>

export default {
//import引入的组件需要注入到对象中才能使用
components: {},
props: {},
    data() {
      return {
        data:[

        ],
        defaultProps: {
          children: 'children',
          label: 'label'
        }
      };
    },
    methods: {
      handleNodeClick(data) {
        console.log(data);
      },
      getMenus(){
            this.dataListLoading = true
        this.$http({
          url: this.$http.adornUrl('/product/category/list/tree'),
          method: 'get'
        }).then(data=>{
            console.log("成功获取到数据",data)
        })
      }
    },
//计算属性 类似于data概念
computed: {},
//监控data中的数据变化
watch: {},
//方法集合

//声明周期 - 创建完成(可以访问当前this实例)
created() {
this.getMenus();
},
//声明周期 - 挂载完成(可以访问DOM元素)
mounted() {},
beforeCreate() {}, //生命周期 - 创建之前
beforeMount() {}, //生命周期 - 挂载之前
beforeUpdate() {}, //生命周期 - 更新之前
updated() {}, //生命周期 - 更新之后
beforeDestroy() {}, //生命周期 - 销毁之前
destroyed() {}, //生命周期 - 销毁完成
activated() {} //如果页面有keep-alive缓存功能,这个函数会触发
};
</script>
<style scoped>
</style>

此时打开分类维护的目录,页面会发送请求,但是请求的地址是http://localhost:8080/product/category/list/tree

因为在static下的config中的index.js中指定了请求地址是8080端口

所以我们将前端的请求转发给gateway网关,由网关做转发

修改前端中的 window.SITE_CONFIG[‘baseUrl’] = ‘http://localhost:88’

测试:发现我们的验证码没了,这是由于验证码是来源于renren-fast这个项目中的,所以我们要将renren-fast也注册到nacos中

直接导入gulimall-common的依赖

		<dependency>
			<groupId>com.atguigu.gulimall</groupId>
			<artifactId>gulimall-common</artifactId>
			<version>0.0.1-SNAPSHOT</version>
		</dependency>

配置nacos的地址

在启动类上使用注解开启服务

为了使前端发送的请求都交给renren-fast去处理

给前端的请求路径上加一个api

 window.SITE_CONFIG['baseUrl'] = 'http://localhost:88/api';//修改为给网关发请求

在gateway中

当遇到由api的路径时交给renren-fast去处理

修改配置文件

- id: admin_root
  uri: lb://renren-fast
  predicates:
    - Path=/api/**

但是这时候发送的验证码请求变为了http://renren-fast/api/captcha.jpg,路径中带由了api

所以需要将请求的路径修改为http://renren-fast/captcha.jpg

需要设置The RewritePath GatewayFilter Factory

        - id: admin_root
          uri: lb://renren-fast
          predicates:
            - Path=/api/**
          ## http://localhost:88/api/captcha.jpg   http://renren-fast/api/captcha.jpg
          filters:
           - RewritePath=/api/(?<segment>/?.*), /renren-fast/$\{segment}

此时就不会出现验证码的问题了

解决跨域问题

此时点击登录,出现跨域问题

在gate网关配置解决跨域问题

@Configuration
public class GulimallCorsConfiguration {

    @Bean
    public CorsWebFilter corsWebFilter(){

        UrlBasedCorsConfigurationSource urlsorce = new UrlBasedCorsConfigurationSource();

        CorsConfiguration corsConfiguration=new CorsConfiguration();
        //配置跨域
        corsConfiguration.addAllowedHeader("*");//允许的请求头
        corsConfiguration.addAllowedMethod("*");//允许的请求方法
        corsConfiguration.addAllowedOrigin("*");//允许的请求来源
        corsConfiguration.setAllowCredentials(true);//是否允许携带cookie


        urlsorce.registerCorsConfiguration("/**",corsConfiguration);

       return new CorsWebFilter(urlsorce);

        }
    }

并且将renrnen-fast中的跨域问题的配置注释了

登录页面成功进入后台

当组件加载完成会发送一个请求获取三级分类数据

我们使用gateway进行转发

1、配置gulimall-product模块到nacos中

  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
  application:
    name: gulimall-product

2、创建product的命名空间,将prodcut的配置拆分(暂时不做)

3、新建bootstarp.yml文件,配置地址和配置文件

spring:
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        namespace: 765a7765-9d98-47ca-9050-95b2450d4457

4、使用注解*@EnableDiscoveryClient*开启服务发现

启动测试是否被注册到注册中心

5、完成gateway的转发

修改gateway的配置文件

配置新的路由

        - id: product
          uri: lb://gulimall-product
          predicates:
            - Path=/api/product/**
          filters:
            - RewritePath=/api/(?<segment>/?.*), /$\{segment}

重新启动网关测试

成功返回到数据

将数据添加到页面上

下一步就是将这些数据添加到页面上

<template>
<div><el-tree :data="menus" :props="defaultProps" @node-click="handleNodeClick"></el-tree></div>
</template>

<script>

export default {
//import引入的组件需要注入到对象中才能使用
components: {},
props: {},
    data() {
      return {
      //数据存放在menus中
        menus:[
        
        ],
        defaultProps: {
          children: 'children',
          label: 'name'//修改label,展示name数据中的属性
        }
      };
    },
    methods: {
      handleNodeClick(data) {
        console.log(data);
      },
      getMenus(){
            this.dataListLoading = true
        this.$http({
          url: this.$http.adornUrl('/product/category/list/tree'),
          method: 'get'
          //对data进行解构
        }).then(({data})=>{
            // console.log("成功获取到数据",data.data)
            this.menus=data.data;
        })
      }
    },
//计算属性 类似于data概念
computed: {},
//监控data中的数据变化
watch: {},
//方法集合

//声明周期 - 创建完成(可以访问当前this实例)
created() {
this.getMenus();
},
//声明周期 - 挂载完成(可以访问DOM元素)
mounted() {},
beforeCreate() {}, //生命周期 - 创建之前
beforeMount() {}, //生命周期 - 挂载之前
beforeUpdate() {}, //生命周期 - 更新之前
updated() {}, //生命周期 - 更新之后
beforeDestroy() {}, //生命周期 - 销毁之前
destroyed() {}, //生命周期 - 销毁完成
activated() {} //如果页面有keep-alive缓存功能,这个函数会触发
};
</script>
<style scoped>
</style>

给页面添加按钮(添加和删除节点的按钮)并且节点前面添加复选框,规定只有1级和2级节点可以使用append按钮,没有子节点的才能使用delete按钮

实现

<template>
<div><el-tree :data="menus" :props="defaultProps"   :expand-on-click-node="false"  node-key="catId"
 show-checkbox>
 
 <span class="custom-tree-node" slot-scope="{ node, data }">
        <span>{{ node.label }}</span>
        <span>
          <el-button v-if="node.level<=2"
            type="text"
            size="mini"
            @click="() => append(data)">
            Append
          </el-button>
          <el-button
     v-if="node.childNodes.length==0"
            type="text"
            size="mini"
            @click="() => remove(node, data)">
            Delete
          </el-button>
        </span>
      </span>
 
 </el-tree></div>
</template>

<script>

    methods: {
      handleNodeClick(data) {
        console.log(data);
      },
      getMenus(){
            this.dataListLoading = true
        this.$http({
          url: this.$http.adornUrl('/product/category/list/tree'),
          method: 'get'
        }).then(({data})=>{
            // console.log("成功获取到数据",data.data)
            this.menus=data.data;
        });
      },

//点击append和remove后触发的方法
       append(data) {
       console.log("append"+data)
      },

      remove(node, data) {
       console.log("remove",node,data)
      }

删除功能实现

逻辑删除(当show-status的值为1时表示没有删除,为0时表示已删除)

查看官方文档所给出的逻辑删除功能的使用方法逻辑删除 | MyBatis-Plus (baomidou.com)

步骤 1: 配置

application.yml

mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

步骤 2: 实体类字段上加上@TableLogic注解

@TableLogic
private Integer deleted;

由于这里我们是1表示未删除,0表示已删除

所有在@TableLogic注解中,规定我们自己的逻辑

	@TableLogic(value ="1",delval = "0")
	private Integer showStatus;

删除代码生成器生成的删除方法

自定义一个deleteMenusByIds()方法

@Override
public void removeMenusByIds(List<Long> asList) {
    //TODO 检查当前删除的菜单是否被别的地方引用
 categoryDao.deleteBatchIds(asList);
}

通过前端的删除按钮发送请求

点击删除弹出确认删除对话框,删除成功后由成功提示,且删除之后默认展开父节点

<template>
<div><el-tree :data="menus" :props="defaultProps"   :expand-on-click-node="false"  node-key="catId"
 show-checkbox
  :default-expanded-keys="isCheckArrays"
  >
 
 <span class="custom-tree-node" slot-scope="{ node, data }">
        <span>{{ node.label }}</span>
        <span>
          <el-button v-if="node.level<=2"
            type="text"
            size="mini"
            @click="() => append(data)">
            Append
          </el-button>
          <el-button
     v-if="node.childNodes.length==0"
            type="text"
            size="mini"
            @click="() => remove(node, data)">
            Delete
          </el-button>
        </span>
      </span>
 
 </el-tree></div>
</template>

<script>

export default {
//import引入的组件需要注入到对象中才能使用
components: {},
props: {},
    data() {
      return {
        menus:[],
        isCheckArrays:[],
        defaultProps: {
          children: 'children',
          label: 'name'
        }
      };
    },
    methods: {
      getMenus(){
            this.dataListLoading = true
        this.$http({
          url: this.$http.adornUrl('/product/category/list/tree'),
          method: 'get'
        }).then(({data})=>{
            // console.log("成功获取到数据",data.data)
          
            this.menus=data.data;
        });
      },

//点击append和remove后触发的方法
       append(data) {
       console.log("append"+data)
      },
      remove(node, data) {
     var ids=[data.catId];

       this.$confirm(`此操作将永久删除【${data.name}】节点, 是否继续?`, '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          
           this.$http({
       url: this.$http.adornUrl('/product/category/delete'),
       method: 'post',
       data: this.$http.adornData(ids, false)
       }).then(({ data }) => {
        //彈出刪除成功提示
         this.$message({
          message: '刪除成功',
          type: 'success'
        });

       this.getMenus();
       //设置要固定的对话框
       this.isCheckArrays=[node.parent.data.catId]; 
       });
    
        }).catch(() => {
          this.$message({
            type: 'info',
            message: '已取消删除'
          });          
        });
      }
    },

新增功能实现

功能描述:点击append后弹出对话框嵌套表单,填好要添加节点的name后点击确定关闭,对话框关闭,请求发送到后端实现新增。点击确定后页面刷新,展开当时点击append的父节点

最终的前端代码

<template>
<div>
  <el-tree :data="menus" :props="defaultProps"   :expand-on-click-node="false"  node-key="catId"
 show-checkbox
  :default-expanded-keys="isCheckArrays"
  >
 
 <span class="custom-tree-node" slot-scope="{ node, data }">
        <span>{{ node.label }}</span>
        <span>
          <el-button v-if="node.level<=2"
            type="text"
            size="mini"
            @click="() => append(data)">
            Append
          </el-button>
          <el-button
     v-if="node.childNodes.length==0"
            type="text"
            size="mini"
            @click="() => remove(node, data)">
            Delete
          </el-button>
        </span>
      </span>
 
 </el-tree>
 
 <el-dialog
  title="提示"
  :visible.sync="dialogVisible"
  width="30%"
 >
 
    <!-- 嵌套表单 -->
<el-dialog title="提示" :visible.sync="dialogVisible" width="30%">
  <el-form :model="category">
    <el-form-item label="分类名称">
      <el-input v-model="category.name" autocomplete="off"></el-input>
    </el-form-item>
  </el-form>
  <div slot="footer" class="dialog-footer">
    <el-button @click="dialogVisible = false">取 消</el-button>
    <el-button type="primary" @click="addCategory">确 定</el-button>
  </div>
</el-dialog>
</el-dialog>

 
 </div>
</template>

<script>
export default {
  //import引入的组件需要注入到对象中才能使用
  components: {},
  props: {},
  data() {
    return {
      category:{
        name:"",
        parentCid:"1",
        cateLevel:"",
        showStatus:"",
        sort:"1"
      },//添加对话框中所填数据
      dialogVisible: false, //添加对话框是否可见
      menus: [],
      isCheckArrays: [],//默认选中打开节点
      defaultProps: {
        children: "children",
        label: "name"
      }
    };
  },
  methods: {
    //提交新增表单
    addCategory(){
      //发送post请求添加
      this.$http({
      url: this.$http.adornUrl('/product/category/save'),
      method: 'post',
      data: this.$http.adornData(this.category, false)
      }).then(({ data }) => {


//添加成功
 this.$message({
          message: '添加成功',
          type: 'success'
        });
        //关闭对话框
        this.dialogVisible=false;
//重新刷新界面
this.getMenus();
//打开选中节点
this.isCheckArrays=[this.category.parentCid]
       });
  // console.log(this.category)
    },


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

        this.menus = data.data;
      });
    },

    //点击append和remove后触发的方法
    append(data) {
      console.log("append" , data);
      this.dialogVisible = true;
//获取添加新节点的属性
    this.category.parentCid=data.catId;
    this.category.showStatus=data.showStatus;
    this.category.catLevel=data.catLevel*1+1;
    this.category.sort=data.sort;

    },
    remove(node, data) {
      var ids = [data.catId];

      this.$confirm(`此操作将永久删除【${data.name}】节点, 是否继续?`, "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning"
      })
        .then(() => {
          this.$http({
            url: this.$http.adornUrl("/product/category/delete"),
            method: "post",
            data: this.$http.adornData(ids, false)
          }).then(({ data }) => {
            //彈出刪除成功提示
            this.$message({
              message: "刪除成功",
              type: "success"
            });

            this.getMenus();
            //设置要固定的对话框
            this.isCheckArrays = [node.parent.data.catId];
          });
        })
        .catch(() => {
          this.$message({
            type: "info",
            message: "已取消删除"
          });
        });
    }
  },
  //计算属性 类似于data概念
  computed: {},
  //监控data中的数据变化
  watch: {},
  //方法集合

  //声明周期 - 创建完成(可以访问当前this实例)
  created() {
    this.getMenus();
  },
  //声明周期 - 挂载完成(可以访问DOM元素)
  mounted() {},
  beforeCreate() {}, //生命周期 - 创建之前
  beforeMount() {}, //生命周期 - 挂载之前
  beforeUpdate() {}, //生命周期 - 更新之前
  updated() {}, //生命周期 - 更新之后
  beforeDestroy() {}, //生命周期 - 销毁之前
  destroyed() {}, //生命周期 - 销毁完成
  activated() {} //如果页面有keep-alive缓存功能,这个函数会触发
};
</script>
<style scoped>
</style>

编辑功能实现

具体效果:

1、新增编辑按钮

2、点击编辑,弹出对话框,使用新增的对话框,进行数据回显,且对话框的title根据业务的不同有所变化

3、进行数据回显时,需要发送get请求查询数据

4、当打开的对话框是endit时,点击确定发送的updata请求,打开的对话框的save时发送save请求

5、点击append按钮时,清空表单中回显的数据

注意:发送修改请求时,只需要修改form中的三个属性即可

前端代码:

<template>
<div>
  <el-tree :data="menus" :props="defaultProps"   :expand-on-click-node="false"  node-key="catId"
 show-checkbox
  :default-expanded-keys="isCheckArrays"
  >
 
 <span class="custom-tree-node" slot-scope="{ node, data }">
        <span>{{ node.label }}</span>
        <span>
          <el-button v-if="node.level<=2"
            type="text"
            size="mini"
            @click="() => append(data)">
            Append
          </el-button>
          <el-button
     v-if="node.childNodes.length==0"
            type="text"
            size="mini"
            @click="() => remove(node, data)">
            Delete
          </el-button>

         <el-button
            type="text"
            size="mini"
            @click="() => endit(data)">
            Endit
          </el-button>

        </span>
      </span>
 
 </el-tree>
 
 <el-dialog
  title="提示"
  :visible.sync="dialogVisible"
  width="30%"
 >
 
    <!-- 嵌套表单 -->
<el-dialog :title="title" :visible.sync="dialogVisible" width="30%" :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.productUnit" autocomplete="off"></el-input>
    </el-form-item>

  </el-form>
  <div slot="footer" class="dialog-footer">
    <el-button @click="dialogVisible = false">取 消</el-button>
    <el-button type="primary" @click="submitForm">确 定</el-button>
  </div>
</el-dialog>
</el-dialog>

 
 </div>
</template>

<script>
export default {
  //import引入的组件需要注入到对象中才能使用
  components: {},
  props: {},
  data() {
    return {
      title:"",//对话框的title
      formType:"",
      category:{
        catId:null,
        name:"",
        parentCid:"0",
        cateLevel:"",
        showStatus:"",
        sort:"1",
        icon:"",
        productUnit:""
      
      },//添加对话框中所填数据
      dialogVisible: false, //添加对话框是否可见
      menus: [],
      isCheckArrays: [],//默认选中打开节点
      defaultProps: {
        children: "children",
        label: "name"
      }
    };
  },
  methods: {
//提交表单

    submitForm(){
if(this.formType=="append"){
  this.addCategory();
}

if(this.formType=="endit"){
  //执行的是保存请求
this.enditData();
}
    },
    //提交新增表单
    addCategory(){
      //发送post请求添加
      this.$http({
      url: this.$http.adornUrl('/product/category/save'),
      method: 'post',
      data: this.$http.adornData(this.category, false)
      }).then(({ data }) => {

//添加成功
 this.$message({
          message: '添加成功',
          type: 'success'
        });
        //关闭对话框
        this.dialogVisible=false;
//重新刷新界面
this.getMenus();
//打开选中节点
this.isCheckArrays=[this.category.parentCid]
       });
  // console.log(this.category)
    },

    //修改

enditData(){



var {catId,name,icon,productUnit}=this.category;
var somdata={
  catId:catId,
  name:name,
  icon:icon,
  productUnit:productUnit
};

// console.log("修改成功");
this.$http({
url: this.$http.adornUrl('/product/category/update'),
method: 'post',
data: this.$http.adornData(somdata, false)
}).then(({ data }) => {
//修改成功
 //对话框消失
 this.dialogVisible=false;
 //刷新界面
   this.getMenus();

 this.$message({
          message: '修改成功',
          type: 'success'
        });
 });

    },

    getMenus() {
      this.dataListLoading = true;
      this.$http({
        url: this.$http.adornUrl("/product/category/list/tree"),
        method: "get"
      }).then(({ data }) => {
        // console.log("成功获取到数据",data.data)
        this.menus = data.data;
      });
    },

    //点击append和remove后触发的方法
    append(data) {


      this.title="新增";
      console.log("append" , data);
      this.dialogVisible = true;
      this.formType="append";//展示的是不回显数据的对话框
//获取添加新节点的属性
    this.category.parentCid=data.catId;
    this.category.showStatus=data.showStatus;
    this.category.catLevel=data.catLevel*1+1;
    this.category.sort=data.sort;
    //清空输入框
    this.category.icon="";
    this.category.sort=0;
    this.category.catId=null;
    this.productUnit="";

    },
    remove(node, data) {
      var ids = [data.catId];

      this.$confirm(`此操作将永久删除【${data.name}】节点, 是否继续?`, "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning"
      })
        .then(() => {
          this.$http({
            url: this.$http.adornUrl("/product/category/delete"),
            method: "post",
            data: this.$http.adornData(ids, false)
          }).then(({ data }) => {
            //彈出刪除成功提示
            this.$message({
              message: "刪除成功",
              type: "success"
            });

            this.getMenus();
            //设置要固定的对话框
            this.isCheckArrays = [node.parent.data.catId];
          });
        })
        .catch(() => {
          this.$message({
            type: "info",
            message: "已取消删除"
          });
        });
    },

//修改按钮绑定的方法
     endit(data){

       this.category.catId=data.catId;
//设置要固定的对话框
this.isCheckArrays = [data.parentCid];

console.log("endit" , data);
this.title="编辑";
       //弹出对话框--还是使用那个对话框
        this.dialogVisible = true;
        this.formType="endit";//展示的是回显数据的对话框

//回显数据--发送查询请求(可能别的管理员登录也要进行修改,导致我们回显的是旧数据,所以需要查询一下数据库)
// this.category.name=data.name;
// this.category.icon=data.icon;
// this.productUnit=data.productUnit;
this.$http({
url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
method: 'get'
}).then(({ data }) => {
//返回数据--记得console一下
// console.log(data)
this.category.name=data.category.name;
this.category.icon=data.category.icon;
this.productUnit=data.category.productUnit;
})

     }
  
  },
  //计算属性 类似于data概念
  computed: {},
  //监控data中的数据变化
  watch: {},
  //方法集合

  //声明周期 - 创建完成(可以访问当前this实例)
  created() {
    this.getMenus();
  },
  //声明周期 - 挂载完成(可以访问DOM元素)
  mounted() {},
  beforeCreate() {}, //生命周期 - 创建之前
  beforeMount() {}, //生命周期 - 挂载之前
  beforeUpdate() {}, //生命周期 - 更新之前
  updated() {}, //生命周期 - 更新之后
  beforeDestroy() {}, //生命周期 - 销毁之前
  destroyed() {}, //生命周期 - 销毁完成
  activated() {} //如果页面有keep-alive缓存功能,这个函数会触发
};
</script>
<style scoped>
</style>

拖拽效果的实现

给树形控件添加参数:allow-drag=“allowDrag”

:allow-drop=“allowDrop”

绑定一个方法allowDrop用来判断是否能被拖动

 //判断是否能被拖拽
    allowDrop(draggingNode, dropNode, type) {
      console.log("draggingNode", draggingNode, dropNode, type);
      //查找最深的level
      this.selectMaxLeven(draggingNode.data);
      let deep = this.MaxLevel - draggingNode.data.catLevel + 1;

      console.log("深度", this.MaxLevel);
      if (type == "inner") {
        //如果是拖动到里面

        return deep + dropNode.level <= 3;
      } else {
        //  console.log(false);
        return deep + dropNode.parent.level <= 3;
      }
    },
    selectMaxLeven(node) {
      //判断是否有子节点
      if (node.children != null && node.children.length > 0) {
        //有子节点,存放在一个Array的数组里面,遍历子节点
        for (let i = 0; i < node.children.length; i++) {
          if (node.children[i].catLevel > this.MaxLevel) {
            this.MaxLevel = node.children[i].catLevel;
          }
          //递归查找
          this.selectMaxLeven(node.children[i]);
        }
      }
    },

拖拽后进行数据更新

使用事件@node-drag-end=“handleDragEnd” 拖拽结束时(可能未成功)触发的事件

1、更新他的父节点

2、对移动后的兄弟节点进行排序

3、更新他的level

4、更新他的子节点的level

 handleDrop(draggingNode, dropNode, dropType, ev) {
 
 //先初始化
       this.updateNodes=[];
      this.MaxLevel= 0;
      console.log("draggingNode ", draggingNode, dropNode, dropType, ev);

      let pCid = 0; //父节点
      let sibling = null; //拖动后的兄弟节点
      //1、获取拖拽后的节点的父节点
      //判断当前的拖动type
      if (dropType == "before" || dropType == "after") {
        
        pCid=dropNode.parent.data.catId==undefined?0:dropNode.parent.data.catId;
         sibling = dropNode.parent.childNodes;
      } else {
        //如果是inner,则父节点为
        pCid = dropNode.data.catId;
        sibling = dropNode.childNodes;
      }

      //2、拿到拖拽后的兄弟节点,并且完成新排序

      for (let i = 0; i < sibling.length; i++) {
        if (sibling[i].data.catId == draggingNode.data.catId) {
          //遍历到当前节点是拖拽的节点,还需要更新parentCid,和当前层级,还有它子节点的层级
          let catLevel = draggingNode.data.catLevel; //层级
// console.log("当前层级为",catLevel)
          if (sibling[i].level !=catLevel) {
            //  console.log("层级发生变化")
            //当前节点层级发生变化
            //层级发生变化
     catLevel=sibling[i].level;
            //修改他的子节点
            this.updateChildNodeLevel(sibling[i])
          }
          this.updateNodes.push({
            catId: sibling[i].data.catId,
            sort: i,
            parentCid: pCid,
            catLevel: catLevel
          });
        }
         else {
          //遍历的是其他子节点
          this.updateNodes.push({
            catId: sibling[i].data.catId,
            sort: i
          });
        }
      }
      //当前拖拽节点的最新层级
      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
          });
          //递归
          this.updateChildNodeLevel(node.childNodes[i]);
        }
    },

后端处理

/*
 批量修改
 */
@RequestMapping("/update/sort")
public R updateByList(@RequestBody CategoryEntity[] categorys){
    categoryService.updateBatchById(Arrays.asList(categorys));

    return R.ok();
}

前端发送请求

      //当前拖拽节点的最新层级
      console.log("updateNodes", this.updateNodes);

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

         this.$message({
          message: '移动节点成功',
          type: 'success'
        });
  //重新刷新界面
        this.getMenus();
        //打开选中节点
        this.isCheckArrays = [pCid];

      });
    },

拖拽功能优化

添加一个按钮,点击后开启拖拽,并且显示批量保存的按钮

点击批量保存实现保存所有拖动过的节点

由于之前判断是否能拖拽的方法中调用的是数据库中的属性,我们修改为当前节点下的属性

前端代码实现:

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

<el-button v-if="draggable" @click="saveAll">批量保存</el-button>

  <el-tree :data="menus" :props="defaultProps"   :expand-on-click-node="false"  node-key="catId"
 show-checkbox
  :default-expanded-keys="isCheckArrays"
   :draggable="draggable"
  :allow-drop="allowDrop"
  @node-drop="handleDrop"
  >
saveAll(){
  
      this.$http({
      url: this.$http.adornUrl('/product/category/update/sort'),
      method: 'post',
      data: this.$http.adornData(this.updateNodes, false)
      }).then(({ data }) => { 

         this.$message({
          message: '移动节点成功',
          type: 'success'
        });
  //重新刷新界面
        this.getMenus();
        //打开选中节点
        this.isCheckArrays =this.pCid;//这里的pCid为一个数组
//初始化
       this.updateNodes=[];
      this.MaxLevel= 0;
      });

修改判断是否能被拖拽的方法的修改

    //判断是否能被拖拽
    allowDrop(draggingNode, dropNode, type) {
      console.log("draggingNode", draggingNode, dropNode, type);
      //查找最深的level
      this.selectMaxLeven(draggingNode);
      let deep = Math.abs(this.MaxLevel - draggingNode.level) + 1;

      console.log("深度", this.MaxLevel);
      if (type == "inner") {
        //如果是拖动到里面
        return deep + dropNode.level <= 3;
      } else {
        //  console.log(false);
        return deep + dropNode.parent.level <= 3;
      }
    },

批量删除实现

使用下面这个方法获取选中的 cateId

getCheckedNodes若节点可被选择(即 show-checkboxtrue),则返回目前被选中的节点所组成的数组

ref 被用来给元素或子组件注册引用信息, 引用信息将会注册在父组件的 $refs 对象上

给树形控件加上ref

deleteAll(){
      let nums=[];
//批量删除
nums=this.$refs.menuTree.getCheckedNodes();//使用$refs指向这个控件

//遍历这个数组
for(var i=0;i<nums.length;i++){
  this.catIds.push(nums[i].catId);
}
//弹出对话框
 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 }) => {
          this.$message({
            type: 'success',
            message: '删除成功!'
          });
          //刷新界面
          this.getMenus();
 });

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

    },

category.vue的全部代码

<template>
<div>

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

<el-button v-if="draggable" @click="saveAll">批量保存</el-button>
  <el-button type="danger" round @click="deleteAll">批量删除</el-button>

  <el-tree :data="menus" :props="defaultProps"   :expand-on-click-node="false"  node-key="catId"
 show-checkbox
  :default-expanded-keys="isCheckArrays"
   :draggable="draggable"
  :allow-drop="allowDrop"
  @node-drop="handleDrop"
  ref="menuTree"
  >
 
 <span class="custom-tree-node" slot-scope="{ node, data }">
        <span>{{ node.label }}</span>
        <span>
          <el-button v-if="node.level<=2"
            type="text"
            size="mini"
            @click="() => append(data)">
            Append
          </el-button>
          <el-button
     v-if="node.childNodes.length==0"
            type="text"
            size="mini"
            @click="() => remove(node, data)">
            Delete
          </el-button>

         <el-button
            type="text"
            size="mini"
            @click="() => endit(data)">
            Endit
          </el-button>

        </span>
      </span>
 
 </el-tree>
 
 <el-dialog
  title="提示"
  :visible.sync="dialogVisible"
  width="30%"
 >
 
    <!-- 嵌套表单 -->
<el-dialog :title="title" :visible.sync="dialogVisible" width="30%" :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.productUnit" autocomplete="off"></el-input>
    </el-form-item>

  </el-form>
  <div slot="footer" class="dialog-footer">
    <el-button @click="dialogVisible = false">取 消</el-button>
    <el-button type="primary" @click="submitForm">确 定</el-button>
  </div>
</el-dialog>
</el-dialog>

 
 </div>
</template>

<script>
export default {
  //import引入的组件需要注入到对象中才能使用
  components: {},
  props: {},
  data() {
    return {
      catIds:[],
      pCid:[],
      draggable: false,
      updateNodes: [],
      MaxLevel: 0,
      title: "", //对话框的title
      formType: "",
      category: {
        catId: null,
        name: "",
        parentCid: "0",
        cateLevel: "",
        showStatus: "",
        sort: "1",
        icon: "",
        productUnit: ""
      }, //添加对话框中所填数据
      dialogVisible: false, //添加对话框是否可见
      menus: [],
      isCheckArrays: [], //默认选中打开节点
      defaultProps: {
        children: "children",
        label: "name"
      }
    };
  },
  methods: {
    deleteAll(){
      let nums=[];
//批量删除
nums=this.$refs.menuTree.getCheckedNodes();

//遍历这个数组
for(var i=0;i<nums.length;i++){
  this.catIds.push(nums[i].catId);
}
//弹出对话框
 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 }) => {
          this.$message({
            type: 'success',
            message: '删除成功!'
          });
          //刷新界面
          this.getMenus();
 });

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

    },

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

         this.$message({
          message: '移动节点成功',
          type: 'success'
        });
  //重新刷新界面
        this.getMenus();
        //打开选中节点
        this.isCheckArrays =this.pCid;
//初始化
       this.updateNodes=[];
      this.MaxLevel= 0;
      });

},

    handleDrop(draggingNode, dropNode, dropType, ev) {

      console.log("draggingNode ", draggingNode, dropNode, dropType, ev);

      let pCid = 0; //父节点
      let sibling = null; //拖动后的兄弟节点
      //1、获取拖拽后的节点的父节点
      //判断当前的拖动type
      if (dropType == "before" || dropType == "after") {
        
        pCid=dropNode.parent.data.catId==undefined?0:dropNode.parent.data.catId;
         sibling = dropNode.parent.childNodes;
      } else {
        //如果是inner,则父节点为
        pCid = dropNode.data.catId;
        sibling = dropNode.childNodes;
      }
      this.pCid.push(pCid);

      //2、拿到拖拽后的兄弟节点,并且完成新排序

      for (let i = 0; i < sibling.length; i++) {
        if (sibling[i].data.catId == draggingNode.data.catId) {
          //遍历到当前节点是拖拽的节点,还需要更新parentCid,和当前层级,还有它子节点的层级
          let catLevel = draggingNode.data.catLevel; //层级
// console.log("当前层级为",catLevel)
          if (sibling[i].level !=catLevel) {
            //  console.log("层级发生变化")
            //当前节点层级发生变化
            //层级发生变化
     catLevel=sibling[i].level;
            //修改他的子节点
            this.updateChildNodeLevel(sibling[i])
          }
          this.updateNodes.push({
            catId: sibling[i].data.catId,
            sort: i,
            parentCid: pCid,
            catLevel: catLevel
          });
        }
         else {
          //遍历的是其他子节点
          this.updateNodes.push({
            catId: sibling[i].data.catId,
            sort: i
          });
        }
      }

      //当前拖拽节点的最新层级
      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
          });
          //递归
          this.updateChildNodeLevel(node.childNodes[i]);
        }
    },
    //判断是否能被拖拽
    allowDrop(draggingNode, dropNode, type) {
      console.log("draggingNode", draggingNode, dropNode, type);
      //查找最深的level
      this.selectMaxLeven(draggingNode);
      let deep = Math.abs(this.MaxLevel - draggingNode.level) + 1;

      console.log("深度", this.MaxLevel);
      if (type == "inner") {
        //如果是拖动到里面
        return deep + dropNode.level <= 3;
      } else {
        //  console.log(false);
        return deep + dropNode.parent.level <= 3;
      }
    },
    selectMaxLeven(node) {
      //判断是否有子节点
      if (node.childNodes != null && node.childNodes.length > 0) {
        //有子节点,存放在一个Array的数组里面,遍历子节点
        for (let i = 0; i < node.childNodes.length; i++) {
          if (node.childNodes[i].level > this.MaxLevel) {
            this.MaxLevel = node.childNodes[i].level;
          }
          //递归查找
          this.selectMaxLeven(node.childNodes[i]);
        }
      }
    },

    //提交表单

    submitForm() {
      if (this.formType == "append") {
        this.addCategory();
      }

      if (this.formType == "endit") {
        //执行的是保存请求
        this.enditData();
      }
    },
    //提交新增表单
    addCategory() {
      //发送post请求添加
      this.$http({
        url: this.$http.adornUrl("/product/category/save"),
        method: "post",
        data: this.$http.adornData(this.category, false)
      }).then(({ data }) => {
        //添加成功
        this.$message({
          message: "添加成功",
          type: "success"
        });
        //关闭对话框
        this.dialogVisible = false;
        //重新刷新界面
        this.getMenus();
        //打开选中节点
        this.isCheckArrays = [this.category.parentCid];
      });
      // console.log(this.category)
    },

    //修改

    enditData() {
      var { catId, name, icon, productUnit } = this.category;
      var somdata = {
        catId: catId,
        name: name,
        icon: icon,
        productUnit: productUnit
      };

      // console.log("修改成功");
      this.$http({
        url: this.$http.adornUrl("/product/category/update"),
        method: "post",
        data: this.$http.adornData(somdata, false)
      }).then(({ data }) => {
        //修改成功
        //对话框消失
        this.dialogVisible = false;
        //刷新界面
        this.getMenus();

        this.$message({
          message: "修改成功",
          type: "success"
        });
      });
    },

    getMenus() {
      this.dataListLoading = true;
      this.$http({
        url: this.$http.adornUrl("/product/category/list/tree"),
        method: "get"
      }).then(({ data }) => {
        // console.log("成功获取到数据",data.data)
        this.menus = data.data;
      });
    },

    //点击append和remove后触发的方法
    append(data) {
      this.title = "新增";
      console.log("append", data);
      this.dialogVisible = true;
      this.formType = "append"; //展示的是不回显数据的对话框
      //获取添加新节点的属性
      this.category.parentCid = data.catId;
      this.category.showStatus = data.showStatus;
      this.category.catLevel = data.catLevel * 1 + 1;
      this.category.sort = data.sort;
      //清空输入框
      this.category.icon = "";
      this.category.sort = 0;
      this.category.catId = null;
      this.productUnit = "";
    },
    remove(node, data) {
      var ids = [data.catId];

      this.$confirm(`此操作将永久删除【${data.name}】节点, 是否继续?`, "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning"
      })
        .then(() => {
          this.$http({
            url: this.$http.adornUrl("/product/category/delete"),
            method: "post",
            data: this.$http.adornData(ids, false)
          }).then(({ data }) => {
            //彈出刪除成功提示
            this.$message({
              message: "刪除成功",
              type: "success"
            });

            this.getMenus();
            //设置要固定的对话框
            this.isCheckArrays = [node.parent.data.catId];
          });
        })
        .catch(() => {
          this.$message({
            type: "info",
            message: "已取消删除"
          });
        });
    },

    //修改按钮绑定的方法
    endit(data) {
      this.category.catId = data.catId;
      //设置要固定的对话框
      this.isCheckArrays = [data.parentCid];

      console.log("endit", data);
      this.title = "编辑";
      //弹出对话框--还是使用那个对话框
      this.dialogVisible = true;
      this.formType = "endit"; //展示的是回显数据的对话框

      //回显数据--发送查询请求(可能别的管理员登录也要进行修改,导致我们回显的是旧数据,所以需要查询一下数据库)
      // this.category.name=data.name;
      // this.category.icon=data.icon;
      // this.productUnit=data.productUnit;
      this.$http({
        url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
        method: "get"
      }).then(({ data }) => {
        //返回数据--记得console一下
        // console.log(data)
        this.category.name = data.category.name;
        this.category.icon = data.category.icon;
        this.productUnit = data.category.productUnit;
      });
    }
  },
  //计算属性 类似于data概念
  computed: {},
  //监控data中的数据变化
  watch: {},
  //方法集合

  //声明周期 - 创建完成(可以访问当前this实例)
  created() {
    this.getMenus();
  },
  //声明周期 - 挂载完成(可以访问DOM元素)
  mounted() {},
  beforeCreate() {}, //生命周期 - 创建之前
  beforeMount() {}, //生命周期 - 挂载之前
  beforeUpdate() {}, //生命周期 - 更新之前
  updated() {}, //生命周期 - 更新之后
  beforeDestroy() {}, //生命周期 - 销毁之前
  destroyed() {}, //生命周期 - 销毁完成
  activated() {} //如果页面有keep-alive缓存功能,这个函数会触发
};
</script>
<style scoped>
</style>

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值