You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

亚马逊克隆网站下单问题:订单页展示与购物车清空异常排查

问题描述

我正在构建一个亚马逊克隆网站,首页支持用户将商品添加至购物车,当前代码为结算(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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 22:49:52