计算机系统应用教程网站

网站首页 > 技术文章 正文

JAVA全栈CMS系统Vue无限级分类拖拽增改查批量删除7

btikc 2024-09-20 15:01:53 技术文章 21 ℃ 0 评论

1.实现页面布局效果



2.后端接口搭建,持久层sql使用mybatis及自定义sql

  1. 创建子分类集合children,缓存子分类虚拟属性
public class CategoryChildrenDto extends CategoryDto{
    private List<CategoryChildrenDto> children;

    public List<CategoryChildrenDto> getChildren() {
        return children;
    }

    public void setChildren(List<CategoryChildrenDto> children) {
        this.children = children;
    }

    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("CategoryChildrenDto{");
        sb.append("children=").append(children);
        sb.append('}');
        return sb.toString();
    }
}

2.service生成tree型结构数据

//tree型结构输出list
public List<CategoryChildrenDto> categoryTreeList(){
    //1.获取所有分类
    CategoryExample categoryExample=new CategoryExample();
    categoryExample.setOrderByClause("sort asc");
    List<Category> categoryList=categoryMapper.selectByExample(categoryExample);
    List<CategoryChildrenDto> categoryChildrenList=DuplicateUtil.copyList(categoryList,CategoryChildrenDto.class);
    //2.组装tree型结构
    //2.1获取一级分类:过滤
    List<CategoryChildrenDto> level1=categoryChildrenList.stream().filter((category)->{
        return category.getParentId().equals("00000000");
        //递归设置子分类
    }).map((cat)->{
        cat.setChildren(getCategoryChildren(cat,categoryChildrenList));
        return cat;
        //遍历结果排序
    }).sorted((cat1,cat2)->{
        return (cat1.getSort()==null?0:cat1.getSort())-(cat2.getSort()==null?0:cat2.getSort());
    }).collect(Collectors.toList());

    return level1;
}
//获取某一个分类的子分类,需要传入(current当前分类实体,all所有分类实体集合),递归查询子分类
private List<CategoryChildrenDto> getCategoryChildren(CategoryChildrenDto categoryChildrenDto,List<CategoryChildrenDto> categoryChildrenDtoList){
    //1.过滤菜单
    List<CategoryChildrenDto> categoryChildren= categoryChildrenDtoList.stream().filter(category->{
        //1.1父子分类判断
        return category.getParentId().equals(categoryChildrenDto.getUniId());
        //1.2递归查询,再次自调用获取子分类
    }).map((category)->{
        category.setChildren(getCategoryChildren(category,categoryChildrenDtoList));
        return category;
        //1.3排序
    }).sorted((cat1,cat2)->{
        //return cat1.getSort()-cat2.getSort();
        //避免空指针异常
        return (cat1.getSort()==null?0:cat1.getSort())-(cat2.getSort()==null?0:cat2.getSort());
        //生成集合
    }).collect(Collectors.toList());
    return categoryChildren;
}

3.controller生成treeList查询接口

//tree结构分类
@RequestMapping("/treeList")
public ResponseDataDto getTreeList(){
    ResponseDataDto responseData=new ResponseDataDto();
    List<CategoryChildrenDto> categoryDtoList=categoryService.categoryTreeList();
    responseData.setResponseData(categoryDtoList);
    return responseData;
}

4.postman测试



3.前端category引用el-tree


  • el-tree参数说明

:props绑定传入的参数及名称

:expand-on-click="false"取消点击行收缩/展开子分类,只有点击箭头时展开

@node-click:节点点击事件

show-checkbox:显示选择框

node-key:做整个树状结构的唯一标识,对应后端sql的uniId

default-expand-all:默认展开全部节点

default-expand-keys:默认展开的节点的数组

draggable:开启拖拽

:allow-drop:判断目标节点能否被放置 Function(draggingNode, dropNode, type)

@node-drop:拖拽成功时,触发的事件。被拖拽节点对应的 Node、结束拖拽时最后进入的节点、被拖拽节点的放置位置(before、after、inner)、event

|- type='prev'、'inner' 和 'next',分别表示放置在目标节点前、插入至目标节点和放置在目标节点后

ref=""为tree起别名,用于批量删除

|-(leafOnly, includeHalfChecked) 接收两个 boolean 类型的参数

1. 是否只是叶子节点,默认值为 false

2. 是否包含半选节点,默认值为 false

