forked from p96170835/amis
优化一下文档,对新人更友好
This commit is contained in:
parent
789a035016
commit
e77b0bf884
|
@ -1,8 +1,8 @@
|
|||
# amis
|
||||
|
||||
A page renderer that renders pages directly based on a JSON configuration in a specific format. Combined with the business-side API, you can quickly develop various management pages.
|
||||
A Low-Code frontend UI Framework. You can quickly develop various management pages by only using JSON configuration. Frontend skill is not required.
|
||||
|
||||
Currently used in Baidu's internal [AMIS] (http://amis.baidu.com) platform, 100+ departments have access to use, create 1.2w+ pages, welcome to use and make suggestions.
|
||||
Currently used in Baidu's internal infrastructure, created more than 40000 pages.
|
||||
|
||||
To build your own backend system with amis, you can refer to this: https://github.com/fex-team/amis-admin
|
||||
|
||||
|
@ -10,39 +10,39 @@ To build your own backend system with amis, you can refer to this: https://githu
|
|||
|
||||
```
|
||||
# Install project npm dependencies.
|
||||
Npm i
|
||||
npm i
|
||||
|
||||
# Start compiling and output the code to the webroot directory of the service you just opened.
|
||||
Npm run dev
|
||||
npm run dev
|
||||
|
||||
# Open the fis3 service, please visit http://127.0.0.1:8888/examples/pages/simple.
|
||||
Npm start
|
||||
npm start
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
#Installation dependency
|
||||
Npm i
|
||||
npm i
|
||||
|
||||
#Executing test cases
|
||||
Npm test
|
||||
npm test
|
||||
|
||||
# View test case coverage
|
||||
Npm run coverage
|
||||
npm run coverage
|
||||
```
|
||||
|
||||
## Working with documents
|
||||
|
||||
For a better reading experience, it is recommended to read https://baidu.github.io/amis/ directly in gh-pages.
|
||||
|
||||
* [Quick Start] (/docs/getting_started.md)
|
||||
* [Basic Usage] (/docs/basic.md)
|
||||
* [Advanced Usage] (/docs/advanced.md)
|
||||
* [Render Manual] (/docs/renderers.md)
|
||||
* [How to customize] (/docs/sdk.md)
|
||||
* [custom component] (/docs/dev.md)
|
||||
* [Auxiliary Style] (/docs/style.md)
|
||||
- [Quick Start](/docs/getting_started.md)
|
||||
- [Basic Usage](/docs/basic.md)
|
||||
- [Advanced Usage](/docs/advanced.md)
|
||||
- [Render Manual](/docs/renderers.md)
|
||||
- [How to customize](/docs/sdk.md)
|
||||
- [custom component](/docs/dev.md)
|
||||
- [Auxiliary Style](/docs/style.md)
|
||||
|
||||
## How to contribute
|
||||
|
||||
|
@ -50,9 +50,9 @@ Please write in typescript, all reasonable changes, new public renderers, use ca
|
|||
|
||||
## Maintainer
|
||||
|
||||
* [2betop](https://github.com/2betop)
|
||||
* [RickCole21] (https://github.com/RickCole21)
|
||||
* [catchonme](https://github.com/catchonme)
|
||||
- [2betop](https://github.com/2betop)
|
||||
- [RickCole21](https://github.com/RickCole21)
|
||||
- [catchonme](https://github.com/catchonme)
|
||||
|
||||
## Discussion
|
||||
|
21
README.md
21
README.md
|
@ -1,24 +1,28 @@
|
|||
# amis
|
||||
|
||||
前端低代码框架,通过 JSON 配置就能生成各种后台页面。
|
||||
前端低代码框架,通过 JSON 配置就能生成各种后台页面,极大减少开发成本,甚至可以不需要了解前端。
|
||||
|
||||
目前在百度大量用于内部平台的前端开发,已有 100+ 部门使用,创建了 2.5w+ 页面。
|
||||
目前在百度广泛用于内部平台的前端开发,已有 100+ 部门使用,创建了 3w+ 页面。
|
||||
|
||||
通过 amis 搭建自己的后台系统,可以参考这: https://github.com/fex-team/amis-admin
|
||||
包含:fis3 版本、webpack 版本和 jssdk 版本。
|
||||
## 入门介绍
|
||||
|
||||
## 快速开始
|
||||
请阅读 <https://baidu.github.io/amis/docs/intro>
|
||||
|
||||
请参考 <https://baidu.github.io/amis/docs/getting-started>
|
||||
## 相关工具及平台
|
||||
|
||||
- 通过 amis 搭建自己的后台系统:https://github.com/fex-team/amis-admin
|
||||
- 可视化编辑器:http://fex-team.github.io/amis-editor
|
||||
|
||||
## 开发指南
|
||||
|
||||
以下是参与开发 amis 才需要看的,使用请看前面的入门文档。
|
||||
|
||||
> 如果 github 下载慢可以使用 [gitee](https://gitee.com/baidu/amis) 上的镜像。
|
||||
|
||||
推荐使用 node 10,node 6 和 node 8 也可以。node 12 未测试,可能 fis3 还有些插件不支持。
|
||||
推荐使用 node 8/10/12。
|
||||
|
||||
```bash
|
||||
# 安装项目 npm 依赖。
|
||||
# 安装项目 npm 依赖,在 node 12 下会有报错但不影响正常使用。
|
||||
npm i
|
||||
|
||||
# 开始编译,把代码产出到刚开启的服务的 webroot 目录。
|
||||
|
@ -51,6 +55,7 @@ npm run coverage
|
|||
- [2betop](https://github.com/2betop)
|
||||
- [RickCole21](https://github.com/RickCole21)
|
||||
- [catchonme](https://github.com/catchonme)
|
||||
- [nwind](https://github.com/nwind)
|
||||
|
||||
## 讨论
|
||||
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
/* eslint-disable */
|
||||
|
||||
var marked = require("marked");
|
||||
var yaml = (yaml = require("js-yaml"));
|
||||
var marked = require('marked');
|
||||
let prism = require('prismjs');
|
||||
let loadLanguages = require('prismjs/components/');
|
||||
loadLanguages(['bash', 'javascript', 'jsx', 'tsx', 'css', 'markup', 'json']);
|
||||
var yaml = (yaml = require('js-yaml'));
|
||||
var rYml = /^\s*---([\s\S]*?)---\s/;
|
||||
var renderer = new marked.Renderer();
|
||||
marked.setOptions({
|
||||
|
@ -15,10 +18,14 @@ marked.setOptions({
|
|||
smartypants: false
|
||||
});
|
||||
|
||||
// Synchronous highlighting with highlight.js
|
||||
// Synchronous highlighting with prism.js
|
||||
marked.setOptions({
|
||||
highlight: function(code) {
|
||||
return require("highlight.js").highlightAuto(code).value;
|
||||
highlight: function (code, lang) {
|
||||
if (lang) {
|
||||
return prism.highlight(code, prism.languages[lang], lang);
|
||||
} else {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -33,29 +40,27 @@ marked.setOptions({
|
|||
// + '</table>\n';
|
||||
// };
|
||||
|
||||
renderer.link = function(href, title, text) {
|
||||
renderer.link = function (href, title, text) {
|
||||
if (this.options.sanitize) {
|
||||
try {
|
||||
var prot = decodeURIComponent(unescape(href))
|
||||
.replace(/[^\w:]/g, "")
|
||||
.replace(/[^\w:]/g, '')
|
||||
.toLowerCase();
|
||||
} catch (e) {
|
||||
return "";
|
||||
return '';
|
||||
}
|
||||
if (
|
||||
prot.indexOf("javascript:") === 0 ||
|
||||
prot.indexOf("vbscript:") === 0
|
||||
) {
|
||||
return "";
|
||||
if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
if (href && href[0] === "#") {
|
||||
href = "#" +
|
||||
if (href && href[0] === '#') {
|
||||
href =
|
||||
'#' +
|
||||
encodeURIComponent(
|
||||
href
|
||||
.substring(1)
|
||||
.toLowerCase()
|
||||
.replace(/[^\u4e00-\u9fa5_a-zA-Z0-9]+/g, "-")
|
||||
.replace(/[^\u4e00-\u9fa5_a-zA-Z0-9]+/g, '-')
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -63,11 +68,11 @@ renderer.link = function(href, title, text) {
|
|||
if (title) {
|
||||
out += ' title="' + title + '"';
|
||||
}
|
||||
out += ">" + text + "</a>";
|
||||
out += '>' + text + '</a>';
|
||||
return out;
|
||||
};
|
||||
|
||||
module.exports = function(content, file) {
|
||||
module.exports = function (content, file) {
|
||||
var m = rYml.exec(content);
|
||||
var info = {};
|
||||
if (m && m[1]) {
|
||||
|
@ -76,23 +81,23 @@ module.exports = function(content, file) {
|
|||
}
|
||||
|
||||
var toc = {
|
||||
label: "目录",
|
||||
type: "toc",
|
||||
label: '目录',
|
||||
type: 'toc',
|
||||
children: [],
|
||||
level: 0
|
||||
};
|
||||
var stack = [toc];
|
||||
|
||||
renderer.heading = function(text, level) {
|
||||
renderer.heading = function (text, level) {
|
||||
var escapedText = encodeURIComponent(
|
||||
text.toLowerCase().replace(/[^\u4e00-\u9fa5_a-zA-Z0-9]+/g, "-")
|
||||
text.toLowerCase().replace(/[^\u4e00-\u9fa5_a-zA-Z0-9]+/g, '-')
|
||||
);
|
||||
|
||||
if (level < 5) {
|
||||
var menu = {
|
||||
label: text,
|
||||
fragment: escapedText,
|
||||
fullPath: "#" + escapedText,
|
||||
fullPath: '#' + escapedText,
|
||||
level: level
|
||||
};
|
||||
|
||||
|
@ -113,7 +118,7 @@ module.exports = function(content, file) {
|
|||
escapedText +
|
||||
'" aria-hidden="true"><svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>';
|
||||
|
||||
return "<h" + level + ">" + anchor + text + "</h" + level + ">";
|
||||
return '<h' + level + '>' + anchor + text + '</h' + level + '>';
|
||||
|
||||
// return '<h' + level + '><a name="' +
|
||||
// escapedText +
|
||||
|
@ -128,60 +133,57 @@ module.exports = function(content, file) {
|
|||
|
||||
content = content.replace(
|
||||
/```(schema|html)(?::(.*?))?\n([\s\S]*?)```/g,
|
||||
function(_, lang, attr, code) {
|
||||
function (_, lang, attr, code) {
|
||||
const setting = {};
|
||||
attr && attr.split(/\s+/).forEach(function(item) {
|
||||
var parts = item.split("=");
|
||||
attr &&
|
||||
attr.split(/\s+/).forEach(function (item) {
|
||||
var parts = item.split('=');
|
||||
|
||||
if (parts[1] && /^('|").*\1/.test(parts[1])) {
|
||||
parts[1] = parts[1].substring(1, parts[1].length - 1);
|
||||
}
|
||||
|
||||
setting[parts[0]] = parts[1] ? decodeURIComponent(parts[1]) : "";
|
||||
setting[parts[0]] = parts[1] ? decodeURIComponent(parts[1]) : '';
|
||||
|
||||
if (parts[0] === 'height') {
|
||||
setting.height = parseInt(setting.height, 10) + 200/*编辑器的高度*/;
|
||||
setting.height =
|
||||
parseInt(setting.height, 10) + 200 /*编辑器的高度*/;
|
||||
attr = attr.replace(item, `height="${setting.height}"`);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// placeholder[index] = `<iframe class="doc-iframe" width="100%" height="${setting.height || 200}px" frameBorder="0" src="/play?code=${encodeURIComponent(code)}&scope=${encodeURIComponent(setting.scope)}"></iframe>`;
|
||||
if (lang === "html") {
|
||||
if (lang === 'html') {
|
||||
if (~code.indexOf('<html')) {
|
||||
return _;
|
||||
}
|
||||
|
||||
placeholder[
|
||||
index
|
||||
] = `<div class="amis-doc"><div class="preview">${code}</div><pre><code class="lang-html">${
|
||||
require("highlight.js").highlightAuto(code).value
|
||||
}</code></pre></div>`;
|
||||
] = `<div class="amis-doc"><div class="preview">${code}</div><pre><code class="lang-html">${prism.highlight(
|
||||
code,
|
||||
prism.languages[lang],
|
||||
lang
|
||||
)}</code></pre></div>`;
|
||||
} else {
|
||||
placeholder[
|
||||
index
|
||||
] = `<div class="amis-preview" style="height: ${
|
||||
setting.height
|
||||
}px"><script type="text/schema" ${attr}>${code}</script></div>`;
|
||||
] = `<div class="amis-preview" style="height: ${setting.height}px"><script type="text/schema" ${attr}>${code}</script></div>`;
|
||||
}
|
||||
|
||||
return `[[${index++}]]`;
|
||||
}
|
||||
);
|
||||
|
||||
content = marked(content).replace(/<p>\[\[(\d+)\]\]<\/p>/g, function(
|
||||
_,
|
||||
id
|
||||
) {
|
||||
return placeholder[id] || "";
|
||||
content = marked(content).replace(/<p>\[\[(\d+)\]\]<\/p>/g, function (_, id) {
|
||||
return placeholder[id] || '';
|
||||
});
|
||||
|
||||
content = fis.compile.partial(content, file, "html") + `\n\n<div class="m-t-lg b-l b-info b-3x wrapper bg-light dk">文档内容有误?欢迎大家一起来编写,文档地址:<i class="fa fa-github"></i><a href="https://github.com/baidu/amis/tree/master${file.subpath}">${file.subpath}</a>。</div>`;
|
||||
content =
|
||||
fis.compile.partial(content, file, 'html') +
|
||||
`\n\n<div class="m-t-lg b-l b-info b-3x wrapper bg-light dk">文档内容有误?欢迎大家一起来编写,文档地址:<i class="fa fa-github"></i><a href="https://github.com/baidu/amis/tree/master${file.subpath}">${file.subpath}</a>。</div>`;
|
||||
info.html = content;
|
||||
info.toc = toc;
|
||||
|
||||
|
||||
|
||||
return "module.exports = " + JSON.stringify(info, null, 2) + ";";
|
||||
return 'module.exports = ' + JSON.stringify(info, null, 2) + ';';
|
||||
};
|
||||
|
|
85
docs/api.md
85
docs/api.md
|
@ -1,14 +1,53 @@
|
|||
---
|
||||
title: API 说明
|
||||
title: 动态数据
|
||||
---
|
||||
|
||||
amis 渲染器的数据都来源于 api,有一定的格式要求。
|
||||
除了渲染静态页面及表单,amis 还能渲染动态数据,比如下面这个表格数据是来自 api 这个接口的请求
|
||||
|
||||
### 整体要求
|
||||
```json
|
||||
{
|
||||
"type": "crud",
|
||||
"api": " http://xxx/api/sample",
|
||||
"columns": [
|
||||
{
|
||||
"name": "engine",
|
||||
"label": "引擎"
|
||||
},
|
||||
{
|
||||
"name": "browser",
|
||||
"label": "浏览器"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
amis 期望这个 api 接口返回的是如下的格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": 0,
|
||||
"msg": "",
|
||||
"data": {
|
||||
"items": [
|
||||
{
|
||||
"engine": "Trident",
|
||||
"browser": "IE 9"
|
||||
},
|
||||
{
|
||||
"engine": "Gecko",
|
||||
"browser": "Firefox 70"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
下面是具体介绍
|
||||
|
||||
### 整体格式
|
||||
|
||||
要求每个接口都返回 `status` 字段用来表示成功还是失败,如果失败了,通过 `msg` 字段来说明失败原因。当然如果成功 `msg` 也可以用来设置提示信息。
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"status": 0, // 0 表示成功,非0 表示失败
|
||||
|
@ -20,12 +59,23 @@ amis 渲染器的数据都来源于 api,有一定的格式要求。
|
|||
}
|
||||
```
|
||||
|
||||
如果你的系统有自己的规范,也没关系,fetcher 整体入口那加个适配器就行了如:
|
||||
如果你的系统有自己的规范,可以在 fetcher 统一进行适配,如:
|
||||
|
||||
```js
|
||||
{
|
||||
fetcher: function(api) {
|
||||
|
||||
renderAmis(
|
||||
{
|
||||
// 这里是 amis 的 Json 配置。
|
||||
type: 'page',
|
||||
title: '简单页面',
|
||||
body: '内容'
|
||||
},
|
||||
{
|
||||
// props
|
||||
},
|
||||
{
|
||||
// 忽略别的设置项
|
||||
fetcher: function (api) {
|
||||
// 适配这种格式 {"code": 0, "message": "", "result": {}}
|
||||
return axios(config).then(response => {
|
||||
let payload = {
|
||||
|
@ -37,9 +87,11 @@ amis 渲染器的数据都来源于 api,有一定的格式要求。
|
|||
return {
|
||||
...response,
|
||||
data: payload
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -47,14 +99,13 @@ amis 渲染器的数据都来源于 api,有一定的格式要求。
|
|||
|
||||
每个渲染的接口返回都有自己的格式要求,主要体现在 data 字段内部,具体请参考每个渲染的接口说明。
|
||||
|
||||
* [Page](./renderers/Page.md#接口说明)
|
||||
* [CRUD](./renderers/CRUD.md#接口说明)
|
||||
* [Form](./renderers/Form/Form.md#接口说明)
|
||||
* [Select](./renderers/Form/Select.md#接口说明)
|
||||
* [Checkboxes](./renderers/Form/Checkboxes.md#接口说明)
|
||||
* [Radios](./renderers/Form/Radios.md#接口说明)
|
||||
* [List](./renderers/Form/List.md#接口说明)
|
||||
* [Wizard](./renderers/Wizard.md#接口说明)
|
||||
- [Page](./renderers/Page.md#接口说明)
|
||||
- [CRUD](./renderers/CRUD.md#接口说明)
|
||||
- [Form](./renderers/Form/Form.md#接口说明)
|
||||
- [Select](./renderers/Form/Select.md#接口说明)
|
||||
- [Checkboxes](./renderers/Form/Checkboxes.md#接口说明)
|
||||
- [Radios](./renderers/Form/Radios.md#接口说明)
|
||||
- [List](./renderers/Form/List.md#接口说明)
|
||||
- [Wizard](./renderers/Wizard.md#接口说明)
|
||||
|
||||
`TBD`
|
||||
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
title: 基本用法
|
||||
---
|
||||
|
||||
为了简化前端开发,amis Renderer 能够直接用配置就能将页面渲染出来。
|
||||
|
||||
先来看个简单的例子。
|
||||
|
||||
```schema:height="300"
|
||||
|
@ -19,9 +17,9 @@ title: 基本用法
|
|||
}
|
||||
```
|
||||
|
||||
> PS: 可以通过编辑器实时修改预览
|
||||
> 可以通过编辑器实时修改预览
|
||||
|
||||
从上面的内容可以看出,一个简单页面框架已经基本出来了,这是 amis 渲染器配置的入口。从 `page` 渲染器开始出发,通过在容器中放置不同的渲染器来配置不同性质的页面。
|
||||
通过使用上面的例子就能配出一个基本页面框架,这是 amis 渲染器配置的入口。从 `page` 渲染器开始出发,通过在容器中放置不同的渲染器来配置不同性质的页面。
|
||||
|
||||
简单说明以上配置信息。
|
||||
|
||||
|
|
|
@ -0,0 +1,433 @@
|
|||
---
|
||||
title: 定制功能
|
||||
---
|
||||
|
||||
如果默认的组件不能满足需求,可以通过定制组件来进行扩展,在 amis 中有两种方法:
|
||||
|
||||
1. 临时扩展,适合无需复用的组件。
|
||||
2. 注册自定义类型,适合需要在很多地方复用的组件。
|
||||
|
||||
> 注意,扩展只支持使用 React 组件方式引入的 amis,使用 JSSDK 无法支持
|
||||
|
||||
## 临时扩展
|
||||
|
||||
amis 的 JSON 配置最终会转成 React 组件来执行,所以如果只是想在某个配置中加入定制功能,可以直接在这个 JSON 配置里写 React 代码,比如下面这个例子:
|
||||
|
||||
```jsx
|
||||
{
|
||||
"type": "page",
|
||||
"title": "自定义组件示例",
|
||||
"body": {
|
||||
"type": "form",
|
||||
"controls": [
|
||||
{
|
||||
"type": "text",
|
||||
"label": "用户名",
|
||||
"name": "usename"
|
||||
},
|
||||
{
|
||||
"name": "mycustom",
|
||||
"children": ({
|
||||
value,
|
||||
onChange
|
||||
}) => (
|
||||
<div>
|
||||
<p>这个是个自定义组件</p>
|
||||
<p>当前值:{value}</p>
|
||||
<a className="btn btn-default" onClick={
|
||||
() => onChange(Math.round(Math.random() * 10000))
|
||||
}>随机修改</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
其中的 `mycustom` 就是一个临时扩展,它的 `children` 属性是一个函数,它的返回内容和 React 的 Render 方法一样,即 jsx,在这个方法里你可以写任意 JavaScript 来实现自己的定制需求,这个函数有两个参数 `value` 和 `onChange`,`value` 就是组件的值,`onChange` 方法用来改变这个值,比如上面的例子中,点击链接后就会修改 `mycustom` 为一个随机数,在提交表单的时候就变成了这个随机数。
|
||||
|
||||
与之类似的还有个 `component` 属性,这个属性可以传入 React Component,如果想用 React Hooks,请通过 `component` 传递,而不是 `children`。
|
||||
|
||||
这种扩展方式既简单又灵活,但它是写在配置中的,如果需要在很多地方,可以使用下面的「注册自定义类型」方式:
|
||||
|
||||
## 注册自定义类型
|
||||
|
||||
注册自定义类型需要了解 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
|
||||
@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>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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 信息去组件池里面找到对应的渲染器,然后交给对应组件去完成渲染。
|
||||
|
||||
### 编写自定义组件
|
||||
|
||||
了解了基本原理后,来看个简单的例子:
|
||||
|
||||
```jsx
|
||||
import * as React from 'react';
|
||||
import {Renderer} from 'amis';
|
||||
|
||||
@Renderer({
|
||||
test: /(^|\/)my\-renderer$/
|
||||
})
|
||||
class CustomRenderer extends React.Component {
|
||||
render() {
|
||||
const {tip} = this.props;
|
||||
return <div>这是自定义组件:{tip}</div>;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
有了以上这段代码后,就可以这样使用了。
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "page",
|
||||
"title": "自定义组件示例",
|
||||
"body": {
|
||||
"type": "my-renderer",
|
||||
"tip": "简单示例"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
看了前面[amis 工作原理](#工作原理)应该不难理解,这里注册一个 React 组件,当节点的 path 信息是 `my-renderer` 结尾时,交给当前组件来完成渲染。
|
||||
|
||||
如果这个组件还能通过 `children` 属性添加子节点,则需要使用下面这种写法:
|
||||
|
||||
```jsx
|
||||
import * as React from 'react';
|
||||
import {Renderer} from 'amis';
|
||||
|
||||
@Renderer({
|
||||
test: /(^|\/)my\-renderer2$/
|
||||
})
|
||||
class CustomRenderer extends React.Component {
|
||||
render() {
|
||||
const {tip, body, render} = this.props;
|
||||
return (
|
||||
<div>
|
||||
<p>这是自定义组件:{tip}</p>
|
||||
{body ? (
|
||||
<div className="container">
|
||||
{render('body', body, {
|
||||
// 这里的信息会作为 props 传递给子组件,一般情况下都不需要这个
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
有了以上这段代码后,就可以这样使用了。
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "page",
|
||||
"title": "自定义组件示例",
|
||||
"body": {
|
||||
"type": "my-renderer2",
|
||||
"tip": "简单示例",
|
||||
"body": {
|
||||
"type": "form",
|
||||
"controls": [
|
||||
{
|
||||
"type": "text",
|
||||
"label": "用户名",
|
||||
"name": "usename"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
跟第一个列子不同的地方是,这里多了个 `render` 方法,这个方法就是专门用来渲染子节点的。来看下参数说明:
|
||||
|
||||
- `region` 区域名称,你有可能有多个区域可以作为容器,请不要重复。
|
||||
- `node` 子节点。
|
||||
- `props` 可选,可以通过此对象跟子节点通信等。
|
||||
|
||||
### 表单项的扩展
|
||||
|
||||
以上是普通渲染器的注册方式,如果是表单项,为了更简单的扩充,请使用 `FormItem` 注解,而不是 `Renderer`。 原因是如果用 `FormItem` 是不用关心:label 怎么摆,表单验证器怎么实现,如何适配表单的 3 中展现方式(水平、上下和内联模式),而只用关心:有了值后如何回显,响应用户交互设置新值。
|
||||
|
||||
```jsx
|
||||
import * as React from 'react';
|
||||
import {FormItem} from 'amis';
|
||||
|
||||
@FormItem({
|
||||
type: 'custom'
|
||||
})
|
||||
class MyFormItem extends React.Component {
|
||||
render() {
|
||||
const {value, onChange} = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>这个是个自定义组件</p>
|
||||
<p>当前值:{value}</p>
|
||||
<a
|
||||
className="btn btn-default"
|
||||
onClick={() => onChange(Math.round(Math.random() * 10000))}
|
||||
>
|
||||
随机修改
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
有了以上这段代码后,就可以这样使用了。
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "page",
|
||||
"title": "自定义组件示例",
|
||||
"body": {
|
||||
"type": "form",
|
||||
"controls": [
|
||||
{
|
||||
"type": "text",
|
||||
"label": "用户名",
|
||||
"name": "usename"
|
||||
},
|
||||
|
||||
{
|
||||
"type": "custom",
|
||||
"label": "随机值",
|
||||
"name": "random"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 注意: 使用 FormItem 默认是严格模式,即只有必要的属性变化才会重新渲染,有可能满足不了你的需求,如果忽略性能问题,可以传入 `strictMode`: `false` 来关闭。
|
||||
|
||||
表单项开发主要关心两件事。
|
||||
|
||||
1. 呈现当前值。如以上例子,通过 `this.props.value` 判定如果勾选了则显示`已勾选`,否则显示`请勾选`。
|
||||
2. 接收用户交互,通过 `this.props.onChange` 修改表单项值。如以上例子,当用户点击按钮时,切换当前选中的值。
|
||||
|
||||
至于其他功能如:label/description 的展示、表单验证功能、表单布局(常规、左右或者内联)等等,只要是通过 FormItem 注册进去的都无需自己实现。
|
||||
|
||||
需要注意,获取或者修改的是什么值跟配置中 `type` 并列的 `name` 属性有关,也就是说直接关联某个变量,自定义中直接通过 props 下发了某个指定变量的值和修改的方法。如果你想获取其他数据,或者设置其他数据可以看下以下说明:
|
||||
|
||||
- `获取其他数据` 可以通过 `this.props.data` 查看,作用域中所有的数据都在这了。
|
||||
- `设置其他数据` 可以通过 `this.props.onBulkChange`, 比如: `this.props.onBulkChange({a: 1, b: 2})` 等于同时设置了两个值。当做数据填充的时候,这个方法很有用。
|
||||
|
||||
### 其它高级定制
|
||||
|
||||
下面是一些不太常用的 amis 扩展方式及技巧。
|
||||
|
||||
#### 自定义验证器
|
||||
|
||||
如果 amis [自带的验证](./renderers/Form/FormItem.md#)能满足需求了,则不需要关心。组件可以有自己的验证逻辑。
|
||||
|
||||
```jsx
|
||||
import * as React from 'react';
|
||||
import {FormItem} from 'amis';
|
||||
import * as cx from 'classnames';
|
||||
|
||||
@FormItem({
|
||||
type: 'custom-checkbox'
|
||||
})
|
||||
export default class CustomCheckbox extends React.Component {
|
||||
validate() {
|
||||
// 通过 this.props.value 可以知道当前值。
|
||||
|
||||
return isValid ? '' : '不合法,说明不合法原因。';
|
||||
}
|
||||
// ... 其他省略了
|
||||
}
|
||||
```
|
||||
|
||||
上面的栗子只是简单说明,另外可以做`异步验证`,validate 方法可以返回一个 promise。
|
||||
|
||||
#### OptionsControl
|
||||
|
||||
如果你的表单组件性质和 amis 的 Select、Checkboxes、List 差不多,用户配置配置 source 可通过 API 拉取选项,你可以用 OptionsControl 取代 FormItem 这个注解。
|
||||
|
||||
用法是一样,功能方面主要多了以下功能。
|
||||
|
||||
- 可以配置 options,options 支持配置 visibleOn hiddenOn 等表达式
|
||||
- 可以配置 `source` 换成动态拉取 options 的功能,source 中有变量依赖会自动重新拉取。
|
||||
- 下发了这些 props,可以更方便选项。
|
||||
- `options` 不管是用户配置的静态 options 还是配置 source 拉取的,下发到组件已经是最终的选项了。
|
||||
- `selectedOptions` 数组类型,当前用户选中的选项。
|
||||
- `loading` 当前选项是否在加载
|
||||
- `onToggle` 切换一个选项的值
|
||||
- `onToggleAll` 切换所有选项的值,类似于全选。
|
||||
|
||||
#### 组件间通信
|
||||
|
||||
关于组件间通信,amis 中有个机制就是,把需要被引用的组件设置一个 name 值,然后其他组件就可以通过这个 name 与其通信,比如这个[栗子](./advanced.md#组件间通信)。其实内部是依赖于内部的一个 Scoped Context。你的组件希望可以被别的组件引用,你需要把自己注册进去,默认自定义的非表单类组件并没有把自己注册进去,可以参考以下代码做添加。
|
||||
|
||||
```js
|
||||
import * as React from 'react';
|
||||
import {Renderer, ScopedContext} from 'amis';
|
||||
@Renderer({
|
||||
test: /(?:^|\/)my\-renderer$/
|
||||
})
|
||||
export class CustomRenderer extends React.Component {
|
||||
static contextType = ScopedContext;
|
||||
|
||||
componentWillMount() {
|
||||
const scoped = this.context;
|
||||
scoped.registerComponent(this);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const scoped = this.context;
|
||||
scoped.unRegisterComponent(this);
|
||||
}
|
||||
|
||||
// 其他部分省略了。
|
||||
}
|
||||
```
|
||||
|
||||
把自己注册进去了,其他组件就能引用到了。同时,如果你想找别的组件,也同样是通过 scoped 这个 context,如: `scoped.getComponentByName("xxxName")` 这样就能拿到目标组件的实例了(前提是目标组件已经配置了 name 为 `xxxName`)。
|
||||
|
||||
#### 其他功能方法
|
||||
|
||||
自定义的渲染器 props 会下发一个非常有用的 env 对象。这个 env 有以下功能方法。
|
||||
|
||||
- `env.fetcher` 可以用来做 ajax 请求如: `this.props.env.fetcher('xxxAPi', this.props.data).then((result) => console.log(result))`
|
||||
- `env.confirm` 确认框,返回一个 promise 等待用户确认如: `this.props.env.confirm('你确定要这么做?').then((confirmed) => console.log(confirmed))`
|
||||
- `env.alert` 用 Modal 实现的弹框,个人觉得更美观。
|
||||
- `env.notify` toast 某个消息 如: `this.props.env.notify("error", "出错了")`
|
||||
- `env.jumpTo` 页面跳转。
|
189
docs/dev.md
189
docs/dev.md
|
@ -1,189 +0,0 @@
|
|||
---
|
||||
title: 自定义组件
|
||||
shortname: dev
|
||||
---
|
||||
|
||||
自定义组件主要分两类。表单类和非表单类。
|
||||
|
||||
### FormItem
|
||||
|
||||
即表单类,它主要用来扩充表单项。先看个例子。
|
||||
|
||||
```jsx
|
||||
import * as React from 'react';
|
||||
import {FormItem} from 'amis';
|
||||
import * as cx from 'classnames';
|
||||
|
||||
@FormItem({
|
||||
type: 'custom-checkbox',
|
||||
})
|
||||
export default class CustomCheckbox extends React.Component {
|
||||
toggle = () => {
|
||||
const {value, onChange} = this.props;
|
||||
|
||||
onChange(!value);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {value} = this.props;
|
||||
const checked = !!value;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<a
|
||||
className={cx('btn btn-default', {
|
||||
'btn-success': checked,
|
||||
})}
|
||||
onClick={this.toggle}
|
||||
>
|
||||
{checked ? '已勾选' : '请勾选'}
|
||||
</a>
|
||||
<div className="inline m-l-xs">{checked ? '已勾选' : '请勾选'}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
有了这个代码后,页面配置 form 的 controls 里面就可以通过这样的配置启动了。
|
||||
|
||||
```js
|
||||
{
|
||||
// 其他信息省略了。。
|
||||
type: 'form',
|
||||
controls: [
|
||||
{
|
||||
type: 'custom-checkbox',
|
||||
name: '变量名',
|
||||
label: '自定义组件。'
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
表单项开发主要关心两件事。
|
||||
|
||||
1. 呈现当前值。如以上例子,通过 `this.props.value` 判定如果勾选了则显示`已勾选`,否则显示`请勾选`。
|
||||
2. 接收用户交互,通过 `this.props.onChange` 修改表单项值。如以上例子,当用户点击按钮时,切换当前选中的值。
|
||||
|
||||
至于其他功能如:label/description 的展示、表单验证功能、表单布局(常规、左右或者内联)等等,只要是通过 FormItem 注册进去的都无需自己实现。
|
||||
|
||||
需要注意,获取或者修改的是什么值跟配置中 `type` 并列的 `name` 属性有关,也就是说直接关联某个变量,自定义中直接通过 props 下发了某个指定变量的值和修改的方法。如果你想获取其他数据,或者设置其他数据可以看下以下说明:
|
||||
|
||||
* `获取其他数据` 可以通过 `this.props.data` 查看,作用域中所有的数据都在这了。
|
||||
* `设置其他数据` 可以通过 `this.props.onBulkChange`, 比如: `this.props.onBulkChange({a: 1, b: 2})` 等于同时设置了两个值。当做数据填充的时候,这个方法很有用。
|
||||
|
||||
|
||||
|
||||
#### 自定义验证器
|
||||
|
||||
如果 amis [自带的验证](./renderers/Form/FormItem.md#)能满足需求了,则不需要关心。组件可以有自己的验证逻辑。
|
||||
|
||||
```jsx
|
||||
import * as React from 'react';
|
||||
import {FormItem} from 'amis';
|
||||
import * as cx from 'classnames';
|
||||
|
||||
@FormItem({
|
||||
type: 'custom-checkbox',
|
||||
})
|
||||
export default class CustomCheckbox extends React.Component {
|
||||
validate() {
|
||||
// 通过 this.props.value 可以知道当前值。
|
||||
|
||||
return isValid ? '' : '不合法,说明不合法原因。'
|
||||
}
|
||||
// ... 其他省略了
|
||||
}
|
||||
```
|
||||
|
||||
上面的栗子只是简单说明,另外可以做`异步验证`,validate 方法可以返回一个 promise。
|
||||
|
||||
#### OptionsControl
|
||||
|
||||
如果你的表单组件性质和 amis 的 Select、Checkboxes、List 差不多,用户配置配置 source 可通过 API 拉取选项,你可以用 OptionsControl 取代 FormItem 这个注解。
|
||||
|
||||
用法是一样,功能方面主要多了以下功能。
|
||||
|
||||
* 可以配置 options,options 支持配置 visibleOn hiddenOn 等表达式
|
||||
* 可以配置 `source` 换成动态拉取 options 的功能,source 中有变量依赖会自动重新拉取。
|
||||
* 下发了这些 props,可以更方便选项。
|
||||
- `options` 不管是用户配置的静态 options 还是配置 source 拉取的,下发到组件已经是最终的选项了。
|
||||
- `selectedOptions` 数组类型,当前用户选中的选项。
|
||||
- `loading` 当前选项是否在加载
|
||||
- `onToggle` 切换一个选项的值
|
||||
- `onToggleAll` 切换所有选项的值,类似于全选。
|
||||
|
||||
### Renderer
|
||||
|
||||
非表单类的组件自定义,主要通过 `Renderer` 实现。在开始阅读之前,请先阅读 [amis 工作原理](./sdk.md#工作原理)。
|
||||
|
||||
```jsx
|
||||
import * as React from 'react';
|
||||
import {Renderer} from 'amis';
|
||||
|
||||
@Renderer({
|
||||
test: /(^|\/)my\-renderer$/,
|
||||
})
|
||||
class CustomRenderer extends React.Component {
|
||||
render() {
|
||||
const {tip, body, render} = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>这是自定义组件:{tip}</p>
|
||||
{body ? (
|
||||
<div className="container">
|
||||
{render('body', body, {
|
||||
// 这里的信息会作为 props 传递给子组件,一般情况下都不需要这个
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这里注册一个 React 组件,当节点的 path 信息是 `my-renderer` 结尾时,交给当前组件来完成渲染。
|
||||
|
||||
请注意 `this.props` 中的 `render` 方法,它用来实现容器功能,通过它可以让使用者动态的配置其他渲染模型。
|
||||
|
||||
### 组件间通信
|
||||
|
||||
关于组件间通信,amis 中有个机制就是,把需要被引用的组件设置一个 name 值,然后其他组件就可以通过这个 name 与其通信,比如这个[栗子](./advanced.md#组件间通信)。其实内部是依赖于内部的一个 Scoped Context。你的组件希望可以被别的组件引用,你需要把自己注册进去,默认自定义的非表单类组件并没有把自己注册进去,可以参考以下代码做添加。
|
||||
|
||||
```js
|
||||
import * as React from 'react';
|
||||
import {Renderer, ScopedContext} from 'amis';
|
||||
@Renderer({
|
||||
test: /(?:^|\/)my\-renderer$/,
|
||||
})
|
||||
export class CustomRenderer extends React.Component {
|
||||
static contextType = ScopedContext;
|
||||
|
||||
componentWillMount() {
|
||||
const scoped = this.context;
|
||||
scoped.registerComponent(this);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const scoped = this.context;
|
||||
scoped.unRegisterComponent(this);
|
||||
}
|
||||
|
||||
// 其他部分省略了。
|
||||
}
|
||||
```
|
||||
|
||||
把自己注册进去了,其他组件就能引用到了。同时,如果你想找别的组件,也同样是通过 scoped 这个 context,如: `scoped.getComponentByName("xxxName")` 这样就能拿到目标组件的实例了(前提是目标组件已经配置了 name 为 `xxxName`)。
|
||||
|
||||
### 其他功能方法
|
||||
|
||||
自定义的渲染器 props 会下发一个非常有用的 env 对象。这个 env 有以下功能方法。
|
||||
|
||||
* `env.fetcher` 可以用来做 ajax 请求如: `this.props.env.fetcher('xxxAPi', this.props.data).then((result) => console.log(result))`
|
||||
* `env.confirm` 确认框,返回一个 promise 等待用户确认如: `this.props.env.confirm('你确定要这么做?').then((confirmed) => console.log(confirmed))`
|
||||
* `env.alert` 用 Modal 实现的弹框,个人觉得更美观。
|
||||
* `env.notify` toast 某个消息 如: `this.props.env.notify("error", "出错了")`
|
||||
* `env.jumpTo` 页面跳转。
|
|
@ -2,11 +2,14 @@
|
|||
title: 快速开始
|
||||
---
|
||||
|
||||
这是一个基于 React 框架的页面渲染器,有配置就能生成页面,配置是什么样的?请前往[基本用法](./basic.md)阅读。知道怎么配置后,就可以用以下方式用于自己的项目了。
|
||||
有两种方式使用 amis:
|
||||
|
||||
如果你不会 React 也没关系可以看看 [JSSDK 用法](#JSSDK)。
|
||||
1. [React 组件](#React 组件),可以整合到 React 项目中,适合熟悉 React 的开发者,可以[开发自定义组件进行扩展](../custom)。
|
||||
2. [JSSDK](#JSSDK),可以放到任意页面中使用,能使用 amis 内置的渲染组件,但无法开发自定义组件,适合不使用 React 的项目或不熟悉前端的开发者。
|
||||
|
||||
## 安装依赖
|
||||
## React 组件
|
||||
|
||||
### 安装依赖
|
||||
|
||||
直接通过 npm 安装即可。
|
||||
|
||||
|
@ -14,9 +17,9 @@ title: 快速开始
|
|||
npm i amis
|
||||
```
|
||||
|
||||
## 如何使用?
|
||||
### 整合到 React 组件中
|
||||
|
||||
可以在 React Component 这么使用。
|
||||
可以在 React Component 这么使用(TypeScript)。
|
||||
|
||||
```tsx
|
||||
import * as React from 'react';
|
||||
|
@ -83,21 +86,24 @@ class MyComponent extends React.Component<any, any> {
|
|||
|
||||
参数说明:
|
||||
|
||||
* `schema` 即页面配置,请前往[基本用法](./basic.md)了解.
|
||||
* `props` 一般都用不上,如果你想传递一些数据给渲染器内部使用,可以传递 data 数据进去。如:
|
||||
- `schema` 即页面配置,请前往[基本用法](./basic.md)了解.
|
||||
- `props` 一般都用不上,如果你想传递一些数据给渲染器内部使用,可以传递 data 数据进去。如:
|
||||
|
||||
```jsx
|
||||
() => renderAmis(schema, {
|
||||
() =>
|
||||
renderAmis(schema, {
|
||||
data: {
|
||||
username: 'amis'
|
||||
}
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
这样,内部所有组件都能拿到 `username` 这个变量的值。
|
||||
* `env` 环境变量,可以理解为这个渲染器工具的配置项,需要调用者实现部分接口。
|
||||
* `session: string` 默认为 'global',决定 store 是否为全局共用的,如果想单占一个 store,请设置不同的值。
|
||||
* `fetcher: (config: fetcherConfig) => Promise<fetcherResult>` 用来实现 ajax 发送。
|
||||
|
||||
- `env` 环境变量,可以理解为这个渲染器工具的配置项,需要调用者实现部分接口。
|
||||
|
||||
- `session: string` 默认为 'global',决定 store 是否为全局共用的,如果想单占一个 store,请设置不同的值。
|
||||
- `fetcher: (config: fetcherConfig) => Promise<fetcherResult>` 用来实现 ajax 发送。
|
||||
|
||||
示例
|
||||
|
||||
|
@ -142,48 +148,54 @@ class MyComponent extends React.Component<any, any> {
|
|||
return (axios as any)[method](url, data, config);
|
||||
}
|
||||
```
|
||||
* `isCancel: (e:error) => boolean` 判断 ajax 异常是否为一个 cancel 请求。
|
||||
|
||||
- `isCancel: (e:error) => boolean` 判断 ajax 异常是否为一个 cancel 请求。
|
||||
|
||||
示例
|
||||
|
||||
```js
|
||||
isCancel: (value: any) => (axios as any).isCancel(value)
|
||||
```
|
||||
* `notify: (type:string, msg: string) => void` 用来实现消息提示。
|
||||
* `alert: (msg:string) => void` 用来实现警告提示。
|
||||
* `confirm: (msg:string) => boolean | Promise<boolean>` 用来实现确认框。
|
||||
* `jumpTo: (to:string, action?: Action, ctx?: object) => void` 用来实现页面跳转,因为不清楚所在环境中是否使用了 spa 模式,所以用户自己实现吧。
|
||||
* `updateLocation: (location:any, replace?:boolean) => void` 地址替换,跟 jumpTo 类似。
|
||||
* `isCurrentUrl: (link:string) => boolean` 判断目标地址是否为当前页面。
|
||||
* `theme: 'default' | 'cxd'` 目前支持两种主题。
|
||||
* `copy: (contents:string, options?: {shutup: boolean}) => void` 用来实现,内容复制。
|
||||
* `getModalContainer: () => HTMLElement` 用来决定弹框容器。
|
||||
* `loadRenderer: (chema:any, path:string) => Promise<Function>` 可以通过它懒加载自定义组件,比如: https://github.com/baidu/amis/blob/master/__tests__/factory.test.tsx#L64-L91。
|
||||
* `affixOffsetTop: number` 固顶间距,当你的有其他固顶元素时,需要设置一定的偏移量,否则会重叠。
|
||||
* `affixOffsetBottom: number` 固底间距,当你的有其他固底元素时,需要设置一定的偏移量,否则会重叠。
|
||||
* `richTextToken: string` 内置 rich-text 为 frolaEditor,想要使用,请自行购买,或者自己实现 rich-text 渲染器。
|
||||
|
||||
- `notify: (type:string, msg: string) => void` 用来实现消息提示。
|
||||
- `alert: (msg:string) => void` 用来实现警告提示。
|
||||
- `confirm: (msg:string) => boolean | Promise<boolean>` 用来实现确认框。
|
||||
- `jumpTo: (to:string, action?: Action, ctx?: object) => void` 用来实现页面跳转,因为不清楚所在环境中是否使用了 spa 模式,所以用户自己实现吧。
|
||||
- `updateLocation: (location:any, replace?:boolean) => void` 地址替换,跟 jumpTo 类似。
|
||||
- `isCurrentUrl: (link:string) => boolean` 判断目标地址是否为当前页面。
|
||||
- `theme: 'default' | 'cxd'` 目前支持两种主题。
|
||||
- `copy: (contents:string, options?: {shutup: boolean}) => void` 用来实现,内容复制。
|
||||
- `getModalContainer: () => HTMLElement` 用来决定弹框容器。
|
||||
- `loadRenderer: (chema:any, path:string) => Promise<Function>` 可以通过它懒加载自定义组件,比如: https://github.com/baidu/amis/blob/master/__tests__/factory.test.tsx#L64-L91。
|
||||
- `affixOffsetTop: number` 固顶间距,当你的有其他固顶元素时,需要设置一定的偏移量,否则会重叠。
|
||||
- `affixOffsetBottom: number` 固底间距,当你的有其他固底元素时,需要设置一定的偏移量,否则会重叠。
|
||||
- `richTextToken: string` 内置 rich-text 为 frolaEditor,想要使用,请自行购买,或者自己实现 rich-text 渲染器。
|
||||
|
||||
## JSSDK
|
||||
|
||||
如果你没有组件定制需求直接使用,而且不想折腾 React 相关的,我建议你直接用这种方式。
|
||||
JSSDK 适合对前端或 React 不了解的开发者,它不依赖 npm 及 webpack,直接引入代码就能使用,但需要注意这种方式不支持[定制组件](../sdk),只能使用 amis 内置的组件。
|
||||
|
||||
首先请引用一下 CSS 和 JS。
|
||||
JSSDK 的代码从以下地址获取:
|
||||
|
||||
* JS 地址: https://houtai.baidu.com/v2/jssdk
|
||||
* CSS 地址: https://houtai.baidu.com/v2/csssdk
|
||||
- JS: https://houtai.baidu.com/v2/jssdk
|
||||
- CSS: https://houtai.baidu.com/v2/csssdk
|
||||
|
||||
然后执行以下代码就能渲染了。
|
||||
然后在页面中插入下面的代码就能渲染出来了:
|
||||
|
||||
```js
|
||||
(function() {
|
||||
(function () {
|
||||
var amis = amisRequire('amis/embed');
|
||||
amis.embed('#container', {
|
||||
amis.embed(
|
||||
'#container',
|
||||
{
|
||||
type: 'page',
|
||||
title: 'AMIS Demo',
|
||||
body: 'This is a simple amis page.'
|
||||
}, {
|
||||
},
|
||||
{
|
||||
// props 一般不用传。
|
||||
}, {
|
||||
},
|
||||
{
|
||||
// env
|
||||
fetcher: () => {
|
||||
// 可以不传,用来实现 ajax 请求
|
||||
|
@ -216,30 +228,34 @@ class MyComponent extends React.Component<any, any> {
|
|||
confirm: () => {
|
||||
// 可以不传,用来实现确认框。
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
})();
|
||||
```
|
||||
|
||||
注意:以上的 SDK 地址是一个页面跳转,会跳转到一个 CDN 地址,而且每次跳转都是最新的版本,随着 amis 的升级这个地址会一直变动,如果你的页面已经完成功能回归,请直接使用某个固定地址,这样才不会因为 amis 升级而导致你的页面不可用。
|
||||
|
||||
另外,sdk 代码也伴随 npm 一起发布了,不使用 CDN 版本,直接替换成npm包里面的 `amis/sdk/sdk.js` 和 `amis/sdk/sdk.css` 即可。
|
||||
另外,sdk 代码也伴随 npm 一起发布了,不使用 CDN 版本,直接替换成 npm 包里面的 `amis/sdk.js` 和 `amis/sdk.css` 即可。
|
||||
|
||||
示例:
|
||||
完整示例:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>AMIS Demo</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
|
||||
<link rel="stylesheet"
|
||||
href="https://houtai.baidu.com/v2/csssdk">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1"
|
||||
/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
|
||||
<link rel="stylesheet" href="amis/sdk.js" />
|
||||
<style>
|
||||
html, body, .app-wrapper {
|
||||
html,
|
||||
body,
|
||||
.app-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
@ -247,12 +263,12 @@ class MyComponent extends React.Component<any, any> {
|
|||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root" class="app-wrapper"></div>
|
||||
<script src="https://houtai.baidu.com/v2/jssdk"></script>
|
||||
<script src="amis/sdk.css"></script>
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
(function () {
|
||||
var amis = amisRequire('amis/embed');
|
||||
amis.embed('#root', {
|
||||
type: 'page',
|
||||
|
@ -261,7 +277,6 @@ class MyComponent extends React.Component<any, any> {
|
|||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
|
|
|
@ -0,0 +1,198 @@
|
|||
---
|
||||
title: AMIS 是什么?
|
||||
shortname: intro
|
||||
---
|
||||
|
||||
amis 是一个前端低代码框架,它使用 JSON 配置来生成页面,可以极大节省页面开发工作量,极大提升开发前端界面的效率。
|
||||
|
||||
## 为什么要做 amis?
|
||||
|
||||
在经历了十几年的发展后,前端开发变得越来越复杂,门槛也越来越高,要使用当下流行的 UI 组件库,你必须懂 npm、webpack、react/vue,必须熟悉 ES 6 语法,最好还了解状态管理,比如 Redux,如果没接触过函数式编程,一开始入门就很困难,而它还有巨大的[生态](https://github.com/markerikson/redux-ecosystem-links),相关的库有 2347 个,然而前端技术的发展不会停滞,等学完这些后可能会发现大家都用 Hooks 了、某个打包工具取代 WebPack 了。。。
|
||||
|
||||
而有时候你只是为了做个普通的增删改查界面,用于系统管理,类似下面这种:
|
||||
|
||||
```schema:height="500"
|
||||
{
|
||||
"$schema": "http://amis.baidu.com/v2/schemas/page.json#",
|
||||
"title": "浏览器内核对 CSS 的支持情况",
|
||||
"remark": "嘿,不保证数据准确性",
|
||||
"type": "page",
|
||||
"body": {
|
||||
"type": "crud",
|
||||
"draggable": true,
|
||||
"api": "/api/sample",
|
||||
"keepItemSelectionOnPageChange": true,
|
||||
"filter": {
|
||||
"title": "筛选",
|
||||
"submitText": "",
|
||||
"controls": [
|
||||
{
|
||||
"type": "text",
|
||||
"name": "keywords",
|
||||
"placeholder": "关键字",
|
||||
"addOn": {
|
||||
"label": "搜索",
|
||||
"type": "submit"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"bulkActions": [
|
||||
{
|
||||
"label": "批量删除",
|
||||
"actionType": "ajax",
|
||||
"api": "delete:/api/sample/${ids|raw}",
|
||||
"confirmText": "确定要批量删除?"
|
||||
},
|
||||
{
|
||||
"label": "批量修改",
|
||||
"actionType": "dialog",
|
||||
"dialog": {
|
||||
"title": "批量编辑",
|
||||
"name": "sample-bulk-edit",
|
||||
"body": {
|
||||
"type": "form",
|
||||
"api": "/api/sample/bulkUpdate2",
|
||||
"controls": [
|
||||
{
|
||||
"type": "hidden",
|
||||
"name": "ids"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "engine",
|
||||
"label": "Engine"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"quickSaveApi": "/api/sample/bulkUpdate",
|
||||
"quickSaveItemApi": "/api/sample/$id",
|
||||
"filterTogglable": true,
|
||||
"headerToolbar": [
|
||||
"filter-toggler",
|
||||
"bulkActions",
|
||||
{
|
||||
"type": "tpl",
|
||||
"tpl": "一共有 ${count} 行数据。",
|
||||
"className": "v-middle"
|
||||
},
|
||||
{
|
||||
"type": "columns-toggler",
|
||||
"align": "right"
|
||||
},
|
||||
{
|
||||
"type": "drag-toggler",
|
||||
"align": "right"
|
||||
},
|
||||
{
|
||||
"type": "pagination",
|
||||
"align": "right"
|
||||
}
|
||||
],
|
||||
"footerToolbar": [
|
||||
"statistics",
|
||||
"switch-per-page",
|
||||
"pagination"
|
||||
],
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"label": "ID",
|
||||
"width": 20,
|
||||
"sortable": true,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"name": "engine",
|
||||
"label": "Rendering engine",
|
||||
"sortable": true,
|
||||
"searchable": true,
|
||||
"type": "text",
|
||||
"remark": "Trident 就是 IE,Gecko 就是 Firefox"
|
||||
},
|
||||
{
|
||||
"name": "platform",
|
||||
"label": "Platform(s)",
|
||||
"popOver": {
|
||||
"body": {
|
||||
"type": "tpl",
|
||||
"tpl": "就是为了演示有个叫 popOver 的功能"
|
||||
},
|
||||
"offset": {
|
||||
"y": 50
|
||||
}
|
||||
},
|
||||
"sortable": true,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"name": "grade",
|
||||
"label": "CSS grade",
|
||||
"quickEdit": {
|
||||
"mode": "inline",
|
||||
"type": "select",
|
||||
"options": [
|
||||
"A",
|
||||
"B",
|
||||
"C",
|
||||
"D",
|
||||
"X"
|
||||
]
|
||||
},
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"type": "operation",
|
||||
"label": "操作",
|
||||
"width": 100,
|
||||
"buttons": [
|
||||
{
|
||||
"type": "button",
|
||||
"icon": "fa fa-times text-danger",
|
||||
"actionType": "ajax",
|
||||
"tooltip": "删除",
|
||||
"confirmText": "您确认要删除?",
|
||||
"api": "delete:/api/sample/$id"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这个界面虽然用 Bootstrap 也能快速搭起来,但要想体验好就需要加很多细节功能,比如数据动态加载、编辑单行数据、批量删除和修改、查询某列、按某列排序、隐藏某列、开启整页内容拖拽排序、表格有分页(页数还会同步到地址栏,刷新页面试试)、如果往下拖动还有首行冻结来方便查看表头等,全部实现这些需要大量的代码。
|
||||
|
||||
然而上面也看到了,在 amis 里只需要 150 行 JSON 配置(嘿,其中 40 多行只有一个括号),你不需要了解 React/Vue、Webpack,甚至不需要了解 JavaScript,即便没学过 amis 也能猜到大部分配置的作用,只需要简单配置就能完成所有页面开发,这正是建立 amis 的初衷,我们认为**对于大部分常用页面,应该使用最简单的方法来实现**,而不是越来越复杂。
|
||||
|
||||
## 用 JSON 写页面有什么好处?
|
||||
|
||||
为了实现用最简单方式来生成大部分页面,amis 的解决方案是基于 JSON 来配置,它的独特好处是:
|
||||
|
||||
- **不需要懂前端**就能做出专业且复杂的后台界面,这是所有其他前端 UI 库都无法做到的。在百度内部,大部分 amis 用户之前从来没写过前端页面,也不会 JavaScript。
|
||||
- **不受前端技术更新的影响**,同时还能享受 amis 升级带来的界面改进,百度内部最老的 amis 页面是 4 年多前创建的,至今还在使用,而当年的 Angular/Vue/React 版本现在都废弃了,当年流行的 Gulp 也被 Webpack 取代了,如果这些页面不是用 amis,现在的维护成本会很高。
|
||||
- 可以**完全**使用[可视化页面编辑器](https://fex-team.github.io/amis-editor/#/edit/0)来制作页面,一般前端可视化编辑器只能用来做静态原型,而 amis 可视化编辑器做出的页面是可以直接上线的。
|
||||
|
||||
## amis 的其它亮点
|
||||
|
||||
- **提供完整的界面解决方案**,其它 UI 框架必须使用 JavaScript 来组装业务逻辑,而 amis 只需 JSON 配置就能完整完整功能开发,包括数据获取、表单提交及验证等功能。
|
||||
- 内置 **92** 种 UI 组件,包括其它 UI 框架都会不提供的富文本编辑器、代码编辑器等,能满足各种页面组件展现的需求,而且对于特殊的展现形式还可以通过[自定义组件](../dev)来扩充。
|
||||
- 容器组件支持**无限层级嵌套**,可以通过组合来满足各种布局需求。
|
||||
- 经历了长时间的实战考验,amis 在百度内部得到了广泛使用,在 4 年多的时间里创建了 3w 多页面,从内容审核到机器管理,从数据分析到模型训练,amis 满足了各种各样的页面需求。
|
||||
|
||||
## amis 不适合做什么?
|
||||
|
||||
使用 JSON 有优点但也有明显缺点,在以下场合并不适合 amis:
|
||||
|
||||
- 大量定制 UI,尤其是面向普通客户(toC)的产品页面
|
||||
- JSON 配置使得 amis 更适合做有大量常见 UI 组件的页面,但对于面向普通客户的页面,往往追求个性化的视觉效果,这种情况下用 amis 就不合适,实际上绝大部分前端 UI 组件库也都不适合,只能定制开发。
|
||||
- 有极为复杂的交互,或者对交互有很特殊的要求
|
||||
- 有些复杂的前端功能,比如可视化编辑器,其中有大量定制的拖拽操作,这种需要依赖原生 DOM 实现的功能无法使用 amis。
|
||||
- 但对于某些交互固定的领域,比如图连线,amis 后续会有专门的组件来实现。
|
||||
|
||||
## 接下来
|
||||
|
||||
请阅读[快速开始](../getting-started)来学习如何使用 amis。
|
|
@ -2,7 +2,7 @@
|
|||
title: 渲染器手册
|
||||
---
|
||||
|
||||
amis 页面是通过 JSON 配置出来的,是由一个一个渲染模型组成的,掌握他们规则,就能灵活配置出各种页面。
|
||||
amis 页面是由一个个渲染模型组成的,并且支持无限层级嵌套,掌握他们规则,就能灵活配置出各种页面。
|
||||
|
||||
开始之前,请您一定要先阅读[基本用法](./basic.md)。
|
||||
|
||||
|
@ -95,6 +95,6 @@ amis 页面是通过 JSON 配置出来的,是由一个一个渲染模型组成
|
|||
- [Button-Group](./renderers/Button-Group.md): 按钮集合
|
||||
- [iFrame](./renderers/iFrame.md): 如果需要内嵌外部站点,可用 iframe 来实现
|
||||
- [Nav](./renderers/Nav.md): 菜单栏
|
||||
- [Tasks](./renderers/Tasks.md): 任务操作集合,类似于 orp 上线
|
||||
- [Tasks](./renderers/Tasks.md): 任务操作集合,适用于一步步操作
|
||||
- [QRCode](./renderers/QRCode.md): 二维码显示组件
|
||||
- [Types](./renderers/Types.md): 类型说明文档
|
||||
|
|
|
@ -5,7 +5,7 @@ Action 是一种特殊的渲染器,它本身是一个按钮,同时它能触
|
|||
```schema:height="100" scope="body"
|
||||
{
|
||||
"label": "弹个框",
|
||||
"type": "button",
|
||||
"type": "action",
|
||||
"level": "dark",
|
||||
"actionType": "dialog",
|
||||
"dialog": {
|
||||
|
@ -23,7 +23,7 @@ Action 是一种特殊的渲染器,它本身是一个按钮,同时它能触
|
|||
|
||||
| 属性名 | 类型 | 默认值 | 说明 |
|
||||
| ---------------- | --------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| type | `string` | `action` | 指定为 Page 渲染器。 |
|
||||
| type | `string` | `action` | 指定为 Action 渲染器,也可以是 `button`、`submit`、`reset`。 |
|
||||
| actionType | `string` | - | 【必填】这是 action 最核心的配置,来指定该 action 的作用类型,支持:`ajax`、`link`、`url`、`drawer`、`dialog`、`confirm`、`cancel`、`prev`、`next`、`copy`、`close`。 |
|
||||
| label | `string` | - | 按钮文本。可用 `${xxx}` 取值。 |
|
||||
| level | `string` | `default` | 按钮样式,支持:`link`、`primary`、`secondary`、`info`、`success`、`warning`、`danger`、`light`、`dark`、`default`。 |
|
||||
|
|
336
docs/sdk.md
336
docs/sdk.md
|
@ -1,336 +0,0 @@
|
|||
---
|
||||
title: 如何定制
|
||||
---
|
||||
|
||||
开始定制之前,请先仔细阅读工作原理。
|
||||
|
||||
## 工作原理
|
||||
|
||||
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
|
||||
@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>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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 信息去组件池里面找到对应的渲染器,然后交给对应组件去完成渲染。
|
||||
|
||||
## 自定义组件
|
||||
|
||||
如果 amis 中组件不能满足你的需求,同时你又会 React 组件开发,那么就自己定制一个吧。
|
||||
|
||||
先来看个简单的例子
|
||||
|
||||
```jsx
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Renderer
|
||||
} from 'amis';
|
||||
|
||||
@Renderer({
|
||||
test: /(^|\/)my\-renderer$/,
|
||||
})
|
||||
class CustomRenderer extends React.Component {
|
||||
render() {
|
||||
const {tip} = this.props;
|
||||
return (
|
||||
<div>这是自定义组件:{tip}</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
有了以上这段代码后,就可以这样使用了。
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "page",
|
||||
"title": "自定义组件示例",
|
||||
"body": {
|
||||
"type": "my-renderer",
|
||||
"tip": "简单示例"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
如果你看了[amis工作原理](#工作原理)应该不难理解,这里注册一个 React 组件,当节点的 path 信息是 `my-renderer` 结尾时,交给当前组件来完成渲染。
|
||||
如果你只写叶子节点的渲染器,已经可以不用看了,如果你的渲染器中有容器需要可以放置其他节点,那么接着看以下这段代码。
|
||||
|
||||
```jsx
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Renderer
|
||||
} from 'amis';
|
||||
|
||||
@Renderer({
|
||||
test: /(^|\/)my\-renderer2$/,
|
||||
})
|
||||
class CustomRenderer extends React.Component {
|
||||
render() {
|
||||
const {
|
||||
tip,
|
||||
body,
|
||||
render
|
||||
} = this.props;
|
||||
return (
|
||||
<div>
|
||||
<p>这是自定义组件:{tip}</p>
|
||||
{body ? (
|
||||
<div className="container">
|
||||
{render('body', body, {
|
||||
// 这里的信息会作为 props 传递给子组件,一般情况下都不需要这个
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
有了以上这段代码后,就可以这样使用了。
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "page",
|
||||
"title": "自定义组件示例",
|
||||
"body": {
|
||||
"type": "my-renderer2",
|
||||
"tip": "简单示例",
|
||||
"body": {
|
||||
"type": "form",
|
||||
"controls": [
|
||||
{
|
||||
"type": "text",
|
||||
"label": "用户名",
|
||||
"name": "usename"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
跟第一个列子不同的地方是,这里多了个 `render` 方法,这个方法就是专门用来渲染子节点的。来看下参数说明:
|
||||
|
||||
* `region` 区域名称,你有可能有多个区域可以作为容器,请不要重复。
|
||||
* `node` 子节点。
|
||||
* `props` 可选,可以通过此对象跟子节点通信等。
|
||||
|
||||
以上是普通渲染器的注册方式,如果是表单项,为了更简单的扩充,请使用 `FormItem` 注解,而不是 `Renderer`。 原因是如果用 `FormItem` 是不用关心:label怎么摆,表单验证器怎么实现,如何适配表单的3中展现方式(水平、上下和内联模式),而只用关心:有了值后如何回显,响应用户交互设置新值。
|
||||
|
||||
```jsx
|
||||
import * as React from 'react';
|
||||
import {
|
||||
FormItem
|
||||
} from 'amis';
|
||||
|
||||
@FormItem({
|
||||
type: 'custom'
|
||||
})
|
||||
class MyFormItem extends React.Component {
|
||||
render() {
|
||||
const {
|
||||
value,
|
||||
onChange
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>这个是个自定义组件</p>
|
||||
<p>当前值:{value}</p>
|
||||
<a className="btn btn-default" onClick={() => onChange(Math.round(Math.random() * 10000))}>随机修改</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
有了以上这段代码后,就可以这样使用了。
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "page",
|
||||
"title": "自定义组件示例",
|
||||
"body": {
|
||||
"type": "form",
|
||||
"controls": [
|
||||
{
|
||||
"type": "text",
|
||||
"label": "用户名",
|
||||
"name": "usename"
|
||||
},
|
||||
|
||||
{
|
||||
"type": "custom",
|
||||
"label": "随机值",
|
||||
"name": "random"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 注意: 使用 FormItem 默认是严格模式,即只有必要的属性变化才会重新渲染,有可能满足不了你的需求,如果忽略性能问题,可以传入 `strictMode`: `false` 来关闭。
|
||||
|
||||
以上的例子都是需要先注册组件,然后再使用的,如果你在自己项目中使用,还有更简单的用法,以下示例直接无需注册。
|
||||
|
||||
```jsx
|
||||
{
|
||||
"type": "page",
|
||||
"title": "自定义组件示例",
|
||||
"body": {
|
||||
"type": "form",
|
||||
"controls": [
|
||||
{
|
||||
"type": "text",
|
||||
"label": "用户名",
|
||||
"name": "usename"
|
||||
},
|
||||
|
||||
{
|
||||
"name": "a",
|
||||
"children": ({
|
||||
value,
|
||||
onChange
|
||||
}) => (
|
||||
<div>
|
||||
<p>这个是个自定义组件</p>
|
||||
<p>当前值:{value}</p>
|
||||
<a className="btn btn-default" onClick={() => onChange(Math.round(Math.random() * 10000))}>随机修改</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
即:通过 `children` 实现一个自定义渲染方法,返回 React.ReactNode 节点。
|
||||
任何节点如果包含 `children` 这个属性,则都会把当前节点交给 `children` 来处理,跳过了从 amis 渲染器池子中选择渲染器的过程。`children` 属性其实更应该叫 `render` 属性,但是历史原因不能改了。与之类似的还有个 `component` 属性,这个属性可以传入 React Component,如果想用 React.hooks,请通过 `component` 传递,而不是 `children`。
|
|
@ -1,11 +1,9 @@
|
|||
---
|
||||
title: 样式表说明
|
||||
title: 定制样式
|
||||
shortname: style
|
||||
---
|
||||
|
||||
amis 中有大量的功能类 class 可以使用,即可以用在 schema 中,也可以用在自定义组件开发中,掌握这些 class, 几乎可以不用写样式。
|
||||
|
||||
amis 中的样式基于 [BootStrap V3](http://getbootstrap.com/css/), 这里主要讲 Bootstrap 中没有涉及到的。
|
||||
绝大部分 amis 组件里都有个 `className` 配置项,设置后就会给对应的组件添加 css class,而 amis 内置了大量的功能类 class,通过这些 class 的组合就能满足大部分展现调整的需求。
|
||||
|
||||
## 图标
|
||||
|
||||
|
|
|
@ -102,6 +102,7 @@ if (process.env.NODE_ENV === 'production') {
|
|||
}
|
||||
|
||||
const navigations = [
|
||||
Docs,
|
||||
{
|
||||
label: '示例',
|
||||
children: [
|
||||
|
@ -533,9 +534,9 @@ const navigations = [
|
|||
component: TestComponent
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Docs
|
||||
];
|
||||
|
||||
function isActive(link, location) {
|
||||
|
@ -898,9 +899,9 @@ export default function entry({pathPrefix}) {
|
|||
<Route component={App}>
|
||||
<Redirect
|
||||
from={`${ContextPath}/`}
|
||||
to={`${ContextPath}${PathPrefix}/pages/simple`}
|
||||
to={`${ContextPath}/docs/intro`}
|
||||
/>
|
||||
<Redirect from={`${PathPrefix}/`} to={`${PathPrefix}/pages/simple`} />
|
||||
<Redirect from={`${PathPrefix}/`} to={`/docs/intro`} />
|
||||
{navigations2route(PathPrefix)}
|
||||
<Route path="*" component={NotFound} />
|
||||
</Route>
|
||||
|
|
|
@ -5,6 +5,16 @@ export default {
|
|||
prefix: ({classnames: cx}) => <li className={cx('AsideNav-divider')} />,
|
||||
label: '文档',
|
||||
children: [
|
||||
{
|
||||
label: 'AMIS 是什么?',
|
||||
icon: 'fa fa-home',
|
||||
path: '/docs/intro',
|
||||
getComponent: (location, cb) =>
|
||||
require(['../../docs/intro.md'], doc => {
|
||||
cb(null, makeMarkdownRenderer(doc));
|
||||
})
|
||||
},
|
||||
|
||||
{
|
||||
label: '快速开始',
|
||||
icon: 'fa fa-flash',
|
||||
|
@ -38,7 +48,7 @@ export default {
|
|||
// {{renderer-docs}}
|
||||
|
||||
{
|
||||
label: 'API 说明',
|
||||
label: '动态数据',
|
||||
path: '/docs/api',
|
||||
icon: 'fa fa-cloud',
|
||||
getComponent: (location, cb) =>
|
||||
|
@ -48,27 +58,17 @@ export default {
|
|||
},
|
||||
|
||||
{
|
||||
label: '如何定制',
|
||||
path: '/docs/sdk',
|
||||
label: '定制功能',
|
||||
path: '/docs/custom',
|
||||
icon: 'fa fa-cubes',
|
||||
getComponent: (location, cb) =>
|
||||
require(['../../docs/sdk.md'], doc => {
|
||||
require(['../../docs/custom.md'], doc => {
|
||||
cb(null, makeMarkdownRenderer(doc));
|
||||
})
|
||||
},
|
||||
|
||||
{
|
||||
label: '自定义组件',
|
||||
path: '/docs/dev',
|
||||
icon: 'fa fa-code',
|
||||
getComponent: (location, cb) =>
|
||||
require(['../../docs/dev.md'], doc => {
|
||||
cb(null, makeMarkdownRenderer(doc));
|
||||
})
|
||||
},
|
||||
|
||||
{
|
||||
label: '样式说明',
|
||||
label: '定制样式',
|
||||
path: '/docs/style',
|
||||
icon: 'fa fa-laptop',
|
||||
getComponent: (location, cb) =>
|
||||
|
|
|
@ -205,6 +205,7 @@ export default function (schema) {
|
|||
>
|
||||
<i className="fa fa-code" />
|
||||
</Button>
|
||||
<span>←点击这里查看源码</span>
|
||||
</Portal>
|
||||
) : null}
|
||||
</div>
|
||||
|
|
|
@ -4,19 +4,38 @@
|
|||
<meta charset="UTF-8" />
|
||||
<title>AMis Renderer</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1"
|
||||
/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
|
||||
<link rel="stylesheet" href="https://bce.bdstatic.com/iconfont/iconfont.css" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://bce.bdstatic.com/iconfont/iconfont.css"
|
||||
/>
|
||||
<link rel="stylesheet" href="font-awesome/css/font-awesome.css" />
|
||||
<link rel="stylesheet" href="bootstrap/dist/css/bootstrap.css" />
|
||||
<link rel="stylesheet" href="animate.css/animate.css" />
|
||||
<link rel="stylesheet" href="highlight.js/styles/github.css" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css" />
|
||||
<link rel="stylesheet" href="prismjs/themes/prism.css" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css"
|
||||
/>
|
||||
<!--DEPENDENCIES_INJECT_PLACEHOLDER-->
|
||||
<link rel="stylesheet" href="./doc.css" />
|
||||
<link rel="stylesheet" title="default" href="../scss/themes/default.scss" />
|
||||
<link rel="stylesheet" title="cxd" disabled href="../scss/themes/cxd.scss" />
|
||||
<link rel="stylesheet" title="dark" disabled href="../scss/themes/dark.scss" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
title="cxd"
|
||||
disabled
|
||||
href="../scss/themes/cxd.scss"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
title="dark"
|
||||
disabled
|
||||
href="../scss/themes/dark.scss"
|
||||
/>
|
||||
<link rel="stylesheet" href="./style.scss" />
|
||||
<style>
|
||||
.app-wrapper {
|
||||
|
@ -35,7 +54,7 @@
|
|||
var _hmt = _hmt || [];
|
||||
|
||||
// 百度统计
|
||||
(function() {
|
||||
(function () {
|
||||
var hm = document.createElement('script');
|
||||
hm.src = 'https://hm.baidu.com/hm.js?1f80f2c9dbe21dc3af239cf9eee90f1f';
|
||||
var s = document.getElementsByTagName('script')[0];
|
||||
|
@ -43,7 +62,7 @@
|
|||
})();
|
||||
|
||||
/* @require ./index.jsx 标记为同步依赖,提前加载 */
|
||||
require(['./index.jsx'], function(app) {
|
||||
require(['./index.jsx'], function (app) {
|
||||
var initialState = {};
|
||||
app.bootstrap(document.getElementById('root'), initialState);
|
||||
});
|
||||
|
|
|
@ -1,20 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<link rel="stylesheet"
|
||||
href="font-awesome/css/font-awesome.css">
|
||||
<link rel="stylesheet"
|
||||
href="bootstrap/dist/css/bootstrap.css">
|
||||
<link rel="stylesheet"
|
||||
href="animate.css/animate.css">
|
||||
<link rel="stylesheet"
|
||||
href="highlight.js/styles/github.css">
|
||||
<link rel="stylesheet" href="highlight.js/styles/github.css">
|
||||
<link rel="stylesheet" title="default" href="../scss/themes/default.scss">
|
||||
<head>
|
||||
<link rel="stylesheet" href="font-awesome/css/font-awesome.css" />
|
||||
<link rel="stylesheet" href="bootstrap/dist/css/bootstrap.css" />
|
||||
<link rel="stylesheet" href="animate.css/animate.css" />
|
||||
<link rel="stylesheet" href="prismjs/themes/prism.css" />
|
||||
<link rel="stylesheet" title="default" href="../scss/themes/default.scss" />
|
||||
<!--DEPENDENCIES_INJECT_PLACEHOLDER-->
|
||||
<!--STYLE_PLACEHOLDER-->
|
||||
</head>
|
||||
<body>
|
||||
</head>
|
||||
<body>
|
||||
<script data-loader src="./sdk-mod.js"></script>
|
||||
<script>
|
||||
/* @require "./embed.tsx" */
|
||||
|
@ -24,10 +19,10 @@
|
|||
amis.resource = {
|
||||
css: css,
|
||||
js: js,
|
||||
moduleId:__moduleId('./embed.tsx')
|
||||
moduleId: __moduleId('./embed.tsx')
|
||||
};
|
||||
w.amis = amis;
|
||||
})(window);
|
||||
</script>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -124,7 +124,6 @@
|
|||
"fis3-preprocessor-js-require-css": "^0.1.3",
|
||||
"font-awesome": "4.7.0",
|
||||
"fs-walk": "0.0.2",
|
||||
"highlight.js": "^9.12.0",
|
||||
"husky": "^2.2.0",
|
||||
"jest": "^24.5.0",
|
||||
"jest-canvas-mock": "^2.1.0",
|
||||
|
@ -133,6 +132,7 @@
|
|||
"marked": "^0.3.7",
|
||||
"mobx-wiretap": "^0.12.0",
|
||||
"prettier": "^2.0.5",
|
||||
"prismjs": "^1.20.0",
|
||||
"react-frame-component": "^2.0.0",
|
||||
"react-router": "3.2.0",
|
||||
"react-test-renderer": "^16.8.6",
|
||||
|
|
Loading…
Reference in New Issue