百度Amis+React低代码实践
背景
在项目中有集成低代码平台的想法,经过多方对比最后选择了 amis,主要是需要通过 amis 进行页面配置,导出 json 供移动端和 PC 端进行渲染,所以接下来讲一下近两周研究 amis 的新的以及一些简单经验,供大家参考.
什么是 amis
amis 是一个低代码前端框架,它使用 JSON 配置来生成页面,可以减少页面开发工作量,极大提升效率。
如何使用 amis
在 amis 官网提供了两种使用 amis 的方式分别是
- JSSDK 可以在任意页面使用
- React 可以在 React 项目中使用
博主是在 umi 框架下结合 React 使用 amis,所以本文主要着重介绍第二种方法
在使用时需要对 amis 进行安装,项目中也需要使用 amis-editor 进行页面配置所以需要同时安装如下两个包
{
"amis": "^3.1.1",
"amis-editor": "^5.4.1"
}
amis
首先介绍 amis,amis 提供了 render 方法来对 amis-editor 生成的 JSON 对象页面配置进行渲染,如下,在使用是 render 主要作用就是进行渲染
import { render as renderAmis } from "amis";
const App = () => {
return (
<div>
{renderAmis({
type: "button",
label: "保存",
level: "primary",
onClick: function () {
console.log("TEST");
},
})}
</div>
);
};
export default App;
amis-editor
amis-editor 提供了一个编译器组件 <Editor />
import { useState } from "react";
import { Editor, setSchemaTpl } from "amis-editor";
import type { SchemaObject } from "amis";
import { render as renderAmis } from "amis";
import type { Schema } from "amis/lib/types";
// 以下样式均生效
import "amis/lib/themes/default.css";
import "amis/lib/helper.css";
import "amis/sdk/iconfont.css";
import "amis-editor-core/lib/style.css";
import "amis-ui/lib/themes/antd.css";
type Props = {
defaultPageConfig?: Schema;
codeGenHandler?: (codeObject: Schema) => void;
pageChangeHandler?: (codeObject: Schema) => void;
};
export function Amis(props: Props) {
const [mobile, setMobile] = useState(false);
const [preview, setPreview] = useState(false);
const [defaultPageConfig] = useState<Schema>(props.defaultPageConfig); // 传入配置
const defaultSchema: Schema | SchemaObject = defaultPageConfig || {
type: "page",
body: "",
title: "标题",
regions: ["body"],
};
const [schema] = useState(defaultSchema);
let pageJsonObj: Schema = defaultSchema;
const onChange = (value: Schema) => {
pageJsonObj = value;
props.pageChangeHandler && props.pageChangeHandler(value);
};
const onSave = () => {
props.codeGenHandler && props.codeGenHandler(pageJsonObj);
};
return (
<>
{renderAmis({
type: "form",
mode: "inline",
title: "",
body: [
{
type: "switch",
option: "预览",
name: "preview",
onChange: function (v: any) {
setPreview(v);
},
},
{
type: "switch",
option: "移动端",
name: "mobile",
onChange: function (v: any) {
setMobile(v);
},
},
{
type: "button",
label: "保存",
level: "primary",
onClick: function () {
onSave();
},
},
{
type: "button",
label: "退出",
level: "danger",
onClick: function () {
// if (!window.confirm('确定退出?')) return;
if (props.cancleGenHandler) props.cancleGenHandler();
},
},
],
})}
<Editor
preview={preview}
isMobile={mobile}
onChange={onChange}
value={schema as SchemaObject}
theme={"antd"}
onSave={onSave}
/>
</>
);
}
export default Amis;
在 amis 中提供了两套组件样式供我们选择,分别是 cxd(云舍)和 antd(仿 Antd),我们可以通过设置Editor
组件中 theme 属性来进行主题的选择,同时需要引入对应的组件样式在以上代码中,我们对Editor
组件进行了二次封装,暴露出了defaultPageConfig
(进入编译器默认页面 JSON 配置)属性和codeGenHandler
(代码生成保存方法),cancleGenHandler
(退出页面编辑器方法),pageChangeHandler
(页面改变方法)供外部使用
自定义组件
在 amis-editor 中使用的组件可以是我们的自定义组件.在编写自定义组件时特别需要主义的是它的 plugin 配置接下来以MyButton
为例来进行自定义组件的介绍
首先来介绍以下组件结构
├─MyButton
│ ├─comp.tsx # 组件本体
│ ├─index.tsx # 整体导出
│ ├─plugin.tsx # 右侧panel配置
在comp.tsx
中主要进行组件的开发
import React from "react";
import type { Schema } from "amis/lib/types";
import { Button } from "antd";
const MyButtonRender = React.forwardRef((props: Schema, ref: any) => {
// const props = this.props
return (
<Button
{...props}
ref={ref}
type={props.level || "primary"}
name={props.name}
>
{props.label}
</Button>
);
});
class MyButtonRender2 extends React.Component<any, any> {
handleClick = (nativeEvent: React.MouseEvent<any>) => {
const { dispatchEvent, onClick } = this.props;
// const params = this.getResolvedEventParams();
dispatchEvent(nativeEvent, {});
onClick?.({});
};
handleMouseEnter = (e: React.MouseEvent<any>) => {
const { dispatchEvent } = this.props;
// const params = this.getResolvedEventParams();
dispatchEvent(e, {});
};
handleMouseLeave = (e: React.MouseEvent<any>) => {
const { dispatchEvent } = this.props;
// const params = this.getResolvedEventParams();
dispatchEvent(e, {});
};
render() {
return (
<MyButtonRender
onClick={this.handleClick}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
>
{this.props.label}2
</MyButtonRender>
);
}
}
export default MyButtonRender2;
在上述代码中MyButtonRender
简单的对Button
组件进行了简单的封装,MyButtonRender2
对 amis 中组件的事件进行了简单的处理并暴露出去
在plugin.tsx
中主要对MyButtonRender
组件进行渲染器注册以及对组件的 plugin 进行配置,注册渲染器是为了将自定义组件拖入中间预览区域是可以正常的显示,这一操作与 amis 的工作原理有关(amis 的渲染过程是将 json 转成对应的 React 组件。先通过 json 的 type 找到对应的 Component,然后把其他属性作为 props 传递过去完成渲染。工作原理)
在plugin.tsx
中进行 panel 配置
import { Renderer } from "amis";
import MyButtonRender from "./comp";
import type { BaseEventContext } from "amis-editor-core";
import { BasePlugin } from "amis-editor-core";
import { getSchemaTpl } from "amis-editor-core";
import type { RendererPluginAction, RendererPluginEvent } from "amis-editor";
import { getEventControlConfig } from "amis-editor/lib/renderer/event-control/helper";
// 渲染器注册
Renderer({
type: "my-button",
autoVar: true,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
})(MyButtonRender);
export class MyButton extends BasePlugin {
// 关联渲染器名字
rendererName = "my-button";
$schema = "/schemas/ActionSchema.json";
order = -400;
// 组件名称
name = "MyButton";
isBaseComponent = true;
description =
"用来展示一个按钮,你可以配置不同的展示样式,配置不同的点击行为。";
docLink = "/amis/zh-CN/components/button";
tags = ["自定义"];
icon = "fa fa-square";
pluginIcon = "button-plugin";
scaffold = {
type: "my-button",
label: "MyButton",
wrapperBody: true,
};
previewSchema: any = {
type: "my-button",
label: "MyButton",
wrapperBody: true,
};
panelTitle = "MyButton";
// 事件定义
events: RendererPluginEvent[] = [
{
eventName: "click",
eventLabel: "点击",
description: "点击时触发",
defaultShow: true,
dataSchema: [
{
type: "object",
properties: {
nativeEvent: {
type: "object",
title: "鼠标事件对象",
},
},
},
],
},
{
eventName: "mouseenter",
eventLabel: "鼠标移入",
description: "鼠标移入时触发",
dataSchema: [
{
type: "object",
properties: {
nativeEvent: {
type: "object",
title: "鼠标事件对象",
},
},
},
],
},
{
eventName: "mouseleave",
eventLabel: "鼠标移出",
description: "鼠标移出时触发",
dataSchema: [
{
type: "object",
properties: {
nativeEvent: {
type: "object",
title: "鼠标事件对象",
},
},
},
],
},
];
// 动作定义
actions: RendererPluginAction[] = [];
panelJustify = true;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
panelBodyCreator = (context: BaseEventContext) => {
return getSchemaTpl("tabs", [
{
title: "属性",
body: [
getSchemaTpl("label", {
label: "按钮名称",
}),
{
type: "input-text",
label: "字段名称",
name: "name",
},
{
type: "select",
label: "按钮类型",
name: "level",
options: [
{
label: "默认",
value: "primary",
},
{
label: "危险",
value: "danger",
},
{
label: "警告",
value: "warn",
},
{
label: "成功",
value: "success",
},
{
label: "浅色",
value: "default",
},
],
multiple: false,
selectFirst: false,
},
{
type: "input-text",
label: "按钮图标",
name: "icon",
// 提示
labelRemark: {
icon: 'icon-close',
trigger: ['hover'],
className: 'Remark--warning',
title: '提示',
content: '图标请从My Iconfont库中选择 部分图标需要加icon-前缀 如close -> icon-close',
// },
},
],
},
{
title: "样式",
body: [
getSchemaTpl("buttonLevel", {
label: "高亮样式",
name: "activeLevel",
visibleOn: "data.active",
}),
getSchemaTpl("switch", {
name: "block",
label: "块状显示",
}),
getSchemaTpl("size", {
label: "尺寸",
}),
],
},
{
title: "事件",
className: "p-none",
body:
!!context.schema.actionType ||
["submit", "reset"].includes(context.schema.type)
? [
getSchemaTpl("eventControl", {
name: "onEvent",
...getEventControlConfig(this.manager, context),
}),
// getOldActionSchema(this.manager, context)
]
: [
getSchemaTpl("eventControl", {
name: "onEvent",
...getEventControlConfig(this.manager, context),
}),
],
},
]);
};
}
当点选某个组件的时候,编辑器内部会触发面板构建动作,每个插件都可以通过实现 buildEditorPanel
来插入右侧面板。通常右侧面板都是表单配置,使用 amis 配置就可以完成。所以推荐的做法是,直接在这个插件上面定义 panelBody
或者 panelBodyCreator
即可。
具体配置可以参考上述代码,其中需要注意的是getSchemaTpl
这一方法,该方法通过获取预先通过setSchemaTpl
设置的模板来进行渲染某些元素组件,一下部分源码可进行参考,tpl 部分全部源码可参考这里
export function getSchemaTpl(
name: string,
patch?: object,
rendererSchema?: any
): any {
const tpl = tpls[name] || {};
let schema = null;
if (typeof tpl === "function") {
schema = tpl(patch, rendererSchema);
} else {
schema = patch
? {
...tpl,
...patch,
}
: tpl;
}
return schema;
}
export function setSchemaTpl(name: string, value: any) {
tpls[name] = value;
}
在index.tsx
中主要进行自定义组件插件的注册以及导出
import { registerEditorPlugin } from "amis-editor";
import { MyButton } from "./plugin";
registerEditorPlugin(MyButton);
其他
在拖拽组件生成页面时,amis-editor 可选择的组件有很多,如果我们想使用自己组建的同时忽略隐藏原有组件可以通过disabledRendererPlugin
来对原生组件进行隐藏
import { registerEditorPlugin, BasePlugin } from "amis-editor";
import type {
RendererEventContext,
SubRendererInfo,
BasicSubRenderInfo,
} from "amis-editor";
/**
* 用于隐藏一些不需要的Editor组件
* 备注: 如果不知道当前Editor中有哪些预置组件,可以在这里设置一个断点,console.log 看一下 renderers。
*/
// 需要在组件面板中隐藏的组件
const disabledRenderers = [
// 'flex',
"crud2",
"crud2",
"crud2",
// 'crud',
// 'input-text',
"input-email",
"input-password",
"input-url",
// "button",
"reset",
"submit",
"tpl",
"grid",
"container",
// 'flex',
// 'flex',
"collapse-group",
"panel",
"tabs",
// 'form',
"service",
"textarea",
"input-number",
// 'select',
"nested-select",
"chained-select",
"dropdown-button",
"checkboxes",
"radios",
"checkbox",
"input-date",
"input-date-range",
"input-file",
"input-image",
"input-excel",
"input-tree",
"input-tag",
"list-select",
"button-group-select",
"button-toolbar",
"picker",
"switch",
"input-range",
"input-rating",
"input-city",
"transfer",
"tabs-transfer",
"input-color",
"condition-builder",
"fieldset",
"combo",
"input-group",
"input-table",
"matrix-checkboxes",
"input-rich-text",
"diff-editor",
"editor",
"search-box",
"input-kv",
"input-repeat",
"uuid",
"location-picker",
"input-sub-form",
"hidden",
"button-group",
"nav",
"anchor-nav",
"tooltip-wrapper",
"alert",
"wizard",
"table-view",
"web-component",
"audio",
"video",
"custom",
"tasks",
"each",
"property",
"iframe",
"qrcode",
"icon",
"link",
"list",
"mapping",
"avatar",
"card",
"card2",
"cards",
"table",
"table2",
"chart",
"sparkline",
"carousel",
"image",
"images",
"date",
"time",
"datetime",
"tag",
"json",
"progress",
"status",
"steps",
"timeline",
"divider",
"code",
"markdown",
"collapse",
"log",
"input-array",
"control",
"input-datetime",
"input-datetime-range",
"formula",
"group",
"input-month",
"input-month-range",
"input-quarter",
"input-quarter-range",
"static",
"input-time",
"input-time-range",
"tree-select",
"input-year",
"input-year-range",
"breadcrumb",
"custom",
"hbox",
"page",
"pagination",
"plain",
"wrapper",
"column-toggler",
];
export class ManagerEditorPlugin extends BasePlugin {
order = 9999;
buildSubRenderers(
context: RendererEventContext,
renderers: SubRendererInfo[]
): BasicSubRenderInfo | BasicSubRenderInfo[] | void {
// 更新NPM自定义组件排序和分类
// console.log(renderers);
for (let index = 0, size = renderers.length; index < size; index++) {
// 判断是否需要隐藏 Editor预置组件
const pluginRendererName = renderers[index].rendererName;
if (
pluginRendererName &&
disabledRenderers.indexOf(pluginRendererName) > -1
) {
renderers[index].disabledRendererPlugin = true; // 更新状态
}
}
}
}
registerEditorPlugin(ManagerEditorPlugin);
写在最后
一个阶段的结束伴随着另一个阶段的开始,在新的阶段中会继续学习继续进步
百度Amis+React低代码实践的更多相关文章
- vivo 低代码平台【后羿】的探索与实践
作者:vivo 互联网前端团队- Wang Ning 本文根据王宁老师在"2022 vivo开发者大会"现场演讲内容整理而成.公众号回复[2022 VDC]获取互联网技术分会场议题 ...
- 2021年哪个低代码平台更值得关注?T媒体盘点国内主流低代码厂商
2020年圣诞前夜,国内知名创投科技媒体T媒体旗下的T研究发布了2020中国低代码平台指数测评报告.报告除了对国内低代码行业现状进行总结外,还对主流低代码厂商的市场渗透和曝光进行测评. 报告认为,低代 ...
- 开源低代码平台开发实践二:从 0 构建一个基于 ER 图的低代码后端
前后端分离了! 第一次知道这个事情的时候,内心是困惑的. 前端都出去搞 SPA,SEO 们同意吗? 后来,SSR 来了. 他说:"SEO 们同意了!" 任何人的反对,都没用了,时代 ...
- 实践案例1-利用低代码开发平台Odoo快速构建律师事务所管理系统
今年10月份中旬的时候,有一段时间没联系的中学同学,我跟他关系比较好,突然打电话给我,希望我给他夫人的律所开发一个小系统.记得十几年前,当他还在他叔叔公司上班的,他是负责销售的,我们几乎每周都碰面,那 ...
- YonBuilder低代码开发实践:4行代码实现跨实体列表数据同步
提到增.删.改.查等数据维护,后端开发者们再熟悉不过了.传统的数据维护通过操作数据库的方式实现,步骤比较繁琐,需要通过Java代码实现数据库链接,然后编写SQL语句.编写实体,将想要的数据存到相应的数 ...
- 微服务低代码Serverless平台(星链)的应用实践
导读 星链是京东科技消金基础研发部研发的一款研发效能提升的工具平台,面向后端服务研发需求,尤其是集成性.场景化.定制化等难度不太高.但比较繁琐的需求,如服务前端的后端(BFF).服务流程编排.异步消息 ...
- Java的BIO和NIO很难懂?用代码实践给你看,再不懂我转行!
本文原题“从实践角度重新理解BIO和NIO”,原文由Object分享,为了更好的内容表现力,收录时有改动. 1.引言 这段时间自己在看一些Java中BIO和NIO之类的东西,也看了很多博客,发现各种关 ...
- 个性化和云端孤岛困扰SaaS用户,低代码PaaS或成解决之道 ZT
近日,中国软件行业协会.中国软件网联合阿里云推出了<2020中国SaaS产业十大趋势>,其中明确指出企业软件SaaS化是大势所趋,但个性化和云端孤岛成为2020年SaaS用户关注的两大问题 ...
- 从表单驱动到模型驱动,解读低代码开发平台的发展趋势 ZT
原文地址:https://www.grapecity.com.cn/blogs/read-the-trends-of-low-code-development-platforms 随着社会数字化进程的 ...
- 企业应用开发的大趋势,65%的应用开发将通过低代码完成 ZT
全球知名的咨询公司Gartner于近日发表了最新版的<低代码开发平台魔力象限>,并在报告中指出,到2024年65%的应用开发工作都将通过低代码的方式完成.Gartner长期关注软件开发领域 ...
随机推荐
- Generative Pre-trained Transformer(GPT)模型技术初探
一.Transformer模型 2017年,Google在论文 Attention is All you need 中提出了 Transformer 模型,其使用 Self-Attention 结构取 ...
- 【FAQ】关于华为推送服务因营销消息频次管控导致服务通讯类消息下发失败的解决方案
一. 问题描述 使用华为推送服务下发IM消息时,下发消息请求成功且code码为80000000,但是手机总是收不到消息: 在华为推送自助分析(Beta)平台查看发现,消息发送触发了频控. 二. 问题原 ...
- Linux 阶段二
1.2 安装JDK JDK具体安装步骤如下: 1). 上传安装包 使用FinalShell自带的上传工具将jdk的二进制发布包上传到Linux 由于上述在进行文件上传时,选择的上传目录为根目录 /,上 ...
- MySQL相关操作(实用函数和sql语法)
1.时间函数 当前时间 select current_timestamp(); 当前时间戳 select UNIX_TIMESTAMP(NOW()); 当前时间戳精确到毫秒 select REPLAC ...
- Unix shell开头的#!
1:位于脚本文件最开始 2:#!告诉系统内核应有哪个shell来执行所指定的shell脚本. 3:如#! /bin/bash ,#!与shell文件名之间可以有空格,没有限定. 4:指定的shell可 ...
- selenium IDE插件的配置使用
开头 Selenium提供了一个可以自动录制脚本的插件 叫selenium IDE 让我们一起看看如何安装使用 安装 因为google扩展商城大多数人用不了,所以我们选用的是edag来下载seleni ...
- 2022-05-16:A -> B,表示A认为B是红人, A -> B -> C,表示A认为B是红人,B认为C是红人,规定“认为”关系有传递性,所以A也认为C是红人, 给定一张有向图,方式是给定M个有
2022-05-16:A -> B,表示A认为B是红人, A -> B -> C,表示A认为B是红人,B认为C是红人,规定"认为"关系有传递性,所以A也认为C是红 ...
- 2021-08-02:按公因数计算最大组件大小。给定一个由不同正整数的组成的非空数组 A,考虑下面的图:有 A.length 个节点,按从 A[0] 到 A[A.length - 1] 标记;只有当
2021-08-02:按公因数计算最大组件大小.给定一个由不同正整数的组成的非空数组 A,考虑下面的图:有 A.length 个节点,按从 A[0] 到 A[A.length - 1] 标记:只有当 ...
- 从零开始使用 Astro 的实用指南
在这个实用的Astro指南中,我将指导你完成设置过程,并告诉你如何构造你的文件.你将学习如何添加页面.交互式组件,甚至是markdown文章.我还会告诉你如何从服务器上获取数据,创建布局,并使用van ...
- 从 DevOps 到平台工程:软件开发的新范式
DevOps 是一种将开发和运营结合起来的方法,在应用规划.开发.交付和运营方面将人员.流程和技术结合起来.DevOps 使以前孤立的角色(如开发.IT运营.质量工程和安全)之间进行协调和合作.一直以 ...