[项目练手笔记-谷粒商城(SpringCloud Alibaba+vue前后端分离)]day03-day07三级分类API
一、获取三级分类树形列表
一、controller调用
/**
* 查询所有产品,并以属性列表展示
*/
@RequestMapping("/list/tree")
//@RequiresPermissions("product:category:list")
public R list(){
List<CategoryEntity> entities=categoryService.listWithTree();
return R.ok().put("data", entities);
}
二、递归查找子节点
1.修改entity
/**
* 所有子节点
*/
@TableField(exist=false)
private List<CategoryEntity> children;
2.递归查找子节点
public List<CategoryEntity> listWithTree() {
//1.查询所有产品信息
List<CategoryEntity> list = baseMapper.selectList(null);
//TODO 2.查询所有子菜单信息
List<CategoryEntity> level1Menu = list.stream().
filter(categoryEntity ->
//找到所有一级菜单
categoryEntity.getParentCid() == 0).
map((menu1) -> {
//给所有的一级菜单找到他们的子节点
menu1.setChildren(getchildren(menu1, list));
return menu1;
}).
sorted(
//排序菜单
Comparator.comparingInt(CategoryEntity::getSort)).
collect(
//收集所有一级菜单信息
Collectors.toList());
return level1Menu;
}
private List<CategoryEntity> getchildren(CategoryEntity menu1, List<CategoryEntity> list) {
List<CategoryEntity> children = list.stream().
filter(
categoryEntity -> categoryEntity.getParentCid().equals(menu1.getCatId())).
map((menu) -> {
menu.setChildren(getchildren(menu, list));
return menu;
}).
sorted(
//排序菜单
Comparator.comparingInt(menuOne -> (menuOne.getSort() == null ? 0 : menuOne.getSort()))).
collect(
//收集所有一级菜单信息
Collectors.toList());
return children;
}
三、前端编写
进入文件,首选项,用户片段配置vue模板
vue全局模板
{
"vue全局模板": {
"prefix": "vue",
"body": [
"<template>",
"<div></div>",
"</template>",
"",
"<script>",
"//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)",
"//例如:import 《组件名称》 from '《组件路径》';",
"",
"export default {",
"//import引入的组件需要注入到对象中才能使用",
"components: {},",
"data() {",
"//这里存放数据",
"return {",
"",
"};",
"},",
"//监听属性 类似于data概念",
"computed: {},",
"//监控data中的数据变化",
"watch: {},",
"//方法集合",
"methods: {",
"",
"},",
"//生命周期 - 创建完成(可以访问当前this实例)",
"created() {",
"",
"},",
"//生命周期 - 挂载完成(可以访问DOM元素)",
"mounted() {",
"",
"},",
"beforeCreate() {}, //生命周期 - 创建之前",
"beforeMount() {}, //生命周期 - 挂载之前",
"beforeUpdate() {}, //生命周期 - 更新之前",
"updated() {}, //生命周期 - 更新之后",
"beforeDestroy() {}, //生命周期 - 销毁之前",
"destroyed() {}, //生命周期 - 销毁完成",
"activated() {}, //如果页面有keep-alive缓存功能,这个函数会触发",
"}",
"</script>",
"<style lang='scss' scoped>",
"//@import url($3); 引入公共css类",
"$4",
"</style>"
],
"description": "生成vue模板"
}
}
vueHTTP请求模板
需要加的话,加在全局模板里面
"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请求"
}
1.创建目录菜单
2.找到对应vue目录
1.创建文件
创建src/views/product/category.vue与之对应
2.创建树形模板
<template>
<div>
<el-tree :data="data" :props="defaultProps" @node-click="handleNodeClick"></el-tree>
</div>
</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() {
this.getMenus();
},
//生命周期 - 挂载完成(可以访问DOM元素)
mounted() {},
//生命周期 - 创建之前
beforeCreate() {},
//生命周期 - 挂载之前
beforeMount() {},
//生命周期 - 更新之前
beforeUpdate() {},
//生命周期 - 更新之后
updated() {},
//生命周期 - 销毁之前
beforeDestroy() {},
//生命周期 - 销毁完成
destroyed() {},
//如果页面有keep-alive缓存功能,这个函数会触发
activated() {}
};
</script>
<style lang='scss' scoped>
//@import url(); 引入公共css类
</style>
3.网关统一访问
1.更改前端访问路径
如果直接调用方法,就会出现404错误,因为访问的地址是http://localhost:8080/renren-fast/product/category/list/tree,需要转发的查询地址是http://localhost:10000/product/category/list/tree
所以要去修改vue的统一访问链接,让其访问后台。
2.注册renren-fast到nacos
application.yml
application:
name: renren-fast
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
3.修改路由
再次连接发现路由地址多了一个api,需要在路由配置中进行修改
gateway:
routes:
- id: base_route
uri: lb://renren-fast
predicates:
- Path=/api/**
filters:
- RewritePath=/api/(?<segment>.*), /renren-fast/$\{segment}
4.配置跨域
@Configuration
public class MyCorsConfiguration {
@Bean
public CorsWebFilter corsWebFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
//配置跨域
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.setAllowCredentials(true);
source.registerCorsConfiguration("/**",corsConfiguration);
return new CorsWebFilter(source);
}
}
3.设置树形结构
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;
});
}
},
//生命周期 - 创建完成(可以访问当前this实例)
created() {
this.getMenus();
},
4.删除功能编写
1.增加一些必要属性
参数 | 说明 | 默认值 |
---|---|---|
node-key | 每个树节点用来作为唯一标识的属性,整棵树应该是唯一的 | |
show-checkbox | 节点是否可被选择 | false |
expand-on-click-node | 是否在点击节点的时候展开或者收缩节点, 默认值为 true,如果为 false,则只有点箭头图标的时候才会展开或者收缩节点。 | true |
2.添加删除添加框
<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 v-if="node.childNodes.length==0" type="text" size="mini" @click="() => remove(node, data)">删除</el-button>
</span>
</span>
3.后台添加删除功能
采用逻辑删除功能
1.配置yml
mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml
global-config:
db-config:
id-type: auto
logic-delete-value: 1
logic-not-delete-value: 0
2.配置entity
/**
* 是否显示[0-不显示,1显示]
*/
@TableLogic(value = "1",delval = "0")
private Integer showStatus;
4.前台添加删除功能
1.给网页发送指定post请求
this.$http({
url: this.$http.adornUrl("/product/category/delete"),
method: "post",
data: this.$http.adornData(catIds, false)
}).then(({ data }) => {
//刷新出删除后菜单
this.getMenus();
console.log(node, data);
});
2.删除确认框
remove(node, data) {
var catIds = [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(catIds, false)
}).then(({ data }) => {
//刷新出删除后菜单
this.getMenus();
console.log(node, data);
});
})
.catch(() => {
});
}
}
3.删除提示成功框
//消息提示删除成功
this.$message({
message: "删除成功!",
type: "success"
});
4.设置默认展开菜单
:default-expanded-keys="expandedkeys" //设置属性
data() {
return {
menus: [],
expandedkeys: [],//设置为空
//刷新出删除后菜单
this.getMenus();
//展开到被删除结点的父菜单
this.expandedkeys=[node.parent.data.catId];
5.添加功能编写
1.使用对话框模拟
<el-dialog title="提示" :visible.sync="dialogVisible" width="30%">
<span>这是一段信息</span>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="dialogVisible = false">确 定</el-button>
</span>
</el-dialog>
设置默认不打开对话框
data() {
return {
menus: [],
expandedkeys: [],
dialogVisible: false,//默认不打开
点击添加触发append函数
this.dialogVisible = true;
2.自定义对话框
<el-dialog title="提示" :visible.sync="dialogVisible" width="30%">
<el-form :model="category">
<el-form-item label="添加目录名">
<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>
3.函数编写
append(data) {
console.log("append方法", data);
this.dialogVisible = true;
this.category.parentCid = data.catId;
this.category.catLevel = data.catLevel * 1 + 1;
},
addCategory() {
console.log("addCategory方法Category", 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.expandedkeys = [this.category.parentCid];
}
6.编辑功能编写
1.添加编辑按钮
<el-button type="text" size="mini" @click="() => edit(data)">编辑</el-button>
2.参数设置
data() {
return {
submitType: "",
title: "",
menus: [],
expandedkeys: [],
dialogVisible: false,
category: {
carId: null,
name: "",
parentCid: 0,
catLevel: 0,
showStatus: 1,
sort: 0,
icon: "",
productUnit: ""
},
3.修改添加,编写编辑方法
getMenus() {
this.$http({
url: this.$http.adornUrl("/product/category/list/tree"),
method: "get",
params: this.$http.adornParams({})
}).then(({ data }) => {
console.log("成功获取到菜单数据...", data.data);
this.menus = data.data;
});
},
append(data) {
console.log("append方法", data);
this.title = "添加目录";
this.submitType = "add";
this.dialogVisible = true;
this.category.name = "";
this.category.parentCid = data.catId;
this.category.catLevel = data.catLevel * 1 + 1;
this.category.icon = "";
this.category.productUnit = "";
this.category.sort = 0;
this.category.showStatus = 1;
},
addCategory() {
console.log("addCategory方法Category", 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.expandedkeys = [this.category.parentCid];
});
},
edit(data) {
this.title = "修改目录";
this.dialogVisible = true;
this.submitType = "edit";
//重新发送请求获取数据
this.$http({
url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
method: "get",
params: this.$http.adornParams({})
}).then(({ data }) => {
//请求查找数据成功
console.log("要回显的数据", data);
this.category.parentCid = data.data.parentCid;
this.category.catId = data.data.catId;
this.category.catLevel = data.data.catLevel * 1;
this.category.name = data.data.name;
this.category.icon = data.data.icon;
this.category.productUnit = data.data.productUnit;
this.category.sort = data.data.sort;
this.category.showStatus = data.data.showStatus;
});
},
editCategory() {
//console.log("editCategory方法Category", this.category);
var { catId, name, icon, productUnit } = this.category; //获取解构需要的数据
//发送修改请求
this.$http({
url: this.$http.adornUrl("/product/category/update"),
method: "post",
data: this.$http.adornData({ catId, name, icon, productUnit }, false)
}).then(({ data }) => {
//消息提示删除成功
this.$message({
message: "修改成功!",
type: "success"
});
//关闭对话框
this.dialogVisible = false;
console.log("看看这里的数据", this.category);
//刷新出删除后菜单
this.getMenus();
//展开到被删除结点的父菜单
this.expandedkeys = [this.category.parentCid];
});
},
submitData() {
if (this.submitType == "add") {
this.addCategory();
}
if (this.submitType == "edit") {
this.editCategory();
}
},
7.开启拖拽
1.开启tree的拖拽
:draggable="true"
2.拖拽方法
//判断是否能够拖拽
allowDrop(draggingNode, dropNode, type) {
console.log(draggingNode, dropNode, type);
//如果当前结点的深度加拖到的结点的深度小于等于3就可以拖动
//计算当前拖动结点的最大深度
this.countDepth(draggingNode);
//计算当前拖动结点的深度
var deep = Math.abs(this.maxDepth-draggingNode.level+1)
console.log("最大深度",deep);
//1.如果拖动类型为inner
if(type="inner") {
return (deep+dropNode.level)<=3;
}else {
return (deep+dropNode.parent.level)<=3;
}
},
//计算最大深度
countDepth(node) {
//如果存在子节点,计算其深度
if (node.childNodes != null && node.childNodes.length > 0) {
var cNodes = node.childNodes;
for (let index = 0; index < cNodes.length; index++) {
this.maxDepth = Math.max(this.maxDepth,cNodes[index].level);
this.countDepth(cNodes[index]);
}
}
}
8.拖拽事件补充
1.增加拖拽完成后事件
handleDrop(draggingNode, dropNode, dropType, ev) {
console.log("tree drop: ", draggingNode, dropNode, dropType);
//1.找到当前结点的父节点
let pCid = 0;
let siblings = null; //定义兄弟结点
//如果是放在内部
if (dropType == "inner") {
pCid = dropNode.data.catId;
siblings = dropNode.childNodes;
} else {
pCid =
dropNode.parent.data.catId == undefined
? 0
: dropNode.parent.data.catId;
siblings = dropNode.parent.childNodes;
}
this.pCid.push(pCid);
//2.找到当前结点最新顺序,需要将所在结点进行排序
//遍历新结点按照遍历顺序进行排序
for (let i = 0; i < siblings.length; i++) {
let catLevel = draggingNode.level;
//如果遍历到了拖拽的结点,需要对层级进行修改
if (siblings[i].data.catId == draggingNode.data.catId) {
//如果层级发生变化
if (catLevel != siblings[i].level) {
//修改层级,并且修改其子结点层级
catLevel = siblings[i].level;
this.modifySubs(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,
catLevel: catLevel
});
}
}
//3.更新当前结点最新的层级
console.log("updateNodes", this.updateNodes);
},
//修改子节点的层级
modifySubs(childNode) {
if (childNode.childNodes != null && childNodes.length > 0) {
for (let i = 0; i < childNode.childNodes.length; i++) {
this.updateNodes.push({
catId: childNode.childNodes[i].data.catId,
cataLevel: childNode.childNodes[i].level
});
this.modifySubs(childNode[i]);
}
}
}
}
9.拖拽功能后台数据获取
1.修改controller
/**
* 批量修改功能
*/
@RequestMapping("/update/sort")
//@RequiresPermissions("product:category:update")
public R update(@RequestBody CategoryEntity[] category){
categoryService.updateBatchById(Arrays.asList(category));
return R.ok();
}
2.发送httppost请求
完善前台handleDrop方法,将要修改的数据发送给后台进行修改
//3.更新当前结点最新的层级
console.log("updateNodes", this.updateNodes);
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.expandedkeys = [pCid];
//初始化
this.updateNodes=[];
this.maxDepth=0;
});
10.拖拽功能完善
1.增加功能开关
<el-switch v-model="draggable" active-text="开启拖拽" inactive-text="关闭拖拽"></el-switch>
<el-button v-if="draggable" @click="batchSave">批量保存</el-button>
2.批量保存
将批量httppost方法放到保存这
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.expandedkeys = this.pCid;
//初始化
this.updateNodes = [];
this.maxDepth = 0;
});
}
11.批量删除功能
1.添加按钮
<el-button type="danger" @click="batchDelete">批量删除</el-button>
2.添加el-tree属性
ref=“menuTree”
该属性表示树形结构的唯一标识
3.获取选中结点
使用自带方法getCheckedNodes,(leafOnly, includeHalfChecked) 接收两个 boolean 类型的参数,1. 是否只是叶子节点,默认值为 false
2. 是否包含半选节点,默认值为 false
//批量删除方法
batchDelete() {
let ids = []; //所有选中结点ID
//获取树形控件中被选中的所有结点
let checkedNodes = this.$refs.menuTree.getCheckedNodes();
for (let i = 0; i < checkedNodes.length; i++) {
ids.push(checkedNodes[i].catId);
}
console.log("被选中的结点", ids);
}
4.将结点信息发送给后台删除
//批量删除方法
batchDelete() {
let ids = []; //所有选中结点ID
//获取树形控件中被选中的所有结点
let checkedNodes = this.$refs.menuTree.getCheckedNodes();
for (let i = 0; i < checkedNodes.length; i++) {
ids.push(checkedNodes[i].catId);
}
this.$confirm(`是否要批量删除目录【${ids}】?`, "提示", {
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();
});
})
.catch(() => {});
}