如何用 Vue.js 實現一個建站應用

隨著互聯網飛速發展,越來越多的傳統服務搬到了線上,商家急需一個官網介紹自己的產品,提高知名度。因此 「建站」 成為了一種剛需。本文就如何用 Vue.js 實現一個建站應用提供了解決思路。

作為前端工程師,相信大家都寫過不少網站和應用,我把網站簡單的分為 「表現型」 和 「操作型」。表現型可以是一個產品介紹網站,而操作型的典型代表是管理後台。不久前有機會參與一個建站項目的設計與開發,它屬於操作類型網站但需要更深一層的抽象,它是一個 「創建網站的網站」。本文嘗試基於 Vue.js 框架設計實現這樣一個應用,用戶通過拖拽模塊就可以建站。希望通過本文的介紹,能夠帶給大家不一樣的視角。

需求

獲取需求是開始項目的第一步。一般來說建站工具大多提供以下功能:

  • 提供模板
  • 主題色
  • 豐富的功能模塊,如圖文、輪播、相冊等
  • 模塊可以拖拽以及根據需求配置
  • 支持創建多個頁面的網站
  • 頁面可以分欄分區,有一些布局上的變化
  • 網站支持手持設備

需求分析

我們在開始動手之前可以先分析一下需求。

從需求中可以提取到幾個關鍵詞:「模板」、「主題色」、「模塊」、「頁面」、「分欄」 。明確這些關鍵詞的意義將有助於我們接下來的設計。

  • 頁面:一個網站由一個或多個頁面(Page)組成。
  • 分欄/分區:頁面由不同的功能區組成,比如公司介紹、成功案例、聯繫我們等。它們可能是縱向排列的,也可能左右分欄排布,我們取個更恰當的名字 「區塊」(Section)。
  • 模塊:每個區塊包含一個或多個組件,這些組件組合起來達到同一個目的。例如公司介紹這個區塊可以用一段文字模塊介紹公司大體情況,用一個輪播模塊展示公司主打產品。這裡所說的模塊和我們熟悉的 「組件」(Component)劃等號,是我們要實現的最小功能單元。
  • 模板:模板可以有很多種定義。此處我們可以理解為一個頁面的布局,類似於 QQ 空間可以選擇的分欄布局。模板定義了一個頁面包含的區塊數量和每個區塊的橫向佔比
  • 主題色:每個網站都應有自己的風格。可以簡單認為:風格 = 模板 + 主題色,不同的模板搭配不同的主題色形成了不同風格的網站。

網站的數據表示

那麼問題來了,我們應該如何存儲我們的網站呢?換句話說,存在資料庫裡面的是一個怎樣的數據結構?是一個包含 HTML 的超大字元串嗎?不急,基於上面的關鍵詞定義我們可以總結一下:

  1. 一個網站包含幾個頁面,一個頁面包含幾個區塊,一個區塊包含幾個模塊。可以看出這是一個樹形結構,見下圖。

用 JSON 格式可以把它表示成

{n "id": 1,n "name": "xxx公司"n "type": "site",n "children": [{n "type": "page",n "name": "首頁",n "children": [{n "type": "section",n "name": "公司簡介",n "children": [{n "type": "paragraph"n }, {n "type": "carousel"n }]n }]n }]n}n

  1. 那麼模塊就是樹中的葉子節點,需求中要求模塊可以配置,我們可以把配置分為兩部分:包含的內容(content)和設置(config)。舉例來說,輪播模塊中 content 存放的是幾張圖片的 URL,config 可以是輪播切換的動畫效果、是否開啟自動播放等設置。
  2. 與模塊一樣,site、page、section 都是樹中的節點,都可以根據需要在節點上增加 content 和 config。只不過對於這幾類節點來說 content 其實就是 children,只有 config 屬性。
  3. 主題色是網站節點的配置項,可以在site.config中增加themeColor屬性來表示。
  4. 如何支持手持設備呢?前面說到模板定義了板塊的橫向佔比,所以在板塊的 config 屬性中可以配置該板塊在不同尺寸的橫向佔比。若採用 Bootstrap 的 12 欄柵格系統的話,可以很方便的通過設置 class 來達到目的。例如某個板塊的 config 中class="col-xs-12 col-sm-6 col-md-3"表示該板塊在手機下橫向占 100%、平板占 50%、PC 占 25%。

