# Tree

对 `antv` 的 tree 组件进行封装

## 代码演示

### 基础树
<CodePreview src="/doc-comp/tree/basic" :height="700">
<details>
<summary>展开查看</summary>

```vue
<template>
  <PageWrapper title="Tree基础示例">
    <ARow :gutter="[16, 16]">
      <ACol :span="8">
        <BasicTree title="基础示例，默认展开第一层" :treeData="treeData" defaultExpandLevel="1">
          <template #title> 123123 </template>
        </BasicTree>
      </ACol>
      <ACol :span="8">
        <BasicTree
          title="可勾选，默认全部展开"
          :treeData="treeData"
          :checkable="true"
          defaultExpandAll
          @check="handleCheck"
        />
      </ACol>
      <ACol :span="8">
        <BasicTree
          title="指定默认展开/勾选示例"
          :treeData="treeData"
          :checkable="true"
          :expandedKeys="['0-0']"
          :checkedKeys="['0-0']"
        />
      </ACol>
      <ACol :span="8">
        <BasicTree
          ref="asyncTreeRef"
          title="懒加载异步树"
          :treeData="tree"
          :load-data="onLoadData"
        />
      </ACol>
      <ACol :span="8">
        <ACard title="异步数据，默认展开">
          <template #extra>
            <a-button :loading="treeLoading" @click="loadTreeData">加载数据</a-button>
          </template>
          <Spin :spinning="treeLoading">
            <BasicTree ref="asyncExpandTreeRef" :treeData="tree2" />
          </Spin>
        </ACard>
      </ACol>
      <ACol :span="8">
        <ACard title="BasicTree内置加载">
          <template #extra>
            <a-button :loading="treeLoading" @click="loadTreeData2">请求数据</a-button>
          </template>
          <BasicTree ref="loadTreeRef" :treeData="tree2" :loading="treeLoading" />
        </ACard>
      </ACol>
    </ARow>
  </PageWrapper>
</template>
<script lang="ts" setup>
  import { BasicTree, PageWrapper, TreeActionType, TreeItem } from '@eciol/ant-ui';
  import { isArray } from '@eciol/shared';
  import { type Nullable } from '@eciol/types';
  import { cloneDeep, uniq } from 'lodash-es';

  import { treeData } from './data';

  const asyncTreeRef = ref<Nullable<TreeActionType>>(null);
  const asyncExpandTreeRef = ref<Nullable<TreeActionType>>(null);
  const loadTreeRef = ref<Nullable<TreeActionType>>(null);
  const tree2 = ref<TreeItem[]>([]);
  const treeLoading = ref(false);

  function handleCheck(checkedKeys, e) {
    console.log('onChecked', checkedKeys, e);
  }

  function loadTreeData() {
    treeLoading.value = true;
    // 以下是模拟异步获取数据
    setTimeout(() => {
      // 设置数据源
      tree2.value = cloneDeep(treeData);
      treeLoading.value = false;
      // 展开全部
      nextTick(() => {
        console.log(unref(asyncExpandTreeRef));
        unref(asyncExpandTreeRef)?.expandAll(true);
      });
    }, 2000);
  }
  function loadTreeData2() {
    treeLoading.value = true;
    // 以下是模拟异步获取数据
    setTimeout(() => {
      // 设置数据源
      tree2.value = cloneDeep(treeData);
      treeLoading.value = false;
    }, 2000);
  }

  const tree = ref([
    {
      title: 'parent ',
      key: '0-0',
    },
  ]);

  function onLoadData(treeNode) {
    return new Promise((resolve: (value?: unknown) => void) => {
      if (isArray(treeNode.children) && treeNode.children.length > 0) {
        resolve();
        return;
      }
      setTimeout(() => {
        const asyncTreeAction: TreeActionType | null = unref(asyncTreeRef);
        if (asyncTreeAction) {
          const nodeChildren = [
            { title: `Child Node ${treeNode.eventKey}-0`, key: `${treeNode.eventKey}-0` },
            { title: `Child Node ${treeNode.eventKey}-1`, key: `${treeNode.eventKey}-1` },
          ];
          asyncTreeAction.updateNodeByKey(treeNode.eventKey, { children: nodeChildren });
          asyncTreeAction.setExpandedKeys(
            uniq([treeNode.eventKey, ...asyncTreeAction.getExpandedKeys()]),
          );
        }

        resolve();
        return;
      }, 300);
    });
  }
</script>

```
</details>
</CodePreview>

