> 此篇會分享 **後台產品列表頁面** 在新增商品時,該如何抓取列表的商品資料;以及新增後又該如何更新到產品列表中。
> 其中會使用到 **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 整合相同程式碼** 以利多檔案間做使用,並且接續進行 **刪除商品** 的設定。