谷粒商城——商品服务-三级分类

news2025/1/24 5:47:04

1.商品服务-三级分类

1.1三级分类介绍

1.2查询三级分类查询-递归树型结构数据获取

1.2.1导入数据pms_catelog.sql到数据表pms_category

1.2.2一次性查出所有分类及子分类

1.2.2.1修改CategoryController.java
    /**
     * 查出所有分类以及子分类,以树形结构组装起来
     */
    @RequestMapping("/list/tree")
    public R list(){
        List<CategoryEntity> entities = categoryService.listWithTree();
 
        return R.ok().put("data", entities);
    }
1.2.2.2CategoryEntity新增子分类属性
	@TableField(exist = false) //表示数据库表中不存在
	private List<CategoryEntity> children;
1.2.2.3实现接口CategoryService.java的listwithTree()
	@Override
    public List<CategoryEntity> listWithTree() {
 
        //1、查出所有分类。baseMapper来自于继承的ServiceImpl<>类,跟CategoryDao一样用法
        List<CategoryEntity> entities = baseMapper.selectList(null);
 
        //2、递归组装多级分类的树形结构。先过滤得到一级分类,再加工递归设置一级分类的子孙分类,再排序,再收集
        List<CategoryEntity> level1Menus = entities.stream()
                .filter(categoryEntity -> categoryEntity.getParentCid() == 0)
                .map((menu)->{
                    // 设置一级分类的子分类
                    menu.setChildren(getChildren(menu, entities));
                    return menu;
                }).sorted((menu1, menu2) -> {
                    //排序,sort是实体类的排序属性,值越小优先级越高,要判断非空防止空指针异常
                    return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
                })
                .collect(Collectors.toList());
 
 
        return level1Menus;
    }
 
    //递归查找所有菜单的子菜单
    private List<CategoryEntity> getChildren(CategoryEntity root, List<CategoryEntity> all){
        List<CategoryEntity> children = all.stream()
                .filter(CategoryEntity -> CategoryEntity.getParentCid().equals(root.getCatId()))
                .map(categoryEntity -> {
                    //1、递归查找子菜单
                    categoryEntity.setChildren(getChildren(categoryEntity, all));
                    return categoryEntity;
                })
                .sorted((menu1, menu2) -> {
                    //2、菜单排序、判空处理空指针异常
                    return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
                })
                .collect(Collectors.toList());
        return children;
    }

1.2.2.4启动product服务测试结果

打开F12访问:localhost:10000/product/category/list/tree

1.3后台页面管理三级分类

1.启动后台管理系统renren-fast

2.启动前端renren-fast-vue;终端输入命令:npm run dev

 1.3.1新增目录-商品系统

系统管理中的菜单管理:点击新增按钮

点击确定后刷新页面可见

数据库中可查到

 1.3.2新增菜单-分类维护

1.3.3前端展示三级分类

需求:在左侧点击【商品系统-分类维护】,希望在此展示3级分类。可以看到

url是http://localhost:8001/#/product-category

填写的菜单路由是product/category

对应的视图是src/view/modules/product/category.vue  (renren-fast-vue文件)

 1.3.3.1创建product/category视图

1.创建src/views/mudules/product/category.vue

2.在category.vue中创建vue模板 (输入vue加回车,可快速生成模板。)

3.elementui看如何使用多级目录

Element组件网址

进入Element官网,进入组件,找到Tree树形控件

模仿用法写入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: [],
        defaultProps: {
          children: 'children',
          label: 'label'
        }
      };
    },
    methods: {
      handleNodeClick(data) {
        console.log(data);
      },
      //获取后台数据
      getMenus(){
          this.$http({
          url: this.$http.adornUrl('/product/category/list/tree'),
          method: 'get'
        }).then(data=>{
            console.log("成功了获取到菜单数据....", data)
        })
      }
    },
//监听属性 类似于data概念
computed: {},
//监控data中的数据变化
watch: {},
//生命周期 - 创建完成(可以访问当前this实例)
created() {
    //创建完成时,就调用getMenus函数
    this.getMenus();
},
//生命周期 - 挂载完成(可以访问DOM元素)
mounted() {
 
},
beforeCreate() {}, //生命周期 - 创建之前
beforeMount() {}, //生命周期 - 挂载之前
beforeUpdate() {}, //生命周期 - 更新之前
updated() {}, //生命周期 - 更新之后
beforeDestroy() {}, //生命周期 - 销毁之前
destroyed() {}, //生命周期 - 销毁完成
activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
}
</script>
<style scoped>
 
</style>
1.3.3.2测试

localhost:8001/#/product-category

 F12刷新页面

 

发现404请求端口问题: 

他是给8080端口发的请求,而我们的商品服务在10000端口。我们以后还会同时发向更多的端口,所以需要配置网关,前端只向网关发送请求,然后由网关自己路由到相应端口。

1.3.3.3查看在哪定义的请求路径

复制 http://localhost:8080/renren-fasthttp://localhost:8080/renren-fast

ctrl + shift + f 全局搜索

1.3.3.4修改请求到网关

请求地址修改为

刷新测试

刷新,发现验证码出不来。

验证码请求路径问题:

分析原因:前端给网关发验证码请求,但是验证码请求在renren-fast服务里,所以要想使验证码好使,需要把renren-fast服务注册到服务中心,并且由网关进行路由

1.3.3.5renren-fast注册到nacos

