可視化搭建工具技術探索之表單

做者:橙子前端

前言

說到頁面可視化搭建,想必不少同窗都有所瞭解,業內已有很是多文章介紹,具體能夠查看底部傳送門,本文僅從 如何搭建一個易用、可擴展的通用可視化搭建工具 出發,探索技術思路,以及在實際實踐中思考,歡迎互相探討。vue

關於相關工具,業內開源及商業化產品很是多,可是通用的、知足定製化業務的卻很難找到,緣由有不少:react

  • 用戶不一樣,相同功能的組件運營同窗與研發同窗訴求不一樣,運營但願簡單,研發但願二次開發能力
  • 場景不一樣,配運營活動與配流程表單,使用的組件幾乎徹底不一樣。
  • 設計器要求不一樣,不一樣系統對設計器界面要求不一樣,面板能力也不一樣。
  • 開發者偏好不一樣,有人偏向react開發,有人偏向vue,使用組件庫也不盡相同。
  • ...

因爲以上某些緣由,致使一些開源工具不能很好的在實際業務落地,不少時候就只能本身開發,或基於開源二次改造。git

爲何要嘗試作一個通用的可視化搭建工具呢?github

可視化配置工具做爲一種提效工具,若是隻是爲了知足自身業務就搞一套,從更大範圍看,是提效了仍是減效了?即便一個團隊、一個部門能夠作到通用,整個公司卻不必定,就會遇到常被DISS的「重複造輪子」,解釋起來基本就是有自身定製的需求,別的工具不能知足。所以也很難造成統一的可視化配置組件及規範。web

雖然有不少定製化場景及偏好問題,但從技術層面來看,有不少類似的地方:json

一、須要設計器,可添加、拖拽、配置組件
二、提供渲染能力
三、組件間可通訊
四、表單場景可聯動等markdown

假設設計器能定製,不一樣組件庫實現的可視化組件可在不一樣設計器中運行,經過底層一套schema或DSL規範約束。這樣就能很大程度解決組件共享問題,從而大幅減小重複開發成本。實現一個設計器及定製組件並不難,難的是如何達成這樣的規範,同時支持擴展。組件和設計器只是上層實現而已。app

借用一句毛爺爺的話:異步

道路是曲折的,前途是光明的

下面具體來看本身在嘗試實現過程的一些思考

1、劃分

一、按場景分

如下爲比較典型的業務可視化配置場景:

場景 用戶 特色 用途
運營活動 運營同窗 數量多,定製化強、需快速上線 通常配置運營活動、落地頁、抽獎等
流程表單 流程實施 對錶單能力要求高,表單內外聯動、公式計算等 配合流程設計,實現業務流轉
業務報表 產品、運營等 以查詢表單+可定製列的表格以及圖表配置爲表明 對流程及業務結果展現
個性化頁面 普通用戶 配置應更簡單,交互要求高 個性化訴求。如用戶主頁、定製工做臺等
中後臺頁面 前端研發 須要有代碼擴展能力、專業性強 前端提效。解決繁瑣、重複開發

固然以上也只是可視化配置的幾種典型場景,若是配置化能力足夠強,或許基於此,解決前端大部分開發工做也不是不可能。

二、按用戶專業性分

從設計器開發到最終使用,涉及不一樣角色的用戶:

  1. 設計器開發者:保證設計器的獨立與業務解耦,關注底層能力、設計器通用性、靈活性
  2. 組件開發者:通用組件、業務組件開發。關注組件用處、業務定製性等
  3. 配置人員:添加、拖拽配置、發佈等。關注配置難度、靈活性、組件是否豐富。
  4. 最終用戶:使用最終發佈的頁面。關注使用體驗,打開是否快、功能是否正常等

從配置難度來看,可視化工具一般有如下幾種:

  • NoCode:顧名思義,徹底不須要編碼能力,好比運營活動配置、用戶個性化主頁、流程表單、業務報表等。一般須要基於特定場景定製化組件
  • LowCode:大部分界面和功能可經過可視化方式配置,可是完整功能還須要藉助少許代碼完成。如定製的表單關聯、表單的提交邏輯等。
  • ProCode:需具有專業前端代碼能力,對應傳統研發。特色是交互週期長,研發成本高

能夠看下【可視化搭建工具與頁面】

工具與頁面實現關係

能夠發現,LowCodeNoCodeProCode都能實現最終頁面(藍色)。ProCode能力是最強的,能夠實現所有場景功能,同時還能實現LowCodeNoCode平臺或工具自己

三、按典型交互分

  • 表單交互:涉及表單校驗、聯動、提交、值回顯等
  • 展現頁:較少或無需用戶輸入,以展現爲主,部分個性化配置。如業務報表、用戶個性化主頁、運營活動等。

