IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 谷粒商城P45-P58 -> 正文阅读

[Java知识库]谷粒商城P45-P58

作者:token annotation punctuation

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dNjAszbS-1651674156445)(https://b3logfile.com/bing/20200113.jpg?imageView2/1/w/960/h/540/interlace/1/q/100)]

分布式基础篇

csdn机制问题导致图片丢失,可以查看本人的个人博客:谷粒商城-P45-P58

1. 三级分类

1.1 sql脚本

三级分类数据脚本

1.2 查出所有分类以及子分类

1.2.1 后段代码编写

在gulimall-product的src/main/java/com/rabbitboss/gulimall/product/controller/CategoryController.java`?以下代码

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

src/main/java/com/rabbitboss/gulimall/product/service/CategoryService.java添加以下代码

List<CategoryEntity> listWithTree();

src/main/java/com/rabbitboss/gulimall/product/service/impl/CategoryServiceImpl.java添加以下代码

@Override
    public List<CategoryEntity> listWithTree() {
        //1.查出所有分类
        List<CategoryEntity> entities = baseMapper.selectList(null);
        //2。组装成父子树状结构

        //2.1找到所有的一级分类
        List<CategoryEntity> level1Menu = entities.stream().filter(categoryEntity ->
                categoryEntity.getParentCid() == 0
        ).map(menu -> {
            //1。找到子分类
            menu.setChildren(getChildrens(menu, entities));
            return menu;
        }).sorted((menu1, menu2) ->
                //2。排序
                (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort())
        ).collect(Collectors.toList());
        return level1Menu;
    }

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

        List<CategoryEntity> children = all.stream().filter((categoryEntity) -> {
            return categoryEntity.getParentCid() == root.getCatId();
        }).map(categoryEntity -> {
            //1。找到子分类
            categoryEntity.setChildren(getChildrens(categoryEntity, all));
            return categoryEntity;
        }).sorted((menu1, menu2) ->
                //2。排序
                (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort())
        ).collect(Collectors.toList());

        return children;
    }

1.2.2前端代码编写

1.2.2.1配置网关路由与路径重写
接着操作后台
localhost:8001 , 点击系统管理,菜单管理,新增
目录
商品系统
一级菜单

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a2SMyzJ5-1651674156447)(https://b3logfile.com/file/2022/05/solo-fetchupload-2215375846762372900-191c3030.png)]

image-20220430233204538

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

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

所以要自定义我们的product/category视图的话,就是创建mudules/product/category.vue

1.2.2.2前端代码
<template>
  <el-tree :data="data" :props="defaultProps" @node-click="handleNodeClick"></el-tree>
</template>

<script>
// 这里可以导入其他文件(比如:组件,工具 js,第三方插件 js,json 文件,图片文件等等)
// 例如:import  《组件名称》  from '《组件路径》 ';

export default {
  // import 引入的组件需要注入到对象中才能使用
  components: {},
  props: {},
  data () {
    return {
      data: [],
      defaultProps: {
        children: 'children',
        label: 'label'
      }
    }
  },
  // 计算属性 类似于 data 概念
  computed: {},
  // 监控 data 中的数据变化
  watch: {},
  // 方法集合
  methods: {
    handleNodeClick (data) {
      console.log(data)
    },
    getMenus () {
      this.dataListLoading = true
      this.$http({
        url: this.$http.adornUrl('/product/category/list/tree'),
        method: 'get'
        // params: this.$http.adornParams({
        //   'page': this.pageIndex,
        //   'limit': this.pageSize,
        //   'roleName': this.dataForm.roleName
        // })
      }).then(data => {
        console.log('成功获取到菜单数据...', data)
      })
    }
  },
  // 生命周期 - 创建完成(可以访问当前this 实例)
  created () {
    this.getMenus()
  },
  // 生命周期 - 挂载完成(可以访问 DOM 元素)
  mounted () {
  },
  beforeCreate () {
  },
  beforeMount () {
  }, // 生命周期 - 挂载之前
  beforeUpdate () {
  }, // 生命周期 - 更新之前
  updated () {
  }, // 生命周期 - 更新之后
  beforeDestroy () {
  }, // 生命周期 - 销毁之前
  destroyed () {
  }, // 生命周期 - 销毁完成
  activated () {
  } // 如果页面有 keep-alive 缓存功能,这个函数会触发
}
</script>

<style scoped>
</style>

他要给8080发请求读取数据,但是数据是在10000端口上,如果找到了这个请求改端口那改起来很麻烦。

  • 方法1是改vue项目里的全局配置

  • 方法2是搭建个网关,让网关路由到10000

    Ctrl+Shift+F全局搜索
    
    在static/config/index.js里
    
    window.SITE_CONFIG['baseUrl'] = 'http://localhost:88/api';
    
    接着让重新登录http://localhost:8001/#/login,验证码是请求88的,所以不显示。而验证码是来源于fast后台的
    
    现在的验证码请求路径为,http://localhost:88/api/captcha.jpg
    原始的验证码请求路径:http://localhost:8001/renren-fast/captcha.jpg
    
  • 88是GateWay的端口

那么怎么将88:/api转发到renren-fast上呢?看下面的步骤

# 1.在renren-fast模块中引入common模块 ?? pom.xml中引入common,因为common中集成了Nacos
		<dependency>
			<groupId>com.rabbitboss.gulimall</groupId>
			<artifactId>gulimall-common</artifactId>
			<version>0.0.1-SNAPSHOT</version>
		</dependency>
# 2.在renren-fast模块的application.yml文件中添加以下内容
  application:
    name: renren-fast
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
# 3.在renren-fast的启动类上加上注解 @EnableDiscoveryClient
# 4在gulimall-gateway的application.yml添加以下代码
spring:
  cloud:
    gateway:
      routes:
        - id: admin_route
          uri: lb://renren-fast
          predicates:
            - Path=/api/**
          filters:
            - RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}

在重启renren-fast的时候可能会出错,具体错误请看下面解决过程



1.2.3renren-fast相关报错

renren-fast启动报错:

Your project setup is incompatible with our requirements due to following reasons:

  • Spring Boot [2.2.4.RELEASE] is not compatible with this Spring Cloud release train

Action:

Consider applying the following actions:

  • Change Spring Boot version to one of the following versions [2.6.x, 2.7.x]

因为在Common模块里面是用的3.1的Nacos,最低支持SpringBoot2.6的版本

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xqp6Z97p-1651674156448)(https://b3logfile.com/file/2022/05/solo-fetchupload-2673391811449110828-19d9724b.png)]

所以需要将renren-fast模块里面的pom.xml把SpringBoot版本指定成2.6.6和谷粒商城其他模块的版本保持一致



renren-fast编译报错

javax.validation.constraints不存在

请看另外一篇博客javax.validation.constraints 不存在



启动报错

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2022-04-30 16:25:02.551 ERROR 35690 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

The dependencies of some of the beans in the application context form a cycle:

   sysLoginController (field private io.renren.modules.sys.service.SysUserService io.renren.modules.sys.controller.SysLoginController.sysUserService)
┌─────┐
|  sysUserService (field private io.renren.modules.sys.service.SysRoleService io.renren.modules.sys.service.impl.SysUserServiceImpl.sysRoleService)
↑     ↓
|  sysRoleService (field private io.renren.modules.sys.service.SysUserService io.renren.modules.sys.service.impl.SysRoleServiceImpl.sysUserService)
└─────┘


Action:

Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.

按照提示在renren-fast项目下的: src/main/resources/application.yml加入以下配置

  • spring.main.allow-circular-references=true

image-20220430165050433



启动报错

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2022-04-30 16:35:10.693 ERROR 40671 --- [           main] o.s.boot.SpringApplication               : Application run failed

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'scheduleJobController': Unsatisfied dependency expressed through field 'scheduleJobService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'scheduleJobService': Unsatisfied dependency expressed through field 'scheduler'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'schedulerFactoryBean' defined in class path resource [io/renren/modules/job/config/ScheduleConfig.class]: Invocation of init method failed; nested exception is org.quartz.SchedulerConfigException: DataSource name not set.

特别强调一下:

  • spring-boot-starter-quartz 2.5.6 之前使用org.quartz.impl.jdbcjobstore.JobStoreTX定义quartz的默认数据源支持,即如下配置:

    • org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
  • 升级spring boot > 2.5.6的版本后将不再支持此方式进行配置默认数据源,需改为如下配置:

    • org.quartz.jobStore.class=org.springframework.scheduling.quartz.LocalDataSourceJobStore

因为谷粒商城其他模块是SpringBoot2.6.6的版本,所以应该采取第二种写法

解决步骤

修改renren-fast项目下的:src/main/java/io/renren/modules/job/config/ScheduleConfig.java这个文件

按照下面的修改:

prop.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");

// 修改成

prop.put("org.quartz.jobStore.class", "org.springframework.scheduling.quartz.LocalDataSourceJobStore");

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o7PsMIlX-1651674156449)(https://b3logfile.com/file/2022/05/solo-fetchupload-1206516960012513860-cf207e0c.png)]



启动报错

报错原因:SpringBoot 2.6.x高版本和swagger2版本冲突

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2022-04-30 15:29:19.138 ERROR 15886 --- [           main] o.s.boot.SpringApplication               : Application run failed

org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException

解决步骤

  • 在renren-fast项目下的:src/main/resources/application.yml添加以下配置
    • spring.mvc.pathmatch.matching-strategy=ant-path-matcher

image-20220430170319083

1.2.4 请求验证码接口返回200,但是不显示出来

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lZc4UWuh-1651674156450)(https://b3logfile.com/file/2022/05/solo-fetchupload-283096653150069449-8e461ea9.png)]

1.2.5 跨域问题

image-20220501001938777

image-20220501181235609

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bnckr0F7-1651674156452)(https://b3logfile.com/file/2022/05/solo-fetchupload-3501653427239191771-31a311e8.png)]

1.2.5.1 简单请求

简单请求

某些请求不会触发 CORS 预检请求。本文称这样的请求为“简单请求”,请注意,该术语并不属于 Fetch(其中定义了 CORS)规范。若请求 满足所有下述条件,则该请求可视为“简单请求”:

使用下列方法之一:

除了被用户代理自动设置的首部字段(例如 ConnectionUser-Agent)和在 Fetch 规范中定义为 禁用首部名称 的其他首部,允许人为设置的字段为 Fetch 规范定义的 对 CORS 安全的首部字段集合。该集合为:

Content-Type 的值仅限于下列三者之一:

  • text/plain
  • multipart/form-data
  • application/x-www-form-urlencoded

请求中的任意 XMLHttpRequest 对象均没有注册任何事件监听器;XMLHttpRequest 对象可以使用 XMLHttpRequest.upload 属性访问。

请求中没有使用 ReadableStream 对象。

1.2.6解决跨域

1.2.6.1 跨域流程

image-20220501194256476

1.2.6.2 方式一: 使用nginx部署为同一域

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dVAMamFc-1651674156453)(https://b3logfile.com/file/2022/05/solo-fetchupload-1558048554553530534-464c03a2.png)]

生产环境 的话 推荐使用这种方法解决跨域,原因如下

  1. 访问静态 域名 可以通过 nginx 直接转到后台管理系统
  2. 后台登录等其他的增删改查功能,直接通过 nginx 转到 gateway 服务
1.2.6.3 方式二: 配置当次请求允许跨域

添加响应头

  • 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:表明该响应的有效时间为多少秒。在有效时间内,浏览器无

    须为同一请求再次发起预检请求。请注意,浏览器自身维护了一个最大有效时间,如果

    该首部字段的值超过了最大有效时间,将不会生效。

后端跨域配置

第一步:注释掉 renrenfast 配置的路由规则

注释掉renrenfast 配置的路由规则的类:src/main/java/io/renren/config/CorsConfig.java

第二步

在gulimall-gateway 模块的 src/main/resources/application.yml 下加入以下代码

spring:
  cloud:
    gateway:
      routes:
        - id: baidu_route
          uri: https://www.baidu.com
          predicates:
            - Query=url,baidu

        - id: qq_route
          uri: https://www.qq.com
          predicates:
            - Query=url,qq

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

第三步

配置跨域详细信息

  • 第一种配置方式:「拦截器的方式:CorsFilter 」

    • /**
       * @description: 跨域配置
       * @author: Jonny
       * @time: 2022/5/1 8:33 PM
       */
      @Configuration
      public class GulimallCorsConfiguration {
          @Bean
          public CorsWebFilter corsWebFilter(){
              UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
      
              CorsConfiguration corsConfiguration = new CorsConfiguration();
      
              // 1.配置跨域
              corsConfiguration.addAllowedHeader("*");
              corsConfiguration.addAllowedMethod("*");
      //        corsConfiguration.addAllowedOrigin("*");
              corsConfiguration.addAllowedOriginPattern("*");
              corsConfiguration.setAllowCredentials(true);
      
      
              source.registerCorsConfiguration("/**",corsConfiguration);
              return new CorsWebFilter(source);
          }
      }
      
  • 第二种配置方式:「全局配置的方式:WebMvcConfigurer接口的addCorsMappings方法中注册CORS配置」

    • @EnableWebMvc
      @Configuration
      public class MvcConfig implements WebMvcConfigurer {
      
          @Override
          public void addCorsMappings(CorsRegistry registry) {
      
              //每次调用registry.addMappin可以添加一个跨域配置,需要多个配置可以多次调用registry.addMapping
              registry.addMapping("/**")
                      .allowedOrigins("*") //放行哪些原始域
                      .allowedMethods("PUT", "DELETE""POST", "GET") //放行哪些请求方式
                      .allowedHeaders("header1", "header2", "header3") //放行哪些原始请求头部信息
                      .exposedHeaders("header1", "header2") //暴露哪些头部信息
                      .allowCredentials(false) //是否发送 Cookie
                      .maxAge(3600);
      
              // Add more mappings...
          }
      }
      
  • 第三种方式:使用@CrossOrigin注解

1.3 三级分类-查询-树形结构

访问http://localhost:8001/#/product-category报404是因为还没有在 nacos 中并没有把 gulimall-product 服务注册进去

1.3.1 将 gulimall-product 注册到nacos 中 具体配置如下:

  1. 修改product 服务的 src/main/resources/application.yml文件
spring:
 datasource:
   username: root
   password: root
   url: jdbc:mysql://192.168.1.17:3306/gulimall_pms
   driver-class-name: com.mysql.jdbc.Driver
 cloud:
   nacos:
     discovery:
       server-addr: 127.0.0.1:8848
 application:
   name: gulimall-product

mybatis-plus:
 mapper-locations: classpath:/mapper/**/*.xml
 global-config:
   db-config:
     id-type: auto
server:
 port: 10000
  1. 在统计目录下创建 bootstrap.properties配置 nacos 以及绑定命名空间 id
spring.application.name=gulimall-product
spring.cloud.nacos.config.server-addr=127.0.0.1:8848

spring.cloud.nacos.config.namespace=f0882d03-cf30-47f9-92e5-19a362b937b6
  1. 在 gulimall-product 模块的启动类加上 @EnableDiscoveryClient这个注解

前面在解决跨域问题的时候已经加了只要是 api 打头的请求都会转发到 renren-fast模块

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

现在需要做的是 http://localhost:8001/#/product-category这个请求是要转发到 gulimall-product 模块去的,之前配置的断言已经不符合要求了,需要再添加断言规则

## 在admin_route 断言上面再加入下面的断言规则
        - id: product_route
          uri: lb://gulimall-product
          predicates:
            - Path=/api/product/**
          filters:
            - RewritePath=/api/(?<segment>.*),/$\{segment}

1.4 前端代码,树形结构的增删改查(P51-P58)

<template>
  <div>
    <el-switch
      v-model="draggable"
      active-text="开启拖拽"
      inactive-text="关闭拖拽">
    </el-switch>
    <el-button v-show="draggable" @click="batchSave">批量保存</el-button>
    <el-button type="danger" @click="batchDelete">批量删除</el-button>
    <el-tree
      :data="menus"
      :props="defaultProps"
      :expand-on-click-node="false"
      show-checkbox
      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-button
            v-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-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="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>
      <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 {
      pCid: [],
      draggable: false,
      updateNodes: [],
      maxLeve: 0,
      title: '',
      dialogType: '',   // add,edit
      category: {
        name: '',
        parentCid: 0,
        catLevel: 0,
        showStatus: 1,
        sort: 0,
        catId: null,
        icon: '',
        productUnit: ''
      },
      dialogVisible: false,
      menus: [],
      expandedKey: [],
      defaultProps: {
        children: 'children',
        label: 'name'
      }
    }
  },
  // 计算属性 类似于 data 概念
  computed: {},
  // 监控 data 中的数据变化
  watch: {},
  // 方法集合
  methods: {
    getMenus () {
      this.dataListLoading = true
      this.$http({
        url: this.$http.adornUrl('/product/category/list/tree'),
        method: 'get'
        // params: this.$http.adornParams({
        //   'page': this.pageIndex,
        //   'limit': this.pageSize,
        //   'roleName': this.dataForm.roleName
        // })
      }).then(({data}) => {
        console.log('成功获取到菜单数据...', data.data)
        this.menus = data.data
      })
    },
    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: '菜单等修改成功!'
        })
        // 刷新菜单
        this.getMenus()
        // 设置默认展开的菜单
        this.expandedKey = this.pCid
        this.updateNodes = []
        this.maxLeve = 0
        // this.pCid = 0
      })
    },
    batchDelete () {
      let catIds = []
      let nodesName = []
      let checkedNodes = this.$refs.menuTree.getCheckedNodes()
      console.log('checkedNodes:', checkedNodes)
      for (let i = 0; i < checkedNodes.length; i++) {
        catIds.push(checkedNodes[i].catId)
        nodesName.push(checkedNodes[i].name)
      }
      this.$confirm(`是否批量删除「${nodesName}」?`, '提示', {
        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({
            type: 'success',
            message: ' 菜单批量删除成功!'
          })
          this.getMenus()
        })
      }).catch(() => {
        this.$message({
          type: 'info',
          message: '已取消删除'
        })
      })
    },
    handleDrop (draggingNode, dropNode, dropType, ev) {
      console.log('handleDrop: ', draggingNode, dropNode, dropType, ev)
      // 1.当前节点的父节点 Id
      let pCid = 0
      let sibling = null
      if (dropType === 'before' || dropType === 'after') {
        pCid = dropNode.parent.data.catId === undefined ? 0 : dropNode.parent.data.catId
        sibling = dropNode.parent.childNodes
      } else {
        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) {
          let catLevel = draggingNode.level
          // 如果层级发生变化
          if (sibling[i].level !== draggingNode.level) {
            // 拿到最新层级
            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})
        }
      }
      // 3.当前拖拽的节点的最新层级
      console.log(this.updateNodes)
    },
    updateChildNodeLevel (node) {
      if (node.childNodes.length > 0) {
        for (let i = 0; i < node.childNodes.length; i++) {
          var cNodes = node.childNodes[i].data
          this.updateNodes.push({catId: cNodes.catId, catLevel: node.childNodes[i].level})
          this.updateChildNodeLevel(node.childNodes[i])
        }
      }
    },
    allowDrop (draggingNode, dropNode, type) {
      // 1.判断被拖动的节点以及所在的父节点层级不能 > 3
      // 1)被拖动的节点的总层数
      console.log('当前节点,xxx,节点所在位置', draggingNode, dropNode, type)
      // 2)求出当前节点的总层数
      this.countNodeLevel(draggingNode)
      // let deep = (this.maxLeve - draggingNode.data.catLevel) + 1
      let deep = Math.abs(this.maxLeve - draggingNode.level) + 1
      console.log('深度', deep)
      // 3)当前正在拖动的父节点不大于3 即可
      if (type === 'inner') {
        return deep + draggingNode.level <= 3
      } else {
        return deep + draggingNode.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.maxLeve) {
            this.maxLeve = node.childNodes[i].level
          }
          this.countNodeLevel(node.childNodes[i])
        }
      }
    },
    append (data) {
      console.log('append', data)
      this.dialogType = 'add'
      this.title = '添加分类'
      this.category.parentCid = data.catId
      this.category.catLevel = data.catLevel * 1 + 1
      // 清空以及添加默认及需要新增的字段
      this.category.catId = null
      this.category.name = ''
      this.category.showStatus = 1
      this.category.productUnit = ''
      this.category.icon = ''
      this.category.sort = 0
      // 显示弹出框
      this.dialogVisible = true
    },
    // 添加三级分类
    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({
          type: 'success',
          message: '菜单保存成功!'
        })
        this.dialogVisible = false
        this.getMenus()
        // 设置默认展开的菜单
        this.expandedKey = [this.category.parentCid]
      })
    },
    edit (data) {
      console.log('要修改的数据:', data)
      this.dialogType = 'edit'
      this.title = '修改分类'
      this.dialogVisible = true
      // 这样写有这样的一个问题:就是当我需要修改时,我不会刷新页面再新增,
      // 比如10分钟之后我再修改,但是在这期间已经有其他人修改了,但是我页面上还是显示的是10分钟之前的数据
      // 发送请求获取最新的分类信息
      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
      })
    },
    editCategory () {
      console.log('提交三级分类的数据:', this.category)
      var {catId, name, icon, productUnit} = this.category
      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: '菜单修改成功!'
        })
        // 关闭对话框
        this.dialogVisible = false
        // 刷新 刷新树状结构
        this.getMenus()
        // 设置默认展开的菜单
        this.expandedKey = [this.category.parentCid]
      })
    },
    submitData () {
      if (this.dialogType === 'add') {
        this.addCategory()
      }
      if (this.dialogType === 'edit') {
        this.editCategory()
      }
    },
    remove (node, data) {
      console.log('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}) => {
          // console.log('删除成功')
          this.$message({
            type: 'success',
            message: '菜单删除成功!'
          })
          // 刷新新的菜单
          this.getMenus()
          // 设置默认展开的菜单
          this.expandedKey = [node.parent.data.catId]
        })
      }).catch(() => {
        this.$message({
          type: 'info',
          message: '已取消删除'
        })
      })
    }
  },
  // 生命周期 - 创建完成(可以访问当前this 实例)
  created () {
    this.getMenus()
  },
  // 生命周期 - 挂载完成(可以访问 DOM 元素)
  mounted () {
  },
  beforeCreate () {
  },
  beforeMount () {
  }, // 生命周期 - 挂载之前
  beforeUpdate () {
  }, // 生命周期 - 更新之前
  updated () {
  }, // 生命周期 - 更新之后
  beforeDestroy () {
  }, // 生命周期 - 销毁之前
  destroyed () {
  }, // 生命周期 - 销毁完成
  activated () {
  } // 如果页面有 keep-alive 缓存功能,这个函数会触发
}
</script>

<style scoped>
</style>

1.5 后端代码,树形结构增删改查

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

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


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

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

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

        return R.ok();
    }

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

        return R.ok();
    }

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

        return R.ok();
    }

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

        // 1.检查当前删除的菜单,是否在别的地方引用
//		categoryService.removeByIds(Arrays.asList(catIds));

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

        return R.ok();
    }

}
  • service
public interface CategoryService extends IService<CategoryEntity> {

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

    List<CategoryEntity> listWithTree();

    void removeMenuByIds(List<Long> asList);
}
@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

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

        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> listWithTree() {
        //1.查出所有分类
        List<CategoryEntity> entities = baseMapper.selectList(null);
        //2。组装成父子树状结构

        //2.1找到所有的一级分类
        List<CategoryEntity> level1Menu = entities.stream().filter(categoryEntity ->
                categoryEntity.getParentCid() == 0
        ).map(menu -> {
            //1。找到子分类
            menu.setChildren(getChildrens(menu, entities));
            return menu;
        }).sorted((menu1, menu2) ->
                //2。排序
                (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort())
        ).collect(Collectors.toList());
        return level1Menu;
    }

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

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

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

        List<CategoryEntity> children = all.stream().filter((categoryEntity) -> {
            return categoryEntity.getParentCid() == root.getCatId();
        }).map(categoryEntity -> {
            //1。找到子分类
            categoryEntity.setChildren(getChildrens(categoryEntity, all));
            return categoryEntity;
        }).sorted((menu1, menu2) ->
                //2。排序
                (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort())
        ).collect(Collectors.toList());

        return children;
    }

}
  • entity
@Data
@TableName("pms_category")
public class CategoryEntity implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * 分类id
	 */
	@TableId
	private Long catId;
	/**
	 * 分类名称
	 */
	private String name;
	/**
	 * 父分类id
	 */
	private Long parentCid;
	/**
	 * 层级
	 */
	private Integer catLevel;
	/**
	 * 是否显示[0-不显示,1显示]
	 */
	@TableLogic(value = "1",delval = "0")
	private Integer showStatus;
	/**
	 * 排序
	 */
	private Integer sort;
	/**
	 * 图标地址
	 */
	private String icon;
	/**
	 * 计量单位
	 */
	private String productUnit;
	/**
	 * 商品数量
	 */
	private Integer productCount;

	/**
	 * 菜单子分类
	 */
	@TableField(exist = false)
	private List<CategoryEntity> children;

}
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-05-06 10:55:43  更:2022-05-06 10:58:15 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 0:17:24-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码