Skip to main content

管理面板 API 插件

¥Admin Panel API for plugins

Strapi plugin 可以与 后端 和 Strapi 应用的前端交互。管理面板 API 是关于前端部分的,即它允许插件自定义 Strapi 的 管理面板

¥A Strapi plugin can interact with both the back end and the front end of a Strapi application. The Admin Panel API is about the front end part, i.e. it allows a plugin to customize Strapi's admin panel.

管理面板是一个 React 应用,可以嵌入其他 React 应用。这些其他 React 应用是每个 Strapi 插件的管理部分。

¥The admin panel is a React application that can embed other React applications. These other React applications are the admin parts of each Strapi plugin.

☑️ Prerequisites

你有 创建了一个 Strapi 插件

¥You have created a Strapi plugin.

管理面板 API 包括:

¥The Admin Panel API includes:

✏️ 注意

插件管理面板部分的整个代码可以位于 /strapi-admin.js|ts/admin/src/index.js|ts 文件中。但是,建议将代码拆分到不同的文件夹中,就像 strapi generate plugin CLI 生成器命令创建的 structure 一样。

¥The whole code for the admin panel part of your plugin could live in the /strapi-admin.js|ts or /admin/src/index.js|ts file. However, it's recommended to split the code into different folders, just like the structure created by the strapi generate plugin CLI generator command.

录入文件

¥Entry file

管理面板 API 的入口文件是 [plugin-name]/admin/src/index.js。该文件导出所需的接口,可用的函数如下:

¥The entry file for the Admin Panel API is [plugin-name]/admin/src/index.js. This file exports the required interface, with the following functions available:

功能类型可用功能
生命周期函数
异步函数registerTrads

生命周期函数

¥Lifecycle functions

register()

类型:Function

¥Type: Function

即使应用实际上是 bootstrapped 之前,也会调用此函数来加载插件。它将正在运行的 Strapi 应用作为参数 (app)。

¥This function is called to load the plugin, even before the app is actually bootstrapped. It takes the running Strapi application as an argument (app).

在注册函数中,插件可以:

¥Within the register function, a plugin can:

registerPlugin()

类型:Function

¥Type: Function

注册插件以使其在管理面板中可用。

¥Registers the plugin to make it available in the admin panel.

该函数返回一个具有以下参数的对象:

¥This function returns an object with the following parameters:

范围类型描述
id字符串插件 ID
name字符串插件名称
injectionZones目的可用 注入区 声明
✏️ 注意

某些参数可以从 package.json 文件导入。

¥Some parameters can be imported from the package.json file.

示例:

¥Example:

my-plugin/admin/src/index.js

// Auto-generated component
import PluginIcon from './components/PluginIcon';
import pluginId from './pluginId'

export default {
register(app) {
app.addMenuLink({
to: `/plugins/${pluginId}`,
icon: PluginIcon,
intlLabel: {
id: `${pluginId}.plugin.name`,
defaultMessage: 'My plugin',
},
Component: async () => {
const component = await import(/* webpackChunkName: "my-plugin" */ './pages/App');

return component;
},
permissions: [], // array of permissions (object), allow a user to access a plugin depending on its permissions
});
app.registerPlugin({
id: pluginId,
name,
});
},
};

bootstrap()

类型:Function

¥Type: Function

公开引导函数,在所有插件都是 registered 后执行。

¥Exposes the bootstrap function, executed after all the plugins are registered.

在引导函数中,插件可以例如:

¥Within the bootstrap function, a plugin can, for instance:

示例:

¥Example:

module.exports = () => {
return {
// ...
bootstrap(app) {
// execute some bootstrap code
app.getPlugin('content-manager').injectComponent('editView', 'right-links', { name: 'my-compo', Component: () => 'my-compo' })
},
};
};

异步函数

¥Async function

register()bootstrap() 是生命周期函数,而 registerTrads() 是异步函数。

¥While register() and bootstrap() are lifecycle functions, registerTrads() is an async function.

registerTrads()

类型:Function

¥Type: Function

为了减少构建大小,管理面板默认仅附带 2 个区域设置(enfr)。registerTrads() 函数用于注册插件的翻译文件并为应用翻译创建单独的块。它不需要修改。

¥To reduce the build size, the admin panel is only shipped with 2 locales by default (en and fr). The registerTrads() function is used to register a plugin's translations files and to create separate chunks for the application translations. It does not need to be modified.