以上按不一樣方式對可視化配置工具進行了分類,不必定很是準確,但基本都有所覆蓋。從技術出發,結合特色和訴求,如何實現這樣一個通用工具是一個值得探索的問題。

2、問題探索

如下暫且列了部分表單問題、自定義訴求、通用能力三個方面典型問題

  • 表單問題
  1. 表單校驗,表單規則定義探索及如何自定義規則?
  2. 表單聯動,表單內字段如何聯動?表單內值變動或者觸發事件,如何聯動表單外?表單外事件如何聯動表單內組件?
  • 自定義訴求
  1. 自定義組件,如何自定義一個普通組件?如何自定義容器組件?組件基礎配置不知足時從新開發仍是擴展配置?
  2. 自定義設計器,當設計器嵌入業務系統時,設計器應具有怎樣的開放能力以實現低成本、無縫銜接?
  • 通用能力
  1. 國際化,設計器國際化、翻譯預料管理
  2. 自定義樣式
  3. PC端與H5同時配置

以上只是工具形式提供能力時可能碰見的幾個典型問題,固然問題遠遠不止這些,升級到平臺會涉及更多的問題。篇幅有限如下主要探索 表單場景 的典型問題,其餘問題留給後續探索。

一、典型組件應該具有哪些部分

輪播圖 組件爲例,不一樣專業程度用戶但願配置的屬性不一樣。

  • 對於 NoCode 用戶而言,可能只須要配置以下屬性:輪播圖個數、拖動添加圖片、配置圖片跳轉連接、輸入輪播時間間隔、選擇輪播切換動畫等
  • 對於 LowCode 用戶而言,除了以上配置之外,還能夠配置圖片上傳接口、請求方式、上傳請求參數、接口返回轉換腳本等,這樣在更大程度上覆用,同時使用難度也增長了。

雖然最終展現結果相同,可是配置卻不一樣,這種狀況下是否能夠作成一個組件呢?我的以爲是能夠的,好比把更多屬性配置放在高級裏,或者讓組件之間能夠繼承等。

那麼一個組件應該具有哪些部分呢?

從示例能夠發現,首先須要有最終展現部分,其次須要有配置部分,還須要定義配置項。這裏將最終展現的部分稱View,配置部分稱Setting,定義配置稱爲Schema。其關係大體以下:

組件內部關係

SettingView經過Schema關聯起來,Schema實例化後爲json數據可保存到服務端。Setting表單修改SchemaSchema變化影響View變化。

二、表單場景典型問題

1)、表單驗證

表單規則定義探索及如何自定義規則?

從主流組件庫來看,不考慮聯動校驗規則狀況下,輸入框比下拉框、單選、時間等組件的規則要複雜些。後者只須要作選擇,通常增長是否必填規則便可,而前者除了必填,還有對字符作特別校驗。一般 用戶可輸入的組件比提供選項選擇的組件在規則上要複雜些

表單組件通常都至少有一條規則,如 必填,固然也有例外,好比 開關組件(switch),無論是true 仍是 false 必填對其來講都沒有意義

所以可在組件schema上能夠定義required字段表示時候必填,如:

{
  "name": "firstName",
  "required": true,
  "errorMessage": "這是必填項"
}
複製代碼

對於只須要必填規則的組件來講,這樣定義彷佛並無什麼問題。然而不少時候一個組件每每有多個規則同時生效,如:但願該字段必填,能配置對應錯誤信息,同時還要求字符串長度有限制,對應過長或太短都能給相應的錯誤提示。用以上定義就不太好知足了,因而能夠升級一下:

{
  "name": "firstName",
  "rules": [
    { "required": true, "message": "這是必填項" },
    { "min": 3, "message": "最小長度不能小於3" },
    { "max": 10, "message": "最大長度不能超過10" }
  ]
}
複製代碼

這樣看起來清晰了不少,同時支持多條規則組合。這也是主流UI組件庫都在用的表單校驗 async-validatorrules字段應與 async-validator 在使用上保持一致,這樣就能夠利用第三方庫作規則校驗了,

由於表單基本都有一條必填規則,能夠約定rules字段第一個規則爲必填,其他規則根據實際狀況由配置人員動態添加。

注意schema.rules中的每條規則字段類型與async-validator並不是一一對應,緣由是咱們的schema將以json的形式保存到服務端或本地,因此一些特殊字段如自定義校驗函數或正則等,就必須轉成相應字符串了。

  • async-validator 字段規則描述:
{
  "type": "string",
  "validator": (rule, value) => value === 'test',
  "message": "請輸入 test"
}
複製代碼
  • schema.rules中單條規則描述
{
  "type": "string",
  "validator": "(rule, value) => value === 'test'",
  "message": "請輸入 test"
}
複製代碼