注意:传入一个数组


--》设置4级分类:3级分类显示append,4级分类显示remove

|-add判定:绑定level属性,sql中默认属性

|-remove判定

A:可以使用绑定的node属性,获取子节点信息的属性名=childNodes --》 v-if="node.level<=3"

node中的parent包含父节点信息data:node-》parent-》data-》list[0]-》uniId,用于展开之前操作元素的父级菜单

B:可以使用绑定的data属性,获取子节点信息的属性名=children --》 v-if="node.childNodes.length===0"


<template>
    <div class="categoryTable">
        <div class="">
            <el-divider class="topLine"><i class="lineIcon el-icon-document-copy"></i><span
                    class="lineTitle">模块-总分类表列表</span></el-divider>
        </div>

        <div class="switchDrag">
            <!--绑定switchDraggable参数,默认false,点击拖拽,显示批量保存-->
            <el-switch class="switchDragChoose"
                       v-model="switchDraggable"
                       active-text="开启拖拽"
                       inactive-text="关闭拖拽">
            </el-switch>
            <el-button v-if="switchDraggable" type="primary" class="switchDragBtn" @click="batchDraggableSave">批量保存
            </el-button>
            <el-button type="danger" class="switchDragBtn" @click="batchDel">批量删除</el-button>
        </div>
        <div class="categoryTree">
            <el-tree
                    :data="categorys"
                    :props="defaultProps"
                    :expand-on-click-node="false"
                    node-key="uniId"
                    show-checkbox
                    :default-expanded-keys="expendedKeys"
                    :draggable="switchDraggable"
                    :allow-drop="allowDrop"
                    @node-drop="handleDrop"
                    ref="categoryTree">
                <span class="tree-node" slot-scope="{node,data}">
                    <span class="nodeIcon glyphicon glyphicon-folder-open"></span>
                    <span class="labelName">{{node.label}}</span>
                    <span class="option-btn">
                    <el-button class="opt-btn-add" icon="el-icon-circle-plus" v-if="node.level<=3" type="primary"
                               size="mini" @click="() => append(data)" plain>新增</el-button>
                    <el-button class="opt-btn-edit" icon="el-icon-question"
                               type="primary"
                               size="mini" @click="() => edit(data)" plain>修改</el-button>
                    <el-button class="opt-btn-del" icon="el-icon-remove" v-if="node.childNodes.length===0"
                               type="primary" size="mini" @click="() => del(node,data)" plain>刪除</el-button>
                    </span>
                </span>

            </el-tree>
        </div>
        <!--
        :visible.sync :接收一个boolean布尔值,true时显示
        -->
        <div class="dialogModal">
            <el-dialog
                    :title="dialogTitle"
                    :visible.sync="dialogVisible"
                    width="30%"
                    :close-on-click-modal="false"
            >
                <el-form :model="category" ref="category" :rules="rules">
                    <el-form-item label="分类名称">
                        <el-input v-model="category.name" autocomplete="off"></el-input>
                    </el-form-item>
                    <el-form-item label="排序">
                        <el-input v-model="category.sort" 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="save('category')">确 定</el-button>
                </span>
            </el-dialog>

        </div>

    </div>
</template>

4.后端删除接口生成

  • controller的del方法
    /**
     * 指定请求的格式为Delete
     * 4.删除模块,如果为多个参数,就定义多个/{param}/{param}
     */
    @DeleteMapping("/del/{uniId}")
    public ResponseDataDto del(@PathVariable String uniId) {
        LOG.info("传入的category uniId:{}", uniId);
        ResponseDataDto responseData = new ResponseDataDto();
        categoryService.delete(uniId);
        return responseData;
    }
  • service的实现
    //4.删除模块
    public void delete(String uniId) {
        CategoryExample categoryExample = new CategoryExample();
        categoryMapper.deleteByPrimaryKey(uniId);
    }
  • 前端页面调用接口
