关于 umbraco-tree
umbraco-tree 是用于构建 Umbraco 后台层级树的编码技能,介绍树注册、数据源实现,以及可点击节点所需的工作区配置,并采用内联数据源模式。
主要功能
- 01注册后台树
- 02获取根节点、子节点和祖先
- 03使用内联数据源
- 04将可点击节点连接到工作区
适用场景
- 01展示自定义内容层级
- 02构建由仓储提供数据的树
- 03从树节点导航到工作区
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ umbraco/ umbraco-cms-backoffice-skills --skill umbraco-tree安装后在 Claude Code 中输入/umbraco-tree确认已加载。
详细介绍
Umbraco 树
什么是树?
Umbraco 中的树是在 Backoffice 扩展注册表中注册的节点层级结构。树用于显示经过组织的内容层级,可通过 <umb-tree /> 元素在 Backoffice 的任何位置渲染。树需要数据源实现来获取根项目、子项和祖先项。
文档
实现前请务必获取最新文档:
- 主要文档:https://docs.umbraco.com/umbraco-cms/extend-your-project/backoffice-extensions/extending-overview/extension-types/tree
- 区段与树:https://docs.umbraco.com/umbraco-cms/extend-your-project/backoffice-extensions
- 基础:https://docs.umbraco.com/umbraco-cms/extend-your-project/backoffice-extensions/foundation
- 扩展注册表:https://docs.umbraco.com/umbraco-cms/extend-your-project/backoffice-extensions/extending-overview/extension-registry
关键:树与工作区集成
树与工作区紧密耦合。 使用 kind: 'default' 树项目时:
-
树项目需要工作区 — 点击树项目会导航到该实体类型对应的工作区。若未为
entityType注册工作区,点击后会导致“无限加载”。 -
工作区必须是
kind: 'routable'— 为确保树项目的正确选中状态,以及在相同类型的项目之间导航,请使用kind: 'routable'工作区(而非kind: 'default')。 -
实体类型将树与工作区关联起来 — 树项目数据中的
entityType必须与工作区清单中的entityType相匹配。
实现包含可点击项目的树时,也请参考 umbraco-workspace 技能。
文件结构
现代树通常使用 2–3 个文件:
my-tree/
├── manifest.ts # Registers repository and tree
├── tree.repository.ts # Repository + inline data source
└── types.ts # Type definitions (optional)
参考示例
Umbraco 源代码中包含一个可运行的示例:
位置:/Umbraco-CMS/src/Umbraco.Web.UI.Client/examples/tree/
此示例展示了包含数据源、存储库和菜单集成的完整自定义树。请参考此示例了解生产环境中的实践模式。
相关基础技能
如果实现树时需要解释以下基础概念,请参考这些技能:
-
存储库模式:实现树数据源、存储库、数据获取或 CRUD 操作时
- 参考技能:
umbraco-repository-pattern
- 参考技能:
-
上下文 API:实现存储库上下文,或解释存储库如何连接 UI 组件时
- 参考技能:
umbraco-context-api
- 参考技能:
-
状态管理:实现响应式树更新、可观察对象或管理树状态时
- 参考技能:
umbraco-state-management
- 参考技能:
工作流程
- 获取文档 — 对上述 URL 使用 WebFetch
- 提出问题 — 树将显示哪些数据?由哪个存储库提供数据?树将显示在哪里?树项目是否可以点击?
- 生成文件 — 根据最新文档创建最精简的文件
- ✅ 创建:
manifest.ts、tree.repository.ts(包含内联数据源) - ❌ 不要创建:
tree.store.ts(已在 v18 中移除)、单独的tree.data-source.ts(可内联) - 使用以下示例中展示的内联数据源模式
- ✅ 创建:
- 如果项目可点击 — 为每种实体类型创建可路由工作区(参考
umbraco-workspace技能) - 说明 — 展示创建了哪些内容以及如何测试
关键配置选项
MenuItem 上的 hideTreeRoot(不在 Tree 上)
若要在根级别显示树项目(不带父文件夹),请在 menuItem 清单上使用 hideTreeRoot: true:
// CORRECT - hideTreeRoot on menuItem
{
type: 'menuItem',
kind: 'tree',
alias: 'My.MenuItem.Tree',
meta: {
treeAlias: 'My.Tree',
menus: ['My.Menu'],
hideTreeRoot: true, // Shows items at root level
},
}
// WRONG - hideTreeRoot on tree (has no effect)
{
type: 'tree',
meta: {
hideTreeRoot: true, // This does nothing!
},
}
最小示例
树清单
export const manifests: UmbExtensionManifest[] = [
// Repository
{
type: 'repository',
alias: 'My.Tree.Repository',
name: 'My Tree Repository',
api: () => import('./tree.repository.js'),
},
// Tree
{
type: 'tree',
kind: 'default',
alias: 'My.Tree',
name: 'My Tree',
meta: {
repositoryAlias: 'My.Tree.Repository',
},
},
// Tree Items - use kind: 'default' when workspaces exist
{
type: 'treeItem',
kind: 'default',
alias: 'My.TreeItem',
name: 'My Tree Item',
forEntityTypes: ['my-entity'],
},
// MenuItem - hideTreeRoot here
{
type: 'menuItem',
kind: 'tree',
alias: 'My.MenuItem.Tree',
meta: {
treeAlias: 'My.Tree',
menus: ['My.Menu'],
hideTreeRoot: true,
},
},
];
内联数据源仓库(tree.repository.ts)
现代简化模式——所有内容都放在一个文件中:
import { UmbTreeRepositoryBase, UmbTreeServerDataSourceBase } from '@umbraco-cms/backoffice/tree';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
import type { MyTreeItemModel, MyTreeRootModel } from './types.js';
import { MY_ROOT_ENTITY_TYPE, MY_ENTITY_TYPE } from './entity.js';
// Data source as simple class using helper base
class MyTreeDataSource extends UmbTreeServerDataSourceBase<any, MyTreeItemModel> {
constructor(host: UmbControllerHost) {
super(host, {
getRootItems: async (args) => {
// Fetch from API or return mock data
const items: MyTreeItemModel[] = [
{
unique: 'item-1',
parent: { unique: null, entityType: MY_ROOT_ENTITY_TYPE },
entityType: MY_ENTITY_TYPE,
name: 'Item 1',
hasChildren: false,
isFolder: false,
icon: 'icon-document',
},
];
return { data: { items, total: items.length } };
},
getChildrenOf: async (args) => {
// Return children for parent
return { data: { items: [], total: 0 } };
},
getAncestorsOf: async (args) => {
// Return ancestor path
return { data: [] };
},
mapper: (item: any) => item, // Identity mapper for this example
});
}
}
// Repository
export class MyTreeRepository
extends UmbTreeRepositoryBase<MyTreeItemModel, MyTreeRootModel>
implements UmbApi
{
constructor(host: UmbControllerHost) {
super(host, MyTreeDataSource);
}
async requestTreeRoot() {
const data: MyTreeRootModel = {
unique: null,
entityType: MY_ROOT_ENTITY_TYPE,
name: 'My Tree',
hasChildren: true,
isFolder: true,
};
return { data };
}
}
export { MyTreeRepository as api };
这种方式更简单的原因:
- ✅ 只需一个文件,而不是两个
- ✅ 使用
UmbTreeServerDataSourceBase辅助工具(传递函数,而不是方法) - ✅ 数据获取逻辑内联编写(更易理解)
- ✅ 无需维护单独的数据源文件
对于包含 API 调用的复杂树,仍然可以拆分到不同文件中,但这不是必需的。
就是这样!始终获取最新文档,保持示例简洁,并生成完整可运行的代码。