diff --git a/apps/web-antd/src/utils/tree.ts b/apps/web-antd/src/utils/tree.ts index accb1daee..3dda8965e 100644 --- a/apps/web-antd/src/utils/tree.ts +++ b/apps/web-antd/src/utils/tree.ts @@ -1,4 +1,5 @@ // todo @芋艿:公用逻辑 +// 已迁移,后续使用 packages/core/base/shared/src/utils/tree.ts 下的方法 interface TreeNode { [key: string]: any; children?: TreeNode[]; diff --git a/apps/web-antd/src/views/infra/codegen/data.ts b/apps/web-antd/src/views/infra/codegen/data.ts index 9dbb81aa6..63625a878 100644 --- a/apps/web-antd/src/views/infra/codegen/data.ts +++ b/apps/web-antd/src/views/infra/codegen/data.ts @@ -10,12 +10,11 @@ import { h } from 'vue'; import { useAccess } from '@vben/access'; import { IconifyIcon } from '@vben/icons'; import { $t } from '@vben/locales'; -import { getRangePickerDefaultProps } from '@vben/utils'; +import { getRangePickerDefaultProps, handleTree } from '@vben/utils'; import { getDataSourceConfigList } from '#/api/infra/data-source-config'; import { getMenuList } from '#/api/system/menu'; import { DICT_TYPE, getDictOptions } from '#/utils/dict'; -import { handleTree } from '#/utils/tree'; const { hasAccessByCodes } = useAccess(); diff --git a/apps/web-antd/src/views/infra/demo/demo02/data.ts b/apps/web-antd/src/views/infra/demo/demo02/data.ts index eb6a1b603..73aaec1a7 100644 --- a/apps/web-antd/src/views/infra/demo/demo02/data.ts +++ b/apps/web-antd/src/views/infra/demo/demo02/data.ts @@ -5,10 +5,9 @@ import type { OnActionClickFn } from '#/adapter/vxe-table'; import type { Demo02CategoryApi } from '#/api/infra/demo/demo02'; import { useAccess } from '@vben/access'; -import { getRangePickerDefaultProps } from '@vben/utils'; +import { getRangePickerDefaultProps, handleTree } from '@vben/utils'; import { getDemo02CategoryList } from '#/api/infra/demo/demo02'; -import { handleTree } from '#/utils/tree'; const { hasAccessByCodes } = useAccess(); diff --git a/apps/web-antd/src/views/system/dept/data.ts b/apps/web-antd/src/views/system/dept/data.ts index 0225228cc..54692317b 100644 --- a/apps/web-antd/src/views/system/dept/data.ts +++ b/apps/web-antd/src/views/system/dept/data.ts @@ -5,13 +5,13 @@ import type { OnActionClickFn } from '#/adapter/vxe-table'; import type { SystemDeptApi } from '#/api/system/dept'; import { useAccess } from '@vben/access'; +import { handleTree } from '@vben/utils'; import { z } from '#/adapter/form'; import { getDeptList } from '#/api/system/dept'; import { getSimpleUserList } from '#/api/system/user'; import { CommonStatusEnum } from '#/utils/constants'; import { DICT_TYPE, getDictOptions } from '#/utils/dict'; -import { handleTree } from '#/utils/tree'; const { hasAccessByCodes } = useAccess(); diff --git a/apps/web-antd/src/views/system/menu/data.ts b/apps/web-antd/src/views/system/menu/data.ts index 0115723e4..bf56bca10 100644 --- a/apps/web-antd/src/views/system/menu/data.ts +++ b/apps/web-antd/src/views/system/menu/data.ts @@ -8,7 +8,7 @@ import { h } from 'vue'; import { useAccess } from '@vben/access'; import { IconifyIcon } from '@vben/icons'; -import { isHttpUrl } from '@vben/utils'; +import { handleTree, isHttpUrl } from '@vben/utils'; import { z } from '#/adapter/form'; import { getMenuList } from '#/api/system/menu'; @@ -16,7 +16,6 @@ import { $t } from '#/locales'; import { componentKeys } from '#/router/routes'; import { CommonStatusEnum, SystemMenuTypeEnum } from '#/utils/constants'; import { DICT_TYPE, getDictOptions } from '#/utils/dict'; -import { handleTree } from '#/utils/tree'; const { hasAccessByCodes } = useAccess(); diff --git a/apps/web-antd/src/views/system/role/modules/assign-data-permission-form.vue b/apps/web-antd/src/views/system/role/modules/assign-data-permission-form.vue index 861ef1d46..ec72f53f0 100644 --- a/apps/web-antd/src/views/system/role/modules/assign-data-permission-form.vue +++ b/apps/web-antd/src/views/system/role/modules/assign-data-permission-form.vue @@ -5,6 +5,7 @@ import type { SystemRoleApi } from '#/api/system/role'; import { ref } from 'vue'; import { useVbenModal, VbenTree } from '@vben/common-ui'; +import { handleTree } from '@vben/utils'; import { Checkbox, message, Spin } from 'ant-design-vue'; @@ -14,7 +15,6 @@ import { assignRoleDataScope } from '#/api/system/permission'; import { getRole } from '#/api/system/role'; import { $t } from '#/locales'; import { SystemDataScopeEnum } from '#/utils/constants'; -import { handleTree } from '#/utils/tree'; import { useAssignDataPermissionFormSchema } from '../data'; diff --git a/apps/web-antd/src/views/system/role/modules/assign-menu-form.vue b/apps/web-antd/src/views/system/role/modules/assign-menu-form.vue index 926e9d196..b49a4b10f 100644 --- a/apps/web-antd/src/views/system/role/modules/assign-menu-form.vue +++ b/apps/web-antd/src/views/system/role/modules/assign-menu-form.vue @@ -5,6 +5,7 @@ import type { SystemRoleApi } from '#/api/system/role'; import { ref } from 'vue'; import { useVbenModal, VbenTree } from '@vben/common-ui'; +import { handleTree } from '@vben/utils'; import { Checkbox, message, Spin } from 'ant-design-vue'; @@ -12,7 +13,6 @@ import { useVbenForm } from '#/adapter/form'; import { getMenuList } from '#/api/system/menu'; import { assignRoleMenu, getRoleMenuList } from '#/api/system/permission'; import { $t } from '#/locales'; -import { handleTree } from '#/utils/tree'; import { useAssignMenuFormSchema } from '../data'; diff --git a/apps/web-antd/src/views/system/tenantPackage/modules/form.vue b/apps/web-antd/src/views/system/tenantPackage/modules/form.vue index 3c7760955..7d2baf987 100644 --- a/apps/web-antd/src/views/system/tenantPackage/modules/form.vue +++ b/apps/web-antd/src/views/system/tenantPackage/modules/form.vue @@ -5,6 +5,7 @@ import type { SystemTenantPackageApi } from '#/api/system/tenant-package'; import { computed, ref } from 'vue'; import { useVbenModal, VbenTree } from '@vben/common-ui'; +import { handleTree } from '@vben/utils'; import { Checkbox, message } from 'ant-design-vue'; @@ -16,7 +17,6 @@ import { updateTenantPackage, } from '#/api/system/tenant-package'; import { $t } from '#/locales'; -import { handleTree } from '#/utils/tree'; import { useFormSchema } from '../data'; diff --git a/apps/web-antd/src/views/system/user/data.ts b/apps/web-antd/src/views/system/user/data.ts index 835f70bb9..37b48530b 100644 --- a/apps/web-antd/src/views/system/user/data.ts +++ b/apps/web-antd/src/views/system/user/data.ts @@ -3,7 +3,7 @@ import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; import type { SystemUserApi } from '#/api/system/user'; import { useAccess } from '@vben/access'; -import { getRangePickerDefaultProps } from '@vben/utils'; +import { getRangePickerDefaultProps, handleTree } from '@vben/utils'; import { z } from '#/adapter/form'; import { getDeptList } from '#/api/system/dept'; @@ -11,7 +11,6 @@ import { getSimplePostList } from '#/api/system/post'; import { getSimpleRoleList } from '#/api/system/role'; import { CommonStatusEnum } from '#/utils/constants'; import { DICT_TYPE, getDictOptions } from '#/utils/dict'; -import { handleTree } from '#/utils/tree'; const { hasAccessByCodes } = useAccess(); diff --git a/apps/web-antd/src/views/system/user/modules/dept-tree.vue b/apps/web-antd/src/views/system/user/modules/dept-tree.vue index 3184e5e41..8e6641777 100644 --- a/apps/web-antd/src/views/system/user/modules/dept-tree.vue +++ b/apps/web-antd/src/views/system/user/modules/dept-tree.vue @@ -4,11 +4,11 @@ import type { SystemDeptApi } from '#/api/system/dept'; import { onMounted, ref } from 'vue'; import { Search } from '@vben/icons'; +import { handleTree } from '@vben/utils'; import { Input, Spin, Tree } from 'ant-design-vue'; import { getSimpleDeptList } from '#/api/system/dept'; -import { handleTree } from '#/utils/tree'; const emit = defineEmits(['select']); const deptList = ref([]); // 部门列表 diff --git a/packages/@core/base/shared/src/utils/tree.ts b/packages/@core/base/shared/src/utils/tree.ts index 09a9481cb..ca2a6765b 100644 --- a/packages/@core/base/shared/src/utils/tree.ts +++ b/packages/@core/base/shared/src/utils/tree.ts @@ -3,6 +3,11 @@ interface TreeConfigOptions { childProps: string; } +interface TreeNode { + [key: string]: any; + children?: TreeNode[]; +} + /** * @zh_CN 遍历树形结构,并返回所有节点中指定的值。 * @param tree 树形结构数组 @@ -94,4 +99,69 @@ function mapTree>( }); } -export { filterTree, mapTree, traverseTreeValues }; +/** + * 构造树型结构数据 + * + * @param {*} data 数据源 + * @param {*} id id字段 默认 'id' + * @param {*} parentId 父节点字段 默认 'parentId' + * @param {*} children 孩子节点字段 默认 'children' + */ +function handleTree( + data: TreeNode[], + id: string = 'id', + parentId: string = 'parentId', + children: string = 'children', +): TreeNode[] { + if (!Array.isArray(data)) { + console.warn('data must be an array'); + return []; + } + const config = { + id, + parentId, + childrenList: children, + }; + const childrenListMap: Record = {}; + const nodeIds: Record = {}; + const tree: TreeNode[] = []; + + // 1. 数据预处理 + // 1.1 第一次遍历,生成 childrenListMap 和 nodeIds 映射 + for (const d of data) { + const pId = d[config.parentId]; + if (childrenListMap[pId] === undefined) { + childrenListMap[pId] = []; + } + nodeIds[d[config.id]] = d; + childrenListMap[pId].push(d); + } + // 1.2 第二次遍历,找出根节点 + for (const d of data) { + const pId = d[config.parentId]; + if (nodeIds[pId] === undefined) { + tree.push(d); + } + } + + // 2. 构建树结:递归构建子节点 + const adaptToChildrenList = (node: TreeNode): void => { + const nodeId = node[config.id]; + if (childrenListMap[nodeId]) { + node[config.childrenList] = childrenListMap[nodeId]; + // 递归处理子节点 + for (const child of node[config.childrenList]) { + adaptToChildrenList(child); + } + } + }; + + // 3. 从根节点开始构建完整树 + for (const rootNode of tree) { + adaptToChildrenList(rootNode); + } + + return tree; +} + +export { filterTree, handleTree, mapTree, traverseTreeValues };