谷粒商城-基础篇-商品服务1-三级分类(P45-P58)

谷粒商城-基础篇2

  • 一、商品服务-API-三级分类
    • 1、三级分类
    • 2、查出所有分类以及子分类
    • 2、配置网关路由与路径重写
    • 3、网关统一配置跨域
    • 4、查询-树形展示三级分类数据
    • 5、删除
    • 6、新增
    • 7、修改
    • 8、修改拖拽效果
    • 9、批量删除


商品服务-三级分类

一、商品服务-API-三级分类

1、三级分类

  • pms_category 表代表商品的分类
cat_id:分类id,cat代表分类,bigint(20)
name:分类名称
parent_cid:在哪个父目录下
cat_level:分类层级
show_status:是否显示,用于逻辑删除
sort:同层级同父目录下显示顺序
ico图标,product_unit商品计量单位,
InnoDB表,自增大小1437,utf编码,动态行格式

2、查出所有分类以及子分类

1.在product服务的package com.ljn.gulimall.product.controller中打开CategoryController,添加方法:

@RestController
@RequestMapping("product/category")
public class CategoryController {
    @Autowired
    private CategoryService categoryService;

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

2…在product服务的package com.ljn.gulimall.product.service;中打开CategoryService,添加方法:

public interface CategoryService extends IService<CategoryEntity> {

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

    List<CategoryEntity> listWithTree();
}

3.在product服务的package com.ljn.gulimall.product.service.impl;中打开CategoryServiceImpl实现方法:

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

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

    // 注入CategoryDao
    @Autowired
    CategoryDao categoryDao;

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

        return new PageUtils(page);
    }

    // 实现方法
    @Override
    public List<CategoryEntity> listWithTree() {
        // 1.查出所有分类
        List<CategoryEntity> entities = categoryDao.selectList(null);

        // 2.组装成父子的树形结构
        //  2.1 找到所有一级分类,一级分类父id=0,并返回为一个集合
        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;
    }

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

        // 从所有菜单中过滤出子菜单
        List<CategoryEntity> children = all.stream().filter(categoryEntity -> {
            // 当前菜单的父ID=指定菜单的id  也就是判断在哪个父目录下
            return categoryEntity.getParentCid().equals(root.getCatId());
        }).map(categoryEntity -> {
            // 当前菜单还可能有子菜单
            categoryEntity.setChildren(getChildrens(categoryEntity, all));
            return categoryEntity;
        }).sorted((menu1, menu2) -> {
            // 排序
            return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
        }).collect(Collectors.toList());

        return children;
    }

}

4.结果
谷粒商城-基础篇-商品服务1-三级分类(P45-P58)_第1张图片

2、配置网关路由与路径重写

启动renren-fast 前后端项目:

  • 点击系统管理,菜单管理,新增
    谷粒商城-基础篇-商品服务1-三级分类(P45-P58)_第2张图片
    刷新,看到左侧多了商品系统,添加的这个菜单其实是添加到了guli-admin.sys_menu表里.

  • 继续新增:分类维护菜单
    谷粒商城-基础篇-商品服务1-三级分类(P45-P58)_第3张图片

在左侧点击【分类维护】,希望在此展示3级分类

注意地址栏http://localhost:8001/#/product-category 可以注意到product-category我们的/被替换为了-

比如sys-role具体的视图在renren-fast-vue/views/modules/sys/role.vue

  • 所以要自定义我们的product/category视图的话,就是创建mudules/product/category.vue
<template>
  <el-tree :data="data" :props="defaultProps" @node-click="handleNodeClick"></el-tree>
</template>

<script>
export default {
    name: 'category',
    components: {},
    directives: {},
     data() {
      return {
        data: [],
        defaultProps: {
          children: 'children',
          label: 'label'
        }
      };
    },
    mounted() {
        
    },
    methods: {
        handleNodeClick(data) {
        console.log(data);
      },
      getMenus(){
        this.$http({
          url: this.$http.adornUrl('/product/category/list/tree'),
          method: 'get'
        }).then(data=>{
            console.log(data)
        })
      }
    },
    created(){
        this.getMenus();
    }
};
</script>


<style>

</style>

