亚马逊克隆网站下单问题:订单页展示与购物车清空异常排查
我正在构建一个亚马逊克隆网站,首页支持用户将商品添加至购物车,当前代码为结算(checkout)页面,负责展示购物车商品并支持商品数量修改、删除操作。
需要实现的功能:点击类名为js-place-order-button的按钮后,将购物车中的商品转移至新页面order.html展示,同时将cartQuantity重置为0以清空购物车。
此前尝试创建新购物车,将原购物车商品存入其中并通过localStorage存储,该步骤可正常运行,但将新购物车导入order.js后,order.html页面出现如下错误:
Uncaught TypeError: Cannot set properties of null (setting 'innerHTML')at checkout.js:110:55)
请问该错误产生的原因是什么?或者有什么方案可以实现商品在order.html展示并清空购物车的需求?
附当前checkout页面代码:
import { cart, cartRemoveItem, topCartQUantity, clearCart } from "../data/cart.js"; import { formatDate } from "../data/utils.js"; import { products } from "../data/products.js"; const DAY3_DELIVERY_FEE = 999/100 const DAY6_DELIVERY_FEE = 499/100 const DAY12_DELIVERY_FEE = 0 const date= new Date(); let cartItemHtml = ''; let checkedRadioValues = {}; cart.forEach( (cartItem) => { const productId = cartItem.productId; let matchingProduct; products.forEach( (product) => { if (product.id === productId){ matchingProduct = product; } } ); cartItemHtml+= ` <div class="cart-item-container js-cart-item-container-${matchingProduct.id}"> <div class="delivery-date js-delivery-date-${matchingProduct.id}"> Delivery date: Pick a delivery date </div> <div class="cart-item-details-grid"> <img class="product-image" src="${matchingProduct.image}"> <div class="cart-item-details"> <div class="product-name"> ${matchingProduct.name} </div> <div class="product-price js-product-price" data-product-id="${matchingProduct.id}"> $${((matchingProduct.priceCents)/100).toFixed(2)} </div> <div class="product-quantity"> <span> Quantity: <span class="quantity-label js-quantity-label-${matchingProduct.id}">${cartItem.quantity}</span> </span> <span class="update-quantity-link link-primary js-update-quantity-link" data-product-id="${matchingProduct.id}"> Update </span> <span class="js-quantity-update-${matchingProduct.id}"></span> <span class="delete-quantity-link link-primary js-delete-quantity-link" data-product-id="${matchingProduct.id}" data-quantity="${cartItem.quantity}"> Delete </span> </div> </div> <div class="delivery-options"> <div class="delivery-options-title"> Choose a delivery option: </div> <div class="delivery-option js-delivery-option"> <input type="radio" checked class="delivery-option-input js-delivery-option-input" name="delivery-option-${matchingProduct.id}" data-product-id="${matchingProduct.id}"> <div> <div class="delivery-option-date js-delivery-option-date"> ${formatDate(date, date.getDay()+12, date.getDate()+12)} </div> <div class="delivery-option-price js-delivery-option-price-${matchingProduct.id}"> FREE Shipping </div> </div> </div> <div class="delivery-option js-delivery-option"> <input type="radio" class="delivery-option-input js-delivery-option-input" name="delivery-option-${matchingProduct.id}" data-product-id="${matchingProduct.id}"> <div> <div class="delivery-option-date js-delivery-option-date"> ${formatDate(date, date.getDay()+6, date.getDate()+6)} </div> <div class="delivery-option-price js-delivery-option-price-${matchingProduct.id}"> $${DAY6_DELIVERY_FEE} - Shipping </div> </div> </div> <div class="delivery-option js-delivery-option"> <input type="radio" class="delivery-option-input js-delivery-option-input" name="delivery-option-${matchingProduct.id}" data-product-id="${matchingProduct.id}"> <div> <div class="delivery-option-date js-delivery-option-date"> ${formatDate(date, date.getDay()+3, date.getDate()+3)} </div> <div class="delivery-option-price js-delivery-option-price-${matchingProduct.id}"> $${DAY3_DELIVERY_FEE} - Shipping </div> </div> </div> </div> </div> </div> ` } ) document.querySelector('.js-order-summary').innerHTML = cartItemHtml; function updateCartQuantity(){ let cartQuantity = 0; cart.forEach((item) => { cartQuantity += item.quantity; }); document.querySelector('.js-checkout-header-middle-section > a').innerHTML = cartQuantity; document.querySelector('.js-item-update').innerHTML = `Items (${cartQuantity}):`; } updateCartQuantity(); document.querySelectorAll('.js-delete-quantity-link').forEach( (link) =>{ link.addEventListener('click', () => { const productId= link.dataset.productId; const quantity = link.dataset.quantity; cartRemoveItem(productId); const container = document.querySelector(`.js-cart-item-container-${productId}`); container.remove(); updateCartQuantity() const radioGroupName = `delivery-option-${productId}`; if (checkedRadioValues[radioGroupName]) { checkedRadioValues[radioGroupName].value = 0; } document.querySelector('.js-shipping-fee').innerHTML = `$${(calculateTotalSum()/100).toFixed(2)}`; calculatePrice() calculateTotalSum(); updatePlaceOrderButton(); }) } ) document.querySelectorAll('.js-update-quantity-link').forEach( (link) => { link.addEventListener('click', () => { let updateInput = ''; const productId= link.dataset.productId; updateInput += ` <input type="number" value="1" class="quantity-input js-quantity-input-${productId}"> <span class="js-save-quantity-link-${productId} link-primary">Save</span> ` document.querySelector(`.js-quantity-update-${productId}`).innerHTML = updateInput; const saveElement = document.querySelector(`.js-save-quantity-link-${productId}`); saveElement.addEventListener('click', () =>{ // let previousQuantity = Number(document.querySelector('.js-quantity-label').innerHTML) let newQuantity = document.querySelector(`.js-quantity-input-${productId}`).value; // previousQuantity+= Number(newQuantity); // console.log(previousQuantity); document.querySelector(`.js-quantity-label-${productId}`).innerHTML = topCartQUantity(productId, Number(newQuantity)); document.querySelector(`.js-quantity-update-${productId}`).innerHTML = ''; updateCartQuantity(); calculatePrice() }) }) }); let shippingPriceTotal = 0; const selectedDeliveryDate = {}; document.querySelectorAll('.js-delivery-option-input').forEach( (radio) => { const productId= radio.dataset.productId; const deliveryOptionDiv = radio.closest('.js-delivery-option'); const dateDiv = deliveryOptionDiv.querySelector(`.js-delivery-option-date`); calculateTotalSum(); if (radio.checked){ selectedDeliveryDate[productId] = { value : dateDiv.textContent.trim()} document.querySelector(`.js-delivery-date-${productId}`).innerHTML = `Delivery date: ${dateDiv.textContent.trim()}`; document.querySelector('.js-shipping-fee').innerHTML = `$${shippingPriceTotal.toFixed(2)}` } radio.addEventListener('click', () => { const currentInputName = radio.getAttribute('name'); const dateValue = dateDiv.textContent.trim(); if (selectedDeliveryDate[productId]){ if (selectedDeliveryDate[productId].value !== dateValue){ selectedDeliveryDate[productId].value = dateValue; } }else { selectedDeliveryDate[productId] = { value : dateValue} } const priceDiv = deliveryOptionDiv.querySelector(`.js-delivery-option-price-${productId}`); const priceValue = priceDiv.textContent.trim(); let extractedNumber = parseFloat(priceValue.slice(1))*100; if (isNaN(extractedNumber)){extractedNumber = 0}; if (checkedRadioValues[currentInputName]) { if (checkedRadioValues[currentInputName].value !== extractedNumber) { checkedRadioValues[currentInputName].value = extractedNumber; document.querySelector('.js-shipping-fee').innerHTML = `$${(calculateTotalSum()/100).toFixed(2)}`; calculatePrice(); } } else { checkedRadioValues[currentInputName] = { value: extractedNumber }; document.querySelector('.js-shipping-fee').innerHTML = `$${(calculateTotalSum()/100).toFixed(2)}`; calculatePrice() } document.querySelector(`.js-delivery-date-${productId}`).innerHTML = `Delivery date: ${valueSelected(dateValue)}`; }); } ) function calculateTotalSum() { let sum = 0; for (const currentInputName in checkedRadioValues) { sum += checkedRadioValues[currentInputName].value; } return sum; } function valueSelected(value){ return value; } calculatePrice() document.querySelector('.js-shipping-fee').innerHTML = `$${(calculateTotalSum()/100).toFixed(2)}`; function calculatePrice(){ let priceTotal = 0; document.querySelectorAll('.js-product-price').forEach( (price) => { const productId= price.dataset.productId; const quantity = document.querySelector(`.js-quantity-label-${productId}`).innerText const priceString = price.innerText; const extractedNumber = parseFloat(priceString.slice(1)); priceTotal += (extractedNumber * 100) * quantity } ) const shippingAndItemPrice = priceTotal + calculateTotalSum(); const tax = shippingAndItemPrice / 10 const orderTotal = shippingAndItemPrice + tax document.querySelector('.js-total-before-tax').innerHTML = `$${(shippingAndItemPrice/100).toFixed(2)}`; document.querySelector('.js-payment-summary-money').innerHTML = `$${(priceTotal/100).toFixed(2)}`; document.querySelector('.js-tax').innerHTML = `$${(tax/100).toFixed(2)}`; document.querySelector('.js-order-total').innerHTML = `$${(orderTotal/100).toFixed(2)}` } document.querySelector('.js-place-order-button').addEventListener('click', () => { // clearCart(); // Clear the cart in local storage cart.length = 0; // Clear the cart array document.querySelector('.js-shipping-fee').innerHTML = ``; checkedRadioValues = {}; // Clear the cart item display document.querySelector('.js-order-summary').innerHTML = ''; updateCartQuantity(); calculatePrice(); updatePlaceOrderButton(); window.location.href = 'orders.html' }); function updatePlaceOrderButton() { const placeOrderButton = document.querySelector('.js-place-order-button'); if (cart.length === 0) { placeOrderButton.disabled = true; } else { placeOrderButton.disabled = false; } } updatePlaceOrderButton()
错误原因分析
这个错误的核心是你在checkout.js的第110行尝试给一个null元素设置innerHTML。结合代码逻辑,触发场景是:
点击下单按钮后,你先清空了cart数组并移除了DOM中的购物车商品,随后调用calculatePrice()函数。该函数会遍历.js-product-price元素,并尝试获取对应商品的数量标签(.js-quantity-label-${productId}),但此时这些标签已被移除,document.querySelector返回null,执行.innerText时就会抛出TypeError。
解决方案
1. 修复当前TypeError错误
修改下单按钮的点击事件逻辑,先保存订单数据、跳转页面,再执行清空购物车的操作(页面跳转后当前页面的JS会终止,无需再处理DOM):
document.querySelector('.js-place-order-button').addEventListener('click', () => { // 1. 深拷贝当前购物车数据作为订单存入localStorage const order = JSON.parse(JSON.stringify(cart)); localStorage.setItem('currentOrder', JSON.stringify(order)); // 2. 清空购物车(同步更新内存和localStorage) clearCart(); cart.length = 0; checkedRadioValues = {}; // 3. 跳转至订单页面 window.location.href = 'orders.html'; });
2. 实现order.html的订单展示功能
在order.js中读取localStorage的订单数据并渲染:
import { products } from "../data/products.js"; // 读取订单数据 const order = JSON.parse(localStorage.getItem('currentOrder')) || []; // 渲染订单商品 let orderItemHtml = ''; order.forEach(cartItem => { const matchingProduct = products.find(product => product.id === cartItem.productId); if (!matchingProduct) return; orderItemHtml += ` <div class="order-item-container"> <img class="product-image" src="${matchingProduct.image}"> <div class="product-details"> <div class="product-name">${matchingProduct.name}</div> <div class="product-price">$${(matchingProduct.priceCents / 100).toFixed(2)}</div> <div class="product-quantity">Quantity: ${cartItem.quantity}</div> </div> </div> `; }); // 确保DOM元素存在再设置innerHTML const orderSummaryElement = document.querySelector('.js-order-summary'); if (orderSummaryElement) { orderSummaryElement.innerHTML = orderItemHtml; } // 可选:清空localStorage中的订单数据(避免刷新页面重复展示) // localStorage.removeItem('currentOrder');
3. 完善clearCart函数
确保cart.js中的clearCart同时更新内存数组和localStorage:
// 在cart.js中修改clearCart函数 export function clearCart() { cart.length = 0; localStorage.setItem('cart', JSON.stringify(cart)); }
内容的提问来源于stack exchange,提问作者Daniel

