项目地址: https://github.com/aisuda/amis-editor-demo 官方示例: https://aisuda.github.io/amis-editor-demo/ 说明文档: https://aisuda.bce.baidu.com/amis/zh-CN/docs/index 组件文档: https://aisuda.bce.baidu.com/amis/zh-CN/components/page
clone https://github.com/aisuda/amis-editor-demo.git
# 修改 amis.config.js
build: {
entry: { // webpack构建入口
index: './src/index.tsx',
// editor: './src/mobile.tsx'
},
// 用于构建生产环境代码的相关配置信息
NODE_ENV: 'production',
assetsRoot: resolve('./demo'), // 打包后的文件绝对路径(物理路径)
assetsPublicPath: './', // 设置静态资源的引用路径(根域名+路径)[必须进行修改]
assetsSubDirectory: '', // 资源引用二级路径
productionSourceMap: false,
productionGzip: false,
productionGzipExtensions: ['js', 'css', 'json'],
plugins: [new MonacoWebpackPlugin()],
bundleAnalyzerReport: false,
}
# 安装依赖
npm install
# build
# IDEA 打开项目, 再文件目录右键"![[package.json]]" 文件, 打开"Show Npm Script", 左键"build
#
# issue 1
# Module not found: Error: Can't resolve 'babel-loader' in 'D:\Project\OBS\amis-editor-demo'
# --save-dev 会更改 package.json 文件, 保存到对应位置上.
npm install --save-dev babel-loader
npm install --save-dev params-replace-loader
npm install --save-dev html-loader
npm install --save-dev ts-loader
npm install --save-dev babel-plugin-import
npm install --save-dev @babel/plugin-transform-runtime
npm install --save-dev @babel/plugin-proposal-decorators
npm install --save-dev @babel/plugin-proposal-function-sent
npm install --save-dev @babel/plugin-proposal-export-namespace-from
npm install --save-dev @babel/plugin-proposal-numeric-separator
npm install --save-dev @babel/plugin-proposal-throw-expressions
npm install --save-dev @babel/plugin-syntax-dynamic-import
npm install --save-dev @babel/plugin-syntax-import-meta
npm install --save-dev @babel/plugin-proposal-class-properties
npm install --save-dev @babel/plugin-proposal-json-strings
npm install --save-dev @babel/preset-env
npm install --save-dev @babel/preset-react
npm install --save-dev @babel/preset-typescript
npm install --save-dev vue-style-loader
npm install --save-dev css-loader
npm install --save-dev postcss-loader
npm install --save-dev deep-diff
npm install --save-dev tinycolor2
npm install --save-dev react-color
npm install --save-dev @rc-component/mini-decimal
npm install --save-dev @svgr/webpack
npm install --save-dev sass-loader
npm install --save-dev
npm install --save-dev
npm install --save-dev
将打包后的文件复制到nginx的html目录的自建文件夹下,我的是dist文件夹。
api
https://aisuda.bce.baidu.com/amis/zh-CN/docs/types/api
- 返回的数据需要是固定的格式{“status”: xxx, “msg”: xxx}
- 当我们需要显示返回的信息的时候, 就需要配置请求的返回适配器, 将返回内容转换成这个格式.
status:payload.status === 0 ? 0 : 1,
msg: payload.msg || '系统错误',
data: payload.data
button
https://aisuda.bce.baidu.com/amis/zh-CN/components/button 实际上是action的别名 https://aisuda.bce.baidu.com/amis/zh-CN/components/action
ajax
请求成功后刷新目标组件
https://aisuda.bce.baidu.com/amis/zh-CN/components/action?page=1#ajax-%E8%AF%B7%E6%B1%82
目标组件需要配置 name 属性 Action 上添加 “reload”: “xxx”,xxx 为目标组件的 name 属性值,如果配置多个组件,name 用逗号分隔,另外如果想让 reload 的时候再携带些数据可以类似这样配置 {“reload”: “xxx?a={a}&b={b}”}, 这样不仅让目标组件刷新,同时还会把当前环境中的数据 a 和 b 传递给 xxx.
{
"type": "page",
"body": [
{
"type": "button",
"label": "ajax 请求刷新目标",
"actionType": "ajax",
"api": "/amis/api/mock2/form/saveForm",
"reload": "crud"
},
{
"type": "divider"
},
{
"type": "crud",
"name": "crud",
"api": "/amis/api/mock2/sample?waitSeconds=1",
"columns": [
{
"name": "id",
"label": "ID"
}
]
}
]
}
link
New page to download pdf file.
{
"type": "button",
"actionType": "url",
"disabledOnAction": false,
"size": "xs",
"blank":"true",
"level": "primary",
"label": "生成新尽职调查报告",
"id": "u:16ff40609919",
"link": "/Document/getDueDiligenceReport/${instance_id}"
}
刷新页面
- 表单按钮, 点击提交后重新加载表单.
{
"type": "button",
"label": "提交",
"onEvent": {
"click": {
"actions": [
{
"actionType": "submit",
"componentId": "u:a268ff5c574c",
"reload": "u:a268ff5c574c"
}
]
}
},
"level": "primary",
"id": "u:fbef13292ab4",
"disabledOnAction": false
}
下载内容
{
"type": "page",
"body": {
"label": "下载",
"type": "action",
"actionType": "download",
"api": "/amis/api/download"
}
}
提交请求, 返回内容
提交后不重置表单
默认提交表单后会重置表单内容, 开启此项配置, 保证内容保留在表单上.
"resetAfterSubmit": false
calendar
https://aisuda.bce.baidu.com/amis/zh-CN/components/calendar
curd
https://aisuda.bce.baidu.com/amis/zh-CN/components/crud
添加组件
- 这个也就是数据容器中的”增删改查”
- 将组件拖进内容区之后, 在”常规”里面可以开启搜索框.
- 在 2 小锤子这个里面可以配置大部分接口调用的内容.
- 在查询条件的位置绑定查询字段名称.
配置接口
- 发送数据的第一个参数是框架默认的, 如下图, 就是用来分页操作的.
配置展示字段
- 配置完接口之后, 可以使用 “校验格式并自动生成列配置”, 会自动绑定下面字段, 只需要改标题即可, 注意接口返回的数据需要按照如下格式.
- 当行数据中存在
children
字段时,CRUD 会自动识别为树形数据,并支持展开收起。
{
"status": "0",
"msg": "处理成功",
"data": {
"perPage": "10",
"page": "1",
"controller": "pageSchema",
"action": "getPageSchemaList",
"items": [
{
"id": 1414,
"jsonSchema": null,
"name": "11",
"description": "11",
"children":[
"id": 1414,
"jsonSchema": null,
"name": "11",
"description": "11"
]
}
],
"total": 1
}
}
def getMenuList() {
// 分页参数处理
int page = params.int('page') ?: 1
int perPage = params.int('perPage') ?: 10
String sql = """
SELECT m.id, m.name, m.url, m.status, m.order_number, m.parent_id,
ps.name as page_schema_name
FROM menu m
LEFT JOIN page_schema ps ON m.page_schema_id = ps.id
WHERE 1=1 AND m.parent_id is null
"""
Map paramsMap = new HashMap()
// 动态添加条件
String name = params.name
if (name) {
sql += " AND m.name LIKE :name "
paramsMap.name = "%" + name + "%"
}
// 动态添加条件
String url = params.url
if (url) {
sql += " AND m.url LIKE :url "
paramsMap.url = "%" + url + "%"
}
sql += " ORDER BY m.order_number ASC"
def sql1 = new Sql(dataSource)
def parentList = sql1.rows(sql, paramsMap, (page - 1) * perPage, perPage)
params.items = parentList.collect { parent ->
def children = sql1.rows("""
SELECT m.id, m.name, m.url, m.status, m.order_number, m.parent_id, ps.name as page_schema_name FROM menu m LEFT JOIN page_schema ps ON m.page_schema_id = ps.id WHERE 1=1 AND m.parent_id = :parentId ORDER BY m.order_number ASC """, [parentId: parent.id])
parent.children = children
return parent
}
params.total = sql1.rows(sql, paramsMap).size()
def res = ResService.success(params)
render res as JSON
}
增
这里配置单独的创建接口,
在这里设计表单, 创建内容
改
找到这个控件, 注意提交编辑的时候, 要带上本条记录的id.
给这个控件配置信息
自定义按钮
- 自定义的功能按钮,可以配置到CURD组件的“新增”按钮位置,通过如下内容访问指定功能,然后reload下面的CURD列表(注意下面的CURD列表需要配置name属性为“740list”)。
{
"type": "button",
"actionType": "ajax",
"disabledOnAction": false,
"reload": "740list",
"size": "xs",
"level": "primary",
"label": "同步数据",
"id": "u:36435c28df7a",
"api": "/Account/sync740List/${id}"
}
date
{
"type": "date",
"format": "YYYY年MM月DD日 HH时mm分ss秒",
"label": "修改时间",
"name": "modified_date",
"id": "u:182e02cc1735",
"placeholder": "-"
}
dialog
https://aisuda.bce.baidu.com/amis/zh-CN/components/dialog?page=1
当curd的列表页要显示详情的时候,这个时候通常会 use a dialog to show this.
How to pass a value to the dialog?
Open the data map option, set the value like this: then you can use user_id
from the dialog, and its value equal the id
from the curd component.
"dataMap": {
"user_id": "${id}"
},
event-action 事件动作
https://aisuda.bce.baidu.com/amis/zh-CN/docs/concepts/event-action
执行JS
/* 自定义JS使用说明:
* 1.动作执行函数doAction,可以执行所有类型的动作
* 2.通过上下文对象context可以获取当前组件实例,例如context.props可以获取该组件相关属性
* 3.事件对象event,在doAction之后执行event.stopPropagation();可以阻止后续动作执行
*/
const myMsg = '我是自定义JS';
console.log(context.props); // 这里可以获取page上定义个data,还有amis初始化时候的data和context内容。
doAction({
actionType: 'toast',
args: {
msg: myMsg
}
});
initApi
在页面初始化的时候, 传递一些权限数据, 来控制部分功能的显示和隐藏. visibleOn
{
"msg": "处理成功",
"data": {
"controller": "Opportunity",
"action": "initData",
"roles": [
"ROLE_ADMIN",
"ROLE_ATTACHMENTS",
"ROLE_STATISTIC",
"ROLE_DUE_DILIGENCE"
]
},
"status": 0
}
input-date
valueFormat
用毫秒时间戳的格式传递给后端
{
"type": "input-date",
"name": "expired_date",
"label": "过期日期",
"id": "u:0202b80811b4",
"placeholder": "请选择日期",
"displayFormat": "YYYY/MM/DD",
"valueFormat": "x",
"minDate": "",
"maxDate": "",
"value": "",
"format": "X",
"for": "X",
"utc": false
}
input-datetime
- 一般用日期时间来显示
- 前端传递给后端固定格式的字符串:
YYYY-MM-DD HH:mm:ss
- 前端显示的时候可以只显示日期, 或者显示日期和时间.
- 后端只需要解析固定格式的即可.
{
"type": "input-datetime",
"name": "credit_period",
"label": "授信期限",
"id": "u:1f3c40c67375",
"keyboard": true,
"showSteps": true,
"step": 1,
"valueFormat": "YYYY-MM-DD HH:mm:ss",
"placeholder": "请选择日期以及时间",
"displayFormat": "YYYY-MM-DD",
"minDate": "",
"maxDate": "",
"value": ""
}
input-file
https://aisuda.bce.baidu.com/amis/zh-CN/components/form/input-file
input-time-range
- 留意传递给后端的数据值
{
"type": "input-datetime-range",
"label": "创建时间",
"name": "createTime",
"id": "u:c3431b9fc961",
"displayFormat": "YYYY-MM-DD HH:mm:ss",
"placeholder": "请选择日期时间范围",
"valueFormat": "YYYY-MM-DD HH:mm:ss",
"minDate": "",
"maxDate": "",
"value": "",
"shortcuts": [
"yesterday",
"7daysago",
"prevweek",
"thismonth",
"prevmonth",
"prevquarter"
]
}
link
https://aisuda.bce.baidu.com/amis/zh-CN/components/link
- 可以放置在Table里面, 数据超连接到详情页面.
{
"type": "link",
"name": "name",
"label": "名称",
"id": "u:dd0ed10eb3da",
"placeholder": "-",
"href": "/PageSchema/edit#/edit/${id}",
"body": "${name}"
}
image
- 注意”可见”属性, ${里面是JavaScript表达式}, 通过结果返回的 true 和 false 来控制当前控件的显示和隐藏.
{
"type": "image",
"id": "u:0c7390b4ae3c",
"enlargeAble": true,
"maxScale": 200,
"minScale": 50,
"style": {
"display": "inline-block"
},
"imageMode": "original",
"src": "${file_url}",
"visibleOn": "${extension == 'jpg'}"
}
form 表单
https://aisuda.bce.baidu.com/amis/zh-CN/components/form/index
group 表单组
https://aisuda.bce.baidu.com/amis/zh-CN/components/form/group
在水平模式下, 更美观的展示表单内容.
{
"type": "group",
"body": [
{
"type": "input-password",
"name": "password",
"label": "密码",
"placeholder": "请输入密码",
"size": "full"
}
]
}
markdown
目前markdown组件只能静态绑定到某个input组件上,当input组件内容发生变化之后,markdown区域同步更新。
示例:设定两个input组件,一个输入问题,一个显示返回结果(隐藏),form提交问题到后端,设置form事件,将返回结果绑定到global变量,隐藏的input也绑定到这个全局变量,最后markdown组件通过name绑定到隐藏的input,这时提交问题后返回的数据就动态呈现到markdown区域。
{
"type": "page",
"pullRefresh": {
"disabled": true
},
"regions": [
"body"
],
"asideResizor": false,
"id": 3079,
"body": [
{
"labelAlign": "left",
"onEvent": {
"submitSucc": {
"weight": 0,
"actions": [
{
"args": {
"value": "event.data.result.data.output",
"path": "global.output"
},
"actionType": "setValue"
}
]
}
},
"dsType": "api",
"style": {
"padding": "10px",
"borderTop": "1px solid [[eee]]"
},
"id": "u:7ec67e6e3a8f",
"api": {
"method": "post",
"dataType": "form",
"adaptor": "",
"messages": {},
"requestAdaptor": "",
"url": "/AI/askAI"
},
"type": "form",
"title": "",
"body": [
{
"minRows": 3,
"maxRows": 20,
"name": "input",
"id": "u:ab83d89c2862",
"label": false,
"placeholder": "请输入您的问题...",
"type": "textarea",
"rows": 2,
"required": true
},
{
"minRows": 3,
"maxRows": 20,
"name": "output",
"id": "u:614de330d37b",
"label": false,
"placeholder": "请输入您的问题...",
"type": "textarea",
"rows": 2,
"value": "${global.output}",
"hidden": true
},
{
"type": "markdown",
"id": "u:69a4d217cd19",
"name": "output"
}
],
"feat": "Insert",
"actions": [
{
"disabledOnAction": false,
"size": "xs",
"level": "primary",
"onEvent": {
"click": {
"actions": []
}
},
"label": "发送",
"id": "u:2a84cb573ff6",
"type": "submit"
}
],
"wrapWithPanel": true,
"debug": false
}
]
}
nav
- 如果想从后端接口获取多级菜单, 需要匹配当前接口的默认格式, 你需要再后端生成如下的接口返回数据.
{
"status": 0,
"msg": "请求成功",
"data": [
{
"label": "首页",
"to": "/home"
},
{
"label": "产品",
"to": "/products",
"children": [
{
"label": "产品1",
"to": "/products/product1"
},
{
"label": "产品2",
"to": "/products/product2"
}
]
},
{
"label": "关于我们",
"to": "/about"
}
]
}
page
"padding": "0.75rem", // 有的时候内容会紧贴着边,这时候要设置page的内边距属性。
onEvent 事件
https://aisuda.bce.baidu.com/amis/zh-CN/components/page#%E4%BA%8B%E4%BB%B6%E8%A1%A8
添加页面onEvent,可以配置init,inited,pullRefresh情况下的事件例如下面情况:
- 需求是页面上一个按钮“小助手”,点击之后弹出一个聊天窗口,这个窗口用Service来从后端去pageJSON,这个取到的json就是一个page,这个page就需要包含聊天使用的ws服务初始化服务,这里就需要使用init来执行JS进行初始化这个ws服务。
// 从当前页面URL中提取基础信息,手动构造WebSocket地址
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; // 协议转换:https→wss,http→ws
const host = window.location.host; // 包含域名和端口(如:localhost:8080)
const contextPath = ''; // 若项目有上下文路径(如 /app),需手动添加,否则留空
const stompEndpoint = '/stomp'; // STOMP服务的端点路径
// 拼接完整的WebSocket URL
const brokerURL = `${protocol}//${host}${contextPath}${stompEndpoint}`;
const stompClient = new StompJs.Client({
brokerURL: brokerURL,
onConnect: () => {
console.log('STOMP连接成功');
// 订阅消息主题
stompClient.subscribe('/topic/hello', (response) => {
try {
// 尝试解析 JSON 格式(兼容未来可能的 JSON 响应)
const message = JSON.parse(response.body);
addMessageToChat({ content: message.content }, false);
} catch (e) {
// 解析失败时直接将响应体作为文本内容处理
addMessageToChat({ content: response.body }, false);
}
});
},
onStompError: (frame) => {
console.error('STOMP错误: ' + frame.headers['message']);
},
reconnectDelay: 3000 // 自动重连延迟(毫秒)
});
// 保存到全局(关键:让后续操作能访问)
window.stompClient = stompClient;
stompClient.activate();
// 监听消息
stompClient.onmessage = function (event) {
console.log('收到消息:', event.data);
// 可通过 this.setState 更新 amis 页面数据
this.setState({ message: event.data });
}.bind(this); // 绑定当前组件上下文,方便更新状态
select 下拉框
https://aisuda.bce.baidu.com/amis/zh-CN/components/form/select
def selectPositionList() {
String sql = """
select p.id, d.name as department_name, p.name as position_name, p.description from position p join department d on p.department_id =d.id
where 1=1 """
Map paramsMap = new HashMap()
// 搜索关键词
String term = params.term
if (term) {
sql += " AND p.name LIKE :term "
paramsMap.term = "%" + term + "%"
}
sql += " ORDER BY p.id DESC"
def sql1 = new Sql(dataSource)
params.items = sql1.rows(sql, paramsMap)
def res = ResService.success(params)
render res as JSON
}
多个下拉框的级联选择
// The first select component, config the change event, let it to reload the second select component.
{
"type": "select",
"strictMode": true,
"name": "province_id",
"label": "省份",
"id": "province_id",
"syncFields": [],
"placeholder": "文本",
"multiple": false,
"source": "/Province/select",
"labelField": "name",
"valueField": "id",
"onEvent": {
"change": {
"weight": 0,
"actions": [
{
"actionType": "reload",
"componentId": "city_id"
}
]
}
}
}
// The second component, through the reload event, get new data with the first select component value.
{
"type": "select",
"strictMode": true,
"name": "city_id",
"label": "城市",
"id": "city_id",
"syncFields": [],
"placeholder": "文本",
"multiple": false,
"labelField": "name",
"valueField": "id",
"source": "/City/select?province_id=${province_id}",
"onEvent": {
"change": {
"weight": 0,
"actions": [
{
"componentId": "district_id",
"ignoreError": false,
"actionType": "reload"
}
]
}
}
}
service
https://aisuda.bce.baidu.com/amis/zh-CN/components/service
有一些组件没有获取数据的功能, 例如table-view, 可以放在一个数据服务组件内, 通过数据服务获取数据, 然后下级的组件来展示这些数据.
动态加载页面
例如在某个弹窗中get schema from the backend, then draw the content.
{
"type": "service",
"initFetchSchema": true,
"dsType": "api",
"schemaApi": {
"method": "get",
"adaptor": "",
"messages": {},
"requestAdaptor": "",
"url": "/PageSchema/getSchemaByProduct/${id}"
},
"id": "u:dc2d061ac907",
"body": []
}
动态加载页面的initApi问题
再CURD中”查看“功能,是通过 dialog 来动态加载一个page,但是这个page的initApi没有被正确执行,不知道为什么? 这里我们在dialog中先加一个service来获取initApi的数据,然后再该service的body再加一个service来获取动态的页面。 这时候通过数据链的特性,就可以使用initApi获取到的数据了。
steps
https://aisuda.bce.baidu.com/amis/zh-CN/components/steps
展示阶段信息
{
"type": "form",
"mode": "flex",
"static": true,
"labelAlign": "top",
"dsType": "api",
"initApi": {
"method": "post",
"data": {
"workflow_instance_id": 1535,
"&": "$$"
},
"dataType": "form",
"adaptor": "",
"messages": {},
"requestAdaptor": "",
"url": "/WorkflowInstance/getStepsAndUsers"
},
"id": "u:2905a3412127",
"title": "",
"body": [
{
"mode": "horizontal",
"visible": true,
"iconPosition": false,
"name": "step",
"id": "u:90f9c0b8f575",
"source": "${steps}",
"type": "steps",
"value": "${value}",
"steps": [
{
"type": "wrapper",
"body": "子节点内容"
},
{
"type": "wrapper",
"body": "子节点内容"
}
],
"status": "process"
}
],
"feat": "View",
"actions": []
}
def getStepsAndUsers(){
Long id = params.workflow_instance_id as Long
WorkflowInstance workflowInstance = WorkflowInstance.findById(id)
if (id) {
String sql = """
select wis.name, wis.start_time
from workflow_instance_stage wis
where 1=1
"""
Map paramsMap = new HashMap()
sql += " AND wis.instance_id = :id order by wis.execution_sequence asc;"
paramsMap.id = id
def sql1 = new Sql(dataSource)
def record = sql1.rows(sql, paramsMap)
Integer current = WorkflowInstanceStage.findAllByInstanceAndExecutionSequenceLessThan(workflowInstance,workflowInstance.stage.executionSequence).size()
def steps = []
record.each {
def step = [:]
step.put("title", it.name)
step.put("subTitle", it.start_time)
steps.add(step)
}
def res = ResService.success([steps: steps, value: current])
render res as JSON
} else {
def res = ResService.fail("参数错误: workflow_instance_id 不能为空")
render res as JSON
}
}
textarea
https://aisuda.bce.baidu.com/amis/zh-CN/components/form/textarea
tabs
https://aisuda.bce.baidu.com/amis/zh-CN/components/tabs 选项卡, 在选项卡里面添加内容, 先清空”内容”, 然后再复制组件进去.
timeline
https://aisuda.bce.baidu.com/amis/zh-CN/components/timeline
transfer-picker
从左边选择到右边的功能, 配合表单使用,
常用属性
visibleOn
然后使用组件的visiableOn属性来控制显示:
编辑器右侧的 状态→可见→表达式ARRAYSOME(roles, item => item === 'ROLE_DUE_DILIGENCE')
"visibleOn": "${ARRAYSOME(roles, item => item === 'ROLE_DUE_DILIGENCE')}"
注意要控制Tab的时候,要手动添加到某个Tab上。
APP 多页面
{
"label": "页面B",
"badge": 3, // 菜单右侧的脚标
"badgeClassName": "bg-info",
"schema": {
"type": "page",
"title": "页面B",
"body": "页面B"
}
},