> 從這篇文章開始我們要正式進到**商店前台**的頁面製作啦! > 首先我們一樣先從產品列表頁面開始製作,那我們就開始吧! ### 商店前台頁面編輯 * 首先在 `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 找到元件來做使用![參考文件](https://icons.getbootstrap.com/) * 在這個專案我們是直接透過 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">