0%

Vue-vue出一個電商網站-撰寫DashBoard-02

增加使用者體驗 - 讀取中效果製作

製作局部和全部的loading效果。
step1.

全域loading效果 - Vue Loading Overlay

在這邊我們會直接安裝Vue Loading Overlay套件,來達成全域的loading效果。
輸入指令 npm install vue-loading-overlay ,來為你的編譯器安裝這個套件。

step2.
在main.js檔案中,加入overlay的套件,也要加入啟用的程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
---main.js---
import Vue from 'vue'
import axios from 'axios'
import VueAxios from 'vue-axios'
import Loading from 'vue-loading-overlay'; // 引入overlay元件
import 'vue-loading-overlay/dist/vue-loading.css'; // 引入overlay元件
import 'bootstrap'
import App from './App'
import router from './router'
Vue.use(VueAxios, axios)
Vue.config.productionTip = false
Vue.component('Loading', Loading); // 將overlay元件宣告成全域的元件

axios.defaults.withCredentials = true;
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})

step3.
接著,在Products.vue元件檔檔案中,加入overlay的套件引入的程式碼。

1
2
3
4
5
6
7
8
9
---Products.vue---
<template>
<div>
<div class="vld-parent">
<loading :active.sync="isLoading"></loading>
</div>
---以下內容筆記省略不寫---
</div>
</template>

你可以看到,我們在Products元件檔中,加入了一段程式碼,就是它官方提供說要加到元件的template中的。
那這邊的話,我們只有留下<loading>標籤中的active.sync部分。
你可以看到,它有一個isLoading的值,當這個值是true的時候,會執行loading特效,若是false的話,則不會顯現。

step4.

1
2
3
4
5
6
7
8
9
10
11
12
methods:{
getProducts(){
const vm = this;
const api = `${process.env.APIPATH}/api/${process.env.CUSTOMPATH}/admin/products/all`;
vm.isLoading = true; // 啟用loading效果
this.$http.get(api).then((response) => {
console.log(response.data);
vm.isLoading = false; // 關閉loading效果
vm.products = response.data.products;
})
}
}

你可以看到,我們在Products.vue元件檔中,在getProducts中,當一進去的時候,啟用loading的動畫,
當數據接收結束之後,就將isLoading 設為 false,關閉loading的動畫。

局部loading效果 - fontawesome

step1.
我們先將fontawesome的css的cdn引入到最外層的index.html檔案中。
step2.
你可以先去fontAwesome的網站上找到那些動態loading的圖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
---Products.vue元件檔---
<template>
<label for="customFile">或 上傳圖片
<i class="fas fa-spinner fa-spin" v-if="Status.isUploading"></i>
</label>
</template>


<script>
data(){
return {
products:[],
tempProduct:{},
isNew: false,
isLoading:false,
Status:{
isUploading:false
}
}
},
</script>

你可以看到,我們先新增了Status屬性,它含有isUploading的屬性。
並在template中那些font-awesome的loaing的圖示中,加上v-if判斷若isUploading是true的話就出現,
若是false就不出現。

step3.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
methods:{
uploadFile(){
console.log(this);
const uploadFile = this.$refs.files.files[0];
const vm = this;
const formData = new FormData();
vm.Status.isUploading = true; // 加入fontawesome的loading效果
formData.append('file-to-upload', uploadFile);
const url = `${process.env.APIPATH}/api/${process.env.CUSTOMPATH}/admin/upload`;
this.$http.post(url, formData, {
headers:{
'Content-Type': 'multipart/form-data'
}
}).then((response)=>{
console.log(response.data);
vm.Status.isUploading = false; // 結束fontawesome的loading 效果
if(response.data.success){
vm.$set(vm.tempProduct, 'imageUrl', response.data.imageUrl)

}
})
}

}

我們在uploadFile方法中,加入了切換Status.isUploading 的旗標,藉此來看開啟或關閉fontawesome的動畫效果。

增加使用者體驗 - 錯誤的訊息回饋 - Event Bus

