# 第四週:元件化
###### tags: `六角學院_Vue直播班_主線任務`
## 目錄
[ToC]
---
## 產品頁面
### 刪除 modal 元件化
1.將原本刪除 modal 的 html,變成 x-template,元件標籤(delete-product-modal)
| 問題 | 成因 | 行動 |
|:--------------- |:------------------------------ |:------------ |
| 預設匯出失敗 | 不知道怎麼寫 | [偷看助教寫的,改用 x-template 比較簡單](https://reurl.cc/qOVxYE) |
1.1 將 bootstap 實體化移到 delete-product-modal 的 mounted 裡面
| 問題 | 成因 | 行動 |
|:-------- |:---------------- |:------------------------ |
| 沒辦法把 modal 關掉| 把關掉的 function ( hideModal ) 放到 methods 外面所以叫不到 | 塞回 methods |
2.註冊 delete-product-modal
| 問題 | 成因 | 行動 |
|:-------- |:---------------- |:------------------------ |
| 註冊失敗 | 忘記區域註冊方法 | [偷看助教的,改成全域註冊](https://reurl.cc/pW3zKx) |
3.使用 delete-product-modal
| 問題 | 成因 | 行動 |
|:-------- |:---------------- |:------------------------ |
| 一直跳錯 | x-template 的 script 標籤要放在 div.app 外面 | 放到外面 |
4.將選擇的產品(tempProduct) 傳入 delete-product-modal
5.點選確認刪除會刪掉商品,跳出提示後,關閉 modal
| 問題 | 成因 | 行動 |
|:-------- |:---------------- |:------------------------ |
| 按下確認刪除之後列表上的資料不會更新 | 刪除完後沒辦法呼叫外面的 this.getProducts() | 內層用 emit 傳一個 update 事件呼叫外層的 getProducts() |
---
### 新增編輯 modal 元件化
**1** 改成全域註冊的原件
**1.1** 將原本新增編輯 modal 的 html,變成 x-template
```htmlembedded=
<script type="text/x-template" id="delProductModal">
<div>原本新增編輯 modal 的 html</div>
</script>
```
**1.2** 進行全域註冊
```javascript=
app.component('update-product-modal', {
template: '#productModal',
data() {
return {
apiUrl: 'https://vue3-course-api.hexschool.io/v2',
path: 'jasonchen',
};
},
});
```
**1.3** 將 bootstrap 實體化移到 update-product-modal 的 mounted
```javascript=
mounted() {
// 創建 bootstrap 實體
// 新增和編輯的 modal
productModal = new bootstrap.Modal(
document.getElementById('productModal'),
{
// esc 沒辦法關掉 modal
keyboard: false,
// 點選旁邊沒辦法關掉 modal
backdrop: 'static',
}
);
}
```
**2** 將元件要用的資料傳入( update-product-modal )
**2.1** 在 html 使用 update-product-modal
```htmlembedded=
<update-product-modal></update-product-modal>
```
**2.2** 將選擇的產品( tempProduct )傳入 update-product-modal
```htmlembedded=
<update-product-modal
:temp-product="tempProduct"
></update-product-modal>
```
**2.3** 將傳入的 tempProduct 用 props 接住
```javascript=
app.component('update-product-modal', {
template: '#productModal',
props: ['tempProduct'],
});
```
**2.4** 將是否是編輯功能( isEdit )傳入,來決定 modal 是新增 modal 還是編輯 modal
```htmlembedded=
<update-product-modal
:temp-product="tempProduct"
:is-edit="isEdit"
></update-product-modal>
```
**2.5** 將傳入的 isEdit 用 props 接住
```javascript=
app.component('update-product-modal', {
template: '#productModal',
props: ['tempProduct', 'isEdit'],
});
```
**3** 將原本在外面的 methods 搬到元件裡面
**3.1** 點選新增 modal 的確認會呼叫 addProduct() 、editProduct(),當成功時會
**3.1.1** 呼叫 hideModal(),關閉 modal
**3.1.1** 向外傳出 update 事件
```javascript=
methods: {
addProduct() {
const url = `${this.apiUrl}/api/${this.path}/admin/product`;
const addData = { data: this.tempProduct };
axios
.post(url, addData)
.then((res) => {
alert(res.data.message);
this.hideModal();
this.$emit('update');
})
.catch((err) => {
alert(err.data.message);
});
},
editProduct() {
const url = `${this.apiUrl}/api/${this.path}/admin/product/${this.tempProduct.id}`;
const editData = { data: this.tempProduct };
axios
.put(url, editData)
.then((res) => {
alert(res.data.message);
this.hideModal();
this.$emit('update');
})
.catch((err) => {
alert(err.data.message);
});
},
// 把關閉 modal 拉出來做成 function,將功能拆分清楚
hideModal() {
productModal.hide();
},
},
```
**3.2** 外層接收 update 事件,呼叫 getProducts()
```htmlembedded=
<update-product-modal
:temp-product="tempProduct"
:is-edit="isEdit"
@update="getProducts"
></update-product-modal>
```
```javascript=
methods: {
getProducts() {
// 清空 tempProduct
this.tempProduct = { imagesUrl: [] };
const url = `${this.apiUrl}/api/${this.path}/admin/products`;
axios
.get(url)
.then((res) => {
this.products = res.data.products;
})
.catch((err) => {
alert(err.data.message);
});
},
}
```
### 分頁製作
**1** 放入分頁結構
```htmlembedded=
<!-- 分頁 -->
<nav aria-label="Page navigation ">
<ul class="pagination">
<!-- 上一頁符號 -->
<li class="page-item">
<a class="page-link" href="#" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
<!-- 頁碼 -->
<li class="page-item"><a class="page-link" href="#">1</a></li>
<li class="page-item"><a class="page-link" href="#">2</a></li>
<li class="page-item"><a class="page-link" href="#">3</a></li>
<!-- 下一頁符號 -->
<li class="page-item">
<a class="page-link" href="#" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
```
**2** data 部分新增 pagination ,在 getProducts() 拿到要做出分頁的資訊
```javascript=
const app = createApp({
data() {
pagination: {},
};
},
methods: {
getProducts() {
// 清空 tempProduct
this.tempProduct = { imagesUrl: [] };
const url = `${this.apiUrl}/api/${this.path}/admin/products`;
axios
.get(url)
.then((res) => {
this.products = res.data.products;
this.pagination = res.data.pagination;
})
.catch((err) => {
alert(err.data.message);
});
}
}
});
```
**3** v-for 出幾個分頁
```htmlembedded=
<li class="page-item" v-for="num in pagination.total_pages" :key="num">
<a class="page-link" href="#">{{num}}</a>
</li>
```
**4** 是否有前一頁和後一頁
```htmlembedded=
<!-- 上一頁符號 -->
<li class="page-item" :class="{disabled : !pagination.has_pre}">
<a class="page-link" href="#" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
<!-- 下一頁符號 -->
<li class="page-item" :class="{disabled : !pagination.has_next}">
<a class="page-link" href="#" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
```
**5** 在 data 新增現在的頁數( nowPage ),增加這個是為了在每次執行 getProducts() 時,不會跳回到第一頁
```javascript=
const app = createApp({
data() {
nowPage: 1,
};
},
methods: {
getProducts() {
// 清空 tempProduct
this.tempProduct = { imagesUrl: [] };
const url = `${this.apiUrl}/api/${this.path}/admin/products?page=${this.nowPage}`;
axios
.get(url)
.then((res) => {
this.products = res.data.products;
this.pagination = res.data.pagination;
this.nowPage = res.data.pagination.current_page;
})
.catch((err) => {
alert(err.data.message);
});
}
});
```
**6** 使用 nowPage 決定頁碼的 style
```htmlembedded=
<li class="page-item" v-for="num in pagination.total_pages" :key="num" :class="{active : num === nowPage}">
<a class="page-link" href="#">{{num}}</a>
</li>
```
**7** 點擊分頁頁碼會去取得該頁的資訊
```htmlembedded=
<li class="page-item" v-for="num in pagination.total_pages" :key="num" :class="{active : num === nowPage}">
<a class="page-link" href="#" @click="changePage(num)">{{num}}</a>
</li>
```
```javascript=
const app = createApp({
methods: {
changePage(page) {
this.nowPage = page;
this.getProducts();
}
});
```
### 分頁元件化
**1** 改成全域註冊的原件
**1.1** 將原本分頁 的 html,變成 x-template
```htmlembedded=
<script type="text/x-template" id="productsPagination">
<nav aria-label="Page navigation">
<ul class="pagination">
<!-- 上一頁符號 -->
<li class="page-item" :class="{disabled : !pagination.has_pre}">
<a class="page-link" href="#" aria-label="Previous" @click.prevent="$emit('update',pagination.current_page-1)">
<span aria-hidden="true">«</span>
</a>
</li>
<!-- 頁碼 -->
<li
class="page-item"
v-for="num in pagination.total_pages"
:key="num"
:class="{active : num === pagination.current_page}"
>
<a class="page-link" href="#" @click.prevent="$emit('update',num)">{{num}}</a>
</li>
<!-- 下一頁符號 -->
<li class="page-item" :class="{disabled : !pagination.has_next}">
<a class="page-link" href="#" aria-label="Next" @click.prevent="$emit('update',pagination.current_page+1)">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
</script>
```
**1.2** 進行全域註冊
```javascript=
app.component('products-pagination', {
template: '#productsPagination'
});
```
**2** 將元件要用的資料傳入( update-product-modal )
**2.1** 在 html 使用 update-product-modal
```htmlembedded=
<products-pagination></products-pagination>
```
**2.2** 將選頁碼資訊( pagination ),傳入 update-product-modal
```htmlembedded=
<products-pagination
:pagination="pagination"
></products-pagination>
```
**2.3** 將傳入的 tempProduct 用 props 接住
```javascript=
app.component('products-pagination', {
template: '#productsPagination',
props: ['pagination'],
});
```
**3** 內元件( products-pagination )向外元件傳送資訊
**3.1** 點選頁碼時會向外面傳出 update 事件和現在點選的頁碼數字
```htmlembedded=
<li class="page-item" v-for="num in pagination.total_pages" :key="num" :class="{active : num === pagination.current_page}">
<a class="page-link" href="#" @click.prevent="$emit('update',num)">{{num}}</a>
</li>
```
**3.2** 外層接收 update 事件和點選數字,呼叫 changePage()
```htmlembedded=
<products-pagination
:pagination="pagination"
@update="changePage"
></products-pagination>
```
```javascript=
const app = createApp({
methods: {
changePage(page) {
this.nowPage = page;
this.getProducts();
}
});
```
**4** 點選上下一頁可以換頁
**4.1** 點選上下一頁會向外面傳出 update 事件和加或減現在點選的頁碼數字( nowPage加或減 1 )
```htmlembedded=
<!-- 上一頁符號 -->
<li class="page-item" :class="{disabled : !pagination.has_pre}">
<a class="page-link" href="#" aria-label="Previous" @click.prevent="$emit('update',pagination.current_page-1)">
<span aria-hidden="true">«</span>
</a>
</li>
<!-- 下一頁符號 -->
<li class="page-item" :class="{disabled : !pagination.has_next}">
<a class="page-link" href="#" aria-label="Next" @click.prevent="$emit('update',pagination.current_page+1)">
<span aria-hidden="true">»</span>
</a>
</li>
```
**4.2** 要避免第一頁和最後一頁爆掉問題
```javascript=
changePage(page) {
// 第一頁不能往前
if (page <= 1) {
page = 1;
}
// 最後一頁不能往後
if (page >= this.pagination.total_pages) {
page = this.pagination.total_pages;
}
this.nowPage = page;
this.getProducts();
}
```
### 外層元件
1.每次 getProducts(),就清空 tempProduct ,原本是寫在按各個 modal 確認按鈕時會執行,現在每次按確認都會傳出一個 update 事件執行 getProducts() ,所以寫在getProducts() 裡面一次就好
```javascript=
getProducts() {
// 清空 tempProduct
this.tempProduct = { imagesUrl: [] };
const url = `${this.apiUrl}/api/${this.path}/admin/products`;
axios
.get(url)
.then((res) => {
this.products = res.data.products;
})
.catch((err) => {
alert(err.data.message);
});
},
```
2.另外在打開新增 modal 時,也要清空一次 tempProduct,保險起見
```javascript=
openModal(type, product) {
// 使用深層拷貝避免改動 modal 的值時,改到外面清單的值
this.tempProduct = JSON.parse(JSON.stringify(product));
if (type === 'delete') {
delProductModal.show();
} else if (type === 'add') {
// 清空 tempProduct
this.tempProduct = { imagesUrl: [] };
this.isEdit = false;
productModal.show();
} else if (type === 'edit') {
this.isEdit = true;
productModal.show();
}
}
```
### 問題紀錄模板
| 問題 | 成因 | 行動 |
|:--------------- |:------------------------------ |:------------ |
| cookie 寫不進去 | 忘記怎麼把回傳的 cookie 寫進去 | 偷看之前寫的 |
| 功能 | 步驟 | 問題 | 成因 | 行動 |
| ---- | ---- |:----------------- | ---- |:----------------------- |
| | | GitHub Sync | | [:link:][GitHub-Sync] |
| | | Browser Extension | | [:link:][HackMD-it] |
| | | Book Mode | | [:link:][Book-mode] |
| | | Slide Mode | | [:link:][Slide-mode] |
| | | Share & Publish | | [:link:][Share-Publish] |
[GitHub-Sync]: https://hackmd.io/c/tutorials/%2Fs%2Flink-with-github
[HackMD-it]: https://hackmd.io/c/tutorials/%2Fs%2Fhackmd-it
[Book-mode]: https://hackmd.io/c/tutorials/%2Fs%2Fhow-to-create-book
[Slide-mode]: https://hackmd.io/c/tutorials/%2Fs%2Fhow-to-create-slide-deck
[Share-Publish]: https://hackmd.io/c/tutorials/%2Fs%2Fhow-to-publish-note