綜上,一個網站可以完整的表示為一個樹形 JSON。該樹中包含了所有頁面、板塊、模塊的內容和配置。

從數據到網站

我們已經有了網站的數據表示,那麼下一個問題是如何從數據中渲染出網站呈現給用戶呢?其實我們只要想辦法渲染這棵 JSON 樹就行了。

兩步走:

  1. 編寫每個節點的代碼,每個節點接受node屬性和themeColor
  2. 遍歷 JSON 樹,在對應位置渲染對應的節點(即父子節點的包含關係)

第一步是個 「體力活」,此處以單段文字模塊為例:

<!-- Paragraph.vue -->n<template>n <div>n <h1 :style="{color: themeColor}">{{node.content.title}}</h1>n <small v-if="node.config.showSubTitle">{{node.content.subTitle}}</small>n <p>{{node.content.detail}}</p>n </div>n</template>nn<script>nexport default {n name: paragraph,n props: [node, themeColor]n}n</script>n

完成所有節點代碼編寫之後,第二步,我們需要寫一個類似於 「renderer」 的組件來遞歸的渲染 JSON 樹。基本思路是該組件先渲染自己,然後渲染自己的後代,每個後代也重複此渲染過程,如此渲染整棵樹。

這裡需要根據節點的type屬性也就是一個 String 來獲取對應的組件定義。幸運的是 Vue.js 中已經有這樣的動態組件Component,此組件的is屬性接受一個 String。由此我們的 render 組件可以這樣寫:

<!-- render.vue -->n<tempplate>n <component :is="node.type" :node="node" :theme="themeColor">n <render v-for="child in node.children" :key="child.id" :node="child" :theme="themeColor" />n </component>n</tempplate>nn<script>n// 導入JSON 樹中所涉及的所有節點nimport Page from ./Page.vuenimport Section from ./Section.vuenimport Paragraph from ./Paragraph.vuennexport default {n name: render,n props: [node, themeColor],n components: {n Page,n Section,n Paragraphn }n}n</script>n

註:若 Vue.js 沒有提供動態 Component 組件,我們也可以利用 Vue.js 中的createElement方法自己實現該組件,詳見此 gist(gist.github.com/github-)。

至此,我們已經設計了網站的數據表示,以及從數據到頁面的渲染。那麼這棵 JSON 樹從何而來呢?

編輯與保存

要創建一個網站,用戶在後台會經歷選擇樣板站、調整色調、拖拽模塊、編輯模塊內容和配置、保存等操作。值得注意的是,此處用戶選擇的樣板站跟文章開頭的模板定義有些差別。如果說模板是網站的骨架的話,那樣板站是填充了初始數據(默認色調、板塊中包含的模塊以及模塊的默認內容)的模板。

從數據的角度來看,可以更清楚地看到這些步驟是如何逐步生成這棵樹的。

界面操作影響數據選擇模板(樣板站)該模板定義的初始樹,包含默認的色調和模塊選擇色調更新site.config.themeColor拖拽模塊到區域中在對應的section.children的數組中 push 一個組件節點在區域中排序模塊在對應的section.children的數組中重新設置組件節點的 index編輯模塊內容和配置更新對應模塊的 content 和 config 中的屬性保存網站把 JSON 樹存入資料庫持久化

既然選擇了 Vue.js,我們可以選擇官方推薦的 Vuex(vuex.vuejs.org/en/)來管理狀態。

首先創建一個 Vuex 實例,該實例包含一個 site 對象和一些對節點的操作:

// store.jsnimport Vue from vuenimport Vuex from vuexnnVue.use(Vuex)nconst store = new Vuex.Store({n state: {n site: {}n },n mutations: {n changeThemeColor () {},n addModule () {},n sortModule () {},n removeModule () {},n updateModule () {}n },n actions: {n getSite () {},n saveSite () {}n }n})n

理論上有了這些方法我們已經可以從通過程序更新這棵樹了,但作為編輯後台,我們還要提供一些界面上的入口讓用戶來編輯這棵樹。