renren-fast修改pom文件依赖gulimall-common

修改renren-fast配置文件application.yml

spring:
  application:
    name: renren-fast
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

启动类添加注解 @EnableDiscoveryClient

1.3.3.6配置网关

需求: http://localhost:88/api/xxx 转发--> http://renren-fast:8080/renren-fast/xxx

例如:http://localhost:88/api/captcha.jpg   转发-->  http://renren-fast:8080/renren-fast/captcha.jpg

修改gulimall-gateway配置文件application.yml

- id: admin_route
  uri: lb://renren-fast #负载均衡
  predicates:
    -Path=/api/**       #断言
##前端项目,/api
## http://localhost:88/api/captcha.jpg
## http://renren-fast:8080/api/captcha.jpg

测试验证发现请求地址为:http://renren-fast:88/api/captcha.jpg

再次修改gulimall-gateway配置文件application.yml,添加下列配置

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    gateway:
      routes:
# 路由id,自定义,只要唯一即可
        - id: admin_route
# uri路由的目标地址。lb就是负载均衡,后面跟服务名称。
          uri: lb://renren-fast
          #断言工厂的Path,请求路径必须符合指定规则
          predicates:
            - Path=/api/**    # 把所有api开头的请求都转发给renren-fast
          #局部过滤器。回顾默认过滤器default-filters是与routes同级
          filters:
#路径重写。逗号左边是原路径,右边是重写后的路径
            - RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}
            # 默认规则, 请求过来:http://localhost:88/api/captcha.jpg   转发-->  http://renren-fast:8080/renren-fast/captcha.jpg

刷新测试,出现跨域错误

1.4解决跨域问题

1.4.1跨域和同源策略

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

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

1.4.2跨域流程

预检请求:options

1.4.3解决跨域方法1:使用nginx反向代理为同一域

1.4.4解决跨域方法2:配置当前请求允许跨域

CORS:CORS 是一个 W3C 标准,全称是“跨域资源共享”(Cross-origin resource sharing)。它允许浏览器向跨域的服务器,发出XMLHttpRequest请求,从而克服了 AJAX 只能同源使用的限制。 

Access-Control-Allow-Origin : 支持哪些来源的请求跨域
Access-Control-Allow-Method : 支持那些方法跨域
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 :表明该响应的有效时间为多少秒。在有效时间内,浏览器无须为同一请求再次发起预检请求。请注意,浏览器自身维护了一个最大有效时间,如果该首部字段的值超过了最大有效时间,将失效。

在网关中统一配置

在gulimall-gateway的gulimall.gateway.config包下中新建配置类

//这个包别导错了,有一个很像的。
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
@Configuration
public class GulimallCorsConfiguration{
 
    @Bean
    public CorsWebFilter corsWebFilter(){
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
 
        CorsConfiguration corsConfiguration= new CorsConfiguration();
        //1、配置跨域
        // 允许跨域的请求头
        corsConfiguration.addAllowedHeader("*");
        // 允许跨域的请求方式
        corsConfiguration.addAllowedMethod("*");
        // 允许跨域的请求来源
        corsConfiguration.addAllowedOriginPattern("*");
//注释的这句会报错。因为当allowCredentials为真时,allowedorigin不能包含特殊值"*",因为不能在"访问-控制-起源“响应头中设置该值。
        //corsConfiguration.addAllowedOrigin("*");//这句会报错
        // 是否允许携带cookie跨域
        corsConfiguration.setAllowCredentials(true);
 
        // 任意url都要进行跨域配置,两个*号就是可以匹配包含0到多个/的路径
        source.registerCorsConfiguration("/**",corsConfiguration);
        return new CorsWebFilter(source);
 
    }
}

启动测试

发现有多个值错误

注释掉renren-fast中的跨域,不然会有一些重复的规则导致跨域失败:

io.renren/config/CorsConfig 

重启测试成功

1.5前端树形展示三级分类数据

1.5.1网关配置product路由

需求:

localhost:88/api/product/xx        ->        localhost:10000/product/xx

网关路由配置

       # 精确的路由要放在/api的admin_route上面
       - id: product_route
         uri: lb://gulimall-product        #路由的目标地址
         predicates:                    # 路由断言。也就是判断请求是否符合路由规则的条件。
           - Path=/api/product/**        # 路径断言。这个是按照路径匹配,只要以/api/product/开头就符合要求
         filters:        #局部过滤器
           - RewritePath=/api/(?<segment>.*),/$\{segment}    #重写路径,/api/xx过滤成/xx

1.5.2将product服务注册并配置到nacos

1.新建bootstrap.properties

2.nacos新建命名空间

3.修改application.yml注册到注册中心

4.启动类添加注解@EnableDiscoveryClient开启服务注册发现功能

 

启动测试数据显示如下

1.5.3修改前端组件category.vue

需求:将显示的数据展示到页面上

 想要的数据在data.data里,需要将其解构出来

结构代码修改如下

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: 'children', //子节点
          label: 'name' //name属性作为标签的值,展示出来
        }
      };
    },
    methods: {
      handleNodeClick(data) {
        console.log(data);
      },
      getMenus(){
          this.$http({
          url: this.$http.adornUrl('/product/category/list/tree'),
          method: 'get'
        }).then(({data})=>{
            console.log("成功了获取到菜单数据....", data.data)
            this.menus = data.data;
        })
      }
    },
//监听属性 类似于data概念
computed: {},
//监控data中的数据变化
watch: {},
//生命周期 - 创建完成(可以访问当前this实例)
created() {
    this.getMenus();
},
//生命周期 - 挂载完成(可以访问DOM元素)
mounted() {
 
},
beforeCreate() {}, //生命周期 - 创建之前
beforeMount() {}, //生命周期 - 挂载之前
beforeUpdate() {}, //生命周期 - 更新之前
updated() {}, //生命周期 - 更新之后
beforeDestroy() {}, //生命周期 - 销毁之前
destroyed() {}, //生命周期 - 销毁完成
activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
}
</script>
<style scoped>
 
</style>

启动product测试

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

1.6逻辑删除三级分类

1.6.1分类的新增和删除

需求:

  • 在每一个菜单后面添加append, delete
  • 点击按钮时,不进行菜单的打开合并,仅点击箭头时展示子分类:expand-on-click-node="false"
  • 当没有子菜单时,才可以显示delete按钮;当为一级、二级菜单时,才显示append按钮。使用v-if判断是否显示
  • <el-tree>添加多选框show-checkbox
  • 设置node-key=""标识每一个节点的不同
<!--  -->
<template>
  <el-tree
    :data="menus"
    show-checkbox
    :props="defaultProps"
    @node-click="handleNodeClick"
    :expand-on-click-node="false"
    node-key="catId"
  >
    <span class="custom-tree-node" slot-scope="{ node, data }">
      <span>{
  
  { node.label }}</span>
      <span>
        <el-button
          type="text"
          v-if="node.level <= 2"
          size="mini"
          @click="() => append(data)"
        >
          Append
        </el-button>
        <el-button
          type="text"
          v-if="node.childNodes.length == 0"
          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: "children", //子节点
        label: "name", //name属性作为标签的值,展示出来
      },
    };
  },
  methods: {
    handleNodeClick(data) {},
    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("remove", node, data);
    },
  },
  //监听属性 类似于data概念
  computed: {},
  //监控data中的数据变化
  watch: {},
  //生命周期 - 创建完成(可以访问当前this实例)
  created() {
    this.getMenus();
  },
  //生命周期 - 挂载完成(可以访问DOM元素)
  mounted() {},
  beforeCreate() {}, //生命周期 - 创建之前
  beforeMount() {}, //生命周期 - 挂载之前
  beforeUpdate() {}, //生命周期 - 更新之前
  updated() {}, //生命周期 - 更新之后
  beforeDestroy() {}, //生命周期 - 销毁之前
  destroyed() {}, //生命周期 - 销毁完成
  activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发
};
</script>
<style scoped>
</style>

1.6.2后端:逻辑删除

实体类成员变量加上@TableLogic(value = "0", delval = "1")注解

 CategoryEntity注解逻辑删除

	/**
	 * 是否显示[0-不显示,1显示]
     *本项目使用的是category表的show_status字段,逻辑删除值正好相反 
     *状态为1表示未删除,状态为0表示删除。
	 */
	@TableLogic(value = "1",delval = "0")
	private Integer showStatus;

 修改CategoryController.java

