目录
三级类目查询后台代码实现
catagory表的表结构,首先一级类目的parent_cid为0,二级类目以及三级类目的parent_cid为cid,sort字段用于排序
baseMapper等价于catagoryDao
代码实现:
/**
* 三级分类查询,以树状结构呈现
* @return
*/
@Override
public List<CategoryEntity> listWithTree() {
// 1.查询所有商品类目
List<CategoryEntity> all=categoryDao.selectList(null);
// 2.1、查询一级类目
List<CategoryEntity> categoryEntities= all.stream().filter(menu1->{
return menu1.getParentCid().equals(0L);
}
// 刷选出一级分类
).map((menu1)->{
menu1.setChildren(getChilren(menu1,all));
return menu1;
}).sorted((item1,item2)->{
// 排序
return (item1.getSort()==null?0:item1.getSort())-(item2.getSort()==null?0:item2.getSort());
}).collect(Collectors.toList());
return categoryEntities;
}
/**
* 查询子节点
* @param menu1
* @param all
* @return
*/
private List<CategoryEntity> getChilren(CategoryEntity menu1, List<CategoryEntity> all) {
List<CategoryEntity> chilrenList= all.stream().filter(chilrenMenu->
chilrenMenu.getParentCid().equals(menu1.getCatId())
// 查询子节点
).map((chilrenMenu)->{
chilrenMenu.setChildren(getChilren(chilrenMenu,all));
return chilrenMenu;
}).sorted((item1,item2)->{
return (item1.getSort()==null?0:item1.getSort())-(item2.getSort()==null?0:item2.getSort());
}).collect(Collectors.toList());
return chilrenList;
}
后台管理系统的菜单创建
①创建一个商品系统(一级菜单)目录
②在商品菜单目录下创建一个分类维护菜单
菜单路由讲解
当我们点击角色管理时,就会发送一个sys-role的请求,角色管理的菜单路由是sys/role,说明会将sys/role转化成sys-role
实际上访问sys-role就是请求sys文件夹下的role.vue
③分类维护的菜单路由为product/category,所以在views中的modules下面创建一个product文件,在product文件夹中创建category.vue
④编写category.vue请求后台数据
参考role.vue的请求数据方法,对category.vue进行编写
访问的是 http://localhost:8080/renren-fast/product/category/list/tree
而我们想要访问的路径是 http://localhost:10000/product/category/list/tree
我们应该将请求统一发送给网关,由网关根据我们的请求路径进行相应路由
配置网关和路径重写
①renren-fast服务注册到网关中
出现问题:
java: You aren't using a compiler supported by lombok, so lombok will not work and has been disabled
出现问题:
The following method did not exist:
com.google.common.collect.Sets$SetView.iterator()Lcom/google/common/collect/UnmodifiableIterator;
The method's class, com.google.common.collect.Sets$SetView, is available from the following locations:
jar:file:/D:/MavenRepository/com/google/guava/guava/18.0/guava-18.0.jar!/com/google/common/collect/Sets$SetView.class
It was loaded from the following location:
file:/D:/MavenRepository/com/google/guava/guava/18.0/guava-18.0.jar
出现原因是:guava-18跟别的jar有冲突,要么将guava的版本升级要么降级
renren-fast成功注册到nacos中
index.js中配置访问的路径
将其改成访问网关的路径,并且都带上api
路由重写
网关路由配置:
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
gateway:
routes:
- id: admin_route
uri: lb://renren-fast # lb:负载均衡
predicates:
- Path=/api/** # 路径中带有api的所有请求的都路由
filters:
- RewritePath=/api/?(?<segment>.*),/renren-fast/$\{segment}
application:
name: gulimall-gateway
server:
port: 88
出现CROS错误:
Access to XMLHttpRequest at 'http://localhost:88/api/sys/menu/nav?t=1639023916234'
from origin 'http://localhost:8001' has been blocked by CORS policy: Response to
preflight request doesn't pass access control
check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
出现原因:当我们想从'http://localhost:8001'访问'http://localhost:88',这个两个ip地址不是在同一个域下的,所以出现了跨域访问问题。'Access-Control-Allow-Origin' header这个请求头不在访问的资源中,解决方案需:要设置允许跨域的请求头
网关统一配置跨域
解决方案:
gateway中编写配置类:所有请求从网关返回的时候都会带上运行跨域访问的请求头
@Configuration
public class GulimallCROSConfig {
@Bean
public CorsWebFilter corsWebFilter(){
UrlBasedCorsConfigurationSource corsConfigurationSource=new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration=new CorsConfiguration();
// 跨域设置
corsConfiguration.addAllowedHeader("*"); //配置那些请求头可以跨域访问
corsConfiguration.addAllowedMethod("*"); //配置那些方法可以跨域访问
corsConfiguration.addAllowedOrigin("*"); //配置那些来源可以跨域访问
corsConfiguration.setAllowCredentials(true); //配置跨域请求可以携带cookie
corsConfigurationSource.registerCorsConfiguration("/**",corsConfiguration);
return new CorsWebFilter(corsConfigurationSource);
}
}
出现问题:
Access to XMLHttpRequest at 'http://localhost:88/api/sys/login' from origin 'http://localhost:8001' has been blocked by CORS policy: The 'Access-Control
-Allow-Origin' header contains multiplevalues'http://localhost:8001,http://localhost:8001', but only one is allowed.
允许跨域的请求头被设置了两个,只允许一个
出现问题的原因:renren-fast中的也设置了允许跨域的配置
解决方案:将renren-fast中的允许跨域配置类注释
三级类目后台管理系统的页面显示
经过网关后,请求路径变成了:http://localhost:88/renrenfast/product/category/list/tree
我们想要让请求路径变成:http://localhost:10000/product/category/list/tree
因此,需要编写路由:
出现问题:访问不到
出现问题的原因是:admin_router中的断言会优先将路径进行重写
解决方案:断言中小范围的路径访问要写在大范围的路径访问之前
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
gateway:
routes:
- id: product_route
uri: lb://gulimall-product
predicates:
- Path=/api/product/**
filters:
- RewritePath=/api/?(?<segment>.*),/$\{segment}
- id: admin_route
uri: lb://renren-fast # lb:负载均衡
predicates:
- Path=/api/** # 路径中带有api的所有请求的都路由
filters:
- RewritePath=/api/?(?<segment>.*),/renren-fast/$\{segment}
application:
name: gulimall-gateway
server:
port: 88
将获取到data在控制台中打印,发现data中的data才是真正的存放数据的对象
<template>
<el-tree :data="menu" :props="defaultProps" ></el-tree>
</template>
<script>
export default {
data() {
//这里存放数据
return {
menu: [],
defaultProps: {
children: 'children', //封装子节点,prop会自动进行一个遍历
label: 'name' //显示数据,prop会自动进行一个遍历
}
};
},
methods: {
//获取数据
getDataList() {
this.dataListLoading = true;
this.$http({
url: this.$http.adornUrl("/product/category/list/tree"),
method: "get",
}).then(({data}) => { // 1.使用解构即{},将data对象解构出来
// 2.将data中的data赋值给menu
this.menu=data.data;
// 3.将data中的data对象的属性children和name赋值给defaultProps中的children和label
});
},
},
created() {
//模板一旦被创建就被加载
this.getDataList();
},
};
三级分类删除页面效果的编写
我们使用scoped slot编写添加和删除按键
补上复选框
补全append和remove方法,并打印看看data和node
出现的问题:当我们点击append或者delete时,会自动进行一个扩展,我们想要的效果是只有当我们点击箭头才进行扩展
解决方案:
出现问题:只有节点没有子节点是才能进行delete,只有一级和二级目录才能append
解决方案:使用v-if进行一个判断,首先使用scoped slot会传进来node和data两个数据,其中node有个level属性,就是节点的层级,node还有个childNodes表示子节点的个数即childNodes数组的长度为0可以删除
出现问题:我们需要为Tree设置node-key,方便标识每个节点
解决方案:catId是每个节点唯一的标识属性
三级分类逻辑删除后台实现
①TODO的使用,相当于备用录,用于提醒还未实现的步骤或功能
②alt+enter:快速创建方法
使用postman进行测试:
这是一个真正的物理删除,在实际开发中并不常用,一般使用逻辑删除
mybatis-plus逻辑删除指南地址:逻辑删除 | MyBatis-Plus
mybatis-plus:
global-config:
db-config:
logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
@TableLogic
private Integer deleted;
说明一下:我使用的是mybatis-plus 3.2版本的没有这个logic-delete-field配置项并不影响使用
出现问题:showStatus属性1代表显示,0代表不显示与我们的逻辑删除配置刚好相反
解决方案:@TableLogic中有两个属性值,可以重新设置逻辑删除的值
日志打印:gulimall包下的都会打印日志
三级类目删除功能的前后联调
在httpRequest.js中url的拼接规则
当我们点击Delete按键就会去调用remove方法,因此需要去重写remove方法
重写remove方法:
出现问题:页面没有即时更新
解决方案:删除成功之后再次调用获取数据方法,进行一个数据的更新
出现问题:1.没有删除确认提示 2.删除成功或失败应该有消息提示 3.删除成功或失败应该父节点应该扩展而不是整个层级合并
<template>
<!-- 使用v-bind/':'才能为属性赋值成功 -->
<div>
<el-tree
show-checkbox
:data="menu"
:props="defaultProps"
:expand-on-click-node="false"
node-key="catId"
:default-expanded-keys="expandKey"
>
<span class="custom-tree-node" slot-scope="{ node, data }">
<span>{{ node.label }}</span>
<span>
<!--箭头函数调用append方法-->
<!-- 一级目录和二级目录才允许append -->
<el-button
v-if="node.level <= 2"
type="text"
size="mini"
@click="() => append(data)"
>
Append
</el-button>
<!-- 没有子节点才允许delete -->
<el-button
v-if="node.childNodes.length == 0"
type="text"
size="mini"
@click="() => remove(node, data)"
>
Delete
</el-button>
</span>
</span>
</el-tree>
</div>
</template>
<script>
export default {
data() {
//这里存放数据
return {
menu: [],
expandKey: [],
defaultProps: {
children: "children", //封装子节点,prop会自动进行一个遍历
label: "name", //显示数据,prop会自动进行一个遍历
},
};
},
methods: {
//获取数据
getDataList() {
this.dataListLoading = true;
this.$http({
url: this.$http.adornUrl("/product/category/list/tree"),
method: "get",
}).then(({ data }) => {
// 1.使用解构即{},将data对象解构出来
// 2.将data中的data赋值给menu
this.menu = data.data;
// 3.将data中的data对象的属性children和name赋值给defaultProps中的children和label
});
},
append(data) {
console.log("append", data);
},
remove(node, data) {
let catId = [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(catId, false),
}).then(({ data }) => {
this.$message({
type: "success",
message: "删除成功!",
});
// 刷新菜单
this.getDataList();
// 默认菜单展示
this.expandKey = [node.parent.data.catId];
});
})
.catch(() => {
this.$message({
type: "info",
message: "已取消删除",
});
});
console.log("remove", node, data);
},
},
//生命周期 - 创建完成(可以访问当前this实例)
created() {
//模板一旦被创建就被加载
this.getDataList();
},
};