1、controller
package com.stu.gulimall.product.controller;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.stu.gulimall.product.entity.CategoryEntity;
import com.stu.gulimall.product.service.CategoryService;
import com.stu.common.utils.PageUtils;
import com.stu.common.utils.R;
/**
* 商品三级分类
*
* @author konglx
* @email konglx@gmail.com
* @date 2022-02-19 10:52:27
*/
@RestController
@RequestMapping("product/category")
public class CategoryController {
@Autowired
private CategoryService categoryService;
/**
* 查出所有分类以及子分类,以树形结构组装起来
*/
@RequestMapping("/list/tree")
//@RequiresPermissions("product:category:list")
public R list(@RequestParam Map<String, Object> params) {
//PageUtils page = categoryService.queryPage(params);
List<CategoryEntity> list = categoryService.listWithTree();
return R.ok().put("list", list);
}
/**
* 信息
*/
@RequestMapping("/info/{catId}")
//@RequiresPermissions("product:category:info")
public R info(@PathVariable("catId") Long catId) {
CategoryEntity category = categoryService.getById(catId);
return R.ok().put("category", category);
}
/**
* 保存
*/
@RequestMapping("/save")
//@RequiresPermissions("product:category:save")
public R save(@RequestBody CategoryEntity category) {
categoryService.save(category);
return R.ok();
}
/**
* 修改
*/
@RequestMapping("/update/sort")
//@RequiresPermissions("product:category:update")
public R update(@RequestBody CategoryEntity[] category) {
categoryService.updateBatchById(Arrays.asList(category));
return R.ok();
}
/**
* 删除
*/
@RequestMapping("/delete")
//@RequiresPermissions("product:category:delete")
public R delete(@RequestBody Long[] catIds) {
// categoryService.removeByIds(Arrays.asList(catIds));
categoryService.removeMenusByIds(Arrays.asList(catIds));
return R.ok();
}
}
CategoryEntity类
package com.stu.gulimall.product.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 商品三级分类
*
* @author konglx
* @email konglx@gmail.com
* @date 2022-02-19 10:52:27
*/
@Data
@TableName("pms_category")
public class CategoryEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 分类id
*/
@TableId
private Long catId;
/**
* 分类名称
*/
private String name;
/**
* 父分类id
*/
private Long parentCid;
/**
* 层级
*/
private Integer catLevel;
/**
* 是否显示[0-不显示,1显示]
*/
@TableLogic(value = "1",delval = "0")
private Integer showStatus;
/**
* 排序
*/
private Integer sort;
/**
* 图标地址
*/
private String icon;
/**
* 计量单位
*/
private String productUnit;
/**
* 商品数量
*/
private Integer productCount;
@TableField(exist = false)
private List<CategoryEntity> children;
}
2、service
package com.stu.gulimall.product.service.impl;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.stu.common.utils.PageUtils;
import com.stu.common.utils.Query;
import com.stu.gulimall.product.dao.CategoryDao;
import com.stu.gulimall.product.entity.CategoryEntity;
import com.stu.gulimall.product.service.CategoryService;
import javax.xml.bind.annotation.XmlEnum;
@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
@Override
public void removeMenusByIds(List<Long> catIds) {
//todo
//1、检查单曲删除的菜单,是否被其他地方引用
//逻辑删除
baseMapper.deleteBatchIds(catIds);
}
@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> categoryEntities = baseMapper.selectList(null);
//2、组装父子结构
//2.1找到所有一级
List<CategoryEntity> oneLevelMenus = categoryEntities
.stream()
.filter(categoryEntity -> categoryEntity.getParentCid() == 0)
.map((menu) -> {
menu.setChildren(getChildrens(menu, categoryEntities));
return menu;
})
.sorted((menu1, menu2) -> {
return (menu1.getSort() == null ? 0:menu1.getSort()) - (menu2.getSort()==null?0:menu2.getSort());
})
.collect(Collectors.toList());
return oneLevelMenus;
}
private List<CategoryEntity> getChildrens(CategoryEntity root, List<CategoryEntity> allList) {
List<CategoryEntity> child = allList.stream()
.filter((categoryEntity) -> {
return categoryEntity.getParentCid().equals(root.getCatId());})
.map((categoryEntity) -> {
//1、找到子菜单
categoryEntity.setChildren(getChildrens(categoryEntity,allList));
return categoryEntity;})
.sorted((menu1,menu2)->{
//菜单排序
return (menu1.getSort() == null ? 0:menu1.getSort()) - (menu2.getSort()==null?0:menu2.getSort());
})
.collect(Collectors.toList());
return child;
}
}
3、vue
<template>
<div>
<el-switch
v-model="draggable"
active-text="开启拖拽"
inactive-text="关闭拖拽"
>
</el-switch>
<el-button v-if="draggable" @click="batchSave">批量保存</el-button>
<el-tree
v-loading="loading"
:data="menus"
:props="defaultProps"
node-key="catId"
:expand-on-click-node="false"
:default-expanded-keys="expandKeys"
:draggable="draggable"
:allow-drop="allowDrop"
@node-drop="handleDrop"
>
<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="() => toAppend(data)"
>
Append
</el-button>
<el-button type="text" size="mini" @click="() => toEdit(data)">
Edit
</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>
<el-dialog
:title="title"
:visible.sync="dialogVisible"
v-loading="loading"
:close-on-click-modal="false"
width="30%"
>
<el-form :model="categoryInfo">
<el-form-item label="分类名称">
<el-input v-model="categoryInfo.name" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="图标">
<el-input v-model="categoryInfo.icon" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="计量单位">
<el-input
v-model="categoryInfo.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="saveOrAddCategorySubmit"
>确 定</el-button
>
</span>
</el-dialog>
</div>
</template>
<script>
//这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';
export default {
data() {
return {
pCid: [],
draggable: false, //是否可以拖拽
updateNodes: [],
maxLevel: 0,
loading: false,
title: "添加分类",
menus: [], //菜单集合
expandKeys: [], //tree展开的集合
dialogVisible: false,
defaultProps: {
children: "children",
label: "name",
},
categoryInfo: {
catId: "", //分类id
name: "", //名称
parentCid: 0, //父分类id
catLevel: 0, //层级
showStatus: 1, //是否显示[0-不显示,1显示]
sort: 0, //排序
icon: "", //图标地址
productCount: 0, //商品数量
productUnit: "", //计量单位
},
};
},
methods: {
//拖拽批量保存
batchSave() {
//更新数据库
let that = this;
if (that.updateNodes.length <= 0) {
that.$message({
message: "请先拖拽要保存的数据",
type: "warning",
});
return false;
}
that.loading = true;
this.$http({
url: this.$http.adornUrl("/product/category/update/sort"),
method: "post",
data: this.$http.adornData(this.updateNodes, false),
})
.then(({ data }) => {
if (data && data.code === 0) {
that.loading = false;
that.$message({
message: "更新成功",
type: "success",
});
that.getMenus();
//设置默认展开
that.expandKeys = that.pCid;
that.updateNodes = [];
that.maxLevel = 0;
that.pCid = [];
} else {
that.$message({
message: "更新失败",
type: "warning",
});
}
})
.catch((e) => {
that.loading = false;
that.updateNodes = [];
that.maxLevel = 0;
that.pCid = [];
console.log("error ==== " + e);
this.$message({
type: "warning",
message: "系统异常 " + e.message,
});
});
},
//
handleDrop(draggingNode, dropNode, dropType, ev) {
let that = this;
//1、当前节点的最新的父节点id
let pCid = 0;
//2、当前节点的最新顺序
let siblings = null;
if ("before" === dropType || "after" === dropType) {
pCid =
dropNode.parent.data.catId == undefined
? 0
: dropNode.parent.data.catId;
siblings = dropNode.parent.childNodes;
} else {
pCid = dropNode.data.catId;
siblings = dropNode.childNodes;
}
that.pCid.push(pCid);
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("this.updateNodes=== " + this.updateNodes);
},
//递归修改所有子节点
updateChildNodeLevel(node) {
if (node.childNodes.length > 0) {
for (let i = 0; i < node.childNodes.length; i++) {
let 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
//被拖到的当前节点总层数
console.log(draggingNode, dropNode, type);
this.countNodeLevel(draggingNode);
//当前正在拖动的节点+父节点所在的深度不大于3,
//let deep = Math.abs(this.maxLevel - draggingNode.level) + 1;
let deep = this.maxLevel - draggingNode.level + 1;
// console.log(" this.maxLevel====== " + this.maxLevel);
// console.log(
// " this.maxLevel - draggingNode.level + 1===== " + deep
// );
// console.log(
// "+draggingNode.level===== " + draggingNode.level
// );
if (type == "inner") {
console.log(" inner====== " + deep + dropNode.level <= 3);
return deep + dropNode.level <= 3;
} else {
console.log(" out====== " + deep + dropNode.parent.level <= 3);
return deep + dropNode.parent.level <= 3;
}
},
//当前被拖动的节点 node
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]);
}
} else {
this.maxLevel = node.level;
}
},
//编辑和新增的提交按钮
saveOrAddCategorySubmit() {
if (this.categoryInfo.catId) {
this.updateCategorySubmit();
} else {
this.addCategorySubmit();
}
},
//编辑页面
toEdit(data) {
this.title = "修改分类";
this.dialogVisible = true;
//获取最新数据
this.$http({
url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
method: "get",
})
.then(({ data }) => {
if (data && data.code === 0) {
this.categoryInfo.name = data.category.name;
this.categoryInfo.catId = data.category.catId;
this.categoryInfo.icon = data.category.icon;
this.categoryInfo.productUnit = data.category.productUnit;
} else {
this.categoryInfo.name = "";
this.categoryInfo.catId = "";
this.categoryInfo.icon = "";
this.categoryInfo.productUnit = "";
}
})
.catch(() => {
this.$message({
type: "warning",
message: "系统异常",
});
});
},
//新增分类的确定事件
addCategorySubmit() {
let a = this.categoryInfo;
let that = this;
that.loading = true;
this.$http({
url: this.$http.adornUrl("/product/category/save"),
method: "post",
data: this.$http.adornData(that.categoryInfo, false),
})
.then(({ data }) => {
if (data && data.code === 0) {
that.$message({
message: "添加成功",
type: "success",
});
that.getMenus();
that.dialogVisible = false;
//设置默认展开
that.expandKeys = [that.categoryInfo.parentCid];
} else {
that.$message({
message: "添加失败",
type: "warning",
});
}
that.loading = false;
})
.catch(() =