生成订单

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);
} 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) {
log.warn("库存不足: 商品ID={}, 当前库存={}, 需要扣减={}", productId, currentStock, quantity);
return false;

View File

@@ -419,13 +419,60 @@
productIds.push(parseInt($(this).val()));
});
// 跳转到结算页面
const form = $('<form method="post" action="${pageContext.request.contextPath}/checkout">');
productIds.forEach(function (productId) {
form.append('<input type="hidden" name="productIds" value="' + productId + '">');
// 显示确认对话框
if (!confirm('确定要结算选中的 ' + selectedItems.length + ' 个商品吗?\n\n结算后将生成订单请及时支付。')) {
return;
}
// 禁用结算按钮防止重复点击
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;
}
.original-price {
color: #999;
text-decoration: line-through;
font-size: 0.9rem;
}
.stock-badge {
position: absolute;
top: 10px;
@@ -58,6 +52,20 @@
text-align: center;
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>
<div class="container my-4">
@@ -96,7 +104,6 @@
<option value="id,desc">最新上架</option>
<option value="price,asc">价格从低到高</option>
<option value="price,desc">价格从高到低</option>
<option value="sales,desc">销量最高</option>
</select>
</div>
<div class="col-md-2">
@@ -172,6 +179,11 @@
searchProducts();
}
});
// 更新购物车数量(如果用户已登录)
<c:if test="${not empty sessionScope.user}">
updateCartCount();
</c:if>
});
// 加载商品列表
@@ -195,7 +207,7 @@
params.append('sortDirection', sortValue[1]);
}
$.get('/api/product/list?' + params.toString())
$.get('${pageContext.request.contextPath}/api/product/list?' + params.toString())
.done(function (response) {
if (response.success) {
displayProducts(response.data.content);
@@ -235,54 +247,46 @@
});
}
// 创建商品卡片
// 创建商品卡片(参考热门商品样式)
function createProductCard(product) {
const stockBadge = product.stock > 0 ?
`<span class="stock-badge bg-success">库存 ${product.stock}</span>` :
`<span class="stock-badge bg-danger">无库存</span>`;
const imageUrl = product.imageUrl || '${pageContext.request.contextPath}/images/default-product.svg';
const productName = product.name;
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 ?
`<span class="original-price">¥${product.originalPrice}</span>` : '';
var cardHtml = '<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="' + 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 ?
`<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>
`;
return cardHtml;
}
// 更新分页
@@ -308,9 +312,9 @@
let endPage = Math.min(totalPages - 1, currentPage + 2);
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) {
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>');
}
}
@@ -325,7 +329,7 @@
if (endPage < totalPages - 1) {
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>`);
}
@@ -345,7 +349,7 @@
function viewProductDetail(productId) {
currentProductId = productId;
$.get(`/api/product/${productId}`)
$.get('${pageContext.request.contextPath}/api/product/' + productId)
.done(function (response) {
if (response.success) {
displayProductDetail(response.data);
@@ -362,91 +366,80 @@
// 显示商品详情
function displayProductDetail(product) {
const modalBody = $('#productModalBody');
modalBody.html(`
<div class="row">
<div class="col-md-6">
<img src="${product.imageUrl || '/static/images/default-product.svg'}"
class="img-fluid rounded" alt="${product.name}">
</div>
<div class="col-md-6">
<h4>${product.name}</h4>
<p class="text-muted">${product.description || '暂无详细描述'}</p>
<div class="mb-3">
<span class="price h4">¥${product.price}</span>
${product.originalPrice && product.originalPrice > product.price ?
'<span class="original-price ms-2">¥' + product.originalPrice + '</span>' : ''}
</div>
<div class="mb-3">
<span class="badge ${product.stock > 0 ? 'bg-success' : 'bg-danger'}">
<c:choose>
<c:when test="${product.stock > 0}">
库存 ${product.stock}
</c:when>
<c:otherwise>
暂时缺货
</c:otherwise>
</c:choose>
</span>
</div>
<div class="mb-3">
<strong>商品类别:</strong> ${product.category || '未分类'}
</div>
<div class="mb-3">
<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 imageUrl = product.imageUrl || '${pageContext.request.contextPath}/images/default-product.svg';
const stock = product.stock || 0;
const price = product.price ? product.price.toFixed(2) : '0.00';
var modalHtml = '<div class="row">' +
'<div class="col-md-6">' +
'<img src="' + imageUrl + '" class="img-fluid rounded" alt="' + product.name + '" ' +
'onerror="this.src=\'${pageContext.request.contextPath}/images/default-product.svg\'; this.onerror=null;">' +
'</div>' +
'<div class="col-md-6">' +
'<h4>' + product.name + '</h4>' +
'<p class="text-muted">' + (product.description || '暂无详细描述') + '</p>' +
'<div class="mb-3">' +
'<span class="text-primary fw-bold h4">¥' + price + '</span>' +
'</div>' +
'<div class="mb-3">' +
'<span class="badge ' + (stock > 0 ? 'bg-success' : 'bg-danger') + '">' +
(stock > 0 ? '库存 ' + stock : '暂时缺货') +
'</span>' +
'</div>' +
'<div class="mb-3">' +
'<strong>商品状态:</strong> ' +
'<span class="badge ' + (product.status === 1 ? 'bg-success' : 'bg-secondary') + '">' +
(product.status === 1 ? '上架中' : '已下架') +
'</span>' +
'</div>' +
'</div>' +
'</div>';
modalBody.html(modalHtml);
// 更新加入购物车按钮状态
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> 加入购物车');
} else {
addToCartBtn.prop('disabled', true).html('<i class="fas fa-ban"></i> 暂时无法购买');
}
}
// 添加到购物车
// 添加到购物车(参考首页实现)
function addToCart(productId) {
if (!isUserLoggedIn()) {
showLoginPrompt();
return;
}
const data = {
<c:choose>
<c:when test="${not empty sessionScope.user}">
$.ajax({
url: '${pageContext.request.contextPath}/api/cart/add',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({
productId: productId,
quantity: 1
};
$.ajax({
url: '/api/cart/add',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify(data)
})
.done(function (response) {
}),
success: function (response) {
if (response.success) {
showSuccess('商品已添加到购物车');
showMessage('商品已添加到购物车', 'success');
updateCartCount();
// 如果是从模态框添加的,关闭模态框
$('#productModal').modal('hide');
} else {
showError('添加失败:' + response.message);
showMessage(response.message, 'error');
}
},
error: function () {
showMessage('添加失败,请重试', 'error');
}
})
.fail(function () {
showError('网络错误,请稍后重试');
});
</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() {
// 这里需要根据实际的登录状态检查逻辑来实现
return sessionStorage.getItem('userToken') ||
document.cookie.includes('JSESSIONID');
// 显示消息(参考首页实现)
function showMessage(message, type = 'info') {
// 创建消息元素
const alertDiv = document.createElement('div');
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() {
if (!isUserLoggedIn()) return;
$.get('/api/cart/count')
$.get('${pageContext.request.contextPath}/api/cart/count')
.done(function (response) {
if (response.success && response.data.count > 0) {
$('.cart-count').text(response.data.count).show();
if (response.success) {
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) {
alert('错误:' + message);
showMessage(message, 'error');
}
// 成功提示
function showSuccess(message) {
alert('成功:' + message);
showMessage(message, 'success');
}
</script>