文章目录

  • 1 三级分类
    • 1.1 数据准备
    • 1.2 查询功能实现
      • 1.2.1 controller
      • 1.2.2 entity
      • 1.2.2 service
      • 1.2.3 测试
    • 1.3 配置网关路由与路径重写
      • 1.3.2 配置renren-fast
    • 1.4 跨域
      • 1.4.1 什么是跨域
      • 1.4.2 跨域流程
      • 1.4.3 解决跨域的方法
      • 1.4.4 网关配置跨域
    • 1.5 树形展示分类数据
      • 1.5.1 添加网关路由规则
      • 1.5.2 配置注册nacos
      • 1.5.3 前端页面
      • 1.5.4 测试
    • 1.6 删除分类实现
      • 1.6.1 前端页面
      • 1.6.2 测试删除逻辑
      • 1.6.3 实现逻辑删除
      • 1.6.4 删除效果细化
    • 1.7 新增分类实现
    • 1.8 修改效果实现
    • 1.9 拖拽效果实现
      • 1.9.1 前端效果实现
      • 1.9.2 前端数据更新
      • 1.9.3 后端数据更新
      • 1.9.4 批量保存拖拽效果
    • 1.10 批量删除节点实现

1 三级分类

1.1 数据准备

导入数据pms_catelog.sql到表 psm_category

文件下载地址:https://share.weiyun.com/bO0OZMCv

连接失效可以到尚硅谷微信公众号输入“谷粒商城”获取

1.2 查询功能实现

1.2.1 controller

修改CategoryController.java里的list方法

/*** 查出所有分类以及子分类,以树形结构组装起来*/
@RequestMapping("/list/tree")
// @RequiresPermissions("product:category:list")
public R list(){List<CategoryEntity> entities =  categoryService.listWithTree();return R.ok().put("data", entities);
}

1.2.2 entity

修改CategoryEntity.java,添加 children属性

@TableField(exist = false)
private List<CategoryEntity> children;

1.2.2 service

在CategoryService.java声明方法

在CategoryServiceImpl.java实现方法

