1080 lines
59 KiB
HTML
1080 lines
59 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||
<title>美味达 · 外卖平台</title>
|
||
<style>
|
||
/* ========== 主题变量 ========== */
|
||
:root{
|
||
--orange:#FF6B35;
|
||
--orange-dark:#F0521B;
|
||
--orange-light:#FF8455;
|
||
--yellow:#FFD23F;
|
||
--yellow-light:#FFE57A;
|
||
--orange-soft:#FFF1EA;
|
||
--bg:#F7F8FA;
|
||
--white:#FFFFFF;
|
||
--text:#1F2329;
|
||
--text-sub:#646A73;
|
||
--text-light:#8F959E;
|
||
--border:#EFF0F1;
|
||
--shadow-sm:0 2px 8px rgba(255,107,53,.06);
|
||
--shadow:0 6px 20px rgba(255,107,53,.10);
|
||
--shadow-lg:0 12px 40px rgba(255,107,53,.16);
|
||
--radius:16px;
|
||
--radius-sm:10px;
|
||
--radius-lg:24px;
|
||
--maxw:1200px;
|
||
--grad-orange:linear-gradient(135deg,#FF6B35 0%,#FF9A3D 100%);
|
||
--grad-yellow:linear-gradient(135deg,#FFD23F 0%,#FFB323 100%);
|
||
--grad-hero:linear-gradient(135deg,#FF6B35 0%,#FF9A3D 45%,#FFD23F 100%);
|
||
--grad-cart:linear-gradient(135deg,#FF6B35 0%,#FF8455 100%);
|
||
}
|
||
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
|
||
html,body{font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei","Helvetica Neue",Arial,sans-serif;background:var(--bg);color:var(--text);font-size:14px;line-height:1.6;-webkit-font-smoothing:antialiased}
|
||
img{display:block;max-width:100%}
|
||
button{font-family:inherit;cursor:pointer;border:none;background:none;outline:none}
|
||
input{font-family:inherit;outline:none;border:none}
|
||
a{color:inherit;text-decoration:none}
|
||
ul{list-style:none}
|
||
|
||
/* ========== 通用 ========== */
|
||
.container{max-width:var(--maxw);margin:0 auto;padding:0 20px}
|
||
.hidden{display:none!important}
|
||
::-webkit-scrollbar{width:6px;height:6px}
|
||
::-webkit-scrollbar-thumb{background:#ddd;border-radius:3px}
|
||
::-webkit-scrollbar-thumb:hover{background:#ccc}
|
||
|
||
/* ========== 顶部导航栏 ========== */
|
||
.navbar{position:sticky;top:0;z-index:100;background:var(--white);box-shadow:0 1px 0 rgba(0,0,0,.04)}
|
||
.navbar-inner{display:flex;align-items:center;gap:20px;height:64px}
|
||
.logo{display:flex;align-items:center;gap:8px;flex-shrink:0;cursor:pointer}
|
||
.logo-icon{width:38px;height:38px;border-radius:11px;background:var(--grad-orange);display:flex;align-items:center;justify-content:center;box-shadow:0 4px 10px rgba(255,107,53,.35)}
|
||
.logo-icon svg{width:22px;height:22px;fill:#fff}
|
||
.logo-text{font-size:20px;font-weight:800;color:var(--orange);letter-spacing:.5px}
|
||
.nav-search{flex:1;max-width:480px;position:relative}
|
||
.nav-search input{width:100%;height:40px;padding:0 44px 0 18px;border-radius:20px;background:var(--orange-soft);font-size:14px;color:var(--text);transition:.2s}
|
||
.nav-search input::placeholder{color:#C9A99A}
|
||
.nav-search input:focus{background:#FFE9DF;box-shadow:0 0 0 3px rgba(255,107,53,.12)}
|
||
.nav-search .s-icon{position:absolute;right:14px;top:50%;transform:translateY(-50%);width:18px;height:18px;color:var(--orange);pointer-events:none}
|
||
.nav-right{display:flex;align-items:center;gap:18px;margin-left:auto}
|
||
.nav-loc{display:flex;align-items:center;gap:5px;color:var(--text-sub);font-size:13px;font-weight:600}
|
||
.nav-loc svg{width:15px;height:15px;color:var(--orange);fill:var(--orange)}
|
||
.nav-cart{position:relative;display:flex;align-items:center;justify-content:center;width:42px;height:42px;border-radius:12px;background:var(--orange-soft);cursor:pointer;transition:.2s}
|
||
.nav-cart:hover{background:#FFE0D2}
|
||
.nav-cart svg{width:22px;height:22px;color:var(--orange)}
|
||
.cart-badge{position:absolute;top:-4px;right:-4px;min-width:18px;height:18px;padding:0 5px;border-radius:9px;background:var(--orange);color:#fff;font-size:11px;font-weight:700;display:flex;align-items:center;justify-content:center;border:2px solid #fff;line-height:1}
|
||
.cart-badge.show{display:flex}
|
||
.cart-badge:not(.show){display:none}
|
||
|
||
/* ========== Hero Banner ========== */
|
||
.hero{background:var(--grad-hero);position:relative;overflow:hidden;padding:48px 0 56px}
|
||
.hero::before{content:"";position:absolute;top:-60px;right:-40px;width:280px;height:280px;border-radius:50%;background:rgba(255,255,255,.12)}
|
||
.hero::after{content:"";position:absolute;bottom:-80px;left:-50px;width:220px;height:220px;border-radius:50%;background:rgba(255,255,255,.10)}
|
||
.hero-inner{position:relative;z-index:2;text-align:center}
|
||
.hero h1{color:#fff;font-size:38px;font-weight:800;letter-spacing:1px;text-shadow:0 2px 8px rgba(0,0,0,.12);margin-bottom:8px}
|
||
.hero p{color:rgba(255,255,255,.92);font-size:16px;margin-bottom:24px;font-weight:500}
|
||
.hero-search{max-width:560px;margin:0 auto;display:flex;background:#fff;border-radius:28px;padding:6px;box-shadow:0 10px 30px rgba(0,0,0,.15)}
|
||
.hero-search input{flex:1;height:44px;padding:0 20px;font-size:15px;color:var(--text)}
|
||
.hero-search input::placeholder{color:#bbb}
|
||
.hero-search button{height:44px;padding:0 30px;border-radius:24px;background:var(--grad-orange);color:#fff;font-size:15px;font-weight:700;transition:.2s;box-shadow:0 4px 12px rgba(255,107,53,.4)}
|
||
.hero-search button:hover{transform:translateY(-1px);box-shadow:0 6px 16px rgba(255,107,53,.5)}
|
||
.hero-tags{display:flex;justify-content:center;gap:10px;margin-top:18px;flex-wrap:wrap}
|
||
.hero-tag{padding:5px 14px;border-radius:14px;background:rgba(255,255,255,.22);color:#fff;font-size:12px;font-weight:600;backdrop-filter:blur(4px);cursor:pointer;transition:.2s}
|
||
.hero-tag:hover{background:rgba(255,255,255,.35)}
|
||
|
||
/* ========== 分类网格 ========== */
|
||
.section{padding:36px 0}
|
||
.section-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:20px}
|
||
.section-title{font-size:22px;font-weight:800;color:var(--text);display:flex;align-items:center;gap:8px}
|
||
.section-title::before{content:"";width:5px;height:22px;border-radius:3px;background:var(--grad-orange)}
|
||
.section-sub{font-size:13px;color:var(--text-light)}
|
||
.cat-grid{display:grid;grid-template-columns:repeat(6,1fr);gap:16px}
|
||
.cat-item{display:flex;flex-direction:column;align-items:center;gap:10px;padding:18px 8px;background:#fff;border-radius:var(--radius);cursor:pointer;transition:.25s;box-shadow:var(--shadow-sm);border:2px solid transparent}
|
||
.cat-item:hover{transform:translateY(-4px);box-shadow:var(--shadow);border-color:var(--orange-soft)}
|
||
.cat-item.active{border-color:var(--orange);background:var(--orange-soft)}
|
||
.cat-icon{width:56px;height:56px;border-radius:50%;display:flex;align-items:center;justify-content:center;box-shadow:0 6px 14px rgba(0,0,0,.12);transition:.25s}
|
||
.cat-item:hover .cat-icon{transform:scale(1.08) rotate(-5deg)}
|
||
.cat-icon svg{width:30px;height:30px;fill:#fff}
|
||
.cat-name{font-size:14px;font-weight:700;color:var(--text)}
|
||
.cat-count{font-size:11px;color:var(--text-light)}
|
||
|
||
/* ========== 推荐餐厅横向滚动 ========== */
|
||
.featured-scroll{display:flex;gap:16px;overflow-x:auto;padding-bottom:8px;scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch}
|
||
.featured-scroll::-webkit-scrollbar{height:4px}
|
||
.feature-card{min-width:280px;max-width:280px;background:#fff;border-radius:var(--radius);overflow:hidden;box-shadow:var(--shadow-sm);cursor:pointer;transition:.25s;scroll-snap-align:start;display:flex;flex-direction:column}
|
||
.feature-card:hover{transform:translateY(-4px);box-shadow:var(--shadow-lg)}
|
||
.feature-cover{height:130px;position:relative;display:flex;align-items:center;justify-content:center}
|
||
.feature-cover svg{width:54px;height:54px;fill:rgba(255,255,255,.9);filter:drop-shadow(0 2px 6px rgba(0,0,0,.15))}
|
||
.feature-rank{position:absolute;top:10px;left:10px;width:26px;height:26px;border-radius:8px;background:rgba(0,0,0,.45);color:#fff;font-size:12px;font-weight:800;display:flex;align-items:center;justify-content:center;backdrop-filter:blur(4px)}
|
||
.feature-rank.top1{background:var(--grad-yellow);color:#8B5A00}
|
||
.feature-rank.top2{background:linear-gradient(135deg,#C0C0C0,#E8E8E8);color:#555}
|
||
.feature-rank.top3{background:linear-gradient(135deg,#CD7F32,#E8A87C);color:#fff}
|
||
.feature-body{padding:14px;flex:1;display:flex;flex-direction:column}
|
||
.feature-name{font-size:15px;font-weight:700;color:var(--text);margin-bottom:4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||
.feature-tags{display:flex;gap:5px;flex-wrap:wrap;margin-bottom:8px;min-height:20px}
|
||
.feature-tag{font-size:10px;padding:2px 7px;border-radius:6px;background:var(--orange-soft);color:var(--orange);font-weight:600}
|
||
.feature-meta{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--text-sub);margin-top:auto}
|
||
.feature-meta .star{color:var(--yellow);font-weight:700}
|
||
.feature-meta .dot{color:#ddd}
|
||
|
||
/* ========== 餐厅列表 ========== */
|
||
.list-toolbar{display:flex;align-items:center;justify-content:space-between;margin-bottom:18px;flex-wrap:wrap;gap:12px}
|
||
.sort-tabs{display:flex;gap:6px;background:#fff;padding:5px;border-radius:12px;box-shadow:var(--shadow-sm)}
|
||
.sort-tab{padding:7px 16px;border-radius:8px;font-size:13px;font-weight:600;color:var(--text-sub);transition:.2s;white-space:nowrap}
|
||
.sort-tab.active{background:var(--grad-orange);color:#fff;box-shadow:0 3px 8px rgba(255,107,53,.35)}
|
||
.sort-tab:not(.active):hover{background:var(--orange-soft);color:var(--orange)}
|
||
.result-info{font-size:13px;color:var(--text-light)}
|
||
.result-info b{color:var(--orange)}
|
||
.rest-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:16px}
|
||
.rest-card{display:flex;gap:14px;background:#fff;border-radius:var(--radius);padding:14px;box-shadow:var(--shadow-sm);cursor:pointer;transition:.25s;position:relative;overflow:hidden}
|
||
.rest-card::before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--grad-orange);transform:scaleY(0);transform-origin:top;transition:.25s}
|
||
.rest-card:hover{transform:translateY(-3px);box-shadow:var(--shadow-lg)}
|
||
.rest-card:hover::before{transform:scaleY(1)}
|
||
.rest-logo{width:92px;height:92px;border-radius:14px;flex-shrink:0;display:flex;align-items:center;justify-content:center;position:relative;overflow:hidden}
|
||
.rest-logo svg{width:44px;height:44px;fill:rgba(255,255,255,.92);filter:drop-shadow(0 2px 4px rgba(0,0,0,.15))}
|
||
.rest-logo .status-badge{position:absolute;bottom:0;left:0;right:0;text-align:center;font-size:10px;color:#fff;padding:2px;background:rgba(0,0,0,.35);font-weight:600}
|
||
.rest-logo .status-badge.closed{background:rgba(120,120,120,.7)}
|
||
.rest-info{flex:1;min-width:0;display:flex;flex-direction:column}
|
||
.rest-name{font-size:16px;font-weight:700;color:var(--text);margin-bottom:4px;display:flex;align-items:center;gap:6px}
|
||
.rest-name .status-dot{width:7px;height:7px;border-radius:50%;background:#34C759;flex-shrink:0}
|
||
.rest-name .status-dot.closed{background:#999}
|
||
.rest-tags{display:flex;gap:5px;flex-wrap:wrap;margin-bottom:6px}
|
||
.rest-tag{font-size:10px;padding:2px 7px;border-radius:6px;background:var(--orange-soft);color:var(--orange);font-weight:600}
|
||
.rest-rate{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--text-sub);margin-bottom:6px}
|
||
.rest-rate .star{color:var(--yellow);font-weight:700;font-size:13px}
|
||
.rest-rate .sales{color:var(--text-light)}
|
||
.rest-meta{display:flex;align-items:center;gap:0;font-size:11px;color:var(--text-sub);margin-top:auto;flex-wrap:wrap}
|
||
.rest-meta span{display:flex;align-items:center;gap:3px;padding-right:8px;margin-right:8px;border-right:1px solid var(--border)}
|
||
.rest-meta span:last-child{border-right:none}
|
||
.rest-meta .free{color:var(--orange);font-weight:700}
|
||
|
||
/* ========== 骨架屏 ========== */
|
||
.skeleton-card{display:flex;gap:14px;background:#fff;border-radius:var(--radius);padding:14px;box-shadow:var(--shadow-sm)}
|
||
.sk-block{background:linear-gradient(90deg,#f0f0f0 25%,#e6e6e6 37%,#f0f0f0 63%);background-size:400% 100%;animation:sk 1.4s ease infinite;border-radius:8px}
|
||
@keyframes sk{0%{background-position:100% 50%}100%{background-position:0 50%}}
|
||
.sk-logo{width:92px;height:92px;border-radius:14px;flex-shrink:0}
|
||
.sk-info{flex:1;display:flex;flex-direction:column;gap:8px;padding-top:4px}
|
||
.sk-line{height:14px;border-radius:7px}
|
||
.sk-line.w60{width:60%}
|
||
.sk-line.w40{width:40%}
|
||
.sk-line.w80{width:80%}
|
||
.sk-cat{width:56px;height:56px;border-radius:50%}
|
||
.cat-grid.skeleton{display:grid;grid-template-columns:repeat(6,1fr);gap:16px}
|
||
.cat-sk{display:flex;flex-direction:column;align-items:center;gap:10px;padding:18px 8px;background:#fff;border-radius:var(--radius);box-shadow:var(--shadow-sm)}
|
||
.featured-sk{display:flex;gap:16px;overflow:hidden}
|
||
.featured-sk .sk-block{min-width:280px;height:230px;border-radius:var(--radius)}
|
||
|
||
/* ========== 餐厅详情 ========== */
|
||
.detail-overlay{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:200;opacity:0;pointer-events:none;transition:.3s;backdrop-filter:blur(2px)}
|
||
.detail-overlay.show{opacity:1;pointer-events:auto}
|
||
.detail-panel{position:fixed;top:0;right:0;bottom:0;width:100%;max-width:960px;background:var(--bg);z-index:201;transform:translateX(100%);transition:.35s cubic-bezier(.4,0,.2,1);display:flex;flex-direction:column;box-shadow:-8px 0 40px rgba(0,0,0,.2)}
|
||
.detail-panel.show{transform:translateX(0)}
|
||
.detail-header{position:relative;height:200px;display:flex;align-items:flex-end;padding:20px;color:#fff;overflow:hidden;flex-shrink:0}
|
||
.detail-header-bg{position:absolute;inset:0;z-index:0}
|
||
.detail-header-bg::after{content:"";position:absolute;inset:0;background:linear-gradient(180deg,rgba(0,0,0,.1) 0%,rgba(0,0,0,.45) 100%)}
|
||
.detail-header-content{position:relative;z-index:2;width:100%}
|
||
.detail-close{position:absolute;top:16px;right:16px;z-index:5;width:36px;height:36px;border-radius:50%;background:rgba(0,0,0,.35);color:#fff;display:flex;align-items:center;justify-content:center;backdrop-filter:blur(6px);transition:.2s}
|
||
.detail-close:hover{background:rgba(0,0,0,.55);transform:rotate(90deg)}
|
||
.detail-close svg{width:18px;height:18px}
|
||
.detail-name{font-size:24px;font-weight:800;margin-bottom:6px;text-shadow:0 2px 6px rgba(0,0,0,.3)}
|
||
.detail-tags{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:6px}
|
||
.detail-tag{font-size:11px;padding:3px 9px;border-radius:6px;background:rgba(255,255,255,.25);backdrop-filter:blur(4px);font-weight:600}
|
||
.detail-notice{font-size:12px;color:rgba(255,255,255,.9);display:flex;align-items:center;gap:5px;margin-top:4px}
|
||
.detail-notice svg{width:14px;height:14px;fill:#fff;flex-shrink:0}
|
||
.detail-info-row{display:flex;gap:0;background:#fff;padding:14px 20px;border-bottom:1px solid var(--border);flex-shrink:0}
|
||
.detail-info-item{flex:1;text-align:center;border-right:1px solid var(--border)}
|
||
.detail-info-item:last-child{border-right:none}
|
||
.detail-info-item .v{font-size:18px;font-weight:800;color:var(--orange)}
|
||
.detail-info-item .v.yellow{color:#F5A623}
|
||
.detail-info-item .l{font-size:11px;color:var(--text-light);margin-top:2px}
|
||
.detail-body{flex:1;display:flex;overflow:hidden;min-height:0}
|
||
.menu-sidebar{width:100px;background:#F2F3F5;overflow-y:auto;flex-shrink:0;-webkit-overflow-scrolling:touch}
|
||
.menu-cat-item{padding:16px 8px;text-align:center;font-size:13px;color:var(--text-sub);cursor:pointer;position:relative;transition:.2s;font-weight:600;line-height:1.4}
|
||
.menu-cat-item.active{background:#fff;color:var(--orange);font-weight:700}
|
||
.menu-cat-item.active::before{content:"";position:absolute;left:0;top:50%;transform:translateY(-50%);width:3px;height:18px;border-radius:0 2px 2px 0;background:var(--orange)}
|
||
.menu-cat-item:not(.active):hover{background:rgba(255,255,255,.4)}
|
||
.menu-list{flex:1;overflow-y:auto;padding:12px 16px 100px;-webkit-overflow-scrolling:touch}
|
||
.menu-section{margin-bottom:8px}
|
||
.menu-section-title{font-size:15px;font-weight:800;color:var(--text);padding:12px 4px 10px;position:sticky;top:0;background:var(--bg);z-index:2}
|
||
.menu-item{display:flex;gap:12px;background:#fff;border-radius:var(--radius);padding:12px;margin-bottom:10px;box-shadow:var(--shadow-sm);transition:.2s}
|
||
.menu-item:hover{box-shadow:var(--shadow)}
|
||
.menu-item-img{width:88px;height:88px;border-radius:12px;flex-shrink:0;display:flex;align-items:center;justify-content:center;position:relative;overflow:hidden}
|
||
.menu-item-img svg{width:40px;height:40px;fill:rgba(255,255,255,.92);filter:drop-shadow(0 2px 4px rgba(0,0,0,.15))}
|
||
.menu-item-img .discount{position:absolute;top:0;left:0;background:var(--orange);color:#fff;font-size:9px;padding:2px 6px;font-weight:700;border-radius:0 0 8px 0}
|
||
.menu-item-info{flex:1;min-width:0;display:flex;flex-direction:column}
|
||
.menu-item-name{font-size:15px;font-weight:700;color:var(--text);margin-bottom:3px}
|
||
.menu-item-desc{font-size:12px;color:var(--text-light);margin-bottom:5px;line-height:1.5;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
|
||
.menu-item-tags{display:flex;gap:5px;flex-wrap:wrap;margin-bottom:5px}
|
||
.menu-item-tag{font-size:10px;padding:1px 6px;border-radius:5px;background:var(--orange-soft);color:var(--orange);font-weight:600}
|
||
.menu-item-bottom{display:flex;align-items:center;justify-content:space-between;margin-top:auto}
|
||
.menu-item-meta{display:flex;flex-direction:column;gap:2px}
|
||
.menu-item-price{display:flex;align-items:baseline;gap:5px}
|
||
.menu-item-price .now{font-size:18px;font-weight:800;color:var(--orange)}
|
||
.menu-item-price .now small{font-size:12px}
|
||
.menu-item-price .old{font-size:12px;color:var(--text-light);text-decoration:line-through}
|
||
.menu-item-stat{font-size:11px;color:var(--text-light)}
|
||
.menu-item-stat .star{color:var(--yellow);font-weight:700}
|
||
.add-btn{width:32px;height:32px;border-radius:50%;background:var(--grad-orange);color:#fff;display:flex;align-items:center;justify-content:center;box-shadow:0 3px 8px rgba(255,107,53,.4);transition:.2s;flex-shrink:0}
|
||
.add-btn:hover{transform:scale(1.1) rotate(90deg)}
|
||
.add-btn svg{width:18px;height:18px;stroke:#fff;stroke-width:2.5;fill:none}
|
||
.qty-control{display:flex;align-items:center;gap:8px;flex-shrink:0}
|
||
.qty-control button{width:28px;height:28px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:16px;font-weight:700;transition:.2s}
|
||
.qty-control .minus{background:#fff;border:1.5px solid var(--orange);color:var(--orange)}
|
||
.qty-control .minus:hover{background:var(--orange-soft)}
|
||
.qty-control .plus{background:var(--grad-orange);color:#fff;box-shadow:0 2px 6px rgba(255,107,53,.4)}
|
||
.qty-control .num{font-size:15px;font-weight:700;color:var(--text);min-width:18px;text-align:center}
|
||
|
||
/* ========== 购物车浮层 ========== */
|
||
.cart-bar{position:fixed;left:0;right:0;bottom:0;z-index:150;pointer-events:none}
|
||
.cart-bar-inner{max-width:var(--maxw);margin:0 auto;padding:0 20px;pointer-events:none}
|
||
.cart-bar-box{background:#2A2A2E;border-radius:30px;display:flex;align-items:center;height:60px;box-shadow:0 -4px 24px rgba(0,0,0,.2);pointer-events:auto;overflow:hidden;transition:.3s;position:relative}
|
||
.cart-bar-box.empty{background:#3A3A3E}
|
||
.cart-icon-wrap{width:60px;height:60px;flex-shrink:0;display:flex;align-items:center;justify-content:center;position:relative;margin-left:8px}
|
||
.cart-icon-circle{width:48px;height:48px;border-radius:50%;background:#5A5A5E;display:flex;align-items:center;justify-content:center;transition:.25s;cursor:pointer}
|
||
.cart-icon-circle.active{background:var(--grad-orange);box-shadow:0 4px 14px rgba(255,107,53,.5)}
|
||
.cart-icon-circle svg{width:24px;height:24px;color:#fff}
|
||
.cart-icon-circle.active svg{animation:bounce .4s}
|
||
@keyframes bounce{0%,100%{transform:scale(1)}50%{transform:scale(1.25)}}
|
||
.cart-total{flex:1;color:#fff;padding-left:14px;display:flex;flex-direction:column;justify-content:center}
|
||
.cart-total.empty .t{color:#999}
|
||
.cart-total .t{font-size:17px;font-weight:800}
|
||
.cart-total .t small{font-size:12px;font-weight:500}
|
||
.cart-total .s{font-size:11px;color:#bbb}
|
||
.cart-checkout{height:60px;padding:0 28px;background:var(--grad-yellow);color:#8B5A00;font-size:16px;font-weight:800;display:flex;align-items:center;cursor:pointer;transition:.2s;flex-shrink:0}
|
||
.cart-checkout:hover{filter:brightness(1.05)}
|
||
.cart-checkout.disabled{background:#5A5A5E;color:#888;cursor:not-allowed}
|
||
.cart-bar .badge{position:absolute;top:2px;right:2px;min-width:20px;height:20px;padding:0 5px;border-radius:10px;background:var(--orange);color:#fff;font-size:11px;font-weight:700;display:flex;align-items:center;justify-content:center;border:2px solid #2A2A2E;line-height:1}
|
||
.cart-bar .badge:not(.show){display:none}
|
||
|
||
/* 购物车详情弹出 */
|
||
.cart-detail{position:fixed;left:0;right:0;bottom:60px;z-index:149;background:#fff;border-radius:20px 20px 0 0;box-shadow:0 -8px 30px rgba(0,0,0,.15);transform:translateY(120%);transition:.3s;max-height:60vh;display:flex;flex-direction:column}
|
||
.cart-detail.show{transform:translateY(0)}
|
||
.cart-detail-mask{position:fixed;inset:0;top:-60vh;background:rgba(0,0,0,.4);z-index:148;opacity:0;pointer-events:none;transition:.3s}
|
||
.cart-detail-mask.show{opacity:1;pointer-events:auto}
|
||
.cart-detail-head{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid var(--border);flex-shrink:0}
|
||
.cart-detail-title{font-size:15px;font-weight:700}
|
||
.cart-clear{font-size:13px;color:var(--text-light);display:flex;align-items:center;gap:4px;cursor:pointer}
|
||
.cart-clear svg{width:14px;height:14px}
|
||
.cart-detail-list{overflow-y:auto;padding:8px 20px 16px;flex:1}
|
||
.cart-detail-item{display:flex;align-items:center;gap:12px;padding:12px 0;border-bottom:1px solid var(--border)}
|
||
.cart-detail-item:last-child{border-bottom:none}
|
||
.cart-detail-item .name{flex:1;font-size:14px;font-weight:600}
|
||
.cart-detail-item .price{font-size:14px;font-weight:800;color:var(--orange);margin-right:8px}
|
||
.cart-detail-empty{text-align:center;padding:40px 0;color:var(--text-light)}
|
||
.cart-detail-empty svg{width:60px;height:60px;fill:#ddd;margin-bottom:10px}
|
||
|
||
/* ========== Toast ========== */
|
||
.toast-wrap{position:fixed;top:80px;left:50%;transform:translateX(-50%);z-index:999;display:flex;flex-direction:column;gap:8px;pointer-events:none}
|
||
.toast{background:rgba(0,0,0,.82);color:#fff;padding:10px 20px;border-radius:24px;font-size:14px;font-weight:500;box-shadow:0 6px 20px rgba(0,0,0,.25);display:flex;align-items:center;gap:8px;animation:toastIn .3s;backdrop-filter:blur(6px)}
|
||
.toast.out{animation:toastOut .3s forwards}
|
||
.toast svg{width:16px;height:16px}
|
||
.toast.success svg{fill:#4CAF50}
|
||
.toast.warn svg{fill:var(--yellow)}
|
||
@keyframes toastIn{from{opacity:0;transform:translateY(-12px)}to{opacity:1;transform:translateY(0)}}
|
||
@keyframes toastOut{to{opacity:0;transform:translateY(-12px)}}
|
||
|
||
/* ========== 页脚 ========== */
|
||
.footer{background:linear-gradient(135deg,#FF6B35 0%,#F0521B 100%);color:#fff;padding:40px 0 24px;margin-top:40px}
|
||
.footer-inner{text-align:center}
|
||
.footer-logo{font-size:22px;font-weight:800;margin-bottom:8px;display:inline-flex;align-items:center;gap:8px}
|
||
.footer-logo svg{width:24px;height:24px;fill:#fff}
|
||
.footer-links{display:flex;justify-content:center;gap:24px;margin:16px 0;font-size:13px;opacity:.92}
|
||
.footer-links a{cursor:pointer;transition:.2s}
|
||
.footer-links a:hover{opacity:.7;text-decoration:underline}
|
||
.footer-copy{font-size:12px;opacity:.75;border-top:1px solid rgba(255,255,255,.2);padding-top:16px;margin-top:8px}
|
||
|
||
/* ========== 空状态 ========== */
|
||
.empty-state{text-align:center;padding:60px 20px;color:var(--text-light)}
|
||
.empty-state svg{width:80px;height:80px;fill:#E0E0E0;margin-bottom:14px}
|
||
.empty-state p{font-size:15px;margin-bottom:4px}
|
||
.empty-state .sub{font-size:13px;color:var(--text-light)}
|
||
|
||
/* ========== 加载更多 ========== */
|
||
.load-more{text-align:center;padding:20px}
|
||
.load-more button{padding:10px 32px;border-radius:24px;background:#fff;color:var(--orange);font-size:14px;font-weight:600;box-shadow:var(--shadow-sm);transition:.2s;border:1px solid var(--orange-soft)}
|
||
.load-more button:hover{background:var(--orange-soft)}
|
||
.load-more button:disabled{color:var(--text-light);cursor:default}
|
||
|
||
/* ========== 响应式 ========== */
|
||
@media(max-width:900px){
|
||
.rest-grid{grid-template-columns:1fr}
|
||
.cat-grid{grid-template-columns:repeat(4,1fr)}
|
||
}
|
||
@media(max-width:768px){
|
||
.hero h1{font-size:28px}
|
||
.hero p{font-size:14px}
|
||
.navbar-inner{gap:10px}
|
||
.nav-loc{display:none}
|
||
.logo-text{font-size:18px}
|
||
.section{padding:28px 0}
|
||
.section-title{font-size:19px}
|
||
.container{padding:0 14px}
|
||
.detail-panel{max-width:100%}
|
||
.menu-sidebar{width:84px}
|
||
.menu-item-img{width:76px;height:76px}
|
||
.rest-logo{width:80px;height:80px}
|
||
.rest-logo svg{width:38px;height:38px}
|
||
}
|
||
@media(max-width:560px){
|
||
.cat-grid{grid-template-columns:repeat(3,1fr);gap:10px}
|
||
.hero{padding:32px 0 40px}
|
||
.hero-search button{padding:0 20px;font-size:14px}
|
||
.feature-card{min-width:240px;max-width:240px}
|
||
.rest-card{padding:12px;gap:10px}
|
||
.rest-name{font-size:15px}
|
||
.detail-header{height:170px}
|
||
.detail-name{font-size:20px}
|
||
.nav-search{max-width:none}
|
||
.sort-tabs{overflow-x:auto;-webkit-overflow-scrolling:touch}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- ========== 顶部导航栏 ========== -->
|
||
<header class="navbar">
|
||
<div class="container navbar-inner">
|
||
<div class="logo" onclick="goHome()">
|
||
<div class="logo-icon">
|
||
<svg viewBox="0 0 24 24"><path d="M11 9H9V2H7v7H5V2H3v7c0 2.12 1.66 3.84 3.75 3.97V22h2.5v-9.03C11.34 12.84 13 11.12 13 9V2h-2v7zm5-3v8h2.5v8H21V2c-2.76 0-5 2.24-5 4z"/></svg>
|
||
</div>
|
||
<span class="logo-text">美味达</span>
|
||
</div>
|
||
<div class="nav-search">
|
||
<input id="navSearchInput" type="text" placeholder="搜索餐厅或美食" onkeydown="if(event.key==='Enter')doSearch(this.value)">
|
||
<svg class="s-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
|
||
</div>
|
||
<div class="nav-right">
|
||
<div class="nav-loc">
|
||
<svg viewBox="0 0 24 24"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5a2.5 2.5 0 0 1 0-5 2.5 2.5 0 0 1 0 5z"/></svg>
|
||
<span>当前位置</span>
|
||
</div>
|
||
<div class="nav-cart" onclick="scrollToCart()">
|
||
<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 id="navCartBadge" class="cart-badge">0</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- ========== Hero Banner ========== -->
|
||
<section class="hero">
|
||
<div class="container hero-inner">
|
||
<h1>美味触手可及</h1>
|
||
<p>周边好店 · 极速配送 · 优惠不停</p>
|
||
<div class="hero-search">
|
||
<input id="heroSearchInput" type="text" placeholder="想吃什么?搜搜看吧" onkeydown="if(event.key==='Enter')doSearch(this.value)">
|
||
<button onclick="doSearch(document.getElementById('heroSearchInput').value)">搜索</button>
|
||
</div>
|
||
<div class="hero-tags" id="heroTags"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ========== 分类网格 ========== -->
|
||
<section class="section">
|
||
<div class="container">
|
||
<div class="section-head">
|
||
<div class="section-title">餐厅分类</div>
|
||
<div class="section-sub">选择分类,发现好店</div>
|
||
</div>
|
||
<div id="catGrid" class="cat-grid">
|
||
<div class="cat-grid skeleton">
|
||
<div class="cat-sk" style="display:flex;flex-direction:column;align-items:center;gap:10px;padding:18px 8px;background:#fff;border-radius:16px;box-shadow:0 2px 8px rgba(255,107,53,.06)"><div class="sk-block sk-cat"></div><div class="sk-block" style="width:40px;height:12px"></div></div>
|
||
<div class="cat-sk" style="display:flex;flex-direction:column;align-items:center;gap:10px;padding:18px 8px;background:#fff;border-radius:16px;box-shadow:0 2px 8px rgba(255,107,53,.06)"><div class="sk-block sk-cat"></div><div class="sk-block" style="width:40px;height:12px"></div></div>
|
||
<div class="cat-sk" style="display:flex;flex-direction:column;align-items:center;gap:10px;padding:18px 8px;background:#fff;border-radius:16px;box-shadow:0 2px 8px rgba(255,107,53,.06)"><div class="sk-block sk-cat"></div><div class="sk-block" style="width:40px;height:12px"></div></div>
|
||
<div class="cat-sk" style="display:flex;flex-direction:column;align-items:center;gap:10px;padding:18px 8px;background:#fff;border-radius:16px;box-shadow:0 2px 8px rgba(255,107,53,.06)"><div class="sk-block sk-cat"></div><div class="sk-block" style="width:40px;height:12px"></div></div>
|
||
<div class="cat-sk" style="display:flex;flex-direction:column;align-items:center;gap:10px;padding:18px 8px;background:#fff;border-radius:16px;box-shadow:0 2px 8px rgba(255,107,53,.06)"><div class="sk-block sk-cat"></div><div class="sk-block" style="width:40px;height:12px"></div></div>
|
||
<div class="cat-sk" style="display:flex;flex-direction:column;align-items:center;gap:10px;padding:18px 8px;background:#fff;border-radius:16px;box-shadow:0 2px 8px rgba(255,107,53,.06)"><div class="sk-block sk-cat"></div><div class="sk-block" style="width:40px;height:12px"></div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ========== 推荐餐厅 ========== -->
|
||
<section class="section" style="padding-top:0">
|
||
<div class="container">
|
||
<div class="section-head">
|
||
<div class="section-title">为你推荐</div>
|
||
<div class="section-sub">人气好店,不容错过</div>
|
||
</div>
|
||
<div id="featuredScroll" class="featured-scroll">
|
||
<div class="featured-sk">
|
||
<div class="sk-block"></div><div class="sk-block"></div><div class="sk-block"></div><div class="sk-block"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ========== 餐厅列表 ========== -->
|
||
<section class="section" style="padding-top:0" id="restSection">
|
||
<div class="container">
|
||
<div class="section-head">
|
||
<div class="section-title" id="listTitle">全部餐厅</div>
|
||
</div>
|
||
<div class="list-toolbar">
|
||
<div class="sort-tabs" id="sortTabs">
|
||
<div class="sort-tab active" data-sort="">默认排序</div>
|
||
<div class="sort-tab" data-sort="rating">评分最高</div>
|
||
<div class="sort-tab" data-sort="sales">销量最高</div>
|
||
<div class="sort-tab" data-sort="delivery_time">配送最快</div>
|
||
</div>
|
||
<div class="result-info">共 <b id="resultTotal">0</b> 家餐厅</div>
|
||
</div>
|
||
<div id="restGrid" class="rest-grid"></div>
|
||
<div id="loadMore" class="load-more hidden">
|
||
<button id="loadMoreBtn" onclick="loadMoreRestaurants()">加载更多</button>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ========== 餐厅详情 ========== -->
|
||
<div class="detail-overlay" id="detailOverlay" onclick="closeDetail()"></div>
|
||
<div class="detail-panel" id="detailPanel">
|
||
<div class="detail-header" id="detailHeader">
|
||
<div class="detail-header-bg" id="detailHeaderBg"></div>
|
||
<button class="detail-close" onclick="closeDetail()">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M18 6 6 18M6 6l12 12"/></svg>
|
||
</button>
|
||
<div class="detail-header-content" id="detailHeaderContent"></div>
|
||
</div>
|
||
<div class="detail-info-row" id="detailInfoRow"></div>
|
||
<div class="detail-body" id="detailBody">
|
||
<div class="menu-sidebar" id="menuSidebar"></div>
|
||
<div class="menu-list" id="menuList"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ========== 购物车详情 ========== -->
|
||
<div class="cart-detail-mask" id="cartDetailMask" onclick="toggleCartDetail(false)"></div>
|
||
<div class="cart-detail" id="cartDetail">
|
||
<div class="cart-detail-head">
|
||
<span class="cart-detail-title" id="cartDetailTitle">购物车</span>
|
||
<span class="cart-clear" onclick="clearCart()">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
|
||
清空
|
||
</span>
|
||
</div>
|
||
<div class="cart-detail-list" id="cartDetailList"></div>
|
||
</div>
|
||
|
||
<!-- ========== 购物车浮层 ========== -->
|
||
<div class="cart-bar" id="cartBar" style="display:none">
|
||
<div class="cart-bar-inner">
|
||
<div class="cart-bar-box empty" id="cartBarBox">
|
||
<div class="cart-icon-wrap">
|
||
<div class="cart-icon-circle" id="cartIconCircle" onclick="toggleCartDetail()">
|
||
<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>
|
||
</div>
|
||
<span class="badge" id="cartBadge">0</span>
|
||
</div>
|
||
<div class="cart-total empty" id="cartTotal">
|
||
<span class="t">¥0</span>
|
||
<span class="s" id="cartSub">配送费¥0 · 另需约¥0</span>
|
||
</div>
|
||
<div class="cart-checkout disabled" id="checkoutBtn" onclick="checkout()">去结算</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ========== Toast ========== -->
|
||
<div class="toast-wrap" id="toastWrap"></div>
|
||
|
||
<!-- ========== 页脚 ========== -->
|
||
<footer class="footer">
|
||
<div class="container footer-inner">
|
||
<div class="footer-logo">
|
||
<svg viewBox="0 0 24 24"><path d="M11 9H9V2H7v7H5V2H3v7c0 2.12 1.66 3.84 3.75 3.97V22h2.5v-9.03C11.34 12.84 13 11.12 13 9V2h-2v7zm5-3v8h2.5v8H21V2c-2.76 0-5 2.24-5 4z"/></svg>
|
||
美味达
|
||
</div>
|
||
<div class="footer-links">
|
||
<a onclick="goHome()">首页</a>
|
||
<a onclick="toast('订单功能演示中','warn')">我的订单</a>
|
||
<a onclick="toast('商家入驻功能演示中','warn')">商家入驻</a>
|
||
<a onclick="toast('客服中心演示中','warn')">联系客服</a>
|
||
</div>
|
||
<div class="footer-copy">© 2026 美味达外卖平台 · 美味触手可及 · 本页面为演示模板</div>
|
||
</div>
|
||
</footer>
|
||
|
||
<script>
|
||
/* ==========================================================
|
||
数据与状态
|
||
========================================================== */
|
||
const API = '/api/v1/food';
|
||
const state = {
|
||
categories: [],
|
||
featured: [],
|
||
restaurants: [],
|
||
total: 0,
|
||
page: 1,
|
||
pageSize: 12,
|
||
currentCategory: 0, // 0 = 全部
|
||
keyword: '',
|
||
sort: '',
|
||
loading: false,
|
||
// 详情
|
||
currentRestaurant: null,
|
||
menuData: null,
|
||
// 购物车: { restaurantId, restaurantName, deliveryFee, minOrder, items: {itemId: {item, qty}} }
|
||
cart: null,
|
||
};
|
||
|
||
/* 分类配色与图标配置(icon 字段映射) */
|
||
const CAT_STYLES = {
|
||
chinese: {grad:'linear-gradient(135deg,#FF6B35,#FF9A3D)', svg:'<path d="M3 3v18h18V3H3zm9 14a4 4 0 1 1 0-8 4 4 0 0 1 0 8z"/>'},
|
||
japanese: {grad:'linear-gradient(135deg,#FF8FA3,#FF6B9D)', svg:'<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/><path d="M12 6v12"/>'},
|
||
western: {grad:'linear-gradient(135deg,#FFD23F,#FFB323)', svg:'<path d="M11 9H9V2H7v7H5V2H3v7c0 2.12 1.66 3.84 3.75 3.97V22h2.5v-9.03C11.34 12.84 13 11.12 13 9V2h-2v7z"/>'},
|
||
hotpot: {grad:'linear-gradient(135deg,#FF4757,#FF6B35)', svg:'<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z"/>'},
|
||
dessert: {grad:'linear-gradient(135deg,#FF6B9D,#FFD23F)', svg:'<path d="M12 3 10.7 8.3 5.3 9.6l4 3.9-.9 5.4 4.6-2.6 4.6 2.6-.9-5.4 4-3.9-5.4-1.3z"/>'},
|
||
healthy: {grad:'linear-gradient(135deg,#5BB318,#7CC576)', svg:'<path d="M17 8C8 10 5.9 16.17 3.82 21.34l1.89.66.95-2.3c.48.17.98.3 1.34.3C19 20 22 3 22 3c-1 2-8 2.25-13 3.25S2 11.5 2 13.5s1.75 3.75 1.75 3.75C4 8 17 8 17 8z"/>'},
|
||
};
|
||
const DEFAULT_CAT_STYLE = {grad:'linear-gradient(135deg,#FF6B35,#FFD23F)', svg:'<circle cx="12" cy="12" r="9"/>'};
|
||
|
||
/* 餐厅 logo 渐变色组(按 id 取模分配) */
|
||
const REST_GRADIENTS = [
|
||
'linear-gradient(135deg,#FF6B35,#FF9A3D)',
|
||
'linear-gradient(135deg,#FF4757,#FF6B35)',
|
||
'linear-gradient(135deg,#FFD23F,#FF9A3D)',
|
||
'linear-gradient(135deg,#FF8FA3,#FF6B9D)',
|
||
'linear-gradient(135deg,#5BB318,#7CC576)',
|
||
'linear-gradient(135deg,#FF9A3D,#FFD23F)',
|
||
'linear-gradient(135deg,#FF6B9D,#FFD23F)',
|
||
'linear-gradient(135deg,#F0521B,#FF8455)',
|
||
];
|
||
const REST_SVGS = [
|
||
'<path d="M3 3v18h18V3H3zm9 14a4 4 0 1 1 0-8 4 4 0 0 1 0 8z"/>',
|
||
'<path d="M11 9H9V2H7v7H5V2H3v7c0 2.12 1.66 3.84 3.75 3.97V22h2.5v-9.03C11.34 12.84 13 11.12 13 9V2h-2v7zm5-3v8h2.5v8H21V2c-2.76 0-5 2.24-5 4z"/>',
|
||
'<path d="M12 3 10.7 8.3 5.3 9.6l4 3.9-.9 5.4 4.6-2.6 4.6 2.6-.9-5.4 4-3.9-5.4-1.3z"/>',
|
||
'<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5a2.5 2.5 0 0 1 0-5 2.5 2.5 0 0 1 0 5z"/>',
|
||
'<path d="M17 8C8 10 5.9 16.17 3.82 21.34l1.89.66.95-2.3c.48.17.98.3 1.34.3C19 20 22 3 22 3c-1 2-8 2.25-13 3.25S2 11.5 2 13.5s1.75 3.75 1.75 3.75C4 8 17 8 17 8z"/>',
|
||
'<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/><path d="M12 6v12"/>',
|
||
'<path d="M21 11H3v8h18v-8zm-2-7H5v5h14V4z"/>',
|
||
'<path d="M20 8h-2.81a5.985 5.985 0 0 0-1.82-1.96L17 4.41 15.59 3l-2.2 2.2A5.99 5.99 0 0 0 12 5c-.47 0-.93.06-1.39.15L8.41 3 7 4.41l1.62 1.63A5.99 5.99 0 0 0 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8z"/>',
|
||
];
|
||
/* 菜品图片渐变色组 */
|
||
const ITEM_GRADIENTS = [
|
||
'linear-gradient(135deg,#FF6B35,#FF9A3D)',
|
||
'linear-gradient(135deg,#FFD23F,#FFB323)',
|
||
'linear-gradient(135deg,#FF8FA3,#FF6B9D)',
|
||
'linear-gradient(135deg,#5BB318,#7CC576)',
|
||
'linear-gradient(135deg,#FF4757,#FF6B35)',
|
||
'linear-gradient(135deg,#FF9A3D,#FFD23F)',
|
||
];
|
||
const ITEM_SVGS = [
|
||
'<path d="M3 3v18h18V3H3zm9 14a4 4 0 1 1 0-8 4 4 0 0 1 0 8z"/>',
|
||
'<path d="M11 9H9V2H7v7H5V2H3v7c0 2.12 1.66 3.84 3.75 3.97V22h2.5v-9.03C11.34 12.84 13 11.12 13 9V2h-2v7z"/>',
|
||
'<path d="M12 3 10.7 8.3 5.3 9.6l4 3.9-.9 5.4 4.6-2.6 4.6 2.6-.9-5.4 4-3.9-5.4-1.3z"/>',
|
||
'<path d="M17 8C8 10 5.9 16.17 3.82 21.34l1.89.66.95-2.3c.48.17.98.3 1.34.3C19 20 22 3 22 3c-1 2-8 2.25-13 3.25S2 11.5 2 13.5s1.75 3.75 1.75 3.75C4 8 17 8 17 8z"/>',
|
||
'<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5a2.5 2.5 0 0 1 0-5 2.5 2.5 0 0 1 0 5z"/>',
|
||
'<path d="M20 8h-2.81a5.985 5.985 0 0 0-1.82-1.96L17 4.41 15.59 3l-2.2 2.2A5.99 5.99 0 0 0 12 5c-.47 0-.93.06-1.39.15L8.41 3 7 4.41l1.62 1.63A5.99 5.99 0 0 0 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8z"/>',
|
||
];
|
||
|
||
/* ==========================================================
|
||
工具函数
|
||
========================================================== */
|
||
function $(id){return document.getElementById(id)}
|
||
function esc(s){return (s==null?'':String(s)).replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]))}
|
||
function parseTags(t){try{return JSON.parse(t||'[]')}catch(e){return []}}
|
||
function safeNum(n,d=0){n=Number(n);return isNaN(n)?d:n}
|
||
function money(n){return safeNum(n).toFixed(1).replace(/\.0$/,'')}
|
||
|
||
function catStyle(icon){return CAT_STYLES[icon]||DEFAULT_CAT_STYLE}
|
||
function restGrad(id){return REST_GRADIENTS[(id-1)%REST_GRADIENTS.length]}
|
||
function restSvg(id){return REST_SVGS[(id-1)%REST_SVGS.length]}
|
||
function itemGrad(id){return ITEM_GRADIENTS[(id-1)%ITEM_GRADIENTS.length]}
|
||
function itemSvg(id){return ITEM_SVGS[(id-1)%ITEM_SVGS.length]}
|
||
|
||
/* Toast */
|
||
function toast(msg,type){
|
||
const wrap=$('toastWrap');
|
||
const el=document.createElement('div');
|
||
el.className='toast '+(type||'');
|
||
const icons={success:'<svg viewBox="0 0 24 24"><path d="M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>',warn:'<svg viewBox="0 0 24 24"><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/></svg>'};
|
||
el.innerHTML=(icons[type]||'')+'<span>'+esc(msg)+'</span>';
|
||
wrap.appendChild(el);
|
||
setTimeout(()=>{el.classList.add('out');setTimeout(()=>el.remove(),300)},2000);
|
||
}
|
||
|
||
/* ==========================================================
|
||
API 请求
|
||
========================================================== */
|
||
async function api(path){
|
||
try{
|
||
const res=await fetch(API+path);
|
||
if(!res.ok) throw new Error('HTTP '+res.status);
|
||
const json=await res.json();
|
||
if(json.code!==0) throw new Error(json.message||'接口错误');
|
||
return json.data;
|
||
}catch(e){
|
||
console.error('API error:',path,e);
|
||
throw e;
|
||
}
|
||
}
|
||
|
||
/* ==========================================================
|
||
初始化
|
||
========================================================== */
|
||
async function init(){
|
||
try{
|
||
const home=await api('/home');
|
||
state.categories=home.categories||[];
|
||
state.featured=home.featured||[];
|
||
renderHeroTags();
|
||
renderCategories();
|
||
renderFeatured();
|
||
await loadRestaurants(true);
|
||
}catch(e){
|
||
toast('数据加载失败,请检查服务','warn');
|
||
$('catGrid').innerHTML='<div class="empty-state" style="grid-column:1/-1"><p>数据加载失败</p><p class="sub">请确保后端服务已启动</p></div>';
|
||
}
|
||
}
|
||
|
||
/* Hero 热门标签 */
|
||
function renderHeroTags(){
|
||
const hot=['麻辣','川菜','奶茶','汉堡','烤肉','沙拉'];
|
||
$('heroTags').innerHTML=hot.map(t=>`<span class="hero-tag" onclick="doSearch('${t}')">${t}</span>`).join('');
|
||
}
|
||
|
||
/* 分类网格 */
|
||
function renderCategories(){
|
||
const html=state.categories.map(c=>{
|
||
const s=catStyle(c.icon);
|
||
return `<div class="cat-item ${state.currentCategory===c.id?'active':''}" onclick="selectCategory(${c.id})">
|
||
<div class="cat-icon" style="background:${s.grad}"><svg viewBox="0 0 24 24">${s.svg}</svg></div>
|
||
<span class="cat-name">${esc(c.name)}</span>
|
||
</div>`;
|
||
}).join('');
|
||
$('catGrid').innerHTML=html;
|
||
}
|
||
|
||
/* 推荐餐厅横向滚动 */
|
||
function renderFeatured(){
|
||
if(!state.featured.length){$('featuredScroll').innerHTML='';return}
|
||
const html=state.featured.map((r,i)=>{
|
||
const tags=parseTags(r.tags).slice(0,2);
|
||
return `<div class="feature-card" onclick="openDetail(${r.id})">
|
||
<div class="feature-cover" style="background:${restGrad(r.id)}">
|
||
${r.cover?`<img src="${esc(r.cover)}" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover" onerror="this.style.display='none';this.parentNode.querySelector('svg').style.display=''">`:''}
|
||
<div class="feature-rank ${i<3?'top'+(i+1):''}">${i+1}</div>
|
||
<svg viewBox="0 0 24 24" style="${r.cover?'display:none':''}">${restSvg(r.id)}</svg>
|
||
</div>
|
||
<div class="feature-body">
|
||
<div class="feature-name">${esc(r.name)}</div>
|
||
<div class="feature-tags">${tags.map(t=>`<span class="feature-tag">${esc(t)}</span>`).join('')}</div>
|
||
<div class="feature-meta">
|
||
<span class="star">★ ${money(r.rating)}</span>
|
||
<span class="dot">·</span>
|
||
<span>月售${r.sales}</span>
|
||
<span class="dot">·</span>
|
||
<span>${r.delivery_time}分钟</span>
|
||
</div>
|
||
</div>
|
||
</div>`;
|
||
}).join('');
|
||
$('featuredScroll').innerHTML=html;
|
||
}
|
||
|
||
/* ==========================================================
|
||
餐厅列表
|
||
========================================================== */
|
||
function buildQuery(){
|
||
const p=new URLSearchParams();
|
||
if(state.currentCategory) p.set('category_id',state.currentCategory);
|
||
if(state.keyword) p.set('keyword',state.keyword);
|
||
if(state.sort) p.set('sort',state.sort);
|
||
p.set('page',state.page);
|
||
p.set('page_size',state.pageSize);
|
||
return p.toString();
|
||
}
|
||
|
||
async function loadRestaurants(reset){
|
||
if(state.loading) return;
|
||
state.loading=true;
|
||
if(reset){
|
||
state.page=1;
|
||
state.restaurants=[];
|
||
$('restGrid').innerHTML=renderSkeleton(4);
|
||
}
|
||
try{
|
||
const data=await api('/restaurants?'+buildQuery());
|
||
const list=data.list||[];
|
||
if(reset){state.restaurants=list}else{state.restaurants=state.restaurants.concat(list)}
|
||
state.total=safeNum(data.total,0);
|
||
renderRestaurantList();
|
||
}catch(e){
|
||
$('restGrid').innerHTML='<div class="empty-state" style="grid-column:1/-1"><p>加载餐厅失败</p><p class="sub">请稍后重试</p></div>';
|
||
}
|
||
state.loading=false;
|
||
}
|
||
|
||
function loadMoreRestaurants(){
|
||
state.page++;
|
||
loadRestaurants(false);
|
||
}
|
||
|
||
function renderSkeleton(n){
|
||
let html='';
|
||
for(let i=0;i<n;i++){
|
||
html+=`<div class="skeleton-card"><div class="sk-block sk-logo"></div><div class="sk-info"><div class="sk-block sk-line w60"></div><div class="sk-block sk-line w40"></div><div class="sk-block sk-line w80"></div><div class="sk-block sk-line w40"></div></div></div>`;
|
||
}
|
||
return html;
|
||
}
|
||
|
||
function renderRestaurantList(){
|
||
$('resultTotal').textContent=state.total;
|
||
const grid=$('restGrid');
|
||
if(!state.restaurants.length){
|
||
grid.innerHTML='<div class="empty-state" style="grid-column:1/-1"><svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 15.93A7.001 7.001 0 0 1 5 12c0-.39.04-.77.1-1.14L10 15.74v.19c0 .74.21 1.43.57 2.02.14-.02.28-.02.43-.02zm6.9-2.54c-.26-1.05-1.2-1.84-2.35-1.84-.16 0-.31.02-.46.05L12.5 11.5V9c1.1 0 2-.9 2-2V5c0-.55-.45-1-1-1h-1c-.55 0-1 .45-1 1v2c0 1.1.9 2 2 2v2.5l-3.5 1.05c-.6.18-1 .7-1 1.3v.39l-3.5 3.5C4.42 16.45 4 15.27 4 14c0-.29.02-.58.05-.86L8 9.19V9c0-1.1.9-2 2-2V5c0-.55-.45-1-1-1H8c-.55 0-1 .45-1 1v2c0 1.1.9 2 2 2v.19l-3.05 3.05C6.84 12.5 8.27 12 10 12c1.73 0 3.16.5 4.05 1.24L8 11.5V11c0-1.1-.9-2-2-2V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v2c0 1.1.9 2 2 2v.5L8 13.05V13c0-.6.4-1.12 1-1.3l3.5-1.05v-.15c0-.6.4-1.12 1-1.3L17 8.5V8c0-1.1.9-2 2-2V4c0-.55-.45-1-1-1h-1c-.55 0-1 .45-1 1z"/></svg><p>没有找到相关餐厅</p><p class="sub">换个关键词或分类试试</p></div>';
|
||
$('loadMore').classList.add('hidden');
|
||
return;
|
||
}
|
||
grid.innerHTML=state.restaurants.map(r=>{
|
||
const tags=parseTags(r.tags).slice(0,3);
|
||
const open=r.status===1;
|
||
return `<div class="rest-card" onclick="openDetail(${r.id})">
|
||
<div class="rest-logo" style="background:${restGrad(r.id)}">
|
||
${r.logo?`<img src="${esc(r.logo)}" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover" onerror="this.style.display='none';this.parentNode.querySelector('svg').style.display=''">`:''}
|
||
<svg viewBox="0 0 24 24" style="${r.logo?'display:none':''}">${restSvg(r.id)}</svg>
|
||
<div class="status-badge ${open?'':'closed'}">${open?'营业中':'休息中'}</div>
|
||
</div>
|
||
<div class="rest-info">
|
||
<div class="rest-name"><span class="status-dot ${open?'':'closed'}"></span>${esc(r.name)}</div>
|
||
<div class="rest-tags">${tags.map(t=>`<span class="rest-tag">${esc(t)}</span>`).join('')}</div>
|
||
<div class="rest-rate"><span class="star">★ ${money(r.rating)}</span><span class="sales">月售 ${r.sales}</span></div>
|
||
<div class="rest-meta">
|
||
<span>起送 ¥${money(r.min_order)}</span>
|
||
<span>${r.delivery_fee==0?'<span class="free">免配送费</span>':'配送 ¥'+money(r.delivery_fee)}</span>
|
||
<span>${r.delivery_time}分钟</span>
|
||
<span>${esc(r.distance)}</span>
|
||
</div>
|
||
</div>
|
||
</div>`;
|
||
}).join('');
|
||
// 加载更多
|
||
const hasMore=state.restaurants.length<state.total;
|
||
$('loadMore').classList.toggle('hidden',!hasMore);
|
||
}
|
||
|
||
/* 分类筛选 */
|
||
function selectCategory(id){
|
||
state.currentCategory = state.currentCategory===id ? 0 : id;
|
||
document.querySelectorAll('.cat-item').forEach((el,i)=>{
|
||
el.classList.toggle('active', state.categories[i]&&state.categories[i].id===state.currentCategory);
|
||
});
|
||
const cat=state.categories.find(c=>c.id===state.currentCategory);
|
||
$('listTitle').textContent = cat ? cat.name+'餐厅' : '全部餐厅';
|
||
loadRestaurants(true);
|
||
$('restSection').scrollIntoView({behavior:'smooth',block:'start'});
|
||
}
|
||
|
||
/* 排序 */
|
||
document.querySelectorAll('.sort-tab').forEach(tab=>{
|
||
tab.addEventListener('click',()=>{
|
||
document.querySelectorAll('.sort-tab').forEach(t=>t.classList.remove('active'));
|
||
tab.classList.add('active');
|
||
state.sort=tab.dataset.sort;
|
||
loadRestaurants(true);
|
||
});
|
||
});
|
||
|
||
/* 搜索 */
|
||
function doSearch(kw){
|
||
state.keyword=(kw||'').trim();
|
||
$('navSearchInput').value=state.keyword;
|
||
$('heroSearchInput').value=state.keyword;
|
||
loadRestaurants(true);
|
||
$('restSection').scrollIntoView({behavior:'smooth',block:'start'});
|
||
}
|
||
|
||
function goHome(){
|
||
state.currentCategory=0;
|
||
state.keyword='';
|
||
state.sort='';
|
||
$('navSearchInput').value='';
|
||
$('heroSearchInput').value='';
|
||
$('listTitle').textContent='全部餐厅';
|
||
document.querySelectorAll('.sort-tab').forEach((t,i)=>t.classList.toggle('active',i===0));
|
||
document.querySelectorAll('.cat-item').forEach(el=>el.classList.remove('active'));
|
||
loadRestaurants(true);
|
||
window.scrollTo({top:0,behavior:'smooth'});
|
||
}
|
||
|
||
/* ==========================================================
|
||
餐厅详情
|
||
========================================================== */
|
||
async function openDetail(id){
|
||
$('detailOverlay').classList.add('show');
|
||
$('detailPanel').classList.add('show');
|
||
document.body.style.overflow='hidden';
|
||
// loading 占位
|
||
$('detailHeaderBg').style.background=restGrad(id);
|
||
$('detailHeaderBg').innerHTML='';
|
||
$('detailHeaderContent').innerHTML='<div style="color:#fff;font-size:16px">加载中...</div>';
|
||
$('detailInfoRow').innerHTML='';
|
||
$('menuSidebar').innerHTML='<div style="padding:20px;color:#999;text-align:center">加载中</div>';
|
||
$('menuList').innerHTML='';
|
||
try{
|
||
const data=await api('/restaurants/'+id+'/menu');
|
||
state.currentRestaurant=data.restaurant||{};
|
||
state.menuData=data;
|
||
renderDetail();
|
||
}catch(e){
|
||
$('detailHeaderContent').innerHTML='<div style="color:#fff;font-size:16px">加载失败</div>';
|
||
toast('详情加载失败','warn');
|
||
}
|
||
}
|
||
|
||
function closeDetail(){
|
||
$('detailOverlay').classList.remove('show');
|
||
$('detailPanel').classList.remove('show');
|
||
document.body.style.overflow='';
|
||
}
|
||
|
||
function renderDetail(){
|
||
const r=state.currentRestaurant;
|
||
const tags=parseTags(r.tags);
|
||
$('detailHeaderBg').style.background=restGrad(r.id);
|
||
$('detailHeaderBg').innerHTML=r.cover?`<img src="${esc(r.cover)}" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover" onerror="this.style.display='none'">`:'';
|
||
$('detailHeaderContent').innerHTML=`
|
||
<div class="detail-name">${esc(r.name)}</div>
|
||
<div class="detail-tags">${tags.map(t=>`<span class="detail-tag">${esc(t)}</span>`).join('')}<span class="detail-tag">${esc(r.category_name)}</span></div>
|
||
${r.notice?`<div class="detail-notice"><svg viewBox="0 0 24 24"><path d="M18 11v2h4v-2h-4zm-2 6.59 3.29-3.29-1.41-1.41L14.59 16 16 17.59zM12 4C7.03 4 3 8.03 3 13s4.03 9 9 9 9-4.03 9-9-4.03-9-9-9zm0 16c-3.86 0-7-3.14-7-7s3.14-7 7-7 7 3.14 7 7-3.14 7-7 7zm-1-11h2v6h-2zm0 8h2v2h-2z"/></svg>${esc(r.notice)}</div>`:''}
|
||
`;
|
||
$('detailInfoRow').innerHTML=`
|
||
<div class="detail-info-item"><div class="v yellow">★ ${money(r.rating)}</div><div class="l">评分</div></div>
|
||
<div class="detail-info-item"><div class="v">${r.sales}</div><div class="l">月售</div></div>
|
||
<div class="detail-info-item"><div class="v">${r.delivery_time}<small style="font-size:11px">分钟</small></div><div class="l">配送时间</div></div>
|
||
<div class="detail-info-item"><div class="v">${esc(r.distance)}</div><div class="l">距离</div></div>
|
||
<div class="detail-info-item"><div class="v">¥${money(r.min_order)}</div><div class="l">起送</div></div>
|
||
<div class="detail-info-item"><div class="v">${r.delivery_fee==0?'免':('¥'+money(r.delivery_fee))}</div><div class="l">配送费</div></div>
|
||
`;
|
||
renderMenu();
|
||
}
|
||
|
||
function renderMenu(){
|
||
const cats=state.menuData.categories||[];
|
||
const items=state.menuData.menu_items||[];
|
||
// 侧边栏
|
||
$('menuSidebar').innerHTML=cats.map((c,i)=>`<div class="menu-cat-item ${i===0?'active':''}" data-cat="${c.id}" onclick="scrollToCat(${c.id})">${esc(c.name)}</div>`).join('');
|
||
// 菜品列表
|
||
let html='';
|
||
cats.forEach(cat=>{
|
||
const catItems=items.filter(it=>it.category_id===cat.id);
|
||
if(!catItems.length) return;
|
||
html+=`<div class="menu-section" id="catsec-${cat.id}">
|
||
<div class="menu-section-title">${esc(cat.name)}</div>`;
|
||
catItems.forEach(it=>{
|
||
const tags=parseTags(it.tags).slice(0,2);
|
||
const hasOld=it.original_price>0 && it.original_price>it.price;
|
||
const qty=getQty(it.id);
|
||
html+=`<div class="menu-item">
|
||
<div class="menu-item-img" style="background:${itemGrad(it.id)}">
|
||
${it.image?`<img src="${esc(it.image)}" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover" onerror="this.style.display='none';this.parentNode.querySelector('svg').style.display=''">`:''}
|
||
${hasOld?'<span class="discount">折</span>':''}
|
||
<svg viewBox="0 0 24 24" style="${it.image?'display:none':''}">${itemSvg(it.id)}</svg>
|
||
</div>
|
||
<div class="menu-item-info">
|
||
<div class="menu-item-name">${esc(it.name)}</div>
|
||
${it.description?`<div class="menu-item-desc">${esc(it.description)}</div>`:''}
|
||
<div class="menu-item-tags">${tags.map(t=>`<span class="menu-item-tag">${esc(t)}</span>`).join('')}</div>
|
||
<div class="menu-item-bottom">
|
||
<div class="menu-item-meta">
|
||
<div class="menu-item-price">
|
||
<span class="now">¥<small></small>${money(it.price)}</span>
|
||
${hasOld?`<span class="old">¥${money(it.original_price)}</span>`:''}
|
||
</div>
|
||
<div class="menu-item-stat"><span class="star">★ ${money(it.rating)}</span> · 月售${it.sales}</div>
|
||
</div>
|
||
${qty>0?`<div class="qty-control">
|
||
<button class="minus" onclick="changeQty(${it.id},-1,event)">−</button>
|
||
<span class="num">${qty}</span>
|
||
<button class="plus" onclick="changeQty(${it.id},1,event)">+</button>
|
||
</div>`:`<button class="add-btn" onclick="addToCart(${it.id},event)">
|
||
<svg viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg>
|
||
</button>`}
|
||
</div>
|
||
</div>
|
||
</div>`;
|
||
});
|
||
html+=`</div>`;
|
||
});
|
||
$('menuList').innerHTML=html || '<div class="empty-state"><p>暂无菜品</p></div>';
|
||
// 滚动联动监听
|
||
setupScrollSync();
|
||
}
|
||
|
||
/* 滚动联动 */
|
||
let scrollSyncOn=true;
|
||
function setupScrollSync(){
|
||
const list=$('menuList');
|
||
list.onscroll=()=>{
|
||
if(!scrollSyncOn) return;
|
||
const cats=state.menuData.categories||[];
|
||
let activeId=cats.length?cats[0].id:0;
|
||
for(const c of cats){
|
||
const sec=$('catsec-'+c.id);
|
||
if(sec && sec.offsetTop-60<=list.scrollTop){activeId=c.id}
|
||
}
|
||
document.querySelectorAll('.menu-cat-item').forEach(el=>{
|
||
el.classList.toggle('active',Number(el.dataset.cat)===activeId);
|
||
});
|
||
};
|
||
}
|
||
function scrollToCat(catId){
|
||
const sec=$('catsec-'+catId);
|
||
const list=$('menuList');
|
||
if(sec){
|
||
scrollSyncOn=false;
|
||
list.scrollTo({top:sec.offsetTop-10,behavior:'smooth'});
|
||
document.querySelectorAll('.menu-cat-item').forEach(el=>{
|
||
el.classList.toggle('active',Number(el.dataset.cat)===catId);
|
||
});
|
||
setTimeout(()=>{scrollSyncOn=true},400);
|
||
}
|
||
}
|
||
|
||
/* ==========================================================
|
||
购物车
|
||
========================================================== */
|
||
function getQty(itemId){
|
||
if(!state.cart||!state.cart.items[itemId]) return 0;
|
||
return state.cart.items[itemId].qty;
|
||
}
|
||
function addToCart(itemId,e){
|
||
if(e){e.stopPropagation()}
|
||
const items=state.menuData.menu_items||[];
|
||
const item=items.find(it=>it.id===itemId);
|
||
if(!item) return;
|
||
const r=state.currentRestaurant;
|
||
// 跨店清空提示
|
||
if(state.cart && state.cart.restaurantId!==r.id && Object.keys(state.cart.items).length){
|
||
if(!confirm('购物车已有「'+state.cart.restaurantName+'」的菜品,是否清空并加入新店菜品?')) return;
|
||
state.cart=null;
|
||
}
|
||
if(!state.cart){
|
||
state.cart={restaurantId:r.id,restaurantName:r.name,deliveryFee:r.delivery_fee,minOrder:r.min_order,items:{}};
|
||
}
|
||
if(state.cart.items[itemId]){
|
||
state.cart.items[itemId].qty++;
|
||
}else{
|
||
state.cart.items[itemId]={item:item,qty:1};
|
||
}
|
||
toast('已加入「'+item.name+'」','success');
|
||
renderMenu();
|
||
renderCart();
|
||
}
|
||
function changeQty(itemId,delta,e){
|
||
if(e){e.stopPropagation()}
|
||
if(!state.cart||!state.cart.items[itemId]) return;
|
||
state.cart.items[itemId].qty+=delta;
|
||
if(state.cart.items[itemId].qty<=0){
|
||
delete state.cart.items[itemId];
|
||
}
|
||
renderMenu();
|
||
renderCart();
|
||
if(state.cart&&!Object.keys(state.cart.items).length){
|
||
toggleCartDetail(false);
|
||
}
|
||
}
|
||
function clearCart(){
|
||
if(!state.cart||!Object.keys(state.cart.items).length) return;
|
||
if(!confirm('确定清空购物车吗?')) return;
|
||
state.cart=null;
|
||
renderMenu();
|
||
renderCart();
|
||
toggleCartDetail(false);
|
||
toast('购物车已清空','warn');
|
||
}
|
||
function cartCount(){
|
||
if(!state.cart) return 0;
|
||
return Object.values(state.cart.items).reduce((s,v)=>s+v.qty,0);
|
||
}
|
||
function cartTotal(){
|
||
if(!state.cart) return 0;
|
||
return Object.values(state.cart.items).reduce((s,v)=>s+v.item.price*v.qty,0);
|
||
}
|
||
function renderCart(){
|
||
const count=cartCount();
|
||
const total=cartTotal();
|
||
const bar=$('cartBar');
|
||
if(count>0){
|
||
bar.style.display='block';
|
||
$('cartBarBox').classList.remove('empty');
|
||
$('cartIconCircle').classList.add('active');
|
||
$('cartTotal').classList.remove('empty');
|
||
$('cartBadge').classList.add('show');
|
||
$('navCartBadge').classList.add('show');
|
||
$('cartBadge').textContent=count;
|
||
$('navCartBadge').textContent=count;
|
||
$('cartTotal').querySelector('.t').innerHTML='¥<small></small>'+money(total);
|
||
const r=state.currentRestaurant||{deliveryFee:state.cart?state.cart.deliveryFee:0,minOrder:state.cart?state.cart.minOrder:0};
|
||
const fee=state.cart?state.cart.deliveryFee:0;
|
||
const min=state.cart?state.cart.minOrder:0;
|
||
$('cartSub').textContent = total>=min ? ('配送费¥'+money(fee)) : ('还差¥'+money(min-total)+'起送');
|
||
const canCheckout=total>=min;
|
||
$('checkoutBtn').classList.toggle('disabled',!canCheckout);
|
||
$('checkoutBtn').textContent=canCheckout?'去结算':'差¥'+money(min-total)+'起送';
|
||
}else{
|
||
bar.style.display='none';
|
||
$('navCartBadge').classList.remove('show');
|
||
}
|
||
renderCartDetail();
|
||
}
|
||
function renderCartDetail(){
|
||
if(!state.cart||!Object.keys(state.cart.items).length){
|
||
$('cartDetailList').innerHTML='<div class="cart-detail-empty"><svg viewBox="0 0 24 24"><path d="M7 18c-1.1 0-1.99.9-1.99 2S5.9 22 7 22s2-.9 2-2-.9-2-2-2zM1 2v2h2l3.6 7.59-1.35 2.45c-.16.28-.25.61-.25.96 0 1.1.9 2 2 2h12v-2H7.42c-.14 0-.25-.11-.25-.25l.03-.12.9-1.63h7.45c.75 0 1.41-.41 1.75-1.03l3.58-6.49A1 1 0 0 0 20 4H5.21l-.94-2H1zm16 16c-1.1 0-1.99.9-1.99 2s.89 2 1.99 2 2-.9 2-2-.9-2-2-2z"/></svg><p>购物车是空的</p><p class="sub">快去选些好吃的吧</p></div>';
|
||
return;
|
||
}
|
||
$('cartDetailTitle').textContent='购物车 · '+state.cart.restaurantName;
|
||
const html=Object.values(state.cart.items).map(v=>{
|
||
const it=v.item;
|
||
return `<div class="cart-detail-item">
|
||
<div style="width:44px;height:44px;border-radius:10px;background:${itemGrad(it.id)};display:flex;align-items:center;justify-content:center;flex-shrink:0;position:relative;overflow:hidden">
|
||
${it.image?`<img src="${esc(it.image)}" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover" onerror="this.style.display='none';this.parentNode.querySelector('svg').style.display=''">`:''}
|
||
<svg viewBox="0 0 24 24" style="width:22px;height:22px;fill:rgba(255,255,255,.92);${it.image?'display:none':''}">${itemSvg(it.id)}</svg>
|
||
</div>
|
||
<span class="name">${esc(it.name)}</span>
|
||
<span class="price">¥${money(it.price)}</span>
|
||
<div class="qty-control">
|
||
<button class="minus" onclick="changeQty(${it.id},-1)">−</button>
|
||
<span class="num">${v.qty}</span>
|
||
<button class="plus" onclick="changeQty(${it.id},1)">+</button>
|
||
</div>
|
||
</div>`;
|
||
}).join('');
|
||
$('cartDetailList').innerHTML=html;
|
||
}
|
||
function toggleCartDetail(force){
|
||
const show = force===undefined ? !$('cartDetail').classList.contains('show') : force;
|
||
if(show && (!state.cart||!Object.keys(state.cart.items).length)){toast('购物车是空的','warn');return}
|
||
$('cartDetail').classList.toggle('show',show);
|
||
$('cartDetailMask').classList.toggle('show',show);
|
||
}
|
||
function scrollToCart(){
|
||
if(!state.cart||!Object.keys(state.cart.items).length){toast('购物车是空的','warn');return}
|
||
toggleCartDetail(true);
|
||
}
|
||
function checkout(){
|
||
if(!state.cart||!Object.keys(state.cart.items).length) return;
|
||
const total=cartTotal();
|
||
if(total<(state.cart?state.cart.minOrder:0)){toast('未达到起送价','warn');return}
|
||
toast('订单提交成功(演示)¥'+money(total),'success');
|
||
state.cart=null;
|
||
renderMenu();
|
||
renderCart();
|
||
toggleCartDetail(false);
|
||
}
|
||
|
||
/* ==========================================================
|
||
启动
|
||
========================================================== */
|
||
document.addEventListener('keydown',e=>{
|
||
if(e.key==='Escape') closeDetail();
|
||
});
|
||
init();
|
||
</script>
|
||
</body>
|
||
</html>
|