如何設(shè)計(jì)低代碼平臺(tái)快速構(gòu)建頁(yè)面

為什么要做頁(yè)面可視化搭建系統(tǒng)

統(tǒng)一微前端架構(gòu)各個(gè)微應(yīng)用頁(yè)面的樣式和交互 我們公司的供應(yīng)鏈 saas 系統(tǒng)而多個(gè)獨(dú)立部署、技術(shù)棧不統(tǒng)一的系統(tǒng)組合而成晰甚,這些系統(tǒng)的樣式,交互存在差異椰于,通過頁(yè)面可視化搭建系統(tǒng)生成的頁(yè)面底層使用同一套組件庫(kù)梭姓,這可以滿足樣式遵班,交互一致屠升,并且面對(duì)之后的樣式和交互變更支持批量修改

縮短常規(guī)頁(yè)面開發(fā)時(shí)間 我們公司的供應(yīng)鏈 saas 系統(tǒng)是一個(gè) toB 系統(tǒng),這里面存在數(shù)量可觀的類似的頁(yè)面狭郑,開發(fā)重復(fù)頁(yè)面容易磨滅開發(fā)人員的積極性腹暖,整理各類頁(yè)面的共同之處,通過可視化搭建系統(tǒng)來減少頁(yè)面開發(fā)重復(fù)度翰萨,讓開發(fā)人員集中精力開發(fā)邏輯復(fù)雜的頁(yè)面

整個(gè)可視化搭建系統(tǒng)分為三部分脏答,分別是配置頁(yè)(setting),視圖頁(yè)(view) 和 json schema。配置頁(yè)生成 json schema,視圖頁(yè)消費(fèi) json schema


image

寫在前面

使用 codemirror 實(shí)現(xiàn)在可視化界面上編輯自定義行為的代碼

接口地址只填寫以/開頭的相對(duì)路徑殖告,視圖頁(yè)在運(yùn)行的時(shí)候決定接口所在的環(huán)境

使用 cool-path 實(shí)現(xiàn)按字段路徑取值阿蝶、按字段路徑修改值

使用 new Function 在視圖頁(yè)將 json schema 對(duì)應(yīng)的字符串轉(zhuǎn)化成對(duì)象或者函數(shù)

可創(chuàng)建的頁(yè)面類型有:列表、詳情黄绩、表單羡洁。詳情和表單頁(yè)的設(shè)計(jì)思路差別不大,列表頁(yè)與另外兩種頁(yè)面差別比較大

功能

列表頁(yè)

定義按鈕操作爽丹、定義搜索項(xiàng)(單行搜索框\事件選擇器\下拉框\級(jí)聯(lián)選擇器\批量輸入搜索)筑煮、動(dòng)態(tài)獲取下拉框和級(jí)聯(lián)選擇器的備選數(shù)據(jù)、列表排序粤蝎、table 行多選真仲、自定義 table 行的操作、自定義 table 列的顯示內(nèi)容

詳情頁(yè)\表單頁(yè)

表單聯(lián)動(dòng)初澎、表格數(shù)據(jù)格式校驗(yàn)秸应、一列布局、多列布局碑宴、表格分頁(yè)灸眼、自定義文本的顯示內(nèi)容

列表頁(yè)設(shè)計(jì)

經(jīng)過分析我們公司的列表頁(yè)布局有一個(gè)統(tǒng)一的模式。列表由右上角的操作按鈕墓懂、左上角的標(biāo)題\面包屑焰宣、正上面的篩選區(qū)域、中間的 table 以及正下方的分頁(yè)器組成捕仔,中間的 table 是必須存在的匕积,其他內(nèi)容可選。如下圖所示:

image

由于列表頁(yè)有一個(gè)統(tǒng)一的布局模式榜跌,在列表的配置頁(yè)闪唆,我將列表頁(yè)分成多個(gè)獨(dú)立的區(qū)域進(jìn)行分別配置,如下圖:

image

基本配置區(qū)域中填寫的數(shù)據(jù)不會(huì)顯示在列表視圖頁(yè)中钓葫,這個(gè)區(qū)域填寫的數(shù)據(jù)只是為了方便列表配置數(shù)據(jù)的查找

全局配置

由于列表頁(yè)是一個(gè)動(dòng)態(tài)的頁(yè)面悄蕾,頁(yè)面中大部分?jǐn)?shù)據(jù)都是從后端開發(fā)人員提供的接口中得到的,每一個(gè)接口都對(duì)應(yīng)了多個(gè)環(huán)境础浮,在我們公司每個(gè)接口至少有開發(fā)環(huán)境帆调、測(cè)試環(huán)境、生成環(huán)境這三個(gè)環(huán)境豆同,所以在列表配置頁(yè)中不能將接口的域名寫死番刊,在需要填寫接口地址的地方只能填寫接口的相對(duì)路徑,除此之外這個(gè)頁(yè)面可視化搭建系統(tǒng)需要為多個(gè)獨(dú)立部署的系統(tǒng)生成頁(yè)面影锈,所以在全局配置區(qū)域要選擇后端接口的所屬系統(tǒng)芹务,如下圖:

image

列表視圖頁(yè)中從 json schema 中得到接口所屬系統(tǒng)標(biāo)識(shí)符蝉绷,再根據(jù)視圖頁(yè)的運(yùn)行環(huán)境動(dòng)態(tài)生成接口的域名

并不是所有的列表頁(yè)都存在按鈕、filterStatus 和搜索框枣抱,在這三個(gè)區(qū)域可以根據(jù)實(shí)際情況進(jìn)行配置

按鈕配置

image

在配置按鈕的時(shí)候必須選擇按鈕的操作類型熔吗,目前可選的操作類型有:上傳、導(dǎo)出佳晶、自定義磁滚,不同操作類型的按鈕需要填寫的配置項(xiàng)有所不同。在這里以導(dǎo)出為例宵晚,不同的列表頁(yè)導(dǎo)出之后需要進(jìn)行的后續(xù)操作有所差異垂攘,所以配置人員可以自定義導(dǎo)出之后的回調(diào)函數(shù),為了減少配置人員對(duì)參數(shù)順序的記憶成本淤刃,在 codemirror 代碼編輯器中只能寫函數(shù)體中的內(nèi)容晒他,配置頁(yè)將 json schema 保存到服務(wù)器之前會(huì)將代碼編輯器中的內(nèi)容包裹在函數(shù)中,簡(jiǎn)化代碼如下:

if(button.type==='upload') {

button.callback ='function (vm,content) {'+ toSwitch(button.callback) +'}'

}else{

button.callback ='function (vm) {'+ toSwitch(button.callback) +'}'

}

當(dāng)再此編輯函數(shù)體的內(nèi)容時(shí)逸贾,需要將函數(shù)中的函數(shù)體取出陨仅,簡(jiǎn)化代碼如下:

consttoSwitch = (func) =>{

constmatchResult =func.toLocaleString().match(/(?:\/\*[\s\S]*?\*\/|\/\/.*?\r?\n|[^{])+\{([\s\S]*)\}$/)

constbody = (matchResult||[])[1] ||''

returnbody.trim();

}button.callback = toSwitch(button.callback)

由于不同的接口需要傳遞的參數(shù)形式有所不同,所以在所有填寫接口地址的地方铝侵,都可以自定義組裝接口的參數(shù)灼伤,視圖頁(yè)在渲染頁(yè)面時(shí)有生成接口參數(shù)的行為,在自定義組裝接口參數(shù)編輯器中可以修改這一默認(rèn)行為

filterStatus 配置較為簡(jiǎn)單咪鲜,在這兒略過

搜索區(qū)域配置

searchBox 區(qū)域可配置的搜索框有:?jiǎn)涡休斎肟蚝摹⑾吕颉⒓?jí)聯(lián)選擇器疟丙、時(shí)間選擇器颖侄、時(shí)間范圍選擇器


e103a085d0df49b6a5452713c214e04c.png

不同的搜索框需要填寫的配置項(xiàng)不同。對(duì)于時(shí)間范圍選擇器而言享郊,有的列表接口要求將開始時(shí)間和結(jié)束時(shí)間放在同一個(gè)數(shù)組中览祖,有的列表接口則要求將開始時(shí)間和結(jié)束時(shí)間分別放在不同的字段中,所以搜索框的字段名具有解構(gòu)的功能炊琉。在填寫字段名時(shí)可以填寫 [param1,param2] 這種格式展蒂。在視圖頁(yè)解析 json schema 時(shí)會(huì)將搜索框的參數(shù)賦給解構(gòu)之后的參數(shù),簡(jiǎn)化代碼如下:

functionseparateParam(originalArr,key){

constkeyArr = key.replace(/^\[/,'').replace(/\]$/,'').split(',');

constresult = {};

keyArr.forEach((key,index) =>{

result[key] = originalArr[index]        });returnresult;

}

在某些列表中可能需要給搜索框設(shè)置默認(rèn)值苔咪,默認(rèn)值可能是固定的靜態(tài)數(shù)據(jù)也可能是視圖頁(yè)運(yùn)行時(shí)動(dòng)態(tài)生成的數(shù)據(jù)锰悼。如果默認(rèn)值輸入框中包含 return,則會(huì)認(rèn)為默認(rèn)值是從函數(shù)中動(dòng)態(tài)生成悼泌,配置頁(yè)在將 json schema 保存到服務(wù)器之前會(huì)將代碼編輯器中輸入的內(nèi)容包裹到函數(shù)中

視圖頁(yè)給搜索框賦默認(rèn)值的代碼如下:

functiongetDefaultValue(searchConfig){

returnisFunction(searchConfig.default) ? searchConfig.default(vm) : searchConfig.default;

}