@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {@Overridepublic PageUtils queryPage(Map<String, Object> params) {IPage<CategoryEntity> page = this.page(new Query<CategoryEntity>().getPage(params),new QueryWrapper<CategoryEntity>());return new PageUtils(page);}@Overridepublic List<CategoryEntity> listWithTree() {//1、查出所有分类List<CategoryEntity> entities = baseMapper.selectList(null);//2、组装成父子的树形结构List<CategoryEntity> level1Menus = entities.stream()// 过滤,取出所有1级分类的记录.filter(categoryEntity -> categoryEntity.getParentCid() == 0)// 递归映射子分类.map((menu) -> {menu.setChildren(getChildrens(menu, entities));return menu;}).sorted(Comparator.comparingInt(menu -> (menu.getSort() == null ? 0 : menu.getSort()))).collect(Collectors.toList());return level1Menus;}//递归查找所有菜单的子菜单private List<CategoryEntity> getChildrens(CategoryEntity root, List<CategoryEntity> all) {List<CategoryEntity> children = all.stream()// 过滤出分类的父级与传入参数的等级一样的记录,级1级分类找2级分类.filter(categoryEntity -> categoryEntity.getParentCid().equals(root.getCatId())).map(categoryEntity -> {//1、递归找子菜单categoryEntity.setChildren(getChildrens(categoryEntity, all));return categoryEntity;}).sorted(Comparator.comparingInt(menu -> (menu.getSort() == null ? 0 : menu.getSort()))).collect(Collectors.toList());return children;}}

1.2.3 测试

启动服务,测试,注意,先启动nacos

访问http://localhost:10000/product/category/list/tree

1.3 配置网关路由与路径重写

1、idea启动renren-fast

2、vscode打开renren-fast-vue启动

3、进入前端开发平台,进入菜单管理新增菜单

可以在gulimall-admin数据库里的sys_menu表里看到

4、新增分类维护

角色管理视图对应renren-fast-vue里的src/views/modules/sys/role.vue文件

5、在modules文件夹下创建product文件夹

6、新建编写category.vue

<!--  -->
<template><el-tree:data="data":props="defaultProps"@node-click="handleNodeClick"></el-tree>
</template><script>
//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';export default {//import引入的组件需要注入到对象中才能使用components: {},data() {//这里存放数据return {};},//监听属性 类似于data概念computed: {},//监控data中的数据变化watch: {},//方法集合methods: {getMenus() {this.$http({url: this.$http.adornUrl("/product/category/list/tree"),method: "get",}).then(({ data }) => {console.log("成功获取到菜单数据...", data.data);this.menus = data.data;});},},//生命周期 - 创建完成(可以访问当前this实例)created() {this.getMenus();},//生命周期 - 挂载完成(可以访问DOM元素)mounted() {},beforeCreate() {}, //生命周期 - 创建之前beforeMount() {}, //生命周期 - 挂载之前beforeUpdate() {}, //生命周期 - 更新之前updated() {}, //生命周期 - 更新之后beforeDestroy() {}, //生命周期 - 销毁之前destroyed() {}, //生命周期 - 销毁完成activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
};
</script>
<style scoped>
</style>

7、static\config\index.js里定义了api接口请求地址,改写成全部给网关地址发请求

// api接口请求地址
window.SITE_CONFIG['baseUrl'] = 'http://localhost:88/api';

验证码失效:需要将renren-fast注册进nacos

1.3.2 配置renren-fast

1、让renren-fast依赖gulimall-common模块

坑:各种报错

**问题原因:**可能是因为renren-fast各种依赖包的版本和common里包的版本不同

**解决方法:**单独导入依赖包,例如我的renren-fast的springboot是2.6.6版本

只导入nacos-discover

<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId><version>2021.0.1.0</version><exclusions><exclusion><groupId>com.netflix.ribbon</groupId><artifactId>ribbon</artifactId></exclusion></exclusions>
</dependency>

2、在application.yml添加配置

spring:application:name: renren-fastcloud:nacos:discovery:server-addr: localhost:8848

3、开启服务的注册发现功能

@SpringBootApplication
@EnableDiscoveryClient
public class RenrenApplication {public static void main(String[] args) {SpringApplication.run(RenrenApplication.class, args);}}

4、重启服务

坑:启动服务报错java: 找不到符号 符号: 方法setOperation(java.lang.String)

解决方法:

将renren-fast里的的lombok依赖的版本改为<lombok.version>1.18.14</lombok.version>

5、到nacos查看是否注册成功

6、配置网关规则

spring:cloud:gateway:routes:- id: baidu_routeuri: https://www.baidu.compredicates:- Query=url, baidu # 如果参数url的值为baidu,则页面跳转至https://www.baidu.com- id: qq_routeuri: https://www.qq.compredicates:- Query=url, qq- id: admin_routeuri: lb://renren-fastpredicates:# 设定前端项目发送请求都带上api前缀- Path=/api/**

7、重启renren-fast和gateway服务

8、发现验证码还是不行

原因是http请求发送http://localhost:88/api被网关匹配后经负载均衡转至了http://localhost:8080/api/captcha.jpg,而正常的验证码请求为http://localhost:8080/renren-fast/captcha.jpg

9、路径进行重写

spring:cloud:gateway:routes:- id: baidu_routeuri: https://www.baidu.compredicates:- Query=url, baidu # 如果参数url的值为baidu,则页面跳转至https://www.baidu.com- id: qq_routeuri: https://www.qq.compredicates:- Query=url, qq- id: admin_routeuri: lb://renren-fastpredicates:# 设定前端项目发送请求都带上api前缀- Path=/api/**filters:- RewritePath=/api/(?<segment>.*), /renren-fast/$\{segment}

可以看到验证码成功显示,但是因为跨域原因不允许登陆

1.4 跨域

1.4.1 什么是跨域

跨域:指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对javascript施加的安全限制。

同源策略:是指协议,域名,端口都要相同,其中有一个不同都会产生跨域

1.4.2 跨域流程

1.4.3 解决跨域的方法

1、使用 nginx 部署为同一域

2、配置当次请求允许跨域

  1. 添加响应头

    • Access-Control-Allow-Origin:支持哪些来源的请求跨域
    • Access-Control-Allow-Methods:支持哪些方法跨域
    • Access-Control-Allow-Credentials:跨域请求默认不包含cookie,设置为true可以包含cookie.
    • Access-Control-Expose-Headers:跨域请求暴露的字段
      • CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。
    • Access-Control-Max-Age:表明该响应的有效时间为多少秒。在有效时间内,浏览器无须为同一请求再次发起预检请求。请注意,浏览器自身维护了一个最大有效时间,如果该首部字段的值超过了最大有效时间,将不会生效。

1.4.4 网关配置跨域

1、在gateway服务里新建config.GulimallCorsConfiguration.java

package com.atguigu.gulimall.gateway.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;/*** @Author: xjhqre* @DateTime: 2022/4/21 15:58*/
@Configuration
public class GulimallCorsConfiguration {@Beanpublic CorsWebFilter corsWebFilter(){UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();CorsConfiguration corsConfiguration = new CorsConfiguration();//1、配置跨域corsConfiguration.addAllowedHeader("*");corsConfiguration.addAllowedMethod("*");corsConfiguration.addAllowedOrigin("*");corsConfiguration.setAllowCredentials(true);source.registerCorsConfiguration("/**",corsConfiguration);return new CorsWebFilter(source);}
}

坑:配置完成之后还是报错Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.

**解决方法:**boot2.4以上的把addAllowedOrigin("*");改成addAllowedOriginPattern("*");

2、重启服务查看效果,报错说有多个策略

原因renren-fast里也配置了跨域,将其注释掉即可

@Configuration
public class CorsConfig implements WebMvcConfigurer {//    @Override
//    public void addCorsMappings(CorsRegistry registry) {//        registry.addMapping("/**")
//            .allowedOriginPatterns("*")
//            .allowCredentials(true)
//            .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
//            .maxAge(3600);
//    }
}

3、最后重启服务登陆页面

1.5 树形展示分类数据

1.5.1 添加网关路由规则

精确的路由规则放在上面

spring:cloud:gateway:routes:####################### 产品请求路由 ######################### 当进入分页维护界面时,会发送http://localhost:88/api/product/category/list/tree请求# 该请求经此规则匹配处理后变成http://localhost:88/product/category/list/tree请求# 经过负载均衡后转至http://localhost:10000/product/category/list/tree,返回查询到的数据- id: product_routeuri: lb://gulimall-productpredicates:- Path=/api/product/**filters:- RewritePath=/api/(?<segment>.*), /$\{segment}####################### 登陆页面路由 ########################- id: admin_routeuri: lb://renren-fastpredicates:# 设定前端项目发送请求都带上api前缀- Path=/api/**filters:- RewritePath=/api/(?<segment>.*), /renren-fast/$\{segment}

1.5.2 配置注册nacos

1、新建product服务的命名空间

2、新建bootstrap.yml标明配置中心地址

spring:application:name: gulimall-productcloud:nacos:config:server-addr: localhost:8848namespace: 1a68e0de-7cea-4360-b817-8d4eba43ff76

3、在product里的application.yml配置服务注册中心的地址和服务名称,PS:不知道在哪一步已经配置好了

spring:datasource:username: rootpassword: rooturl: jdbc:mysql://192.168.56.10:3306/gulimall_pmsdriver-class-name: com.mysql.jdbc.Drivercloud:nacos:discovery:server-addr: 127.0.0.1:8848application:name: gulimall-productmybatis-plus:mapper-locations: classpath:/mapper/**/*.xmlglobal-config:db-config:id-type: auto # 自增主键server:port: 10000

4、开启服务发现注解

@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.atguigu.gulimall.product.dao")
public class GulimallProductApplication {public static void main(String[] args) {SpringApplication.run(GulimallProductApplication.class, args);}}

1.5.3 前端页面

1、修改category.vue页面

<!--  -->
<template><el-tree :data="menus" :props="defaultProps" @node-click="handleNodeClick"></el-tree>
</template><script>
//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';export default {//import引入的组件需要注入到对象中才能使用components: {},data() {//这里存放数据return {menus: [],defaultProps: {// children和name都是menus里的数据children: "children",label: "name",},};},//监听属性 类似于data概念computed: {},//监控data中的数据变化watch: {},//方法集合methods: {getMenus() {this.$http({url: this.$http.adornUrl("/product/category/list/tree"),method: "get",}).then(({ data }) => {console.log("成功获取到菜单数据...", data.data);this.menus = data.data;});},},//生命周期 - 创建完成(可以访问当前this实例)created() {// 在实例创建时使用该方法,获取分类数据,保存到menus中this.getMenus();},//生命周期 - 挂载完成(可以访问DOM元素)mounted() {},beforeCreate() {}, //生命周期 - 创建之前beforeMount() {}, //生命周期 - 挂载之前beforeUpdate() {}, //生命周期 - 更新之前updated() {}, //生命周期 - 更新之后beforeDestroy() {}, //生命周期 - 销毁之前destroyed() {}, //生命周期 - 销毁完成activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
};
</script>
<style scoped>
</style>

1.5.4 测试

1、启动服务

  • vue
  • gulimall-gateway
  • gulimall-product
  • renren-fast
  • nacos

访问http://localhost:8001/

登陆后进入分类维护页面即可看到效果

1.6 删除分类实现

1.6.1 前端页面

修改category.vue

实现最末尾的分类可以删除,其他有子节点的节点不能被删除

末尾节点不能添加,其他节点可以添加

添加选项框

<!--  -->
<template><!-- expand-on-click-node:是否在点击节点的时候展开或者收缩节点, 默认值为 true,如果为 false,则只有点箭头图标的时候才会展开或者收缩节点。 --><!-- show-checkbox 节点是否可被选择 --><!-- node-key 每个树节点用来作为唯一标识的属性,整棵树应该是唯一的 --><el-tree:data="menus":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-buttonv-if="node.level <= 2"type="text"size="mini"@click="() => append(data)">Append</el-button><el-buttonv-if="node.childNodes.length == 0"type="text"size="mini"@click="() => remove(node, data)">Delete</el-button></span></span></el-tree>
</template><script>
//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';export default {//import引入的组件需要注入到对象中才能使用components: {},data() {//这里存放数据return {menus: [],defaultProps: {// children和name都是menus里的数据children: "children",label: "name",},};},//监听属性 类似于data概念computed: {},//监控data中的数据变化watch: {},//方法集合methods: {getMenus() {this.$http({url: this.$http.adornUrl("/product/category/list/tree"),method: "get",}).then(({ data }) => {console.log("成功获取到菜单数据...", data.data);this.menus = data.data;});},append(data) {console.log("append", data);},remove(node, data) {console.log("append", node, data);},},//生命周期 - 创建完成(可以访问当前this实例)created() {// 在实例创建时使用该方法,获取分类数据,保存到menus中this.getMenus();},//生命周期 - 挂载完成(可以访问DOM元素)mounted() {},beforeCreate() {}, //生命周期 - 创建之前beforeMount() {}, //生命周期 - 挂载之前beforeUpdate() {}, //生命周期 - 更新之前updated() {}, //生命周期 - 更新之后beforeDestroy() {}, //生命周期 - 销毁之前destroyed() {}, //生命周期 - 销毁完成activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
};
</script>
<style scoped>
</style>

1.6.2 测试删除逻辑

使用postman测试删除逻辑

1.6.3 实现逻辑删除

1、修改CategoryController里原来的删除逻辑

/*** 删除* @RequestBody:获取请求体,只有POST请求才有请求体,必须发送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();
}

2、创建removeMenuByIds接口并实现

CategoryServiceImpl:

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

3、配置全局的逻辑删除规则并调整日志打印级别,gulimall-product\src\main\resources\application.yml

spring:datasource:username: rootpassword: rooturl: jdbc:mysql://192.168.56.10:3306/gulimall_pmsdriver-class-name: com.mysql.jdbc.Drivercloud:nacos:discovery:server-addr: 127.0.0.1:8848application:name: gulimall-productmybatis-plus:mapper-locations: classpath:/mapper/**/*.xmlglobal-config:db-config:id-type: auto # 自增主键logic-delete-value: 0 # 删除逻辑为0logic-not-delete-value: 1 # 不删除逻辑为1server:port: 10000

4、实体类字段上加上@TableLogic注解,逻辑不删除用value,逻辑删除用delval,gulimall-product\src\main\java\com\atguigu\gulimall\product\entity\CategoryEntity.java

@TableLogic
private Integer showStatus;

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

6、重启服务,用postman测试逻辑删除,查看打印的日志

1.6.4 删除效果细化

1、前端发送删除请求

2、删除确认

3、消息提示

4、依旧展开父节点

修改后的category.vue代码

