Tree 树 
对于文件夹、分类目录、组织架构等层级较多的内容,树可以清楚显示他们的层级关系,并具有展开、收起、选择等交互功能。
基础用法 
为每个节点赋予全局唯一的 key(必填项),title 为该节点显示的内容。
叶子 1-1-1
叶子 1-1-2
叶子 1-2-1
<template>
  <tu-tree :data="treeData" />
</template>
<script lang="ts" setup>
const treeData = [
  {
    title: '主干 1',
    key: '1',
    children: [
      {
        title: '分支 1-1',
        key: '1-1',
        disabled: true,
        children: [
          {
            title: '叶子 1-1-1',
            key: '1-1-1'
          },
          {
            title: '叶子 1-1-2',
            key: '1-1-2'
          }
        ]
      },
      {
        title: '分支 1-2',
        key: '1-2',
        children: [
          {
            title: '叶子 1-2-1',
            key: '1-2-1'
          }
        ]
      }
    ]
  }
];
</script><template>
  <tu-tree :data="treeData" />
</template>
<script lang="ts" setup>
const treeData = [
  {
    title: '主干 1',
    key: '1',
    children: [
      {
        title: '分支 1-1',
        key: '1-1',
        disabled: true,
        children: [
          {
            title: '叶子 1-1-1',
            key: '1-1-1'
          },
          {
            title: '叶子 1-1-2',
            key: '1-1-2'
          }
        ]
      },
      {
        title: '分支 1-2',
        key: '1-2',
        children: [
          {
            title: '叶子 1-2-1',
            key: '1-2-1'
          }
        ]
      }
    ]
  }
];
</script>节点占一行 
节点占据一整行。
叶子 1-1-1
叶子 1-1-2
叶子 1-2-1
<template>
  <tu-tree blockNode :data="treeData" />
</template>
<script lang="ts" setup>
const treeData = [
  {
    title: '主干 1',
    key: '1',
    children: [
      {
        title: '分支 1-1',
        key: '1-1',
        children: [
          {
            title: '叶子 1-1-1',
            key: '1-1-1'
          },
          {
            title: '叶子 1-1-2',
            key: '1-1-2'
          }
        ]
      },
      {
        title: '分支 1-2',
        key: '1-2',
        children: [
          {
            title: '叶子 1-2-1',
            key: '1-2-1'
          }
        ]
      }
    ]
  }
];
</script><template>
  <tu-tree blockNode :data="treeData" />
</template>
<script lang="ts" setup>
const treeData = [
  {
    title: '主干 1',
    key: '1',
    children: [
      {
        title: '分支 1-1',
        key: '1-1',
        children: [
          {
            title: '叶子 1-1-1',
            key: '1-1-1'
          },
          {
            title: '叶子 1-1-2',
            key: '1-1-2'
          }
        ]
      },
      {
        title: '分支 1-2',
        key: '1-2',
        children: [
          {
            title: '叶子 1-2-1',
            key: '1-2-1'
          }
        ]
      }
    ]
  }
];
</script>多选 
Tree 设置 multiple 属性为true,可以启用多选。
叶子 1-1-1
叶子 1-1-2
叶子 1-2-1
<template>
  <tu-tree multiple :data="treeData" />
</template>
<script lang="ts" setup>
const treeData = [
  {
    title: '主干 1',
    key: '1',
    children: [
      {
        title: '分支 1-1',
        key: '1-1',
        children: [
          {
            title: '叶子 1-1-1',
            key: '1-1-1'
          },
          {
            title: '叶子 1-1-2',
            key: '1-1-2'
          }
        ]
      },
      {
        title: '分支 1-2',
        key: '1-2',
        children: [
          {
            title: '叶子 1-2-1',
            key: '1-2-1'
          }
        ]
      }
    ]
  }
];
</script><template>
  <tu-tree multiple :data="treeData" />
</template>
<script lang="ts" setup>
const treeData = [
  {
    title: '主干 1',
    key: '1',
    children: [
      {
        title: '分支 1-1',
        key: '1-1',
        children: [
          {
            title: '叶子 1-1-1',
            key: '1-1-1'
          },
          {
            title: '叶子 1-1-2',
            key: '1-1-2'
          }
        ]
      },
      {
        title: '分支 1-2',
        key: '1-2',
        children: [
          {
            title: '叶子 1-2-1',
            key: '1-2-1'
          }
        ]
      }
    ]
  }
];
</script>带复选框的树 
为 Tree 添加 checkable 属性即可使树具有复选框功能,可以用 defaultCheckedKeys 指定复选框默认选中的节点。
父子关联
叶子 1-1
叶子 1-2-1
叶子 1-2-2
叶子 2-1-1
叶子 2-1-2
叶子 2-2
不关联
叶子 1-1
叶子 1-2-1
叶子 1-2-2
叶子 2-1-1
叶子 2-1-2
叶子 2-2
<template>
  <tu-row :gutter="20">
    <tu-col :span="12">
      <p>父子关联</p>
      <tu-tree checkable :data="treeData" />
    </tu-col>
    <tu-col :span="12">
      <p>不关联</p>
      <tu-tree checkable check-strictly :data="treeData" />
    </tu-col>
  </tu-row>