下拉框和級(jí)聯(lián)選擇器需要有下拉備選項(xiàng)松捉,這些下拉備選項(xiàng)可以從接口中獲取也可以配置靜態(tài)的數(shù)據(jù)

table 區(qū)域

table 配置是列表頁(yè)配置中最為復(fù)雜的地方,table 也是列表視圖中主要的內(nèi)容馆里,它的復(fù)雜之處在于隘世,列數(shù)不固定,每列的顯示形式不固定鸠踪,配置區(qū)域如下:

image

由于 table 每一列要展示的數(shù)據(jù)的嵌套層級(jí)不固定丙者,所以表頭字段支持按路徑取值。例如:表頭字段可以是order.id营密,這使用cool-path來實(shí)現(xiàn)這個(gè)功能

table 支持的列的展示形式有:多選械媒、操作、文本评汰。如果某一列是操作列纷捞,就必須自定義操作列的展示形式。如果某一列是文本被去,默認(rèn)情況會(huì)根據(jù)表頭字段去取值主儡,然后將文本內(nèi)容顯示在界面上,考慮到實(shí)際的需求惨缆,配置人員也可以改變這一默認(rèn)行為糜值,去自定義顯示內(nèi)容。自定義顯示內(nèi)容使用的 Vue 的渲染函數(shù)來實(shí)現(xiàn),簡(jiǎn)化代碼如下:

:row="scope.row"

:index="scope.$index"

:col="col"

/>

    </template>

    // v-render 組件定義如下

components:{      vRender:{          render(createElement) {//這兒的this.renderFunc 是在列表配置界面寫的函數(shù)

returnthis.renderFunc(createElement,this.row,vm.$parent,this.col,this.index,this.oldRowData)

},          props:{            renderFunc:{              type:Function,              required:true

},            row:{              type:Object,default(){return{}}

},            index:{              type:Number,              default:0

},            col:{              type:Object,default() {return{} }

}          },          data(){return{

oldRowData:deepClone(this.row)

}          }        }    }

由于 table 中要展示的數(shù)據(jù)都是從后端提供的接口獲取坯墨,在我們公司內(nèi)部這個(gè)頁(yè)面搭建系統(tǒng)要服務(wù)于多個(gè)獨(dú)立的系統(tǒng)寂汇,這些系統(tǒng)的后端接口規(guī)范不盡相同,所以在配置頁(yè)可以根據(jù)接口返回的值組裝 table 要展示的數(shù)據(jù)捣染。組裝 table 數(shù)據(jù)與組裝接口參數(shù)類似骄瓣,都是在代碼編輯框中寫函數(shù),然后函數(shù)必須有一個(gè)返回值耍攘,視圖頁(yè)會(huì)將返回值當(dāng)作接口參數(shù)或者 table 數(shù)據(jù)

詳情頁(yè)/表單頁(yè)的設(shè)計(jì)

詳情頁(yè)和表單頁(yè)的設(shè)計(jì)思路相同累贤,不同的是在頁(yè)面上展示的組件不同,在下面的文字中統(tǒng)稱為詳情頁(yè)少漆。詳情頁(yè)中有兩種類型的組件臼膏,分別是布局組件和基礎(chǔ)組件,基礎(chǔ)組件只能放置在布局組件中示损,布局組件不能相互嵌套

在這里我以行為緯度來創(chuàng)建詳情頁(yè)渗磅,并且將行分成一至三列,每一列可以容納任意多個(gè)基礎(chǔ)組件检访,選中基礎(chǔ)組件或者布局組件對(duì)這個(gè)組件進(jìn)行配置始鱼,可以將配置詳情頁(yè)當(dāng)做搭積木

image

頁(yè)面數(shù)據(jù)的獲取

由于創(chuàng)建的是動(dòng)態(tài)頁(yè)面需要請(qǐng)求后端接口,所以在創(chuàng)建詳情頁(yè)時(shí)需要選擇接口所屬的后端系統(tǒng)并且在需要填寫接口地址的地方只能填寫接口的相對(duì)路徑脆贵,這一點(diǎn)與配置列表頁(yè)相同

對(duì)于所有的詳情頁(yè)而言医清,它們都需要將詳情數(shù)據(jù)展示在界面上,在這里暫且將這些數(shù)據(jù)統(tǒng)稱為詳情頁(yè)面數(shù)據(jù)卖氨。在我們公司的業(yè)務(wù)系統(tǒng)中通常通過詳情 ID 或者其他的參數(shù)從接口中獲取頁(yè)面數(shù)據(jù)

在頁(yè)面可視化搭建系統(tǒng)中有兩種方式獲取頁(yè)面數(shù)據(jù)会烙,分別是:

填寫獲取頁(yè)面數(shù)據(jù)的接口地址负懦,這種方式將大部分的工作都交給視圖頁(yè)自動(dòng)完成,最為簡(jiǎn)單柏腻。

