# 第四週:元件化 ###### 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">&laquo;</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">&raquo;</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">&laquo;</span> </a> </li> <!-- 下一頁符號 --> <li class="page-item" :class="{disabled : !pagination.has_next}"> <a class="page-link" href="#" aria-label="Next"> <span aria-hidden="true">&raquo;</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">&laquo;</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">&raquo;</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">&laquo;</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">&raquo;</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