<!--  -->
<template><!-- expand-on-click-node:是否在点击节点的时候展开或者收缩节点, 默认值为 true,如果为 false,则只有点箭头图标的时候才会展开或者收缩节点。 --><!-- show-checkbox 节点是否可被选择 --><!-- node-key 每个树节点用来作为唯一标识的属性,整棵树应该是唯一的 --><el-tree:data="menus":props="defaultProps":expand-on-click-node="false"show-checkbox="true"node-key="catId":default-expanded-keys="expandedKey"><span class="custom-tree-node" slot-scope="{ node, data }"><span>{{ node.label }}</span><span><el-buttonv-if="node.level <= 2"type="text"size="mini"@click="() => append(data)">Append</el-button><el-buttonv-if="node.childNodes.length == 0"type="text"size="mini"@click="() => remove(node, data)">Delete</el-button></span></span></el-tree>
</template><script>
//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';export default {//import引入的组件需要注入到对象中才能使用components: {},props: {},data() {//这里存放数据return {menus: [],expandedKey: [],defaultProps: {// children和name都是menus里的数据children: "children",label: "name",},};},//监听属性 类似于data概念computed: {},//监控data中的数据变化watch: {},//方法集合methods: {getMenus() {this.$http({url: this.$http.adornUrl("/product/category/list/tree"),method: "get",}).then(({ data }) => {console.log("成功获取到菜单数据...", data.data);this.menus = data.data;});},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.expandedKey = [node.parent.data.catId]});}).catch(() => {});console.log("append", node, data);},},//生命周期 - 创建完成(可以访问当前this实例)created() {// 在实例创建时使用该方法,获取分类数据,保存到menus中this.getMenus();},//生命周期 - 挂载完成(可以访问DOM元素)mounted() {},beforeCreate() {}, //生命周期 - 创建之前beforeMount() {}, //生命周期 - 挂载之前beforeUpdate() {}, //生命周期 - 更新之前updated() {}, //生命周期 - 更新之后beforeDestroy() {}, //生命周期 - 销毁之前destroyed() {}, //生命周期 - 销毁完成activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
};
</script>
<style scoped>
</style>

设置数据库所有数据的show_status为1

UPDATE pms_category SET show_status=1

1.7 新增分类实现

1、点击新增弹出对话框

2、填写表单数据

3、发送post请求保存分类

4、关闭对话框

5、刷新菜单并展开

修改后的category.vue

<!--  -->
<template><div><!-- expand-on-click-node:是否在点击节点的时候展开或者收缩节点, 默认值为 true,如果为 false,则只有点箭头图标的时候才会展开或者收缩节点。 --><!-- show-checkbox  节点是否可被选择 --><!-- node-key 每个树节点用来作为唯一标识的属性,整棵树应该是唯一的 --><el-tree:data="menus":props="defaultProps":expand-on-click-node="false"show-checkbox="true"node-key="catId":default-expanded-keys="expandedKey"><span class="custom-tree-node" slot-scope="{ node, data }"><span>{{ node.label }}</span><span><el-buttonv-if="node.level <= 2"type="text"size="mini"@click="() => append(data)">Append</el-button><el-buttonv-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-form :model="category"><el-form-item label="分类名"><el-input v-model="category.name" autocomplete="off"></el-input></el-form-item></el-form><span slot="footer" class="dialog-footer"><el-button @click="dialogVisible = false">取 消</el-button><el-button type="primary" @click="addCategory">确 定</el-button></span></el-dialog></div>
</template><script>
//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';export default {//import引入的组件需要注入到对象中才能使用components: {},props: {},data() {//这里存放数据return {category: { name: "", parentCid: 0, catLevel: 0, showStatus: 1, sort: 0 },dialogVisible: false,menus: [],expandedKey: [],defaultProps: {// children和name都是menus里的数据children: "children",label: "name",},};},//监听属性 类似于data概念computed: {},//监控data中的数据变化watch: {},//方法集合methods: {getMenus() {this.$http({url: this.$http.adornUrl("/product/category/list/tree"),method: "get",}).then(({ data }) => {console.log("成功获取到菜单数据...", data.data);this.menus = data.data;});},append(data) {console.log("append", data);this.dialogVisible = true;this.category.parentCid = data.catId;this.category.catLevel = data.catLevel * 1 + 1;},// 添加三级分类addCategory() {console.log("提交三级分类的数据:", this.category);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.expandedKey = [this.category.parentCid];});},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.expandedKey = [node.parent.data.catId];});}).catch(() => {});console.log("append", node, data);},},//生命周期 - 创建完成(可以访问当前this实例)created() {// 在实例创建时使用该方法,获取分类数据,保存到menus中this.getMenus();},//生命周期 - 挂载完成(可以访问DOM元素)mounted() {},beforeCreate() {}, //生命周期 - 创建之前beforeMount() {}, //生命周期 - 挂载之前beforeUpdate() {}, //生命周期 - 更新之前updated() {}, //生命周期 - 更新之后beforeDestroy() {}, //生命周期 - 销毁之前destroyed() {}, //生命周期 - 销毁完成activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
};
</script>
<style scoped>
</style>

1.8 修改效果实现

1、添加修改按钮

2、区分对话框功能

3、不同提示信息

4、新增表单填写选项

5、回显图标和计量单位

6、回显最新的数据

7、修改后端的info方法,改完后记得重启

@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);
}

修改后的category.vue:

<!--  -->
<template><div><!-- expand-on-click-node:是否在点击节点的时候展开或者收缩节点, 默认值为 true,如果为 false,则只有点箭头图标的时候才会展开或者收缩节点。 --><!-- show-checkbox  节点是否可被选择 --><!-- node-key 每个树节点用来作为唯一标识的属性,整棵树应该是唯一的 --><el-tree:data="menus":props="defaultProps":expand-on-click-node="false"show-checkbox="true"node-key="catId":default-expanded-keys="expandedKey"><span class="custom-tree-node" slot-scope="{ node, data }"><span>{{ node.label }}</span><span><el-buttonv-if="node.level <= 2"type="text"size="mini"@click="() => append(data)">Append</el-button><el-button type="text" size="mini" @click="() => edit(data)">Edit</el-button><el-buttonv-if="node.childNodes.length == 0"type="text"size="mini"@click="() => remove(node, data)">Delete</el-button></span></span></el-tree><el-dialog :title="dialogTitle" :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-item label="图标"><el-input v-model="category.icon" autocomplete="off"></el-input></el-form-item><el-form-item label="计量单位"><el-inputv-model="category.productUnit"autocomplete="off"></el-input></el-form-item></el-form><span slot="footer" class="dialog-footer"><el-button @click="dialogVisible = false">取 消</el-button><el-button type="primary" @click="submitData">确 定</el-button></span></el-dialog></div>
</template><script>
//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';export default {//import引入的组件需要注入到对象中才能使用components: {},props: {},data() {//这里存放数据return {dialogTitle: "",dialogType: "",category: {name: "",parentCid: 0,catLevel: 0,showStatus: 1,sort: 0,productUnit: "",icon: "",catId: null,},dialogVisible: false,menus: [],expandedKey: [],defaultProps: {// children和name都是menus里的数据children: "children",label: "name",},};},//监听属性 类似于data概念computed: {},//监控data中的数据变化watch: {},//方法集合methods: {getMenus() {this.$http({url: this.$http.adornUrl("/product/category/list/tree"),method: "get",}).then(({ data }) => {console.log("成功获取到菜单数据...", data.data);this.menus = data.data;});},append(data) {console.log("append", data);this.dialogType = "add";this.dialogTitle = "添加分类";this.dialogVisible = true;this.category.parentCid = data.catId;this.category.catLevel = data.catLevel * 1 + 1;// 因为修改后数据变化,需要改回默认数据this.category.catId = null;this.category.name = "";this.category.icon = "";this.category.productUnit = "";this.category.sort = 0;this.category.showStatus = 1;},// 添加三级分类addCategory() {console.log("提交三级分类的数据:", this.category);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.expandedKey = [this.category.parentCid];});},// 修改方法edit(data) {console.log("要修改的数据:", data);this.dialogType = "edit";this.dialogTitle = "修改分类";this.dialogVisible = true;// 发送请求查询最新的数据this.$http({url: this.$http.adornUrl(`/product/category/info/${data.catId}`),method: "get",params: this.$http.adornParams({}),}).then(({ data }) => {// 请求成功后的处理console.log("要回显的数据:", data);this.category.name = data.data.name;this.category.catId = data.data.catId;this.category.icon = data.data.icon;this.category.productUnit = data.data.productUnit;this.category.parentCid = data.data.parentCid;this.category.catLevel = data.data.catLevel;this.category.sort = data.data.sort;this.category.showStatus = data.data.showStatus;});},// 修改三级分类数据editCategory() {var { catId, name, icon, productUnit } = this.category;var categoryData = {catId: catId,name: name,icon: icon,productUnit: productUnit,};this.$http({url: this.$http.adornUrl("/product/category/update"),method: "post",data: this.$http.adornData(categoryData, false),}).then(({ data }) => {this.$message({message: "菜单修改成功",type: "success",});// 关闭对话框this.dialogVisible = false;// 刷新菜单this.getMenus();// 设置需要默认展开的菜单this.expandedKey = [this.category.parentCid];});},// 判断是修改请求还是添加请求submitData() {if (this.dialogType == "add") {this.addCategory();} else if ((this.dialogType = "edit")) {this.editCategory();}},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.expandedKey = [node.parent.data.catId];});}).catch(() => {});console.log("append", node, data);},},//生命周期 - 创建完成(可以访问当前this实例)created() {// 在实例创建时使用该方法,获取分类数据,保存到menus中this.getMenus();},//生命周期 - 挂载完成(可以访问DOM元素)mounted() {},beforeCreate() {}, //生命周期 - 创建之前beforeMount() {}, //生命周期 - 挂载之前beforeUpdate() {}, //生命周期 - 更新之前updated() {}, //生命周期 - 更新之后beforeDestroy() {}, //生命周期 - 销毁之前destroyed() {}, //生命周期 - 销毁完成activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
};
</script>
<style scoped>
</style>