所以,設計器底層須要對錶單規則提供解析模塊(Rule)。這個只是實現規則層面,對配置層面的話,讓配置人員寫這些代碼實在有些勉強,而提供可視化的方式選擇或簡單填寫就頗有必要,以下圖:

自定義擴展規則

經常使用規則能夠內置到設計器底層。實際業務中,每每會有自定義的複雜規則,或者異步校驗等,那麼:

如何能配置規則的同時,還能根據不一樣業務場景擴展規則呢?

這裏就要求設計器對錶單規則有擴展能力。一種多是在配置的時候,直接經過腳本實現規則,僅適用於前端開發。第二種是組件開發同窗,提早開發好規則,而後建立設計器時擴展規則,最後在配置規則時選擇便可。這裏討論第二種實現。

  • 實現手機號規則示例
// ./PhoneRule.js
export default class PhoneRule {
  static get type () {
    return 'phone'
  }
  static get name () {
    return '手機號'  // 用於可視化顯示
  }
  constructor (rule = {}) {
    const defaultRule = {
      type: 'pattern',
      pattern: '',
      message: '手機號不正確'
    }
    this.origin = Object.assign({}, defaultRule, rule)
    this.rule = {
      type: 'pattern',
      trigger: 'blur',
      pattern: /^1[3-9]\d{9}$/g,
      message: ''
    }
    this.update(this.origin)
  }

  update (rule) {
    if (rule) {
      this.rule.message = rule.message
      Object.assign(this.origin, rule)
    }
  }
}

複製代碼
  • 應用規則及傳入規則示例
import { Rule } from 'epage-core'
import PhoneRule from './PhoneRule.js'

Rule.set({ PhoneRule })
// 應用規則:PhoneRule的type靜態屬性對應phone
helper.setValidators(widgets, { input: ['phone'] })
// 傳入規則
new Epage({
  Rule,
  // ...
})
複製代碼
  • 最終配置input組件時,能夠看見增長了 手機號 規則

自定義擴展規則

2)、表單聯動

這裏先給一個我的理解的聯動定義

表單聯動通常是指 一個或多個表單字段值或屬性 發生 變化,使其餘 一個或多個表單字段值或屬性 變化的交互。

這裏有幾個關鍵點:一個或多個表單字段值或屬性變化

i、聯動示例

表單聯動

好比能夠爲如下任意聯動關係:

  • 一對一:
No. 影響字段 關係 被影響字段 屬性
1 城市 屬於 中國 --> 學校 可選學校
  • 一對多:
No. 影響字段 關係 被影響字段 屬性
1 城市 屬於 中國 --> 學校 可選學校
專業 可選專業
  • 多對一:
No. 影響字段 關係 被影響字段 屬性
1 城市 屬於 中國 --> 學校 可選學校
2 在校人數 大於 1萬

12之間多是 也多是 的關係

  • 多對多:
No. 影響字段 關係 被影響字段 屬性
1 城市 屬於 中國 --> 學校 可選學校
2 在校人數 大於 1萬 專業 可選專業

12之間多是 也多是 的關係

注意:

  • 多個影響字段之間多是也多是關係
  • 影響字段能夠等於屬於等多關係與值創建條件
  • 被影響字段之間通常不存在的關係
  • 多級關聯,如 a字段影響b字段,b字段影響c字段等,可經過多個兩級關聯配置

以上是基於影響字段角度考慮關聯。固然也能夠從被影響字段的角度考慮關聯,在一些時候更直觀,如:

{
  "widget": "input",
  "name": "c",
  "hidden": "$a.hidden === false && $b.hiden === true"
}
複製代碼

以上schema描述會有如下很差的地方:

一、會讓hidden原本爲boolean類型,卻變成了字符串表達式。
二、若是hidden原本就是字符串類型的字段,又怎麼區分是具體值仍是表達式呢?固然也能夠在擴展字段
三、不一樣字段屬性邏輯比較分散,不方便統一管理

以上示例聯動中,影響字段經過改變自身的表單值來觸發聯動邏輯。這裏的值能夠是等於關係,也能夠是包含小於等關係,取決於值類型。如:

  • 布爾能夠是等於不等於
  • 字符串能夠是等於不等於包含不包含
  • 數字能夠是等於不等於大於小於大於等於小於不等於

因爲不一樣表單組件值類型可能不一樣,因此能夠做爲靜態屬性定義到組件的Schema上,如:

class InputSchema extends FormSchema {}

Object.assign(InputSchema, {
  logic: {
    value: ['=', '!=', '<>', '><'] // [等於, 不等於, 包含, 不包含]
  }
})
複製代碼

若是把 表單字段 當作一個對象,表單值(value)當作一個特殊屬性,還有一些普通屬性,如顯隱(hidden)、禁用(disabled)等,就會發現聯動就是屬性與屬性之間邏輯綁定。如何作到監聽value變化以及普通屬性變化呢?