/**
删除
@RequestBody:获取请求体,必须发送POST请求
*SpringMvc自动将请求体的数据(json),转为对应的对象
*/
@RequestMapping("/delete")
public R delete(@RequestBody Long[] catIds){
//1、检查当前删除的菜单,是否被别的地方引用
categoryService.removeMenuByIds(Arrays.asList(catId));
return R.ok();
}

 实现类CategoryServicelmpl.java实现CategoryService.java接口方法(Ctrl+N快速检索)

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

修改日志级别

logging:
  level:
    com.xmh.guliamll.product: debug

在测试工具发送POST请求 

http://localhost:88/api/product/category/delete

查看控制台打印语句,发现是update操作

1.6.3前端:逻辑删除

需求:

  • 编写前端remove方法,实现向后端发送请求
  • 点击delete弹出提示框,是否删除这个节点: elementui中MessageBox 弹框中的确认消息添加到删除之前
  • 删除成功后有消息提示: elementui中Message 消息提示
  • 删除后刷新页面后,分类应该保持之前展开状态: el-tree组件的default-expanded-keys属性,默认展开。 每次删除之后,把删除菜单的父菜单的id值赋给默认展开值即可。

1.6.3.1创建请求代码块快捷命令

文件->首选项->用户片段->新建全局代码片段,文件名vue.code-snippets(snippets译为代码片段,片段)

 

    "http-get请求": {
        "prefix": "httpget",
        "body":[
            "this.\\$http({",
            "url: this.\\$http.adornUrl(''),",
            "method:'get',",
            "params:this.\\$http.adornParams({})",
            "}).then(({data})=>{",
            "})"
        ],
        "description":"httpGET请求"
    },
 
    "http-post请求":{
        "prefix":"httppost",
        "body":[
            "this.\\$http({",
            "url:this.\\$http.adornUrl(''),",
            "method:'post',",
            "data: this.\\$http.adornData(data, false)",
            "}).then(({data})=>{ })"
        ],
        "description":"httpPOST请求"
    }
//在<el-tree>中设置默认展开属性,绑定给expandedKey
:default-expanded-keys="expandedKey"
 
//data中添加属性,删除后给它赋值父节点id,令树形控件刷新后展开
expandedKey: [],
 
