findAncestors
查询树形结构实体的所有祖先节点。
方法签名
findAncestors(options: FindTreeOptions): Observable<InstanceType<T>[]>
参数
interface FindTreeOptions<T> {
entityId?: string; // 当前节点 ID
where?: RuleGroup<T>; // 过滤条件
level?: number; // 查询深度,0 表示所有层级
}
示例
查询所有祖先
import { firstValueFrom } from 'rxjs';
const ancestors = await firstValueFrom(Menu.findAncestors({ entityId: 'menu-child-id' }));
限制深度
// 只查询直接父节点
const parent = await firstValueFrom(Menu.findAncestors({ entityId: 'menu-child-id', level: 1 }));
// 查询两层
const twoLevels = await firstValueFrom(Menu.findAncestors({ entityId: 'menu-child-id', level: 2 }));
过滤条件
// 查询启用的祖先节点
const active = await firstValueFrom(
Menu.findAncestors({
entityId: 'menu-child-id',
where: {
combinator: 'and',
rules: [{ field: 'enabled', operator: '=', value: true }]
}
})
);
应用场景
面包屑导航
async function generateBreadcrumb(currentMenuId: string) {
const ancestors = await firstValueFrom(Menu.findAncestors({ entityId: currentMenuId }));
const current = await firstValueFrom(Menu.get(currentMenuId));
return [...ancestors.reverse(), current].map(m => ({ name: m.name, path: m.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;
}
注意事项
- 返回顺序:祖先节点按照从父到根的顺序返回
- 根节点:根节点没有祖先,返回空数组
- 性能考虑:对于深层树形结构,考虑限制查询深度
- level 参数:
level: 0查询所有祖先,level: 1只查询直接父节点 - 实体定义:只能在使用
@TreeEntity装饰器定义的实体上使用此方法
相关方法
- countAncestors - 统计祖先节点数量
- findDescendants - 查询后代节点
- countDescendants - 统计后代节点数量