DESKTOP-USV654P\pc пре 11 месеци
родитељ
комит
ee22f1c37a

+ 227 - 1
apps/web-baicai/src/components/tree/src/tree.vue

@@ -1,3 +1,229 @@
+<script lang="ts" setup>
+import type { TreeProps } from 'ant-design-vue';
+
+import type { PropType } from 'vue';
+
+import type { ApiConfig } from '#/components/form/types';
+
+import { computed, reactive, ref, unref, watch, watchEffect } from 'vue';
+
+import { isFunction } from '@vben/utils';
+
+import { Input, Tree } from 'ant-design-vue';
+
+import { createApiFunction } from '#/components/form/helper';
+import { Icon } from '#/components/icon';
+import { filter, get, treeToList } from '#/utils';
+
+const props = defineProps({
+  multiple: {
+    type: Boolean as PropType<boolean>,
+    default: true,
+  },
+  title: {
+    type: String as PropType<string>,
+    default: '',
+  },
+  api: {
+    type: Object as PropType<ApiConfig>,
+    default: () => ({
+      type: 'none',
+      method: 'get',
+      params: {},
+      result: '',
+      url: null,
+    }),
+  },
+  fieldNames: {
+    type: Object as PropType<{ children: any; label: string; value: string }>,
+    default: () => ({ label: 'label', value: 'value', children: null }),
+  },
+  expandOnSearch: {
+    type: Boolean,
+    default: true,
+  },
+  immediate: {
+    type: Boolean,
+    default: true,
+  },
+});
+
+const emit = defineEmits(['update:searchValue']);
+
+const state = reactive<{
+  autoExpandParent: boolean;
+  expandedKeys: (number | string)[];
+}>({
+  expandedKeys: [],
+  autoExpandParent: true,
+});
+
+const searchListData = ref('');
+const searchState = reactive({
+  startSearch: false,
+  searchText: '',
+  searchData: [] as TreeProps['treeData'],
+});
+
+const treeDataRef = ref<TreeProps['treeData']>([]);
+
+const loading = ref(false);
+const isFirstLoad = ref(true);
+const handleExpand = (keys: string[]) => {
+  state.expandedKeys = keys;
+  state.autoExpandParent = false;
+};
+
+// const getParentKey = (
+//   key: number | string,
+//   tree: TreeProps['treeData'],
+// ): number | string | undefined => {
+//   let parentKey;
+//   tree &&
+//     tree.forEach((node) => {
+//       if (node.children) {
+//         if (node.children.some((item) => item.key === key)) {
+//           parentKey = node.key;
+//         } else if (getParentKey(key, node.children)) {
+//           parentKey = getParentKey(key, node.children);
+//         }
+//       }
+//     });
+//   return parentKey;
+// };
+
+const fetch = async () => {
+  const api = createApiFunction({ ...props.api });
+  if (!api || !isFunction(api)) return;
+  try {
+    loading.value = true;
+    const res = await api(props.api.params);
+    if (Array.isArray(res)) {
+      treeDataRef.value = res;
+    } else {
+      treeDataRef.value = props.api.result ? get(res, props.api.result) : [];
+    }
+  } catch (error) {
+    console.warn(error);
+  } finally {
+    loading.value = false;
+  }
+};
+
+watchEffect(() => {
+  props.immediate && fetch();
+});
+
+watch(
+  () => props.api.params,
+  () => {
+    !unref(isFirstLoad) && fetch();
+  },
+  { deep: true },
+);
+
+const setExpandedKeys = (keys: (number | string)[]) => {
+  state.expandedKeys = keys;
+};
+
+// const getExpandedKeys = () => {
+//   return state.expandedKeys;
+// };
+
+const handleSearch = (searchValue: string) => {
+  if (searchValue !== searchState.searchText)
+    searchState.searchText = searchValue;
+  emit('update:searchValue', searchValue);
+  if (!searchValue) {
+    searchState.startSearch = false;
+    return;
+  }
+
+  searchState.startSearch = true;
+  const { fieldNames, expandOnSearch } = props;
+
+  const matchedKeys: string[] = [];
+  searchState.searchData = filter(
+    unref(treeDataRef),
+    (node) => {
+      const result = node[fieldNames.label]?.includes(searchValue) ?? false;
+      if (result) {
+        matchedKeys.push(node[fieldNames.value]);
+      }
+      return result;
+    },
+    fieldNames,
+  );
+
+  if (expandOnSearch) {
+    const expandKeys = treeToList(searchState.searchData).map((val: any) => {
+      return val[fieldNames.value];
+    });
+    if (expandKeys && expandKeys.length > 0) {
+      setExpandedKeys(expandKeys);
+    }
+  }
+
+  // if (checkOnSearch && checkable && matchedKeys.length > 0) {
+  //   setCheckedKeys(matchedKeys);
+  // }
+
+  // if (selectedOnSearch && matchedKeys.length > 0) {
+  //   setSelectedKeys(matchedKeys);
+  // }
+};
+function handleKeyChange(v: any) {
+  handleSearch(v.target.value);
+}
+
+const getTreeData = computed((): TreeProps['treeData'] =>
+  searchState.startSearch ? searchState.searchData : unref(treeDataRef),
+);
+</script>
 <template>
