0%

vue出一個電商網站-shopping頁面

這邊紀錄的內容是專門撰寫有關使用者在使用的shopping的頁面,
跟之前紀錄的管理者在使用的狀態是不一樣的喔~~

Dashboard 新增模擬購物頁面 - 新增卡片式產品列表

step1.
首先,先在pages資料夾中,新建一個CustomerOrder的元件檔。
step2.
在index.js中引入這個CustomerOrder元件。

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
---index.js檔案---
import CustomerOrder from '@/components/pages/CustomerOrder' // 引入CustomerOrder元件
Vue.use(Router)

export default new Router({
routes: [
{
path:'*',
redirect:'login'
},
{
path:'/login',
name: 'Login',
component: Login
},
{
path: '/admin',
name: 'HelloWorld',
component: DashBoard,
meta: { requiresAuth: true },
children:[
{
path:'products',
name: 'Products',
component: Products,
meta: { requiresAuth: true },
},
{
path:'order',
name: 'order',
component: Order,
meta: { requiresAuth: true },
},
{
path:'coupon',
name: 'coupon',
component: Coupon,
meta: { requiresAuth: true },
}
]
},
{ // 引入 CustomerOrder的路徑
path: '/',
name: 'HelloWorld',
component: DashBoard,
children:[
{
path:'customer_order',
name: 'CustomerOrder',
component: CustomerOrder,
},
]
},
]
});

這邊要注意一下CustomerOrder的路徑,它就不是掛在admin後面,是直接首頁的後面,
而且這個頁面也不需要驗證就可以登入進去,因為,它是使用者在用的頁面。
不過,要特別注意的是,它也是屬於DahBoard的巢狀router之一喔。

step3.
記得去sidebar的元件中加入連到這個頁面的連結

1
2
3
4
---sidebar.vue元件檔---
<li class="nav-item">
<router-link class="nav-link" to="/customer_order"><i class="fas fa-shopping-cart mr-1"></i>模擬訂單</router-link>
</li>

step4.
接著,你就去把取得商品列表的api接進來。
在六角課程有提供這個api
你要記得,這邊的話是要用客戶免驗證那邊的api,不是用後台的那一個admin的api喔。

step5.
將網上提供的模擬訂單的card模板拉到你的這個CustomerOrder元件檔中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
---CusteomerOrder.vue元件檔---
<div class="card border-0 shadow-sm">
<div style="height: 150px; background-size: cover; background-position: center" :style="{backgroundImage:`url(${item.imageUrl})`}">
</div>
<div class="card-body">
<span class="badge badge-secondary float-right ml-2">分類</span>
<h5 class="card-title">
<a href="#" class="text-dark">標題</a>
</h5>
<p class="card-text">內容</p>
<div class="d-flex justify-content-between align-items-baseline">
<!-- <div class="h5">2,800 元</div> -->
<del class="h6">原價 2,800 元</del>
<div class="h5">現在只要 1,400 元</div>
</div>
</div>

你要特別注意一下這邊card的背景圖案引用圖片的寫法,原本的background-image要寫成駝峰式backgroundImag這樣才
有辦法吃到這個css樣式。

取得單一產品

此小節要完成,當你點擊查看更多的按鈕時,會挑出一個大Modal,呈現該品項的詳細資訊。
step1.
取得單一商品也有它自己的api喔。
以下就是六角提供的單一商品api

step2.
我們先在customer_order元件檔中,新增一個getProduct的方法。

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
---customer_order.vue元件檔---
<template>
<button type="button" class="btn btn-outline-secondary btn-sm" @click="getProduct(item.id)">
<i class="fas fa-spinner fa-spin"></i>
查看更多
</button>
</template>

<script>
data(){
return {
pagination:{},
products:[],
product:{}, // 新增product物件
tempProduct:{},
isLoading:false,
}
},
methods:{

getProduct(id){
const vm = this;
const api = `${process.env.APIPATH}/api/${process.env.CUSTOMPATH}/product/${id}`;
this.$http.get(api).then((response) => {
vm.product = response.data.product;

})
}
},
</script>

step3.
接著,我們就引用使用Modal的部分。
所以,我們一樣需要引用jQuery

1
2
3
<script>
import $ from 'jquery';
</script>

step4.
那在跳出來的樣式的modal,我們就先沿用之前的productModal的樣式。
裡面的一些樣式,你還是要自己修改
那以下是我自己寫的Modal樣式

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
38
39
40
<div class="modal fade" id="productModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content border-0">
<div class="modal-header text-dark">
<h3 class="modal-title" id="exampleModalLabel">
<span class="font-weight-bold">{{product.title}}</span>
</h3>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="card">
<img class="card-img-top" :src= "product.imageUrl" alt="Card image cap">
<div class="card-body">
<h5 class="card-title">{{product.description}}</h5>
<div class="d-flex justify-content-between align-items-end">
<del class="h4">原價{{product.origin_price}}元</del>
<strong class="h2">現在只要{{product.price}}元</strong>
</div>

