谷粒商城-基础篇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 {@Autowiredprivate 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@AutowiredCategoryDao categoryDao;@Overridepublic PageUtils queryPage(Map<String, Object> params) {IPage<CategoryEntity> page = this.page(new Query<CategoryEntity>().getPage(params),new QueryWrapper<CategoryEntity>());return new PageUtils(page);}// 实现方法@Overridepublic List<CategoryEntity> listWithTree() {// 1.查出所有分类List<CategoryEntity> entities = 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.结果
2、配置网关路由与路径重写
启动renren-fast 前后端项目:
-
点击系统管理,菜单管理,新增
刷新,看到左侧多了商品系统,添加的这个菜单其实是添加到了guli-admin.sys_menu表里. -
继续新增:分类维护菜单
在左侧点击【分类维护】,希望在此展示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。
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-fastpredicates: # 什么情况下路由给它- Path=/api/** # 默认前端项目都带上api前缀,就是我们前面的localhost:88/apifilters:- RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment} # 把/api/* 改变成 /renren-fast/*
3、网关统一配置跨域
- 跨域:指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对js施加的安全限制。(ajax可以)
- 同源策略:是指协议,域名,端囗都要相同,其中有一个不同都会产生跨域;
跨域流程:
解决跨域:
- 使用ngnix部署为同一域:设置nginx包含admin和gateway。都先请求nginx,这样端口就统一了。
- 配置当次请求允许跨域:让服务器告诉预检请求能跨域
在网关中定义“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异常。
2、解决方法就是定义一个product路由规则,以后/ap/product 的路径都转发给product服务,进行路径重写:
- (1)在gateway网关增加三级分类的路由
# product服务路由- id: product_routeuri: lb://gulimall-productpredicates:- Path=/api/product/** filters:- # 把/api/* 去掉,剩下的留下来- RewritePath=/api/(?<segment>.*),/$\{segment}
- (2)在nacos中新建命名空间(product),用命名空间隔离项目,(可以在其中新建gulimall-product.yml 抽取配置)
- (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子句中异常的处理顺序。
访问 http://localhost:88/api/product/category/list/tree 正常
访问 http://localhost:8001/#/product-category,也就是点击分类维护,正常,数据获取成功
原因是:先访问网关88,网关路径重写后访问nacos8848,通过nacos找到服务
3、前端渲染
5、删除
1、使用scoped slot(插槽)实现:在el-tree标签里把内容写到span标签栏里即可
- :expand-on-click-node=“false” :点击按钮的时候不展开
- show-checkbox :代表节点是否可被选择
- node-key=“catId” :节点唯一标识
<template><!-- :expand-on-click-node="false" 点击按钮的时候不展开 show-checkbox 代表节点是否可被选择 --><el-tree :data="menus" :props="defaultProps" :expand-on-click-node="false" show-checkbox node-key="catId"><!-- scoped slot 实现标签删除功能 --><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 }) => {// 解构出数据中有用的dataconsole.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 实现自定义的方法
@Overridepublic void removeMenuByIds(List<Long> asList) {//TODO 1、检查当前删除的菜单,是否被其它地方引用// 2、逻辑删除baseMapper.deleteBatchIds(asList);}
- mybatis-plus逻辑删除配置(可有可无)
mybatis-plus:mapper-locations: classpath:/mapper/**/*.xmlglobal-config:db-config:id-type: autologic-delete-value: 1 logic-not-delete-value: 0
- 修改product.entity.CategoryEntity实体类,添加上@TableLogic,表明使用逻辑删除
/*** 是否显示[0-不显示,1显示]*/@TableLogic(value = "1",delval = "0")private Integer showStatus;
3、apifox测试请求
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. 获取当前节点idvar 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、新增
- 点击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)},
- 对话框中添加表单
- 属性 <el-form :model=“数据对象”>
<!-- 对话框 --><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>
- 定义添加菜单的方法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的catIdthis.category.parentCid = data.catId;// 2.2 层级catLevel 当前点击append 的层级+1this.category.catLevel = data.catLevel * 1 + 1;},
7、修改
- 增加修改按钮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)},}
- 复用对话框
- 定义对话框类型: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, // 是否打开对话框,默认为falseexpandedKey: [], // 展开基准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设置对话框类型为deitthis.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. 设置对话框类型为appendthis.dialogType = "append";// 1. 点击append 按钮打开对话框this.dialogVisible = true;// 设置标题this.title = "添加分类";// 2. 点击按钮为category获取默认值// 2.1 父id,当前点击append的catIdthis.category.parentCid = data.catId;// 2.2 层级catLevel 当前点击append 的层级+1this.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、修改拖拽效果
- 实现拖拽效果
- :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) {// 可以拖动放置的条件:被拖动的当前节点以及所在的父节点总层数不能大于3console.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;}},
- 拖拽数据收集
- 拖拽效果影响的数据: 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. 当前节点最新的父节点idlet pCid = 0;let siblings = null;// 1.1 以兄弟关系拖拽if (dropType == "before" || dropType == "after") {// 最新父id=进入节点的父idpCid = dropNode.parent.data.catId == undefined ? 0 : dropNode.parent.data.catId;siblings = dropNode.parent.childNodes;}// 1.2 inner 方式拖入else {// 最新父id=进入节点的idpCid = 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)},
}
- 发送请求
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(() => { });},