前言
熟悉我的朋友可能會知道,我一向是不寫熱點的。為什么不寫呢?是因為我不關(guān)注熱點嗎?其實也不是。有些事件我還是很關(guān)注的,也確實有不少想法和觀點。但我一直奉行一個原則,就是:要做有生命力的內(nèi)容。
這篇文章是一篇應(yīng)用性極強的文章,我們通過一個實際的應(yīng)用場景,去解決某一類的問題,提供一種或者幾種解決方案,來探索技術(shù)的魅力。接下來筆者主要分析表單定制平臺的實現(xiàn)思路和技術(shù)方案,來實現(xiàn)一個類似于金數(shù)據(jù)或者問卷星一樣的表單配置平臺,大家也可以基于此方案,擴展出功能更加強大的可視化平臺。
正文
為什么要做一個這樣的平臺呢?一方面是因為筆者多年來一直服務(wù)于B端產(chǎn)品,對于動態(tài)表單以及配置化表單有一定的項目積累,并且深知配置化表單的價值所在。舉一個很傳統(tǒng)的B端表單配置化的例子:傳統(tǒng)2B企業(yè)在提供saas服務(wù)時,為了滿足不同企業(yè)的定制化需求,往往會給企業(yè)客戶提供定制化或者自由配置的功能,如下圖:

對于saas系統(tǒng)而言,軟件即服務(wù),在提供基礎(chǔ)服務(wù)的同時,同樣要滿足用戶個性化需求,所以傳統(tǒng)的saas軟件提供商往往會提供給客戶自由配置的空間,這種自由配置的橋梁就是通過表單,舉一個簡單的例子:

通過這種方法就可以定制不同風(fēng)格的企業(yè)產(chǎn)品,這里只是舉了個比較簡單的例子,往往實際項目中會更加復(fù)雜,可能會有幾十個配置項,當然這種模式是比較傳統(tǒng)的配置化方案,也僅僅是saas軟件提供的很小的一個服務(wù)模塊。目前主流的做法是采用可視化方案,而且國內(nèi)也有非常成熟的方案,但基本的思想是一致的,只不過后者的體驗更好,操作難度更低。
筆者簡單介紹一下saas,方便大家更容易理解其模式:
saas(軟件即服務(wù))是一種云計算產(chǎn)品,為用戶提供對供應(yīng)商云端軟件的訪問。用戶無需在其本地設(shè)備上安裝應(yīng)用。相反,應(yīng)用駐留在遠程云網(wǎng)絡(luò)中,通過 Web 或 API 進行訪問。通過應(yīng)用,用戶可以存儲和分析數(shù)據(jù),并可進行項目協(xié)作。
類似的云計算產(chǎn)品也有很多,比如Paas(平臺即服務(wù)),Iaas(基礎(chǔ)架構(gòu)即服務(wù))等,感興趣的朋友可以學(xué)習(xí)了解一下。
以上介紹更多的是為了讓大家理解筆者設(shè)計這套平臺的基本背景,我們還可以舉個更實際的例子就是金數(shù)據(jù)或者問卷星的表單配置模式,用戶可以在管理后臺定制自己的表單,并生成一個可訪問的鏈接來向目標用戶發(fā)放問卷,填寫信息,收集信息,最后實現(xiàn)數(shù)據(jù)分析的目的。
本文介紹的表單定制平臺,也同樣支持表單管理,表單數(shù)據(jù)分析, 表單數(shù)據(jù)收集, 表單定制等功能, 筆者將采用比較熟悉的技術(shù)棧react以及第三方ui庫antd4.0來開發(fā), 后端采用node + koa來設(shè)計路由接口.
設(shè)計思路

實現(xiàn)效果與分析
1. 表單定制管理列表

管理列表主要用來查看我們配置的表單模板,分析不同表單模板收集的數(shù)據(jù),對表單模板進行編輯刪除等操作.
2. 表單定制頁面


由上圖可知表單定制頁面主要用來編輯自定義表單模板,我們可以添加表單標題,表單字段等,目前提供了幾種自定義表單控件如下:
- 文本框
- 多行文本框
- 下拉框
- 單選框
- 復(fù)選框
- 文件上傳控件
基本涵蓋了我們所需要的所有表單業(yè)務(wù)場景.由上圖可知我們可以在任意位置插入自定義字段,同時可以編輯修改刪除表單字段.如果想象力再大一點,我們可以基于它來實現(xiàn)不僅僅是表單問卷型應(yīng)用,還可以實現(xiàn)答題,發(fā)布內(nèi)容等場景.(后期可支持富文本控件)
3. 草稿管理

草稿箱設(shè)計的目的是方便使用者在配置表單的過程中不確定是否符合需求或者由于某種臨時性舉動而無法繼續(xù)配置,這個時候可以將以配置好的內(nèi)容存入草稿箱,下次繼續(xù)編輯,所以筆者專門設(shè)計了草稿箱管理列表,一旦用戶存在草稿,會在管理頁面通知用戶并顯示草稿的數(shù)量.作為一個追求體驗的技術(shù)人,這一塊的設(shè)計還是相當有必要的.
4. 生成前臺表單訪問鏈接