value之因此認爲是特殊屬性主要緣由:

該屬性的變化會觸發onchange事件。對應hiddendisabled等普通屬性卻沒有,理論上也應該有onhiddenondisabled相應事件。

若是把 表單字段 全部屬性定義成響應式,任意屬性變化時就能很方便通知到。也能夠本身實現訂閱發佈方式,來修改表單屬性。

表單聯動示意2

  • 一種是表單字段屬性符合某種條件後,聯動其餘表單字段屬性變化,這裏稱值聯動
  • 另外一種是表單組件發生了某個事件(如onchange),聯動其餘表單字段屬性變化,這裏稱事件聯動

從必定程度講兩者方式都能解決部分相同功能的聯動,如A組件value值發生變化,也能夠認爲是A組件發生onchange事件

ii、聯動實現

如下以開發 epage 部分實現爲例分析(暫未實現多對1、多對多關聯邏輯)

首先,邏輯定義

定義schema上應該保存的邏輯結構。具體邏輯定在單個組件的Schema上仍是最外層Schema均可以,這裏定義到統一的地方,方便管理。

主要定義 影響組件被影響組件:包括聯動類型、影響表單組件值符合某種條件、被影響表單組件哪些屬性聯動、影響表單觸發的什麼事件等

{
  // schema 其餘字段
  logics: [
    {
      "key": "kB1mKTnek", // 影響組件key
      "type": "value",    // 關聯類型,值聯動 或 事件聯動
      "action": "=",      // 值聯動是相等關係,這裏定義不一樣符號,應該提供符號解析能力
      "value": "show",    // 具體值
      "effects": [        // 被影響組件列表
        {
          "key": "kASJAJwRB", // 被影響組件key
          "properties": [
            { "key": "hidden", "value": true },   // 被影響組件隱藏
            { "key": "disabled", "value": true }  // 被影響組件禁用,還應能夠爲其餘屬性
          ]
        }
      ]
    }
  ]
}

複製代碼

其次,邏輯解析

  • 邏輯管理

基於以上分析,應具有值邏輯事件邏輯。在渲染或預覽時執行生效

import EventLogic from './EventLogic'
import ValueLogic from './ValueLogic'

class Logic {
  // 檢查值邏輯配置是否合法,是否有重複邏輯等
  // 返回 { patches, scripts },對應比較結果和可能的自定義腳本
  diffValueLogics(){}
  // 同上
  diffEventLogics(){}
  // 根據以上比較結果,最終修改組件Schema屬性
  applyPatches(){}
  // 檢查被影響組件是否有效等
  checkEffect(){}
}
複製代碼
  • 值邏輯部分實現示例

對以上生成的 邏輯關係 進行解析。如值聯動中 action字段就有不少比較關係(=(等於)、!=(不等於)、>(大於)、<(小於)、<>(包含)等),以=爲例:

class ValueLogic{
  constructor () {
    this.map = {
      '=': {
        key: '=',
        value: '等於',
        // left、right爲用戶輸入值都爲字符串,valueType爲應該的數據類型
        // 但左右值類型與valueType不一致時,根據狀況進行轉換後比較
        validator: (left, right, { valueType }) => {
          const booleanMap = { true: true, false: false }
          let leftValue = left
          let rightValue = right

          if (valueType === 'number') {
            leftValue = parseFloat(left)
            rightValue = parseFloat(right)

            return (isNaN(leftValue) || isNaN(leftValue)) ? false : leftValue === rightValue
          } else if (valueType === 'boolean') {
            if (right in booleanMap) {
              rightValue = booleanMap[right]
            }
          }
          return leftValue === rightValue
        }
      },
      // ...
    }
  }
}
複製代碼

爲了讓設計器更具備通用性,邏輯關係定義及解析也應支持組件開發者擴展。

具體值邏輯或事件邏輯的一些實現能夠參考 epage#Logic

3、總結

作一個可視化配置工具並不難,可是既要保證通用,又能保證擴展性,同時統一標準一塊兒共建卻不容易。須要創建一套統一SchemaDSL,不一樣開發者能認同,可根據須要擴展定製,進而達到快速實現業務交付目標。

傳送門

關於做者團隊

滴滴效能平臺前端團隊EFE,感召於經過技術持續提高組織效能的組織使命,致力於打造技術領先的前端技術團隊,深耕於性能監控、質量監控、低代碼配置、文檔協做、微前端、webIDE等多個領域,技術方向廣闊,探索空間和成長空間極大。

咱們是一個充滿激情和有夢想的團隊,期待您的加入。感興趣的可聯繫 dumingtan@didiglobal.com

相關文章
相關標籤/搜索