树状结构中最大的问题就是关系问题,在数据库中,每条数据通过某个字段关联自己的父节点,每个业务中这个字段的名字都不同,如何解决这个问题呢?
使用
定义结构
我们假设要构建一个菜单,可以实现系统管理和店铺管理,菜单的样子如下:
系统管理
|- 用户管理
|- 添加用户
店铺管理
|- 商品管理
|- 添加商品
那这种结构如何保存在数据库中呢?一般是这样的:
我们看到,每条数据根据parentId相互关联并表示层级关系,parentId在这里也叫外键。
树结构工具-TreeUtil
实际使用后端代码
/*查询结果*/
List<Category> categoryLst = categoryService.select(query);
//配置树形结构
TreeNodeConfig treeNodeConfig = new TreeNodeConfig();
// 最大递归深度
treeNodeConfig.setDeep(categoryQueryDto.getDeep());
//转换器
String parentId = categoryQueryDto.getParentId() == null ? "0" : categoryQueryDto.getParentId().toString();
List<Tree<String>> treeNodes = TreeUtil.build(categoryLst, parentId, treeNodeConfig,
(category, tree) -> {
tree.setId(category.getId().toString());
tree.setParentId(category.getParentId().toString());
tree.setWeight(category.getSeq());
tree.setName(category.getName());
// 扩展属性为了和前端vue属性保持一致
tree.putExtra("value", category.getId().toString());
tree.putExtra("label", category.getName());
tree.putExtra("img", category.getImg());
tree.putExtra("status", category.getStatus());
tree.putExtra("lastUpdateBy", category.getLastUpdateBy());
tree.putExtra("lastUpdateTime", category.getLastUpdateTime());
tree.putExtra("statusX", CategoryStatus.findByStatus(category.getStatus()).getName());
});
return treeNodes;
前端vue页面
<template>
<el-form :model="form" label-width="120px">
<el-form-item label="品类名称">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="图片">
<el-upload
class="avatar-uploader"
ref="uploadRef"
:auto-upload="false"
:show-file-list="false"
:on-change="onchange"
>
<img v-if="form.img" :src="form.img" class="avatar" />
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
</el-upload>
</el-form-item>
<el-form-item label="父类">
<el-tree-select v-model="form.parentId" :data="categoryData" />
</el-form-item>
<el-form-item label="排序号">
<el-input v-model="form.seq" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">保存</el-button>
<el-button>重置</el-button>
</el-form-item>
</el-form>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted } from "vue";
import http from "@/http/index";
import { useRoute, useRouter } from "vue-router";
import type { UploadInstance } from "element-plus";
const uploadRef = ref<UploadInstance>();
const route = useRoute();
const router = useRouter();
const form = reactive({
name: "",
seq: 0,
img: "",
parentId:"0",
imgName:"",
});
const categoryData = ref([
{ value: "0", label: "根节点" }]);
onMounted(()=>{
http
.post("/api/category/select", {
// parentId: 0,
// status:1
// deep:3
})
.then((data: any) => {
categoryData.value=categoryData.value.concat(data);
})
.catch((err: any) => {
console.log(err);
});
})
// 上传文件
const onchange = (file: any, fileList: any ) => {
var reader = new FileReader();
reader.readAsDataURL(file.raw);
reader.onload = () => {
form.img = reader.result;
form.imgName=file.raw.name;
console.log(reader.result)
console.log(file.raw.name)
};
};
const onSubmit = () => {
http
.post("/api/category/insert", {
name: form.name,
img: form.img,
seq:form.seq,
parentId:form.parentId,
imgName:form.imgName,
})
.then((res: any) => {
console.log(res);
})
.catch((err: any) => {
console.log(err);
});
};
</script>
<style scoped>
.avatar-uploader .avatar {
width: 80px;
height: 80px;
display: block;
}
</style>
<style>
.avatar-uploader .el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}
.avatar-uploader .el-upload:hover {
border-color: var(--el-color-primary);
}
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 80px;
height: 80px;
text-align: center;
}
</style>