From d34c5c707e6fe19a8a3382715d58a233a0e871af Mon Sep 17 00:00:00 2001 From: rickcole Date: Tue, 28 Jul 2020 14:55:49 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B7=AE=E4=B8=8D=E5=A4=9A=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build/md-parser.js | 5 +- docs/components/page.md | 16 +- docs/index.md | 219 ++++++++- docs/start/custom.md | 433 ++++++++++++++++++ docs/start/getting-started.md | 215 --------- docs/start/usage.md | 280 +++++++++++ docs/types/api.md | 116 +++-- examples/components/App.tsx | 10 +- examples/components/Doc.tsx | 59 ++- examples/components/Example.tsx | 32 +- .../{MdRenderer.jsx => MdRenderer.tsx} | 40 ++ examples/components/Play.jsx | 2 - examples/components/SchemaRender.jsx | 24 +- examples/index.html | 2 +- examples/style.scss | 54 ++- src/factory.tsx | 6 - 16 files changed, 1160 insertions(+), 353 deletions(-) create mode 100644 docs/start/custom.md delete mode 100755 docs/start/getting-started.md rename examples/components/{MdRenderer.jsx => MdRenderer.tsx} (78%) diff --git a/build/md-parser.js b/build/md-parser.js index 72f5a998..0031fa2b 100644 --- a/build/md-parser.js +++ b/build/md-parser.js @@ -179,9 +179,8 @@ module.exports = function (content, file) { return placeholder[id] || ''; }); - content = - fis.compile.partial(content, file, 'html') + - `\n\n
文档内容有误?欢迎大家一起来编写,文档地址:${file.subpath}
`; + content = fis.compile.partial(content, file, 'html'); + // + `\n\n
文档内容有误?欢迎大家一起来编写,文档地址:${file.subpath}
`; info.html = content; info.toc = toc; diff --git a/docs/components/page.md b/docs/components/page.md index f950f64f..64bd1fac 100755 --- a/docs/components/page.md +++ b/docs/components/page.md @@ -1,13 +1,14 @@ --- title: Page 页面 -description: +description: type: 0 group: ⚙ 组件 menuName: Page 页面 -icon: +icon: order: 23 --- -Page 组件是 amis 页面结构中,**必须也是唯一的** 顶级容器组件,是整个页面配置的入口组件。 + +Page 组件是 amis 页面 JSON 配置中,**唯一的** 顶级容器组件,是整个页面配置的入口组件。 ## 基本用法 @@ -85,14 +86,14 @@ Page 默认将页面分为几个区域,分别是**内容区(`body`)**、** } ``` -具体 API 规范查看 [API文档](./api)。 +具体 API 规范查看 [API 文档](./api)。 ## 轮训初始化接口 想要在页面渲染后,轮训请求初始化接口,步骤如下: 1. 配置 initApi; -2. 配置 interval:单位为ms,最低值3000,低于该值按3000处理。 +2. 配置 interval:单位为 ms,最低值 3000,低于该值按 3000 处理。 ```schema:height="200" { @@ -147,8 +148,3 @@ Page 默认将页面分为几个区域,分别是**内容区(`body`)**、** | interval | `number` | `3000` | 刷新时间(最低 3000) | | silentPolling | `boolean` | `false` | 配置刷新时是否显示加载动画 | | stopAutoRefreshWhen | [表达式](./expression) | `""` | 通过表达式来配置停止刷新的条件 | - - - - - diff --git a/docs/index.md b/docs/index.md index fdc1ae09..421f273e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -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 就是 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: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) + +## 让我们马上开始吧! + +点击页面底部的下一篇,继续后续部分的阅读。 diff --git a/docs/start/custom.md b/docs/start/custom.md new file mode 100644 index 00000000..f3253d6a --- /dev/null +++ b/docs/start/custom.md @@ -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 + }) => ( +
+

这个是个自定义组件

+

当前值:{value}

