> 此篇會分享 **後台產品列表頁面** 在新增商品時,該如何抓取列表的商品資料;以及新增後又該如何更新到產品列表中。 > 其中會使用到 **Vue 資料傳遞**的概念,故會提及 **資料向內傳遞 `Props`** 以及 **向外傳遞 `Emit`** 的用法,那我們就開始吧! 如圖所示,當內、外層元件進行資料傳遞時,就會需要用到 `Props` & `Emit` 來進行溝通。 <img src="https://firebasestorage.googleapis.com/v0/b/emmablog-e5a1c.appspot.com/o/props___emit.png?alt=media&token=9fac5309-bdb8-438d-9572-9ee528e6fa56"> ### 資料向內傳遞 Props * 可以在內層元件透過 `props` 宣告要從外層元件傳遞資料的屬性名稱,並且搭配 `v-bind(:)` 做處理 * 口訣:**前內後外** <img src="https://firebasestorage.googleapis.com/v0/b/emmablog-e5a1c.appspot.com/o/props.png?alt=media&token=5a5dbce2-531e-4df8-981a-ccd959b5a3b7"> * **單向數據流** * 外部定義的資料,往內層傳遞時是單向性的,**無法透過 `v-modal` 來改變 `props` 傳入的內容** * **命名限制** * JS 命名需要使用 駝峰命名法 `superUrl` * 但是 HTML 的標籤都須以**小寫**顯示 * 故在 `<template></template>` 中可以透過 `super-url` 來做調整 ### 資料向外傳遞 Emit #### 觸發外部事件 * 可以在內層元件透過 emit 宣告要觸發外層元件傳遞資料的屬性名稱,並且搭配 `v-on(@)` 做處理 * 口訣:前內後外 * 在內層的 **刪除按鈕** 透過 emit 觸發外部事件 `@click="$emit('del-item')"` * 在外層的 `<DelModal></DelModal>` 加入 `@del-item="delProduct"` 以利觸發外層 `delProduct()` 事件,進而刪除商品。 <img src="https://firebasestorage.googleapis.com/v0/b/emmablog-e5a1c.appspot.com/o/emit_demo.png?alt=media&token=d12d081f-9f78-43ab-b117-da88bca3e0b7"> #### 傳遞資料狀態 * 將內部的資料傳到外部,函式需要帶入參數 <img src="https://firebasestorage.googleapis.com/v0/b/emmablog-e5a1c.appspot.com/o/emit_demo2.png?alt=media&token=f53f3577-2c05-4f5a-98ea-5d0d44f4b1ff"> ### 透過彈出視窗 Modal 新增商品 * 首先回到 ***ProductModal.vue*** 製作建立商品模板 <img src="https://firebasestorage.googleapis.com/v0/b/emmablog-e5a1c.appspot.com/o/addProducts_modal.png?alt=media&token=79ae6169-9055-4133-a224-7cc5997fd176"> * 接著在 ***ProductsPage.vue*** 新增以下資料: * 於 `data` 新增 `tempProduct: {}` * 於 `methods` 新增 `openModal()`、`updateProduct()` * **打 api 時要帶入參數 `this.axios.post(api, { data: this.tempProduct })`** ```javascript= <script> import ProductModal from '../components/ProductModal.vue' export default { data () { return { products: [], pagination: {}, tempProduct: {} } }, components: { ProductModal }, methods: { getProducts () { const api = `${process.env.VUE_APP_API}api/${process.env.VUE_APP_PATH}/admin/products` this.axios.get(api) .then((res) => { if (res.data.success) { console.log(res.data) this.products = res.data.products this.pagination = res.data.pagination } }) }, // 開啟 modal openModal () { this.tempProduct = {} const productComponent = this.$refs.productModal productComponent.showModal() }, // 新增商品 updateProduct (item) { this.tempProduct = item const api = `${process.env.VUE_APP_API}api/${process.env.VUE_APP_PATH}/admin/product` const productComponent = this.$refs.productModal this.axios.post(api, { data: this.tempProduct }) .then((res) => { console.log(res) // 完成後關閉 modal productComponent.hideModal() // 再次更新商品列表 this.getProducts() }) } }, created () { this.getProducts() } } </script> ``` * 完成後可以新增商品做確認 * 商品新增**成功** <img src="https://firebasestorage.googleapis.com/v0/b/emmablog-e5a1c.appspot.com/o/add_success.png?alt=media&token=17c9d168-89a1-4e55-a562-87323046f6ac"> * 商品新增**失敗**:(可能是必填欄位沒填,可以確認 message) <img src="https://firebasestorage.googleapis.com/v0/b/emmablog-e5a1c.appspot.com/o/add_false.png?alt=media&token=76cd6c36-d44c-44be-adba-5b7e806323c7"> ### 編輯、更新商品 * 在 ***ProductsPage.vue*** 新增以下: * 在 `data` 新增 `isNew: false` * 將 `openModal()` 新增參數: `openModal(isNew, item)` * 針對新增商品與編輯商品做區隔: * 如果是 **新增商品** 就帶出空物件 * **編輯商品** 就抓 item 的資料 ```javascript= openModal (isNew, item) { // 如果是新的就帶出空物件,不是就抓 item 的資料 if (isNew) { this.tempProduct = {} } else { this.tempProduct = { ...item } } this.isNew = isNew const productComponent = this.$refs.productModal productComponent.showModal() } ``` * 將 **`增加一個產品`** 按鈕帶入參數 `@click="openModal(true)` ```html= <button class="btn btn-primary" type="button" @click="openModal(true)">增加一個產品</button> ``` * 回到 **`編輯`** 按鈕新增觸發事件 `@click="openModal(false, item)"` ```html= <button class="btn btn-outline-primary btn-sm" @click="openModal(false, item)">編輯</button> ``` * 調整 `updateProduct()` 函式,分為 **新增商品**、**編輯商品** ```javascript= updateProduct (item) { this.tempProduct = item // 新增 let api = `${process.env.VUE_APP_API}api/${process.env.VUE_APP_PATH}/admin/product` let httpMethod = 'post' // 編輯 if (!this.isNew) { api = `${process.env.VUE_APP_API}api/${process.env.VUE_APP_PATH}/admin/product/${item.id}` httpMethod = 'put' } const productComponent = this.$refs.productModal this.axios[httpMethod](api, { data: this.tempProduct }) .then((res) => { console.log(res) productComponent.hideModal() this.getProducts() }) } ``` * 回到 ***ProductModal.vue*** 新增以下 * 宣告 `props` ```javascript= props: { product: { type: Object, default () { return {} } } }, watch: { product () { this.tempProduct = this.product } }, ``` * 針對 **`確認`** 按鈕新增 `emit` 觸發外部事件 ```html= <button type="button" class="btn btn-primary" @click="$emit('update-product', tempProduct)">確認</button> ``` * 回到 ***ProductsPage.vue*** 針對 `<ProductModal></ProductModal>` 新增 `ref` & `props` & `emit` : ```html= <ProductModal ref="productModal" :product="tempProduct" @update-product="updateProduct"></ProductModal> ``` <img src="https://firebasestorage.googleapis.com/v0/b/emmablog-e5a1c.appspot.com/o/editProduct_demo.png?alt=media&token=af335d34-9bb7-42cb-ab66-74d58999d107"> 下篇文章將會插播 **使用 mixin 整合相同程式碼** 以利多檔案間做使用,並且接續進行 **刪除商品** 的設定。