commit
80d58cce2b
@ -32,6 +32,48 @@ const component: AuthRoute.Route = {
|
|||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
icon: 'mdi:table-large'
|
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: {
|
meta: {
|
||||||
|
7
src/typings/page-route.d.ts
vendored
7
src/typings/page-route.d.ts
vendored
@ -30,6 +30,10 @@ declare namespace PageRoute {
|
|||||||
| 'component_button'
|
| 'component_button'
|
||||||
| 'component_card'
|
| 'component_card'
|
||||||
| 'component_table'
|
| 'component_table'
|
||||||
|
| 'component_tree'
|
||||||
|
| 'component_tree_tree-basic'
|
||||||
|
| 'component_tree_tree-custom'
|
||||||
|
| 'component_tree_tree-functions'
|
||||||
| 'dashboard'
|
| 'dashboard'
|
||||||
| 'dashboard_analysis'
|
| 'dashboard_analysis'
|
||||||
| 'dashboard_workbench'
|
| 'dashboard_workbench'
|
||||||
@ -89,6 +93,9 @@ declare namespace PageRoute {
|
|||||||
| 'component_button'
|
| 'component_button'
|
||||||
| 'component_card'
|
| 'component_card'
|
||||||
| 'component_table'
|
| 'component_table'
|
||||||
|
| 'component_tree_tree-basic'
|
||||||
|
| 'component_tree_tree-custom'
|
||||||
|
| 'component_tree_tree-functions'
|
||||||
| 'dashboard_analysis'
|
| 'dashboard_analysis'
|
||||||
| 'dashboard_workbench'
|
| 'dashboard_workbench'
|
||||||
| 'document_naive'
|
| 'document_naive'
|
||||||
|
383
src/views/component/tree/tree-basic/index.vue
Normal file
383
src/views/component/tree/tree-basic/index.vue
Normal 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>
|
226
src/views/component/tree/tree-custom/index.vue
Normal file
226
src/views/component/tree/tree-custom/index.vue
Normal 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>
|
245
src/views/component/tree/tree-functions/index.vue
Normal file
245
src/views/component/tree/tree-functions/index.vue
Normal 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>
|
@ -16,6 +16,9 @@ export const views: Record<
|
|||||||
component_button: () => import('./component/button/index.vue'),
|
component_button: () => import('./component/button/index.vue'),
|
||||||
component_card: () => import('./component/card/index.vue'),
|
component_card: () => import('./component/card/index.vue'),
|
||||||
component_table: () => import('./component/table/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_analysis: () => import('./dashboard/analysis/index.vue'),
|
||||||
dashboard_workbench: () => import('./dashboard/workbench/index.vue'),
|
dashboard_workbench: () => import('./dashboard/workbench/index.vue'),
|
||||||
document_naive: () => import('./document/naive/index.vue'),
|
document_naive: () => import('./document/naive/index.vue'),
|
||||||
|
Loading…
Reference in New Issue
Block a user