<div class="form-group mt-3">
<select class="form-control form-control-lg" aria-label="Default select example">
<option selected disabled value="">---選購商品---</option>
<option value="1">選購1件</option>
<option value="2">選購2件</option>
<option value="3">選購3件</option>
</select>
</div>

</div>
</div>
</div>
<div class="modal-footer">
<span class="h4 text-muted">小計 {{product.price}} 元</span>
<button type="button" class="btn btn-primary">加到購物車</button>
</div>
</div>
</div>
</div>

step5.
接著,我們要專門新增一個變數,用來放Modal目前正在顯示的物品的id。
並透過這個變數,來判斷目前是哪個產品在loading中。

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<template>
<button type="button" class="btn btn-outline-secondary btn-sm" @click="getProduct(item.id)">
<i class="fas fa-spinner fa-spin" v-if="status.loadingItem===item.id"></i>
查看更多
</button>
<button type="button" class="btn btn-outline-danger btn-sm ml-auto">
<i class="fas fa-spinner fa-spin" v-if="status.loadingItem===item.id"></i>
加到購物車
</button>
</template>

<script>
import $ from 'jquery'; // 引入jquery
import pagination from './pagination';

export default{
data(){
return {
pagination:{},
products:[],
product:{},
tempProduct:{},
isLoading:false,
status:{ // 新增status變數
loadingItem: ''
}
}
},
methods:{
getProducts(page = 1){
const vm = this;
const api = `${process.env.APIPATH}/api/${process.env.CUSTOMPATH}/products?page=${page}`;
vm.isLoading = true;
this.$http.get(api).then((response) => {

vm.isLoading = false;
vm.products = response.data.products;

vm.pagination = response.data.pagination;
})
},
getProduct(id){
const vm = this;
const api = `${process.env.APIPATH}/api/${process.env.CUSTOMPATH}/product/${id}`;
vm.status.loadingItem = id; // 存取丟進來的id
this.$http.get(api).then((response) => {
$('#productModal').modal('show');
vm.product = response.data.product;
console.log(vm.product);
vm.status.loadingItem = ''; // 完成之後,將這個id還原成預設值
})
}
},
created(){
this.getProducts();
},
components:{
pagination,
}
}

</script>

你可以看到,我們在察看更多 和 加到購物車 這兩個按鈕加入了,判斷當下的loadingItem的值
是不是跟該樣品的id一樣,如果一樣的話,就會跑fontawesome的動畫特效。

購產品及加入購物車

本小節要教怎麼寫加入到購物車的功能。
step1.
這邊要注意,加入購物車的功能,只會傳入該產品的id和數量。
那加入購物車也有自己的api網址 喔 。

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
---CustomerOrder.vue---
<template>
<button type="button" class="btn btn-outline-danger btn-sm ml-auto" @click="addToCart(item.id)">
<i class="fas fa-spinner fa-spin" v-if="status.loadingItem===item.id"></i>
加到購物車
</button>
</template>

<script>

methods:{

addToCart(id, qty = 1){
const vm = this;
const api = `${process.env.APIPATH}/api/${process.env.CUSTOMPATH}/cart`;
vm.status.loadingItem = id;
const cart = {
product_id : id,
qty
}
this.$http.post(api, {data: cart}).then((response) => {
console.log(response);
vm.status.loadingItem = '';
})
}


}

</script>

以上你就可以看到,我們加入了addToCart的方法,並傳入產品id和數量(預設是1),
並創一個物件cart把這兩個值包起來,丟到後端去。
step2.
把跳出來的Modal也加上加到購物車的效果。

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
38
<div class="modal fade" id="productModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content border-0">
<div class="modal-header text-dark">
<h3 class="modal-title" id="exampleModalLabel">
<span class="font-weight-bold">{{product.title}}</span>
</h3>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="card">
<img class="card-img-top" :src= "product.imageUrl" alt="Card image cap">
<div class="card-body">
<h5 class="card-title">{{product.description}}</h5>
<div class="d-flex justify-content-between align-items-end">
<del class="h4">原價{{product.origin_price}}元</del>
<strong class="h2">現在只要{{product.price}}元</strong>
</div>

<div class="form-group mt-3">
<select class="form-control form-control-lg" aria-label="Default select example" v-model="product.num"> // 將選擇的數量綁在product的num屬性上
<option :value="num" v-for="num in 10" :key="num">選購{{num}} {{product.unit}}</option> // 動態產生10個選項

</select>
</div>

