差不多了

This commit is contained in:
rickcole 2020-07-28 14:55:49 +08:00
parent 62a3956026
commit d34c5c707e
16 changed files with 1160 additions and 353 deletions

View File

@ -179,9 +179,8 @@ module.exports = function (content, file) {
return placeholder[id] || ''; return placeholder[id] || '';
}); });
content = content = fis.compile.partial(content, file, 'html');
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>`;
`\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.html = content;
info.toc = toc; info.toc = toc;

View File

@ -7,7 +7,8 @@ menuName: Page 页面
icon: icon:
order: 23 order: 23
--- ---
Page 组件是 amis 页面结构中,**必须也是唯一的** 顶级容器组件,是整个页面配置的入口组件。
Page 组件是 amis 页面 JSON 配置中,**唯一的** 顶级容器组件,是整个页面配置的入口组件。
## 基本用法 ## 基本用法
@ -85,14 +86,14 @@ Page 默认将页面分为几个区域,分别是**内容区(`body`**、**
} }
``` ```
具体 API 规范查看 [API文档](./api)。 具体 API 规范查看 [API 文档](./api)。
## 轮训初始化接口 ## 轮训初始化接口
想要在页面渲染后,轮训请求初始化接口,步骤如下: 想要在页面渲染后,轮训请求初始化接口,步骤如下:
1. 配置 initApi 1. 配置 initApi
2. 配置 interval单位为ms最低值3000低于该值按3000处理。 2. 配置 interval单位为 ms最低值 3000低于该值按 3000 处理。
```schema:height="200" ```schema:height="200"
{ {
@ -147,8 +148,3 @@ Page 默认将页面分为几个区域,分别是**内容区(`body`**、**
| interval | `number` | `3000` | 刷新时间(最低 3000) | | interval | `number` | `3000` | 刷新时间(最低 3000) |
| silentPolling | `boolean` | `false` | 配置刷新时是否显示加载动画 | | silentPolling | `boolean` | `false` | 配置刷新时是否显示加载动画 |
| stopAutoRefreshWhen | [表达式](./expression) | `""` | 通过表达式来配置停止刷新的条件 | | stopAutoRefreshWhen | [表达式](./expression) | `""` | 通过表达式来配置停止刷新的条件 |

View File

@ -1 +1,218 @@
## 欢迎来到 amis 的世界 ---
title: 起步
description: ddsaad
type: 0
group: 💡 概念
menuName: 起步
icon:
order: 8
---
## 什么是 amis
amis 是一个低代码前端框架,它使用 JSON 配置来生成页面,可以节省页面开发工作量,极大提升开发前端页面的效率。
## 为什么要做 amis🤔
在经历了十几年的发展后,前端开发变得越来越复杂,门槛也越来越高,要使用当下流行的 UI 组件库,你必须懂 npm、webpack、react/vue必须熟悉 ES 6 语法,最好还了解状态管理(比如 Redux如果没接触过函数式编程一开始入门就很困难而它还有巨大的 [生态](https://github.com/markerikson/redux-ecosystem-links),相关的库有 2347 个。
然而前端技术的发展不会停滞,等学完这些后可能会发现大家都用 Hooks 了、某个打包工具取代 Webpack 了。。。
而有时候你只是为了做个普通的增删改查界面,用于系统管理,类似下面这种:
```schema:height="900"
{
"title": "浏览器内核对 CSS 的支持情况",
"remark": "嘿,不保证数据准确性",
"type": "page",
"body": {
"type": "crud",
"draggable": true,
"api": "https://houtai.baidu.com/api/sample",
"keepItemSelectionOnPageChange": true,
"filter": {
"title": "筛选",
"submitText": "",
"controls": [
{
"type": "text",
"name": "keywords",
"placeholder": "关键字",
"addOn": {
"label": "搜索",
"type": "submit"
}
}
]
},
"bulkActions": [
{
"label": "批量删除",
"actionType": "ajax",
"api": "delete:https://houtai.baidu.com/api/sample/${ids|raw}",
"confirmText": "确定要批量删除?"
},
{
"label": "批量修改",
"actionType": "dialog",
"dialog": {
"title": "批量编辑",
"name": "sample-bulk-edit",
"body": {
"type": "form",
"api": "https://houtai.baidu.com/api/sample/bulkUpdate2",
"controls": [
{
"type": "hidden",
"name": "ids"
},
{
"type": "text",
"name": "engine",
"label": "Engine"
}
]
}
}
}
],
"quickSaveApi": "https://houtai.baidu.com/api/sample/bulkUpdate",
"quickSaveItemApi": "https://houtai.baidu.com/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:https://houtai.baidu.com/api/sample/$id"
}
]
}
]
}
}
```
这个界面虽然用 Bootstrap 也能快速搭起来,但要想体验好就需要加很多细节功能,比如:
- 数据动态加载
- 编辑单行数据
- 批量删除和修改
- 查询某列
- 按某列排序
- 隐藏某列
- 开启整页内容拖拽排序
- 表格有分页(页数还会同步到地址栏,刷新页面试试)
- 如果往下拖动还有首行冻结来方便查看表头等
全部实现这些需要大量的代码。
然而上面也看到了,在 amis 里只需要 150 行 JSON 配置(嘿,其中 40 多行只有一个括号),你不需要了解 React/Vue、Webpack甚至不需要了解 JavaScript即便没学过 amis 也能猜到大部分配置的作用,只需要简单配置就能完成所有页面开发
这正是建立 amis 的初衷,我们认为:**对于大部分常用页面,应该使用最简单的方法来实现**,而不是越来越复杂。
## 用 JSON 写页面有什么好处 ❓
为了实现用最简单方式来生成大部分页面amis 的解决方案是基于 JSON 来配置,它的独特好处是:
- **不需要懂前端**:在百度内部,大部分 amis 用户之前从来没写过前端页面,也不会 `JavaScript`,就能做出专业且复杂的后台界面,这是所有其他前端 UI 库都无法做到的;
- **不受前端技术更新的影响**:百度内部最老的 amis 页面是 4 年多前创建的,至今还在使用,而当年的 `Angular/Vue/React` 版本现在都废弃了,当年流行的 `Gulp` 也被 `Webpack` 取代了,如果这些页面不是用 amis现在的维护成本会很高同时还能享受 amis 升级带来的界面改进;
- 可以 **完全** 使用 [可视化页面编辑器](https://fex-team.github.io/amis-editor/#/edit/0) 来制作页面:一般前端可视化编辑器只能用来做静态原型,而 amis 可视化编辑器做出的页面是可以直接上线的。
> JSON 是一种轻量级的数据交换格式,简洁和清晰的层次结构使得它成为理想的数据交换语言。它易于人阅读和编写,同时也易于机器解析和生成,能够有效地提升网络传输效率。
>
> 更多关于 JSON 的知识,可以阅读[百度百科](https://baike.baidu.com/item/JSON)
## amis 的其它亮点 ✨
- **提供完整的界面解决方案**:其它 UI 框架必须使用 JavaScript 来组装业务逻辑,而 amis 只需 JSON 配置就能完成完整功能开发,包括数据获取、表单提交及验证等功能;
- **内置 100+ 种 UI 组件**:包括其它 UI 框架都会不提供的富文本编辑器、代码编辑器等,能满足各种页面组件展现的需求,而且对于特殊的展现形式还可以通过 [自定义组件](./start/custom.md) 来扩充;
- **容器支持无限级嵌套**:可以通过组合来满足各种布局需求;
- **经历了长时间的实战考验**amis 在百度内部得到了广泛使用,在 4 年多的时间里创建了 **3w** 多页面从内容审核到机器管理从数据分析到模型训练amis 满足了各种各样的页面需求。
## amis 不适合做什么?😶
使用 JSON 有优点但也有明显缺点,在以下场合并不适合 amis
- 大量定制 UI尤其是面向普通客户toC的产品页面
- JSON 配置使得 amis 更适合做有大量常见 UI 组件的页面,但对于面向普通客户的页面,往往追求个性化的视觉效果,这种情况下用 amis 就不合适,实际上绝大部分前端 UI 组件库也都不适合,只能定制开发。
- 有极为复杂的交互,或者对交互有很特殊的要求
- 有些复杂的前端功能,比如可视化编辑器,其中有大量定制的拖拽操作,这种需要依赖原生 DOM 实现的功能无法使用 amis。
- 但对于某些交互固定的领域比如图连线amis 后续会有专门的组件来实现。
## 阅读建议 👆
- 如果你是第一次接触 amis 的新同学,那么请 **务必认真阅读完概念部分**,它会让你对 amis 有个整体的认识
- 如果你已经掌握 amis 基本概念,且有一定的开发经验,需要参考 amis 组件相关文档的同学,那么请移步 [组件文档](./components/component)
## 让我们马上开始吧!
点击页面底部的下一篇,继续后续部分的阅读。

433
docs/start/custom.md Normal file
View File

@ -0,0 +1,433 @@
---
title: 自定义组件
---
如果默认的组件不能满足需求,可以通过自定义组件来进行扩展,在 amis 中有两种方法:
1. 临时扩展,适合无需复用的组件。
2. 注册自定义类型,适合需要在很多地方复用的组件。
> 注意,自定义组件只支持 npm 方式,不支持 SDK
## 临时扩展
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,215 +0,0 @@
---
title: 起步
description: ddsaad
type: 0
group: 💡 概念
menuName: 起步
icon:
order: 8
---
## 什么是 amis
amis 是一个前端低代码框架,它使用 JSON 配置来生成页面,可以极大节省页面开发工作量,极大提升开发前端界面的效率。
## 为什么要做 amis🤔
在经历了十几年的发展后,前端开发变得越来越复杂,门槛也越来越高,要使用当下流行的 UI 组件库,你必须懂 npm、webpack、react/vue必须熟悉 ES 6 语法,最好还了解状态管理,比如 Redux如果没接触过函数式编程一开始入门就很困难而它还有巨大的 [生态](https://github.com/markerikson/redux-ecosystem-links),相关的库有 2347 个,然而前端技术的发展不会停滞,等学完这些后可能会发现大家都用 Hooks 了、某个打包工具取代 WebPack 了。。。
而有时候你只是为了做个普通的增删改查界面,用于系统管理,类似下面这种:
```schema:height="900"
{
"title": "浏览器内核对 CSS 的支持情况",
"remark": "嘿,不保证数据准确性",
"type": "page",
"body": {
"type": "crud",
"draggable": true,
"api": "https://houtai.baidu.com/api/sample",
"keepItemSelectionOnPageChange": true,
"filter": {
"title": "筛选",
"submitText": "",
"controls": [
{
"type": "text",
"name": "keywords",
"placeholder": "关键字",
"addOn": {
"label": "搜索",
"type": "submit"
}
}
]
},
"bulkActions": [
{
"label": "批量删除",
"actionType": "ajax",
"api": "delete:https://houtai.baidu.com/api/sample/${ids|raw}",
"confirmText": "确定要批量删除?"
},
{
"label": "批量修改",
"actionType": "dialog",
"dialog": {
"title": "批量编辑",
"name": "sample-bulk-edit",
"body": {
"type": "form",
"api": "https://houtai.baidu.com/api/sample/bulkUpdate2",
"controls": [
{
"type": "hidden",
"name": "ids"
},
{
"type": "text",
"name": "engine",
"label": "Engine"
}
]
}
}
}
],
"quickSaveApi": "https://houtai.baidu.com/api/sample/bulkUpdate",
"quickSaveItemApi": "https://houtai.baidu.com/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:https://houtai.baidu.com/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 可视化编辑器做出的页面是可以直接上线的。
> JSON 是一种轻量级的数据交换格式,简洁和清晰的层次结构使得它成为理想的数据交换语言。它易于人阅读和编写,同时也易于机器解析和生成,能够有效地提升网络传输效率。
>
> 更多关于 JSON 的知识,可以阅读[百度百科](https://baike.baidu.com/item/JSON)
## amis 的其它亮点✨
- **提供完整的界面解决方案**,其它 UI 框架必须使用 JavaScript 来组装业务逻辑,而 amis 只需 JSON 配置就能完成完整功能开发,包括数据获取、表单提交及验证等功能。
- 内置 **92** 种 UI 组件,包括其它 UI 框架都会不提供的富文本编辑器、代码编辑器等,能满足各种页面组件展现的需求,而且对于特殊的展现形式还可以通过[自定义组件](./custom.md)来扩充。
- 容器组件支持**无限层级嵌套**,可以通过组合来满足各种布局需求。
- 经历了长时间的实战考验amis 在百度内部得到了广泛使用,在 4 年多的时间里创建了 3w 多页面从内容审核到机器管理从数据分析到模型训练amis 满足了各种各样的页面需求。
## amis 不适合做什么?😶
使用 JSON 有优点但也有明显缺点,在以下场合并不适合 amis
- 大量定制 UI尤其是面向普通客户toC的产品页面
- JSON 配置使得 amis 更适合做有大量常见 UI 组件的页面,但对于面向普通客户的页面,往往追求个性化的视觉效果,这种情况下用 amis 就不合适,实际上绝大部分前端 UI 组件库也都不适合,只能定制开发。
- 有极为复杂的交互,或者对交互有很特殊的要求
- 有些复杂的前端功能,比如可视化编辑器,其中有大量定制的拖拽操作,这种需要依赖原生 DOM 实现的功能无法使用 amis。
- 但对于某些交互固定的领域比如图连线amis 后续会有专门的组件来实现。
## 阅读建议👆
- 如果你是第一次接触 amis 的新同学,那么请**务必认真阅读完概念部分**,它会让你对 amis 有个整体的认识
- 如果你已经掌握 amis 基本概念,且有一定的开发经验,需要参考 amis 组件相关文档的同学,那么请移步 [组件文档](./component)
## 让我们马上开始吧!
点击页面底部的下一篇,继续概念部分的阅读。

View File

@ -0,0 +1,280 @@
---
title: 快速开始
description:
---
## 速搭
suda
## npm
### 安装
```
npm i amis
```
### 使用说明
可以在 React Component 这么使用TypeScript
```tsx
import * as React from 'react';
import {
render as renderAmis
} from 'amis';
class MyComponent extends React.Component<any, any> {
render() {
return (
<div>
<p>通过 amis 渲染页面</p>
{renderAmis({
// schema
// 这里是 amis 的 Json 配置。
type: 'page',
title: '简单页面',
body: '内容'
}, {
// props
}, {
// env
// 这些是 amis 需要的一些接口实现
// 可以参考本项目里面的 Demo 部分代码。
updateLocation: (location:string/*目标地址*/, replace:boolean/*是replace还是push*/) => {
// 用来更新地址栏
},
jumpTo: (location:string/*目标地址*/) => {
// 页面跳转, actionType: link、url 都会进来。
},
fetcher: ({
url,
method,
data,
config
}:{
url:string/*目标地址*/,
method:'get' | 'post' | 'put' | 'delete'/*发送方式*/,
data: object | void/*数据*/,
config: object/*其他配置*/
}) => {
// 用来发送 Ajax 请求,建议使用 axios
},
notify: (type:'error'|'success'/**/, msg:string/*提示内容*/) => {
// 用来提示用户
},
alert: (content:string/*提示信息*/) => {
// 另外一种提示,可以直接用系统框
},
confirm: (content:string/*提示信息*/) => {
// 确认框。
}
});}
</div>
);
}
}
```
`(schema:Schema, props?:any, env?: any) => JSX.Element`
参数说明:
- `schema` 即页面配置,请前往[基本用法](./basic.md)了解.
- `props` 一般都用不上,如果你想传递一些数据给渲染器内部使用,可以传递 data 数据进去。如:
```jsx
() =>
renderAmis(schema, {
data: {
username: 'amis'
}
});
```
这样,内部所有组件都能拿到 `username` 这个变量的值。
- `env` 环境变量,可以理解为这个渲染器工具的配置项,需要调用者实现部分接口。
- `session: string` 默认为 'global',决定 store 是否为全局共用的,如果想单占一个 store请设置不同的值。
- `fetcher: (config: fetcherConfig) => Promise<fetcherResult>` 用来实现 ajax 发送。
示例
```js
fetcher: ({
url,
method,
data,
responseType,
config,
headers
}: any) => {
config = config || {};
config.withCredentials = true;
responseType && (config.responseType = responseType);
if (config.cancelExecutor) {
config.cancelToken = new (axios as any).CancelToken(config.cancelExecutor);
}
config.headers = headers || {};
if (method !== 'post' && method !== 'put' && method !== 'patch') {
if (data) {
config.params = data;
}
return (axios as any)[method](url, config);
} else if (data && data instanceof FormData) {
// config.headers = config.headers || {};
// config.headers['Content-Type'] = 'multipart/form-data';
} else if (data
&& typeof data !== 'string'
&& !(data instanceof Blob)
&& !(data instanceof ArrayBuffer)
) {
data = JSON.stringify(data);
// config.headers = config.headers || {};
config.headers['Content-Type'] = 'application/json';
}
return (axios as any)[method](url, data, config);
}
```
- `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 渲染器。
## SDK
SDK 适合对前端或 React 不了解的开发者,它不依赖 npm 及 webpack直接引入代码就能使用但需要注意这种方式不支持[定制组件](../sdk),只能使用 amis 内置的组件。
JSSDK 的代码从以下地址获取:
- JS https://houtai.baidu.com/v2/jssdk
- CSS https://houtai.baidu.com/v2/csssdk
然后在页面中插入下面的代码就能渲染出来了:
```js
(function () {
var amis = amisRequire('amis/embed');
amis.embed(
'#container',
{
type: 'page',
title: 'AMIS Demo',
body: 'This is a simple amis page.'
},
{
// props 一般不用传。
},
{
// env
fetcher: () => {
// 可以不传,用来实现 ajax 请求
},
jumpTo: () => {
// 可以不传,用来实现页面跳转
},
updateLocation: () => {
// 可以不传,用来实现地址栏更新
},
isCurrentUrl: () => {
// 可以不传,用来判断是否目标地址当前地址。
},
copy: () => {
// 可以不传,用来实现复制到剪切板
},
notify: () => {
// 可以不传,用来实现通知
},
alert: () => {
// 可以不传,用来实现提示
},
confirm: () => {
// 可以不传,用来实现确认框。
}
}
);
})();
```
注意:以上的 SDK 地址是一个页面跳转,会跳转到一个 CDN 地址,而且每次跳转都是最新的版本,随着 amis 的升级这个地址会一直变动,如果你的页面已经完成功能回归,请直接使用某个固定地址,这样才不会因为 amis 升级而导致你的页面不可用。
另外sdk 代码也伴随 npm 一起发布了,不使用 CDN 版本,直接替换成 npm 包里面的 `amis/sdk.js``amis/sdk.css` 即可。
完整示例:
```html
<!DOCTYPE html>
<html lang="zh">
<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="amis/sdk.css" />
<style>
html,
body,
.app-wrapper {
position: relative;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div id="root" class="app-wrapper"></div>
<script src="amis/sdk.js"></script>
<script type="text/javascript">
(function () {
var amis = amisRequire('amis/embed');
amis.embed('#root', {
type: 'page',
title: 'AMIS Demo',
body: 'hello world'
});
})();
</script>
</body>
</html>
```

View File

@ -7,6 +7,7 @@ menuName: API
icon: icon:
order: 20 order: 20
--- ---
API 类型用于配置请求接口的格式,涉及请求方式、请求地址、请求数据体等等相关配置 API 类型用于配置请求接口的格式,涉及请求方式、请求地址、请求数据体等等相关配置
## 简单配置 ## 简单配置
@ -16,17 +17,18 @@ API 类型用于配置请求接口的格式,涉及请求方式、请求地址
``` ```
[<method>:]<url> [<method>:]<url>
``` ```
- **method**get、post、put、delete默认为get
- **method**get、post、put、delete默认为 get
- **url**:接口地址,即模板字符串 - **url**:接口地址,即模板字符串
示例: 示例:
```json ```json
{ {
"api": "get:/api/initData", // get 请求 "api": "get:/api/initData", // get 请求
"api": "post:/api/initData", // post 请求 "api": "post:/api/initData", // post 请求
"api": "put:/api/initData", // put 请求 "api": "put:/api/initData", // put 请求
"api": "delete:/api/initData", // delete 请求 "api": "delete:/api/initData" // delete 请求
} }
``` ```
@ -56,7 +58,8 @@ API 类型用于配置请求接口的格式,涉及请求方式、请求地址
{ {
"status": 0, "status": 0,
"msg": "", "msg": "",
"data": { // 正确 "data": {
// 正确
"text": "World!" "text": "World!"
} }
} }
@ -157,7 +160,7 @@ API 还支持配置对象类型
可以配置`dataType`,来指定请求的数据体格式,默认为`json` 可以配置`dataType`,来指定请求的数据体格式,默认为`json`
> 下面例子中api没有配置`data`属性,因为`form`会默认将所有表单项的值进行提交。 > 下面例子中 api 没有配置`data`属性,因为`form`会默认将所有表单项的值进行提交。
#### application/json #### application/json
@ -346,7 +349,7 @@ API 还支持配置对象类型
} }
``` ```
查看 **选项2** 的`source`属性他是API类型值支持配置`sendOn` [表达式](./expression),实现根据条件请求接口。 查看 **选项 2** 的`source`属性,他是 API 类型值,支持配置`sendOn` [表达式](./expression),实现根据条件请求接口。
### 配置接口缓存 ### 配置接口缓存
@ -408,13 +411,13 @@ API 还支持配置对象类型
### 配置请求适配器 ### 配置请求适配器
amis的API配置如果无法配置出你想要的请求结构那么可以配置`requestAdaptor`发送适配器 amis API 配置,如果无法配置出你想要的请求结构,那么可以配置`requestAdaptor`发送适配器
**发送适配器**是指在接口请求前,对请求进行一些自定义处理,例如修改发送数据体、添加请求头、等等,基本用法是,获取暴露的`api`参数,并且对该参数进行一些修改,并`return`出去: **发送适配器**是指在接口请求前,对请求进行一些自定义处理,例如修改发送数据体、添加请求头、等等,基本用法是,获取暴露的`api`参数,并且对该参数进行一些修改,并`return`出去:
#### 字符串形式 #### 字符串形式
如果在JSON文件中配置的话`requestAdaptor`只支持字符串形式,如下: 如果在 JSON 文件中配置的话,`requestAdaptor`只支持字符串形式,如下:
```schema:height="330" scope="body" ```schema:height="330" scope="body"
{ {
@ -443,17 +446,17 @@ amis的API配置如果无法配置出你想要的请求结构那么可以
```js ```js
return { return {
...api, ...api,
data: { data: {
...api.data, // 获取暴露的 api 中的 data 变量 ...api.data, // 获取暴露的 api 中的 data 变量
foo: 'bar' // 新添加数据 foo: 'bar' // 新添加数据
} }
} };
``` ```
#### 函数形式 #### 函数形式
如果你的使用环境为js文件则可以直接传入函数如下 如果你的使用环境为 js 文件,则可以直接传入函数,如下:
```js ```js
const schema = { const schema = {
@ -461,7 +464,7 @@ const schema = {
api: { api: {
method: 'post', method: 'post',
url: 'https://houtai.baidu.com/api/mock2/form/saveForm', url: 'https://houtai.baidu.com/api/mock2/form/saveForm',
requestAdaptor: function(api) { requestAdaptor: function (api) {
return { return {
...api, ...api,
data: { data: {
@ -499,53 +502,52 @@ const schema = {
### 配置接收适配器 ### 配置接收适配器
同样的如果后端返回的响应结构不符合amis的[接口格式要求](./types-api#%E6%8E%A5%E5%8F%A3%E8%BF%94%E5%9B%9E%E6%A0%BC%E5%BC%8F-%E9%87%8D%E8%A6%81-),而后端不方便调整时,可以配置`adaptor`实现接收适配器 同样的,如果后端返回的响应结构不符合 amis 的[接口格式要求](./types-api#%E6%8E%A5%E5%8F%A3%E8%BF%94%E5%9B%9E%E6%A0%BC%E5%BC%8F-%E9%87%8D%E8%A6%81-),而后端不方便调整时,可以配置`adaptor`实现接收适配器
**接受欧适配器**是指在接口请求后,对响应进行一些自定义处理,例如修改响应的数据结构、修改响应的数据等等。 **接受欧适配器**是指在接口请求后,对响应进行一些自定义处理,例如修改响应的数据结构、修改响应的数据等等。
例如:接口正确返回的格式中,会返回`"code": 200`而amis中接口返回格式需要`"status": 0`,这时候就需要接收适配器进行调整结构。 例如:接口正确返回的格式中,会返回`"code": 200`,而 amis 中,接口返回格式需要`"status": 0`,这时候就需要接收适配器进行调整结构。
#### 字符串形式 #### 字符串形式
如果在JSON文件中配置的话`adaptor`只支持字符串形式,如下: 如果在 JSON 文件中配置的话,`adaptor`只支持字符串形式,如下:
```json ```json
{ {
"type": "form", "type": "form",
"api": { "api": {
"method": "post", "method": "post",
"url": "https://houtai.baidu.com/api/mock2/form/saveForm", "url": "https://houtai.baidu.com/api/mock2/form/saveForm",
"adaptor": "return {\n ...payload,\n status: payload.code === 200 ? 0 : payload.code\n}" "adaptor": "return {\n ...payload,\n status: payload.code === 200 ? 0 : payload.code\n}"
},
"controls": [
{
"type": "text",
"name": "name",
"label": "姓名:"
}, },
"controls": [ {
{ "name": "file",
"type": "text", "type": "file",
"name": "name", "label": "附件:",
"label": "姓名:" "asBlob": true
}, }
{ ]
"name": "file", }
"type": "file",
"label": "附件:",
"asBlob": true
}
]
}
``` ```
上例中的适配器实际上是如下代码的字符串形式: 上例中的适配器实际上是如下代码的字符串形式:
```js ```js
return { return {
...payload, ...payload,
status: payload.code === 200 ? 0 : payload.code status: payload.code === 200 ? 0 : payload.code
} };
``` ```
#### 函数形式 #### 函数形式
如果你的使用环境为js文件则可以直接传入函数如下 如果你的使用环境为 js 文件,则可以直接传入函数,如下:
```js ```js
const schema = { const schema = {
@ -553,11 +555,11 @@ const schema = {
api: { api: {
method: 'post', method: 'post',
url: 'https://houtai.baidu.com/api/mock2/form/saveForm', url: 'https://houtai.baidu.com/api/mock2/form/saveForm',
adaptor: function(payload, response) { adaptor: function (payload, response) {
return { return {
...payload, ...payload,
status: payload.code === 200 ? 0 : payload.code status: payload.code === 200 ? 0 : payload.code
} };
} }
}, },
controls: [ controls: [
@ -587,16 +589,10 @@ const schema = {
| url | 请求地址 | [模板字符串](https://suda.bce.baidu.com/docs/template#%E6%A8%A1%E6%9D%BF%E5%AD%97%E7%AC%A6%E4%B8%B2) | - | | url | 请求地址 | [模板字符串](https://suda.bce.baidu.com/docs/template#%E6%A8%A1%E6%9D%BF%E5%AD%97%E7%AC%A6%E4%B8%B2) | - |
| data | 请求数据 | 对象或字符串 | 支持数据映射 | | data | 请求数据 | 对象或字符串 | 支持数据映射 |
| dataType | 数据体格式 | 字符串 | 默认为 `json` 可以配置成 `form` 或者 `form-data`。当 `data` 中包含文件时,自动会采用 `form-datamultipart/form-data` 格式。当配置为 `form` 时为 `application/x-www-form-urlencoded` 格式。 | | dataType | 数据体格式 | 字符串 | 默认为 `json` 可以配置成 `form` 或者 `form-data`。当 `data` 中包含文件时,自动会采用 `form-datamultipart/form-data` 格式。当配置为 `form` 时为 `application/x-www-form-urlencoded` 格式。 |
| qsOptions | -- | 对象或字符串 | 当 dataType 为form或者form-data 的时候有用。具体参数请参考这里,默认设置为: `{ arrayFormat: 'indices', encodeValuesOnly: true }` | | qsOptions | -- | 对象或字符串 | 当 dataType 为 form 或者 form-data 的时候有用。具体参数请参考这里,默认设置为: `{ arrayFormat: 'indices', encodeValuesOnly: true }` |
| headers | 请求头 | 对象 | - | | headers | 请求头 | 对象 | - |
| sendOn | 请求条件 | [表达式](https://suda.bce.baidu.com/docs/expression) | - | | sendOn | 请求条件 | [表达式](https://suda.bce.baidu.com/docs/expression) | - |
| cache | 接口缓存时间 | 整型数字 | - | | cache | 接口缓存时间 | 整型数字 | - |
| requestAdaptor | 发送适配器 | 字符串 | ,支持字符串串格式,或者直接就是函数如: | | requestAdaptor | 发送适配器 | 字符串 | ,支持字符串串格式,或者直接就是函数如: |
| adaptor | 接收适配器 | 字符串 | 如果接口返回不符合要求,可以通过配置一个适配器来处理成 amis 需要的。同样支持 Function 或者 字符串函数体格式 | | adaptor | 接收适配器 | 字符串 | 如果接口返回不符合要求,可以通过配置一个适配器来处理成 amis 需要的。同样支持 Function 或者 字符串函数体格式 |
| replaceData | 替换当前数据 | 布尔 | 返回的数据是否替换掉当前的数据,默认为 `false`,即:`追加`,设置成 `true` 就是完全替换。 | | replaceData | 替换当前数据 | 布尔 | 返回的数据是否替换掉当前的数据,默认为 `false`,即:`追加`,设置成 `true` 就是完全替换。 |

View File

@ -267,7 +267,15 @@ export class App extends React.PureComponent {
'is-open': isOpen 'is-open': isOpen
})} })}
> >
<Link to={`${path || (hasChildren && nav.children[0].path)}`}> <Link
onClick={e => {
browserHistory.push(
`${path || (hasChildren && nav.children[0].path)}`
);
this.toggleOpen(e, nav);
}}
// to={`${path || (hasChildren && nav.children[0].path)}`}
>
{nav.label} {nav.label}
{hasChildren ? ( {hasChildren ? (
<i <i

View File

@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import makeMarkdownRenderer from './MdRenderer'; import makeMarkdownRenderer from './MdRenderer';
import {flattenTree, filterTree} from '../../src/utils/helper';
export const docs = [ export const docs = [
{ {
@ -7,7 +8,7 @@ export const docs = [
label: '开始', label: '开始',
children: [ children: [
{ {
label: 'AMIS 是什么?', label: '介绍',
path: '/docs/index', path: '/docs/index',
getComponent: (location, cb) => getComponent: (location, cb) =>
require(['../../docs/index.md'], doc => { require(['../../docs/index.md'], doc => {
@ -16,12 +17,21 @@ export const docs = [
}, },
{ {
label: '使用', label: '快速开始',
path: '/docs/start/usage', path: '/docs/start/usage',
getComponent: (location, cb) => getComponent: (location, cb) =>
require(['../../docs/start/usage.md'], doc => { require(['../../docs/start/usage.md'], doc => {
cb(null, makeMarkdownRenderer(doc)); cb(null, makeMarkdownRenderer(doc));
}) })
},
{
label: '自定义组件',
path: '/docs/start/custom',
getComponent: (location, cb) =>
require(['../../docs/start/custom.md'], doc => {
cb(null, makeMarkdownRenderer(doc));
})
} }
// { // {
@ -159,12 +169,21 @@ export const docs = [
}, },
{ {
label: 'Form 表单', label: 'Form 表单',
path: '/docs/components/form/index', // path: '/docs/components/form/index',
getComponent: (location, cb) => // getComponent: (location, cb) =>
require(['../../docs/components/form/index.md'], doc => { // require(['../../docs/components/form/index.md'], doc => {
cb(null, makeMarkdownRenderer(doc)); // cb(null, makeMarkdownRenderer(doc));
}), // }),
children: [ children: [
// @todo 完了想办法把这个放上面,暂时先这样
{
label: 'Form 表单',
path: '/docs/components/form/index',
getComponent: (location, cb) =>
require(['../../docs/components/form/index.md'], doc => {
cb(null, makeMarkdownRenderer(doc));
})
},
{ {
label: 'FormItem 表单项', label: 'FormItem 表单项',
path: '/docs/components/form/formitem', path: '/docs/components/form/formitem',
@ -948,12 +967,30 @@ export const docs = [
]; ];
export default class Doc extends React.PureComponent { export default class Doc extends React.PureComponent {
state = {
prevDoc: null,
nextDoc: null
};
componentDidMount() { componentDidMount() {
this.props.setNavigations(docs); this.props.setNavigations(docs);
this.setDocFooter();
} }
componentDidUpdate() { componentDidUpdate(preProps) {
this.props.setNavigations(docs); if (this.props.location.pathname !== preProps.location.pathname) {
this.props.setNavigations(docs);
this.setDocFooter();
}
}
setDocFooter() {
const flattenDocs = flattenTree(docs).filter(i => !!i.path);
const docIndex = flattenDocs.findIndex(d => d.path === location.pathname);
this.setState({
prevDoc: flattenDocs[docIndex - 1],
nextDoc: flattenDocs[docIndex + 1]
});
} }
render() { render() {
@ -963,7 +1000,9 @@ export default class Doc extends React.PureComponent {
...this.props.children.props, ...this.props.children.props,
theme: this.props.theme, theme: this.props.theme,
classPrefix: this.props.classPrefix, classPrefix: this.props.classPrefix,
locale: this.props.locale locale: this.props.locale,
prevDoc: this.state.prevDoc,
nextDoc: this.state.nextDoc
})} })}
</> </>
); );

View File

@ -511,20 +511,22 @@ export const examples = [
icon: 'fa fa-cloud', icon: 'fa fa-cloud',
path: '/examples/iframe', path: '/examples/iframe',
component: makeSchemaRenderer(IFrameSchema) component: makeSchemaRenderer(IFrameSchema)
},
{
label: 'SDK',
icon: 'fa fa-rocket',
path: '/examples/sdk',
component: SdkTest
},
{
label: 'Test',
icon: 'fa fa-code',
path: '/examples/test',
component: TestComponent
} }
// 放到使用中
// {
// label: 'SDK',
// icon: 'fa fa-rocket',
// path: '/examples/sdk',
// component: SdkTest
// }
// {
// label: 'Test',
// icon: 'fa fa-code',
// path: '/examples/test',
// component: TestComponent
// }
] ]
} }
]; ];
@ -540,14 +542,14 @@ export default class Example extends React.PureComponent {
render() { render() {
return ( return (
<div className="Doc-content"> <>
{React.cloneElement(this.props.children, { {React.cloneElement(this.props.children, {
...this.props.children.props, ...this.props.children.props,
theme: this.props.theme, theme: this.props.theme,
classPrefix: this.props.classPrefix, classPrefix: this.props.classPrefix,
locale: this.props.locale locale: this.props.locale
})} })}
</div> </>
); );
} }
} }

View File

@ -10,6 +10,7 @@ import PopOver from '../../src/components/PopOver';
import NestedLinks from '../../src/components/AsideNav'; import NestedLinks from '../../src/components/AsideNav';
import {Portal} from 'react-overlays'; import {Portal} from 'react-overlays';
import classnames from 'classnames'; import classnames from 'classnames';
import {Link} from 'react-router';
class CodePreview extends React.Component { class CodePreview extends React.Component {
state = { state = {
@ -183,6 +184,10 @@ export default function (doc) {
} }
render() { render() {
console.log('this.props', this.props);
const {prevDoc, nextDoc} = this.props;
return ( return (
<> <>
<div className="Doc-content"> <div className="Doc-content">
@ -191,7 +196,42 @@ export default function (doc) {
<h1>{doc.title}</h1> <h1>{doc.title}</h1>
</div> </div>
) : null} ) : null}
<Preview {...this.props} doc={doc} /> <Preview {...this.props} doc={doc} />
<div className="Doc-footer">
<div className="Doc-navLinks">
{prevDoc ? (
<Link className="Doc-navLinks--prev" to={prevDoc.path}>
<div className="Doc-navLinks-icon">
<i className="iconfont icon-arrow-left"></i>
</div>
<div className="Doc-navLinks-body text-right">
<div className="Doc-navLinks-subtitle">
- {prevDoc.group || '其他'}
</div>
<div className="Doc-navLinks-title">{prevDoc.label} </div>
</div>
</Link>
) : null}
{nextDoc ? (
<Link className="Doc-navLinks--next" to={nextDoc.path}>
<div className="Doc-navLinks-body">
<div className="Doc-navLinks-subtitle">
- {nextDoc.group || '其他'}
</div>
<div className="Doc-navLinks-title">{nextDoc.label}</div>
</div>
<div className="Doc-navLinks-icon">
<i className="iconfont icon-arrow-right"></i>
</div>
</Link>
) : null}
</div>
</div>
</div> </div>
{doc.toc && doc.toc.children && doc.toc.children.length > 1 ? ( {doc.toc && doc.toc.children && doc.toc.children.length > 1 ? (
<div className="Doc-toc"> <div className="Doc-toc">

View File

@ -185,8 +185,6 @@ export default class PlayGround extends React.Component {
locale: this.props.locale locale: this.props.locale
}; };
console.log('render', render(schema, props, this.env));
if (!this.props.useIFrame) { if (!this.props.useIFrame) {
return render(schema, props, this.env); return render(schema, props, this.env);
} }

View File

@ -185,32 +185,16 @@ export default function (schema) {
size="lg" size="lg"
onHide={this.close} onHide={this.close}
show={this.state.open} show={this.state.open}
position="left" position="right"
> >
{this.state.open ? this.renderCode() : null} {this.state.open ? this.renderCode() : null}
</DrawerContainer> </DrawerContainer>
) : null} ) : null}
{this.renderSchema()} {this.renderSchema()}
{showCode !== false ? ( {showCode !== false ? (
<Portal <span onClick={this.toggleCode} className="view-code-btn">
container={() => document.querySelector('#headerLeftBtns')} 查看配置 <i className="fa fa-code p-l-xs"></i>
> </span>
<Button
classPrefix={ns}
onClick={this.toggleCode}
active={this.state.open}
iconOnly
tooltip="查看源码"
level="link"
placement="bottom"
className="view-code"
>
<i className="fa fa-code" />
</Button>
<span className="inline v-middle text-info">
点击这里查看源码
</span>
</Portal>
) : null} ) : null}
</div> </div>
); );

View File

@ -2,7 +2,7 @@
<html lang="zh"> <html lang="zh">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<title>AMis Renderer</title> <title>amis - 低代码前端框架</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta <meta
name="viewport" name="viewport"

View File

@ -342,16 +342,9 @@ body {
&-content { &-content {
position: relative; position: relative;
> .schema-wrapper > .a-Page {
position: absolute;
top: 36px;
left: 0;
right: 0;
}
.Doc-title { .Doc-title {
margin-top: 50px; margin-top: 50px;
padding-left: 45px; padding-left: 40px;
h1 { h1 {
margin: 0; margin: 0;
@ -444,6 +437,49 @@ body {
.is-flipped { .is-flipped {
transform: rotateX(180deg); transform: rotateX(180deg);
} }
> .schema-wrapper {
.view-code-btn {
position: absolute;
font-weight: bold;
right: -100px;
top: 112px;
color: #666;
cursor: pointer;
&::before {
position: absolute;
top: 0;
left: -21px;
bottom: 0;
content: ' ';
border-left: 1px solid #e8ebee;
width: 1px;
}
&:hover {
color: #333;
}
> i {
font-weight: bold;
}
}
> .a-Page,
> .cxd-Page,
> .dark-Page {
position: absolute;
top: 100px;
left: 30px;
right: 60px;
bottom: 0;
height: auto;
&--withSidebar {
padding-right: 80px;
}
}
}
} }
@include media-breakpoint-up(lg) { @include media-breakpoint-up(lg) {
@ -457,7 +493,7 @@ body {
} }
&-toc { &-toc {
margin-left: 30px; margin-left: 20px;
} }
&-toc > div { &-toc > div {

View File

@ -636,12 +636,6 @@ class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
const {data: defaultData, ...restSchema} = schema; const {data: defaultData, ...restSchema} = schema;
const Component = renderer.component; const Component = renderer.component;
if (schema.type === 'page') {
console.log(schema);
console.log(schema.title);
console.log('='.repeat(10));
}
return ( return (
<Component <Component
{...theme.getRendererConfig(renderer.name)} {...theme.getRendererConfig(renderer.name)}