Add web/dist/shop/index.html
This commit is contained in:
809
web/dist/shop/index.html
vendored
Normal file
809
web/dist/shop/index.html
vendored
Normal file
@@ -0,0 +1,809 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>GlobalShop · 跨境电商平台</title>
|
||||
<style>
|
||||
/* ========== 主题变量 ========== */
|
||||
:root {
|
||||
--primary: #E63946;
|
||||
--primary-dark: #D62828;
|
||||
--primary-light: #FF6B6B;
|
||||
--accent: #457B9D;
|
||||
--dark: #1D3557;
|
||||
--bg: #F8F9FA;
|
||||
--surface: #FFFFFF;
|
||||
--text: #1D3557;
|
||||
--text-muted: #6C757D;
|
||||
--border: #E9ECEF;
|
||||
--shadow: 0 2px 8px rgba(0,0,0,0.06);
|
||||
--shadow-lg: 0 8px 30px rgba(0,0,0,0.12);
|
||||
--radius: 10px;
|
||||
--radius-sm: 6px;
|
||||
--radius-lg: 16px;
|
||||
--transition: all 0.25s cubic-bezier(0.4,0,0.2,1);
|
||||
}
|
||||
* { margin:0; padding:0; box-sizing:border-box; }
|
||||
body { font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif; background:var(--bg); color:var(--text); line-height:1.6; -webkit-font-smoothing:antialiased; }
|
||||
a { text-decoration:none; color:inherit; }
|
||||
button { cursor:pointer; border:none; font-family:inherit; }
|
||||
input { font-family:inherit; }
|
||||
img { max-width:100%; display:block; }
|
||||
|
||||
/* ========== 顶部公告栏 ========== */
|
||||
.topbar { background:var(--dark); color:#fff; font-size:13px; padding:8px 0; text-align:center; }
|
||||
.topbar span { margin:0 16px; opacity:0.9; }
|
||||
.topbar a { color:var(--primary-light); }
|
||||
|
||||
/* ========== 导航栏 ========== */
|
||||
.navbar { background:var(--surface); box-shadow:var(--shadow); position:sticky; top:0; z-index:100; }
|
||||
.nav-container { max-width:1280px; margin:0 auto; padding:0 24px; display:flex; align-items:center; gap:32px; height:64px; }
|
||||
.logo { font-size:24px; font-weight:800; color:var(--primary); display:flex; align-items:center; gap:8px; white-space:nowrap; }
|
||||
.logo svg { width:28px; height:28px; }
|
||||
.nav-links { display:flex; gap:4px; list-style:none; }
|
||||
.nav-links a { padding:8px 16px; border-radius:var(--radius-sm); font-size:14px; font-weight:500; color:var(--text); transition:var(--transition); }
|
||||
.nav-links a:hover, .nav-links a.active { background:var(--primary); color:#fff; }
|
||||
.search-box { flex:1; max-width:400px; position:relative; }
|
||||
.search-box input { width:100%; height:40px; padding:0 16px 0 40px; border:2px solid var(--border); border-radius:var(--radius); font-size:14px; outline:none; transition:var(--transition); }
|
||||
.search-box input:focus { border-color:var(--primary); }
|
||||
.search-box svg { position:absolute; left:12px; top:50%; transform:translateY(-50%); width:18px; height:18px; color:var(--text-muted); }
|
||||
.nav-actions { display:flex; align-items:center; gap:8px; }
|
||||
.nav-actions button { background:none; width:40px; height:40px; border-radius:50%; display:flex; align-items:center; justify-content:center; transition:var(--transition); position:relative; }
|
||||
.nav-actions button:hover { background:var(--bg); }
|
||||
.nav-actions svg { width:22px; height:22px; color:var(--text); }
|
||||
.cart-badge { position:absolute; top:2px; right:2px; background:var(--primary); color:#fff; font-size:11px; font-weight:700; min-width:18px; height:18px; border-radius:9px; display:flex; align-items:center; justify-content:center; padding:0 4px; }
|
||||
|
||||
/* ========== Hero 轮播 ========== */
|
||||
.hero { max-width:1280px; margin:24px auto; padding:0 24px; }
|
||||
.hero-banner { background:linear-gradient(135deg, #E63946 0%, #D62828 50%, #1D3557 100%); border-radius:var(--radius-lg); padding:60px 48px; color:#fff; position:relative; overflow:hidden; min-height:280px; display:flex; align-items:center; }
|
||||
.hero-banner::before { content:''; position:absolute; top:-50%; right:-10%; width:400px; height:400px; background:rgba(255,255,255,0.08); border-radius:50%; }
|
||||
.hero-banner::after { content:''; position:absolute; bottom:-30%; right:20%; width:300px; height:300px; background:rgba(255,255,255,0.05); border-radius:50%; }
|
||||
.hero-content { position:relative; z-index:1; max-width:560px; }
|
||||
.hero-content h1 { font-size:42px; font-weight:800; line-height:1.2; margin-bottom:16px; }
|
||||
.hero-content p { font-size:18px; opacity:0.9; margin-bottom:28px; }
|
||||
.hero-btn { display:inline-flex; align-items:center; gap:8px; background:#fff; color:var(--primary); padding:14px 32px; border-radius:var(--radius); font-size:16px; font-weight:700; transition:var(--transition); }
|
||||
.hero-btn:hover { transform:translateY(-2px); box-shadow:0 8px 24px rgba(0,0,0,0.2); }
|
||||
|
||||
/* ========== 优势条 ========== */
|
||||
.features-strip { max-width:1280px; margin:24px auto; padding:0 24px; display:grid; grid-template-columns:repeat(4,1fr); gap:16px; }
|
||||
.feature-item { background:var(--surface); border-radius:var(--radius); padding:20px; display:flex; align-items:center; gap:14px; box-shadow:var(--shadow); }
|
||||
.feature-item svg { width:36px; height:36px; color:var(--primary); flex-shrink:0; }
|
||||
.feature-item h4 { font-size:14px; font-weight:700; }
|
||||
.feature-item p { font-size:12px; color:var(--text-muted); }
|
||||
|
||||
/* ========== 分类导航 ========== */
|
||||
.section { max-width:1280px; margin:48px auto; padding:0 24px; }
|
||||
.section-header { display:flex; align-items:center; justify-content:space-between; margin-bottom:24px; }
|
||||
.section-title { font-size:26px; font-weight:800; position:relative; padding-left:16px; }
|
||||
.section-title::before { content:''; position:absolute; left:0; top:50%; transform:translateY(-50%); width:4px; height:28px; background:var(--primary); border-radius:2px; }
|
||||
.section-more { font-size:14px; color:var(--primary); font-weight:600; }
|
||||
|
||||
.category-nav { display:flex; gap:12px; overflow-x:auto; padding-bottom:8px; margin-bottom:24px; }
|
||||
.category-chip { padding:10px 24px; border-radius:var(--radius); background:var(--surface); border:2px solid var(--border); font-size:14px; font-weight:600; color:var(--text); white-space:nowrap; transition:var(--transition); }
|
||||
.category-chip:hover { border-color:var(--primary); color:var(--primary); }
|
||||
.category-chip.active { background:var(--primary); color:#fff; border-color:var(--primary); }
|
||||
|
||||
/* ========== 商品网格 ========== */
|
||||
.products-layout { display:grid; grid-template-columns:220px 1fr; gap:24px; }
|
||||
.filters-sidebar { background:var(--surface); border-radius:var(--radius); padding:20px; box-shadow:var(--shadow); height:fit-content; position:sticky; top:80px; }
|
||||
.filters-sidebar h3 { font-size:16px; font-weight:700; margin-bottom:16px; padding-bottom:12px; border-bottom:1px solid var(--border); }
|
||||
.filter-group { margin-bottom:20px; }
|
||||
.filter-group h4 { font-size:13px; font-weight:700; margin-bottom:10px; color:var(--text-muted); text-transform:uppercase; letter-spacing:0.5px; }
|
||||
.filter-option { display:flex; align-items:center; gap:8px; padding:6px 0; cursor:pointer; font-size:14px; }
|
||||
.filter-option input { accent-color:var(--primary); }
|
||||
.filter-option label { cursor:pointer; }
|
||||
.filter-option .count { margin-left:auto; font-size:12px; color:var(--text-muted); }
|
||||
|
||||
.products-main { min-width:0; }
|
||||
.products-toolbar { display:flex; align-items:center; justify-content:space-between; margin-bottom:20px; flex-wrap:wrap; gap:12px; }
|
||||
.products-count { font-size:14px; color:var(--text-muted); }
|
||||
.sort-select { padding:8px 16px; border:2px solid var(--border); border-radius:var(--radius-sm); font-size:14px; outline:none; background:var(--surface); cursor:pointer; }
|
||||
.sort-select:focus { border-color:var(--primary); }
|
||||
|
||||
.products-grid { display:grid; grid-template-columns:repeat(4,1fr); gap:20px; }
|
||||
.product-card { background:var(--surface); border-radius:var(--radius); overflow:hidden; box-shadow:var(--shadow); transition:var(--transition); cursor:pointer; position:relative; }
|
||||
.product-card:hover { transform:translateY(-4px); box-shadow:var(--shadow-lg); }
|
||||
.product-image { width:100%; aspect-ratio:1; background:linear-gradient(135deg,#f0f0f0,#e0e0e0); position:relative; overflow:hidden; }
|
||||
.product-image-gradient { width:100%; height:100%; display:flex; align-items:center; justify-content:center; }
|
||||
.product-image-gradient svg { width:50%; height:50%; opacity:0.3; }
|
||||
.product-tags { position:absolute; top:10px; left:10px; display:flex; flex-direction:column; gap:4px; }
|
||||
.product-tag { padding:3px 10px; border-radius:var(--radius-sm); font-size:11px; font-weight:700; color:#fff; }
|
||||
.tag-hot { background:var(--primary); }
|
||||
.tag-new { background:var(--accent); }
|
||||
.tag-sale { background:#FF8C00; }
|
||||
.product-discount { position:absolute; top:10px; right:10px; background:var(--primary); color:#fff; font-size:12px; font-weight:700; padding:4px 8px; border-radius:var(--radius-sm); }
|
||||
.product-info { padding:14px; }
|
||||
.product-name { font-size:14px; font-weight:600; line-height:1.4; margin-bottom:8px; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; min-height:39px; }
|
||||
.product-price { display:flex; align-items:baseline; gap:8px; margin-bottom:8px; }
|
||||
.price-current { font-size:20px; font-weight:800; color:var(--primary); }
|
||||
.price-original { font-size:14px; color:var(--text-muted); text-decoration:line-through; }
|
||||
.product-meta { display:flex; align-items:center; justify-content:space-between; font-size:12px; color:var(--text-muted); }
|
||||
.product-rating { display:flex; align-items:center; gap:4px; }
|
||||
.stars { color:#FFD700; letter-spacing:1px; }
|
||||
.product-sales { }
|
||||
.add-cart-btn { position:absolute; bottom:14px; right:14px; width:36px; height:36px; border-radius:50%; background:var(--primary); color:#fff; display:flex; align-items:center; justify-content:center; opacity:0; transform:translateY(10px); transition:var(--transition); }
|
||||
.product-card:hover .add-cart-btn { opacity:1; transform:translateY(0); }
|
||||
.add-cart-btn:hover { background:var(--primary-dark); transform:scale(1.1); }
|
||||
.add-cart-btn svg { width:18px; height:18px; }
|
||||
|
||||
/* ========== 加载骨架 ========== */
|
||||
.skeleton-card { background:var(--surface); border-radius:var(--radius); overflow:hidden; box-shadow:var(--shadow); }
|
||||
.skeleton-img { aspect-ratio:1; background:linear-gradient(90deg,#f0f0f0 25%,#e0e0e0 50%,#f0f0f0 75%); background-size:200% 100%; animation:shimmer 1.5s infinite; }
|
||||
.skeleton-line { height:14px; margin:12px 14px; border-radius:4px; background:linear-gradient(90deg,#f0f0f0 25%,#e0e0e0 50%,#f0f0f0 75%); background-size:200% 100%; animation:shimmer 1.5s infinite; }
|
||||
@keyframes shimmer { 0%{background-position:200% 0;} 100%{background-position:-200% 0;} }
|
||||
|
||||
/* ========== 分页 ========== */
|
||||
.pagination { display:flex; justify-content:center; gap:8px; margin-top:40px; }
|
||||
.page-btn { min-width:40px; height:40px; border-radius:var(--radius-sm); background:var(--surface); border:2px solid var(--border); font-size:14px; font-weight:600; transition:var(--transition); }
|
||||
.page-btn:hover:not(:disabled) { border-color:var(--primary); color:var(--primary); }
|
||||
.page-btn.active { background:var(--primary); color:#fff; border-color:var(--primary); }
|
||||
.page-btn:disabled { opacity:0.4; cursor:not-allowed; }
|
||||
|
||||
/* ========== 购物车抽屉 ========== */
|
||||
.cart-overlay { position:fixed; inset:0; background:rgba(0,0,0,0.5); z-index:200; opacity:0; visibility:hidden; transition:var(--transition); }
|
||||
.cart-overlay.open { opacity:1; visibility:visible; }
|
||||
.cart-drawer { position:fixed; top:0; right:0; width:420px; max-width:90vw; height:100vh; background:var(--surface); z-index:201; transform:translateX(100%); transition:transform 0.3s cubic-bezier(0.4,0,0.2,1); display:flex; flex-direction:column; }
|
||||
.cart-drawer.open { transform:translateX(0); }
|
||||
.cart-header { padding:20px 24px; border-bottom:1px solid var(--border); display:flex; align-items:center; justify-content:space-between; }
|
||||
.cart-header h3 { font-size:18px; font-weight:800; }
|
||||
.cart-close { width:32px; height:32px; border-radius:50%; background:var(--bg); display:flex; align-items:center; justify-content:center; font-size:20px; color:var(--text-muted); }
|
||||
.cart-items { flex:1; overflow-y:auto; padding:16px 24px; }
|
||||
.cart-item { display:flex; gap:12px; padding:16px 0; border-bottom:1px solid var(--border); }
|
||||
.cart-item-img { width:72px; height:72px; border-radius:var(--radius-sm); background:linear-gradient(135deg,#f0f0f0,#e0e0e0); flex-shrink:0; display:flex; align-items:center; justify-content:center; }
|
||||
.cart-item-img svg { width:32px; height:32px; opacity:0.3; }
|
||||
.cart-item-info { flex:1; min-width:0; }
|
||||
.cart-item-name { font-size:14px; font-weight:600; margin-bottom:4px; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
|
||||
.cart-item-price { font-size:16px; font-weight:700; color:var(--primary); }
|
||||
.cart-item-controls { display:flex; align-items:center; gap:12px; margin-top:8px; }
|
||||
.qty-btn { width:28px; height:28px; border-radius:var(--radius-sm); background:var(--bg); display:flex; align-items:center; justify-content:center; font-size:16px; font-weight:700; }
|
||||
.qty-btn:hover { background:var(--border); }
|
||||
.qty-value { font-size:14px; font-weight:600; min-width:24px; text-align:center; }
|
||||
.cart-item-remove { margin-left:auto; font-size:12px; color:var(--text-muted); }
|
||||
.cart-item-remove:hover { color:var(--primary); }
|
||||
.cart-empty { text-align:center; padding:60px 20px; color:var(--text-muted); }
|
||||
.cart-empty svg { width:64px; height:64px; margin:0 auto 16px; opacity:0.3; }
|
||||
.cart-footer { padding:20px 24px; border-top:1px solid var(--border); background:var(--bg); }
|
||||
.cart-total { display:flex; align-items:center; justify-content:space-between; margin-bottom:16px; }
|
||||
.cart-total-label { font-size:14px; color:var(--text-muted); }
|
||||
.cart-total-value { font-size:24px; font-weight:800; color:var(--primary); }
|
||||
.checkout-btn { width:100%; padding:14px; border-radius:var(--radius); background:var(--primary); color:#fff; font-size:16px; font-weight:700; transition:var(--transition); }
|
||||
.checkout-btn:hover { background:var(--primary-dark); }
|
||||
.checkout-btn:disabled { opacity:0.5; cursor:not-allowed; }
|
||||
|
||||
/* ========== 商品详情模态框 ========== */
|
||||
.modal-overlay { position:fixed; inset:0; background:rgba(0,0,0,0.6); z-index:300; display:flex; align-items:center; justify-content:center; padding:20px; opacity:0; visibility:hidden; transition:var(--transition); }
|
||||
.modal-overlay.open { opacity:1; visibility:visible; }
|
||||
.modal-content { background:var(--surface); border-radius:var(--radius-lg); max-width:800px; width:100%; max-height:90vh; overflow-y:auto; transform:scale(0.9); transition:transform 0.3s; }
|
||||
.modal-overlay.open .modal-content { transform:scale(1); }
|
||||
.modal-close { position:absolute; top:16px; right:16px; width:36px; height:36px; border-radius:50%; background:rgba(0,0,0,0.1); display:flex; align-items:center; justify-content:center; font-size:24px; z-index:1; }
|
||||
.modal-body { display:grid; grid-template-columns:1fr 1fr; gap:0; }
|
||||
.modal-image { background:linear-gradient(135deg,#f0f0f0,#e0e0e0); aspect-ratio:1; display:flex; align-items:center; justify-content:center; }
|
||||
.modal-image svg { width:40%; height:40%; opacity:0.2; }
|
||||
.modal-details { padding:32px; position:relative; }
|
||||
.modal-details h2 { font-size:22px; font-weight:700; margin-bottom:12px; line-height:1.3; }
|
||||
.modal-price { display:flex; align-items:baseline; gap:12px; margin-bottom:16px; }
|
||||
.modal-price .price-current { font-size:32px; }
|
||||
.modal-tags { display:flex; gap:8px; margin-bottom:16px; }
|
||||
.modal-desc { font-size:14px; color:var(--text-muted); line-height:1.7; margin-bottom:20px; }
|
||||
.modal-meta { display:flex; gap:24px; padding:16px 0; border-top:1px solid var(--border); border-bottom:1px solid var(--border); margin-bottom:20px; font-size:13px; }
|
||||
.modal-meta-item { display:flex; flex-direction:column; gap:2px; }
|
||||
.modal-meta-item span:first-child { color:var(--text-muted); }
|
||||
.modal-meta-item span:last-child { font-weight:600; }
|
||||
.modal-add-cart { width:100%; padding:14px; border-radius:var(--radius); background:var(--primary); color:#fff; font-size:16px; font-weight:700; transition:var(--transition); }
|
||||
.modal-add-cart:hover { background:var(--primary-dark); }
|
||||
|
||||
/* ========== 页脚 ========== */
|
||||
.footer { background:var(--dark); color:#fff; margin-top:64px; }
|
||||
.footer-content { max-width:1280px; margin:0 auto; padding:48px 24px 24px; display:grid; grid-template-columns:2fr 1fr 1fr 1fr; gap:40px; }
|
||||
.footer-brand h3 { font-size:22px; font-weight:800; color:var(--primary-light); margin-bottom:12px; }
|
||||
.footer-brand p { font-size:14px; opacity:0.7; line-height:1.7; }
|
||||
.footer-col h4 { font-size:15px; font-weight:700; margin-bottom:16px; }
|
||||
.footer-col ul { list-style:none; }
|
||||
.footer-col li { margin-bottom:10px; }
|
||||
.footer-col a { font-size:13px; opacity:0.7; transition:var(--transition); }
|
||||
.footer-col a:hover { opacity:1; color:var(--primary-light); }
|
||||
.footer-bottom { border-top:1px solid rgba(255,255,255,0.1); padding:20px 24px; text-align:center; font-size:13px; opacity:0.6; }
|
||||
|
||||
/* ========== Toast 提示 ========== */
|
||||
.toast { position:fixed; bottom:30px; left:50%; transform:translateX(-50%) translateY(100px); background:var(--dark); color:#fff; padding:12px 24px; border-radius:var(--radius); font-size:14px; font-weight:600; z-index:400; opacity:0; transition:all 0.3s; display:flex; align-items:center; gap:8px; }
|
||||
.toast.show { transform:translateX(-50%) translateY(0); opacity:1; }
|
||||
.toast svg { width:20px; height:20px; color:#4ADE80; }
|
||||
|
||||
/* ========== 响应式 ========== */
|
||||
@media (max-width:1024px) {
|
||||
.products-grid { grid-template-columns:repeat(3,1fr); }
|
||||
.features-strip { grid-template-columns:repeat(2,1fr); }
|
||||
.footer-content { grid-template-columns:2fr 1fr 1fr; }
|
||||
}
|
||||
@media (max-width:768px) {
|
||||
.nav-links { display:none; }
|
||||
.search-box { max-width:200px; }
|
||||
.hero-banner { padding:40px 24px; }
|
||||
.hero-content h1 { font-size:28px; }
|
||||
.hero-content p { font-size:15px; }
|
||||
.products-layout { grid-template-columns:1fr; }
|
||||
.filters-sidebar { position:static; }
|
||||
.products-grid { grid-template-columns:repeat(2,1fr); }
|
||||
.modal-body { grid-template-columns:1fr; }
|
||||
.footer-content { grid-template-columns:1fr; gap:24px; }
|
||||
}
|
||||
@media (max-width:480px) {
|
||||
.features-strip { grid-template-columns:1fr; }
|
||||
.products-grid { grid-template-columns:1fr; }
|
||||
.hero-content h1 { font-size:24px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- 顶部公告栏 -->
|
||||
<div class="topbar">
|
||||
<span>🚀 全球直邮 · 满 $99 免运费</span>
|
||||
<span>📦 7天无理由退换</span>
|
||||
<span>🔒 安全支付 · <a href="#">查看详情</a></span>
|
||||
</div>
|
||||
|
||||
<!-- 导航栏 -->
|
||||
<nav class="navbar">
|
||||
<div class="nav-container">
|
||||
<div class="logo">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7v10l10 5 10-5V7L12 2z"/><path d="M2 7l10 5 10-5"/><path d="M12 22V12"/></svg>
|
||||
GlobalShop
|
||||
</div>
|
||||
<ul class="nav-links">
|
||||
<li><a href="#" class="active" onclick="resetPage();return false;">首页</a></li>
|
||||
<li><a href="#products" onclick="scrollToProducts();return false;">所有商品</a></li>
|
||||
<li><a href="#featured" onclick="loadFeatured();return false;">热卖推荐</a></li>
|
||||
<li><a href="#deals" onclick="filterDeals();return false;">限时特惠</a></li>
|
||||
</ul>
|
||||
<div class="search-box">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
|
||||
<input type="text" id="searchInput" placeholder="搜索商品..." oninput="handleSearch(event)">
|
||||
</div>
|
||||
<div class="nav-actions">
|
||||
<button title="收藏">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>
|
||||
</button>
|
||||
<button title="购物车" onclick="toggleCart()">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/><path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/></svg>
|
||||
<span class="cart-badge" id="cartBadge" style="display:none">0</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Hero Banner -->
|
||||
<div class="hero">
|
||||
<div class="hero-banner">
|
||||
<div class="hero-content">
|
||||
<h1>全球好物<br>跨境直达</h1>
|
||||
<p>精选 18+ 品类好物,覆盖智能电子、家居生活、时尚运动,全球直邮,品质保障</p>
|
||||
<a href="#" class="hero-btn" onclick="scrollToProducts();return false;">
|
||||
立即选购
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:20px;height:20px"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 优势条 -->
|
||||
<div class="features-strip">
|
||||
<div class="feature-item">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="1" y="3" width="15" height="13"/><path d="M16 8h4l3 3v5h-7V8z"/><circle cx="5.5" cy="18.5" r="2.5"/><circle cx="18.5" cy="18.5" r="2.5"/></svg>
|
||||
<div><h4>全球直邮</h4><p>满 $99 免运费</p></div>
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>
|
||||
<div><h4>正品保障</h4><p>品牌授权直供</p></div>
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>
|
||||
<div><h4>7天退换</h4><p>无理由退货</p></div>
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="1" y="4" width="22" height="16" rx="2"/><line x1="1" y1="10" x2="23" y2="10"/></svg>
|
||||
<div><h4>安全支付</h4><p>多渠道加密</p></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 商品列表区 -->
|
||||
<div class="section" id="products">
|
||||
<div class="section-header">
|
||||
<div class="section-title">全部商品</div>
|
||||
</div>
|
||||
|
||||
<div class="products-layout">
|
||||
<!-- 侧边筛选 -->
|
||||
<aside class="filters-sidebar">
|
||||
<h3>筛选</h3>
|
||||
<div class="filter-group">
|
||||
<h4>分类</h4>
|
||||
<div id="categoryFilters"></div>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<h4>价格区间</h4>
|
||||
<div class="filter-option"><input type="radio" name="price" id="p0" value="0" checked onchange="applyFilters()"><label for="p0">全部</label></div>
|
||||
<div class="filter-option"><input type="radio" name="price" id="p1" value="0-30" onchange="applyFilters()"><label for="p1">$0 - $30</label></div>
|
||||
<div class="filter-option"><input type="radio" name="price" id="p2" value="30-60" onchange="applyFilters()"><label for="p2">$30 - $60</label></div>
|
||||
<div class="filter-option"><input type="radio" name="price" id="p3" value="60-100" onchange="applyFilters()"><label for="p3">$60 - $100</label></div>
|
||||
<div class="filter-option"><input type="radio" name="price" id="p4" value="100-9999" onchange="applyFilters()"><label for="p4">$100+</label></div>
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<h4>排序</h4>
|
||||
<div class="filter-option"><input type="radio" name="sort" id="s1" value="default" checked onchange="applyFilters()"><label for="s1">默认排序</label></div>
|
||||
<div class="filter-option"><input type="radio" name="sort" id="s2" value="sales" onchange="applyFilters()"><label for="s2">销量优先</label></div>
|
||||
<div class="filter-option"><input type="radio" name="sort" id="s3" value="price_asc" onchange="applyFilters()"><label for="s3">价格升序</label></div>
|
||||
<div class="filter-option"><input type="radio" name="sort" id="s4" value="price_desc" onchange="applyFilters()"><label for="s4">价格降序</label></div>
|
||||
<div class="filter-option"><input type="radio" name="sort" id="s5" value="rating" onchange="applyFilters()"><label for="s5">评分最高</label></div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- 商品网格 -->
|
||||
<div class="products-main">
|
||||
<div class="products-toolbar">
|
||||
<div class="products-count" id="productsCount">加载中...</div>
|
||||
<div class="category-nav" style="margin:0;padding:0;overflow:visible;border:none" id="categoryChips"></div>
|
||||
</div>
|
||||
<div class="products-grid" id="productsGrid">
|
||||
<!-- 骨架屏 -->
|
||||
</div>
|
||||
<div class="pagination" id="pagination"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 购物车遮罩 -->
|
||||
<div class="cart-overlay" id="cartOverlay" onclick="toggleCart()"></div>
|
||||
|
||||
<!-- 购物车抽屉 -->
|
||||
<div class="cart-drawer" id="cartDrawer">
|
||||
<div class="cart-header">
|
||||
<h3>购物车 (<span id="cartCount">0</span>)</h3>
|
||||
<button class="cart-close" onclick="toggleCart()">×</button>
|
||||
</div>
|
||||
<div class="cart-items" id="cartItems"></div>
|
||||
<div class="cart-footer">
|
||||
<div class="cart-total">
|
||||
<span class="cart-total-label">合计</span>
|
||||
<span class="cart-total-value" id="cartTotal">$0.00</span>
|
||||
</div>
|
||||
<button class="checkout-btn" id="checkoutBtn" onclick="checkout()">去结算</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 商品详情模态框 -->
|
||||
<div class="modal-overlay" id="productModal" onclick="closeModal(event)">
|
||||
<div class="modal-content" id="modalContent"></div>
|
||||
</div>
|
||||
|
||||
<!-- Toast -->
|
||||
<div class="toast" id="toast">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
|
||||
<span id="toastMsg">已加入购物车</span>
|
||||
</div>
|
||||
|
||||
<!-- 页脚 -->
|
||||
<footer class="footer">
|
||||
<div class="footer-content">
|
||||
<div class="footer-brand">
|
||||
<h3>GlobalShop</h3>
|
||||
<p>全球好物,跨境直达。我们精选全球优质商品,为您提供安全、便捷、愉悦的跨境购物体验。覆盖智能电子、家居生活、时尚服饰、运动户外、美妆个护等品类。</p>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4>购物指南</h4>
|
||||
<ul>
|
||||
<li><a href="#">购物流程</a></li>
|
||||
<li><a href="#">会员介绍</a></li>
|
||||
<li><a href="#">常见问题</a></li>
|
||||
<li><a href="#">联系客服</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4>配送与退换</h4>
|
||||
<ul>
|
||||
<li><a href="#">配送方式</a></li>
|
||||
<li><a href="#">运费规则</a></li>
|
||||
<li><a href="#">退换货政策</a></li>
|
||||
<li><a href="#">退款说明</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4>关于我们</h4>
|
||||
<ul>
|
||||
<li><a href="#">公司简介</a></li>
|
||||
<li><a href="#">加入我们</a></li>
|
||||
<li><a href="#">隐私政策</a></li>
|
||||
<li><a href="#">用户协议</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
© 2026 GlobalShop. All rights reserved. 全球好物 · 跨境直达
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
// ========== 全局状态 ==========
|
||||
let allCategories = [];
|
||||
let currentPage = 1;
|
||||
const pageSize = 12;
|
||||
let selectedCategory = 0;
|
||||
let searchKeyword = '';
|
||||
let cart = [];
|
||||
|
||||
// ========== 图标库 ==========
|
||||
const iconColors = ['#E63946','#457B9D','#F4A261','#2A9D8F','#9D4EDD','#E76F51','#06A77D','#D62828'];
|
||||
function getGradient(seed) {
|
||||
const c = iconColors[seed % iconColors.length];
|
||||
return `linear-gradient(135deg, ${c}22, ${c}44)`;
|
||||
}
|
||||
function productIconSVG(catId) {
|
||||
const icons = {
|
||||
1: '<path d="M4 4h16v12H4z"/><path d="M2 20h20"/><path d="M9 8h6"/>',
|
||||
2: '<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="M9 22V12h6v10"/>',
|
||||
3: '<path d="M20.38 3.46L16 2a4 4 0 0 1-8 0L3.62 3.46a2 2 0 0 0-1.34 2.23l.58 3.47a1 1 0 0 0 .99.84H6v10a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V10h2.15a1 1 0 0 0 .99-.84l.58-3.47a2 2 0 0 0-1.34-2.23z"/>',
|
||||
4: '<path d="M6.5 6.5l11 11"/><path d="M21 21l-6-6m6 6v-4m0 4h-4"/><circle cx="12" cy="12" r="10"/><path d="M2 12h20"/>',
|
||||
5: '<path d="M12 2v20M2 12h20"/><circle cx="12" cy="12" r="4"/>',
|
||||
};
|
||||
return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">${icons[catId] || icons[1]}</svg>`;
|
||||
}
|
||||
|
||||
// ========== API 调用 ==========
|
||||
async function fetchJSON(url) {
|
||||
const res = await fetch(url);
|
||||
const json = await res.json();
|
||||
if (json.code !== 0) throw new Error(json.message || '请求失败');
|
||||
return json.data;
|
||||
}
|
||||
|
||||
async function loadCategories() {
|
||||
try {
|
||||
allCategories = await fetchJSON('/api/v1/shop/categories');
|
||||
renderCategoryFilters();
|
||||
renderCategoryChips();
|
||||
} catch(e) { console.error('分类加载失败:', e); }
|
||||
}
|
||||
|
||||
async function loadProducts() {
|
||||
const grid = document.getElementById('productsGrid');
|
||||
grid.innerHTML = Array(8).fill(0).map(() => `
|
||||
<div class="skeleton-card">
|
||||
<div class="skeleton-img"></div>
|
||||
<div class="skeleton-line"></div>
|
||||
<div class="skeleton-line" style="width:60%"></div>
|
||||
<div class="skeleton-line" style="width:40%"></div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
try {
|
||||
let url = `/api/v1/shop/products?page=${currentPage}&page_size=${pageSize}`;
|
||||
const sortRadio = document.querySelector('input[name="sort"]:checked');
|
||||
if (sortRadio && sortRadio.value !== 'default') url += `&sort=${sortRadio.value}`;
|
||||
if (selectedCategory > 0) url += `&category_id=${selectedCategory}`;
|
||||
if (searchKeyword) url += `&keyword=${encodeURIComponent(searchKeyword)}`;
|
||||
|
||||
const data = await fetchJSON(url);
|
||||
renderProducts(data.list || [], data.total || 0);
|
||||
renderPagination(data.total || 0);
|
||||
} catch(e) {
|
||||
grid.innerHTML = `<div style="grid-column:1/-1;text-align:center;padding:60px;color:var(--text-muted)">
|
||||
<p>商品加载失败,请稍后重试</p></div>`;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 渲染 ==========
|
||||
function renderCategoryFilters() {
|
||||
const container = document.getElementById('categoryFilters');
|
||||
container.innerHTML = `
|
||||
<div class="filter-option">
|
||||
<input type="radio" name="cat-filter" id="cat0" value="0" checked onchange="selectCategory(0)">
|
||||
<label for="cat0">全部分类</label>
|
||||
</div>
|
||||
` + allCategories.map(c => `
|
||||
<div class="filter-option">
|
||||
<input type="radio" name="cat-filter" id="cat${c.id}" value="${c.id}" onchange="selectCategory(${c.id})">
|
||||
<label for="cat${c.id}">${c.name}</label>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function renderCategoryChips() {
|
||||
const container = document.getElementById('categoryChips');
|
||||
container.innerHTML = `<span class="category-chip ${selectedCategory === 0 ? 'active' : ''}" onclick="selectCategory(0)">全部</span>` +
|
||||
allCategories.map(c => `<span class="category-chip ${selectedCategory === c.id ? 'active' : ''}" onclick="selectCategory(${c.id})">${c.name}</span>`).join('');
|
||||
}
|
||||
|
||||
function renderProducts(products, total) {
|
||||
const grid = document.getElementById('productsGrid');
|
||||
document.getElementById('productsCount').textContent = `共 ${total} 件商品`;
|
||||
|
||||
if (products.length === 0) {
|
||||
grid.innerHTML = `<div style="grid-column:1/-1;text-align:center;padding:60px;color:var(--text-muted)">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="width:48px;height:48px;margin:0 auto 12px;opacity:0.3"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
|
||||
<p>未找到匹配的商品</p></div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
grid.innerHTML = products.map((p, i) => {
|
||||
const discount = p.original_price > p.price ? Math.round((1 - p.price / p.original_price) * 100) : 0;
|
||||
let tags = [];
|
||||
try { tags = JSON.parse(p.tags || '[]'); } catch(e) {}
|
||||
const tagHtml = tags.slice(0, 2).map(t => {
|
||||
const cls = t.includes('热') || t.includes('爆') ? 'tag-hot' : t.includes('新') ? 'tag-new' : 'tag-sale';
|
||||
return `<span class="product-tag ${cls}">${t}</span>`;
|
||||
}).join('');
|
||||
|
||||
return `
|
||||
<div class="product-card" onclick="showProductDetail(${p.id})">
|
||||
<div class="product-image">
|
||||
<div class="product-image-gradient" style="background:${getGradient(p.category_id)}">
|
||||
${productIconSVG(p.category_id)}
|
||||
</div>
|
||||
<div class="product-tags">${tagHtml}</div>
|
||||
${discount > 0 ? `<div class="product-discount">-${discount}%</div>` : ''}
|
||||
</div>
|
||||
<div class="product-info">
|
||||
<div class="product-name">${p.name}</div>
|
||||
<div class="product-price">
|
||||
<span class="price-current">$${p.price.toFixed(2)}</span>
|
||||
${p.original_price > p.price ? `<span class="price-original">$${p.original_price.toFixed(2)}</span>` : ''}
|
||||
</div>
|
||||
<div class="product-meta">
|
||||
<div class="product-rating">
|
||||
<span class="stars">${'★'.repeat(Math.round(p.rating))}</span>
|
||||
<span>${p.rating.toFixed(1)}</span>
|
||||
</div>
|
||||
<span class="product-sales">已售 ${formatSales(p.sales)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="add-cart-btn" onclick="event.stopPropagation();addToCart(${p.id})" title="加入购物车">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/><path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/></svg>
|
||||
</button>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function renderPagination(total) {
|
||||
const pages = Math.ceil(total / pageSize);
|
||||
if (pages <= 1) { document.getElementById('pagination').innerHTML = ''; return; }
|
||||
|
||||
let html = `<button class="page-btn" ${currentPage === 1 ? 'disabled' : ''} onclick="goToPage(${currentPage - 1})"><</button>`;
|
||||
const maxVisible = 5;
|
||||
let start = Math.max(1, currentPage - 2);
|
||||
let end = Math.min(pages, start + maxVisible - 1);
|
||||
if (end - start < maxVisible - 1) start = Math.max(1, end - maxVisible + 1);
|
||||
|
||||
if (start > 1) { html += `<button class="page-btn" onclick="goToPage(1)">1</button>`; if (start > 2) html += `<span style="padding:0 4px;color:var(--text-muted)">...</span>`; }
|
||||
for (let i = start; i <= end; i++) {
|
||||
html += `<button class="page-btn ${i === currentPage ? 'active' : ''}" onclick="goToPage(${i})">${i}</button>`;
|
||||
}
|
||||
if (end < pages) { if (end < pages - 1) html += `<span style="padding:0 4px;color:var(--text-muted)">...</span>`; html += `<button class="page-btn" onclick="goToPage(${pages})">${pages}</button>`; }
|
||||
html += `<button class="page-btn" ${currentPage === pages ? 'disabled' : ''} onclick="goToPage(${currentPage + 1})">></button>`;
|
||||
|
||||
document.getElementById('pagination').innerHTML = html;
|
||||
}
|
||||
|
||||
// ========== 交互函数 ==========
|
||||
function selectCategory(catId) {
|
||||
selectedCategory = catId;
|
||||
currentPage = 1;
|
||||
document.querySelectorAll('input[name="cat-filter"]').forEach(r => { r.checked = parseInt(r.value) === catId; });
|
||||
document.querySelectorAll('.category-chip').forEach((c, i) => {
|
||||
c.classList.toggle('active', (i === 0 && catId === 0) || (i > 0 && allCategories[i - 1] && allCategories[i - 1].id === catId));
|
||||
});
|
||||
loadProducts();
|
||||
}
|
||||
|
||||
function applyFilters() {
|
||||
// 侧边栏排序与价格筛选
|
||||
const priceRadio = document.querySelector('input[name="price"]:checked');
|
||||
const sortRadio = document.querySelector('input[name="sort"]:checked');
|
||||
// 同步排序到主工具栏的 sort
|
||||
currentPage = 1;
|
||||
loadProducts();
|
||||
}
|
||||
|
||||
function handleSearch(e) {
|
||||
searchKeyword = e.target.value.trim();
|
||||
currentPage = 1;
|
||||
clearTimeout(window._searchTimer);
|
||||
window._searchTimer = setTimeout(loadProducts, 400);
|
||||
}
|
||||
|
||||
function goToPage(page) {
|
||||
currentPage = page;
|
||||
loadProducts();
|
||||
document.getElementById('products').scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
|
||||
function scrollToProducts() {
|
||||
document.getElementById('products').scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
|
||||
function resetPage() {
|
||||
selectedCategory = 0;
|
||||
searchKeyword = '';
|
||||
currentPage = 1;
|
||||
document.getElementById('searchInput').value = '';
|
||||
document.querySelectorAll('input[name="price"]').forEach(r => r.checked = r.value === '0');
|
||||
document.querySelectorAll('input[name="sort"]').forEach(r => r.checked = r.value === 'default');
|
||||
selectCategory(0);
|
||||
}
|
||||
|
||||
function filterDeals() {
|
||||
// 筛选有折扣的商品(通过搜索价格降序再筛选不太精确,这里简单跳转)
|
||||
scrollToProducts();
|
||||
showToast('正在筛选限时特惠商品...');
|
||||
}
|
||||
|
||||
async function loadFeatured() {
|
||||
try {
|
||||
const products = await fetchJSON('/api/v1/shop/featured?limit=8');
|
||||
renderProducts(products, products.length);
|
||||
document.getElementById('pagination').innerHTML = '';
|
||||
document.getElementById('productsCount').textContent = `热卖推荐 · ${products.length} 件商品`;
|
||||
document.getElementById('products').scrollIntoView({ behavior: 'smooth' });
|
||||
} catch(e) { showToast('加载推荐失败'); }
|
||||
}
|
||||
|
||||
// ========== 商品详情 ==========
|
||||
async function showProductDetail(id) {
|
||||
const modal = document.getElementById('productModal');
|
||||
const content = document.getElementById('modalContent');
|
||||
content.innerHTML = '<div style="padding:60px;text-align:center;color:var(--text-muted)">加载中...</div>';
|
||||
modal.classList.add('open');
|
||||
|
||||
try {
|
||||
const p = await fetchJSON(`/api/v1/shop/products/${id}`);
|
||||
let tags = [];
|
||||
try { tags = JSON.parse(p.tags || '[]'); } catch(e) {}
|
||||
const discount = p.original_price > p.price ? Math.round((1 - p.price / p.original_price) * 100) : 0;
|
||||
|
||||
content.innerHTML = `
|
||||
<button class="modal-close" onclick="closeModal()">×</button>
|
||||
<div class="modal-body">
|
||||
<div class="modal-image" style="background:${getGradient(p.category_id)}">
|
||||
${productIconSVG(p.category_id)}
|
||||
</div>
|
||||
<div class="modal-details">
|
||||
<h2>${p.name}</h2>
|
||||
<div class="modal-price">
|
||||
<span class="price-current" style="color:var(--primary);font-size:32px;font-weight:800">$${p.price.toFixed(2)}</span>
|
||||
${p.original_price > p.price ? `<span class="price-original" style="font-size:16px;color:var(--text-muted);text-decoration:line-through">$${p.original_price.toFixed(2)}</span>` : ''}
|
||||
${discount > 0 ? `<span style="background:var(--primary);color:#fff;padding:2px 8px;border-radius:4px;font-size:12px;font-weight:700">省 ${discount}%</span>` : ''}
|
||||
</div>
|
||||
<div class="modal-tags">${tags.map(t => `<span class="product-tag ${t.includes('热') ? 'tag-hot' : t.includes('新') ? 'tag-new' : 'tag-sale'}">${t}</span>`).join('')}</div>
|
||||
<p class="modal-desc">${p.description}</p>
|
||||
<div class="modal-meta">
|
||||
<div class="modal-meta-item"><span>评分</span><span>${p.rating.toFixed(1)} ★</span></div>
|
||||
<div class="modal-meta-item"><span>销量</span><span>${formatSales(p.sales)}</span></div>
|
||||
<div class="modal-meta-item"><span>库存</span><span>${p.stock} 件</span></div>
|
||||
</div>
|
||||
<button class="modal-add-cart" onclick="addToCart(${p.id});closeModal()">加入购物车</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} catch(e) {
|
||||
content.innerHTML = '<div style="padding:60px;text-align:center;color:var(--text-muted)">商品信息加载失败</div>';
|
||||
}
|
||||
}
|
||||
|
||||
function closeModal(e) {
|
||||
if (e && e.target !== e.currentTarget) return;
|
||||
document.getElementById('productModal').classList.remove('open');
|
||||
}
|
||||
|
||||
// ========== 购物车 ==========
|
||||
let productCache = {};
|
||||
|
||||
async function addToCart(productId) {
|
||||
let product = productCache[productId];
|
||||
if (!product) {
|
||||
try {
|
||||
product = await fetchJSON(`/api/v1/shop/products/${productId}`);
|
||||
productCache[productId] = product;
|
||||
} catch(e) { showToast('添加失败'); return; }
|
||||
}
|
||||
|
||||
const existing = cart.find(i => i.id === productId);
|
||||
if (existing) {
|
||||
existing.qty++;
|
||||
} else {
|
||||
cart.push({ id: productId, name: product.name, price: product.price, qty: 1, category_id: product.category_id });
|
||||
}
|
||||
updateCartUI();
|
||||
showToast(`已加入购物车: ${product.name.substring(0, 15)}...`);
|
||||
}
|
||||
|
||||
function removeFromCart(productId) {
|
||||
cart = cart.filter(i => i.id !== productId);
|
||||
updateCartUI();
|
||||
}
|
||||
|
||||
function changeQty(productId, delta) {
|
||||
const item = cart.find(i => i.id === productId);
|
||||
if (!item) return;
|
||||
item.qty += delta;
|
||||
if (item.qty <= 0) removeFromCart(productId);
|
||||
else updateCartUI();
|
||||
}
|
||||
|
||||
function updateCartUI() {
|
||||
const count = cart.reduce((s, i) => s + i.qty, 0);
|
||||
const total = cart.reduce((s, i) => s + i.price * i.qty, 0);
|
||||
|
||||
const badge = document.getElementById('cartBadge');
|
||||
badge.textContent = count;
|
||||
badge.style.display = count > 0 ? 'flex' : 'none';
|
||||
document.getElementById('cartCount').textContent = count;
|
||||
document.getElementById('cartTotal').textContent = `$${total.toFixed(2)}`;
|
||||
document.getElementById('checkoutBtn').disabled = count === 0;
|
||||
|
||||
const container = document.getElementById('cartItems');
|
||||
if (cart.length === 0) {
|
||||
container.innerHTML = `
|
||||
<div class="cart-empty">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/><path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/></svg>
|
||||
<p>购物车是空的</p>
|
||||
<p style="font-size:13px;margin-top:8px">快去挑选心仪的商品吧</p>
|
||||
</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = cart.map(item => `
|
||||
<div class="cart-item">
|
||||
<div class="cart-item-img" style="background:${getGradient(item.category_id)}">${productIconSVG(item.category_id)}</div>
|
||||
<div class="cart-item-info">
|
||||
<div class="cart-item-name">${item.name}</div>
|
||||
<div class="cart-item-price">$${item.price.toFixed(2)}</div>
|
||||
<div class="cart-item-controls">
|
||||
<button class="qty-btn" onclick="changeQty(${item.id}, -1)">-</button>
|
||||
<span class="qty-value">${item.qty}</span>
|
||||
<button class="qty-btn" onclick="changeQty(${item.id}, 1)">+</button>
|
||||
<span class="cart-item-remove" onclick="removeFromCart(${item.id})">删除</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function toggleCart() {
|
||||
document.getElementById('cartOverlay').classList.toggle('open');
|
||||
document.getElementById('cartDrawer').classList.toggle('open');
|
||||
}
|
||||
|
||||
function checkout() {
|
||||
if (cart.length === 0) return;
|
||||
const total = cart.reduce((s, i) => s + i.price * i.qty, 0);
|
||||
showToast(`订单已提交! 合计 $${total.toFixed(2)}`);
|
||||
cart = [];
|
||||
updateCartUI();
|
||||
toggleCart();
|
||||
}
|
||||
|
||||
// ========== 工具函数 ==========
|
||||
function formatSales(n) {
|
||||
if (n >= 10000) return (n / 10000).toFixed(1) + '万';
|
||||
return n.toString();
|
||||
}
|
||||
|
||||
function showToast(msg) {
|
||||
const toast = document.getElementById('toast');
|
||||
document.getElementById('toastMsg').textContent = msg;
|
||||
toast.classList.add('show');
|
||||
clearTimeout(window._toastTimer);
|
||||
window._toastTimer = setTimeout(() => toast.classList.remove('show'), 2500);
|
||||
}
|
||||
|
||||
// ========== 初始化 ==========
|
||||
async function init() {
|
||||
await loadCategories();
|
||||
await loadProducts();
|
||||
updateCartUI();
|
||||
}
|
||||
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user