//真删
            del(node, category) {
                console.log("del的node:", node, "del的category:", category);
                /**
                 * 前端=》路径内携带参数使用url:'../'+{param}
                 * 后端=》requestMapping('../{param}')
                 *       void ...(@PathVariable String {param})
                 *
                 * ***引用toast的showConfirm全局方法,需要定义局部变量_this
                 * ***并且将存入的category转化为局部对象,才能获取到uniId
                 */
                let _this = this;
                let categoryParam = category;

                //el的确认框
                this.$confirm(`是否删除当前【${category.name}】分类?`, '提示', {
                    confirmButtonText: '确定',
                    cancelButtonText: '取消',
                    type: 'warning'
                }).then(() => {
                    _this.$axios.delete(process.env.VUE_APP_SERVER + '/business/admin/category/del/' + categoryParam.uniId)
                        .then((response) => {
                            let resp = response.data;
                            if (resp.success) {
                                this.$message({
                                    type: 'success',
                                    message: `【${category.name}】分类删除成功!`
                                });
                                console.log("删除模块-总分类表成功,删除的模块-总分类表名:", _this.category.name);
                                _this.treeList();
                                //删除成功,展开之前的父节点
                                this.expendedKeys = [node.parent.data.uniId];
                            }
                        })

                })
                    .catch(() => {
                        this.$message({
                            type: 'info',
                            message: '已取消删除'
                        })
                    })
            },


5.element-node节点获取父节点,展开之前操作的分类子分类,页面参数图解

6.后端接口生成:新增/修改

  • controller-save(新增/修改统一接口)
//3.新增,流方式传参,加入@RequestBody
@PostMapping("/save")
public ResponseDataDto save(@RequestBody CategoryDto categoryDto) {
    LOG.info("传入的category DTO:{}", categoryDto);

    ValidatorUtil.requiredEmpty(categoryDto.getName(), "名称");
    ValidatorUtil.requiredLength(categoryDto.getName(), "名称", 3, 255);
    ValidatorUtil.requiredEmpty(categoryDto.getParentId(), "父ID");
    ResponseDataDto responseData = new ResponseDataDto();
    categoryService.save(categoryDto);
    responseData.setResponseData(categoryDto);
    return responseData;
}
  • service-实现新增/修改/删除方法
//3.新增、修改category,将传入的id转为category对象本身
public void save(CategoryDto categoryDto) {
    Category category = DuplicateUtil.copy(categoryDto, Category.class);
    if (StringUtils.isEmpty(categoryDto.getUniId())) {
        this.insert(category);
    } else {
        this.update(category);
    }

}

//4.删除模块
public void delete(String uniId) {
    CategoryExample categoryExample = new CategoryExample();
    categoryMapper.deleteByPrimaryKey(uniId);
}

//5.向外暴露dto,不暴露实体类:插入数据
private void insert(Category category) {
    category.setUniId(UUID8Util.getShortUUID());
    if (category.getParentId() == null) {
        category.setParentId("0");
    }
    if (category.getModuleId() == null) {
        category.setModuleId("0");
    }
    if (category.getLevel() == null) {
        category.setLevel(0);
    }

    categoryMapper.insert(category);
}

//6.更新模块
private void update(Category category) {
    try {
        Date now = new Date();
        String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(now);
        long time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(date).getTime();
        int timeInt = (int) (time / 1000);

    } catch (ParseException e) {
        e.printStackTrace();
    }

    categoryMapper.updateByPrimaryKey(category);
}
  • 前端新增/更新调用后端接口,modal模态框传参页面实现
 <!--
        :visible.sync :接收一个boolean布尔值,true时显示
        -->
        <div class="dialogModal">
            <el-dialog
                    :title="dialogTitle"
                    :visible.sync="dialogVisible"
                    width="30%"
                    :close-on-click-modal="false"
            >
                <el-form :model="category" ref="category" :rules="rules">
                    <el-form-item label="分类名称">
                        <el-input v-model="category.name" autocomplete="off"></el-input>
                    </el-form-item>
                    <el-form-item label="排序">
                        <el-input v-model="category.sort" 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="save('category')">确 定</el-button>
                </span>
            </el-dialog>

        </div>

....
<script>
  append(data) {
                console.log("新增分类:", data);
                this.dialogType="新增分类";
                this.dialogTitle="新增模块分类";
                this.category={};
                this.dialogVisible = true;
                this.category.parentId = data.uniId;
                this.category.level = data.level * 1 + 1;
                this.category.isShow = 1;
                //this.category.sort = data.sort * 1 + 1;
            },
