基於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數據可視化等前端知識和實戰,歡迎在公號《趣談前端》加入咱們的技術羣一塊兒學習討論,共同探索前端的邊界。

更多推薦

相關文章
相關標籤/搜索