+ onChange(Math.round(Math.random() * 10000)) + }>随机修改 +
+ ) + } + ] + } +} +``` + +其中的 `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 + +
+ +``` + +把以上配置方式换成 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 ( +
+

{title}

+
+ {render('body', body) /*渲染孩子节点*/} +
+
+ ); + } +} +``` + +Form 组件的示例代码 + +```jsx +@Renderer({ + test: /(^|\/)form$/ + // ... 其他信息隐藏了 +}) +export class FormRenderer extends React.Component { + // ... 其他信息隐藏了 + render() { + const { + title, + controls, + render // 用来渲染孩子节点,如果当前是叶子节点则可以忽略。 + } = this.props; + return ( + + {controls.map((control, index) => ( +
+ {render(`${index}/control`, control)} +
+ ))} +
+ ); + } +} +``` + +Text 组件的示例代码 + +```jsx +@Renderer({ + test: /(^|\/)form(?:\/\d+)?\/control(?\/\d+)?\/text$/ + // ... 其他信息隐藏了 +}) +export class FormItemTextRenderer extends React.Component { + // ... 其他信息隐藏了 + render() { + const { + label, + name, + onChange + } = this.props; + return ( +
+
+ ); + } +} +``` + +那么渲染过程就是根据节点 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
这是自定义组件:{tip}
; + } +} +``` + +有了以上这段代码后,就可以这样使用了。 + +```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 ( +
+

这是自定义组件:{tip}

+ {body ? ( +
+ {render('body', body, { + // 这里的信息会作为 props 传递给子组件,一般情况下都不需要这个 + })} +
+ ) : null} +
+ ); + } +} +``` + +有了以上这段代码后,就可以这样使用了。 + +```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 ( +
+

这个是个自定义组件

+

当前值:{value}

+ onChange(Math.round(Math.random() * 10000))} + > + 随机修改 + +
+ ); + } +} +``` + +有了以上这段代码后,就可以这样使用了。 + +```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` 页面跳转。 diff --git a/docs/start/getting-started.md b/docs/start/getting-started.md deleted file mode 100755 index 4482d997..00000000 --- a/docs/start/getting-started.md +++ /dev/null @@ -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 就是 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: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) - -## 让我们马上开始吧! - -点击页面底部的下一篇,继续概念部分的阅读。 - diff --git a/docs/start/usage.md b/docs/start/usage.md index e69de29b..6851a2f4 100644 --- a/docs/start/usage.md +++ b/docs/start/usage.md @@ -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 { + render() { + return ( +
+

通过 amis 渲染页面

+ {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/*提示信息*/) => { + // 确认框。 + } + });} +
+ ); + } +} +``` + +`(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` 用来实现 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` 用来实现确认框。 + - `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` 可以通过它懒加载自定义组件,比如: 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 + + + + + AMIS Demo + + + + + + + +
+ + + + +``` diff --git a/docs/types/api.md b/docs/types/api.md index bc2cc46c..f455e5b4 100755 --- a/docs/types/api.md +++ b/docs/types/api.md @@ -1,12 +1,13 @@ --- title: API -description: +description: type: 0 group: 🔧 类型 menuName: API -icon: +icon: order: 20 --- + API 类型用于配置请求接口的格式,涉及请求方式、请求地址、请求数据体等等相关配置 ## 简单配置 @@ -16,17 +17,18 @@ API 类型用于配置请求接口的格式,涉及请求方式、请求地址 ``` [:] ``` -- **method**:get、post、put、delete,默认为get + +- **method**:get、post、put、delete,默认为 get - **url**:接口地址,即模板字符串 示例: ```json { - "api": "get:/api/initData", // get 请求 - "api": "post:/api/initData", // post 请求 - "api": "put:/api/initData", // put 请求 - "api": "delete:/api/initData", // delete 请求 + "api": "get:/api/initData", // get 请求 + "api": "post:/api/initData", // post 请求 + "api": "put:/api/initData", // put 请求 + "api": "delete:/api/initData" // delete 请求 } ``` @@ -56,7 +58,8 @@ API 类型用于配置请求接口的格式,涉及请求方式、请求地址 { "status": 0, "msg": "", - "data": { // 正确 + "data": { + // 正确 "text": "World!" } } @@ -110,7 +113,7 @@ API 还支持配置对象类型 可以配置`method`指定接口的请求方式,支持:`get`、`post`、`put`、`delete`。 > `method`值留空时: -> +> > - 在初始化接口中,默认为`get`请求 > - 在`form`提交接口,默认为`post`请求 @@ -157,7 +160,7 @@ API 还支持配置对象类型 可以配置`dataType`,来指定请求的数据体格式,默认为`json` -> 下面例子中api没有配置`data`属性,因为`form`会默认将所有表单项的值进行提交。 +> 下面例子中 api 没有配置`data`属性,因为`form`会默认将所有表单项的值进行提交。 #### 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`出去: #### 字符串形式 -如果在JSON文件中配置的话,`requestAdaptor`只支持字符串形式,如下: +如果在 JSON 文件中配置的话,`requestAdaptor`只支持字符串形式,如下: ```schema:height="330" scope="body" { @@ -443,17 +446,17 @@ amis的API配置,如果无法配置出你想要的请求结构,那么可以 ```js return { - ...api, - data: { - ...api.data, // 获取暴露的 api 中的 data 变量 - foo: 'bar' // 新添加数据 - } -} + ...api, + data: { + ...api.data, // 获取暴露的 api 中的 data 变量 + foo: 'bar' // 新添加数据 + } +}; ``` #### 函数形式 -如果你的使用环境为js文件,则可以直接传入函数,如下: +如果你的使用环境为 js 文件,则可以直接传入函数,如下: ```js const schema = { @@ -461,7 +464,7 @@ const schema = { api: { method: 'post', url: 'https://houtai.baidu.com/api/mock2/form/saveForm', - requestAdaptor: function(api) { + requestAdaptor: function (api) { return { ...api, 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 { - "type": "form", - "api": { - "method": "post", - "url": "https://houtai.baidu.com/api/mock2/form/saveForm", - "adaptor": "return {\n ...payload,\n status: payload.code === 200 ? 0 : payload.code\n}" + "type": "form", + "api": { + "method": "post", + "url": "https://houtai.baidu.com/api/mock2/form/saveForm", + "adaptor": "return {\n ...payload,\n status: payload.code === 200 ? 0 : payload.code\n}" + }, + "controls": [ + { + "type": "text", + "name": "name", + "label": "姓名:" }, - "controls": [ - { - "type": "text", - "name": "name", - "label": "姓名:" - }, - { - "name": "file", - "type": "file", - "label": "附件:", - "asBlob": true - } - ] - } + { + "name": "file", + "type": "file", + "label": "附件:", + "asBlob": true + } + ] +} ``` - 上例中的适配器实际上是如下代码的字符串形式: ```js return { - ...payload, - status: payload.code === 200 ? 0 : payload.code -} + ...payload, + status: payload.code === 200 ? 0 : payload.code +}; ``` #### 函数形式 -如果你的使用环境为js文件,则可以直接传入函数,如下: +如果你的使用环境为 js 文件,则可以直接传入函数,如下: ```js const schema = { @@ -553,11 +555,11 @@ const schema = { api: { method: 'post', url: 'https://houtai.baidu.com/api/mock2/form/saveForm', - adaptor: function(payload, response) { - return { - ...payload, - status: payload.code === 200 ? 0 : payload.code - } + adaptor: function (payload, response) { + return { + ...payload, + status: payload.code === 200 ? 0 : payload.code + }; } }, 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) | - | | data | 请求数据 | 对象或字符串 | 支持数据映射 | | dataType | 数据体格式 | 字符串 | 默认为 `json` 可以配置成 `form` 或者 `form-data`。当 `data` 中包含文件时,自动会采用 `form-data(multipart/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 | 请求头 | 对象 | - | | sendOn | 请求条件 | [表达式](https://suda.bce.baidu.com/docs/expression) | - | | cache | 接口缓存时间 | 整型数字 | - | | requestAdaptor | 发送适配器 | 字符串 | ,支持字符串串格式,或者直接就是函数如: | | adaptor | 接收适配器 | 字符串 | 如果接口返回不符合要求,可以通过配置一个适配器来处理成 amis 需要的。同样支持 Function 或者 字符串函数体格式 | | replaceData | 替换当前数据 | 布尔 | 返回的数据是否替换掉当前的数据,默认为 `false`,即:`追加`,设置成 `true` 就是完全替换。 | - - - - - - diff --git a/examples/components/App.tsx b/examples/components/App.tsx index 34a2fb1f..8d96fea6 100644 --- a/examples/components/App.tsx +++ b/examples/components/App.tsx @@ -267,7 +267,15 @@ export class App extends React.PureComponent { 'is-open': isOpen })} > - + { + browserHistory.push( + `${path || (hasChildren && nav.children[0].path)}` + ); + this.toggleOpen(e, nav); + }} + // to={`${path || (hasChildren && nav.children[0].path)}`} + > {nav.label} {hasChildren ? ( require(['../../docs/index.md'], doc => { @@ -16,12 +17,21 @@ export const docs = [ }, { - label: '使用', + label: '快速开始', path: '/docs/start/usage', getComponent: (location, cb) => require(['../../docs/start/usage.md'], 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 表单', - path: '/docs/components/form/index', - getComponent: (location, cb) => - require(['../../docs/components/form/index.md'], doc => { - cb(null, makeMarkdownRenderer(doc)); - }), + // path: '/docs/components/form/index', + // getComponent: (location, cb) => + // require(['../../docs/components/form/index.md'], doc => { + // cb(null, makeMarkdownRenderer(doc)); + // }), 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 表单项', path: '/docs/components/form/formitem', @@ -948,12 +967,30 @@ export const docs = [ ]; export default class Doc extends React.PureComponent { + state = { + prevDoc: null, + nextDoc: null + }; + componentDidMount() { this.props.setNavigations(docs); + this.setDocFooter(); } - componentDidUpdate() { - this.props.setNavigations(docs); + componentDidUpdate(preProps) { + 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() { @@ -963,7 +1000,9 @@ export default class Doc extends React.PureComponent { ...this.props.children.props, theme: this.props.theme, classPrefix: this.props.classPrefix, - locale: this.props.locale + locale: this.props.locale, + prevDoc: this.state.prevDoc, + nextDoc: this.state.nextDoc })} ); diff --git a/examples/components/Example.tsx b/examples/components/Example.tsx index ba2409e6..888219b0 100644 --- a/examples/components/Example.tsx +++ b/examples/components/Example.tsx @@ -511,20 +511,22 @@ export const examples = [ icon: 'fa fa-cloud', path: '/examples/iframe', 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() { return ( -
+ <> {React.cloneElement(this.props.children, { ...this.props.children.props, theme: this.props.theme, classPrefix: this.props.classPrefix, locale: this.props.locale })} -
+ ); } } diff --git a/examples/components/MdRenderer.jsx b/examples/components/MdRenderer.tsx similarity index 78% rename from examples/components/MdRenderer.jsx rename to examples/components/MdRenderer.tsx index bb7b799f..9d1f2745 100644 --- a/examples/components/MdRenderer.jsx +++ b/examples/components/MdRenderer.tsx @@ -10,6 +10,7 @@ import PopOver from '../../src/components/PopOver'; import NestedLinks from '../../src/components/AsideNav'; import {Portal} from 'react-overlays'; import classnames from 'classnames'; +import {Link} from 'react-router'; class CodePreview extends React.Component { state = { @@ -183,6 +184,10 @@ export default function (doc) { } render() { + console.log('this.props', this.props); + + const {prevDoc, nextDoc} = this.props; + return ( <>
@@ -191,7 +196,42 @@ export default function (doc) {

{doc.title}

) : null} + + +
+
+ {prevDoc ? ( + +
+ +
+ +
+
+ 上一篇 - {prevDoc.group || '其他'} +
+
{prevDoc.label}
+
+ + ) : null} + + {nextDoc ? ( + +
+
+ 下一篇 - {nextDoc.group || '其他'} +
+
{nextDoc.label}
+
+ +
+ +
+ + ) : null} +
+
{doc.toc && doc.toc.children && doc.toc.children.length > 1 ? (
diff --git a/examples/components/Play.jsx b/examples/components/Play.jsx index e04c82cc..37481321 100644 --- a/examples/components/Play.jsx +++ b/examples/components/Play.jsx @@ -185,8 +185,6 @@ export default class PlayGround extends React.Component { locale: this.props.locale }; - console.log('render', render(schema, props, this.env)); - if (!this.props.useIFrame) { return render(schema, props, this.env); } diff --git a/examples/components/SchemaRender.jsx b/examples/components/SchemaRender.jsx index a377da4e..e3deb3a4 100644 --- a/examples/components/SchemaRender.jsx +++ b/examples/components/SchemaRender.jsx @@ -185,32 +185,16 @@ export default function (schema) { size="lg" onHide={this.close} show={this.state.open} - position="left" + position="right" > {this.state.open ? this.renderCode() : null} ) : null} {this.renderSchema()} {showCode !== false ? ( - document.querySelector('#headerLeftBtns')} - > - - - ←点击这里查看源码 - - + + 查看配置 + ) : null}
); diff --git a/examples/index.html b/examples/index.html index 711f734f..e02c8e92 100644 --- a/examples/index.html +++ b/examples/index.html @@ -2,7 +2,7 @@ - AMis Renderer + amis - 低代码前端框架 .schema-wrapper > .a-Page { - position: absolute; - top: 36px; - left: 0; - right: 0; - } - .Doc-title { margin-top: 50px; - padding-left: 45px; + padding-left: 40px; h1 { margin: 0; @@ -444,6 +437,49 @@ body { .is-flipped { 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) { @@ -457,7 +493,7 @@ body { } &-toc { - margin-left: 30px; + margin-left: 20px; } &-toc > div { diff --git a/src/factory.tsx b/src/factory.tsx index faf6afe3..99bba146 100644 --- a/src/factory.tsx +++ b/src/factory.tsx @@ -636,12 +636,6 @@ class SchemaRenderer extends React.Component { const {data: defaultData, ...restSchema} = schema; const Component = renderer.component; - if (schema.type === 'page') { - console.log(schema); - console.log(schema.title); - console.log('='.repeat(10)); - } - return (