### 可搜索/工具栏

<CodePreview src="/doc-comp/tree/editTree" :height="600">
<details>
<summary>展开查看</summary>

```vue
<template>
  <PageWrapper title="Tree函数操作示例">
    <ARow :gutter="[16, 16]">
      <ACol :span="8">
        <BasicTree
          title="右侧操作按钮/自定义图标"
          helpMessage="帮助信息"
          :treeData="treeData"
          :actionList="actionList"
          :renderIcon="createIcon"
        />
      </ACol>
      <ACol :span="8">
        <BasicTree title="右键菜单" :treeData="treeData" :beforeRightClick="getRightMenuList" />
      </ACol>
      <ACol :span="8">
        <BasicTree
          title="工具栏使用"
          toolbar
          checkable
          search
          :treeData="treeData"
          :beforeRightClick="getRightMenuList"
        />
      </ACol>
      <ACol :span="8">
        <BasicTree title="没有fieldNames，插槽有效" helpMessage="正确的示例" :treeData="treeData3">
          <template #title="item"> 插槽：{{ item.name }} </template>
        </BasicTree>
      </ACol>
      <ACol :span="8">
        <BasicTree
          title="有fieldNames后，插槽失效"
          helpMessage="错误的示例, 应该显示插槽的内容才对"
          :fieldNames="{ key: 'id', title: 'name' }"
          :treeData="treeData2"
        >
          <template #title="item"> 插槽：{{ item.title }} </template>
        </BasicTree>
      </ACol>
      <ACol :span="8">
        <BasicTree
          title="有fieldNames后，actionList失效"
          helpMessage="错误的示例，应该在鼠标移上去时，显示新增和删除按钮才对"
          :treeData="treeData3"
          :actionList="actionList"
          :fieldNames="{ key: 'key', title: 'name' }"
        />
      </ACol>
    </ARow>
  </PageWrapper>
</template>
<script lang="ts" setup>
  import { DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue';
  import { BasicTree, ContextMenuItem, PageWrapper, TreeActionItem } from '@eciol/ant-ui';

  import { treeData, treeData2, treeData3 } from './data';

  function handlePlus(node: any) {
    console.log(node);
  }

  function getRightMenuList(node: any): ContextMenuItem[] {
    return [
      {
        label: '新增',
        handler: () => {
          console.log('点击了新增', node);
        },
        icon: 'bi:plus',
      },
      {
        label: '删除',
        handler: () => {
          console.log('点击了删除', node);
        },
        icon: 'bx:bxs-folder-open',
      },
    ];
  }
  const actionList: TreeActionItem[] = [
    {
      // show:()=>boolean;
      render: (node) => {
        return h(PlusOutlined, {
          class: 'ml-2',
          onClick: () => {
            handlePlus(node);
          },
        });
      },
    },
    {
      render: () => {
        return h(DeleteOutlined);
      },
    },
  ];

  function createIcon({ level }) {
    if (level === 1) {
      return 'ion:git-compare-outline';
    }
    if (level === 2) {
      return 'ion:home';
    }
    if (level === 3) {
      return 'ion:airplane';
    }
    return '';
  }
</script>

```
</details>
</CodePreview>

### 函数操作示例

<CodePreview src="/doc-comp/tree/actionTree" :height="600">
<details>
<summary>展开查看</summary>