一般如果用傳統的方式,當某個底層元件發生錯誤,像要呼叫Alert,就需要一層一層向上呼叫,這個很麻煩的。
所以,在這邊直接使用一個Vue Event Bus的概念來達成這樣的效果,直接將Alert掛載在vue的原型底下,讓vue的原型直接操作這些Alert。
step1.
首先我們現在components資料夾底下創出一個AlertMessage.vue的元件檔。
接著,將課堂上提供的alert模板貼上去。
step2.
到DashBoard.vue元件檔中,將AlertMessage.vue引入進去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<template>
<div>
<navbar></navbar>
<alert/> // 將alert元件放進來
<div class="container-fluid">
<div class="row">
<sidebar></sidebar>

<main class="col-md-9 ml-sm-auto col-lg-10 px-md-4">
<router-view></router-view>
</main>
</div>
</div>
</div>
</template>

<script>
import sidebar from './sidebar';
import navbar from './navbar';
import alert from './AlertMessage' // 引入AlertMessage元件檔
export default{
components:{
sidebar,
navbar,
alert
},
created(){
const myCookie = document.cookie.replace(/(?:(?:^|.*;\s*)hextoken\s*=\s*([^;]*).*$)|^.*$/, '$1');
this.$http.defaults.headers.common.Authorization = myCookie;
}
}

</script>

step3.
在AlertMessage.vue元件檔案中,會有一個message的陣列,它裡面的元素都是物件。
物件的屬性值,分別是放訊息內容、css樣式和timestamp。

step4.
接著,我們在src的資料夾底下創一個bus.js的檔案,這個檔案跟App.vue檔案是同一層喔。

step5.
我們在bus.js檔案中,引入vue,並將bus掛載在vue的原型底下。

1
2
3
4
---bus.js檔案---
import Vue from 'vue'

Vue.prototype.$bus = new Vue();

此時,各vue元件就都能提取到$bus了。

step6.
接著,在main.js檔案中引入剛剛的bus.js檔案。

1
2
---main.js---
import './bus'

step7.
回到AlertMessage.vue的檔案中,註冊有關eventbus的事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
---AlertMessage.vue元件檔---
<script>
export default {
---以上筆記省略不寫---
created() {
const vm = this;

// 自定義名稱 'messsage:push'
// message: 傳入參數
// status: 樣式,預設值為 warning
vm.$bus.$on('messsage:push', (message, status = 'warning') => {
vm.updateMessage(message, status);
});
},
};
</script>

你可以看到,我們用on的方式註冊了一個messsage:push的方法。

step8.
接著,我們在Products.vue檔案中,利用emit來使用註冊的messsage:push方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
---Products.vue元件檔---
methods:{
uploadFile(){
console.log(this);
const uploadFile = this.$refs.files.files[0];
const vm = this;
const formData = new FormData();
vm.Status.isUploading = true;
formData.append('file-to-upload', uploadFile);
const url = `${process.env.APIPATH}/api/${process.env.CUSTOMPATH}/admin/upload`;
console.log('url',url);
this.$http.post(url, formData, {
headers:{
'Content-Type': 'multipart/form-data'
}
}).then((response)=>{
console.log('report',response.data);
vm.Status.isUploading = false;
if(response.data.success){
vm.$set(vm.tempProduct, 'imageUrl', response.data.imageUrl)
console.log(response.data);
} else {
vm.$bus.$emit('messsage:push', response.data.message, 'danger'); // 加入將錯誤訊息丟到event bus來送出訊息
}
})
}

}

這個時候,你可以試著將一個svg格式的圖片丟上去,就會傳出相對應的錯誤訊息囉。

產品列表的分頁邏輯