</template>
<script lang="ts" setup>
const treeData = [
  {
    title: '主干 1',
    key: '1',
    children: [
      {
        title: '叶子 1-1',
        key: '1-1'
      },
      {
        title: '分支 1-2',
        key: '1-2',
        disabled: true,
        children: [
          {
            title: '叶子 1-2-1',
            key: '1-2-1'
          },
          {
            title: '叶子 1-2-2',
            key: '1-2-2',
            disableCheckbox: true
          }
        ]
      }
    ]
  },
  {
    title: '主干 2',
    key: '2',
    children: [
      {
        title: '分支 2-1',
        key: '2-1',
        children: [
          {
            title: '叶子 2-1-1',
            key: '2-1-1'
          },
          {
            title: '叶子 2-1-2',
            key: '2-1-2'
          }
        ]
      },
      {
        title: '叶子 2-2',
        key: '2-2'
      }
    ]
  }
];
</script><template>
  <tu-row :gutter="20">
    <tu-col :span="12">
      <p>父子关联</p>
      <tu-tree checkable :data="treeData" />
    </tu-col>
    <tu-col :span="12">
      <p>不关联</p>
      <tu-tree checkable check-strictly :data="treeData" />
    </tu-col>
  </tu-row>
</template>
<script lang="ts" setup>
const treeData = [
  {
    title: '主干 1',
    key: '1',
    children: [
      {
        title: '叶子 1-1',
        key: '1-1'
      },
      {
        title: '分支 1-2',
        key: '1-2',
        disabled: true,
        children: [
          {
            title: '叶子 1-2-1',
            key: '1-2-1'
          },
          {
            title: '叶子 1-2-2',
            key: '1-2-2',
            disableCheckbox: true
          }
        ]
      }
    ]
  },
  {
    title: '主干 2',
    key: '2',
    children: [
      {
        title: '分支 2-1',
        key: '2-1',
        children: [
          {
            title: '叶子 2-1-1',
            key: '2-1-1'
          },
          {
            title: '叶子 2-1-2',
            key: '2-1-2'
          }
        ]
      },
      {
        title: '叶子 2-2',
        key: '2-2'
      }
    ]
  }
];
</script>双向绑定 
selectedKeys 、 checkedKeys 、 expandedKeys 属性均可受控,不仅支持 v-model ,还可以在对应的 select / check / expand 事件中自行控制如何更新属性值。
叶子 1-1
叶子 1-2-1
叶子 2-1
叶子 2-2
<template>
  <tu-button-group class="mb-2">
    <tu-button type="primary" @click="toggleChecked">
      {{ checkedKeys?.length ? '取消选择' : '全部选择' }}
    </tu-button>
    <tu-button type="primary" @click="toggleExpanded">
      {{ expandedKeys?.length ? '折叠全部' : '展开全部' }}
    </tu-button>
  </tu-button-group>
  <tu-tree
    :data="treeData"
    :checkable="true"
    v-model:selected-keys="selectedKeys"
    v-model:checked-keys="checkedKeys"
    v-model:expanded-keys="expandedKeys"
    @select="onSelect"
    @check="onCheck"
    @expand="onExpand"
  />
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const treeData = [
  {
    title: '主干 1',
    key: '1',
    children: [
      {
        title: '叶子 1-1',
        key: '1-1'
      },
      {
        title: '分支 1-2',
        key: '1-2',
        children: [
          {
            title: '叶子 1-2-1',
            key: '1-2-1'
          }
        ]
      }
    ]
  },
  {
    title: '主干 2',
    key: '2',
    children: [
      {
        title: '叶子 2-1',
        key: '2-1'
      },
      {
        title: '叶子 2-2',
        key: '2-2'
      }
    ]
  }
];
const allCheckedKeys = ['1', '1-1', '1-2', '1-2-1', '2', '2-1', '2-2'];
const allExpandedKeys = ['1', '1-2', '2'];
const selectedKeys = ref<string[]>([]);
const checkedKeys = ref<string[]>([]);
const expandedKeys = ref<string[]>(allExpandedKeys);
const toggleChecked = () => {
  checkedKeys.value = checkedKeys?.value.length ? [] : allCheckedKeys;
};
const toggleExpanded = () => {
  expandedKeys.value = expandedKeys?.value.length ? [] : allExpandedKeys;
};
const onSelect = (newSelectedKeys: string[], event: any) => {
  console.log('select: ', newSelectedKeys, event);
};
const onCheck = (newCheckedKeys: string[], event: any) => {
  console.log('check: ', newCheckedKeys, event);
};
const onExpand = (newExpandedKeys: string[], event: any) => {
  console.log('expand: ', newExpandedKeys, event);
};
</script><template>
  <tu-button-group class="mb-2">
    <tu-button type="primary" @click="toggleChecked">
      {{ checkedKeys?.length ? '取消选择' : '全部选择' }}
    </tu-button>
    <tu-button type="primary" @click="toggleExpanded">
      {{ expandedKeys?.length ? '折叠全部' : '展开全部' }}
    </tu-button>
  </tu-button-group>
  <tu-tree
    :data="treeData"
    :checkable="true"
    v-model:selected-keys="selectedKeys"
    v-model:checked-keys="checkedKeys"
    v-model:expanded-keys="expandedKeys"
    @select="onSelect"
    @check="onCheck"
    @expand="onExpand"
  />
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const treeData = [
  {
    title: '主干 1',
    key: '1',
    children: [
      {
        title: '叶子 1-1',
        key: '1-1'
      },
      {
        title: '分支 1-2',
        key: '1-2',
        children: [
          {
            title: '叶子 1-2-1',
            key: '1-2-1'
          }
        ]
      }
    ]
  },
  {
    title: '主干 2',
    key: '2',
    children: [
      {
        title: '叶子 2-1',
        key: '2-1'
      },
      {
        title: '叶子 2-2',
        key: '2-2'
      }
    ]
  }
];
const allCheckedKeys = ['1', '1-1', '1-2', '1-2-1', '2', '2-1', '2-2'];
const allExpandedKeys = ['1', '1-2', '2'];
const selectedKeys = ref<string[]>([]);
const checkedKeys = ref<string[]>([]);
const expandedKeys = ref<string[]>(allExpandedKeys);
const toggleChecked = () => {
  checkedKeys.value = checkedKeys?.value.length ? [] : allCheckedKeys;
};
const toggleExpanded = () => {
  expandedKeys.value = expandedKeys?.value.length ? [] : allExpandedKeys;
};
const onSelect = (newSelectedKeys: string[], event: any) => {
  console.log('select: ', newSelectedKeys, event);
};
const onCheck = (newCheckedKeys: string[], event: any) => {
  console.log('check: ', newCheckedKeys, event);
};
const onExpand = (newExpandedKeys: string[], event: any) => {
  console.log('expand: ', newExpandedKeys, event);
};
</script>动态加载 
动态加载节点。
<template>
  <tu-tree :data="treeData" :load-more="loadMore" />
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const treeData = ref([
  {
    title: '主干 1',
    key: '1'
  },
  {
    title: '主干 2',
    key: '2',
    children: [
      {
        title: '分支 2-1',
        key: '2-1'
      }
    ]
  }
]);
const loadMore = (nodeData) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      nodeData.children = [
        {
          title: `叶子 ${nodeData.key}-1`,
          key: `${nodeData.key}-1`,
          isLeaf: true
        }
      ];
      resolve();
    }, 1000);
  });
};
</script><template>
  <tu-tree :data="treeData" :load-more="loadMore" />
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const treeData = ref([
  {
    title: '主干 1',
    key: '1'
  },
  {
    title: '主干 2',
    key: '2',
    children: [
      {
        title: '分支 2-1',
        key: '2-1'
      }
    ]
  }
]);
const loadMore = (nodeData) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      nodeData.children = [
        {
          title: `叶子 ${nodeData.key}-1`,
          key: `${nodeData.key}-1`,
          isLeaf: true
        }
      ];
      resolve();
    }, 1000);
  });
};
</script>拖拽 
可拖拽的树节点。
叶子 1-1-1
叶子 1-1-2
叶子 1-2-1
<template>
  <tu-tree draggable blockNode :data="treeData" @drop="onDrop" />
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const treeData = ref([
  {
    title: '主干 1',
    key: '1',
    children: [
      {
        title: '分支 1-1',
        key: '1-1',
        children: [
          {
            title: '叶子 1-1-1',
            key: '1-1-1'
          },
          {
            title: '叶子 1-1-2',
            key: '1-1-2'
          }
        ]
      },
      {
        title: '分支 1-2',
        key: '1-2',
        children: [
          {
            title: '叶子 1-2-1',
            key: '1-2-1'
          }
        ]
      }
    ]
  }
]);
const onDrop = ({ dragNode, dropNode, dropPosition }) => {
  const data = treeData.value;
  const loop = (data, key, callback) => {
    data.some((item, index, arr) => {
      if (item.key === key) {
        callback(item, index, arr);
        return true;
      }
      if (item.children) {
        return loop(item.children, key, callback);
      }
      return false;
    });
  };
  loop(data, dragNode.key, (_, index, arr) => {
    arr.splice(index, 1);
  });
  if (dropPosition === 0) {
    loop(data, dropNode.key, (item) => {
      item.children = item.children || [];
      item.children.push(dragNode);
    });
  } else {
    loop(data, dropNode.key, (_, index, arr) => {
      arr.splice(dropPosition < 0 ? index : index + 1, 0, dragNode);
    });
  }
};
</script><template>
  <tu-tree draggable blockNode :data="treeData" @drop="onDrop" />
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const treeData = ref([
  {
    title: '主干 1',
    key: '1',
    children: [
      {
        title: '分支 1-1',
        key: '1-1',
        children: [
          {
            title: '叶子 1-1-1',
            key: '1-1-1'
          },
          {
            title: '叶子 1-1-2',
            key: '1-1-2'
          }
        ]
      },
      {
        title: '分支 1-2',
        key: '1-2',
        children: [
          {
            title: '叶子 1-2-1',
            key: '1-2-1'
          }
        ]
      }
    ]
  }
]);
const onDrop = ({ dragNode, dropNode, dropPosition }) => {
  const data = treeData.value;
  const loop = (data, key, callback) => {
    data.some((item, index, arr) => {
      if (item.key === key) {
        callback(item, index, arr);
        return true;
      }
      if (item.children) {
        return loop(item.children, key, callback);
      }
      return false;
    });
  };
  loop(data, dragNode.key, (_, index, arr) => {
    arr.splice(index, 1);
  });
  if (dropPosition === 0) {
    loop(data, dropNode.key, (item) => {
      item.children = item.children || [];
      item.children.push(dragNode);
    });
  } else {
    loop(data, dropNode.key, (_, index, arr) => {
      arr.splice(dropPosition < 0 ? index : index + 1, 0, dragNode);
    });
  }
};
</script>设置回填方式 
为 Tree 添加 checkedStrategy 可以设置选中时的回填方式。
叶子 1-1-1
叶子 1-1-2
叶子 1-2-1
当前选择节点:
<template>
  <tu-radio-group
    class="mb-2"
    type="button"
    v-model="checkedStrategy"
    @change="
      () => {
        checkedKeys = [];
      }
    "
  >
    <tu-radio label="all">所有节点</tu-radio>
    <tu-radio label="parent">父节点</tu-radio>
    <tu-radio label="child">子节点</tu-radio>
  </tu-radio-group>
  <tu-tree
    checkable
    v-model:checked-keys="checkedKeys"
    :data="treeData"
    :checked-strategy="checkedStrategy"
  />
  <p>当前选择节点:{{ checkedKeys }}</p>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const treeData = [
  {
    title: '主干 1',
    key: '1',
    children: [
      {
        title: '分支 1-1',
        key: '1-1',
        children: [
          {
            title: '叶子 1-1-1',
            key: '1-1-1'
          },
          {
            title: '叶子 1-1-2',
            key: '1-1-2'
          }
        ]
      },
      {
        title: '分支 1-2',
        key: '1-2',
        children: [
          {
            title: '叶子 1-2-1',
            key: '1-2-1'
          }
        ]
      }
    ]
  }
];
const checkedKeys = ref();
const checkedStrategy = ref('all');
</script><template>
  <tu-radio-group
    class="mb-2"
    type="button"
    v-model="checkedStrategy"
    @change="
      () => {
        checkedKeys = [];
      }
    "
  >
    <tu-radio label="all">所有节点</tu-radio>
    <tu-radio label="parent">父节点</tu-radio>
    <tu-radio label="child">子节点</tu-radio>
  </tu-radio-group>
  <tu-tree
    checkable
    v-model:checked-keys="checkedKeys"
    :data="treeData"
    :checked-strategy="checkedStrategy"
  />
  <p>当前选择节点:{{ checkedKeys }}</p>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const treeData = [
  {
    title: '主干 1',
    key: '1',
    children: [
      {
        title: '分支 1-1',
        key: '1-1',
        children: [
          {
            title: '叶子 1-1-1',
            key: '1-1-1'
          },
          {
            title: '叶子 1-1-2',
            key: '1-1-2'
          }
        ]
      },
      {
        title: '分支 1-2',
        key: '1-2',
        children: [
          {
            title: '叶子 1-2-1',
            key: '1-2-1'
          }
        ]
      }
    ]
  }
];
const checkedKeys = ref();
const checkedStrategy = ref('all');
</script>显示连接线 
为 Tree 添加 showLine 属性即可使树具有连接线。
叶子 1-1-1
叶子 1-1-2-1
叶子 1-1-3
分支 1-2
叶子 1-3-1
叶子 1-3-2
主干 2
叶子 3-1-1
叶子 3-1-2
<template>
  <tu-tree show-line :data="treeData" />