在配置頁(yè)自定義函數(shù)得到頁(yè)面數(shù)據(jù)纸厉,在這里支持 promise 和 同步執(zhí)行的函數(shù),這種方式很靈活

先介紹第一種方式五嫂,界面如下:

image

在接口地址輸入框中颗品,可以填寫類似這樣的內(nèi)容/basic/someApi/detail?poId=202004130000121&type&code=333,視圖頁(yè)在拿到 json schema 去生成視圖的時(shí)候會(huì)將poId沃缘,type和code 作為接口的參數(shù)躯枢,并且視圖頁(yè)會(huì)優(yōu)先從瀏覽器地址欄中取這些參數(shù)的值,如果瀏覽器不存在某個(gè)參數(shù),程序就使用 json schema 中給定的值槐臀。例如:瀏覽器地址欄的查詢字符串為?po_id=99&type=2,視圖頁(yè)在請(qǐng)求/basic/someApi/detail這個(gè)接口時(shí)锄蹂,傳給接口的參數(shù)為:{po_id:99,type:2,code:333}。這種方式會(huì)將接口返回的content字段當(dāng)前頁(yè)面數(shù)據(jù)

根據(jù)接口地址輸入框中的值與瀏覽器地址欄中的 query 獲取接口參數(shù)的代碼如下:

/**

* 從 query 中得到接口的參數(shù)

*@param params

*@param query

*@returns{{[p: string]: *}}

*/

exportfunctiongetParams(params, query){

constresult = {

...params  };Object.keys(result).forEach(key=>{

// 用瀏覽器 query 中的參數(shù)值替換 params 中的值

    if(query[key]){

      result[key] = query[key]

    }

  });

  return result

}

第二種方式:在配置頁(yè)自定義函數(shù)得到頁(yè)面數(shù)據(jù)峰档,這種方式你只需要寫函數(shù)體败匹,并且必須有一個(gè)返回值,界面如下:

image

這種方式支持 promise 和同步執(zhí)行的函數(shù)讥巡。如果函數(shù)返回 promise掀亩,視圖頁(yè)會(huì)將 promise resolve 的值當(dāng)作頁(yè)面數(shù)據(jù)受楼,如果是同步執(zhí)行的函數(shù)鼎文,視圖頁(yè)會(huì)將同步函數(shù)的返回值當(dāng)作頁(yè)面數(shù)據(jù)

結(jié)合這兩種方式視圖頁(yè)獲取頁(yè)面數(shù)據(jù)的代碼如下:

/**

* 獲取頁(yè)面數(shù)據(jù)

*@param pageConfig 頁(yè)面配置

*@param vm 詳情頁(yè)的 Vue 實(shí)例

*@returns{Promise<any | never>}

*/

exportfunctionfetchPageData({pageConfig,vm}){

returnnewPromise((resolve, reject) =>{

// 從接口中獲取頁(yè)面數(shù)據(jù)

    if(pageConfig.url) {

      const paramsFromUrl = getParamsFromUrl(pageConfig.url)

      // 得到完整的接口地址

      const fullUrl = getFullUrl(pageConfig.belong,paramsFromUrl.origin)

      request(fullUrl, getParams(paramsFromUrl.params,vm.$route.query)).then(res => {

        resolve(res.content)

      })

    }

    // 通過自定義函數(shù)獲取頁(yè)面數(shù)據(jù)

    else if(pageConfig.getPageData ){

      if(typeof pageConfig.getPageData === 'function') {

        const result = pageConfig.getPageData.call(vm,vm)

        resolve(result);

      } else {

        resolve(pageConfig.getPageData)

      }

    } else {

      resolve({})

    }

  }).then((content) => {

    return content

  })

}

組件的配置參數(shù)

如下是一個(gè)輸入框組件的配置:

{

"title":"用戶名",

"path":"user.name",

"key":"userName",

"type":"string",

"visible":true,

"x-linkages":[],

"x-component":"dm-input",

"x-component-props":{

"type":"text",

"size":"small",

"placeholder":"請(qǐng)輸入用戶名"

},"x-props":{

"style":{

"margin":"7px 5px",

"color":"#333333"

}  },"editable":true,

"triggerType":"submit",

"events":{},

"x-rules":{

"format":"",

"required":false,

"pattern":"",

"max":"5",

"min":"2"

}}

組件可配置的字段如下:

屬性名 描述 類型

**title **字段標(biāo)題 string

**path **取值路徑 string

key 接口字段名 string

description 字段描述 string

**defaultUI **組件字段默認(rèn)值 any

editable 是否可編輯 boolean

**type **字段值類型 string,object,array,number,boolean

**enum **枚舉數(shù)據(jù) array,object,function

url 獲取枚舉數(shù)據(jù)或者 UI 組件數(shù)據(jù)的接口地址 string

**items **組件的子組件的配置字段 array

**trigger **Type字段校驗(yàn)時(shí)機(jī) string

