mirror of
				https://github.com/soybeanjs/soybean-admin.git
				synced 2025-11-04 15:53:43 +08:00 
			
		
		
		
	@@ -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'),
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user