> 從這篇文章開始我們要正式進到**商店前台**的頁面製作啦!
> 首先我們一樣先從產品列表頁面開始製作,那我們就開始吧!
### 商店前台頁面編輯
* 首先在 `src > views` 新增以下檔案:
* 前台總覽頁面:***ShopBoard.vue***
* 前台商品列表頁:***ShopProductsList.vue***
* 前台單一商品頁:***ShopPerProduct.vue***
* 接著進行路由設定:
```javascript=
{
path: '/shop',
component: () => import('../views/ShopBoard.vue'),
children: [
{
path: 'products',
component: () => import('../views/ShopProductsList.vue')
},
{
path: 'product/:productId',
component: () => import('../views/ShopPerProduct.vue')
}
]
}
```
#### ShopBoard.vue 商店總覽頁面編輯
* 新增 `navbar` 、`footer`
* 插入 `toast` 訊息回饋
```html=
<template>
<!-- 此為 navbar 內容過程不贅述 -->
<ToastList></ToastList>
<CartCanvas ref="cartCanvas"></CartCanvas>
<NavCanvas ref="offcanvasNavbar"></NavCanvas>
<router-view></router-view>
<ShopFooter></ShopFooter>
</template>
<script>
import emitter from '@/methods/emitter'
import ToastList from '@/components/ToastList.vue'
import cartStore from '@/stores/cartStore'
import CartCanvas from '../components/CartCanvas.vue'
import { mapState, mapActions } from 'pinia'
import NavCanvas from '../components/NavCanvas.vue'
import ShopFooter from '@/components/ShopFooter.vue'
export default {
components: {
ToastList,
CartCanvas,
NavCanvas,
ShopFooter
},
provide () {
return {
emitter
}
},
computed: {
...mapState(cartStore, ['carts'])
},
methods: {
// 取得購物車數量
...mapActions(cartStore, ['getCart'])
},
created () {
this.getCart()
}
}
</script>
```
<img src="https://firebasestorage.googleapis.com/v0/b/emmablog-e5a1c.appspot.com/o/shop.png?alt=media&token=de8a6301-04e8-40cd-ba77-c935d945a4db">
#### ShopProductsList.vue 產品列表頁面編輯
* 新增商品列表表格
* 插入 `LoadingOverlay` loading 效果
* 插入 `PaginationCard` 頁籤
* 透過 `this.$router.push(產品頁面路由)` **進到單一商品頁**
```html=
<template>
<LoadingOverlay :active="isLoading"></LoadingOverlay>
<div class="container d-flex justify-content-center align-items-center flex-column">
<!-- 產品列表 -->
<div class="row mt-4">
<div class="col">
<table class="table align-middle">
<thead>
<tr>
<th>圖片</th>
<th>商品名稱</th>
<th>價格</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="item in products" :key="item.id">
<td style="width:200px">
<div style="height: 150px; background-size: cover; background-position: top"
:style="{backgroundImage: `url(${item.imageUrl})`}"></div>
</td>
<td>{{ item.title }}</td>
<td>{{ item.price }}</td>
<td>
<button type="button" class="btn btn-outline-secondary" @click="getProduct(item.id)">查看更多</button>
<button class="btn btn-outline-danger">加入購物車</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- 頁籤 -->
<PaginationCard :pages="pagination" @emit-pages="getProducts"></PaginationCard>
</div>
</template>
<script>
import PaginationCard from '@/components/PaginationCard.vue'
export default {
data () {
return {
products: [],
pagination: {},
isLoading: false
}
},
components: {
PaginationCard
},
methods: {
// 取得產品列表
getProducts (page = 1) {
const api = `${process.env.VUE_APP_API}api/${process.env.VUE_APP_PATH}/products/?page=${page}`
this.isLoading = true
this.$http.get(api)
.then((res) => {
this.products = res.data.products
this.pagination = res.data.pagination
this.isLoading = false
console.log(res.data)
})
},
// 進入單一商品介紹頁
getProduct (id) {
this.$router.push(`/shop/product/${id}`)
}
},
created () {
this.getProducts()
}
}
</script>
```
<img src="https://firebasestorage.googleapis.com/v0/b/emmablog-e5a1c.appspot.com/o/shop_product.png?alt=media&token=d549f71a-2cac-4435-bd4d-05d651bc5751">
#### ShopPerProduct.vue 單一商品頁面編輯
* 插入 `LoadingOverlay` loading 樣式
* 透過**路由去抓商品 ID** `this.$route.params.productId` ,以取得商品資訊
```html=
<template>
<LoadingOverlay :active="isLoading"></LoadingOverlay>
<div class="container mt-4">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><router-link to="/shop/products">全部商品</router-link></li>
<li class="breadcrumb-item active" aria-current="page">{{ product.title }}</li>
</ol>
</nav>
<div class="row">
<div class="col-8">
<h2>{{ product.title }}</h2>
<p>商品描述:Lorem ipsum dolor sit amet consectetur adipisicing elit. Velit, suscipit animi aut, consequatur voluptate nam molestiae praesentium maiores error eligendi quisquam at facere et, ducimus non accusantium dolorem dolore provident.</p>
<div style="height: 800px; background-size: cover; background-position: top"
:style="{backgroundImage: `url(${product.imageUrl})`}"></div>
</div>
<div class="col-4">
<p class="fs-6 mb-0"><del>原價 NT${{ product.origin_price }}</del></p>
<p class="fs-5"><strong>現在只要 NT${{ product.price }}</strong></p>
<hr>
<button type="button" class="btn btn-outline-danger">加入購物車</button>
</div>
</div>
</div>
</template>
<script>
export default {
data () {
return {
product: {},
isLoading: false,
id: ''
}
},
methods: {
getProduct (id) {
const api = `${process.env.VUE_APP_API}api/${process.env.VUE_APP_PATH}/product/${id}`
this.isLoading = true
this.$http.get(api).then((res) => {
this.product = res.data.product
this.isLoading = false
console.log(res)
})
}
},
created () {
// 透過路由去抓商品 ID
this.id = this.$route.params.productId
this.getProduct(this.id)
}
}
</script>
```
<img src="https://firebasestorage.googleapis.com/v0/b/emmablog-e5a1c.appspot.com/o/shop-perproduct.png?alt=media&token=c78cc313-572c-442b-9774-e9de95e1c571">
### Bootstrap Icon
講到商品頁面就不能少了加入購物車的按鈕,像這類型的 icon 圖示我們也可以在 Bootstrap 找到元件來做使用
* 在這個專案我們是直接透過 npm 來安裝套件做使用:
```
npm i bootstrap-icons
```
* 接著回到 ***main.js*** 匯入
```javascript
import 'bootstrap-icons/font/bootstrap-icons.css'
```
* 完成後即可開始使用 BS5 icons
* 找到喜歡的 icon,進到內容頁可以看到右側的 `icon font` ,複製起來貼到 <tempalte></template> 指定位置就可以囉!
<img src="https://firebasestorage.googleapis.com/v0/b/emmablog-e5a1c.appspot.com/o/bs5_icon.png?alt=media&token=f5c575bf-a586-477e-b45b-04b756f87309">