</template>
<script lang="ts" setup>
const treeData = [
  {
    title: '主干 1',
    key: '1',
    children: [
      {
        title: '分支 1-1',
        key: '1-1',
        children: [
          { title: '叶子 1-1-1', key: '1-1-1' },
          {
            title: '叶子 1-1-2',
            key: '1-1-2',
            children: [{ title: '叶子 1-1-2-1', key: '1-1-2-1' }]
          },
          { title: '叶子 1-1-3', key: '1-1-3' }
        ]
      },
      {
        title: '分支 1-2',
        key: '1-2'
      },
      {
        title: '分支 1-3',
        key: '1-3',
        children: [
          { title: '叶子 1-3-1', key: '1-3-1' },
          {
            title: '叶子 1-3-2',
            key: '1-3-2'
          }
        ]
      }
    ]
  },
  {
    title: '主干 2',
    key: '2'
  },
  {
    title: '主干 3',
    key: '3',
    children: [
      {
        title: '分支 3-1',
        key: '3-1',
        children: [
          { title: '叶子 3-1-1', key: '3-1-1' },
          { title: '叶子 3-1-2', key: '3-1-2' }
        ]
      }
    ]
  }
];
</script><template>
  <tu-tree show-line :data="treeData" />
</template>
<script lang="ts" setup>
const treeData = [
  {
    title: '主干 1',
    key: '1',
    children: [
      {
        title: '分支 1-1',
        key: '1-1',
        children: [
          { title: '叶子 1-1-1', key: '1-1-1' },
          {
            title: '叶子 1-1-2',
            key: '1-1-2',
            children: [{ title: '叶子 1-1-2-1', key: '1-1-2-1' }]
          },
          { title: '叶子 1-1-3', key: '1-1-3' }
        ]
      },
      {
        title: '分支 1-2',
        key: '1-2'
      },
      {
        title: '分支 1-3',
        key: '1-3',
        children: [
          { title: '叶子 1-3-1', key: '1-3-1' },
          {
            title: '叶子 1-3-2',
            key: '1-3-2'
          }
        ]
      }
    ]
  },
  {
    title: '主干 2',
    key: '2'
  },
  {
    title: '主干 3',
    key: '3',
    children: [
      {
        title: '分支 3-1',
        key: '3-1',
        children: [
          { title: '叶子 3-1-1', key: '3-1-1' },
          { title: '叶子 3-1-2', key: '3-1-2' }
        ]
      }
    ]
  }
];
</script>定制节点图标 
节点图标可以通过 tree 的 icon 插槽全局指定,也可以通过节点的 icon 属性单独指定。
叶子 1-1-1
叶子 1-1-2
叶子 1-2-1
<template>
  <tu-tree :data="treeData">
    <template #icon>
      <Sunny />
    </template>
  </tu-tree>