//完整的remove方法
    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();
            //设置需要默认展开的菜单。删除后给它赋值父节点id,令树形控件刷新后展开
            this.expandedKey = [node.parent.data.catId]
          });
        })
        .catch(() => {});
    },

1.7新增三级分类

1.7.1前端:新增

需求:

  • 点击append新增弹出对话框,输入分类名称
  • 确定/取消后,关闭对话框
  • 确定后发送post请求,成功后刷新前端展示页面

 创建对话框组件el-dialog:

对话框标签el-dialog放在el-tree标签上下都是可以的,主要是visible.sync属性控制对话框的显示和隐藏

<!--对话框组件-->
	<el-dialog title="提示" :visible.sync="dialogVisible" width="30%">
      <el-form :model="categroy">
        <el-form-item label="分类名称">
          <el-input v-model="categroy.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>
//data中新增数据
//按照数据库格式声明的数据。注意category属性用来接收输入框的参数,需要赋值默认属性,包括父id,层级、展示状态为1,排序值是0
      categroy: { name: "", parentCid: 0, catLevel: 0, showStatus: 1, sort: 0 },
//判断是否显示对话框
      dialogVisible: false,
 
          
//修改append方法,新增addCategory方法
//点击append后,计算category属性,显示对话框
    append(data) {
      console.log("append", data);
      this.dialogVisible = true;
      this.categroy.parentCid = data.catId;
      this.categroy.catLevel = data.catLevel * 1 + 1;
    },
 
//点击确定后,发送post请求
//成功后显示添加成功,展开刚才的菜单
    addCategory() {
      console.log("提交的数据", this.categroy);
      this.$http({
        url: this.$http.adornUrl("/product/category/save"),
        method: "post",
        data: this.$http.adornData(this.categroy, false),
      }).then(({ data }) => {
          this.$message({
              message: "添加成功",
              type: "success",
            });
            //刷新出新的菜单
            this.getMenus();
            //设置需要默认展开的菜单
            this.expandedKey = [this.categroy.parentCid];
            this.dialogVisible = false;
      });

1.8修改三级分类

1.8.1后端:修改

后端,修改“回显方法”结果对象的键为"data"

product模块的CategoryController

1.8.2前端:修改

需求:

  1. 新增Edit按钮:复制之前的append
  2. updata方法是由id进行更新的,所以data中的category中新增catId
  3. 增加、修改的同时修改图标和计量单位,所以data的category新增inco,productUnit
  4. 新建edit方法,用来绑定Edit按钮。新建editCategory方法,用来绑定对话框的确定按钮
  5. 复用对话框(新增、修改)
  6. 新建方法submitData,与对话框的确定按钮进行绑定,在方法中判断,如果dialogType==add调用addCategory(),如果dialogType==edit调用editCategory()
  7. data数据中新增title,绑定对话框的title,用来做提示信息。判断dialogType的值,动态提示信息
  8. 修改回显必须发请求,而非直接从实参中获取。防止多个人同时操作,对话框中的回显的信息应该是由数据库中读出来的:点击Edit按钮,发送httpget请求。
  9. 成功之后发送提示消息,展开刚才的菜单
  10. 编辑之后,再点击添加,发现会回显刚才编辑的信息。所以在append方法中重置回显的信息

新增修改共享表单对话框,所以还要修改添加方法,第一步初始化数据

提交修改表单时,不能像新增一样把携带初始化值的category直接提交上去

方法一:局部更新,只解构出表单里的属性封装成对象,再提交(推荐

方法二:全量更新,回显时,把其他数据库字段也赋值

<!--编辑按钮-->
		  <el-button type="text" size="mini" @click="() => edit(data)">
            Edit
          </el-button>
 
<!--可复用的对话框-->
	<el-dialog :title="title" :visible.sync="dialogVisible" width="30%">
      <el-form :model="categroy">
        <el-form-item label="分类名称">
          <el-input v-model="categroy.name" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="图标">
          <el-input v-model="categroy.inco" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="计量单位">
          <el-input
            v-model="categroy.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>
//data, 新增了title、dialogType。 categroy中新增了inco、productUnit、catId
  data() {
    return {
      title: "",
      dialogType: "",
      categroy: {
        name: "",
        parentCid: 0,
        catLevel: 0,
        showStatus: 1,
        sort: 0,
        inco: "",
        productUnit: "",
        catId: null,
      },
      dialogVisible: false,
      menus: [],
      expandedKey: [],
      defaultProps: {
        children: "children", //子节点
        label: "name", //name属性作为标签的值,展示出来
      },
    };
      
 
//方法
     //绑定对话框的确定按钮,根据dialogType判断调用哪个函数
    submitData() {
      if (this.dialogType == "add") {
        this.addCategory();
      }
      if (this.dialogType == "edit") {
        this.editCategory();
      }
    },
        
        //绑定Edit按钮,设置dialogType、title,从后台读取数据,展示到对话框内
    edit(data) {
      this.dialogType = "edit";
      this.title = "修改菜单";
      this.dialogVisible = true;
      this.$http({
        url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
        method: "get",
      }).then(({ data }) => {
        console.log(data);
        this.categroy.catId = data.data.catId;
        this.categroy.name = data.data.name;
        this.categroy.inco = data.data.inco;
        this.categroy.productUnit = data.data.productUnit;
      });
    },
        
        //绑定对话框的确定按钮,向后台发送更新请求,传过去想要修改的字段
    editCategory() {
      var { catId, name, inco, productUnit } = this.categroy;
      this.$http({
        url: this.$http.adornUrl("/product/category/update"),
        method: "post",
        data: this.$http.adornData({ catId, name, inco, productUnit }, false),
      }).then(({ data }) => {
        this.$message({
          message: "修改成功",
          type: "success",
        });
        //刷新出新的菜单
        this.getMenus();
        //设置需要默认展开的菜单
        this.expandedKey = [this.categroy.parentCid];
        this.dialogVisible = false;
      });
    },
    
    //点击append按钮,清空编辑之后的回显数据
    append(data) {
      this.dialogType = "add";
      this.title = "添加菜单";
      console.log("append", data);
      this.dialogVisible = true;
      this.categroy.parentCid = data.catId;
      this.categroy.catLevel = data.catLevel * 1 + 1;
      this.categroy.name = "",
      this.categroy.inco = "",
      this.categroy.productUnit = ""
    },

1.9修改层级关系,实现拖拽效果

1.9.1前端:拖拽

实现逻辑:

1.<el-tree>中加入属性draggable表示节点可拖拽

2.在<el-tree>中加入属性:allow-drop="allowDrop",拖拽时判定目标节点能否被放置。

3.allowDrop有三个参数draggingNode表示拖拽的节点,dropNode表示拖拽到哪个节点,type表示拖拽的类型’prev’、‘inner’ 和 ‘next’,表示拖拽到目标节点之前、里面、之后

注意:函数实现判断,拖拽后必须保持数型的三层结构。

  • 节点的深度 = 最深深度 - 当前深度 + 1
  • 当拖拽节点拖拽到目标节点的内部,要满足: 拖拽节点的深度 + 目标节点的深度 <= 3
  • 当拖拽节点拖拽的目标节点的两侧,要满足: 拖拽节点的深度 + 目标节点的父节点的深度 <= 3
<!--el-tree中添加属性-->
	   draggable
      :allow-drop="allowDrop"
// data中新增属性,用来记录当前节点的最大深度
maxLevel: 1,
    
    
//新增方法
    allowDrop(draggingNode, dropNode, type) {
      console.log("allowDrag:", draggingNode, dropNode, type);
      //节点的最大深度
      this.countNodeLevel(draggingNode.data);
      console.log("level:", this.maxLevel);
      //当前节点的深度
      let deep = (this.maxLevel - draggingNode.data.catLevel) + 1;
      console.log(deep)
      if (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]);
            }
        }
    },