```vue
<template>
  <PageWrapper title="Tree函数操作示例" contentBackground contentClass="p-4">
    <div class="mb-4">
      <a-button class="mr-2" @click="expandAll(true)"> 展开全部 </a-button>
      <a-button class="mr-2" @click="expandAll(false)"> 折叠全部 </a-button>
      <a-button class="mr-2" @click="checkAll(true)"> 全选 </a-button>
      <a-button class="mr-2" @click="checkAll(false)"> 全不选 </a-button>
      <a-button class="mr-2" @click="handleLevel(2)"> 显示到第2级 </a-button>
      <a-button class="mr-2" @click="handleLevel(1)"> 显示到第1级 </a-button>
    </div>
    <div class="mb-4">
      <a-button class="mr-2" @click="handleSetCheckData"> 设置勾选数据 </a-button>
      <a-button class="mr-2" @click="handleGetCheckData"> 获取勾选数据 </a-button>
      <a-button class="mr-2" @click="handleSetSelectData"> 设置选中数据 </a-button>
      <a-button class="mr-2" @click="handleGetSelectData"> 获取选中数据 </a-button>
      <a-button class="mr-2" @click="handleGetSelectNode"> 获取选中节点 </a-button>

      <a-button class="mr-2" @click="handleSetExpandData"> 设置展开数据 </a-button>
      <a-button class="mr-2" @click="handleGetExpandData"> 获取展开数据 </a-button>
    </div>
    <div class="mb-4">
      <a-button class="mr-2" @click="appendNodeByKey(null)"> 添加根节点 </a-button>
      <a-button class="mr-2" @click="appendNodeByKey('2-2')"> 添加在parent3内添加节点 </a-button>
      <a-button class="mr-2" @click="deleteNodeByKey('2-2')"> 删除parent3节点 </a-button>
      <a-button class="mr-2" @click="updateNodeByKey('1-1')"> 更新parent2节点 </a-button>
    </div>
    <BasicTree ref="treeRef" :treeData="treeData" title="函数操作" :checkable="true" />
  </PageWrapper>
</template>
<script lang="ts" setup>
  import { BasicTree, PageWrapper, TreeActionType, useMessage } from '@eciol/ant-ui';
  import { type Nullable } from '@eciol/types';

  import { treeData } from './data';

  const treeRef = ref<Nullable<TreeActionType>>(null);
  const { createMessage } = useMessage();

  function getTree() {
    const tree = unref(treeRef);
    if (!tree) {
      throw new Error('tree is null!');
    }
    return tree;
  }

  function handleLevel(level: number) {
    getTree().filterByLevel(level);
  }

  function handleSetCheckData() {
    getTree().setCheckedKeys(['0-0']);
  }

  function handleGetCheckData() {
    const keys = getTree().getCheckedKeys();
    createMessage.success(JSON.stringify(keys));
  }

  function handleSetSelectData() {
    getTree().setSelectedKeys(['0-0']);
  }

  function handleGetSelectData() {
    const keys = getTree().getSelectedKeys();
    createMessage.success(JSON.stringify(keys));
  }

  function handleGetSelectNode() {
    const keys = getTree().getSelectedKeys();
    const node = getTree().getSelectedNode(keys[0]);
    createMessage.success(node !== null ? JSON.stringify(node) : null);
  }

  function handleSetExpandData() {
    getTree().setExpandedKeys(['0-0']);
  }

  function handleGetExpandData() {
    const keys = getTree().getExpandedKeys();
    createMessage.success(JSON.stringify(keys));
  }

  function checkAll(checkAll: boolean) {
    getTree().checkAll(checkAll);
  }

  function expandAll(checkAll: boolean) {
    getTree().expandAll(checkAll);
  }

  function appendNodeByKey(parentKey: string | null = null) {
    getTree().insertNodeByKey({
      parentKey,
      node: {
        title: '新增节点',
        key: '2-2-2',
      },
      // 往后插入
      push: 'push',
      // 往前插入
      // push:'unshift'
    });
  }

  function deleteNodeByKey(key: string) {
    getTree().deleteNodeByKey(key);
  }

  function updateNodeByKey(key: string) {
    getTree().updateNodeByKey(key, {
      title: 'parent2-new',
    });
  }
</script>

```
</details>
</CodePreview>

## Usage

```vue
<template>
  <BasicTree :treeData="treeData" />
</template>
<script lang="ts" setup>
  import { BasicTree, type TreeItem } from '@eciol/ant-ui';
  import { treeData } from './data';
  import { CollapseContainer } from '@eciol/share-ui';

  export const treeData: TreeItem[] = [
    {
      title: 'parent 1',
      key: '0-0',
      icon: 'home|svg',
      children: [
        { title: 'leaf', key: '0-0-0' },
        {
          title: 'leaf',
          key: '0-0-1',
          children: [
            { title: 'leaf', key: '0-0-0-0' },
            { title: 'leaf', key: '0-0-0-1' },
          ],
        },
      ],
    },
    {
      title: 'parent 2',
      key: '1-1',
      icon: 'home|svg',
      children: [
        { title: 'leaf', key: '1-1-0' },
        { title: 'leaf', key: '1-1-1' },
      ],
    },
    {
      title: 'parent 3',
      key: '2-2',
      icon: 'home|svg',
      children: [
        { title: 'leaf', key: '2-2-0' },
        { title: 'leaf', key: '2-2-1' },
      ],
    },
  ];
</script>
```