</template>
<script lang="ts" setup>
import { h } from 'vue';
import { Sunny, Pouring } from '@tu-view-plus/icons-vue';
const treeData = [
  {
    title: '主干 1',
    key: '1',
    children: [
      {
        title: '分支 1-1',
        key: '1-1',
        children: [
          {
            title: '叶子 1-1-1',
            key: '1-1-1',
            icon: () => h(Pouring)
          },
          {
            title: '叶子 1-1-2',
            key: '1-1-2',
            icon: () => h(Pouring)
          }
        ]
      },
      {
        title: '分支 1-2',
        key: '1-2',
        children: [
          {
            title: '叶子 1-2-1',
            key: '1-2-1'
          }
        ]
      }
    ]
  }
];
</script><template>
  <tu-tree :data="treeData">
    <template #icon>
      <Sunny />
    </template>
  </tu-tree>
</template>
<script lang="ts" setup>
import { h } from 'vue';
import { Sunny, Pouring } from '@tu-view-plus/icons-vue';
const treeData = [
  {
    title: '主干 1',
    key: '1',
    children: [
      {
        title: '分支 1-1',
        key: '1-1',
        children: [
          {
            title: '叶子 1-1-1',
            key: '1-1-1',
            icon: () => h(Pouring)
          },
          {
            title: '叶子 1-1-2',
            key: '1-1-2',
            icon: () => h(Pouring)
          }
        ]
      },
      {
        title: '分支 1-2',
        key: '1-2',
        children: [
          {
            title: '叶子 1-2-1',
            key: '1-2-1'
          }
        ]
      }
    ]
  }
];
</script>定制组件图标 
节点的图标 loadingIcon, switcherIcon,同时支持在 tree 和 node 两个纬度上定制,其中 node 的优先级较高。
主干 1
分支 1-1
叶子 1-1-1
叶子 1-1-2
分支 1-2
叶子 1-2-1
<template>
  <tu-tree :data="treeData">
    <template #switcher-icon="node, { isLeaf }">
      <ArrowDown v-if="!isLeaf" />
      <Star v-if="isLeaf" />
    </template>
  </tu-tree>
