跳到主要内容

findAncestors

查询树形结构实体的所有祖先节点。

方法签名

findAncestors(options: FindTreeOptions): Promise<EntityType[]>

参数说明

FindTreeOptions

interface FindTreeOptions<T extends EntityType = any> {
/**
* 查询的实体 ID
*/
entityId?: string;

/**
* 查询条件
*/
where?: RuleGroup<InstanceType<T>>;

/**
* 查询级别(深度)
* @default 0 表示查询所有层级
*/
level?: number;
}
参数类型必填默认值说明
entityIdstring-当前节点的 ID
whereRuleGroup-额外的过滤条件
levelnumber0查询深度,0 表示查询所有层级,1 表示只查询直接父节点

基础用法

查询所有祖先节点

import { Menu } from './entities/Menu';

// 查询指定菜单项的所有祖先节点(从父节点到根节点)
const ancestors = await Menu.findAncestors({
entityId: 'menu-child-id'
});

console.log('所有祖先节点:', ancestors);

限制查询深度

// 只查询直接父节点
const parent = await Menu.findAncestors({
entityId: 'menu-child-id',
level: 1
});

// 查询两层祖先节点
const twoLevels = await Menu.findAncestors({
entityId: 'menu-child-id',
level: 2
});

使用过滤条件

// 查询所有启用状态的祖先节点
const activeAncestors = await Menu.findAncestors({
entityId: 'menu-child-id',
where: {
combinator: 'and',
rules: [
{ field: 'enabled', operator: '=', value: true }
]
}
});

// 查询特定类型的祖先节点
const folderAncestors = await Menu.findAncestors({
entityId: 'menu-child-id',
where: {
combinator: 'and',
rules: [
{ field: 'type', operator: '=', value: 'folder' }
]
}
});

响应式查询

使用 RxJS 订阅祖先节点的实时变化:

import { Subscription } from 'rxjs';

// 订阅祖先节点变化
const subscription: Subscription = Menu.findAncestors({
entityId: 'menu-child-id'
}).subscribe({
next: (ancestors) => {
console.log('祖先节点更新:', ancestors);
},
error: (err) => {
console.error('查询错误:', err);
}
});

// 取消订阅
subscription.unsubscribe();

实际应用场景

1. 面包屑导航

@Entity({
type: 'tree',
name: 'Menu',
properties: [
{ name: 'name', type: PropertyType.string, required: true },
{ name: 'path', type: PropertyType.string }
]
})
export class Menu extends TreeEntityBase {}

// 生成面包屑导航
async function generateBreadcrumb(currentMenuId: string) {
const ancestors = await Menu.findAncestors({
entityId: currentMenuId
});

// 祖先节点从根到父,需要反转顺序
const breadcrumb = [...ancestors.reverse(), await Menu.get(currentMenuId)];

return breadcrumb.map(menu => ({
name: menu.name,
path: menu.path
}));
}

// 使用示例
const breadcrumb = await generateBreadcrumb('menu-leaf-id');
// 结果: [
// { name: '首页', path: '/' },
// { name: '产品', path: '/products' },
// { name: '详情', path: '/products/detail' }
// ]

2. 权限继承检查

@Entity({
type: 'tree',
name: 'Department',
properties: [
{ name: 'name', type: PropertyType.string, required: true },
{ name: 'hasSpecialPermission', type: PropertyType.boolean, default: false }
]
})
export class Department extends TreeEntityBase {}

// 检查当前部门或其祖先部门是否有特殊权限
async function hasInheritedPermission(deptId: string): Promise<boolean> {
const ancestors = await Department.findAncestors({
entityId: deptId,
where: {
combinator: 'and',
rules: [
{ field: 'hasSpecialPermission', operator: '=', value: true }
]
}
});

return ancestors.length > 0;
}

3. 文件路径生成

@Entity({
type: 'tree',
name: 'Folder',
properties: [
{ name: 'name', type: PropertyType.string, required: true }
]
})
export class Folder extends TreeEntityBase {}

// 生成完整文件路径
async function getFullPath(folderId: string): Promise<string> {
const ancestors = await Folder.findAncestors({
entityId: folderId
});

const folder = await Folder.get(folderId);

// 从根到当前节点
const pathParts = [
...ancestors.reverse().map(f => f.name),
folder.name
];

return '/' + pathParts.join('/');
}

