---
# System prepended metadata

title: Vue.js ecshop 14.商店產品列表頁面製作
tags: [ecshop 專案, "Vue.js\_"]

---

> 從這篇文章開始我們要正式進到**商店前台**的頁面製作啦！
> 首先我們一樣先從產品列表頁面開始製作，那我們就開始吧！

### 商店前台頁面編輯
* 首先在 `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"> 
  