生成订单
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
// 加载推荐商品
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user