隨着互聯網飛速發展,愈來愈多的傳統服務搬到了線上,商家急需一個官網介紹本身的產品,提升知名度。所以 「建站」 成爲了一種剛需。本文就如何用 Vue.js 實現一個建站應用提供瞭解決思路。javascript
做爲前端工程師,相信你們都寫過很多網站和應用,我把網站簡單的分爲 「表現型」 和 「操做型」。表現型能夠是一個產品介紹網站,而操做型的典型表明是管理後臺。不久前有機會參與一個建站項目的設計與開發,它屬於操做類型網站但須要更深一層的抽象,它是一個 「建立網站的網站」。本文嘗試基於 Vue.js 框架設計實現這樣一個應用,用戶經過拖拽模塊就能夠建站。但願經過本文的介紹,可以帶給你們不同的視角。html
獲取需求是開始項目的第一步。通常來講建站工具大多提供如下功能:前端
咱們在開始動手以前能夠先分析一下需求。vue
從需求中能夠提取到幾個關鍵詞:「模板」、「主題色」、「模塊」、「頁面」、「分欄」 。明確這些關鍵詞的意義將有助於咱們接下來的設計。java
頁面:一個網站由一個或多個頁面(Page)組成。node
分欄/分區:頁面由不一樣的功能區組成,好比公司介紹、成功案例、聯繫咱們等。它們多是縱向排列的,也可能左右分欄排布,咱們取個更恰當的名字 「區塊」(Section)。git
模塊:每一個區塊包含一個或多個組件,這些組件組合起來達到同一個目的。例如公司介紹這個區塊能夠用一段文字模塊介紹公司大致狀況,用一個輪播模塊展現公司主打產品。這裏所說的模塊和咱們熟悉的 「組件」(Component)劃等號,是咱們要實現的最小功能單元。github
模板:模板能夠有不少種定義。此處咱們能夠理解爲一個頁面的佈局,相似於 QQ 空間能夠選擇的分欄佈局。模板定義了一個頁面包含的區塊數量和每一個區塊的橫向佔比。vuex
主題色:每一個網站都應有本身的風格。能夠簡單認爲:風格 = 模板 + 主題色,不一樣的模板搭配不一樣的主題色造成了不一樣風格的網站。數據庫
那麼問題來了,咱們應該如何存儲咱們的網站呢?換句話說,存在數據庫裏面的是一個怎樣的數據結構?是一個包含 HTML 的超大字符串嗎?不急,基於上面的關鍵詞定義咱們能夠總結一下:
用 JSON 格式能夠把它表示成:
{
"id": 1,
"name": "xxx公司"
"type": "site",
"children": [{
"type": "page",
"name": "首頁",
"children": [{
"type": "section",
"name": "公司簡介",
"children": [{
"type": "paragraph"
}, {
"type": "carousel"
}]
}]
}]
}複製代碼
那麼模塊就是樹中的葉子節點,需求中要求模塊能夠配置,咱們能夠把配置分爲兩部分:包含的內容(content)和設置(config)。舉例來講,輪播模塊中 content 存放的是幾張圖片的 URL,config 能夠是輪播切換的動畫效果、是否開啓自動播放等設置。
與模塊同樣,site、page、section 都是樹中的節點,均可以根據須要在節點上增長 content 和 config。只不過對於這幾類節點來講 content 其實就是 children,只有 config 屬性。
主題色是網站節點的配置項,能夠在 site.config
中增長 themeColor
屬性來表示。
如何支持手持設備呢?前面說到模板定義了板塊的橫向佔比,因此在板塊的 config 屬性中能夠配置該板塊在不一樣尺寸的橫向佔比。若採用 Bootstrap 的 12 欄柵格系統的話,能夠很方便的經過設置 class 來達到目的。例如某個板塊的 config 中 class="col-xs-12 col-sm-6 col-md-3"
表示該板塊在手機下橫向佔 100%、平板佔 50%、PC 佔 25%。
綜上,一個網站能夠完整的表示爲一個樹形 JSON。該樹中包含了全部頁面、板塊、模塊的內容和配置。
咱們已經有了網站的數據表示,那麼下一個問題是如何從數據中渲染出網站呈現給用戶呢?其實咱們只要想辦法渲染這棵 JSON 樹就好了。
兩步走:
node
屬性和 themeColor
第一步是個 「體力活」,此處以單段文字模塊爲例:
<!-- Paragraph.vue -->
<template>
<div>
<h1 :style="{color: themeColor}">{{node.content.title}}</h1>
<small v-if="node.config.showSubTitle">{{node.content.subTitle}}</small>
<p>{{node.content.detail}}</p>
</div>
</template>
<script> export default { name: 'paragraph', props: ['node', 'themeColor'] } </script>複製代碼
完成全部節點代碼編寫以後,第二步,咱們須要寫一個相似於 「renderer」 的組件來遞歸的渲染 JSON 樹。基本思路是該組件先渲染本身,而後渲染本身的後代,每一個後代也重複此渲染過程,如此渲染整棵樹。
這裏須要根據節點的 type
屬性也就是一個 String 來獲取對應的組件定義。幸運的是 Vue.js 中已經有這樣的動態組件 Component
,此組件的 is
屬性接受一個 String。由此咱們的 render 組件能夠這樣寫:
<!-- render.vue -->
<tempplate>
<component :is="node.type" :node="node" :theme="themeColor">
<render v-for="child in node.children" :key="child.id" :node="child" :theme="themeColor" />
</component>
</tempplate>
<script> // 導入JSON 樹中所涉及的全部節點 import Page from './Page.vue' import Section from './Section.vue' import Paragraph from './Paragraph.vue' export default { name: 'render', props: ['node', 'themeColor'], components: { Page, Section, Paragraph } } </script>複製代碼
注:若 Vue.js 沒有提供動態 Component 組件,咱們也能夠利用 Vue.js 中的 createElement
方法本身實現該組件,詳見此 gist( gist.github.com/github-libr… )。
至此,咱們已經設計了網站的數據表示,以及從數據到頁面的渲染。那麼這棵 JSON 樹從何而來呢?
要建立一個網站,用戶在後臺會經歷選擇樣板站、調整色調、拖拽模塊、編輯模塊內容和配置、保存等操做。值得注意的是,此處用戶選擇的樣板站跟文章開頭的模板定義有些差異。若是說模板是網站的骨架的話,那樣板站是填充了初始數據(默認色調、板塊中包含的模塊以及模塊的默認內容)的模板。
從數據的角度來看,能夠更清楚地看到這些步驟是如何逐步生成這棵樹的。
界面操做 | 影響數據 |
---|---|
選擇模板(樣板站) | 該模板定義的初始樹,包含默認的色調和模塊 |
選擇色調 | 更新 site.config.themeColor |
拖拽模塊到區域中 | 在對應的 section.children 的數組中 push 一個組件節點 |
在區域中排序模塊 | 在對應的 section.children 的數組中從新設置組件節點的 index |
編輯模塊內容和配置 | 更新對應模塊的 content 和 config 中的屬性 |
保存網站 | 把 JSON 樹存入數據庫持久化 |
既然選擇了 Vue.js,咱們能夠選擇官方推薦的 Vuex( vuex.vuejs.org/en/ )來管理狀態。
首先建立一個 Vuex 實例,該實例包含一個 site 對象和一些對節點的操做:
// store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
site: {}
},
mutations: {
changeThemeColor () {},
addModule () {},
sortModule () {},
removeModule () {},
updateModule () {}
},
actions: {
getSite () {},
saveSite () {}
}
})複製代碼
理論上有了這些方法咱們已經能夠從經過程序更新這棵樹了,但做爲編輯後臺,咱們還要提供一些界面上的入口讓用戶來編輯這棵樹。
也就是說,咱們須要在編輯後臺渲染一個能夠編輯的網站,在網站上線後渲染一個只讀的網站,即同一個 JSON 樹須要渲染兩次。由於 renderer 是根據節點的 type 來渲染對應的組件的,因此對於編輯後臺咱們須要給每一個節點取另外一個 type,好比統一加一個前綴 edit-
。例如 Paragraph 這個組件的編輯組件能夠編寫以下:
<!-- EditParagraph.vue -->
<template>
<edit-wrapper>
<paragraph :node="node" :themeColor="themeColor" />
</edit-wrapper>
</template>
<script> // EditWrapper提供統一的編輯入口,內部仍須要渲染一次 Paragraph 便於實時預覽編輯結果 import EditWrapper from './EditWrapper.vue' import Paragraph from './Paragraph.vue' import { mapMutations } from 'vuex' export default { name: 'edit-paragraph', props: ['node', 'themeColor'], methods: { ...mapMutations(['updateModule']) } } </script>複製代碼
用戶能夠經過這個組件的界面入口編輯 Paragraph 這個模塊(此處略去 edit-wrapper
的實現,事實上組件的編輯能夠經過彈窗加表單實現,也能夠是更方便的 inline editing )。
如此一來,每一個模塊都有一個對應的編輯模塊,在兩次渲染的時候便存在一個轉換的過程。假設在數據庫中存的是隻讀的樹,那麼在編輯後臺獲取到該樹時須要轉換成可編輯樹從而渲染成帶有編輯入口的網站,在保存時則須要轉換成只讀樹保存。轉換的過程其實很簡單,把每一個節點的 type 屬性增長或刪除前綴便可。
咱們選用拖拽的方式在區域中加入模塊以及排序模塊。有不少開源的庫幫咱們作了拖拽的實現,甚至有 Vue.js 的封裝。此處推薦 Vue.Draggable
( github.com/SortableJS/… )這個庫,它是基於 Sortable.js 作的一層封裝。其典型的應用場景以下:
<draggable v-model="myArray" :options="{group:'people'}" @start="drag=true" @end="drag=false">
<div v-for="element in myArray">{{element.name}}</div>
</draggable>複製代碼
在咱們的場景中,只需在 draggable
組件上監聽 add
和 sort
事件調用 store 中對應的方法便可。
如本節表格所述,拖拽只是界面上的操做方式,本質上是對數組中元素的增長和調整 index 的操做。
爲了及時保存用戶的編輯,咱們能夠在用戶修改主題色、編輯模塊、刪除模塊等操做時自動保存。本質上是須要檢測 store 中 site 這個狀態的變動。Vuex 中的插件(plugin)能夠針對改動作一些類如記錄日誌和持久化的操做,咱們能夠寫一個 autoSave 的插件來實現。
// store.js
const autoSave = (store) => {
store.watch(
state => state.site,
(newV, oldV) => {
store.dispatch('saveSite')
},
{ deep: true }
)
}
const store = new Vuex.Store({
state: {
site: {}
},
plugins: [autoSave]
})複製代碼
至此,咱們已經用 Vue.js 和相關技術實現了文章開頭列出的建站應用的需求。
本文從需求分析、方案設計、編碼實現分別介紹了用 Vue.js 寫一個建站應用可能會遇到的問題以及大體的思路。能夠看出,文章很大篇幅都在講數據,包括數據格式的設計、數據的操做、數據變動的檢測。這是由於 Vue.js 框架幫咱們作了從數據到界面的渲染以及數據變動後界面的更新的工做,咱們要作的是管理好這些數據。Vue.js 框架分擔了咱們的工做,提升了開發效率,使得咱們能夠專一於業務邏輯設計,這也是框架價值的體現。
做者:唐鶴俊
簡介:百姓網前端工程師。本文僅爲做者我的觀點,不表明百姓網立場。
本文在 「百姓網技術團隊」 微信公衆號首發,掃碼當即訂閱: