mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-30 02:48:55 +08:00
parent
eaa58a81d3
commit
6706445183
@ -29,7 +29,7 @@ order: 1
|
||||
}
|
||||
```
|
||||
|
||||
- `name`: **必填属性**,标识表单数据域中,当前表单项值的`key`
|
||||
- `name`: **必填属性**,标识表单数据域中,当前表单项值的`key`,这个 name 可以是深层结构,比如 `aa.bb`
|
||||
- `type`: **必填属性**,标识表单项类型
|
||||
- `label`: 标识表单项的标签
|
||||
|
||||
|
@ -46,7 +46,7 @@ order: 10
|
||||
|
||||
渲染后页面如下:
|
||||
|
||||
```schema:height="200"
|
||||
```schema:height="50"
|
||||
{
|
||||
"type": "page",
|
||||
"initApi": "https://houtai.baidu.com/api/mock2/page/initData",
|
||||
@ -81,7 +81,7 @@ order: 10
|
||||
|
||||
毫无疑问,`${text}`将会解析为空白文本,最终渲染的文本是 `Hello`
|
||||
|
||||
```schema:height="200"
|
||||
```schema:height="50"
|
||||
{
|
||||
"type": "page",
|
||||
"body": "Hello ${text}"
|
||||
@ -92,7 +92,7 @@ order: 10
|
||||
|
||||
再观察下面这段配置:
|
||||
|
||||
```schema:height="200"
|
||||
```schema:height="50"
|
||||
{
|
||||
"data": {
|
||||
"text": "World!"
|
||||
@ -106,7 +106,7 @@ order: 10
|
||||
|
||||
相信你可能已经猜到,**组件的`data`属性值是数据域的一种形式**,实际上当我们没有显式的配置数据域时,可以假想成这样:
|
||||
|
||||
```schema:height="200"
|
||||
```schema:height="50"
|
||||
{
|
||||
"data": {}, // 空的数据域
|
||||
"type": "page",
|
||||
@ -114,8 +114,6 @@ order: 10
|
||||
}
|
||||
```
|
||||
|
||||
> amis 中大部分组件都具有数据域。
|
||||
>
|
||||
> 而前面我们知道 amis 的特性之一是基于组件树,因此自然数据域也会形成类似于树型结构,如何来处理这些数据域之间的联系呢,这就是我们马上要介绍到的 **[数据链](./datascope-and-datachain#%E6%95%B0%E6%8D%AE%E9%93%BE)**
|
||||
|
||||
## 数据链
|
||||
@ -132,7 +130,7 @@ order: 10
|
||||
|
||||
继续来看这个例子:
|
||||
|
||||
```schema:height="200"
|
||||
```schema:height="70"
|
||||
{
|
||||
"type": "page",
|
||||
"data": {
|
||||
@ -193,6 +191,67 @@ page
|
||||
|
||||
> **注意:** 当前例子中,对数据域中数据的获取使用的是 **\${xxx}** 模板语法,但是在不同的组件配置项中,获取数据的语法会有差异,我们会在后续的[模板](./template)和[表达式章节](./expression)中一一介绍。
|
||||
|
||||
### 常见误解
|
||||
|
||||
需要注意,只有少数几个容器组件会创建新的数据域,除了最顶层的 Page,还有 CRUD、Dialog、IFrame、Form、Serice 等。
|
||||
|
||||
常见的错误写法是给容器组件加 data 属性,比如:
|
||||
|
||||
```schema:height="70"
|
||||
{
|
||||
"type": "page",
|
||||
"data": {
|
||||
"name": "zhangsan"
|
||||
},
|
||||
"body": [
|
||||
{
|
||||
"type": "tpl",
|
||||
"tpl": "my name is ${name}"
|
||||
},
|
||||
{
|
||||
"type": "container",
|
||||
"data": {
|
||||
"name": "lisi"
|
||||
},
|
||||
"body": {
|
||||
"type": "tpl",
|
||||
"tpl": "my name is ${name}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
这样是不会生效的,正确的做法是使用 Service 包裹一层,如下所示
|
||||
|
||||
```schema:height="70"
|
||||
{
|
||||
"type": "page",
|
||||
"data": {
|
||||
"name": "zhangsan"
|
||||
},
|
||||
"body": [
|
||||
{
|
||||
"type": "tpl",
|
||||
"tpl": "my name is ${name}"
|
||||
},
|
||||
{
|
||||
"type": "service",
|
||||
"data": {
|
||||
"name": "lisi"
|
||||
},
|
||||
"body": {
|
||||
"type": "container",
|
||||
"body": {
|
||||
"type": "tpl",
|
||||
"tpl": "my name is ${name}"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 初始化数据域
|
||||
|
||||
通过上面的介绍你可能发现,初始化数据域有两种方式:
|
||||
|
147
docs/extend/addon.md
Normal file
147
docs/extend/addon.md
Normal file
@ -0,0 +1,147 @@
|
||||
---
|
||||
title: 扩展现有组件
|
||||
---
|
||||
|
||||
除了新增组件,在 amis 中还能扩展和修改现有组件。
|
||||
|
||||
## 事件扩展
|
||||
|
||||
amis 默认会将配置项剩余参数都作为 React 的 props 传入对应标签,使得可以添加自己的自定义事件,比如 下面的例子
|
||||
|
||||
```javascript
|
||||
let amis = amisRequire('amis/embed');
|
||||
let amisLib = amisRequire('amis');
|
||||
let amisScoped = amis.embed('#root', {
|
||||
type: 'page',
|
||||
title: '表单页面',
|
||||
body: {
|
||||
type: 'form',
|
||||
mode: 'horizontal',
|
||||
api: '/saveForm',
|
||||
controls: [
|
||||
{
|
||||
type: 'button',
|
||||
label: '按钮',
|
||||
onClick: () => {
|
||||
amisLib.toast.info('消息通知');
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
这样就能在点击按钮的时候执行自定义代码了。
|
||||
|
||||
## 同时支持多种类型编辑
|
||||
|
||||
在表单编辑中,每个 name 一般对应一种类型,如果这个 name 有多种类型,比如下面的例子中 id 的值有可能是字符串,也有可能是数字,但 type 只能设置为一种类型,这种情况如何处理?
|
||||
|
||||
```schema:height="200" scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
"mode": "horizontal",
|
||||
"controls": [
|
||||
{
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"label": "id"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
有两种方式:
|
||||
|
||||
### 使用另一个名称作为状态
|
||||
|
||||
```schema:height="250" scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
"mode": "horizontal",
|
||||
"controls": [
|
||||
{
|
||||
"name": "idIsNumber",
|
||||
"type": "switch",
|
||||
"label": "id 是数字类型"
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"label": "id",
|
||||
"hiddenOn": "data.idIsNumber"
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"type": "number",
|
||||
"label": "id",
|
||||
"visibleOn": "data.idIsNumber"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
可以看到在一个 form 中可以有两个 name 相同的组件,通过 hiddenOn 或 visibleOn 来控制同时只显示一个。
|
||||
|
||||
### 使用 PipeIn/PipeOut 方法
|
||||
|
||||
如果不想增加一个新的 name,在 JS SDK 或 React 还有更高级的处理方法,可以增加一个 name 同样为 id 的 switch,实现 PipeIn/PipeOut 函数来进行自动识别,下面是个示例:
|
||||
|
||||
```javascript
|
||||
let amis = amisRequire('amis/embed');
|
||||
let amisScoped = amis.embed('#root', {
|
||||
type: 'page',
|
||||
title: '表单页面',
|
||||
// 可以通过去掉下面的注释来测试
|
||||
// data: {
|
||||
// id: 1
|
||||
// },
|
||||
body: {
|
||||
type: 'form',
|
||||
mode: 'horizontal',
|
||||
api: '/saveForm',
|
||||
controls: [
|
||||
{
|
||||
type: 'switch',
|
||||
label: 'id 是数字',
|
||||
name: 'id',
|
||||
// pipeIn 返回的应该是这个组件所需的值,比如 switch 的返回值就应该是 true 或 false
|
||||
// 这里的 value 就是初始值,如果不设置将会是 undefined
|
||||
pipeIn: (value, data) => {
|
||||
if (typeof value === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
return typeof value !== 'string';
|
||||
},
|
||||
// 这里的 value 是点击 switch 之后的值,比如打开就是 true,关闭就是 false
|
||||
pipeOut: (value, oldValue, data) => {
|
||||
if (value) {
|
||||
return 1; // 切换到数字之后的默认值
|
||||
} else {
|
||||
return 'id1'; // 关闭之后的默认值
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'id',
|
||||
type: 'text',
|
||||
label: 'id',
|
||||
visibleOn:
|
||||
'typeof data.id === "undefined" || typeof data.id === "string"'
|
||||
},
|
||||
{
|
||||
name: 'id',
|
||||
type: 'number',
|
||||
label: 'id',
|
||||
visibleOn: 'typeof data.id === "number"'
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
不过这种写法的复杂度较高
|
||||
|
||||
## 修改组件标签
|
||||
|
||||
有些组件可以设置 `wrapperComponent`,比如 Form 下默认使用 form 标签,在浏览器中会自带回车提交功能,如果想去掉这个功能,可以将 `wrapperComponent` 设置为 `div`。
|
@ -1,123 +1,7 @@
|
||||
---
|
||||
title: 自定义
|
||||
title: 自定义组件 - React
|
||||
---
|
||||
|
||||
如果默认的组件不能满足需求,可以通过自定义组件来进行扩展,根据不同使用方式,一共有四种方法:
|
||||
|
||||
1. JS SDK
|
||||
1. 使用 custom 组件临时扩展,但不支持组件复用。
|
||||
2. JS SDK 注册组件,支持组件复用。
|
||||
2. React
|
||||
1. React 临时扩展,适合无需复用的组件。
|
||||
2. React 注册自定义类型,支持组件复用。
|
||||
|
||||
## 使用 custom 组件临时扩展
|
||||
|
||||
基于 custom 组件可以直接在 amis 配置实现自定义功能,它的支持面最广,是唯一支持在可视化编辑器中使用的方法。
|
||||
|
||||
使用 custom 组件类似如下写法:
|
||||
|
||||
```javascript
|
||||
{
|
||||
label: '使用 custom 组件',
|
||||
name: 'username', // 如果要放在 form 中,需要设置 name,onChange 将会设置这个值
|
||||
type: 'custom',
|
||||
// onMount 将会在组件创建时执行,默认会创建一个空 div 标签,也可以设置 inline: true 来创建 span 标签
|
||||
// dom 是 dom 节点,value 是初始数据,比如表单 name 初始拿到的数据,onChange 只有在表单下才会有
|
||||
onMount: (dom, value, onChange) => {
|
||||
const button = document.createElement('button');
|
||||
button.innerText = '点击修改姓名';
|
||||
button.onclick = event => {
|
||||
onChange('new name');
|
||||
event.preventDefault();
|
||||
};
|
||||
dom.appendChild(button);
|
||||
},
|
||||
// onUpdate 将会在数据更新时被调用
|
||||
// dom 是 dom 节点、data 将包含表单所有数据,prevData 是之前表单的所有数据
|
||||
onUpdate: (dom, data, prevData) => {
|
||||
console.log('数据有变化', data);
|
||||
},
|
||||
// onUnmount 将会在组件被销毁的时候调用,用于清理资源
|
||||
onUnmount:() => {
|
||||
console.log('组件被销毁');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
注意上面的代码用到了 JavaScript 函数,无法转成 json 格式,但这三个函数还支持字符串形式,上面的代码可以改成如下形式,这样就能在可视化编辑器里支持自定义组件了:
|
||||
|
||||
```schema:height="330" scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
"title": "custom 组件",
|
||||
"controls": [
|
||||
{
|
||||
"type": "text",
|
||||
"name": "username",
|
||||
"label": "姓名"
|
||||
},
|
||||
{
|
||||
"name": "username",
|
||||
"type": "custom",
|
||||
"label": "自定义组件",
|
||||
"onMount": "const button = document.createElement('button'); button.innerText = '点击修改姓名'; button.onclick = event => { onChange('new name'); event.preventDefault(); }; dom.appendChild(button);"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
注意上面的例子中两个组件的 name 是一样的,这是为了方便示例,因为 amis 中的数据是双向绑定的,因此 onChange 修改自身的时候,另一个「姓名」输入框由于 name 一样,也会同步更新。
|
||||
|
||||
关于 custom 组件的更多属性请参考「[Custom 组件](../components/custom)」。
|
||||
|
||||
## JS SDK 注册组件
|
||||
|
||||
amis 组件都是基于 React 的,所以需要使用一个简单的 React 组件来注册,可以是函数组件也可以是类组件,下面以函数组件为例,将[快速开始](getting-started)中的代码替换成如下示例:
|
||||
|
||||
```javascript
|
||||
(function () {
|
||||
let amis = amisRequire('amis/embed');
|
||||
let amisLib = amisRequire('amis');
|
||||
let React = amisRequire('react');
|
||||
|
||||
// 自定义组件
|
||||
function CustomComponent() {
|
||||
let dom = React.useRef(null);
|
||||
React.useEffect(function () {
|
||||
// 从这里开始写自定义代码,dom.current 就是新创建的 dom 节点
|
||||
// 可以基于这个 dom 节点对接任意 JavaScript 框架,比如 jQuery/Vue 等
|
||||
dom.current.innerHTML = 'custom';
|
||||
});
|
||||
return React.createElement('div', {
|
||||
ref: dom
|
||||
});
|
||||
}
|
||||
|
||||
//注册自定义组件,请参考后续对工作原理的介绍
|
||||
amisLib.Renderer({
|
||||
test: /(^|\/)my-custom/
|
||||
})(CustomComponent);
|
||||
|
||||
let amisScoped = amis.embed('#root', {
|
||||
type: 'page',
|
||||
title: '表单页面',
|
||||
body: {
|
||||
type: 'form',
|
||||
mode: 'horizontal',
|
||||
api: '/saveForm',
|
||||
controls: [
|
||||
{
|
||||
label: 'Name',
|
||||
type: 'my-custom', // 注意这个的 type 对应之前注册的 test
|
||||
name: 'custom'
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
})();
|
||||
```
|
||||
|
||||
## React 临时扩展
|
||||
|
||||
amis 的配置最终会转成 React 组件来执行,所以如果只是想在某个配置中加入定制功能,可以直接在这个 JSON 配置里写 React 代码,比如下面这个例子:
|
||||
@ -163,157 +47,7 @@ amis 的配置最终会转成 React 组件来执行,所以如果只是想在
|
||||
|
||||
## React 注册自定义类型
|
||||
|
||||
注册自定义类型需要了解 amis 的工作原理。
|
||||
|
||||
### 工作原理
|
||||
|
||||
amis 的渲染过程是将 `json` 转成对应的 React 组件。先通过 `json` 的 type 找到对应的 `Component` 然后,然后把其他属性作为 `props` 传递过去完成渲染。
|
||||
|
||||
拿一个表单页面来说,如果用 React 组件开发一般长这样。
|
||||
|
||||
```jsx
|
||||
<Page title="页面标题" subTitle="副标题">
|
||||
<Form
|
||||
title="用户登录"
|
||||
controls={[
|
||||
{
|
||||
type: 'text',
|
||||
name: 'username',
|
||||
label: '用户名'
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</Page>
|
||||
```
|
||||
|
||||
把以上配置方式换成 amis JSON, 则是:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "page",
|
||||
"title": "页面标题",
|
||||
"subTitle": "副标题",
|
||||
"body": {
|
||||
"type": "form",
|
||||
"title": "用户登录",
|
||||
"controls": [
|
||||
{
|
||||
"type": "text",
|
||||
"name": "username",
|
||||
"label": "用户名"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
那么,amis 是如何将 JSON 转成组件的呢?直接根据节点的 type 去跟组件一一对应?这样会重名,比如在表格里面展示的类型 `text` 跟表单里面的 `text` 是完全不一样的,一个负责展示,一个却负责输入。所以说一个节点要被什么组件渲染,还需要携带上下文(context)信息。
|
||||
|
||||
如何携带上下文(context)信息?amis 中是用节点的路径(path)来作为上下文信息。从上面的例子来看,一共有三个节点,path 信息分别是。
|
||||
|
||||
- `page` 页面节点
|
||||
- `page/body/form` 表单节点
|
||||
- `page/body/form/controls/0/text` 文本框节点。
|
||||
|
||||
根据 path 的信息就能很容易注册组件跟节点对应了。
|
||||
|
||||
Page 组件的示例代码
|
||||
|
||||
```jsx
|
||||
import * as React from 'react';
|
||||
import {Renderer} from 'amis';
|
||||
|
||||
@Renderer({
|
||||
test: /^page$/
|
||||
// ... 其他信息隐藏了
|
||||
})
|
||||
export class PageRenderer extends React.Component {
|
||||
// ... 其他信息隐藏了
|
||||
render() {
|
||||
const {
|
||||
title,
|
||||
body,
|
||||
render // 用来渲染孩子节点,如果当前是叶子节点则可以忽略。
|
||||
} = this.props;
|
||||
return (
|
||||
<div className="page">
|
||||
<h1>{title}</h1>
|
||||
<div className="body-container">
|
||||
{render('body', body) /*渲染孩子节点*/}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果不支持 Decorators 语法也可以使用如下写法
|
||||
export Renderer({
|
||||
test: /^page$/
|
||||
})(class PageRenderer extends React.Component {
|
||||
render() {
|
||||
// ...同上
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Form 组件的示例代码
|
||||
|
||||
```jsx
|
||||
@Renderer({
|
||||
test: /(^|\/)form$/
|
||||
// ... 其他信息隐藏了
|
||||
})
|
||||
export class FormRenderer extends React.Component {
|
||||
// ... 其他信息隐藏了
|
||||
render() {
|
||||
const {
|
||||
title,
|
||||
controls,
|
||||
render // 用来渲染孩子节点,如果当前是叶子节点则可以忽略。
|
||||
} = this.props;
|
||||
return (
|
||||
<form className="form">
|
||||
{controls.map((control, index) => (
|
||||
<div className="form-item" key={index}>
|
||||
{render(`${index}/control`, control)}
|
||||
</div>
|
||||
))}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Text 组件的示例代码
|
||||
|
||||
```jsx
|
||||
@Renderer({
|
||||
test: /(^|\/)form(?:\/\d+)?\/control(?\/\d+)?\/text$/
|
||||
// ... 其他信息隐藏了
|
||||
})
|
||||
export class FormItemTextRenderer extends React.Component {
|
||||
// ... 其他信息隐藏了
|
||||
render() {
|
||||
const {
|
||||
label,
|
||||
name,
|
||||
onChange
|
||||
} = this.props;
|
||||
return (
|
||||
<div className="form-group">
|
||||
<label>{label}<label>
|
||||
<input type="text" onChange={(e) => onChange(e.currentTarget.value)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
那么渲染过程就是根据节点 path 信息,跟组件池中的组件 `test` (检测) 信息做匹配,如果命中,则把当前节点转给对应组件渲染,节点中其他属性将作为目标组件的 props。需要注意的是,如果是容器组件,比如以上例子中的 `page` 组件,从 props 中拿到的 `body` 是一个子节点,由于节点类型是不固定,由使用者决定,所以不能直接完成渲染,所以交给属性中下发的 `render` 方法去完成渲染,`{render('body', body)}`,他的工作就是拿子节点的 path 信息去组件池里面找到对应的渲染器,然后交给对应组件去完成渲染。
|
||||
|
||||
### 编写自定义组件
|
||||
|
||||
了解了基本原理后,来看个简单的例子:
|
||||
首先需要了解「[基本原理](internal)」,了解了基本原理后,来看个简单的例子:
|
||||
|
||||
```jsx
|
||||
import * as React from 'react';
|
||||
@ -330,6 +64,14 @@ class CustomRenderer extends React.Component {
|
||||
}
|
||||
```
|
||||
|
||||
> 上面这个语法需要开启 Decorator 功能,如果不支持,可以改成如下写法
|
||||
|
||||
```javascript
|
||||
Renderer({
|
||||
test: /(^|\/)my\-renderer$/
|
||||
})(CustomRenderer);
|
||||
```
|
||||
|
||||
有了以上这段代码后,就可以这样使用了。
|
||||
|
||||
```json
|
141
docs/extend/custom-sdk.md
Normal file
141
docs/extend/custom-sdk.md
Normal file
@ -0,0 +1,141 @@
|
||||
---
|
||||
title: 自定义组件 - SDK
|
||||
---
|
||||
|
||||
## 使用 custom 组件临时扩展
|
||||
|
||||
基于 custom 组件可以直接在 amis 配置实现自定义功能,它的支持面最广,是唯一支持在可视化编辑器中使用的方法。
|
||||
|
||||
使用 custom 组件类似如下写法:
|
||||
|
||||
```javascript
|
||||
{
|
||||
label: '使用 custom 组件',
|
||||
name: 'username', // 如果要放在 form 中,需要设置 name,onChange 将会设置这个值
|
||||
type: 'custom',
|
||||
// onMount 将会在组件创建时执行,默认会创建一个空 div 标签,也可以设置 inline: true 来创建 span 标签
|
||||
// dom 是 dom 节点,value 是初始数据,比如表单 name 初始拿到的数据,onChange 只有在表单下才会有
|
||||
onMount: (dom, value, onChange) => {
|
||||
const button = document.createElement('button');
|
||||
button.innerText = '点击修改姓名';
|
||||
button.onclick = event => {
|
||||
onChange('new name');
|
||||
event.preventDefault();
|
||||
};
|
||||
dom.appendChild(button);
|
||||
},
|
||||
// onUpdate 将会在数据更新时被调用
|
||||
// dom 是 dom 节点、data 将包含表单所有数据,prevData 是之前表单的所有数据
|
||||
onUpdate: (dom, data, prevData) => {
|
||||
console.log('数据有变化', data);
|
||||
},
|
||||
// onUnmount 将会在组件被销毁的时候调用,用于清理资源
|
||||
onUnmount:() => {
|
||||
console.log('组件被销毁');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
注意上面的代码用到了 JavaScript 函数,无法转成 json 格式,但这三个函数还支持字符串形式,上面的代码可以改成如下形式,这样就能在可视化编辑器里支持自定义组件了:
|
||||
|
||||
```schema:height="330" scope="body"
|
||||
{
|
||||
"type": "form",
|
||||
"title": "custom 组件",
|
||||
"controls": [
|
||||
{
|
||||
"type": "text",
|
||||
"name": "username",
|
||||
"label": "姓名"
|
||||
},
|
||||
{
|
||||
"name": "username",
|
||||
"type": "custom",
|
||||
"label": "自定义组件",
|
||||
"onMount": "const button = document.createElement('button'); button.innerText = '点击修改姓名'; button.onclick = event => { onChange('new name'); event.preventDefault(); }; dom.appendChild(button);"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
注意上面的例子中两个组件的 name 是一样的,这是为了方便示例,因为 amis 中的数据是双向绑定的,因此 onChange 修改自身的时候,另一个「姓名」输入框由于 name 一样,也会同步更新。
|
||||
|
||||
关于 custom 组件的更多属性请参考「[Custom 组件](../components/custom)」。
|
||||
|
||||
## JS SDK 注册组件
|
||||
|
||||
amis 组件都是基于 React 的,所以需要使用一个简单的 React 组件来注册,可以是函数组件也可以是类组件,下面以函数组件为例,将[快速开始](../start/getting-started)中的代码替换成如下示例:
|
||||
|
||||
```javascript
|
||||
let amis = amisRequire('amis/embed');
|
||||
let amisLib = amisRequire('amis');
|
||||
let React = amisRequire('react');
|
||||
|
||||
// 自定义组件,props 中可以拿到配置中的所有参数,比如 props.label 是 'Name'
|
||||
function CustomComponent(props) {
|
||||
let dom = React.useRef(null);
|
||||
React.useEffect(function () {
|
||||
// 从这里开始写自定义代码,dom.current 就是新创建的 dom 节点
|
||||
// 可以基于这个 dom 节点对接任意 JavaScript 框架,比如 jQuery/Vue 等
|
||||
dom.current.innerHTML = 'custom';
|
||||
// 而 props 中能拿到这个
|
||||
});
|
||||
return React.createElement('div', {
|
||||
ref: dom
|
||||
});
|
||||
}
|
||||
|
||||
//注册自定义组件,请参考后续对工作原理的介绍
|
||||
amisLib.Renderer({
|
||||
test: /(^|\/)my-custom/
|
||||
})(CustomComponent);
|
||||
|
||||
let amisScoped = amis.embed('#root', {
|
||||
type: 'page',
|
||||
title: '表单页面',
|
||||
body: {
|
||||
type: 'form',
|
||||
mode: 'horizontal',
|
||||
api: '/saveForm',
|
||||
controls: [
|
||||
{
|
||||
label: 'Name',
|
||||
type: 'my-custom', // 注意这个的 type 对应之前注册的 test
|
||||
name: 'custom'
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 示例:引入 Element UI
|
||||
|
||||
首先在页面中加入 Element UI 所需的依赖
|
||||
|
||||
```html
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"
|
||||
/>
|
||||
<script src="https://unpkg.com/vue/dist/vue.js"></script>
|
||||
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
|
||||
```
|
||||
|
||||
然后将前面的 `React.useEffect` 改成如下即可:
|
||||
|
||||
```javascript
|
||||
React.useEffect(function () {
|
||||
dom.current.innerHTML = `
|
||||
<el-button @click="visible = true">Button</el-button>
|
||||
<el-dialog :visible.sync="visible" title="Hello world">
|
||||
<p>Try Element</p>
|
||||
</el-dialog>
|
||||
`;
|
||||
new Vue({
|
||||
el: dom.current,
|
||||
data: function () {
|
||||
return {visible: false};
|
||||
}
|
||||
});
|
||||
});
|
||||
```
|
83
docs/extend/i18n.md
Normal file
83
docs/extend/i18n.md
Normal file
@ -0,0 +1,83 @@
|
||||
---
|
||||
title: 多语言
|
||||
---
|
||||
|
||||
amis 内置对英文的支持,同时你也可以扩展其他语言。
|
||||
|
||||
## JS SDK
|
||||
|
||||
从 1.1.0 版本开始已经自带英文翻译,所以只需要在 props 里设置 locale 即可。
|
||||
|
||||
```javascript
|
||||
let amisScoped = amis.embed(
|
||||
'#root',
|
||||
{
|
||||
type: 'page',
|
||||
title: '表单页面',
|
||||
body: {
|
||||
type: 'form',
|
||||
mode: 'horizontal',
|
||||
api: '/saveForm',
|
||||
controls: [
|
||||
{
|
||||
label: 'Name',
|
||||
type: 'text',
|
||||
name: 'name'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
locale: 'en'
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
## React
|
||||
|
||||
React 中没有内置英文版本,需要自己 import,使用如下方法:
|
||||
|
||||
```javascript
|
||||
import 'amis/lib/locale/en';
|
||||
```
|
||||
|
||||
在渲染 amis 组件的时候设置 locale 为 en
|
||||
|
||||
```javascript
|
||||
{
|
||||
renderAmis(
|
||||
{
|
||||
type: 'page',
|
||||
title: '简单页面',
|
||||
body: '内容'
|
||||
},
|
||||
{
|
||||
locale: 'en'
|
||||
}
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## 扩展其它语言
|
||||
|
||||
如果想扩展其他语言,首先参考 `https://github.com/baidu/amis/blob/master/src/locale/en.ts` 文件,了解需要翻译哪些文字,以中文为 key,然后参考后面的示例注册新语言,未翻译的文字都将使用默认语言,即中文。
|
||||
|
||||
> 目前这种方式将会在未来修改,为了支持更多语言而不再使用中文为 key
|
||||
|
||||
### JS SDK 扩展方法
|
||||
|
||||
```javascript
|
||||
let amisLib = amisRequire('amis');
|
||||
amisLib.registerLocale('jp', {
|
||||
提交: '送信'
|
||||
});
|
||||
```
|
||||
|
||||
### React 扩展方法
|
||||
|
||||
```javascript
|
||||
import {registerLocale} from 'amis';
|
||||
registerLocale('jp', {
|
||||
提交: '送信'
|
||||
});
|
||||
```
|
151
docs/extend/internal.md
Normal file
151
docs/extend/internal.md
Normal file
@ -0,0 +1,151 @@
|
||||
---
|
||||
title: 工作原理
|
||||
---
|
||||
|
||||
实现自定义类型需要了解 amis 的工作原理。
|
||||
|
||||
## 工作原理
|
||||
|
||||
amis 的渲染过程是将 `json` 转成对应的 React 组件。先通过 `json` 的 type 找到对应的 `Component` 然后,然后把其他属性作为 `props` 传递过去完成渲染。
|
||||
|
||||
拿一个表单页面来说,如果用 React 组件开发一般长这样。
|
||||
|
||||
```jsx
|
||||
<Page title="页面标题" subTitle="副标题">
|
||||
<Form
|
||||
title="用户登录"
|
||||
controls={[
|
||||
{
|
||||
type: 'text',
|
||||
name: 'username',
|
||||
label: '用户名'
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</Page>
|
||||
```
|
||||
|
||||
把以上配置方式换成 amis JSON, 则是:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "page",
|
||||
"title": "页面标题",
|
||||
"subTitle": "副标题",
|
||||
"body": {
|
||||
"type": "form",
|
||||
"title": "用户登录",
|
||||
"controls": [
|
||||
{
|
||||
"type": "text",
|
||||
"name": "username",
|
||||
"label": "用户名"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
那么,amis 是如何将 JSON 转成组件的呢?直接根据节点的 type 去跟组件一一对应?这样会重名,比如在表格里面展示的类型 `text` 跟表单里面的 `text` 是完全不一样的,一个负责展示,一个却负责输入。所以说一个节点要被什么组件渲染,还需要携带上下文(context)信息。
|
||||
|
||||
如何携带上下文(context)信息?amis 中是用节点的路径(path)来作为上下文信息。从上面的例子来看,一共有三个节点,path 信息分别是。
|
||||
|
||||
- `page` 页面节点
|
||||
- `page/body/form` 表单节点
|
||||
- `page/body/form/controls/0/text` 文本框节点。
|
||||
|
||||
根据 path 的信息就能很容易注册组件跟节点对应了。
|
||||
|
||||
Page 组件的示例代码
|
||||
|
||||
```jsx
|
||||
import * as React from 'react';
|
||||
import {Renderer} from 'amis';
|
||||
|
||||
@Renderer({
|
||||
test: /^page$/
|
||||
// ... 其他信息隐藏了
|
||||
})
|
||||
export class PageRenderer extends React.Component {
|
||||
// ... 其他信息隐藏了
|
||||
render() {
|
||||
const {
|
||||
title,
|
||||
body,
|
||||
render // 用来渲染孩子节点,如果当前是叶子节点则可以忽略。
|
||||
} = this.props;
|
||||
return (
|
||||
<div className="page">
|
||||
<h1>{title}</h1>
|
||||
<div className="body-container">
|
||||
{render('body', body) /*渲染孩子节点*/}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果不支持 Decorators 语法也可以使用如下写法
|
||||
export Renderer({
|
||||
test: /^page$/
|
||||
})(class PageRenderer extends React.Component {
|
||||
render() {
|
||||
// ...同上
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Form 组件的示例代码
|
||||
|
||||
```jsx
|
||||
@Renderer({
|
||||
test: /(^|\/)form$/
|
||||
// ... 其他信息隐藏了
|
||||
})
|
||||
export class FormRenderer extends React.Component {
|
||||
// ... 其他信息隐藏了
|
||||
render() {
|
||||
const {
|
||||
title,
|
||||
controls,
|
||||
render // 用来渲染孩子节点,如果当前是叶子节点则可以忽略。
|
||||
} = this.props;
|
||||
return (
|
||||
<form className="form">
|
||||
{controls.map((control, index) => (
|
||||
<div className="form-item" key={index}>
|
||||
{render(`${index}/control`, control)}
|
||||
</div>
|
||||
))}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Text 组件的示例代码
|
||||
|
||||
```jsx
|
||||
@Renderer({
|
||||
test: /(^|\/)form(?:\/\d+)?\/control(?\/\d+)?\/text$/
|
||||
// ... 其他信息隐藏了
|
||||
})
|
||||
export class FormItemTextRenderer extends React.Component {
|
||||
// ... 其他信息隐藏了
|
||||
render() {
|
||||
const {
|
||||
label,
|
||||
name,
|
||||
onChange
|
||||
} = this.props;
|
||||
return (
|
||||
<div className="form-group">
|
||||
<label>{label}<label>
|
||||
<input type="text" onChange={(e) => onChange(e.currentTarget.value)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
那么渲染过程就是根据节点 path 信息,跟组件池中的组件 `test` (检测) 信息做匹配,如果命中,则把当前节点转给对应组件渲染,节点中其他属性将作为目标组件的 props。需要注意的是,如果是容器组件,比如以上例子中的 `page` 组件,从 props 中拿到的 `body` 是一个子节点,由于节点类型是不固定,由使用者决定,所以不能直接完成渲染,所以交给属性中下发的 `render` 方法去完成渲染,`{render('body', body)}`,他的工作就是拿子节点的 path 信息去组件池里面找到对应的渲染器,然后交给对应组件去完成渲染。
|
@ -2,7 +2,7 @@
|
||||
title: 常见问题
|
||||
---
|
||||
|
||||
### 如何实现左侧导航栏页面跳转?
|
||||
## 如何实现左侧导航栏页面跳转?
|
||||
|
||||
为了能更容易嵌入其它平台,amis 只负责单页面渲染,不接管前端路由,因此无法只靠 amis 配置实现多页面切换功能,推荐使用以下几种方法:
|
||||
|
||||
@ -10,6 +10,13 @@ title: 常见问题
|
||||
2. 使用传统的页面跳转,这样就能使用 amis 的 aside,其中通过 link 类型来跳转到另一个页面。
|
||||
3. 使用「[爱速搭](http://suda.baidu.com/)」,它可以配置左侧导航,还自带了权限管理等功能。
|
||||
|
||||
### 集成到 React 项目中报错
|
||||
## 集成到 React 项目中报错
|
||||
|
||||
一般都是因为 React、Mobx、mobx-react 版本有关,参考 amis 项目的 [package.json](https://github.com/baidu/amis/blob/master/package.json),将版本保持一致,尤其是 Mobx,目前 amis 中使用的版本是 4,因为兼容性的考虑短期内不会升级到 5,使用 MobX 5 肯定会报错。
|
||||
|
||||
## 有的功能在官网示例中能用,但在 React/SDK 中无法使用
|
||||
|
||||
amis 大概每个月发布一个正式版本,但官网是 master 分支的版本,因此很可能是正式版本未更新。
|
||||
|
||||
- React 可以使用最新 beta 版本。
|
||||
- SDK 可以手动编译一个,下载开源项目源码后运行 `fis3 release publish-sdk -c`,然后在 sdk 目录就能找到。
|
||||
|
@ -5,10 +5,8 @@ description:
|
||||
|
||||
amis 有两种使用方法:
|
||||
|
||||
- [JS SDK](#SDK)
|
||||
- [React](#react)
|
||||
|
||||
React 版本可以完整使用 amis 的所有功能。
|
||||
- [JS SDK](#SDK),可以用在任意项目中
|
||||
- [React](#react),可以用在 React 项目中
|
||||
|
||||
SDK 版本适合对前端或 React 不了解的开发者,它不依赖 npm 及 webpack,可以像 Vue/jQuery 那样外链代码就能使用。
|
||||
|
||||
@ -33,9 +31,9 @@ JSSDK 版本可以在 github 的 [releases](https://github.com/baidu/amis/releas
|
||||
/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
|
||||
<link rel="stylesheet" href="sdk.css" />
|
||||
<!-- 从 1.0.20 开始默认不支持 IE 11,如果要支持 IE11 请引用这个 css -->
|
||||
<!-- 不过 amis 开发团队几乎没测试过 IE 11 下的效果,所以可能有不少功能用不了 -->
|
||||
<!-- 从 1.1.0 开始 sdk.css 将不支持 IE 11,如果要支持 IE11 请引用这个 css -->
|
||||
<!-- <link rel="stylesheet" href="sdk-ie11.css" /> -->
|
||||
<!-- 不过 amis 开发团队几乎没测试过 IE 11 下的效果,所以可能有不少功能用不了 -->
|
||||
<style>
|
||||
html,
|
||||
body,
|
||||
@ -84,7 +82,7 @@ JSSDK 版本可以在 github 的 [releases](https://github.com/baidu/amis/releas
|
||||
|
||||
### 控制 amis 的行为
|
||||
|
||||
`amis.embed` 函数还支持以下配置项来控制 amis 的行为,比如在 fetcher 的时候加入自己的处理逻辑,这些函数参数的说明在前面也有介绍。
|
||||
`amis.embed` 函数还支持以下配置项来控制 amis 的行为,比如在 fetcher 的时候加入自己的处理逻辑,这些函数参数的说明在后面 React 中也有介绍。
|
||||
|
||||
```js
|
||||
let amisScoped = amis.embed(
|
||||
@ -96,38 +94,37 @@ let amisScoped = amis.embed(
|
||||
},
|
||||
{
|
||||
// props 一般不用传。
|
||||
// locale: 'en' // 语言
|
||||
},
|
||||
{
|
||||
fetcher: (url, method, data, config) => {
|
||||
// 可以不传,用来实现 ajax 请求
|
||||
},
|
||||
// 可以不传,用来实现 ajax 请求
|
||||
fetcher: (url, method, data, config) => {},
|
||||
|
||||
// 全局 api 适配器。
|
||||
// 可以不传,全局 api 适配器。
|
||||
// api 自己也可以配置适配器,这里最好只处理通用逻辑。
|
||||
responseAdpater(api, response, query, request) {
|
||||
debugger;
|
||||
return response;
|
||||
}
|
||||
|
||||
// // 可以不传,用来实现页面跳转
|
||||
// 可以不传,用来实现页面跳转
|
||||
// jumpTo: location => {},
|
||||
|
||||
// // 可以不传,用来实现地址栏更新
|
||||
// 可以不传,用来实现地址栏更新
|
||||
// updateLocation: (location, replace) => {},
|
||||
|
||||
// // 可以不传,用来判断是否目标地址当前地址。
|
||||
// 可以不传,用来判断是否目标地址当前地址。
|
||||
// isCurrentUrl: url => {},
|
||||
|
||||
// // 可以不传,用来实现复制到剪切板
|
||||
// 可以不传,用来实现复制到剪切板
|
||||
// copy: content => {},
|
||||
|
||||
// // 可以不传,用来实现通知
|
||||
// 可以不传,用来实现通知
|
||||
// notify: (type, msg) => {},
|
||||
|
||||
// // 可以不传,用来实现提示
|
||||
// 可以不传,用来实现提示
|
||||
// alert: content => {},
|
||||
|
||||
// // 可以不传,用来实现确认框。
|
||||
// 可以不传,用来实现确认框。
|
||||
// confirm: content => {}
|
||||
}
|
||||
);
|
||||
@ -232,7 +229,7 @@ import * as React from 'react';
|
||||
import axios from 'axios';
|
||||
import copy from 'copy-to-clipboard';
|
||||
|
||||
import {render as renderAmis} from 'amis';
|
||||
import {render as renderAmis, ToastComponent, AlertComponent} from 'amis';
|
||||
import {alert, confirm} from 'amis/lib/components/Alert';
|
||||
import {toast} from 'amis/lib/components/Toast';
|
||||
|
||||
@ -241,6 +238,8 @@ class MyComponent extends React.Component<any, any> {
|
||||
return (
|
||||
<div>
|
||||
<p>通过 amis 渲染页面</p>
|
||||
<ToastComponent key="toast" position={'top-right'} />
|
||||
<AlertComponent key="alert" />
|
||||
{renderAmis(
|
||||
{
|
||||
// 这里是 amis 的 Json 配置。
|
||||
@ -250,6 +249,7 @@ class MyComponent extends React.Component<any, any> {
|
||||
},
|
||||
{
|
||||
// props...
|
||||
// locale: 'en' // 请参考「多语言」的文档
|
||||
},
|
||||
{
|
||||
// 下面三个接口必须实现
|
||||
|
@ -2,7 +2,7 @@
|
||||
title: 快速开始
|
||||
---
|
||||
|
||||
> 这是 1.0.20 版本中新增的功能
|
||||
> 这是 1.1.0 版本中新增的功能
|
||||
|
||||
在 amis 中自定义样式有三种方式:
|
||||
|
||||
|
@ -26,14 +26,6 @@ export const docs = [
|
||||
)
|
||||
},
|
||||
|
||||
{
|
||||
label: '自定义',
|
||||
path: '/docs/start/custom',
|
||||
getComponent: () =>
|
||||
// @ts-ignore
|
||||
import('../../docs/start/custom.md').then(makeMarkdownRenderer)
|
||||
},
|
||||
|
||||
{
|
||||
label: '常见问题',
|
||||
path: '/docs/start/faq',
|
||||
@ -41,26 +33,6 @@ export const docs = [
|
||||
// @ts-ignore
|
||||
import('../../docs/start/faq.md').then(makeMarkdownRenderer)
|
||||
}
|
||||
|
||||
// {
|
||||
// label: '基本用法',
|
||||
// icon: 'fa fa-file',
|
||||
// path: '/docs/basic',
|
||||
// getComponent: (location, cb) =>
|
||||
// (require as any)(['../../docs/basic.md'], doc => {
|
||||
// cb(null, makeMarkdownRenderer(doc));
|
||||
// })
|
||||
// },
|
||||
|
||||
// {
|
||||
// label: '高级用法',
|
||||
// icon: 'fa fa-rocket',
|
||||
// path: '/docs/advanced',
|
||||
// getComponent: (location, cb) =>
|
||||
// (require as any)(['../../docs/advanced.md'], doc => {
|
||||
// cb(null, makeMarkdownRenderer(doc));
|
||||
// })
|
||||
// }
|
||||
]
|
||||
},
|
||||
|
||||
@ -130,6 +102,47 @@ export const docs = [
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
label: '💎 高级',
|
||||
children: [
|
||||
{
|
||||
label: '工作原理',
|
||||
path: '/docs/extend/internal',
|
||||
getComponent: () =>
|
||||
// @ts-ignore
|
||||
import('../../docs/extend/internal.md').then(makeMarkdownRenderer)
|
||||
},
|
||||
{
|
||||
label: '自定义组件 - SDK',
|
||||
path: '/docs/extend/custom-sdk',
|
||||
getComponent: () =>
|
||||
// @ts-ignore
|
||||
import('../../docs/extend/custom-sdk.md').then(makeMarkdownRenderer)
|
||||
},
|
||||
{
|
||||
label: '自定义组件 - React',
|
||||
path: '/docs/extend/custom-react',
|
||||
getComponent: () =>
|
||||
// @ts-ignore
|
||||
import('../../docs/extend/custom-react.md').then(makeMarkdownRenderer)
|
||||
},
|
||||
{
|
||||
label: '扩展现有组件',
|
||||
path: '/docs/extend/addon',
|
||||
getComponent: () =>
|
||||
// @ts-ignore
|
||||
import('../../docs/extend/addon.md').then(makeMarkdownRenderer)
|
||||
},
|
||||
{
|
||||
label: '多语言',
|
||||
path: '/docs/extend/i18n',
|
||||
getComponent: () =>
|
||||
// @ts-ignore
|
||||
import('../../docs/extend/i18n.md').then(makeMarkdownRenderer)
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
label: '🎼 类型',
|
||||
children: [
|
||||
|
@ -15,6 +15,8 @@ import {
|
||||
render as renderAmis
|
||||
} from '../src/index';
|
||||
|
||||
import '../src/locale/en';
|
||||
|
||||
export function embed(
|
||||
container: string | HTMLElement,
|
||||
schema: any,
|
||||
|
Loading…
Reference in New Issue
Block a user