熟悉个人朋友可能会知道,我一贯是不写热点的。为何不写呢?是由于我不关注热点吗?其实也不是。有些事件我仍是很关注的,也确实有很多想法和观点。 但我一直奉行一个原则,就是:要作有生命力的内容。javascript
这篇文章是一篇应用性极强的文章,咱们经过一个实际的应用场景,去解决某一类的问题,提供一种或者几种解决方案,来探索技术的魅力。接下来笔者主要分析表单定制平台的实现思路和技术方案,来实现一个相似于金数据或者问卷星同样的表单配置平台,你们也能够基于此方案,扩展出功能更增强大的可视化平台。css
为何要作一个这样的平台呢?一方面是由于笔者多年来一直服务于B端产品,对于动态表单以及配置化表单有必定的项目积累,而且深知配置化表单的价值所在。举一个很传统的B端表单配置化的例子:传统2B企业在提供saas服务时,为了知足不一样企业的定制化需求,每每会给企业客户提供定制化或者自由配置的功能,以下图: 前端
笔者简单介绍一下saas,方便你们更容易理解其模式:vue
saas(软件即服务)是一种云计算产品,为用户提供对供应商云端软件的访问。用户无需在其本地设备上安装应用。相反,应用驻留在远程云网络中,经过 Web 或 API 进行访问。经过应用,用户能够存储和分析数据,并可进行项目协做。java
相似的云计算产品也有不少,好比Paas(平台即服务),Iaas(基础架构即服务)等,感兴趣的朋友能够学习了解一下。node
以上介绍更多的是为了让你们理解笔者设计这套平台的基本背景,咱们还能够举个更实际的例子就是金数据或者问卷星的表单配置模式,用户能够在管理后台定制本身的表单,并生成一个可访问的连接来向目标用户发放问卷,填写信息,收集信息,最后实现数据分析的目的。react
本文介绍的表单定制平台,也一样支持表单管理,表单数据分析, 表单数据收集, 表单定制等功能, 笔者将采用比较熟悉的技术栈react以及第三方ui库antd4.0来开发, 后端采用node + koa来设计路由接口.webpack
基本涵盖了咱们所须要的全部表单业务场景.由上图可知咱们能够在任意位置插入自定义字段,同时能够编辑修改删除表单字段.若是想象力再大一点,咱们能够基于它来实现不只仅是表单问卷型应用,还能够实现答题,发布内容等场景.(后期可支持富文本控件)css3
以上主要介绍了自定义表单定制平台的一些功能和交互效果, 咱们能够利用该平台作不少有意思的事情.由于表单的抽象是数据,咱们拿到定制化的表单json数据以后,咱们能够有不一样的展示形式,好比用户的问卷调查, 网站平台的投票, 答题页面, 发布动态等功能,以下图配置: 程序员
若是咱们再打开本身的脑洞,咱们能够这样配置,配置一个这样的表单,表单包括一个文件上传控件和n个文本输入控件,以下图:
固然基于该平台甚至能直接配置小型的宣传网站,还有更多想象空间,期待你们去挖掘。
要想开发这样一个表单定制平台, 核心在于如何实现表单动态配置的机制.这里笔者将其划分为两部分:基础表单物料和表单编辑生成器, 以下图所示拆分图:
基础表单物料主要是为了用户选择自定义表单控件使用,咱们经常使用的表单动态渲染有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部分, 第一部分是用来生成表单项的容器组件,封装了添加,删除,编辑操做功能,代码以下:
// 表单容器组件
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游戏, webpack,node,gulp,css3,javascript,nodeJS,canvas数据可视化等前端知识和实战,欢迎在公号《趣谈前端》加入咱们的技术群一块儿学习讨论,共同探索前端的边界。