**visible **字段是否可見 boolean

eventsUI 組件的事件 Object

x-props 字段的擴(kuò)展屬性 object

**x-component **字段的 UI 組件名 string

**x-component-props **字段 UI 組件的屬性 object

**x-linkages **字段聯(lián)動(dòng) array

**x-rules **字段規(guī)則 object

x-props 數(shù)據(jù)屬性

屬性名 描述 類型

**style **字段的 UI 組件的 style 樣式 object

className 字段的 UI 組件的 className string

**label **字段的 UI 組件的枚舉 label 取值路徑 string

value 字段的 UI 組件的枚舉 value 取值路徑 string

**button **Type按鈕的操作類型 string

render 自定義組件的顯示內(nèi)容 function

**buttonSubmitUrl **提交按鈕的接口地址 string

**paging **列表是否分頁(yè) boolean

x-rules 數(shù)據(jù)屬性

屬性名描述類型

**format **字段值類型 string

**required **是否必填 boolean

**pattern **正則 RegExp,string

**max **最大長(zhǎng)度 number

**min **最小長(zhǎng)度 number

**len **長(zhǎng)度 number

**maximum **最大數(shù)值 number

minimum 最小數(shù)值 number

**message **錯(cuò)誤文案 string

format 的可選值:url,郵箱汽纠,手機(jī)號(hào)抬驴,金額炼七,數(shù)字

x-linkages 字段聯(lián)動(dòng)

屬性名描述類型可選值

**type **聯(lián)動(dòng)類型 String linkage:hidden,linkage:disabled,linkage:value

subscribe 聯(lián)動(dòng)訂閱器 Function-

下面以文本組件,下拉框組件布持,按鈕組件為例進(jìn)行說明

文本組件

文件組件用于在詳情頁(yè)中顯示某個(gè)字段對(duì)應(yīng)的值豌拙,他的配置界面如下:

image

先介紹非自定義文本組件顯示內(nèi)容的情況,這個(gè)時(shí)候文本組件的取值路徑是必填項(xiàng)的题暖,視圖頁(yè)會(huì)根據(jù)取值路徑從頁(yè)面數(shù)據(jù)中取文本組件的顯示內(nèi)容按傅。取值路徑還支持在路徑后面增加過濾器,這里的過濾器和Vue 中的過濾器功能一致胧卤。取值路徑例如為:

create_at|formatDate('datetime'): 從頁(yè)面數(shù)據(jù)的create_at字段中取值唯绍,然后使用formatDate格式化create_at字段對(duì)應(yīng)值

簡(jiǎn)化代碼如下:

... vue 組件

computed:{

//使用計(jì)算屬性得到文本組件要顯示的內(nèi)容

textContent(){    const p =this.fieldSchema.path.split('|')

//如果填寫了取值路徑

if(formatPathStr(p[0])) {

const filters = p.slice(1)

//這里的 Path 指 cool-path

const path =newPath(p[0]);

//從頁(yè)面數(shù)據(jù)中取值

let value = path.getIn(this.pageVm.pageData)

//過濾器

if(filters && filters.length) {

value = filters.reduce((a, b)=>{

returnthis.evalFilter(b, a,this)

}, value)      }returnvalue ||'- -'

}else{

returnthis.fieldSchema.default||'- -'

}  }},methods:{  evalFilter(filterStr,val){    const parms = filterStr.match(/^([_$0-9A-Za-z]+)\(([^()]+)\)$/) || ['', filterStr]

const fn = parms[1]

let args = [val]try{

args = args.concat(eval(`[${parms[2]}]`))

}catch(e) {

console.error(e)

this.$message.error(this.fieldSchema.title+'執(zhí)行過濾器時(shí)拼接參數(shù)出錯(cuò)了')

}//根據(jù)過濾器名得到過濾器對(duì)應(yīng)的方法

const filterFn =this.$options.filters &&this.$options.filters[fn]

if(typeoffilterFn =='function') {

returnfilterFn.apply(this, args)

}returnval

}}

從上面的配置文件組件的可視化界面中可以看到,我們還可以配置文本組件的枚舉數(shù)據(jù)枝誊,這個(gè)枚舉數(shù)據(jù)主要是考慮到接口返回的頁(yè)面數(shù)據(jù)中的某些字段是數(shù)字或者英語(yǔ)單詞况芒,但是在界面上我們需要顯示這些字段的中文含義,枚舉數(shù)據(jù)可以是從接口中獲取會(huì)可以在配置頁(yè)中寫死叶撒,枚舉數(shù)據(jù)的獲取方式與上面介紹的頁(yè)面數(shù)據(jù)獲取方式類似绝骚,在這里不再贅述

不自定義文本組件顯示內(nèi)容已經(jīng)可以滿足大部分使用場(chǎng)景耐版,這種方式有一個(gè)局限性:一個(gè)文本組件只能顯示一個(gè)字段的值,在某些時(shí)候可能需要將多個(gè)字段合并在一個(gè)文本組件中顯示在界面皮壁,在這種情況下我使用Vue的渲染函數(shù)來自定義文本組件的顯示內(nèi)容椭更。自定義的渲染函數(shù)類似于下面這樣:

