|
|
@@ -1,22 +1,27 @@
|
|
|
<script lang="ts" setup>
|
|
|
import type { TreeProps } from 'ant-design-vue';
|
|
|
import type { Key } from 'ant-design-vue/es/_util/type';
|
|
|
+import type { TreeDataItem } from 'ant-design-vue/es/tree';
|
|
|
|
|
|
import type { PropType, StyleValue } from 'vue';
|
|
|
|
|
|
-import type { ApiConfig } from '#/components/form/types';
|
|
|
+import type { ApiConfig, FieldNames } from '#/components/form/types';
|
|
|
|
|
|
import { computed, reactive, ref, unref, watch, watchEffect } from 'vue';
|
|
|
|
|
|
-import { VbenScrollbar } from '@vben/common-ui';
|
|
|
+import { VbenLoading, VbenScrollbar } from '@vben/common-ui';
|
|
|
+import { EmptyIcon } from '@vben/icons';
|
|
|
import { isFunction } from '@vben/utils';
|
|
|
|
|
|
-import { DirectoryTree, Input } from 'ant-design-vue';
|
|
|
+import { DirectoryTree, Dropdown, Input, Menu } from 'ant-design-vue';
|
|
|
|
|
|
import { createApiFunction } from '#/components/form/helper';
|
|
|
import { Icon } from '#/components/icon';
|
|
|
import { filter, get, treeToList } from '#/utils';
|
|
|
|
|
|
+import { ToolbarEnum } from './types';
|
|
|
+import { useTree } from './useTree';
|
|
|
+
|
|
|
defineOptions({
|
|
|
name: 'BcTree',
|
|
|
});
|
|
|
@@ -41,11 +46,7 @@ const props = defineProps({
|
|
|
}),
|
|
|
},
|
|
|
fieldNames: {
|
|
|
- type: Object as PropType<{
|
|
|
- children: string;
|
|
|
- label: string;
|
|
|
- value: string;
|
|
|
- }>,
|
|
|
+ type: Object as PropType<FieldNames>,
|
|
|
default: () => ({ label: 'label', value: 'value', children: 'children' }),
|
|
|
},
|
|
|
expandOnSearch: {
|
|
|
@@ -60,6 +61,10 @@ const props = defineProps({
|
|
|
type: Boolean,
|
|
|
default: true,
|
|
|
},
|
|
|
+ checkable: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false,
|
|
|
+ },
|
|
|
});
|
|
|
|
|
|
const emit = defineEmits(['update:searchValue', 'change']);
|
|
|
@@ -72,17 +77,54 @@ const state = reactive<{
|
|
|
autoExpandParent: true,
|
|
|
});
|
|
|
|
|
|
+const toolbarList = computed(() => {
|
|
|
+ const { checkable } = props;
|
|
|
+ const defaultToolbarList = [
|
|
|
+ { label: '刷新数据', value: ToolbarEnum.REFRESH, divider: checkable },
|
|
|
+ { label: '展开全部', value: ToolbarEnum.EXPAND_ALL },
|
|
|
+ {
|
|
|
+ label: '折叠全部',
|
|
|
+ value: ToolbarEnum.UN_EXPAND_ALL,
|
|
|
+ divider: checkable,
|
|
|
+ },
|
|
|
+ ];
|
|
|
+
|
|
|
+ return checkable
|
|
|
+ ? [
|
|
|
+ { label: '选择全部', value: ToolbarEnum.SELECT_ALL },
|
|
|
+ {
|
|
|
+ label: '取消选择',
|
|
|
+ value: ToolbarEnum.UN_SELECT_ALL,
|
|
|
+ divider: checkable,
|
|
|
+ },
|
|
|
+ ...defaultToolbarList,
|
|
|
+ { label: '层级关联', value: ToolbarEnum.CHECK_STRICTLY },
|
|
|
+ { label: '层级独立', value: ToolbarEnum.CHECK_UN_STRICTLY },
|
|
|
+ ]
|
|
|
+ : defaultToolbarList;
|
|
|
+});
|
|
|
+
|
|
|
const searchListData = ref('');
|
|
|
const searchState = reactive({
|
|
|
startSearch: false,
|
|
|
searchText: '',
|
|
|
- searchData: [] as TreeProps['treeData'],
|
|
|
+ searchData: [] as TreeDataItem[],
|
|
|
});
|
|
|
|
|
|
-const treeDataRef = ref<TreeProps['treeData']>([]);
|
|
|
+const treeDataRef = ref<TreeDataItem[]>([]);
|
|
|
|
|
|
const loading = ref(false);
|
|
|
const isFirstLoad = ref(true);
|
|
|
+
|
|
|
+const getFieldNames = computed((): Required<FieldNames> => {
|
|
|
+ const { fieldNames } = props;
|
|
|
+ return {
|
|
|
+ children: 'children',
|
|
|
+ ...fieldNames,
|
|
|
+ };
|
|
|
+});
|
|
|
+
|
|
|
+const { getAllKeys } = useTree(treeDataRef, getFieldNames);
|
|
|
const handleExpand = (keys: Key[]) => {
|
|
|
state.expandedKeys = keys;
|
|
|
state.autoExpandParent = false;
|
|
|
@@ -169,7 +211,7 @@ const handleSearch = (searchValue: string) => {
|
|
|
|
|
|
const matchedKeys: string[] = [];
|
|
|
searchState.searchData = filter(
|
|
|
- unref(treeDataRef),
|
|
|
+ unref(treeDataRef) as any[],
|
|
|
(node) => {
|
|
|
const result = node[fieldNames.label]?.includes(searchValue) ?? false;
|
|
|
if (result) {
|
|
|
@@ -204,6 +246,47 @@ function handleKeyChange(v: any) {
|
|
|
const getTreeData = computed((): TreeProps['treeData'] =>
|
|
|
searchState.startSearch ? searchState.searchData : unref(treeDataRef),
|
|
|
);
|
|
|
+
|
|
|
+const getNotFound = computed((): boolean => {
|
|
|
+ return !getTreeData.value || getTreeData.value.length === 0;
|
|
|
+});
|
|
|
+
|
|
|
+const expandAll = (expandAll: boolean) => {
|
|
|
+ state.expandedKeys = expandAll ? getAllKeys() : ([] as KeyType[]);
|
|
|
+};
|
|
|
+const handleMenuClick = async (e: any) => {
|
|
|
+ const { key } = e;
|
|
|
+ switch (key) {
|
|
|
+ case ToolbarEnum.CHECK_STRICTLY: {
|
|
|
+ // emit('strictly-change', false);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case ToolbarEnum.CHECK_UN_STRICTLY: {
|
|
|
+ // emit('strictly-change', true);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case ToolbarEnum.EXPAND_ALL: {
|
|
|
+ expandAll(true);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case ToolbarEnum.REFRESH: {
|
|
|
+ await fetch();
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case ToolbarEnum.SELECT_ALL: {
|
|
|
+ // props.checkAll?.(true);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case ToolbarEnum.UN_EXPAND_ALL: {
|
|
|
+ expandAll(false);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case ToolbarEnum.UN_SELECT_ALL: {
|
|
|
+ // props.checkAll?.(false);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
</script>
|
|
|
<template>
|
|
|
<div class="h-full">
|
|
|
@@ -211,8 +294,20 @@ const getTreeData = computed((): TreeProps['treeData'] =>
|
|
|
class="flex h-[45px] items-center justify-between border-b px-2 shadow"
|
|
|
>
|
|
|
<h1 class="text-xl font-bold">{{ title }}</h1>
|
|
|
- <div>
|
|
|
- <Icon icon="uil:ellipsis-v" />
|
|
|
+ <div class="cursor-pointer">
|
|
|
+ <Dropdown @click.prevent>
|
|
|
+ <Icon icon="ion:ellipsis-vertical" />
|
|
|
+ <template #overlay>
|
|
|
+ <Menu @click="handleMenuClick">
|
|
|
+ <template v-for="item in toolbarList" :key="item.value">
|
|
|
+ <Menu.Item v-bind="{ key: item.value }">
|
|
|
+ {{ item.label }}
|
|
|
+ </Menu.Item>
|
|
|
+ <Menu.Divider v-if="item.divider" />
|
|
|
+ </template>
|
|
|
+ </Menu>
|
|
|
+ </template>
|
|
|
+ </Dropdown>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="flex h-[45px] items-center px-2" v-if="search">
|
|
|
@@ -223,6 +318,7 @@ const getTreeData = computed((): TreeProps['treeData'] =>
|
|
|
/>
|
|
|
</div>
|
|
|
<VbenScrollbar :style="contentStyle">
|
|
|
+ <VbenLoading :spinning="loading" />
|
|
|
<DirectoryTree
|
|
|
:expanded-keys="state.expandedKeys"
|
|
|
:auto-expand-parent="state.autoExpandParent"
|
|
|
@@ -259,6 +355,13 @@ const getTreeData = computed((): TreeProps['treeData'] =>
|
|
|
<span v-else>{{ treeDataItem[fieldNames.label] }}</span>
|
|
|
</template>
|
|
|
</DirectoryTree>
|
|
|
+ <div
|
|
|
+ v-if="getNotFound"
|
|
|
+ class="flex-col-center text-muted-foreground min-h-[150px] w-full"
|
|
|
+ >
|
|
|
+ <EmptyIcon class="size-10" />
|
|
|
+ <div class="mt-1 text-sm">暂无数据</div>
|
|
|
+ </div>
|
|
|
</VbenScrollbar>
|
|
|
</div>
|
|
|
</template>
|