為什么要做頁面可視化搭建系統(tǒng)
- 統(tǒng)一微前端架構(gòu)各個微應(yīng)用頁面的樣式和交互 我們公司的供應(yīng)鏈 saas 系統(tǒng)而多個獨立部署、技術(shù)棧不統(tǒng)一的系統(tǒng)組合而成,這些系統(tǒng)的樣式,交互存在差異,通過頁面可視化搭建系統(tǒng)生成的頁面底層使用同一套組件庫,這可以滿足樣式,交互一致,并且面對之后的樣式和交互變更支持批量修改
- 縮短常規(guī)頁面開發(fā)時間 我們公司的供應(yīng)鏈 saas 系統(tǒng)是一個 toB 系統(tǒng),這里面存在數(shù)量可觀的類似的頁面,開發(fā)重復頁面容易磨滅開發(fā)人員的積極性,整理各類頁面的共同之處,通過可視化搭建系統(tǒng)來減少頁面開發(fā)重復度,讓開發(fā)人員集中精力開發(fā)邏輯復雜的頁面
整個可視化搭建系統(tǒng)分為三部分,分別是配置頁(setting),視圖頁(view) 和 json schema。配置頁生成 json schema,視圖頁消費 json schema
寫在前面
- 使用 codemirror 實現(xiàn)在可視化界面上編輯自定義行為的代碼
- 接口地址只填寫以/開頭的相對路徑,視圖頁在運行的時候決定接口所在的環(huán)境
- 使用 cool-path 實現(xiàn)按字段路徑取值、按字段路徑修改值
- 使用 new Function 在視圖頁將 json schema 對應(yīng)的字符串轉(zhuǎn)化成對象或者函數(shù)
可創(chuàng)建的頁面類型有:列表、詳情、表單。詳情和表單頁的設(shè)計思路差別不大,列表頁與另外兩種頁面差別比較大
功能
列表頁
定義按鈕操作、定義搜索項(單行搜索框事件選擇器下拉框級聯(lián)選擇器批量輸入搜索)、動態(tài)獲取下拉框和級聯(lián)選擇器的備選數(shù)據(jù)、列表排序、table 行多選、自定義 table 行的操作、自定義 table 列的顯示內(nèi)容
詳情頁表單頁
表單聯(lián)動、表格數(shù)據(jù)格式校驗、一列布局、多列布局、表格分頁、自定義文本的顯示內(nèi)容
列表頁設(shè)計
經(jīng)過分析我們公司的列表頁布局有一個統(tǒng)一的模式。列表由右上角的操作按鈕、左上角的標題面包屑、正上面的篩選區(qū)域、中間的 table 以及正下方的分頁器組成,中間的 table 是必須存在的,其他內(nèi)容可選。如下圖所示:
由于列表頁有一個統(tǒng)一的布局模式,在列表的配置頁,我將列表頁分成多個獨立的區(qū)域進行分別配置,如下圖:
基本配置區(qū)域中填寫的數(shù)據(jù)不會顯示在列表視圖頁中,這個區(qū)域填寫的數(shù)據(jù)只是為了方便列表配置數(shù)據(jù)的查找
全局配置
由于列表頁是一個動態(tài)的頁面,頁面中大部分數(shù)據(jù)都是從后端開發(fā)人員提供的接口中得到的,每一個接口都對應(yīng)了多個環(huán)境,在我們公司每個接口至少有開發(fā)環(huán)境、測試環(huán)境、生成環(huán)境這三個環(huán)境,所以在列表配置頁中不能將接口的域名寫死,在需要填寫接口地址的地方只能填寫接口的相對路徑,除此之外這個頁面可視化搭建系統(tǒng)需要為多個獨立部署的系統(tǒng)生成頁面,所以在全局配置區(qū)域要選擇后端接口的所屬系統(tǒng),如下圖:
列表視圖頁中從 json schema 中得到接口所屬系統(tǒng)標識符,再根據(jù)視圖頁的運行環(huán)境動態(tài)生成接口的域名
并不是所有的列表頁都存在按鈕、filterStatus 和搜索框,在這三個區(qū)域可以根據(jù)實際情況進行配置
按鈕配置
在配置按鈕的時候必須選擇按鈕的操作類型,目前可選的操作類型有:上傳、導出、自定義,不同操作類型的按鈕需要填寫的配置項有所不同。在這里以導出為例,不同的列表頁導出之后需要進行的后續(xù)操作有所差異,所以配置人員可以自定義導出之后的回調(diào)函數(shù),為了減少配置人員對參數(shù)順序的記憶成本,在 codemirror 代碼編輯器中只能寫函數(shù)體中的內(nèi)容,配置頁將 json schema 保存到服務(wù)器之前會將代碼編輯器中的內(nèi)容包裹在函數(shù)中,簡化代碼如下:
if(button.type === 'upload') { button.callback = 'function (vm,content) {' toSwitch(button.callback) '}'} else { button.callback = 'function (vm) {' toSwitch(button.callback) '}'}
當再此編輯函數(shù)體的內(nèi)容時,需要將函數(shù)中的函數(shù)體取出,簡化代碼如下:
const toSwitch = (func) => { const matchResult = func.toLocaleString().match(/(?:/*[sS]*?*/|//.*?r?n|[^{]) {([sS]*)}$/) const body = (matchResult||[])[1] || '' return body.trim();}button.callback = toSwitch(button.callback)
由于不同的接口需要傳遞的參數(shù)形式有所不同,所以在所有填寫接口地址的地方,都可以自定義組裝接口的參數(shù),視圖頁在渲染頁面時有生成接口參數(shù)的行為,在自定義組裝接口參數(shù)編輯器中可以修改這一默認行為
filterStatus 配置較為簡單,在這兒略過
搜索區(qū)域配置
searchBox 區(qū)域可配置的搜索框有:單行輸入框、下拉框、級聯(lián)選擇器、時間選擇器、時間范圍選擇器
不同的搜索框需要填寫的配置項不同。對于時間范圍選擇器而言,有的列表接口要求將開始時間和結(jié)束時間放在同一個數(shù)組中,有的列表接口則要求將開始時間和結(jié)束時間分別放在不同的字段中,所以搜索框的字段名具有解構(gòu)的功能。在填寫字段名時可以填寫 [param1,param2] 這種格式。在視圖頁解析 json schema 時會將搜索框的參數(shù)賦給解構(gòu)之后的參數(shù),簡化代碼如下:
function separateParam (originalArr,key){ const keyArr = key.replace(/^[/,'').replace(/]$/,'').split(','); const result = {}; keyArr.forEach((key,index) => { result[key] = originalArr[index] }); return result;}
在某些列表中可能需要給搜索框設(shè)置默認值,默認值可能是固定的靜態(tài)數(shù)據(jù)也可能是視圖頁運行時動態(tài)生成的數(shù)據(jù)。如果默認值輸入框中包含 return,則會認為默認值是從函數(shù)中動態(tài)生成,配置頁在將 json schema 保存到服務(wù)器之前會將代碼編輯器中輸入的內(nèi)容包裹到函數(shù)中
視圖頁給搜索框賦默認值的代碼如下:
function getDefaultValue(searchConfig) { return isFunction(searchConfig.default) ? searchConfig.default(vm) : searchConfig.default;}
下拉框和級聯(lián)選擇器需要有下拉備選項,這些下拉備選項可以從接口中獲取也可以配置靜態(tài)的數(shù)據(jù)
table 區(qū)域
table 配置是列表頁配置中最為復雜的地方,table 也是列表視圖中主要的內(nèi)容,它的復雜之處在于,列數(shù)不固定,每列的顯示形式不固定,配置區(qū)域如下:
由于 table 每一列要展示的數(shù)據(jù)的嵌套層級不固定,所以表頭字段支持按路徑取值。例如:表頭字段可以是order.id,這使用cool-path來實現(xiàn)這個功能
table 支持的列的展示形式有:多選、操作、文本。如果某一列是操作列,就必須自定義操作列的展示形式。如果某一列是文本,默認情況會根據(jù)表頭字段去取值,然后將文本內(nèi)容顯示在界面上,考慮到實際的需求,配置人員也可以改變這一默認行為,去自定義顯示內(nèi)容。自定義顯示內(nèi)容使用的 Vue 的渲染函數(shù)來實現(xiàn),簡化代碼如下:
<template v-if="col.render"> <v-render :render-func="col.render" :row="scope.row" :index="scope.$index" :col="col" /> </template> // v-render 組件定義如下 components:{ vRender:{ render(createElement) { // 這兒的 this.renderFunc 是在列表配置界面寫的函數(shù) return this.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)部這個頁面搭建系統(tǒng)要服務(wù)于多個獨立的系統(tǒng),這些系統(tǒng)的后端接口規(guī)范不盡相同,所以在配置頁可以根據(jù)接口返回的值組裝 table 要展示的數(shù)據(jù)。組裝 table 數(shù)據(jù)與組裝接口參數(shù)類似,都是在代碼編輯框中寫函數(shù),然后函數(shù)必須有一個返回值,視圖頁會將返回值當作接口參數(shù)或者 table 數(shù)據(jù)
詳情頁/表單頁的設(shè)計
詳情頁和表單頁的設(shè)計思路相同,不同的是在頁面上展示的組件不同,在下面的文字中統(tǒng)稱為詳情頁。詳情頁中有兩種類型的組件,分別是布局組件和基礎(chǔ)組件,基礎(chǔ)組件只能放置在布局組件中,布局組件不能相互嵌套
在這里我以行為緯度來創(chuàng)建詳情頁,并且將行分成一至三列,每一列可以容納任意多個基礎(chǔ)組件,選中基礎(chǔ)組件或者布局組件對這個組件進行配置,可以將配置詳情頁當做搭積木
頁面數(shù)據(jù)的獲取
由于創(chuàng)建的是動態(tài)頁面需要請求后端接口,所以在創(chuàng)建詳情頁時需要選擇接口所屬的后端系統(tǒng)并且在需要填寫接口地址的地方只能填寫接口的相對路徑,這一點與配置列表頁相同
對于所有的詳情頁而言,它們都需要將詳情數(shù)據(jù)展示在界面上,在這里暫且將這些數(shù)據(jù)統(tǒng)稱為詳情頁面數(shù)據(jù)。在我們公司的業(yè)務(wù)系統(tǒng)中通常通過詳情 ID 或者其他的參數(shù)從接口中獲取頁面數(shù)據(jù)
在頁面可視化搭建系統(tǒng)中有兩種方式獲取頁面數(shù)據(jù),分別是:
- 填寫獲取頁面數(shù)據(jù)的接口地址,這種方式將大部分的工作都交給視圖頁自動完成,最為簡單。
- 在配置頁自定義函數(shù)得到頁面數(shù)據(jù),在這里支持 promise 和 同步執(zhí)行的函數(shù),這種方式很靈活
先介紹第一種方式,界面如下:
在接口地址輸入框中,可以填寫類似這樣的內(nèi)容/basic/someApi/detail?poId=202004130000121&type&code=333,視圖頁在拿到 json schema 去生成視圖的時候會將poId,type和code 作為接口的參數(shù),并且視圖頁會優(yōu)先從瀏覽器地址欄中取這些參數(shù)的值,如果瀏覽器不存在某個參數(shù),程序就使用 json schema 中給定的值。例如:瀏覽器地址欄的查詢字符串為?po_id=99&type=2,視圖頁在請求/basic/someApi/detail這個接口時,傳給接口的參數(shù)為:{po_id:99,type:2,code:333}。這種方式會將接口返回的content字段當前頁面數(shù)據(jù)
根據(jù)接口地址輸入框中的值與瀏覽器地址欄中的 query 獲取接口參數(shù)的代碼如下:
/** * 從 query 中得到接口的參數(shù) * @param params * @param query * @returns {{[p: string]: *}} */export function getParams(params, query) { const result = { ...params }; Object.keys(result).forEach(key => { // 用瀏覽器 query 中的參數(shù)值替換 params 中的值 if(query[key]){ result[key] = query[key] } }); return result}
第二種方式:在配置頁自定義函數(shù)得到頁面數(shù)據(jù),這種方式你只需要寫函數(shù)體,并且必須有一個返回值,界面如下:
這種方式支持 promise 和同步執(zhí)行的函數(shù)。如果函數(shù)返回 promise,視圖頁會將 promise resolve 的值當作頁面數(shù)據(jù),如果是同步執(zhí)行的函數(shù),視圖頁會將同步函數(shù)的返回值當作頁面數(shù)據(jù)
結(jié)合這兩種方式視圖頁獲取頁面數(shù)據(jù)的代碼如下:
/** * 獲取頁面數(shù)據(jù) * @param pageConfig 頁面配置 * @param vm 詳情頁的 Vue 實例 * @returns {Promise<any | never>} */export function fetchPageData({pageConfig,vm}){ return new Promise((resolve, reject) => { // 從接口中獲取頁面數(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ù)獲取頁面數(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ù)
如下是一個輸入框組件的配置:
{ "title": "用戶名", "path":"user.name", "key":"userName", "type":"string", "visible":true, "x-linkages":[], "x-component":"dm-input", "x-component-props":{ "type":"text", "size":"small", "placeholder":"請輸入用戶名" }, "x-props":{ "style":{ "margin":"7px 5px", "color":"#333333" } }, "editable":true, "triggerType":"submit", "events":{}, "x-rules":{ "format":"", "required":false, "pattern":"", "max":"5", "min":"2" }}
組件可配置的字段如下:
屬性名 描述 類型
title 字段標題 string
path 取值路徑 string
key 接口字段名 string
description 字段描述 string
defaultUI 組件字段默認值 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字段校驗時機 string
visible 字段是否可見 boolean
eventsUI 組件的事件 Object
x-props 字段的擴展屬性 object
x-component 字段的 UI 組件名 string
x-component-props 字段 UI 組件的屬性 object
x-linkages 字段聯(lián)動 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 列表是否分頁 boolean
x-rules 數(shù)據(jù)屬性
屬性名描述類型
format 字段值類型 string
required 是否必填 boolean
pattern 正則 RegExp,string
max 最大長度 number
min 最小長度 number
len 長度 number
maximum 最大數(shù)值 number
minimum 最小數(shù)值 number
message 錯誤文案 string
format 的可選值:url,郵箱,手機號,金額,數(shù)字
x-linkages 字段聯(lián)動
屬性名描述類型可選值
type 聯(lián)動類型 String linkage:hidden,linkage:disabled,linkage:value
subscribe 聯(lián)動訂閱器 Function-
下面以文本組件,下拉框組件,按鈕組件為例進行說明
文本組件
文件組件用于在詳情頁中顯示某個字段對應(yīng)的值,他的配置界面如下:
先介紹非自定義文本組件顯示內(nèi)容的情況,這個時候文本組件的取值路徑是必填項的,視圖頁會根據(jù)取值路徑從頁面數(shù)據(jù)中取文本組件的顯示內(nèi)容。取值路徑還支持在路徑后面增加過濾器,這里的過濾器和Vue 中的過濾器功能一致。取值路徑例如為:
create_at|formatDate('datetime'): 從頁面數(shù)據(jù)的 create_at 字段中取值,然后使用 formatDate 格式化 create_at 字段對應(yīng)值
簡化代碼如下:
… vue 組件
computed:{ // 使用計算屬性得到文本組件要顯示的內(nèi)容 textContent(){ const p = this.fieldSchema.path.split('|') // 如果填寫了取值路徑 if(formatPathStr(p[0])) { const filters = p.slice(1) // 這里的 Path 指 cool-path const path = new Path(p[0]); // 從頁面數(shù)據(jù)中取值 let value = path.getIn(this.pageVm.pageData) // 過濾器 if (filters && filters.length) { value = filters.reduce((a, b) => { return this.evalFilter(b, a, this) }, value) } return value || '- -' } else { return this.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ù)出錯了') } // 根據(jù)過濾器名得到過濾器對應(yīng)的方法 const filterFn = this.$options.filters && this.$options.filters[fn] if (typeof filterFn == 'function') { return filterFn.apply(this, args) } return val }}
從上面的配置文件組件的可視化界面中可以看到,我們還可以配置文本組件的枚舉數(shù)據(jù),這個枚舉數(shù)據(jù)主要是考慮到接口返回的頁面數(shù)據(jù)中的某些字段是數(shù)字或者英語單詞,但是在界面上我們需要顯示這些字段的中文含義,枚舉數(shù)據(jù)可以是從接口中獲取會可以在配置頁中寫死,枚舉數(shù)據(jù)的獲取方式與上面介紹的頁面數(shù)據(jù)獲取方式類似,在這里不再贅述
不自定義文本組件顯示內(nèi)容已經(jīng)可以滿足大部分使用場景,這種方式有一個局限性:一個文本組件只能顯示一個字段的值,在某些時候可能需要將多個字段合并在一個文本組件中顯示在界面,在這種情況下我使用Vue的渲染函數(shù)來自定義文本組件的顯示內(nèi)容。自定義的渲染函數(shù)類似于下面這樣:
return h('div',[ h('span',pageData.user.name), h('span',pageData.uesr.age) ])
在視圖頁在渲染視圖時會執(zhí)行文件組件的渲染函數(shù),簡化代碼如下:
<template><!--do something--> <v-render :renderFunc="fieldSchema['x-props'].render" /></template><script>// do somethingcomponents:{ 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)建詳情頁中,除了文本組件支持寫渲染函數(shù)之外,表格組件中的列也支持寫渲染函數(shù)
下拉框組件
下拉框組件的配置界面如下:
下拉框組件有三個區(qū)域進行配置,在這里著重介紹下拉框的顯示配置和聯(lián)動配置,先介紹顯示配置再介紹聯(lián)動配置
下拉框是一個表單組件,它除了可以對數(shù)據(jù)進行展示還可以對數(shù)據(jù)進行修改,我將表單組件的值(即:組件 value 屬性對應(yīng)的值)存放在 vuex 中。對于詳情頁而言,表單組件需要顯示它的初始值,表單的初始值位于頁面數(shù)據(jù)中,為了讓表單組件在 vuex 中取到它要展示的值,在表單組件 created 鉤子函數(shù)中,我將這個表單組件在頁面數(shù)據(jù)中的值另存到 vuex 中,在此之后表單組件取值和修改值都是針對 vuex 中的數(shù)據(jù)進行操作,簡化之后的代碼如下:
<template> <dm-select v-model="value" v-bind="fieldSchema['x-component-props']" :class="fieldSchema['x-props'].className" > <!--....some options--> </dm-select></template><script> export default { 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(){ // 從頁面數(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ù)可以通過從接口中獲取也可以在配置中寫死,支持返回一個 promise,返回同步計算的值或者填寫 url。下拉框的備選數(shù)據(jù)獲取方式與上面介紹的頁面數(shù)據(jù)的獲取方式類似,不再贅述
表單聯(lián)動配置
表單聯(lián)動是指:這個表單組件的狀態(tài)受其他表單組件的值的影響,目前支持的聯(lián)動類型有:隱藏、禁用、組件值聯(lián)動。聯(lián)動訂閱器用于觀察 formData 中值的變化,針對表單組件的聯(lián)動類型對組件的狀態(tài)作出影響。聯(lián)動訂閱器是一個函數(shù),在視圖頁中使用聯(lián)動訂閱器計算計算屬性的值,所以只要在聯(lián)動訂閱器中訪問的值發(fā)生了變化,就會重新計算計算屬性,進而影響組件的狀態(tài)。簡化的代碼如下:
<template> <dm-select v-model="value" :disabled="disabled" :hidden="hidden" > <!--....some options--> </dm-select></template><script>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)動比隱藏聯(lián)動和禁用聯(lián)動要復雜一些,這是因為聯(lián)動訂閱器可以改變表單組件的值,表單組件它自身也可以改變它的值。表單組件的值由最后一次變化為準
對于禁用聯(lián)動,它的聯(lián)動訂閱器中可填寫的內(nèi)容如下:
if(formData.status '' === '2') { return true} else { return false}
上面的聯(lián)動訂閱器表示:當 vuex 中的 formData.status 等于 2 時,這個表單組件會被禁用
對于隱藏聯(lián)動,它的聯(lián)動訂閱器中可填寫的內(nèi)容如下:
if(formData.username.length > 3) { return true} else { return false}
上面的聯(lián)動訂閱器表示:當 vuex 中的 formData.username 的長度 > 3 時,這個表單組件會被隱藏
對于值聯(lián)動,它的聯(lián)動訂閱器中可以填寫的內(nèi)容如下:
if(formData.id) { return 3} else { return ''}
上面的聯(lián)動訂閱器表示:當 vuex 中的 formData.id 為 truly 時,這個表單組件的值會被置為 3
按鈕組件
按鈕組件的配置界面如下:
按鈕組件是一種比較特別的組件,與其他組件相比它的操作行為不固定而且影響范圍比較廣。根據(jù)業(yè)務(wù)需求分為三種操作類型,分別是:提交(即:將表單數(shù)據(jù)提交到服務(wù)器),重置(即:將表單組件的值重置為初始狀態(tài)),自定義(即:自定義按鈕的點擊事件處理程序)。在下面只介紹提交和自定義這兩種類型
提交操作
通常在將表單數(shù)據(jù)提交到服務(wù)器之前,我們需要對表單數(shù)據(jù)進行校驗,只有所有的數(shù)據(jù)符合要求才能將表單數(shù)據(jù)提交到服務(wù)器,否則將錯誤語顯示到界面上。為了滿足這個需求,我們需要在按鈕提交事件的處理程序中訪問到所有的表單數(shù)據(jù)以及表單組件的數(shù)據(jù)校驗規(guī)則,由于表單數(shù)據(jù)保存在 Vuex 中,并且存放數(shù)據(jù)校驗規(guī)則的 json schema 在視圖頁中全局共享,所以在提交事件處理程序中能夠很容易拿到想要的數(shù)據(jù)。需要注意的是,如果某個表單組件的數(shù)據(jù)沒有通過校驗,它錯誤信息要顯示在表單組件所在的位置,這就意味著消費錯誤信息的位置和生成錯誤信息的位置不相同
我將對錯誤信息進行操作的方法收集到單獨的模塊中。簡化代碼如下:
/** *表單錯誤收集器 **/import Vue from 'vue'export const errorCollector = new Vue({ data(){ return { errorObj:{} } }, methods:{ clearError(){ this.errorObj = {} }, delError(name){ const errorObj = { ... this.errorObj } delete errorObj[name] this.errorObj = errorObj }, setError(name,value){ this.errorObj = { ... this.errorObj, [name]: value } }, initFieldError(name){ this.errorObj = { ... this.errorObj, [name]: '' } } }})
錯誤信息收集器是一個 Vue 實例,在每個表單組件中引入錯誤信息收集器,并且將它作為組件的一個 data 屬性,錯誤信息作為組件的計算屬性,這樣一來只要錯誤信息收集器中的數(shù)據(jù)發(fā)生變化界面就會更新,簡化代碼如下:
<template><!-- do something--><div>{{ errorMsg }}</div></template><script>export default { data(){ return { errorCollector:errorCollector } }, computed:{ errorMsg(){ return this.errorCollector.errorObj[this.fieldSchema.key] } }}</script>
自定義操作
自定義操作實際上 json schema 中定義按鈕的點擊事件處理程序,在視圖頁中的實現(xiàn)比較簡單
如何使用
在開發(fā)環(huán)境 json schema 保存在數(shù)據(jù)庫,要在測試環(huán)境和生產(chǎn)環(huán)境使用 json schema 生成頁面,需要將 json schema 下載到項目中的一個特定文件夾中,當在瀏覽器中訪問這個視圖頁時,會根據(jù)頁面 ID 到下載好的靜態(tài)文件中讀取頁面的 json schema,然后視圖頁將頁面渲染出來
從靜態(tài)文件中讀取配置代碼如下:
import("@static/jsons/tables/table_string_" id ".json").then(fileContent => { console.log('配置數(shù)據(jù):',fileContent) })
json 文件中保存的 json schema 是一個字符串,但是在視圖頁渲染界面的時候需要的是一個對象,并且對象的某些字段必須是函數(shù)。為了將字符串轉(zhuǎn)成需要的格式,我使用 new Function('return ' strConfig)() 來完成這一需求,簡化代碼如下:
function parseStrConfig(jsonSchema) { return new Function('return ' jsonSchema)();}
存在的不足
- 生產(chǎn)出的頁面不能獨立于頁面搭建系統(tǒng)運行。要想在其他系統(tǒng)中使用生成的頁面,必須在對應(yīng)系統(tǒng)中使用 iframe 或者 single-spa 微前端技術(shù)引入頁面搭建系統(tǒng)
- 頁面的 json schema 沒有與頁面搭建系統(tǒng)獨立。由于每創(chuàng)建一個頁面就要該頁面的 json schema 下載到頁面可視化搭建系統(tǒng)中,這導致頁面可視化搭建系統(tǒng)需要被頻繁的發(fā)布,但是頁面可視化搭建系統(tǒng)的業(yè)務(wù)功能相對穩(wěn)定
作者:何遇design
鏈接:https://juejin.im/post/6855579207448133646
來源:掘金
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。
版權(quán)聲明:本文內(nèi)容由互聯(lián)網(wǎng)用戶自發(fā)貢獻,該文觀點僅代表作者本人。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔相關(guān)法律責任。如發(fā)現(xiàn)本站有涉嫌抄襲侵權(quán)/違法違規(guī)的內(nèi)容, 請發(fā)送郵件至 舉報,一經(jīng)查實,本站將立刻刪除。