拖拽后的数据收集
  • 在<el-tree>中加入属性@node-drop="handleDrop",表示拖拽事件结束后触发事件handleDrop,handleDrop共四个参数,draggingNode:被拖拽节点对应的 Node; dropNode:结束拖拽时最后进入的节点; dropType:被拖拽节点的放置位置(before、after、inner);ev:event
  • 拖拽可能影响的节点的数据:parentCid、catLevel、sort
  • data中新增updateNodes,把所有要修改的节点都传进来。
  • 要修改的数据:拖拽节点的parentCid、catLevel、sort
  • 要修改的数据:新的兄弟节点的sort (把新的节点收集起来,然后重新排序)
  • 要修改的数据:子节点的catLevel

 

//el-tree中新增属性,绑定handleDrop,表示拖拽完触发
@node-drop="handleDrop"
 
//data 中新增数据,用来记录需要更新的节点(拖拽的节点(parentCid、catLevel、sort),拖拽后的兄弟节点(sort),拖拽节点的子节点(catLevel))
updateNodes: [],
    
 
//新增方法
    handleDrop(draggingNode, dropNode, dropType, ev) {
      console.log("handleDrop: ", draggingNode, dropNode, dropType);
      //1、当前节点最新父节点的id
      let pCid = 0;
      //拖拽后的兄弟节点,分两种情况,一种是拖拽到两侧,一种是拖拽到内部
      let sibings = null;
      if (dropType == "before" || dropType == "after") {
        pCid = dropNode.parent.data.catId == undefined ? 0: dropNode.parent.data.catId;
        sibings = dropNode.parent.childNodes;
      } else {
        pCid = dropNode.data.catId;
        sibings = dropNode.childNodes;
      }
 
      //2、当前拖拽节点的最新顺序
      //遍历所有的兄弟节点,如果是拖拽节点,传入(catId,sort,parentCid,catLevel),如果是兄弟节点传入(catId,sort)
      for (let i = 0; i < sibings.length; i++) {
          if (sibings[i].data.catId == draggingNode.data.catId){
              //如果遍历的是当前正在拖拽的节点
              let catLevel = draggingNode.level;
              if (sibings[i].level != draggingNode.level){
                  //当前节点的层级发生变化
                  catLevel = sibings[i].level;
                  //修改他子节点的层级
                  this.updateChildNodeLevel(sibings[i]);
              }
              this.updateNodes.push({catId:sibings[i].data.catId, sort: i, parentCid: pCid, catLevel:catLevel});
          }else{
              this.updateNodes.push({catId:sibings[i].data.catId, sort: i});
          }
          
      }
    
    
    
    //每次拖拽后把数据清空,否则要修改的节点将会越拖越多
    this.updateNodes = [],
    this.maxLevel = 1,
    }
 
