Umi UI 插件开发
示例
index.js
// 普通的 umi 插件写法,新增 api.onUISocket 和 api.addUIPlugin 接口
export default api => {
// 处理 socket 通讯数据
api.onUISocket(({ action, send, log }) => {
// 通过 action 处理
// 处理完后 send 数据到客户端
send({ type, payload });
// 过程中的日志通过 log 打到客户端
log(`Adding block Foo/Bar...`);
});
// 添加编辑态的插件
api.addUIPlugin(require.resolve('./dist/client.umd'));
};
ui.js(通过 father-build 打包到 dist/ui.umd.js
)
// 这个文件打 umd 到 ./dist/client.umd.js,external react、react-dom 和 antd,用 father-build 很容易打出来
export default api => {
const {
// 调用服务端方法
callRemote,
} = api;
function Blocks() {
return <h1>Blocks</h1>;
}
// 添加 panel,类似 vscode 点击左边的 Icon 后切换 Panel
api.addPanel({
title: '区块管理',
icon: 'home',
path: '/blocks',
component: Blocks,
// 顶部右侧按钮
actions: [
{
title: '打开配置文件',
// antd Button type
type: 'default',
// 点击后的 action
action: {
type: '@@actions/openConfigFile',
payload: {
projectPath: api.currentProject.path,
}
},
onClick: () => {},
},
],
});
// 更多功能...
};
服务端接口
可访问 所有插件接口和属性,以下是几个与 UI 相关 API。
api.onUISocket
处理 socket 数据相关,比如:
api.onUISocket(({ type, payload }, { log, send, success, failure }) => {
if (type === 'config/fetch') {
send({ type: `${type}/success`, payload: getConfig() });
}
});
注:
- 按约定,如果客户端用
api.callRemote
调用服务端接口,处理完数据需send
加/success
或/failure
后缀的数据表示成功和失败。
send({ type, payload })
向客户端发送消息。
success(payload)
send({ type: `${type}/success` })
的快捷方式。
failure(payload)
send({ type: `${type}/failure` })
的快捷方式。
progress(payload)
send({ type: `${type}/progress` })
的快捷方式。
log(level, message)
在控制台和客户端同时打印日志。
示例:
log('info', 'abc');
log('error', 'abc');
api.addUIPlugin
注册 UI 插件,指向客户端文件。
api.addUIPlugin(require.resolve('./dist/ui'));
注:
- 文件需是
umd
格式(例如./dist/ui.umd.js
)
客户端接口
api.callRemote()
调服务端接口,并等待 type
加上 /success
或 /failure
消息的返回。若有进度的返回,可通过 onProgress
处理回调。
参数如下:
api.callRemote({
// 接口名称
type: string;
// 传入参数
payload: object;
// 监听服务端推送来的数据
onProgress: (data) => void;
// 是否建立长久连接
keep: boolean;
})
示例:
import React from 'react';
const { useState } = React;
// 组件 props api 从插件传入
export default (props) => {
const { api } = props;
const [progress, setProgress] = useState(0);
const handleClick = async () => {
await api.callRemote({
type: 'org.umi.plugin.bar.create',
payload: {
id: 'id',
},
onProgress: async (data) => {
useState(data);
}
})
}
return (
<div>
<button onClick={handleClick}>Click</button>
<p>progress: {progress}</p>
</div>
)
}
注:
callRemote
会自动带上lang
属性,供服务端区分语言- 有
keep
属性,则不会在 success 或 failure 后清除掉
api.listenRemote()
监听 socket
请求,有消息时通过 onMessage
处理回调。
返回一个 unlisten
函数,用于取消监听。
示例:
const unlisten = api.listenRemote({
// 接口名称
type: 'org.umi.plugin.foo',
onMessage: (data) => {
// 函数处理
}
});
// 组件卸载时可调用,取消监听
unlisten();
api.send()
发送消息到服务端。
api.addDashboard()
添加入口卡片到『总览』页
调用参数如下:
(dashboard: IDashboard | IDashboard[]) => void;
interface IDashboard {
/** card key 唯一标识,约定格式为:org.umi.dashboard.card.${key} */
key: string;
/** card 标题 */
title: ReactNode;
/** card 描述 */
description: ReactNode;
/** icon 图标 */
icon: ReactNode;
/** icon 图标背景色,默认为 #459BF7 */
backgroundColor?: string;
/** card body */
content: ReactNode | ReactNode[];
/** card 右侧区域扩展 */
right?: ReactNode;
/** 栅格,默认为 { xl: 6, sm: 12, lg: 12, xs: 24 } */
span?: Partial<{
/** default 6 */
xl: number;
/** default 12 */
sm: number;
/** default 12 */
lg: number;
/** default 24 */
xs: number;
}>;
}
示例:
import React from 'react';
import { ControlFilled } from '@ant-design/icons';
export default () => {
api.addDashboard({
key: 'org.umi.dashboard.card.testId',
title: '卡片标题',
description: '卡片描述',
icon: <ControlFilled />,
content: [
<a onClick={() => alert('部署成功')}>
一键部署
</a>,
],
});
}
api.addPanel()
添加客户端插件入口及路由,调用此方法会在 Umi UI 中增加一级菜单。
调用参数有:
api.addPanel({
// 插件路由
path: string;
// 组件
component: ReactNode;;
// 图标,同 antd icon
icon: IconType | string;
// 全局操作按钮,位于插件面板右上角
actions?: ReactNode | React.FC | {
// 标题
title: string;
// 按钮样式
type?: 'default' | 'primary';
// 与 callRemote 参数一致
action?: IAction;
// 额外的点击事件
onClick?: () => void;
}[];
// 实验室插件,开启后插件会加入到实验室中,默认 false
beta?: boolean;
});
示例:
// ui.(jsx|tsx)
import React from 'react';
import Template from './ui/index';
export default (api) => {
api.addPanel({
title: '插件模板',
path: '/plugin-bar',
icon: 'environment',
// api 透传至组件
component: () => <Template api={api} />,
});
};
添加插件全局操作区(动态修改全局操作区,参考 api.addLocales()
添加全局国际化信息。
例如:
添加国际化字段
// ui.(jsx|tsx)
import React from 'react';
import Template from './ui/index';
export default (api) => {
// 你也可以在顶部
// import zh from './your-locale/zh.js'
// import en from './your-locale/en.js'
// { 'zh-CN': zh, 'en-US': en }
api.addLocales({
'zh-CN': {
'org.sorrycc.react.name': '陈成',
},
'en-US': {
'org.sorrycc.react.name': 'chencheng',
},
});
};
api.intl.*
使用国际化,使用 api.getLocale()
返回当前语言,zh-CN
、en-US
等。
api.hooks.*
集成 UI 开发中常用 hooks,更多 API 见 [http://hooks.umijs.org/]。
例如:
import React from 'react';
export default (api) => {
const { useDebounceFn } = api.hooks;
const [value, setValue] = useState(0);
const { run } = useDebounceFn(() => {
setValue(value + 1);
}, 500);
const Component = (
<div>
<p style={{ marginTop: 16 }}> Clicked count: {value} </p>
<Button onClick={run}>Click fast!</Button>
</div>
)
api.addPanel({
title: '插件模板',
path: '/plugin-bar',
icon: 'environment',
component: Component,
});
};
api.showLogPanel()
打开 Umi UI 底部日志栏。
api.hideLogPanel()
隐藏 Umi UI 底部日志栏。
api.moment
与 moment 一致。
api.event
与 events 一致。
api.TwoColumnPanel
两栏布局组件
比如:
const { TwoColumnPanel } = api;
function Configuration() {
return (
<TwoColumnPanel
sections={[
{
// 访问 /${插件路由}?active=${key}
// 可定位到具体插件的具体面板
key?: 'basic',
title: '基本配置', description,
icon: '',
component: C1
},
{
key?: 'config',
title: 'umi-plugin-react 配置',
description,
icon: '',
component: C2
},
]}
/>
);
}
api.addPanel({
component: Configuration,
});
api.Terminal
终端命令行组件,基于 xterm.js@4.x。
参数如下:
interface ITerminalProps {
/** Terminal title */
title?: ReactNode;
className?: string;
terminalClassName?: string;
/** defaultValue in Terminal */
defaultValue?: string;
/** terminal init event */
onInit?: (ins: XTerminal, fitAddon: any) => void;
/** https://xtermjs.org/docs/api/terminal/interfaces/iterminaloptions/ */
config?: ITerminalOptions;
onResize?: (ins: XTerminal) => void;
[key: string]: any;
}
示例:
使用终端实例,调用日志输出函数:
import React, { useState } from 'react'
export default (api) => {
const { Terminal } = api;
function Component() {
let terminal;
const handleClick = () => {
terminal.write('Hello World');
}
return (
<div>
<Terminal
title="插件日志"
onInit={ins => {
terminal = ins;
}}
/>
<button onClick={handleClick}>开始</button>
</div>
);
}
api.addPanel({
component: Component,
});
}
终端更多用法见 文档
api.DirectoryForm
目录选择表单控件
参数如下:
interface IDirectoryForm {
/** path, default */
value?: string;
onChange?: (value: string) => void;
}
示例:
import React from 'react';
import { Form } from 'antd';
export default () => {
const [form] = Form.useForm();
return (
<Form
form={form}
onFinish={values => {
console.log('values', values)
}}
initialValues={{
baseDir: cwd,
}}
>
<Form.Item
label={null}
name="baseDir"
rules={[
{
validator: async (rule, value) => {
await validateDirPath(value);
},
},
]}
>
<DirectoryForm />
</Form.Item>
</Form>
)
}
api.StepForm
步骤表单组件
使用示例:
<StepForm onFinish={handleSubmit} className={stepCls}>
<StepForm.StepItem title="a-form">
<Form>
<Form.Item name="a">
<input />
</Form.Item>
</Form>
</StepForm.StepItem>
<StepForm.StepItem title="b-form">
<Form>
<Form.Item name="b">
<input />
</Form.Item>
</Form>
</StepForm.StepItem>
</StepForm>
api.Field
配置表单组件,结合 antd 4.x 一起使用,简化表单组件,使用配置式生成表单。
api.Field
参数如下:
interface IFieldProps {
/** 表单类型 */
/** 具体类型有:"string" | "boolean" | "object" | "string[]" | "object[]" | "list" | "textarea" | "any" */
type: IConfigTypes;
/** 表单 字段名,通过 `.` 来确定字段之间的联动关系 */
name: string;
/** 可选列表,只用在 type 为 object */
defaultValue?: IValue;
/** 主要用于数组表单类型,提供可选值列表 */
options?: string[];
/** antd 4.x form 实例 */
form: object;
/** antd label, 如果是 object,则使用内置的 <Label /> 组件 */
/** object 参数有 { title: string, description: string, link?: string } */
label: string | ReactNode | IFieldLabel;
/** 控件大小, 默认是 default */
size?: 'default' | 'small' | 'large';
/** 其它类型与 Form.Item 一致 */
[key: string]: any;
}
interface IFieldLabel {
/** label title */
title: string;
/** label description */
description: string;
/** description detail link */
link: string;
}
例如,联动示例 :
import { Form } from 'antd'
const { TwoColumnPanel } = api;
function Configuration() {
const [form] = Form.useForm();
return (
<Form
form={form}
onFinish={values => {
console.log('valuesvalues', values);
}}
initialValues={{
'parent.child2': ['**/a.js', '**/b.js'],
'parent.child3': '<script>alert("Hello")</script>',
'parent2.child': 'Method1',
}}
>
<Field form={form} name="parent" label="SpeedUp-boolean" type="boolean" />
<Field form={form} name="parent.child" label="Speed-string" type="string" />
<Field
form={form}
name="parent.child2"
label="Speed-string[]"
type="string[]"
/>
<Field form={form} name="parent.child3" label="Speed-textarea" type="textarea" />
<Field form={form} name="parent.child4" label="Speed-any" type="any" />
<Field form={form} name="parent2" label="Config-boolean" type="boolean" />
<Field
form={form}
name="parent2.child"
label="Config-list"
type="list"
options={['Method1', 'Method2']}
/>
<Field
form={form}
name="parent2.child2"
label="Config-list"
type="object"
options={['Target1', 'Target2']}
/>
<Form.Item shouldUpdate>
{({ getFieldsValue }) => <pre>{JSON.stringify(getFieldsValue(), null, 2)}</pre>}
</Form.Item>
<Button htmlType="submit">Submit</Button>
</Form>
);
}
api.addPanel({
component: Configuration,
});
api.ConfigForm
配置表单页面,对 api.notify()
调用 Umi UI 通知栏,若用户停留在当前浏览器窗口,通知栏样式为 antd Notification,否则为系统原生通知栏。
传入参数:
{
title: string;
message: string;
/** notify type, default info */
type?: 'error' | 'info' | 'warning' | 'success';
subtitle?: string;
/** URL to open on click */
open?: string;
/**
* The amount of seconds before the notification closes.
* Takes precedence over wait if both are defined.
*/
timeout?: number;
}
比如:
const { notify } = api;
notify({
/** 前提已经调用过 api.addLocales 添加 key */
title: 'org.umi.ui.blocks.notify.title',
message: '可以不使用国际化',
type: 'success',
});
api.redirect()
项目详情内的路由跳转,在不同插件之间进行跳转。
示例:
const { redirect } = api;
export default () => (
<Button
onClick={() => redirect('/project/select')}
>
跳转到项目列表
</Button>
);
api.currentProject
获取当前项目基本信息,信息包括:
{
// KEY
key?: string;
// 应用名
name?: string;
// 应用路径
path?: string;
}
示例:
const { currentProject } = api;
export default () => (
<div>
<p>当前应用名:{currentProject.name}</p>
<p>当前路径:{currentProject.path}</p>
</div>
);
api.debug()
debug
API。
调试插件的时候,localStorage
修改为 debug: umiui:UIPlugin*
。就可以看到所有插件的 debug 信息。
使用(以配置管理插件为例):
export default () => {
const { debug } = api;
// 声明插件 namespace
const _log = api.debug.extend('configuration');
_log('Hello UI Configuration');
}
如果需要调试 mini
版日志,再增加一个 debugMini: umiui
。
不建议在插件里使用
console.log
调用。
api.getCwd()
获取 Umi UI 启动时的路径。
示例:
const cwd = await api.getCwd();
// "/private/tmp/xxxx"
api.getSharedDataDir()
获取当前项目的临时目录。
示例:
const dir = await api.getSharedDataDir();
// "/Users/xxxxx/.umi/ui/shared-data/5bc6bd"
api.detectLanguage()
获取当前项目的语言类型。
示例:
const language = await api.detectLanguage();
// "JavaScript" | "TypeScript"
api.detectNpmClients()
获取当前项目可能在用的 npm 客户端数组。
示例:
const npmClients = await api.detectNpmClients();
// ["tnpm", "ayarn", "npm", "yarn"]
api.addConfigSection()
添加配置区块。
示例:
api.addConfigSection({
key: 'umi-plugin-react',
title: 'umi-plugin-react 配置',
description: '配置 dva、antd、按需加载、国际化等',
icon: (
<img
src="https://img.alicdn.com/tfs/TB1aqdSeEY1gK0jSZFMXXaWcVXa-64-64.png"
width={32}
height={32}
/>
),
component: () => <div>TODO</div>,
});
api.isMini() / api.mini
获取当前环境是否是 Umi UI mini。
示例:
const isMini = api.isMini(); // true / false
// or const isMini = api.mini
api.showMini()
打开 Umi UI mini 窗口(mini 环境下启用)。
api.hideMini()
关闭 Umi UI mini 窗口(mini 环境下启用)。
api.setActionPanel()
运行时动态修改右上角全局操作区,与 React 中 setState
类似。
参数如下:
示例:
// ui.(jsx|tsx)
import React from 'react';
export default (api) => {
// init Action
const ActionComp = () => (
<Input />
)
api.addPanel({
title: '插件模板',
path: '/plugin-bar',
icon: 'environment',
actions: [
ActionComp
]
// api 透传至组件
component: () => {
const handleClick = () => {
// 类似于 React 中 setState
api.setActionPanel((actions) => {
return [
...actions,
() => <Button onClick={() => alert('hello')}>New Button</Button>
]
})
}}
}
return (
<Button onClick={handleClick}>
添加一个新操作按钮
</Button>
)
},
});
};