Vue 2.x 實戰之後臺管理系統開發(二)

Alexee發表於2017-04-16

1. 導語

承接上文:Vue 2.x 實戰之後臺管理系統開發(一)

在上一篇文章中,我詳細敘述瞭如何建立專案框架和引入各種後臺常用外掛,做好這些準備工作後,我們就可以著手進行頁面的開發了。在開發過程中,會遇到一些常見的需求和問題,我整理了一下,有以下幾點。

2. 常見需求

01. 父子元件通訊

a. 父 -> 子(父元件傳遞資料給子元件)

使用 props,具體檢視文件 - 使用 Prop 傳遞資料(cn.vuejs.org/v2/guide/co…

b. 父 -> 子(在父元件上呼叫子元件內的方法)

使用 ref,具體檢視文件 - 子元件索引(cn.vuejs.org/v2/guide/co…

<!--父元件 template-->
<div id="parent">
  <!--子元件-->
  <user-profile ref="profile"></user-profile>
</div>複製程式碼
// 父元件 script
this.$refs.profile.someMethod();複製程式碼

注意:如果在子元件上設定 ref 屬性,則可以通過 this.$refs 獲取到該子元件物件,如果在普通的 html 標籤上設定 ref 屬性,則獲取到的是 Dom 節點。

c. 子 -> 父(在父元件上獲取子元件內的資料)

同上,也是利用 ref

// 父元件 script
let childData = this.$refs.profile.someData;複製程式碼

d. 子 -> 父(子元件內觸發事件,父元件監聽事件)

父元件可以在使用子元件的地方直接用 v-on 來監聽子元件觸發的事件,具體檢視文件 - 使用 v-on 繫結自定義事件(cn.vuejs.org/v2/guide/co…

<!--父元件 template-->
<div id="parent">
  <!--子元件-->
  <user-profile @childTrigger="parentHandle"></user-profile>
</div>複製程式碼
// 父元件 script
methods: {
    parentHandle(params){
        // 這個方法在子元件 emit childTrigger 事件後會執行
        // params 為子元件裡觸發事件時傳的引數
    }
}複製程式碼
// 子元件 user-profile script
this.$emit('childTrigger', params);複製程式碼

e. 子 -> 父(子元件傳值,父元件裡使用,具體實現見 03

01總結:
應用場景示例:在父元件上開啟側邊欄子元件,可以傳 prop visible(true)來控制側邊欄開啟;側邊欄內部有關閉按鈕,就在點選關閉按鈕後觸發一個事件,父元件監聽事件執行方法將 data visible 改為 false
PS:父元件傳值到子元件,傳的值是 Object 型別,子元件使用 v-model 可以修改該值(將某個表單元素的 v-model 設為該值),父元件可直接獲取到改變後的值。

02. 全域性函式

有時候會用到一些工具類函式,希望可以全域性呼叫,而不是侷限於某個元件中。

Step 1:
專案根目錄/static/js/ 目錄下新建一個 util.js 檔案,將常用的工具函式寫在這裡面。

Step 2:
index.html 裡面引入 util.js,就可以在 .vue 檔案裡使用那些方法了,如下:

<body>
  <div id="app"></div>
  <!-- 引入常用 js 方法 -->
  <script type="text/javascript" src="/static/js/util.js"></script>
   <!-- built files will be auto injected -->
</body>複製程式碼

02總結:
使用這個方法可以使得一些使用頻率高的函式可以在所有 .vue 檔案中被呼叫,笨拙而又簡單。

03. slot

以前看文件時一直不理解如何使用 slot,現在用多了 elementui 的元件之後,就漸漸發現了它的實用性。
簡單來說,使用 slot 可以使我們做到:在父元件裡使用子元件時,在子元件裡插入一些自定義的內容(html 程式碼啥的),具體檢視文件:cn.vuejs.org/v2/guide/co…
更神奇的功能是 作用域插槽,可以讓我們在父元件裡使用子元件時,獲取子元件傳來的資料,具體檢視文件:cn.vuejs.org/v2/guide/co…

簡單應用示例

<!-- a-button 子元件 -->
<button type="button" class="a-button" @click="$emit('btn-click')"><slot></slot></button>
<!-- 這裡監聽了 button 的 click 事件,然後又觸發了 btn-click 事件 -->複製程式碼
<!-- 父元件 -->
<a-button @btn-click="handleClick">這裡寫的東西會覆蓋子元件裡的 slot 標籤所在的位置</a-button>
<!-- 這裡監聽了子元素觸發的 btn-click 事件,然後執行 handleClick 函式 -->複製程式碼

渲染結果:

<button type="button" class="a-button">這裡寫的東西會覆蓋子元件裡的 slot 標籤所在的位置</button>複製程式碼

可以應用簡單的 slot 來達到為不同的按鈕填充文字的目的:

<a-button @click="handleClick">詳情</a-button>
<a-button @click="handleClick">搜尋</a-button>

<!-- 渲染結果 -->
<button type="button" class="a-button">詳情</button>
<button type="button" class="a-button">搜尋</button>複製程式碼

作用域插槽示例

<!-- 子元件 -->
<div class="child">
  <!-- slot 這個位置會在子元件被使用時被父元件傳來的 html 程式碼覆蓋 -->
  <!-- slot 上的 text/child 就相當於傳給父元件的 props (假設 name 為子元件的 data,name: someChild) -->
  <slot text="hello from child" :child="name"></slot>
</div>複製程式碼

在父級中,具有特殊屬性 scope<template> 元素,表示它是作用域插槽的模板。scope 的值對應一個臨時變數名,此變數接收從子元件中傳遞的 prop 物件:

<!-- 父元件 -->
<div class="parent">
  <child>
    <template scope="props">
      <span>hello from parent</span>
      <span>{{ props.text }}</span>
      <span>{{ props.child }}</span>
    </template>
  </child>
</div>複製程式碼

渲染結果:

<div class="parent">
  <div class="child">
    <span>hello from parent</span>
    <span>hello from child</span>
    <span>someChild</span>
  </div>
</div>複製程式碼

03總結:
應用場景示例:elementui 的 button 元件中有簡單插槽的使用,table 元件則使用到了 作用域插槽

<!-- button 元件 -->
<el-button>預設按鈕</el-button>
<el-button type="primary">主要按鈕</el-button>
<el-button type="text">文字按鈕</el-button>

<!-- table 元件 -->
<el-table
  :data="tableData">
  <el-table-column
    prop="zip"
    label="郵編"
    width="120">
  </el-table-column>
  <el-table-column
    fixed="right"
    label="操作"
    width="100">
    <template scope="scope">
      <el-button
        <!-- 可以通過 scope.$index 獲取到當前行的索引 -->
        @click.native.prevent="deleteRow(scope.$index)">
        移除
      </el-button>
    </template>
  </el-table-column>
</el-table>複製程式碼

04. router 使用小記

vue-router 的使用,簡單來說就是通過配置,實現在不同的 url 路徑下,頁面渲染不同的元件。具體檢視文件:vue-router 2

使用示例
一級路由:

<!-- App.vue -->
<!-- 該元件為最高階父元件,使用 router-view 來根據路徑確定要顯示的子元件 -->
<template>
  <div id="app">
    <!-- router-view 位置用來顯示元件,如顯示下面的 index.vue -->
    <router-view></router-view>
  </div>
</template>複製程式碼

二級路由(路由可巢狀):

<!-- page/index.vue -->
<!-- 該元件包含一個頂部欄和側邊選單欄,內容區使用 router-view 來根據 url 路徑顯示子元件 -->
<template>
  <div class="index">
    <!-- 頂部導航條 -->
    <header class="main-header">
      ...
    </header>
    <!-- /頂部導航條 -->
    <!-- 側邊導航欄 -->
    <aside class="main-sidebar sidebar-gradient">
      ...
    </aside>
    <!-- /側邊導航欄 -->
    <!-- 根據頁面一級選單的點選而進行切換的內容區 -->
    <transition name="fade">
      <!-- router-view 位置用來顯示元件 --> 
      <router-view></router-view>
    </transition>
    <!-- /內容區 -->
  </div>
</template>複製程式碼

router 配置:

// router/index.js

import Vue from 'vue';
import Router from 'vue-router';
// 引入元件
import index from 'page/index'; // 該元件包含一個頂部欄和側邊選單欄,內容區使用 router-view 來根據 url 路徑顯示子元件
import notFoundComponent from 'page/404'; // 該元件為 404 頁面,當你路由使用 history 模式時需要用到
import monitorIndex from 'page/monitor/index'; // 該元件為一個監控頁面,用於顯示在 page/index.vue 頁面上的 router-view 處(即頁面的內容區域)

Vue.use(Router);

// 定義 scrollBehavior 方法
const scrollBehavior = (to, from, savedPosition) => {
  if (savedPosition) {
    return savedPosition
  } else {
    return { x: 0, y: 0 }
  }
}

export default new Router({
  mode: 'history',
  // mode 預設 hash 值,但是 hash (url中包含 # 符號)不太好看也不符合我們一般的網址瀏覽習慣
  // 當你使用 history 模式時,URL 就像正常的 URL,例如 http://yoursite.com/user/id,也好看!

  linkActiveClass: 'active',
  // 預設值: 'router-link-active',就是當前元件被啟用,相應路由會自動新增類 'router-link-active',這裡是為了全域性設定啟用類名,如果不設定,直接用預設的也是可以的
  // 如:使用 router-link 元件來導航,通過傳入 `to` 屬性指定連結
  // <router-link to="/foo">Go to Foo</router-link>
  // <router-link> 預設會被渲染成一個 `<a>` 標籤,'/foo' 路由下的元件顯示時,該 a 標籤上會自動新增類 'active'

  scrollBehavior: scrollBehavior,
  // 通過這個屬性(是個函式),可以讓應用像瀏覽器的原生表現那樣,在按下 後退/前進 按鈕時,簡單地讓頁面滾動到頂部或原來的位置,如果不設定,則元件切換時滾動條位置不變

  routes: [
    {
      // 一級路由
      path: '/',
      component: index,
      children: [
        // 二級路由
        // -----------預設首頁-------------
        // 當 / 匹配成功,monitorIndex 會被渲染在 index 的 <router-view> 中
        { path: '', component: monitorIndex, alias: 'index.html' },
        // 這裡的 alias 'index.html' 為當前頁面的別名
        // http://localhost:8080/index.html 等同於 http://localhost:8080/ 

        // -----------監控中心-------------
        {
          // 當 /monitor 匹配成功,
          // monitorIndex 會被渲染在 index 的 <router-view> 中
          path: 'monitor',
          name: '監控中心',
          component: monitorIndex
        }
      ]
    },

    // 同一個路徑可以匹配多個路由,此時,匹配的優先順序就按照路由的定義順序:誰先定義的,誰的優先順序就最高
    // 因此下面的路由配置為備用,如果某個路徑未被配置顯示相應的元件,則顯示 404 頁面
    { path: '*', component: notFoundComponent }
  ]
});複製程式碼

引入 router 配置:

// main.js 

import Vue from 'vue';

// 引入 element ui 元件
import { Dropdown, DropdownMenu ...} from 'element-ui';

// 引入 App.vue
import App from './App';

// 引入 router 配置
import router from './router'; // 預設會找到 router 資料夾下的 index.js 檔案

// 引入專案圖示的 sprite css,可以簡單的通過這種方式引入 css 檔案
import './assets/css/sprite.css'

// 使用 element ui 元件
Vue.use(Dropdown)
Vue.use(DropdownMenu)
...

new Vue({
  el: '#app',
  router, // 使用 router 配置
  template: '<App/>',
  components: { App },
});複製程式碼

04總結:
關於 vue-router 的使用,看文件一般都能解決你的疑問,vue-router 2
其他參考文章:Vue.js系列之vue-router(中)(4)
PS:使用 history 模式的話,還需要 後臺配置 支援。因為我們的應用是個單頁客戶端應用,如果後臺沒有正確的配置,當使用者在瀏覽器直接訪問 http://oursite.com/user/id 就會返回 404(因為的確找不到該頁面),這就不好看了。並且在後臺配置後,還需要前端來提供 404 頁面,我上面的示例程式碼中有提到,可供參考。

05. 測試介面

使用 Vue 開發單頁應用時,前後端分離開發,進度不一。因此前端有時候就需要自己模擬介面的 json 檔案,然後直接使用非同步請求方法(如 ajax) 去獲取資料,渲染頁面,測試程式碼等。

Step 1:
專案根目錄/static/api/ 目錄下新建一個 test.json 檔案,寫入模擬的介面資料:

{
  "status": true,
  "data": {
    ...
  }
}複製程式碼

Step 2:
.vue 元件檔案裡任意需要請求資料的方法裡(如 created 鉤子,或者某個 methods 方法裡)編寫相關程式碼:

let vm = this;
// ajax 請求資料
$.ajax({
    type: 'GET',
    url: 'static/api/test.json',
    data: '',
    beforeSend: function() {
      // 顯示 Loading
      vm.loading = true;
    },
    complete: function() {
      // 隱藏 Loading
      vm.loading = false;
    },
    success: function(data) {
      // 處理返回資料
      ...
    },
    error: function() {
      // 資料請求失敗,給使用者適當的反饋資訊
      ...
    },
    dataType: 'json'
});複製程式碼

05總結:
在後端尚未提供介面時,我都是用這個方法來測試前端獲取資料和處理資料的程式碼是否正確。

3. 零碎問題

01. prop 傳值小技巧

我們可以為元件的 props 指定驗證規格。如果傳入的資料不符合規格,Vue 會發出警告。當元件給其他人使用時,這很有用。

示例如下:

  props: {
    // 基礎型別檢測 (`null` 意思是任何型別都可以)
    propA: Number,
    // 多種型別
    propB: [String, Number],
    // 必傳且是字串
    propC: {
      type: String,
      required: true
    },
    // 數字,有預設值
    propD: {
      type: Number,
      default: 100
    },
    // 陣列/物件的預設值應當由一個工廠函式返回
    propE: {
      type: Object,
      default: function () {
        return { message: 'hello' }
      }
    },
    // 自定義驗證函式
    propF: {
      validator: function (value) {
        return value > 10
      }
    }
  }複製程式碼

愚蠢的我每次想要傳 Number 或者 Boolean 型別的值到子元件時,都在父元件裡定義好值,然後再繫結到子元件上:

// 這樣會報錯,因為 show type 為 Boolean,rows type 為 Number
// 預設情況下直接傳值,子元件接收到的都是 String 型別

// template
<child show="true" rows="6"></child>複製程式碼
// 於是我這樣做:

// template
<child :show="show" :rows="rows"></child>

// script
show: true,
rows: 6複製程式碼
// 實際上可以直接這樣做:

// template
<child :show="true" :rows="6"></child>

// 官網如是說:如果想傳遞一個實際的 number,需要使用 v-bind ,從而讓它的值被當作 JavaScript 表示式計算。複製程式碼

小技巧:當某個 prop 型別為 Boolean 時,可以直接把該 prop 的名稱寫在元件上,預設會傳 true,不寫的話預設為 false。比如 <child show :rows="6"></child> 這麼寫,子元件內部就能收到 show 為 true。

02. autoprefixer

有些人會問如何在專案裡使用 autoprefixer 外掛,事實上使用 vue-cliwebpack 模板生成的專案裡已經帶有 autoprefixer 的使用了,如下圖:

Vue 2.x 實戰之後臺管理系統開發(二)
autoprefixer

03. build 時不生成 .map 檔案

對專案進行 npm run build 操作後,發現生成的檔案超大(比想象中的大),尤其是那些 .map 檔案,不過,我們可以通過配置選擇不生成該類檔案。

// 專案根目錄/config/index.js

var path = require('path')

module.exports = {
  build: {
    ...
    productionSourceMap: false, // 將該值設為 false,就不會生成 .map 檔案了

    // Gzip off by default as many popular static hosts such as
    // Surge or Netlify already gzip all static assets for you.
    // Before setting to `true`, make sure to:
    // npm install --save-dev compression-webpack-plugin
    productionGzip: false,
    productionGzipExtensions: ['js', 'css'],
    ...
  },
  dev: {
    ...
  }
}複製程式碼

4. 總結

一句話,有空多看文件,可以避免很多實踐中的問題。

相關文章