// 修改拖拽节点的子节点的层级
updateChildNodeLevel(node){
        if (node.childNodes.length > 0){
            for (let i = 0; i < node.childNodes.length; i++){
                //遍历子节点,传入(catId,catLevel)
                var cNode = node.childNodes[i].data;
                this.updateNodes.push({catId:cNode.catId,catLevel:node.childNodes[i].level});
                //处理子节点的子节点
                this.updateChildNodeLevel(node.childNodes[i]);
            }
        }
    },
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];
      });

1.9.2批量拖拽功能

  • 添加开关,控制拖拽功能是否开启
  • 每次拖拽都要和数据库交互,不合理。批量拖拽过后,一次性保存
<!--添加拖拽开关和批量保存按钮-->
	<el-switch
      v-model="draggable"
      active-text="开启拖拽"
      inactive-text="关闭拖拽"
    >
    </el-switch>
    <el-button v-if="draggable" size="small" round @click="batchSave"
      >批量保存</el-button
    >
//data中新增数据
 pCid:[], //批量保存过后要展开的菜单id
 draggable: false, //绑定拖拽开关是否打开
  
 
//修改了一些方法,修复bug,修改过的方法都贴在下面了
     
//点击批量保存按钮,发送请求
    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.expandedKey = this.pCid;
      });
 
      this.updateNodes = [];
    },
  
        
//
    handleDrop(draggingNode, dropNode, dropType, ev) {
      console.log("handleDrop: ", draggingNode, dropNode, dropType);
      //1、当前节点最新父节点的id
      let pCid = 0;
      let sibings = null;
      if (dropType == "before" || dropType == "after") {
        pCid =
          dropNode.parent.data.catId == undefined
            ? 0
            : dropNode.parent.data.catId;
        sibings = dropNode.parent.childNodes;
      } else {
        pCid = dropNode.data.catId;
        sibings = dropNode.childNodes;
      }
 
      //2、当前拖拽节点的最新顺序
      for (let i = 0; i < sibings.length; i++) {
        if (sibings[i].data.catId == draggingNode.data.catId) {
          //如果遍历的是当前正在拖拽的节点
          let catLevel = draggingNode.level;
          if (sibings[i].level != draggingNode.level) {
            //当前节点的层级发生变化
            catLevel = sibings[i].level;
            //修改他子节点的层级
            this.updateChildNodeLevel(sibings[i]);
          }
          this.updateNodes.push({
            catId: sibings[i].data.catId,
            sort: i,
            parentCid: pCid,
            catLevel: catLevel,
          });
        } else {
          this.updateNodes.push({ catId: sibings[i].data.catId, sort: i });
        }
      }
 
      this.pCid.push(pCid);
      console.log(this.pCid)
      //3、当前拖拽节点的最新层级
      //console.log("updateNodes", this.updateNodes)
      //拖拽之后重新置1
      this.maxLevel = 1;
    },
        
// 修改拖拽判断逻辑
   allowDrop(draggingNode, dropNode, type) {
      console.log("allowDrag:", draggingNode, dropNode, type);
      this.maxLevel = draggingNode.level;
      //节点的最大深度
      this.countNodeLevel(draggingNode);
      console.log("maxLevel:", this.maxLevel);
      //当前节点的深度
      let deep = Math.abs(this.maxLevel - draggingNode.level) + 1;
      console.log("level",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]);
        }
      }
    },

1.9.3后端:拖拽

思路:

  • 在后端编写批量修改的方法update/sort
  • 前端发送post请求,把要修改的数据发送过来
  • 提示信息,展开拖拽节点的父节点

 CategoryController批量修改功能

    //批量修改,参数要传数组,不能传list
	@RequestMapping("/update/sort")
    public R updateSort(@RequestBody CategoryEntity[] category){
        categoryService.updateBatchById(Arrays.asList(category));
        return R.ok();
    }

测试批量修改功能

http://localhost:88/api/product/category/update/sort

1.10批量删除分类

1.10.1后端:批量删除

1.10.2前端:批量删除

1.新增删除按钮

<el-button type="danger" size="small" @click="batchDelete" round>批量删除</el-button>
 
<!--eltree中新增属性,用作组件的唯一标示-->
ref="menuTree"

2.批量删除方法