1.9 拖拽效果实现

1.9.1 前端效果实现

1、在树形结构中允许拖拽

2、编写拖拽函数

修改后的category.vue:

<!--  -->
<template><div><!-- expand-on-click-node:是否在点击节点的时候展开或者收缩节点, 默认值为 true,如果为 false,则只有点箭头图标的时候才会展开或者收缩节点。 --><!-- show-checkbox  节点是否可被选择 --><!-- node-key 每个树节点用来作为唯一标识的属性,整棵树应该是唯一的 --><el-tree:data="menus":props="defaultProps":expand-on-click-node="false":show-checkbox="true"node-key="catId":default-expanded-keys="expandedKey"draggable:allow-drop="allowDrop"><span class="custom-tree-node" slot-scope="{ node, data }"><span>{{ node.label }}</span><span><el-buttonv-if="node.level <= 2"type="text"size="mini"@click="() => append(data)">Append</el-button><el-button type="text" size="mini" @click="() => edit(data)">Edit</el-button><el-buttonv-if="node.childNodes.length == 0"type="text"size="mini"@click="() => remove(node, data)">Delete</el-button></span></span></el-tree><el-dialog :title="dialogTitle" :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-item label="图标"><el-input v-model="category.icon" autocomplete="off"></el-input></el-form-item><el-form-item label="计量单位"><el-inputv-model="category.productUnit"autocomplete="off"></el-input></el-form-item></el-form><span slot="footer" class="dialog-footer"><el-button @click="dialogVisible = false">取 消</el-button><el-button type="primary" @click="submitData">确 定</el-button></span></el-dialog></div>
</template><script>
//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';export default {//import引入的组件需要注入到对象中才能使用components: {},props: {},data() {//这里存放数据return {maxLevel: 0,dialogTitle: "",dialogType: "",category: {name: "",parentCid: 0,catLevel: 0,showStatus: 1,sort: 0,productUnit: "",icon: "",catId: null,},dialogVisible: false,menus: [],expandedKey: [],defaultProps: {// children和name都是menus里的数据children: "children",label: "name",},};},//监听属性 类似于data概念computed: {},//监控data中的数据变化watch: {},//方法集合methods: {getMenus() {this.$http({url: this.$http.adornUrl("/product/category/list/tree"),method: "get",}).then(({ data }) => {console.log("成功获取到菜单数据...", data.data);this.menus = data.data;});},append(data) {console.log("append", data);this.dialogType = "add";this.dialogTitle = "添加分类";this.dialogVisible = true;this.category.parentCid = data.catId;this.category.catLevel = data.catLevel * 1 + 1;// 因为修改后数据变化,需要改回默认数据this.category.catId = null;this.category.name = "";this.category.icon = "";this.category.productUnit = "";this.category.sort = 0;this.category.showStatus = 1;},// 添加三级分类addCategory() {console.log("提交三级分类的数据:", this.category);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.expandedKey = [this.category.parentCid];});},// 修改方法edit(data) {console.log("要修改的数据:", data);this.dialogType = "edit";this.dialogTitle = "修改分类";this.dialogVisible = true;// 发送请求查询最新的数据this.$http({url: this.$http.adornUrl(`/product/category/info/${data.catId}`),method: "get",params: this.$http.adornParams({}),}).then(({ data }) => {// 请求成功后的处理console.log("要回显的数据:", data);this.category.name = data.data.name;this.category.catId = data.data.catId;this.category.icon = data.data.icon;this.category.productUnit = data.data.productUnit;this.category.parentCid = data.data.parentCid;this.category.catLevel = data.data.catLevel;this.category.sort = data.data.sort;this.category.showStatus = data.data.showStatus;});},// 修改三级分类数据editCategory() {var { catId, name, icon, productUnit } = this.category;var categoryData = {catId: catId,name: name,icon: icon,productUnit: productUnit,};this.$http({url: this.$http.adornUrl("/product/category/update"),method: "post",data: this.$http.adornData(categoryData, false),}).then(({ data }) => {this.$message({message: "菜单修改成功",type: "success",});// 关闭对话框this.dialogVisible = false;// 刷新菜单this.getMenus();// 设置需要默认展开的菜单this.expandedKey = [this.category.parentCid];});},// 判断是修改请求还是添加请求submitData() {if (this.dialogType == "add") {this.addCategory();} else if ((this.dialogType = "edit")) {this.editCategory();}},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.expandedKey = [node.parent.data.catId];});}).catch(() => {});console.log("append", node, data);},// 允许拖拽函数allowDrop(draggingNode, dropNode, type) {//1、被拖动的当前节点以及所在的父节点总层数不能大于3//1)、被拖动的当前节点总层数console.log("allowDrop:", draggingNode, dropNode, type);//this.countNodeLevel(draggingNode.data);//当前正在拖动的节点+父节点所在的深度不大于3即可let deep = this.maxLevel - draggingNode.data.catLevel + 1;console.log("深度:", deep);//   this.maxLevelif (type == "inner") {return deep + dropNode.level <= 3;} else {return deep + dropNode.parent.level <= 3;}},//找到所有子节点,求出最大深度countNodeLevel(node) {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]);}}},},//生命周期 - 创建完成(可以访问当前this实例)created() {// 在实例创建时使用该方法,获取分类数据,保存到menus中this.getMenus();},//生命周期 - 挂载完成(可以访问DOM元素)mounted() {},beforeCreate() {}, //生命周期 - 创建之前beforeMount() {}, //生命周期 - 挂载之前beforeUpdate() {}, //生命周期 - 更新之前updated() {}, //生命周期 - 更新之后beforeDestroy() {}, //生命周期 - 销毁之前destroyed() {}, //生命周期 - 销毁完成activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
};
</script>
<style scoped>
</style>

1.9.2 前端数据更新

1、影响的数据有parent_cid、cat_level、sort

2、添加拖拽结束函数

修改后的category.vue