</template>
<script lang="ts" setup>
import { Star, ArrowDown } from '@tu-view-plus/icons-vue';
const treeData = [
  {
    title: '主干 1',
    key: '1',
    children: [
      {
        title: '分支 1-1',
        key: '1-1',
        disabled: true,
        children: [
          {
            title: '叶子 1-1-1',
            key: '1-1-1'
          },
          {
            title: '叶子 1-1-2',
            key: '1-1-2'
          }
        ]
      },
      {
        title: '分支 1-2',
        key: '1-2',
        children: [
          {
            title: '叶子 1-2-1',
            key: '1-2-1'
          }
        ]
      }
    ]
  }
];
</script><template>
  <tu-tree :data="treeData">
    <template #switcher-icon="node, { isLeaf }">
      <ArrowDown v-if="!isLeaf" />
      <Star v-if="isLeaf" />
    </template>
  </tu-tree>
</template>
<script lang="ts" setup>
import { Star, ArrowDown } from '@tu-view-plus/icons-vue';
const treeData = [
  {
    title: '主干 1',
    key: '1',
    children: [
      {
        title: '分支 1-1',
        key: '1-1',
        disabled: true,
        children: [
          {
            title: '叶子 1-1-1',
            key: '1-1-1'
          },
          {
            title: '叶子 1-1-2',
            key: '1-1-2'
          }
        ]
      },
      {
        title: '分支 1-2',
        key: '1-2',
        children: [
          {
            title: '叶子 1-2-1',
            key: '1-2-1'
          }
        ]
      }
    ]
  }
];
</script>定制额外节点 
Tree 提供了名为 extra 的 Slot, 可以在节点上定制额外的内容。
叶子 1-1-1
叶子 1-1-2
叶子 1-2-1
<template>
  <tu-tree block-node :data="treeData">
    <template #extra="nodeData">
      <tu-button class="ml-2" size="mini" @click="() => handleAdd(nodeData)">
        <template #icon><Plus /></template>
      </tu-button>
    </template>
  </tu-tree>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { Plus } from '@tu-view-plus/icons-vue';
const treeData = ref([
  {
    title: '主干 1',
    key: '1',
    children: [
      {
        title: '分支 1-1',
        key: '1-1',
        disabled: true,
        children: [
          {
            title: '叶子 1-1-1',
            key: '1-1-1'
          },
          {
            title: '叶子 1-1-2',
            key: '1-1-2'
          }
        ]
      },
      {
        title: '分支 1-2',
        key: '1-2',
        children: [
          {
            title: '叶子 1-2-1',
            key: '1-2-1'
          }
        ]
      }
    ]
  }
]);
const handleAdd = (nodeData) => {
  const children = nodeData.children || [];
  children.push({
    title: '新节点',
    key: `${nodeData.key}-${children.length + 1}`
  });
  nodeData.children = children;
  treeData.value = [...treeData.value];
};
</script><template>
  <tu-tree block-node :data="treeData">
    <template #extra="nodeData">
      <tu-button class="ml-2" size="mini" @click="() => handleAdd(nodeData)">
        <template #icon><Plus /></template>
      </tu-button>
    </template>
  </tu-tree>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { Plus } from '@tu-view-plus/icons-vue';
