Merge pull request #210 from SonyLeo/feature/tree

Feature/tree
This commit is contained in:
Soybean 2023-04-02 12:56:00 +08:00 committed by GitHub
commit 80d58cce2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 906 additions and 0 deletions

View File

@ -32,6 +32,48 @@ const component: AuthRoute.Route = {
requiresAuth: true,
icon: 'mdi:table-large'
}
},
{
name: 'component_tree',
path: '/component/tree',
component: 'multi',
children: [
{
name: 'component_tree_tree-basic',
path: '/component/tree/tree-basic',
component: 'self',
meta: {
title: '基础树',
requiresAuth: true,
icon: 'fluent:tree-deciduous-20-regular'
}
},
{
name: 'component_tree_tree-custom',
path: '/component/tree/tree-custom',
component: 'self',
meta: {
title: '自定义树',
requiresAuth: true,
icon: 'fluent:tree-deciduous-20-filled'
}
},
{
name: 'component_tree_tree-functions',
path: '/component/tree/tree-functions',
component: 'self',
meta: {
title: '函数示例',
requiresAuth: true,
icon: 'fluent:tree-evergreen-20-filled'
}
}
],
meta: {
title: '树',
requiresAuth: true,
icon: 'carbon:tree-view-alt'
}
}
],
meta: {

View File

@ -30,6 +30,10 @@ declare namespace PageRoute {
| 'component_button'
| 'component_card'
| 'component_table'
| 'component_tree'
| 'component_tree_tree-basic'
| 'component_tree_tree-custom'
| 'component_tree_tree-functions'
| 'dashboard'
| 'dashboard_analysis'
| 'dashboard_workbench'
@ -89,6 +93,9 @@ declare namespace PageRoute {
| 'component_button'
| 'component_card'
| 'component_table'
| 'component_tree_tree-basic'
| 'component_tree_tree-custom'
| 'component_tree_tree-functions'
| 'dashboard_analysis'
| 'dashboard_workbench'
| 'document_naive'

View File

@ -0,0 +1,383 @@
<!--
* 基础树组件
* @author: SonnyLeo
* @since: 2023-03-30
* index.vue
-->
<template>
<div>
<n-card title="基础组件" class="h-full shadow-sm rounded-16px">
<n-grid :x-gap="20" :y-gap="20" cols="0 600:2 1000:3">
<n-grid-item>
<n-card title="基础实例 | 默认展开第一层" :segmented="segmented" class="shadow-sm rounded-16px">
<n-tree
ref="caseOneTreeRef"
block-line
:data="data"
checkable
selectable
expand-on-click
:cascade="cascade"
:default-expanded-keys="caseOneDefaultExpandedKeys"
@update-checked-keys="caseOneUpdateCheckedKeys"
/>
<template #footer> 当前选中的节点是: {{ caseOneCheckedKeys }} </template>
</n-card></n-grid-item
>
<n-grid-item>
<n-card title="可勾选 | 默认全部展开" :segmented="segmented" class="shadow-sm rounded-16px">
<n-tree
block-line
:data="data"
checkable
selectable
expand-on-click
default-expand-all
:cascade="cascade"
:checked-keys="caseTwoCheckedKeys"
@update:checked-keys="caseTwoCheckedKeysChange"
/> </n-card
></n-grid-item>
<n-grid-item>
<n-card title="指定项目 | 展开或勾选" class="shadow-sm rounded-16px">
<template #header-extra>
<n-space justify="space-around">
<n-button rounded @click="caseThreeExpandAll">全部展开</n-button>
<n-button rounded type="primary" @click="caseThreeCheckedAll">全部勾选</n-button>
</n-space>
</template>
<n-tree
block-line
:data="data"
checkable
selectable
expand-on-click
:checked-keys="caseThreeCheckedKeys"
:expanded-keys="caseThreeExpandedKeys"
@update:checked-keys="caseThreeCheckedKeysChange"
@update:expanded-keys="caseThreeExpandKeysChange"
/>
<template #footer> 当前选中的节点是: {{ caseThreeCheckedKeys }} </template>
</n-card>
</n-grid-item>
<n-grid-item>
<n-card title="懒加载" class="shadow-sm rounded-16px" :segmented="segmented">
<n-tree
block-line
checkable
draggable
:data="remoteData"
:checked-keys="caseFourCheckedKeys"
:on-load="handleLoad"
:expanded-keys="caseFourExpandedKeys"
check-strategy="all"
:allow-checking-not-loaded="true"
:cascade="cascade"
expand-on-click
@update:checked-keys="caseFourCheckedKeysChange"
@update:expanded-keys="caseFourExpandKeysChange"
/>
</n-card>
</n-grid-item>
<n-grid-item>
<n-card title="异步数据 | 默认全部展开" class="shadow-sm rounded-16px" :segmented="segmented">
<template #header-extra>
<n-button @click="handleInitData">加载数据</n-button>
</template>
<n-spin :show="loading">
<n-tree
block-line
:data="caseFiveData"
checkable
selectable
expand-on-click
:cascade="cascade"
:checked-keys="caseFiveCheckedKeys"
:default-expanded-keys="caseFiveDefaultExpandedKeys"
@update:checked-keys="caseFiveCheckedKeysChange"
/>
<template #description> 正在加载数据中... </template>
</n-spin>
</n-card>
</n-grid-item>
<n-grid-item>
<n-card title="为节点绑定点击事件" class="shadow-sm rounded-16px" :segmented="segmented">
<n-tree
block-line
:data="data"
:default-expanded-keys="caseSixDefaultExpandedKeys"
:node-props="nodeProps"
/>
</n-card>
</n-grid-item>
<n-grid-item>
<n-card title="自定义前缀 | 文件树" class="shadow-sm rounded-16px" :segmented="segmented"
><n-tree
block-line
expand-on-click
:data="caseSevenData"
:node-props="nodeProps"
:default-expanded-keys="caseSevenDefaultExpandedKeys"
:on-update:expanded-keys="updatePrefixWithExpanded"
/>
</n-card>
</n-grid-item>
<n-grid-item>
<n-card title="自定义开关 | 打开和关闭的图标" class="shadow-sm rounded-16px" :segmented="segmented">
<n-tree
block-line
expand-on-click
:data="data"
:default-expanded-keys="caseEightDefaultExpandedKeys"
:render-switcher-icon="renderSwitcherIconWithExpaned"
selectable
/>
</n-card>
</n-grid-item>
</n-grid>
</n-card>
</div>
</template>
<script setup lang="tsx">
import { ref, h } from 'vue';
import type { TreeOption, TreeInst } from 'naive-ui';
import { NIcon } from 'naive-ui';
import { repeat } from 'seemly';
import { useIconRender } from '@/composables';
import { useLoading } from '@/hooks';
const { iconRender } = useIconRender();
const cascade = ref<boolean>(true);
const segmented = ref<boolean>(true);
const caseOneTreeRef = ref<TreeInst | null>(null);
const caseOneCheckedKeys = ref<string[]>([]);
const caseOneDefaultExpandedKeys = ref<string[]>(['20']);
function caseOneUpdateCheckedKeys(checkedKeys: string[]) {
caseOneCheckedKeys.value = checkedKeys;
}
const caseTwoCheckedKeys = ref<string[]>([]);
function caseTwoCheckedKeysChange(checkedKeys: Array<string>) {
caseTwoCheckedKeys.value = checkedKeys;
}
const caseThreeCheckedKeys = ref<string[]>([]);
const caseThreeExpandedKeys = ref<string[]>([]);
function caseThreeCheckedKeysChange(checkedKeys: Array<string>) {
caseThreeCheckedKeys.value = checkedKeys;
}
function caseThreeExpandKeysChange(expandKeys: Array<string>) {
caseThreeExpandedKeys.value = expandKeys;
}
function caseThreeCheckedAll() {
caseThreeCheckedKeys.value = getAllKeys(createData());
}
function caseThreeExpandAll() {
caseThreeExpandedKeys.value = ['20', '21'];
}
const caseFourCheckedKeys = ref<string[]>([]);
const caseFourExpandedKeys = ref<string[]>([]);
function caseFourCheckedKeysChange(checkedKeys: Array<string>) {
caseFourCheckedKeys.value = checkedKeys;
}
function caseFourExpandKeysChange(expandKeys: Array<string>) {
caseFourExpandedKeys.value = expandKeys;
}
function handleLoad(node: TreeOption) {
return new Promise<void>(resolve => {
setTimeout(() => {
node.children = [
{
label: nextLabel(node.label),
key: node.key + nextLabel(node.label),
isLeaf: false
}
];
resolve();
}, 1000);
});
}
const { startLoading, endLoading, loading } = useLoading();
const caseFiveData = ref<TreeOption[]>();
const caseFiveCheckedKeys = ref<string[]>([]);
const caseFiveDefaultExpandedKeys = ref<string[]>(['20', '21']);
function caseFiveCheckedKeysChange(checkedKeys: Array<string>) {
caseFiveCheckedKeys.value = checkedKeys;
}
function handleInitData() {
loadRemoteData().then(res => {
caseFiveData.value = res as any;
});
}
function loadRemoteData() {
startLoading();
return new Promise<void>(resolve => {
const tempData: any = createData();
setTimeout(() => {
endLoading();
resolve(tempData);
}, 1000);
});
}
const caseSixDefaultExpandedKeys = ref<string[]>(['20']);
const updatePrefixWithExpanded = (
_keys: Array<string | number>,
_option: Array<TreeOption | null>,
meta: {
node: TreeOption | null;
action: 'expand' | 'collapse' | 'filter';
}
) => {
if (!meta.node) return;
switch (meta.action) {
case 'expand':
meta.node.prefix = () =>
h(NIcon, null, {
default: () => <icon-ph-folder-open />
});
break;
case 'collapse':
meta.node.prefix = () =>
h(NIcon, null, {
default: () => <icon-ph-folder-fill />
});
break;
default:
break;
}
};
const nodeProps = ({ option }: { option: TreeOption }) => {
return {
onClick() {
if (!option.children && !option.disabled) {
window.$message?.info(`[Click] ${option.label}`);
}
}
};
};
const caseSevenData = [
{
key: '文件夹',
label: '文件夹',
prefix: () =>
h(NIcon, null, {
default: () => h(<icon-ph-folder-fill />)
}),
children: [
{
key: '空的',
label: '空的',
disabled: true,
prefix: () =>
h(NIcon, null, {
default: () => h(<icon-ph-folder-open />)
})
},
{
key: '我的文件',
label: '我的文件',
prefix: () =>
h(NIcon, null, {
default: () => h(<icon-ph-folder-fill />)
}),
children: [
{
label: 'template.txt',
key: 'template.txt',
prefix: () =>
h(NIcon, null, {
default: () => h(<icon-bi-filetype-txt />)
})
}
]
}
]
}
];
const caseSevenDefaultExpandedKeys = ref<string[]>(['文件夹', '我的文件']);
const caseEightDefaultExpandedKeys = ref<string[]>(['20']);
const renderSwitcherIconWithExpaned = ({ expanded }: { expanded: boolean }) =>
h(NIcon, null, {
default: () => h(expanded ? iconRender({ icon: 'solar:moon-broken' }) : iconRender({ icon: 'solar:sun-broken' }))
});
function createRemoteData() {
return [
{
label: nextLabel(),
key: 1,
isLeaf: false
},
{
label: nextLabel(),
key: 2,
isLeaf: false
}
];
}
function nextLabel(currentLabel?: string): string {
if (!currentLabel) return 'Out of Tao, One is born';
if (currentLabel === 'Out of Tao, One is born') return 'Out of One, Two';
if (currentLabel === 'Out of One, Two') return 'Out of Two, Three';
if (currentLabel === 'Out of Two, Three') {
return 'Out of Three, the created universe';
}
if (currentLabel === 'Out of Three, the created universe') {
return 'Out of Tao, One is born';
}
return '';
}
const remoteData = ref(createRemoteData());
function createData(level = 2, baseKey = ''): TreeOption[] | undefined {
if (!level) return undefined;
return repeat(4 - level, undefined).map((_, index) => {
const key = String(baseKey) + level + index;
return {
label: createLabel(level),
key,
children: createData(level - 1, key)
};
});
}
function createLabel(level: number): string {
if (level === 2) return '道生一';
if (level === 1) return '一生二';
return '';
}
function getAllKeys(data: TreeOption[] | undefined): string[] {
const keys: string[] = [];
if (data !== undefined) {
data.forEach(item => {
keys.push(item.key as string);
if (item.children) {
keys.push(...getAllKeys(item.children as TreeOption[]));
}
});
}
return keys;
}
const data = ref(createData());
</script>
<style scoped></style>

View File

@ -0,0 +1,226 @@
<!--
* 可搜索树组件
* @author: SonnyLeo
* @since: 2023-03-30
* index.vue
-->
<template>
<div>
<n-card title="自定义树" class="h-full shadow-sm rounded-16px">
<n-grid :x-gap="20" :y-gap="20" cols="0 600:2 1000:3">
<n-grid-item>
<n-card title="右侧自定义后缀图标" :segmented="segmented" class="shadow-sm rounded-16px">
<n-tree
block-line
:data="caseOneData"
expand-on-click
:selectable="false"
:default-expanded-keys="caseOneDefaultExpandedKeys"
/> </n-card
></n-grid-item>
<n-grid-item>
<n-card title="右键菜单" :segmented="segmented" class="shadow-sm rounded-16px">
<n-tree
block-line
:data="data"
:default-expanded-keys="caseTwoDefaultExpandedKeys"
:node-props="caseTwoNodeProps"
/>
<n-dropdown
trigger="manual"
placement="bottom-start"
:show="showDropdown"
:options="options"
:x="x"
:y="y"
@select="handleSelect"
@clickoutside="handleClickOutside"
/> </n-card
></n-grid-item>
<n-grid-item>
<n-card title="可搜索的树组件" class="shadow-sm rounded-16px">
<n-space vertical :size="12">
<n-input v-model:value="pattern" placeholder="搜索" />
<n-switch v-model:value="showIrrelevantNodes">
<template #checked> 展示搜索无关的节点 </template>
<template #unchecked> 隐藏搜索无关的节点 </template>
</n-switch>
<n-tree :show-irrelevant-nodes="showIrrelevantNodes" :pattern="pattern" :data="data" block-line />
</n-space>
</n-card>
</n-grid-item>
<n-grid-item>
<n-card title="自定义前缀和后缀" class="shadow-sm rounded-16px" :segmented="segmented">
<n-tree
block-line
:data="caseFourData"
:default-expanded-keys="caseFourDefaultExpandedKeys"
:selectable="false"
/>
</n-card>
</n-grid-item>
<n-grid-item>
<n-card title="批量渲染前缀和后缀" class="shadow-sm rounded-16px" :segmented="segmented">
<n-tree
block-line
:data="data"
:default-expanded-keys="caseFiveDefaultExpandedKeys"
:render-prefix="renderPrefix"
:render-label="renderLabel"
:render-suffix="renderSuffix"
:selectable="false"
/>
</n-card>
</n-grid-item>
<n-grid-item>
<n-card title="滚动到指定节点" class="shadow-sm rounded-16px" :segmented="segmented">
<template #header-extra>
<n-button @click="handleCaseSixClick">滚动</n-button>
</template>
<n-tree
ref="caseSixTreeRef"
block-line
:data="data"
default-expand-all
virtual-scroll
style="height: 120px"
/>
</n-card>
</n-grid-item>
</n-grid>
</n-card>
</div>
</template>
<script setup lang="tsx">
import { ref, h } from 'vue';
import type { TreeOption, DropdownOption, TreeInst } from 'naive-ui';
import { NSpace, NButton } from 'naive-ui';
import { repeat } from 'seemly';
import { useIconRender } from '@/composables';
const { iconRender } = useIconRender();
const segmented = ref<boolean>(true);
/** 实例一相关 */
const caseOneData = ref<TreeOption[] | undefined>(createSuffixData());
const caseOneDefaultExpandedKeys = ref<string[]>([]);
/** 实例二相关 */
const caseTwoDefaultExpandedKeys = ref<string[]>(['20']);
const showDropdown = ref<boolean>(false);
const x = ref<number>(0);
const y = ref<number>(0);
const options = ref<DropdownOption[] | undefined>([
{ label: '编辑', key: 'edit', icon: iconRender({ icon: 'mdi:pencil', fontSize: 18 }) },
{ label: '删除', key: 'delete', icon: iconRender({ icon: 'mdi:trash-can-outline', fontSize: 18 }) }
]);
/** 右键单击事件 */
function handleClickOutside() {
showDropdown.value = false;
}
/** 左键单击事件 */
function handleSelect(key: string | number, option: DropdownOption) {
console.log(key, option);
showDropdown.value = false;
}
/** 树节点绑定事件 */
function caseTwoNodeProps() {
return {
onContextmenu(e: MouseEvent): void {
showDropdown.value = true;
x.value = e.clientX;
y.value = e.clientY;
e.preventDefault();
}
};
}
/** 实例三相关 */
const pattern = ref<string>('');
const showIrrelevantNodes = ref(false);
/** 实例四相关 */
const caseFourData = ref<TreeOption[] | undefined>(createPrefixAndSuffixData());
const caseFourDefaultExpandedKeys = ref<string[]>(['20', '21']);
/** 实例五相关 */
const caseFiveDefaultExpandedKeys = ref<string[]>(['20', '21']);
function renderPrefix({ option }: { option: TreeOption }) {
return h(NButton, { text: true, type: 'primary' }, { default: () => `Prefix-${option.level}` });
}
function renderLabel({ option }: { option: TreeOption }) {
return `${option.label} :)`;
}
function renderSuffix({ option }: { option: TreeOption }) {
return h(NButton, { text: true, type: 'primary' }, { default: () => `Suffix-${option.level}` });
}
/** 实例六相关 */
const caseSixTreeRef = ref<TreeInst | null>(null);
function handleCaseSixClick() {
caseSixTreeRef.value?.scrollTo({ key: '21' });
}
/** 模拟数据相关 */
function createPrefixAndSuffixData(level = 2, baseKey = ''): TreeOption[] | undefined {
if (!level) return undefined;
return repeat(4 - level, undefined).map((_, index) => {
const key = String(baseKey) + level + index;
return {
label: createLabel(level),
key,
children: createPrefixAndSuffixData(level - 1, key),
suffix: () => h(NButton, { text: true, type: 'primary' }, { default: () => 'Suffix' }),
prefix: () => h(NButton, { text: true, type: 'primary' }, { default: () => 'Prefix' })
};
});
}
function createSuffixData(level = 2, baseKey = ''): TreeOption[] | undefined {
if (!level) return undefined;
return repeat(4 - level, undefined).map((_, index) => {
const key = String(baseKey) + level + index;
return {
label: createLabel(level),
key,
children: createSuffixData(level - 1, key),
suffix: () =>
h(NSpace, { justify: 'space-evenly' }, () => [
h(NButton, { text: true, class: 'pt-1.5' }, { default: () => <icon-mdi-plus class="text-18px" /> }),
h(
NButton,
{ text: true, class: 'pt-1.5' },
{ default: () => <icon-mdi-trash-can-outline class="text-18px" /> }
)
])
};
});
}
function createData(level = 2, baseKey = ''): TreeOption[] | undefined {
if (!level) return undefined;
return repeat(4 - level, undefined).map((_, index) => {
const key = String(baseKey) + level + index;
return {
label: createLabel(level),
level,
key,
children: createData(level - 1, key)
};
});
}
function createLabel(level: number): string {
if (level === 2) return '道生一';
if (level === 1) return '一生二';
return '';
}
const data = ref(createData());
</script>
<style scoped></style>

View File

@ -0,0 +1,245 @@
<!--
* 树的相关函数
* @author: SonnyLeo
* @since: 2023-03-30
* index.vue
-->
<template>
<n-card class="h-full shadow-sm rounded-16px">
<n-space :vertical="true">
<n-card title="函数示例">
<n-card>
<template #header-extra>
<n-space justify="space-around">
<n-button dashed type="primary" @click="handleGenerateTreeData">重置数据</n-button>
<n-button dashed type="warning" @click="handleNotSelectAll">清空选择</n-button>
</n-space>
</template>
<n-grid :y-gap="20" cols="1 s:3 m:4 l:5 xl:6 2xl:7" responsive="screen">
<n-grid-item><n-button @click="handleExpandAll">展开全部</n-button></n-grid-item>
<n-grid-item><n-button @click="handleCollapseAll">折叠全部</n-button></n-grid-item>
<n-grid-item><n-button @click="handleSelectAll">全选</n-button></n-grid-item>
<n-grid-item><n-button @click="handleNotSelectAll">全不选</n-button></n-grid-item>
<n-grid-item><n-button @click="handleSelectedSecondLevel">显示到第二级</n-button></n-grid-item>
<n-grid-item><n-button @click="handleSelectedThirdLevel">显示到第三级</n-button></n-grid-item>
<n-grid-item><n-button @click="handleSelectData">设置勾选节点</n-button></n-grid-item>
<n-grid-item><n-button @click="handleGetCheckedData">获取选中数据</n-button></n-grid-item>
<n-grid-item><n-button @click="handleGetIndeterminateData">获取半选数据</n-button></n-grid-item>
<n-grid-item><n-button @click="handleAddRootDOM">添加根节点</n-button></n-grid-item>
<n-grid-item><n-button @click="handleAddChildrenDOM">增加子节点</n-button></n-grid-item>
<n-grid-item><n-button @click="handleDeleteTreeNode">删除子节点</n-button></n-grid-item>
<n-grid-item><n-button @click="handleUpdateTreeNode">更新节点</n-button></n-grid-item>
<n-grid-item><n-button @click="handleScrollTo">滚动至某节点</n-button></n-grid-item>
</n-grid>
</n-card>
</n-card>
<n-card title="结构预览" segmented>
<template #header-extra>
<n-space>
<n-switch v-model:value="cascade" size="large">
<template #checked-icon>
<n-icon>
<icon-ic:round-arrow-circle-right color="#1890ff" />
</n-icon>
</template>
<template #unchecked-icon>
<n-icon>
<icon-ic:round-arrow-circle-left />
</n-icon>
</template>
</n-switch>
<n-button text>Cascade</n-button>
</n-space>
</template>
<n-tree
ref="treeRef"
block-line
:data="treeDataRef"
checkable
:selectable="true"
:cascade="cascade"
:checked-keys="checkedKeysRef"
:expanded-keys="expandedKeysRef"
:check-on-click="false"
virtual-scroll
style="height: 320px"
@update-checked-keys="handleUpdateCheckedKeys"
@update-expanded-keys="handleUpdateExpandedKeys"
/>
<template #footer> 当前选中的数据是 {{ checkedKeysRef }} </template>
</n-card>
</n-space>
</n-card>
</template>
<script setup lang="ts">
import { ref, toRaw } from 'vue';
import type { TreeInst } from 'naive-ui';
import { repeat } from 'seemly';
type TreeNode = {
label: string;
level: number;
key: string;
children?: TreeNode[];
};
const cascade = ref<boolean>(true);
const checkedKeysRef = ref<string[]>([]);
const expandedKeysRef = ref<string[]>([]);
const treeRef = ref<TreeInst | null>();
const treeDataRef = ref(createData());
function handleExpandAll() {
expandedKeysRef.value = getKeysByRange(toRaw(treeDataRef.value), 1, 4);
}
function handleCollapseAll() {
expandedKeysRef.value = [];
}
function handleSelectAll() {
checkedKeysRef.value = getKeysByRange(toRaw(treeDataRef.value));
}
function handleNotSelectAll() {
checkedKeysRef.value = [];
}
function handleSelectedSecondLevel() {
expandedKeysRef.value = getKeysByRange(toRaw(treeDataRef.value), 4);
}
function handleSelectedThirdLevel() {
expandedKeysRef.value = getKeysByRange(toRaw(treeDataRef.value), 3, 4);
}
function handleSelectData() {
checkedKeysRef.value = ['40302010', '40302011'];
}
function handleGetCheckedData() {
window.$message?.info('请打开浏览器控制台查看.');
const checkedData = treeRef.value?.getCheckedData();
console.log(checkedData!.keys, checkedData!.options);
}
function handleGetIndeterminateData() {
window.$message?.info('请打开浏览器控制台查看.');
const checkedData = treeRef.value?.getIndeterminateData();
console.log(checkedData!.keys, checkedData!.options);
}
function handleAddRootDOM() {
treeDataRef.value?.push({
label: `根节点`,
key: `${Date.now()}`,
level: 4
});
}
function handleAddChildrenDOM() {
if (treeDataRef.value !== undefined) {
treeDataRef.value[0].children?.push({
label: `子节点`,
key: `${Date.now()}`,
level: 3
});
}
}
function handleDeleteTreeNode() {
if (treeDataRef.value !== undefined) {
const treeDOM = treeDataRef.value[0];
treeDOM.children = treeDOM.children?.filter(item => item.key !== '4031');
}
}
function handleUpdateTreeNode() {
if (treeDataRef.value !== undefined) {
treeDataRef.value[0]?.children?.forEach((item, index) => (item.label = `${item.label}-${index} 已更新`));
}
}
function handleScrollTo() {
treeRef.value?.scrollTo({ key: '40302213' });
}
function handleUpdateCheckedKeys(checkedKeys: Array<string>) {
checkedKeysRef.value = checkedKeys;
}
function handleGenerateTreeData() {
treeDataRef.value = createData();
}
function handleUpdateExpandedKeys(expandedKeys: Array<string>) {
expandedKeysRef.value = expandedKeys;
}
function createData(level = 4, baseKey = ''): TreeNode[] | undefined {
if (!level) return undefined;
return repeat(5 - level, undefined).map((_, index) => {
const key = String(baseKey) + level + index;
return {
label: `${createLabel(level)} - ${index}`,
level,
key,
children: createData(level - 1, key)
};
});
}
function createLabel(level: number): string {
if (level === 4) return '第一层';
if (level === 3) return '第二层';
if (level === 2) return '第三层';
if (level === 1) return '第四层';
return '';
}
/**
* 根据传入的层级 level data 进行筛选并返回节点的key数组
* 包含三种情况
* 1. 传入 data 返回所有节点的 key 数组
* 2. 传入 data 任意 level (startLevel endLevel) , 返回一级目录到该级目录的所有节点的 key 数组
* 3. 传入 data startLevel endLevel 返回指定 level 范围节点对应的 key 数组
*
* 注意: startLevel 必须小于 endLevel
* @param data
* @param startLevel
* @param endLevel
*/
function getKeysByRange(data: TreeNode[] | undefined, startLevel?: number, endLevel?: number): string[] {
const result: string[] = [];
function traverseTree(treeData: TreeNode[]) {
treeData.forEach(node => {
if (startLevel === undefined && endLevel === undefined) {
result.push(node.key);
} else if (startLevel !== undefined && endLevel === undefined && node.level === startLevel) {
result.push(node.key);
} else if (
startLevel !== undefined &&
endLevel !== undefined &&
node.level >= startLevel &&
node.level <= endLevel
) {
result.push(node.key);
}
if (node.children) {
traverseTree(node.children);
}
});
}
if (data) {
traverseTree(data);
}
return result;
}
</script>
<style scoped></style>

View File

@ -16,6 +16,9 @@ export const views: Record<
component_button: () => import('./component/button/index.vue'),
component_card: () => import('./component/card/index.vue'),
component_table: () => import('./component/table/index.vue'),
'component_tree_tree-basic': () => import('./component/tree/tree-basic/index.vue'),
'component_tree_tree-custom': () => import('./component/tree/tree-custom/index.vue'),
'component_tree_tree-functions': () => import('./component/tree/tree-functions/index.vue'),
dashboard_analysis: () => import('./dashboard/analysis/index.vue'),
dashboard_workbench: () => import('./dashboard/workbench/index.vue'),
document_naive: () => import('./document/naive/index.vue'),