Browse Source

feat: 修改控件

DESKTOP-USV654P\pc 9 months ago
parent
commit
58a9fc7e60

+ 116 - 13
apps/web-baicai/src/components/bc-tree/src/bc-tree.vue

@@ -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>

+ 9 - 0
apps/web-baicai/src/components/bc-tree/src/types.ts

@@ -0,0 +1,9 @@
+export enum ToolbarEnum {
+  SELECT_ALL,
+  UN_SELECT_ALL,
+  EXPAND_ALL,
+  UN_EXPAND_ALL,
+  CHECK_STRICTLY,
+  CHECK_UN_STRICTLY,
+  REFRESH,
+}

+ 32 - 0
apps/web-baicai/src/components/bc-tree/src/useTree.ts

@@ -0,0 +1,32 @@
+import type { TreeDataItem } from 'ant-design-vue/es/tree';
+
+import type { ComputedRef, Ref } from 'vue';
+
+import type { FieldNames } from '#/components/form/types';
+
+import { unref } from 'vue';
+
+export function useTree(
+  treeDataRef: Ref<TreeDataItem[]>,
+  getFieldNames: ComputedRef<FieldNames>,
+) {
+  function getAllKeys(list?: TreeDataItem[]) {
+    const keys: string[] = [];
+    const treeData = list || unref(treeDataRef);
+    const { value: keyField, children: childrenField } = unref(getFieldNames);
+    if (!childrenField || !keyField) return keys;
+
+    for (const node of treeData) {
+      keys.push(node[keyField]);
+      const children = node[childrenField];
+      if (children && children.length > 0) {
+        keys.push(...(getAllKeys(children) as string[]));
+      }
+    }
+    return keys as KeyType[];
+  }
+
+  return {
+    getAllKeys,
+  };
+}

+ 6 - 0
apps/web-baicai/src/components/form/types/index.d.ts

@@ -11,3 +11,9 @@ export type ApiConfig = {
   type?: 'api' | 'dict' | 'enum' | 'none';
   url: PropType<(arg?: any) => Promise<BasicOptionResult[]> | string>;
 };
+
+export interface FieldNames {
+  children?: string;
+  label: string;
+  value: string;
+}

+ 1 - 1
apps/web-baicai/src/views/system/design/database/data.config.ts

@@ -78,7 +78,7 @@ export function useColumns(
 export const useSchema = (): VbenFormSchema[] => {
   return [
     {
-      component: 'ApiSelect',
+      component: 'BcSelect',
       componentProps: {
         placeholder: '请输入',
         api: {

+ 1 - 1
apps/web-baicai/src/views/system/design/query/components/step/baseConfig.vue

@@ -26,7 +26,7 @@ const [Form, { validate, setValues, getValues }] = useVbenForm(
         rules: 'required',
       },
       {
-        component: 'ApiSelect',
+        component: 'BcSelect',
         componentProps: {
           placeholder: '请输入',
           api: {

+ 1 - 1
apps/web-baicai/src/views/system/design/table/components/step/baseConfig.vue

@@ -13,7 +13,7 @@ const [Form, { validate, setValues, getValues }] = useVbenForm({
   },
   schema: [
     {
-      component: 'ApiSelect',
+      component: 'BcSelect',
       componentProps: {
         placeholder: '请输入页面类型',
         api: {

+ 1 - 1
apps/web-baicai/src/views/system/user/index.vue

@@ -158,7 +158,7 @@ const handleDepartmentChange = (keys: any[]) => {
       <BcTree
         title="组织架构"
         :api="{ url: DepartmentApi.getTree, type: 'api' }"
-        :field-names="{ label: 'name', value: 'id', children: 'children' }"
+        :field-names="{ label: 'name', value: 'id' }"
         @change="handleDepartmentChange"
       />
     </template>