基于react搭建一个通用的表单管理配置平台(vue同)

前言

熟悉个人朋友可能会知道,我一贯是不写热点的。为何不写呢?是由于我不关注热点吗?其实也不是。有些事件我仍是很关注的,也确实有很多想法和观点。 但我一直奉行一个原则,就是:要作有生命力的内容javascript

这篇文章是一篇应用性极强的文章,咱们经过一个实际的应用场景,去解决某一类的问题,提供一种或者几种解决方案,来探索技术的魅力。接下来笔者主要分析表单定制平台的实现思路和技术方案,来实现一个相似于金数据或者问卷星同样的表单配置平台,你们也能够基于此方案,扩展出功能更增强大的可视化平台css

正文

为何要作一个这样的平台呢?一方面是由于笔者多年来一直服务于B端产品,对于动态表单以及配置化表单有必定的项目积累,而且深知配置化表单的价值所在。举一个很传统的B端表单配置化的例子:传统2B企业在提供saas服务时,为了知足不一样企业的定制化需求,每每会给企业客户提供定制化或者自由配置的功能,以下图: 前端

对于 saas系统而言,软件即服务,在提供基础服务的同时,一样要知足用户个性化需求,因此传统的 saas软件提供商每每会提供给客户自由配置的空间,这种自由配置的桥梁就是经过表单,举一个简单的例子:
经过这种方法就能够定制不一样风格的企业产品,这里只是举了个比较简单的例子,每每实际项目中会更加复杂,可能会有几十个配置项,固然这种模式是比较传统的配置化方案,也仅仅是 saas软件提供的很小的一个服务模块。目前主流的作法是采用可视化方案,并且国内也有很是成熟的方案,但基本的思想是一致的,只不事后者的体验更好,操做难度更低。

笔者简单介绍一下saas,方便你们更容易理解其模式:vue

saas(软件即服务)是一种云计算产品,为用户提供对供应商云端软件的访问。用户无需在其本地设备上安装应用。相反,应用驻留在远程云网络中,经过 Web 或 API 进行访问。经过应用,用户能够存储和分析数据,并可进行项目协做。java

相似的云计算产品也有不少,好比Paas(平台即服务),Iaas(基础架构即服务)等,感兴趣的朋友能够学习了解一下。node

以上介绍更多的是为了让你们理解笔者设计这套平台的基本背景,咱们还能够举个更实际的例子就是金数据或者问卷星的表单配置模式,用户能够在管理后台定制本身的表单,并生成一个可访问的连接来向目标用户发放问卷,填写信息,收集信息,最后实现数据分析的目的。react

本文介绍的表单定制平台,也一样支持表单管理,表单数据分析, 表单数据收集, 表单定制等功能, 笔者将采用比较熟悉的技术栈react以及第三方ui库antd4.0来开发, 后端采用node + koa来设计路由接口.webpack

设计思路

实现效果与分析

1. 表单定制管理列表

管理列表主要用来查看咱们配置的表单模板,分析不一样表单模板收集的数据,对表单模板进行编辑删除等操做.

2. 表单定制页面

由上图可知表单定制页面主要用来编辑自定义表单模板,咱们能够添加表单标题,表单字段等,目前提供了几种自定义表单控件以下:

  • 文本框
  • 多行文本框
  • 下拉框
  • 单选框
  • 复选框
  • 文件上传控件

基本涵盖了咱们所须要的全部表单业务场景.由上图可知咱们能够在任意位置插入自定义字段,同时能够编辑修改删除表单字段.若是想象力再大一点,咱们能够基于它来实现不只仅是表单问卷型应用,还能够实现答题,发布内容等场景.(后期可支持富文本控件)css3

3. 草稿管理

草稿箱设计的目的是方便使用者在配置表单的过程当中不肯定是否符合需求或者因为某种临时性举动而没法继续配置,这个时候能够将以配置好的内容存入草稿箱,下次继续编辑,因此笔者专门设计了草稿箱管理列表,一旦用户存在草稿,会在管理页面通知用户并显示草稿的数量.做为一个追求体验的技术人,这一块的设计仍是至关有必要的.

4. 生成前台表单访问连接

当咱们配置好表单以后,咱们点击保存, 会生成一个前台访问地址,实时访问表单信息,以下图为点击连接以后的页面:
咱们也能够根据本身的风格,设计本身的表单录入页面, 具体如何实现这样的过程, 后面我会详细介绍.

5. 查看用户已有数据录入

