countAncestors
统计树形结构实体的祖先节点数量。
方法签名
countAncestors(options: FindTreeOptions): Promise<number>
参数说明
FindTreeOptions
interface FindTreeOptions<T extends EntityType = any> {
/**
* 查询的实体 ID
*/
entityId?: string;
/**
* 查询条件
*/
where?: RuleGroup<InstanceType<T>>;
/**
* 查询级别(深度)
* @default 0 表示查询所有层级
*/
level?: number;
}
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| entityId | string | 否 | - | 当前节点的 ID |
| where | RuleGroup | 否 | - | 额外的过滤条件 |
| level | number | 否 | 0 | 查询深度,0 表示统计所有层级,1 表示只统计直接父节点 |
基础用法
统计所有祖先节点
import { Menu } from './entities/Menu';
// 统计指定菜单项的所有祖先节点数量
const count = await Menu.countAncestors({
entityId: 'menu-child-id'
});
console.log('祖先节点总数:', count);
限制统计深度
// 检查是否有直接父节点(count = 1 表示有父节点)
const hasParent = await Menu.countAncestors({
entityId: 'menu-child-id',
level: 1
});
// 统计两层祖先节点
const twoLevelsCount = await Menu.countAncestors({
entityId: 'menu-child-id',
level: 2
});
使用过滤条件
// 统计所有启用状态的祖先节点
const activeCount = await Menu.countAncestors({
entityId: 'menu-child-id',
where: {
combinator: 'and',
rules: [{ field: 'enabled', operator: '=', value: true }]
}
});
// 统计特定类型的祖先节点
const folderCount = await Menu.countAncestors({
entityId: 'menu-child-id',
where: {
combinator: 'and',
rules: [{ field: 'type', operator: '=', value: 'folder' }]
}
});
响应式统计
使用 RxJS 订阅祖先节点数量的实时变化:
import { Subscription } from 'rxjs';
// 订阅祖先节点数量变化
const subscription: Subscription = Menu.countAncestors({
entityId: 'menu-child-id'
}).subscribe({
next: count => {
console.log('祖先节点数量:', count);
},
error: err => {
console.error('统计错误:', err);
}
});
// 取消订阅
subscription.unsubscribe();
实际应用场景
1. 计算节点层级深度
@Entity({
type: 'tree',
name: 'Menu',
properties: [{ name: 'name', type: PropertyType.string, required: true }]
})
export class Menu extends TreeEntityBase {}
2. 判断节点类型
// 判断是否为根节点
async function isRootNode(menuId: string): Promise<boolean> {
const ancestorCount = await Menu.countAncestors({
entityId: menuId
});
return ancestorCount === 0;
}
// 判断是否为叶子节点(需要结合 countDescendants)
async function isLeafNode(menuId: string): Promise<boolean> {
const descendantCount = await Menu.countDescendants({
entityId: menuId,
level: 1
});
return descendantCount === 0;
}
// 获取节点类型
async function getNodeType(menuId: string): Promise<'root' | 'branch' | 'leaf'> {
const [ancestorCount, descendantCount] = await Promise.all([
Menu.countAncestors({ entityId: menuId }),
Menu.countDescendants({ entityId: menuId, level: 1 })
]);
if (ancestorCount === 0) return 'root';
if (descendantCount === 0) return 'leaf';
return 'branch';
}
3. 面包屑长度控制
// 检查面包屑是否需要省略
async function shouldTruncateBreadcrumb(menuId: string): Promise<boolean> {
const ancestorCount = await Menu.countAncestors({
entityId: menuId
});
// 超过 5 层则需要省略中间部分
return ancestorCount > 5;
}
// 生成省略式面包屑
async function generateCompactBreadcrumb(menuId: string) {
const ancestorCount = await Menu.countAncestors({
entityId: menuId
});
if (ancestorCount <= 5) {
// 不省略,返回完整路径
const ancestors = await Menu.findAncestors({ entityId: menuId });
return ancestors.reverse();
} else {
// 省略中间部分,只显示根节点、父节点和当前节点
const allAncestors = await Menu.findAncestors({ entityId: menuId });
const root = allAncestors[allAncestors.length - 1];
const parent = allAncestors[0];
return [root, '...', parent];
}
}
4. 组织架构层级限制
@Entity({
type: 'tree',
name: 'Department',
properties: [{ name: 'name', type: PropertyType.string, required: true }]
})
export class Department extends TreeEntityBase {}
5. 权限层级检查
@Entity({
type: 'tree',
name: 'Menu',
properties: [
{ name: 'name', type: PropertyType.string, required: true },
{ name: 'requiredRole', type: PropertyType.string }
]
})
export class Menu extends TreeEntityBase {}
5. 权限层级检查
@Entity({
type: 'tree',
name: 'Menu',
properties: [
{ name: 'name', type: PropertyType.string, required: true },
{ name: 'requiredRole', type: PropertyType.string }
]
})
export class Menu extends TreeEntityBase {}
// 检查用户是否有权访问某个菜单(需要检查所有祖先菜单的权限)
async function hasAccessToMenu(menuId: string, userRole: string): Promise<boolean> {
// 统计需要特定角色的祖先菜单数量
const restrictedAncestorCount = await Menu.countAncestors({
entityId: menuId,
where: {
combinator: 'and',
rules: [{ field: 'requiredRole', operator: '!=', value: userRole }]
}
});
// 如果有任何祖先菜单需要其他角色,则无权访问
return restrictedAncestorCount === 0;
}
6. 显示节点缩进
// 根据层级深度计算缩进量
async function getNodeIndent(menuId: string): Promise<number> {
const level = await Menu.countAncestors({
entityId: menuId
});
const INDENT_PER_LEVEL = 20; // 每层缩进 20px
return level * INDENT_PER_LEVEL;
}
// 在 UI 中使用
async function renderMenuItem(menuId: string) {
const menu = await Menu.get(menuId);
const indent = await getNodeIndent(menuId);
return {
...menu,
style: {
paddingLeft: `${indent}px`
}
};
}
性能优化
1. 批量统计
对于需要统计多个节点的场景,使用并行查询:
// ❌ 不推荐:逐个统计
const levels = [];
for (const menuId of menuIds) {
const level = await Menu.countAncestors({ entityId: menuId });
levels.push(level);
}
// ✅ 推荐:并行统计
const levels = await Promise.all(menuIds.map(menuId => Menu.countAncestors({ entityId: menuId })));
2. 缓存统计结果
const levelCache = new Map<string, number>();
async function getCachedLevel(menuId: string): Promise<number> {
if (levelCache.has(menuId)) {
return levelCache.get(menuId)!;
}
const level = await Menu.countAncestors({
entityId: menuId
});
levelCache.set(menuId, level);
return level;
}
// 清除缓存
function clearLevelCache() {
levelCache.clear();
}
3. 预计算层级
对于不经常变化的树形结构,可以将层级存储在实体中:
@Entity({
type: 'tree',
name: 'Menu',
properties: [
{ name: 'name', type: PropertyType.string, required: true },
{ name: 'cachedLevel', type: PropertyType.number } // 缓存的层级
]
})
export class Menu extends TreeEntityBase {
// 保存时更新层级
async beforeSave() {
this.cachedLevel = await Menu.countAncestors({
entityId: this.id
});
}
}
// 直接使用缓存的层级
const menu = await Menu.get(menuId);
console.log('层级:', menu.cachedLevel);
与 findAncestors 的比较
| 方法 | 用途 | 返回值 | 性能 | 适用场景 |
|---|---|---|---|---|
| countAncestors | 统计数量 | number | 更快 | 只需要知道层级或数量时 |
| findAncestors | 查询详情 | Entity[] | 较慢 | 需要具体祖先数据时 |
// 只需要层级时使用 countAncestors
const level = await Menu.countAncestors({ entityId: menuId });
console.log(`节点在第 ${level} 层`);
// 需要生成面包屑时使用 findAncestors
const ancestors = await Menu.findAncestors({ entityId: menuId });
const breadcrumb = ancestors.reverse();
注意事项
- 性能优势:
countAncestors只统计数量,性能优于findAncestors - 层级计算:祖先数量即为节点的层级深度(根节点为 0)
- 根节点:根节点没有祖先,返回 0
- level 参数:
level: 0统计所有祖先,level: 1检查是否有直接父节点 - 实体定义:只能在使用
@TreeEntity装饰器定义的实体上使用此方法
相关方法
- findAncestors - 查询祖先节点
- findDescendants - 查询后代节点
- countDescendants - 统计后代节点数量