生成订单

This commit is contained in:
2025-07-03 00:25:30 +08:00
parent 883839e97c
commit bd9330675e
3 changed files with 234 additions and 168 deletions

View File

@@ -261,7 +261,22 @@ public class ProductService {
productRepository.increaseStock(productId, quantity); productRepository.increaseStock(productId, quantity);
} else if ("decrease".equals(operation)) { } else if ("decrease".equals(operation)) {
// 减少库存 // 减少库存
Long currentStock = (Long) redisService.get(stockKey); Object stockObj = redisService.get(stockKey);
Long currentStock = null;
if (stockObj != null) {
if (stockObj instanceof Integer) {
currentStock = ((Integer) stockObj).longValue();
} else if (stockObj instanceof Long) {
currentStock = (Long) stockObj;
} else if (stockObj instanceof String) {
try {
currentStock = Long.parseLong((String) stockObj);
} catch (NumberFormatException e) {
log.error("库存数据格式错误: 商品ID={}, 数据={}", productId, stockObj);
return false;
}
}
}
if (currentStock == null || currentStock < quantity) { if (currentStock == null || currentStock < quantity) {
log.warn("库存不足: 商品ID={}, 当前库存={}, 需要扣减={}", productId, currentStock, quantity); log.warn("库存不足: 商品ID={}, 当前库存={}, 需要扣减={}", productId, currentStock, quantity);
return false; return false;

View File

@@ -419,13 +419,60 @@
productIds.push(parseInt($(this).val())); productIds.push(parseInt($(this).val()));
}); });
// 跳转到结算页面 // 显示确认对话框
const form = $('<form method="post" action="${pageContext.request.contextPath}/checkout">'); if (!confirm('确定要结算选中的 ' + selectedItems.length + ' 个商品吗?\n\n结算后将生成订单请及时支付。')) {
productIds.forEach(function (productId) { return;
form.append('<input type="hidden" name="productIds" value="' + productId + '">'); }
// 禁用结算按钮防止重复点击
const checkoutBtn = $('#checkoutBtn');
const originalText = checkoutBtn.html();
checkoutBtn.prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i> 结算中...');
// 通过AJAX调用购物车结算接口
$.ajax({
url: '${pageContext.request.contextPath}/api/cart/checkout',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({
productIds: productIds
}),
success: function (response) {
if (response.success) {
showMessage('✅ 订单生成成功!订单号:' + response.data.orderNo, 'success');
// 清空购物车显示
loadCart();
// 3秒后跳转到订单详情页面
setTimeout(() => {
window.location.href = '${pageContext.request.contextPath}/order/' + response.data.id;
}, 2000);
} else {
showMessage('❌ 下单失败:' + response.message, 'error');
// 恢复按钮状态
checkoutBtn.prop('disabled', false).html(originalText);
}
},
error: function (xhr, status, error) {
let errorMessage = '网络异常,请重试';
if (xhr.status === 401) {
errorMessage = '登录已过期,请重新登录';
setTimeout(() => {
window.location.href = '${pageContext.request.contextPath}/login?returnUrl=' + encodeURIComponent(window.location.pathname);
}, 1500);
} else if (xhr.status === 400 && xhr.responseJSON) {
errorMessage = xhr.responseJSON.message || '请求参数错误';
}
showMessage('❌ 结算失败:' + errorMessage, 'error');
// 恢复按钮状态
checkoutBtn.prop('disabled', false).html(originalText);
}
}); });
$('body').append(form);
form.submit();
} }
// 加载推荐商品 // 加载推荐商品

View File

