TreeView 树形列表
嵌套树形展示,支持单选 / 复选框(带级联)、展开 / 折叠、自定义图标。
基础用法
nodes 是一棵树(每个节点可有 children)。selectedKey + update:selectedKey 控制单选高亮;defaultExpandedKeys 设置初始展开的节点 key。disabled 节点不响应点击。
背景 视口
- src
- components
- Home.vue
- About.vue
- Contact.vue
- main.ts
- styles.css
- README.md
<script setup lang="ts">
import { ref } from 'vue';
import { CfTreeView, type TreeNode } from '@chufix-design/vue';
const selected = ref<string | null>('home');
const nodes: TreeNode[] = [
{
key: 'src',
label: 'src',
children: [
{
key: 'components',
label: 'components',
children: [
{ key: 'home', label: 'Home.vue' },
{ key: 'about', label: 'About.vue' },
{ key: 'contact', label: 'Contact.vue', disabled: true },
],
},
{ key: 'main', label: 'main.ts' },
{ key: 'styles', label: 'styles.css' },
],
},
{ key: 'readme', label: 'README.md' },
];
</script>
<template>
<div style="border: 1px solid var(--line-1); border-radius: 8px; padding: 8px; max-width: 18rem;">
<CfTreeView
:nodes="nodes"
:selected-key="selected"
:default-expanded-keys="['src', 'components']"
@update:selected-key="selected = $event"
/>
</div>
</template> <script setup>
import { ref } from 'vue';
import { CfTreeView } from '@chufix-design/vue';
const selected = ref<string | null>('home');
const nodes= [
{
key: 'src',
label: 'src',
children: [
{
key: 'components',
label: 'components',
children: [
{ key: 'home', label: 'Home.vue' },
{ key: 'about', label: 'About.vue' },
{ key: 'contact', label: 'Contact.vue', disabled: true },
],
},
{ key: 'main', label: 'main.ts' },
{ key: 'styles', label: 'styles.css' },
],
},
{ key: 'readme', label: 'README.md' },
];
</script>
<template>
<div style="border: 1px solid var(--line-1); border-radius: 8px; padding: 8px; max-width: 18rem;">
<CfTreeView
:nodes="nodes"
:selected-key="selected"
:default-expanded-keys="['src', 'components']"
@update:selected-key="selected = $event"
/>
</div>
</template> import { useState } from 'react';
import { CfTreeView } from '@chufix-design/react';
export default function Demo() {
const [selected, setSelected] = useState<string | null>('home');
const nodes: TreeNode[] = [
{
key: 'src',
label: 'src',
children: [
{
key: 'components',
label: 'components',
children: [
{ key: 'home', label: 'Home.vue' },
{ key: 'about', label: 'About.vue' },
{ key: 'contact', label: 'Contact.vue', disabled: true },
],
},
{ key: 'main', label: 'main.ts' },
{ key: 'styles', label: 'styles.css' },
],
},
{ key: 'readme', label: 'README.md' },
];
return (
<>
<div style={{ border: "1px solid var(--line-1)", borderRadius: 8, padding: 8, maxWidth: "18rem" }}>
<CfTreeView nodes={nodes} selectedKey={selected} defaultExpandedKeys={['src', 'components']} onSelectedKeyChange={() => setSelected($event)}
/>
</div>
</>
);
} import { useState } from 'react';
import { CfTreeView } from '@chufix-design/react';
export default function Demo() {
const [selected, setSelected] = useState<string | null>('home');
const nodes= [
{
key: 'src',
label: 'src',
children: [
{
key: 'components',
label: 'components',
children: [
{ key: 'home', label: 'Home.vue' },
{ key: 'about', label: 'About.vue' },
{ key: 'contact', label: 'Contact.vue', disabled: true },
],
},
{ key: 'main', label: 'main.ts' },
{ key: 'styles', label: 'styles.css' },
],
},
{ key: 'readme', label: 'README.md' },
];
return (
<>
<div style={{ border: "1px solid var(--line-1)", borderRadius: 8, padding: 8, maxWidth: "18rem" }}>
<CfTreeView nodes={nodes} selectedKey={selected} defaultExpandedKeys={['src', 'components']} onSelectedKeyChange={() => setSelected($event)}
/>
</div>
</>
);
} 复选框 + 级联
checkable 在每个节点前加复选框,配合 v-model (Vue) / value + onChange (React) 绑定 string[]。cascade=true(默认)让父子勾选互相联动 — 勾父勾全部子节点,勾全部子节点反过来勾父;半选状态自动算 indeterminate。
cascade=false 时各节点独立,常用于按行业 / 标签多选场景。
背景 视口
- src
- components
- Home.vue
- About.vue
- main.ts
- styles.css
- README.md
- src
- components
- Home.vue
- About.vue
- main.ts
- styles.css
- README.md
<script setup lang="ts">
import { ref } from 'vue';
import { CfTreeView, type TreeNode } from '@chufix-design/vue';
const checked = ref<string[]>(['styles']);
const nodes: TreeNode[] = [
{
key: 'src',
label: 'src',
children: [
{
key: 'components',
label: 'components',
children: [
{ key: 'home', label: 'Home.vue' },
{ key: 'about', label: 'About.vue' },
],
},
{ key: 'main', label: 'main.ts' },
{ key: 'styles', label: 'styles.css' },
],
},
{ key: 'readme', label: 'README.md' },
];
</script>
<template>
<div style="display:grid; grid-template-columns: 1fr 1fr; gap: 16px;">
<div style="border: 1px solid var(--line-1); border-radius: 8px; padding: 8px;">
<CfTreeView
v-model="checked"
:nodes="nodes"
checkable
:default-expanded-keys="['src', 'components']"
/>
</div>
<div style="border: 1px solid var(--line-1); border-radius: 8px; padding: 8px;">
<CfTreeView
v-model="checked"
:nodes="nodes"
checkable
:cascade="false"
:default-expanded-keys="['src', 'components']"
/>
</div>
</div>
</template> <script setup>
import { ref } from 'vue';
import { CfTreeView } from '@chufix-design/vue';
const checked = ref<string[]>(['styles']);
const nodes= [
{
key: 'src',
label: 'src',
children: [
{
key: 'components',
label: 'components',
children: [
{ key: 'home', label: 'Home.vue' },
{ key: 'about', label: 'About.vue' },
],
},
{ key: 'main', label: 'main.ts' },
{ key: 'styles', label: 'styles.css' },
],
},
{ key: 'readme', label: 'README.md' },
];
</script>
<template>
<div style="display:grid; grid-template-columns: 1fr 1fr; gap: 16px;">
<div style="border: 1px solid var(--line-1); border-radius: 8px; padding: 8px;">
<CfTreeView
v-model="checked"
:nodes="nodes"
checkable
:default-expanded-keys="['src', 'components']"
/>
</div>
<div style="border: 1px solid var(--line-1); border-radius: 8px; padding: 8px;">
<CfTreeView
v-model="checked"
:nodes="nodes"
checkable
:cascade="false"
:default-expanded-keys="['src', 'components']"
/>
</div>
</div>
</template> import { useState } from 'react';
import { CfTreeView } from '@chufix-design/react';
export default function Demo() {
const [checked, setChecked] = useState<string[]>(['styles']);
const nodes: TreeNode[] = [
{
key: 'src',
label: 'src',
children: [
{
key: 'components',
label: 'components',
children: [
{ key: 'home', label: 'Home.vue' },
{ key: 'about', label: 'About.vue' },
],
},
{ key: 'main', label: 'main.ts' },
{ key: 'styles', label: 'styles.css' },
],
},
{ key: 'readme', label: 'README.md' },
];
return (
<>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16 }}>
<div style={{ border: "1px solid var(--line-1)", borderRadius: 8, padding: 8 }}>
<CfTreeView value={checked} onChange={setChecked} nodes={nodes} checkable defaultExpandedKeys={['src', 'components']} />
</div>
<div style={{ border: "1px solid var(--line-1)", borderRadius: 8, padding: 8 }}>
<CfTreeView value={checked} onChange={setChecked} nodes={nodes} checkable cascade={false} defaultExpandedKeys={['src', 'components']} />
</div>
</div>
</>
);
} import { useState } from 'react';
import { CfTreeView } from '@chufix-design/react';
export default function Demo() {
const [checked, setChecked] = useState<string[]>(['styles']);
const nodes= [
{
key: 'src',
label: 'src',
children: [
{
key: 'components',
label: 'components',
children: [
{ key: 'home', label: 'Home.vue' },
{ key: 'about', label: 'About.vue' },
],
},
{ key: 'main', label: 'main.ts' },
{ key: 'styles', label: 'styles.css' },
],
},
{ key: 'readme', label: 'README.md' },
];
return (
<>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16 }}>
<div style={{ border: "1px solid var(--line-1)", borderRadius: 8, padding: 8 }}>
<CfTreeView value={checked} onChange={setChecked} nodes={nodes} checkable defaultExpandedKeys={['src', 'components']} />
</div>
<div style={{ border: "1px solid var(--line-1)", borderRadius: 8, padding: 8 }}>
<CfTreeView value={checked} onChange={setChecked} nodes={nodes} checkable cascade={false} defaultExpandedKeys={['src', 'components']} />
</div>
</div>
</>
);
} 三档尺寸
size 控制节点行高与字号 — sm 紧凑(侧栏文件树常用)/ md 默认 / lg 大号触摸友好。
背景 视口
- src
- main.ts
- App.vue
- package.json
- src
- main.ts
- App.vue
- package.json
- src
- main.ts
- App.vue
- package.json
<script setup lang="ts">
import { ref } from 'vue';
import { CfTreeView, type TreeNode } from '@chufix-design/vue';
const a = ref<string | null>('main');
const b = ref<string | null>('main');
const c = ref<string | null>('main');
const nodes: TreeNode[] = [
{
key: 'src',
label: 'src',
children: [
{ key: 'main', label: 'main.ts' },
{ key: 'app', label: 'App.vue' },
],
},
{ key: 'package', label: 'package.json' },
];
</script>
<template>
<div style="display:grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px;">
<div style="border: 1px solid var(--line-1); border-radius: 8px; padding: 8px;">
<CfTreeView size="sm" :nodes="nodes" :selected-key="a" :default-expanded-keys="['src']" @update:selected-key="a = $event" />
</div>
<div style="border: 1px solid var(--line-1); border-radius: 8px; padding: 8px;">
<CfTreeView size="md" :nodes="nodes" :selected-key="b" :default-expanded-keys="['src']" @update:selected-key="b = $event" />
</div>
<div style="border: 1px solid var(--line-1); border-radius: 8px; padding: 8px;">
<CfTreeView size="lg" :nodes="nodes" :selected-key="c" :default-expanded-keys="['src']" @update:selected-key="c = $event" />
</div>
</div>
</template> <script setup>
import { ref } from 'vue';
import { CfTreeView } from '@chufix-design/vue';
const a = ref<string | null>('main');
const b = ref<string | null>('main');
const c = ref<string | null>('main');
const nodes= [
{
key: 'src',
label: 'src',
children: [
{ key: 'main', label: 'main.ts' },
{ key: 'app', label: 'App.vue' },
],
},
{ key: 'package', label: 'package.json' },
];
</script>
<template>
<div style="display:grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px;">
<div style="border: 1px solid var(--line-1); border-radius: 8px; padding: 8px;">
<CfTreeView size="sm" :nodes="nodes" :selected-key="a" :default-expanded-keys="['src']" @update:selected-key="a = $event" />
</div>
<div style="border: 1px solid var(--line-1); border-radius: 8px; padding: 8px;">
<CfTreeView size="md" :nodes="nodes" :selected-key="b" :default-expanded-keys="['src']" @update:selected-key="b = $event" />
</div>
<div style="border: 1px solid var(--line-1); border-radius: 8px; padding: 8px;">
<CfTreeView size="lg" :nodes="nodes" :selected-key="c" :default-expanded-keys="['src']" @update:selected-key="c = $event" />
</div>
</div>
</template> import { useState } from 'react';
import { CfTreeView } from '@chufix-design/react';
export default function Demo() {
const [a, setA] = useState<string | null>('main');
const [b, setB] = useState<string | null>('main');
const [c, setC] = useState<string | null>('main');
const nodes: TreeNode[] = [
{
key: 'src',
label: 'src',
children: [
{ key: 'main', label: 'main.ts' },
{ key: 'app', label: 'App.vue' },
],
},
{ key: 'package', label: 'package.json' },
];
return (
<>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 16 }}>
<div style={{ border: "1px solid var(--line-1)", borderRadius: 8, padding: 8 }}>
<CfTreeView size="sm" nodes={nodes} selectedKey={a} defaultExpandedKeys={['src']} onSelectedKeyChange={() => setA($event)} />
</div>
<div style={{ border: "1px solid var(--line-1)", borderRadius: 8, padding: 8 }}>
<CfTreeView size="md" nodes={nodes} selectedKey={b} defaultExpandedKeys={['src']} onSelectedKeyChange={() => setB($event)} />
</div>
<div style={{ border: "1px solid var(--line-1)", borderRadius: 8, padding: 8 }}>
<CfTreeView size="lg" nodes={nodes} selectedKey={c} defaultExpandedKeys={['src']} onSelectedKeyChange={() => setC($event)} />
</div>
</div>
</>
);
} import { useState } from 'react';
import { CfTreeView } from '@chufix-design/react';
export default function Demo() {
const [a, setA] = useState<string | null>('main');
const [b, setB] = useState<string | null>('main');
const [c, setC] = useState<string | null>('main');
const nodes= [
{
key: 'src',
label: 'src',
children: [
{ key: 'main', label: 'main.ts' },
{ key: 'app', label: 'App.vue' },
],
},
{ key: 'package', label: 'package.json' },
];
return (
<>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 16 }}>
<div style={{ border: "1px solid var(--line-1)", borderRadius: 8, padding: 8 }}>
<CfTreeView size="sm" nodes={nodes} selectedKey={a} defaultExpandedKeys={['src']} onSelectedKeyChange={() => setA($event)} />
</div>
<div style={{ border: "1px solid var(--line-1)", borderRadius: 8, padding: 8 }}>
<CfTreeView size="md" nodes={nodes} selectedKey={b} defaultExpandedKeys={['src']} onSelectedKeyChange={() => setB($event)} />
</div>
<div style={{ border: "1px solid var(--line-1)", borderRadius: 8, padding: 8 }}>
<CfTreeView size="lg" nodes={nodes} selectedKey={c} defaultExpandedKeys={['src']} onSelectedKeyChange={() => setC($event)} />
</div>
</div>
</>
);
} 隐藏连接线
showLine={false} 关掉父子之间的虚线 — 视觉更干净,常配合自定义 icon 使用。
背景 视口
- src
- components
- Home.vue
- About.vue
- main.ts
<script setup lang="ts">
import { ref } from 'vue';
import { CfTreeView, type TreeNode } from '@chufix-design/vue';
const selected = ref<string | null>('home');
const nodes: TreeNode[] = [
{
key: 'src',
label: 'src',
children: [
{
key: 'components',
label: 'components',
children: [
{ key: 'home', label: 'Home.vue' },
{ key: 'about', label: 'About.vue' },
],
},
{ key: 'main', label: 'main.ts' },
],
},
];
</script>
<template>
<div style="border: 1px solid var(--line-1); border-radius: 8px; padding: 8px; max-width: 18rem;">
<CfTreeView
:nodes="nodes"
:selected-key="selected"
:default-expanded-keys="['src', 'components']"
:show-line="false"
@update:selected-key="selected = $event"
/>
</div>
</template> <script setup>
import { ref } from 'vue';
import { CfTreeView } from '@chufix-design/vue';
const selected = ref<string | null>('home');
const nodes= [
{
key: 'src',
label: 'src',
children: [
{
key: 'components',
label: 'components',
children: [
{ key: 'home', label: 'Home.vue' },
{ key: 'about', label: 'About.vue' },
],
},
{ key: 'main', label: 'main.ts' },
],
},
];
</script>
<template>
<div style="border: 1px solid var(--line-1); border-radius: 8px; padding: 8px; max-width: 18rem;">
<CfTreeView
:nodes="nodes"
:selected-key="selected"
:default-expanded-keys="['src', 'components']"
:show-line="false"
@update:selected-key="selected = $event"
/>
</div>
</template> import { useState } from 'react';
import { CfTreeView } from '@chufix-design/react';
export default function Demo() {
const [selected, setSelected] = useState<string | null>('home');
const nodes: TreeNode[] = [
{
key: 'src',
label: 'src',
children: [
{
key: 'components',
label: 'components',
children: [
{ key: 'home', label: 'Home.vue' },
{ key: 'about', label: 'About.vue' },
],
},
{ key: 'main', label: 'main.ts' },
],
},
];
return (
<>
<div style={{ border: "1px solid var(--line-1)", borderRadius: 8, padding: 8, maxWidth: "18rem" }}>
<CfTreeView nodes={nodes} selectedKey={selected} defaultExpandedKeys={['src', 'components']} showLine={false} onSelectedKeyChange={() => setSelected($event)}
/>
</div>
</>
);
} import { useState } from 'react';
import { CfTreeView } from '@chufix-design/react';
export default function Demo() {
const [selected, setSelected] = useState<string | null>('home');
const nodes= [
{
key: 'src',
label: 'src',
children: [
{
key: 'components',
label: 'components',
children: [
{ key: 'home', label: 'Home.vue' },
{ key: 'about', label: 'About.vue' },
],
},
{ key: 'main', label: 'main.ts' },
],
},
];
return (
<>
<div style={{ border: "1px solid var(--line-1)", borderRadius: 8, padding: 8, maxWidth: "18rem" }}>
<CfTreeView nodes={nodes} selectedKey={selected} defaultExpandedKeys={['src', 'components']} showLine={false} onSelectedKeyChange={() => setSelected($event)}
/>
</div>
</>
);
} API
Props
| Prop | 类型 | 默认值 | 说明 |
|---|---|---|---|
nodes | TreeNode[] | [] | 树形数据 |
modelValue (Vue) / value (React) | string[] | [] | 已勾选的 key(仅 checkable) |
expandedKeys / defaultExpandedKeys | string[] | — | 受控 / 非受控的展开列表 |
selectedKey | string | null | null | 当前单选的 key |
checkable | boolean | false | 显示复选框 |
cascade | boolean | true | 父子勾选联动 |
selectable | 'single' | 'multiple' | — | 选择模式(仅影响交互,不影响 checkable) |
size | 'sm' | 'md' | 'lg' | 'md' | 字号 + 内距 |
showLine | boolean | true | 显示嵌套连接线 |
emptyText | string | ReactNode | '暂无数据' | 空状态文案 |
TreeNode
interface TreeNode {
key: string;
label: string;
icon?: string; // 原始 SVG 字符串,通过 v-html 注入;用 currentColor 描边可继承节点文字色
children?: TreeNode[];
disabled?: boolean;
selectable?: boolean; // 单独控制本节点是否可被单选高亮
isLeaf?: boolean; // 强制视为叶子节点(用于懒加载占位)
}
Events
| Vue 事件 | React 回调 | 载荷类型 | 说明 |
|---|---|---|---|
update:modelValue | onChange | string[] | 勾选项变化时触发,仅在 checkable=true 时有效 |
update:expandedKeys | onExpandedKeysChange | string[] | 展开节点列表变化时触发 |
update:selectedKey | onSelectedKeyChange | string | null | 单选节点变化时触发 |
select | onSelect | TreeNode | 点击节点(在可选状态下)时触发,载荷是原始 node |
expand | onExpand | (node: TreeNode, expanded: boolean) | 节点展开 / 折叠时触发 |
check | onCheck | (node: TreeNode, checked: boolean) | 节点勾选 / 取消时触发(仅 checkable=true) |
反馈与讨论
TreeView 树形列表 的讨论