當我們配置好表單之后,我們點擊保存, 會生成一個前臺訪問地址,實時訪問表單信息,如下圖為點擊鏈接之后的頁面:

我們也可以根據(jù)自己的風(fēng)格,設(shè)計自己的表單錄入頁面, 具體如何實現(xiàn)這樣的過程, 后面我會詳細介紹.
5. 查看用戶已有數(shù)據(jù)錄入


我們可以通過點擊"查看數(shù)據(jù)"來訪問收集到的表單數(shù)據(jù),并通過可視化的工具對數(shù)據(jù)做分析比較,同時我們也可以在數(shù)據(jù)列表中刪除數(shù)據(jù),來控制我們數(shù)據(jù)展示的純凈.
6. 表單數(shù)據(jù)分析



收集到數(shù)據(jù)只有,我們會自動集成幾個可視化組件來分析表單數(shù)據(jù),以上是筆者列出的幾個可視化組件,基于antv G2來封裝.
應(yīng)用場景
以上主要介紹了自定義表單定制平臺的一些功能和交互效果, 我們可以利用該平臺做很多有意思的事情.因為表單的抽象是數(shù)據(jù),我們拿到定制化的表單json數(shù)據(jù)之后,我們可以有不同的展現(xiàn)形式,比如用戶的問卷調(diào)查, 網(wǎng)站平臺的投票, 答題頁面, 發(fā)布動態(tài)等功能,如下圖配置:


以上配置可以實現(xiàn)類似于微信的發(fā)布朋友圈的功能, 然后我們可以通過前端的手段根據(jù)用戶發(fā)表的數(shù)據(jù)渲染成一個朋友圈列表.
如果我們再打開自己的腦洞,我們可以這樣配置,配置一個這樣的表單,表單包括一個文件上傳控件和n個文本輸入控件,如下圖:

將這樣的表單配置到H5管理模塊,我們只需要上傳三張圖,然后填寫好對應(yīng)的配文,然后利用市面上成熟的H5全屏滾動插件,就能輕松的定制各種H5活動頁面了。該方案已被筆者的很多子系統(tǒng)使用,效果還是非常好的。
當然基于該平臺甚至能直接配置小型的宣傳網(wǎng)站,還有更多想象空間,期待大家去挖掘。
代碼實現(xiàn)
要想開發(fā)這樣一個表單定制平臺, 核心在于如何實現(xiàn)表單動態(tài)配置的機制.這里筆者將其劃分為兩部分:基礎(chǔ)表單物料和表單編輯生成器, 如下圖所示拆分圖:

接下來我們一步步實現(xiàn)以上兩個核心模塊。
1. 基礎(chǔ)表單物料
基礎(chǔ)表單物料主要是為了用戶選擇自定義表單控件使用,我們常用的表單動態(tài)渲染有map循環(huán)+條件判斷和單層map+對象法,前者如果要渲染一個動態(tài)表單,可能實現(xiàn)如下:
{
list.map((item, i) => {
return <React.Fragment key={i}>
{
item.type === 'input' && <Input />
}
{
item.type === 'radio' && <Radio />
}
// ...
</React.Fragment>
})
}
但是這樣做有個明顯的缺點就是會產(chǎn)生很多沒必要的判斷,如果對于復(fù)雜表單,性能往往很低,所以筆者采用后者來實現(xiàn),復(fù)雜度可以降到O(n).我們先來做配置模版:
// 基礎(chǔ)模版數(shù)據(jù)
const tpl = [
{
label: '文本框',
placeholder: '請輸入內(nèi)容',
type: 'text',
value: '',
index: uuid(5)
},
{
label: '單選框',
type: 'radio',
option: [{label: '男', value: 0}, {label: '女', value: 1}],
index: uuid(5)
},
{
label: '復(fù)選框',
type: 'checkbox',
option: [{label: '男', value: 0}, {label: '女', value: 1}],
index: uuid(5)
},
{
label: '多行文本',
placeholder: '請輸入內(nèi)容',
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
}
基礎(chǔ)物料在下圖所示中使用:

當我們要添加一個表單項時,我們就可以在左邊預(yù)覽操作區(qū)看到添加的項,并可以基于表單編輯生成器來編輯表單字段。
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>
}
第二部分主要用來渲染操作區(qū)模版,基于BaseFormEl包裝不同類型的表單組件, 這里舉一個比較復(fù)雜的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>
}
至此,基本功能模塊已經(jīng)開發(fā)完成,我們只需要將這些物料和組件導(dǎo)入到編輯頁面,基于業(yè)務(wù)來操作和請求即可。由于實現(xiàn)該案例還是有一定復(fù)雜度的,筆者沒有將所有組件都一一寫出來,希望為大家提供一個思考空間,后續(xù)筆者將會把該平臺整合到筆者的開源CMS系統(tǒng)中,供大家學(xué)習(xí)使用。