<!--  -->
<template><div><!-- expand-on-click-node:是否在点击节点的时候展开或者收缩节点, 默认值为 true,如果为 false,则只有点箭头图标的时候才会展开或者收缩节点。 --><!-- show-checkbox  节点是否可被选择 --><!-- node-key 每个树节点用来作为唯一标识的属性,整棵树应该是唯一的 --><el-tree:data="menus":props="defaultProps":expand-on-click-node="false":show-checkbox="true"node-key="catId":default-expanded-keys="expandedKey"draggable:allow-drop="allowDrop"@node-drop="handleDrop"><span class="custom-tree-node" slot-scope="{ node, data }"><span>{{ node.label }}</span><span><el-buttonv-if="node.level <= 2"type="text"size="mini"@click="() => append(data)">Append</el-button><el-button type="text" size="mini" @click="() => edit(data)">Edit</el-button><el-buttonv-if="node.childNodes.length == 0"type="text"size="mini"@click="() => remove(node, data)">Delete</el-button></span></span></el-tree><el-dialog :title="dialogTitle" :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-item label="图标"><el-input v-model="category.icon" autocomplete="off"></el-input></el-form-item><el-form-item label="计量单位"><el-inputv-model="category.productUnit"autocomplete="off"></el-input></el-form-item></el-form><span slot="footer" class="dialog-footer"><el-button @click="dialogVisible = false">取 消</el-button><el-button type="primary" @click="submitData">确 定</el-button></span></el-dialog></div>
</template><script>
//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';export default {//import引入的组件需要注入到对象中才能使用components: {},props: {},data() {//这里存放数据return {updateNodes: [],maxLevel: 0,dialogTitle: "",dialogType: "",category: {name: "",parentCid: 0,catLevel: 0,showStatus: 1,sort: 0,productUnit: "",icon: "",catId: null,},dialogVisible: false,menus: [],expandedKey: [],defaultProps: {// children和name都是menus里的数据children: "children",label: "name",},};},//监听属性 类似于data概念computed: {},//监控data中的数据变化watch: {},//方法集合methods: {getMenus() {this.$http({url: this.$http.adornUrl("/product/category/list/tree"),method: "get",}).then(({ data }) => {console.log("成功获取到菜单数据...", data.data);this.menus = data.data;});},append(data) {console.log("append", data);this.dialogType = "add";this.dialogTitle = "添加分类";this.dialogVisible = true;this.category.parentCid = data.catId;this.category.catLevel = data.catLevel * 1 + 1;// 因为修改后数据变化,需要改回默认数据this.category.catId = null;this.category.name = "";this.category.icon = "";this.category.productUnit = "";this.category.sort = 0;this.category.showStatus = 1;},// 添加三级分类addCategory() {console.log("提交三级分类的数据:", this.category);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.expandedKey = [this.category.parentCid];});},// 修改方法edit(data) {console.log("要修改的数据:", data);this.dialogType = "edit";this.dialogTitle = "修改分类";this.dialogVisible = true;// 发送请求查询最新的数据this.$http({url: this.$http.adornUrl(`/product/category/info/${data.catId}`),method: "get",params: this.$http.adornParams({}),}).then(({ data }) => {// 请求成功后的处理console.log("要回显的数据:", data);this.category.name = data.data.name;this.category.catId = data.data.catId;this.category.icon = data.data.icon;this.category.productUnit = data.data.productUnit;this.category.parentCid = data.data.parentCid;this.category.catLevel = data.data.catLevel;this.category.sort = data.data.sort;this.category.showStatus = data.data.showStatus;});},// 修改三级分类数据editCategory() {var { catId, name, icon, productUnit } = this.category;var categoryData = {catId: catId,name: name,icon: icon,productUnit: productUnit,};this.$http({url: this.$http.adornUrl("/product/category/update"),method: "post",data: this.$http.adornData(categoryData, false),}).then(({ data }) => {this.$message({message: "菜单修改成功",type: "success",});// 关闭对话框this.dialogVisible = false;// 刷新菜单this.getMenus();// 设置需要默认展开的菜单this.expandedKey = [this.category.parentCid];});},// 判断是修改请求还是添加请求submitData() {if (this.dialogType == "add") {this.addCategory();} else if ((this.dialogType = "edit")) {this.editCategory();}},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.expandedKey = [node.parent.data.catId];});}).catch(() => {});console.log("append", node, data);},// 允许拖拽函数allowDrop(draggingNode, dropNode, type) {//1、被拖动的当前节点以及所在的父节点总层数不能大于3//1)、被拖动的当前节点总层数console.log("allowDrop:", draggingNode, dropNode, type);//this.countNodeLevel(draggingNode.data);//当前正在拖动的节点+父节点所在的深度不大于3即可let deep = this.maxLevel - draggingNode.data.catLevel + 1;console.log("深度:", deep);//   this.maxLevelif (type == "inner") {return deep + dropNode.level <= 3;} else {return deep + dropNode.parent.level <= 3;}},//找到所有子节点,求出最大深度countNodeLevel(node) {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]);}}},// 拖拽事件结束后的回调函数handleDrop(draggingNode, dropNode, dropType, ev) {console.log("handleDrop: ", draggingNode, dropNode, dropType);//1、当前节点最新的父节点idlet pCid = 0;let siblings = null; // 同层级的节点if (dropType == "before" || dropType == "after") {pCid =dropNode.parent.data.catId == undefined? 0: dropNode.parent.data.catId;// 同层级的节点siblings = dropNode.parent.childNodes;} else {pCid = dropNode.data.catId;// 同层级的节点siblings = dropNode.childNodes;}//2、当前拖拽节点的最新顺序,for (let i = 0; i < siblings.length; i++) {if (siblings[i].data.catId == draggingNode.data.catId) {//如果遍历的是当前正在拖拽的节点let catLevel = draggingNode.level;if (siblings[i].level != draggingNode.level) {//当前节点的层级发生变化catLevel = siblings[i].level;//修改他子节点的层级this.updateChildNodeLevel(siblings[i]);}this.updateNodes.push({catId: siblings[i].data.catId,sort: i,parentCid: pCid,catLevel: catLevel,});} else {this.updateNodes.push({ catId: siblings[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,});this.updateChildNodeLevel(node.childNodes[i]);}}},},//生命周期 - 创建完成(可以访问当前this实例)created() {// 在实例创建时使用该方法,获取分类数据,保存到menus中this.getMenus();},//生命周期 - 挂载完成(可以访问DOM元素)mounted() {},beforeCreate() {}, //生命周期 - 创建之前beforeMount() {}, //生命周期 - 挂载之前beforeUpdate() {}, //生命周期 - 更新之前updated() {}, //生命周期 - 更新之后beforeDestroy() {}, //生命周期 - 销毁之前destroyed() {}, //生命周期 - 销毁完成activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
};
</script>
<style scoped>
</style>

1.9.3 后端数据更新

1、后端编写批量修改方法,改完后记得重启

/*** 拖拽后批量修改*/
@RequestMapping("/update/sort")
// @RequiresPermissions("product:category:update")
public R updateSort(@RequestBody CategoryEntity[] category) {categoryService.updateBatchById(Arrays.asList(category));return R.ok();
}

2、用postman测试方法

3、前端发送请求

4、拖拽完成后初始化updateNodes和maxLevel

修改后的category.vue:

