购物车是前端JS学习过程中一个重要且经典的案例,其中涉及到的内容很多,若想要购物车的功能完善,需要扎实的基础知识和细腻的逻辑思维,现在我们就一起来看看吧。
一、购物车的功能
1、渲染数据:购物车的数据是变化的,例如淘宝、京东等,当用户点击“加入购物车”时,该商品会自动加入到我们的购物车,所以购物车的数据不是一成不变的,需要前后端的合作,从后端获取数据,前端将商品渲染到页面,也就成了我们所看到的那样
2、更新购物车:
1)用户点击“删除”按钮,该商品会在购物车清除。
2)用户点击数量的加减按钮,商品的数量会随之变化,总数量也随之变化,同时总价随着数量的变化而变化。
3)用户点击“全选”按钮,可以实现商品的全选,点击“取消”按钮可以取消全选状态;若是对每个商品逐一选择,当选择了全部商品时,商品的“全选”按钮会自动选上,按钮的状态也要改变。
4)用户点击“清空购物车”按钮,购物车会清空,并显示“您还未购买商品,快去购物吧”。
5)用户点击“结算”按钮,可以结算所勾选的商品,并弹出相应的提示。
二、JS逻辑
1、给出商品数据,获取DOM元素
2、渲染商品列表
function renderProducts() {
productsList.innerHTML = '';
if (cart.length === 0) {
productsList.innerHTML = `
<div class="empty-cart">
<i class="fas fa-shopping-basket"></i>
<p>购物车是空的,快去选购商品吧!</p>
</div>
`;
return;
}
cart.forEach(product => {
const productEl = document.createElement('div');
productEl.className = 'product';
productEl.dataset.id = product.id;
productEl.innerHTML = `
<div class="product-checkbox">
<input type="checkbox" id="product-${product.id}" class="product-select" checked>
</div>
<img src="${product.image}" alt="${product.name}" class="product-image">
<div class="product-info">
<h3 class="product-name">${product.name}</h3>
<div class="product-price">¥${product.price.toFixed(2)}</div>
<div class="product-quantity">
<button class="quantity-btn minus" data-id="${product.id}">-</button>
<input type="number" class="quantity-input" value="${product.quantity}" min="1" data-id="${product.id}">
<button class="quantity-btn plus" data-id="${product.id}">+</button>
</div>
</div>
<div class="product-remove" data-id="${product.id}">
<i class="fas fa-trash"></i>
</div>
`;
productsList.appendChild(productEl);
});
// 更新全选按钮状态
updateSelectAllButtonState();
}
1)首先清空购物车列表显示区域,productsList
是一个 HTML 元素,用于显示购物车商品的容器,将其 innerHTML
属性设置为空字符串,可以确保在重新渲染商品列表之前,清除之前显示的所有商品内容。
2)检查购物车是否为空,如果为空,显示p标签
3)return 结束renderProducts
函数的执行,因为购物车为空,不需要再继续渲染商品列表。
4)cart.forEach(product => { ... })
:如果购物车不为空,就会使用 forEach
方法遍历 cart
数组中的每个商品。product
是一个对象,代表购物车中的一个商品,包含商品的属性,如 id
(商品编号)、image
(商品图片)、name
(商品名称)、price
(商品价格)、quantity
(商品数量)等。
5)const productEl = document.createElement('div')
:创建一个新的 <div>
元素,用于表示一个商品的显示卡片。
6)productEl.className = 'product'
:给这个新创建的 <div>
元素设置类名为 “product”,这样可以通过 CSS 样式来美化商品的显示外观。
7)productEl.dataset.id = product.id
:将商品的 id
添加到 productEl
的 dataset 属性中。这在后续可以通过 dataset.id
来获取商品的编号,方便对特定商品进行操作,比如修改数量、删除等。
8)productEl.innerHTML = ...
:设置 productEl
元素的内部 HTML 内容,也就是商品卡片的布局和结构。
9)将商品元素添加到购物车列表中:productsList.appendChild(productEl)
:将构建好的商品元素(productEl
)添加到购物车列表的容器元素(productsList
)中,使其成为购物车列表的一个子元素,从而在页面上显示该商品。
10)更新全选按钮状态:updateSelectAllButtonState()
:在渲染完所有商品后,调用 updateSelectAllButtonState
函数来更新全选按钮的状态。全选按钮可能是一个可以同时选中或取消选中购物车中所有商品的按钮,这个函数会根据当前购物车中商品的选中情况来确定全选按钮的勾选状态。例如,如果购物车中所有商品都被选中,全选按钮也应该处于选中状态;如果有任何一个商品未被选中,全选按钮就处于未选中状态。
3、更新购物车的总价总量,设置函数
function updateCartSummary() {
const selectedProducts = cart.filter(product => {
const checkbox = document.getElementById(`product-${product.id}`);
return checkbox ? checkbox.checked : false;
});
const selectedCount = selectedProducts.reduce((sum, product) => sum + product.quantity, 0);
const total = selectedProducts.reduce((sum, product) => sum + (product.price * product.quantity), 0);
selectedCountEl.textContent = selectedCount;
totalEl.textContent = total.toFixed(2);
// 保存到本地存储
localStorage.setItem('cart', JSON.stringify(cart));
}
4、更新全选按钮
function updateSelectAllButtonState() {
if (cart.length === 0) {
selectAllBtn.textContent = '全选';
return;
}
const allChecked = cart.every(product => {
const checkbox = document.getElementById(`product-${product.id}`);
return checkbox && checkbox.checked;
});
selectAllBtn.textContent = allChecked ? '取消全选' : '全选';
}
5、事件委托处理各种交互:数量增加与减少,删除商品,全选按钮点击,清空购物车按钮与结算按钮
document.addEventListener('click', (e) => {
// 数量减少按钮
if (e.target.classList.contains('minus')) {
const productId = parseInt(e.target.dataset.id);
const input = document.querySelector(`.quantity-input[data-id="${productId}"]`);
if (parseInt(input.value) > 1) {
input.value = parseInt(input.value) - 1;
updateProductQuantity(productId, parseInt(input.value));
}
}
// 数量增加按钮
if (e.target.classList.contains('plus')) {
const productId = parseInt(e.target.dataset.id);
const input = document.querySelector(`.quantity-input[data-id="${productId}"]`);
input.value = parseInt(input.value) + 1;
updateProductQuantity(productId, parseInt(input.value));
}
// 删除商品
if (e.target.classList.contains('fa-trash') || e.target.classList.contains('product-remove')) {
const productId = parseInt(e.target.closest('.product-remove').dataset.id);
removeFromCart(productId);
}
});
selectAllBtn.addEventListener('click', () => {
const allChecked = selectAllBtn.textContent === '全选';
cart.forEach(product => {
const checkbox = document.getElementById(`product-${product.id}`);
if (checkbox) {
checkbox.checked = allChecked;
}
});
updateSelectAllButtonState();
updateCartSummary();
});
clearCartBtn.addEventListener('click', () => {
cart = [];
localStorage.removeItem('cart');
renderProducts();
updateCartSummary();
});
checkoutBtn.addEventListener('click', () => {
const selectedProducts = cart.filter(product => {
const checkbox = document.getElementById(`product-${product.id}`);
return checkbox && checkbox.checked;
});
if (selectedProducts.length === 0) {
alert('请至少选择一件商品');
return;
}
alert(`结算成功!共 ${selectedProducts.reduce((sum, p) => sum + p.quantity, 0)} 件商品,总价 ¥${selectedProducts.reduce((sum, p) => sum + (p.price * p.quantity), 0).toFixed(2)}`);
// 从购物车中移除已结算商品
cart = cart.filter(product => {
const checkbox = document.getElementById(`product-${product.id}`);
return !(checkbox && checkbox.checked);
});
localStorage.setItem('cart', JSON.stringify(cart));
renderProducts();
updateCartSummary();
});