forked from p96170835/amis
commit
fe7dcade48
|
@ -1,8 +1,8 @@
|
||||||
# amis
|
# 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
|
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.
|
# Install project npm dependencies.
|
||||||
Npm i
|
npm i
|
||||||
|
|
||||||
# Start compiling and output the code to the webroot directory of the service you just opened.
|
# 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.
|
# Open the fis3 service, please visit http://127.0.0.1:8888/examples/pages/simple.
|
||||||
Npm start
|
npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
#Installation dependency
|
#Installation dependency
|
||||||
Npm i
|
npm i
|
||||||
|
|
||||||
#Executing test cases
|
#Executing test cases
|
||||||
Npm test
|
npm test
|
||||||
|
|
||||||
# View test case coverage
|
# View test case coverage
|
||||||
Npm run coverage
|
npm run coverage
|
||||||
```
|
```
|
||||||
|
|
||||||
## Working with documents
|
## Working with documents
|
||||||
|
|
||||||
For a better reading experience, it is recommended to read https://baidu.github.io/amis/ directly in gh-pages.
|
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)
|
- [Quick Start](/docs/getting_started.md)
|
||||||
* [Basic Usage] (/docs/basic.md)
|
- [Basic Usage](/docs/basic.md)
|
||||||
* [Advanced Usage] (/docs/advanced.md)
|
- [Advanced Usage](/docs/advanced.md)
|
||||||
* [Render Manual] (/docs/renderers.md)
|
- [Render Manual](/docs/renderers.md)
|
||||||
* [How to customize] (/docs/sdk.md)
|
- [How to customize](/docs/sdk.md)
|
||||||
* [custom component] (/docs/dev.md)
|
- [custom component](/docs/dev.md)
|
||||||
* [Auxiliary Style] (/docs/style.md)
|
- [Auxiliary Style](/docs/style.md)
|
||||||
|
|
||||||
## How to contribute
|
## How to contribute
|
||||||
|
|
||||||
|
@ -50,9 +50,9 @@ Please write in typescript, all reasonable changes, new public renderers, use ca
|
||||||
|
|
||||||
## Maintainer
|
## Maintainer
|
||||||
|
|
||||||
* [2betop](https://github.com/2betop)
|
- [2betop](https://github.com/2betop)
|
||||||
* [RickCole21] (https://github.com/RickCole21)
|
- [RickCole21](https://github.com/RickCole21)
|
||||||
* [catchonme](https://github.com/catchonme)
|
- [catchonme](https://github.com/catchonme)
|
||||||
|
|
||||||
## Discussion
|
## Discussion
|
||||||
|
|
21
README.md
21
README.md
|
@ -1,24 +1,28 @@
|
||||||
# amis
|
# 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) 上的镜像。
|
> 如果 github 下载慢可以使用 [gitee](https://gitee.com/baidu/amis) 上的镜像。
|
||||||
|
|
||||||
推荐使用 node 10,node 6 和 node 8 也可以。node 12 未测试,可能 fis3 还有些插件不支持。
|
推荐使用 node 8/10/12。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 安装项目 npm 依赖。
|
# 安装项目 npm 依赖,在 node 12 下会有报错但不影响正常使用。
|
||||||
npm i
|
npm i
|
||||||
|
|
||||||
# 开始编译,把代码产出到刚开启的服务的 webroot 目录。
|
# 开始编译,把代码产出到刚开启的服务的 webroot 目录。
|
||||||
|
@ -51,6 +55,7 @@ npm run coverage
|
||||||
- [2betop](https://github.com/2betop)
|
- [2betop](https://github.com/2betop)
|
||||||
- [RickCole21](https://github.com/RickCole21)
|
- [RickCole21](https://github.com/RickCole21)
|
||||||
- [catchonme](https://github.com/catchonme)
|
- [catchonme](https://github.com/catchonme)
|
||||||
|
- [nwind](https://github.com/nwind)
|
||||||
|
|
||||||
## 讨论
|
## 讨论
|
||||||
|
|
||||||
|
|
|
@ -1,25 +1,32 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
var marked = require("marked");
|
var marked = require('marked');
|
||||||
var yaml = (yaml = require("js-yaml"));
|
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 rYml = /^\s*---([\s\S]*?)---\s/;
|
||||||
var renderer = new marked.Renderer();
|
var renderer = new marked.Renderer();
|
||||||
marked.setOptions({
|
marked.setOptions({
|
||||||
renderer: renderer,
|
renderer: renderer,
|
||||||
gfm: true,
|
gfm: true,
|
||||||
tables: true,
|
tables: true,
|
||||||
breaks: false,
|
breaks: false,
|
||||||
pedantic: false,
|
pedantic: false,
|
||||||
sanitize: true,
|
sanitize: true,
|
||||||
smartLists: true,
|
smartLists: true,
|
||||||
smartypants: false
|
smartypants: false
|
||||||
});
|
});
|
||||||
|
|
||||||
// Synchronous highlighting with highlight.js
|
// Synchronous highlighting with prism.js
|
||||||
marked.setOptions({
|
marked.setOptions({
|
||||||
highlight: function(code) {
|
highlight: function (code, lang) {
|
||||||
return require("highlight.js").highlightAuto(code).value;
|
if (lang) {
|
||||||
|
return prism.highlight(code, prism.languages[lang], lang);
|
||||||
|
} else {
|
||||||
|
return code;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// renderer.table = function(header, body) {
|
// renderer.table = function(header, body) {
|
||||||
|
@ -33,155 +40,150 @@ marked.setOptions({
|
||||||
// + '</table>\n';
|
// + '</table>\n';
|
||||||
// };
|
// };
|
||||||
|
|
||||||
renderer.link = function(href, title, text) {
|
renderer.link = function (href, title, text) {
|
||||||
if (this.options.sanitize) {
|
if (this.options.sanitize) {
|
||||||
try {
|
try {
|
||||||
var prot = decodeURIComponent(unescape(href))
|
var prot = decodeURIComponent(unescape(href))
|
||||||
.replace(/[^\w:]/g, "")
|
.replace(/[^\w:]/g, '')
|
||||||
.toLowerCase();
|
.toLowerCase();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return "";
|
return '';
|
||||||
}
|
|
||||||
if (
|
|
||||||
prot.indexOf("javascript:") === 0 ||
|
|
||||||
prot.indexOf("vbscript:") === 0
|
|
||||||
) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (href && href[0] === "#") {
|
if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0) {
|
||||||
href = "#" +
|
return '';
|
||||||
encodeURIComponent(
|
|
||||||
href
|
|
||||||
.substring(1)
|
|
||||||
.toLowerCase()
|
|
||||||
.replace(/[^\u4e00-\u9fa5_a-zA-Z0-9]+/g, "-")
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (href && href[0] === '#') {
|
||||||
|
href =
|
||||||
|
'#' +
|
||||||
|
encodeURIComponent(
|
||||||
|
href
|
||||||
|
.substring(1)
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[^\u4e00-\u9fa5_a-zA-Z0-9]+/g, '-')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
var out = '<a href="' + href + '"';
|
var out = '<a href="' + href + '"';
|
||||||
if (title) {
|
if (title) {
|
||||||
out += ' title="' + title + '"';
|
out += ' title="' + title + '"';
|
||||||
}
|
}
|
||||||
out += ">" + text + "</a>";
|
out += '>' + text + '</a>';
|
||||||
return out;
|
return out;
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = function(content, file) {
|
module.exports = function (content, file) {
|
||||||
var m = rYml.exec(content);
|
var m = rYml.exec(content);
|
||||||
var info = {};
|
var info = {};
|
||||||
if (m && m[1]) {
|
if (m && m[1]) {
|
||||||
info = yaml.safeLoad(m[1]);
|
info = yaml.safeLoad(m[1]);
|
||||||
content = content.substring(m[0].length);
|
content = content.substring(m[0].length);
|
||||||
}
|
}
|
||||||
|
|
||||||
var toc = {
|
var toc = {
|
||||||
label: "目录",
|
label: '目录',
|
||||||
type: "toc",
|
type: 'toc',
|
||||||
children: [],
|
children: [],
|
||||||
level: 0
|
level: 0
|
||||||
};
|
};
|
||||||
var stack = [toc];
|
var stack = [toc];
|
||||||
|
|
||||||
renderer.heading = function(text, level) {
|
renderer.heading = function (text, level) {
|
||||||
var escapedText = encodeURIComponent(
|
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,
|
|
||||||
level: level
|
|
||||||
};
|
|
||||||
|
|
||||||
while (stack.length && stack[0].level >= level) {
|
|
||||||
stack.shift();
|
|
||||||
}
|
|
||||||
|
|
||||||
stack[0].children = stack[0].children || [];
|
|
||||||
stack[0].children.push(menu);
|
|
||||||
|
|
||||||
stack.unshift(menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
var anchor =
|
|
||||||
'<a class="anchor" name="' +
|
|
||||||
escapedText +
|
|
||||||
'" href="#' +
|
|
||||||
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 + '><a name="' +
|
|
||||||
// escapedText +
|
|
||||||
// '" class="anchor" href="#' +
|
|
||||||
// escapedText +
|
|
||||||
// '"><span class="header-link"></span></a>' +
|
|
||||||
// text + '</h' + level + '>';
|
|
||||||
};
|
|
||||||
|
|
||||||
const placeholder = {};
|
|
||||||
let index = 1;
|
|
||||||
|
|
||||||
content = content.replace(
|
|
||||||
/```(schema|html)(?::(.*?))?\n([\s\S]*?)```/g,
|
|
||||||
function(_, lang, attr, code) {
|
|
||||||
const setting = {};
|
|
||||||
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]) : "";
|
|
||||||
|
|
||||||
if (parts[0] === 'height') {
|
|
||||||
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 (~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>`;
|
|
||||||
} else {
|
|
||||||
placeholder[
|
|
||||||
index
|
|
||||||
] = `<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(
|
if (level < 5) {
|
||||||
_,
|
var menu = {
|
||||||
id
|
label: text,
|
||||||
) {
|
fragment: escapedText,
|
||||||
return placeholder[id] || "";
|
fullPath: '#' + escapedText,
|
||||||
});
|
level: level
|
||||||
|
};
|
||||||
|
|
||||||
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>`;
|
while (stack.length && stack[0].level >= level) {
|
||||||
info.html = content;
|
stack.shift();
|
||||||
info.toc = toc;
|
}
|
||||||
|
|
||||||
|
stack[0].children = stack[0].children || [];
|
||||||
|
stack[0].children.push(menu);
|
||||||
|
|
||||||
return "module.exports = " + JSON.stringify(info, null, 2) + ";";
|
stack.unshift(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
var anchor =
|
||||||
|
'<a class="anchor" name="' +
|
||||||
|
escapedText +
|
||||||
|
'" href="#' +
|
||||||
|
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 + '><a name="' +
|
||||||
|
// escapedText +
|
||||||
|
// '" class="anchor" href="#' +
|
||||||
|
// escapedText +
|
||||||
|
// '"><span class="header-link"></span></a>' +
|
||||||
|
// text + '</h' + level + '>';
|
||||||
|
};
|
||||||
|
|
||||||
|
const placeholder = {};
|
||||||
|
let index = 1;
|
||||||
|
|
||||||
|
content = content.replace(
|
||||||
|
/```(schema|html)(?::(.*?))?\n([\s\S]*?)```/g,
|
||||||
|
function (_, lang, attr, code) {
|
||||||
|
const setting = {};
|
||||||
|
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]) : '';
|
||||||
|
|
||||||
|
if (parts[0] === 'height') {
|
||||||
|
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 (~code.indexOf('<html')) {
|
||||||
|
return _;
|
||||||
|
}
|
||||||
|
|
||||||
|
placeholder[
|
||||||
|
index
|
||||||
|
] = `<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>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `[[${index++}]]`;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
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>`;
|
||||||
|
info.html = content;
|
||||||
|
info.toc = toc;
|
||||||
|
|
||||||
|
return 'module.exports = ' + JSON.stringify(info, null, 2) + ';';
|
||||||
};
|
};
|
||||||
|
|
121
docs/api.md
121
docs/api.md
|
@ -1,60 +1,111 @@
|
||||||
---
|
---
|
||||||
title: API 说明
|
title: 动态数据
|
||||||
---
|
---
|
||||||
|
|
||||||
amis 渲染器的数据都来源于 api,有一定的格式要求。
|
除了渲染静态页面及表单,amis 还能渲染动态数据,比如下面这个表格数据是来自 api 这个接口的请求
|
||||||
|
|
||||||
### 整体要求
|
|
||||||
|
|
||||||
要求每个接口都返回 `status` 字段用来表示成功还是失败,如果失败了,通过 `msg` 字段来说明失败原因。当然如果成功 `msg` 也可以用来设置提示信息。
|
|
||||||
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"status": 0, // 0 表示成功,非0 表示失败
|
"type": "crud",
|
||||||
"msg": "", // 提示信息 包括失败和成功
|
"api": " http://xxx/api/sample",
|
||||||
"data": {
|
"columns": [
|
||||||
// ...
|
{
|
||||||
// 具体的数据
|
"name": "engine",
|
||||||
|
"label": "引擎"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "browser",
|
||||||
|
"label": "浏览器"
|
||||||
}
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
如果你的系统有自己的规范,也没关系,fetcher 整体入口那加个适配器就行了如:
|
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 表示失败
|
||||||
|
"msg": "", // 提示信息 包括失败和成功
|
||||||
|
"data": {
|
||||||
|
// ...
|
||||||
|
// 具体的数据
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
如果你的系统有自己的规范,可以在 fetcher 统一进行适配,如:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
{
|
{
|
||||||
fetcher: function(api) {
|
renderAmis(
|
||||||
|
{
|
||||||
|
// 这里是 amis 的 Json 配置。
|
||||||
|
type: 'page',
|
||||||
|
title: '简单页面',
|
||||||
|
body: '内容'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// props
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 忽略别的设置项
|
||||||
|
fetcher: function (api) {
|
||||||
// 适配这种格式 {"code": 0, "message": "", "result": {}}
|
// 适配这种格式 {"code": 0, "message": "", "result": {}}
|
||||||
return axios(config).then(response => {
|
return axios(config).then(response => {
|
||||||
let payload = {
|
let payload = {
|
||||||
status: response.data.code,
|
status: response.data.code,
|
||||||
msg: response.data.message,
|
msg: response.data.message,
|
||||||
data: response.data.result
|
data: response.data.result
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...response,
|
...response,
|
||||||
data: payload
|
data: payload
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 具体要求
|
### 具体要求
|
||||||
|
|
||||||
每个渲染的接口返回都有自己的格式要求,主要体现在 data 字段内部,具体请参考每个渲染的接口说明。
|
每个渲染的接口返回都有自己的格式要求,主要体现在 data 字段内部,具体请参考每个渲染的接口说明。
|
||||||
|
|
||||||
* [Page](./renderers/Page.md#接口说明)
|
- [Page](./renderers/Page.md#接口说明)
|
||||||
* [CRUD](./renderers/CRUD.md#接口说明)
|
- [CRUD](./renderers/CRUD.md#接口说明)
|
||||||
* [Form](./renderers/Form/Form.md#接口说明)
|
- [Form](./renderers/Form/Form.md#接口说明)
|
||||||
* [Select](./renderers/Form/Select.md#接口说明)
|
- [Select](./renderers/Form/Select.md#接口说明)
|
||||||
* [Checkboxes](./renderers/Form/Checkboxes.md#接口说明)
|
- [Checkboxes](./renderers/Form/Checkboxes.md#接口说明)
|
||||||
* [Radios](./renderers/Form/Radios.md#接口说明)
|
- [Radios](./renderers/Form/Radios.md#接口说明)
|
||||||
* [List](./renderers/Form/List.md#接口说明)
|
- [List](./renderers/Form/List.md#接口说明)
|
||||||
* [Wizard](./renderers/Wizard.md#接口说明)
|
- [Wizard](./renderers/Wizard.md#接口说明)
|
||||||
|
|
||||||
`TBD`
|
`TBD`
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
title: 基本用法
|
title: 基本用法
|
||||||
---
|
---
|
||||||
|
|
||||||
为了简化前端开发,amis Renderer 能够直接用配置就能将页面渲染出来。
|
|
||||||
|
|
||||||
先来看个简单的例子。
|
先来看个简单的例子。
|
||||||
|
|
||||||
```schema:height="300"
|
```schema:height="300"
|
||||||
|
@ -19,20 +17,20 @@ title: 基本用法
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
> PS: 可以通过编辑器实时修改预览
|
> 可以通过编辑器实时修改预览
|
||||||
|
|
||||||
从上面的内容可以看出,一个简单页面框架已经基本出来了,这是 amis 渲染器配置的入口。从 `page` 渲染器开始出发,通过在容器中放置不同的渲染器来配置不同性质的页面。
|
通过使用上面的例子就能配出一个基本页面框架,这是 amis 渲染器配置的入口。从 `page` 渲染器开始出发,通过在容器中放置不同的渲染器来配置不同性质的页面。
|
||||||
|
|
||||||
简单说明以上配置信息。
|
简单说明以上配置信息。
|
||||||
|
|
||||||
- `$schema` 这个字段可以忽略,他是指定当前 JSON 配置是符合指定路径 https://houtai.baidu.com/v2/schemas/page.json 的 JSON SCHEMA 文件描述的。PS: 编辑器就是靠这个描述文件提示的,可以 hover 到字段上看效果。
|
- `$schema` 这个字段可以忽略,他是指定当前 JSON 配置是符合指定路径 https://houtai.baidu.com/v2/schemas/page.json 的 JSON SCHEMA 文件描述的。PS: 编辑器就是靠这个描述文件提示的,可以 hover 到字段上看效果。
|
||||||
- `type` 指定渲染器类型,这里指定的类型为 `page`。 更多渲染器类型可以去[这里面查看](./renderers.md)。
|
- `type` 指定渲染器类型,这里指定的类型为 `page`。 更多渲染器类型可以去[这里面查看](./renderers.md)。
|
||||||
- `title` 从 title 开始就是对应的渲染模型上的属性了。这里用来指定标题内容。
|
- `title` 从 title 开始就是对应的渲染模型上的属性了。这里用来指定标题内容。
|
||||||
- `subTitle` 副标题.
|
- `subTitle` 副标题.
|
||||||
- `remark` 标题上面的提示信息
|
- `remark` 标题上面的提示信息
|
||||||
- `aside` 边栏区域内容
|
- `aside` 边栏区域内容
|
||||||
- `body` 内容区域的内容
|
- `body` 内容区域的内容
|
||||||
- `toolbar` 工具栏部分的内容
|
- `toolbar` 工具栏部分的内容
|
||||||
|
|
||||||
这里有三个配置都是容器类型的。`aside`、`body` 和 `toolbar`。什么是容器类型?容器类型表示,他能够把其他渲染类型放进来。以上的例子为了简单,直接放了个字符串。字符串类型内部是把他当成了 [tpl](./renderers/Tpl.md) 渲染器来处理,在这里也可以通过对象的形式指定,如以下的例子的 body 区域是完全等价的。
|
这里有三个配置都是容器类型的。`aside`、`body` 和 `toolbar`。什么是容器类型?容器类型表示,他能够把其他渲染类型放进来。以上的例子为了简单,直接放了个字符串。字符串类型内部是把他当成了 [tpl](./renderers/Tpl.md) 渲染器来处理,在这里也可以通过对象的形式指定,如以下的例子的 body 区域是完全等价的。
|
||||||
|
|
||||||
|
|
|
@ -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: 快速开始
|
title: 快速开始
|
||||||
---
|
---
|
||||||
|
|
||||||
这是一个基于 React 框架的页面渲染器,有配置就能生成页面,配置是什么样的?请前往[基本用法](./basic.md)阅读。知道怎么配置后,就可以用以下方式用于自己的项目了。
|
有两种方式使用 amis:
|
||||||
|
|
||||||
如果你不会 React 也没关系可以看看 [JSSDK 用法](#JSSDK)。
|
1. [React 组件](#React 组件),可以整合到 React 项目中,适合熟悉 React 的开发者,可以[开发自定义组件进行扩展](../custom)。
|
||||||
|
2. [JSSDK](#JSSDK),可以放到任意页面中使用,能使用 amis 内置的渲染组件,但无法开发自定义组件,适合不使用 React 的项目或不熟悉前端的开发者。
|
||||||
|
|
||||||
## 安装依赖
|
## React 组件
|
||||||
|
|
||||||
|
### 安装依赖
|
||||||
|
|
||||||
直接通过 npm 安装即可。
|
直接通过 npm 安装即可。
|
||||||
|
|
||||||
|
@ -14,9 +17,9 @@ title: 快速开始
|
||||||
npm i amis
|
npm i amis
|
||||||
```
|
```
|
||||||
|
|
||||||
## 如何使用?
|
### 整合到 React 组件中
|
||||||
|
|
||||||
可以在 React Component 这么使用。
|
可以在 React Component 这么使用(TypeScript)。
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
@ -83,21 +86,24 @@ class MyComponent extends React.Component<any, any> {
|
||||||
|
|
||||||
参数说明:
|
参数说明:
|
||||||
|
|
||||||
* `schema` 即页面配置,请前往[基本用法](./basic.md)了解.
|
- `schema` 即页面配置,请前往[基本用法](./basic.md)了解.
|
||||||
* `props` 一般都用不上,如果你想传递一些数据给渲染器内部使用,可以传递 data 数据进去。如:
|
- `props` 一般都用不上,如果你想传递一些数据给渲染器内部使用,可以传递 data 数据进去。如:
|
||||||
|
|
||||||
```jsx
|
```jsx
|
||||||
() => renderAmis(schema, {
|
() =>
|
||||||
data: {
|
renderAmis(schema, {
|
||||||
username: 'amis'
|
data: {
|
||||||
}
|
username: 'amis'
|
||||||
})
|
}
|
||||||
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
这样,内部所有组件都能拿到 `username` 这个变量的值。
|
这样,内部所有组件都能拿到 `username` 这个变量的值。
|
||||||
* `env` 环境变量,可以理解为这个渲染器工具的配置项,需要调用者实现部分接口。
|
|
||||||
* `session: string` 默认为 'global',决定 store 是否为全局共用的,如果想单占一个 store,请设置不同的值。
|
- `env` 环境变量,可以理解为这个渲染器工具的配置项,需要调用者实现部分接口。
|
||||||
* `fetcher: (config: fetcherConfig) => Promise<fetcherResult>` 用来实现 ajax 发送。
|
|
||||||
|
- `session: string` 默认为 'global',决定 store 是否为全局共用的,如果想单占一个 store,请设置不同的值。
|
||||||
|
- `fetcher: (config: fetcherConfig) => Promise<fetcherResult>` 用来实现 ajax 发送。
|
||||||
|
|
||||||
示例
|
示例
|
||||||
|
|
||||||
|
@ -142,126 +148,135 @@ class MyComponent extends React.Component<any, any> {
|
||||||
return (axios as any)[method](url, data, config);
|
return (axios as any)[method](url, data, config);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
* `isCancel: (e:error) => boolean` 判断 ajax 异常是否为一个 cancel 请求。
|
|
||||||
|
- `isCancel: (e:error) => boolean` 判断 ajax 异常是否为一个 cancel 请求。
|
||||||
|
|
||||||
示例
|
示例
|
||||||
|
|
||||||
```js
|
```js
|
||||||
isCancel: (value: any) => (axios as any).isCancel(value)
|
isCancel: (value: any) => (axios as any).isCancel(value)
|
||||||
```
|
```
|
||||||
* `notify: (type:string, msg: string) => void` 用来实现消息提示。
|
|
||||||
* `alert: (msg:string) => void` 用来实现警告提示。
|
- `notify: (type:string, msg: string) => void` 用来实现消息提示。
|
||||||
* `confirm: (msg:string) => boolean | Promise<boolean>` 用来实现确认框。
|
- `alert: (msg:string) => void` 用来实现警告提示。
|
||||||
* `jumpTo: (to:string, action?: Action, ctx?: object) => void` 用来实现页面跳转,因为不清楚所在环境中是否使用了 spa 模式,所以用户自己实现吧。
|
- `confirm: (msg:string) => boolean | Promise<boolean>` 用来实现确认框。
|
||||||
* `updateLocation: (location:any, replace?:boolean) => void` 地址替换,跟 jumpTo 类似。
|
- `jumpTo: (to:string, action?: Action, ctx?: object) => void` 用来实现页面跳转,因为不清楚所在环境中是否使用了 spa 模式,所以用户自己实现吧。
|
||||||
* `isCurrentUrl: (link:string) => boolean` 判断目标地址是否为当前页面。
|
- `updateLocation: (location:any, replace?:boolean) => void` 地址替换,跟 jumpTo 类似。
|
||||||
* `theme: 'default' | 'cxd'` 目前支持两种主题。
|
- `isCurrentUrl: (link:string) => boolean` 判断目标地址是否为当前页面。
|
||||||
* `copy: (contents:string, options?: {shutup: boolean}) => void` 用来实现,内容复制。
|
- `theme: 'default' | 'cxd'` 目前支持两种主题。
|
||||||
* `getModalContainer: () => HTMLElement` 用来决定弹框容器。
|
- `copy: (contents:string, options?: {shutup: boolean}) => void` 用来实现,内容复制。
|
||||||
* `loadRenderer: (chema:any, path:string) => Promise<Function>` 可以通过它懒加载自定义组件,比如: https://github.com/baidu/amis/blob/master/__tests__/factory.test.tsx#L64-L91。
|
- `getModalContainer: () => HTMLElement` 用来决定弹框容器。
|
||||||
* `affixOffsetTop: number` 固顶间距,当你的有其他固顶元素时,需要设置一定的偏移量,否则会重叠。
|
- `loadRenderer: (chema:any, path:string) => Promise<Function>` 可以通过它懒加载自定义组件,比如: https://github.com/baidu/amis/blob/master/__tests__/factory.test.tsx#L64-L91。
|
||||||
* `affixOffsetBottom: number` 固底间距,当你的有其他固底元素时,需要设置一定的偏移量,否则会重叠。
|
- `affixOffsetTop: number` 固顶间距,当你的有其他固顶元素时,需要设置一定的偏移量,否则会重叠。
|
||||||
* `richTextToken: string` 内置 rich-text 为 frolaEditor,想要使用,请自行购买,或者自己实现 rich-text 渲染器。
|
- `affixOffsetBottom: number` 固底间距,当你的有其他固底元素时,需要设置一定的偏移量,否则会重叠。
|
||||||
|
- `richTextToken: string` 内置 rich-text 为 frolaEditor,想要使用,请自行购买,或者自己实现 rich-text 渲染器。
|
||||||
|
|
||||||
## JSSDK
|
## JSSDK
|
||||||
|
|
||||||
如果你没有组件定制需求直接使用,而且不想折腾 React 相关的,我建议你直接用这种方式。
|
JSSDK 适合对前端或 React 不了解的开发者,它不依赖 npm 及 webpack,直接引入代码就能使用,但需要注意这种方式不支持[定制组件](../sdk),只能使用 amis 内置的组件。
|
||||||
|
|
||||||
首先请引用一下 CSS 和 JS。
|
JSSDK 的代码从以下地址获取:
|
||||||
|
|
||||||
* JS 地址: https://houtai.baidu.com/v2/jssdk
|
- JS: https://houtai.baidu.com/v2/jssdk
|
||||||
* CSS 地址: https://houtai.baidu.com/v2/csssdk
|
- CSS: https://houtai.baidu.com/v2/csssdk
|
||||||
|
|
||||||
然后执行以下代码就能渲染了。
|
然后在页面中插入下面的代码就能渲染出来了:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
(function() {
|
(function () {
|
||||||
var amis = amisRequire('amis/embed');
|
var amis = amisRequire('amis/embed');
|
||||||
amis.embed('#container', {
|
amis.embed(
|
||||||
type: 'page',
|
'#container',
|
||||||
title: 'AMIS Demo',
|
{
|
||||||
body: 'This is a simple amis page.'
|
type: 'page',
|
||||||
}, {
|
title: 'AMIS Demo',
|
||||||
// props 一般不用传。
|
body: 'This is a simple amis page.'
|
||||||
}, {
|
},
|
||||||
// env
|
{
|
||||||
fetcher: () => {
|
// props 一般不用传。
|
||||||
// 可以不传,用来实现 ajax 请求
|
},
|
||||||
},
|
{
|
||||||
|
// env
|
||||||
|
fetcher: () => {
|
||||||
|
// 可以不传,用来实现 ajax 请求
|
||||||
|
},
|
||||||
|
|
||||||
jumpTo: () => {
|
jumpTo: () => {
|
||||||
// 可以不传,用来实现页面跳转
|
// 可以不传,用来实现页面跳转
|
||||||
},
|
},
|
||||||
|
|
||||||
updateLocation: () => {
|
updateLocation: () => {
|
||||||
// 可以不传,用来实现地址栏更新
|
// 可以不传,用来实现地址栏更新
|
||||||
},
|
},
|
||||||
|
|
||||||
isCurrentUrl: () => {
|
isCurrentUrl: () => {
|
||||||
// 可以不传,用来判断是否目标地址当前地址。
|
// 可以不传,用来判断是否目标地址当前地址。
|
||||||
},
|
},
|
||||||
|
|
||||||
copy: () => {
|
copy: () => {
|
||||||
// 可以不传,用来实现复制到剪切板
|
// 可以不传,用来实现复制到剪切板
|
||||||
},
|
},
|
||||||
|
|
||||||
notify: () => {
|
notify: () => {
|
||||||
// 可以不传,用来实现通知
|
// 可以不传,用来实现通知
|
||||||
},
|
},
|
||||||
|
|
||||||
alert: () => {
|
alert: () => {
|
||||||
// 可以不传,用来实现提示
|
// 可以不传,用来实现提示
|
||||||
},
|
},
|
||||||
|
|
||||||
confirm: () => {
|
confirm: () => {
|
||||||
// 可以不传,用来实现确认框。
|
// 可以不传,用来实现确认框。
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
);
|
||||||
})();
|
})();
|
||||||
```
|
```
|
||||||
|
|
||||||
注意:以上的 SDK 地址是一个页面跳转,会跳转到一个 CDN 地址,而且每次跳转都是最新的版本,随着 amis 的升级这个地址会一直变动,如果你的页面已经完成功能回归,请直接使用某个固定地址,这样才不会因为 amis 升级而导致你的页面不可用。
|
注意:以上的 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
|
```html
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="zh">
|
<html lang="zh">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8" />
|
||||||
<title>AMIS Demo</title>
|
<title>AMIS Demo</title>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
<meta name="viewport"
|
<meta
|
||||||
content="width=device-width, initial-scale=1, maximum-scale=1">
|
name="viewport"
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
|
content="width=device-width, initial-scale=1, maximum-scale=1"
|
||||||
<link rel="stylesheet"
|
/>
|
||||||
href="https://houtai.baidu.com/v2/csssdk">
|
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
|
||||||
|
<link rel="stylesheet" href="amis/sdk.js" />
|
||||||
<style>
|
<style>
|
||||||
html, body, .app-wrapper {
|
html,
|
||||||
position: relative;
|
body,
|
||||||
width: 100%;
|
.app-wrapper {
|
||||||
height: 100%;
|
position: relative;
|
||||||
margin: 0;
|
width: 100%;
|
||||||
padding: 0;
|
height: 100%;
|
||||||
}
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root" class="app-wrapper"></div>
|
<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">
|
<script type="text/javascript">
|
||||||
(function() {
|
(function () {
|
||||||
var amis = amisRequire('amis/embed');
|
var amis = amisRequire('amis/embed');
|
||||||
amis.embed('#root', {
|
amis.embed('#root', {
|
||||||
type: 'page',
|
type: 'page',
|
||||||
title: 'AMIS Demo',
|
title: 'AMIS Demo',
|
||||||
body: 'hello world'
|
body: 'hello world'
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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: 渲染器手册
|
title: 渲染器手册
|
||||||
---
|
---
|
||||||
|
|
||||||
amis 页面是通过 JSON 配置出来的,是由一个一个渲染模型组成的,掌握他们规则,就能灵活配置出各种页面。
|
amis 页面是由一个个渲染模型组成的,并且支持无限层级嵌套,掌握他们规则,就能灵活配置出各种页面。
|
||||||
|
|
||||||
开始之前,请您一定要先阅读[基本用法](./basic.md)。
|
开始之前,请您一定要先阅读[基本用法](./basic.md)。
|
||||||
|
|
||||||
|
@ -95,6 +95,6 @@ amis 页面是通过 JSON 配置出来的,是由一个一个渲染模型组成
|
||||||
- [Button-Group](./renderers/Button-Group.md): 按钮集合
|
- [Button-Group](./renderers/Button-Group.md): 按钮集合
|
||||||
- [iFrame](./renderers/iFrame.md): 如果需要内嵌外部站点,可用 iframe 来实现
|
- [iFrame](./renderers/iFrame.md): 如果需要内嵌外部站点,可用 iframe 来实现
|
||||||
- [Nav](./renderers/Nav.md): 菜单栏
|
- [Nav](./renderers/Nav.md): 菜单栏
|
||||||
- [Tasks](./renderers/Tasks.md): 任务操作集合,类似于 orp 上线
|
- [Tasks](./renderers/Tasks.md): 任务操作集合,适用于一步步操作
|
||||||
- [QRCode](./renderers/QRCode.md): 二维码显示组件
|
- [QRCode](./renderers/QRCode.md): 二维码显示组件
|
||||||
- [Types](./renderers/Types.md): 类型说明文档
|
- [Types](./renderers/Types.md): 类型说明文档
|
||||||
|
|
|
@ -5,7 +5,7 @@ Action 是一种特殊的渲染器,它本身是一个按钮,同时它能触
|
||||||
```schema:height="100" scope="body"
|
```schema:height="100" scope="body"
|
||||||
{
|
{
|
||||||
"label": "弹个框",
|
"label": "弹个框",
|
||||||
"type": "button",
|
"type": "action",
|
||||||
"level": "dark",
|
"level": "dark",
|
||||||
"actionType": "dialog",
|
"actionType": "dialog",
|
||||||
"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`。 |
|
| actionType | `string` | - | 【必填】这是 action 最核心的配置,来指定该 action 的作用类型,支持:`ajax`、`link`、`url`、`drawer`、`dialog`、`confirm`、`cancel`、`prev`、`next`、`copy`、`close`。 |
|
||||||
| label | `string` | - | 按钮文本。可用 `${xxx}` 取值。 |
|
| label | `string` | - | 按钮文本。可用 `${xxx}` 取值。 |
|
||||||
| level | `string` | `default` | 按钮样式,支持:`link`、`primary`、`secondary`、`info`、`success`、`warning`、`danger`、`light`、`dark`、`default`。 |
|
| 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
|
shortname: style
|
||||||
---
|
---
|
||||||
|
|
||||||
amis 中有大量的功能类 class 可以使用,即可以用在 schema 中,也可以用在自定义组件开发中,掌握这些 class, 几乎可以不用写样式。
|
绝大部分 amis 组件里都有个 `className` 配置项,设置后就会给对应的组件添加 css class,而 amis 内置了大量的功能类 class,通过这些 class 的组合就能满足大部分展现调整的需求。
|
||||||
|
|
||||||
amis 中的样式基于 [BootStrap V3](http://getbootstrap.com/css/), 这里主要讲 Bootstrap 中没有涉及到的。
|
|
||||||
|
|
||||||
## 图标
|
## 图标
|
||||||
|
|
||||||
|
|
|
@ -102,6 +102,7 @@ if (process.env.NODE_ENV === 'production') {
|
||||||
}
|
}
|
||||||
|
|
||||||
const navigations = [
|
const navigations = [
|
||||||
|
Docs,
|
||||||
{
|
{
|
||||||
label: '示例',
|
label: '示例',
|
||||||
children: [
|
children: [
|
||||||
|
@ -533,9 +534,9 @@ const navigations = [
|
||||||
component: TestComponent
|
component: TestComponent
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
|
|
||||||
|
|
||||||
Docs
|
|
||||||
];
|
];
|
||||||
|
|
||||||
function isActive(link, location) {
|
function isActive(link, location) {
|
||||||
|
@ -898,9 +899,9 @@ export default function entry({pathPrefix}) {
|
||||||
<Route component={App}>
|
<Route component={App}>
|
||||||
<Redirect
|
<Redirect
|
||||||
from={`${ContextPath}/`}
|
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)}
|
{navigations2route(PathPrefix)}
|
||||||
<Route path="*" component={NotFound} />
|
<Route path="*" component={NotFound} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
|
@ -5,6 +5,16 @@ export default {
|
||||||
prefix: ({classnames: cx}) => <li className={cx('AsideNav-divider')} />,
|
prefix: ({classnames: cx}) => <li className={cx('AsideNav-divider')} />,
|
||||||
label: '文档',
|
label: '文档',
|
||||||
children: [
|
children: [
|
||||||
|
{
|
||||||
|
label: 'AMIS 是什么?',
|
||||||
|
icon: 'fa fa-home',
|
||||||
|
path: '/docs/intro',
|
||||||
|
getComponent: (location, cb) =>
|
||||||
|
require(['../../docs/intro.md'], doc => {
|
||||||
|
cb(null, makeMarkdownRenderer(doc));
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
label: '快速开始',
|
label: '快速开始',
|
||||||
icon: 'fa fa-flash',
|
icon: 'fa fa-flash',
|
||||||
|
@ -38,7 +48,7 @@ export default {
|
||||||
// {{renderer-docs}}
|
// {{renderer-docs}}
|
||||||
|
|
||||||
{
|
{
|
||||||
label: 'API 说明',
|
label: '动态数据',
|
||||||
path: '/docs/api',
|
path: '/docs/api',
|
||||||
icon: 'fa fa-cloud',
|
icon: 'fa fa-cloud',
|
||||||
getComponent: (location, cb) =>
|
getComponent: (location, cb) =>
|
||||||
|
@ -48,27 +58,17 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
label: '如何定制',
|
label: '定制功能',
|
||||||
path: '/docs/sdk',
|
path: '/docs/custom',
|
||||||
icon: 'fa fa-cubes',
|
icon: 'fa fa-cubes',
|
||||||
getComponent: (location, cb) =>
|
getComponent: (location, cb) =>
|
||||||
require(['../../docs/sdk.md'], doc => {
|
require(['../../docs/custom.md'], doc => {
|
||||||
cb(null, makeMarkdownRenderer(doc));
|
cb(null, makeMarkdownRenderer(doc));
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
label: '自定义组件',
|
label: '定制样式',
|
||||||
path: '/docs/dev',
|
|
||||||
icon: 'fa fa-code',
|
|
||||||
getComponent: (location, cb) =>
|
|
||||||
require(['../../docs/dev.md'], doc => {
|
|
||||||
cb(null, makeMarkdownRenderer(doc));
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
label: '样式说明',
|
|
||||||
path: '/docs/style',
|
path: '/docs/style',
|
||||||
icon: 'fa fa-laptop',
|
icon: 'fa fa-laptop',
|
||||||
getComponent: (location, cb) =>
|
getComponent: (location, cb) =>
|
||||||
|
|
|
@ -205,6 +205,7 @@ export default function (schema) {
|
||||||
>
|
>
|
||||||
<i className="fa fa-code" />
|
<i className="fa fa-code" />
|
||||||
</Button>
|
</Button>
|
||||||
|
<span>←点击这里查看源码</span>
|
||||||
</Portal>
|
</Portal>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,52 +1,71 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="zh">
|
<html lang="zh">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<title>AMis Renderer</title>
|
<title>AMis Renderer</title>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
<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
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
|
name="viewport"
|
||||||
<link rel="stylesheet" href="https://bce.bdstatic.com/iconfont/iconfont.css" />
|
content="width=device-width, initial-scale=1, maximum-scale=1"
|
||||||
<link rel="stylesheet" href="font-awesome/css/font-awesome.css" />
|
/>
|
||||||
<link rel="stylesheet" href="bootstrap/dist/css/bootstrap.css" />
|
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
|
||||||
<link rel="stylesheet" href="animate.css/animate.css" />
|
<link
|
||||||
<link rel="stylesheet" href="highlight.js/styles/github.css" />
|
rel="stylesheet"
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css" />
|
href="https://bce.bdstatic.com/iconfont/iconfont.css"
|
||||||
<!--DEPENDENCIES_INJECT_PLACEHOLDER-->
|
/>
|
||||||
<link rel="stylesheet" href="./doc.css" />
|
<link rel="stylesheet" href="font-awesome/css/font-awesome.css" />
|
||||||
<link rel="stylesheet" title="default" href="../scss/themes/default.scss" />
|
<link rel="stylesheet" href="bootstrap/dist/css/bootstrap.css" />
|
||||||
<link rel="stylesheet" title="cxd" disabled href="../scss/themes/cxd.scss" />
|
<link rel="stylesheet" href="animate.css/animate.css" />
|
||||||
<link rel="stylesheet" title="dark" disabled href="../scss/themes/dark.scss" />
|
<link rel="stylesheet" href="prismjs/themes/prism.css" />
|
||||||
<link rel="stylesheet" href="./style.scss" />
|
<link
|
||||||
<style>
|
rel="stylesheet"
|
||||||
.app-wrapper {
|
href="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css"
|
||||||
position: relative;
|
/>
|
||||||
width: 100%;
|
<!--DEPENDENCIES_INJECT_PLACEHOLDER-->
|
||||||
height: 100%;
|
<link rel="stylesheet" href="./doc.css" />
|
||||||
}
|
<link rel="stylesheet" title="default" href="../scss/themes/default.scss" />
|
||||||
</style>
|
<link
|
||||||
</head>
|
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 {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="root" class="app-wrapper"></div>
|
<div id="root" class="app-wrapper"></div>
|
||||||
<script src="./mod.js"></script>
|
<script src="./mod.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.js"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var _hmt = _hmt || [];
|
var _hmt = _hmt || [];
|
||||||
|
|
||||||
// 百度统计
|
// 百度统计
|
||||||
(function() {
|
(function () {
|
||||||
var hm = document.createElement('script');
|
var hm = document.createElement('script');
|
||||||
hm.src = 'https://hm.baidu.com/hm.js?1f80f2c9dbe21dc3af239cf9eee90f1f';
|
hm.src = 'https://hm.baidu.com/hm.js?1f80f2c9dbe21dc3af239cf9eee90f1f';
|
||||||
var s = document.getElementsByTagName('script')[0];
|
var s = document.getElementsByTagName('script')[0];
|
||||||
s.parentNode.insertBefore(hm, s);
|
s.parentNode.insertBefore(hm, s);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
/* @require ./index.jsx 标记为同步依赖,提前加载 */
|
/* @require ./index.jsx 标记为同步依赖,提前加载 */
|
||||||
require(['./index.jsx'], function(app) {
|
require(['./index.jsx'], function (app) {
|
||||||
var initialState = {};
|
var initialState = {};
|
||||||
app.bootstrap(document.getElementById('root'), initialState);
|
app.bootstrap(document.getElementById('root'), initialState);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,33 +1,28 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="zh">
|
<html lang="zh">
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet"
|
<link rel="stylesheet" href="font-awesome/css/font-awesome.css" />
|
||||||
href="font-awesome/css/font-awesome.css">
|
<link rel="stylesheet" href="bootstrap/dist/css/bootstrap.css" />
|
||||||
<link rel="stylesheet"
|
<link rel="stylesheet" href="animate.css/animate.css" />
|
||||||
href="bootstrap/dist/css/bootstrap.css">
|
<link rel="stylesheet" href="prismjs/themes/prism.css" />
|
||||||
<link rel="stylesheet"
|
<link rel="stylesheet" title="default" href="../scss/themes/default.scss" />
|
||||||
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">
|
|
||||||
<!--DEPENDENCIES_INJECT_PLACEHOLDER-->
|
<!--DEPENDENCIES_INJECT_PLACEHOLDER-->
|
||||||
<!--STYLE_PLACEHOLDER-->
|
<!--STYLE_PLACEHOLDER-->
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script data-loader src="./sdk-mod.js"></script>
|
<script data-loader src="./sdk-mod.js"></script>
|
||||||
<script>
|
<script>
|
||||||
/* @require "./embed.tsx" */
|
/* @require "./embed.tsx" */
|
||||||
/* @require "./loader" */
|
/* @require "./loader" */
|
||||||
(function (w) {
|
(function (w) {
|
||||||
var amis = w.amis || {};
|
var amis = w.amis || {};
|
||||||
amis.resource = {
|
amis.resource = {
|
||||||
css: css,
|
css: css,
|
||||||
js: js,
|
js: js,
|
||||||
moduleId:__moduleId('./embed.tsx')
|
moduleId: __moduleId('./embed.tsx')
|
||||||
};
|
};
|
||||||
w.amis = amis;
|
w.amis = amis;
|
||||||
})(window);
|
})(window);
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -124,7 +124,6 @@
|
||||||
"fis3-preprocessor-js-require-css": "^0.1.3",
|
"fis3-preprocessor-js-require-css": "^0.1.3",
|
||||||
"font-awesome": "4.7.0",
|
"font-awesome": "4.7.0",
|
||||||
"fs-walk": "0.0.2",
|
"fs-walk": "0.0.2",
|
||||||
"highlight.js": "^9.12.0",
|
|
||||||
"husky": "^2.2.0",
|
"husky": "^2.2.0",
|
||||||
"jest": "^24.5.0",
|
"jest": "^24.5.0",
|
||||||
"jest-canvas-mock": "^2.1.0",
|
"jest-canvas-mock": "^2.1.0",
|
||||||
|
@ -133,6 +132,7 @@
|
||||||
"marked": "^0.3.7",
|
"marked": "^0.3.7",
|
||||||
"mobx-wiretap": "^0.12.0",
|
"mobx-wiretap": "^0.12.0",
|
||||||
"prettier": "^2.0.5",
|
"prettier": "^2.0.5",
|
||||||
|
"prismjs": "^1.20.0",
|
||||||
"react-frame-component": "^2.0.0",
|
"react-frame-component": "^2.0.0",
|
||||||
"react-router": "3.2.0",
|
"react-router": "3.2.0",
|
||||||
"react-test-renderer": "^16.8.6",
|
"react-test-renderer": "^16.8.6",
|
||||||
|
|
Loading…
Reference in New Issue