為什么要做頁面可視化搭建系統
統一微前端架構各個微應用頁面的樣式和交互 我們公司的供應鏈 saas 系統而多個獨立部署、技術棧不統一的系統組合而成,這些系統的樣式,交互存在差異,通過頁面可視化搭建系統生成的頁面底層使用同一套組件庫,這可以滿足樣式,交互一致,并且面對之后的樣式和交互變更支持批量修改
縮短常規頁面開發時間 我們公司的供應鏈 saas 系統是一個 toB 系統,這里面存在數量可觀的類似的頁面,開發重復頁面容易磨滅開發人員的積極性,整理各類頁面的共同之處,通過可視化搭建系統來減少頁面開發重復度,讓開發人員集中精力開發邏輯復雜的頁面
整個可視化搭建系統分為三部分,分別是配置頁(setting),視圖頁(view) 和 json schema。配置頁生成 json schema,視圖頁消費 json schema
寫在前面
使用 codemirror 實現在可視化界面上編輯自定義行為的代碼
接口地址只填寫以/開頭的相對路徑,視圖頁在運行的時候決定接口所在的環境
使用 cool-path 實現按字段路徑取值、按字段路徑修改值
使用 new Function 在視圖頁將 json schema 對應的字符串轉化成對象或者函數
可創建的頁面類型有:列表、詳情、表單。詳情和表單頁的設計思路差別不大,列表頁與另外兩種頁面差別比較大
功能
列表頁
定義按鈕操作、定義搜索項(單行搜索框\事件選擇器\下拉框\級聯選擇器\批量輸入搜索)、動態獲取下拉框和級聯選擇器的備選數據、列表排序、table 行多選、自定義 table 行的操作、自定義 table 列的顯示內容
詳情頁\表單頁
表單聯動、表格數據格式校驗、一列布局、多列布局、表格分頁、自定義文本的顯示內容
列表頁設計
經過分析我們公司的列表頁布局有一個統一的模式。列表由右上角的操作按鈕、左上角的標題\面包屑、正上面的篩選區域、中間的 table 以及正下方的分頁器組成,中間的 table 是必須存在的,其他內容可選。如下圖所示:
由于列表頁有一個統一的布局模式,在列表的配置頁,我將列表頁分成多個獨立的區域進行分別配置,如下圖:
基本配置區域中填寫的數據不會顯示在列表視圖頁中,這個區域填寫的數據只是為了方便列表配置數據的查找
全局配置
由于列表頁是一個動態的頁面,頁面中大部分數據都是從后端開發人員提供的接口中得到的,每一個接口都對應了多個環境,在我們公司每個接口至少有開發環境、測試環境、生成環境這三個環境,所以在列表配置頁中不能將接口的域名寫死,在需要填寫接口地址的地方只能填寫接口的相對路徑,除此之外這個頁面可視化搭建系統需要為多個獨立部署的系統生成頁面,所以在全局配置區域要選擇后端接口的所屬系統,如下圖:
列表視圖頁中從 json schema 中得到接口所屬系統標識符,再根據視圖頁的運行環境動態生成接口的域名
并不是所有的列表頁都存在按鈕、filterStatus 和搜索框,在這三個區域可以根據實際情況進行配置
按鈕配置
在配置按鈕的時候必須選擇按鈕的操作類型,目前可選的操作類型有:上傳、導出、自定義,不同操作類型的按鈕需要填寫的配置項有所不同。在這里以導出為例,不同的列表頁導出之后需要進行的后續操作有所差異,所以配置人員可以自定義導出之后的回調函數,為了減少配置人員對參數順序的記憶成本,在 codemirror 代碼編輯器中只能寫函數體中的內容,配置頁將 json schema 保存到服務器之前會將代碼編輯器中的內容包裹在函數中,簡化代碼如下:
if(button.type==='upload') {
button.callback ='function (vm,content) {'+ toSwitch(button.callback) +'}'
}else{
button.callback ='function (vm) {'+ toSwitch(button.callback) +'}'
}
當再此編輯函數體的內容時,需要將函數中的函數體取出,簡化代碼如下:
consttoSwitch = (func) =>{
constmatchResult =func.toLocaleString().match(/(?:\/\*[\s\S]*?\*\/|\/\/.*?\r?\n|[^{])+\{([\s\S]*)\}$/)
constbody = (matchResult||[])[1] ||''
returnbody.trim();
}button.callback = toSwitch(button.callback)
由于不同的接口需要傳遞的參數形式有所不同,所以在所有填寫接口地址的地方,都可以自定義組裝接口的參數,視圖頁在渲染頁面時有生成接口參數的行為,在自定義組裝接口參數編輯器中可以修改這一默認行為
filterStatus 配置較為簡單,在這兒略過
搜索區域配置
searchBox 區域可配置的搜索框有:單行輸入框、下拉框、級聯選擇器、時間選擇器、時間范圍選擇器
不同的搜索框需要填寫的配置項不同。對于時間范圍選擇器而言,有的列表接口要求將開始時間和結束時間放在同一個數組中,有的列表接口則要求將開始時間和結束時間分別放在不同的字段中,所以搜索框的字段名具有解構的功能。在填寫字段名時可以填寫 [param1,param2] 這種格式。在視圖頁解析 json schema 時會將搜索框的參數賦給解構之后的參數,簡化代碼如下:
functionseparateParam(originalArr,key){
constkeyArr = key.replace(/^\[/,'').replace(/\]$/,'').split(',');
constresult = {};
keyArr.forEach((key,index) =>{
result[key] = originalArr[index] });returnresult;
}
在某些列表中可能需要給搜索框設置默認值,默認值可能是固定的靜態數據也可能是視圖頁運行時動態生成的數據。如果默認值輸入框中包含 return,則會認為默認值是從函數中動態生成,配置頁在將 json schema 保存到服務器之前會將代碼編輯器中輸入的內容包裹到函數中
視圖頁給搜索框賦默認值的代碼如下:
functiongetDefaultValue(searchConfig){
returnisFunction(searchConfig.default) ? searchConfig.default(vm) : searchConfig.default;
}
下拉框和級聯選擇器需要有下拉備選項,這些下拉備選項可以從接口中獲取也可以配置靜態的數據
table 區域
table 配置是列表頁配置中最為復雜的地方,table 也是列表視圖中主要的內容,它的復雜之處在于,列數不固定,每列的顯示形式不固定,配置區域如下:
由于 table 每一列要展示的數據的嵌套層級不固定,所以表頭字段支持按路徑取值。例如:表頭字段可以是order.id,這使用cool-path來實現這個功能
table 支持的列的展示形式有:多選、操作、文本。如果某一列是操作列,就必須自定義操作列的展示形式。如果某一列是文本,默認情況會根據表頭字段去取值,然后將文本內容顯示在界面上,考慮到實際的需求,配置人員也可以改變這一默認行為,去自定義顯示內容。自定義顯示內容使用的 Vue 的渲染函數來實現,簡化代碼如下:
:row="scope.row"
:index="scope.$index"
:col="col"
/>
</template>
// v-render 組件定義如下
components:{ vRender:{ render(createElement) {//這兒的this.renderFunc 是在列表配置界面寫的函數
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 中要展示的數據都是從后端提供的接口獲取,在我們公司內部這個頁面搭建系統要服務于多個獨立的系統,這些系統的后端接口規范不盡相同,所以在配置頁可以根據接口返回的值組裝 table 要展示的數據。組裝 table 數據與組裝接口參數類似,都是在代碼編輯框中寫函數,然后函數必須有一個返回值,視圖頁會將返回值當作接口參數或者 table 數據
詳情頁/表單頁的設計
詳情頁和表單頁的設計思路相同,不同的是在頁面上展示的組件不同,在下面的文字中統稱為詳情頁。詳情頁中有兩種類型的組件,分別是布局組件和基礎組件,基礎組件只能放置在布局組件中,布局組件不能相互嵌套
在這里我以行為緯度來創建詳情頁,并且將行分成一至三列,每一列可以容納任意多個基礎組件,選中基礎組件或者布局組件對這個組件進行配置,可以將配置詳情頁當做搭積木
頁面數據的獲取
由于創建的是動態頁面需要請求后端接口,所以在創建詳情頁時需要選擇接口所屬的后端系統并且在需要填寫接口地址的地方只能填寫接口的相對路徑,這一點與配置列表頁相同
對于所有的詳情頁而言,它們都需要將詳情數據展示在界面上,在這里暫且將這些數據統稱為詳情頁面數據。在我們公司的業務系統中通常通過詳情 ID 或者其他的參數從接口中獲取頁面數據
在頁面可視化搭建系統中有兩種方式獲取頁面數據,分別是:
填寫獲取頁面數據的接口地址,這種方式將大部分的工作都交給視圖頁自動完成,最為簡單。
在配置頁自定義函數得到頁面數據,在這里支持 promise 和 同步執行的函數,這種方式很靈活
先介紹第一種方式,界面如下:
在接口地址輸入框中,可以填寫類似這樣的內容/basic/someApi/detail?poId=202004130000121&type&code=333,視圖頁在拿到 json schema 去生成視圖的時候會將poId,type和code 作為接口的參數,并且視圖頁會優先從瀏覽器地址欄中取這些參數的值,如果瀏覽器不存在某個參數,程序就使用 json schema 中給定的值。例如:瀏覽器地址欄的查詢字符串為?po_id=99&type=2,視圖頁在請求/basic/someApi/detail這個接口時,傳給接口的參數為:{po_id:99,type:2,code:333}。這種方式會將接口返回的content字段當前頁面數據
根據接口地址輸入框中的值與瀏覽器地址欄中的 query 獲取接口參數的代碼如下:
/**
* 從 query 中得到接口的參數
*@param params
*@param query
*@returns{{[p: string]: *}}
*/
exportfunctiongetParams(params, query){
constresult = {
...params };Object.keys(result).forEach(key=>{
// 用瀏覽器 query 中的參數值替換 params 中的值
if(query[key]){
result[key] = query[key]
}
});
return result
}
第二種方式:在配置頁自定義函數得到頁面數據,這種方式你只需要寫函數體,并且必須有一個返回值,界面如下:
這種方式支持 promise 和同步執行的函數。如果函數返回 promise,視圖頁會將 promise resolve 的值當作頁面數據,如果是同步執行的函數,視圖頁會將同步函數的返回值當作頁面數據
結合這兩種方式視圖頁獲取頁面數據的代碼如下:
/**
* 獲取頁面數據
*@param pageConfig 頁面配置
*@param vm 詳情頁的 Vue 實例
*@returns{Promise<any | never>}
*/
exportfunctionfetchPageData({pageConfig,vm}){
returnnewPromise((resolve, reject) =>{
// 從接口中獲取頁面數據
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)
})
}
// 通過自定義函數獲取頁面數據
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
})
}
組件的配置參數
如下是一個輸入框組件的配置:
{
"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 **枚舉數據 array,object,function
url 獲取枚舉數據或者 UI 組件數據的接口地址 string
**items **組件的子組件的配置字段 array
**trigger **Type字段校驗時機 string
**visible **字段是否可見 boolean
eventsUI 組件的事件 Object
x-props 字段的擴展屬性 object
**x-component **字段的 UI 組件名 string
**x-component-props **字段 UI 組件的屬性 object
**x-linkages **字段聯動 array
**x-rules **字段規則 object
x-props 數據屬性
屬性名 描述 類型
**style **字段的 UI 組件的 style 樣式 object
className 字段的 UI 組件的 className string
**label **字段的 UI 組件的枚舉 label 取值路徑 string
value 字段的 UI 組件的枚舉 value 取值路徑 string
**button **Type按鈕的操作類型 string
render 自定義組件的顯示內容 function
**buttonSubmitUrl **提交按鈕的接口地址 string
**paging **列表是否分頁 boolean
x-rules 數據屬性
屬性名描述類型
**format **字段值類型 string
**required **是否必填 boolean
**pattern **正則 RegExp,string
**max **最大長度 number
**min **最小長度 number
**len **長度 number
**maximum **最大數值 number
minimum 最小數值 number
**message **錯誤文案 string
format 的可選值:url,郵箱,手機號,金額,數字
x-linkages 字段聯動
屬性名描述類型可選值
**type **聯動類型 String linkage:hidden,linkage:disabled,linkage:value
subscribe 聯動訂閱器 Function-
下面以文本組件,下拉框組件,按鈕組件為例進行說明
文本組件
文件組件用于在詳情頁中顯示某個字段對應的值,他的配置界面如下:
先介紹非自定義文本組件顯示內容的情況,這個時候文本組件的取值路徑是必填項的,視圖頁會根據取值路徑從頁面數據中取文本組件的顯示內容。取值路徑還支持在路徑后面增加過濾器,這里的過濾器和Vue 中的過濾器功能一致。取值路徑例如為:
create_at|formatDate('datetime'): 從頁面數據的create_at字段中取值,然后使用formatDate格式化create_at字段對應值
簡化代碼如下:
... vue 組件
computed:{
//使用計算屬性得到文本組件要顯示的內容
textContent(){ const p =this.fieldSchema.path.split('|')
//如果填寫了取值路徑
if(formatPathStr(p[0])) {
const filters = p.slice(1)
//這里的 Path 指 cool-path
const path =newPath(p[0]);
//從頁面數據中取值
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+'執行過濾器時拼接參數出錯了')
}//根據過濾器名得到過濾器對應的方法
const filterFn =this.$options.filters &&this.$options.filters[fn]
if(typeoffilterFn =='function') {
returnfilterFn.apply(this, args)
}returnval
}}
從上面的配置文件組件的可視化界面中可以看到,我們還可以配置文本組件的枚舉數據,這個枚舉數據主要是考慮到接口返回的頁面數據中的某些字段是數字或者英語單詞,但是在界面上我們需要顯示這些字段的中文含義,枚舉數據可以是從接口中獲取會可以在配置頁中寫死,枚舉數據的獲取方式與上面介紹的頁面數據獲取方式類似,在這里不再贅述
不自定義文本組件顯示內容已經可以滿足大部分使用場景,這種方式有一個局限性:一個文本組件只能顯示一個字段的值,在某些時候可能需要將多個字段合并在一個文本組件中顯示在界面,在這種情況下我使用Vue的渲染函數來自定義文本組件的顯示內容。自定義的渲染函數類似于下面這樣:
returnh('div',[
h('span',pageData.user.name),
h('span',pageData.uesr.age)
])
在視圖頁在渲染視圖時會執行文件組件的渲染函數,簡化代碼如下:
<!--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>
在可視化創建詳情頁中,除了文本組件支持寫渲染函數之外,表格組件中的列也支持寫渲染函數
下拉框組件
下拉框組件的配置界面如下:
下拉框組件有三個區域進行配置,在這里著重介紹下拉框的顯示配置和聯動配置,先介紹顯示配置再介紹聯動配置
下拉框是一個表單組件,它除了可以對數據進行展示還可以對數據進行修改,我將表單組件的值(即:組件 value 屬性對應的值)存放在 vuex 中。對于詳情頁而言,表單組件需要顯示它的初始值,表單的初始值位于頁面數據中,為了讓表單組件在 vuex 中取到它要展示的值,在表單組件 created 鉤子函數中,我將這個表單組件在頁面數據中的值另存到 vuex 中,在此之后表單組件取值和修改值都是針對 vuex 中的數據進行操作,簡化之后的代碼如下:
<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(){
// 從頁面數據中取表單組件的初始值
let initValue = new Path(this.fieldSchema.path).getIn(this.pageData)
// 將表單組件的初始值保存到 Vuex 的 formData 中
this.saveFormData({name:this.fieldSchema.key,value:initValue})
}
}
}
</script>
下拉組件除了要顯示選中的值,還需要備選數據,它的備選數據可以通過從接口中獲取也可以在配置中寫死,支持返回一個 promise,返回同步計算的值或者填寫 url。下拉框的備選數據獲取方式與上面介紹的頁面數據的獲取方式類似,不再贅述
表單聯動配置
表單聯動是指:這個表單組件的狀態受其他表單組件的值的影響,目前支持的聯動類型有:隱藏、禁用、組件值聯動。聯動訂閱器用于觀察 formData 中值的變化,針對表單組件的聯動類型對組件的狀態作出影響。聯動訂閱器是一個函數,在視圖頁中使用聯動訂閱器計算計算屬性的值,所以只要在聯動訂閱器中訪問的值發生了變化,就會重新計算計算屬性,進而影響組件的狀態。簡化的代碼如下:
<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>
表單組件的值聯動比隱藏聯動和禁用聯動要復雜一些,這是因為聯動訂閱器可以改變表單組件的值,表單組件它自身也可以改變它的值。表單組件的值由最后一次變化為準
對于禁用聯動,它的聯動訂閱器中可填寫的內容如下:
if(formData.status +''==='2') {
returntrue
}else{
returnfalse
}
上面的聯動訂閱器表示:當 vuex 中的 formData.status 等于 2 時,這個表單組件會被禁用
對于隱藏聯動,它的聯動訂閱器中可填寫的內容如下:
if(formData.username.length >3) {
returntrue
}else{
returnfalse
}
上面的聯動訂閱器表示:當 vuex 中的 formData.username 的長度 > 3 時,這個表單組件會被隱藏
對于值聯動,它的聯動訂閱器中可以填寫的內容如下:
if(formData.id) {
return3
}else{
return''
}
上面的聯動訂閱器表示:當 vuex 中的 formData.id 為 truly 時,這個表單組件的值會被置為 3
按鈕組件
按鈕組件的配置界面如下:
按鈕組件是一種比較特別的組件,與其他組件相比它的操作行為不固定而且影響范圍比較廣。根據業務需求分為三種操作類型,分別是:提交(即:將表單數據提交到服務器),重置(即:將表單組件的值重置為初始狀態),自定義(即:自定義按鈕的點擊事件處理程序)。在下面只介紹提交和自定義這兩種類型
提交操作
通常在將表單數據提交到服務器之前,我們需要對表單數據進行校驗,只有所有的數據符合要求才能將表單數據提交到服務器,否則將錯誤語顯示到界面上。為了滿足這個需求,我們需要在按鈕提交事件的處理程序中訪問到所有的表單數據以及表單組件的數據校驗規則,由于表單數據保存在 Vuex 中,并且存放數據校驗規則的 json schema 在視圖頁中全局共享,所以在提交事件處理程序中能夠很容易拿到想要的數據。需要注意的是,如果某個表單組件的數據沒有通過校驗,它錯誤信息要顯示在表單組件所在的位置,這就意味著消費錯誤信息的位置和生成錯誤信息的位置不相同
我將對錯誤信息進行操作的方法收集到單獨的模塊中。簡化代碼如下:
/**
*表單錯誤收集器
**/
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]:''
} } }})
錯誤信息收集器是一個 Vue 實例,在每個表單組件中引入錯誤信息收集器,并且將它作為組件的一個 data 屬性,錯誤信息作為組件的計算屬性,這樣一來只要錯誤信息收集器中的數據發生變化界面就會更新,簡化代碼如下:
<template>
<!-- do something-->
{{ errorMsg }}
exportdefault{
data(){
return{
errorCollector:errorCollector
}
},
computed:{
errorMsg(){
returnthis.errorCollector.errorObj[this.fieldSchema.key]
}
}
}
自定義操作
自定義操作實際上 json schema 中定義按鈕的點擊事件處理程序,在視圖頁中的實現比較簡單
如何使用
在開發環境 json schema 保存在數據庫,要在測試環境和生產環境使用 json schema 生成頁面,需要將 json schema 下載到項目中的一個特定文件夾中,當在瀏覽器中訪問這個視圖頁時,會根據頁面 ID 到下載好的靜態文件中讀取頁面的 json schema,然后視圖頁將頁面渲染出來
從靜態文件中讀取配置代碼如下:
import("@static/jsons/tables/table_string_"+id+".json").then(fileContent=>{
console.log('配置數據:',fileContent)
})
json 文件中保存的 json schema 是一個字符串,但是在視圖頁渲染界面的時候需要的是一個對象,并且對象的某些字段必須是函數。為了將字符串轉成需要的格式,我使用 new Function('return ' + strConfig)() 來完成這一需求,簡化代碼如下:
functionparseStrConfig(jsonSchema){
returnnewFunction('return '+ jsonSchema)();
}
存在的不足
1.生產出的頁面不能獨立于頁面搭建系統運行。要想在其他系統中使用生成的頁面,必須在對應系統中使用 iframe 或者 single-spa 微前端技術引入頁面搭建系統
2.頁面的 json schema 沒有與頁面搭建系統獨立。由于每創建一個頁面就要該頁面的 json schema 下載到頁面可視化搭建系統中,這導致頁面可視化搭建系統需要被頻繁的發布,但是頁面可視化搭建系統的業務功能相對穩定