@@ -29,12 +29,6 @@
font-size: 1.2rem; font-size: 1.2rem;
} }
.original-price {
color: #999;
text-decoration: line-through;
font-size: 0.9rem;
}
.stock-badge { .stock-badge {
position: absolute; position: absolute;
top: 10px; top: 10px;
@@ -58,6 +52,20 @@
text-align: center; text-align: center;
padding: 40px; padding: 40px;
} }
.btn:hover {
transform: translateY(-1px);
}
/* 卡片悬停效果 */
.card:hover .card-img-top {
transform: scale(1.05);
transition: transform 0.3s ease;
}
.card-img-top {
transition: transform 0.3s ease;
}
</style> </style>
<div class="container my-4"> <div class="container my-4">
@@ -96,7 +104,6 @@
<option value="id,desc">最新上架</option> <option value="id,desc">最新上架</option>
<option value="price,asc">价格从低到高</option> <option value="price,asc">价格从低到高</option>
<option value="price,desc">价格从高到低</option> <option value="price,desc">价格从高到低</option>
<option value="sales,desc">销量最高</option>
</select> </select>
</div> </div>
<div class="col-md-2"> <div class="col-md-2">
@@ -172,6 +179,11 @@
searchProducts(); searchProducts();
} }
}); });
// 更新购物车数量(如果用户已登录)
<c:if test="${not empty sessionScope.user}">
updateCartCount();
</c:if>
}); });
// 加载商品列表 // 加载商品列表
@@ -195,7 +207,7 @@
params.append('sortDirection', sortValue[1]); params.append('sortDirection', sortValue[1]);
} }
$.get('/api/product/list?' + params.toString()) $.get('${pageContext.request.contextPath}/api/product/list?' + params.toString())
.done(function (response) { .done(function (response) {
if (response.success) { if (response.success) {
displayProducts(response.data.content); displayProducts(response.data.content);
@@ -235,54 +247,46 @@
}); });
} }
// 创建商品卡片 // 创建商品卡片(参考热门商品样式)
function createProductCard(product) { function createProductCard(product) {
const stockBadge = product.stock > 0 ? const imageUrl = product.imageUrl || '${pageContext.request.contextPath}/images/default-product.svg';
`<span class="stock-badge bg-success">库存 ${product.stock}</span>` : const productName = product.name;
`<span class="stock-badge bg-danger">无库存</span>`; const productDescription = product.description || '暂无描述';
const price = product.price ? product.price.toFixed(2) : '0.00';
const stock = product.stock || 0;
const originalPrice = product.originalPrice && product.originalPrice > product.price ? var cardHtml = '<div class="col-lg-3 col-md-4 col-sm-6 mb-4">' +
`<span class="original-price">¥${product.originalPrice}</span>` : ''; '<div class="card product-card h-100">' +
'<div class="position-relative">' +
'<img src="' + imageUrl + '" class="card-img-top product-image" alt="' + productName + '" ' +
'onerror="this.src=\'${pageContext.request.contextPath}/images/default-product.svg\'; this.onerror=null;">' +
'<span class="stock-badge ' + (stock > 0 ? 'bg-success' : 'bg-danger') + '">' +
(stock > 0 ? '库存 ' + stock : '无库存') +
'</span>' +
'</div>' +
'<div class="card-body d-flex flex-column">' +
'<h6 class="card-title text-truncate" title="' + productName + '">' + productName + '</h6>' +
'<p class="card-text text-muted small flex-grow-1 text-truncate" title="' + productDescription + '">' +
productDescription +
'</p>' +
'<div class="d-flex justify-content-between align-items-center mb-2">' +
'<span class="text-primary fw-bold">¥' + price + '</span>' +
'<small class="text-muted">库存: ' + stock + '</small>' +
'</div>' +
'<div class="d-flex gap-2">' +
(stock > 0 ?
'<button class="btn btn-primary btn-sm flex-grow-1" onclick="addToCart(' + product.id + ')"><i class="fas fa-cart-plus"></i> 加入购物车</button>' :
'<button class="btn btn-secondary btn-sm flex-grow-1" disabled><i class="fas fa-ban"></i> 暂时缺货</button>'
) +
'<button class="btn btn-outline-secondary btn-sm" onclick="viewProductDetail(' + product.id + ')" title="查看详情">' +
'<i class="fas fa-eye"></i>' +
'</button>' +
'</div>' +
'</div>' +
'</div>' +
'</div>';
const addToCartBtn = product.stock > 0 ? return cardHtml;
`<button class="btn btn-primary btn-sm" onclick="addToCart(${product.id})">
<i class="fas fa-cart-plus"></i> 加入购物车
</button>` :
`<button class="btn btn-secondary btn-sm" disabled>
<i class="fas fa-ban"></i> 暂时缺货
</button>`;
return `
<div class="col-lg-3 col-md-4 col-sm-6 mb-4">
<div class="card product-card h-100">
<div class="position-relative">
<img src="${product.imageUrl || '/static/images/default-product.svg'}"
class="card-img-top product-image" alt="${product.name}">
${stockBadge}
</div>
<div class="card-body d-flex flex-column">
<h6 class="card-title" title="${product.name}">
${product.name.length > 30 ? product.name.substring(0, 30) + '...' : product.name}
</h6>
<p class="card-text text-muted small flex-grow-1">
${product.description ?
(product.description.length > 50 ? product.description.substring(0, 50) + '...' : product.description) :
'暂无描述'}
</p>
<div class="price-section mb-2">
<span class="price">¥${product.price}</span>
${originalPrice}
</div>
<div class="d-flex justify-content-between align-items-center">
${addToCartBtn}
<button class="btn btn-outline-secondary btn-sm" onclick="viewProductDetail(${product.id})">
<i class="fas fa-eye"></i> 详情
</button>
</div>
</div>
</div>
</div>
`;
} }
// 更新分页 // 更新分页
@@ -296,36 +300,36 @@
// 上一页 // 上一页
const prevDisabled = currentPage === 0 ? 'disabled' : ''; const prevDisabled = currentPage === 0 ? 'disabled' : '';
pagination.append(` pagination.append(`
<li class="page-item ${prevDisabled}"> <li class="page-item ${prevDisabled}">
<a class="page-link" onclick="loadProducts(${currentPage - 1})" href="javascript:void(0)"> <a class="page-link" onclick="loadProducts(${currentPage - 1})" href="javascript:void(0)">
<i class="fas fa-chevron-left"></i> <i class="fas fa-chevron-left"></i>
</a> </a>
</li> </li>
`); `);
// 页码 // 页码
let startPage = Math.max(0, currentPage - 2); let startPage = Math.max(0, currentPage - 2);
let endPage = Math.min(totalPages - 1, currentPage + 2); let endPage = Math.min(totalPages - 1, currentPage + 2);
if (startPage > 0) { if (startPage > 0) {
pagination.append(`<li class="page-item"><a class="page-link" onclick="loadProducts(0)" href="javascript:void(0)">1</a></li>`); pagination.append('<li class="page-item"><a class="page-link" onclick="loadProducts(0)" href="javascript:void(0)">1</a></li>');
if (startPage > 1) { if (startPage > 1) {
pagination.append(`<li class="page-item disabled"><span class="page-link">...</span></li>`); pagination.append('<li class="page-item disabled"><span class="page-link">...</span></li>');
} }
} }
for (let i = startPage; i <= endPage; i++) { for (let i = startPage; i <= endPage; i++) {
const active = i === currentPage ? 'active' : ''; const active = i === currentPage ? 'active' : '';
pagination.append(` pagination.append(`
<li class="page-item ${active}"> <li class="page-item ${active}">
<a class="page-link" onclick="loadProducts(${i})" href="javascript:void(0)">${i + 1}</a> <a class="page-link" onclick="loadProducts(${i})" href="javascript:void(0)">${i + 1}</a>
</li> </li>
`); `);
} }
if (endPage < totalPages - 1) { if (endPage < totalPages - 1) {
if (endPage < totalPages - 2) { if (endPage < totalPages - 2) {
pagination.append(`<li class="page-item disabled"><span class="page-link">...</span></li>`); pagination.append('<li class="page-item disabled"><span class="page-link">...</span></li>');
} }
pagination.append(`<li class="page-item"><a class="page-link" onclick="loadProducts(${totalPages - 1})" href="javascript:void(0)">${totalPages}</a></li>`); pagination.append(`<li class="page-item"><a class="page-link" onclick="loadProducts(${totalPages - 1})" href="javascript:void(0)">${totalPages}</a></li>`);
} }
@@ -333,19 +337,19 @@
// 下一页 // 下一页
const nextDisabled = currentPage === totalPages - 1 ? 'disabled' : ''; const nextDisabled = currentPage === totalPages - 1 ? 'disabled' : '';
pagination.append(` pagination.append(`
<li class="page-item ${nextDisabled}"> <li class="page-item ${nextDisabled}">
<a class="page-link" onclick="loadProducts(${currentPage + 1})" href="javascript:void(0)"> <a class="page-link" onclick="loadProducts(${currentPage + 1})" href="javascript:void(0)">
<i class="fas fa-chevron-right"></i> <i class="fas fa-chevron-right"></i>
</a> </a>
</li> </li>
`); `);
} }
// 查看商品详情 // 查看商品详情
function viewProductDetail(productId) { function viewProductDetail(productId) {
currentProductId = productId; currentProductId = productId;
$.get(`/api/product/${productId}`) $.get('${pageContext.request.contextPath}/api/product/' + productId)
.done(function (response) { .done(function (response) {
if (response.success) { if (response.success) {
displayProductDetail(response.data); displayProductDetail(response.data);
@@ -362,91 +366,80 @@
// 显示商品详情 // 显示商品详情
function displayProductDetail(product) { function displayProductDetail(product) {
const modalBody = $('#productModalBody'); const modalBody = $('#productModalBody');
modalBody.html(` const imageUrl = product.imageUrl || '${pageContext.request.contextPath}/images/default-product.svg';
<div class="row"> const stock = product.stock || 0;
<div class="col-md-6"> const price = product.price ? product.price.toFixed(2) : '0.00';
<img src="${product.imageUrl || '/static/images/default-product.svg'}"
class="img-fluid rounded" alt="${product.name}"> var modalHtml = '<div class="row">' +
</div> '<div class="col-md-6">' +
<div class="col-md-6"> '<img src="' + imageUrl + '" class="img-fluid rounded" alt="' + product.name + '" ' +
<h4>${product.name}</h4> 'onerror="this.src=\'${pageContext.request.contextPath}/images/default-product.svg\'; this.onerror=null;">' +
<p class="text-muted">${product.description || '暂无详细描述'}</p> '</div>' +
<div class="mb-3"> '<div class="col-md-6">' +
<span class="price h4">¥${product.price}</span> '<h4>' + product.name + '</h4>' +
${product.originalPrice && product.originalPrice > product.price ? '<p class="text-muted">' + (product.description || '暂无详细描述') + '</p>' +
'<span class="original-price ms-2">¥' + product.originalPrice + '</span>' : ''} '<div class="mb-3">' +
</div> '<span class="text-primary fw-bold h4">¥' + price + '</span>' +
<div class="mb-3"> '</div>' +
<span class="badge ${product.stock > 0 ? 'bg-success' : 'bg-danger'}"> '<div class="mb-3">' +
<c:choose> '<span class="badge ' + (stock > 0 ? 'bg-success' : 'bg-danger') + '">' +
<c:when test="${product.stock > 0}"> (stock > 0 ? '库存 ' + stock : '暂时缺货') +
库存 ${product.stock} '</span>' +
</c:when> '</div>' +
<c:otherwise> '<div class="mb-3">' +
暂时缺货 '<strong>商品状态:</strong> ' +
</c:otherwise> '<span class="badge ' + (product.status === 1 ? 'bg-success' : 'bg-secondary') + '">' +
</c:choose> (product.status === 1 ? '上架中' : '已下架') +
</span> '</span>' +
</div> '</div>' +
<div class="mb-3"> '</div>' +
<strong>商品类别:</strong> ${product.category || '未分类'} '</div>';
</div>
<div class="mb-3"> modalBody.html(modalHtml);
<strong>商品状态:</strong>
<span class="badge">
<c:choose>
<c:when test="${product.status == 1}">
<span class="bg-success">上架中</span>
</c:when>
<c:otherwise>
<span class="bg-secondary">已下架</span>
</c:otherwise>
</c:choose>
</span>
</div>
</div>
</div>
`);
// 更新加入购物车按钮状态 // 更新加入购物车按钮状态
const addToCartBtn = $('#addToCartBtn'); const addToCartBtn = $('#addToCartBtn');
if (product.stock > 0 && product.status === 1) { if (stock > 0 && product.status === 1) {
addToCartBtn.prop('disabled', false).html('<i class="fas fa-cart-plus"></i> 加入购物车'); addToCartBtn.prop('disabled', false).html('<i class="fas fa-cart-plus"></i> 加入购物车');
} else { } else {
addToCartBtn.prop('disabled', true).html('<i class="fas fa-ban"></i> 暂时无法购买'); addToCartBtn.prop('disabled', true).html('<i class="fas fa-ban"></i> 暂时无法购买');
} }
} }
// 添加到购物车 // 添加到购物车(参考首页实现)
function addToCart(productId) { function addToCart(productId) {
if (!isUserLoggedIn()) { <c:choose>
showLoginPrompt(); <c:when test="${not empty sessionScope.user}">
return;
}
const data = {
productId: productId,
quantity: 1
};
$.ajax({ $.ajax({
url: '/api/cart/add', url: '${pageContext.request.contextPath}/api/cart/add',
method: 'POST', type: 'POST',
contentType: 'application/json', contentType: 'application/json',
data: JSON.stringify(data) data: JSON.stringify({
}) productId: productId,
.done(function (response) { quantity: 1
}),
success: function (response) {
if (response.success) { if (response.success) {
showSuccess('商品已添加到购物车'); showMessage('商品已添加到购物车', 'success');
updateCartCount(); updateCartCount();
// 如果是从模态框添加的,关闭模态框
$('#productModal').modal('hide'); $('#productModal').modal('hide');
} else { } else {
showError('添加失败:' + response.message); showMessage(response.message, 'error');
} }
}) },
.fail(function () { error: function () {
showError('网络错误,请稍后重试'); showMessage('添加失败,请重试', 'error');
}); }
});
</c:when>
<c:otherwise>
showMessage('请先登录', 'warning');
setTimeout(() => {
window.location.href = '${pageContext.request.contextPath}/login?returnUrl=' + encodeURIComponent(window.location.pathname);
}, 1500);
</c:otherwise>
</c:choose>
} }
// 显示加载状态 // 显示加载状态
@@ -461,40 +454,51 @@
} }
} }
// 检查用户是否登录 // 显示消息(参考首页实现)
function isUserLoggedIn() { function showMessage(message, type = 'info') {
// 这里需要根据实际的登录状态检查逻辑来实现 // 创建消息元素
return sessionStorage.getItem('userToken') || const alertDiv = document.createElement('div');
document.cookie.includes('JSESSIONID'); alertDiv.className = `alert alert-${type == 'error' ? 'danger' : type} alert-dismissible fade show position-fixed`;
alertDiv.style.cssText = 'top: 20px; right: 20px; z-index: 9999; min-width: 300px;';
alertDiv.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
document.body.appendChild(alertDiv);
// 3秒后自动消失
setTimeout(() => {
if (alertDiv.parentNode) {
alertDiv.remove();
}
}, 3000);
} }
// 显示登录提示 // 更新购物车数量(参考首页实现)
function showLoginPrompt() {
if (confirm('请先登录后再进行购物,是否前往登录页面?')) {
window.location.href = '/login?returnUrl=' + encodeURIComponent(window.location.pathname);
}
}
// 更新购物车数量
function updateCartCount() { function updateCartCount() {
if (!isUserLoggedIn()) return; $.get('${pageContext.request.contextPath}/api/cart/count')
$.get('/api/cart/count')
.done(function (response) { .done(function (response) {
if (response.success && response.data.count > 0) { if (response.success) {
$('.cart-count').text(response.data.count).show(); const cartBadge = document.querySelector('.cart-count');
if (cartBadge) {
const count = response.data.count || 0;
cartBadge.textContent = count;
cartBadge.style.display = count > 0 ? 'inline' : 'none';
}
} }
}); });
} }
// 错误提示 // 错误提示
function showError(message) { function showError(message) {
alert('错误:' + message); showMessage(message, 'error');
} }
// 成功提示 // 成功提示
function showSuccess(message) { function showSuccess(message) {
alert('成功:' + message); showMessage(message, 'success');
} }
</script> </script>