跳到主要内容

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;
}

注意事项

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

相关方法

参考