## Props

::: tip 温馨提醒

除以下参数外，官方文档内的 props 也都支持，具体可以参考 [antv tree](https://www.antdv.com/components/tree-cn/#Tree-props)

:::

| 属性             | 类型                      | 默认值 | 可选值 | 说明                         | 版本 |
| ---------------- | ------------------------- | ------ | ------ | ---------------------------- | - |
| treeData         | `TreeItem[]`              | -      | -      | 树组件数据                   |   |
| rightMenuList    | `ContextMenuItem[]`       | -      | -      | 右键菜单列表                 |   |
| checkedKeys      | `string[]`                | -      | -      | 勾选的节点                   |   |
| selectedKeys     | `string[]`                | -      | -      | 选中的节点                   |   |
| expandedKeys     | `string[]`                | -      | -      | 展开的节点                   |  |
| actionList       | `ActionItem[]`            | -      | -      | 鼠标移动上去右边操作按钮列表 |  |
| title            | `string`                  | -      | -      | 定制标题字符串              |  |
| toolbar            | `boolean`                  | -      | -      | 是否显示工具栏             |  |
| search            | `boolean`                  | -      | -      | 显示搜索框             |  |
| clickRowToExpand            | `boolean`                  | -      | -      | 是否在点击行时自动展开             |  |
| beforeRightClick            | `(node, event)=>ContextMenuItem[]`                  | -      | -      | 右键点击回调，可返回右键菜单列表数据来生成右键菜单 |  |
| rightMenuList            | `ContextMenuItem[]`                  | -      | -      | 右键菜单列表数据             |  |
| defaultExpandLevel | `string ｜ number`        | -      | -      | 初次渲染后默认展开的层级     | 2.4.1 |
| defaultExpandAll | `boolean`       | `false`  | `true/false`      | 初次渲染后默认全部     | 2.4.1 |
| searchValue(v-model)       | `string`  | -   | - | 当前搜索词 | 2.7.1 |

::: tip 注意

`defaultExpandLevel`、`defaultExpandAll` 仅在**初次渲染**时生效。如果`basicTree`是在创建完毕之后才设置的`treeData`(如异步数据)，需要在更新后自己调用`basicTree`提供的`expandAll`、`filterByLevel`来执行展开

:::

**ActionItem**

```ts
{
  // 渲染的图标
  render: (record: any) => any;
  // 是否显示
  show?: boolean | ((record: Recordable) => boolean);
}
```

**ContextMenuItem**

```ts
{
  // 文本
  label: string;
  // 图标
  icon?: string;
  // 是否禁用
  disabled?: boolean;
  // 事件
  handler?: (...arg) => any;
  // 是否显示分隔线
  divider?: boolean;
  // 子级菜单数据
  children?: ContextMenuItem[];
}
```

## Slots

::: tip 温馨提醒

官方文档内的 slot 都支持，具体可以参考 [antv tree](https://www.antdv.com/components/tree-cn/#Tree-props)

:::

## Methods

| 名称            | 回调参数                                             | 说明                   |
| --------------- | ---------------------------------------------------- | ---------------------- |
| checkAll        | `(checkAll: boolean) => void`                        | 选择所有               |
| expandAll       | `(expandAll: boolean) => void`                       | 展开所有               |
| setExpandedKeys | `(keys: Keys) => void`                               | 设置展开节点           |
| getExpandedKeys | `() => Keys`                                         | 获取展开节点           |
| setSelectedKeys | `(keys: Keys) => void`                               | 设置选中节点           |
| getSelectedKeys | `() => Keys`                                         | 获取选中节点           |
| setCheckedKeys  | `(keys: CheckKeys) => void`                          | 设置勾选节点           |
| getCheckedKeys  | `() => CheckKeys`                                    | 获取勾选节点           |
| filterByLevel   | `(level: number) => void`                            | 显示指定等级           |
| insertNodeByKey | `(opt: InsertNodeParams) => void`                    | 插入子节点到指定节点内 |
| deleteNodeByKey | `(key: string) => void`                              | 根据 key 删除节点      |
| updateNodeByKey | `(key: string, node: Omit<TreeItem, 'key'>) => void` | 根据 key 更新节点      |
| setSearchValue | `(value: string) => void` | 设置当前搜索词（v2.7.1）      |
| getSearchValue | `() => string` | 获取当前搜索词(v2.7.1)      |
