H5-Dooring 调研报告

1. 产品简介

1.1 简介

H5-Dooring 是一款低代码(LowCode),高可扩展的 H5 可视化页面配置解决方案,致力于提供一套简单方便、专业可靠、无限可能的 H5 落地页最佳实践。

H5-Dooring 正是为了解决企业内部可视化搭建需求的解决方案, 它更多的是提供一套可视化搭建解决方案, 以源码的方式交付企业, 使得企业能从此方案中受益, 二次开发适合自身业务需求的搭建平台。H5-dooring也提供部署服务, 来快速帮企业做项目部署。目前已完成了常用的功能:

  • 微信/QQ分享(支持配置微信分享文案, 自定义分享图标)
  • https服务支持解决方案
  • oss解决方案(已跑通七牛云上传服务)
  • 国际化方案

1.2 可视化编辑器

可视化编辑器包括的核心功能区有:组件区、画布区、顶部功能区、数据源

1.2.1 组件区

组件主要包括 基础组件, 图表组件, 媒体组件, 商品组件, 功能组件, 当然企业也可以基于自身业务划分不同的分类, 并进行组件的二次开发。我们只需要从左侧拖拽组件到画布, 即可使用该组件。

同时我们还提供了组件定制的能力, 让用户选择自己常用的组件, 这样用户可以更高效的搭建页面。

1.2.2 画布区

画布区可以动态调整画布大小来试试预览不同尺寸的样式, 也可以移动画布, 缩放画布来快捷的操作页面。

1.2.3 顶部功能区

顶部功能区包括的功能有:模版库、保存、下载源码、导出json、导入json、预览、真机预览、撤销/重做、删除、截图、页面设置。

1.2.4 数据源

数据源主要为平台用户提供一种高效的数据接入机制, 不同页面或者统同一页面的不同组件可以共享数据。

dooring

1.3 后台管理简介

Dooring后台管理 主要是为 H5-Dooring 提供数据支撑, 比如增删查改等操作, 同时随着用户需求的不断增加, Dooring后台管理 目前已实现了非常多的功能, 比如说表单数据收集, 表单数据分析, 导出数据, 基本的页面数据监控。

1.3.1 后台主页

后台主页主要是对编辑器页面提供基本的访问量统计, 同时对用户数, 模版数, 页面数进行统计, 企业可以根据自身需求二次开发更多数据统计方案。

dooring

1.3.2 用户管理

用户管理主要是对网站用户进行管理(注册, 修改, 删除, 查看等), 当然只有超级管理员能看到, 目前我们做了简单的权限管理: 超级管理员, 普通用户. 普通用户可以管理自己的页面, 查看页面数据分析等,超级管理员可以使用所有功能, 比如管理用户, 生成注册链接, 模版管理, 页面管理等, 同时可以审核页面, 一键删除其他用户产生的不符合规定的页面。

dooring

1.3.3 页面管理

页面管理主要是对用户搭建的H5页面进行管理, 我们可以查看页面的链接, 页面访问量, 编辑页面标题, 删除页面等,如果这个页面包含表单, 我们还能一键查看表单数据的收集情况,并一键进行数据分析。

dooring

2. 快速开始

2.1 可视化搭建H5表单页面

使用 1.2 可视化编辑器 中介绍的组件来搭建一个H5表单页面,并下载源码工程。

2.2 环境准备

首先得有 node,并确保 node 版本是 10.13 或以上,(mac/win 下推荐使用 n 来管理 node 版本)

$ node-v
v10.13.0

注:推荐使用 yarn 管理 npm 依赖

2.3 源码工程

h5_plus(编辑器项目)admin(管理后台)Server(服务端项目)

本地拿到源码工程之后先安装对应依赖,在对应工程目录里执行 yarn 命令,等待依赖安装完成。

2.4 本地运行

1.首先本地启动 server,在 src 目录的 index.js 中修改跨域白名单,改为本地的 ip+端口,如http://192.167.0.3:8000

2.其次本地启动 h5_plus,启动完毕在浏览器打开对应的启动地址即可查看,如下:

foo

2.5 路径说明

  • /h5_plus H5编辑器项目
  • /iH5 Dooring后台管理系统
  • /doc Dooring文档

3. 组件开发

3.1 组件设计

我们这里拿基本的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。

3.2 template设计

const template = {
  type: 'Header',
  h: 28,
  displayName: '页头组件'
};
export default template;

以上就是我们template的结构,type用来定义组件的类型,方便渲染器动态查找,h代表组件的初始化高度,我们可以自由设置。displayName是组件的中文名,用来在左侧组件面板中展示,方便用户理解,我们可以在template中自定义更多辅助信息,方便使用者更高效的使用我们的编辑器。