save(formName) {
                let responseMsg = '';
                console.log("新增分类:", this.category);
                //前端校验
                this.$refs[formName].validate((valid) => {
                    if (valid) {

                        this.$axios.post(process.env.VUE_APP_SERVER + '/business/admin/category/save', this.category)
                            .then((response) => {
                                let resp = response.data;
                                responseMsg = response;
                                console.log("响应的错误信息:", responseMsg);
                                console.log("response.data:", resp);

                                if (resp.success) {
                                    console.log("保存总分类表成功:", resp.responseData);
                                    //关闭对话框
                                    this.dialogVisible = false;
                                    this.treeList();
                                    //展开父级菜单
                                    this.expendedKeys = [this.category.parentId];
                                    toast.success("保存成功", "bottom-end");

                                } else {
                                    this.$notify({
                                        title: '填写内容错误',
                                        message: resp.responseMsg,
                                        position: "top-right",
                                        type: 'warning'
                                    });
                                }
                            })
                    } else {
                        console.log('error submit!!');
                        this.$notify({
                            title: '填写内容错误',
                            message: '请按照提示内容填写正确信息',
                            position: "top-right",
                            type: 'warning'
                        });
                        return false;
                    }
                });

            },
            //4.修改
            edit(category) {
                console.log("edit的category:", category);
                /*jquery继承对象: $.extend({新对象},旧对象)
                避免vue数据绑定漏洞,更改数据时,随之更改显示的data,但实际没有进行真实保存数据库
                 */
                this.dialogType="更新分类";
                this.dialogTitle="更新模块分类";
                this.dialogVisible=true;
                this.category = category;
            },

  </script>
  • 实现效果


5.element-el-tree(draggingNode、dropNode、type全图解析)

参数说明:【开启:allow-drop=" allowDrop "】

draggingNode.level:当前node节点level

draggingNode.data:当前拖动节点属性

draggingNode.data.level:当前拖动节点层级

draggingNode.data.children:当前拖动节点à子节点属性

draggingNode.data.parenId:当前拖动节点à父节点ID

draggingNode.parent.data:当前拖动节点à父节点属性

draggingNode.parent.level:当前拖动节点à父节点层级

参数说明【开启@node-drop="nodeDrop"】返回拖拽成功节点

dropNode.parent.childNodes[*].data.*:获取拖拽到目标节点的所有子节点,包括draggingNode位置索引

6.后端批量拖拽/删除更新接口

  • controller增加接口:/update/level,请求体为json数组dto-list
//6.拖拽接口:批量修改level/sort和parentId
@RequestMapping("/update/level")
public ResponseDataDto updateLevel(@RequestBody CategoryDto[] categoryDtos){
    ResponseDataDto responseData=new ResponseDataDto();
    //返回collection集合,需要asList转换
    responseData.setResponseData(categoryService.updateLevelByUniId(categoryDtos));
    return responseData;
}

//7.批量删除
@DeleteMapping("/delBatch")
public ResponseDataDto delBatch(@RequestBody String[] uniIds){
    ResponseDataDto responseData=new ResponseDataDto();
    responseData.setResponseData(categoryService.delBatchCategory(uniIds));
    return responseData;
}
  • server-common创建方法,将传入的数组转化为list,解构传入sql实现
//8.批量更新节点level
public List<Category> updateLevelByUniId(CategoryDto[] categoryDto){
    //1.将传入的数组,转化为dtoList
    List<CategoryDto> categoryDtoList=Arrays.asList(categoryDto);
    //2.将dtoList复制到domainList
    List<Category> categoryList=DuplicateUtil.copyList(categoryDtoList,Category.class);
    //3.循环domainList
    for(int i=0;i<categoryList.size();i++){
        //4.获取每一个category对象
        Category category=categoryList.get(i);
        //5.批量更新sql
        categoryMapper.updateByPrimaryKeySelective(category);
    }
    //boolean flag=commonModuleMapper.updateBatchDraggingById(categoryList);
    return categoryList;
}

//9.批量删除
public int delBatchCategory(String[] uniIds){
    CategoryExample categoryExample=new CategoryExample();
    //将String转化为list
    categoryExample.createCriteria().andUniIdIn(Arrays.asList(uniIds));
    //1.根据传入的数组删除
    int flag=categoryMapper.deleteByExample(categoryExample);
    return flag;
}
  • 前端页面更新方法,注意请求参数this.xxx



  • gitee提交,源码开放,长期维护,欢迎fork,关注,mark,点赞,收藏,转发

gitee地址:https://gitee.com/cevent_OS/yameng-cevent-source-cloudcenter.git

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表