咱们能够经过点击"查看数据"来访问收集到的表单数据,并经过可视化的工具对数据作分析比较,同时咱们也能够在数据列表中删除数据,来控制咱们数据展现的纯净.

6. 表单数据分析

收集到数据只有,咱们会自动集成几个可视化组件来分析表单数据,以上是笔者列出的几个可视化组件,基于 antv G2来封装.

应用场景

以上主要介绍了自定义表单定制平台的一些功能和交互效果, 咱们能够利用该平台作不少有意思的事情.由于表单的抽象是数据,咱们拿到定制化的表单json数据以后,咱们能够有不一样的展示形式,好比用户的问卷调查, 网站平台的投票, 答题页面, 发布动态等功能,以下图配置: 程序员

以上配置能够实现相似于微信的发布朋友圈的功能, 而后咱们能够经过前端的手段根据用户发表的数据渲染成一个朋友圈列表.

若是咱们再打开本身的脑洞,咱们能够这样配置,配置一个这样的表单,表单包括一个文件上传控件和n个文本输入控件,以下图:

将这样的表单配置到H5管理模块, 咱们只须要上传三张图,而后填写好对应的配文,而后利用市面上成熟的H5全屏滚动插件,就能轻松的定制各类H5活动页面了。该方案已被笔者的不少子系统使用,效果仍是很是好的。

固然基于该平台甚至能直接配置小型的宣传网站,还有更多想象空间,期待你们去挖掘。

代码实现

要想开发这样一个表单定制平台, 核心在于如何实现表单动态配置的机制.这里笔者将其划分为两部分:基础表单物料表单编辑生成器, 以下图所示拆分图:

接下来咱们一步步实现以上两个核心模块。

1. 基础表单物料

基础表单物料主要是为了用户选择自定义表单控件使用,咱们经常使用的表单动态渲染有map循环+条件判断和单层map+对象法,前者若是要渲染一个动态表单,可能实现以下:

