跳到主要内容

countAncestors

统计树形结构实体的祖先节点数量。

方法签名

countAncestors(options: FindTreeOptions): Promise<number>

参数说明

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 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();

注意事项

  1. 性能优势countAncestors 只统计数量,性能优于 findAncestors
  2. 层级计算:祖先数量即为节点的层级深度(根节点为 0)
  3. 根节点:根节点没有祖先,返回 0
  4. level 参数level: 0 统计所有祖先,level: 1 检查是否有直接父节点
  5. 实体定义:只能在使用 @TreeEntity 装饰器定义的实体上使用此方法

相关方法

参考