mcpField
U
umbraco-tree

umbraco-tree

github.com/umbraco开发27383 次安装6 周前更新
代码仓库

关于 umbraco-tree

umbraco-tree 是用于构建 Umbraco 后台层级树的编码技能,介绍树注册、数据源实现,以及可点击节点所需的工作区配置,并采用内联数据源模式。

主要功能

  1. 01注册后台树
  2. 02获取根节点、子节点和祖先
  3. 03使用内联数据源
  4. 04将可点击节点连接到工作区

适用场景

  1. 01展示自定义内容层级
  2. 02构建由仓储提供数据的树
  3. 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 的任何位置渲染。树需要数据源实现来获取根项目、子项和祖先项。

文档

实现前请务必获取最新文档:

关键:树与工作区集成

树与工作区紧密耦合。 使用 kind: 'default' 树项目时:

  1. 树项目需要工作区 — 点击树项目会导航到该实体类型对应的工作区。若未为 entityType 注册工作区,点击后会导致“无限加载”。

  2. 工作区必须是 kind: 'routable' — 为确保树项目的正确选中状态,以及在相同类型的项目之间导航,请使用 kind: 'routable' 工作区(而非 kind: 'default')。

  3. 实体类型将树与工作区关联起来 — 树项目数据中的 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

工作流程

  1. 获取文档 — 对上述 URL 使用 WebFetch
  2. 提出问题 — 树将显示哪些数据?由哪个存储库提供数据?树将显示在哪里?树项目是否可以点击?
  3. 生成文件 — 根据最新文档创建最精简的文件
    • ✅ 创建:manifest.ts、tree.repository.ts(包含内联数据源)
    • ❌ 不要创建:tree.store.ts(已在 v18 中移除)、单独的 tree.data-source.ts(可内联)
    • 使用以下示例中展示的内联数据源模式
  4. 如果项目可点击 — 为每种实体类型创建可路由工作区(参考 umbraco-workspace 技能)
  5. 说明 — 展示创建了哪些内容以及如何测试

关键配置选项

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 调用的复杂树,仍然可以拆分到不同文件中,但这不是必需的。

就是这样!始终获取最新文档,保持示例简洁,并生成完整可运行的代码。

评论

支持 Markdown