Tree 组件设计文档
概述
Tree 组件是本组件库中用于展示树形结构数据的核心组件。它延续了我们组件库一贯的设计理念,采用"关注点分离"和"逻辑抽象"的原则,通过 tree.vue 和 treeNode.vue 等核心组件实现了一套完整且易用的树形结构解决方案。
Tree 组件专注于处理树形数据的展示、交互和样式管理,为用户提供了一套强大、灵活的树形结构管理功能,支持节点展开/折叠、复选框选择、搜索过滤等多种功能,有效提升用户在处理层级数据时的体验。
设计理念
分层架构
Tree 组件严格按照三层架构设计:
- 结构层 tree.vue, treeNode.vue - 负责组件的 DOM 结构和基本布局
- 逻辑层 type.ts 和组件内部逻辑 - 处理复杂的响应式计算和交互逻辑
- 表现层 tree.scss, treeNode.scss - 定义组件的视觉呈现和样式规则
逻辑抽象
Tree 组件将复杂的逻辑抽象为核心函数:
- toggle - 处理节点展开/折叠逻辑
- handleCheck - 处理节点选择逻辑
- flattenTreeDatan - 处理树形数据的扁平化
- findNodeByKey - 处理节点查找逻辑
这种设计使得组件本身只需要声明所需逻辑,而具体的计算由逻辑函数完成。
核心功能设计
Props 结构化设计
Tree 组件的 props 设计简洁明了:
typescript
defineProps<{
/** 树形数据 */
data: TreeNodes[]
/** 树配置 */
props: TreeProps
/** 默认展开 */
defaultExpanded?: boolean
/** 默认展开的节点key */
defaultExpandedKeys?: ExpandedKeys
/** 是否显示复选框 */
showCheckbox?: boolean
/** 是否显示箭头 */
showArrow?: boolean
/** 选中的节点keys */
checkedKeys?: (string | number)[]
/** 是否严格模式(不级联选择) */
checkStrictly?: boolean
/** 宽度 */
width?: string
/** 高度 */
height?: string
}>()参数说明:
- data:树形数据源。
- props:树配置,定义 label、children、value等字段。
- showCheckbox:控制是否显示复选框。
- checkStrictly:控制是否严格模式(不级联选择)。
- defaultExpandedKeys:控制默认展开的节点。
TreeSelect 组件设计
Tree 组件还提供 treeSelect 组件,用于实现下拉树选择功能:
typescript
export const treeSelectProps = {
/** 绑定值 */
modelValue: {
type: [String, Number, Array] as PropType<string | number | (string | number)[] | null>,
default: null
},
/** 是否多选 */
multiple: {
type: Boolean,
default: false
},
/** 是否可过滤 */
filterable: {
type: Boolean,
default: false
},
/** 是否显示复选框 */
showCheckbox: {
type: Boolean,
default: false
},
/** 是否严格模式(不级联选择) */
checkStrictly: {
type: Boolean,
default: false
},
}核心功能:
- 下拉选择:通过点击输入框显示树形结构
- 搜索过滤:通过 filterable 属性启用搜索功能
- 多选支持:通过 multiple 属性控制是否多选
- 标签显示:已选择的节点显示为标签
样式系统设计
CSS 变量控制
Tree 组件通过 CSS 变量实现样式控制:
--tree-bg-color- 树背景色--tree-text-color- 树文本颜色--tree-active-color- 激活状态颜色--tree-input-height- 输入框高度--tree-arrow-color- 箭头颜色
布局系统
Tree 组件支持灵活的布局控制:
- 层级缩进 - 通过子节点的
margin-left实现 - 连接线样式 - 通过 CSS 绘制节点连接线
- 布局方向 - 支持垂直树形结构
- 响应式设计 - 适配不同尺寸
状态样式
Tree 组件根据不同状态提供相应的样式:
- 默认状态 - 标准树形样式
- 展开状态 - 显示子节点和连接线
- 选中状态 - 高亮显示选中节点
- 悬停状态 - 行悬停时的背景色变化
- 禁用状态 - 通过 disabled 属性控制
组件结构设计
整体结构
Tree 组件的整体结构如下:
html
<div class="my-tree" :style="treeStyle">
<TreeNode
v-for="node in data"
:key="getNodeKey(node)"
:node="node"
:treeProps="internalProps"
:defaultExpanded="defaultExpanded || defaultExpandedKeys?.includes(getNodeKey(node))"
:showCheckbox="showCheckbox"
:showArrow="showArrow"
:checkedKeys="checkedKeys"
:checkStrictly="checkStrictly"
@node-click="handleNodeClick"
@check-change="handleCheckChange"
/>
</div>TreeNode 组件的结构:
html
<div class="my-tree-node">
<div class="my-tree-node__content" :class="{ expanded, leaf: isLeaf, selected: isSelected }"
@click="handleContentClick">
<!-- 展开箭头 -->
<span v-if="showArrow && !isLeaf" class="arrow" :class="{ expanded }" @click.stop="toggle">
{{ expanded ? '▼' : '▶' }}
</span>
<!-- 复选框 -->
<MYCheckbox
v-if="showCheckbox"
:modelValue="isChecked"
:indeterminate="isIndeterminate"
:disabled="isDisabled"
@update:modelValue="handleCheck"
class="tree-checkbox"
/>
<!-- 节点标签 -->
<span class="label" @click.stop="handleLabelClick">
{{ node[treeProps.label] }}
</span>
</div>
<!-- 子节点 -->
<div v-if="expanded && !isLeaf" class="my-tree-node__children">
<TreeNode
v-for="child in children"
:key="getNodeKey(child)"
:node="child"
:treeProps="treeProps"
:showCheckbox="showCheckbox"
:showArrow="showArrow"
:checkedKeys="checkedKeys"
:checkStrictly="checkStrictly"
@node-click="$emit('node-click', $event)"
@check-change="$emit('check-change', $event)"
/>
</div>
</div>组件层次
Tree 组件包含多个主要部分:
- 树容器 -
.my-tree- 包裹整个树形组件 - 树节点 -
.my-tree-node- 单个树节点的容器 - 节点内容 -
.my-tree-node__content- 节点内容区域 - 展开箭头 -
.arrow- 控制节点展开/折叠 - 复选框 -
.tree-checkbox- 节点选择控件 - 节点标签 -
.label- 显示节点文本 - 子节点容器 -
.my-tree-node__children- 包含子节点
交互设计
节点展开/折叠
Tree 组件通过 toggle方法实现节点展开/折叠:
typescript
const toggle = () => {
if (!isLeaf.value) {
expanded.value = !expanded.value
}
}交互规则:
- 点击箭头图标:切换展开/折叠状态
- 点击内容区域(无复选框时):切换展开/折叠状态
- 叶子节点:不显示箭头,不可展开
选择功能
Tree 组件通过 handleCheck 方法实现节点选择:
typescript
const handleCheck = (checked: boolean) => {
const nodeKey = props.node[nodeValueKey.value] as string | number
let newCheckedKeys = [...(props.checkedKeys || [])]
if (checked) {
// 添加当前节点
if (!newCheckedKeys.includes(nodeKey)) {
newCheckedKeys.push(nodeKey)
}
// 如果不是严格模式且不是叶子节点,需要级联选择子节点
if (!props.checkStrictly && !isLeaf.value) {
const addChildKeys = (nodes: TreeNodesArray) => {
nodes.forEach(child => {
const childKey = child[nodeValueKey.value] as string | number
if (!newCheckedKeys.includes(childKey)) {
newCheckedKeys.push(childKey)
}
const childNodes = child[props.treeProps.children] as TreeNodesArray || []
if (childNodes.length > 0) {
addChildKeys(childNodes)
}
})
}
addChildKeys(children.value)
}
} else {
// 取消选中当前节点
newCheckedKeys = newCheckedKeys.filter(k => k !== nodeKey)
// 如果不是严格模式且不是叶子节点,需要级联取消子节点
if (!props.checkStrictly && !isLeaf.value) {
const removeChildKeys = (nodes: TreeNodesArray) => {
nodes.forEach(child => {
const childKey = child[nodeValueKey.value] as string | number
newCheckedKeys = newCheckedKeys.filter(k => k !== childKey)
const childNodes = child[props.treeProps.children] as TreeNodesArray || []
if (childNodes.length > 0) {
removeChildKeys(childNodes)
}
})
}
removeChildKeys(children.value)
}
}
emit('check-change', newCheckedKeys)
}选择模式:
- 严格模式:
checkStrictly=true,节点选择互不影响 - 非严格模式:
checkStrictly=false,级联选择(选中父节点自动选中所有子节点)
搜索过滤
TreeSelect 组件通过 filterText实现搜索过滤:
typescript
const filteredData = computed(() => {
if (!filterText.value) return props.data
const filterNodes = (nodes: TreeNodesArray): TreeNodesArray => {
return nodes.filter(node => {
const label = node[props.props.label] as string
const match = label.toLowerCase().includes(filterText.value.toLowerCase())
if (match) return true
const children = node[props.props.children] as TreeNodesArray || []
if (children.length && filterNodes(children).length > 0) return true
return false
}).map(node => ({
...node,
[props.props.children]: filterNodes(node[props.props.children] as TreeNodesArray || [])
}))
}
return filterNodes(props.data)
})过滤规则:
- 匹配节点标签
- 如果子节点匹配,父节点也显示
- 保持树形结构,只过滤不匹配的节点
扩展性设计
Tree 组件具备良好的扩展性:
- 树形结构:通过 props 属性灵活定义树形结构
- 复选框支持:通过 showCheckbox 属性控制是否显示复选框
- 箭头显示:通过 showArrow 属性控制是否显示箭头
- 选择模式:通过 checkStrictly 属性控制选择模式
- 树选择组件:通过 treeselect 组件扩展为下拉选择功能
- 样式定制:通过 CSS 变量定制组件样式
样式细节
Tree 组件在样式处理上有以下特点:
- 响应式设计:支持灵活的布局适配
- 一致性:与组件库其他组件保持样式一致性
- 无障碍设计:确保键盘导航和屏幕阅读器友好
- 清晰的视觉层次:通过缩进和连接线创建清晰的视觉层次
- 状态反馈:提供清晰的视觉反馈(如展开状态、选中状态)
性能优化
通过以下方式优化性能:
- 使用计算属性缓存树形数据处理结果
- 将复杂逻辑移至独立的函数中
- 避免不必要的重新渲染
- 使用 Vue 3 Composition API 提高运行效率
- 按需渲染树节点(只渲染展开的节点)
- 使用 CSS 变量动态调整样式
设计价值
Tree 组件延续了我们组件库的设计哲学:
- 关注点分离:结构、样式、逻辑完全解耦
- 高度复用:通过函数抽象通用逻辑
- 易于维护:清晰的代码结构和命名规范
- 可扩展性强:支持丰富的定制选项
- 一致性:与 Cascader、Menu 等其他组件保持一致的设计语言