batchDelete(){
      let catIds = [];
      let catNames = [];
      let checkedNodes = this.$refs.menuTree.getCheckedNodes();
      for (let i = 0; i < checkedNodes.length; i++){
          catIds.push(checkedNodes[i].catId);
          catNames.push(checkedNodes[i].name);
      }
      this.$confirm(`是否批量删除【${catNames}】菜单?`, "提示", {
        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(()=>{
 
      });
 
    },

1.11前端category.vue最终代码

<template>
  <div>
    <el-switch v-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>
    <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)"
          >新增</el-button>
          <el-button type="text" size="mini" @click="edit(data)">编辑</el-button>
          <el-button
            v-if="node.childNodes.length==0"
            type="text"
            size="mini"
            @click="() => remove(node, data)"
          >删除</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: [],
      maxLevel: 0,
      title: "",
      dialogType: "", //edit,add
      category: {
        name: "",
        parentCid: 0,
        catLevel: 0,
        showStatus: 1,
        sort: 0,
        productUnit: "",
        icon: "",
        catId: null
      },
      dialogVisible: false,
      menus: [],
      expandedKey: [],
      defaultProps: {
        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;
      });
    },
    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(() => {});
    },
    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.expandedKey = this.pCid;
        this.updateNodes = [];
        this.maxLevel = 0;
        // this.pCid = 0;
      });
    },
    handleDrop(draggingNode, dropNode, dropType, ev) {
      console.log("handleDrop: ", draggingNode, dropNode, dropType);
      //1、当前节点最新的父节点id
      let 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;
      }
      this.pCid.push(pCid);
 
      //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]);
        }
      }
    },
    allowDrop(draggingNode, dropNode, type) {
      //1、被拖动的当前节点以及所在的父节点总层数不能大于3
 
      //1)、被拖动的当前节点总层数
      console.log("allowDrop:", draggingNode, dropNode, type);
      //
      this.countNodeLevel(draggingNode);
      //当前正在拖动的节点+父节点所在的深度不大于3即可
      let deep = Math.abs(this.maxLevel - draggingNode.level) + 1;
      console.log("深度:", deep);
 
      //   this.maxLevel
      if (type == "inner") {
        // console.log(
        //   `this.maxLevel:${this.maxLevel};draggingNode.data.catLevel:${draggingNode.data.catLevel};dropNode.level:${dropNode.level}`
        // );
        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]);
        }
      }
    },
    edit(data) {
      console.log("要修改的数据", data);
      this.dialogType = "edit";
      this.title = "修改分类";
      this.dialogVisible = true;
 
      //发送请求获取当前节点最新的数据
      this.$http({
        url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
        method: "get"
      }).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;
        /**  
         *         parentCid: 0,
        catLevel: 0,
        showStatus: 1,
        sort: 0,
         */
      });
    },
    append(data) {
      console.log("append", data);
      this.dialogType = "add";
      this.title = "添加分类";
      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;
    },
 
    submitData() {
      if (this.dialogType == "add") {
        this.addCategory();
      }
      if (this.dialogType == "edit") {
        this.editCategory();
      }
    },
    //提交修改三级分类数据
    editCategory() {
      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({
          message: "菜单修改成功",
          type: "success"
        });
        //关闭对话框
        this.dialogVisible = false;
        //刷新出新的菜单
        this.getMenus();
        //设置需要默认展开的菜单
        this.expandedKey = [this.category.parentCid];
      });
    },
    //提交添加三级分类
    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("remove", node, data);
    }
  },
  //生命周期 - 创建完成(可以访问当前this实例)
  created() {
    this.getMenus();
  },
  //生命周期 - 挂载完成(可以访问DOM元素)
  mounted() {},
  beforeCreate() {}, //生命周期 - 创建之前
  beforeMount() {}, //生命周期 - 挂载之前
  beforeUpdate() {}, //生命周期 - 更新之前
  updated() {}, //生命周期 - 更新之后
  beforeDestroy() {}, //生命周期 - 销毁之前
  destroyed() {}, //生命周期 - 销毁完成
  activated() {} //如果页面有keep-alive缓存功能,这个函数会触发
};
</script>
<style scoped>
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2281241.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AviatorScript用法

AviatorScript的介绍&#xff0c;网上有很多&#xff0c;这里就不啰嗦了。这里介绍下传参的用法 应用场景&#xff1a;如果不想频繁的打包升级&#xff0c;而是想只更新某些规则脚本重启服务就可以升级的话&#xff0c;AviatorScript无疑是最佳选择。比如说&#xff0c;今天制…

云计算和服务器

一、云计算概述 ICT是世界电信协会在2001年的全球性会议上提出的综合性概念&#xff0c;ICT分为IT和CT&#xff0c;IT(information technology)信息技术&#xff0c;负责对数据生命周期的管理&#xff1b;CT(communication technology)&#xff0c;负责数据的传输管理。 CT技术…

论文:深度可分离神经网络存内计算处理芯片

引言&#xff1a;SRAM - CIM芯片在处理深度可分离神经网络时面临的挑战 深度可分离卷积&#xff08;Depthwise separable convolution, DSC&#xff09;由逐深度卷积&#xff08;DW&#xff09;和逐点卷积&#xff08;PW)组成&#xff0c;逐深度卷积用于提取空间特征&#xff…

代码随想录刷题day14(1)|(链表篇)142.环形链表 II

目录 一、链表理论基础 二、环形链表思路 1.如何判断有环&#xff1f; 2.如何找出环的入口&#xff1f; 3.其他疑问 三、相关算法题目 四、总结 一、链表理论基础 代码随想录 (programmercarl.com) 二、环形链表思路 1.如何判断有环&#xff1f; 使用快慢指针法&…

运算放大器应用电路设计笔记(六)

6.1输出失调电压发生的原因与计算 6.1.1用噪声增益进行评价 若运算放大器两个输入端接地&#xff0c;则理想运放输出为零&#xff0c;但实际的运放输出不为零&#xff0c;有一定的直流输出电压。这种直流电压称为输出失调电压。发生的原因是&#xff0c;运算放大器内部元件尤…

openresty(nginx)+lua+kafka实现日志搜集系统

今天我们来实现一下快捷的nginx日志搜集系统&#xff0c;主讲的是nginx服务里面的openresty的日志搜集。采用的手段是采用lua做中间桥梁。 一、安装openresty 具体安装步骤在这里《centos7 二进制安装openresty》 二、安装kafka 1、安装Java及配置Java 具体安装步骤在这里《采…

PortSwigger靶场练习---网页 LLM 攻击:间接提示注入