也就是說,我們需要在編輯後台渲染一個可以編輯的網站,在網站上線後渲染一個只讀的網站,即同一個 JSON 樹需要渲染兩次。因為 renderer 是根據節點的 type 來渲染對應的組件的,所以對於編輯後台我們需要給每個節點取另一個 type,比如統一加一個前綴edit-。例如 Paragraph 這個組件的編輯組件可以編寫如下:

<!-- EditParagraph.vue -->n<template>n <edit-wrapper>n <paragraph :node="node" :themeColor="themeColor" />n </edit-wrapper>n</template>nn<script>n// EditWrapper提供統一的編輯入口,內部仍需要渲染一次 Paragraph 便於實時預覽編輯結果nimport EditWrapper from ./EditWrapper.vuenimport Paragraph from ./Paragraph.vuenimport { mapMutations } from vuexnnexport default {n name: edit-paragraph,n props: [node, themeColor],n methods: { ...mapMutations([updateModule]) }n}n</script>n

用戶可以通過這個組件的界面入口編輯 Paragraph 這個模塊(此處略去edit-wrapper的實現,事實上組件的編輯可以通過彈窗加表單實現,也可以是更方便的 inline editing )。

如此一來,每個模塊都有一個對應的編輯模塊,在兩次渲染的時候便存在一個轉換的過程。假設在資料庫中存的是只讀的樹,那麼在編輯後台獲取到該樹時需要轉換成可編輯樹從而渲染成帶有編輯入口的網站,在保存時則需要轉換成只讀樹保存。轉換的過程其實很簡單,把每個節點的 type 屬性增加或刪除前綴即可。

拖拽功能

我們選用拖拽的方式在區域中加入模塊以及排序模塊。有很多開源的庫幫我們做了拖拽的實現,甚至有 Vue.js 的封裝。此處推薦Vue.Draggable(github.com/SortableJS/Vue.Draggable)這個庫,它是基於 Sortable.js 做的一層封裝。其典型的應用場景如下:

<draggable v-model="myArray" :options="{group:people}" @start="drag=true" @end="drag=false">n <div v-for="element in myArray">{{element.name}}</div>n</draggable>n

在我們的場景中,只需在draggable組件上監聽addsort事件調用 store 中對應的方法即可。

如本節表格所述,拖拽只是界面上的操作方式,本質上是對數組中元素的增加和調整 index 的操作。

變更檢測

為了及時保存用戶的編輯,我們可以在用戶修改主題色、編輯模塊、刪除模塊等操作時自動保存。本質上是需要檢測 store 中 site 這個狀態的變更。Vuex 中的插件(plugin)可以針對改動做一些類如記錄日誌和持久化的操作,我們可以寫一個 autoSave 的插件來實現。

// store.jsnconst autoSave = (store) => {n store.watch(n state => state.site,n (newV, oldV) => {n store.dispatch(saveSite)n },n { deep: true }n )n}nnconst store = new Vuex.Store({n state: {n site: {}n },n plugins: [autoSave]n})n

至此,我們已經用 Vue.js 和相關技術實現了文章開頭列出的建站應用的需求。

結語

本文從需求分析、方案設計、編碼實現分別介紹了用 Vue.js 寫一個建站應用可能會遇到的問題以及大致的思路。可以看出,文章很大篇幅都在講數據,包括數據格式的設計、數據的操作、數據變更的檢測。這是因為 Vue.js 框架幫我們做了從數據到界面的渲染以及數據變更後界面的更新的工作,我們要做的是管理好這些數據。Vue.js 框架分擔了我們的工作,提高了開發效率,使得我們可以專註於業務邏輯設計,這也是框架價值的體現。

附註

  • demo 地址:github.com/github-libra/lego

作者:唐鶴俊

簡介:百姓網前端工程師。

本文僅為作者個人觀點,不代表百姓網立場。


本文在 「百姓網技術團隊」 微信公眾號首發,點此掃碼訂閱。


推薦閱讀:

已放棄了遊戲開發,不知道選擇什麼開發?
vue-router 源碼分析-history

TAG:Vuejs | 前端开发 |