<!--  -->
<template><div><!-- expand-on-click-node:是否在点击节点的时候展开或者收缩节点, 默认值为 true,如果为 false,则只有点箭头图标的时候才会展开或者收缩节点。 --><!-- show-checkbox  节点是否可被选择 --><!-- node-key 每个树节点用来作为唯一标识的属性,整棵树应该是唯一的 --><el-tree:data="menus":props="defaultProps":expand-on-click-node="false":show-checkbox="true"node-key="catId":default-expanded-keys="expandedKey"draggable:allow-drop="allowDrop"@node-drop="handleDrop"><span class="custom-tree-node" slot-scope="{ node, data }"><span>{{ node.label }}</span><span><el-buttonv-if="node.level <= 2"type="text"size="mini"@click="() => append(data)">Append</el-button><el-button type="text" size="mini" @click="() => edit(data)">Edit</el-button><el-buttonv-if="node.childNodes.length == 0"type="text"size="mini"@click="() => remove(node, data)">Delete</el-button></span></span></el-tree><el-dialog :title="dialogTitle" :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-item label="图标"><el-input v-model="category.icon" autocomplete="off"></el-input></el-form-item><el-form-item label="计量单位"><el-inputv-model="category.productUnit"autocomplete="off"></el-input></el-form-item></el-form><span slot="footer" class="dialog-footer"><el-button @click="dialogVisible = false">取 消</el-button><el-button type="primary" @click="submitData">确 定</el-button></span></el-dialog></div>
</template><script>
//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';export default {//import引入的组件需要注入到对象中才能使用components: {},props: {},data() {//这里存放数据return {updateNodes: [],maxLevel: 0,dialogTitle: "",dialogType: "",category: {name: "",parentCid: 0,catLevel: 0,showStatus: 1,sort: 0,productUnit: "",icon: "",catId: null,},dialogVisible: false,menus: [],expandedKey: [],defaultProps: {// children和name都是menus里的数据children: "children",label: "name",},};},//监听属性 类似于data概念computed: {},//监控data中的数据变化watch: {},//方法集合methods: {getMenus() {this.$http({url: this.$http.adornUrl("/product/category/list/tree"),method: "get",}).then(({ data }) => {console.log("成功获取到菜单数据...", data.data);this.menus = data.data;});},append(data) {console.log("append", data);this.dialogType = "add";this.dialogTitle = "添加分类";this.dialogVisible = true;this.category.parentCid = data.catId;this.category.catLevel = data.catLevel * 1 + 1;// 因为修改后数据变化,需要改回默认数据this.category.catId = null;this.category.name = "";this.category.icon = "";this.category.productUnit = "";this.category.sort = 0;this.category.showStatus = 1;},// 添加三级分类addCategory() {console.log("提交三级分类的数据:", this.category);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.expandedKey = [this.category.parentCid];});},// 修改方法edit(data) {console.log("要修改的数据:", data);this.dialogType = "edit";this.dialogTitle = "修改分类";this.dialogVisible = true;// 发送请求查询最新的数据this.$http({url: this.$http.adornUrl(`/product/category/info/${data.catId}`),method: "get",params: this.$http.adornParams({}),}).then(({ data }) => {// 请求成功后的处理console.log("要回显的数据:", data);this.category.name = data.data.name;this.category.catId = data.data.catId;this.category.icon = data.data.icon;this.category.productUnit = data.data.productUnit;this.category.parentCid = data.data.parentCid;this.category.catLevel = data.data.catLevel;this.category.sort = data.data.sort;this.category.showStatus = data.data.showStatus;});},// 修改三级分类数据editCategory() {var { catId, name, icon, productUnit } = this.category;var categoryData = {catId: catId,name: name,icon: icon,productUnit: productUnit,};this.$http({url: this.$http.adornUrl("/product/category/update"),method: "post",data: this.$http.adornData(categoryData, false),}).then(({ data }) => {this.$message({message: "菜单修改成功",type: "success",});// 关闭对话框this.dialogVisible = false;// 刷新菜单this.getMenus();// 设置需要默认展开的菜单this.expandedKey = [this.category.parentCid];});},// 判断是修改请求还是添加请求submitData() {if (this.dialogType == "add") {this.addCategory();} else if ((this.dialogType = "edit")) {this.editCategory();}},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.expandedKey = [node.parent.data.catId];});}).catch(() => {});console.log("append", node, data);},// 允许拖拽函数allowDrop(draggingNode, dropNode, type) {//1、被拖动的当前节点以及所在的父节点总层数不能大于3//1)、被拖动的当前节点总层数console.log("allowDrop:", draggingNode, dropNode, type);//this.countNodeLevel(draggingNode.data);//当前正在拖动的节点+父节点所在的深度不大于3即可let deep = this.maxLevel - draggingNode.data.catLevel + 1;console.log("深度:", deep);//   this.maxLevelif (type == "inner") {return deep + dropNode.level <= 3;} else {return deep + dropNode.parent.level <= 3;}},//找到所有子节点,求出最大深度countNodeLevel(node) {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]);}}},// 拖拽事件结束后的回调函数handleDrop(draggingNode, dropNode, dropType, ev) {console.log("handleDrop: ", draggingNode, dropNode, dropType);//1、当前节点最新的父节点idlet pCid = 0;let siblings = null; // 同层级的节点if (dropType == "before" || dropType == "after") {pCid =dropNode.parent.data.catId == undefined? 0: dropNode.parent.data.catId;// 同层级的节点siblings = dropNode.parent.childNodes;} else {pCid = dropNode.data.catId;// 同层级的节点siblings = dropNode.childNodes;}//2、当前拖拽节点的最新顺序,for (let i = 0; i < siblings.length; i++) {if (siblings[i].data.catId == draggingNode.data.catId) {//如果遍历的是当前正在拖拽的节点let catLevel = draggingNode.level;if (siblings[i].level != draggingNode.level) {//当前节点的层级发生变化catLevel = siblings[i].level;//修改他子节点的层级this.updateChildNodeLevel(siblings[i]);}this.updateNodes.push({catId: siblings[i].data.catId,sort: i,parentCid: pCid,catLevel: catLevel,});} else {this.updateNodes.push({ catId: siblings[i].data.catId, sort: i });}}//3、当前拖拽节点的最新层级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.expandedKey = [pCid];this.updateNodes = [];this.maxLevel = 0;});},// 递归更新子节点的层级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]);}}},},//生命周期 - 创建完成(可以访问当前this实例)created() {// 在实例创建时使用该方法,获取分类数据,保存到menus中this.getMenus();},//生命周期 - 挂载完成(可以访问DOM元素)mounted() {},beforeCreate() {}, //生命周期 - 创建之前beforeMount() {}, //生命周期 - 挂载之前beforeUpdate() {}, //生命周期 - 更新之前updated() {}, //生命周期 - 更新之后beforeDestroy() {}, //生命周期 - 销毁之前destroyed() {}, //生命周期 - 销毁完成activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
};
</script>
<style scoped>
</style>

1.9.4 批量保存拖拽效果

1、添加可选拖拽功能

2、统一提交拖拽结果

3、这里我做了一下修改,在点击保存拖拽结果后不展开最后的拖拽的父节点,就不设置pCid了

4、修改计算深度的代码逻辑

修改后的category.vue:

<!--  -->
<template><div><el-switchv-model="draggable"active-text="开启拖拽"inactive-text="关闭拖拽"></el-switch><el-button v-if="draggable" @click="batchSave">批量保存</el-button><!-- expand-on-click-node:是否在点击节点的时候展开或者收缩节点, 默认值为 true,如果为 false,则只有点箭头图标的时候才会展开或者收缩节点。 --><!-- show-checkbox   节点是否可被选择 --><!-- node-key 每个树节点用来作为唯一标识的属性,整棵树应该是唯一的 --><el-tree:data="menus":props="defaultProps":expand-on-click-node="false":show-checkbox="true"node-key="catId":default-expanded-keys="expandedKey":draggable="draggable":allow-drop="allowDrop"@node-drop="handleDrop"><span class="custom-tree-node" slot-scope="{ node, data }"><span>{{ node.label }}</span><span><el-buttonv-if="node.level <= 2"type="text"size="mini"@click="() => append(data)">Append</el-button><el-button type="text" size="mini" @click="() => edit(data)">Edit</el-button><el-buttonv-if="node.childNodes.length == 0"type="text"size="mini"@click="() => remove(node, data)">Delete</el-button></span></span></el-tree><el-dialog :title="dialogTitle" :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-item label="图标"><el-input v-model="category.icon" autocomplete="off"></el-input></el-form-item><el-form-item label="计量单位"><el-inputv-model="category.productUnit"autocomplete="off"></el-input></el-form-item></el-form><span slot="footer" class="dialog-footer"><el-button @click="dialogVisible = false">取 消</el-button><el-button type="primary" @click="submitData">确 定</el-button></span></el-dialog></div>
</template><script>
//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';export default {//import引入的组件需要注入到对象中才能使用components: {},props: {},data() {//这里存放数据return {draggable: false,updateNodes: [],dialogTitle: "",dialogType: "",category: {name: "",parentCid: 0,catLevel: 0,showStatus: 1,sort: 0,productUnit: "",icon: "",catId: null,},dialogVisible: false,menus: [],expandedKey: [],defaultProps: {// children和name都是menus里的数据children: "children",label: "name",},};},//监听属性 类似于data概念computed: {},//监控data中的数据变化watch: {},//方法集合methods: {getMenus() {this.$http({url: this.$http.adornUrl("/product/category/list/tree"),method: "get",}).then(({ data }) => {console.log("成功获取到菜单数据...", data.data);this.menus = data.data;});},append(data) {console.log("append", data);this.dialogType = "add";this.dialogTitle = "添加分类";this.dialogVisible = true;this.category.parentCid = data.catId;this.category.catLevel = data.catLevel * 1 + 1;// 因为修改后数据变化,需要改回默认数据this.category.catId = null;this.category.name = "";this.category.icon = "";this.category.productUnit = "";this.category.sort = 0;this.category.showStatus = 1;},// 添加三级分类addCategory() {console.log("提交三级分类的数据:", this.category);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.expandedKey = [this.category.parentCid];});},// 修改方法edit(data) {console.log("要修改的数据:", data);this.dialogType = "edit";this.dialogTitle = "修改分类";this.dialogVisible = true;// 发送请求查询最新的数据this.$http({url: this.$http.adornUrl(`/product/category/info/${data.catId}`),method: "get",params: this.$http.adornParams({}),}).then(({ data }) => {// 请求成功后的处理console.log("要回显的数据:", data);this.category.name = data.data.name;this.category.catId = data.data.catId;this.category.icon = data.data.icon;this.category.productUnit = data.data.productUnit;this.category.parentCid = data.data.parentCid;this.category.catLevel = data.data.catLevel;this.category.sort = data.data.sort;this.category.showStatus = data.data.showStatus;});},// 修改三级分类数据editCategory() {var { catId, name, icon, productUnit } = this.category;var categoryData = {catId: catId,name: name,icon: icon,productUnit: productUnit,};this.$http({url: this.$http.adornUrl("/product/category/update"),method: "post",data: this.$http.adornData(categoryData, false),}).then(({ data }) => {this.$message({message: "菜单修改成功",type: "success",});// 关闭对话框this.dialogVisible = false;// 刷新菜单this.getMenus();// 设置需要默认展开的菜单this.expandedKey = [this.category.parentCid];});},// 判断是修改请求还是添加请求submitData() {if (this.dialogType == "add") {this.addCategory();} else if ((this.dialogType = "edit")) {this.editCategory();}},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.expandedKey = [node.parent.data.catId];});}).catch(() => {});console.log("append", node, data);},// 允许拖拽函数allowDrop(draggingNode, dropNode, type) {//1、被拖动的当前节点以及所在的父节点总层数不能大于3//1)、被拖动的当前节点总层数console.log("allowDrop:", draggingNode, dropNode, type);this.maxLevel = draggingNode.level;this.countNodeLevel(draggingNode);//当前正在拖动的节点+父节点所在的深度不大于3即可let deep = Math.abs(this.maxLevel - draggingNode.level) + 1;console.log("深度:", deep);if (type == "inner") {return deep + dropNode.level <= 3;} else {return deep + dropNode.parent.level <= 3;}},countNodeLevel(node) {//找到所有子节点,求出最大深度if (node.childNodes != null && node.childNodes.length > 0) {for (let i = 0; i < node.childNodes.length; i++) {if (node.childNodes[i].level > this.maxLevel) {this.maxLevel = node.childNodes[i].level;}this.countNodeLevel(node.childNodes[i]);}}},// 拖拽事件结束后的回调函数handleDrop(draggingNode, dropNode, dropType, ev) {console.log("handleDrop: ", draggingNode, dropNode, dropType);//1、当前节点最新的父节点idlet pCid = 0;let siblings = null; // 同层级的节点if (dropType == "before" || dropType == "after") {pCid =dropNode.parent.data.catId == undefined? 0: dropNode.parent.data.catId;// 同层级的节点siblings = dropNode.parent.childNodes;} else {pCid = dropNode.data.catId;// 同层级的节点siblings = dropNode.childNodes;}//2、当前拖拽节点的最新顺序,for (let i = 0; i < siblings.length; i++) {if (siblings[i].data.catId == draggingNode.data.catId) {//如果遍历的是当前正在拖拽的节点let catLevel = draggingNode.level;if (siblings[i].level != draggingNode.level) {//当前节点的层级发生变化catLevel = siblings[i].level;//修改他子节点的层级this.updateChildNodeLevel(siblings[i]);}this.updateNodes.push({catId: siblings[i].data.catId,sort: i,parentCid: pCid,catLevel: catLevel,});} else {this.updateNodes.push({ catId: siblings[i].data.catId, sort: i });}}//3、当前拖拽节点的最新层级console.log("updateNodes", this.updateNodes);// 展开节点this.expandedKey = [pCid];},// 递归更新子节点的层级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]);}}},batchSave() {// 发送请求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.updateNodes = [];});},},//生命周期 - 创建完成(可以访问当前this实例)created() {// 在实例创建时使用该方法,获取分类数据,保存到menus中this.getMenus();},//生命周期 - 挂载完成(可以访问DOM元素)mounted() {},beforeCreate() {}, //生命周期 - 创建之前beforeMount() {}, //生命周期 - 挂载之前beforeUpdate() {}, //生命周期 - 更新之前updated() {}, //生命周期 - 更新之后beforeDestroy() {}, //生命周期 - 销毁之前destroyed() {}, //生命周期 - 销毁完成activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
};
</script>
<style scoped>
</style>

1.10 批量删除节点实现

1、添加删除按钮

2、给tree添加引用名

3、编写删除函数

改写后的category.vue:

<!--  -->
<template><div><el-switchv-model="draggable"active-text="开启拖拽"inactive-text="关闭拖拽"></el-switch><el-button v-if="draggable" @click="batchSave">批量保存</el-button><el-button type="danger" @click="batchDelete">批量删除</el-button><!-- expand-on-click-node:是否在点击节点的时候展开或者收缩节点, 默认值为 true,如果为 false,则只有点箭头图标的时候才会展开或者收缩节点。 --><!-- show-checkbox  节点是否可被选择 --><!-- node-key 每个树节点用来作为唯一标识的属性,整棵树应该是唯一的 --><el-tree:data="menus":props="defaultProps":expand-on-click-node="false":show-checkbox="true"node-key="catId":default-expanded-keys="expandedKey":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-buttonv-if="node.level <= 2"type="text"size="mini"@click="() => append(data)">Append</el-button><el-button type="text" size="mini" @click="() => edit(data)">Edit</el-button><el-buttonv-if="node.childNodes.length == 0"type="text"size="mini"@click="() => remove(node, data)">Delete</el-button></span></span></el-tree><el-dialog :title="dialogTitle" :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-item label="图标"><el-input v-model="category.icon" autocomplete="off"></el-input></el-form-item><el-form-item label="计量单位"><el-inputv-model="category.productUnit"autocomplete="off"></el-input></el-form-item></el-form><span slot="footer" class="dialog-footer"><el-button @click="dialogVisible = false">取 消</el-button><el-button type="primary" @click="submitData">确 定</el-button></span></el-dialog></div>
</template><script>
//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';export default {//import引入的组件需要注入到对象中才能使用components: {},props: {},data() {//这里存放数据return {draggable: false,updateNodes: [],dialogTitle: "",dialogType: "",category: {name: "",parentCid: 0,catLevel: 0,showStatus: 1,sort: 0,productUnit: "",icon: "",catId: null,},dialogVisible: false,menus: [],expandedKey: [],defaultProps: {// children和name都是menus里的数据children: "children",label: "name",},};},//监听属性 类似于data概念computed: {},//监控data中的数据变化watch: {},//方法集合methods: {getMenus() {this.$http({url: this.$http.adornUrl("/product/category/list/tree"),method: "get",}).then(({ data }) => {console.log("成功获取到菜单数据...", data.data);this.menus = data.data;});},append(data) {console.log("append", data);this.dialogType = "add";this.dialogTitle = "添加分类";this.dialogVisible = true;this.category.parentCid = data.catId;this.category.catLevel = data.catLevel * 1 + 1;// 因为修改后数据变化,需要改回默认数据this.category.catId = null;this.category.name = "";this.category.icon = "";this.category.productUnit = "";this.category.sort = 0;this.category.showStatus = 1;},// 添加三级分类addCategory() {console.log("提交三级分类的数据:", this.category);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.expandedKey = [this.category.parentCid];});},// 修改方法edit(data) {console.log("要修改的数据:", data);this.dialogType = "edit";this.dialogTitle = "修改分类";this.dialogVisible = true;// 发送请求查询最新的数据this.$http({url: this.$http.adornUrl(`/product/category/info/${data.catId}`),method: "get",params: this.$http.adornParams({}),}).then(({ data }) => {// 请求成功后的处理console.log("要回显的数据:", data);this.category.name = data.data.name;this.category.catId = data.data.catId;this.category.icon = data.data.icon;this.category.productUnit = data.data.productUnit;this.category.parentCid = data.data.parentCid;this.category.catLevel = data.data.catLevel;this.category.sort = data.data.sort;this.category.showStatus = data.data.showStatus;});},// 修改三级分类数据editCategory() {var { catId, name, icon, productUnit } = this.category;var categoryData = {catId: catId,name: name,icon: icon,productUnit: productUnit,};this.$http({url: this.$http.adornUrl("/product/category/update"),method: "post",data: this.$http.adornData(categoryData, false),}).then(({ data }) => {this.$message({message: "菜单修改成功",type: "success",});// 关闭对话框this.dialogVisible = false;// 刷新菜单this.getMenus();// 设置需要默认展开的菜单this.expandedKey = [this.category.parentCid];});},// 判断是修改请求还是添加请求submitData() {if (this.dialogType == "add") {this.addCategory();} else if ((this.dialogType = "edit")) {this.editCategory();}},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.expandedKey = [node.parent.data.catId];});}).catch(() => {});console.log("append", node, data);},// 允许拖拽函数allowDrop(draggingNode, dropNode, type) {//1、被拖动的当前节点以及所在的父节点总层数不能大于3//1)、被拖动的当前节点总层数console.log("allowDrop:", draggingNode, dropNode, type);this.maxLevel = draggingNode.level;this.countNodeLevel(draggingNode);//当前正在拖动的节点+父节点所在的深度不大于3即可let deep = Math.abs(this.maxLevel - draggingNode.level) + 1;console.log("深度:", deep);if (type == "inner") {return deep + dropNode.level <= 3;} else {return deep + dropNode.parent.level <= 3;}},countNodeLevel(node) {//找到所有子节点,求出最大深度if (node.childNodes != null && node.childNodes.length > 0) {for (let i = 0; i < node.childNodes.length; i++) {if (node.childNodes[i].level > this.maxLevel) {this.maxLevel = node.childNodes[i].level;}this.countNodeLevel(node.childNodes[i]);}}},// 拖拽事件结束后的回调函数handleDrop(draggingNode, dropNode, dropType, ev) {console.log("handleDrop: ", draggingNode, dropNode, dropType);//1、当前节点最新的父节点idlet pCid = 0;let siblings = null; // 同层级的节点if (dropType == "before" || dropType == "after") {pCid =dropNode.parent.data.catId == undefined? 0: dropNode.parent.data.catId;// 同层级的节点siblings = dropNode.parent.childNodes;} else {pCid = dropNode.data.catId;// 同层级的节点siblings = dropNode.childNodes;}//2、当前拖拽节点的最新顺序,for (let i = 0; i < siblings.length; i++) {if (siblings[i].data.catId == draggingNode.data.catId) {//如果遍历的是当前正在拖拽的节点let catLevel = draggingNode.level;if (siblings[i].level != draggingNode.level) {//当前节点的层级发生变化catLevel = siblings[i].level;//修改他子节点的层级this.updateChildNodeLevel(siblings[i]);}this.updateNodes.push({catId: siblings[i].data.catId,sort: i,parentCid: pCid,catLevel: catLevel,});} else {this.updateNodes.push({ catId: siblings[i].data.catId, sort: i });}}//3、当前拖拽节点的最新层级console.log("updateNodes", this.updateNodes);// 展开节点this.expandedKey = [pCid];},// 递归更新子节点的层级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]);}}},batchSave() {// 发送请求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.updateNodes = [];});},// 批量删除函数batchDelete() {let catIds = [];let checkedNodes = this.$refs.menuTree.getCheckedNodes();console.log("被选中的元素", checkedNodes);for (let i = 0; i < checkedNodes.length; i++) {catIds.push(checkedNodes[i].catId);}this.$confirm(`是否批量删除【${catIds}】菜单?`, "提示", {confirmButtonText: "确定",cancelButtonText: "取消",type: "warning",}).then(() => {this.$http({url: this.$http.adornUrl("/product/category/delete"),method: "post",data: this.$http.adornData(catIds, false),}).then(({ data }) => {this.$message({message: "菜单批量删除成功",type: "success",});this.getMenus();});}).catch(() => {});},},//生命周期 - 创建完成(可以访问当前this实例)created() {// 在实例创建时使用该方法,获取分类数据,保存到menus中this.getMenus();},//生命周期 - 挂载完成(可以访问DOM元素)mounted() {},beforeCreate() {}, //生命周期 - 创建之前beforeMount() {}, //生命周期 - 挂载之前beforeUpdate() {}, //生命周期 - 更新之前updated() {}, //生命周期 - 更新之后beforeDestroy() {}, //生命周期 - 销毁之前destroyed() {}, //生命周期 - 销毁完成activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
};
</script>
<style scoped>
</style>