const treeData = ref([
  {
    title: '主干 1',
    key: '1',
    children: [
      {
        title: '分支 1-1',
        key: '1-1',
        disabled: true,
        children: [
          {
            title: '叶子 1-1-1',
            key: '1-1-1'
          },
          {
            title: '叶子 1-1-2',
            key: '1-1-2'
          }
        ]
      },
      {
        title: '分支 1-2',
        key: '1-2',
        children: [
          {
            title: '叶子 1-2-1',
            key: '1-2-1'
          }
        ]
      }
    ]
  }
]);
const handleAdd = (nodeData) => {
  const children = nodeData.children || [];
  children.push({
    title: '新节点',
    key: `${nodeData.key}-${children.length + 1}`
  });
  nodeData.children = children;
  treeData.value = [...treeData.value];
};
</script>虚拟列表 
通过指定 virtualListProps 来开启虚拟列表,在大量数据时获得高性能表现。
1-1-1-1
1-1-1-2
1-1-1-3
1-1-1-4
1-1-1-5
1-1-1-6
1-1-1-7
1-1-1-8
1-1-1-9
1-1-1-10
1-1-2-1
1-1-2-2
1-1-2-3
1-1-2-4
1-1-2-5
1-1-2-6
1-1-2-7
1-1-2-8
1-1-2-9
1-1-2-10
1-1-3-1
1-1-3-2
1-1-3-3
1-1-3-4
1-1-3-5
1-1-3-6
<template>
  <tu-button class="mb-2" @click="scrollIntoView">滚动到 1-6-6-6</tu-button>
  <tu-tree
    ref="treeRef"
    blockNode
    checkable
    :data="treeData"
    :virtualListProps="{
      height: 300
    }"
  />
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const loop = (path = '1', level = 2) => {
  const list = [];
  for (let i = 0; i < 10; i += 1) {
    const key = `${path}-${i + 1}`;
    const treeNode = {
      title: key,
      key
    };
    if (level > 0) {
      treeNode.children = loop(key, level - 1);
    }
    list.push(treeNode);
  }
  return list;
};
const treeRef = ref();
const treeData = loop();
const scrollIntoView = () => {
  treeRef.value && treeRef.value.scrollIntoView({ key: '1-6-6-6' });
};
</script><template>
  <tu-button class="mb-2" @click="scrollIntoView">滚动到 1-6-6-6</tu-button>
  <tu-tree
    ref="treeRef"
    blockNode
    checkable
    :data="treeData"
    :virtualListProps="{
      height: 300
    }"
  />
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const loop = (path = '1', level = 2) => {
  const list = [];
  for (let i = 0; i < 10; i += 1) {
    const key = `${path}-${i + 1}`;
    const treeNode = {
      title: key,
      key
    };
    if (level > 0) {
      treeNode.children = loop(key, level - 1);
    }
    list.push(treeNode);
  }
  return list;
};
const treeRef = ref();
const treeData = loop();
const scrollIntoView = () => {
  treeRef.value && treeRef.value.scrollIntoView({ key: '1-6-6-6' });
};
</script>搜索树 
展示如何实现搜索树的效果。
<template>
  <tu-input
    class="mb-2"
    placeholder="请输入关键词搜索"
    allow-clear
    v-model="searchKey"
  >
  </tu-input>
  <tu-tree :data="treeData">
    <template #title="nodeData">
      <template v-if="((index = getMatchIndex(nodeData?.title)), index < 0)">
        {{ nodeData?.title }}
      </template>
      <template v-else>
        <span> {{ nodeData?.title?.substr(0, index) }}</span>
        <span style="color: #5e7ce0">
          {{ nodeData?.title?.substr(index, searchKey.length) }}
        </span>
        <span> {{ nodeData?.title?.substr(index + searchKey.length) }}</span>
      </template>
    </template>
  </tu-tree>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue';
const originTreeData = [
  {
    title: '主干 1',
    key: '1',
    children: [
      {
        title: '分支 1-1',
        key: '1-1',
        children: [
          {
            title: '叶子 1-1-1',
            key: '1-1-1'
          },
          {
            title: '叶子 1-1-2',
            key: '1-1-2'
          }
        ]
      },
      {
        title: '分支 1-2',
        key: '1-2',
        children: [
          {
            title: '叶子 1-2-1',
            key: '1-2-1'
          }
        ]
      }
    ]
  }
];
const searchKey = ref('');
const treeData = computed(() => {
  if (!searchKey.value) return originTreeData;
  return handleSearch(searchKey.value);
});
const handleSearch = (keyword) => {
  const loop = (data) => {
    const result = [];
    data.forEach((item) => {
      if (item.title.toLowerCase().indexOf(keyword.toLowerCase()) > -1) {
        result.push({ ...item });
      } else if (item.children) {
        const filterData = loop(item.children);
        if (filterData.length) {
          result.push({
            ...item,
            children: filterData
          });
        }
      }
    });
    return result;
  };
  return loop(originTreeData);
};
const getMatchIndex = (title) => {
  if (!searchKey.value) return -1;
  return title.toLowerCase().indexOf(searchKey.value.toLowerCase());
};
</script><template>
  <tu-input
    class="mb-2"
    placeholder="请输入关键词搜索"
    allow-clear
    v-model="searchKey"
  >
  </tu-input>
  <tu-tree :data="treeData">
    <template #title="nodeData">
      <template v-if="((index = getMatchIndex(nodeData?.title)), index < 0)">
        {{ nodeData?.title }}
      </template>
      <template v-else>
        <span> {{ nodeData?.title?.substr(0, index) }}</span>
        <span style="color: #5e7ce0">
          {{ nodeData?.title?.substr(index, searchKey.length) }}
        </span>
        <span> {{ nodeData?.title?.substr(index + searchKey.length) }}</span>
      </template>
    </template>
  </tu-tree>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue';
