基于React/Vue搭建一个通用的表单管理配置平台(下)

发表于:2023-1-20 09:39

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:徐小夕    来源:趣谈前端

分享:
  接下来我们一步步实现以上两个核心模块。
  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}
                actinotallow="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} notallow={onDel}><MinusCircleOutlined /></span>
        <span className={styles.actionItem} notallow={onAdd}><PlusCircleOutlined /></span>
        <span className={styles.actionItem} notallow={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 
          notallow={onDel.bind(this, index)}
          notallow={onAdd.bind(this, index)}
          notallow={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}
                    notallow={handleDelete} 
                    notallow={handleAdd}
                    notallow={handleEdit}
                    curIndex={curEditRowIdx}
                  />
                })
              }
           </Form>
  }
  至此,基本功能模块已经开发完成,我们只需要将这些物料和组件导入到编辑页面,基于业务来操作和请求即可。由于实现该案例还是有一定复杂度的,笔者没有将所有组件都一一写出来,希望为大家提供一个思考空间,后续笔者将会把该平台整合到笔者的开源CMS系统中,供大家学习使用。
  本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号