• 前端低代码平台H5-Dooring使用文档


    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
    
    • 1
    • 2

    注:推荐使用 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>
      );
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    我们只需要按照上面的方式编写组件即可,props是DSL定义的数据层,用来控制组件的shape,也就是组件的表现。我们看看header对应的template。

    3.2 template设计

    const template = {
      type: 'Header',
      h: 28,
      displayName: '页头组件'
    };
    export default template;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    以上就是我们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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84

    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
      },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    由以上代码可知,我们可以在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
    
    • 1

    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">
    
    • 1

    目的是强制将页面中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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    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';
    
    • 1

    其次我们修改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)
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    其他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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    如果需要集成第三方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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    自定义上传的核心逻辑放在了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;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    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);
        }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    数据收集提交的核心代码在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

  • 相关阅读:
    05-树8 File Transfer
    2023-10-19 指针与指针的指针,我就不信你脑壳不疼
    Springboot:静态资源映射方式
    leetcode 102.二叉树的层序遍历
    LeetCode220802_67、三数之和
    不太会讲爱,其实已经偷偷幸福很久啦----我们的故事
    一款基于 GitHub 的 Web 笔记应用
    redis以后台的方式启动
    【Vuex+ElementUI】Vuex中取值存值以及异步加载的使用
    RK3588平台开发系列讲解(显示篇)MIPI DSI协议介绍之分层
  • 原文地址:https://blog.csdn.net/u012960155/article/details/125595850