一款所见即所得的H5编辑器
简洁方便
任何人只需傻瓜式拖拽或进行简单编辑即可生成精美的H5页面
插拔式体验
产品以GPL协议开源, 授权后可植入任何系统,并支持二次开发
持续迭代,无限可能
目前正在持续迭代中,后续可根据需求开发功能更强大的可视化系统
diff --git a/doc-dist/404.html b/doc-dist/404.html new file mode 100644 index 00000000..a1fa237f --- /dev/null +++ b/doc-dist/404.html @@ -0,0 +1,20 @@ + + +
+ + +正在建设中...
DSL层主要约定了Dooring组件的数据协议,包括组件的可编辑属性、编辑类型、初始值等,之所以定义一致的协议层,主要是方便后期的组件扩展,配置后移,有助于不同后端语言开发和数据存储,接下来我们看看header组件的schema。
1.editData 可编辑的属性类型DSL
2.config 可编辑组件的默认属性
const Header: IHeaderSchema = {
+ editData: [
+ {
+ key: 'bgColor',
+ name: '背景色',
+ type: 'Color',
+ },
+ {
+ key: 'height',
+ name: '高度',
+ type: 'Number',
+ },
+ {
+ key: 'logo',
+ name: 'logo',
+ type: 'Upload',
+ isCrop: true,
+ cropRate: 1000 / 618,
+ },
+ {
+ key: 'logoText',
+ name: 'logo文字',
+ type: 'Text',
+ },
+ {
+ key: 'color',
+ name: '文字颜色',
+ type: 'Color',
+ },
+ {
+ key: 'fontSize',
+ name: '文字大小',
+ type: 'Number',
+ }
+ ],
+ config: {
+ bgColor: 'rgba(245,245,245,1)',
+ logo: [
+ {
+ uid: '001',
+ name: 'image.png',
+ status: 'done',
+ url: `${serverUrl}/uploads/3_1740be8a482.png`,
+ },
+ ],
+ logoText: '页头Header',
+ fontSize: 20,
+ color: 'rgba(47,84,235,1)',
+ height: 50
+ },
+};
+
由以上代码可知,我们可以在editData属性中给组件添加可编辑的属性,比如背景图,然后再component中接受属性从而设置样式。
在config属性中,我们可以设置组件默认属性值,和editData中每一项的key一一对应。
dooring的组件设计包含以下3个部分组件:
1、component 组件主体
2、schema 组件的DSL,结构协议层
3、template 定义了组件的类型、外观、从属关系,后期考虑纳入schema
接下来我会介绍一个基本的组件主体设计,以为template设计,在下一章会具体介绍schema部分。
我们这里拿基本的header组件来举例,如下是header组件的代码:
interface HeaderPropTypes extends IHeaderConfig {
+ isTpl: boolean;
+}
+
+const Header = memo((props: HeaderPropTypes) => {
+ const { bgColor, logo, logoText, fontSize, color } = props;
+ return props.isTpl ? (
+ <div>
+ < img style={{width: '100%'}} src={logos} alt="" />
+ </div>
+ ) : (
+ <header className={styles.header} style={{ backgroundColor: bgColor }}>
+ <div className={styles.logo}>
+ < img src={logo && logo[0].url} alt={logoText} />
+ </div>
+ <div className={styles.title} style={{ fontSize, color }}>
+ {logoText}
+ </div>
+ </header>
+ );
+});
+
我们只需要按照上面的方式编写组件即可,props是DSL定义的数据层,用来控制组件的shape,也就是组件的表现。我们看看header对应的template。
const template = {
+ type: 'Header',
+ h: 28,
+ displayName: '页头组件'
+};
+export default template;
+
以上就是我们template的结构,type用来定义组件的类型,方便渲染器动态查找,h代表组件的初始化高度,我们可以自由设置。displayName是组件的中文名,用来在左侧组件面板中展示,方便用户理解,我们可以在template中自定义更多辅助信息,方便使用者更高效的使用我们的编辑器。
开发一个自定义组件需要包含3部分, Component
, Schema
和 Template
. 接下来我们看一下 Header
组件的 Schema
.
import {
+ IColorConfigType,
+ INumberConfigType,
+ ITextConfigType,
+ IUploadConfigType,
+ TColorDefaultType,
+ TNumberDefaultType,
+ TTextDefaultType,
+ TUploadDefaultType,
+} from '@/components/FormComponents/types';
+import { baseConfig, baseDefault, ICommonBaseType } from '../../common';
+
+export type THeaderEditData = Array<
+ IColorConfigType | INumberConfigType | IUploadConfigType | ITextConfigType
+>;
+export interface IHeaderConfig extends ICommonBaseType {
+ bgColor: TColorDefaultType;
+ logo: TUploadDefaultType;
+ logoText: TTextDefaultType;
+ fontSize: TNumberDefaultType;
+ color: TColorDefaultType;
+ height: TNumberDefaultType;
+}
+
+export interface IHeaderSchema {
+ editData: THeaderEditData;
+ config: IHeaderConfig;
+}
+
+const Header: IHeaderSchema = {
+ editData: [
+ ...baseConfig,
+ {
+ key: 'bgColor',
+ name: '背景色',
+ type: 'Color',
+ },
+ {
+ key: 'height',
+ name: '高度',
+ type: 'Number',
+ },
+ {
+ key: 'logo',
+ name: 'logo',
+ type: 'Upload',
+ isCrop: true,
+ cropRate: 1000 / 618,
+ },
+ {
+ key: 'logoText',
+ name: 'logo文字',
+ type: 'Text',
+ },
+ {
+ key: 'color',
+ name: '文字颜色',
+ type: 'Color',
+ },
+ {
+ key: 'fontSize',
+ name: '文字大小',
+ type: 'Number',
+ },
+ ],
+ config: {
+ bgColor: 'rgba(0,0,0,1)',
+ logo: [
+ {
+ uid: '001',
+ name: 'image.png',
+ status: 'done',
+ url: 'http://49.234.61.19/uploads/3_1740be8a482.png',
+ },
+ ],
+ logoText: '页头Header',
+ fontSize: 20,
+ color: 'rgba(255,255,255,1)',
+ height: 50,
+ ...baseDefault,
+ },
+};
+
+export default Header;
+
editData
表示组件的可编辑属性, 我们可以自定义哪些组件可编辑. config
为组件接收的属性, 和editData
数组项中的key
一一对应.
Dooring
组件编辑面板有如下对应编辑类型:
目前H5-Dooring的组件都是通过动态加载的方式引入,好处是我们在页面中只会加载我们需要的组件,不需要的组件不会被加载,这样可以提高页面加载的速度,这样做也会出现一些问题,比如一个长页面,配置了很多组件,那么一个页面加载过程可以会触发多次请求,目前还没有遇到性能问题,但后续会逐渐优化这个问题。
目前组件的动态加载我们采用的umi的dynamic方案,基于它我们上层封装了一个组件动态加载器,原理如下:
具体代码可以参考Dooring的Github地址:https://github.com/MrXujiang/h5-Dooring (opens new window)
H5-Dooring后端部分主要使用 Nodejs
开发, 为了满足更多定制化需求和服务的可移植性, 特意编写了API接口文档,
+方便大家使用不同的后端语言实现服务接入.
/api/v0
用户登录接口
POST
/vip/check参数名 | 是否必选 | 类型 | 说明 |
---|---|---|---|
n | true | string | 用户名 |
co | true | string | 密码 |
返回示例
{
+ "result": {
+ "n": "test",
+ "od": [],
+ "h5": [
+ {
+ "t": "23242ED",
+ "n": "测试页面"
+ }
+ ],
+ "rp": "AAAAA",
+ "maxage": 300000
+ }
+}
+
注销接口
POST
/vip/checkout返回示例
{
+ "result": null,
+ "msg": "退出成功"
+}
+
不同用户级别所访问的页面权限不同, 这块可结合服务端已有代码设计属于自己的权限字段, 地址为server/src/router
获取用户列表接口
GET
/vip/all获取用户列表需要账号满足以下条件:
返回示例
[
+ {
+ "id": "",
+ "n": "test",
+ "co": "123456",
+ "od": [],
+ "h5": [
+ {
+ "t": "23242ED",
+ "n": "测试页面"
+ }
+ ],
+ "wx": "Mr_xuxiaoxi",
+ "rp": "AAAAA"
+ }
+]
+
添加用户接口
POST
/vip/add先决条件:
参数名 | 是否必选 | 类型 | 说明 |
---|---|---|---|
nickname | true | string | 用户名 |
wx | true | string | 微信号 |
co | true | string | 密码 |
注: co是由笔者写的加密算法
实现, 不需要手动填写, 详情见dooirng
后台管理/用户管理页面.
返回示例
{
+ "id": "3422EF",
+ "n": "test",
+ "wx": "Mr_xuxiaoxi",
+ "co": "123456",
+ "od": [],
+ "h5": [],
+ "tpl": [],
+ "rp": "AAAAA",
+ "h5Num": 10,
+ "tplNum": 3
+}
+
生成登录码接口
GET
/vip/gcode先决条件:
注: 生成登录码是由笔者写的加密算法
实现, 不需要手动实现, 如果有自定义需求, 可以自行二次开发实现.
返回示例
{
+ "co": "1x2fgggteee3456_zdd4",
+}
+
说明:
为了保护用户信息安全, 返回的登录码是加密后的密文, 会调用笔者写的xib.xip
方法进行加密, 如果想看到原始密码, 需要调用xib.uxip
进行解密.
获取用户真实密码接口
GET
/vip/gcode/get先决条件:
参数名 | 是否必选 | 类型 | 说明 |
---|---|---|---|
co | true | string | 加密后的密码 |
返回示例
{
+ "co": "12345678",
+}
+
修改用户接口
POST
/vip/edit先决条件:
参数名 | 是否必选 | 类型 | 说明 |
---|---|---|---|
id | false | string | 用户ID |
nickname | false | string | 用户名 |
co | false | string | 登录码 |
wx | false | string | 微信号 |
返回示例
{
+ "state": 200,
+ "result": null,
+ "msg": "修改成功",
+}
+
删除用户接口
DELETE
/vip/del先决条件:
参数名 | 是否必选 | 类型 | 说明 |
---|---|---|---|
id | true | string | 用户ID |
wx | true | string | 微信号 |
n | true | string | 用户名 |
返回示例
{
+ "state": 200,
+ "result": null,
+ "msg": "删除成功",
+}
+
GET
/visible/h5/get先决条件:
参数名 | 是否必选 | 类型 | 说明 |
---|---|---|---|
tid | true | string | H5唯一id |
返回示例
{
+ "pageConfig": {
+
+ },
+ "tpl": [
+ {
+ "id": "879742",
+ "item": {
+ "type": "Carousel",
+ "config": {
+ "direction": "left",
+ "swipeable": false,
+ "autoPlay": false,
+ "imgList": [
+ {
+ "id": "1",
+ "title": "趣谈小课1",
+ "desc": "致力于打造优质小课程",
+ "link": "xxxxx",
+ "imgUrl": [
+ {
+ "uid": "001",
+ "name": "image.png",
+ "status": "done",
+ "url": "http://io.nainor.com/uploads/1_1740bd7c3dc.png"
+ }
+ ]
+ },
+ {
+ "id": "2",
+ "title": "趣谈小课1",
+ "desc": "致力于打造优质小课程",
+ "link": "xxxxx",
+ "imgUrl": [
+ {
+ "uid": "001",
+ "name": "image.png",
+ "status": "done",
+ "url": "http://io.nainor.com/uploads/2_1740bd8d525.png"
+ }
+ ]
+ }
+ ],
+ "tplImg": "http://io.nainor.com/uploads/carousal_17442e1420f.png"
+ },
+ "h": 82,
+ "editableEl": [
+ {
+ "key": "direction",
+ "name": "方向",
+ "type": "Radio",
+ "range": [
+ {
+ "key": "down",
+ "text": "从上到下"
+ },
+ {
+ "key": "left",
+ "text": "从左到右"
+ }
+ ]
+ },
+ {
+ "key": "swipeable",
+ "name": "是否可拖拽",
+ "type": "Switch"
+ },
+ {
+ "key": "autoPlay",
+ "name": "是否自动播放",
+ "type": "Switch"
+ },
+ {
+ "key": "imgList",
+ "name": "图片列表",
+ "type": "DataList"
+ }
+ ],
+ "category": "base"
+ },
+ "point": {
+ "i": "x-0",
+ "x": 0,
+ "y": 13,
+ "w": 24,
+ "h": 82,
+ "isBounded": true
+ },
+ "status": "inToCanvas"
+ },
+ {
+ "id": "481194",
+ "item": {
+ "type": "Form",
+ "config": {
+ "title": "表单定制组件",
+ "fontSize": 18,
+ "titColor": "rgba(60,60,60,1)",
+ "titWeight": "400",
+ "bgColor": "rgba(255,255,255,1)",
+ "btnColor": "rgba(20,54,226,100)",
+ "btnTextColor": "rgba(255,255,255,1)",
+ "api": "",
+ "formControls": [
+ {
+ "id": "1",
+ "type": "Text",
+ "label": "姓名",
+ "placeholder": "请输入姓名"
+ },
+ {
+ "id": "2",
+ "type": "Number",
+ "label": "年龄",
+ "placeholder": " 请输入年龄"
+ },
+ {
+ "id": "4",
+ "type": "MySelect",
+ "label": "爱好",
+ "options": [
+ {
+ "label": "选项一",
+ "value": "1"
+ },
+ {
+ "label": "选项二",
+ "value": "2"
+ },
+ {
+ "label": "选项三",
+ "value": "3"
+ }
+ ]
+ }
+ ]
+ },
+ "h": 172,
+ "category": "base"
+ },
+ "point": {
+ "i": "x-1",
+ "x": 0,
+ "y": 98,
+ "w": 24,
+ "h": 172,
+ "isBounded": true
+ },
+ "status": "inToCanvas"
+ }
+ ]
+}
+
POST
/visible/h5/save先决条件:
参数名 | 是否必选 | 类型 | 说明 |
---|---|---|---|
pageConfig | false | object | H5页面配置数据 |
tpl | true | object | H5页面组件配置数据 |
tid | true | string | H5页面唯一id |
参数示例
{
+ "pageConfig": {
+ "bgColor":"rgba(151,25,25,1)",
+ "title":"医院宣传页"
+ },
+ "tpl": [],
+ "tid": "EF123D3"
+}
+
返回示例
{
+ "state": 200,
+ "result": {
+ "tid": "EF123D3"
+ },
+ "msg": "保存成功"
+}
+
DELETE
/visible/h5/del先决条件:
参数名 | 是否必选 | 类型 | 说明 |
---|---|---|---|
tid | true | string | H5页面唯一id |
返回示例
{
+ "state": 200,
+ "result": [
+ {
+ "tid": "EF123D3",
+ "name": "test页面"
+ },
+ {
+ "tid": "EF123D6",
+ "name": "test2页面"
+ }
+ ],
+ "msg": "删除成功"
+}
+
POST
/vip/h5/form/post参数名 | 是否必选 | 类型 | 说明 |
---|---|---|---|
tid(query) | true | string | H5页面唯一id |
formData(body) | true | array | H5页面表单数据 |
返回示例
{
+ "state": 200,
+ "result": null,
+ "msg": "表单提交成功"
+}
+
POST
/vip/h5/form/import参数名 | 是否必选 | 类型 | 说明 |
---|---|---|---|
tid(query) | true | string | H5页面唯一id |
formData(body) | true | array | H5页面表单数据集合 |
返回示例
{
+ "state": 200,
+ "result": null,
+ "msg": "批量导入成功"
+}
+
DELETE
/vip/h5/form/del参数名 | 是否必选 | 类型 | 说明 |
---|---|---|---|
tid | true | string | H5页面唯一id |
ID | true | string | 表单专属id |
返回示例
{
+ "state": 200,
+ "result": null,
+ "msg": "删除成功"
+}
+
GET
/visible/tpls/free返回示例
{
+ "state": 200,
+ "result": [
+ {
+ "img": "http://xxx/uploads/tpl_175adabd8dd.jpg",
+ "name": "合作模版",
+ "tid": "B73349B6"
+ }
+ ]
+}
+
POST
/visible/tpl/save先决条件:
参数名 | 是否必选 | 类型 | 说明 |
---|---|---|---|
name | true | string | H5模版名称 |
cate | true | string | H5模版分类 |
img | false | string | H5模版封面图 |
tpl | true | array | H5模版数据 |
pageConfig | false | object | H5模版全局配置 |
返回示例
{
+ "state": 200,
+ "result": {
+ "tid": "B73349B6"
+ },
+ "msg": "保存成功"
+}
+
DELETE
/visible/tpl/del先决条件:
参数名 | 是否必选 | 类型 | 说明 |
---|---|---|---|
tid | true | string | H5模版id |
返回示例
{
+ "state": 200,
+ "result": null,
+ "msg": "删除成功"
+}
+
+ ← + + 获取Form组件的值数据 + + 更新日志 + + → +
私有化部署需要获取4个核心项目包, 包括
获取以上四个核心源码工程需要满足商业授权协议, 具体可联系作者徐小夕 (opens new window)
部署流程如下:
server
的static目录下server
下本地运行 yarn start
或 npm start
启动服务端进行本地测试yarn build
生成 dist
目录, 建议使用 pm2
做nodejs
服务的负载均衡, 运行 pm2 start dist/index.js
启动生产环境代码也可以将以上步骤集成到gitlab等CI, CD服务中, 进行自动化打包发布, 或者采用docker
进行容器化部署.
服务器需提前安装node和pm2, 将本项目上传至服务器指定的目录(如/www/activity), 进入项目目录, 执行:
npm install
+
进入./src/config/index.js
, 修改staticPath
变量为当前服务器域名/ip, 如http://xxx.com
或http://xxx.com:8080
(如非80端口)
执行npm run build
编译项目, 生成dist
目录
在项目根目录执行 pm2 start dist/index.js
启动项目
+ ← + + 截图功能 + + v6.dooring私有化部署(临时) + + → +
私有化部署需要获取3个核心项目包, 包括
获取以上三个核心源码工程需要满足商业授权协议, 具体可参考商业授权方案 (opens new window)
部署流程如下:
server
的static目录下server
下本地运行 yarn start
或 npm start
启动服务端进行本地测试yarn build
生成 dist
目录, 建议使用 pm2
做nodejs
服务的负载均衡, 运行 pm2 start dist/index.js
启动生产环境代码也可以将以上步骤集成到gitlab等CI, CD服务中, 进行自动化打包发布, 或者采用docker
进行容器化部署.
服务器需提前安装node和pm2, 将本项目上传至服务器指定的目录(如/www/activity), 进入项目目录, 执行:
npm install
+
进入./src/config/index.js
, 修改staticPath
变量为当前服务器域名/ip, 如http://xxx.com
或http://xxx.com:8080
(如非80端口)
执行npm run build
编译项目, 生成dist
目录
在项目根目录执行 pm2 start dist/index.js
启动项目
+ ← + + 私有化部署 + + 服务端数据说明 + + → +
服务端主要是我们的server
工程, 数据主要存放在server/public
下, 具体数据指代含义我们接下来会详细介绍.
+ ← + + v6.dooring私有化部署(临时) + + 支持https + + → +
Form表单组件在editor
目录下src/components/BasicShop/BasicComponents
位置.
Form组件是Dooring
的核心组件之一, 内部的值通过Form组件内部收集, 当然我们也可以暴露出来让其他交互或者组件消费(需要一定的二次开发), 关键代码如下:
req.post(`/vip/h5/form/post${location.search}`, {...fields, ...formData}).then(res => {
+ if(type === 'link') {
+ // 解析参数
+ let isPre = content.indexOf('?') < 0;
+ let query = {dr: Date.now(), from: urlParmas.tid};
+ try {
+ query = params ? {...JSON.parse(params), ...query} : query;
+ }catch(err) {
+ console.log(err)
+ }
+
+ // 跳转
+ if(content.indexOf('http') > -1) {
+ window.location.href = content + urlencode(query, isPre);
+ return
+ }
+
+ history.push(`/m?tid=${content}&${urlencode(query)}`);
+ }else if(type === 'modal') {
+ setVisible(true);
+ }else if(type === 'code') {
+ eval(content);
+ }
+})
+
数据收集提交的核心代码在Form组件的第56-149行, 也就是submit
方法. 表单组件收集到的数据统一存放在代码中的formData
字段, 所以要想在其他地方获取用户表单填写的值, 我们只需要手动将formData
传递出去, 或者挂载到全局(如window对象, localStorage, indexedDB等).
+ ← + + 接入第三方oss + + API接口文档 + + → +
目前H5-Dooring全面支持https部署, 具体方式方案如下.
我们需要在前端工程中的src/pages/document.ejs
中的head
中添加如下代码:
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
+
目的是强制将页面中HTTP请求转换为HTTPS.
server
中的src/index.js
按如下方式修改// 忽略部分无影响代码
+import https from 'https';
+
+// 你的ssl存放路径, 建议直接放在server目录下
+const filePath = path.join(__dirname, '../ssl')
+
+// 启动逻辑
+async function start() {
+ // https配置
+ const httpsOptions = {
+ key: fs.readFileSync(path.join(filePath, '3536084__doctopia.com.cn.key')), //ssl文件路径
+ cert: fs.readFileSync(path.join(filePath, '3536084__doctopia.com.cn.pem')) //ssl文件路径
+ };
+
+ // https服务
+ const server = https.createServer(httpsOptions, app.callback());
+
+ const io = require('socket.io')(server);
+
+ // 忽略其他无影响代码
+
+ // https默认443, 这里我们可以走公共配置
+ server.listen(443, () => {
+ console.log(`服务器地址:${config.staticPath}`)
+ });
+}
+
+start()
+
+ ← + + 服务端数据说明 + + 接入第三方oss + + → +
+ ← + + API接口文档 +
H5-Dooring全面支持第三方对象存储服务, 我们以七牛云对象存储为例.
首先我们需要在第三方对象储存服务中配置对应的服务和域名. 其次安装对应的sdk, 如七牛云sdk:
import * as qiniu from 'qiniu-js';
+
其次我们修改h5_plus
工程的Upload
组件, 详细地址为src/core/FormComponents/Upload
.
修改内容如下:
const fileName = file.name
+const suffix = '自定义文件后缀'
+const putExtra = {
+ fname: fileName,
+ params: {}
+}
+const uid = +new Date() + uuid(16, 8) + suffix
+// 使用七牛云上传api, 前提是提前在前端拿到对应的ticket, 可以通过请求的方式获取
+const observe = qiniu.upload(file, uid, this.state.qnToken.ticket, putExtra, {})
+observe.subscribe(() => {}, null, (res) => {
+ // 拼接路径
+ const url = `${this.state.qnToken.domain}/${res.key}`;
+ // 存库
+ const fileList = [{ uid, name: fileName, status: 'done', url }];
+ this.setState({
+ curImgUrl: url,
+ fileList
+ })
+ this.props.onChange && this.props.onChange(fileList)
+})
+
其他oss服务类似, 如果不清楚如何配置, 可以在H5-Dooring官网 (opens new window)中找到我们.
首先我们的上传组件Upload
使用内部的服务接口来实现上传功能, 所以需要给组件的action
赋值, 如下:
<Upload
+ fileList={fileList}
+ onPreview={this.handlePreview}
+ onChange={this.handleChange}
+ onRemove={this.handleRemove}
+ name="file"
+ listType="picture-card"
+ className={styles.avatarUploader}
+ action={sdk_upload_api || action}
+ withCredentials={withCredentials}
+ headers={{
+ 'x-requested-with': localStorage.getItem('user') || '',
+ 'authorization': localStorage.getItem('token') || '',
+ ...headers
+ }}
+ beforeUpload={this.handleBeforeUpload}
+>
+ {fileList.length >= maxLen ? null : uploadButton}
+</Upload>
+
如果需要集成第三方oss, 如七牛云, 阿里oss等, 我们需要将Upload
组件的action
属性设置为空字符串, 其次删除onChange
属性, 上传操作统一在beforeUpload
中进行. 案例如下:
<Upload
+ fileList={fileList}
+ action=""
+ onPreview={this.handlePreview}
+ onRemove={this.onRemove}
+ name="file"
+ listType="picture-card"
+ className={styles.avatarUploader}
+ headers={{...headers}}
+ beforeUpload={this.handleBeforeUpload}
+>
+ {fileList.length >= maxLen ? null : uploadButton}
+</Upload>
+
自定义上传的核心逻辑放在了beforeUpload
上. 我们具体看看beforeUpload
这个方法如何实现.
handleBeforeUpload = (file:RcFile) => {
+ // 1. 限制图片类型
+ const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/jpg' || file.type === 'image/gif';
+ if (!isJpgOrPng) {
+ message.error('只能上传格式为jpeg/png/gif的图片');
+ }
+ // 限制上传文件大小
+ const isLt3M = file.size / 1024 / 1024 < 3;
+ if (!isLt3M) {
+ message.error('图片必须小于3MB!');
+ }
+ if(isJpgOrPng && isLt3M) {
+ // 3. 正常上传逻辑
+ const fileName = file.name
+ // 3.1 调用oss接口, 将图片上传oss
+ // 3.2 将接口返回的url信息, 组装成fileList数据结构, 并更新state
+ const fileList = [{ uid, name: fileName, status: 'done', url }];
+ this.setState({
+ curImgUrl: url,
+ fileList,
+ })
+ // 3.3 将数据传给上层保存
+ this.props.onChange && this.props.onChange(fileList)
+ }
+ return isJpgOrPng && isLt3M;
+ }
+
+ ← + + 支持https + + 获取Form组件的值数据 + + → +
src
+├─ assets
+│ ├─ header.png
+│ ├─ form.png
+│ ├─ footer.png
+│ ├─ icon.png
+│ ├─ picture.png
+├─ components
+│ ├─ BackTop
+│ │ └─ index.js
+│ ├─ BasicShop
+│ │ ├─ BasicComponents
+│ │ │ ├─ Card
+│ │ │ │ ├─ index.tsx
+│ │ │ │ ├─ schema.ts
+│ │ │ │ └─ template.ts
+│ │ │ ├─ Carousel
+│ │ │ │ ├─ index.less
+│ │ │ │ ├─ index.tsx
+│ │ │ │ ├─ schema.ts
+│ │ │ │ └─ template.ts
+│ │ │ ├─ Footer
+│ │ │ │ ├─ index.tsx
+│ │ │ │ ├─ schema.ts
+│ │ │ │ └─ template.ts
+│ │ │ ├─ Form
+│ │ │ │ ├─ BaseForm.tsx
+│ │ │ │ ├─ BasePopoverForm.tsx
+│ │ │ │ ├─ baseForm.less
+│ │ │ │ ├─ index.less
+│ │ │ │ ├─ index.tsx
+│ │ │ │ ├─ schema.ts
+│ │ │ │ └─ template.ts
+│ │ │ ├─ Header
+│ │ │ │ ├─ index.less
+│ │ │ │ ├─ index.tsx
+│ │ │ │ ├─ schema.ts
+│ │ │ │ └─ template.ts
+│ │ │ ├─ Icon
+│ │ │ │ ├─ icon.ts
+│ │ │ │ ├─ index.tsx
+│ │ │ │ ├─ schema.ts
+│ │ │ │ └─ template.ts
+│ │ │ ├─ Image
+│ │ │ │ ├─ index.tsx
+│ │ │ │ ├─ schema.ts
+│ │ │ │ └─ template.ts
+│ │ │ ├─ LongText
+│ │ │ │ ├─ index.tsx
+│ │ │ │ ├─ schema.ts
+│ │ │ │ └─ template.ts
+│ │ │ ├─ Nav
+│ │ │ │ ├─ index.less
+│ │ │ │ ├─ index.tsx
+│ │ │ │ ├─ schema.ts
+│ │ │ │ └─ template.ts
+│ │ │ ├─ Notice
+│ │ │ │ ├─ index.tsx
+│ │ │ │ ├─ schema.ts
+│ │ │ │ └─ template.ts
+│ │ │ ├─ Qrcode
+│ │ │ │ ├─ index.tsx
+│ │ │ │ ├─ schema.ts
+│ │ │ │ └─ template.ts
+│ │ │ ├─ RichText
+│ │ │ │ ├─ index.less
+│ │ │ │ ├─ index.tsx
+│ │ │ │ ├─ schema.ts
+│ │ │ │ └─ template.ts
+│ │ │ ├─ Text
+│ │ │ │ ├─ index.tsx
+│ │ │ │ ├─ schema.ts
+│ │ │ │ └─ template.ts
+│ │ │ ├─ WhiteTpl
+│ │ │ │ ├─ index.less
+│ │ │ │ ├─ index.tsx
+│ │ │ │ ├─ schema.ts
+│ │ │ │ └─ template.ts
+│ │ │ ├─ XButton
+│ │ │ │ ├─ Modal.tsx
+│ │ │ │ ├─ index.less
+│ │ │ │ ├─ index.tsx
+│ │ │ │ ├─ schema.ts
+│ │ │ │ └─ template.ts
+│ │ │ ├─ schema.ts
+│ │ │ └─ template.ts
+│ │ ├─ MediaComponents
+│ │ │ ├─ Audio
+│ │ │ │ ├─ index.less
+│ │ │ │ ├─ index.tsx
+│ │ │ │ ├─ schema.ts
+│ │ │ │ └─ template.ts
+│ │ │ ├─ Calendar
+│ │ │ │ ├─ index.less
+│ │ │ │ ├─ index.tsx
+│ │ │ │ ├─ schema.ts
+│ │ │ │ └─ template.ts
+│ │ │ ├─ Map
+│ │ │ │ ├─ index.less
+│ │ │ │ ├─ index.tsx
+│ │ │ │ ├─ schema.ts
+│ │ │ │ └─ template.ts
+│ │ │ ├─ Video
+│ │ │ │ ├─ index.css
+│ │ │ │ ├─ index.tsx
+│ │ │ │ ├─ schema.ts
+│ │ │ │ └─ template.ts
+│ │ │ ├─ schema.ts
+│ │ │ └─ template.ts
+│ │ ├─ ShopComponents
+│ │ │ ├─ CardLabel
+│ │ │ │ ├─ index.less
+│ │ │ │ ├─ index.tsx
+│ │ │ │ ├─ schema.ts
+│ │ │ │ └─ template.ts
+│ │ │ ├─ Coupons
+│ │ │ │ ├─ index.less
+│ │ │ │ ├─ index.tsx
+│ │ │ │ ├─ schema.ts
+│ │ │ │ └─ template.ts
+│ │ │ ├─ List
+│ │ │ │ ├─ index.less
+│ │ │ │ ├─ index.tsx
+│ │ │ │ ├─ schema.ts
+│ │ │ │ └─ template.ts
+│ │ │ ├─ Tab
+│ │ │ │ ├─ index.less
+│ │ │ │ ├─ index.tsx
+│ │ │ │ ├─ schema.ts
+│ │ │ │ └─ template.ts
+│ │ │ ├─ ZhuanLan
+│ │ │ │ ├─ index.less
+│ │ │ │ ├─ index.tsx
+│ │ │ │ ├─ schema.ts
+│ │ │ │ └─ template.ts
+│ │ │ ├─ schema.ts
+│ │ │ └─ template.ts
+│ │ ├─ VisualComponents
+│ │ │ ├─ Area
+│ │ │ │ ├─ index.less
+│ │ │ │ ├─ index.tsx
+│ │ │ │ ├─ schema.ts
+│ │ │ │ └─ template.ts
+│ │ │ ├─ Chart
+│ │ │ │ ├─ index.less
+│ │ │ │ ├─ index.tsx
+│ │ │ │ ├─ schema.ts
+│ │ │ │ └─ template.ts
+│ │ │ ├─ Funnel
+│ │ │ │ ├─ index.less
+│ │ │ │ ├─ index.tsx
+│ │ │ │ ├─ schema.ts
+│ │ │ │ └─ template.ts
+│ │ │ ├─ Line
+│ │ │ │ ├─ index.less
+│ │ │ │ ├─ index.tsx
+│ │ │ │ ├─ schema.ts
+│ │ │ │ └─ template.ts
+│ │ │ ├─ Pie
+│ │ │ │ ├─ index.less
+│ │ │ │ ├─ index.tsx
+│ │ │ │ ├─ schema.ts
+│ │ │ │ └─ template.ts
+│ │ │ ├─ Radar
+│ │ │ │ ├─ index.less
+│ │ │ │ ├─ index.tsx
+│ │ │ │ ├─ schema.ts
+│ │ │ │ └─ template.ts
+│ │ │ ├─ WordCloud
+│ │ │ │ ├─ index.less
+│ │ │ │ ├─ index.tsx
+│ │ │ │ ├─ schema.ts
+│ │ │ │ └─ template.ts
+│ │ │ ├─ XProgress
+│ │ │ │ ├─ index.less
+│ │ │ │ ├─ index.tsx
+│ │ │ │ ├─ schema.ts
+│ │ │ │ └─ template.ts
+│ │ │ ├─ schema.ts
+│ │ │ └─ template.ts
+│ │ ├─ common.ts
+│ │ └─ schema.ts
+│ ├─ Calibration
+│ │ ├─ index.less
+│ │ └─ index.tsx
+│ ├─ ErrorBundaries
+│ │ └─ index.tsx
+│ ├─ LoadingCp
+│ │ └─ index.tsx
+│ ├─ ModalTpl
+│ │ ├─ cate.js
+│ │ ├─ index.js
+│ │ └─ index.less
+│ └─ Zan
+│ ├─ index.less
+│ └─ index.tsx
+├─ core
+│ ├─ FormComponents
+│ │ ├─ CardPicker
+│ │ │ ├─ index.less
+│ │ │ └─ index.tsx
+│ │ ├─ Color
+│ │ │ ├─ index.less
+│ │ │ └─ index.tsx
+│ │ ├─ DataList
+│ │ │ ├─ editorModal.tsx
+│ │ │ ├─ index.less
+│ │ │ └─ index.tsx
+│ │ ├─ FormItems
+│ │ │ ├─ EditorModal.tsx
+│ │ │ ├─ FormItems.tsx
+│ │ │ ├─ formItems.less
+│ │ │ └─ index.tsx
+│ │ ├─ InteractionData
+│ │ │ ├─ index.less
+│ │ │ └─ index.tsx
+│ │ ├─ MutiText
+│ │ │ ├─ index.less
+│ │ │ └─ index.tsx
+│ │ ├─ Pos
+│ │ │ ├─ index.less
+│ │ │ └─ index.tsx
+│ │ ├─ Table
+│ │ │ ├─ index.less
+│ │ │ └─ index.tsx
+│ │ ├─ Upload
+│ │ │ ├─ index.less
+│ │ │ └─ index.tsx
+│ │ ├─ XEditor
+│ │ │ ├─ index.less
+│ │ │ └─ index.tsx
+│ │ └─ types.ts
+│ ├─ DynamicEngine.tsx
+│ ├─ FormRender.tsx
+│ ├─ ViewRender.tsx
+│ └─ viewRender.less
+├─ layouts
+│ ├─ __tests__
+│ │ └─ index.test.js
+│ ├─ index.less
+│ └─ index.tsx
+├─ pages
+│ ├─ __tests__
+│ │ └─ index.test.js
+│ ├─ editor
+│ │ ├─ components
+│ │ │ ├─ AvatorGroup
+│ │ │ │ └─ index.tsx
+│ │ │ ├─ CanvasControl
+│ │ │ │ ├─ index.less
+│ │ │ │ └─ index.tsx
+│ │ │ └─ Header
+│ │ │ ├─ index.js
+│ │ │ └─ index.less
+│ │ ├─ models
+│ │ │ └─ editorModal.js
+│ │ ├─ services
+│ │ │ └─ editorService.js
+│ │ ├─ Container.js
+│ │ ├─ SourceBox.tsx
+│ │ ├─ TargetBox.js
+│ │ ├─ index.js
+│ │ ├─ index.less
+│ │ └─ preview.tsx
+│ ├─ help
+│ │ ├─ index.less
+│ │ └─ index.tsx
+│ ├─ home
+│ │ ├─ index.less
+│ │ └─ index.tsx
+│ ├─ ide
+│ │ ├─ _draft.tsx
+│ │ ├─ index.less
+│ │ └─ index.tsx
+│ ├─ login
+│ │ ├─ index.less
+│ │ └─ index.tsx
+│ ├─ document.ejs
+│ └─ mobileTip.js
+├─ utils
+│ ├─ req.ts
+│ └─ tool.ts
+├─ app.tsx
+└─ global.css
+
+
目前Dooring已支持下载源码功能, 我们可以使用编辑器页面头部的下载按钮来实现下载用户搭建的H5源码. +
源码下载之后是完整的React项目源代码, 开发人员可以直接根据自己的业务需求来二次编写代码来满足不同的业务需求.
在拿到源码之后, 我们需要进入项目, 使用npm或者yarn安装项目依赖, 如下:
npm install
+// 或者
+yarn
+
之后我们就可以本地运行项目了:
npm start
+// 或者
+yarn start
+
因为源码工程采用umi3.0
搭建, 所以代码配置可以参考umi3.0
规范, 比如路由配置, history
模式, 打包路径等, 二次开发完成之后, 我们可以执行:
npm run build
+// 或者
+yarn build
+
将项目打包成html, 以便部署到任何服务器中.
+ ← + + 保存json + + 网页预览 + + → +
撤销重做我们主要使用了redux-undo这个库,配合Dva使用,具体使用方法参考如下操作:
import { createLogger } from 'redux-logger';
+import { message } from 'antd';
+import undoable, { StateWithHistory } from 'redux-undo';
+import { Reducer, AnyAction } from 'redux';
+
+export const dva = {
+ config: {
+ onAction: createLogger(),
+ onError(e: Error) {
+ message.error(e.message, 3);
+ },
+ onReducer: (reducer: Reducer<any, AnyAction>) => {
+ let undoReducer = undoable(reducer);
+ return function(state: StateWithHistory<any>, action: AnyAction) {
+ let newState = undoReducer(state, action);
+ let router = newState.present.router ? newState.present.router : newState.present.routing;
+ return { ...newState, router: router };
+ };
+ },
+ },
+};
+
以上我们就实现了全局配置redux-undo,在撤销重做按钮中我们就可以触发对应的方法来实现撤销重做的功能,其次我们还使用了redux-logger来实现redux的日志输出。
我们配置好H5页面之后,如果希望其他人观看,我们可以保存页面并发送链接。但是如果有多人协作的需求,比如一个H5页面可能由多个人完成,这个时候该怎么实现呢?基于已有的方案,我们可以采用socket实现多人协同编辑,但是成本比较大,所有这里我们提供了保存json的功能。
我们可以将配置好的页面导出为json,发送给另一个人,这样另一个人通过导入该json文件可以实时看到当前的页面,这里还是依靠我们的页面渲染引擎viewEngine。实现思路也很简单,可以在github[https://github.com/MrXujiang/h5-Dooring (opens new window)]上参考体验。
截图功能这里我们主要使用了dom-to-image这个库,来将html转化为图片,并进行分享。
我们目前开放了模板库功能,一方面我们会定期配置行业模板,另一个方面Dooring还支持用户自己配置模板,可以一键保存到云端供用户使用。我们也可以将模板变成自己的页面共享给其他人。实现方式本质上是保存用户的配置信息,上传到服务器中做存储,在后台提供了管理模板的模块,可以修改,删除模板。如下图所示:
+ ← + + 动态加载 + + 保存json + + → +
H5-Dooring 是一款功能强大,高可扩展的 H5 可视化页面配置解决方案,致力于提供一套简单方便、专业可靠、无限可能的 H5 落地页最佳实践。
🎉 可扩展, Dooring 实现了较为完整的业务闭环,并使其模块化,编辑器内部功能接口也全部可以对接不同服务端语言,实现了标准化接口。此外还支持自定义组件,二次开发,设计模板等能力,以满足功能和跨领域的分层需求。
📦 开箱即用, Dooring 内置了表单渲染器、页面渲染器、动态加载内核等,仅需一套源码即可上手开发。并且还提供针对 React 的定制插件,内涵丰富的功能,可满足日常 80%的页面制作需求。
🚀 大量自研, 包含整个编辑器架构、组件设计、文档、请求库封装,后台管理系统等,满足日常项目的周边需求。
🚄 与时俱进, 在满足需求的同时,我们也不会停止对新技术的探索。比如更多营销组件、业务功能,后台管理可视化,PC 页面编辑器,数据大屏定制等等。
目前github已超过 3000+star,上线 2 个月累计 500+用户使用,解决完善了 100+问题,后续会持续迭代,更新,自研优秀,先进的 lowcode/nocode 解决方案。
+ doring如何工作 + + → +
注:灰色部分还未实现,正在更新中...
首先得有 node,并确保 node 版本是 10.13
或以上,(mac/win 下推荐使用 n 来管理 node 版本)
$ node-v
+v10.13.0
+
注:推荐使用 yarn 管理 npm 依赖
h5_plus(编辑器项目) | admin(管理后台) | Server(服务端项目) |
---|
本地拿到源码工程之后先安装对应依赖,在对应工程目录里执行 yarn 命令,等待依赖安装完成。
1.首先本地启动 server,在 src 目录的 index.js 中修改跨域白名单,改为本地的 ip+端口,如http://192.167.0.3:8000
2.其次本地启动 h5_plus,启动完毕在浏览器打开对应的启动地址即可查看,如下:
+ ← + + doring如何工作 + + 目录结构 + + → +