這邊紀錄的內容是專門撰寫有關使用者在使用的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">×</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">×</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"
的屬性喔。