网关88配置

  • 在登录管理后台的时候,我们会发现,他要请求localhost:8080/renren-fast/product/category/list/tree这个url
  • 他要给8080发请求读取数据,但是数据是在10000端口上,如果找到了这个请求改端口那改起来很麻烦。
  • 方法1是改vue项目里的全局配置.
  • 方法2是搭建个网关,让网关路由到10000(即将vue项目里的请求都给网关,网关经过url处理后,去nacos里找到管理后台的微服务,就可以找到对应的端口了,这样我们就无需管理端口,统一交给网关管理端口接口)

1.在vue项目的 static/config/index.js里修改

 window.SITE_CONFIG['baseUrl'] = 'http://localhost:88/api';
// 意思是说本vue项目中要请求的资源url都发给88/api,那么我们就让网关端口为88,然后匹配到/api请求即可,
// 网关可以通过过滤器处理url后指定给某个微服务
// renren-fast服务已经注册到了nacos中

刷新后需要重新登录,此时验证码不显示,因为此时验证码是请求88的,所以不显示。而验证码是来源于fast后台即8080端口的的。

解决:将renren-fast 注册到nacos注册中心,这样请求88网关转发到8080fast。
谷粒商城-基础篇-商品服务1-三级分类(P45-P58)_第4张图片

2.让fast里加入注册中心的依赖:

 <!--        Nacos 配置管理-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            <version>2.1.0.RELEASE</version>
        </dependency>


        <!--        Nacos 服务注册发现-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>2.1.0.RELEASE</version>
        </dependency>

3.在renren-fast项目的application.yml中添加:

spring:
  application:
    name: renren-fast  # 意思是把renren-fast项目也注册到nacos中,这样网关才能转发给
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 # nacos
        

4.开启服务注册与发现

@EnableDiscoveryClient
@SpringBootApplication
public class RenrenApplication {

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

5.在gateway服务中按格式加入

