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

百姓網技術團隊發表於2017-11-09

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

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

需求

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

  • 提供模板
  • 主題色
  • 豐富的功能模組,如圖文、輪播、相簿等
  • 模組可以拖拽以及根據需求配置
  • 支援建立多個頁面的網站
  • 頁面可以分欄分割槽,有一些佈局上的變化
  • 網站支援手持裝置

需求分析

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

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

  • 頁面:一個網站由一個或多個頁面(Page)組成。

  • 分欄/分割槽:頁面由不同的功能區組成,比如公司介紹、成功案例、聯絡我們等。它們可能是縱向排列的,也可能左右分欄排布,我們取個更恰當的名字 “區塊”(Section)。

  • 模組:每個區塊包含一個或多個元件,這些元件組合起來達到同一個目的。例如公司介紹這個區塊可以用一段文字模組介紹公司大體情況,用一個輪播模組展示公司主打產品。這裡所說的模組和我們熟悉的 “元件”(Component)劃等號,是我們要實現的最小功能單元。

  • 模板:模板可以有很多種定義。此處我們可以理解為一個頁面的佈局,類似於 QQ 空間可以選擇的分欄佈局。模板定義了一個頁面包含的區塊數量和每個區塊的橫向佔比

  • 主題色:每個網站都應有自己的風格。可以簡單認為:風格 = 模板 + 主題色,不同的模板搭配不同的主題色形成了不同風格的網站。

網站的資料表示

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

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

用 JSON 格式可以把它表示成:

{
  "id": 1,
  "name": "xxx公司"
  "type": "site",
  "children": [{
    "type": "page",
    "name": "首頁",
    "children": [{
      "type": "section",
      "name": "公司簡介",
      "children": [{
        "type": "paragraph" 
      }, {
        "type": "carousel"
      }]
    }]
  }]
}複製程式碼
  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%。

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

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

從資料到網站

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

兩步走:

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

第一步是個 “體力活”,此處以單段文字模組為例:

<!-- 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.Draggablegithub.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 元件上監聽 addsort 事件呼叫 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 框架分擔了我們的工作,提高了開發效率,使得我們可以專注於業務邏輯設計,這也是框架價值的體現。

附註


作者:唐鶴俊
簡介:百姓網前端工程師。

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


本文在 “百姓網技術團隊” 微信公眾號首發,掃碼立即訂閱:

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

相關文章