本文主要介紹如何設計一個高擴展的在線網頁製做平臺,會交代一些背景和最終的效果以及核心設計方案。體驗地址: godspen.ymm56.comjavascript
官網: godspen.ymm56.com/css
開源代碼: github.com/ymm-tech/go…html
使用手冊: godspen.ymm56.com/doc/cookboo…前端
在線體驗: godspen.ymm56.com/admin/#/hom…vue
私有部署: godspen.ymm56.com/doc/cookboo…java
運營活動對一些簡單的動畫提供支持,方便作一些入場和出場的動畫,提高活動的交互感,咱們使用了 animate.css 提供的一套css動畫。下面提供簡單的展現 node
合成組件就是選擇已有的節點保存爲一個通用的組件,方便下次直接使用git
頁面模板的目的和組合組件相似,都是提供已經作好的內容,運營快速選擇使用達到快速上線活動的目的,下面是簡單演示 github
2018年3月份開始,隨着運滿滿的快速發展,開始在頻繁的迭代各類活動,那時最快的方式就是拷貝老的活動項目,而後按需求修改,接着上線,然而這種方式很快就遇到了瓶頸,迫使運營團隊也會去尋找一些第三方平臺去知足本身的運營要求,不過因爲定製化弱和用戶信息沒打通致使沒辦法大量使用,仍是隻能等待前端資源排期,兩個比較突出的問題。編程
針對這些問題團隊迫切須要一個平臺來提供運營快速建立活動,開發也能在這平臺作一些功能擴展。最好能知足已下幾個要求:
針對這些要求咱們作了碼良平臺,碼良是一個在線H5編輯器,用於快速製做H5頁面。用戶無需掌握複雜的編程技術,經過簡單拖拽、少許配置便可製做精美的頁面,可用於營銷場景下的頁面製做。同時,也爲開發者提供了完備的編程接入能力,經過腳本和組件的形式得到強大的組件行爲和交互控制能力。
下面會分享下咱們的核心設計,此次主要重點說明下面幾方面內容
{
"id": "truck/button1l",
"type": "truck/button",
"label": "按鈕1l",
"version": "0.1.4",
"visible": true,
"style": {
"position": "absolute",
"width": "100px",
"height": "40px"
},
"animate": [],
"props": {
"text": "輸入文字",
"type": "danger",
"click": []
},
"path": "https://ymm-maliang.oss-cn-hangzhou.aliyuncs.com/truck/button/0.1.4/index.js",
"script": "",
"events": []
}
複製代碼
每一個組件比較核心的元素由以下幾部分組成
上圖的頁面包括一個圖片,圖片下面兩個文字,圖片兄弟節點有個按鈕元素。對應頁面的詳細數據結構以下,能夠感覺下完整結構。
{
"id": "node",
"type": "node",
"visible": true,
"style": {
},
"props": {},
"child": [
{
"id": "truck/image15j",
"type": "truck/image",
"label": "圖片15j",
"version": "0.1.4",
"visible": true,
"style": {
"position": "absolute",
"width": "320px"
},
"animate": [],
"props": {
"url": "https://ymm-maliang.oss-cn-hangzhou.aliyuncs.com/ymm-maliang/access/ymm_1533366999689.png",
"click": []
},
"path": "https://ymm-maliang.oss-cn-hangzhou.aliyuncs.com/truck/image/0.1.4/index.js",
"script": "",
"events": [],
"child": [
{
"id": "truck/text3l",
"type": "truck/text",
"label": "文本3l",
"version": "0.1.4",
"visible": true,
"style": {
"position": "absolute"
},
"animate": [],
"props": {
"text": "文字內容1",
"click": []
},
"path": "https://ymm-maliang.oss-cn-hangzhou.aliyuncs.com/truck/text/0.1.4/index.js",
"script": "",
"events": []
},
{
"id": "truck/text3l5g",
"type": "truck/text",
"label": "文本3l",
"version": "0.1.4",
"visible": true,
"style": {
"position": "absolute",
"width": "114px"
},
"animate": [],
"props": {
"text": "文字內容2",
"click": []
},
"path": "https://ymm-maliang.oss-cn-hangzhou.aliyuncs.com/truck/text/0.1.4/index.js",
"script": "",
"events": []
}
]
},
{
"id": "truck/button1l",
"type": "truck/button",
"label": "按鈕1l",
"version": "0.1.4",
"visible": true,
"style": {
},
"animate": [],
"props": {
"text": "輸入文字",
"type": "danger",
"click": []
},
"path": "https://ymm-maliang.oss-cn-hangzhou.aliyuncs.com/truck/button/0.1.4/index.js",
"script": "",
"events": []
}
],
"script": [],
"animate": [],
"version": "0.1.0",
"events": []
}
複製代碼
一句話小結:頁面是由不少節點遞歸生成,每一個節點包含佈局,事件,腳本,參數,版本等信息,而後編輯器編輯這些信息,解析器解析這些信息。
一個頁面都是由一個個遞歸嵌套的組件組成,組件是整個項目的最核心的一部分,爲了讓組件具備擴展能力,咱們對組件的功能使用了 mixin 方式,經過基礎組件邏輯+自定義腳本的形式來生成組件。下面介紹下總體組件結構和初始化流程,方便理解咱們是如何實現的。
<div class="node" v-show="visible" :style="nodeInfo.style">
<component :is="nodeInfo.id" v-bind="nodeInfo.props" :ref="nodeInfo.id" :style="componentStyle"></component>
<node v-if="nodeInfo.child" :info="item" v-for="item in nodeInfo.child " :key="item.id"></node>
</div>
複製代碼
一個節點的邏輯功能=組件邏輯+腳本1+腳本2+腳本3... 每一個組件在根據本身的類型加載對應js腳本後,會對該組件 nodeInfo.script 裏面的 邏輯進行mixin. 而後建立一個最終的組件註冊到Vue.component 裏面方便後續使用,核心代碼以下
// 經過加載到的組件腳本得到的全局對象建立vue對象 window['image_1.0.3'] load組件腳本運行後會生成的對象
var component = Vue.extend( window['image_1.0.3'])
// 遍歷全部加入的腳本混合組件對象中
nodeInfo.script.forEach((value)=>{
component =component.extent(value)
})
// 以節點id爲key,註冊最終組件對象
Vue.component(nodeInfo.id,component)
// 修改該節點的動態組件 :is 參數爲 該節點id
// done
複製代碼
一句話小結:經過不斷的mixin新的自定義腳本進來擴展組件能力
屬性編輯主要目的是開發組件的人會暴露一些可配置的參數給運營人員在編輯器裏面填寫和修改。 好比選擇一個組件後再右側屬性面板能夠對這個組件進行一些屬性設置.
下面針對這兩塊比較核心的內容說明下咱們如何作的。
對於一個組件的開發者來講,一是定義該組件那些參數須要暴露到編輯器讓運營操做,二是定義該屬性對應的值經過什麼控件操做。 上文在總體架構數據結構中提到了每一個node節點都有一個 props 屬性,該屬性就是存放着該組件可配置的參數所配置的最終值,在初始化組件的時候會把這個 props的數據傳入組件進行初始化。而定義一個組件能接受那些參數則是在每一個Vue組件的props 屬性上定義, 而編輯器的做用就是經過編輯器去獲取到每一個對象定義的props,而後根據每一個參數的類型提供不一樣的編輯控件,好比 boolean 咱們會提供 切換按鈕,image 咱們會提供選擇圖片控件等等。擴展腳本一樣能夠擴展組件的可編輯屬性,下面是一個擴展腳本的例子。主要說明支持的那些類型,可定義的格式。總體流程以下。
下面咱們先看一下每一個組件可定義的props 例子。
/** * * @param type: 字段類型,支持原生類型以及【碼良輸入類型】 * * 碼良輸入類型: * input 單行輸入框 * text 多行輸入框 * enum 列表單選 需提供選項字段defaultList, 支持數組、map結構 * image 圖片選擇 * audio 音頻選擇 * video 視頻選擇 * richtext 富文本 * number 數字 * function 方法設置 * data json數據 * date 時間選擇 * checkbox 多選框 同enum 不提供defaultList字段時,輸入值爲布爾類型 * radio 單選框 同enum * */
return {
props: {
// 原生類型
foo: {
type: String
},
// 圖片輸入
fooImage: {
type: String,
editer: {
type: 'image'
}
},
// 日期
fooDate: {
editer: {
type: 'date'
}
},
// checkbox 多選
fooCheckbox: {
type: Array, // 此項必須爲Array
default: () => { // 且需提供初始值
return [] // ['day', 'hour', 'min', 'sec']
},
editer: {
label: '顯示精度',
type: 'checkbox',
defaultList: [ // array 形式的選項
'day',
'hour',
'min',
'sec',
]
}
},
// checkbox 布爾
fooCheckboxBool: {
type: Boolean, // 此項必須爲Boolean
editer: {
type: 'checkbox'
}
},
// enum 含選項
fooEnum: {
default: 'value1',
type: String,
editer: {
label: '我是字段名', // 將字段名顯示爲可讀性更強的文本,不提供此項時,顯示字段名
desc: '我是幫助文本', // 爲字段提供提示信息,幫助理解字段的意義
type: 'enum',
defaultList: { // map結構的選項 key爲值,value爲顯示文本
'value1': '條件1',
'value2': '條件2',
'value3': '條件3',
}
}
},
// 條件屬性
ifFoo1: {
type: [Number],
default: 0,
editer: {
work: function () {
return this.fooEnum == 'value1' // 只有當 `fooEnum` 字段取值爲 'value1' 時才顯示此項
},
label: '條件屬性1',
type: 'number',
}
},
ifFoo2: {
type: [Date, String],
default: null,
editer: {
work: function () {
return this.fooEnum == 'value2' // 只有當 `fooEnum` 字段取值爲 'value2' 時才顯示此項
},
label: '條件屬性2',
type: 'date',
}
},
},
mounted: function () {
console.log('hello ' + this.foo)
console.log('hello ' + this.fooImage)
// ...
}
}
複製代碼
上面腳本擴展的組件對應的增長的可配置的屬性以下圖。
這裏面的的主要設計在於每一個props屬性裏面添加了一個 editer字段進行該字段在編輯器環境下提供什麼組件對該屬性進行編輯。editer的字段主要包括以下。
{
label: '我是字段名', // 將字段名顯示爲可讀性更強的文本,不提供此項時,顯示字段名
desc: '我是幫助文本', // 爲字段提供提示信息,幫助理解字段的意義
type: 'enum',
ignore: true, // 不在編輯器顯示
work:function(){
// 若是知足什麼條件纔會顯示
},
defaultList: { // map結構的選項 key爲值,value爲顯示文本
'value1': '條件1',
'value2': '條件2',
'value3': '條件3',
}
}
複製代碼
一句話小結:編輯器經過獲取每一個組件的props,遍歷每個屬性,按類型提供不一樣的操做控件,編輯生成最終的數據放到 nodeInfo.props上。
不少時候一個組件可配置的屬性按咱們的規劃來講就下面幾種類型。
/** * * @param type: 字段類型,支持原生類型以及【碼良輸入類型】 * * 碼良輸入類型: * input 單行輸入框 * text 多行輸入框 * enum 列表單選 需提供選項字段defaultList, 支持數組、map結構 * image 圖片選擇 * audio 音頻選擇 * video 視頻選擇 * richtext 富文本 * number 數字 * function 方法設置 * data json數據 * date 時間選擇 * checkbox 多選框 同enum 不提供defaultList字段時,輸入值爲布爾類型 * radio 單選框 同enum * */
複製代碼
若是按每一個類型提供一個基本的編輯組件就能完成90%的需求,不過在隨着組件的複雜度增長,每一個組件可配置的屬性變得千奇百怪,各類需求均可能。好比一個簡單的多選,原來的可選項只能寫死,如今須要本身請求接口獲取。但這些邏輯咱們不能作到統一的編輯器裏面,也不能作到組件裏面,因此只能在作組件的時候提供一種機制讓開發組件的同窗開發組件的同時,還能對這個組件開發一個自定義的編輯器,並能整合到咱們的屬性編輯面板中。 總體架構以下,最終效果能夠參考上圖的自定義面板部分
一個組件打包完通常會有兩個必要的腳本,一個是組件對應的js。一個是該組件對應編輯器的腳本js。 整個平臺對編輯器的功能擴展都是相通的,經過加載腳本,建立對象,註冊到Vue,而後經過動態組件渲染。對編輯器屬性的擴展也是同樣。加載對應組件的編輯器腳本,而後按相同的方法進行植入。這裏就不在細講。這裏簡單分享下咱們對一個組件的開發最終的結果。以下圖
爲了提供一套對運營友好,而且高擴展的h5活動製做平臺咱們作了這個碼良平臺。如今碼良的平臺如今支撐着運滿滿天天新增5-10個的新活動頁面的需求,已有活動模板的活動95% 能夠營銷人員經過模板建立,作些樣式或圖片的修改,而後發佈到線上,整個過程就幾分鐘。活動的模板和組件模板也在不斷沉澱,相信沉澱一段時間後隨着模板愈來愈全,對營銷活動的快速製做和可選擇性都會更強。
王坤明 運滿滿上海前端Leader 碼良核心架構設計 魏明圓 碼良項目主要開發 潘阿茹 核心模板貢獻者 柳剛 碼良後臺服務代碼主要貢獻者 彭輝 表單,步驟條等組件貢獻者
問題諮詢聯繫方式:
釘釘羣
微信羣(二維碼可能會過時) 推薦添加上面的釘釘羣