介绍
用于显示层次结构数据,可展开或折叠。
引入
import Tree from 'sard-uniapp/components/tree/tree.vue'代码演示
基础使用
通过 data 属性设置数据,通过 node-keys 属性告知如何从传入的数据中获取每个节点的数据。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="基础用法">
<sar-tree :data="treeData" :node-keys="{ title: 'name', key: 'code' }" />
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { getRegionData } from 'region-data'
const regionData = getRegionData()
const treeData = regionData.slice(0, 5).map((item) => ({
...item,
children: item.children.slice(0, 3).map((item) => ({
...item,
children: item.children.slice(0, 3),
})),
}))
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="基础用法">
<sar-tree :data="treeData" :node-keys="{ title: 'name', key: 'code' }" />
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { getRegionData } from "region-data";
const regionData = getRegionData();
const treeData = regionData.slice(0, 5).map((item) => ({
...item,
children: item.children.slice(0, 3).map((item2) => ({
...item2,
children: item2.children.slice(0, 3)
}))
}));
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>手风琴
对于同一级的节点,每次只能展开一个。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="手风琴">
<sar-tree
:data="treeData"
:node-keys="{ title: 'name', key: 'code' }"
accordion
/>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { getRegionData } from 'region-data'
const regionData = getRegionData()
const treeData = regionData.slice(0, 5).map((item) => ({
...item,
children: item.children.slice(0, 3).map((item) => ({
...item,
children: item.children.slice(0, 3),
})),
}))
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="手风琴">
<sar-tree
:data="treeData"
:node-keys="{ title: 'name', key: 'code' }"
accordion
/>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { getRegionData } from "region-data";
const regionData = getRegionData();
const treeData = regionData.slice(0, 5).map((item) => ({
...item,
children: item.children.slice(0, 3).map((item2) => ({
...item2,
children: item2.children.slice(0, 3)
}))
}));
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>可选择的
设置 selectable 属性可以显示复选框让用户选择节点,还可以禁用节点的选择。
节点的 key 属性是非常重要的(可以自定义 key 的键名),必须保证节点的 key 在所有节点中的唯一性。 如果没有设置 key,则由程序生成一个全局唯一的标识作为 key。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="可选择的">
<sar-list>
<sar-list-item
hover
arrow
title="设置选中的节点"
@click="setCheckedKeys"
/>
<sar-list-item
hover
arrow
title="获取选中的节点"
@click="getCheckedKeys"
/>
<sar-list-item hover arrow title="清空" @click="clear" />
</sar-list>
<sar-tree
ref="treeRef"
:data="treeData"
:node-keys="{ title: 'name', key: 'code' }"
selectable
default-expand-all
root-style="margin-top: 30rpx"
@check="(event) => console.log(event)"
/>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { getRegionData } from 'region-data'
import { toast, type TreeExpose } from 'sard-uniapp'
import { ref } from 'vue'
const regionData = getRegionData()
const treeData = regionData.slice(0, 5).map((item, index) => ({
...item,
disabled: index === 0 ? true : false,
children: item.children.slice(0, 3).map((item, index) => ({
...item,
disabled: index === 0 ? true : false,
children: item.children.slice(0, 3),
})),
}))
const treeRef = ref<TreeExpose>()
const setCheckedKeys = () => {
treeRef.value?.setCheckedKeys([110101, 120100])
}
const getCheckedKeys = () => {
toast(treeRef.value?.getCheckedKeys().join(', '))
}
const clear = () => {
treeRef.value?.setCheckedKeys([])
}
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="可选择的">
<sar-list>
<sar-list-item
hover
arrow
title="设置选中的节点"
@click="setCheckedKeys"
/>
<sar-list-item
hover
arrow
title="获取选中的节点"
@click="getCheckedKeys"
/>
<sar-list-item hover arrow title="清空" @click="clear" />
</sar-list>
<sar-tree
ref="treeRef"
:data="treeData"
:node-keys="{ title: 'name', key: 'code' }"
selectable
default-expand-all
root-style="margin-top: 30rpx"
@check="(event) => console.log(event)"
/>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { getRegionData } from "region-data";
import { toast } from "sard-uniapp";
import { ref } from "vue";
const regionData = getRegionData();
const treeData = regionData.slice(0, 5).map((item, index) => ({
...item,
disabled: index === 0 ? true : false,
children: item.children.slice(0, 3).map((item2, index2) => ({
...item2,
disabled: index2 === 0 ? true : false,
children: item2.children.slice(0, 3)
}))
}));
const treeRef = ref();
const setCheckedKeys = () => {
treeRef.value?.setCheckedKeys([110101, 120100]);
};
const getCheckedKeys = () => {
toast(treeRef.value?.getCheckedKeys().join(", "));
};
const clear = () => {
treeRef.value?.setCheckedKeys([]);
};
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>严格选择
在显示复选框的情况下,设置 check-strictly 属性来严格遵循父子不互相关联规则。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="严格选择">
<sar-tree
:data="treeData"
:node-keys="{ title: 'name', key: 'code' }"
selectable
check-strictly
/>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { getRegionData } from 'region-data'
const regionData = getRegionData()
const treeData = regionData.slice(0, 5).map((item) => ({
...item,
children: item.children.slice(0, 3).map((item) => ({
...item,
children: item.children.slice(0, 3),
})),
}))
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="严格选择">
<sar-tree
:data="treeData"
:node-keys="{ title: 'name', key: 'code' }"
selectable
check-strictly
/>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { getRegionData } from "region-data";
const regionData = getRegionData();
const treeData = regionData.slice(0, 5).map((item) => ({
...item,
children: item.children.slice(0, 3).map((item2) => ({
...item2,
children: item2.children.slice(0, 3)
}))
}));
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>默认展开以及默认选中
树节点可以在初始化阶段被设置为展开和选中。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="默认展开以及默认选中">
<sar-tree
:data="treeData"
:node-keys="{ title: 'name', key: 'code' }"
selectable
:default-checked-keys="defaultCheckedKeys"
:default-expanded-keys="defaultExpandedKeys"
/>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { getRegionData } from 'region-data'
const regionData = getRegionData()
const treeData = regionData.slice(0, 5).map((item) => ({
...item,
children: item.children.slice(0, 3).map((item) => ({
...item,
children: item.children.slice(0, 3),
})),
}))
const defaultCheckedKeys = [110101, 120100]
const defaultExpandedKeys = [110101, 120100]
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="默认展开以及默认选中">
<sar-tree
:data="treeData"
:node-keys="{ title: 'name', key: 'code' }"
selectable
:default-checked-keys="defaultCheckedKeys"
:default-expanded-keys="defaultExpandedKeys"
/>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { getRegionData } from "region-data";
const regionData = getRegionData();
const treeData = regionData.slice(0, 5).map((item) => ({
...item,
children: item.children.slice(0, 3).map((item2) => ({
...item2,
children: item2.children.slice(0, 3)
}))
}));
const defaultCheckedKeys = [110101, 120100];
const defaultExpandedKeys = [110101, 120100];
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>单一选择
设置 single-selectable 属性即可实现单选,选择后会触发 select 事件,可使用 current 属性设置当前选择的节点。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="单一选择">
<sar-tree
:data="treeData"
:node-keys="{ title: 'name', key: 'code' }"
v-model:current="value"
single-selectable
default-expand-all
root-style="margin-top: 30rpx"
@select="onSelect"
/>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { getRegionData } from 'region-data'
import { type TreeStateNode } from 'sard-uniapp'
import { ref } from 'vue'
const regionData = getRegionData()
const treeData = regionData.slice(0, 5).map((item, index) => ({
...item,
disabled: index === 0 ? true : false,
children: item.children.slice(0, 3).map((item, index) => ({
...item,
disabled: index === 0 ? true : false,
children: item.children.slice(0, 3),
})),
}))
const value = ref(110102)
const onSelect = (key: number | string, node: TreeStateNode) => {
console.log(key, node)
}
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="单一选择">
<sar-tree
:data="treeData"
:node-keys="{ title: 'name', key: 'code' }"
v-model:current="value"
single-selectable
default-expand-all
root-style="margin-top: 30rpx"
@select="onSelect"
/>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { getRegionData } from "region-data";
import { ref } from "vue";
const regionData = getRegionData();
const treeData = regionData.slice(0, 5).map((item, index) => ({
...item,
disabled: index === 0 ? true : false,
children: item.children.slice(0, 3).map((item2, index2) => ({
...item2,
disabled: index2 === 0 ? true : false,
children: item2.children.slice(0, 3)
}))
}));
const value = ref(110102);
const onSelect = (key, node) => {
console.log(key, node);
};
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>仅选择叶子节点
设置 leaf-only 属性让其仅能选择叶子节点。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="仅选择叶子节点">
<sar-tree
:data="treeData"
:node-keys="{ title: 'name', key: 'code' }"
single-selectable
leaf-only
default-expand-all
root-style="margin-top: 30rpx"
@select="onSelect"
/>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { getRegionData } from 'region-data'
import { type TreeStateNode } from 'sard-uniapp'
const regionData = getRegionData()
const treeData = regionData.slice(0, 5).map((item) => ({
...item,
children: item.children.slice(0, 3).map((item) => ({
...item,
children: item.children.slice(0, 3),
})),
}))
const onSelect = (key: number | string, node: TreeStateNode) => {
console.log(key, node)
}
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="仅选择叶子节点">
<sar-tree
:data="treeData"
:node-keys="{ title: 'name', key: 'code' }"
single-selectable
leaf-only
default-expand-all
root-style="margin-top: 30rpx"
@select="onSelect"
/>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { getRegionData } from "region-data";
const regionData = getRegionData();
const treeData = regionData.slice(0, 5).map((item) => ({
...item,
children: item.children.slice(0, 3).map((item2) => ({
...item2,
children: item2.children.slice(0, 3)
}))
}));
const onSelect = (key, node) => {
console.log(key, node);
};
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>可拖拽的
设置 draggable 属性使节点可拖拽,短按拖拽按钮便可进入拖拽状态; 单击拖拽按钮,还可以设置节点的层级。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="可拖拽的">
<sar-tree
:data="treeData"
:node-keys="{ title: 'name', key: 'code' }"
draggable
/>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { getRegionData } from 'region-data'
const regionData = getRegionData()
const treeData = regionData.slice(0, 5).map((item) => ({
...item,
children: item.children.slice(0, 3).map((item) => ({
...item,
children: item.children.slice(0, 3),
})),
}))
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="可拖拽的">
<sar-tree
:data="treeData"
:node-keys="{ title: 'name', key: 'code' }"
draggable
/>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { getRegionData } from "region-data";
const regionData = getRegionData();
const treeData = regionData.slice(0, 5).map((item) => ({
...item,
children: item.children.slice(0, 3).map((item2) => ({
...item2,
children: item2.children.slice(0, 3)
}))
}));
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>可编辑的
设置 editable 属性使节点可编辑(增删改),配合 draggable 属性便可以随意编辑树形数据。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="可编辑的">
<sar-button @click="addRootNode" root-style="margin: 32rpx">
添加根节点
</sar-button>
<sar-button @click="getCleanTreeData" root-style="margin: 32rpx">
获取树形数据
</sar-button>
<sar-tree
ref="treeRef"
:data="treeData"
:node-keys="{ title: 'name', key: 'code' }"
draggable
editable
/>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { getRegionData } from 'region-data'
import { toast, type TreeExpose } from 'sard-uniapp'
import { ref } from 'vue'
const regionData = getRegionData()
const treeData = regionData.slice(0, 5).map((item) => ({
...item,
children: item.children.slice(0, 3).map((item) => ({
...item,
children: item.children.slice(0, 3),
})),
}))
const treeRef = ref<TreeExpose>()
const addRootNode = () => {
treeRef.value?.addRootNode()
}
const getCleanTreeData = () => {
toast('打开控制台查看')
console.log(treeRef.value?.getCleanTreeData())
}
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="可编辑的">
<sar-button @click="addRootNode" root-style="margin: 32rpx">
添加根节点
</sar-button>
<sar-button @click="getCleanTreeData" root-style="margin: 32rpx">
获取树形数据
</sar-button>
<sar-tree
ref="treeRef"
:data="treeData"
:node-keys="{ title: 'name', key: 'code' }"
draggable
editable
/>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { getRegionData } from "region-data";
import { toast } from "sard-uniapp";
import { ref } from "vue";
const regionData = getRegionData();
const treeData = regionData.slice(0, 5).map((item) => ({
...item,
children: item.children.slice(0, 3).map((item2) => ({
...item2,
children: item2.children.slice(0, 3)
}))
}));
const treeRef = ref();
const addRootNode = () => {
treeRef.value?.addRootNode();
};
const getCleanTreeData = () => {
toast("\u6253\u5F00\u63A7\u5236\u53F0\u67E5\u770B");
console.log(treeRef.value?.getCleanTreeData());
};
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>异步编辑与拖拽 1.26+
使用 before-drop 和 before-edit 属性可在节点拖拽或编辑时与远程服务器通信,在接口请求失败时会取消拖拽和编辑。
这两属性也可用于同步阻止拖拽或编辑。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="异步编辑与拖拽">
<sar-tree
ref="treeRef"
:data="treeData"
:node-keys="{ title: 'name', key: 'code' }"
draggable
editable
:before-edit="beforeEdit"
:before-drop="beforeDrop"
/>
</doc-page>
</template>
<script setup lang="ts">
import {
sleep,
TreeProps,
useCurrentPageLock,
usePageTopPopup,
} from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { getRegionData } from 'region-data'
import { toast, type TreeExpose } from 'sard-uniapp'
import { ref } from 'vue'
// page
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
// tree
const regionData = getRegionData()
const treeData = regionData.slice(0, 5).map((item) => ({
...item,
name: `${item.name} (编辑会失败)`,
children: item.children.slice(0, 3).map((item) => ({
...item,
name: `${item.name} (拖拽会失败)`,
children: item.children.slice(0, 3),
})),
}))
const treeRef = ref<TreeExpose>()
const beforeEdit: TreeProps['beforeEdit'] = async (node, title, type) => {
console.log(node, title, type)
// 模拟网络请求
await sleep(500)
if (type === 'edit' && /0{4}$/.test(String(node.key))) {
toast('编辑失败')
return Promise.reject()
}
}
const beforeDrop: TreeProps['beforeDrop'] = async (
draggingNode,
dropNode,
type,
) => {
console.log(draggingNode, dropNode, type)
// 模拟网络请求
await sleep(500)
if (/[^0]0{2}$/.test(String(draggingNode.key))) {
toast('拖拽失败')
return Promise.reject()
}
}
</script><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="异步编辑与拖拽">
<sar-tree
ref="treeRef"
:data="treeData"
:node-keys="{ title: 'name', key: 'code' }"
draggable
editable
:before-edit="beforeEdit"
:before-drop="beforeDrop"
/>
</doc-page>
</template>
<script setup lang="js">
import {
sleep,
useCurrentPageLock,
usePageTopPopup
} from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { getRegionData } from "region-data";
import { toast } from "sard-uniapp";
import { ref } from "vue";
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
const regionData = getRegionData();
const treeData = regionData.slice(0, 5).map((item) => ({
...item,
name: `${item.name} (\u7F16\u8F91\u4F1A\u5931\u8D25)`,
children: item.children.slice(0, 3).map((item2) => ({
...item2,
name: `${item2.name} (\u62D6\u62FD\u4F1A\u5931\u8D25)`,
children: item2.children.slice(0, 3)
}))
}));
const treeRef = ref();
const beforeEdit = async (node, title, type) => {
console.log(node, title, type);
await sleep(500);
if (type === "edit" && /0{4}$/.test(String(node.key))) {
toast("\u7F16\u8F91\u5931\u8D25");
return Promise.reject();
}
};
const beforeDrop = async (draggingNode, dropNode, type) => {
console.log(draggingNode, dropNode, type);
await sleep(500);
if (/[^0]0{2}$/.test(String(draggingNode.key))) {
toast("\u62D6\u62FD\u5931\u8D25");
return Promise.reject();
}
};
</script>树节点过滤
树节点是可以被过滤的。
调用 filter 方法来过滤树节点。方法的参数就是过滤关键字。 通过设置 filter-method 属性还可以实现自定义的过滤。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="树节点过滤">
<view style="padding: 32rpx; background: var(--sar-emphasis-bg)">
<sar-input v-model="searchString" placeholder="请输入过滤关键词" />
</view>
<sar-tree
ref="treeRef"
:data="treeData"
:node-keys="{ title: 'name', key: 'code' }"
/>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { type TreeExpose } from 'sard-uniapp'
import { ref, watch } from 'vue'
import { getRegionData } from 'region-data'
const searchString = ref('')
const treeRef = ref<TreeExpose>()
watch(searchString, () => {
treeRef.value?.filter(searchString.value)
})
const regionData = getRegionData()
const treeData = regionData.slice(0, 5).map((item) => ({
...item,
children: item.children.slice(0, 3).map((item) => ({
...item,
children: item.children.slice(0, 3),
})),
}))
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="树节点过滤">
<view style="padding: 32rpx; background: var(--sar-emphasis-bg)">
<sar-input v-model="searchString" placeholder="请输入过滤关键词" />
</view>
<sar-tree
ref="treeRef"
:data="treeData"
:node-keys="{ title: 'name', key: 'code' }"
/>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { ref, watch } from "vue";
import { getRegionData } from "region-data";
const searchString = ref("");
const treeRef = ref();
watch(searchString, () => {
treeRef.value?.filter(searchString.value);
});
const regionData = getRegionData();
const treeData = regionData.slice(0, 5).map((item) => ({
...item,
children: item.children.slice(0, 3).map((item2) => ({
...item2,
children: item2.children.slice(0, 3)
}))
}));
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>严格的树节点过滤
默认的过滤是宽松的,即匹配到一个节点后便停止后代节点的匹配,其后代节点都会被显示出来。
设置 filter-mode="strict" 进入严格模式,即所有节点都要进行匹配,只有匹配成功的节点才会显示出来。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="严格的树节点过滤">
<view style="padding: 32rpx; background: var(--sar-emphasis-bg)">
<sar-input v-model="searchString" placeholder="请输入过滤关键词" />
</view>
<sar-tree
ref="treeRef"
:data="treeData"
:node-keys="{ title: 'name', key: 'code' }"
filter-mode="strict"
/>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { type TreeExpose } from 'sard-uniapp'
import { ref, watch } from 'vue'
import { getRegionData } from 'region-data'
const searchString = ref('')
const treeRef = ref<TreeExpose>()
watch(searchString, () => {
treeRef.value?.filter(searchString.value)
})
const regionData = getRegionData()
const treeData = regionData.slice(0, 5).map((item) => ({
...item,
children: item.children.slice(0, 3).map((item) => ({
...item,
children: item.children.slice(0, 3),
})),
}))
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="严格的树节点过滤">
<view style="padding: 32rpx; background: var(--sar-emphasis-bg)">
<sar-input v-model="searchString" placeholder="请输入过滤关键词" />
</view>
<sar-tree
ref="treeRef"
:data="treeData"
:node-keys="{ title: 'name', key: 'code' }"
filter-mode="strict"
/>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { ref, watch } from "vue";
import { getRegionData } from "region-data";
const searchString = ref("");
const treeRef = ref();
watch(searchString, () => {
treeRef.value?.filter(searchString.value);
});
const regionData = getRegionData();
const treeData = regionData.slice(0, 5).map((item) => ({
...item,
children: item.children.slice(0, 3).map((item2) => ({
...item2,
children: item2.children.slice(0, 3)
}))
}));
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>懒加载 1.24.7+
设置 lazy 属性标识为懒加载,设置 load 属性用于获取数据,会在点击节点时调用,当获取的数据为空时,会将点击的节点标识为叶子节点,并隐藏箭头按钮。当然,你也可以提前设置节点是否为叶子节点,以避免为叶子节点时渲染箭头按钮。
<template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="基础用法">
<sar-tree
lazy
:load="loadNode"
:node-keys="{ title: 'name', key: 'code', isLeaf: 'leaf' }"
selectable
/>
</doc-page>
</template>
<script setup lang="ts">
import { useCurrentPageLock, usePageTopPopup } from 'sard-uniapp'
import { onBackPress } from '@dcloudio/uni-app'
import { mapProvinces, mapCities, mapCounties } from 'region-data'
import { type TreeStateNode } from 'sard-uniapp'
const doGetTreeData = (parentId?: number) => {
if (!parentId) {
return Object.entries(mapProvinces).map(([code, name]) => ({
code: +code,
name,
isLeaf: false,
}))
} else {
const isProvince = /^\d{2}0{4}/.test(String(parentId))
const isCity = !isProvince && /^\d{4}0{2}/.test(String(parentId))
const prefixCode = isProvince
? String(parentId).slice(0, 2)
: isCity
? String(parentId).slice(0, 4)
: ''
const map = isProvince ? mapCities : isCity ? mapCounties : {}
return Object.entries(map)
.filter(([code]) => String(code).startsWith(prefixCode))
.map(([code, name]) => ({
code: +code,
name,
isLeaf: isCity,
}))
}
}
const getTreeData = (parentId?: number) => {
return new Promise<ReturnType<typeof doGetTreeData>>((resolve) => {
setTimeout(() => {
const treeData = doGetTreeData(parentId)
resolve(treeData)
}, 500)
})
}
const loadNode = async (node?: TreeStateNode) => {
const treeData = await getTreeData(node?.key as number)
return treeData.map((item) => ({
...item,
leaf: node?.depth === 1,
}))
}
const { isLocked } = useCurrentPageLock()
const { shouldStopBack, hidePopup } = usePageTopPopup()
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup()
return true
}
})
</script><template>
<page-meta :page-style="isLocked ? 'overflow: hidden' : ''"></page-meta>
<doc-page title="基础用法">
<sar-tree
lazy
:load="loadNode"
:node-keys="{ title: 'name', key: 'code', isLeaf: 'leaf' }"
selectable
/>
</doc-page>
</template>
<script setup lang="js">
import { useCurrentPageLock, usePageTopPopup } from "sard-uniapp";
import { onBackPress } from "@dcloudio/uni-app";
import { mapProvinces, mapCities, mapCounties } from "region-data";
const doGetTreeData = (parentId) => {
if (!parentId) {
return Object.entries(mapProvinces).map(([code, name]) => ({
code: +code,
name,
isLeaf: false
}));
} else {
const isProvince = /^\d{2}0{4}/.test(String(parentId));
const isCity = !isProvince && /^\d{4}0{2}/.test(String(parentId));
const prefixCode = isProvince ? String(parentId).slice(0, 2) : isCity ? String(parentId).slice(0, 4) : "";
const map = isProvince ? mapCities : isCity ? mapCounties : {};
return Object.entries(map).filter(([code]) => String(code).startsWith(prefixCode)).map(([code, name]) => ({
code: +code,
name,
isLeaf: isCity
}));
}
};
const getTreeData = (parentId) => {
return new Promise((resolve) => {
setTimeout(() => {
const treeData = doGetTreeData(parentId);
resolve(treeData);
}, 500);
});
};
const loadNode = async (node) => {
const treeData = await getTreeData(node?.key);
return treeData.map((item) => ({
...item,
leaf: node?.depth === 1
}));
};
const { isLocked } = useCurrentPageLock();
const { shouldStopBack, hidePopup } = usePageTopPopup();
onBackPress(() => {
if (shouldStopBack.value) {
hidePopup();
return true;
}
});
</script>API
TreeProps
| 属性 | 描述 | 类型 | 默认值 |
|---|---|---|---|
| root-class | 组件根元素类名 | string | - |
| root-style | 组件根元素样式 | StyleValue | - |
| data | 树形数据 | TreeNode[] | - |
| node-keys | 节点对象的键名 | TreeNodeKeys | defaultNodeKeys |
| default-expand-all | 是否默认展开所有节点 | boolean | false |
| default-expanded-keys | 默认展开的节点的 key | (string | number)[] | - |
| accordion | 是否每次只展示一个同级树节点 | boolean | false |
| selectable | 节点是否可被选择(复选) | boolean | false |
| check-strictly | 可选时是否严格遵循父子不关联的做法(复选) | boolean | false |
| default-checked-keys | 默认勾选的节点的 key 的数组(复选) | (string | number)[] | - |
| single-selectable 1.17+ | 节点是否可被选择(单选) | boolean | false |
| leaf-only 1.17+ | 是否只能选择叶子节点(单选) | boolean | false |
| current (v-model) 1.17+ | 当前选择的节点的 key(单选) | string | number | - |
| draggable | 是否可以拖拽节点 | boolean | false |
| editable | 是否可编辑节点(增删改) | boolean | false |
| filter-mode | 节点过滤模式 | 'lenient' | 'strict' | 'lenient' |
| filter-method | 自定义过滤方法 | (value: string, node: TreeStateNode) => boolean | - |
| lazy 1.24.7+ | 是否懒加载子节点,需与 load 方法结合使用 | boolean | false |
| load 1.24.7+ | 加载子树数据的方法,仅当 lazy 属性为true 时生效 | (node?: TreeStateNode) => Promise<TreeNode[]> | TreeNode[] | - |
| auto-height 1.25.9+ | 默认只可展示一行文字,设置此属性可让节点高度跟随内容变化,只可用在非拖拽模式 | boolean | false |
| allow-drag 1.26+ | 判断节点能否被拖拽,返回 false 不允许拖拽 | (node: TreeStateNode) => boolean | - |
| before-drop 1.26+ | 拖拽完放置前回调,返回 false 或 Promise{<rejected>} 可阻止放置 | (draggingNode: TreeStateNode, dropNode: TreeStateNode, type: TreeDropType) => any | Promise\<any> | - |
| before-edit 1.26+ | 节点编辑前回调,返回 false 或 Promise{<rejected>} 可阻止编辑 | (node: TreeStateNode, title: string, type: TreeEditType) => any | Promise \<any> | - |
| edit-options 1.26+ | 自定义编辑菜单选项 | TreeEditOption[] | - |
TreeEmits
| 事件 | 描述 | 类型 |
|---|---|---|
| update:current 1.17+ | 选择节点后触发(单选) | (key: string | number, node: TreeStateNode) => void |
| select 1.17+ | 选择节点后触发(单选) | (key: string | number, node: TreeStateNode) => void |
| check 1.24+ | 点击树节点复选框时触发 | (event: {checked: boolean; node: TreeStateNode}) => void |
| node-click 1.24.1+ | 点击树节点时触发 | (event: {event: any; node: TreeStateNode}) => void |
TreeExpose
| 属性 | 描述 | 类型 |
|---|---|---|
| getCheckedKeys | 获取所有选中节点的 key | () => (string | number)[] |
| getHalfCheckedKeys | 获取所有半选节点的 key | () => (string | number)[] |
| setCheckedKeys | 设置指定节点为选中状态 | (keys: (string | number)[]) => void |
| setChecked | 设置节点是否选中 | (key: string | number, checked: boolean) => void |
| setExpandedKeys | 设置指定节点为展开状态 | (keys: (string | number)[]) => void |
| setExpanded | 设置节点是否展开 | (key: string | number, expanded: boolean) => void |
| toggleExpanded | 切换节点展开状态 | (key: string | number) => void |
| addRootNode | 添加根节点 | () => void |
| getCleanTreeData | 获取干净的当前树形数据 | () => TreeCleanNode[] |
| filter | 过滤树节点 | (searchString: string) => void |
TreeDropType
type TreeDropType = 'before' | 'after' | 'prepend' | 'append'TreeEditType
type TreeEditType = 'addSibling' | 'addChild' | 'addRoot' | 'edit' | 'delete'TreeEditOption
interface TreeEditOption {
type: TreeEditType
icon?: string
text?: string
}TreeNode
interface TreeNode {
title?: string | number
key?: any
children?: TreeNode[]
disabled?: boolean
isLeaf?: boolean
[prop: string]: any
}TreeCleanNode
interface TreeCleanNode {
title: string | number
key: string | number
children?: TreeCleanNode[]
}TreeStateNode
export interface TreeStateNode {
title: string | number
key: string | number
expanded: boolean
checked: boolean
children?: TreeStateNode[]
parent: TreeStateNode | null
indeterminate: boolean
level: number
offsetLevel: number
visible: boolean
disabled: boolean
isLeaf: boolean
loadStatus: 'idle' | 'loading' | 'loaded'
depth: number
}TreeNodeKeys
interface TreeNodeKeys {
title?: string
key?: string
children?: string
isLeaf?: string
}defaultNodeKeys
const defaultNodeKeys = {
title: 'title',
key: 'key',
children: 'children',
isLeaf: 'isLeaf',
}const defaultNodeKeys = {
title: 'title',
key: 'key',
children: 'children',
isLeaf: 'isLeaf',
}主题定制
CSS 变量
page,
.sar-portal {
--sar-tree-border-color: var(--sar-border-color);
--sar-tree-bg: var(--sar-emphasis-bg);
--sar-tree-duration: var(--sar-duration);
--sar-tree-loading-size: var(--sar-text-base);
--sar-tree-loading-color: var(--sar-fourth-color);
--sar-tree-loading-margin-y: 20rpx;
--sar-tree-node-height: 80rpx;
--sar-tree-node-padding-x: 32rpx;
--sar-tree-node-padding-y: 20rpx;
--sar-tree-node-bg: var(--sar-emphasis-bg);
--sar-tree-node-active-bg: var(--sar-active-bg);
--sar-tree-node-dragging-bg: var(--sar-mask-white-legible);
--sar-tree-node-dragging-shadow: var(--sar-shadow-dragging);
--sar-tree-node-loading-margin-right: 16rpx;
--sar-tree-toolbar-margin-right: -24rpx;
--sar-tree-indent-width: 48rpx;
--sar-tree-arrow-padding-x: 24rpx;
--sar-tree-arrow-font-size: 24rpx;
--sar-tree-arrow-color: var(--sar-tertiary-color);
--sar-tree-arrow-duration: var(--sar-duration);
--sar-tree-selection-padding-x: 24rpx;
--sar-tree-edit-padding-x: 24rpx;
--sar-tree-edit-font-size: 36rpx;
--sar-tree-edit-color: var(--sar-fourth-color);
--sar-tree-edit-active-opacity: var(--sar-active-opacity);
--sar-tree-level-btn-padding-x: 24rpx;
--sar-tree-level-btn-font-size: 36rpx;
--sar-tree-level-btn-color: var(--sar-fourth-color);
--sar-tree-level-btn-active-opacity: var(--sar-active-opacity);
--sar-tree-drag-padding-x: 24rpx;
--sar-tree-drag-font-size: 36rpx;
--sar-tree-drag-color: var(--sar-fourth-color);
--sar-tree-drag-active-opacity: var(--sar-active-opacity);
}