為什么要做頁面可視化搭建系統(tǒng)
- 統(tǒng)一微前端架構(gòu)各個(gè)微應(yīng)用頁面的樣式和交互 我們公司的供應(yīng)鏈 saas 系統(tǒng)而多個(gè)獨(dú)立部署、技術(shù)棧不統(tǒng)一的系統(tǒng)組合而成,這些系統(tǒng)的樣式,交互存在差異,通過頁面可視化搭建系統(tǒng)生成的頁面底層使用同一套組件庫(kù),這可以滿足樣式,交互一致,并且面對(duì)之后的樣式和交互變更支持批量修改
- 縮短常規(guī)頁面開發(fā)時(shí)間 我們公司的供應(yīng)鏈 saas 系統(tǒng)是一個(gè) toB 系統(tǒng),這里面存在數(shù)量可觀的類似的頁面,開發(fā)重復(fù)頁面容易磨滅開發(fā)人員的積極性,整理各類頁面的共同之處,通過可視化搭建系統(tǒng)來減少頁面開發(fā)重復(fù)度,讓開發(fā)人員集中精力開發(fā)邏輯復(fù)雜的頁面
整個(gè)可視化搭建系統(tǒng)分為三部分,分別是配置頁(setting),視圖頁(view) 和 json schema。配置頁生成 json schema,視圖頁消費(fèi) json schema
寫在前面
- 使用 codemirror 實(shí)現(xiàn)在可視化界面上編輯自定義行為的代碼
- 接口地址只填寫以/開頭的相對(duì)路徑,視圖頁在運(yùn)行的時(shí)候決定接口所在的環(huán)境
- 使用 cool-path 實(shí)現(xiàn)按字段路徑取值、按字段路徑修改值
- 使用 new Function 在視圖頁將 json schema 對(duì)應(yīng)的字符串轉(zhuǎn)化成對(duì)象或者函數(shù)
可創(chuàng)建的頁面類型有:列表、詳情、表單。詳情和表單頁的設(shè)計(jì)思路差別不大,列表頁與另外兩種頁面差別比較大
功能
列表頁
定義按鈕操作、定義搜索項(xiàng)(單行搜索框事件選擇器下拉框級(jí)聯(lián)選擇器批量輸入搜索)、動(dòng)態(tài)獲取下拉框和級(jí)聯(lián)選擇器的備選數(shù)據(jù)、列表排序、table 行多選、自定義 table 行的操作、自定義 table 列的顯示內(nèi)容
詳情頁表單頁
表單聯(lián)動(dòng)、表格數(shù)據(jù)格式校驗(yàn)、一列布局、多列布局、表格分頁、自定義文本的顯示內(nèi)容
列表頁設(shè)計(jì)
經(jīng)過分析我們公司的列表頁布局有一個(gè)統(tǒng)一的模式。列表由右上角的操作按鈕、左上角的標(biāo)題面包屑、正上面的篩選區(qū)域、中間的 table 以及正下方的分頁器組成,中間的 table 是必須存在的,其他內(nèi)容可選。如下圖所示:
由于列表頁有一個(gè)統(tǒng)一的布局模式,在列表的配置頁,我將列表頁分成多個(gè)獨(dú)立的區(qū)域進(jìn)行分別配置,如下圖:
基本配置區(qū)域中填寫的數(shù)據(jù)不會(huì)顯示在列表視圖頁中,這個(gè)區(qū)域填寫的數(shù)據(jù)只是為了方便列表配置數(shù)據(jù)的查找
全局配置
由于列表頁是一個(gè)動(dòng)態(tài)的頁面,頁面中大部分?jǐn)?shù)據(jù)都是從后端開發(fā)人員提供的接口中得到的,每一個(gè)接口都對(duì)應(yīng)了多個(gè)環(huán)境,在我們公司每個(gè)接口至少有開發(fā)環(huán)境、測(cè)試環(huán)境、生成環(huán)境這三個(gè)環(huán)境,所以在列表配置頁中不能將接口的域名寫死,在需要填寫接口地址的地方只能填寫接口的相對(duì)路徑,除此之外這個(gè)頁面可視化搭建系統(tǒng)需要為多個(gè)獨(dú)立部署的系統(tǒng)生成頁面,所以在全局配置區(qū)域要選擇后端接口的所屬系統(tǒng),如下圖:
列表視圖頁中從 json schema 中得到接口所屬系統(tǒng)標(biāo)識(shí)符,再根據(jù)視圖頁的運(yùn)行環(huán)境動(dòng)態(tài)生成接口的域名
并不是所有的列表頁都存在按鈕、filterStatus 和搜索框,在這三個(gè)區(qū)域可以根據(jù)實(shí)際情況進(jìn)行配置
按鈕配置
在配置按鈕的時(shí)候必須選擇按鈕的操作類型,目前可選的操作類型有:上傳、導(dǎo)出、自定義,不同操作類型的按鈕需要填寫的配置項(xiàng)有所不同。在這里以導(dǎo)出為例,不同的列表頁導(dǎo)出之后需要進(jìn)行的后續(xù)操作有所差異,所以配置人員可以自定義導(dǎo)出之后的回調(diào)函數(shù),為了減少配置人員對(duì)參數(shù)順序的記憶成本,在 codemirror 代碼編輯器中只能寫函數(shù)體中的內(nèi)容,配置頁將 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)化代碼如下:
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ù)的行為,在自定義組裝接口參數(shù)編輯器中可以修改這一默認(rèn)行為
filterStatus 配置較為簡(jiǎn)單,在這兒略過
搜索區(qū)域配置
searchBox 區(qū)域可配置的搜索框有:?jiǎn)涡休斎肟?、下拉框、?jí)聯(lián)選擇器、時(shí)間選擇器、時(shí)間范圍選擇器
不同的搜索框需要填寫的配置項(xiàng)不同。對(duì)于時(shí)間范圍選擇器而言,有的列表接口要求將開始時(shí)間和結(jié)束時(shí)間放在同一個(gè)數(shù)組中,有的列表接口則要求將開始時(shí)間和結(jié)束時(shí)間分別放在不同的字段中,所以搜索框的字段名具有解構(gòu)的功能。在填寫字段名時(shí)可以填寫 [param1,param2] 這種格式。在視圖頁解析 json schema 時(shí)會(huì)將搜索框的參數(shù)賦給解構(gòu)之后的參數(shù),簡(jiǎn)化代碼如下:
function separateParam (originalArr,key){ const keyArr = key.replace(/^[/,'').replace(/]$/,'').split(','); const result = {}; keyArr.forEach((key,index) => { result[key] = originalArr[index] }); return result;}
在某些列表中可能需要給搜索框設(shè)置默認(rèn)值,默認(rèn)值可能是固定的靜態(tài)數(shù)據(jù)也可能是視圖頁運(yùn)行時(shí)動(dòng)態(tài)生成的數(shù)據(jù)。如果默認(rèn)值輸入框中包含 return,則會(huì)認(rèn)為默認(rèn)值是從函數(shù)中動(dòng)態(tài)生成,配置頁在將 json schema 保存到服務(wù)器之前會(huì)將代碼編輯器中輸入的內(nèi)容包裹到函數(shù)中
視圖頁給搜索框賦默認(rèn)值的代碼如下:
function getDefaultValue(searchConfig) { return isFunction(searchConfig.default) ? searchConfig.default(vm) : searchConfig.default;}
下拉框和級(jí)聯(lián)選擇器需要有下拉備選項(xiàng),這些下拉備選項(xiàng)可以從接口中獲取也可以配置靜態(tài)的數(shù)據(jù)
table 區(qū)域
table 配置是列表頁配置中最為復(fù)雜的地方,table 也是列表視圖中主要的內(nèi)容,它的復(fù)雜之處在于,列數(shù)不固定,每列的顯示形式不固定,配置區(qū)域如下:
由于 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)化代碼如下:
<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)部這個(gè)頁面搭建系統(tǒng)要服務(wù)于多個(gè)獨(dú)立的系統(tǒng),這些系統(tǒng)的后端接口規(guī)范不盡相同,所以在配置頁可以根據(jù)接口返回的值組裝 table 要展示的數(shù)據(jù)。組裝 table 數(shù)據(jù)與組裝接口參數(shù)類似,都是在代碼編輯框中寫函數(shù),然后函數(shù)必須有一個(gè)返回值,視圖頁會(huì)將返回值當(dāng)作接口參數(shù)或者 table 數(shù)據(jù)
詳情頁/表單頁的設(shè)計(jì)
詳情頁和表單頁的設(shè)計(jì)思路相同,不同的是在頁面上展示的組件不同,在下面的文字中統(tǒng)稱為詳情頁。詳情頁中有兩種類型的組件,分別是布局組件和基礎(chǔ)組件,基礎(chǔ)組件只能放置在布局組件中,布局組件不能相互嵌套
在這里我以行為緯度來創(chuàng)建詳情頁,并且將行分成一至三列,每一列可以容納任意多個(gè)基礎(chǔ)組件,選中基礎(chǔ)組件或者布局組件對(duì)這個(gè)組件進(jìn)行配置,可以將配置詳情頁當(dāng)做搭積木
頁面數(shù)據(jù)的獲取
由于創(chuàng)建的是動(dòng)態(tài)頁面需要請(qǐng)求后端接口,所以在創(chuàng)建詳情頁時(shí)需要選擇接口所屬的后端系統(tǒng)并且在需要填寫接口地址的地方只能填寫接口的相對(duì)路徑,這一點(diǎn)與配置列表頁相同
對(duì)于所有的詳情頁而言,它們都需要將詳情數(shù)據(jù)展示在界面上,在這里暫且將這些數(shù)據(jù)統(tǒng)稱為詳情頁面數(shù)據(jù)。在我們公司的業(yè)務(wù)系統(tǒng)中通常通過詳情 ID 或者其他的參數(shù)從接口中獲取頁面數(shù)據(jù)
在頁面可視化搭建系統(tǒng)中有兩種方式獲取頁面數(shù)據(jù),分別是:
- 填寫獲取頁面數(shù)據(jù)的接口地址,這種方式將大部分的工作都交給視圖頁自動(dòng)完成,最為簡(jiǎn)單。
- 在配置頁自定義函數(shù)得到頁面數(shù)據(jù),在這里支持 promise 和 同步執(zhí)行的函數(shù),這種方式很靈活
先介紹第一種方式,界面如下:
在接口地址輸入框中,可以填寫類似這樣的內(nèi)容/basic/someApi/detail?poId=202004130000121&type&code=333,視圖頁在拿到 json schema 去生成視圖的時(shí)候會(huì)將poId,type和code 作為接口的參數(shù),并且視圖頁會(huì)優(yōu)先從瀏覽器地址欄中取這些參數(shù)的值,如果瀏覽器不存在某個(gè)參數(shù),程序就使用 json schema 中給定的值。例如:瀏覽器地址欄的查詢字符串為?po_id=99&type=2,視圖頁在請(qǐng)求/basic/someApi/detail這個(gè)接口時(shí),傳給接口的參數(shù)為:{po_id:99,type:2,code:333}。這種方式會(huì)將接口返回的content字段當(dāng)前頁面數(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ù)體,并且必須有一個(gè)返回值,界面如下:
這種方式支持 promise 和同步執(zhí)行的函數(shù)。如果函數(shù)返回 promise,視圖頁會(huì)將 promise resolve 的值當(dāng)作頁面數(shù)據(jù),如果是同步執(zhí)行的函數(shù),視圖頁會(huì)將同步函數(shù)的返回值當(dāng)作頁面數(shù)據(jù)
結(jié)合這兩種方式視圖頁獲取頁面數(shù)據(jù)的代碼如下:
/** * 獲取頁面數(shù)據(jù) * @param pageConfig 頁面配置 * @param vm 詳情頁的 Vue 實(shí)例 * @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ù)
如下是一個(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 列表是否分頁 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)行說明
文本組件
文件組件用于在詳情頁中顯示某個(gè)字段對(duì)應(yīng)的值,他的配置界面如下:
先介紹非自定義文本組件顯示內(nèi)容的情況,這個(gè)時(shí)候文本組件的取值路徑是必填項(xiàng)的,視圖頁會(huì)根據(jù)取值路徑從頁面數(shù)據(jù)中取文本組件的顯示內(nèi)容。取值路徑還支持在路徑后面增加過濾器,這里的過濾器和Vue 中的過濾器功能一致。取值路徑例如為:
create_at|formatDate('datetime'): 從頁面數(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 = 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í)拼接參數(shù)出錯(cuò)了') } // 根據(jù)過濾器名得到過濾器對(duì)應(yīng)的方法 const filterFn = this.$options.filters && this.$options.filters[fn] if (typeof filterFn == 'function') { return filterFn.apply(this, args) } return val }}
從上面的配置文件組件的可視化界面中可以看到,我們還可以配置文本組件的枚舉數(shù)據(jù),這個(gè)枚舉數(shù)據(jù)主要是考慮到接口返回的頁面數(shù)據(jù)中的某些字段是數(shù)字或者英語單詞,但是在界面上我們需要顯示這些字段的中文含義,枚舉數(shù)據(jù)可以是從接口中獲取會(huì)可以在配置頁中寫死,枚舉數(shù)據(jù)的獲取方式與上面介紹的頁面數(shù)據(jù)獲取方式類似,在這里不再贅述
不自定義文本組件顯示內(nèi)容已經(jīng)可以滿足大部分使用場(chǎng)景,這種方式有一個(gè)局限性:一個(gè)文本組件只能顯示一個(gè)字段的值,在某些時(shí)候可能需要將多個(gè)字段合并在一個(gè)文本組件中顯示在界面,在這種情況下我使用Vue的渲染函數(shù)來自定義文本組件的顯示內(nèi)容。自定義的渲染函數(shù)類似于下面這樣:
return h('div',[ h('span',pageData.user.name), h('span',pageData.uesr.age) ])
在視圖頁在渲染視圖時(shí)會(huì)執(zhí)行文件組件的渲染函數(shù),簡(jiǎn)化代碼如下:
<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ù)
下拉框組件
下拉框組件的配置界面如下:
下拉框組件有三個(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ì)于詳情頁而言,表單組件需要顯示它的初始值,表單的初始值位于頁面數(shù)據(jù)中,為了讓表單組件在 vuex 中取到它要展示的值,在表單組件 created 鉤子函數(shù)中,我將這個(gè)表單組件在頁面數(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--> </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ù)可以通過從接口中獲取也可以在配置中寫死,支持返回一個(gè) promise,返回同步計(jì)算的值或者填寫 url。下拉框的備選數(shù)據(jù)獲取方式與上面介紹的頁面數(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ù),在視圖頁中使用聯(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--> </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)動(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') { return true} else { return false}
上面的聯(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) { return true} else { return false}
上面的聯(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) { return 3} else { return ''}
上面的聯(lián)動(dòng)訂閱器表示:當(dāng) vuex 中的 formData.id 為 truly 時(shí),這個(gè)表單組件的值會(huì)被置為 3
按鈕組件
按鈕組件的配置界面如下:
按鈕組件是一種比較特別的組件,與其他組件相比它的操作行為不固定而且影響范圍比較廣。根據(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ò)誤語顯示到界面上。為了滿足這個(gè)需求,我們需要在按鈕提交事件的處理程序中訪問到所有的表單數(shù)據(jù)以及表單組件的數(shù)據(jù)校驗(yàn)規(guī)則,由于表單數(shù)據(jù)保存在 Vuex 中,并且存放數(shù)據(jù)校驗(yàn)規(guī)則的 json schema 在視圖頁中全局共享,所以在提交事件處理程序中能夠很容易拿到想要的數(shù)據(jù)。需要注意的是,如果某個(gè)表單組件的數(shù)據(jù)沒有通過校驗(yàn),它錯(cuò)誤信息要顯示在表單組件所在的位置,這就意味著消費(fèi)錯(cuò)誤信息的位置和生成錯(cuò)誤信息的位置不相同
我將對(duì)錯(cuò)誤信息進(jìn)行操作的方法收集到單獨(dú)的模塊中。簡(jiǎn)化代碼如下:
/** *表單錯(cuò)誤收集器 **/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]: '' } } }})
錯(cuò)誤信息收集器是一個(gè) Vue 實(shí)例,在每個(gè)表單組件中引入錯(cuò)誤信息收集器,并且將它作為組件的一個(gè) data 屬性,錯(cuò)誤信息作為組件的計(jì)算屬性,這樣一來只要錯(cuò)誤信息收集器中的數(shù)據(jù)發(fā)生變化界面就會(huì)更新,簡(jiǎn)化代碼如下:
<template><!-- do something--><div>{{ errorMsg }}</div></template><script>export default { data(){ return { errorCollector:errorCollector } }, computed:{ errorMsg(){ return this.errorCollector.errorObj[this.fieldSchema.key] } }}</script>
自定義操作
自定義操作實(shí)際上 json schema 中定義按鈕的點(diǎn)擊事件處理程序,在視圖頁中的實(shí)現(xiàn)比較簡(jiǎn)單
如何使用
在開發(fā)環(huán)境 json schema 保存在數(shù)據(jù)庫(kù),要在測(cè)試環(huán)境和生產(chǎn)環(huán)境使用 json schema 生成頁面,需要將 json schema 下載到項(xiàng)目中的一個(gè)特定文件夾中,當(dāng)在瀏覽器中訪問這個(gè)視圖頁時(shí),會(huì)根據(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 是一個(gè)字符串,但是在視圖頁渲染界面的時(shí)候需要的是一個(gè)對(duì)象,并且對(duì)象的某些字段必須是函數(shù)。為了將字符串轉(zhuǎn)成需要的格式,我使用 new Function('return ' strConfig)() 來完成這一需求,簡(jiǎn)化代碼如下:
function parseStrConfig(jsonSchema) { return new Function('return ' jsonSchema)();}
存在的不足
- 生產(chǎn)出的頁面不能獨(dú)立于頁面搭建系統(tǒng)運(yùn)行。要想在其他系統(tǒng)中使用生成的頁面,必須在對(duì)應(yīng)系統(tǒng)中使用 iframe 或者 single-spa 微前端技術(shù)引入頁面搭建系統(tǒng)
- 頁面的 json schema 沒有與頁面搭建系統(tǒng)獨(dú)立。由于每創(chuàng)建一個(gè)頁面就要該頁面的 json schema 下載到頁面可視化搭建系統(tǒng)中,這導(dǎo)致頁面可視化搭建系統(tǒng)需要被頻繁的發(fā)布,但是頁面可視化搭建系統(tǒng)的業(yè)務(wù)功能相對(duì)穩(wěn)定
作者:何遇design
鏈接:https://juejin.im/post/6855579207448133646
來源:掘金
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。
版權(quán)聲明:本文內(nèi)容由互聯(lián)網(wǎng)用戶自發(fā)貢獻(xiàn),該文觀點(diǎn)僅代表作者本人。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如發(fā)現(xiàn)本站有涉嫌抄襲侵權(quán)/違法違規(guī)的內(nèi)容, 請(qǐng)發(fā)送郵件至 舉報(bào),一經(jīng)查實(shí),本站將立刻刪除。