# 管理员注入区

> Source: https://strapi.nodejs.cn/cms/plugins-development/admin-injection-zones

🌐 Admin Panel API: Injection zones

注入区域是在管理界面中预定义的区域，插件可以在这些区域注入 React 组件。使用 `getPlugin('content-manager').injectComponent()` 来扩展内置视图，或在 `registerPlugin` 中使用 `injectionZones` 定义自己的区域。

🌐 Injection zones are predefined areas in the admin UI where plugins can inject React components. Use `getPlugin('content-manager').injectComponent()` to extend built-in views, or define your own zones with `injectionZones` in `registerPlugin`.

插件可以通过将自定义 React 组件注入预定义区域来扩展和自定义现有的管理面板部分。这使你能够在不修改核心代码的情况下，为 Strapi 的内置界面添加功能。

🌐 Plugins can extend and customize existing admin panel sections by injecting custom React components into predefined areas. This allows you to add functionality to Strapi's built-in interfaces without modifying core code.

:::prerequisites

在深入了解本页的概念之前，请确保你已经：

🌐 Before diving deeper into the concepts on this page, please ensure you have:

- [创建了一个 Strapi 插件](/cms/plugins-development/create-a-plugin)，
- 已阅读并理解了 [管理面板 API](/cms/plugins-development/admin-panel-api) 的基础知识

:::

:::note