Example: Register a plugin's translation files
export default {
async registerTrads({ locales }) {
const importedTrads = await Promise.all(
locales.map(locale => {
return import(
/* webpackChunkName: "[pluginId]-[request]" */ `./translations/${locale}.json`
)
.then(({ default: data }) => {
return {
data: prefixPluginTranslations(data, pluginId),
locale,
};
})
.catch(() => {
return {
data: {},
locale,
};
});
})
);

return Promise.resolve(importedTrads);
},
};

可用操作

¥Available actions

管理面板 API 允许插件利用几个小型 API 来执行操作。使用此表作为参考:

¥The Admin Panel API allows a plugin to take advantage of several small APIs to perform actions. Use this table as a reference:

行动使用的 API使用功能相关生命周期函数
添加新链接到主导航菜单 APIaddMenuLink()register()
创建一个新的设置部分设置接口createSettingSection()register()
声明注入区注入区 APIregisterPlugin()register()
添加 reducerReducer APIaddReducers()register()
创建一个钩子钩子 APIcreateHook()register()
将单个链接添加到设置部分设置接口addSettingsLink()bootstrap()
将多个链接添加到设置部分设置接口addSettingsLinks()bootstrap()
在注入区注入组件注入区 APIinjectComponent()bootstrap()
将选项和操作添加到内容管理器的编辑视图和列表视图内容管理器 APIbootstrap()
注册一个钩子钩子 APIregisterHook()bootstrap()
💡 取代所见即所得

所见即所得编辑器可以通过利用 自定义字段 来替换,例如使用 CKEditor 自定义字段插件

¥The WYSIWYG editor can be replaced by taking advantage of custom fields, for instance using the CKEditor custom field plugin.

👀 信息

管理面板支持 dotenv 变量。

¥The admin panel supports dotenv variables.

通过 process.env 自定义管理面板时,.env 文件中定义并以 STRAPI_ADMIN_ 为前缀的所有变量都可用。

¥All variables defined in a .env file and prefixed by STRAPI_ADMIN_ are available while customizing the admin panel through process.env.

¥Menu API

Menu API 允许插件通过 addMenuLink() 函数使用以下参数向主导航添加新链接:

¥The Menu API allows a plugin to add a new link to the main navigation through the addMenuLink() function with the following parameters:

范围类型描述
to字符串链接应指向的路径
iconReact 组件在主导航中显示的图标
intlLabel目的链接的标签,遵循 React 国际化 约定,带有:
  • id:用于插入本地化标签的 id
  • defaultMessage:链接的默认标签
