- 所有18套前端模板 + 管理后台共20个HTML文件添加i18n引擎 - 前端JavaScript方案:I18N_LOCALES翻译字典 + data-i18n属性 + t()函数调用 - 语言切换器UI:下拉菜单选择,匹配各模板主题风格 - 语言持久化:localStorage存储 + URL参数覆盖 + 浏览器语言自动检测 - onLangChange回调:语言切换时动态重新渲染所有内容 - 支持语言:zh-CN(简体中文) / zh-TW(繁体中文) / en(English)
1362 lines
75 KiB
HTML
1362 lines
75 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 data-i18n="site_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}
|
|
.lang-switcher{position:relative;margin-left:8px}
|
|
.lang-btn{
|
|
display:flex;align-items:center;gap:6px;padding:8px 14px;border-radius:10px;
|
|
font-size:13px;font-weight:600;color:var(--text-sub);cursor:pointer;
|
|
background:var(--orange-soft);border:1px solid var(--border);
|
|
transition:all .25s ease;
|
|
}
|
|
.lang-btn:hover{color:var(--orange);background:#FFE0D2;border-color:var(--orange)}
|
|
.lang-btn svg{width:16px;height:16px}
|
|
.lang-dropdown{
|
|
position:absolute;top:calc(100% + 8px);right:0;min-width:160px;
|
|
background:var(--white);border:1px solid var(--border);border-radius:var(--radius-sm);
|
|
box-shadow:0 12px 40px rgba(0,0,0,.15);overflow:hidden;
|
|
opacity:0;transform:translateY(-8px);pointer-events:none;
|
|
transition:all .25s ease;z-index:1001;
|
|
}
|
|
.lang-dropdown.open{opacity:1;transform:none;pointer-events:auto}
|
|
.lang-option{
|
|
display:flex;align-items:center;gap:10px;padding:12px 16px;
|
|
font-size:14px;color:var(--text-sub);cursor:pointer;transition:all .2s;
|
|
}
|
|
.lang-option:hover{background:var(--orange-soft);color:var(--orange)}
|
|
.lang-option.active{color:var(--orange);font-weight:600}
|
|
.lang-option .check{width:16px;height:16px;opacity:0;transition:opacity .2s}
|
|
.lang-option.active .check{opacity:1}
|
|
.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-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}
|
|
.lang-switcher{position:static;margin-left:0}
|
|
}
|
|
@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" data-i18n="logo">美味达</span>
|
|
</div>
|
|
<div class="nav-search">
|
|
<input id="navSearchInput" type="text" data-i18n-placeholder="search_placeholder" 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 data-i18n="current_location">当前位置</span>
|
|
</div>
|
|
<div class="lang-switcher" id="langSwitcher">
|
|
<div class="lang-btn" onclick="toggleLangDropdown()">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15 15 0 0 1 0 20 15 15 0 0 1 0-20z"/></svg>
|
|
<span id="langLabel">中文</span>
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="width:12px;height:12px"><path d="m6 9 6 6 6-6"/></svg>
|
|
</div>
|
|
<div class="lang-dropdown" id="langDropdown">
|
|
<div class="lang-option" data-lang="zh-CN" onclick="switchLang('zh-CN')">
|
|
<svg class="check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><path d="M5 12l5 5L20 7"/></svg>
|
|
中文
|
|
</div>
|
|
<div class="lang-option" data-lang="zh-TW" onclick="switchLang('zh-TW')">
|
|
<svg class="check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><path d="M5 12l5 5L20 7"/></svg>
|
|
繁體中文
|
|
</div>
|
|
<div class="lang-option" data-lang="en" onclick="switchLang('en')">
|
|
<svg class="check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><path d="M5 12l5 5L20 7"/></svg>
|
|
English
|
|
</div>
|
|
</div>
|
|
</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>
|
|
|
|
<section class="hero">
|
|
<div class="container hero-inner">
|
|
<h1 data-i18n="hero_title">美味触手可及</h1>
|
|
<p data-i18n="hero_desc">周边好店 · 极速配送 · 优惠不停</p>
|
|
<div class="hero-search">
|
|
<input id="heroSearchInput" type="text" data-i18n-placeholder="hero_search_placeholder" placeholder="想吃什么?搜搜看吧" onkeydown="if(event.key==='Enter')doSearch(this.value)">
|
|
<button onclick="doSearch(document.getElementById('heroSearchInput').value)" data-i18n="search_btn">搜索</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" data-i18n="cat_title">餐厅分类</div>
|
|
<div class="section-sub" data-i18n="cat_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" data-i18n="featured_title">为你推荐</div>
|
|
<div class="section-sub" data-i18n="featured_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" data-i18n="all_restaurants">全部餐厅</div>
|
|
</div>
|
|
<div class="list-toolbar">
|
|
<div class="sort-tabs" id="sortTabs">
|
|
<div class="sort-tab active" data-sort="" data-i18n="sort_default">默认排序</div>
|
|
<div class="sort-tab" data-sort="rating" data-i18n="sort_rating">评分最高</div>
|
|
<div class="sort-tab" data-sort="sales" data-i18n="sort_sales">销量最高</div>
|
|
<div class="sort-tab" data-sort="delivery_time" data-i18n="sort_delivery">配送最快</div>
|
|
</div>
|
|
<div class="result-info"><span data-i18n="result_prefix">共 </span><b id="resultTotal">0</b><span data-i18n="result_suffix"> 家餐厅</span></div>
|
|
</div>
|
|
<div id="restGrid" class="rest-grid"></div>
|
|
<div id="loadMore" class="load-more hidden">
|
|
<button id="loadMoreBtn" onclick="loadMoreRestaurants()" data-i18n="load_more">加载更多</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" data-i18n="cart">购物车</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 data-i18n="clear_cart">清空</span>
|
|
</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"></span>
|
|
</div>
|
|
<div class="cart-checkout disabled" id="checkoutBtn" onclick="checkout()"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<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>
|
|
<span data-i18n="logo">美味达</span>
|
|
</div>
|
|
<div class="footer-links">
|
|
<a onclick="goHome()" data-i18n="footer_home">首页</a>
|
|
<a onclick="toast(t('order_demo'),'warn')" data-i18n="footer_orders">我的订单</a>
|
|
<a onclick="toast(t('merchant_demo'),'warn')" data-i18n="footer_merchant">商家入驻</a>
|
|
<a onclick="toast(t('service_demo'),'warn')" data-i18n="footer_service">联系客服</a>
|
|
</div>
|
|
<div class="footer-copy" data-i18n="footer_copy">© 2026 美味达外卖平台 · 美味触手可及 · 本页面为演示模板</div>
|
|
</div>
|
|
</footer>
|
|
|
|
<script>
|
|
var I18N_LOCALES = {
|
|
'zh-CN': {
|
|
site_title: '\u7f8e\u5473\u8fbe \u00b7 \u5916\u5356\u5e73\u53f0',
|
|
logo: '\u7f8e\u5473\u8fbe',
|
|
search_placeholder: '\u641c\u7d22\u9910\u5385\u6216\u7f8e\u98df',
|
|
current_location: '\u5f53\u524d\u4f4d\u7f6e',
|
|
hero_title: '\u7f8e\u5473\u89e6\u624b\u53ef\u53ca',
|
|
hero_desc: '\u5468\u8fb9\u597d\u5e97 \u00b7 \u6781\u901f\u914d\u9001 \u00b7 \u4f18\u60e0\u4e0d\u505c',
|
|
hero_search_placeholder: '\u60f3\u5403\u4ec0\u4e48\uff1f\u641c\u641c\u770b\u5427',
|
|
search_btn: '\u641c\u7d22',
|
|
cat_title: '\u9910\u5385\u5206\u7c7b',
|
|
cat_sub: '\u9009\u62e9\u5206\u7c7b\uff0c\u53d1\u73b0\u597d\u5e97',
|
|
featured_title: '\u4e3a\u4f60\u63a8\u8350',
|
|
featured_sub: '\u4eba\u6c14\u597d\u5e97\uff0c\u4e0d\u5bb9\u9519\u8fc7',
|
|
all_restaurants: '\u5168\u90e8\u9910\u5385',
|
|
sort_default: '\u9ed8\u8ba4\u6392\u5e8f',
|
|
sort_rating: '\u8bc4\u5206\u6700\u9ad8',
|
|
sort_sales: '\u9500\u91cf\u6700\u9ad8',
|
|
sort_delivery: '\u914d\u9001\u6700\u5feb',
|
|
result_prefix: '\u5171 ',
|
|
result_suffix: ' \u5bb6\u9910\u5385',
|
|
load_more: '\u52a0\u8f7d\u66f4\u591a',
|
|
cart: '\u8d2d\u7269\u8f66',
|
|
clear_cart: '\u6e05\u7a7a',
|
|
checkout: '\u53bb\u7ed3\u7b97',
|
|
footer_home: '\u9996\u9875',
|
|
footer_orders: '\u6211\u7684\u8ba2\u5355',
|
|
footer_merchant: '\u5546\u5bb6\u5165\u9a7b',
|
|
footer_service: '\u8054\u7cfb\u5ba2\u670d',
|
|
footer_copy: '\u00a9 2026 \u7f8e\u5473\u8fbe\u5916\u5356\u5e73\u53f0 \u00b7 \u7f8e\u5473\u89e6\u624b\u53ef\u53ca \u00b7 \u672c\u9875\u9762\u4e3a\u6f14\u793a\u6a21\u677f',
|
|
order_demo: '\u8ba2\u5355\u529f\u80fd\u6f14\u793a\u4e2d',
|
|
merchant_demo: '\u5546\u5bb6\u5165\u9a7b\u529f\u80fd\u6f14\u793a\u4e2d',
|
|
service_demo: '\u5ba2\u670d\u4e2d\u5fc3\u6f14\u793a\u4e2d',
|
|
load_data_fail: '\u6570\u636e\u52a0\u8f7d\u5931\u8d25\uff0c\u8bf7\u68c0\u67e5\u670d\u52a1',
|
|
load_error_data: '\u6570\u636e\u52a0\u8f7d\u5931\u8d25',
|
|
load_error_service: '\u8bf7\u786e\u4fdd\u540e\u7aef\u670d\u52a1\u5df2\u542f\u52a8',
|
|
load_restaurant_fail: '\u52a0\u8f7d\u9910\u5385\u5931\u8d25',
|
|
retry_later: '\u8bf7\u7a0d\u540e\u91cd\u8bd5',
|
|
no_restaurant: '\u6ca1\u6709\u627e\u5230\u76f8\u5173\u9910\u5385',
|
|
try_other: '\u6362\u4e2a\u5173\u952e\u8bcd\u6216\u5206\u7c7b\u8bd5\u8bd5',
|
|
open: '\u8425\u4e1a\u4e2d',
|
|
closed: '\u4f11\u606f\u4e2d',
|
|
monthly_sales: '\u6708\u552e ',
|
|
mins: '\u5206\u949f',
|
|
min_order_prefix: '\u8d77\u9001 \u00a5',
|
|
free_delivery: '\u514d\u914d\u9001\u8d39',
|
|
delivery_fee_prefix: '\u914d\u9001 \u00a5',
|
|
detail_rating: '\u8bc4\u5206',
|
|
detail_monthly: '\u6708\u552e',
|
|
detail_delivery_time: '\u914d\u9001\u65f6\u95f4',
|
|
detail_distance: '\u8ddd\u79bb',
|
|
detail_min_order: '\u8d77\u9001',
|
|
detail_delivery_fee: '\u914d\u9001\u8d39',
|
|
detail_free: '\u514d',
|
|
no_menu: '\u6682\u65e0\u83dc\u54c1',
|
|
loading: '\u52a0\u8f7d\u4e2d...',
|
|
load_fail: '\u52a0\u8f7d\u5931\u8d25',
|
|
detail_load_fail: '\u8be6\u60c5\u52a0\u8f7d\u5931\u8d25',
|
|
cross_store_confirm: '\u8d2d\u7269\u8f66\u5df2\u6709\u300c{name}\u300d\u7684\u83dc\u54c1\uff0c\u662f\u5426\u6e05\u7a7a\u5e76\u52a0\u5165\u65b0\u5e97\u83dc\u54c1\uff1f',
|
|
added_item: '\u5df2\u52a0\u5165\u300c{name}\u300d',
|
|
clear_cart_confirm: '\u786e\u5b9a\u6e05\u7a7a\u8d2d\u7269\u8f66\u5417\uff1f',
|
|
cart_cleared: '\u8d2d\u7269\u8f66\u5df2\u6e05\u7a7a',
|
|
cart_empty: '\u8d2d\u7269\u8f66\u662f\u7a7a\u7684',
|
|
cart_empty_hint: '\u5feb\u53bb\u9009\u4e9b\u597d\u5403\u7684\u5427',
|
|
cart_with_name: '\u8d2d\u7269\u8f66 \u00b7 {name}',
|
|
not_reach_min: '\u672a\u8fbe\u5230\u8d77\u9001\u4ef7',
|
|
order_success: '\u8ba2\u5355\u63d0\u4ea4\u6210\u529f\uff08\u6f14\u793a\uff09\u00a5{total}',
|
|
delivery_fee_text: '\u914d\u9001\u8d39\u00a5{fee}',
|
|
need_more: '\u8fd8\u5dee\u00a5{diff}\u8d77\u9001',
|
|
checkout_disabled: '\u5dee\u00a5{diff}\u8d77\u9001',
|
|
hot_tags: ['\u9ebb\u8fa3','\u5ddd\u83dc','\u5976\u8336','\u6c49\u5821','\u70e4\u8089','\u6c99\u62c9']
|
|
},
|
|
'en': {
|
|
site_title: 'MeiWeiDa \u00b7 Food Delivery',
|
|
logo: 'MeiWeiDa',
|
|
search_placeholder: 'Search restaurants or food',
|
|
current_location: 'Current Location',
|
|
hero_title: 'Delicious Food at Your Doorstep',
|
|
hero_desc: 'Top local restaurants \u00b7 Fast delivery \u00b7 Non-stop deals',
|
|
hero_search_placeholder: 'What are you craving? Search here',
|
|
search_btn: 'Search',
|
|
cat_title: 'Categories',
|
|
cat_sub: 'Pick a category to explore',
|
|
featured_title: 'Recommended',
|
|
featured_sub: 'Popular restaurants you can\'t miss',
|
|
all_restaurants: 'All Restaurants',
|
|
sort_default: 'Default',
|
|
sort_rating: 'Top Rated',
|
|
sort_sales: 'Best Sellers',
|
|
sort_delivery: 'Fastest Delivery',
|
|
result_prefix: '',
|
|
result_suffix: ' restaurants found',
|
|
load_more: 'Load More',
|
|
cart: 'Cart',
|
|
clear_cart: 'Clear',
|
|
checkout: 'Checkout',
|
|
footer_home: 'Home',
|
|
footer_orders: 'My Orders',
|
|
footer_merchant: 'Merchant Signup',
|
|
footer_service: 'Contact Support',
|
|
footer_copy: '\u00a9 2026 MeiWeiDa \u00b7 Delicious food at your doorstep \u00b7 Demo template',
|
|
order_demo: 'Orders feature (demo)',
|
|
merchant_demo: 'Merchant signup (demo)',
|
|
service_demo: 'Customer support (demo)',
|
|
load_data_fail: 'Failed to load data, please check the server',
|
|
load_error_data: 'Failed to load data',
|
|
load_error_service: 'Please make sure the backend is running',
|
|
load_restaurant_fail: 'Failed to load restaurants',
|
|
retry_later: 'Please try again later',
|
|
no_restaurant: 'No restaurants found',
|
|
try_other: 'Try a different keyword or category',
|
|
open: 'Open',
|
|
closed: 'Closed',
|
|
monthly_sales: 'Monthly ',
|
|
mins: 'min',
|
|
min_order_prefix: 'Min \u00a5',
|
|
free_delivery: 'Free Delivery',
|
|
delivery_fee_prefix: 'Delivery \u00a5',
|
|
detail_rating: 'Rating',
|
|
detail_monthly: 'Monthly Sales',
|
|
detail_delivery_time: 'Delivery Time',
|
|
detail_distance: 'Distance',
|
|
detail_min_order: 'Min Order',
|
|
detail_delivery_fee: 'Delivery Fee',
|
|
detail_free: 'Free',
|
|
no_menu: 'No menu items',
|
|
loading: 'Loading...',
|
|
load_fail: 'Load Failed',
|
|
detail_load_fail: 'Failed to load details',
|
|
cross_store_confirm: 'Your cart has items from "{name}". Clear cart and add items from this store?',
|
|
added_item: 'Added "{name}"',
|
|
clear_cart_confirm: 'Clear the entire cart?',
|
|
cart_cleared: 'Cart cleared',
|
|
cart_empty: 'Your cart is empty',
|
|
cart_empty_hint: 'Pick something delicious!',
|
|
cart_with_name: 'Cart \u00b7 {name}',
|
|
not_reach_min: 'Minimum order not reached',
|
|
order_success: 'Order placed (demo) \u00a5{total}',
|
|
delivery_fee_text: 'Delivery \u00a5{fee}',
|
|
need_more: '\u00a5{diff} more to order',
|
|
checkout_disabled: '\u00a5{diff} more to checkout',
|
|
hot_tags: ['Spicy','Sichuan','Bubble Tea','Burger','BBQ','Salad']
|
|
},
|
|
'zh-TW': {
|
|
site_title: '\u7f8e\u5473\u9054 \u00b7 \u5916\u8ca9\u5e73\u53f0',
|
|
logo: '\u7f8e\u5473\u9054',
|
|
search_placeholder: '\u641c\u5bfb\u9910\u5385\u6216\u7f8e\u98df',
|
|
current_location: '\u76ee\u524d\u4f4d\u7f6e',
|
|
hero_title: '\u7f8e\u5473\u89f8\u624b\u53ef\u53ca',
|
|
hero_desc: '\u5468\u908a\u597d\u5e97 \u00b7 \u6975\u901f\u914d\u9001 \u00b7 \u512a\u60e0\u4e0d\u505c',
|
|
hero_search_placeholder: '\u60f3\u5403\u4ec0\u9ebc\uff1f\u641c\u641c\u770b\u5427',
|
|
search_btn: '\u641c\u5bfb',
|
|
cat_title: '\u9910\u5385\u5206\u985e',
|
|
cat_sub: '\u9078\u64c7\u5206\u985e\uff0c\u767c\u73b0\u597d\u5e97',
|
|
featured_title: '\u70ba\u4f60\u63a8\u85a6',
|
|
featured_sub: '\u4eba\u6c23\u597d\u5e97\uff0c\u4e0d\u5bb9\u932f\u904e',
|
|
all_restaurants: '\u5168\u90e8\u9910\u5385',
|
|
sort_default: '\u9810\u8a2d\u6392\u5e8f',
|
|
sort_rating: '\u8a55\u5206\u6700\u9ad8',
|
|
sort_sales: '\u92b7\u91cf\u6700\u9ad8',
|
|
sort_delivery: '\u914d\u9001\u6700\u5feb',
|
|
result_prefix: '\u5171 ',
|
|
result_suffix: ' \u5bb6\u9910\u5385',
|
|
load_more: '\u8f09\u5165\u66f4\u591a',
|
|
cart: '\u8cfc\u7269\u8eca',
|
|
clear_cart: '\u6e05\u7a7a',
|
|
checkout: '\u53bb\u7d50\u5e33',
|
|
footer_home: '\u9996\u9801',
|
|
footer_orders: '\u6211\u7684\u8a02\u55ae',
|
|
footer_merchant: '\u5546\u5bb6\u5165\u9a7b',
|
|
footer_service: '\u806f\u7e6c\u5ba2\u670d',
|
|
footer_copy: '\u00a9 2026 \u7f8e\u5473\u9054\u5916\u8ca9\u5e73\u53f0 \u00b7 \u7f8e\u5473\u89f8\u624b\u53ef\u53ca \u00b7 \u672c\u9801\u9762\u70ba\u6f14\u793a\u6a21\u677f',
|
|
order_demo: '\u8a02\u55ae\u529f\u80fd\u6f14\u793a\u4e2d',
|
|
merchant_demo: '\u5546\u5bb6\u5165\u9a7b\u529f\u80fd\u6f14\u793a\u4e2d',
|
|
service_demo: '\u5ba2\u670d\u4e2d\u5fc3\u6f14\u793a\u4e2d',
|
|
load_data_fail: '\u8cc7\u6599\u8f09\u5165\u5931\u6557\uff0c\u8acb\u6aa2\u67e5\u670d\u52d9',
|
|
load_error_data: '\u8cc7\u6599\u8f09\u5165\u5931\u6557',
|
|
load_error_service: '\u8acb\u78ba\u4fdd\u5f8c\u7aef\u670d\u52d9\u5df2\u555f\u52d5',
|
|
load_restaurant_fail: '\u8f09\u5165\u9910\u5385\u5931\u6557',
|
|
retry_later: '\u8acb\u7a0d\u5f8c\u91cd\u8a66',
|
|
no_restaurant: '\u6ca1\u6709\u627e\u5230\u76f8\u95dc\u9910\u5385',
|
|
try_other: '\u63db\u500b\u95dc\u9375\u8a5e\u6216\u5206\u985e\u8a66\u8a66',
|
|
open: '\u71df\u696d\u4e2d',
|
|
closed: '\u4f11\u606f\u4e2d',
|
|
monthly_sales: '\u6708\u552e ',
|
|
mins: '\u5206\u9418',
|
|
min_order_prefix: '\u8d77\u9001 \u00a5',
|
|
free_delivery: '\u514d\u914d\u9001\u8cbb',
|
|
delivery_fee_prefix: '\u914d\u9001 \u00a5',
|
|
detail_rating: '\u8a55\u5206',
|
|
detail_monthly: '\u6708\u552e',
|
|
detail_delivery_time: '\u914d\u9001\u6642\u9593',
|
|
detail_distance: '\u8ddd\u96e2',
|
|
detail_min_order: '\u8d77\u9001',
|
|
detail_delivery_fee: '\u914d\u9001\u8cbb',
|
|
detail_free: '\u514d',
|
|
no_menu: '\u6682\u7121\u83dc\u54c1',
|
|
loading: '\u8f09\u5165\u4e2d...',
|
|
load_fail: '\u8f09\u5165\u5931\u6557',
|
|
detail_load_fail: '\u8a73\u60c5\u8f09\u5165\u5931\u6557',
|
|
cross_store_confirm: '\u8cfc\u7269\u8eca\u5df2\u6709\u300c{name}\u300d\u7684\u83dc\u54c1\uff0c\u662f\u5426\u6e05\u7a7a\u4e26\u52a0\u5165\u65b0\u5e97\u83dc\u54c1\uff1f',
|
|
added_item: '\u5df2\u52a0\u5165\u300c{name}\u300d',
|
|
clear_cart_confirm: '\u78ba\u5b9a\u6e05\u7a7a\u8cfc\u7269\u8eca\u55ce\uff1f',
|
|
cart_cleared: '\u8cfc\u7269\u8eca\u5df2\u6e05\u7a7a',
|
|
cart_empty: '\u8cfc\u7269\u8eca\u662f\u7a7a\u7684',
|
|
cart_empty_hint: '\u5feb\u53bb\u9078\u4e9b\u597d\u5403\u7684\u5427',
|
|
cart_with_name: '\u8cfc\u7269\u8eca \u00b7 {name}',
|
|
not_reach_min: '\u672a\u9054\u5230\u8d77\u9001\u50f9',
|
|
order_success: '\u8a02\u55ae\u63d0\u4ea4\u6210\u529f\uff08\u6f14\u793a\uff09\u00a5{total}',
|
|
delivery_fee_text: '\u914d\u9001\u8cbb\u00a5{fee}',
|
|
need_more: '\u9084\u5dee\u00a5{diff}\u8d77\u9001',
|
|
checkout_disabled: '\u5dee\u00a5{diff}\u8d77\u9001',
|
|
hot_tags: ['\u9ebb\u8fa3','\u5ddd\u83dc','\u5976\u8336','\u6f22\u5821','\u70e4\u8089','\u6c99\u62c9']
|
|
}
|
|
};
|
|
|
|
var LANG_LABELS = {'zh-CN':'\u4e2d\u6587','en':'EN','zh-TW':'\u7e41\u4e2d'};
|
|
|
|
var currentLang = (function(){
|
|
try{
|
|
var u=new URLSearchParams(location.search).get('lang');
|
|
if(u&&I18N_LOCALES[u]) return u;
|
|
}catch(e){}
|
|
try{var s=localStorage.getItem('lang');if(s&&I18N_LOCALES[s]) return s;}catch(e){}
|
|
var b=navigator.language||navigator.userLanguage||'';
|
|
if(b.startsWith('zh-TW')||b.startsWith('zh-Hant')) return 'zh-TW';
|
|
if(b.startsWith('zh')) return 'zh-CN';
|
|
return 'en';
|
|
})();
|
|
|
|
function t(key){
|
|
var v=I18N_LOCALES[currentLang]&&I18N_LOCALES[currentLang][key];
|
|
if(v===undefined) v=I18N_LOCALES['zh-CN'][key];
|
|
return v!==undefined?v:key;
|
|
}
|
|
|
|
function tParam(key,params){
|
|
var s=t(key);
|
|
if(params){Object.keys(params).forEach(function(k){s=s.replace(new RegExp('\\{'+k+'\\}','g'),params[k]);});}
|
|
return s;
|
|
}
|
|
|
|
function applyI18n(){
|
|
document.querySelectorAll('[data-i18n]').forEach(function(el){
|
|
var key=el.getAttribute('data-i18n');
|
|
el.textContent=t(key);
|
|
});
|
|
document.querySelectorAll('[data-i18n-placeholder]').forEach(function(el){
|
|
var key=el.getAttribute('data-i18n-placeholder');
|
|
el.placeholder=t(key);
|
|
});
|
|
document.documentElement.lang=currentLang==='zh-TW'?'zh-TW':currentLang==='zh-CN'?'zh-CN':'en';
|
|
document.title=t('site_title');
|
|
document.getElementById('langLabel').textContent=LANG_LABELS[currentLang]||currentLang;
|
|
document.querySelectorAll('.lang-option').forEach(function(opt){
|
|
opt.classList.toggle('active',opt.getAttribute('data-lang')===currentLang);
|
|
});
|
|
}
|
|
|
|
function switchLang(lang){
|
|
if(!I18N_LOCALES[lang]) return;
|
|
currentLang=lang;
|
|
try{localStorage.setItem('lang',lang);}catch(e){}
|
|
applyI18n();
|
|
if(typeof onLangChange==='function') onLangChange(lang);
|
|
document.getElementById('langDropdown').classList.remove('open');
|
|
}
|
|
|
|
function toggleLangDropdown(){
|
|
document.getElementById('langDropdown').classList.toggle('open');
|
|
}
|
|
document.addEventListener('click',function(e){
|
|
var sw=document.getElementById('langSwitcher');
|
|
if(sw&&!sw.contains(e.target)) document.getElementById('langDropdown').classList.remove('open');
|
|
});
|
|
|
|
/* ==========================================================
|
|
Data & State
|
|
========================================================== */
|
|
var API = '/api/v1/food';
|
|
var state = {
|
|
categories: [],
|
|
featured: [],
|
|
restaurants: [],
|
|
total: 0,
|
|
page: 1,
|
|
pageSize: 12,
|
|
currentCategory: 0,
|
|
keyword: '',
|
|
sort: '',
|
|
loading: false,
|
|
currentRestaurant: null,
|
|
menuData: null,
|
|
cart: null,
|
|
};
|
|
|
|
var 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"/>'},
|
|
};
|
|
var DEFAULT_CAT_STYLE = {grad:'linear-gradient(135deg,#FF6B35,#FFD23F)', svg:'<circle cx="12" cy="12" r="9"/>'};
|
|
|
|
var 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)',
|
|
];
|
|
var 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"/>',
|
|
];
|
|
var 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)',
|
|
];
|
|
var 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"/>',
|
|
];
|
|
|
|
/* ==========================================================
|
|
Utilities
|
|
========================================================== */
|
|
function $(id){return document.getElementById(id)}
|
|
function esc(s){return (s==null?'':String(s)).replace(/[&<>"']/g,function(c){return({'&':'&','<':'<','>':'>','"':'"',"'":'''})[c]})}
|
|
function parseTags(t){try{return JSON.parse(t||'[]')}catch(e){return []}}
|
|
function safeNum(n,d){n=Number(n);return isNaN(n)?(d||0):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]}
|
|
|
|
function toast(msg,type){
|
|
var wrap=$('toastWrap');
|
|
var el=document.createElement('div');
|
|
el.className='toast '+(type||'');
|
|
var 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(function(){el.classList.add('out');setTimeout(function(){el.remove()},300)},2000);
|
|
}
|
|
|
|
/* ==========================================================
|
|
API
|
|
========================================================== */
|
|
async function api(path){
|
|
try{
|
|
var res=await fetch(API+path);
|
|
if(!res.ok) throw new Error('HTTP '+res.status);
|
|
var json=await res.json();
|
|
if(json.code!==0) throw new Error(json.message||'API error');
|
|
return json.data;
|
|
}catch(e){
|
|
console.error('API error:',path,e);
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
/* ==========================================================
|
|
Init
|
|
========================================================== */
|
|
async function init(){
|
|
try{
|
|
var home=await api('/home');
|
|
state.categories=home.categories||[];
|
|
state.featured=home.featured||[];
|
|
renderHeroTags();
|
|
renderCategories();
|
|
renderFeatured();
|
|
await loadRestaurants(true);
|
|
}catch(e){
|
|
toast(t('load_data_fail'),'warn');
|
|
$('catGrid').innerHTML='<div class="empty-state" style="grid-column:1/-1"><p>'+t('load_error_data')+'</p><p class="sub">'+t('load_error_service')+'</p></div>';
|
|
}
|
|
}
|
|
|
|
function renderHeroTags(){
|
|
var hot=t('hot_tags');
|
|
if(!Array.isArray(hot)) hot=['Spicy','Sichuan','Bubble Tea','Burger','BBQ','Salad'];
|
|
$('heroTags').innerHTML=hot.map(function(tag){return '<span class="hero-tag" onclick="doSearch(\''+esc(tag)+'\')">'+esc(tag)+'</span>'}).join('');
|
|
}
|
|
|
|
function renderCategories(){
|
|
var html=state.categories.map(function(c){
|
|
var 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}
|
|
var html=state.featured.map(function(r,i){
|
|
var 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(function(tag){return '<span class="feature-tag">'+esc(tag)+'</span>'}).join('')+'</div>'+
|
|
'<div class="feature-meta">'+
|
|
'<span class="star">\u2605 '+money(r.rating)+'</span>'+
|
|
'<span class="dot">\u00b7</span>'+
|
|
'<span>'+t('monthly_sales')+r.sales+'</span>'+
|
|
'<span class="dot">\u00b7</span>'+
|
|
'<span>'+r.delivery_time+t('mins')+'</span>'+
|
|
'</div>'+
|
|
'</div>'+
|
|
'</div>';
|
|
}).join('');
|
|
$('featuredScroll').innerHTML=html;
|
|
}
|
|
|
|
/* ==========================================================
|
|
Restaurant List
|
|
========================================================== */
|
|
function buildQuery(){
|
|
var 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{
|
|
var data=await api('/restaurants?'+buildQuery());
|
|
var 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>'+t('load_restaurant_fail')+'</p><p class="sub">'+t('retry_later')+'</p></div>';
|
|
}
|
|
state.loading=false;
|
|
}
|
|
|
|
function loadMoreRestaurants(){
|
|
state.page++;
|
|
loadRestaurants(false);
|
|
}
|
|
|
|
function renderSkeleton(n){
|
|
var html='';
|
|
for(var 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;
|
|
var 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>'+t('no_restaurant')+'</p><p class="sub">'+t('try_other')+'</p></div>';
|
|
$('loadMore').classList.add('hidden');
|
|
return;
|
|
}
|
|
grid.innerHTML=state.restaurants.map(function(r){
|
|
var tags=parseTags(r.tags).slice(0,3);
|
|
var 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?t('open'):t('closed'))+'</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(function(tag){return '<span class="rest-tag">'+esc(tag)+'</span>'}).join('')+'</div>'+
|
|
'<div class="rest-rate"><span class="star">\u2605 '+money(r.rating)+'</span><span class="sales">'+t('monthly_sales')+r.sales+'</span></div>'+
|
|
'<div class="rest-meta">'+
|
|
'<span>'+t('min_order_prefix')+money(r.min_order)+'</span>'+
|
|
'<span>'+(r.delivery_fee==0?'<span class="free">'+t('free_delivery')+'</span>':t('delivery_fee_prefix')+money(r.delivery_fee))+'</span>'+
|
|
'<span>'+r.delivery_time+t('mins')+'</span>'+
|
|
'<span>'+esc(r.distance)+'</span>'+
|
|
'</div>'+
|
|
'</div>'+
|
|
'</div>';
|
|
}).join('');
|
|
var 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(function(el,i){
|
|
el.classList.toggle('active', state.categories[i]&&state.categories[i].id===state.currentCategory);
|
|
});
|
|
var cat=state.categories.find(function(c){return c.id===state.currentCategory});
|
|
$('listTitle').textContent = cat ? cat.name+(currentLang==='en'?' ':'') : t('all_restaurants');
|
|
loadRestaurants(true);
|
|
$('restSection').scrollIntoView({behavior:'smooth',block:'start'});
|
|
}
|
|
|
|
document.querySelectorAll('.sort-tab').forEach(function(tab){
|
|
tab.addEventListener('click',function(){
|
|
document.querySelectorAll('.sort-tab').forEach(function(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=t('all_restaurants');
|
|
document.querySelectorAll('.sort-tab').forEach(function(t,i){t.classList.toggle('active',i===0)});
|
|
document.querySelectorAll('.cat-item').forEach(function(el){el.classList.remove('active')});
|
|
loadRestaurants(true);
|
|
window.scrollTo({top:0,behavior:'smooth'});
|
|
}
|
|
|
|
/* ==========================================================
|
|
Restaurant Detail
|
|
========================================================== */
|
|
async function openDetail(id){
|
|
$('detailOverlay').classList.add('show');
|
|
$('detailPanel').classList.add('show');
|
|
document.body.style.overflow='hidden';
|
|
$('detailHeaderBg').style.background=restGrad(id);
|
|
$('detailHeaderBg').innerHTML='';
|
|
$('detailHeaderContent').innerHTML='<div style="color:#fff;font-size:16px">'+t('loading')+'</div>';
|
|
$('detailInfoRow').innerHTML='';
|
|
$('menuSidebar').innerHTML='<div style="padding:20px;color:#999;text-align:center">'+t('loading')+'</div>';
|
|
$('menuList').innerHTML='';
|
|
try{
|
|
var 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">'+t('load_fail')+'</div>';
|
|
toast(t('detail_load_fail'),'warn');
|
|
}
|
|
}
|
|
|
|
function closeDetail(){
|
|
$('detailOverlay').classList.remove('show');
|
|
$('detailPanel').classList.remove('show');
|
|
document.body.style.overflow='';
|
|
}
|
|
|
|
function renderDetail(){
|
|
var r=state.currentRestaurant;
|
|
var 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(function(tag){return '<span class="detail-tag">'+esc(tag)+'</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">\u2605 '+money(r.rating)+'</div><div class="l">'+t('detail_rating')+'</div></div>'+
|
|
'<div class="detail-info-item"><div class="v">'+r.sales+'</div><div class="l">'+t('detail_monthly')+'</div></div>'+
|
|
'<div class="detail-info-item"><div class="v">'+r.delivery_time+'<small style="font-size:11px">'+t('mins')+'</small></div><div class="l">'+t('detail_delivery_time')+'</div></div>'+
|
|
'<div class="detail-info-item"><div class="v">'+esc(r.distance)+'</div><div class="l">'+t('detail_distance')+'</div></div>'+
|
|
'<div class="detail-info-item"><div class="v">\u00a5'+money(r.min_order)+'</div><div class="l">'+t('detail_min_order')+'</div></div>'+
|
|
'<div class="detail-info-item"><div class="v">'+(r.delivery_fee==0?t('detail_free'):('\u00a5'+money(r.delivery_fee)))+'</div><div class="l">'+t('detail_delivery_fee')+'</div></div>';
|
|
renderMenu();
|
|
}
|
|
|
|
function renderMenu(){
|
|
var cats=state.menuData.categories||[];
|
|
var items=state.menuData.menu_items||[];
|
|
$('menuSidebar').innerHTML=cats.map(function(c,i){return '<div class="menu-cat-item '+(i===0?'active':'')+'" data-cat="'+c.id+'" onclick="scrollToCat('+c.id+')">'+esc(c.name)+'</div>'}).join('');
|
|
var html='';
|
|
cats.forEach(function(cat){
|
|
var catItems=items.filter(function(it){return 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(function(it){
|
|
var tags=parseTags(it.tags).slice(0,2);
|
|
var hasOld=it.original_price>0 && it.original_price>it.price;
|
|
var 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(function(tag){return '<span class="menu-item-tag">'+esc(tag)+'</span>'}).join('')+'</div>'+
|
|
'<div class="menu-item-bottom">'+
|
|
'<div class="menu-item-meta">'+
|
|
'<div class="menu-item-price">'+
|
|
'<span class="now">\u00a5<small></small>'+money(it.price)+'</span>'+
|
|
(hasOld?'<span class="old">\u00a5'+money(it.original_price)+'</span>':'')+
|
|
'</div>'+
|
|
'<div class="menu-item-stat"><span class="star">\u2605 '+money(it.rating)+'</span> \u00b7 '+t('monthly_sales')+it.sales+'</div>'+
|
|
'</div>'+
|
|
(qty>0?'<div class="qty-control">'+
|
|
'<button class="minus" onclick="changeQty('+it.id+',-1,event)">\u2212</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>'+t('no_menu')+'</p></div>';
|
|
setupScrollSync();
|
|
}
|
|
|
|
var scrollSyncOn=true;
|
|
function setupScrollSync(){
|
|
var list=$('menuList');
|
|
list.onscroll=function(){
|
|
if(!scrollSyncOn) return;
|
|
var cats=state.menuData.categories||[];
|
|
var activeId=cats.length?cats[0].id:0;
|
|
for(var ci=0;ci<cats.length;ci++){
|
|
var c=cats[ci];
|
|
var sec=$('catsec-'+c.id);
|
|
if(sec && sec.offsetTop-60<=list.scrollTop){activeId=c.id}
|
|
}
|
|
document.querySelectorAll('.menu-cat-item').forEach(function(el){
|
|
el.classList.toggle('active',Number(el.dataset.cat)===activeId);
|
|
});
|
|
};
|
|
}
|
|
function scrollToCat(catId){
|
|
var sec=$('catsec-'+catId);
|
|
var list=$('menuList');
|
|
if(sec){
|
|
scrollSyncOn=false;
|
|
list.scrollTo({top:sec.offsetTop-10,behavior:'smooth'});
|
|
document.querySelectorAll('.menu-cat-item').forEach(function(el){
|
|
el.classList.toggle('active',Number(el.dataset.cat)===catId);
|
|
});
|
|
setTimeout(function(){scrollSyncOn=true},400);
|
|
}
|
|
}
|
|
|
|
/* ==========================================================
|
|
Cart
|
|
========================================================== */
|
|
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()}
|
|
var items=state.menuData.menu_items||[];
|
|
var item=items.find(function(it){return it.id===itemId});
|
|
if(!item) return;
|
|
var r=state.currentRestaurant;
|
|
if(state.cart && state.cart.restaurantId!==r.id && Object.keys(state.cart.items).length){
|
|
if(!confirm(tParam('cross_store_confirm',{name: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(tParam('added_item',{name: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(t('clear_cart_confirm'))) return;
|
|
state.cart=null;
|
|
renderMenu();
|
|
renderCart();
|
|
toggleCartDetail(false);
|
|
toast(t('cart_cleared'),'warn');
|
|
}
|
|
function cartCount(){
|
|
if(!state.cart) return 0;
|
|
return Object.values(state.cart.items).reduce(function(s,v){return s+v.qty},0);
|
|
}
|
|
function cartTotal(){
|
|
if(!state.cart) return 0;
|
|
return Object.values(state.cart.items).reduce(function(s,v){return s+v.item.price*v.qty},0);
|
|
}
|
|
function renderCart(){
|
|
var count=cartCount();
|
|
var total=cartTotal();
|
|
var 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='\u00a5<small></small>'+money(total);
|
|
var fee=state.cart?state.cart.deliveryFee:0;
|
|
var min=state.cart?state.cart.minOrder:0;
|
|
var canCheckout=total>=min;
|
|
if(canCheckout){
|
|
$('cartSub').textContent=tParam('delivery_fee_text',{fee:money(fee)});
|
|
$('checkoutBtn').textContent=t('checkout');
|
|
}else{
|
|
$('cartSub').textContent=tParam('need_more',{diff:money(min-total)});
|
|
$('checkoutBtn').textContent=tParam('checkout_disabled',{diff:money(min-total)});
|
|
}
|
|
$('cartSub').style.display='';
|
|
$('checkoutBtn').classList.toggle('disabled',!canCheckout);
|
|
}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>'+t('cart_empty')+'</p><p class="sub">'+t('cart_empty_hint')+'</p></div>';
|
|
return;
|
|
}
|
|
$('cartDetailTitle').textContent=tParam('cart_with_name',{name:state.cart.restaurantName});
|
|
var html=Object.values(state.cart.items).map(function(v){
|
|
var 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">\u00a5'+money(it.price)+'</span>'+
|
|
'<div class="qty-control">'+
|
|
'<button class="minus" onclick="changeQty('+it.id+',-1)">\u2212</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){
|
|
var show = force===undefined ? !$('cartDetail').classList.contains('show') : force;
|
|
if(show && (!state.cart||!Object.keys(state.cart.items).length)){toast(t('cart_empty'),'warn');return}
|
|
$('cartDetail').classList.toggle('show',show);
|
|
$('cartDetailMask').classList.toggle('show',show);
|
|
}
|
|
function scrollToCart(){
|
|
if(!state.cart||!Object.keys(state.cart.items).length){toast(t('cart_empty'),'warn');return}
|
|
toggleCartDetail(true);
|
|
}
|
|
function checkout(){
|
|
if(!state.cart||!Object.keys(state.cart.items).length) return;
|
|
var total=cartTotal();
|
|
if(total<(state.cart?state.cart.minOrder:0)){toast(t('not_reach_min'),'warn');return}
|
|
toast(tParam('order_success',{total:money(total)}),'success');
|
|
state.cart=null;
|
|
renderMenu();
|
|
renderCart();
|
|
toggleCartDetail(false);
|
|
}
|
|
|
|
/* ==========================================================
|
|
onLangChange: re-render all dynamic content
|
|
========================================================== */
|
|
function onLangChange(lang){
|
|
renderHeroTags();
|
|
renderCategories();
|
|
renderFeatured();
|
|
renderRestaurantList();
|
|
renderCart();
|
|
if(state.currentRestaurant){
|
|
renderDetail();
|
|
}
|
|
}
|
|
|
|
/* ==========================================================
|
|
Bootstrap
|
|
========================================================== */
|
|
document.addEventListener('keydown',function(e){
|
|
if(e.key==='Escape') closeDetail();
|
|
});
|
|
applyI18n();
|
|
init();
|
|
</script>
|
|
</body>
|
|
</html> |