注入区域在 [`register`](/cms/plugins-development/admin-panel-api#register) 生命周期函数中定义，但组件在 [`bootstrap`](/cms/plugins-development/admin-panel-api#bootstrap) 生命周期函数中注入。

🌐 Injection zones are defined in the [`register`](/cms/plugins-development/admin-panel-api#register) lifecycle function, but components are injected in the [`bootstrap`](/cms/plugins-development/admin-panel-api#bootstrap) lifecycle function.

:::

## 注入区与内容管理器 API {#injection-zones-vs-content-manager-apis}

🌐 Injection zones vs. Content Manager APIs

:::tip tl;dr

要向内容管理器添加面板、操作或按钮，[内容管理器 API](/cms/plugins-development/content-manager-apis)（`addDocumentAction`、`addEditViewSidePanel` 等）通常比注入区更稳健且类型更明确。当需要将组件插入内容管理器 API 未覆盖的特定 UI 区域时，请使用注入区。

🌐 For adding panels, actions, or buttons to the Content Manager, the [Content Manager APIs](/cms/plugins-development/content-manager-apis) (`addDocumentAction`, `addEditViewSidePanel`, etc.) are often more robust and better typed than injection zones. Use injection zones when you need to insert components into specific UI areas not covered by the Content Manager APIs.

:::

内容管理器 API 和注入区都是用于自定义管理面板的扩展点，但它们解决的是不同的需求：

🌐 Content Manager APIs and injection zones are both extension points to customize the admin panel, but they solve different needs:

| 需求 | 推荐的 API | 原因 |
| --- | --- | --- |
| 在编辑视图侧边区域添加自定义面板 | 内容管理器 API ([`addEditViewSidePanel`](/cms/plugins-development/content-manager-apis#addeditviewsidepanel)) | 最适合在编辑时保持可见的上下文信息或控制。 |
| 在文档操作菜单中添加操作 | 内容管理器 API ([`addDocumentAction`](/cms/plugins-development/content-manager-apis#adddocumentaction)) | 最适合在编辑视图操作菜单中执行文档级操作。 |
| 在编辑视图标题中添加操作 | 内容管理器 API ([`addDocumentHeaderAction`](/cms/plugins-development/content-manager-apis#adddocumentheaderaction)) | 最适合在文档标题旁进行快速、显眼的操作。 |
| 为列表视图中选定的条目添加操作 | 内容管理器 API ([`addBulkAction`](/cms/plugins-development/content-manager-apis#addbulkaction)) | 最适合用于同时应用于多个条目的工作流程。 |
| 在插件视图的预定义区域添加 UI（本地化视觉定制）| 注入区域 API ([`injectComponent`](/cms/plugins-development/admin-injection-zones#injecting-into-content-manager-zones)) | 当你针对插件公开的特定区域时效果最佳。 |

有关实现细节和最新的 API 签名，请参阅 Strapi 代码库中的 [content-manager](https://github.com/strapi/strapi/blob/develop/packages/core/content-manager/admin/src/content-manager.ts) 文件。

**迷你示例（在 `bootstrap(app)` 内）**

```js
// Document action menu item
app.getPlugin('content-manager').apis.addDocumentAction(() => ({
  label: 'Run custom action',
  onClick: ({ documentId }) => runCustomAction(documentId),
}));

// Edit View header action
app.getPlugin('content-manager').apis.addDocumentHeaderAction(() => ({
  label: 'Open preview',
  onClick: ({ document }) => openPreview(document),
}));

// List View bulk action
app.getPlugin('content-manager').apis.addBulkAction(() => ({
  label: 'Bulk publish',
  onClick: ({ documentIds }) => bulkPublish(documentIds),
}));

// Edit View side panel
app.getPlugin('content-manager').apis.addEditViewSidePanel([
  {
    name: 'my-plugin.side-panel',
    Component: MySidePanel,
  },
]);

// Injection zone (plugin-defined zone)
app.getPlugin('content-manager').injectComponent('editView', 'right-links', {
  name: 'my-plugin.custom-link',
  Component: MyCustomLink,
});
```

## 预定义注入区 {#predefined-injection-zones}

🌐 Predefined injection zones

Strapi 的内容管理器提供了插件可以使用的预定义注入区域：

🌐 Strapi's Content Manager provides predefined injection zones that plugins can use:

| 视图 | 注入区域 | 位置 |
|---|---|---|
| 列表视图 | `listView.actions` | 过滤器和齿轮图标之间 |
| 列表视图 | `listView.publishModalAdditionalInfos` | 发布确认模态中的信息内容 |
| 列表视图 | `listView.unpublishModalAdditionalInfos` | 取消发布确认模态中的信息内容 |
| 列表视图 | `listView.deleteModalAdditionalInfos` | 删除确认模态中的信息内容 |
| 编辑视图 | `editView.right-links` | 在“配置视图”和“编辑”按钮之间 |
| 编辑视图 | `editView.informations` | 编辑视图的信息框内（内部，见下方备注） |
| 预览 | `preview.actions` | 在预览视图操作区域 |

`listView.*ModalAdditionalInfos` 区域旨在丰富发布、取消发布和删除确认模态框中显示的信息内容。

🌐 The `listView.*ModalAdditionalInfos` zones are intended to enrich the informational content displayed in publish, unpublish, and delete confirmation modals.

:::caution

`editView.informations` 区域存在于内容管理器源代码中，但被视为内部区域。对于第三方插件，`editView.right-links` 是最稳定且官方推荐的编辑视图扩展点。只有在你特别需要信息面板区域并且可以接受不同版本间潜在的 UI 变化时，才使用 `editView.informations`。

🌐 The `editView.informations` zone exists in the Content Manager source code but is considered internal. For third-party plugins, `editView.right-links` is the most stable and officially recommended Edit view extension point. Use `editView.informations` only when you specifically need the information panel area and accept potential UI changes between versions.

:::

### 注入到内容管理器区域 {#injecting-into-content-manager-zones}

🌐 Injecting into Content Manager zones

要将组件注入内容管理器注入区域，请在 `bootstrap` 生命周期中使用 `getPlugin('content-manager').injectComponent()`：

🌐 To inject a component into a Content Manager injection zone, use `getPlugin('content-manager').injectComponent()` in the `bootstrap` lifecycle:

<Tabs groupId="js-ts">
<TabItem value="js" label="JavaScript">

```jsx title="admin/src/index.js"

  register(app) {
    app.registerPlugin({
      id: 'my-plugin',
      name: 'My Plugin',
    });
  },
  bootstrap(app) {
    // Inject a button into the Edit view's right-links zone
    // highlight-start
    app
      .getPlugin('content-manager')
      .injectComponent('editView', 'right-links', {
        name: 'my-plugin-custom-button',
        Component: MyCustomButton,
      });
    // highlight-end

    // Inject a component into the List view's actions zone
    // highlight-start
    app
      .getPlugin('content-manager')
      .injectComponent('listView', 'actions', {
        name: 'my-plugin-list-action',
        Component: () => <button>Custom List Action</button>,
      });
    // highlight-end

    // Inject additional information into the publish modal
    // highlight-start
    app
      .getPlugin('content-manager')
      .injectComponent('listView', 'publishModalAdditionalInfos', {
        name: 'my-plugin-publish-modal-info',
        Component: PublishModalInfo,
      });
    // highlight-end

    // Inject a component into the Preview view's actions zone
    // highlight-start
    app
      .getPlugin('content-manager')
      .injectComponent('preview', 'actions', {
        name: 'my-plugin-preview-action',
        Component: PreviewAction,
      });
    // highlight-end
  },
};
```

<TabItem value="ts" label="TypeScript">

```tsx title="admin/src/index.ts"

  register(app: StrapiApp) {
    app.registerPlugin({
      id: 'my-plugin',
      name: 'My Plugin',
    });
  },
  bootstrap(app: StrapiApp) {
    // Inject a button into the Edit view's right-links zone
    // highlight-start
    app
      .getPlugin('content-manager')
      .injectComponent('editView', 'right-links', {
        name: 'my-plugin-custom-button',
        Component: MyCustomButton,
      });
    // highlight-end

    // Inject a component into the List view's actions zone
    // highlight-start
    app
      .getPlugin('content-manager')
      .injectComponent('listView', 'actions', {
        name: 'my-plugin-list-action',
        Component: () => <button>Custom List Action</button>,
      });
    // highlight-end

    // Inject additional information into the publish modal
    // highlight-start
    app
      .getPlugin('content-manager')
      .injectComponent('listView', 'publishModalAdditionalInfos', {
        name: 'my-plugin-publish-modal-info',
        Component: PublishModalInfo,
      });
    // highlight-end

    // Inject a component into the Preview view's actions zone
    // highlight-start
    app
      .getPlugin('content-manager')
      .injectComponent('preview', 'actions', {
        name: 'my-plugin-preview-action',
        Component: PreviewAction,
      });
    // highlight-end
  },
};
```

## 自定义注入区域 {#custom-injection-zones}

🌐 Custom injection zones

插件可以定义自己的注入区域，以允许其他插件扩展它们的用户界面。在 `registerPlugin` 配置中声明注入区域：

🌐 Plugins can define their own injection zones to allow other plugins to extend their UI. Declare injection zones in the `registerPlugin` configuration:

```jsx title="admin/src/index.js"

  register(app) {
    app.registerPlugin({
      id: 'dashboard',
      name: 'Dashboard',
      // highlight-start
      injectionZones: {
        homePage: {
          top: [],
          middle: [],
          bottom: [],
        },
        sidebar: {
          before: [],
          after: [],
        },
      },
      // highlight-end
    });
  },
};
```
```ts title="admin/src/index.ts"

  register(app: StrapiApp) {
    app.registerPlugin({
      id: 'dashboard',
      name: 'Dashboard',
      // highlight-start
      injectionZones: {
        homePage: {
          top: [],
          middle: [],
          bottom: [],
        },
        sidebar: {
          before: [],
          after: [],
        },
      },
      // highlight-end
    });
  },
};
```

### 在组件中呈现注入区域 {#rendering-injection-zones-in-components}

🌐 Rendering injection zones in components

在 Strapi 5 中，来自 `@strapi/helper-plugin` 的 `InjectionZone` 组件已被移除，且没有直接的替代导出。要渲染注入的组件，请使用来自 `@strapi/strapi/admin` 的 `useStrapiApp` 创建你自己的组件。

🌐 In Strapi 5, the `InjectionZone` component from `@strapi/helper-plugin` is removed and has no direct replacement export. To render injected components, create your own component with `useStrapiApp` from `@strapi/strapi/admin`.

<Tabs groupId="js-ts">
<TabItem value="js" label="JavaScript">

```jsx title="admin/src/components/CustomInjectionZone.jsx"

  const getPlugin = useStrapiApp('CustomInjectionZone', (state) => state.getPlugin);
  const [pluginName, view, zone] = area.split('.');

  const plugin = getPlugin(pluginName);
  const components = plugin?.getInjectedComponents(view, zone);

  if (!components?.length) {
    return null;
  }

  return components.map(({ name, Component }) => );
};
```

```jsx title="admin/src/pages/Dashboard.jsx"

const Dashboard = () => {
  return (
    <div>
      <h1>Dashboard</h1>

      {/* Render components injected into the top zone */}

      <div className="main-content">{/* Main dashboard content */}</div>

      {/* Render components injected into the bottom zone */}
      
    </div>
  );
};

```

<TabItem value="ts" label="TypeScript">

```tsx title="admin/src/components/CustomInjectionZone.tsx"

type CustomInjectionZoneProps = {
  area: `${string}.${string}.${string}`;
  [key: string]: unknown;
};

  const getPlugin = useStrapiApp('CustomInjectionZone', (state) => state.getPlugin);
  const [pluginName, view, zone] = area.split('.');

  const plugin = getPlugin(pluginName);
  const components = plugin?.getInjectedComponents(view, zone);

  if (!components?.length) {
    return null;
  }

  return components.map(({ name, Component }) => );
};
```

```tsx title="admin/src/pages/Dashboard.tsx"

const Dashboard = () => {
  return (
    <div>
      <h1>Dashboard</h1>

      {/* Render components injected into the top zone */}

      <div className="main-content">{/* Main dashboard content */}</div>

      {/* Render components injected into the bottom zone */}
      
    </div>
  );
};

```

### 注入自定义区域 {#injecting-into-custom-zones}

🌐 Injecting into custom zones

其他插件可以使用它们 `bootstrap` 生命周期中的 `injectComponent()` 方法将组件注入到你的自定义注入区域：

🌐 Other plugins can inject components into your custom injection zones using the `injectComponent()` method in their `bootstrap` lifecycle:

```jsx title="another-plugin/admin/src/index.js"

  register(app) {
    app.registerPlugin({
      id: 'widget-plugin',
      name: 'Widget Plugin',
    });
  },
  // highlight-start
  bootstrap(app) {
    const dashboardPlugin = app.getPlugin('dashboard');

    if (dashboardPlugin) {
      dashboardPlugin.injectComponent('homePage', 'top', {
        name: 'widget-plugin-statistics',
        Component: Widget,
      });
    }
  },
  // highlight-end
};
```
```tsx title="another-plugin/admin/src/index.ts"

  register(app: StrapiApp) {
    app.registerPlugin({
      id: 'widget-plugin',
      name: 'Widget Plugin',
    });
  },
  // highlight-start
  bootstrap(app: StrapiApp) {
    const dashboardPlugin = app.getPlugin('dashboard');

    if (dashboardPlugin) {
      dashboardPlugin.injectComponent('homePage', 'top', {
        name: 'widget-plugin-statistics',
        Component: Widget,
      });
    }
  },
  // highlight-end
};
```

## 喷射组件参数 {#injection-component-parameters}

🌐 Injection component parameters

`injectComponent()` 方法接受以下参数：

🌐 The `injectComponent()` method accepts the following parameters:

| 参数 | 类型 | 描述 |
|---|---|---|
| `view` | `string` | 组件应该被注入的视图名称 |
| `zone` | `string` | 视图中组件应该被注入的区域名称 |
| `component` | `object` | 配置对象，包含 `name`（唯一字符串）和 `Component`（要注入的 React 组件） |

## 内容管理数据访问 {#content-manager-data-access}

🌐 Content Manager data access

在将组件注入内容管理器注入区域时，你可以使用 `useContentManagerContext` 钩子访问编辑视图数据：

🌐 When injecting components into Content Manager injection zones, you can access the Edit View data using the `useContentManagerContext` hook:

<Tabs groupId="js-ts">
<TabItem value="js" label="JavaScript">

```jsx title="admin/src/components/MyCustomButton.jsx"

  unstable_useContentManagerContext as useContentManagerContext,
} from '@strapi/strapi/admin';

  const {
    slug, // Content type slug (e.g., 'api::article.article')
    model, // Content type model
    id, // Document ID (undefined when creating)
    collectionType, // 'single-types' or 'collection-types'
    isCreatingEntry, // Whether creating a new entry
    isSingleType, // Whether the content type is a single type
    hasDraftAndPublish, // Whether draft & publish is enabled
    contentType, // Content type schema
    components, // Component schemas
    layout, // Content type layout
    form, // Form state and handlers
  } = useContentManagerContext();

  const { initialValues, values, onChange } = form;

  const handleCustomAction = () => {
    onChange({ target: { name: 'customField', value: 'new value' } });
  };

  return <button onClick={handleCustomAction}>Custom Action</button>;
};
```

<TabItem value="ts" label="TypeScript">

```tsx title="admin/src/components/MyCustomButton.tsx"

  unstable_useContentManagerContext as useContentManagerContext,
} from '@strapi/strapi/admin';

  const {
    slug, // Content type slug (e.g., 'api::article.article')
    model, // Content type model
    id, // Document ID (undefined when creating)
    collectionType, // 'single-types' or 'collection-types'
    isCreatingEntry, // Whether creating a new entry
    isSingleType, // Whether the content type is a single type
    hasDraftAndPublish, // Whether draft & publish is enabled
    contentType, // Content type schema
    components, // Component schemas
    layout, // Content type layout
    form, // Form state and handlers
  } = useContentManagerContext();

  const { initialValues, values, onChange } = form;

  const handleCustomAction = () => {
    onChange({ target: { name: 'customField', value: 'new value' } });
  };

  return <button onClick={handleCustomAction}>Custom Action</button>;
};
```

:::caution

`useContentManagerContext` 钩子目前以 `unstable_useContentManagerContext` 导出。`unstable_` 前缀表示该 API 在未来版本中可能会发生变化。该钩子替代了来自 `@strapi/helper-plugin` 的已弃用 `[useCMEditViewDataManager](/cms/migration/v4-to-v5/additional-resources/helper-plugin#usecmeditviewdatamanager)`，该钩子在 Strapi 5 中不可用。

🌐 The `useContentManagerContext` hook is currently exported as `unstable_useContentManagerContext`. The `unstable_` prefix indicates the API may change in future releases. This hook replaces the [deprecated `useCMEditViewDataManager`](/cms/migration/v4-to-v5/additional-resources/helper-plugin#usecmeditviewdatamanager) from `@strapi/helper-plugin` which is not available in Strapi 5.

:::

## 最佳实践 {#best-practices}

🌐 Best practices

- **使用描述性的区域名称。** 为你的注入区域选择清晰的名称（例如，`top`、`bottom`、`before`、`after`）。
- **检查插件可用性。** 在将组件注入插件的区域之前，请始终确认该插件是否存在：

  ```js
  bootstrap(app) {
    const targetPlugin = app.getPlugin('target-plugin');
    if (targetPlugin) {
      targetPlugin.injectComponent('view', 'zone', {
        name: 'my-component',
        Component: MyComponent,
      });
    }
  }
  ```

- **使用唯一的组件名称。** 确保组件名称是唯一的，以避免与其他插件发生冲突。
- **优雅地处理缺失的区域。** 组件应处理注入区域可能不可用的情况。
- **记录你的注入区域。** 清晰地记录你的插件提供了哪些注入区域以及它们的预期用途。