Component异步函数返回插件入口点的动态导入
permissions对象数组插件的 permissions.js 文件中声明的权限
position整数菜单中的位置
licenseOnly布尔值如果设置为 true,则在图标或菜单项旁边添加一个闪电⚡️图标,以指示该功能或插件需要付费许可。
(默认为 false
✏️ 注意

intlLabel.id 是翻译文件中使用的 ID ([plugin-name]/admin/src/translations/[language].json)

¥intlLabel.id are ids used in translation files ([plugin-name]/admin/src/translations/[language].json)

示例:

¥Example:

my-plugin/admin/src/index.js
import PluginIcon from './components/PluginIcon';

export default {
register(app) {
app.addMenuLink({
to: '/plugins/my-plugin',
icon: PluginIcon,
intlLabel: {
id: 'my-plugin.plugin.name',
defaultMessage: 'My plugin',
},
Component: () => 'My plugin',
permissions: [], // permissions to apply to the link
position: 3, // position in the menu
licenseOnly: true, // mark the feature as a paid one not available in your license
});
app.registerPlugin({ ... });
},
bootstrap() {},
};

设置接口

¥Settings API

设置 API 允许:

¥The Settings API allows:

✏️ 注意

添加新部分发生在 register 生命周期中,而添加链接发生在 bootstrap 生命周期中。

¥Adding a new section happens in the register lifecycle while adding links happens during the bootstrap lifecycle.

所有函数都接受链接作为具有以下参数的对象:

¥All functions accept links as objects with the following parameters:

范围类型描述
id字符串React ID
to字符串链接应指向的路径
intlLabel目的链接的标签,遵循 React 国际化 约定,带有:
  • id:用于插入本地化标签的 id
  • defaultMessage:链接的默认标签
Component异步函数返回插件入口点的动态导入
permissions对象数组插件的 permissions.js 文件中声明的权限
licenseOnly布尔值如果设置为 true,则在图标或菜单项旁边添加一个闪电⚡️图标,以指示该功能或插件需要付费许可。
(默认为 false

createSettingSection()

类型:Function

¥Type: Function

创建一个新的设置部分。

¥Create a new settings section.

该函数有 2 个参数:

¥The function takes 2 arguments:

争论类型描述
第一个参数目的部分标签:
  • id(字符串):部分 ID
  • intlLabel(对象):该部分的本地化标签,遵循 React 国际化 约定,其中:
    • id:用于插入本地化标签的 id
    • defaultMessage:该部分的默认标签
第二个参数对象数组该部分中包含的链接
✏️ 注意

intlLabel.id 是翻译文件中使用的 ID ([plugin-name]/admin/src/translations/[language].json)

¥intlLabel.id are ids used in translation files ([plugin-name]/admin/src/translations/[language].json)

示例:

¥Example:

my-plugin/admin/src/index.js



const myComponent = async () => {


const component = await import(
/* webpackChunkName: "users-providers-settings-page" */ './pages/Providers'
);

return component;
};

export default {
register(app) {
app.createSettingSection(
{ id: String, intlLabel: { id: String, defaultMessage: String } }, // Section to create
[
// links
{
intlLabel: { id: String, defaultMessage: String },
id: String,
to: String,
Component: myComponent,
permissions: Object[],
},
]
);
},
};

类型:Function

¥Type: Function

添加到现有设置部分的唯一链接。

¥Add a unique link to an existing settings section.

示例:

¥Example:

my-plugin/admin/src/index.js



const myComponent = async () => {


const component = await import(
/* webpackChunkName: "users-providers-settings-page" */ './pages/Providers'
);

return component;
};

export default {
bootstrap(app) {
// Adding a single link
app.addSettingsLink(
'global', // id of the section to add the link to
{
intlLabel: { id: String, defaultMessage: String },
id: String,
to: String,
Component: myComponent,
permissions: Object[],
licenseOnly: true, // mark the feature as a paid one not available in your license
}
)
}
}

类型:Function

¥Type: Function

将多个链接添加到现有设置部分。

¥Add multiple links to an existing settings section.

示例:

¥Example:

my-plugin/admin/src/index.js



const myComponent = async () => {


const component = await import(
/* webpackChunkName: "users-providers-settings-page" */ './pages/Providers'
);

return component;
};

export default {
bootstrap(app) {
// Adding several links at once
app.addSettingsLinks(
'global', // id of the section to add the link in
[{
intlLabel: { id: String, defaultMessage: String },
id: String,
to: String,
Component: myComponent,
permissions: Object[],
licenseOnly: true, // mark the feature as a paid one not available in your license
}]
)
}
}

注入区 API

¥Injection Zones API

注入区域是指视图布局的区域,其中一个插件允许另一个插件注入自定义 React 组件(例如按钮等 UI 元素)。

¥Injection zones refer to areas of a view's layout where a plugin allows another to inject a custom React component (e.g. a UI element like a button).

插件可以使用:

¥Plugins can use:

✏️ 注意

注入区域在 register() 生命周期中定义,但组件在 bootstrap() 生命周期中注入。

¥Injection zones are defined in the register() lifecycle but components are injected in the bootstrap() lifecycle.

使用预定义的注入区域

¥Using predefined injection zones

Strapi 管理面板带有预定义的注入区域,因此可以将组件添加到 内容管理者 的 UI 中:

¥Strapi admin panel comes with predefined injection zones so components can be added to the UI of the Content Manager:

看法注入区名称和位置
列表显示actions:位于过滤器和齿轮图标之间
编辑视图right-links:位于 "配置视图" 和 "编辑" 按钮之间

创建自定义注入区域

¥Creating a custom injection zone

要创建自定义注入区域,请将其声明为带有 area prop 的 <InjectionZone /> React 组件,该组件采用具有以下命名约定的字符串:plugin-name.viewName.injectionZoneName

¥To create a custom injection zone, declare it as a <InjectionZone /> React component with an area prop that takes a string with the following naming convention: plugin-name.viewName.injectionZoneName.

注入组件

¥Injecting components

插件有两种不同的方式来注入组件:

¥A plugin has 2 different ways of injecting a component:

  • 要将插件中的组件注入另一个插件的注入区域,请使用 injectComponent() 函数

    ¥to inject a component from a plugin into another plugin's injection zones, use the injectComponent() function

  • 要专门将组件注入内容管理器的 预定义注入区 之一,请改用 getPlugin('content-manager').injectComponent() 函数

    ¥to specifically inject a component into one of the Content Manager's predefined injection zones, use the getPlugin('content-manager').injectComponent() function instead

injectComponent()getPlugin('content-manager').injectComponent() 方法都接受以下参数:

¥Both the injectComponent() and getPlugin('content-manager').injectComponent() methods accept the following arguments:

争论类型描述
第一个参数字符串组件注入的视图
第二个参数字符串注入组件的区域
第三个参数目的具有以下键的对象:
  • name(字符串):组件的名称
  • Component(函数或类):要注入的 React 组件
Example: Inject a component in the informations box of the Edit View of the Content Manager:
my-plugin/admin/src/index.js

export default {
bootstrap(app) {
app.getPlugin('content-manager').injectComponent()('editView', 'informations', {
name: 'my-plugin-my-compo',
Component: () => 'my-compo',
});
}
}
Example: Creating a new injection zone and injecting it from a plugin to another one:
my-plugin/admin/src/injectionZones.js
// Use the injection zone in a view

import { InjectionZone } from '@strapi/helper-plugin';



const HomePage = () => {


return (
<main>
<h1>This is the homepage</h1>
<InjectionZone area="my-plugin.homePage.right" />
</main>
);
};
my-plugin/admin/src/index.js
// Declare this injection zone in the register lifecycle of the plugin

export default {
register() {
app.registerPlugin({
// ...
injectionZones: {
homePage: {
right: []
}
}
});
},
}
my-other-plugin/admin/src/index.js
// Inject the component from a plugin in another plugin

export default {
register() {
// ...
},
bootstrap(app) {
app.getPlugin('my-plugin').injectComponent('homePage', 'right', {
name: 'my-other-plugin-component',
Component: () => 'This component is injected',
});
}
};

使用 useCMEditViewDataManager React hook 访问数据

¥Accessing data with the useCMEditViewDataManager React hook

一旦定义了注入区域,内容管理器中要注入的组件就可以通过 useCMEditViewDataManager React hook 访问编辑视图的所有数据。

¥Once an injection zone is defined, the component to be injected in the Content Manager can have access to all the data of the Edit View through the useCMEditViewDataManager React hook.

Example of a basic component using the 'useCMEditViewDataManager' hook
import { useCMEditViewDataManager } from '@strapi/helper-plugin';



const MyCompo = () => {


const {
createActionAllowedFields: [], // Array of fields that the user is allowed to edit
formErrors: {}, // Object errors
readActionAllowedFields: [], // Array of field that the user is allowed to edit
slug: 'api::address.address', // Slug of the content-type
updateActionAllowedFields: [],
allLayoutData: {
components: {}, // components layout
contentType: {}, // content-type layout
},
initialData: {},
isCreatingEntry: true,
isSingleType: true,
status: 'resolved',
layout: {}, // Current content-type layout
hasDraftAndPublish: true,
modifiedData: {},
onPublish: () => {},
onUnpublish: () => {},
addComponentToDynamicZone: () => {},
addNonRepeatableComponentToField: () => {},
addRelation: () => {},
addRepeatableComponentToField: () => {},
moveComponentDown: () => {},
moveComponentField: () => {},
moveComponentUp: () => {},
moveRelation: () => {},
onChange: () => {},
onRemoveRelation: () => {},
removeComponentFromDynamicZone: () => {},
removeComponentFromField: () => {},
removeRepeatableField: () => {},
} = useCMEditViewDataManager()

return null
}

Reducer API

reducer 是 Redux reducer,可用于在组件之间共享状态。在以下情况下,Reducer 会很有用:

¥Reducers are Redux reducers that can be used to share state between components. Reducers can be useful when:

  • 应用中的许多地方都需要大量的应用状态。

    ¥Large amounts of application state are needed in many places in the application.

  • 应用状态经常更新。

    ¥The application state is updated frequently.

  • 更新该状态的逻辑可能很复杂。

    ¥The logic to update that state may be complex.

可以在 register 生命周期期间使用 addReducers() 函数将 reducer 添加到插件接口。

¥Reducers can be added to a plugin interface with the addReducers() function during the register lifecycle.

使用以下语法将化简器声明为对象:

¥A reducer is declared as an object with this syntax:

示例:

¥Example:

my-plugin/admin/src/index.js
import { exampleReducer } from './reducers'



const reducers = {


// Reducer Syntax
[`${pluginId}_exampleReducer`]: exampleReducer
}

export default {
register(app) {
app.addReducers(reducers)
},
bootstrap() {},
};


钩子 API

¥Hooks API

Hooks API 允许插件创建和注册钩子,即应用中插件可以添加个性化行为的位置。

¥The Hooks API allows a plugin to create and register hooks, i.e. places in the application where plugins can add personalized behavior.

Hooks 应在插件的 bootstrap 生命周期内注册。

¥Hooks should be registered during the bootstrap lifecycle of a plugin.

然后,Hook 可以串联、瀑布式或并行运行:

¥Hooks can then be run in series, in waterfall or in parallel:

  • runHookSeries 返回每个函数执行结果对应的数组,有序

    ¥runHookSeries returns an array corresponding to the result of each function executed, ordered

  • runHookParallel 返回一个与执行函数解析的 Promise 结果相对应的数组,有序

    ¥runHookParallel returns an array corresponding to the result of the promise resolved by the function executed, ordered

  • runHookWaterfall 返回与从初始值 args 开始的不同函数应用的所有转换相对应的单个值。

    ¥runHookWaterfall returns a single value corresponding to all the transformations applied by the different functions starting with the initial value args.

Example: Create a hook in a plugin and use it in another plugin
my-plugin/admin/src/index.js
// Create a hook in a plugin
export default {
register(app) {
app.createHook('My-PLUGIN/MY_HOOK');
}
}

my-other-plugin/admin/src/index.js
// Use the hook in another plugin
export default {
bootstrap(app) {
app.registerHook('My-PLUGIN/MY_HOOK', (...args) => {
console.log(args)

// important: return the mutated data
return args
});

app.registerPlugin({...})
}
}

预定义钩子

¥Predefined hooks

Strapi 包含一个预定义的 Admin/CM/pages/ListView/inject-column-in-table 钩子,可用于添加或改变 内容管理者 列表视图的列:

¥Strapi includes a predefined Admin/CM/pages/ListView/inject-column-in-table hook that can be used to add or mutate a column of the List View of the Content Manager:

runHookWaterfall(INJECT_COLUMN_IN_TABLE, {
displayedHeaders: ListFieldLayout[],
layout: ListFieldLayout,
});
interface ListFieldLayout {
/**

* The attribute data from the content-type's schema for the field
*/
attribute: Attribute.Any | { type: 'custom' };
/**

* Typically used by plugins to render a custom cell
*/
cellFormatter?: (
data: Document,
header: Omit<ListFieldLayout, 'cellFormatter'>,
{ collectionType, model }: { collectionType: string; model: string }
) => React.ReactNode;
label: string | MessageDescriptor;
/**

* the name of the attribute we use to display the actual name e.g. relations

* are just ids, so we use the mainField to display something meaninginful by

* looking at the target's schema
*/
mainField?: string;
name: string;
searchable?: boolean;
sortable?: boolean;
}

interface ListLayout {
layout: ListFieldLayout[];
components?: never;
metadatas: {
[K in keyof Contracts.ContentTypes.Metadatas]: Contracts.ContentTypes.Metadatas[K]['list'];
};
options: LayoutOptions;
settings: LayoutSettings;
}

type LayoutOptions = Schema['options'] & Schema['pluginOptions'] & object;

interface LayoutSettings extends Contracts.ContentTypes.Settings {
displayName?: string;
icon?: never;
}

Strapi 还包括一个 Admin/CM/pages/EditView/mutate-edit-view-layout 钩子,可用于改变 内容管理者 的编辑视图:

¥Strapi also includes a Admin/CM/pages/EditView/mutate-edit-view-layout hook that can be used to mutate the Edit View of the Content Manager:

interface EditLayout {
layout: Array<Array<EditFieldLayout[]>>;
components: {
[uid: string]: {
layout: Array<EditFieldLayout[]>;
settings: Contracts.Components.ComponentConfiguration['settings'] & {
displayName?: string;
icon?: string;
};
};
};
metadatas: {
[K in keyof Contracts.ContentTypes.Metadatas]: Contracts.ContentTypes.Metadatas[K]['edit'];
};
options: LayoutOptions;
settings: LayoutSettings;
}

interface EditFieldSharedProps extends Omit<InputProps, 'hint' | 'type'> {
hint?: string;
mainField?: string;
size: number;
unique?: boolean;
visible?: boolean;
}

/**

* Map over all the types in Attribute Types and use that to create a union of new types where the attribute type

* is under the property attribute and the type is under the property type.
*/
type EditFieldLayout = {
[K in Attribute.Kind]: EditFieldSharedProps & {
attribute: Extract<Attribute.Any, { type: K }>;
type: K;
};
}[Attribute.Kind];

type LayoutOptions = Schema['options'] & Schema['pluginOptions'] & object;

interface LayoutSettings extends Contracts.ContentTypes.Settings {
displayName?: string;
icon?: never;
}
✏️ 注意

EditViewLayoutListViewLayoutuseDocumentLayout 钩子的一部分(参见 源代码)。

¥EditViewLayout and ListViewLayout are parts of the useDocumentLayout hook (see source code).