网页 LLM 攻击&#xff1a; Indirect prompt injection 间接提示注入 PortSwigger靶场地址&#xff1a; Dashboard | Web Security Academy - PortSwigger 题目&#xff1a; 官方提示&#xff1a; 发现攻击面 点击实时聊天以访问实验室的聊天功能。 询问LLM它有权访问哪些 AP…

【2024.12】西电英语听说雨课堂期末考试答案

前言 这次的英语听说1和2雨课堂期末是同一张卷子。 26-40为填空题&#xff0c;后面有些话太长了 37. when they are physicial active 38. people could need two times as much water as others do 39. why people have the idea that good health requires eight…

人声检测原理VAD

在机器人的研究中&#xff0c;机器人与人语音交互是一个重要的功能&#xff0c;在语音交互中&#xff0c;人声检测至关重要。不论是在手机中&#xff0c;还是在esp32芯片上&#xff0c;都需要一种简单快捷的方式来检测本地语音&#xff0c;滤掉杂音和噪音。 机器人启动后会一直…

2_高并发内存池_各层级的框架设计及ThreadCache(线程缓存)申请内存设计

一、高并发内存池框架设计 高并发池框架设计&#xff0c;特别是针对内存池的设计&#xff0c;需要充分考虑多线程环境下&#xff1a; 性能问题锁竞争问题内存碎片问题 高并发内存池的整体框架设计旨在提高内存的申请和释放效率&#xff0c;减少锁竞争和内存碎片。 高并发内存…

后端开发基础——JavaWeb(Servlet)

Servlet 关于系统架构 系统架构包括什么形式&#xff1f; C/S架构 B/S架构 C/S架构&#xff1f; Client / Server&#xff08;客户端 / 服务器&#xff09; C/S架构的软件或者说系统有哪些呢&#xff1f; QQ&#xff08;先去腾讯官网下载一个QQ软件&#xff0c;几十MB&…

c++ 与 Matlab 程序的数据比对

文章目录 背景环境数据保存数据加载 背景 ***避免数据精度误差&#xff0c;快速对比变量 *** 环境 c下载 https://github.com/BlueBrain/HighFive 以及hdf5库 在vs 中配置库 数据保存 #include <highfive/highfive.hpp> using namespace HighFive;std::string fil…

Leecode刷题C语言之收集所有金币可获得的最大积分

执行结果:通过 执行用时和内存消耗如下&#xff1a; int dfs(int node, int parent, int f, int* coins, int k, int **children, int *childCount, int **memo) {if (memo[node][f] ! -1) {return memo[node][f];}int res0 (coins[node] >> f) - k;int res1 coins[no…

mybatis(57/134)

今天没什么想法&#xff0c;搭了个转账平台&#xff0c;加深了点之前javaweb的mvc架构的印象&#xff0c;还有异常的抛出处理等

ONNX 简介

ONNX &#xff08;Open Neural Network Exchange&#xff09;是一套表示深度神经网络模型的开放格式&#xff0c;由微软和 Facebook 于 2017 推出&#xff0c;然后迅速得到了各大厂商和框架的支持。目前&#xff0c;在数家机构的共同维护下&#xff0c;ONNX 已经对接了多种深度…

Linux的中断上半部和中断下半部的概念,并利用任务队列(Tasklet)实现中断下半部的处理

中断上半部和中断下半部的介绍 在Linux内核中&#xff0c;中断处理机制被设计成“中断上半部&#xff08;Top Half&#xff09;”和“中断下半部&#xff08;Bottom Half&#xff09;”两个部分&#xff0c;这种设计主要目的是提高系统的中断响应效率&#xff0c;同时减少中断…

数学规划问题2 .有代码(非线性规划模型,最大最小化模型,多目标规划模型)

非线性规划模型 FIrst:转化为标准型 在matlab中求非线性规划的函数 练习题: 典型例题: 最大最小化模型 核心思想&#xff1a; matlab的模型求解 经典例题: 多目标规划模型 基本概念 求解思路: 模型构建步骤 经典例题: 非线性规划模型 非线性规划&#xff08;Nonl…

linux 下tensorrt的yolov8的前向推理(c++ 版本)的实现

一、环境搭建 cuda 11.4 ubuntu 20.04 opencv-4.5.2 1.1 配置tensorrt 根据本机的硬件配置及cuda的版本&#xff0c;选择TensorRT-8.6.1.6的版本&#xff0c;下载网址为: TensorRT SDK | NVIDIA Developer 根据官网的说明&#xff0c;下载对应的压缩包即可。解压后&…

VUE elTree 无子级 隐藏展开图标

这4个并没有下级节点&#xff0c;即它并不是叶子节点&#xff0c;就不需求展示前面的三角展开图标! 查阅官方文档如下描述&#xff0c;支持bool和函数回调处理&#xff0c;这里咱们选择更灵活的函数回调实现。 给el-tree结构配置一下props&#xff0c;注意&#xff01; :pr…

windows git bash 使用zsh 并集成 oh my zsh

参考了 这篇文章 进行配置&#xff0c;记录了自己的踩坑过程&#xff0c;并增加了 zsh-autosuggestions 插件的集成。 主要步骤&#xff1a; 1. git bash 这个就不说了&#xff0c;自己去网上下&#xff0c;windows 使用git时候 命令行基本都有它。 主要也是用它不方便&…