Files
FlashSaleSystem/src/main/webapp/WEB-INF/views/index.jsp
2025-07-04 22:14:22 +08:00

676 lines
26 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://flashsale.org/functions" %>
<c:set var="pageTitle" value="首页" />
<%@ include file="common/header.jsp" %>
<!-- 轮播图 -->
<div id="heroCarousel" class="carousel slide" data-bs-ride="carousel">
<div class="carousel-indicators">
<button type="button" data-bs-target="#heroCarousel" data-bs-slide-to="0" class="active"></button>
<button type="button" data-bs-target="#heroCarousel" data-bs-slide-to="1"></button>
<button type="button" data-bs-target="#heroCarousel" data-bs-slide-to="2"></button>
</div>
<div class="carousel-inner">
<div class="carousel-item active">
<div class="bg-gradient-danger text-white py-5" style="min-height: 400px;">
<div class="container d-flex align-items-center h-100">
<div class="row w-100">
<div class="col-md-6">
<h1 class="display-4 fw-bold mb-4">
<i class="fas fa-bolt"></i> 秒杀系统
</h1>
<p class="lead mb-4">基于Redis集群构建的高并发秒杀系统支持分布式锁、接口限流、库存预热等核心功能。</p>
<div class="d-flex gap-3">
<a href="${pageContext.request.contextPath}/flashsales" class="btn btn-light btn-lg">
<i class="fas fa-fire"></i> 立即抢购
</a>
<a href="${pageContext.request.contextPath}/products" class="btn btn-outline-light btn-lg">
<i class="fas fa-shopping-bag"></i> 浏览商品
</a>
</div>
</div>
<div class="col-md-6 text-center">
<i class="fas fa-rocket fa-10x opacity-50"></i>
</div>
</div>
</div>
</div>
</div>
<div class="carousel-item">
<div class="bg-gradient-primary text-white py-5" style="min-height: 400px;">
<div class="container d-flex align-items-center h-100">
<div class="row w-100">
<div class="col-md-6">
<h1 class="display-4 fw-bold mb-4">
<i class="fas fa-shield-alt"></i> 防超卖机制
</h1>
<p class="lead mb-4">采用Redis分布式锁和Lua脚本确保高并发场景下的数据一致性彻底解决超卖问题。</p>
<a href="#features" class="btn btn-light btn-lg">
<i class="fas fa-info-circle"></i> 了解更多
</a>
</div>
<div class="col-md-6 text-center">
<i class="fas fa-lock fa-10x opacity-50"></i>
</div>
</div>
</div>
</div>
</div>
<div class="carousel-item">
<div class="bg-gradient-success text-white py-5" style="min-height: 400px;">
<div class="container d-flex align-items-center h-100">
<div class="row w-100">
<div class="col-md-6">
<h1 class="display-4 fw-bold mb-4">
<i class="fas fa-tachometer-alt"></i> 高性能缓存
</h1>
<p class="lead mb-4">Redis集群架构支持五种数据类型应用实现毫秒级响应轻松应对高并发访问。</p>
<a href="#performance" class="btn btn-light btn-lg">
<i class="fas fa-chart-line"></i> 性能指标
</a>
</div>
<div class="col-md-6 text-center">
<i class="fas fa-database fa-10x opacity-50"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<button class="carousel-control-prev" type="button" data-bs-target="#heroCarousel" data-bs-slide="prev">
<span class="carousel-control-prev-icon"></span>
</button>
<button class="carousel-control-next" type="button" data-bs-target="#heroCarousel" data-bs-slide="next">
<span class="carousel-control-next-icon"></span>
</button>
</div>
<div class="container my-5">
<!-- 正在进行的秒杀活动 -->
<section class="mb-5">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2 class="fw-bold">
<i class="fas fa-fire text-danger"></i> 正在秒杀
</h2>
<a href="${pageContext.request.contextPath}/flashsales" class="btn btn-outline-danger">
查看全部 <i class="fas fa-arrow-right"></i>
</a>
</div>
<div id="activeFlashSales" class="row">
<!-- 动态加载秒杀活动 -->
<div class="col-12 text-center py-5">
<i class="fas fa-spinner fa-spin fa-2x text-muted"></i>
<p class="text-muted mt-2">加载中...</p>
</div>
</div>
</section>
<!-- 热门商品 -->
<section class="mb-5">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2 class="fw-bold">
<i class="fas fa-star text-warning"></i> 热门商品
</h2>
<a href="${pageContext.request.contextPath}/products" class="btn btn-outline-primary">
查看全部 <i class="fas fa-arrow-right"></i>
</a>
</div>
<div id="hotProducts" class="row">
<!-- 动态加载热门商品 -->
<div class="col-12 text-center py-5">
<i class="fas fa-spinner fa-spin fa-2x text-muted"></i>
<p class="text-muted mt-2">加载中...</p>
</div>
</div>
</section>
<!-- 系统特性 -->
<section id="features" class="mb-5">
<h2 class="text-center fw-bold mb-5">
<i class="fas fa-cogs"></i> 系统特性
</h2>
<div class="row g-4">
<div class="col-md-3 col-sm-6">
<div class="card h-100 text-center border-0 shadow-sm">
<div class="card-body">
<i class="fas fa-bolt fa-3x text-danger mb-3"></i>
<h5 class="card-title">秒杀抢购</h5>
<p class="card-text text-muted">高并发秒杀系统,支持大量用户同时抢购</p>
</div>
</div>
</div>
<div class="col-md-3 col-sm-6">
<div class="card h-100 text-center border-0 shadow-sm">
<div class="card-body">
<i class="fas fa-shield-alt fa-3x text-success mb-3"></i>
<h5 class="card-title">防超卖</h5>
<p class="card-text text-muted">分布式锁机制,确保库存数据一致性</p>
</div>
</div>
</div>
<div class="col-md-3 col-sm-6">
<div class="card h-100 text-center border-0 shadow-sm">
<div class="card-body">
<i class="fas fa-database fa-3x text-info mb-3"></i>
<h5 class="card-title">Redis缓存</h5>
<p class="card-text text-muted">五种数据类型应用,毫秒级响应</p>
</div>
</div>
</div>
<div class="col-md-3 col-sm-6">
<div class="card h-100 text-center border-0 shadow-sm">
<div class="card-body">
<i class="fas fa-tachometer-alt fa-3x text-warning mb-3"></i>
<h5 class="card-title">接口限流</h5>
<p class="card-text text-muted">多种限流策略,防止恶意刷单</p>
</div>
</div>
</div>
</div>
</section>
</div>
<script>
$(document).ready(function() {
// 加载正在进行的秒杀活动
loadActiveFlashSales();
// 加载热门商品
loadHotProducts();
// 启动性能指标动画
animateCounters();
// 更新购物车数量(如果用户已登录)
<c:if test="${not empty sessionScope.user}">
updateCartCount();
</c:if>
});
// 加载正在进行的秒杀活动
function loadActiveFlashSales() {
$.get('${pageContext.request.contextPath}/api/flashsale/active')
.done(function(response) {
if (response.success && response.data.length > 0) {
renderFlashSales(response.data.slice(0, 4)); // 只显示前4个
} else {
$('#activeFlashSales').html(`
<div class="col-12 text-center py-5">
<i class="fas fa-info-circle fa-2x text-muted"></i>
<p class="text-muted mt-2">暂无进行中的秒杀活动</p>
</div>
`);
}
})
.fail(function() {
$('#activeFlashSales').html(`
<div class="col-12 text-center py-5">
<i class="fas fa-exclamation-triangle fa-2x text-warning"></i>
<p class="text-muted mt-2">加载失败,请刷新页面重试</p>
</div>
`);
});
}
// 渲染秒杀活动
function renderFlashSales(flashSales) {
let html = '';
flashSales.forEach(function(flashSale) {
const discountPercent = Math.round((1 - flashSale.flashPrice / flashSale.originalPrice) * 100);
html += `
<div class="col-lg-3 col-md-6 mb-4">
<div class="card h-100 border-danger">
<div class="position-relative">
<img src="` + (flashSale.productImageUrl || '${pageContext.request.contextPath}/images/default-product.svg') + `"
class="card-img-top" alt="` + flashSale.productName + `" style="height: 200px; object-fit: cover;"
onerror="this.src='${pageContext.request.contextPath}/images/default-product.svg'; this.onerror=null;">
<div class="position-absolute top-0 start-0 bg-danger text-white px-2 py-1 rounded-end">
<small><i class="fas fa-fire"></i> 秒杀中</small>
</div>
<div class="position-absolute top-0 end-0 bg-warning text-dark px-2 py-1 rounded-start">
<small>` + discountPercent + `% OFF</small>
</div>
</div>
<div class="card-body">
<h6 class="card-title text-truncate">` + flashSale.productName + `</h6>
<div class="d-flex justify-content-between align-items-center mb-2">
<div>
<span class="text-danger fw-bold fs-5">¥` + (flashSale.flashPrice ? flashSale.flashPrice.toFixed(2) : '0.00') + `</span>
<small class="text-muted text-decoration-line-through ms-2">¥` + (flashSale.originalPrice ? flashSale.originalPrice.toFixed(2) : '0.00') + `</small>
</div>
</div>
<div class="mb-2">
<small class="text-muted">剩余: ` + (flashSale.remainingStock || 0) + `件</small>
<div class="progress" style="height: 4px;">
<div class="progress-bar bg-danger" style="width: ` + ((flashSale.remainingStock || 0) / (flashSale.flashStock || 1) * 100) + `%"></div>
</div>
</div>
<div class="text-center">
<div class="text-danger fw-bold mb-2" id="countdown_${flashSale.id}">
计算中...
</div>
<button class="btn btn-danger btn-sm w-100 flash-sale-btn"
onclick="participateFlashSale(` + flashSale.id + `)"
data-flashsale-id="` + flashSale.id + `">
<i class="fas fa-bolt"></i> 立即抢购
</button>
</div>
</div>
</div>
</div>
`;
// 启动倒计时
setTimeout(() => {
if (flashSale.timeToEnd > 0) {
countdown(Date.now() + flashSale.timeToEnd, 'countdown_' + flashSale.id);
}
}, 100);
});
$('#activeFlashSales').html(html);
}
// 加载热门商品
function loadHotProducts() {
$.get('${pageContext.request.contextPath}/api/product/hot?limit=8')
.done(function(response) {
if (response.success && response.data.length > 0) {
renderHotProducts(response.data);
} else {
$('#hotProducts').html(`
<div class="col-12 text-center py-5">
<i class="fas fa-info-circle fa-2x text-muted"></i>
<p class="text-muted mt-2">暂无热门商品</p>
</div>
`);
}
})
.fail(function() {
$('#hotProducts').html(`
<div class="col-12 text-center py-5">
<i class="fas fa-exclamation-triangle fa-2x text-warning"></i>
<p class="text-muted mt-2">加载失败,请刷新页面重试</p>
</div>
`);
});
}
// 渲染热门商品
function renderHotProducts(products) {
let html = '';
products.forEach(function(product) {
html += `
<div class="col-lg-3 col-md-6 mb-4">
<div class="card h-100">
<img src="` + (product.imageUrl || '${pageContext.request.contextPath}/images/default-product.svg') + `"
class="card-img-top" alt="` + product.name + `" style="height: 200px; object-fit: cover;"
onerror="this.src='${pageContext.request.contextPath}/images/default-product.svg'; this.onerror=null;">
<div class="card-body">
<h6 class="card-title text-truncate">` + product.name + `</h6>
<p class="card-text text-muted small text-truncate">` + (product.description || '暂无描述') + `</p>
<div class="d-flex justify-content-between align-items-center">
<span class="text-primary fw-bold">¥` + (product.price ? product.price.toFixed(2) : '0.00') + `</span>
<small class="text-muted">库存: ` + (product.stock || 0) + `</small>
</div>
<div class="mt-2">
<button class="btn btn-primary btn-sm w-100" onclick="addToCart(` + product.id + `)">
<i class="fas fa-cart-plus"></i> 加入购物车
</button>
</div>
</div>
</div>
</div>
`;
});
$('#hotProducts').html(html);
}
// 参与秒杀(首页版)
function participateFlashSale(flashSaleId) {
<c:choose>
<c:when test="${not empty sessionScope.user}">
// 防止重复点击
if (window.flashSaleInProgress) {
showMessage('操作进行中,请稍候...', 'warning');
return;
}
// 确认对话框
if (!confirm('确定要参与这个秒杀活动吗?\n\n注意每人限购一件确认后将立即抢购')) {
return;
}
// 找到按钮元素
const button = event.target.closest('button');
if (!button) return;
// 设置全局锁
window.flashSaleInProgress = true;
// 保存原始状态
const originalText = button.innerHTML;
const originalClass = button.className;
// 更新按钮状态
button.disabled = true;
button.className = 'btn btn-warning btn-sm w-100';
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 抢购中...';
const startTime = Date.now();
$.ajax({
url: '${pageContext.request.contextPath}/api/flashsale/participate',
type: 'POST',
contentType: 'application/json',
timeout: 10000,
data: JSON.stringify({
flashSaleId: flashSaleId,
quantity: 1,
timestamp: startTime
}),
success: function (response) {
const duration = Date.now() - startTime;
if (response.success) {
// 成功状态
button.className = 'btn btn-success btn-sm w-100';
button.innerHTML = '<i class="fas fa-check"></i> 抢购成功!';
showMessage(`🎉 恭喜您!秒杀成功,订单已生成 (耗时: ${duration}ms)`, 'success');
// 刷新活动数据
setTimeout(() => {
loadActiveFlashSales();
}, 1000);
// 跳转到订单页面
setTimeout(() => {
window.location.href = '${pageContext.request.contextPath}/orders';
}, 3000);
} else {
// 失败状态
button.className = 'btn btn-danger btn-sm w-100';
button.innerHTML = '<i class="fas fa-times"></i> ' + (response.message || '抢购失败');
showMessage(response.message || '抢购失败,请重试', 'error');
// 恢复按钮状态
setTimeout(() => {
button.disabled = false;
button.className = originalClass;
button.innerHTML = originalText;
}, 2000);
}
},
error: function (xhr, status, error) {
let errorMessage = '网络异常,请重试';
if (status === 'timeout') {
errorMessage = '请求超时,请检查网络连接';
button.innerHTML = '<i class="fas fa-clock"></i> 请求超时';
} else if (xhr.status === 429) {
errorMessage = '请求过于频繁,请稍后再试';
button.innerHTML = '<i class="fas fa-ban"></i> 请求频繁';
} else {
button.innerHTML = '<i class="fas fa-exclamation-triangle"></i> 网络异常';
}
button.className = 'btn btn-danger btn-sm w-100';
showMessage(errorMessage, 'error');
// 恢复按钮状态
setTimeout(() => {
button.disabled = false;
button.className = originalClass;
button.innerHTML = originalText;
}, 3000);
},
complete: function () {
// 释放全局锁
setTimeout(() => {
window.flashSaleInProgress = false;
}, 1000);
}
});
</c:when>
<c:otherwise>
showMessage('请先登录后参与秒杀', 'warning');
setTimeout(() => {
window.location.href = '${pageContext.request.contextPath}/login';
}, 1500);
</c:otherwise>
</c:choose>
}
// 添加到购物车
function addToCart(productId) {
<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
}),
success: function(response) {
if (response.success) {
showMessage('商品已添加到购物车', 'success');
updateCartCount();
} else {
showMessage(response.message, 'error');
}
},
error: function() {
showMessage('添加失败,请重试', 'error');
}
});
</c:when>
<c:otherwise>
showMessage('请先登录', 'warning');
setTimeout(() => {
window.location.href = '${pageContext.request.contextPath}/login';
}, 1000);
</c:otherwise>
</c:choose>
}
// 性能指标动画
function animateCounters() {
const counters = [
{ id: 'qpsCounter', target: 10000, suffix: '+' },
{ id: 'concurrentUsers', target: 50000, suffix: '+' }
];
counters.forEach(counter => {
animateCounter(counter.id, counter.target, counter.suffix);
});
}
function animateCounter(elementId, target, suffix = '') {
const element = document.getElementById(elementId);
let current = 0;
const increment = target / 100;
const timer = setInterval(() => {
current += increment;
if (current >= target) {
current = target;
clearInterval(timer);
}
element.textContent = Math.floor(current).toLocaleString() + suffix;
}, 20);
}
// 倒计时函数
function countdown(endTime, elementId) {
const element = document.getElementById(elementId);
if (!element) return;
const timer = setInterval(() => {
const now = Date.now();
const timeLeft = endTime - now;
if (timeLeft <= 0) {
element.innerHTML = '<span class="text-muted">已结束</span>';
clearInterval(timer);
return;
}
const hours = Math.floor(timeLeft / (1000 * 60 * 60));
const minutes = Math.floor((timeLeft % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((timeLeft % (1000 * 60)) / 1000);
element.innerHTML = `
<i class="fas fa-clock"></i>
${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}
`;
}, 1000);
}
// 显示消息
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 updateCartCount() {
$.get('${pageContext.request.contextPath}/api/cart/count')
.done(function (response) {
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';
}
}
});
}
</script>
<style>
/* 秒杀活动卡片样式 */
.card.border-danger {
border-width: 2px !important;
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
}
.card.border-danger:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(220, 53, 69, 0.3);
}
/* 热门商品卡片样式 */
.card:hover {
transform: translateY(-3px);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
transition: all 0.3s ease;
}
/* 进度条样式 */
.progress {
background-color: rgba(220, 53, 69, 0.1);
}
/* 倒计时样式 */
.text-danger.fw-bold {
font-family: 'Courier New', monospace;
letter-spacing: 1px;
}
/* 折扣标签样式 */
.position-absolute.bg-warning {
font-weight: bold;
font-size: 0.75rem;
}
/* 商品图片样式 */
.card-img-top {
transition: transform 0.3s ease;
}
.card:hover .card-img-top {
transform: scale(1.05);
}
/* 按钮悬停效果 */
.btn {
transition: all 0.2s ease;
}
.btn:hover {
transform: translateY(-1px);
}
/* 响应式调整 */
@media (max-width: 768px) {
.card.border-danger:hover,
.card:hover {
transform: none;
}
.card:hover .card-img-top {
transform: none;
}
}
/* 加载动画 */
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.fa-spinner.fa-spin {
animation: spin 1s linear infinite;
}
/* 消息提示样式 */
.alert.position-fixed {
animation: slideInRight 0.3s ease-out;
}
@keyframes slideInRight {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
</style>
<%@ include file="common/footer.jsp" %>