6.商品服务-API-三级分类相关推荐

  1. 商品服务3-三级分类-查询展示三级分类数据

    一.配置如何发送请求访问到真正的三级分类数据 在解决跨域问题之后可以登录人人快速开发平台,但是点进去分类维护列表时,会发现在访问product/category/list/tree时报错,因为请求是去 ...

  2. sql语句查询商品的一二三级分类都是一个字段怎么办_畅购商城(三):商品管理...

    好好学习,天天向上 本文已收录至我的Github仓库「DayDayUP」:github.com/RobodLee/DayDayUP,欢迎Star 小练手 这里有三个小练手的任务,内容比较简单,就是对一 ...

  3. 谷粒商城P85【商品系统】-> 【商品维护】->【发布商品】~ 获取三级分类下所有属性分组及其关联的属性

    点击下一步会发送一个请求: vo: @Data public class AttrGroupWithAttrsVo {/*** 分组id*/private Long attrGroupId;/*** ...

  4. 谷粒商城--API三级分类--网关统一配置跨域

    什么是跨域,为什么会有跨域问题的出现 一.什么是同源策略? 1.同源策略/SOP(Same origin policy)是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本 ...

  5. 微盟电子商城网络交易系统——Day03【商品服务-API-三级分类】

    ❤ 作者主页:欢迎来到我的技术博客

  6. 谷粒商城九商品服务之商品属性及仓储服务todo

    之前的文章我都是把整篇的代码直接复制到文章中,这样容易抓不住重点, 但是一段代码都贴出来,又显得繁琐, 从这篇开始,我会把重点步骤写出来,代码还是贴完整的 从这篇开始的mybatis-plus分页插件 ...

  7. 电商项目——商品服务-API-属性分组——第十一章——上篇

    电商项目--初识电商--第一章--上篇 电商项目--分布式基础概念和电商项目微服务架构图,划分图的详解--第二章--上篇 电商项目--电商项目的虚拟机环境搭建_VirtualBox,Vagrant-- ...

  8. 谷粒商城基础篇------商品服务 - 三级分类(gulimall-product:pms_category表)

    文章目录 4.1 递归树形结构获取商品分类(pms_category)数据 4.2 配置网关路由与路径重写 1. 前端修改 2.后端-配置网关路由 3.跨域问题的解决 4.再次树形展示商品数据 4.3 ...

  9. 谷粒商城六商品服务三级分类

    递归-树形结构数据获取 sql文件 sql文件太大了,这个博主写的非常厉害,看他的就ok了 CategoryController package com.atguigu.gulistore.produ ...

最新文章

  1. 单链表-单链表A拆分为A(偶)和C(奇) (尾插法建表)
  2. 西南大学计算机英语统考时间,西南大学2018年9月全国计算机等级考试报名通知...
  3. 1.08亿条在线赌场信息泄露,来源指向 ElasticSearch
  4. 编写python程序_如何进行Python程序的编写
  5. pl/sql查询表数据,报错ORA-03115:不支持的网络数据类型或表示法
  6. Jupyter-Notebook插入公式
  7. tomcat8+idea远程调试
  8. Mathtype6.9保姆级安装教程,亲测可用-----经常踩坑
  9. nodejs项目实例房屋租赁管理系统VUE.js
  10. CAS配置REST请求方式
  11. 小管家进销存_美团入局共享充电宝,专家称或收购一两家,同行称雷声大雨点小...
  12. 同个网络找不到计算机打印机共享,搜索不到共享打印机怎么办_同一局域网找不到共享打印机的解决方法-系统城...
  13. access9磅字体是多_字体大小(几号-几磅)
  14. XMind中怎么导入图标?
  15. C++实现空间中两个三角形位置关系(相交、平行)的判断
  16. 协议和服务器有什么区别,服务期协议是什么,劳动合同与服务期协议有什么区别?...
  17. Springboot汽车配件销售管理系统毕业设计源码131650
  18. 一份较详细的MS服务
  19. K-近邻(K-Nearest Neighbor, KNN)分类原理及相关公式
  20. 积米浏览器下载|积米浏览器免费下载

热门文章

  1. 助力中小企业级连云端,促进工业互联网平台蓬勃发展,全套解决方案。附:技术产品...
  2. 【爱找茬】linux系统和windows系统,你找到了几处不同?
  3. ios 中生成随机数
  4. AES加密算法之行移位变换
  5. 安装sinaweibo.py
  6. SQL SERVER使用链接服务器
  7. kdj指标主要看哪个值_KDJ指标的J值与D值差别
  8. 基于Socket的五子棋游戏
  9. Unity学习笔记:Unity 3D 飞机大战
  10. 希腊字母大小写及其读音、英文