依据传入的树形结构渲染 并选择节点
根据产品经理提出的原型图做了一个根据传入的数据渲染选择节点
思路
1.根据数据结构
{ id: '2', name: '店铺', children: [ { id: '2-1', name: '店铺管理', children: [...] } ] }
来进行渲染列表
3.父子关系管理
- 使用 Map 存储节点间的父子关系
- buildParentMap 方法初始化时建立映射
- getParentIds 和 getChildrenIds 方法用于获取相关节点
3.选中逻辑
3-1选中某个节点时:
- 获取所有子节点 ID
- 获取所有父节点 ID
- 将这些 ID 添加到选中列表
3-2取消选中时:
移除当前节点及其子节点
4.组件通信
- 使用 v-model 进行数据双向绑定
- 通过自定义事件 parent-check 处理选中状态变化
5.样式设计
使用 flex 布局实现层级结构
- 不同层级使用不同的样式
- 最后一层使用横向排列
实现代码
父组件 PermissionSelect.vue
<template>
<div class="permission-select">
<div class="permission-header">
<div>
<span class="required">*</span>
选择权限(仅适用于本店铺)
</div>
<div class="operation-btns">
<el-button type="text" @click="handleCheckAll">全选</el-button>
<el-button type="text" @click="handleClear">清空</el-button>
</div>
</div>
<el-checkbox-group v-model="checkedPermissions" @change="handleCheckedChange">
<div v-for="(item, index) in permissionData" :key="index" class="permission-group">
<permission-level
:item="item"
:depth="1"
v-model="checkedPermissions"
@parent-check="handleParentCheck"
/>
</div>
</el-checkbox-group>
</div>
</template>
<script>
import PermissionLevel from './PermissionLevel.vue'
export default {
name: 'PermissionSelect',
components: {
PermissionLevel
},
data() {
return {
checkedPermissions: [],
permissionData: [
{
id: '1',
name: '概况'
},
{
id: '2',
name: '店铺',
children: [
{
id: '2-1',
name: '店铺管理',
children: [
{
id: '2-1-1',
name: '移动门店',
children: [
{ id: '2-1-1-1', name: '查看' },
{ id: '2-1-1-2', name: '编辑' },
{ id: '2-1-1-3', name: '禁用' },
{ id: '2-1-1-4', name: '启用' },
{ id: '2-1-1-5', name: '推广' }
]
},
{
id: '2-1-2',
name: '门店管理',
children: [
{ id: '2-1-2-1', name: '查看' },
{ id: '2-1-2-2', name: '新增' },
{ id: '2-1-2-3', name: '编辑' },
{ id: '2-1-2-4', name: '下架' },
{ id: '2-1-2-5', name: '推广' },
{ id: '2-1-2-6', name: '商品管理' },
{ id: '2-1-2-7', name: '关联仓库' }
]
}
]
},
{
id: '2-2',
name: '店铺装修',
children: [
{ id: '2-2-1', name: '自主装修' },
{ id: '2-2-2', name: '店铺主页' },
{ id: '2-2-3', name: '营养窗' }
]
}
]
},
{
id: '3',
name: '商品',
children: [
{
id: '3-1',
name: '商品管理',
children: [
{
id: '3-1-1',
name: '商品管理',
children: [
{ id: '3-1-1-1', name: '查看' },
{ id: '3-1-1-2', name: '新增' },
{ id: '3-1-1-3', name: '编辑' },
{ id: '3-1-1-4', name: '上架' },
{ id: '3-1-1-5', name: '下架' },
{ id: '3-1-1-6', name: '删除' },
{ id: '3-1-1-7', name: '改分类' }
]
}
]
}
]
}
],
// 添加一个用于存储父子关系的映射
parentMap: new Map()
}
},
created() {
// 初始化时建立父子关系映射
this.buildParentMap(this.permissionData)
},
methods: {
// 建立父子关系映射
buildParentMap(data, parent = null) {
data.forEach(item => {
if (parent) {
this.parentMap.set(item.id, parent)
}
if (item.children) {
this.buildParentMap(item.children, item)
}
})
},
// 获取所有父节点ID
getParentIds(id) {
const parentIds = []
let currentParent = this.parentMap.get(id)
while (currentParent) {
parentIds.push(currentParent.id)
currentParent = this.parentMap.get(currentParent.id)
}
return parentIds
},
// 获取所有子节点ID
getChildrenIds(item) {
let ids = []
if (item.children) {
item.children.forEach(child => {
ids.push(child.id)
ids = ids.concat(this.getChildrenIds(child))
})
}
return ids
},
// 处理节点选中
handleParentCheck(checked, item) {
const updateIds = new Set()
// 获取所有子节点ID
const childrenIds = this.getChildrenIds(item)
childrenIds.forEach(id => updateIds.add(id))
// 获取所有父节点ID
const parentIds = this.getParentIds(item.id)
parentIds.forEach(id => updateIds.add(id))
// 添加当前节点ID
updateIds.add(item.id)
if (checked) {
// 选中时,将所有相关节点添加到选中列表
this.checkedPermissions = [...new Set([...this.checkedPermissions, ...updateIds])]
} else {
// 取消选中时,移除所有子节点
this.checkedPermissions = this.checkedPermissions.filter(id => !childrenIds.includes(id) && id !== item.id)
}
},
// 处理选中变化
handleCheckedChange(value) {
this.$emit('change', value)
},
// 获取所有权限ID
getAllPermissionIds(data = this.permissionData) {
let ids = []
data.forEach(item => {
ids.push(item.id)
if (item.children) {
ids = ids.concat(this.getAllPermissionIds(item.children))
}
})
return ids
},
// 全选
handleCheckAll() {
this.checkedPermissions = this.getAllPermissionIds()
},
// 清空
handleClear() {
this.checkedPermissions = []
}
}
}
</script>
<style lang="scss" scoped>
.permission-select {
.permission-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
.required {
color: #F56C6C;
margin-right: 4px;
}
.operation-btns {
.el-button {
padding: 0;
margin-left: 15px;
color: #409EFF;
font-size: 12px;
}
}
}
.el-checkbox-group {
border: 1px solid #EBEEF5;
border-radius: 2px;
}
.permission-group {
display: flex;
border: 1px solid #EBEEF5;
margin-bottom: 10px;
// 第一级复选框容器
> .el-checkbox {
width: 80px;
flex-shrink: 0;
padding: 10px;
border-right: 1px solid #EBEEF5;
}
.permission-sub-group {
flex: 1;
.sub-item {
display: flex;
border-bottom: 1px solid #EBEEF5;
&:last-child {
border-bottom: none;
}
// 第二级复选框容器
> .el-checkbox {
width: 100px;
flex-shrink: 0;
padding: 10px;
border-right: 1px solid #EBEEF5;
background-color: #FAFAFA;
}
.permission-actions {
flex: 1;
display: flex;
flex-wrap: wrap;
gap: 10px;
padding: 10px;
background-color: #FAFAFA;
// 第三级复选框
.el-checkbox {
margin-right: 15px;
}
}
}
}
}
::v-deep .el-checkbox {
margin-right: 0;
.el-checkbox__input {
margin-right: 6px;
.el-checkbox__inner {
border-radius: 2px;
}
}
.el-checkbox__label {
font-weight: normal;
font-size: 13px;
color: #606266;
}
&.is-checked {
.el-checkbox__input {
.el-checkbox__inner {
background-color: #409EFF;
border-color: #409EFF;
}
}
}
}
// 最后一个组去掉底部边框
.permission-group:last-child {
margin-bottom: 0;
}
// 每个权限组之间的间隔
.permission-group + .permission-group {
margin-top: 10px;
}
}
</style>
子组件 PermissionLevel.vue
<template>
<div class="permission-level" :class="'level-' + depth">
<el-checkbox
:label="item.id"
@change="(val) => handleParentCheck(val, item)"
>
{{ item.name }}
</el-checkbox>
<div v-if="item.children" class="children-group">
<permission-level
v-for="(child, index) in item.children"
:key="index"
:item="child"
:depth="depth + 1"
v-model="checkedPermissions"
@parent-check="handleParentCheck"
/>
</div>
</div>
</template>
<script>
export default {
name: 'PermissionLevel',
props: {
item: {
type: Object,
required: true
},
depth: {
type: Number,
default: 1
},
modelValue: {
type: Array,
default: () => []
}
},
emits: ['update:modelValue', 'parent-check'],
computed: {
checkedPermissions: {
get() {
return this.modelValue
},
set(val) {
this.$emit('update:modelValue', val)
}
}
},
methods: {
handleParentCheck(checked, item) {
this.$emit('parent-check', checked, item)
}
}
}
</script>
<style lang="scss" scoped>
.permission-level {
display: flex;
&.level-1 {
> .el-checkbox {
width: 80px;
flex-shrink: 0;
padding: 10px;
border-right: 1px solid #EBEEF5;
}
}
&.level-2, &.level-3{
border-bottom: 1px solid #EBEEF5;
&:last-child {
border-bottom: none;
}
> .el-checkbox {
width: 100px;
flex-shrink: 0;
padding: 10px;
border-right: 1px solid #EBEEF5;
background-color: #FAFAFA;
}
}
.children-group {
flex: 1;
display: flex;
flex-direction: column;
// 最后一层的样式
.permission-level:not(.level-1):not(.level-2) {
.children-group {
display: flex;
flex-wrap: wrap;
flex-direction: row;
gap: 10px;
padding: 10px;
background-color: #FAFAFA;
.el-checkbox {
margin-right: 15px;
}
}
}
}
}
</style>

浙公网安备 33010602011771号