          - id: admin_route
            #            lb 代表负载均衡
            uri: lb://renren-fast       # 路由给renren-fast
            predicates:                 # 什么情况下路由给它
              - Path=/api/**            # 默认前端项目都带上api前缀,就是我们前面的localhost:88/api
            filters:
              - RewritePath=/api/(?>.*),/renren-fast/$\{segment}        # 把/api/* 改变成 /renren-fast/*
      

3、网关统一配置跨域

  • 跨域:指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对js施加的安全限制。(ajax可以)
  • 同源策略:是指协议,域名,端囗都要相同,其中有一个不同都会产生跨域;

跨域流程:
谷粒商城-基础篇-商品服务1-三级分类(P45-P58)_第5张图片

解决跨域:

  1. 使用ngnix部署为同一域:设置nginx包含admin和gateway。都先请求nginx,这样端口就统一了。
    谷粒商城-基础篇-商品服务1-三级分类(P45-P58)_第6张图片
  2. 配置当次请求允许跨域:让服务器告诉预检请求能跨域
    在网关中定义“GulimallCorsConfiguration”类,该类用来做过滤,允许所有的请求跨域。
package com.ljn.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;

@Configuration // gateway
public class GulimallCorsConfiguration {

    @Bean // 添加过滤器
    public CorsWebFilter corsWebFilter() {
        // 基于url跨域,选择reactive包下的
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        // 跨域配置信息
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        // 允许跨域的头
        corsConfiguration.addAllowedHeader("*");
        // 允许跨域的请求方式
        corsConfiguration.addAllowedMethod("*");
        // 允许跨域的请求来源
        corsConfiguration.addAllowedOrigin("*");
        // 是否允许携带cookie跨域
        corsConfiguration.setAllowCredentials(true);

        // 任意url都要进行跨域配置
        source.registerCorsConfiguration("/**", corsConfiguration);
        return new CorsWebFilter(source);
    }
}

4、查询-树形展示三级分类数据

1、问题描述

  • 在显示商品系统/分类信息的时候,出现了404异常,请求的http://localhost:88/api/product/category/list/tree不存在

  • 这是因为网关上所做的路径映射不正确,映射后的路径为http://localhost:8001/renren-fast/product/category/list/tree

  • 但是只有通过http://localhost:10000/product/category/list/tree路径才能够正常访问,所以会报404异常。
    谷粒商城-基础篇-商品服务1-三级分类(P45-P58)_第7张图片

2、解决方法就是定义一个product路由规则,以后/ap/product 的路径都转发给product服务,进行路径重写:

  • (1)在gateway网关增加三级分类的路由
   		# product服务路由
        - id: product_route
          uri: lb://gulimall-product
          predicates:
            - Path=/api/product/**  
          filters:
            - # 把/api/* 去掉,剩下的留下来
            - RewritePath=/api/(?>.*),/$\{segment}
  • (2)在nacos中新建命名空间(product),用命名空间隔离项目,(可以在其中新建gulimall-product.yml 抽取配置)
    谷粒商城-基础篇-商品服务1-三级分类(P45-P58)_第8张图片
  • (3) nacos 配置中心管理product服务,创建bootstrap.proterties
# 应用名称
spring.application.name=gulimall-product
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
#命名空间的唯一ID
spring.cloud.nacos.config.namespace=8a9fe873-8e40-4d56-a77f-0b5993c6f52c
  • (4)配置 produc t服务的 application.yml,配置服务注册与发现
# 配置nacos服务注册与发现
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
  • (5)GulimallProductApplication 主启动类中开启服务注册与发现功能 @EnableDiscoveryClient

  • (6)启动服务后访问 localhost:88/api/product/category/list/tree

{"msg":"invalid token","code":401}

invalid token,非法令牌,后台管理系统中没有登录,所以没有带令牌

原因:先匹配的先路由,fast和product路由重叠,fast要求登录

修正:在路由规则的顺序上,将精确的路由规则放置到模糊的路由规则的前面,否则的话,精确的路由规则将不会被匹配到,类似于异常体系中try catch子句中异常的处理顺序。

谷粒商城-基础篇-商品服务1-三级分类(P45-P58)_第9张图片

访问 http://localhost:88/api/product/category/list/tree 正常

访问 http://localhost:8001/#/product-category,也就是点击分类维护,正常,数据获取成功
谷粒商城-基础篇-商品服务1-三级分类(P45-P58)_第10张图片

原因是:先访问网关88,网关路径重写后访问nacos8848,通过nacos找到服务

3、前端渲染
谷粒商城-基础篇-商品服务1-三级分类(P45-P58)_第11张图片
谷粒商城-基础篇-商品服务1-三级分类(P45-P58)_第12张图片


5、删除

1、使用scoped slot(插槽)实现:在el-tree标签里把内容写到span标签栏里即可

  • :expand-on-click-node=“false” :点击按钮的时候不展开
  • show-checkbox :代表节点是否可被选择
  • node-key=“catId” :节点唯一标识
<template>
  
  <el-tree :data="menus" :props="defaultProps" :expand-on-click-node="false" show-checkbox node-key="catId">
    
    <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>
template>

<script>
export default {
  name: "category",
  components: {},
  directives: {},
  data() {
    return {
      menus: [],
      data: [],
      defaultProps: {
        children: "children",
        label: "name", // 要显显示的内容
      },
    };
  },
  mounted() { },
  methods: {

    // 3. 增加标签的方法
    append(data) {
      console.log("append", data)
    },

    // 2. 删除分类的方法
    remove(node, data) {
      console.log("delete", node, data)
    },

    // 1. 获取分类数据的方法
    getMenus() {
      this.$http({
        url: this.$http.adornUrl("/product/category/list/tree"),
        method: "get",
      }).then(({ data }) => {
        // 解构出数据中有用的data
        console.log(data.data);
        this.menus = data.data;
      });
    },
  },
  created() {
    this.getMenus();
  },
};
script>

<style>style>

2、逻辑删除

  • 修改CategoryController类,添加如下代码
    @RequestMapping("/delete")
    public R delete(@RequestBody Long[] catIds) {
    
        // 1、删除之前需要判断待删除的菜单那是否被别的地方所引用。

        // 2、自定义删除方法
        categoryService.removeMenuByIds(Arrays.asList(catIds));
        return R.ok();
    }
  • CategoryServiceImpl 实现自定义的方法
 @Override
    public void removeMenuByIds(List<Long> asList) {
        //TODO  1、检查当前删除的菜单,是否被其它地方引用

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

    }
  • mybatis-plus逻辑删除配置(可有可无)
mybatis-plus:
  mapper-locations: classpath:/mapper/**/*.xml
  global-config:
    db-config:
      id-type: auto
      logic-delete-value: 1   
      logic-not-delete-value: 0
  • 修改product.entity.CategoryEntity实体类,添加上@TableLogic,表明使用逻辑删除
	/**
	 * 是否显示[0-不显示,1显示]
	 */
	@TableLogic(value = "1",delval = "0")
	private Integer showStatus;

3、apifox测试请求
谷粒商城-基础篇-商品服务1-三级分类(P45-P58)_第13张图片

4、前端请求处理

发送的请求:delete
发送的数据:this.$http.adornData(ids, false)
util/httpRequest.js中,封装了一些拦截器
http.adornParams是封装get请求的数据
http.adornData封装post请求的数据
ajax的get请求会被缓存,就不会请求服务器了。
所以我们在url后面拼接个date时间戳,让他每次都请求服务器