-  <div>TRee</div>
+  <div class="h-full">
+    <div class="flex h-[30px] items-center justify-between">
+      <div>
+        {{ title }}
+      </div>
+      <div>
+        <Icon icon="uil:ellipsis-v" />
+      </div>
+    </div>
+    <div style="height: calc(100% - 30px)" class="overflow-y-auto">
+      <div class="h-[35px]">
+        <Input
+          style="height: 35px; padding: 3px 2px 3px 10px"
+          v-model:value="searchListData"
+          @change="handleKeyChange"
+          placeholder="输入关键字搜索"
+        />
+      </div>
+      <Tree
+        :expanded-keys="state.expandedKeys"
+        :auto-expand-parent="state.autoExpandParent"
+        :tree-data="getTreeData"
+        :field-names="{
+          title: fieldNames.label,
+          key: fieldNames.value,
+          children: fieldNames.children,
+        }"
+        style="height: calc(100% - 35px)"
+        @expand="handleExpand"
+      >
+        <template #title="{ title }">
+          <span v-if="title.indexOf(searchListData) > -1">
+            {{ title.substring(0, title.indexOf(searchListData)) }}
+            <span style="color: #f50">{{ searchListData }}</span>
+            {{
+              title.substring(
+                title.indexOf(searchListData) + searchListData.length,
+              )
+            }}
+          </span>
+          <span v-else>{{ title }}</span>
+        </template>
+      </Tree>
+    </div>
+  </div>
 </template>

+ 1 - 0
apps/web-baicai/src/utils/index.ts

@@ -1,2 +1,3 @@
 export * from './cryptogram';
+export * from './tree';
 export * from './utils';

+ 45 - 0
apps/web-baicai/src/utils/tree.ts

@@ -0,0 +1,45 @@
+interface TreeHelperConfig {
+  id: string;
+  children: string;
+  pid: string;
+}
+const DEFAULT_CONFIG: TreeHelperConfig = {
+  id: 'id',
+  children: 'children',
+  pid: 'pid',
+};
+
+const getConfig = (config: Partial<TreeHelperConfig>) =>
+  Object.assign({}, DEFAULT_CONFIG, config);
+
+export function filter<T = any>(
+  tree: T[],
+  func: (n: T) => boolean,
+  config: Partial<TreeHelperConfig> = {},
+): T[] {
+  config = getConfig(config);
+  const children = config.children as string;
+  function listFilter(list: T[]) {
+    return list
+      .map((node: any) => ({ ...node }))
+      .filter((node) => {
+        node[children] = node[children] && listFilter(node[children]);
+        return func(node) || (node[children] && node[children].length > 0);
+      });
+  }
+  return listFilter(tree);
+}
+
+export function treeToList<T = any>(
+  tree: any,
+  config: Partial<TreeHelperConfig> = {},
+): T {
+  config = getConfig(config);
+  const { children = 'children' } = config;
+  const result: any = [...tree];
+  for (let i = 0; i < result.length; i++) {
+    if (!result[i][children]) continue;
+    result.splice(i + 1, 0, ...result[i][children]);
+  }
+  return result;
+}