--- tags: JS 5組 - 2022 秋季 JS直播班, 最終關卡挑戰 Day2 - 前台(購物車 - 功能整合) title: 最終關卡挑戰 Day2 - 前台(購物車 - 功能整合) --- ###### tags: `JS 5組 - 2022 秋季 JS直播班` 、`最終關卡挑戰 Day2 - 前台(購物車 - 功能整合)` ###### *date: 2022 / 11/ 22* # Day 2 - 前台(購物車- 功能整合) 昨天我們已經完成取得商品與購物車列表資訊,今天我們將專注於把產品新增到購物車內與整合購物車功能。 ### 今日挑戰目標: 前台(新增產品、修改產品數量、單筆刪除、全部刪除) ![](https://i.imgur.com/c9iYE6W.png) ### 使用API ![](https://i.imgur.com/B4O3mKc.jpg) ### 購物車功能任務 - 新增產品到購物車 - 單筆刪除購物車內的產品 - 修改購物車內的單筆產品數量 - 清空購物車 - 優化使用者體驗 - 使用 [sweetalert2](https://sweetalert2.github.io/#usage) 套件,製作提示使用者訊息。 - 增加 [loading](https://loading.io/) 動畫 --- ### shinyhung#3825 ```javascript ``` ### WeiJ#7376 實作 API 的優化處理 (關鍵字 `Debounce` & `Throttle`) 其目的是防止使用者重複按下按鈕導至送出多次 API 常會用於送出訂單 (實際上可能會影響庫存計算) 做法大概為送出 api 時再 api 回傳結果前要將原本的按鈕加上 `disabled`,讓按鈕不能按,等到回傳結果後再移除 `disabled` 狀態 我是使用 bootstrap5 來做開發,元件中有一個功能為 [spinners](https://getbootstrap.com/docs/5.2/components/spinners/#buttons) 他可以放在按鈕之中讓按鈕顯示 loading 狀態,而在非 `loadding` 時則會加上 `d-none` display:none 不顯示這個 className,所以我就用這個 className 做判斷 以商品加入購物車為例,我的 html 會是 ```htmlembedded <button class="btn btn-black btn-block" data-id="id"> 加入購物車 <span class="d-none loading spinner-border spinner-border-sm" role="status"></span> </button> ``` 除了按鈕外也放入 spinner 元件,並預設為 d-none 不顯示,當我按下這個 button 時會顯示 spinner 同時不能再點選第二下,直到 spinner 消失 ```javascript const addCartBtns = document.querySelectorAll('.product__item__btn button') addCartBtns.forEach(btn => { btn.addEventListener('click', (e) => { // 找到 button 下的 .loading 元素 const loading = e.target.querySelector('.loading'); // 如果 .loading 的 className 沒有 d-none 則代表 API 正在執行 return 結束函式 if (!loading.classList.contains('d-none')) return; const id = e.target.dataset.id; loading.classList.remove('d-none') // 移除 d-none 讓 spinner 顯示,接著執行 API cart.add(id); }) }) // cart.add(id) api.apiAddCart({ data: { productId: id, quantity: 1 } }).then((res) => { const { status, carts, finalTotal, message } = res.data; if (status) { // ... loading.classList.add('d-none') // 完成時要把 d-none 加回讓按鈕變成可點 successAlert('加入購物車成功'); } else { errorAlert(message); } }) ``` ### charlottelee849#0366 待辦事項 (未完成) - 後台 - [ ] 觀看後台訂單 **前台知識點** validate.js跟 mail 獨立驗證混合運用 ```JavaScript // mail 驗證放入送出訂單函式 做 if 判斷 if (validateEmail(customerEmail) == false) { Swal.fire("請填寫正確的Email", "格式輸入錯誤", "error"); return; } // 獨立對 email 驗證欄位做監聽 const customerEmail = document.querySelector("#customerEmail"); customerEmail.addEventListener("blur", function (e) { if (validateEmail(customerEmail.value) == false) { document.querySelector(`[data-message=Email]`).textContent = "請填寫正確 Email 格式"; return; } else { document.querySelector(`[data-message=Email]`).textContent = ""; return; } }); // mail欄位驗證 function validateEmail(mail) { if ( /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/.test( mail ) ) { return true; } return false; } ``` ### jimmyFang#9575 **任務清單(已完成)** - [x] 新增產品到購物車 - [x] 單筆刪除購物車內的產品 - [x] 修改購物車內的單筆產品數量 - [x] 清空購物車 - [x] 優化使用者體驗 - 使用 [sweetalert2](https://sweetalert2.github.io/#usage) 套件,製作提示使用者訊息。 - 增加 [loading](https://loading.io/) 動畫 目前進度: [demo](https://jimmyfang-ai.github.io/js-finalWork-wowoRoom/#orderInfo) **知識點分享:** 使用 [closest](https://hackmd.io/@w4wBc9wkR4CvPsIeEWiLbg/S1XivuMAu/%2FqUsdMyT8TcS2YEsdi0oFSw) + [dataset](https://hackmd.io/@w4wBc9wkR4CvPsIeEWiLbg/S1XivuMAu/%2FnCJeegx1QfiSnYK2TRqmAA) 取得產品列表上的 li 的 productId,因為實作上可能在產品卡片上有加入購物車、加入收藏的按鈕..等,透過這個方法可以簡化在多個按鈕上綁定 id 。 ```jsx= // 產品 - 顯示產品列表 function renderProductsList(data) { productWrap.innerHTML = data.map((product) => { return `<li class="productCard" data-product-id="${product.id}"> <h4 class="productType">${product.category}</h4> <img src="${product.images}" alt="${product.title}"> <a href="#" class="addCardBtn">加入購物車</a> <h3>${product.title}</h3> <del class="originPrice">NT$${tothousands(product.origin_price)}</del> <p class="nowPrice">NT$${tothousands(product.price)}</p> </li>` }).join(''); }; // 產品 - 新增產品到購物車 productWrap.addEventListener('click', (e) => { e.preventDefault(); // 只有點擊到加入購物車按鈕, 就取得 li 內的 產品 id if (e.target.getAttribute('class') !== "addCardBtn") return; // 產品數量(預設第一次新增產品的值) let productQty = 1; // 產品 id let productId = e.target.closest('li').dataset.productId; // 比對購物車內是否存在相同產品 cartsData.forEach((cart) => { if (cart.product.id === productId) { productQty = cart.quantity += 1; }; }); // 新增產品到購物車 addCartItem(productId, productQty); }); ``` ### Judy Wei#6103 ```JavaScript ``` ### Mia小福#4473 ```JavaScript ``` ### Ringo#7583 ```JavaScript ``` ### yawun#0042 - 任務清單(已完成) - 購物車 - [x] 新增產品到購物車 - [x] 刪除單筆購物車 - [x] 清空購物車 - [ ] 修改購物車內的單筆產品數量 - 優化工具 - [x] sweetalert2 - 知識點分享 - 使用sweetalert2的方式 1. 載入cdn: `<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.6.11/dist/sweetalert2.all.min.js"></script>` 2. 將要alert的地方改為下列這段(之後有空,再拉出成函式&重構) ```javascript= function sweetAlert(text,icon){ Swal.fire({ text: text, icon: icon, confirmButtonText: 'OK' }) } sweetAlert('已加入購物車','success') ``` ```JavaScript ``` ### 法希娜#3206 ->>完成: 一般商品顯示 購物車顯示+正確總額 購物車單筆刪除 全部刪除 加入購物車 送出訂單 ->>待完成:輸入驗證/ 送出訂單後清空購物車 **本次新增知識點: (一)抓取擁有同樣class但各自不同id的元素,求個別click事件的該id ```JavaScript const addCardBtn = document.querySelectorAll('.addCardBtn'); addCardBtn.forEach((btn) =>{ btn.addEventListener('click', (e) => { const idVal = e.target.getAttribute('id'); }) }) ```