const originTreeData = [
  {
    title: '主干 1',
    key: '1',
    children: [
      {
        title: '分支 1-1',
        key: '1-1',
        children: [
          {
            title: '叶子 1-1-1',
            key: '1-1-1'
          },
          {
            title: '叶子 1-1-2',
            key: '1-1-2'
          }
        ]
      },
      {
        title: '分支 1-2',
        key: '1-2',
        children: [
          {
            title: '叶子 1-2-1',
            key: '1-2-1'
          }
        ]
      }
    ]
  }
];
const searchKey = ref('');
const treeData = computed(() => {
  if (!searchKey.value) return originTreeData;
  return handleSearch(searchKey.value);
});
const handleSearch = (keyword) => {
  const loop = (data) => {
    const result = [];
    data.forEach((item) => {
      if (item.title.toLowerCase().indexOf(keyword.toLowerCase()) > -1) {
        result.push({ ...item });
      } else if (item.children) {
        const filterData = loop(item.children);
        if (filterData.length) {
          result.push({
            ...item,
            children: filterData
          });
        }
      }
    });
    return result;
  };
  return loop(originTreeData);
};
const getMatchIndex = (title) => {
  if (!searchKey.value) return -1;
  return title.toLowerCase().indexOf(searchKey.value.toLowerCase());
};
</script>自定义 data 的字段名称 
通过 fieldNames 字段可以自定义 data 的字段名。
叶子 1-1-1
叶子 1-1-2
叶子 1-2-1
<template>
  <tu-tree
    :data="treeData"
    :fieldNames="{
      key: 'value',
      title: 'label',
      children: 'options',
      icon: 'customIcon'
    }"
  />
</template>
<script lang="ts" setup>
import { h } from 'vue';
import { Star } from '@tu-view-plus/icons-vue';
const treeData = [
  {
    label: '主干 1',
    value: '1',
    options: [
      {
        label: '分支 1-1',
        value: '1-1',
        customIcon: () => h(Star),
        options: [
          {
            label: '叶子 1-1-1',
            value: '1-1-1'
          },
          {
            label: '叶子 1-1-2',
            value: '1-1-2'
          }
        ]
      },
      {
        label: '分支 1-2',
        value: '1-2',
        options: [
          {
            label: '叶子 1-2-1',
            value: '1-2-1'
          }
        ]
      }
    ]
  }
];
</script><template>
  <tu-tree
    :data="treeData"
    :fieldNames="{
      key: 'value',
      title: 'label',
      children: 'options',
      icon: 'customIcon'
    }"
  />
