- 一、商品服务-API-三级分类
- 1、三级分类
- 2、查出所有分类以及子分类
- 2、配置网关路由与路径重写
- 3、网关统一配置跨域
- 4、查询-树形展示三级分类数据
- 5、删除
- 6、新增
- 7、修改
- 8、修改拖拽效果
- 9、批量删除
一、商品服务-API-三级分类 1、三级分类商品服务-三级分类
- pms_category 表代表商品的分类
cat_id:分类id,cat代表分类,bigint(20)
name:分类名称
parent_cid:在哪个父目录下
cat_level:分类层级
show_status:是否显示,用于逻辑删除
sort:同层级同父目录下显示顺序
ico图标,product_unit商品计量单位,
InnoDB表,自增大小1437,utf编码,动态行格式
2、查出所有分类以及子分类
1.在product服务的package com.ljn.gulimall.product.controller中打开CategoryController,添加方法:
@RestController
@RequestMapping("product/category")
public class CategoryController {
@Autowired
private CategoryService categoryService;
/**
* 查出所有分类以及子分类,以树形结构组装起来
*/
@RequestMapping("/list/tree")
public R list(){
List<CategoryEntity> entities = categoryService.listWithTree();
return R.ok().put("data", entities);
}
2…在product服务的package com.ljn.gulimall.product.service;中打开CategoryService,添加方法:
public interface CategoryService extends IService<CategoryEntity> {
PageUtils queryPage(Map<String, Object> params);
List<CategoryEntity> listWithTree();
}
3.在product服务的package com.ljn.gulimall.product.service.impl;中打开CategoryServiceImpl实现方法:
package com.ljn.gulimall.product.service.impl;
@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
// 注入CategoryDao
@Autowired
CategoryDao categoryDao;
@Override
public PageUtils queryPage(Map<String, Object> params) {
IPage<CategoryEntity> page = this.page(
new Query<CategoryEntity>().getPage(params),
new QueryWrapper<CategoryEntity>()
);
return new PageUtils(page);
}
// 实现方法
@Override
public List<CategoryEntity> listWithTree() {
// 1.查出所有分类
List<CategoryEntity> entities = categoryDao.selectList(null);
// 2.组装成父子的树形结构
// 2.1 找到所有一级分类,一级分类父id=0,并返回为一个集合
List<CategoryEntity> Level1Menus = entities.stream().filter(categoryEntity -> {
// 一级分类
return categoryEntity.getParentCid() == 0;
}).map((menu) -> {
// 将查找到的子菜单放入
menu.setChildren(getChildrens(menu, entities));
return menu;
}).sorted((menu1, menu2) -> {
// 排序
return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
}).collect(Collectors.toList());
return Level1Menus;
}
// 递归查找所有菜单的子菜单,root:当前菜单 all:所有菜单
private List<CategoryEntity> getChildrens(CategoryEntity root, List<CategoryEntity> all) {
// 从所有菜单中过滤出子菜单
List<CategoryEntity> children = all.stream().filter(categoryEntity -> {
// 当前菜单的父ID=指定菜单的id 也就是判断在哪个父目录下
return categoryEntity.getParentCid().equals(root.getCatId());
}).map(categoryEntity -> {
// 当前菜单还可能有子菜单
categoryEntity.setChildren(getChildrens(categoryEntity, all));
return categoryEntity;
}).sorted((menu1, menu2) -> {
// 排序
return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
}).collect(Collectors.toList());
return children;
}
}
4.结果
启动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-fast
predicates: # 什么情况下路由给它
- Path=/api/** # 默认前端项目都带上api前缀,就是我们前面的localhost:88/api
filters:
- RewritePath=/api/(?>.*),/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_route
uri: lb://gulimall-product
predicates:
- Path=/api/product/**
filters:
- # 把/api/* 去掉,剩下的留下来
- RewritePath=/api/(?>.*),/$\{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>
<el-tree :data="menus" :props="defaultProps" :expand-on-click-node="false" show-checkbox node-key="catId">
<span class="custom-tree-node" slot-scope="{ node, data }">
<span>{{ node.label }}span>
<span>
<el-button v-if="node.level <= 2" type="text" size="mini" @click="() => append(data)">
Append
el-button>
<el-button v-if="node.childNodes.length == 0" type="text" size="mini" @click="() => remove(node, data)">
Delete
el-button>
span>
span>
el-tree>
template>
<script>
export default {
name: "category",
components: {},
directives: {},
data() {
return {
menus: [],
data: [],
defaultProps: {
children: "children",
label: "name", // 要显显示的内容
},
};
},
mounted() { },
methods: {
// 3. 增加标签的方法
append(data) {
console.log("append", data)
},
// 2. 删除分类的方法
remove(node, data) {
console.log("delete", node, data)
},
// 1. 获取分类数据的方法
getMenus() {
this.$http({
url: this.$http.adornUrl("/product/category/list/tree"),
method: "get",
}).then(({ data }) => {
// 解构出数据中有用的data
console.log(data.data);
this.menus = data.data;
});
},
},
created() {
this.getMenus();
},
};
script>
<style>style>
2、逻辑删除
- 修改CategoryController类,添加如下代码
@RequestMapping("/delete")
public R delete(@RequestBody Long[] catIds) {
// 1、删除之前需要判断待删除的菜单那是否被别的地方所引用。
// 2、自定义删除方法
categoryService.removeMenuByIds(Arrays.asList(catIds));
return R.ok();
}
- CategoryServiceImpl 实现自定义的方法
@Override
public void removeMenuByIds(List<Long> asList) {
//TODO 1、检查当前删除的菜单,是否被其它地方引用
// 2、逻辑删除
baseMapper.deleteBatchIds(asList);
}
- mybatis-plus逻辑删除配置(可有可无)
mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml
global-config:
db-config:
id-type: auto
logic-delete-value: 1
logic-not-delete-value: 0
- 修改product.entity.CategoryEntity实体类,添加上@TableLogic,表明使用逻辑删除
/**
* 是否显示[0-不显示,1显示]
*/
@TableLogic(value = "1",delval = "0")
private Integer showStatus;
3、apifox测试请求
4、前端请求处理
发送的请求:delete
发送的数据:this.$http.adornData(ids, false)
util/httpRequest.js中,封装了一些拦截器
http.adornParams是封装get请求的数据
http.adornData封装post请求的数据
ajax的get请求会被缓存,就不会请求服务器了。
所以我们在url后面拼接个date时间戳,让他每次都请求服务器
- 发送请求
// 2. 删除分类的方法
remove(node, data) {
// 1. 获取当前节点id
var ids = [data.catId]
// 2. 发送请求前d框提示
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-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的catId
this.category.parentCid = data.catId;
// 2.2 层级catLevel 当前点击append 的层级+1
this.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, // 是否打开对话框,默认为false
expandedKey: [], // 展开基准
menus: [],
data: [],
defaultProps: {
children: "children",
label: "name", // 要显显示的内容
},
};
},
mounted() { },
methods: {
// 7. 对话框确认按钮,提交数据的方法
submitDate() {
if (this.dialogType == "append") {
// 打开的是添加的对话框,保存分类
this.addCategory();
}
if (this.dialogType == "edit") {
// 打开的是修改的对话框,修改分类
this.editCategory();
}
},
// 6. 修改三级分类数据
editCategory() {
// 1. 解构要发送的数据
var { catId, name, icon, productUnit } = this.category;
// 2. 发送修改请求
this.$http({
url: this.$http.adornUrl("/product/category/update"),
method: 'post',
data: this.$http.adornData({ catId, name, icon, productUnit }, false)
}).then(({ data }) => {
this.$message({
type: "success",
message: "菜单修改成功!",
});
// 3. 关闭对话框
this.dialogVisible = false;
// 4. 刷新菜单
this.getMenus();
// 5. 展开父菜单
this.expandedKey = [this.category.parentCid];
})
},
// 5. 点击修改分类按钮
edit(node, data) {
// 1.1设置对话框类型为deit
this.dialogType = "edit";
// 1.2显示对话框
this.dialogVisible = true;
// 1.3对话框标题
this.title = "修改分类"
// 2. 发送请求回显最新数据
this.$http({
url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
method: 'get',
}).then(({ data }) => {
// 3. 请求成功,回显数据
this.category.name = data.data.name;
this.category.catId = data.data.catId;
this.category.icon = data.data.icon;
this.category.productUnit = data.data.productUnit;
this.category.parentCid = data.data.parentCid;
})
console.log(node, data)
},
// 4. 添加三级分类数据的方法
addCategory() {
console.log("提交的三级分类数据", this.category)
// 1. 发送保存请求,提交
this.$http({
url: this.$http.adornUrl('/product/category/save'),
method: 'post',
data: this.$http.adornData(this.category, false) // 要发送的数据
}).then(({ data }) => {
// 2. 保存成功提示消息
this.$message({
type: "success",
message: "菜单保存成功!",
});
// 3. 保存成功后关闭对话框
this.dialogVisible = false;
// 4. 刷新出新菜单
this.getMenus();
// 5. 设置默认展示的菜单
this.expandedKey = [this.category.parentCid];
})
},
// 3. 点击增加标签按钮
append(data) {
console.log("append", data)
// 0. 设置对话框类型为append
this.dialogType = "append";
// 1. 点击append 按钮打开对话框
this.dialogVisible = true;
// 设置标题
this.title = "添加分类";
// 2. 点击按钮为category获取默认值
// 2.1 父id,当前点击append的catId
this.category.parentCid = data.catId;
// 2.2 层级catLevel 当前点击append 的层级+1
this.category.catLevel = data.catLevel * 1 + 1;
// 3. 清空修改后的回显信息
this.category.name = "";
this.category.showStatus = 1;
this.category.sort = 0;
this.category.catId = null;
this.category.icon = ""; // 输入什么绑定什么
this.category.productUnit = "";
},
8、修改拖拽效果
- 实现拖拽效果
- :draggable 属性开启拖拽效果
- :allow-drop 拖拽目标是否放置
<el-tree :data="menus" :props="defaultProps" :expand-on-click-node="false" show-checkbox node-key="catId" :default-expanded-keys="expandedKey" draggable :allow-drop="allowDrop">
// 9. 统计当前拖拽节点总层数的方法
countNodeLevel(node) {
// 找到所有子节点,求出最大深度
if (node.childNodes != null && node.childNodes.length > 0) { // 有子节点
// 遍历
for (let i = 0; i < node.childNodes.length; i++) {
if (node.childNodes[i].level > this.maxLevel) {
this.maxLevel = node.childNodes[i].level;
}
// 还有子节点,递归调用
this.countNodeLevel(node.childNodes[i]);
}
}
},
// 8. 拖拽是否放置的方法
allowDrop(draggingNode, dropNode, type) {
// 可以拖动放置的条件:被拖动的当前节点以及所在的父节点总层数不能大于3
console.log("allowFrop:",draggingNode,dropNode,type)
// 1. 统计当前节点的总层数
this.countNodeLevel(draggingNode.data);
// 2.当前正在拖动的节点+父节点所在的深度不大于3即可
let deep = (this.maxLevel - draggingNode.data.catLevel) + 1;
console.log("深度:", deep);
// 3. this.maxLevel
// 3.1 拖到节点里面
if (type == "inner") {
return deep + dropNode.level <= 3;
} else {
return deep + dropNode.parent.level <=3;
}
},
- 拖拽数据收集
- 拖拽效果影响的数据: parentCid 、 catLevel 、sort
- 属性 node-drop : 拖拽完成时触发的事件
// 11. 修改子节点层级的方法
data() {
return {
updateNodes: [], // 拖拽时修改的节点
},
};
},
methods: {
updateChildNodeLevlel(node) {
if (node.childNodes.length > 0) {
for (let i = 0; i < node.childNodes.length; i++) {
// 正在遍历的子节点
var cNode = node.childNodes[i].data;
this.updateNodes.push({
catId: cNode.catId,
catLevel: node.childNodes[i].level,
});
this.updateChildNodeLevlel(node.childNodes[i]);
}
}
},
// 10. 拖拽完成事件处理
handleDrop(draggingNode, dropNode, dropType, ev) {
console.log('handleDrop: ', draggingNode, dropNode, dropType);
// 1. 当前节点最新的父节点id
let pCid = 0;
let siblings = null;
// 1.1 以兄弟关系拖拽
if (dropType == "before" || dropType == "after") {
// 最新父id=进入节点的父id
pCid = dropNode.parent.data.catId == undefined ? 0 : dropNode.parent.data.catId;
siblings = dropNode.parent.childNodes;
}
// 1.2 inner 方式拖入
else {
// 最新父id=进入节点的id
pCid = dropNode.data.catId;
siblings = dropNode.childNodes;
}
// 2. 当前拖拽节点的最新顺序
for (let i = 0; i < siblings.length; i++) {
if (siblings[i].data.catId == draggingNode.data.catId) {
// 如果当前遍历的是正在拖拽的节点
let catLevel = draggingNode.level;
if (siblings[i].level != draggingNode.level) {
// 当前节点的层级发生变化
catLevel = siblings[i].level;
// 修改他子节点的层级
this.updateChildNodeLevlel(siblings[i]);
}
this.updateNodes.push({ catId: siblings[i].data.catId, sort: i, parentCid: pCid });
} else {
this.updateNodes.push({ catId: siblings[i].data.catId, sort: i });
}
}
// 3. 当前拖拽节点的最新层级
console.log("updateNodes", this.updateNodes)
},
}
- 发送请求
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(() => { });
},
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)