</div>
</div>
</div>
<div class="modal-footer">
<span class="h4 text-muted">小計 {{product.price*product.num}} 元</span> // 計算總共多少錢
<button type="button" class="btn btn-primary" @click="addToCart(product.id, product.num)">加到購物車</button> // 將product.id和選購數量丟到addToCart中
</div>
</div>
</div>
</div>

以上的部分就是為跳出來的Modal新增的部分,最主要是在<select>標籤那邊為它綁定product.num的屬性,以取得選購了多少商品。

step3.
取得購物車列表也有自己的api 喔。
那它會回傳一個陣列,裏面包含了所有選購商品,另外,它也回傳總價 和 經過優惠券計算之後的價格。

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
methods:{
addToCart(id, qty = 1){
const vm = this;
const api = `${process.env.APIPATH}/api/${process.env.CUSTOMPATH}/cart`;
vm.status.loadingItem = id;
const cart = {
product_id : id,
qty
}
this.$http.post(api, {data: cart}).then((response) => {
console.log(response);
vm.status.loadingItem = '';
$('#productModal').modal('hide');
})
vm.getCart(); // 取得購物車列表
},
getCart(){
const vm = this;
const api = `${process.env.APIPATH}/api/${process.env.CUSTOMPATH}/cart`;
this.$http.get(api).then((response) => {
console.log(response);
})
}
},
created(){
this.getProducts();
this.getCart(); // 取得購物車列表
},

你可以看到我們除了增加了取得購物車列表的方法getCart之外,我們也分別在created和完成增加購物車的這兩個動作之後,
也分別加上了取得購物車列表的功能喔。

刪除購物車品項及新增優惠碼

step1.
刪除某一筆購物車內的資料

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
<template>
<tr v-for="(item, key) in cartTotal" :key="key">
<td>
<a class="btn fas fa-trash-alt btn-outline-danger rounded" @click.prevent="delCart(item.id)"></a>
</td>
<td>{{item.product.title}}</td>
<td>{{item.qty}}/{{item.unit}}</td>
<td class="text-right">{{item.total}}</td>
</tr>
</template>

<script>
methods:{
delCart(id){
const vm = this;
console.log(id);
vm.isLoading = true;
const api = `${process.env.APIPATH}/api/${process.env.CUSTOMPATH}/cart/${id}`;
this.$http.delete(api).then((response) => {
vm.isLoading = false;
console.log(response);
this.getCart();
})
}
}
</script>

你可以看到,在刪除的按鈕連動到刪除購物車產品的方法delCart。
如此就可以刪除該購物車項目囉。

另外,要注意,當購物車有加入產品的時候,購物車列表才會顯現出來,不然,就不要產出來。

step2.
套用優惠碼的功能。
那在課程中也是有提供,套用優惠券的api喔。

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
<template>
<div class="input-group w-50">
<input type="text" class="form-control" placeholder="Username" v-model="coupon_code" aria-label="Username" aria-describedby="basic-addon1">
<div class="input-group-append">
<button class="btn btn-primary" id="basic-addon1" @click="addCouponCode">套用優惠碼</button>
</div>
</div>
</template>

<script>
addCouponCode(){
const vm = this;

vm.isLoading = true;
const api = `${process.env.APIPATH}/api/${process.env.CUSTOMPATH}/coupon`;
const coupon = {
code:vm.coupon_code
}
this.$http.post(api, {data:coupon}).then((response) => {
this.getCart(); // 取得購物車列表
vm.isLoading = false;
})


}
</script>

你可以看到,我們額外新增了一個coupon_code的屬性來綁定輸入的coupon內容,接著,在addCouponCode的方法裡面,
在新增一個變數coupon物件,來將coupon_code包起來,丟到後端api就可以囉。

step3.
那當你輸入正確的coupon_code後,就會跳出不一樣的total和final_total,
那每一個品項也都會有不一樣的品項價格

所以,就介面上就要加入哪個產品有加套入優惠券的字樣喔。

*建立訂單及表單驗證技巧

這邊除了要做出送出表單的功能外,還要做出表單驗證的功能。
這邊我們就會用一個叫vee-validate的套件來實做出這個功能。
step1.
你可以將網上提供的公版 先抓下來使用。
step2.
接著,你去結帳頁面的api
可以看到你回傳的資料格式

1
2
3
4
5
6
7
8
9
10
11
{
"data": {
"user": {
"name": "test",
"email": "test@gmail.com",
"tel": "0912346768",
"address": "kaohsiung"
},
"message": "這是留言"
}
}

而為了要跟這個格式一樣,所以,我們也創出了以下的資料成員,並將它們分別綁入每個結帳頁面中的對應欄位中。

1
2
3
4
5
6
7
8
9
10
11
12
data:{
form:{
user:{
name:'',
email:'',
tel:'',
address:'',
},
message:''
}

}

step3.
製作提交的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<form @submit.prevent="createOrder">