returnh('div',[

h('span',pageData.user.name),

h('span',pageData.uesr.age)

])

在視圖頁(yè)在渲染視圖時(shí)會(huì)執(zhí)行文件組件的渲染函數(shù)哪审,簡(jiǎn)化代碼如下:

<!--do something-->

:renderFunc="fieldSchema['x-props'].render"

  />

// do something

components:{

  vRender:{

    render(createElement) {

      const parentVm = this.$parent;

      return this.renderFunc(createElement,parentVm,parentVm.pageVm,parentVm.pageVm.pageData)

    },

    props:{

      renderFunc:{

        type:Function,

        required: true

      },

    }

  }

}
</script>

在可視化創(chuàng)建詳情頁(yè)中蛾魄,除了文本組件支持寫渲染函數(shù)之外,表格組件中的列也支持寫渲染函數(shù)

下拉框組件

下拉框組件的配置界面如下:

image

下拉框組件有三個(gè)區(qū)域進(jìn)行配置湿滓,在這里著重介紹下拉框的顯示配置和聯(lián)動(dòng)配置滴须,先介紹顯示配置再介紹聯(lián)動(dòng)配置

下拉框是一個(gè)表單組件,它除了可以對(duì)數(shù)據(jù)進(jìn)行展示還可以對(duì)數(shù)據(jù)進(jìn)行修改叽奥,我將表單組件的值(即:組件 value 屬性對(duì)應(yīng)的值)存放在 vuex 中扔水。對(duì)于詳情頁(yè)而言,表單組件需要顯示它的初始值朝氓,表單的初始值位于頁(yè)面數(shù)據(jù)中魔市,為了讓表單組件在 vuex 中取到它要展示的值,在表單組件 created 鉤子函數(shù)中赵哲,我將這個(gè)表單組件在頁(yè)面數(shù)據(jù)中的值另存到 vuex 中待德,在此之后表單組件取值和修改值都是針對(duì) vuex 中的數(shù)據(jù)進(jìn)行操作,簡(jiǎn)化之后的代碼如下:

<template>
  <dm-select
v-model="value"

v-bind="fieldSchema['x-component-props']"

:class="fieldSchema['x-props'].className"

    >

<!--....some options-->

exportdefault{

computed:{

value:{

get() {

// 從 Vuex 的 formData 中取值

          return new Path(this.fieldSchema.key).getIn(this.formData)

        },

        set(value){

          // 將表單字段的保存到 Vuex 的 formData 中

          this.saveFormData({name:this.fieldSchema.key,value:value})

        }

      },

    },

    created(){

      this.setFieldInitValue()

    },

    methods:{

      setFieldInitValue(){

            // 從頁(yè)面數(shù)據(jù)中取表單組件的初始值

            let initValue = new Path(this.fieldSchema.path).getIn(this.pageData)

            // 將表單組件的初始值保存到 Vuex 的 formData 中

            this.saveFormData({name:this.fieldSchema.key,value:initValue})

      }

    }

  }
</script>

下拉組件除了要顯示選中的值枫夺,還需要備選數(shù)據(jù)将宪,它的備選數(shù)據(jù)可以通過從接口中獲取也可以在配置中寫死,支持返回一個(gè) promise橡庞,返回同步計(jì)算的值或者填寫 url较坛。下拉框的備選數(shù)據(jù)獲取方式與上面介紹的頁(yè)面數(shù)據(jù)的獲取方式類似,不再贅述

表單聯(lián)動(dòng)配置

表單聯(lián)動(dòng)是指:這個(gè)表單組件的狀態(tài)受其他表單組件的值的影響扒最,目前支持的聯(lián)動(dòng)類型有:隱藏丑勤、禁用、組件值聯(lián)動(dòng)吧趣。聯(lián)動(dòng)訂閱器用于觀察 formData 中值的變化法竞,針對(duì)表單組件的聯(lián)動(dòng)類型對(duì)組件的狀態(tài)作出影響。聯(lián)動(dòng)訂閱器是一個(gè)函數(shù)再菊,在視圖頁(yè)中使用聯(lián)動(dòng)訂閱器計(jì)算計(jì)算屬性的值爪喘,所以只要在聯(lián)動(dòng)訂閱器中訪問的值發(fā)生了變化,就會(huì)重新計(jì)算計(jì)算屬性纠拔,進(jìn)而影響組件的狀態(tài)秉剑。簡(jiǎn)化的代碼如下:

<template>
  <dm-select
v-model="value"

:disabled="disabled"

:hidden="hidden"

    >

<!--....some options-->