// 使用示例
const path = await getFullPath('folder-id');
// 结果: '/root/documents/projects/2024'

4. 层级展开状态管理

// 获取需要展开的所有父节点 ID
async function getExpandedNodeIds(currentNodeId: string): Promise<string[]> {
const ancestors = await Menu.findAncestors({
entityId: currentNodeId
});

return ancestors.map(node => node.id);
}

// 在树形组件中使用
async function expandToNode(nodeId: string) {
const ancestorIds = await getExpandedNodeIds(nodeId);

// 设置所有祖先节点为展开状态
ancestorIds.forEach(id => {
expandNode(id);
});

// 选中当前节点
selectNode(nodeId);
}

5. 组织架构层级

@Entity({
type: 'tree',
name: 'Department',
properties: [
{ name: 'name', type: PropertyType.string, required: true },
{ name: 'level', type: PropertyType.string } // 如: 'L1', 'L2', 'L3'
]
})
export class Department extends TreeEntityBase {}

// 获取部门的完整层级信息
async function getDepartmentHierarchy(deptId: string) {
const ancestors = await Department.findAncestors({
entityId: deptId
});

const current = await Department.get(deptId);

return {
current,
ancestors: ancestors.reverse(),
depth: ancestors.length,
rootDepartment: ancestors[0] || current
};
}

性能优化

1. 限制查询深度

对于深层树形结构,限制查询深度可以提高性能:

// ❌ 不推荐:查询所有祖先可能很慢
const allAncestors = await Menu.findAncestors({
entityId: nodeId
});

// ✅ 推荐:只查询必要的层级
const recentAncestors = await Menu.findAncestors({
entityId: nodeId,
level: 3 // 只查询 3 层
});

2. 缓存查询结果

const ancestorCache = new Map<string, Menu[]>();

async function getCachedAncestors(menuId: string) {
if (ancestorCache.has(menuId)) {
return ancestorCache.get(menuId)!;
}

const ancestors = await Menu.findAncestors({
entityId: menuId
});

ancestorCache.set(menuId, ancestors);
return ancestors;
}

// 数据更新时清除缓存
function clearAncestorCache() {
ancestorCache.clear();
}

3. 批量查询优化

// ❌ 不推荐:逐个查询
const allAncestors = [];
for (const nodeId of nodeIds) {
const ancestors = await Menu.findAncestors({ entityId: nodeId });
allAncestors.push(ancestors);
}

// ✅ 推荐:并行查询
const allAncestors = await Promise.all(
nodeIds.map(nodeId =>
Menu.findAncestors({ entityId: nodeId })
)
);

返回结果顺序

findAncestors 返回的祖先节点顺序是从最近的父节点到根节点

const ancestors = await Menu.findAncestors({
entityId: 'leaf-node-id'
});

// ancestors 顺序:
// [0] 直接父节点
// [1] 父节点的父节点
// [2] 父节点的父节点的父节点
// ...
// [n] 根节点

// 如果需要从根到父的顺序,使用 reverse()
const rootToParent = ancestors.reverse();

与其他方法的组合使用

获取完整的节点路径

// 获取从根到当前节点的完整路径
async function getNodePath(nodeId: string) {
const ancestors = await Menu.findAncestors({ entityId: nodeId });
const current = await Menu.get(nodeId);

// 从根到当前节点
return [...ancestors.reverse(), current];
}

查找共同祖先

// 查找两个节点的最近共同祖先
async function findCommonAncestor(nodeId1: string, nodeId2: string) {
const ancestors1 = await Menu.findAncestors({ entityId: nodeId1 });
const ancestors2 = await Menu.findAncestors({ entityId: nodeId2 });

const ids1 = new Set(ancestors1.map(a => a.id));

// 找到第一个共同的祖先
for (const ancestor of ancestors2) {
if (ids1.has(ancestor.id)) {
return ancestor;
}
}

return null;
}

注意事项

  1. 返回顺序:祖先节点按照从父到根的顺序返回
  2. 根节点:根节点没有祖先,返回空数组
  3. 性能考虑:对于深层树形结构,考虑限制查询深度
  4. level 参数level: 0 查询所有祖先,level: 1 只查询直接父节点
  5. 实体定义:只能在使用 @TreeEntity 装饰器定义的实体上使用此方法

相关方法

参考