</form>
</template>

methods:{
createOrder(){
const vm = this;
const order = vm.form;
vm.isLoading = true;
const api = `${process.env.APIPATH}/api/${process.env.CUSTOMPATH}/order`;

this.$http.post(api,{data:order}).then((response) => {
console.log('訂單已建立', response);
vm.isLoading = false;
})
}
}

以上你可以看到我們新增了createOrder方法之外,也在form標籤上新增了綁定createOrder的提交事件。

step4.

安裝表單驗證工具vee-validate

a.
首先,先輸入指令npm install vee-validate --save
安裝這個套件。
b.
接下來就到main.js檔案中,來引用這個套件。

1
2
3
4
5
6
---main.js---

import VeeValidate from 'vee-validate'; // 注入vee-validate

Vue.use(VueAxios, axios)
Vue.use(VeeValidate); // 啟用 vee-validate

小插曲-安裝vue-i18n來支援中文化

在這邊我們又要額外安裝vue-i18n這個套件來支援中文化,不然,按照當時教課的環境跟步驟是無法跑出驗證的效果喔。
那詳細的安裝介紹,在這位同學的網誌 上有提到,安照這個步驟裝就ok囉。

step5.
接下來我們再表單驗證中的其中一個單位加入vee-validate的驗證內容。

1
2
3
4
5
6
<div class="form-group">
<label for="username">收件人姓名</label>
<input type="text" class="form-control" v-validate="'required'" name="name" id="username"
v-model="form.user.name" placeholder="輸入姓名" :class="{'is-invalid': errors.has('name')}">
<span class="text-danger" v-if="errors.has('name')">姓名必須輸入</span>
</div>

這邊的寫法有幾個重點,
a.
input欄位裡面一定要有name屬性,那name屬性裡面的內容值是自訂義的,這邊剛好是name,就看當下的情況決定。
b.
你可以看到我們在input欄位加入了v-validate="'requierd'",來讓這個套件知道這個欄位需要被驗證。
c.
最後,在是否跳出驗證提是內容的地方,我們利用判斷v-if="errors.has('name')",若它為true就顯示,若為false就不顯示。
那這個name就是input欄位的name屬性裡面的值。
d.
另外,我在input欄位裡面,也加入了透過判斷errors.has('name'),是否要為該input欄位加入紅框提示框或不用加入紅框提示框。

step6.
接下來,我們為email的欄位做驗證。
像這種有特定內容的欄位,vee-validate就有特定的相對應的驗證方式。

1
2
3
4
5
6
<div class="form-group">
<label for="useremail">Email</label>
<input type="email" class="form-control" name="email" id="useremail"
v-model="form.user.email" placeholder="請輸入 Email" required v-validate="'required|email'">
<span class="text-danger" v-if="errors.has('email')">{{errors.first('email')}}</span>
</div>

那你可以看到,在錯誤提示部分,我們是用官方提供的寫法errors.first('email'),但是,這種提示的內容是英文的,所以,接下來是介紹怎麼
把它弄成中文的。

step7.
首先,你先到main.js檔案分別引入vee-validate的中文檔案並啟用它

1
2
3
4
---main.js---
import zhTWValidate from 'vee-validate/dist/locale/zh_TW' // 引入中文檔

VeeValidate.Validator.localize('zh_TW', zhTWValidate); // 啟用這個這文檔

要注意一下,已用的啟用該中文檔是用VeeValidate.Validator.localize的語法,比較特別一點。

step8.
接著,加入vee-validate的表單整體驗證的js程式碼,在官方文件 有提供。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script>
methods:{
createOrder(){
const vm = this;
const order = vm.form;
const api = `${process.env.APIPATH}/api/${process.env.CUSTOMPATH}/order`;
this.$validator.validate().then(valid => {
if (!valid) {
console.log('欄位不完整');
} else {
this.$http.post(api,{data:order}).then((response) => {
console.log('訂單已建立', response);

})
}
});

}

}
</script>

你可以看到,我們將提交表單的程式碼塞到vee-valid官方提供的驗正程式碼裡面,如此,就可以達到總表單驗證的功能囉。
另外,我自己改了一下發送表單的觸發程式碼

1
2
3
4
5
6
7
8
9
10
<form class="col-md-6">  // 把@submit.prevent="createOrder"拿掉

<div class="form-group">
<label for="comment">留言</label>
<textarea name="" id="comment" class="form-control" cols="30" rows="10" v-model="form.message"></textarea>
</div>
<div class="text-right">
<button class="btn btn-danger" type="submit" @click.prevent="createOrder">送出訂單</button> // 由它來發送表單
</div>
</form>

你可以看到我把form標籤上的發送功能拿掉了,換成由送出訂單的按鈕來擔任發送表單的功能,另外,記得要為它加上type="submit"的屬性喔。