3.3 schema设计

开发一个自定义组件需要包含3部分, Component, SchemaTemplate. 接下来我们看一下 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一一对应。

3.4 DSL设计

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一一对应。

4. 组件商店

4.1 组件商店工作流

我们要想实现完整的组件商店工作流,需要满足以下几点:

  • 组件线上编辑(上传)模块
  • 组件审核模块
  • 组件更新/发布模块
  • 组件管理(上架/下架/删除/下载)

有了以上4块的支持,基本的组件商店就可以 work 了。具体流程如下:

img

4.2 上传组件

当“ 生产者 ”编写好组件代码之后,需要对组件自身进行定义。因为可视化平台组件物料很依赖平台的组件开发协议,我们需要按照平台的规范去上传规范的自定义组件,这样平台才能更好的理解应用组件,保持用户认知的一致性。 组件描述信息笔者这里设计了如下字段:

  • 组件名称 (中文)
  • 组件名 (英文,方便存库)
  • 组件分类 (基础,可视化,营销,媒体等)
  • 组件默认大小 (宽高)
  • 组件图标 (方便用户认知,查找)

4.3 组件审核

组件审批主要由网站管理人员来操作,当用户组件提交成功之后,客户端会通过消息信令通知管理员,管理员收到消息后会审核组件。

5. 私有化部署 & 二次开发

5.1 私有化部署

H5-dooring部署

部署流程如下:

  1. 下载4个源码工程, 安装依赖(npm install 或 yarn)
  2. 打包3个前端工程至server的static目录下
  3. server下本地运行 yarn startnpm start 启动服务端进行本地测试
  4. 打包服务端代码, yarn build 生成 dist 目录, 建议使用 pm2nodejs服务的负载均衡, 运行 pm2 start dist/index.js启动生产环境代码

也可以将以上步骤集成到gitlab等CI, CD服务中, 进行自动化打包发布, 或者采用docker进行容器化部署.

5.1.1 安装项目环境

服务器需提前安装node和pm2, 将本项目上传至服务器指定的目录(如/www/activity), 进入项目目录, 执行:

npm install
5.1.2 修改项目域名

进入./src/config/index.js, 修改staticPath变量为当前服务器域名/ip, 如http://xxx.comhttp://xxx.com:8080(如非80端口)

5.1.3 编译项目

执行npm run build编译项目, 生成dist目录

5.1.4 运行项目

在项目根目录执行 pm2 start dist/index.js启动项目

5.2 支持Https

目前H5-Dooring全面支持https部署, 具体方式方案如下。

我们需要在前端工程中的src/pages/document.ejs中的head中添加如下代码:

<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">

目的是强制将页面中HTTP请求转换为HTTPS。

5.2.1 申请SSL证书
5.2.2 生成 server.csr+server.key
5.2.3 通过证书链生成.pem文件
5.2.4 在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()

5.3 服务端数据说明

服务端主要是我们的server工程, 数据主要存放在server/public下, 具体数据指代含义我们接下来会详细介绍.

  • bed 存放图片库中的分类图片, 私有化部署的用户可以直接在此处扩充图片(更好的建议是直接存到第三方图床)
  • h5 用户保存的h5数据文件, 一个页面对应一个json文件
  • h5_tpl 平台保存的模版数据文件夹
    • xxx.json 模版页面文件
    • tpls.json 模版库中的模版列表数据, 可以手动清空
  • h5_vip 会员数据目录
    • form 会员制作的含表单页面的表单收集数据
    • view.json 用户浏览量数据
    • vip.json 会员列表数据
    • vipCard.json 会员订单数据(暂时无用, 可删除)
  • image.json 图片库, 主要用来渲染页面的图片库数据
  • city.json 省市3级联动数据, 为表单组件提供数据支持

5.4 接入第三方 oss

**H5-Dooring **全面支持第三方对象存储服务, 我们以七牛云对象存储为例.

5.4.1 前端上传文件到oss

首先我们需要在第三方对象储存服务中配置对应的服务和域名. 其次安装对应的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)中找到我们.

5.4.2 如何接入任何第三方上传服务

首先我们的上传组件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;
  }

5.5 获取 Form (表单组件) 的值数据

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等).

5.6 API 接口

详见 http://h5.dooring.cn/doc/zh/guide/deployDev/api.html#%E7%94%A8%E6%88%B7%E7%9B%B8%E5%85%B3

Logo

低代码爱好者的网上家园

更多推荐