{
    list.map((item, i) => {
        return <React.Fragment key={i}> { item.type === 'input' && <Input /> } { item.type === 'radio' && <Radio /> } // ... </React.Fragment> }) } 复制代码

可是这样作有个明显的缺点就是会产生不少不必的判断,若是对于复杂表单,性能每每很低,因此笔者采用后者来实现,复杂度能够降到O(n).咱们先来作配置模版:

// 基础模版数据
const tpl = [
  {
    label: '文本框',
    placeholder: '请输入内容',
    type: 'text',
    value: '',
    index: uuid(5)
  },
  {
    label: '单选框',
    type: 'radio',
    option: [{label: '男', value: 0}, {label: '女', value: 1}],
    index: uuid(5)
  },
  {
    label: '复选框',
    type: 'checkbox',
    option: [{label: '男', value: 0}, {label: '女', value: 1}],
    index: uuid(5)
  },
  {
    label: '多行文本',
    placeholder: '请输入内容',
    type: 'textarea',
    index: uuid(5)
  },
  {
    label: '选择框',
    placeholder: '请选择',
    type: 'select',
    option: [{label: '中国', value: 0}, {label: '俄罗斯', value: 1}],
    index: uuid(5)
  },
  {
    label: '文件上传',
    type: 'upload',
    index: uuid(5)
  }
]

// 模版渲染组件
const tplMap = {
  text: {
    component: (props) => {
      const { placeholder, label } = props
      return <div className={styles.fieldOption}><span className={styles.fieldLabel}>{label}:</span><Input placeholder={placeholder} /></div>
    }
  },
  textarea: {
    component: (props) => {
      const { placeholder, label } = props
      return <div className={styles.fieldOption}><span className={styles.fieldLabel}>{label}:</span><TextArea placeholder={placeholder} /></div>
    }
  },
  radio: {
    component: (props) => {
      const { option, label } = props
      return <div className={styles.fieldOption}>
              <span className={styles.fieldLabel}>{label}:</span>
              <Radio.Group>
                {
                  option && option.map((item, i) => {
                    return <Radio style={radioStyle} value={item.value} key={item.label}>
                      { item.label }
                    </Radio>
                  })
                }
            </Radio.Group>
        </div>
    }
  },
  checkbox: {
    component: (props) => {
      const { option, label } = props
      return <div className={styles.fieldOption}>
              <span className={styles.fieldLabel}>{label}:</span>
              <Checkbox.Group>
                <Row>
                  {
                    option && option.map(item => {
                      return <Col span={16} key={item.label}>
                              <Checkbox value={item.value} style={{ lineHeight: '32px' }}>
                                { item.label }
                              </Checkbox>
                            </Col>
                    })
                  }
                </Row>
            </Checkbox.Group>
        </div>
    }
  },
  select: {
    component: (props) => {
      const { placeholder, option, label } = props
      return <div className={styles.fieldOption}>
              <span className={styles.fieldLabel}>{label}:</span>
              <Select placeholder={placeholder} style={{width: '100%'}}>
                {
                  option && option.map(item => {
                    return <Option value={item.value} key={item.label}>{item.label}</Option>
                  })
                }
            </Select>
        </div>
    }
  },
  upload: {
    component: (props) => {
      return <div className={styles.fieldOption}>
              <span className={styles.fieldLabel}>{props.label}:</span>
              <Upload
              listType="picture-card"
              className="avatar-uploader"
              showUploadList={false}
              action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
            >
              <div>+</div>
            </Upload>
        </div>
    }
  }
}

export {
  tpl,
  tplMap
}
复制代码

基础物料在下图所示中使用:

当咱们要添加一个表单项时,咱们就能够在左边预览操做区看到添加的项,并能够基于表单编辑生成器来编辑表单字段。

2. 表单编辑生成器

表单编辑生成器分为2部分, 第一部分是用来生成表单项的容器组件,封装了添加,删除,编辑操做功能,代码以下:

// 表单容器组件
const BaseFormEl = (props) => {
  const {isEdit, onEdit, onDel, onAdd} = props
  const handleEdit = (v) => {
    onEdit && onEdit(v)
  }
  return <div className={styles.formControl}> <div className={styles.formItem}>{ props.children }</div> <div className={styles.actionBar}> <span className={styles.actionItem} onClick={onDel}><MinusCircleOutlined /></span> <span className={styles.actionItem} onClick={onAdd}><PlusCircleOutlined /></span> <span className={styles.actionItem} onClick={handleEdit}><EditOutlined /></span> </div> </div>
}
复制代码

第二部分主要用来渲染操做区模版,基于BaseFormEl包装不一样类型的表单组件, 这里举一个比较复杂的select来讲明,其余表单控件相似:

const formMap = {
  title: {},
  text: {},
  textarea: {},
  radio: {},
  checkbox: {},
  select: {
    component: (props) => {
      const { onDel, onAdd, onEdit, curIndex, index, type, label, placeholder, required, message, option } = props
      return <BaseFormEl onDel={onDel.bind(this, index)} onAdd={onAdd.bind(this, index)} onEdit={onEdit.bind(this, {index, type, placeholder, label, option, required})} isEdit={curIndex === index} > <Form.Item name={label} label={label} rules={[{ message, required }]}> <Select placeholder={placeholder}> { option && option.map(item => { return <Option value={item.value} key={item.label}>{item.label}</Option> }) } </Select> </Form.Item> </BaseFormEl> }, editAttrs: [ { title: '字段名称', key: 'label' }, { title: '选项', key: 'option' }, { title: '提示文本', key: 'placeholder' }, { title: '是否必填', key: 'required' }, ] }, upload: {} } 复制代码

editAttrs主要用来渲染编辑列表,说明哪些表单项能够编辑,这部分代码比较简单,这里直接用图举例:

最后咱们来渲染表单生成器组件:

export default (props) => {
  const { 
    formData, 
    handleDelete, 
    handleAdd, 
    handleEdit, 
    curEditRowIdx 
  } = props
  return <Form name="customForm"> { formData && formData.map(item => { let CP = formMap[item.type].component return <CP {...item} key={item.index} onDel={handleDelete} onAdd={handleAdd} onEdit={handleEdit} curIndex={curEditRowIdx} /> }) } </Form> } 复制代码

至此,基本功能模块已经开发完成,咱们只须要将这些物料和组件导入到编辑页面,基于业务来操做和请求便可。因为实现该案例仍是有必定复杂度的,笔者没有将全部组件都一一写出来,但愿为你们提供一个思考空间,后续笔者将会把该平台整合到笔者的开源CMS系统中,供你们学习使用。有关nodejs部分的内容,因为笔者后期会陆续整理,若是有其余疑问,能够和笔者多交流。

最后

若是想学习更多H5游戏, webpacknodegulpcss3javascriptnodeJScanvas数据可视化等前端知识和实战,欢迎在公号《趣谈前端》加入咱们的技术群一块儿学习讨论,共同探索前端的边界。

更多推荐

相关文章
相关标签/搜索