  • 发送请求
 // 2. 删除分类的方法
remove(node, data) {
  // 1. 获取当前节点id
  var ids = [data.catId]

  // 2. 发送请求前弹框提示
  this.$confirm(`是否删除${data.name}菜单?`, '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    // 3. 确认删除,发送post请求
    this.$http({
      url: this.$http.adornUrl('/product/category/delete'),
      method: 'post',
      data: this.$http.adornData(ids, false)
    }).then(({ data }) => {
      // 4. 删除成功提示消息
      this.$message({
        type: "success",
        message: "菜单删除成功!",
      });
      // 5. 删除成功后重新请求菜单
      this.getMenus();
      // 6. 设置默认展开菜单
      this.expandedKey=[node.parent.data.catId]
    })
  }).catch(() => {

  });

  console.log("delete", node, data)
},

  • 删除后展开标准
<el-tree :data="menus" :props="defaultProps" :expand-on-click-node="false" show-checkbox node-key="catId" :default-expanded-keys="expandedKey">

 data() {
    return {
      expandedKey: [], // 展开基准
    };
  },

6、新增

  1. 点击append按钮的时候打开对话框
  • 用到属性 visible.sync,动态绑定,:visible.sync="dialogVisible"
  <!-- 对话框 -->
    <el-dialog title="提示" :visible.sync="dialogVisible" width="30%">
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary">确 定</el-button>
      </span>
    </el-dialog>

	 data() {
    	return {
      	dialogVisible: false, // 是否打开对话框,默认为false
    };
  },
 	methods: {
    	append(data) {
      	// 1. 点击append 按钮打开对话框
      	this.dialogVisible = true;
      	console.log("append", data)
    },
  1. 对话框中添加表单
  • 属性
<!-- 对话框 -->
    <el-dialog title="提示" :visible.sync="dialogVisible" width="30%">

      <!-- 表单项 -->
      <el-form :model="category">
        <el-form-item label="分类名称">
          <!-- 输入框,双向绑定category中的属性 -->
          <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>
  1. 定义添加菜单的方法addCategory,并在append方法中设置默认值
 // 4. 添加三级分类的方法
    addCategory() {
      console.log("提交的三级分类数据", this.category)
      // 1. 发送保存请求,提交
      this.$http({
        url: this.$http.adornUrl('/product/category/save'),
        method: 'post',
        data: this.$http.adornData(this.category, false)  // 要发送的数据
      }).then(({ data }) => {
        // 2. 保存成功提示消息
        this.$message({
          type: "success",
          message: "菜单保存成功!",
        });
        // 3. 保存成功后关闭对话框
        this.dialogVisible = false;
        // 4. 刷新出新菜单
        this.getMenus();
        // 5. 设置默认展示的菜单
        this.expandedKey = [this.category.parentCid];

      })

    },

	 append(data) {
      console.log("append", data)
      // 1. 点击append 按钮打开对话框
      this.dialogVisible = true;
      // 2. 点击按钮为category获取默认值
      //    2.1 父id,当前点击append的catId
      this.category.parentCid = data.catId;
      //    2.2 层级catLevel 当前点击append 的层级+1
      this.category.catLevel = data.catLevel * 1 + 1;
    },

7、修改

  1. 增加修改按钮edit
<!-- 按需展示:所有都展示修改按钮 -->
         <el-button  type="text" size="mini" @click="() => edit(node, data)">
           Edit
         </el-button>

	 methods: {
		// 5. 点击修改分类按钮
	    edit(node, data){
	    // 显示对话框
     	 this.dialogVisible=true;
     	 // 回显数据
     	 this.category.name=data.name;
	      console.log(node,data)
	    },
	}

  1. 复用对话框
  • 定义对话框类型:dialogType
  • 修改对话框确认按钮绑定的事件:submitDate (提交数据)
  • 根据对话框类型动态提交数据
  • 修改完后,在append中清除回显信息

    <!-- 对话框 -->
    <el-dialog :title="title" :visible.sync="dialogVisible" width="30%">

      <!-- 表单项  close-on-click-modal 关闭点击关闭空白处关闭对话框 -->
      <el-form :model="category" :close-on-click-modal="false">
        <el-form-item label="分类名称">
          <!-- 输入框,双向绑定category中的属性 -->
          <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>

      <!-- 按钮 -->
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="submitDate">确 定</el-button>
      </span>

    </el-dialog>

  </div>
</template>

<script>
export default {
  name: 'category',
  components: {},
  directives: {},
  data() {
    return {
      dialogType: "", // 对话框类型
      title: "", // 对话框标题
      category: { name: "", parentCid: 0, catLevel: 0, showStatus: 1, sort: 0, catId: null, icon: "", productUnit: "" }, // 表单中的数据对象
      dialogVisible: false, // 是否打开对话框,默认为false
      expandedKey: [], // 展开基准
      menus: [],
      data: [],
      defaultProps: {
        children: "children",
        label: "name", // 要显显示的内容
      },
    };
  },
  mounted() { },
  methods: {


    // 7. 对话框确认按钮,提交数据的方法
    submitDate() {
      if (this.dialogType == "append") {
        // 打开的是添加的对话框,保存分类
        this.addCategory();
      }

      if (this.dialogType == "edit") {
        // 打开的是修改的对话框,修改分类
        this.editCategory();

      }
    },

    // 6. 修改三级分类数据
    editCategory() {
      // 1. 解构要发送的数据
      var { catId, name, icon, productUnit } = this.category;
      // 2. 发送修改请求
      this.$http({
        url: this.$http.adornUrl("/product/category/update"),
        method: 'post',
        data: this.$http.adornData({ catId, name, icon, productUnit }, false)
      }).then(({ data }) => {
        this.$message({
          type: "success",
          message: "菜单修改成功!",
        });
        // 3. 关闭对话框
        this.dialogVisible = false;
        // 4. 刷新菜单
        this.getMenus();
        // 5. 展开父菜单
        this.expandedKey = [this.category.parentCid];
      })
    },

    // 5. 点击修改分类按钮
    edit(node, data) {
      // 1.1设置对话框类型为deit
      this.dialogType = "edit";
      // 1.2显示对话框
      this.dialogVisible = true;
      // 1.3对话框标题
      this.title = "修改分类"

      // 2. 发送请求回显最新数据
      this.$http({
        url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
        method: 'get',
      }).then(({ data }) => {
        // 3. 请求成功,回显数据 
        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;
      })
      console.log(node, data)
    },

    // 4. 添加三级分类数据的方法
    addCategory() {
      console.log("提交的三级分类数据", this.category)
      // 1. 发送保存请求,提交
      this.$http({
        url: this.$http.adornUrl('/product/category/save'),
        method: 'post',
        data: this.$http.adornData(this.category, false)  // 要发送的数据
      }).then(({ data }) => {
        // 2. 保存成功提示消息
        this.$message({
          type: "success",
          message: "菜单保存成功!",
        });
        // 3. 保存成功后关闭对话框
        this.dialogVisible = false;
        // 4. 刷新出新菜单
        this.getMenus();
        // 5. 设置默认展示的菜单
        this.expandedKey = [this.category.parentCid];
      })
    },

    // 3. 点击增加标签按钮
    append(data) {
      console.log("append", data)
      // 0. 设置对话框类型为append
      this.dialogType = "append";
      // 1. 点击append 按钮打开对话框
      this.dialogVisible = true;
      // 设置标题
      this.title = "添加分类";
      // 2. 点击按钮为category获取默认值
      //    2.1 父id,当前点击append的catId
      this.category.parentCid = data.catId;
      //    2.2 层级catLevel 当前点击append 的层级+1
      this.category.catLevel = data.catLevel * 1 + 1;

      // 3. 清空修改后的回显信息
      this.category.name = "";
      this.category.showStatus = 1;
      this.category.sort = 0;
      this.category.catId = null;
      this.category.icon = "";   // 输入什么绑定什么
      this.category.productUnit = "";

    },

8、修改拖拽效果

  1. 实现拖拽效果
  • :draggable 属性开启拖拽效果
  • :allow-drop 拖拽目标是否放置
  <el-tree :data="menus" :props="defaultProps" :expand-on-click-node="false" show-checkbox node-key="catId" :default-expanded-keys="expandedKey" draggable :allow-drop="allowDrop">


// 9. 统计当前拖拽节点总层数的方法
    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]);
        }
      }
    },


    // 8. 拖拽是否放置的方法
    allowDrop(draggingNode, dropNode, type) {
      // 可以拖动放置的条件:被拖动的当前节点以及所在的父节点总层数不能大于3
      console.log("allowFrop:",draggingNode,dropNode,type)

      // 1. 统计当前节点的总层数
      this.countNodeLevel(draggingNode.data);
      // 2.当前正在拖动的节点+父节点所在的深度不大于3即可
      let deep = (this.maxLevel - draggingNode.data.catLevel) + 1;
      console.log("深度:", deep);

      // 3. this.maxLevel
      // 3.1 拖到节点里面
      if (type == "inner") {
        return deep + dropNode.level <= 3;
      } else {
        return deep + dropNode.parent.level <=3;
      }
    },

   
  1. 拖拽数据收集
  • 拖拽效果影响的数据: parentCid 、 catLevel 、sort
  • 属性 node-drop : 拖拽完成时触发的事件
 // 11.  修改子节点层级的方法
  data() {
    return {
      updateNodes: [], // 拖拽时修改的节点
      },
    };
  },
  methods: {
    updateChildNodeLevlel(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.updateChildNodeLevlel(node.childNodes[i]);
        }
      }
    },

    // 10. 拖拽完成事件处理
    handleDrop(draggingNode, dropNode, dropType, ev) {
      console.log('handleDrop: ', draggingNode, dropNode, dropType);
      // 1. 当前节点最新的父节点id
      let pCid = 0;
      let siblings = null;
      // 1.1  以兄弟关系拖拽
      if (dropType == "before" || dropType == "after") {
        // 最新父id=进入节点的父id
        pCid = dropNode.parent.data.catId == undefined ? 0 : dropNode.parent.data.catId;
        siblings = dropNode.parent.childNodes;
      }
      // 1.2 inner 方式拖入
      else {
        // 最新父id=进入节点的id
        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.updateChildNodeLevlel(siblings[i]);
          }
          this.updateNodes.push({ catId: siblings[i].data.catId, sort: i, parentCid: pCid });
        } else {
          this.updateNodes.push({ catId: siblings[i].data.catId, sort: i });
        }
      }

      // 3. 当前拖拽节点的最新层级
      console.log("updateNodes", this.updateNodes)
    },
}
  1. 发送请求
  • CategoryController 中添加方法
    /**
     * 批量修改
     */
    @RequestMapping("/update/sort")
    public R updateSort(@RequestBody CategoryEntity category) {
        categoryService.updateBatchById(Arrays.asList(category));
        return R.ok();
    }
 
    // 12. 批量保存方法 
    batchSave() {
      this.$http({
        url: this.$http.adornUrl("/product/category/update/sort"),
        method: 'post',
        data: this.$http.adornData(this.updateNodes, false)
      }).then(({ data }) => {
        this.$message({
          type: "success",
          message: "菜单顺序修改成功!",
        });
        // 3.2. 刷新菜单
        this.getMenus();
        // 3.3. 展开父菜单
        this.expandedKey = [this.pCid];
        this.updateNodes = [];
        this.maxLevel = 0;
        this.pCid = 0;
      });
    },

9、批量删除

  • ref=“menuTree”
  • getCheckedNodes() : 获取选中的元素
<el-button type="danger" @click="batchDelete">批量删除</el-button>

 // 13.批量删除
    batchDelete() {
      // 1. 要删除元素的数组
      let catIds = [];
      // 2. 获取选中元素
      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.name}】菜单?`, "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
      })
        .then(() => {
          // 3.发送请求
          this.$http({
            url: this.$http.adornUrl("/product/category/delete"),
            method: "post",
            data: this.$http.adornData(catIds, false),
          })
            .then(({ data }) => {
              this.$message({
                type: "success",
                message: "菜单批量删除成功!",
              });
              // 刷新出新的菜单
              this.getMenus();
            })
            .catch(() => { });
        })
        .catch(() => { });
    },

你可能感兴趣的:(谷粒商城-学习记录,微服务,java,电商项目,vue,springboot)