用Vue CLI 3 + Vuetify 製作說明頁面

用Vue CLI 3 + Vuetify 製作說明頁面

本篇最後會產生的頁面在這:

https://plugins.letswrite.tw/


關於「套件集合站」

集合站是寫給同事看的,有一些網站常用的功能不一定要前端手刻才可以使用,網路上很多猛人寫了套件後開放給大家使用,而那些套件的說明文件跟demo有些都蠻齊全的。

通常對於使用套件會遇到的問題就是,同一個功能,比方輪播,就有很多人有出套件:Slick、Owl Carousel、Swiper……等,或是說明文件都英文,參數又多,等研究完了前端也差不多寫好了。

當初會製作這個套件集合站就是為了解決這二個問題。

首先,從常用的套件中挑出一個覺得好用的、常用的出來,比方輪播就選用了Slick。

接著,附上要引用的js、css的cdn網址,並加入一鍵copy路徑的功能。

最後,在CodePen上寫個使用的範例,iframe到頁面中。

原本的頁面是用Vue CLI 2開發,UI是用Vuetify 1.5.x版。最近在看Vue CLI 3,又發現Vuetify升到2.0.5版了,就想說不然來更新一下集合站,當作練習,也順便記錄一下開發的過程。


本篇使用資源

本篇用到的資源如下:

本文最後會附上套件集合站整理過的原始碼。


安裝Vue CLI 3

安裝Vue CLI 3之前,必須先安裝Node.js,官方是推薦8.11.0以上的版本。

裝完Node.js後,開啟終端機,輸入以下命令:

npm install -g @vue/cli

mac的話前面要多輸入「sudo」。

安裝完後,要檢查有沒有安裝成功,就在終端機輸入:

vue --version

如果有出現版本號就代表成功了。


建立Vue CLI 3專案

在目錄底下開啟終端機,輸入:

vue create 專案名稱

接著選擇「Manually select features」,就會出現安裝的選項:

Vue CLI 3 安裝選項
Vue CLI 3 安裝選項

這邊就簡單的用編ES6的Babel跟控各頁網址的Router。按下Enter後就會問3個問題。

Router用history mode。

config一起寫進package.json裡。

要不要設成預設選項之一,就看自己未來的需求。

三個問題都回答後,就開始安裝專案。安裝完後會出現如何開啟服務的說明。

Vue CLI 3 專案安裝完成訊息
Vue CLI 3 專案安裝完成訊息

點進資料夾裡的readme.md,可以看到使用的命令,以下2個是比較常用的部份:

Compiles and hot-reloads for development

npm run serve

Compiles and minifies for production

npm run build

輸入 npm run serve後,預設的網址是 http://localhost:8080/,打開就可以看見初始的頁面:

Vue CLI 3 安裝完後的初始頁面
Vue CLI 3 安裝完後的初始頁面

因為有選擇裝Router,所以頁面預設有2頁:Home、About,點選頂部的導覽列可以切換。

看到設定的路由檔案,有一招是用CLI 2時沒看到的,就是對component做lazy-load:

{
  path: '/about',
  name: 'about',
  component: () => import('./views/About.vue')
}

以前還真的不知道可以這樣用,長知識了。


安裝Vuetify

Vuetify是走Google Material Design的UI framework,裡面包了很多常用的UI,裝了以後就不用花時間自己刻樣式。

Vue CLI 3 可以安裝插件,也有提供命令:

vue add 插件來源

Vuetify安裝說明中,也有說明怎麼在Vue CLI 3 上安裝,只要在終端機上輸入:

vue add vuetify

裝完後會出現自定義安裝的選項,文件中說預設Default就有提供導覽列的App,這邊就選Default。

安裝完Vuetify後,會出現哪些檔案被更動的訊息:

Vuetify更動檔案的訊息
Vuetify更動檔案的訊息

執行「npm run serve」打開頁面後,會看見首頁整個不一樣了:

