676 lines
26 KiB
Plaintext
676 lines
26 KiB
Plaintext
<%@ 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" %>
|