</template>
<script lang="ts" setup>
import { h } from 'vue';
import { Star } from '@tu-view-plus/icons-vue';
const treeData = [
  {
    label: '主干 1',
    value: '1',
    options: [
      {
        label: '分支 1-1',
        value: '1-1',
        customIcon: () => h(Star),
        options: [
          {
            label: '叶子 1-1-1',
            value: '1-1-1'
          },
          {
            label: '叶子 1-1-2',
            value: '1-1-2'
          }
        ]
      },
      {
        label: '分支 1-2',
        value: '1-2',
        options: [
          {
            label: '叶子 1-2-1',
            value: '1-2-1'
          }
        ]
      }
    ]
  }
];
</script>不同尺寸 
不同尺寸的树。
叶子 1-1-1
叶子 1-1-2
叶子 1-2-1
<template>
  <tu-radio-group class="mb-2" v-model="size" type="button">
    <tu-radio label="mini">超小</tu-radio>
    <tu-radio label="small">较小</tu-radio>
    <tu-radio label="medium">中等</tu-radio>
    <tu-radio label="large">较大</tu-radio>
  </tu-radio-group>
  <tu-tree :data="treeData" :size="size" checkable />
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const treeData = [
  {
    title: '主干 1',
    key: '1',
    children: [
      {
        title: '分支 1-1',
        key: '1-1',
        children: [
          {
            title: '叶子 1-1-1',
            key: '1-1-1'
          },
          {
            title: '叶子 1-1-2',
            key: '1-1-2'
          }
        ]
      },
      {
        title: '分支 1-2',
        key: '1-2',
        children: [
          {
            title: '叶子 1-2-1',
            key: '1-2-1'
          }
        ]
      }
    ]
  }
];
const size = ref('medium');
</script><template>
  <tu-radio-group class="mb-2" v-model="size" type="button">
    <tu-radio label="mini">超小</tu-radio>
    <tu-radio label="small">较小</tu-radio>
    <tu-radio label="medium">中等</tu-radio>
    <tu-radio label="large">较大</tu-radio>
  </tu-radio-group>
  <tu-tree :data="treeData" :size="size" checkable />
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const treeData = [
  {
    title: '主干 1',
    key: '1',
    children: [
      {
        title: '分支 1-1',
        key: '1-1',
        children: [
          {
            title: '叶子 1-1-1',
            key: '1-1-1'
          },
          {
            title: '叶子 1-1-2',
            key: '1-1-2'
          }
        ]
      },
      {
        title: '分支 1-2',
        key: '1-2',
        children: [
          {
            title: '叶子 1-2-1',
            key: '1-2-1'
          }
        ]
      }
    ]
  }
];
const size = ref('medium');
</script>Tree API 
Tree Attributes 
| 参数名 | 描述 | 类型 | 默认值 | 
|---|---|---|---|
| size | 尺寸 | String | medium | 
| block-node | 节点是否占据一行 | Boolean | false | 
| default-expand-all | 是否默认展开父节点 | Boolean | true | 
| multiple | 是否支持多选 | Boolean | false | 
| checkable | 是否在节点前添加复选框 | Boolean | false | 
| selectable | 是否支持选择 | Boolean | true | 
| check-strictly | 是否取消父子节点关联 | Boolean | false | 
| checked-strategy | 定制回填方式  all: 返回所有选中的节点 parent: 父子节点都选中时只返回父节点 child: 只返回子节点  | String | all | 
| default-selected-keys | 默认选中的树节点 | Array | - | 
| selected-keys / v-model | 选中的树节点 | Array | - | 
| default-checked-keys | 默认选中复选框的树节点 | Array | - | 
| checked-keys / v-model | 选中复选框的树节点 | Array | - | 
| default-expanded-keys | 默认展开的节点 | Array | - | 
| expanded-keys / v-model | 展开的节点 | Array | - | 
| data | 传入 data, 生成对应的树结构 | Array | [] | 
| field-names | 指定节点数据中的字段名 | Object | - | 
| show-line | 是否展示连接线 | Boolean | false | 
| load-more | 异步加载数据的回调,返回一个 Promise | Function | - | 
| draggable | 是否可以拖拽 | Boolean | false | 
| allow-drop | 拖拽时是否允许在某节点上释放 | Function | - | 
| virtual-list-props | 传递虚拟列表属性,传入此参数以开启虚拟滚动,VirtualListProps | Object | - | 
| default-expand-selected | 是否默认展开已选中节点的父节点 | Boolean | false | 
| default-expand-checked | 是否默认展开已选中复选框节点的父节点 | Boolean | false | 
| auto-expand-parent | 是否自动展开已展开节点的父节点 | Boolean | true | 
| half-checked-keys / v-model | 半选状态的节点.仅在 checkable 且 checkStrictly 时生效 | Array | - | 
| only-check-leaf | 开启后 checkedKeys 只处理叶子节点,父节点状态由子节点决定(仅在 checkable 且 checkStrictly 为 false 时生效) | Boolean | false | 
| animation | 是否开启展开时的过渡动效 | Boolean | true | 
| action-on-node-click | 点击节点的时候触发的动作 | String | - | 
Tree Events 
| 事件名 | 描述 | 参数 | 
|---|---|---|
| select | 点击树节点时触发 | Function | 
| check | 点击树节点复选框时触发。 | Function | 
| expand | 展开/关闭 | Function | 
| drag-start | 节点开始拖拽 | - | 
| drag-end | 节点结束拖拽 | Function | 
| drag-over | 节点被拖拽至可释放目标 | Function | 
| drag-leave | 节点离开可释放目标 | Function | 
| drop | 节点在可释放目标上释放 | Function | 
Tree Methods 
| 方法名 | 描述 | 参数 | 返回值 | 
|---|---|---|---|
| scrollIntoView | 虚拟列表滚动某个元素 | Function | - | 
| getSelectedNodes | 获取选中的节点 | - | TreeNodeData[] | 
| getCheckedNodes | 获取选中复选框的节点。支持传入 checkedStrategy,没有传则取组件的配置。 | Function | TreeNodeData[] | 
| getHalfCheckedNodes | 获取复选框半选的节点 | - | TreeNodeData[] | 
| getExpandedNodes | 获取展开的节点 | - | TreeNodeData[] | 
| checkAll | 设置全部节点的复选框状态 | Function | - | 
| checkNode | 设置指定节点的复选框状态 | Function | - | 
| selectAll | 设置全部节点的选中状态 | Function | - | 
| selectNode | 设置指定节点的选中状态 | Function | - | 
| expandAll | 设置全部节点的展开状态 | Function | - | 
| expandNode | 设置指定节点的展开状态 | Function | - | 
Tree Slots 
| 参数名 | 描述 | 参数 | 
|---|---|---|
| title | 标题 | - | 
| extra | 渲染额外的节点内容 | - | 
| drag-icon | 定制 drag 图标 | node | 
| loading-icon | 定制 loading 图标 | - | 
| switcher-icon | 定制 switcher 图标 | - | 
| icon | 定制节点图标 | node | 
TreeNodeData 
| 参数名 | 描述 | 类型 | 默认值 | 
|---|---|---|---|
| key | 唯一标示 | StringNumber | - | 
| title | 该节点显示的标题 | String | - | 
| selectable | 是否允许选中 | Boolean | false | 
| disabled | 是否禁用节点 | Boolean | false | 
| disableCheckbox | 是否禁用复选框 | Boolean | false | 
| checkable | 是否显示多选框 | Boolean | false | 
| draggable | 是否可以拖拽 | Boolean | false | 
| isLeaf | 是否是叶子节点。动态加载时有效 | Boolean | false | 
| icon | 节点的图标 | Function | - | 
| switcherIcon | 定制 switcher 图标,优先级大于 tree | Function | - | 
| loadingIcon | 定制 loading 图标,优先级大于 tree | Function | - | 
| dragIcon | 定制 drag 图标,优先级大于 tree | Function | - | 
| children | 子节点 | Array | - | 
TreeFieldNames 
| 参数名 | 描述 | 类型 | 默认值 | 
|---|---|---|---|
| key | 指定 key 在 TreeNodeData 中的字段名 | String | key | 
| title | 指定 title 在 TreeNodeData 中的字段名 | String | title | 
| disabled | 指定 disabled 在 TreeNodeData 中的字段名 | String | disabled | 
| children | 指定 children 在 TreeNodeData 中的字段名 | String | children | 
| isLeaf | 指定 isLeaf 在 TreeNodeData 中的字段名 | String | isLeaf | 
| disableCheckbox | 指定 disableCheckbox 在 TreeNodeData 中的字段名 | String | disableCheckbox | 
| checkable | 指定 checkable 在 TreeNodeData 中的字段名 | String | checkable | 
| icon | 指定 icon 在 TreeNodeData 中的字段名 | String | checkable | 
VirtualListProps 
| 参数名 | 描述 | 类型 | 默认值 | 
|---|---|---|---|
| height | 可视区域高度 | NumberString | - | 
| threshold | 开启虚拟滚动的元素数量阈值,当数据数量小于阈值时不会开启虚拟滚动。 | Number | - | 
| isStaticItemHeight | (已废除)元素高度是否是固定的。 | Boolean | false | 
| fixedSize | 元素高度是否是固定的。 | Boolean | false | 
| estimatedSize | 元素高度不固定时的预估高度。 | Number | - | 
| buffer | 视口边界外提前挂载的元素数量。 | Number | 10 |