step1.
我們先把後端傳過來的有關分頁的內容存下來。
首先,我們在Products.vue元件檔中,新增pagination的物件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
---Products.vue---
export default{
data(){
return {
pagination:{}, // 新增pagination物件
products:[],
tempProduct:{},
isNew: false,
isLoading:false,
Status:{
isUploading:false
}
}
},
methods:{
getProducts(){
const vm = this;
//const api = `${process.env.APIPATH}/api/${process.env.CUSTOMPATH}/admin/products/all`; // 原本的商品api註解掉
const api = `${process.env.APIPATH}/api/${process.env.CUSTOMPATH}/admin/products`; // 修改取得商品的api
vm.isLoading = true;
this.$http.get(api).then((response) => {
console.log(response.data);
vm.isLoading = false;
vm.products = response.data.products;
vm.pagination = response.data.pagination; // 取得伺服器端的pagination

})
},
},
created(){
this.getProducts();

}
}

你可以看到我們將取得所有商品的api網址改掉了,因為,原本的api網址是不會回傳有關分頁的資訊的。
step2.
將bootstrap的分頁模板,複製下來
然後,貼到Products.vue元件裡面,接著,再用v-for遍歷總頁碼,製作出所有的分頁出來

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<nav aria-label="Page navigation example">
<ul class="pagination">
<li class="page-item">
<a class="page-link" href="#" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
<li class="page-item" v-for="page in pagination.total_pages" :key="page"><a class="page-link" href="#">{{page}}</a></li>

<li class="page-item">
<a class="page-link" href="#" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
</ul>
</nav>
</template>

這樣一來就可以成功做出頁碼囉。

step3.
製作’當前頁面’的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<nav aria-label="Page navigation example">
<ul class="pagination">
<li class="page-item">
<a class="page-link" href="#" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
<li class="page-item" v-for="page in pagination.total_pages" :key="page" :class="{'active':pagination.current_page===page}"><a class="page-link" href="#">{{page}}</a></li>

<li class="page-item">
<a class="page-link" href="#" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
</ul>
</nav>
</template>

利用v-bind動態綁定class並判斷current_page來達到這種效果。

step4.
製作有無上一頁和下一頁的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<nav aria-label="Page navigation example">
<ul class="pagination">
<li class="page-item" :class="{'disabled':!pagination.has_pre}">
<a class="page-link" href="#" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
<li class="page-item" v-for="page in pagination.total_pages" :key="page" :class="{'active':pagination.current_page===page}"><a class="page-link" href="#">{{page}}</a></li>

<li class="page-item" :class="{'disabled':!pagination.has_next}">
<a class="page-link" href="#" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
</ul>
</nav>
</template>

利用以上的方式判斷pre和next是否為false,若是false的話,就為它套上disabled的className和無法被選取的效果。

step5.
製作利用分頁跳轉頁面的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
---Products.vue---
<template>
<nav aria-label="Page navigation example">
<ul class="pagination">
<li class="page-item" :class="{'disabled':!pagination.has_pre}">
<a class="page-link" href="#" aria-label="Previous" @click.prevent="getProducts(pagination.current_page-1)">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
<li class="page-item" v-for="page in pagination.total_pages" :key="page" :class="{'active':pagination.current_page===page}">
<a class="page-link" href="#" @click.prevent="getProducts(page)">{{page}}</a>
</li>

<li class="page-item" :class="{'disabled':!pagination.has_next}">
<a class="page-link" href="#" aria-label="Next" @click.prevent="getProducts(pagination.current_page+1)">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
</ul>
</nav>
</template>

<script>
methods:{
getProducts(page = 1){
const vm = this;
const api = `${process.env.APIPATH}/api/${process.env.CUSTOMPATH}/admin/products?page=${page}`;
vm.isLoading = true;
this.$http.get(api).then((response) => {
console.log('response',response.data);
vm.isLoading = false;
vm.products = response.data.products;
vm.pagination = response.data.pagination;
})
}
}
</script>

a.
你可以看到我們先把跳轉頁面的api換成六角提供的跳轉到特定頁面的api。
b.
接著,我們在getProducts方法加入參數page並給它預設值為1。
c.
我們在各分頁上都用@click綁定連動到getProducts方法,藉此來跳轉到特定頁面。
d.
在前一頁和後一頁的按鈕上也綁定連動到getProducts的方法,但是,傳入的參數是利用pagination.current_page加一或減一來達到回上一頁和跳下一頁的功能。