export default {  computed:{    disabled(){      if(this.linkages['linkage:disabled']) {

return this.linkages['linkage:disabled'](this.pageVm,this.pageVm.pageData,this.formData)

} else {        return false      }    },    hidden(){      if(this.linkages['linkage:hidden']) {

return this.linkages['linkage:hidden'](this.pageVm,this.pageVm.pageData,this.formData)

} else {        return false      }    },    value:{      get() {        // 從 Vuex 的 formData 中取值        return new Path(this.fieldSchema.key).getIn(this.formData)      },      set(value){        // 將表單組件的值保存到 Vuex 的 formData 中        this.saveFormData({name:this.fieldSchema.key,value:value})      }    },    valueOfLinkage(){      if(this.linkages['linkage:value']) {

return this.linkages['linkage:value'](this.pageVm,this.pageVm.pageData,this.formData)

} else {        return ''      }    }  },  watch:{    valueOfLinkage(val){      this.value = val    }  }}</script>

表單組件的值聯(lián)動(dòng)比隱藏聯(lián)動(dòng)和禁用聯(lián)動(dòng)要復(fù)雜一些,這是因?yàn)槁?lián)動(dòng)訂閱器可以改變表單組件的值稠诲,表單組件它自身也可以改變它的值侦鹏。表單組件的值由最后一次變化為準(zhǔn)

對(duì)于禁用聯(lián)動(dòng)诡曙,它的聯(lián)動(dòng)訂閱器中可填寫的內(nèi)容如下:

if(formData.status +''==='2') {

returntrue

}else{

returnfalse

}

上面的聯(lián)動(dòng)訂閱器表示:當(dāng) vuex 中的 formData.status 等于 2 時(shí),這個(gè)表單組件會(huì)被禁用

對(duì)于隱藏聯(lián)動(dòng)略水,它的聯(lián)動(dòng)訂閱器中可填寫的內(nèi)容如下:

if(formData.username.length >3) {

returntrue

}else{

returnfalse

}

上面的聯(lián)動(dòng)訂閱器表示:當(dāng) vuex 中的 formData.username 的長(zhǎng)度 > 3 時(shí)价卤,這個(gè)表單組件會(huì)被隱藏

對(duì)于值聯(lián)動(dòng),它的聯(lián)動(dòng)訂閱器中可以填寫的內(nèi)容如下:

if(formData.id) {

return3

}else{

return''

}

上面的聯(lián)動(dòng)訂閱器表示:當(dāng) vuex 中的 formData.id 為 truly 時(shí)渊涝,這個(gè)表單組件的值會(huì)被置為 3

按鈕組件

按鈕組件的配置界面如下:

image

按鈕組件是一種比較特別的組件慎璧,與其他組件相比它的操作行為不固定而且影響范圍比較廣。根據(jù)業(yè)務(wù)需求分為三種操作類型跨释,分別是:提交(即:將表單數(shù)據(jù)提交到服務(wù)器)胸私,重置(即:將表單組件的值重置為初始狀態(tài)),自定義(即:自定義按鈕的點(diǎn)擊事件處理程序)鳖谈。在下面只介紹提交和自定義這兩種類型

提交操作

通常在將表單數(shù)據(jù)提交到服務(wù)器之前岁疼,我們需要對(duì)表單數(shù)據(jù)進(jìn)行校驗(yàn),只有所有的數(shù)據(jù)符合要求才能將表單數(shù)據(jù)提交到服務(wù)器缆娃,否則將錯(cuò)誤語(yǔ)顯示到界面上捷绒。為了滿足這個(gè)需求,我們需要在按鈕提交事件的處理程序中訪問到所有的表單數(shù)據(jù)以及表單組件的數(shù)據(jù)校驗(yàn)規(guī)則贯要,由于表單數(shù)據(jù)保存在 Vuex 中暖侨,并且存放數(shù)據(jù)校驗(yàn)規(guī)則的 json schema 在視圖頁(yè)中全局共享,所以在提交事件處理程序中能夠很容易拿到想要的數(shù)據(jù)郭毕。需要注意的是它碎,如果某個(gè)表單組件的數(shù)據(jù)沒有通過校驗(yàn),它錯(cuò)誤信息要顯示在表單組件所在的位置显押,這就意味著消費(fèi)錯(cuò)誤信息的位置和生成錯(cuò)誤信息的位置不相同

我將對(duì)錯(cuò)誤信息進(jìn)行操作的方法收集到單獨(dú)的模塊中扳肛。簡(jiǎn)化代碼如下:

/**

*表單錯(cuò)誤收集器

**/

importVue from'vue'

exportconsterrorCollector = new Vue({

data(){

return{

errorObj:{}    }  },  methods:{    clearError(){this.errorObj = {}

},    delError(name){consterrorObj = {

...this.errorObj

}      delete errorObj[name]this.errorObj = errorObj

},    setError(name,value){this.errorObj = {

...this.errorObj,

[name]: value      }    },    initFieldError(name){this.errorObj = {

...this.errorObj,

[name]:''

}    }  }})