裝完Vuetify後的首頁
裝完Vuetify後的首頁

Vue CLI 3裝完了,Vuetify也裝完了,接下來就是製作頁面跟接資料。


製作頁面模版

製作一個頁面,一開始很花時間的就是做出一個模版給其它頁套用,模版中要包含天地跟導覽列。

而Vuetify在Application這段就有提供導覽列在側邊的版型,版型中也包了天地,因此就copy下來修改,把用按鈕控制側邊導覽列的部份也寫進去,整個模版的架構如下(App.vue):

側邊導覽列的部份綁一個v-model=”drawer”,用app-bar-nav-icon去控制true/false,就可以打開/關閉側邊導覽列了。

抓firebase資料

選單的項目用v-for處理,資料放在firebase上,這邊用realtime database。

firebase的規則設定,如果read設成true,就可以直接GET資料回來,request url在database的灰底那塊(第一個紅框處):

firebase GET Url
firebase GET Url

比方這邊資料分成article、nav,我想抓的是nav的那個json,那url就是:

https://xxxxxxxxx.firebaseio.com/nav.json

抓回來後的資料在放寫v-for就行了。

這邊的資料架構是這樣:

firebase上的資料架構
firebase上的資料架構

article是給內文用的,nav是導覽列用,下一層都是每一個套件的id名稱。

之所以會這樣子用,就是為了方便路由抓id。

我的路由是這樣寫:

<v-list-item
   v-for="(n, v) in asideNav" :key="v"
   :to="'/plugin-' + v"
>

用「plugin-」當開頭是為了路由可以判斷要渲染哪一個component。

「v」就是ajaxLoading、alert……等等的key。

因此當點擊了導覽列,進到頁面後,就可以抓到那一頁的id是什麼,用id去GET:

https://xxxxxxxx.firebaseio.com/article/${this.id}.json

這樣就可以抓到這個套件在article裡的資料。

這邊有踩到一個坑,說是坑,不如說是當初學Vue Router時,文件沒看仔細。

Vue Router:id、pathMatch

內文頁的路由是設這樣:

{
  path: '/plugin-*',
  name: 'plugin',
  component: () => import('./views/Plugins.vue')
}

之前在抓路由的id時,都是這樣抓:

this.$route.params.id

這邊一樣用相同方式抓時,就發現報錯,console.log後又發現params裡沒有id,卻有一個pathMatch。

翻了一下文件後,才發現當路由path設成「*」,要抓任意路徑時,$route.params會提供的參數就是pathMatch。詳情可以看文件說明:

捕獲所有路由或404 Not found路由

模版頁最後的原始碼在本文最後的原始檔中可以看到。


製作頁面

整個站的頁面就兩種版型:首頁、內文頁。

首頁主要是網站說明跟放公告,而內文頁都是針對路由給出的不同id去抓不同的資料回來渲染。

其實當模版頁完成後,做首頁、內文頁就快多了。

上段中有說資料庫的架構可以讓我從路由上判斷要去抓哪個資料了,因此做頁面的部份就是把抓到的資料塞進去而己。

這段要特別說明的是,因為用的是Vue Router,所以實際頁面並不會重新讀取一次,這會造成一個問題,就是GA的數據不會因為進到不同頁面而page view +1。

為了解決頁面瀏覽量可以確實進到GA,必需手動寫PV+1,寫在路由變動後執行的methods裡就行,如下:

gtag('config', 'UA-xxxxxxxx-x', {
  'page_title': 頁面title,
  'page_location': location.origin,
  'page_path': location.pathname
});

然後又會遇到另一個坑,就是因為首頁也是路由的一個網址,埋GA code時預設是PV+1了,如果又因為在methods我們手動發PV+1,就會造成首頁的流量每次都多計一次。

解決這問題很簡單,就是把GA提供的追蹤碼,埋碼時刪掉config那句就行。

一般GA提供要埋的追蹤碼是這樣:

<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-xxxxxxxx-x"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());

  gtag('config', 'UA-xxxxxxxx-x');
