优化一下文档,对新人更友好

This commit is contained in:
wuduoyi 2020-06-08 18:11:26 +08:00
parent 789a035016
commit e77b0bf884
19 changed files with 1132 additions and 941 deletions

View File

@ -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

View File

@ -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 10node 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)
## 讨论

View File

@ -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) + ';';
};

View File

@ -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`

View File

@ -2,8 +2,6 @@
title: 基本用法
---
为了简化前端开发amis Renderer 能够直接用配置就能将页面渲染出来。
先来看个简单的例子。
```schema:height="300"
@ -19,9 +17,9 @@ title: 基本用法
}
```
> PS: 可以通过编辑器实时修改预览
> 可以通过编辑器实时修改预览
从上面的内容可以看出,一个简单页面框架已经基本出来了,这是 amis 渲染器配置的入口。从 `page` 渲染器开始出发,通过在容器中放置不同的渲染器来配置不同性质的页面。
通过使用上面的例子就能配出一个基本页面框架,这是 amis 渲染器配置的入口。从 `page` 渲染器开始出发,通过在容器中放置不同的渲染器来配置不同性质的页面。
简单说明以上配置信息。

433
docs/custom.md Normal file
View File

@ -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 这个注解。
用法是一样,功能方面主要多了以下功能。
- 可以配置 optionsoptions 支持配置 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` 页面跳转。

View File

@ -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 这个注解。
用法是一样,功能方面主要多了以下功能。
* 可以配置 optionsoptions 支持配置 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` 页面跳转。

View File

@ -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>
```

198
docs/intro.md Normal file
View File

@ -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 就是 IEGecko 就是 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。

View File

@ -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): 类型说明文档

View File

@ -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`。 |

View File

@ -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`

View File

@ -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 的组合就能满足大部分展现调整的需求。
## 图标

View File

@ -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>

View File

@ -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) =>

View File

@ -205,6 +205,7 @@ export default function (schema) {
>
<i className="fa fa-code" />
</Button>
<span>点击这里查看源码</span>
</Portal>
) : null}
</div>

View File

@ -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);
});

View File

@ -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>

View File

@ -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",