錯(cuò)誤信息收集器是一個(gè) Vue 實(shí)例,在每個(gè)表單組件中引入錯(cuò)誤信息收集器乘碑,并且將它作為組件的一個(gè) data 屬性挖息,錯(cuò)誤信息作為組件的計(jì)算屬性,這樣一來只要錯(cuò)誤信息收集器中的數(shù)據(jù)發(fā)生變化界面就會(huì)更新兽肤,簡(jiǎn)化代碼如下:

<template>
<!-- do something-->

{{ errorMsg }}

exportdefault{

  data(){

return{

errorCollector:errorCollector

    }

  },

computed:{

    errorMsg(){

returnthis.errorCollector.errorObj[this.fieldSchema.key]

    }

  }

}

自定義操作

自定義操作實(shí)際上 json schema 中定義按鈕的點(diǎn)擊事件處理程序套腹,在視圖頁(yè)中的實(shí)現(xiàn)比較簡(jiǎn)單

如何使用

在開發(fā)環(huán)境 json schema 保存在數(shù)據(jù)庫(kù),要在測(cè)試環(huán)境和生產(chǎn)環(huán)境使用 json schema 生成頁(yè)面资铡,需要將 json schema 下載到項(xiàng)目中的一個(gè)特定文件夾中电禀,當(dāng)在瀏覽器中訪問這個(gè)視圖頁(yè)時(shí),會(huì)根據(jù)頁(yè)面 ID 到下載好的靜態(tài)文件中讀取頁(yè)面的 json schema笤休,然后視圖頁(yè)將頁(yè)面渲染出來

從靜態(tài)文件中讀取配置代碼如下:

import("@static/jsons/tables/table_string_"+id+".json").then(fileContent=>{

console.log('配置數(shù)據(jù):',fileContent)

})

json 文件中保存的 json schema 是一個(gè)字符串尖飞,但是在視圖頁(yè)渲染界面的時(shí)候需要的是一個(gè)對(duì)象,并且對(duì)象的某些字段必須是函數(shù)。為了將字符串轉(zhuǎn)成需要的格式政基,我使用 new Function('return ' + strConfig)() 來完成這一需求,簡(jiǎn)化代碼如下:

functionparseStrConfig(jsonSchema){

returnnewFunction('return '+ jsonSchema)();

}

存在的不足

1.生產(chǎn)出的頁(yè)面不能獨(dú)立于頁(yè)面搭建系統(tǒng)運(yùn)行贞铣。要想在其他系統(tǒng)中使用生成的頁(yè)面,必須在對(duì)應(yīng)系統(tǒng)中使用 iframe 或者 single-spa 微前端技術(shù)引入頁(yè)面搭建系統(tǒng)

2.頁(yè)面的 json schema 沒有與頁(yè)面搭建系統(tǒng)獨(dú)立沮明。由于每創(chuàng)建一個(gè)頁(yè)面就要該頁(yè)面的 json schema 下載到頁(yè)面可視化搭建系統(tǒng)中辕坝,這導(dǎo)致頁(yè)面可視化搭建系統(tǒng)需要被頻繁的發(fā)布,但是頁(yè)面可視化搭建系統(tǒng)的業(yè)務(wù)功能相對(duì)穩(wěn)定

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末荐健,一起剝皮案震驚了整個(gè)濱河市酱畅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌摧扇,老刑警劉巖圣贸,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挚歧,死亡現(xiàn)場(chǎng)離奇詭異扛稽,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)滑负,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門在张,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人矮慕,你說我怎么就攤上這事帮匾。” “怎么了痴鳄?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵瘟斜,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我痪寻,道長(zhǎng)螺句,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任橡类,我火速辦了婚禮蛇尚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘顾画。我一直安慰自己取劫,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布研侣。 她就那樣靜靜地躺著谱邪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪庶诡。 梳的紋絲不亂的頭發(fā)上惦银,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼璧函。 笑死傀蚌,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蘸吓。 我是一名探鬼主播善炫,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼库继!你這毒婦竟也來了箩艺?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤宪萄,失蹤者是張志新(化名)和其女友劉穎艺谆,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拜英,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡静汤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了居凶。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片虫给。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖侠碧,靈堂內(nèi)的尸體忽然破棺而出抹估,到底是詐尸還是另有隱情,我是刑警寧澤弄兜,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布药蜻,位于F島的核電站,受9級(jí)特大地震影響替饿,放射性物質(zhì)發(fā)生泄漏语泽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一盛垦、第九天 我趴在偏房一處隱蔽的房頂上張望湿弦。 院中可真熱鬧,春花似錦腾夯、人聲如沸颊埃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)班利。三九已至,卻和暖如春榨呆,著一層夾襖步出監(jiān)牢的瞬間罗标,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留闯割,地道東北人彻消。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像宙拉,于是被迫代替她去往敵國(guó)和親宾尚。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345