</script>

「gtag(‘config’, ‘UA-xxxxxxxx-x’)」這行,因為我們在method裡有寫了,這行就要刪掉,就解決了瀏覽數重複計算的問題。


build 產出原始碼

code都寫好後,最後一步就是打包原始碼傳到主機。

打包的命令是:

npm run build

在Vue CLI 3上,有一些打包上的config可以使用,可以參考官方文件

首先,要先新增一個檔案,檔名為「vue.config.js」,目前比較常用的設定是以下:

module.exports = {
  publicPath: '',
  outputDir: 'plugin-lib', // 產出的資料夾名稱
  assetsDir: 'dist', // 靜態檔的資料夾名稱
  filenameHashing: true // bundle出來的檔案是否要有hash值
}

由於有改變產出的資料夾名稱,在build後,會看到的資料夾結構如下:

build出來後的資料夾結構
build出來後的資料夾結構

一開始看到build出來,會build成一個獨立的資料夾,覺得蠻好的,這樣就可以開發、發行兩個分別控管。如果要跟後端合作時,也只要提供build後的資料夾,那就會是很乾淨的版本。


vue-router用history mode的注意事項

把build的檔案傳到主機上,遇到了一個錯誤。

我的router是用history mode,當我點開其他頁面,然後按下重新整理時,就會出現error500的伺服器錯誤。

這點是沒想到的,因為在開發時沒這問題,上到主機後就有了。

看了一下vue-router的文件說明,上面有提到:

這種模式要玩好,還需要後台配置支持。
因為我們的應用是個單頁客戶端應用,如果後台沒有正確的配置,當用戶在瀏覽器直接訪問
http://oursite.com/user/id就會返回404,這就不好看了。
所以呢,你要在服務端增加一個覆蓋所有情況的候選資源:如果URL匹配不到任何靜態資源,則應該返回同一個
index.html頁面,這個頁面就是你app依賴的頁面。

HTML5 History模式

照著說明新增 .htaccess 的檔案後,就沒出現這個錯誤了。

但……我還是不知道原因啊!?希望對server了解的大大可以開示一下。

019/08/14補充:
留言區裡的Denny大大有解說,大家可以看一下留言內容,很清楚的解釋。


本篇原始碼

本篇套件集合站的原始碼,整理上傳到Github了:

https://github.com/letswritetw/letswrite-plugin-lib


Summary
用Vue CLI 3 + Vuetify 製作說明頁面
Article Name
用Vue CLI 3 + Vuetify 製作說明頁面
Description
用Vue CLI 3 + Vuetify 製作說明頁面 本篇大綱:關於「套件集合站」、本篇使用資源、安裝Vue CLI 3、建立Vue CLI 3專案、安裝Vuetify、製作頁面模版、抓firebase資料、Vue Router:id、pathMatch、製作頁面、build 產出原始碼、本篇原始碼。
Augustus
Let's Write
Let's Write
Publisher Logo

2 Replies to “用Vue CLI 3 + Vuetify 製作說明頁面”

  1. 關於 Server error,提供淺見但不一定正確,供參考。

    Vue Router 在 hash 模式下 URL 會多出 # 號,
    不論 # 號後面的值,都還算是同一個 page file,
    因此 refresh,server 都維持跑 index.html 檔,再由 Vue router 依據 # 號來判斷路由。

    而在 history 模式時少了 # 號,如果還未設置 server 端的 URL 跳轉設定,
    頁面 refresh 時,會用一般 server 跑 file path 方式去讀檔案,
    如 /about ,就會因找不到 about 這個實體檔案,而出 error,
    Vue router 所需的 server rewrite 規則,就是不論 URL 為何,
    都讓它統一跑 index.html (或 index.php 之類),再由 Vue router 配置路由~

    1. 這不是D大嗎~
      是本站第一位留言的(灑花)~
      謝謝你的意見,有看懂了,也補充在文章中。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *