feat: TakeoutSaaS 原型项目首版

包含商品管理(9页)和门店管理(8页)完整原型:
- 全局设计系统(Swiss Modernism + Bento Box 风格)
- 商品列表/详情/分类/规格/加料/套餐/标签/时段/批量工具
- 门店列表/营业时间/配送/自提/堂食/费用/员工/资质
- 通用商品选择器弹窗、SKU矩阵、渠道按分类管理
This commit is contained in:
2026-02-12 10:28:37 +08:00
commit 50147887d8
19 changed files with 8188 additions and 0 deletions

1435
index.html Normal file

File diff suppressed because it is too large Load Diff

227
pages/dashboard.html Normal file
View File

@@ -0,0 +1,227 @@
<!-- 工作台 Dashboard -->
<style>
.dashboard { display:flex; flex-direction:column; gap:16px; }
.dash-row { display:grid; gap:16px; }
.dash-row.kpi { grid-template-columns:repeat(4,1fr); }
.dash-row.mid { grid-template-columns:2fr 1fr; }
.dash-row.bot { grid-template-columns:1fr 1fr 1fr; }
.kpi-card { border-radius:10px; padding:20px 24px; color:#fff; position:relative; overflow:hidden; min-height:120px; display:flex; flex-direction:column; justify-content:space-between; box-shadow:var(--g-shadow-sm); transition:var(--g-transition); }
.kpi-card:hover { box-shadow:var(--g-shadow-md); transform:translateY(-2px); }
.kpi-card::before { content:""; position:absolute; top:-30px; right:-30px; width:100px; height:100px; border-radius:50%; background:rgba(255,255,255,0.1); }
.kpi-card::after { content:""; position:absolute; bottom:-20px; right:30px; width:60px; height:60px; border-radius:50%; background:rgba(255,255,255,0.08); }
.kpi-card.c1 { background:linear-gradient(135deg,#1890ff,#096dd9); }
.kpi-card.c2 { background:linear-gradient(135deg,#22c55e,#16a34a); }
.kpi-card.c3 { background:linear-gradient(135deg,#f59e0b,#d97706); }
.kpi-card.c4 { background:linear-gradient(135deg,#722ed1,#531dab); }
.kpi-top { display:flex; align-items:center; justify-content:space-between; }
.kpi-label { font-size:13px; opacity:0.85; }
.kpi-icon { width:40px; height:40px; border-radius:10px; background:rgba(255,255,255,0.2); display:flex; align-items:center; justify-content:center; }
.kpi-value { font-size:34px; font-weight:800; letter-spacing:-1px; margin:8px 0 4px; }
.kpi-sub { font-size:12px; opacity:0.75; display:flex; align-items:center; gap:4px; }
.kpi-sub .up { color:#b7eb8f; }
.kpi-sub .down { color:#fecaca; }
.dash-card-more { font-size:12px; color:#9ca3af; font-weight:400; cursor:pointer; transition:var(--g-transition); }
.dash-card-more:hover { color:var(--primary); }
.bar-chart { display:flex; align-items:flex-end; gap:10px; height:200px; padding-top:10px; }
.bar-chart-wrap { display:flex; height:200px; }
.bar-y-axis { display:flex; flex-direction:column; justify-content:space-between; padding-right:8px; font-size:11px; color:#9ca3af; text-align:right; min-width:36px; padding-bottom:22px; }
.bar-chart-body { flex:1; display:flex; align-items:flex-end; gap:10px; }
.bar-col { flex:1; display:flex; flex-direction:column; align-items:center; gap:6px; }
.bar-wrap { width:100%; display:flex; gap:4px; justify-content:center; height:170px; align-items:flex-end; }
.bar { width:18px; border-radius:4px 4px 0 0; transition:height 0.6s cubic-bezier(0.2,0,0,1); cursor:pointer; }
.bar:hover { opacity:0.85; }
.bar.income { background:linear-gradient(180deg,#1890ff,#69c0ff); }
.bar.orders { background:linear-gradient(180deg,#22c55e,#86efac); }
.bar-label { font-size:11px; color:#9ca3af; }
.donut-wrap { display:flex; align-items:center; gap:24px; padding:10px 0; }
.donut { width:140px; height:140px; border-radius:50%; position:relative; flex-shrink:0; }
.donut-center { position:absolute; inset:30px; background:#fff; border-radius:50%; display:flex; flex-direction:column; align-items:center; justify-content:center; }
.donut-center .num { font-size:24px; font-weight:800; color:#1a1a2e; }
.donut-center .txt { font-size:11px; color:#9ca3af; }
.donut-legend { display:flex; flex-direction:column; gap:10px; flex:1; }
.legend-item { display:flex; align-items:center; gap:8px; font-size:13px; color:#1a1a2e; }
.legend-dot { width:8px; height:8px; border-radius:2px; flex-shrink:0; }
.legend-val { margin-left:auto; font-weight:600; }
.rank-list { display:flex; flex-direction:column; gap:12px; }
.rank-item { display:flex; align-items:center; gap:10px; }
.rank-num { width:20px; height:20px; border-radius:4px; display:flex; align-items:center; justify-content:center; font-size:11px; font-weight:600; color:#fff; flex-shrink:0; }
.rank-num.t1 { background:linear-gradient(135deg,#ef4444,#f97316); }
.rank-num.t2 { background:linear-gradient(135deg,#f59e0b,#fbbf24); }
.rank-num.t3 { background:linear-gradient(135deg,#1890ff,#69c0ff); }
.rank-num.tn { background:#e5e7eb; color:#4b5563; }
.rank-name { font-size:13px; color:#1a1a2e; flex:1; }
.rank-bar-bg { width:100px; height:6px; background:#e5e7eb; border-radius:3px; overflow:hidden; }
.rank-bar-fill { height:100%; border-radius:3px; background:linear-gradient(90deg,#1890ff,#69c0ff); }
.rank-count { font-size:12px; color:#9ca3af; width:40px; text-align:right; font-weight:600; }
.feed-list { display:flex; flex-direction:column; max-height:320px; overflow-y:auto; }
.feed-list::-webkit-scrollbar { width:3px; }
.feed-list::-webkit-scrollbar-thumb { background:#e5e7eb; border-radius:2px; }
.feed-item { display:flex; align-items:center; gap:10px; padding:10px 0; border-bottom:1px solid #f3f4f6; transition:var(--g-transition); }
.feed-item:hover { background:color-mix(in srgb, var(--primary) 3%, #fff); }
.feed-item:last-child { border-bottom:none; }
.feed-avatar { width:32px; height:32px; border-radius:50%; background:#f8f9fb; display:flex; align-items:center; justify-content:center; font-size:12px; flex-shrink:0; color:#4b5563; }
.feed-info { flex:1; }
.feed-info .top { font-size:13px; color:#1a1a2e; display:flex; justify-content:space-between; }
.feed-info .bot { font-size:11px; color:#9ca3af; margin-top:3px; }
.feed-badge { padding:2px 8px; border-radius:6px; font-size:11px; white-space:nowrap; font-weight:600; }
.feed-badge.new { background:#eff6ff; color:#2563eb; }
.feed-badge.cooking { background:#fffbeb; color:#d97706; }
.feed-badge.delivering { background:#f0fdf4; color:#16a34a; }
.feed-badge.done { background:#f8f9fb; color:#9ca3af; }
.alert-list { display:flex; flex-direction:column; gap:8px; }
.alert-item { display:flex; align-items:center; gap:10px; padding:10px 12px; border-radius:8px; font-size:13px; transition:var(--g-transition); cursor:pointer; }
.alert-item:hover { filter:brightness(0.97); transform:translateX(2px); }
.alert-item.warn { background:#fffbeb; border:1px solid #fde68a; color:#92400e; }
.alert-item.danger { background:#fef2f2; border:1px solid #fecaca; color:#991b1b; }
.alert-item.info { background:#eff6ff; border:1px solid #bfdbfe; color:#1e40af; }
.alert-icon { flex-shrink:0; }
.quota-item { display:flex; flex-direction:column; gap:6px; margin-bottom:14px; }
.quota-item:last-child { margin-bottom:0; }
.quota-head { display:flex; justify-content:space-between; font-size:13px; }
.quota-name { color:#1a1a2e; font-weight:500; }
.quota-val { color:#9ca3af; }
.quota-bar { height:8px; background:#e5e7eb; border-radius:4px; overflow:hidden; }
.quota-fill { height:100%; border-radius:4px; transition:width 0.6s; }
.quota-fill.ok { background:linear-gradient(90deg,#1890ff,#69c0ff); }
.quota-fill.mid { background:linear-gradient(90deg,#f59e0b,#fbbf24); }
.quota-fill.high { background:linear-gradient(90deg,#ef4444,#f97316); }
@keyframes fadeInUp { from { opacity:0; transform:translateY(10px); } to { opacity:1; transform:translateY(0); } }
.dashboard .dash-row > * { animation: fadeInUp 0.4s ease both; }
.dashboard .dash-row > *:nth-child(2) { animation-delay:0.05s; }
.dashboard .dash-row > *:nth-child(3) { animation-delay:0.1s; }
.dashboard .dash-row > *:nth-child(4) { animation-delay:0.15s; }
</style>
<div class="dashboard">
<!-- Date Picker -->
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:-8px">
<div style="font-size:15px;font-weight:600;color:#1a1a2e">数据概览</div>
<div style="display:flex;align-items:center;gap:8px">
<span style="font-size:13px;color:#666">日期</span>
<input type="date" class="g-input" style="width:140px;height:30px;font-size:12px" value="2025-01-15">
<span style="font-size:13px;color:#999"></span>
<input type="date" class="g-input" style="width:140px;height:30px;font-size:12px" value="2025-01-15">
</div>
</div>
<!-- KPI -->
<div class="dash-row kpi">
<div class="kpi-card c1">
<div class="kpi-top"><span class="kpi-label">今日订单</span><div class="kpi-icon"><i data-lucide="shopping-bag" style="width:20px;height:20px;"></i></div></div>
<div class="kpi-value">386</div>
<div class="kpi-sub"><span class="up">+12.5%</span> 较昨日</div>
</div>
<div class="kpi-card c2">
<div class="kpi-top"><span class="kpi-label">今日营收</span><div class="kpi-icon"><i data-lucide="trending-up" style="width:20px;height:20px;"></i></div></div>
<div class="kpi-value">&yen;12,680</div>
<div class="kpi-sub"><span class="up">+8.3%</span> 较昨日</div>
</div>
<div class="kpi-card c3">
<div class="kpi-top"><span class="kpi-label">客单价</span><div class="kpi-icon"><i data-lucide="receipt" style="width:20px;height:20px;"></i></div></div>
<div class="kpi-value">&yen;32.8</div>
<div class="kpi-sub"><span class="down">-2.1%</span> 较昨日</div>
</div>
<div class="kpi-card c4">
<div class="kpi-top"><span class="kpi-label">新增客户</span><div class="kpi-icon"><i data-lucide="user-plus" style="width:20px;height:20px;"></i></div></div>
<div class="kpi-value">57</div>
<div class="kpi-sub"><span class="up">+23.9%</span> 较昨日</div>
</div>
</div>
<!-- Revenue Chart + Order Status -->
<div class="dash-row mid">
<div class="g-card" style="padding:0;">
<div class="g-card-title" style="padding:14px 18px; margin-bottom:0; border-bottom:1px solid #f5f5f5; display:flex; align-items:center; justify-content:space-between;">近7日营收趋势<span class="dash-card-more">查看详情 &rarr;</span></div>
<div style="padding:16px 18px;">
<div class="bar-chart-wrap">
<div class="bar-y-axis"><span>¥15k</span><span>¥12k</span><span>¥9k</span><span>¥6k</span><span>¥3k</span><span>0</span></div>
<div class="bar-chart">
<div class="bar-col"><div class="bar-wrap"><div class="bar income" style="height:60%;"></div><div class="bar orders" style="height:45%;"></div></div><span class="bar-label">周一</span></div>
<div class="bar-col"><div class="bar-wrap"><div class="bar income" style="height:75%;"></div><div class="bar orders" style="height:55%;"></div></div><span class="bar-label">周二</span></div>
<div class="bar-col"><div class="bar-wrap"><div class="bar income" style="height:55%;"></div><div class="bar orders" style="height:40%;"></div></div><span class="bar-label">周三</span></div>
<div class="bar-col"><div class="bar-wrap"><div class="bar income" style="height:85%;"></div><div class="bar orders" style="height:65%;"></div></div><span class="bar-label">周四</span></div>
<div class="bar-col"><div class="bar-wrap"><div class="bar income" style="height:95%;"></div><div class="bar orders" style="height:72%;"></div></div><span class="bar-label">周五</span></div>
<div class="bar-col"><div class="bar-wrap"><div class="bar income" style="height:100%;"></div><div class="bar orders" style="height:80%;"></div></div><span class="bar-label">周六</span></div>
<div class="bar-col"><div class="bar-wrap"><div class="bar income" style="height:90%;"></div><div class="bar orders" style="height:70%;"></div></div><span class="bar-label">周日</span></div>
</div>
</div>
<div style="display:flex;gap:16px;justify-content:center;margin-top:10px;font-size:12px;color:#999;">
<span><span style="display:inline-block;width:10px;height:10px;border-radius:2px;background:#1890ff;margin-right:4px;"></span>营收</span>
<span><span style="display:inline-block;width:10px;height:10px;border-radius:2px;background:#52c41a;margin-right:4px;"></span>订单量</span>
</div>
</div>
</div>
<div class="g-card" style="padding:0;">
<div class="g-card-title" style="padding:14px 18px; margin-bottom:0; border-bottom:1px solid #f5f5f5; display:flex; align-items:center; justify-content:space-between;">今日订单状态</div>
<div style="padding:16px 18px;">
<div class="donut-wrap">
<div class="donut" style="background:conic-gradient(#1890ff 0% 15%,#fa8c16 15% 30%,#52c41a 30% 55%,#722ed1 55% 75%,#d9d9d9 75% 100%);">
<div class="donut-center"><span class="num">386</span><span class="txt">总订单</span></div>
</div>
<div class="donut-legend">
<div class="legend-item"><span class="legend-dot" style="background:#1890ff;"></span>待接单<span class="legend-val">58</span></div>
<div class="legend-item"><span class="legend-dot" style="background:#fa8c16;"></span>制作中<span class="legend-val">59</span></div>
<div class="legend-item"><span class="legend-dot" style="background:#52c41a;"></span>配送中<span class="legend-val">96</span></div>
<div class="legend-item"><span class="legend-dot" style="background:#722ed1;"></span>已完成<span class="legend-val">77</span></div>
<div class="legend-item"><span class="legend-dot" style="background:#d9d9d9;"></span>已取消<span class="legend-val">96</span></div>
</div>
</div>
</div>
</div>
</div>
<!-- Ranking + Feed + Alerts -->
<div class="dash-row bot">
<div class="g-card" style="padding:0;">
<div class="g-card-title" style="padding:14px 18px; margin-bottom:0; border-bottom:1px solid #f5f5f5; display:flex; align-items:center; justify-content:space-between;">热销菜品 TOP5<span class="dash-card-more">更多 &rarr;</span></div>
<div style="padding:16px 18px;">
<div class="rank-list">
<div class="rank-item"><span class="rank-num t1">1</span><span class="rank-name">招牌红烧肉饭</span><div class="rank-bar-bg"><div class="rank-bar-fill" style="width:100%;"></div></div><span class="rank-count">128</span></div>
<div class="rank-item"><span class="rank-num t2">2</span><span class="rank-name">香辣鸡腿堡套餐</span><div class="rank-bar-bg"><div class="rank-bar-fill" style="width:78%;"></div></div><span class="rank-count">99</span></div>
<div class="rank-item"><span class="rank-num t3">3</span><span class="rank-name">番茄牛腩面</span><div class="rank-bar-bg"><div class="rank-bar-fill" style="width:62%;"></div></div><span class="rank-count">79</span></div>
<div class="rank-item"><span class="rank-num tn">4</span><span class="rank-name">鱼香肉丝盖饭</span><div class="rank-bar-bg"><div class="rank-bar-fill" style="width:45%;"></div></div><span class="rank-count">58</span></div>
<div class="rank-item"><span class="rank-num tn">5</span><span class="rank-name">冰柠檬红茶</span><div class="rank-bar-bg"><div class="rank-bar-fill" style="width:35%;"></div></div><span class="rank-count">45</span></div>
</div>
</div>
</div>
<div class="g-card" style="padding:0;">
<div class="g-card-title" style="padding:14px 18px; margin-bottom:0; border-bottom:1px solid #f5f5f5; display:flex; align-items:center; justify-content:space-between;">实时订单<span class="dash-card-more">全部订单 &rarr;</span></div>
<div style="padding:8px 18px;">
<div class="feed-list">
<div class="feed-item"><div class="feed-avatar"></div><div class="feed-info"><div class="top"><span>#20260211001</span><span>&yen;45.5</span></div><div class="bot">张先生 &middot; 朝阳店 &middot; 2分钟前</div></div><span class="feed-badge new">待接单</span></div>
<div class="feed-item"><div class="feed-avatar"></div><div class="feed-info"><div class="top"><span>#20260211002</span><span>&yen;68.0</span></div><div class="bot">李女士 &middot; 海淀店 &middot; 5分钟前</div></div><span class="feed-badge cooking">制作中</span></div>
<div class="feed-item"><div class="feed-avatar"></div><div class="feed-info"><div class="top"><span>#20260211003</span><span>&yen;32.0</span></div><div class="bot">王先生 &middot; 望京店 &middot; 12分钟前</div></div><span class="feed-badge delivering">配送中</span></div>
<div class="feed-item"><div class="feed-avatar"></div><div class="feed-info"><div class="top"><span>#20260211004</span><span>&yen;55.5</span></div><div class="bot">赵女士 &middot; 朝阳店 &middot; 18分钟前</div></div><span class="feed-badge done">已完成</span></div>
<div class="feed-item"><div class="feed-avatar"></div><div class="feed-info"><div class="top"><span>#20260211005</span><span>&yen;28.0</span></div><div class="bot">刘先生 &middot; 通州店 &middot; 25分钟前</div></div><span class="feed-badge done">已完成</span></div>
</div>
</div>
</div>
<div class="g-card" style="padding:0;">
<div class="g-card-title" style="padding:14px 18px; margin-bottom:0; border-bottom:1px solid #f5f5f5; display:flex; align-items:center; justify-content:space-between;">运营提醒</div>
<div style="padding:16px 18px;">
<div class="alert-list">
<div class="alert-item danger"><i data-lucide="alert-triangle" style="width:16px;height:16px;" class="alert-icon"></i>3款商品库存不足请及时补货</div>
<div class="alert-item warn"><i data-lucide="clock" style="width:16px;height:16px;" class="alert-icon"></i>食品经营许可证将于30天后到期</div>
<div class="alert-item warn"><i data-lucide="message-square" style="width:16px;height:16px;" class="alert-icon"></i>5条新评价待回复</div>
<div class="alert-item info"><i data-lucide="gift" style="width:16px;height:16px;" class="alert-icon"></i>满减活动明日到期,建议续期</div>
</div>
<div style="margin-top:16px;">
<div style="font-size:13px;font-weight:600;color:#333;margin-bottom:10px;">套餐配额</div>
<div class="quota-item"><div class="quota-head"><span class="quota-name">门店数量</span><span class="quota-val">5 / 10</span></div><div class="quota-bar"><div class="quota-fill ok" style="width:50%;"></div></div></div>
<div class="quota-item"><div class="quota-head"><span class="quota-name">员工账号</span><span class="quota-val">18 / 20</span></div><div class="quota-bar"><div class="quota-fill high" style="width:90%;"></div></div></div>
<div class="quota-item"><div class="quota-head"><span class="quota-name">短信额度</span><span class="quota-val">320 / 1000</span></div><div class="quota-bar"><div class="quota-fill ok" style="width:32%;"></div></div></div>
<div class="quota-item"><div class="quota-head"><span class="quota-name">存储空间</span><span class="quota-val">1.8G / 5G</span></div><div class="quota-bar"><div class="quota-fill mid" style="width:36%;"></div></div></div>
</div>
</div>
</div>
</div>
</div>

303
pages/product-addons.html Normal file
View File

@@ -0,0 +1,303 @@
<!-- 加料管理页 -->
<style>
.page-addons { max-width:960px; }
.pad-toolbar { display:flex; align-items:center; gap:12px; margin-bottom:16px; box-shadow:var(--g-shadow-sm); border-radius:10px; padding:12px 16px; background:#fff; flex-wrap:wrap; }
.pad-search { position:relative; }
.pad-search input { height:34px; padding:0 10px 0 32px; border:1px solid #e5e7eb; border-radius:8px; font-size:13px; outline:none; width:200px; transition:var(--g-transition); }
.pad-search input:focus { border-color:var(--primary); box-shadow:0 0 0 3px color-mix(in srgb, var(--primary) 12%, transparent); }
.pad-search i { position:absolute; left:9px; top:50%; transform:translateY(-50%); color:#bbb; pointer-events:none; }
.pad-stats { display:flex; gap:24px; margin-bottom:16px; padding:10px 16px; background:#fff; border-radius:10px; box-shadow:var(--g-shadow-sm); font-size:13px; color:#4b5563; }
.pad-stats span { display:flex; align-items:center; gap:6px; }
.pad-stats strong { color:#1a1a2e; font-weight:600; }
.pad-card { background:#fff; border-radius:10px; border:none; box-shadow:var(--g-shadow-sm); padding:20px; margin-bottom:16px; transition:var(--g-transition); }
.pad-card:hover { box-shadow:var(--g-shadow-md); }
.pad-card.disabled { opacity:0.5; }
.pad-card-header { display:flex; align-items:center; gap:10px; margin-bottom:14px; }
.pad-card-name { font-size:14px; font-weight:600; color:#1a1a2e; }
.pad-tag-required { background:#fef2f2; color:#ef4444; }
.pad-tag-optional { background:#eff6ff; color:#3b82f6; }
.pad-tag-disabled { background:#f3f4f6; color:#9ca3af; }
.pad-rule { font-size:12px; color:#9ca3af; margin-left:4px; }
.pad-table { width:100%; border-collapse:collapse; font-size:13px; margin-bottom:12px; }
.pad-table th { background:#f8f9fb; padding:8px 12px; text-align:left; font-weight:600; color:#6b7280; border-bottom:1px solid #f3f4f6; }
.pad-table td { padding:8px 12px; border-bottom:1px solid #f3f4f6; color:#1a1a2e; }
.pad-table tr:last-child td { border-bottom:none; }
.pad-table tr:hover td { background:color-mix(in srgb, var(--primary) 3%, #fff); }
.pad-stock-ok { color:#22c55e; font-size:12px; font-weight:600; }
.pad-stock-low { color:#f59e0b; font-size:12px; font-weight:600; }
.pad-assoc { font-size:12px; color:#9ca3af; margin-bottom:12px; }
.pad-card-footer { display:flex; align-items:center; gap:8px; border-top:1px solid #f3f4f6; padding-top:12px; }
.pad-sel-row { display:flex; align-items:center; gap:8px; margin-bottom:16px; }
.pad-sel-row span { font-size:13px; color:#4b5563; font-weight:500; }
.pad-opt-row { display:flex; align-items:center; gap:8px; margin-bottom:10px; }
.pad-opt-row input { height:34px; padding:0 10px; border:1px solid #e5e7eb; border-radius:8px; font-size:13px; outline:none; transition:var(--g-transition); }
.pad-opt-row input:focus { border-color:var(--primary); box-shadow:0 0 0 3px color-mix(in srgb, var(--primary) 12%, transparent); }
.pad-opt-name { flex:1; }
.pad-opt-price { width:80px; }
.pad-opt-stock { width:80px; }
.pad-opt-del { width:30px; height:30px; border:none; background:none; cursor:pointer; color:#9ca3af; display:flex; align-items:center; justify-content:center; border-radius:8px; transition:var(--g-transition); }
.pad-opt-del:hover { background:#fef2f2; color:#ef4444; }
.pad-opt-list-header { display:flex; align-items:center; gap:8px; margin-bottom:8px; font-size:12px; color:#6b7280; font-weight:600; }
.pad-opt-list-header .h-name { flex:1; }
.pad-opt-list-header .h-price { width:80px; }
.pad-opt-list-header .h-stock { width:80px; }
.pad-opt-list-header .h-act { width:30px; }
.pad-input-num { width:80px; }
</style>
<div class="page-addons">
<div class="pad-toolbar">
<select class="g-select" style="width:200px;">
<option>老三家外卖(朝阳店)</option>
<option>老三家外卖(海淀店)</option>
<option>老三家外卖(望京店)</option>
<option>老三家外卖(通州店)</option>
<option>老三家外卖(丰台店)</option>
</select>
<div class="pad-search">
<i data-lucide="search" style="width:14px;height:14px;"></i>
<input type="text" placeholder="搜索加料组名称…" oninput="searchAddons(this.value)" />
</div>
<div style="flex:1;"></div>
<button class="g-btn g-btn-primary" onclick="openAddonDrawer('add')"><i data-lucide="plus" style="width:14px;height:14px;"></i>添加加料组</button>
</div>
<div class="pad-stats" id="padStats">
<span>加料组 <strong>5</strong></span>
<span>选项总数 <strong>20</strong></span>
<span>关联商品 <strong>41</strong></span>
</div>
<!-- 加料 -->
<div class="pad-card" data-name="加料">
<div class="pad-card-header">
<span class="pad-card-name">加料</span>
<span class="g-tag pad-tag-optional">可选</span>
<span class="pad-rule">最少选0最多选3</span>
</div>
<table class="pad-table">
<thead><tr><th>选项名称</th><th>加价</th><th>库存</th><th>操作</th></tr></thead>
<tbody>
<tr><td>珍珠</td><td>+¥3.00</td><td><span class="pad-stock-ok">充足</span></td><td><span class="g-action">改名</span><span class="g-action g-action-danger">移除</span></td></tr>
<tr><td>椰果</td><td>+¥3.00</td><td><span class="pad-stock-ok">充足</span></td><td><span class="g-action">改名</span><span class="g-action g-action-danger">移除</span></td></tr>
<tr><td>芋圆</td><td>+¥4.00</td><td><span class="pad-stock-low">偏少</span></td><td><span class="g-action">改名</span><span class="g-action g-action-danger">移除</span></td></tr>
<tr><td>芝士奶盖</td><td>+¥5.00</td><td><span class="pad-stock-ok">充足</span></td><td><span class="g-action">改名</span><span class="g-action g-action-danger">移除</span></td></tr>
<tr><td>红豆</td><td>+¥3.00</td><td><span class="pad-stock-low">偏少</span></td><td><span class="g-action">改名</span><span class="g-action g-action-danger">移除</span></td></tr>
</tbody>
</table>
<div class="pad-assoc">已关联 8 个商品</div>
<div class="pad-card-footer">
<span class="g-action" onclick="openAddonDrawer('edit','加料')">编辑</span>
<span class="g-action" onclick="openProductPicker({title:'关联商品',onConfirm:function(){}})">关联商品</span>
<span class="g-action g-action-danger">删除</span>
</div>
</div>
<!-- 配菜 -->
<div class="pad-card" data-name="配菜">
<div class="pad-card-header">
<span class="pad-card-name">配菜</span>
<span class="g-tag pad-tag-optional">可选</span>
<span class="pad-rule">最少选0最多选2</span>
</div>
<table class="pad-table">
<thead><tr><th>选项名称</th><th>加价</th><th>库存</th><th>操作</th></tr></thead>
<tbody>
<tr><td>卤蛋</td><td>+¥2.00</td><td><span class="pad-stock-ok">充足</span></td><td><span class="g-action">改名</span><span class="g-action g-action-danger">移除</span></td></tr>
<tr><td>海带</td><td>+¥1.00</td><td><span class="pad-stock-ok">充足</span></td><td><span class="g-action">改名</span><span class="g-action g-action-danger">移除</span></td></tr>
<tr><td>豆腐</td><td>+¥1.00</td><td><span class="pad-stock-low">偏少</span></td><td><span class="g-action">改名</span><span class="g-action g-action-danger">移除</span></td></tr>
<tr><td>午餐肉</td><td>+¥4.00</td><td><span class="pad-stock-ok">充足</span></td><td><span class="g-action">改名</span><span class="g-action g-action-danger">移除</span></td></tr>
</tbody>
</table>
<div class="pad-assoc">已关联 12 个商品</div>
<div class="pad-card-footer">
<span class="g-action" onclick="openAddonDrawer('edit','配菜')">编辑</span>
<span class="g-action" onclick="openProductPicker({title:'关联商品',onConfirm:function(){}})">关联商品</span>
<span class="g-action g-action-danger">删除</span>
</div>
</div>
<!-- 主食升级 -->
<div class="pad-card" data-name="主食升级">
<div class="pad-card-header">
<span class="pad-card-name">主食升级</span>
<span class="g-tag pad-tag-required">必选</span>
<span class="pad-rule">必须选1</span>
</div>
<table class="pad-table">
<thead><tr><th>选项名称</th><th>加价</th><th>库存</th><th>操作</th></tr></thead>
<tbody>
<tr><td>米饭(默认)</td><td>+¥0.00</td><td><span class="pad-stock-ok">充足</span></td><td><span class="g-action">改名</span><span class="g-action g-action-danger">移除</span></td></tr>
<tr><td>炒饭</td><td>+¥3.00</td><td><span class="pad-stock-ok">充足</span></td><td><span class="g-action">改名</span><span class="g-action g-action-danger">移除</span></td></tr>
<tr><td>拌面</td><td>+¥3.00</td><td><span class="pad-stock-ok">充足</span></td><td><span class="g-action">改名</span><span class="g-action g-action-danger">移除</span></td></tr>
</tbody>
</table>
<div class="pad-assoc">已关联 15 个商品</div>
<div class="pad-card-footer">
<span class="g-action" onclick="openAddonDrawer('edit','主食升级')">编辑</span>
<span class="g-action" onclick="openProductPicker({title:'关联商品',onConfirm:function(){}})">关联商品</span>
<span class="g-action g-action-danger">删除</span>
</div>
</div>
<!-- 锅底选择 -->
<div class="pad-card" data-name="锅底选择">
<div class="pad-card-header">
<span class="pad-card-name">锅底选择</span>
<span class="g-tag pad-tag-required">必选</span>
<span class="pad-rule">必须选1</span>
</div>
<table class="pad-table">
<thead><tr><th>选项名称</th><th>加价</th><th>库存</th><th>操作</th></tr></thead>
<tbody>
<tr><td>番茄锅</td><td>+¥0.00</td><td><span class="pad-stock-ok">充足</span></td><td><span class="g-action">改名</span><span class="g-action g-action-danger">移除</span></td></tr>
<tr><td>麻辣锅</td><td>+¥8.00</td><td><span class="pad-stock-ok">充足</span></td><td><span class="g-action">改名</span><span class="g-action g-action-danger">移除</span></td></tr>
<tr><td>菌汤锅</td><td>+¥5.00</td><td><span class="pad-stock-low">偏少</span></td><td><span class="g-action">改名</span><span class="g-action g-action-danger">移除</span></td></tr>
<tr><td>鸳鸯锅</td><td>+¥12.00</td><td><span class="pad-stock-ok">充足</span></td><td><span class="g-action">改名</span><span class="g-action g-action-danger">移除</span></td></tr>
</tbody>
</table>
<div class="pad-assoc">已关联 6 个商品</div>
<div class="pad-card-footer">
<span class="g-action" onclick="openAddonDrawer('edit','锅底选择')">编辑</span>
<span class="g-action" onclick="openProductPicker({title:'关联商品',onConfirm:function(){}})">关联商品</span>
<span class="g-action g-action-danger">删除</span>
</div>
</div>
<!-- 饮品加料(已停用) -->
<div class="pad-card disabled" data-name="饮品加料">
<div class="pad-card-header">
<span class="pad-card-name">饮品加料</span>
<span class="g-tag pad-tag-disabled">已停用</span>
<span class="pad-rule">最少选0最多选2</span>
</div>
<table class="pad-table">
<thead><tr><th>选项名称</th><th>加价</th><th>库存</th><th>操作</th></tr></thead>
<tbody>
<tr><td>冰淇淋球</td><td>+¥6.00</td><td><span class="pad-stock-ok">充足</span></td><td><span class="g-action">改名</span><span class="g-action g-action-danger">移除</span></td></tr>
<tr><td>奶油顶</td><td>+¥4.00</td><td><span class="pad-stock-ok">充足</span></td><td><span class="g-action">改名</span><span class="g-action g-action-danger">移除</span></td></tr>
<tr><td>巧克力碎</td><td>+¥3.00</td><td><span class="pad-stock-ok">充足</span></td><td><span class="g-action">改名</span><span class="g-action g-action-danger">移除</span></td></tr>
<tr><td>抹茶粉</td><td>+¥2.00</td><td><span class="pad-stock-low">偏少</span></td><td><span class="g-action">改名</span><span class="g-action g-action-danger">移除</span></td></tr>
</tbody>
</table>
<div class="pad-assoc">已关联 0 个商品</div>
<div class="pad-card-footer">
<span class="g-action" onclick="openAddonDrawer('edit','饮品加料')">编辑</span>
<span class="g-action">启用</span>
<span class="g-action g-action-danger">删除</span>
</div>
</div>
</div>
<!-- 添加/编辑加料组 Drawer -->
<div class="g-drawer-mask" id="padDrawerMask" onclick="closeAddonDrawer()"></div>
<div class="g-drawer" id="padDrawer" style="width:520px;">
<div class="g-drawer-hd">
<span class="g-drawer-title" id="padDrawerTitle">添加加料组</span>
<button class="g-drawer-close" onclick="closeAddonDrawer()"><i data-lucide="x" style="width:16px;height:16px;"></i></button>
</div>
<div class="g-drawer-bd">
<div class="g-form-group">
<label class="g-form-label required">组名称</label>
<input class="g-input" type="text" id="padGroupName" placeholder="请输入加料组名称,如:加料、配菜" />
</div>
<div style="display:flex; align-items:center; gap:10px; margin-bottom:16px;">
<button class="g-toggle on" id="padRequiredToggle" onclick="toggleSwitch(this)"></button>
<span class="g-toggle-label">是否必选</span>
</div>
<div class="pad-sel-row">
<span>选择数量:最少</span>
<input class="g-input pad-input-num" type="number" id="padMin" value="0" min="0" placeholder="如0" />
<span>最多</span>
<input class="g-input pad-input-num" type="number" id="padMax" value="3" min="1" placeholder="如3" />
</div>
<div class="g-form-group">
<label class="g-form-label required">选项列表</label>
<div class="g-hint" style="margin-bottom:8px;">每个选项可设置加价和库存,加价为 0 表示免费</div>
<div class="pad-opt-list-header">
<span class="h-name">名称</span>
<span class="h-price">加价(¥)</span>
<span class="h-stock">库存</span>
<span class="h-act"></span>
</div>
<div id="padOptList">
<div class="pad-opt-row">
<input class="pad-opt-name" type="text" placeholder="如:珍珠" value="珍珠" />
<input class="pad-opt-price" type="number" placeholder="如3.00" value="3" min="0" step="0.01" />
<input class="pad-opt-stock" type="number" placeholder="库存数" value="999" min="0" />
<button class="pad-opt-del" onclick="removeAddonOption(this)"><i data-lucide="trash-2" style="width:14px;height:14px;"></i></button>
</div>
<div class="pad-opt-row">
<input class="pad-opt-name" type="text" placeholder="如:椰果" value="椰果" />
<input class="pad-opt-price" type="number" placeholder="如3.00" value="3" min="0" step="0.01" />
<input class="pad-opt-stock" type="number" placeholder="库存数" value="999" min="0" />
<button class="pad-opt-del" onclick="removeAddonOption(this)"><i data-lucide="trash-2" style="width:14px;height:14px;"></i></button>
</div>
<div class="pad-opt-row">
<input class="pad-opt-name" type="text" placeholder="如:芋圆" value="芋圆" />
<input class="pad-opt-price" type="number" placeholder="如4.00" value="5" min="0" step="0.01" />
<input class="pad-opt-stock" type="number" placeholder="库存数" value="500" min="0" />
<button class="pad-opt-del" onclick="removeAddonOption(this)"><i data-lucide="trash-2" style="width:14px;height:14px;"></i></button>
</div>
</div>
<button class="g-btn" style="width:100%;margin-top:8px;justify-content:center;border-style:dashed;" onclick="addAddonOption()"><i data-lucide="plus" style="width:14px;height:14px;"></i>添加选项</button>
</div>
<div class="g-form-group">
<label class="g-form-label">备注</label>
<textarea class="g-textarea" rows="2" placeholder="可选,如:仅限堂食商品使用"></textarea>
</div>
</div>
<div class="g-drawer-ft">
<button class="g-btn" onclick="closeAddonDrawer()">取消</button>
<button class="g-btn g-btn-primary" onclick="closeAddonDrawer()">确认保存</button>
</div>
</div>
<script>
function openAddonDrawer(mode, name) {
document.getElementById('padDrawerMask').classList.add('open');
document.getElementById('padDrawer').classList.add('open');
if (mode === 'edit' && name) {
document.getElementById('padDrawerTitle').textContent = '编辑加料组 - ' + name;
document.getElementById('padGroupName').value = name;
} else {
document.getElementById('padDrawerTitle').textContent = '添加加料组';
document.getElementById('padGroupName').value = '';
}
if (typeof lucide !== 'undefined') lucide.createIcons();
}
function closeAddonDrawer() {
document.getElementById('padDrawerMask').classList.remove('open');
document.getElementById('padDrawer').classList.remove('open');
}
function addAddonOption() {
var list = document.getElementById('padOptList');
var row = document.createElement('div');
row.className = 'pad-opt-row';
row.innerHTML = '<input class="pad-opt-name" type="text" placeholder="请输入选项名称" />' +
'<input class="pad-opt-price" type="number" placeholder="如3.00" value="0" min="0" step="0.01" />' +
'<input class="pad-opt-stock" type="number" placeholder="库存数" value="999" min="0" />' +
'<button class="pad-opt-del" onclick="removeAddonOption(this)"><i data-lucide="trash-2" style="width:14px;height:14px;"></i></button>';
list.appendChild(row);
if (typeof lucide !== 'undefined') lucide.createIcons();
}
function removeAddonOption(el) {
var row = el.closest('.pad-opt-row');
if (row) row.remove();
}
function searchAddons(keyword) {
var kw = keyword.trim().toLowerCase();
document.querySelectorAll('.pad-card').forEach(function(card) {
var name = (card.getAttribute('data-name') || '').toLowerCase();
card.style.display = (!kw || name.includes(kw)) ? '' : 'none';
});
}
function toggleSwitch(el) {
el.classList.toggle('on');
}
</script>

282
pages/product-batch.html Normal file
View File

@@ -0,0 +1,282 @@
<!-- 批量工具页 -->
<style>
.pbt-toolbar { display:flex; align-items:center; gap:12px; margin-bottom:16px; box-shadow:var(--g-shadow-sm); border-radius:10px; padding:12px 16px; background:#fff; flex-wrap:wrap; }
.pbt-grid { display:grid; grid-template-columns:repeat(2,1fr); gap:16px; margin-bottom:16px; }
.pbt-card-icon { width:40px; height:40px; border-radius:10px; display:flex; align-items:center; justify-content:center; margin-bottom:12px; }
.pbt-card-name { font-size:15px; font-weight:600; color:#1a1a2e; margin-bottom:4px; }
.pbt-card-desc { font-size:13px; color:#9ca3af; margin-bottom:14px; line-height:1.5; }
.pbt-expand { background:#fff; border-radius:10px; border:none; box-shadow:var(--g-shadow-sm); padding:20px; margin-bottom:16px; display:none; transition:var(--g-transition); }
.pbt-expand.open { display:block; }
.pbt-expand-title { font-size:14px; font-weight:600; color:#1a1a2e; margin-bottom:16px; display:flex; align-items:center; justify-content:space-between; padding-left:10px; border-left:3px solid var(--primary); }
.pbt-expand-close { background:none; border:none; color:#9ca3af; cursor:pointer; display:flex; align-items:center; justify-content:center; width:28px; height:28px; border-radius:8px; transition:var(--g-transition); }
.pbt-expand-close:hover { color:#1a1a2e; background:#f8f9fb; }
.pbt-step { margin-bottom:18px; }
.pbt-step-label { font-size:13px; font-weight:500; color:#4b5563; margin-bottom:8px; display:flex; align-items:center; gap:6px; }
.pbt-step-num { width:20px; height:20px; border-radius:50%; background:var(--primary); color:#fff; font-size:11px; display:inline-flex; align-items:center; justify-content:center; font-weight:600; }
.pbt-pills { display:flex; gap:8px; flex-wrap:wrap; margin-bottom:8px; }
.pbt-pill { height:34px; padding:0 14px; border-radius:8px; font-size:13px; cursor:pointer; border:1px solid #e5e7eb; background:#fff; color:#4b5563; display:inline-flex; align-items:center; transition:var(--g-transition); }
.pbt-pill:hover { border-color:var(--primary); color:var(--primary); }
.pbt-pill.active { background:var(--primary); color:#fff; border-color:var(--primary); }
.pbt-table { width:100%; border-collapse:collapse; font-size:13px; margin-top:8px; }
.pbt-table th { background:#f8f9fb; padding:8px 12px; text-align:left; font-weight:600; color:#6b7280; border-bottom:1px solid #f3f4f6; }
.pbt-table td { padding:8px 12px; border-bottom:1px solid #f3f4f6; color:#1a1a2e; }
.pbt-table tr:last-child td { border-bottom:none; }
.pbt-table tr:hover td { background:color-mix(in srgb, var(--primary) 3%, #fff); }
.pbt-price-up { color:#ef4444; font-weight:600; }
.pbt-price-down { color:#22c55e; font-weight:600; }
.pbt-actions { display:flex; gap:8px; margin-top:16px; padding-top:16px; border-top:1px solid #f3f4f6; }
.pbt-chk-group { display:flex; flex-wrap:wrap; gap:10px; margin-top:6px; }
.pbt-chk-group label { font-size:13px; color:#4b5563; display:flex; align-items:center; gap:4px; cursor:pointer; }
.pbt-chk-group label input[type=checkbox] { accent-color:var(--primary); cursor:pointer; }
.pbt-info { font-size:13px; color:#4b5563; background:#f0fdf4; border:1px solid #bbf7d0; border-radius:8px; padding:8px 12px; margin-top:10px; display:flex; align-items:center; gap:6px; }
.pbt-upload-area { border:2px dashed #e5e7eb; border-radius:10px; padding:30px; text-align:center; color:#9ca3af; font-size:13px; cursor:pointer; transition:var(--g-transition); display:flex; flex-direction:column; align-items:center; gap:8px; }
.pbt-upload-area:hover { border-color:var(--primary); color:var(--primary); }
.pbt-sub-cards { display:grid; grid-template-columns:1fr 1fr; gap:16px; }
.pbt-sub-card { border:none; border-radius:10px; padding:16px; box-shadow:var(--g-shadow-sm); transition:var(--g-transition); }
.pbt-sub-card:hover { box-shadow:var(--g-shadow-md); }
.pbt-sub-card h4 { font-size:14px; font-weight:600; color:#1a1a2e; margin:0 0 12px 0; }
.pbt-link { color:var(--primary); font-size:13px; text-decoration:none; cursor:pointer; display:inline-flex; align-items:center; gap:4px; transition:var(--g-transition); }
.pbt-link:hover { text-decoration:underline; }
</style>
<div class="pbt-toolbar">
<select class="g-select" style="width:200px;">
<option>老三家外卖(朝阳店)</option>
<option>老三家外卖(海淀店)</option>
<option>老三家外卖(望京店)</option>
<option>老三家外卖(通州店)</option>
<option>老三家外卖(丰台店)</option>
</select>
<div style="flex:1;"></div>
<span style="font-size:13px;color:#9ca3af;">选择门店后操作将应用于该门店的商品</span>
</div>
<div class="pbt-grid" id="pbtGrid">
<div class="g-card">
<div class="pbt-card-icon" style="background:#fff7e6;color:#fa8c16;"><i data-lucide="dollar-sign" style="width:20px;height:20px;"></i></div>
<div class="pbt-card-name">批量调价</div>
<div class="pbt-card-desc">按分类或手动选择商品,统一涨价或降价</div>
<button class="g-btn" onclick="expandBatchTool(0)">开始操作</button>
</div>
<div class="g-card">
<div class="pbt-card-icon" style="background:#f6ffed;color:#52c41a;"><i data-lucide="arrow-up-down" style="width:20px;height:20px;"></i></div>
<div class="pbt-card-name">批量上下架</div>
<div class="pbt-card-desc">快速批量上架或下架商品</div>
<button class="g-btn" onclick="expandBatchTool(1)">开始操作</button>
</div>
<div class="g-card">
<div class="pbt-card-icon" style="background:#e6f7ff;color:#1890ff;"><i data-lucide="folder-input" style="width:20px;height:20px;"></i></div>
<div class="pbt-card-name">批量移动分类</div>
<div class="pbt-card-desc">将商品批量移动到其他分类</div>
<button class="g-btn" onclick="expandBatchTool(2)">开始操作</button>
</div>
<div class="g-card">
<div class="pbt-card-icon" style="background:#f9f0ff;color:#722ed1;"><i data-lucide="refresh-cw" style="width:20px;height:20px;"></i></div>
<div class="pbt-card-name">批量同步门店</div>
<div class="pbt-card-desc">将商品同步到其他门店</div>
<button class="g-btn" onclick="expandBatchTool(3)">开始操作</button>
</div>
<div class="g-card">
<div class="pbt-card-icon" style="background:#e6fffb;color:#13c2c2;"><i data-lucide="file-spreadsheet" style="width:20px;height:20px;"></i></div>
<div class="pbt-card-name">导入导出</div>
<div class="pbt-card-desc">通过Excel批量导入或导出商品数据</div>
<button class="g-btn" onclick="expandBatchTool(4)">开始操作</button>
</div>
</div>
<!-- Expand 0: 批量调价 -->
<div class="pbt-expand" id="pbtExpand0">
<div class="pbt-expand-title">批量调价 <button class="pbt-expand-close" onclick="expandBatchTool(-1)"><i data-lucide="x" style="width:16px;height:16px;"></i></button></div>
<div class="pbt-step">
<div class="pbt-step-label"><span class="pbt-step-num">1</span> 选择范围</div>
<div class="pbt-pills" id="pbtRange">
<span class="pbt-pill active" onclick="selectBatchRange('all')">全部商品</span>
<span class="pbt-pill" onclick="selectBatchRange('category')">按分类</span>
<span class="pbt-pill" onclick="selectBatchRange('manual')">手动选择</span>
</div>
<div id="pbtRangeCat" style="display:none;" class="pbt-chk-group">
<label><input type="checkbox"> 热销套餐</label>
<label><input type="checkbox"> 招牌菜品</label>
<label><input type="checkbox"> 饮品小食</label>
<label><input type="checkbox"> 时令特供</label>
</div>
</div>
<div class="pbt-step">
<div class="pbt-step-label"><span class="pbt-step-num">2</span> 调价方式</div>
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
<div class="pbt-pills" id="pbtPriceDir">
<span class="pbt-pill active" onclick="selectPriceMode('up')">涨价</span>
<span class="pbt-pill" onclick="selectPriceMode('down')">降价</span>
</div>
<div class="pbt-pills" id="pbtPriceType">
<span class="pbt-pill active" onclick="selectPriceType('fixed')">固定金额</span>
<span class="pbt-pill" onclick="selectPriceType('percent')">百分比</span>
</div>
<input class="g-input" style="width:120px" type="number" placeholder="如2.00" min="0" value="2">
</div>
</div>
<div class="pbt-step">
<div class="pbt-step-label"><span class="pbt-step-num">3</span> 预览</div>
<table class="pbt-table">
<thead><tr><th>商品名</th><th>原价</th><th>新价</th><th>变动</th></tr></thead>
<tbody>
<tr><td>宫保鸡丁</td><td>¥28.00</td><td>¥30.00</td><td class="pbt-price-up">+¥2.00</td></tr>
<tr><td>鱼香肉丝</td><td>¥26.00</td><td>¥28.00</td><td class="pbt-price-up">+¥2.00</td></tr>
<tr><td>可乐鸡翅</td><td>¥32.00</td><td>¥34.00</td><td class="pbt-price-up">+¥2.00</td></tr>
</tbody>
</table>
</div>
<div class="pbt-actions">
<button class="g-btn g-btn-primary">确认调价</button>
<button class="g-btn" onclick="expandBatchTool(-1)">取消</button>
</div>
</div>
<!-- Expand 1: 批量上下架 -->
<div class="pbt-expand" id="pbtExpand1">
<div class="pbt-expand-title">批量上下架 <button class="pbt-expand-close" onclick="expandBatchTool(-1)"><i data-lucide="x" style="width:16px;height:16px;"></i></button></div>
<div class="pbt-step">
<div class="pbt-step-label"><span class="pbt-step-num">1</span> 选择分类</div>
<div class="pbt-chk-group">
<label><input type="checkbox"> 热销套餐</label>
<label><input type="checkbox"> 招牌菜品</label>
<label><input type="checkbox"> 饮品小食</label>
<label><input type="checkbox"> 时令特供</label>
</div>
</div>
<div class="pbt-step">
<div class="pbt-step-label"><span class="pbt-step-num">2</span> 操作类型</div>
<div class="pbt-pills" id="pbtShelfMode">
<span class="pbt-pill active">上架</span>
<span class="pbt-pill">下架</span>
</div>
</div>
<div class="pbt-info"><i data-lucide="info" style="width:14px;height:14px;flex-shrink:0;"></i> 预计影响 <strong>24</strong> 个商品</div>
<div class="pbt-actions">
<button class="g-btn g-btn-primary">确认执行</button>
<button class="g-btn" onclick="expandBatchTool(-1)">取消</button>
</div>
</div>
<!-- Expand 2: 批量移动分类 -->
<div class="pbt-expand" id="pbtExpand2">
<div class="pbt-expand-title">批量移动分类 <button class="pbt-expand-close" onclick="expandBatchTool(-1)"><i data-lucide="x" style="width:16px;height:16px;"></i></button></div>
<div class="pbt-step">
<div class="pbt-step-label"><span class="pbt-step-num">1</span> 源分类</div>
<select class="g-select" style="min-width:200px"><option value="">请选择源分类</option><option>热销套餐</option><option>招牌菜品</option><option>饮品小食</option><option>时令特供</option></select>
</div>
<div class="pbt-step">
<div class="pbt-step-label"><span class="pbt-step-num">2</span> 目标分类</div>
<select class="g-select" style="min-width:200px"><option value="">请选择目标分类</option><option>招牌菜品</option><option>热销套餐</option><option>饮品小食</option><option>时令特供</option></select>
</div>
<div class="pbt-info"><i data-lucide="info" style="width:14px;height:14px;flex-shrink:0;"></i> 该分类下共 <strong>12</strong> 个商品将被移动</div>
<div class="pbt-actions">
<button class="g-btn g-btn-primary">确认移动</button>
<button class="g-btn" onclick="expandBatchTool(-1)">取消</button>
</div>
</div>
<!-- Expand 3: 批量同步门店 -->
<div class="pbt-expand" id="pbtExpand3">
<div class="pbt-expand-title">批量同步门店 <button class="pbt-expand-close" onclick="expandBatchTool(-1)"><i data-lucide="x" style="width:16px;height:16px;"></i></button></div>
<div class="pbt-step">
<div class="pbt-step-label"><span class="pbt-step-num">1</span> 源门店</div>
<select class="g-select" style="min-width:200px"><option value="">请选择源门店</option><option>中关村店</option><option>望京店</option><option>国贸店</option><option>西单店</option></select>
</div>
<div class="pbt-step">
<div class="pbt-step-label"><span class="pbt-step-num">2</span> 目标门店</div>
<div class="pbt-chk-group">
<label><input type="checkbox"> 望京店</label>
<label><input type="checkbox"> 国贸店</label>
<label><input type="checkbox"> 西单店</label>
<label><input type="checkbox"> 三里屯店</label>
</div>
</div>
<div class="pbt-step">
<div class="pbt-step-label"><span class="pbt-step-num">3</span> 同步选项</div>
<div class="pbt-chk-group">
<label><input type="checkbox" checked> 价格</label>
<label><input type="checkbox" checked> 库存</label>
<label><input type="checkbox"> 状态</label>
</div>
</div>
<div class="pbt-actions">
<button class="g-btn g-btn-primary">确认同步</button>
<button class="g-btn" onclick="expandBatchTool(-1)">取消</button>
</div>
</div>
<!-- Expand 4: 导入导出 -->
<div class="pbt-expand" id="pbtExpand4">
<div class="pbt-expand-title">导入导出 <button class="pbt-expand-close" onclick="expandBatchTool(-1)"><i data-lucide="x" style="width:16px;height:16px;"></i></button></div>
<div class="pbt-sub-cards">
<div class="pbt-sub-card">
<h4>导入商品</h4>
<div class="pbt-upload-area">
<i data-lucide="upload" style="width:24px;height:24px;"></i>
<span>点击或拖拽 Excel 文件到此处上传</span>
<span style="font-size:11px;color:#bbb;">支持 .xlsx、.xls 格式,不超过 10MB</span>
</div>
<div style="margin-top:10px;"><a class="pbt-link"><i data-lucide="download" style="width:13px;height:13px;"></i> 下载导入模板</a></div>
</div>
<div class="pbt-sub-card">
<h4>导出商品</h4>
<div class="pbt-step" style="margin-bottom:12px;">
<div class="pbt-pills" id="pbtExportRange">
<span class="pbt-pill active">全部</span>
<span class="pbt-pill">按分类</span>
</div>
</div>
<button class="g-btn g-btn-primary"><i data-lucide="download" style="width:14px;height:14px;"></i> 导出 Excel</button>
</div>
</div>
<div class="pbt-actions">
<button class="g-btn" onclick="expandBatchTool(-1)">关闭</button>
</div>
</div>
<script>
var _pbtCurrent = -1;
function expandBatchTool(idx) {
for (var i = 0; i < 5; i++) {
var el = document.getElementById('pbtExpand' + i);
if (el) el.classList.remove('open');
}
if (idx === _pbtCurrent || idx < 0) { _pbtCurrent = -1; return; }
var target = document.getElementById('pbtExpand' + idx);
if (target) target.classList.add('open');
_pbtCurrent = idx;
if (typeof lucide !== 'undefined') lucide.createIcons();
}
function selectBatchRange(mode) {
var pills = document.querySelectorAll('#pbtRange .pbt-pill');
pills.forEach(function(p) { p.classList.remove('active'); });
var map = { all: 0, category: 1, manual: 2 };
if (pills[map[mode]]) pills[map[mode]].classList.add('active');
var catEl = document.getElementById('pbtRangeCat');
if (catEl) catEl.style.display = mode === 'category' ? 'flex' : 'none';
}
function selectPriceMode(mode) {
var pills = document.querySelectorAll('#pbtPriceDir .pbt-pill');
pills.forEach(function(p) { p.classList.remove('active'); });
if (mode === 'up') pills[0].classList.add('active');
else pills[1].classList.add('active');
}
function selectPriceType(mode) {
var pills = document.querySelectorAll('#pbtPriceType .pbt-pill');
pills.forEach(function(p) { p.classList.remove('active'); });
if (mode === 'fixed') pills[0].classList.add('active');
else pills[1].classList.add('active');
}
document.querySelectorAll('#pbtShelfMode .pbt-pill, #pbtExportRange .pbt-pill').forEach(function(pill) {
pill.addEventListener('click', function() {
this.parentElement.querySelectorAll('.pbt-pill').forEach(function(p) { p.classList.remove('active'); });
this.classList.add('active');
});
});
</script>

474
pages/product-category.html Normal file
View File

@@ -0,0 +1,474 @@
<!-- 分类管理页 -->
<style>
.pcat-toolbar{display:flex;align-items:center;gap:12px;margin-bottom:16px;box-shadow:var(--g-shadow-sm);border-radius:10px;padding:12px 16px;background:#fff;flex-wrap:wrap}
.pcat-store-select{height:34px;border-radius:8px;border:1px solid #e5e7eb;padding:0 12px;font-size:13px;min-width:180px;outline:none;color:#1a1a2e;transition:var(--g-transition)}
.pcat-store-select:focus{border-color:var(--primary);box-shadow:0 0 0 3px color-mix(in srgb,var(--primary) 12%,transparent)}
.pcat-toolbar .pcat-spacer{flex:1}
.pcat-stats{display:flex;gap:24px;margin-bottom:16px;padding:10px 16px;background:#fff;border-radius:10px;box-shadow:var(--g-shadow-sm);font-size:13px;color:#4b5563;flex-wrap:wrap}
.pcat-stats span{display:flex;align-items:center;gap:6px}
.pcat-stats strong{color:#1a1a2e;font-weight:600}
.pcat-stats .pcat-ch-stat{display:inline-flex;align-items:center;gap:4px;margin-left:2px}
.pcat-stats .pcat-ch-dot{width:6px;height:6px;border-radius:50%;display:inline-block}
.pcat-main{display:flex;gap:18px}
/* 左侧分类列表 */
.pcat-left{width:280px;flex-shrink:0;background:#fff;border-radius:10px;box-shadow:var(--g-shadow-sm);display:flex;flex-direction:column;max-height:600px}
.pcat-left-hd{padding:14px 16px 12px;border-bottom:1px solid #f3f4f6;flex-shrink:0}
.pcat-left-title{font-size:14px;font-weight:600;color:#1a1a2e;margin-bottom:10px;display:flex;align-items:center;justify-content:space-between}
.pcat-left-title span{font-size:12px;color:#9ca3af;font-weight:400}
.pcat-ch-filter{display:flex;gap:6px;margin-bottom:10px}
.pcat-ch-filter .pcat-ch-pill{padding:3px 10px;border-radius:6px;font-size:11px;font-weight:500;cursor:pointer;border:1px solid #e5e7eb;background:#fff;color:#6b7280;transition:var(--g-transition);user-select:none}
.pcat-ch-filter .pcat-ch-pill:hover{border-color:var(--primary);color:var(--primary)}
.pcat-ch-filter .pcat-ch-pill.active{background:var(--primary);color:#fff;border-color:var(--primary)}
.pcat-search{position:relative}
.pcat-search input{width:100%;height:32px;border-radius:8px;border:1px solid #e5e7eb;padding:0 10px 0 32px;font-size:12px;outline:none;color:#1a1a2e;transition:var(--g-transition)}
.pcat-search input:focus{border-color:var(--primary);box-shadow:0 0 0 3px color-mix(in srgb,var(--primary) 12%,transparent)}
.pcat-search i{position:absolute;left:9px;top:50%;transform:translateY(-50%);color:#bbb;pointer-events:none}
.pcat-left-list{flex:1;overflow-y:auto;padding:6px 0}
.pcat-left-list::-webkit-scrollbar{width:3px}
.pcat-left-list::-webkit-scrollbar-thumb{background:#e5e7eb;border-radius:2px}
.pcat-cat-item{display:flex;align-items:center;gap:8px;padding:9px 16px;cursor:pointer;font-size:13px;transition:var(--g-transition);user-select:none;color:#4b5563}
.pcat-cat-item:hover{background:color-mix(in srgb,var(--primary) 3%,#fff)}
.pcat-cat-item.active{background:color-mix(in srgb,var(--primary) 10%,transparent);color:var(--primary);font-weight:600}
.pcat-cat-item.disabled{opacity:0.5}
.pcat-drag-handle{color:#bbb;cursor:grab;flex-shrink:0;display:flex;align-items:center}
.pcat-cat-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pcat-cat-tags{display:flex;gap:3px;flex-shrink:0}
.pcat-ch-mini{font-size:10px;font-weight:600;padding:1px 4px;border-radius:3px;line-height:14px;color:#fff}
.pcat-ch-mini.wm{background:#1890ff}
.pcat-ch-mini.zt{background:#52c41a}
.pcat-ch-mini.ts{background:#fa8c16}
.pcat-cat-badge{background:#f8f9fb;border-radius:10px;padding:1px 8px;font-size:11px;color:#6b7280;flex-shrink:0}
.pcat-cat-item.active .pcat-cat-badge{background:color-mix(in srgb,var(--primary) 18%,transparent);color:var(--primary)}
/* 右侧详情面板 */
.pcat-right{flex:1;display:flex;flex-direction:column;gap:16px}
.pcat-info-card{background:#fff;border-radius:10px;box-shadow:var(--g-shadow-sm);padding:20px}
.pcat-info-hd{display:flex;align-items:flex-start;justify-content:space-between;margin-bottom:16px}
.pcat-info-title{font-size:18px;font-weight:600;color:#1a1a2e}
.pcat-info-desc{font-size:13px;color:#9ca3af;margin-top:4px}
.pcat-info-actions{display:flex;gap:8px;flex-shrink:0}
.pcat-attr-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;padding-top:16px;border-top:1px solid #f3f4f6}
.pcat-attr-item{display:flex;flex-direction:column;gap:4px;padding:10px 12px;background:#f8f9fb;border-radius:8px}
.pcat-attr-label{font-size:11px;color:#9ca3af;font-weight:500}
.pcat-attr-value{font-size:14px;color:#1a1a2e;font-weight:600}
.pcat-attr-value .g-tag{font-size:11px}
/* 渠道可见性卡片 */
.pcat-channel-card{background:#fff;border-radius:10px;box-shadow:var(--g-shadow-sm);padding:16px 20px}
.pcat-channel-hd{font-size:14px;font-weight:600;color:#1a1a2e;padding-left:10px;border-left:3px solid var(--primary);margin-bottom:14px}
.pcat-channel-list{display:flex;gap:12px;flex-wrap:wrap}
.pcat-channel-item{display:flex;align-items:center;gap:10px;padding:10px 16px;background:#f8f9fb;border-radius:8px;border:1px solid #f3f4f6;min-width:160px;flex:1}
.pcat-channel-item .pcat-ch-icon{width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;flex-shrink:0}
.pcat-channel-item .pcat-ch-icon.wm{background:color-mix(in srgb,#1890ff 12%,#fff);color:#1890ff}
.pcat-channel-item .pcat-ch-icon.zt{background:color-mix(in srgb,#52c41a 12%,#fff);color:#52c41a}
.pcat-channel-item .pcat-ch-icon.ts{background:color-mix(in srgb,#fa8c16 12%,#fff);color:#fa8c16}
.pcat-channel-item .pcat-ch-info{flex:1}
.pcat-channel-item .pcat-ch-name{font-size:13px;font-weight:600;color:#1a1a2e}
.pcat-channel-item .pcat-ch-sub{font-size:11px;color:#9ca3af;margin-top:2px}
.pcat-channel-item .pcat-ch-status{font-size:11px;font-weight:600;display:flex;align-items:center;gap:4px}
.pcat-channel-item .pcat-ch-status.on{color:#52c41a}
.pcat-channel-item .pcat-ch-status.off{color:#9ca3af}
.pcat-channel-item .pcat-ch-status-dot{width:6px;height:6px;border-radius:50%;background:currentColor}
/* 商品预览表 */
.pcat-prod-card{background:#fff;border-radius:10px;box-shadow:var(--g-shadow-sm);padding:0;flex:1}
.pcat-prod-hd{display:flex;align-items:center;justify-content:space-between;padding:14px 18px;border-bottom:1px solid #f3f4f6}
.pcat-prod-title{font-size:14px;font-weight:600;color:#1a1a2e;display:flex;align-items:center;gap:8px}
.pcat-prod-title span{font-size:12px;color:#9ca3af;font-weight:400}
.pcat-prod-table{width:100%;border-collapse:collapse;font-size:13px}
.pcat-prod-table th{text-align:left;padding:10px 18px;font-size:12px;font-weight:500;color:#6b7280;background:#f8f9fb}
.pcat-prod-table td{padding:10px 18px;border-bottom:1px solid #f3f4f6;color:#1a1a2e}
.pcat-prod-table tr:last-child td{border-bottom:none}
.pcat-prod-table tr:hover td{background:color-mix(in srgb,var(--primary) 3%,#fff)}
.pcat-prod-name{display:flex;align-items:center;gap:10px}
.pcat-prod-thumb{width:36px;height:36px;border-radius:6px;background:#f3f4f6;display:flex;align-items:center;justify-content:center;flex-shrink:0;color:#bbb}
.pcat-prod-info .name{font-weight:500}
.pcat-prod-info .spu{font-size:11px;color:#9ca3af;margin-top:1px}
.pcat-prod-price{font-weight:600;color:#1a1a2e}
/* 抽屉内渠道选择 */
.pcat-drawer-channels{display:flex;gap:8px;flex-wrap:wrap}
.pcat-drawer-ch{display:flex;align-items:center;gap:6px;padding:8px 14px;border-radius:8px;border:1.5px solid #e5e7eb;cursor:pointer;transition:var(--g-transition);user-select:none;font-size:13px;color:#4b5563;background:#fff}
.pcat-drawer-ch:hover{border-color:var(--primary)}
.pcat-drawer-ch.checked{border-color:var(--primary);background:color-mix(in srgb,var(--primary) 6%,#fff);color:var(--primary);font-weight:500}
.pcat-drawer-ch .pcat-ch-check{width:16px;height:16px;border-radius:4px;border:1.5px solid #d1d5db;display:flex;align-items:center;justify-content:center;transition:var(--g-transition);flex-shrink:0}
.pcat-drawer-ch.checked .pcat-ch-check{background:var(--primary);border-color:var(--primary)}
.pcat-drawer-ch .pcat-ch-check svg{display:none}
.pcat-drawer-ch.checked .pcat-ch-check svg{display:block}
/* 图标上传 */
.pcat-icon-upload{width:80px;height:80px;border:1px dashed #e5e7eb;border-radius:10px;display:flex;flex-direction:column;align-items:center;justify-content:center;color:#bbb;cursor:pointer;transition:var(--g-transition);gap:4px}
.pcat-icon-upload:hover{border-color:var(--primary);color:var(--primary)}
</style>
<div class="pcat-toolbar">
<select class="pcat-store-select">
<option>老三家外卖 总店</option>
<option>老三家外卖 朝阳店</option>
<option>老三家外卖 海淀店</option>
<option>老三家外卖 丰台店</option>
<option>老三家外卖 通州店</option>
</select>
<button class="g-btn" onclick="openCopyStoreModal()">复制到其他门店</button>
<span class="pcat-spacer"></span>
<button class="g-btn g-btn-primary" onclick="openCatDrawer()"><i data-lucide="plus" style="width:14px;height:14px;"></i> 添加分类</button>
</div>
<div class="pcat-stats">
<span>分类总数 <strong>7</strong></span>
<span>已启用 <strong>6</strong></span>
<span>商品总数 <strong>58</strong></span>
<span>渠道覆盖
<span class="pcat-ch-stat"><span class="pcat-ch-dot" style="background:#1890ff"></span>外卖 <strong>7</strong></span>
<span class="pcat-ch-stat"><span class="pcat-ch-dot" style="background:#52c41a"></span>自提 <strong>6</strong></span>
<span class="pcat-ch-stat"><span class="pcat-ch-dot" style="background:#fa8c16"></span>堂食 <strong>5</strong></span>
</span>
</div>
<div class="pcat-main">
<!-- 左侧分类列表 -->
<div class="pcat-left">
<div class="pcat-left-hd">
<div class="pcat-left-title">全部分类 <span>共 7 个</span></div>
<div class="pcat-ch-filter">
<span class="pcat-ch-pill active" onclick="filterByChannel(this,'all')">全部</span>
<span class="pcat-ch-pill" onclick="filterByChannel(this,'wm')">外卖</span>
<span class="pcat-ch-pill" onclick="filterByChannel(this,'zt')">自提</span>
<span class="pcat-ch-pill" onclick="filterByChannel(this,'ts')">堂食</span>
</div>
<div class="pcat-search">
<i data-lucide="search" style="width:14px;height:14px;"></i>
<input type="text" placeholder="搜索分类名称…" oninput="searchCategories(this.value)">
</div>
</div>
<div class="pcat-left-list" id="pcatList">
<div class="pcat-cat-item active" onclick="selectCategory(this)" data-name="热销推荐" data-desc="店铺热销菜品推荐" data-count="12" data-sort="1" data-channels="wm,zt,ts" data-status="on">
<span class="pcat-drag-handle"><i data-lucide="grip-vertical" style="width:14px;height:14px;"></i></span>
<span class="pcat-cat-name">热销推荐</span>
<span class="pcat-cat-tags"><span class="pcat-ch-mini wm"></span><span class="pcat-ch-mini zt"></span><span class="pcat-ch-mini ts"></span></span>
<span class="pcat-cat-badge">12</span>
</div>
<div class="pcat-cat-item" onclick="selectCategory(this)" data-name="主食" data-desc="米饭、面条等主食" data-count="8" data-sort="2" data-channels="wm,zt,ts" data-status="on">
<span class="pcat-drag-handle"><i data-lucide="grip-vertical" style="width:14px;height:14px;"></i></span>
<span class="pcat-cat-name">主食</span>
<span class="pcat-cat-tags"><span class="pcat-ch-mini wm"></span><span class="pcat-ch-mini zt"></span><span class="pcat-ch-mini ts"></span></span>
<span class="pcat-cat-badge">8</span>
</div>
<div class="pcat-cat-item" onclick="selectCategory(this)" data-name="小吃凉菜" data-desc="各类小吃与凉拌菜" data-count="15" data-sort="3" data-channels="wm,zt" data-status="on">
<span class="pcat-drag-handle"><i data-lucide="grip-vertical" style="width:14px;height:14px;"></i></span>
<span class="pcat-cat-name">小吃凉菜</span>
<span class="pcat-cat-tags"><span class="pcat-ch-mini wm"></span><span class="pcat-ch-mini zt"></span></span>
<span class="pcat-cat-badge">15</span>
</div>
<div class="pcat-cat-item" onclick="selectCategory(this)" data-name="汤羹粥品" data-desc="汤类、羹类、粥品" data-count="6" data-sort="4" data-channels="wm,ts" data-status="on">
<span class="pcat-drag-handle"><i data-lucide="grip-vertical" style="width:14px;height:14px;"></i></span>
<span class="pcat-cat-name">汤羹粥品</span>
<span class="pcat-cat-tags"><span class="pcat-ch-mini wm"></span><span class="pcat-ch-mini ts"></span></span>
<span class="pcat-cat-badge">6</span>
</div>
<div class="pcat-cat-item" onclick="selectCategory(this)" data-name="饮品" data-desc="各类冷热饮品" data-count="10" data-sort="5" data-channels="wm,zt,ts" data-status="on">
<span class="pcat-drag-handle"><i data-lucide="grip-vertical" style="width:14px;height:14px;"></i></span>
<span class="pcat-cat-name">饮品</span>
<span class="pcat-cat-tags"><span class="pcat-ch-mini wm"></span><span class="pcat-ch-mini zt"></span><span class="pcat-ch-mini ts"></span></span>
<span class="pcat-cat-badge">10</span>
</div>
<div class="pcat-cat-item" onclick="selectCategory(this)" data-name="酒水" data-desc="啤酒、白酒、红酒等" data-count="4" data-sort="6" data-channels="ts" data-status="on">
<span class="pcat-drag-handle"><i data-lucide="grip-vertical" style="width:14px;height:14px;"></i></span>
<span class="pcat-cat-name">酒水</span>
<span class="pcat-cat-tags"><span class="pcat-ch-mini ts"></span></span>
<span class="pcat-cat-badge">4</span>
</div>
<div class="pcat-cat-item disabled" onclick="selectCategory(this)" data-name="季节限定" data-desc="季节性限定菜品" data-count="3" data-sort="7" data-channels="wm" data-status="off">
<span class="pcat-drag-handle"><i data-lucide="grip-vertical" style="width:14px;height:14px;"></i></span>
<span class="pcat-cat-name">季节限定</span>
<span class="pcat-cat-tags"><span class="pcat-ch-mini wm"></span></span>
<span class="pcat-cat-badge">3</span>
</div>
</div>
</div>
<!-- 右侧详情 -->
<div class="pcat-right" id="pcatDetail">
<!-- 分类信息卡片 -->
<div class="pcat-info-card">
<div class="pcat-info-hd">
<div>
<div class="pcat-info-title" id="pcatDetailName">热销推荐</div>
<div class="pcat-info-desc" id="pcatDetailDesc">店铺热销菜品推荐</div>
</div>
<div class="pcat-info-actions">
<button class="g-btn" onclick="openCatDrawer('edit',document.getElementById('pcatDetailName').textContent)"><i data-lucide="pencil" style="width:13px;height:13px;"></i> 编辑</button>
<button class="g-btn g-btn-danger"><i data-lucide="trash-2" style="width:13px;height:13px;"></i> 删除</button>
</div>
</div>
<div class="pcat-attr-grid">
<div class="pcat-attr-item">
<span class="pcat-attr-label">商品数量</span>
<span class="pcat-attr-value" id="pcatDetailCount">12</span>
</div>
<div class="pcat-attr-item">
<span class="pcat-attr-label">排序权重</span>
<span class="pcat-attr-value" id="pcatDetailSort">1</span>
</div>
<div class="pcat-attr-item">
<span class="pcat-attr-label">启用状态</span>
<span class="pcat-attr-value" id="pcatDetailStatus"><span class="g-tag g-tag-green">已启用</span></span>
</div>
<div class="pcat-attr-item">
<span class="pcat-attr-label">分类图标</span>
<span class="pcat-attr-value" style="color:#9ca3af;font-weight:400;font-size:12px">未设置</span>
</div>
</div>
</div>
<!-- 渠道可见性 -->
<div class="pcat-channel-card">
<div class="pcat-channel-hd">渠道可见性</div>
<div class="pcat-channel-list" id="pcatChannelList">
<div class="pcat-channel-item">
<div class="pcat-ch-icon wm"><i data-lucide="bike" style="width:18px;height:18px;"></i></div>
<div class="pcat-ch-info">
<div class="pcat-ch-name">外卖</div>
<div class="pcat-ch-sub">外卖平台展示此分类</div>
</div>
<div class="pcat-ch-status on"><span class="pcat-ch-status-dot"></span>已开启</div>
</div>
<div class="pcat-channel-item">
<div class="pcat-ch-icon zt"><i data-lucide="shopping-bag" style="width:18px;height:18px;"></i></div>
<div class="pcat-ch-info">
<div class="pcat-ch-name">自提</div>
<div class="pcat-ch-sub">到店自提展示此分类</div>
</div>
<div class="pcat-ch-status on"><span class="pcat-ch-status-dot"></span>已开启</div>
</div>
<div class="pcat-channel-item">
<div class="pcat-ch-icon ts"><i data-lucide="utensils" style="width:18px;height:18px;"></i></div>
<div class="pcat-ch-info">
<div class="pcat-ch-name">堂食</div>
<div class="pcat-ch-sub">堂食扫码点餐展示此分类</div>
</div>
<div class="pcat-ch-status on"><span class="pcat-ch-status-dot"></span>已开启</div>
</div>
</div>
</div>
<!-- 分类下商品列表 -->
<div class="pcat-prod-card">
<div class="pcat-prod-hd">
<div class="pcat-prod-title">分类商品 <span id="pcatProdCount">12 个商品</span></div>
<button class="g-btn g-btn-sm" onclick="openProductPicker({title:'添加商品到分类',subtitle:document.getElementById('pcatDetailName').textContent,onConfirm:function(items){document.getElementById('pcatProdCount').textContent=items.length+' 个商品'}})"><i data-lucide="plus" style="width:12px;height:12px;"></i> 添加商品到此分类</button>
</div>
<table class="pcat-prod-table">
<thead>
<tr><th>商品</th><th>价格</th><th>月销</th><th>状态</th><th>操作</th></tr>
</thead>
<tbody id="pcatProdList">
<tr>
<td><div class="pcat-prod-name"><div class="pcat-prod-thumb"><i data-lucide="image" style="width:16px;height:16px;"></i></div><div class="pcat-prod-info"><div class="name">招牌红烧肉饭</div><div class="spu">SPU20240001</div></div></div></td>
<td class="pcat-prod-price">&yen;28.00</td>
<td>128</td>
<td><span class="g-tag g-tag-green">在售</span></td>
<td><span class="g-action">移出</span></td>
</tr>
<tr>
<td><div class="pcat-prod-name"><div class="pcat-prod-thumb"><i data-lucide="image" style="width:16px;height:16px;"></i></div><div class="pcat-prod-info"><div class="name">香辣鸡腿堡套餐</div><div class="spu">SPU20240005</div></div></div></td>
<td class="pcat-prod-price">&yen;35.00</td>
<td>99</td>
<td><span class="g-tag g-tag-green">在售</span></td>
<td><span class="g-action">移出</span></td>
</tr>
<tr>
<td><div class="pcat-prod-name"><div class="pcat-prod-thumb"><i data-lucide="image" style="width:16px;height:16px;"></i></div><div class="pcat-prod-info"><div class="name">番茄牛腩面</div><div class="spu">SPU20240008</div></div></div></td>
<td class="pcat-prod-price">&yen;26.00</td>
<td>79</td>
<td><span class="g-tag g-tag-green">在售</span></td>
<td><span class="g-action">移出</span></td>
</tr>
<tr>
<td><div class="pcat-prod-name"><div class="pcat-prod-thumb"><i data-lucide="image" style="width:16px;height:16px;"></i></div><div class="pcat-prod-info"><div class="name">鱼香肉丝盖饭</div><div class="spu">SPU20240002</div></div></div></td>
<td class="pcat-prod-price">&yen;22.00</td>
<td>58</td>
<td><span class="g-tag g-tag-green">在售</span></td>
<td><span class="g-action">移出</span></td>
</tr>
<tr>
<td><div class="pcat-prod-name"><div class="pcat-prod-thumb"><i data-lucide="image" style="width:16px;height:16px;"></i></div><div class="pcat-prod-info"><div class="name">冰柠檬红茶</div><div class="spu">SPU20240012</div></div></div></td>
<td class="pcat-prod-price">&yen;8.00</td>
<td>45</td>
<td><span class="g-tag g-tag-gray" style="opacity:0.7">下架</span></td>
<td><span class="g-action">移出</span></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- 添加/编辑分类抽屉 -->
<div class="g-drawer-mask" id="pcatMask" onclick="closeCatDrawer()"></div>
<div class="g-drawer" id="pcatDrawer" style="width:480px">
<div class="g-drawer-hd">
<span class="g-drawer-title" id="pcatDrawerTitle">添加分类</span>
<button class="g-drawer-close" onclick="closeCatDrawer()"><i data-lucide="x" style="width:18px;height:18px;"></i></button>
</div>
<div class="g-drawer-bd">
<div class="g-form-group">
<label class="g-form-label required">分类名称</label>
<input type="text" class="g-input" id="pcatFormName" placeholder="请输入分类名称,如:热销推荐">
</div>
<div class="g-form-group">
<label class="g-form-label">分类描述</label>
<textarea class="g-textarea" id="pcatFormDesc" rows="2" placeholder="请输入分类描述,帮助顾客理解分类内容"></textarea>
</div>
<div class="g-form-group">
<label class="g-form-label">分类图标</label>
<div class="pcat-icon-upload" title="上传图标">
<i data-lucide="image-plus" style="width:22px;height:22px;"></i>
<span style="font-size:11px;">上传图标</span>
</div>
<div class="g-hint">建议尺寸 120x120px支持 PNG/SVG</div>
</div>
<div class="g-form-group">
<label class="g-form-label required">销售渠道</label>
<div class="g-hint" style="margin-bottom:8px;">选择该分类在哪些渠道展示,至少选择一个</div>
<div class="pcat-drawer-channels" id="pcatFormChannels">
<div class="pcat-drawer-ch checked" onclick="toggleChannel(this)" data-ch="wm">
<span class="pcat-ch-check"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></span>
<i data-lucide="bike" style="width:14px;height:14px;"></i>
外卖
</div>
<div class="pcat-drawer-ch checked" onclick="toggleChannel(this)" data-ch="zt">
<span class="pcat-ch-check"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></span>
<i data-lucide="shopping-bag" style="width:14px;height:14px;"></i>
自提
</div>
<div class="pcat-drawer-ch checked" onclick="toggleChannel(this)" data-ch="ts">
<span class="pcat-ch-check"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></span>
<i data-lucide="utensils" style="width:14px;height:14px;"></i>
堂食
</div>
</div>
</div>
<div class="g-form-group">
<label class="g-form-label">排序</label>
<input type="number" class="g-input" id="pcatFormSort" placeholder="数字越小越靠前1" style="width:180px">
</div>
<div class="g-form-group">
<label class="g-form-label">启用状态</label>
<div style="display:flex;align-items:center;gap:10px;">
<button class="g-toggle on" id="pcatFormToggle" onclick="toggleSwitch(this)"></button>
<span class="g-toggle-label">启用</span>
</div>
</div>
</div>
<div class="g-drawer-ft">
<button class="g-btn" onclick="closeCatDrawer()">取消</button>
<button class="g-btn g-btn-primary" id="pcatDrawerSubmit" onclick="closeCatDrawer()">确认添加</button>
</div>
</div>
<script>
var _channelMap = {wm:{name:'外卖',icon:'bike',sub:'外卖平台展示此分类',cls:'wm'},zt:{name:'自提',icon:'shopping-bag',sub:'到店自提展示此分类',cls:'zt'},ts:{name:'堂食',icon:'utensils',sub:'堂食扫码点餐展示此分类',cls:'ts'}};
function openCatDrawer(mode, name) {
var title = document.getElementById('pcatDrawerTitle');
var submit = document.getElementById('pcatDrawerSubmit');
var nameInput = document.getElementById('pcatFormName');
var descInput = document.getElementById('pcatFormDesc');
var sortInput = document.getElementById('pcatFormSort');
var tog = document.getElementById('pcatFormToggle');
if (mode === 'edit' && name) {
title.textContent = '编辑分类';
submit.textContent = '保存修改';
nameInput.value = name;
var active = document.querySelector('#pcatList .pcat-cat-item.active');
if (active) {
descInput.value = active.getAttribute('data-desc') || '';
sortInput.value = active.getAttribute('data-sort') || '';
var channels = (active.getAttribute('data-channels') || '').split(',');
document.querySelectorAll('#pcatFormChannels .pcat-drawer-ch').forEach(function(ch) {
var key = ch.getAttribute('data-ch');
ch.classList.toggle('checked', channels.indexOf(key) > -1);
});
var isOn = active.getAttribute('data-status') !== 'off';
tog.classList.toggle('on', isOn);
}
} else {
title.textContent = '添加分类';
submit.textContent = '确认添加';
nameInput.value = '';
descInput.value = '';
sortInput.value = '';
document.querySelectorAll('#pcatFormChannels .pcat-drawer-ch').forEach(function(ch) { ch.classList.add('checked'); });
if (!tog.classList.contains('on')) tog.classList.add('on');
}
document.getElementById('pcatMask').classList.add('open');
document.getElementById('pcatDrawer').classList.add('open');
if (typeof lucide !== 'undefined') lucide.createIcons();
}
function closeCatDrawer() {
document.getElementById('pcatMask').classList.remove('open');
document.getElementById('pcatDrawer').classList.remove('open');
}
function selectCategory(el) {
document.querySelectorAll('#pcatList .pcat-cat-item').forEach(function(item) { item.classList.remove('active'); });
el.classList.add('active');
document.getElementById('pcatDetailName').textContent = el.getAttribute('data-name');
document.getElementById('pcatDetailDesc').textContent = el.getAttribute('data-desc');
document.getElementById('pcatDetailCount').textContent = el.getAttribute('data-count');
document.getElementById('pcatDetailSort').textContent = el.getAttribute('data-sort');
document.getElementById('pcatProdCount').textContent = el.getAttribute('data-count') + ' 个商品';
var isOn = el.getAttribute('data-status') !== 'off';
document.getElementById('pcatDetailStatus').innerHTML = isOn
? '<span class="g-tag g-tag-green">已启用</span>'
: '<span class="g-tag g-tag-gray">已停用</span>';
updateChannelDisplay(el.getAttribute('data-channels') || '');
}
function updateChannelDisplay(channelsStr) {
var channels = channelsStr ? channelsStr.split(',') : [];
var html = '';
['wm','zt','ts'].forEach(function(key) {
var ch = _channelMap[key];
var isOn = channels.indexOf(key) > -1;
html += '<div class="pcat-channel-item">'
+ '<div class="pcat-ch-icon ' + ch.cls + '"><i data-lucide="' + ch.icon + '" style="width:18px;height:18px;"></i></div>'
+ '<div class="pcat-ch-info"><div class="pcat-ch-name">' + ch.name + '</div><div class="pcat-ch-sub">' + ch.sub + '</div></div>'
+ '<div class="pcat-ch-status ' + (isOn ? 'on' : 'off') + '"><span class="pcat-ch-status-dot"></span>' + (isOn ? '已开启' : '未开启') + '</div>'
+ '</div>';
});
document.getElementById('pcatChannelList').innerHTML = html;
if (typeof lucide !== 'undefined') lucide.createIcons();
}
function searchCategories(keyword) {
var kw = keyword.trim().toLowerCase();
document.querySelectorAll('#pcatList .pcat-cat-item').forEach(function(item) {
var name = (item.getAttribute('data-name') || '').toLowerCase();
item.style.display = (!kw || name.indexOf(kw) > -1) ? '' : 'none';
});
}
function filterByChannel(el, channel) {
document.querySelectorAll('.pcat-ch-filter .pcat-ch-pill').forEach(function(p) { p.classList.remove('active'); });
el.classList.add('active');
document.querySelectorAll('#pcatList .pcat-cat-item').forEach(function(item) {
if (channel === 'all') { item.style.display = ''; return; }
var channels = (item.getAttribute('data-channels') || '').split(',');
item.style.display = channels.indexOf(channel) > -1 ? '' : 'none';
});
}
function toggleChannel(el) {
el.classList.toggle('checked');
}
function toggleSwitch(el) {
el.classList.toggle('on');
}
function openCopyStoreModal() {}
</script>

344
pages/product-combos.html Normal file
View File

@@ -0,0 +1,344 @@
<!-- 套餐管理页 -->
<style>
.pcmb-toolbar{display:flex;align-items:center;gap:12px;margin-bottom:16px;box-shadow:var(--g-shadow-sm);border-radius:10px;padding:12px 16px;background:#fff;flex-wrap:wrap}
.pcmb-search{position:relative}
.pcmb-search input{height:34px;padding:0 10px 0 32px;border:1px solid #e5e7eb;border-radius:8px;font-size:13px;outline:none;width:200px;transition:var(--g-transition)}
.pcmb-search input:focus{border-color:var(--primary);box-shadow:0 0 0 3px color-mix(in srgb,var(--primary) 12%,transparent)}
.pcmb-search i{position:absolute;left:9px;top:50%;transform:translateY(-50%);color:#bbb;pointer-events:none}
.pcmb-filters{display:flex;gap:8px}
.pcmb-filter-pill{padding:6px 16px;border-radius:8px;border:1px solid #e5e7eb;background:#fff;font-size:13px;cursor:pointer;color:#4b5563;transition:var(--g-transition)}
.pcmb-filter-pill:hover{border-color:var(--primary);color:var(--primary)}
.pcmb-filter-pill.active{background:var(--primary);color:#fff;border-color:var(--primary)}
.pcmb-stats{display:flex;gap:24px;margin-bottom:16px;padding:10px 16px;background:#fff;border-radius:10px;box-shadow:var(--g-shadow-sm);font-size:13px;color:#4b5563}
.pcmb-stats span{display:flex;align-items:center;gap:6px}
.pcmb-stats strong{color:#1a1a2e;font-weight:600}
.pcmb-list{display:flex;flex-direction:column;gap:12px}
.pcmb-card{display:flex;align-items:flex-start;gap:16px;background:#fff;border:none;border-radius:10px;padding:20px;font-size:13px;box-shadow:var(--g-shadow-sm);transition:var(--g-transition)}
.pcmb-card:hover{box-shadow:var(--g-shadow-md)}
.pcmb-card.disabled{opacity:0.5}
.pcmb-cover{width:100px;height:100px;border-radius:8px;background:#f8f9fb;display:flex;align-items:center;justify-content:center;color:#bbb;font-size:12px;flex-shrink:0;border:1px solid #e5e7eb}
.pcmb-info{flex:1;min-width:0}
.pcmb-name{font-weight:600;font-size:15px;margin-bottom:6px;display:flex;align-items:center;gap:8px;color:#1a1a2e}
.pcmb-tag-fixed{background:#e6f0ff;color:#1a6dff;border-radius:6px;font-weight:600}
.pcmb-tag-custom{background:#e6f7e9;color:#18a058;border-radius:6px;font-weight:600}
.pcmb-desc{color:#9ca3af;margin-bottom:6px;font-size:12px}
.pcmb-breakdown{color:#4b5563;margin-bottom:8px;line-height:1.6}
.pcmb-breakdown .group-label{color:#6b7280;font-size:12px}
.pcmb-price-row{display:flex;align-items:baseline;gap:10px;margin-bottom:8px}
.pcmb-price{color:#ef4444;font-size:18px;font-weight:700}
.pcmb-price-orig{color:#9ca3af;text-decoration:line-through;font-size:12px}
.pcmb-price-save{color:#22c55e;font-size:12px;font-weight:600}
.pcmb-stats-text{color:#9ca3af;font-size:12px}
.pcmb-right{display:flex;flex-direction:column;align-items:flex-end;gap:8px;flex-shrink:0}
.pcmb-status{padding:2px 10px;border-radius:6px;font-size:11px;font-weight:600}
.pcmb-status-on{background:#dcfce7;color:#22c55e}
.pcmb-status-off{background:#f8f9fb;color:#9ca3af}
.pcmb-actions{display:flex;gap:6px}
.pcmb-act{height:34px;padding:0 10px;border:1px solid #e5e7eb;border-radius:8px;background:#fff;font-size:12px;cursor:pointer;color:#4b5563;transition:var(--g-transition)}
.pcmb-act:hover{border-color:var(--primary);color:var(--primary)}
.pcmb-act-del:hover{border-color:#ef4444;color:#ef4444}
.pcmb-type-pills{display:flex;gap:8px}
.pcmb-type-pill{padding:6px 20px;border-radius:8px;border:1px solid #e5e7eb;background:#fff;font-size:13px;cursor:pointer;color:#4b5563;transition:var(--g-transition)}
.pcmb-type-pill:hover{border-color:var(--primary);color:var(--primary)}
.pcmb-type-pill.active{background:var(--primary);color:#fff;border-color:var(--primary)}
.pcmb-upload-box{width:100px;height:100px;border:1px dashed #e5e7eb;border-radius:8px;display:flex;flex-direction:column;align-items:center;justify-content:center;color:#bbb;font-size:12px;cursor:pointer;gap:4px;transition:var(--g-transition)}
.pcmb-upload-box:hover{border-color:var(--primary);color:var(--primary)}
.pcmb-section-title{font-size:14px;font-weight:600;margin-bottom:10px;color:#1a1a2e;padding-left:10px;border-left:3px solid var(--primary)}
.pcmb-group-card{background:#f8f9fb;border-radius:8px;padding:14px;margin-bottom:10px;border:1px solid #e5e7eb}
.pcmb-group-header{display:flex;align-items:center;gap:8px;margin-bottom:10px}
.pcmb-group-header .pcmb-input{flex:1;height:34px}
.pcmb-group-header span{font-size:12px;color:#6b7280;white-space:nowrap}
.pcmb-group-header .pcmb-input-sm{width:50px;height:34px;text-align:center}
.pcmb-group-remove{background:none;border:none;color:#9ca3af;cursor:pointer;display:flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:8px;transition:var(--g-transition)}
.pcmb-group-remove:hover{color:#ef4444;background:#fef2f2}
.pcmb-group-items{margin-bottom:8px}
.pcmb-group-item{display:flex;align-items:center;gap:8px;margin-bottom:6px;font-size:12px;color:#4b5563}
.pcmb-group-item span{flex:1}
.pcmb-group-item .qty{color:#9ca3af;width:40px;text-align:center}
.pcmb-item-remove{background:none;border:none;color:#9ca3af;cursor:pointer;display:flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:6px;transition:var(--g-transition)}
.pcmb-item-remove:hover{color:#ef4444;background:#fef2f2}
.pcmb-add-link{color:var(--primary);font-size:12px;cursor:pointer;background:none;border:none;padding:0;display:flex;align-items:center;gap:4px;transition:var(--g-transition)}
.pcmb-add-link:hover{text-decoration:underline}
.pcmb-add-group-btn{width:100%;height:36px;border:1px dashed #e5e7eb;border-radius:8px;background:#fff;color:#9ca3af;font-size:13px;cursor:pointer;margin-top:4px;display:flex;align-items:center;justify-content:center;gap:4px;transition:var(--g-transition)}
.pcmb-add-group-btn:hover{border-color:var(--primary);color:var(--primary)}
.pcmb-price-inputs{display:flex;gap:12px;align-items:center}
.pcmb-price-inputs label{font-size:12px;color:#6b7280;font-weight:500;white-space:nowrap}
.pcmb-price-inputs .pcmb-input{width:120px}
.pcmb-input{width:100%;height:34px;padding:0 10px;border:1px solid #e5e7eb;border-radius:8px;font-size:13px;box-sizing:border-box;transition:var(--g-transition);color:#1a1a2e}
.pcmb-input::placeholder{color:#bbb}
.pcmb-input:focus{outline:none;border-color:var(--primary);box-shadow:0 0 0 3px color-mix(in srgb,var(--primary) 12%,transparent)}
</style>
<div class="pcmb-toolbar">
<select class="g-select" style="width:200px;">
<option>老三家外卖(朝阳店)</option>
<option>老三家外卖(海淀店)</option>
<option>老三家外卖(望京店)</option>
<option>老三家外卖(通州店)</option>
<option>老三家外卖(丰台店)</option>
</select>
<div class="pcmb-search">
<i data-lucide="search" style="width:14px;height:14px;"></i>
<input type="text" placeholder="搜索套餐名称…" oninput="searchCombos(this.value)" />
</div>
<div class="pcmb-filters">
<span class="pcmb-filter-pill active" onclick="filterComboType(this)" data-type="all">全部</span>
<span class="pcmb-filter-pill" onclick="filterComboType(this)" data-type="fixed">固定套餐</span>
<span class="pcmb-filter-pill" onclick="filterComboType(this)" data-type="custom">自选套餐</span>
</div>
<div style="flex:1;"></div>
<button class="g-btn g-btn-primary" onclick="openComboDrawer('add')"><i data-lucide="plus" style="width:14px;height:14px;"></i>添加套餐</button>
</div>
<div class="pcmb-stats">
<span>套餐 <strong>4</strong></span>
<span>在售 <strong>3</strong></span>
<span>月总销量 <strong>265</strong></span>
</div>
<div class="pcmb-list">
<div class="pcmb-card" data-combo-type="fixed" data-name="双人超值套餐">
<div class="pcmb-cover">暂无图片</div>
<div class="pcmb-info">
<div class="pcmb-name">双人超值套餐 <span class="g-tag pcmb-tag-fixed">固定套餐</span></div>
<div class="pcmb-desc">超值双人组合,荤素搭配</div>
<div class="pcmb-breakdown">宫保鸡丁 + 鱼香肉丝 + 米饭×2 + 饮品×2</div>
<div class="pcmb-price-row"><span class="pcmb-price">¥68</span><span class="pcmb-price-orig">¥96</span><span class="pcmb-price-save">已省 ¥28</span></div>
<div class="pcmb-stats-text">月销 45</div>
</div>
<div class="pcmb-right">
<span class="pcmb-status pcmb-status-on">在售</span>
<div class="pcmb-actions">
<button class="pcmb-act" onclick="openComboDrawer('edit','双人超值套餐')">编辑</button>
<button class="pcmb-act">下架</button>
<button class="pcmb-act pcmb-act-del">删除</button>
</div>
</div>
</div>
<div class="pcmb-card" data-combo-type="fixed" data-name="单人工作餐">
<div class="pcmb-cover">暂无图片</div>
<div class="pcmb-info">
<div class="pcmb-name">单人工作餐 <span class="g-tag pcmb-tag-fixed">固定套餐</span></div>
<div class="pcmb-desc">简单快捷,工作日首选</div>
<div class="pcmb-breakdown">蛋炒饭 + 酸辣汤 + 凉拌黄瓜</div>
<div class="pcmb-price-row"><span class="pcmb-price">¥35</span><span class="pcmb-price-orig">¥45</span><span class="pcmb-price-save">已省 ¥10</span></div>
<div class="pcmb-stats-text">月销 120</div>
</div>
<div class="pcmb-right">
<span class="pcmb-status pcmb-status-on">在售</span>
<div class="pcmb-actions">
<button class="pcmb-act" onclick="openComboDrawer('edit','单人工作餐')">编辑</button>
<button class="pcmb-act">下架</button>
<button class="pcmb-act pcmb-act-del">删除</button>
</div>
</div>
</div>
<div class="pcmb-card" data-combo-type="custom" data-name="任选午餐">
<div class="pcmb-cover">暂无图片</div>
<div class="pcmb-info">
<div class="pcmb-name">任选午餐 <span class="g-tag pcmb-tag-custom">自选套餐</span></div>
<div class="pcmb-desc">自由搭配,满足不同口味</div>
<div class="pcmb-breakdown">
<span class="group-label">A 主食(选1):</span> 宫保鸡丁 / 鱼香肉丝 / 麻婆豆腐<br>
<span class="group-label">B 小食(选1):</span> 凉拌黄瓜 / 酸辣汤<br>
<span class="group-label">C 饮品(选1):</span> 冰粉 / 可乐
</div>
<div class="pcmb-price-row"><span class="pcmb-price">¥38</span></div>
<div class="pcmb-stats-text">月销 88</div>
</div>
<div class="pcmb-right">
<span class="pcmb-status pcmb-status-on">在售</span>
<div class="pcmb-actions">
<button class="pcmb-act" onclick="openComboDrawer('edit','任选午餐')">编辑</button>
<button class="pcmb-act">下架</button>
<button class="pcmb-act pcmb-act-del">删除</button>
</div>
</div>
</div>
<div class="pcmb-card disabled" data-combo-type="fixed" data-name="家庭聚餐套餐">
<div class="pcmb-cover">暂无图片</div>
<div class="pcmb-info">
<div class="pcmb-name">家庭聚餐套餐 <span class="g-tag pcmb-tag-fixed">固定套餐</span></div>
<div class="pcmb-desc">家庭聚餐,丰盛实惠</div>
<div class="pcmb-breakdown">红烧排骨 + 宫保鸡丁 + 麻婆豆腐 + 蛋炒饭×2 + 酸辣汤×2</div>
<div class="pcmb-price-row"><span class="pcmb-price">¥128</span><span class="pcmb-price-orig">¥168</span><span class="pcmb-price-save">已省 ¥40</span></div>
<div class="pcmb-stats-text">月销 12</div>
</div>
<div class="pcmb-right">
<span class="pcmb-status pcmb-status-off">已下架</span>
<div class="pcmb-actions">
<button class="pcmb-act" onclick="openComboDrawer('edit','家庭聚餐套餐')">编辑</button>
<button class="pcmb-act">上架</button>
<button class="pcmb-act pcmb-act-del">删除</button>
</div>
</div>
</div>
</div>
<!-- 添加/编辑套餐抽屉 -->
<div class="g-drawer-mask" id="pcmbDrawerMask" onclick="closeComboDrawer()"></div>
<div class="g-drawer" id="pcmbDrawer" style="width:560px;">
<div class="g-drawer-hd">
<span class="g-drawer-title" id="pcmbDrawerTitle">添加套餐</span>
<button class="g-drawer-close" onclick="closeComboDrawer()"><i data-lucide="x" style="width:18px;height:18px;"></i></button>
</div>
<div class="g-drawer-bd">
<div class="g-form-group">
<label class="g-form-label required">套餐名称</label>
<input class="g-input" id="pcmbName" placeholder="请输入套餐名称,如:双人超值套餐">
</div>
<div class="g-form-group">
<label class="g-form-label required">套餐类型</label>
<div class="pcmb-type-pills">
<span class="pcmb-type-pill active" onclick="selectComboType(this)">固定套餐</span>
<span class="pcmb-type-pill" onclick="selectComboType(this)">自选套餐</span>
</div>
<div class="g-hint">固定套餐:商品固定搭配;自选套餐:顾客从分组中自由选择</div>
</div>
<div class="g-form-group">
<label class="g-form-label">封面图</label>
<div class="pcmb-upload-box">
<i data-lucide="image-plus" style="width:24px;height:24px;"></i>
<span>上传图片</span>
</div>
</div>
<div class="g-form-group">
<label class="g-form-label">套餐描述</label>
<textarea class="g-textarea" rows="2" placeholder="请输入套餐描述,如:超值双人组合,荤素搭配"></textarea>
</div>
<div class="g-form-group">
<div class="pcmb-section-title">套餐分组</div>
<div class="g-hint" style="margin-bottom:10px;">每个分组可包含多个商品,顾客按分组选择</div>
<div id="pcmbGroups">
<div class="pcmb-group-card">
<div class="pcmb-group-header">
<input class="pcmb-input" value="主菜" placeholder="请输入分组名称">
<span></span><input class="pcmb-input pcmb-input-sm" type="number" value="1" min="1"><span></span>
<button class="pcmb-group-remove" onclick="removeComboGroup(this)"><i data-lucide="trash-2" style="width:14px;height:14px;"></i></button>
</div>
<div class="pcmb-group-items">
<div class="pcmb-group-item"><span>宫保鸡丁</span><span class="qty">×1</span><button class="pcmb-item-remove" onclick="this.closest('.pcmb-group-item').remove()"><i data-lucide="x" style="width:12px;height:12px;"></i></button></div>
<div class="pcmb-group-item"><span>鱼香肉丝</span><span class="qty">×1</span><button class="pcmb-item-remove" onclick="this.closest('.pcmb-group-item').remove()"><i data-lucide="x" style="width:12px;height:12px;"></i></button></div>
</div>
<button class="pcmb-add-link" onclick="openProductPicker({title:'添加商品到分组',onConfirm:addComboProducts})"><i data-lucide="plus" style="width:12px;height:12px;"></i> 添加商品</button>
</div>
<div class="pcmb-group-card">
<div class="pcmb-group-header">
<input class="pcmb-input" value="主食" placeholder="请输入分组名称">
<span></span><input class="pcmb-input pcmb-input-sm" type="number" value="1" min="1"><span></span>
<button class="pcmb-group-remove" onclick="removeComboGroup(this)"><i data-lucide="trash-2" style="width:14px;height:14px;"></i></button>
</div>
<div class="pcmb-group-items">
<div class="pcmb-group-item"><span>米饭</span><span class="qty">×2</span><button class="pcmb-item-remove" onclick="this.closest('.pcmb-group-item').remove()"><i data-lucide="x" style="width:12px;height:12px;"></i></button></div>
</div>
<button class="pcmb-add-link" onclick="openProductPicker({title:'添加商品到分组',onConfirm:addComboProducts})"><i data-lucide="plus" style="width:12px;height:12px;"></i> 添加商品</button>
</div>
</div>
<button class="pcmb-add-group-btn" onclick="addComboGroup()"><i data-lucide="plus" style="width:14px;height:14px;"></i> 添加分组</button>
</div>
<div class="g-form-group">
<label class="g-form-label required">价格设置</label>
<div class="pcmb-price-inputs">
<label>套餐价</label><input class="pcmb-input" type="number" placeholder="如68.00">
<label>原价</label><input class="pcmb-input" type="number" placeholder="自动计算" readonly style="background:#f9f9f9">
</div>
<div class="g-hint">原价根据分组内商品单价自动计算</div>
</div>
</div>
<div class="g-drawer-ft">
<button class="g-btn" onclick="closeComboDrawer()">取消</button>
<button class="g-btn g-btn-primary" onclick="closeComboDrawer()">保存</button>
</div>
</div>
<script>
function openComboDrawer(mode, name) {
document.getElementById('pcmbDrawer').classList.add('open');
document.getElementById('pcmbDrawerMask').classList.add('open');
document.getElementById('pcmbDrawerTitle').textContent = mode === 'edit' ? '编辑套餐' : '添加套餐';
if (mode === 'edit' && name) {
document.getElementById('pcmbName').value = name;
} else {
document.getElementById('pcmbName').value = '';
}
if (typeof lucide !== 'undefined') lucide.createIcons();
}
function closeComboDrawer() {
document.getElementById('pcmbDrawer').classList.remove('open');
document.getElementById('pcmbDrawerMask').classList.remove('open');
}
function selectComboType(el) {
el.parentElement.querySelectorAll('.pcmb-type-pill').forEach(function(p) { p.classList.remove('active'); });
el.classList.add('active');
}
function addComboGroup() {
var g = document.createElement('div');
g.className = 'pcmb-group-card';
var h = document.createElement('div');
h.className = 'pcmb-group-header';
h.innerHTML = '<input class="pcmb-input" placeholder="请输入分组名称"><span>选</span><input class="pcmb-input pcmb-input-sm" type="number" value="1" min="1"><span>份</span>';
var rb = document.createElement('button');
rb.className = 'pcmb-group-remove';
rb.onclick = function(){ removeComboGroup(this); };
rb.innerHTML = '<i data-lucide="trash-2" style="width:14px;height:14px;"></i>';
h.appendChild(rb);
var items = document.createElement('div');
items.className = 'pcmb-group-items';
var addBtn = document.createElement('button');
addBtn.className = 'pcmb-add-link';
addBtn.innerHTML = '<i data-lucide="plus" style="width:12px;height:12px;"></i> 添加商品';
addBtn.onclick = function(){ openProductPicker({title:'添加商品到分组',onConfirm:addComboProducts}); };
g.appendChild(h);
g.appendChild(items);
g.appendChild(addBtn);
document.getElementById('pcmbGroups').appendChild(g);
if (typeof lucide !== 'undefined') lucide.createIcons();
}
function removeComboGroup(el) {
var card = el.closest('.pcmb-group-card');
if (card) card.remove();
}
function filterComboType(el) {
el.parentElement.querySelectorAll('.pcmb-filter-pill').forEach(function(p) { p.classList.remove('active'); });
el.classList.add('active');
var type = el.getAttribute('data-type');
document.querySelectorAll('.pcmb-card').forEach(function(card) {
if (type === 'all') { card.style.display = ''; }
else { card.style.display = card.getAttribute('data-combo-type') === type ? '' : 'none'; }
});
}
function searchCombos(keyword) {
var kw = keyword.trim().toLowerCase();
document.querySelectorAll('.pcmb-card').forEach(function(card) {
var name = (card.getAttribute('data-name') || '').toLowerCase();
card.style.display = (!kw || name.includes(kw)) ? '' : 'none';
});
}
function addComboProducts(items) {
var groups = document.querySelectorAll('#pcmbGroups .pcmb-group-card');
var lastGroup = groups.length ? groups[groups.length - 1] : null;
if (!lastGroup) return;
var list = lastGroup.querySelector('.pcmb-group-items');
items.forEach(function(p) {
var d = document.createElement('div');
d.className = 'pcmb-group-item';
var nameSpan = document.createElement('span');
nameSpan.textContent = p.name;
var qtySpan = document.createElement('span');
qtySpan.className = 'qty';
qtySpan.textContent = '×1';
var rmBtn = document.createElement('button');
rmBtn.className = 'pcmb-item-remove';
rmBtn.innerHTML = '<i data-lucide="x" style="width:12px;height:12px;"></i>';
rmBtn.onclick = function(){ this.closest('.pcmb-group-item').remove(); };
d.appendChild(nameSpan);
d.appendChild(qtySpan);
d.appendChild(rmBtn);
list.appendChild(d);
});
if (typeof lucide !== 'undefined') lucide.createIcons();
}
</script>

473
pages/product-detail.html Normal file
View File

@@ -0,0 +1,473 @@
<!-- 商品详情页 -->
<style>
.pd-page{font-size:13px}
.pd-header{display:flex;align-items:center;gap:12px;margin-bottom:18px;flex-wrap:wrap}
.pd-back{width:34px;height:34px;border-radius:8px;border:1px solid #e5e7eb;background:#fff;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;color:#4b5563;transition:var(--g-transition);flex-shrink:0}
.pd-back:hover{border-color:var(--primary);color:var(--primary);box-shadow:var(--g-shadow-sm)}
.pd-back:focus{box-shadow:0 0 0 3px color-mix(in srgb, var(--primary) 12%, transparent)}
.pd-hd-name{font-size:18px;font-weight:700;color:#1a1a2e}
.pd-hd-spu{font-size:12px;color:#9ca3af;font-family:monospace}
.pd-hd-status{display:inline-block;padding:2px 12px;border-radius:6px;font-size:11px;font-weight:600;color:#fff;margin-left:4px}
.pd-hd-spacer{flex:1}
.pd-body{display:flex;gap:18px;align-items:flex-start}
.pd-nav{width:160px;flex-shrink:0;background:#fff;border-radius:10px;border:none;box-shadow:var(--g-shadow-md);padding:8px 0;position:sticky;top:0}
.pd-nav-item{display:block;padding:9px 20px;font-size:13px;color:#4b5563;cursor:pointer;border-left:3px solid transparent;transition:var(--g-transition);text-decoration:none}
.pd-nav-item:hover{color:var(--primary);background:#f8f9fb}
.pd-nav-item.active{color:var(--primary);font-weight:600;border-left-color:var(--primary);background:color-mix(in srgb,var(--primary) 6%,transparent)}
.pd-content{flex:1;min-width:0;display:flex;flex-direction:column;gap:16px}
.pd-row{display:flex;align-items:flex-start;margin-bottom:14px;gap:12px}
.pd-label{width:80px;flex-shrink:0;font-size:13px;color:#4b5563;font-weight:500;line-height:34px;text-align:right}
.pd-label.required::before{content:'*';color:#ef4444;margin-right:2px}
.pd-ctrl{flex:1;min-width:0}
.pd-inline{display:flex;gap:10px;align-items:center}
.pd-inline .g-input{width:auto;flex:1}
.pd-unit{font-size:12px;color:#9ca3af;white-space:nowrap}
/* Upload */
.pd-thumbs{display:flex;gap:10px;margin-top:12px;flex-wrap:wrap}
.pd-thumb{width:80px;height:80px;border-radius:8px;background:#f8f9fb;display:flex;align-items:center;justify-content:center;position:relative;border:1px solid #e5e7eb;transition:var(--g-transition)}
.pd-thumb:hover{box-shadow:var(--g-shadow-sm)}
.pd-thumb-main{border-color:var(--primary);box-shadow:0 0 0 1px var(--primary)}
.pd-thumb-badge{position:absolute;bottom:-1px;left:0;right:0;text-align:center;font-size:10px;color:#fff;background:var(--primary);border-radius:0 0 7px 7px;padding:1px 0}
.pd-thumb-del{position:absolute;top:-6px;right:-6px;width:18px;height:18px;border-radius:50%;background:#ef4444;color:#fff;font-size:12px;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;line-height:1;transition:var(--g-transition)}
.pd-thumb-del:hover{background:#dc2626}
/* Pills */
.pd-pills{display:flex;flex-wrap:wrap;gap:8px}
/* Toggle */
.pd-toggle-row{display:flex;align-items:center;gap:12px;margin-bottom:10px}
/* Tags */
.pd-tag-sel{display:inline-flex;align-items:center;padding:4px 12px;border-radius:6px;font-size:12px;cursor:pointer;border:1px solid #e5e7eb;transition:var(--g-transition);user-select:none;margin:0 8px 8px 0}
.pd-tag-sel:hover{border-color:var(--primary);box-shadow:var(--g-shadow-sm)}
.pd-tag-sel.selected{border-color:var(--primary);background:color-mix(in srgb,var(--primary) 8%,transparent);color:var(--primary);font-weight:600}
/* Shelf radio */
.pd-shelf-group{display:flex;flex-direction:column;gap:10px}
.pd-shelf-opt{display:flex;align-items:flex-start;gap:8px;padding:12px;border:1px solid #e5e7eb;border-radius:10px;cursor:pointer;transition:var(--g-transition)}
.pd-shelf-opt:hover{border-color:var(--primary);box-shadow:var(--g-shadow-sm)}
.pd-shelf-opt.active{border-color:var(--primary);background:color-mix(in srgb,var(--primary) 5%,transparent)}
.pd-shelf-opt input[type=radio]{margin-top:2px;accent-color:var(--primary);cursor:pointer}
.pd-shelf-opt-body{flex:1}
.pd-shelf-opt-title{font-size:13px;font-weight:600;color:#1a1a2e}
.pd-shelf-opt-desc{font-size:12px;color:#9ca3af;margin-top:2px}
.pd-shelf-time{margin-top:8px;display:none;gap:8px;align-items:center}
.pd-shelf-opt.active .pd-shelf-time.show-active{display:flex}
/* Save bar */
.pd-savebar{background:#fafbfc;border-radius:10px;border:none;box-shadow:var(--g-shadow-sm);padding:14px 24px;display:flex;justify-content:flex-end;gap:10px;position:sticky;bottom:0;z-index:10}
/* SKU Matrix */
.pd-sku-hd{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px}
.pd-sku-hd-left{display:flex;align-items:center;gap:10px}
.pd-sku-hd-left .g-card-title{margin:0}
.pd-sku-count{font-size:12px;color:#9ca3af;font-weight:400}
.pd-sku-hint{font-size:12px;color:#9ca3af}
.pd-sku-table{width:100%;border-collapse:collapse;font-size:13px}
.pd-sku-table th{text-align:left;padding:9px 12px;font-size:12px;font-weight:500;color:#6b7280;background:#f8f9fb;border-bottom:1px solid #f0f0f0;white-space:nowrap}
.pd-sku-table td{padding:8px 12px;border-bottom:1px solid #f3f4f6;color:#1a1a2e;vertical-align:middle}
.pd-sku-table tr:last-child td{border-bottom:none}
.pd-sku-table tr:hover td{background:color-mix(in srgb,var(--primary) 3%,#fff)}
.pd-sku-table input[type=number]{width:90px;height:30px;padding:0 8px;border:1px solid #e5e7eb;border-radius:6px;font-size:13px;color:#1a1a2e;transition:var(--g-transition);text-align:right}
.pd-sku-table input[type=number]:focus{border-color:var(--primary);box-shadow:0 0 0 3px color-mix(in srgb,var(--primary) 12%,transparent);outline:none}
.pd-sku-spec{display:inline-block;padding:2px 8px;border-radius:4px;font-size:11px;font-weight:500;background:#f0f0f0;color:#4b5563}
.pd-sku-spec.s1{background:color-mix(in srgb,var(--primary) 10%,#fff);color:var(--primary)}
.pd-sku-spec.s2{background:color-mix(in srgb,#fa8c16 10%,#fff);color:#fa8c16}
.pd-sku-code{font-size:11px;color:#9ca3af;font-family:monospace}
.pd-sku-stock-warn{color:#fa8c16;font-size:11px;display:flex;align-items:center;gap:3px;margin-top:2px}
.pd-sku-batch{display:flex;gap:8px;align-items:center;margin-bottom:12px}
.pd-sku-batch label{font-size:12px;color:#6b7280;white-space:nowrap}
.pd-sku-batch input[type=number]{width:100px;height:30px;padding:0 8px;border:1px solid #e5e7eb;border-radius:6px;font-size:13px;color:#1a1a2e}
.pd-sku-toggle{display:flex;align-items:center;justify-content:center}
</style>
<div class="pd-page">
<!-- Header -->
<div class="pd-header">
<button class="pd-back" onclick="navigateTo('商品列表')" title="返回列表">&#x2190;</button>
<span class="pd-hd-name" id="pdName">宫保鸡丁</span>
<span class="pd-hd-spu">SPU20240001</span>
<span class="pd-hd-status" style="background:#52c41a">在售</span>
<span class="pd-hd-spacer"></span>
<button class="g-btn g-btn-danger">删除商品</button>
<button class="g-btn">下架</button>
</div>
<div class="pd-body">
<!-- Left anchor nav -->
<div class="pd-nav" id="pdNav">
<a class="pd-nav-item active" data-target="pd-sec-basic" onclick="scrollToPdSec(this)">基本信息</a>
<a class="pd-nav-item" data-target="pd-sec-images" onclick="scrollToPdSec(this)">商品图片</a>
<a class="pd-nav-item" data-target="pd-sec-pricing" onclick="scrollToPdSec(this)">价格库存</a>
<a class="pd-nav-item" data-target="pd-sec-specs" onclick="scrollToPdSec(this)">规格做法</a>
<a class="pd-nav-item" data-target="pd-sec-sku" onclick="scrollToPdSec(this)">SKU管理</a>
<a class="pd-nav-item" data-target="pd-sec-addons" onclick="scrollToPdSec(this)">加料管理</a>
<a class="pd-nav-item" data-target="pd-sec-tags" onclick="scrollToPdSec(this)">商品标签</a>
<a class="pd-nav-item" data-target="pd-sec-shelf" onclick="scrollToPdSec(this)">上架设置</a>
</div>
<!-- Right content -->
<div class="pd-content" id="pdContent">
<!-- 基本信息 -->
<div class="g-card" id="pd-sec-basic">
<div class="g-card-title">基本信息</div>
<div class="pd-row">
<label class="pd-label required">商品名称</label>
<div class="pd-ctrl"><input class="g-input" value="宫保鸡丁" maxlength="30" /><div class="g-hint">最多30个字符</div></div>
</div>
<div class="pd-row">
<label class="pd-label">副标题</label>
<div class="pd-ctrl"><input class="g-input" value="经典川菜,香辣可口" maxlength="50" /></div>
</div>
<div class="pd-row">
<label class="pd-label">SPU编码</label>
<div class="pd-ctrl"><input class="g-input" value="SPU20240001" readonly style="background:#fafafa;color:#999" /></div>
</div>
<div class="pd-row">
<label class="pd-label required">所属分类</label>
<div class="pd-ctrl">
<select class="g-select" style="cursor:pointer">
<option>热销推荐</option><option selected>主食</option><option>小吃凉菜</option>
<option>汤羹粥品</option><option>饮品</option><option>酒水</option><option>套餐</option>
</select>
</div>
</div>
<div class="pd-row">
<label class="pd-label">商品描述</label>
<div class="pd-ctrl"><textarea class="g-textarea" rows="3" placeholder="详细描述,展示在商品详情页">花生米、鸡丁、干辣椒爆炒,麻辣鲜香,佐饭佳品。</textarea></div>
</div>
<div class="pd-row">
<label class="pd-label">排序权重</label>
<div class="pd-ctrl"><div class="pd-inline"><input class="g-input" type="number" min="0" value="10" style="width:100px" /><span class="pd-unit">数值越大越靠前</span></div></div>
</div>
</div>
<!-- 商品图片 -->
<div class="g-card" id="pd-sec-images">
<div class="g-card-title">商品图片</div>
<div class="g-upload-zone" onclick="this.querySelector('input').click()">
<input type="file" accept="image/*" multiple style="display:none" />
<svg viewBox="0 0 24 24" width="32" height="32" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 5v14M5 12h14"/></svg>
点击或拖拽上传图片<br><span style="font-size:11px;color:#bbb">建议尺寸 750×750最多5张首张为主图</span>
</div>
<div class="pd-thumbs">
<div class="pd-thumb pd-thumb-main"><svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#ccc" stroke-width="1.5"><path d="M3 6h18v12H3z"/><circle cx="12" cy="12" r="3"/></svg><span class="pd-thumb-badge">主图</span><button class="pd-thumb-del">&times;</button></div>
<div class="pd-thumb"><svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#ccc" stroke-width="1.5"><path d="M3 6h18v12H3z"/><circle cx="12" cy="12" r="3"/></svg><button class="pd-thumb-del">&times;</button></div>
<div class="pd-thumb"><svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#ccc" stroke-width="1.5"><path d="M3 6h18v12H3z"/><circle cx="12" cy="12" r="3"/></svg><button class="pd-thumb-del">&times;</button></div>
</div>
</div>
<!-- 价格库存 -->
<div class="g-card" id="pd-sec-pricing">
<div class="g-card-title">价格库存</div>
<div style="background:color-mix(in srgb,var(--primary) 6%,#fff);border:1px solid color-mix(in srgb,var(--primary) 15%,#fff);border-radius:8px;padding:8px 12px;margin-bottom:14px;font-size:12px;color:var(--primary);display:flex;align-items:center;gap:6px">
<i data-lucide="info" style="width:14px;height:14px;flex-shrink:0"></i>
已启用多规格售价和库存以下方「SKU管理」中的各规格组合为准此处为默认基准价
</div>
<div class="pd-row">
<label class="pd-label required">售价</label>
<div class="pd-ctrl"><div class="pd-inline"><span class="pd-unit">&yen;</span><input class="g-input" type="number" min="0" step="0.01" value="32" /></div></div>
</div>
<div class="pd-row">
<label class="pd-label">划线价</label>
<div class="pd-ctrl"><div class="pd-inline"><span class="pd-unit">&yen;</span><input class="g-input" type="number" min="0" step="0.01" value="38" /></div><div class="g-hint">展示在售价旁,划线显示原价</div></div>
</div>
<div class="pd-row">
<label class="pd-label">库存数量</label>
<div class="pd-ctrl"><div class="pd-inline"><input class="g-input" type="number" min="0" value="86" /><span class="pd-unit"></span></div></div>
</div>
<div class="pd-row">
<label class="pd-label">库存预警</label>
<div class="pd-ctrl"><div class="pd-inline"><input class="g-input" type="number" min="0" value="10" /><span class="pd-unit"></span></div><div class="g-hint">库存低于此值时提醒补货</div></div>
</div>
<div class="pd-row">
<label class="pd-label">打包费</label>
<div class="pd-ctrl"><div class="pd-inline"><span class="pd-unit">&yen;</span><input class="g-input" type="number" min="0" step="0.1" value="1.0" /><span class="pd-unit">/份</span></div><div class="g-hint">商品打包费优先;未设置时使用门店统一打包费</div></div>
</div>
</div>
<!-- 规格做法 -->
<div class="g-card" id="pd-sec-specs">
<div class="g-card-title">规格做法</div>
<div class="pd-row">
<label class="pd-label">关联规格</label>
<div class="pd-ctrl">
<div class="pd-pills">
<span class="g-pill" onclick="this.classList.toggle('checked')">杯型</span>
<span class="g-pill" onclick="this.classList.toggle('checked')">温度</span>
<span class="g-pill checked" onclick="this.classList.toggle('checked')">份量</span>
<span class="g-pill checked" onclick="this.classList.toggle('checked')">辣度</span>
<span class="g-pill" onclick="this.classList.toggle('checked')">甜度</span>
<span class="g-pill" onclick="this.classList.toggle('checked')">烹饪方式</span>
</div>
<div class="g-hint">选中后顾客下单时可选择对应规格,前往「规格做法」页面管理具体选项</div>
</div>
</div>
</div>
<!-- 加料管理 -->
<!-- SKU管理 -->
<div class="g-card" id="pd-sec-sku">
<div class="pd-sku-hd">
<div class="pd-sku-hd-left">
<div class="g-card-title">SKU管理</div>
<span class="pd-sku-count">共 8 个SKU</span>
</div>
<div class="pd-sku-hint">基于已选规格自动生成组合,可独立设置价格和库存</div>
</div>
<div class="pd-sku-batch">
<label>批量设价</label>
<span style="color:#9ca3af;font-size:12px">&yen;</span>
<input type="number" placeholder="统一售价" />
<button class="g-btn g-btn-sm" onclick="batchSkuPrice()">应用</button>
<span style="width:16px"></span>
<label>批量设库存</label>
<input type="number" placeholder="统一库存" />
<button class="g-btn g-btn-sm" onclick="batchSkuStock()">应用</button>
</div>
<table class="pd-sku-table">
<thead>
<tr>
<th>份量</th>
<th>辣度</th>
<th>SKU编码</th>
<th>售价 (&yen;)</th>
<th>划线价 (&yen;)</th>
<th>库存</th>
<th>状态</th>
</tr>
</thead>
<tbody>
<tr>
<td><span class="pd-sku-spec s1">整份</span></td>
<td><span class="pd-sku-spec s2">不辣</span></td>
<td><span class="pd-sku-code">SKU20240001-01</span></td>
<td><input type="number" value="32" min="0" step="0.01" /></td>
<td><input type="number" value="38" min="0" step="0.01" /></td>
<td>
<input type="number" value="50" min="0" />
</td>
<td class="pd-sku-toggle"><button class="g-toggle on" onclick="toggleSwitch(this)"></button></td>
</tr>
<tr>
<td><span class="pd-sku-spec s1">整份</span></td>
<td><span class="pd-sku-spec s2">微辣</span></td>
<td><span class="pd-sku-code">SKU20240001-02</span></td>
<td><input type="number" value="32" min="0" step="0.01" /></td>
<td><input type="number" value="38" min="0" step="0.01" /></td>
<td>
<input type="number" value="30" min="0" />
</td>
<td class="pd-sku-toggle"><button class="g-toggle on" onclick="toggleSwitch(this)"></button></td>
</tr>
<tr>
<td><span class="pd-sku-spec s1">整份</span></td>
<td><span class="pd-sku-spec s2">中辣</span></td>
<td><span class="pd-sku-code">SKU20240001-03</span></td>
<td><input type="number" value="32" min="0" step="0.01" /></td>
<td><input type="number" value="38" min="0" step="0.01" /></td>
<td>
<input type="number" value="20" min="0" />
</td>
<td class="pd-sku-toggle"><button class="g-toggle on" onclick="toggleSwitch(this)"></button></td>
</tr>
<tr>
<td><span class="pd-sku-spec s1">整份</span></td>
<td><span class="pd-sku-spec s2">特辣</span></td>
<td><span class="pd-sku-code">SKU20240001-04</span></td>
<td><input type="number" value="32" min="0" step="0.01" /></td>
<td><input type="number" value="38" min="0" step="0.01" /></td>
<td>
<input type="number" value="8" min="0" />
<div class="pd-sku-stock-warn"><i data-lucide="alert-triangle" style="width:11px;height:11px"></i>库存偏低</div>
</td>
<td class="pd-sku-toggle"><button class="g-toggle on" onclick="toggleSwitch(this)"></button></td>
</tr>
<tr>
<td><span class="pd-sku-spec s1">半份</span></td>
<td><span class="pd-sku-spec s2">不辣</span></td>
<td><span class="pd-sku-code">SKU20240001-05</span></td>
<td><input type="number" value="18" min="0" step="0.01" /></td>
<td><input type="number" value="22" min="0" step="0.01" /></td>
<td>
<input type="number" value="40" min="0" />
</td>
<td class="pd-sku-toggle"><button class="g-toggle on" onclick="toggleSwitch(this)"></button></td>
</tr>
<tr>
<td><span class="pd-sku-spec s1">半份</span></td>
<td><span class="pd-sku-spec s2">微辣</span></td>
<td><span class="pd-sku-code">SKU20240001-06</span></td>
<td><input type="number" value="18" min="0" step="0.01" /></td>
<td><input type="number" value="22" min="0" step="0.01" /></td>
<td>
<input type="number" value="25" min="0" />
</td>
<td class="pd-sku-toggle"><button class="g-toggle on" onclick="toggleSwitch(this)"></button></td>
</tr>
<tr>
<td><span class="pd-sku-spec s1">半份</span></td>
<td><span class="pd-sku-spec s2">中辣</span></td>
<td><span class="pd-sku-code">SKU20240001-07</span></td>
<td><input type="number" value="18" min="0" step="0.01" /></td>
<td><input type="number" value="22" min="0" step="0.01" /></td>
<td>
<input type="number" value="15" min="0" />
</td>
<td class="pd-sku-toggle"><button class="g-toggle on" onclick="toggleSwitch(this)"></button></td>
</tr>
<tr>
<td><span class="pd-sku-spec s1">半份</span></td>
<td><span class="pd-sku-spec s2">特辣</span></td>
<td><span class="pd-sku-code">SKU20240001-08</span></td>
<td><input type="number" value="18" min="0" step="0.01" /></td>
<td><input type="number" value="22" min="0" step="0.01" /></td>
<td>
<input type="number" value="0" min="0" />
<div class="pd-sku-stock-warn"><i data-lucide="alert-triangle" style="width:11px;height:11px"></i>已售罄</div>
</td>
<td class="pd-sku-toggle"><button class="g-toggle" onclick="toggleSwitch(this)"></button></td>
</tr>
</tbody>
</table>
</div>
<!-- 加料管理 -->
<div class="g-card" id="pd-sec-addons">
<div class="g-card-title">加料管理</div>
<div class="pd-row">
<label class="pd-label">关联加料</label>
<div class="pd-ctrl">
<div class="pd-pills">
<span class="g-pill checked" onclick="this.classList.toggle('checked')">加料</span>
<span class="g-pill" onclick="this.classList.toggle('checked')">配菜</span>
<span class="g-pill" onclick="this.classList.toggle('checked')">主食升级</span>
<span class="g-pill" onclick="this.classList.toggle('checked')">锅底</span>
</div>
<div class="g-hint">前往「加料管理」页面管理具体加料项和价格</div>
</div>
</div>
</div>
<!-- 商品标签 -->
<div class="g-card" id="pd-sec-tags">
<div class="g-card-title">商品标签</div>
<div style="display:flex;flex-wrap:wrap">
<span class="pd-tag-sel selected" onclick="this.classList.toggle('selected')">招牌</span>
<span class="pd-tag-sel" onclick="this.classList.toggle('selected')">必点</span>
<span class="pd-tag-sel" onclick="this.classList.toggle('selected')">新品</span>
<span class="pd-tag-sel" onclick="this.classList.toggle('selected')">限时</span>
<span class="pd-tag-sel" onclick="this.classList.toggle('selected')"></span>
<span class="pd-tag-sel" onclick="this.classList.toggle('selected')">素食</span>
<span class="pd-tag-sel" onclick="this.classList.toggle('selected')">低卡</span>
<span class="pd-tag-sel" onclick="this.classList.toggle('selected')">人气</span>
</div>
</div>
<!-- 上架设置 -->
<div class="g-card" id="pd-sec-shelf">
<div class="g-card-title">上架设置</div>
<div class="pd-shelf-group">
<div class="pd-shelf-opt" onclick="selectPdShelf(this)">
<input type="radio" name="pdShelf" value="draft" />
<div class="pd-shelf-opt-body">
<div class="pd-shelf-opt-title">暂不上架(草稿)</div>
<div class="pd-shelf-opt-desc">保存修改但不同步到顾客端</div>
</div>
</div>
<div class="pd-shelf-opt active" onclick="selectPdShelf(this)">
<input type="radio" name="pdShelf" value="now" checked />
<div class="pd-shelf-opt-body">
<div class="pd-shelf-opt-title">立即上架</div>
<div class="pd-shelf-opt-desc">保存后顾客可见</div>
</div>
</div>
<div class="pd-shelf-opt" onclick="selectPdShelf(this)">
<input type="radio" name="pdShelf" value="scheduled" />
<div class="pd-shelf-opt-body">
<div class="pd-shelf-opt-title">定时上架</div>
<div class="pd-shelf-opt-desc">到达指定时间后自动上架</div>
<div class="pd-shelf-time show-active" style="gap:8px;align-items:center">
<span style="font-size:12px;color:#666">上架时间</span>
<input class="g-input" type="datetime-local" style="width:auto;flex:1" />
</div>
</div>
</div>
</div>
</div>
<!-- Sticky save bar -->
<div class="pd-savebar">
<button class="g-btn" onclick="navigateTo('商品列表')">取消</button>
<button class="g-btn g-btn-primary">保存</button>
</div>
</div>
</div>
</div>
<script>
/* Anchor nav scroll */
function scrollToPdSec(el){
var id=el.dataset.target;
var sec=document.getElementById(id);
if(sec) sec.scrollIntoView({behavior:'smooth',block:'start'});
document.querySelectorAll('.pd-nav-item').forEach(function(n){n.classList.remove('active')});
el.classList.add('active');
}
/* Scroll spy */
(function(){
var content=document.getElementById('pdContent');
var nav=document.getElementById('pdNav');
if(!content||!nav) return;
var secs=content.querySelectorAll('.g-card[id]');
var items=nav.querySelectorAll('.pd-nav-item');
var area=document.getElementById('contentArea');
if(!area) return;
area.addEventListener('scroll',function(){
var top=area.scrollTop+60;
var current='';
secs.forEach(function(s){
if(s.offsetTop<=top) current=s.id;
});
items.forEach(function(it){
it.classList.toggle('active',it.dataset.target===current);
});
});
})();
/* Shelf mode */
function selectPdShelf(el){
var group=el.parentElement;
group.querySelectorAll('.pd-shelf-opt').forEach(function(r){r.classList.remove('active');r.querySelector('input').checked=false});
el.classList.add('active');
el.querySelector('input').checked=true;
}
/* Prefill from navigateTo options */
(function(){
var opts=window._pageOptions||{};
if(opts.name){
var el=document.getElementById('pdName');
if(el) el.textContent=opts.name;
}
})();
function toggleSwitch(el){el.classList.toggle('on')}
function batchSkuPrice(){
var v=document.querySelector('.pd-sku-batch input[placeholder="统一售价"]').value;
if(!v)return;
document.querySelectorAll('.pd-sku-table tbody td:nth-child(4) input').forEach(function(inp){inp.value=v});
}
function batchSkuStock(){
var v=document.querySelector('.pd-sku-batch input[placeholder="统一库存"]').value;
if(!v)return;
document.querySelectorAll('.pd-sku-table tbody td:nth-child(6) input').forEach(function(inp){inp.value=v});
}
</script>

247
pages/product-labels.html Normal file
View File

@@ -0,0 +1,247 @@
<!-- 商品标签页 -->
<style>
.plbl-toolbar { display:flex; align-items:center; gap:12px; margin-bottom:16px; box-shadow:var(--g-shadow-sm); border-radius:10px; padding:12px 16px; background:#fff; flex-wrap:wrap; }
.plbl-search { position:relative; }
.plbl-search input { height:34px; padding:0 10px 0 32px; border:1px solid #e5e7eb; border-radius:8px; font-size:13px; outline:none; width:200px; transition:var(--g-transition); }
.plbl-search input:focus { border-color:var(--primary); box-shadow:0 0 0 3px color-mix(in srgb, var(--primary) 12%, transparent); }
.plbl-search i { position:absolute; left:9px; top:50%; transform:translateY(-50%); color:#bbb; pointer-events:none; }
.plbl-stats { display:flex; gap:24px; margin-bottom:16px; padding:10px 16px; background:#fff; border-radius:10px; box-shadow:var(--g-shadow-sm); font-size:13px; color:#4b5563; }
.plbl-stats span { display:flex; align-items:center; gap:6px; }
.plbl-stats strong { color:#1a1a2e; font-weight:600; }
.plbl-card { background:#fff; border-radius:10px; border:none; box-shadow:var(--g-shadow-sm); padding:20px; }
.plbl-table { width:100%; border-collapse:collapse; font-size:13px; }
.plbl-table th { background:#f8f9fb; padding:10px 12px; text-align:left; font-weight:600; color:#6b7280; border-bottom:1px solid #f3f4f6; white-space:nowrap; }
.plbl-table td { padding:12px; border-bottom:1px solid #f3f4f6; color:#1a1a2e; vertical-align:middle; }
.plbl-table tr:hover td { background:color-mix(in srgb, var(--primary) 3%, #fff); }
.plbl-table tr:last-child td { border-bottom:none; }
.plbl-table tr.disabled td { opacity:0.5; }
.plbl-pill { display:inline-block; padding:2px 10px; border-radius:6px; font-size:12px; font-weight:600; color:#fff; line-height:20px; }
.plbl-color-dot { width:14px; height:14px; border-radius:50%; display:inline-block; vertical-align:middle; }
.plbl-status { display:inline-flex; align-items:center; gap:5px; font-size:12px; font-weight:600; }
.plbl-status-dot { width:6px; height:6px; border-radius:50%; }
.plbl-status-on .plbl-status-dot { background:#22c55e; }
.plbl-status-on { color:#22c55e; }
.plbl-status-off .plbl-status-dot { background:#9ca3af; }
.plbl-status-off { color:#9ca3af; }
.plbl-color-grid { display:grid; grid-template-columns:repeat(4,1fr); gap:10px; }
.plbl-color-swatch { width:100%; aspect-ratio:1; border-radius:10px; cursor:pointer; border:2px solid transparent; display:flex; align-items:center; justify-content:center; transition:var(--g-transition); position:relative; }
.plbl-color-swatch:hover { box-shadow:0 0 0 3px rgba(0,0,0,0.1); }
.plbl-color-swatch.selected { border-color:#1a1a2e; box-shadow:0 0 0 2px rgba(0,0,0,0.1); }
.plbl-color-swatch .plbl-check { display:none; color:#fff; }
.plbl-color-swatch.selected .plbl-check { display:block; }
.plbl-preview-area { padding:16px; background:#f8f9fb; border-radius:10px; border:1px solid #e5e7eb; display:flex; align-items:center; justify-content:center; min-height:40px; }
</style>
<div class="plbl-toolbar">
<select class="g-select" style="width:200px;">
<option>老三家外卖(朝阳店)</option>
<option>老三家外卖(海淀店)</option>
<option>老三家外卖(望京店)</option>
<option>老三家外卖(通州店)</option>
<option>老三家外卖(丰台店)</option>
</select>
<div class="plbl-search">
<i data-lucide="search" style="width:14px;height:14px;"></i>
<input type="text" placeholder="搜索标签名称…" oninput="searchLabels(this.value)" />
</div>
<div style="flex:1;"></div>
<button class="g-btn g-btn-primary" onclick="openLabelDrawer('add')"><i data-lucide="plus" style="width:14px;height:14px;"></i>添加标签</button>
</div>
<div class="plbl-stats">
<span>标签总数 <strong>8</strong></span>
<span>启用 <strong>7</strong></span>
<span>关联商品 <strong>51</strong></span>
</div>
<div class="plbl-card">
<table class="plbl-table">
<thead>
<tr>
<th>标签预览</th>
<th>标签名称</th>
<th>颜色</th>
<th>已关联商品</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr data-name="新品">
<td><span class="plbl-pill" style="background:#1890ff">新品</span></td>
<td>新品</td>
<td><span class="plbl-color-dot" style="background:#1890ff"></span> #1890ff</td>
<td>5个商品</td>
<td><span class="plbl-status plbl-status-on"><span class="plbl-status-dot"></span>启用</span></td>
<td><span class="g-action" onclick="openLabelDrawer('edit','新品','#1890ff')">编辑</span><span class="g-action g-action-danger">删除</span></td>
</tr>
<tr data-name="招牌">
<td><span class="plbl-pill" style="background:#ff4d4f">招牌</span></td>
<td>招牌</td>
<td><span class="plbl-color-dot" style="background:#ff4d4f"></span> #ff4d4f</td>
<td>12个商品</td>
<td><span class="plbl-status plbl-status-on"><span class="plbl-status-dot"></span>启用</span></td>
<td><span class="g-action" onclick="openLabelDrawer('edit','招牌','#ff4d4f')">编辑</span><span class="g-action g-action-danger">删除</span></td>
</tr>
<tr data-name="必点">
<td><span class="plbl-pill" style="background:#fa8c16">必点</span></td>
<td>必点</td>
<td><span class="plbl-color-dot" style="background:#fa8c16"></span> #fa8c16</td>
<td>8个商品</td>
<td><span class="plbl-status plbl-status-on"><span class="plbl-status-dot"></span>启用</span></td>
<td><span class="g-action" onclick="openLabelDrawer('edit','必点','#fa8c16')">编辑</span><span class="g-action g-action-danger">删除</span></td>
</tr>
<tr data-name="辣">
<td><span class="plbl-pill" style="background:#f5222d"></span></td>
<td></td>
<td><span class="plbl-color-dot" style="background:#f5222d"></span> #f5222d</td>
<td>15个商品</td>
<td><span class="plbl-status plbl-status-on"><span class="plbl-status-dot"></span>启用</span></td>
<td><span class="g-action" onclick="openLabelDrawer('edit','辣','#f5222d')">编辑</span><span class="g-action g-action-danger">删除</span></td>
</tr>
<tr data-name="素食">
<td><span class="plbl-pill" style="background:#52c41a">素食</span></td>
<td>素食</td>
<td><span class="plbl-color-dot" style="background:#52c41a"></span> #52c41a</td>
<td>6个商品</td>
<td><span class="plbl-status plbl-status-on"><span class="plbl-status-dot"></span>启用</span></td>
<td><span class="g-action" onclick="openLabelDrawer('edit','素食','#52c41a')">编辑</span><span class="g-action g-action-danger">删除</span></td>
</tr>
<tr data-name="限时">
<td><span class="plbl-pill" style="background:#722ed1">限时</span></td>
<td>限时</td>
<td><span class="plbl-color-dot" style="background:#722ed1"></span> #722ed1</td>
<td>3个商品</td>
<td><span class="plbl-status plbl-status-on"><span class="plbl-status-dot"></span>启用</span></td>
<td><span class="g-action" onclick="openLabelDrawer('edit','限时','#722ed1')">编辑</span><span class="g-action g-action-danger">删除</span></td>
</tr>
<tr data-name="买一送一">
<td><span class="plbl-pill" style="background:#eb2f96">买一送一</span></td>
<td>买一送一</td>
<td><span class="plbl-color-dot" style="background:#eb2f96"></span> #eb2f96</td>
<td>2个商品</td>
<td><span class="plbl-status plbl-status-on"><span class="plbl-status-dot"></span>启用</span></td>
<td><span class="g-action" onclick="openLabelDrawer('edit','买一送一','#eb2f96')">编辑</span><span class="g-action g-action-danger">删除</span></td>
</tr>
<tr data-name="已下架" class="disabled">
<td><span class="plbl-pill" style="background:#999">已下架</span></td>
<td>已下架</td>
<td><span class="plbl-color-dot" style="background:#999"></span> #999999</td>
<td>0个商品</td>
<td><span class="plbl-status plbl-status-off"><span class="plbl-status-dot"></span>停用</span></td>
<td><span class="g-action" onclick="openLabelDrawer('edit','已下架','#999')">编辑</span><span class="g-action g-action-danger">删除</span></td>
</tr>
</tbody>
</table>
</div>
<!-- 添加/编辑标签抽屉 -->
<div class="g-drawer-mask" id="plblDrawerMask" onclick="closeLabelDrawer()"></div>
<div class="g-drawer" id="plblDrawer" style="width:460px;">
<div class="g-drawer-hd">
<span class="g-drawer-title" id="plblDrawerTitle">添加标签</span>
<button class="g-drawer-close" onclick="closeLabelDrawer()"><i data-lucide="x" style="width:18px;height:18px;"></i></button>
</div>
<div class="g-drawer-bd">
<div class="g-form-group">
<label class="g-form-label required">标签名称</label>
<input class="g-input" id="plblName" placeholder="请输入标签名称,如:新品、招牌" oninput="updateLabelPreview()">
</div>
<div class="g-form-group">
<label class="g-form-label required">标签颜色</label>
<div class="plbl-color-grid" id="plblColorGrid">
<div class="plbl-color-swatch selected" style="background:#1890ff" onclick="pickLabelColor(this)" data-color="#1890ff"><span class="plbl-check"><i data-lucide="check" style="width:18px;height:18px;"></i></span></div>
<div class="plbl-color-swatch" style="background:#ff4d4f" onclick="pickLabelColor(this)" data-color="#ff4d4f"><span class="plbl-check"><i data-lucide="check" style="width:18px;height:18px;"></i></span></div>
<div class="plbl-color-swatch" style="background:#fa8c16" onclick="pickLabelColor(this)" data-color="#fa8c16"><span class="plbl-check"><i data-lucide="check" style="width:18px;height:18px;"></i></span></div>
<div class="plbl-color-swatch" style="background:#52c41a" onclick="pickLabelColor(this)" data-color="#52c41a"><span class="plbl-check"><i data-lucide="check" style="width:18px;height:18px;"></i></span></div>
<div class="plbl-color-swatch" style="background:#722ed1" onclick="pickLabelColor(this)" data-color="#722ed1"><span class="plbl-check"><i data-lucide="check" style="width:18px;height:18px;"></i></span></div>
<div class="plbl-color-swatch" style="background:#eb2f96" onclick="pickLabelColor(this)" data-color="#eb2f96"><span class="plbl-check"><i data-lucide="check" style="width:18px;height:18px;"></i></span></div>
<div class="plbl-color-swatch" style="background:#13c2c2" onclick="pickLabelColor(this)" data-color="#13c2c2"><span class="plbl-check"><i data-lucide="check" style="width:18px;height:18px;"></i></span></div>
<div class="plbl-color-swatch" style="background:#999" onclick="pickLabelColor(this)" data-color="#999"><span class="plbl-check"><i data-lucide="check" style="width:18px;height:18px;"></i></span></div>
</div>
</div>
<div class="g-form-group">
<label class="g-form-label">预览</label>
<div class="plbl-preview-area">
<span class="plbl-pill" id="plblPreviewPill" style="background:#1890ff">标签名称</span>
</div>
</div>
<div class="g-form-group">
<label class="g-form-label">排序</label>
<input class="g-input" id="plblSort" type="number" placeholder="数字越小越靠前0" value="0" min="0">
</div>
<div class="g-form-group">
<label class="g-form-label">启用状态</label>
<div style="display:flex;align-items:center;gap:10px;">
<button class="g-toggle on" id="plblToggle" onclick="toggleSwitch(this)"></button>
<span class="g-toggle-label">启用</span>
</div>
</div>
</div>
<div class="g-drawer-ft">
<button class="g-btn" onclick="closeLabelDrawer()">取消</button>
<button class="g-btn g-btn-primary" onclick="closeLabelDrawer()">保存</button>
</div>
</div>
<script>
var _plblColor = '#1890ff';
function openLabelDrawer(mode, name, color) {
document.getElementById('plblDrawerMask').classList.add('open');
document.getElementById('plblDrawer').classList.add('open');
if (mode === 'edit') {
document.getElementById('plblDrawerTitle').textContent = '编辑标签';
document.getElementById('plblName').value = name || '';
if (color) {
_plblColor = color;
document.querySelectorAll('.plbl-color-swatch').forEach(function(s) {
s.classList.toggle('selected', s.getAttribute('data-color') === color);
});
}
} else {
document.getElementById('plblDrawerTitle').textContent = '添加标签';
document.getElementById('plblName').value = '';
_plblColor = '#1890ff';
document.querySelectorAll('.plbl-color-swatch').forEach(function(s, i) {
s.classList.toggle('selected', i === 0);
});
document.getElementById('plblSort').value = '0';
var tog = document.getElementById('plblToggle');
if (!tog.classList.contains('on')) tog.classList.add('on');
}
updateLabelPreview();
if (typeof lucide !== 'undefined') lucide.createIcons();
}
function closeLabelDrawer() {
document.getElementById('plblDrawerMask').classList.remove('open');
document.getElementById('plblDrawer').classList.remove('open');
}
function pickLabelColor(el) {
document.querySelectorAll('.plbl-color-swatch').forEach(function(s) { s.classList.remove('selected'); });
el.classList.add('selected');
_plblColor = el.getAttribute('data-color');
updateLabelPreview();
}
function updateLabelPreview() {
var name = document.getElementById('plblName').value || '标签名称';
var pill = document.getElementById('plblPreviewPill');
pill.textContent = name;
pill.style.background = _plblColor;
}
function searchLabels(keyword) {
var kw = keyword.trim().toLowerCase();
document.querySelectorAll('.plbl-table tbody tr').forEach(function(row) {
var name = (row.getAttribute('data-name') || '').toLowerCase();
row.style.display = (!kw || name.includes(kw)) ? '' : 'none';
});
}
function toggleSwitch(el) {
el.classList.toggle('on');
}
</script>

1407
pages/product-list.html Normal file

File diff suppressed because it is too large Load Diff

323
pages/product-schedule.html Normal file
View File

@@ -0,0 +1,323 @@
<!-- 时段供应页 -->
<style>
.ptm-toolbar { display:flex; align-items:center; gap:12px; margin-bottom:16px; box-shadow:var(--g-shadow-sm); border-radius:10px; padding:12px 16px; background:#fff; flex-wrap:wrap; }
.ptm-search { position:relative; }
.ptm-search input { height:34px; padding:0 10px 0 32px; border:1px solid #e5e7eb; border-radius:8px; font-size:13px; outline:none; width:200px; transition:var(--g-transition); }
.ptm-search input:focus { border-color:var(--primary); box-shadow:0 0 0 3px color-mix(in srgb, var(--primary) 12%, transparent); }
.ptm-search i { position:absolute; left:9px; top:50%; transform:translateY(-50%); color:#bbb; pointer-events:none; }
.ptm-stats { display:flex; gap:24px; margin-bottom:16px; padding:10px 16px; background:#fff; border-radius:10px; box-shadow:var(--g-shadow-sm); font-size:13px; color:#4b5563; }
.ptm-stats span { display:flex; align-items:center; gap:6px; }
.ptm-stats strong { color:#1a1a2e; font-weight:600; }
.ptm-banner { background:#e6f7ff; border:none; border-radius:10px; padding:12px 16px; font-size:13px; color:#0050b3; margin-bottom:16px; display:flex; align-items:center; gap:8px; box-shadow:var(--g-shadow-sm); }
.ptm-card-hd { display:flex; align-items:center; gap:10px; margin-bottom:14px; }
.ptm-card-name { font-size:15px; font-weight:600; color:#1a1a2e; }
.ptm-tag-on { background:#dcfce7; color:#22c55e; border:1px solid #bbf7d0; border-radius:6px; font-weight:600; }
.ptm-tag-off { background:#f8f9fb; color:#9ca3af; border:1px solid #e5e7eb; border-radius:6px; font-weight:600; }
.ptm-time-row { display:flex; align-items:center; gap:16px; margin-bottom:12px; }
.ptm-time-text { font-size:22px; font-weight:700; color:#1a1a2e; letter-spacing:1px; }
.ptm-timebar-wrap { flex:1; height:18px; background:#f8f9fb; border-radius:9px; position:relative; overflow:hidden; }
.ptm-timebar-fill { position:absolute; top:0; bottom:0; border-radius:9px; opacity:0.7; }
.ptm-days { display:flex; gap:6px; margin-bottom:12px; flex-wrap:wrap; }
.ptm-day { width:36px; height:26px; border-radius:13px; font-size:12px; display:inline-flex; align-items:center; justify-content:center; border:1px solid #e5e7eb; color:#9ca3af; background:#f8f9fb; transition:var(--g-transition); }
.ptm-day.active { background:var(--primary); color:#fff; border-color:var(--primary); }
.ptm-products { display:flex; gap:6px; flex-wrap:wrap; margin-bottom:14px; }
.ptm-prod-pill { display:inline-block; padding:2px 10px; border-radius:12px; font-size:12px; background:#f8f9fb; color:#4b5563; border:1px solid #e5e7eb; transition:var(--g-transition); }
.ptm-prod-more { color:var(--primary); cursor:pointer; }
.ptm-card-ft { display:flex; gap:16px; border-top:1px solid #f3f4f6; padding-top:12px; }
.ptm-tl-title { font-size:15px; font-weight:600; color:#1a1a2e; margin-bottom:16px; padding-left:10px; border-left:3px solid var(--primary); }
.ptm-tl-axis { position:relative; height:20px; margin-bottom:4px; }
.ptm-tl-axis span { position:absolute; transform:translateX(-50%); font-size:10px; color:#9ca3af; }
.ptm-tl-rows { display:flex; flex-direction:column; gap:8px; }
.ptm-tl-row { display:flex; align-items:center; gap:10px; }
.ptm-tl-label { width:80px; font-size:12px; color:#4b5563; text-align:right; flex-shrink:0; }
.ptm-tl-track { flex:1; height:22px; background:#f8f9fb; border-radius:11px; position:relative; overflow:hidden; }
.ptm-tl-bar { position:absolute; top:0; bottom:0; border-radius:11px; display:flex; align-items:center; justify-content:center; font-size:10px; color:#fff; font-weight:500; white-space:nowrap; overflow:hidden; }
.ptm-fg-row { display:flex; gap:10px; align-items:center; }
.ptm-fg-row .g-input { flex:1; }
.ptm-fg-sep { font-size:14px; color:#9ca3af; }
.ptm-day-sel { display:flex; gap:6px; flex-wrap:wrap; margin-bottom:8px; }
.ptm-day-sel .ptm-day { cursor:pointer; transition:var(--g-transition); }
.ptm-day-quick { display:flex; gap:6px; }
.ptm-prod-search { position:relative; margin-bottom:10px; }
.ptm-prod-selected { display:flex; gap:6px; flex-wrap:wrap; }
.ptm-prod-chip { display:inline-flex; align-items:center; gap:4px; padding:4px 10px; border-radius:14px; font-size:12px; background:#f0f5ff; color:var(--primary); border:1px solid #adc6ff; transition:var(--g-transition); }
.ptm-prod-chip-x { cursor:pointer; color:#9ca3af; display:flex; align-items:center; transition:var(--g-transition); }
.ptm-prod-chip-x:hover { color:#ef4444; }
</style>
<div class="ptm-toolbar">
<select class="g-select" style="width:200px;">
<option>老三家外卖(朝阳店)</option>
<option>老三家外卖(海淀店)</option>
<option>老三家外卖(望京店)</option>
<option>老三家外卖(通州店)</option>
<option>老三家外卖(丰台店)</option>
</select>
<div class="ptm-search">
<i data-lucide="search" style="width:14px;height:14px;"></i>
<input type="text" placeholder="搜索规则名称…" oninput="searchSchedules(this.value)" />
</div>
<div style="flex:1;"></div>
<button class="g-btn g-btn-primary" onclick="openScheduleDrawer('add')"><i data-lucide="plus" style="width:14px;height:14px;"></i>添加时段规则</button>
</div>
<div class="ptm-stats">
<span>时段规则 <strong>4</strong></span>
<span>启用 <strong>3</strong></span>
<span>覆盖商品 <strong>14</strong></span>
</div>
<div class="ptm-banner">
<i data-lucide="info" style="width:16px;height:16px;flex-shrink:0;"></i>
设置商品在特定时段内供应,未被任何规则覆盖的商品默认全天供应。
</div>
<div id="ptmRuleList">
<div class="g-card" style="margin-bottom:16px" data-name="早餐供应">
<div class="ptm-card-hd">
<span class="ptm-card-name">早餐供应</span>
<span class="g-tag ptm-tag-on">启用</span>
</div>
<div class="ptm-time-row">
<span class="ptm-time-text">06:00 ~ 10:00</span>
<div class="ptm-timebar-wrap">
<div class="ptm-timebar-fill" style="left:25%;width:16.67%;background:#1890ff"></div>
</div>
</div>
<div class="ptm-days">
<span class="ptm-day active">周一</span><span class="ptm-day active">周二</span><span class="ptm-day active">周三</span><span class="ptm-day active">周四</span><span class="ptm-day active">周五</span><span class="ptm-day active">周六</span><span class="ptm-day active">周日</span>
</div>
<div class="ptm-products">
<span class="ptm-prod-pill">皮蛋瘦肉粥</span><span class="ptm-prod-pill">小笼包</span><span class="ptm-prod-pill">油条</span><span class="ptm-prod-pill ptm-prod-more">+2更多</span>
</div>
<div class="ptm-card-ft">
<span class="g-action" onclick="openScheduleDrawer('edit','早餐供应')">编辑</span>
<span class="g-action">停用</span>
<span class="g-action g-action-danger">删除</span>
</div>
</div>
<div class="g-card" style="margin-bottom:16px" data-name="午市套餐">
<div class="ptm-card-hd">
<span class="ptm-card-name">午市套餐</span>
<span class="g-tag ptm-tag-on">启用</span>
</div>
<div class="ptm-time-row">
<span class="ptm-time-text">11:00 ~ 14:00</span>
<div class="ptm-timebar-wrap">
<div class="ptm-timebar-fill" style="left:45.83%;width:12.5%;background:#52c41a"></div>
</div>
</div>
<div class="ptm-days">
<span class="ptm-day active">周一</span><span class="ptm-day active">周二</span><span class="ptm-day active">周三</span><span class="ptm-day active">周四</span><span class="ptm-day active">周五</span><span class="ptm-day">周六</span><span class="ptm-day">周日</span>
</div>
<div class="ptm-products">
<span class="ptm-prod-pill">单人工作餐</span><span class="ptm-prod-pill">任选午餐</span>
</div>
<div class="ptm-card-ft">
<span class="g-action" onclick="openScheduleDrawer('edit','午市套餐')">编辑</span>
<span class="g-action">停用</span>
<span class="g-action g-action-danger">删除</span>
</div>
</div>
<div class="g-card" style="margin-bottom:16px" data-name="下午茶">
<div class="ptm-card-hd">
<span class="ptm-card-name">下午茶</span>
<span class="g-tag ptm-tag-on">启用</span>
</div>
<div class="ptm-time-row">
<span class="ptm-time-text">14:00 ~ 17:00</span>
<div class="ptm-timebar-wrap">
<div class="ptm-timebar-fill" style="left:58.33%;width:12.5%;background:#722ed1"></div>
</div>
</div>
<div class="ptm-days">
<span class="ptm-day active">周一</span><span class="ptm-day active">周二</span><span class="ptm-day active">周三</span><span class="ptm-day active">周四</span><span class="ptm-day active">周五</span><span class="ptm-day active">周六</span><span class="ptm-day active">周日</span>
</div>
<div class="ptm-products">
<span class="ptm-prod-pill">冰粉</span><span class="ptm-prod-pill">奶茶</span><span class="ptm-prod-pill">蛋糕</span><span class="ptm-prod-pill ptm-prod-more">+1更多</span>
</div>
<div class="ptm-card-ft">
<span class="g-action" onclick="openScheduleDrawer('edit','下午茶')">编辑</span>
<span class="g-action">停用</span>
<span class="g-action g-action-danger">删除</span>
</div>
</div>
<div class="g-card" style="margin-bottom:16px;opacity:0.5" data-name="夜宵时段">
<div class="ptm-card-hd">
<span class="ptm-card-name">夜宵时段</span>
<span class="g-tag ptm-tag-off">停用</span>
</div>
<div class="ptm-time-row">
<span class="ptm-time-text">21:00 ~ 02:00 (次日)</span>
<div class="ptm-timebar-wrap">
<div class="ptm-timebar-fill" style="left:87.5%;width:12.5%;background:#fa8c16"></div>
<div class="ptm-timebar-fill" style="left:0%;width:8.33%;background:#fa8c16"></div>
</div>
</div>
<div class="ptm-days">
<span class="ptm-day">周一</span><span class="ptm-day">周二</span><span class="ptm-day">周三</span><span class="ptm-day">周四</span><span class="ptm-day active">周五</span><span class="ptm-day active">周六</span><span class="ptm-day active">周日</span>
</div>
<div class="ptm-products">
<span class="ptm-prod-pill">烧烤拼盘</span><span class="ptm-prod-pill">啤酒</span><span class="ptm-prod-pill">毛豆</span><span class="ptm-prod-pill ptm-prod-more">+1更多</span>
</div>
<div class="ptm-card-ft">
<span class="g-action" onclick="openScheduleDrawer('edit','夜宵时段')">编辑</span>
<span class="g-action">启用</span>
<span class="g-action g-action-danger">删除</span>
</div>
</div>
</div>
<div class="g-card">
<div class="ptm-tl-title">今日供应时间轴</div>
<div class="ptm-tl-axis" style="margin-left:90px;margin-right:10px;">
<span style="left:0%">0</span><span style="left:12.5%">3</span><span style="left:25%">6</span><span style="left:37.5%">9</span><span style="left:50%">12</span><span style="left:62.5%">15</span><span style="left:75%">18</span><span style="left:87.5%">21</span><span style="left:100%">24</span>
</div>
<div class="ptm-tl-rows">
<div class="ptm-tl-row">
<span class="ptm-tl-label">早餐供应</span>
<div class="ptm-tl-track">
<div class="ptm-tl-bar" style="left:25%;width:16.67%;background:#1890ff">06~10</div>
</div>
</div>
<div class="ptm-tl-row">
<span class="ptm-tl-label">午市套餐</span>
<div class="ptm-tl-track">
<div class="ptm-tl-bar" style="left:45.83%;width:12.5%;background:#52c41a">11~14</div>
</div>
</div>
<div class="ptm-tl-row">
<span class="ptm-tl-label">下午茶</span>
<div class="ptm-tl-track">
<div class="ptm-tl-bar" style="left:58.33%;width:12.5%;background:#722ed1">14~17</div>
</div>
</div>
<div class="ptm-tl-row">
<span class="ptm-tl-label">夜宵时段</span>
<div class="ptm-tl-track">
<div class="ptm-tl-bar" style="left:87.5%;width:12.5%;background:#fa8c16;border-radius:11px 0 0 11px">21~24</div>
<div class="ptm-tl-bar" style="left:0%;width:8.33%;background:#fa8c16;border-radius:0 11px 11px 0">0~2</div>
</div>
</div>
</div>
</div>
<!-- 添加/编辑时段规则抽屉 -->
<div class="g-drawer-mask" id="ptmDrawerMask" onclick="closeScheduleDrawer()"></div>
<div class="g-drawer" id="ptmDrawer" style="width:520px">
<div class="g-drawer-hd">
<span class="g-drawer-title" id="ptmDrawerTitle">添加时段规则</span>
<button class="g-drawer-close" onclick="closeScheduleDrawer()"><i data-lucide="x" style="width:18px;height:18px;"></i></button>
</div>
<div class="g-drawer-bd">
<div class="g-form-group">
<label class="g-form-label required">规则名称</label>
<input class="g-input" id="ptmRuleName" placeholder="请输入规则名称,如:早餐供应、午市套餐">
</div>
<div class="g-form-group">
<label class="g-form-label required">时间范围</label>
<div class="ptm-fg-row">
<input class="g-input" type="time" id="ptmTimeStart" value="06:00">
<span class="ptm-fg-sep">~</span>
<input class="g-input" type="time" id="ptmTimeEnd" value="10:00">
</div>
<div class="g-hint">结束时间小于开始时间时视为跨天(如 21:00~02:00</div>
</div>
<div class="g-form-group">
<label class="g-form-label required">适用星期</label>
<div class="ptm-day-sel" id="ptmDaySel">
<span class="ptm-day active" onclick="toggleSchedDay(this)">周一</span>
<span class="ptm-day active" onclick="toggleSchedDay(this)">周二</span>
<span class="ptm-day active" onclick="toggleSchedDay(this)">周三</span>
<span class="ptm-day active" onclick="toggleSchedDay(this)">周四</span>
<span class="ptm-day active" onclick="toggleSchedDay(this)">周五</span>
<span class="ptm-day active" onclick="toggleSchedDay(this)">周六</span>
<span class="ptm-day active" onclick="toggleSchedDay(this)">周日</span>
</div>
<div class="ptm-day-quick">
<button class="g-btn g-btn-sm" onclick="schedQuickDays('all')">全选</button>
<button class="g-btn g-btn-sm" onclick="schedQuickDays('weekday')">工作日</button>
<button class="g-btn g-btn-sm" onclick="schedQuickDays('weekend')">周末</button>
</div>
</div>
<div class="g-form-group">
<label class="g-form-label required">关联商品</label>
<div class="ptm-prod-search">
<input class="g-input" placeholder="搜索并选择要关联的商品…" onclick="openProductPicker({title:'关联商品',subtitle:'时段规则',onConfirm:function(items){var c=document.getElementById('ptmProdSelected');items.forEach(function(p){var s=document.createElement('span');s.className='ptm-prod-chip';s.innerHTML=p.name+' <span class=ptm-prod-chip-x onclick=removeSchedProduct(this)><i data-lucide=x style=width:12px;height:12px></i></span>';c.appendChild(s)});if(typeof lucide!=='undefined')lucide.createIcons()}})" readonly style="cursor:pointer">
</div>
<div class="ptm-prod-selected" id="ptmProdSelected">
<span class="ptm-prod-chip">皮蛋瘦肉粥 <span class="ptm-prod-chip-x" onclick="removeSchedProduct(this)"><i data-lucide="x" style="width:12px;height:12px;"></i></span></span>
<span class="ptm-prod-chip">小笼包 <span class="ptm-prod-chip-x" onclick="removeSchedProduct(this)"><i data-lucide="x" style="width:12px;height:12px;"></i></span></span>
<span class="ptm-prod-chip">油条 <span class="ptm-prod-chip-x" onclick="removeSchedProduct(this)"><i data-lucide="x" style="width:12px;height:12px;"></i></span></span>
</div>
</div>
<div class="g-form-group">
<label class="g-form-label">启用状态</label>
<div style="display:flex;align-items:center;gap:10px;">
<button class="g-toggle on" id="ptmToggle" onclick="toggleSwitch(this)"></button>
<span class="g-toggle-label">启用</span>
</div>
</div>
</div>
<div class="g-drawer-ft">
<button class="g-btn" onclick="closeScheduleDrawer()">取消</button>
<button class="g-btn g-btn-primary" onclick="closeScheduleDrawer()">保存</button>
</div>
</div>
<script>
function openScheduleDrawer(mode, name) {
document.getElementById('ptmDrawerMask').classList.add('open');
document.getElementById('ptmDrawer').classList.add('open');
if (mode === 'edit') {
document.getElementById('ptmDrawerTitle').textContent = '编辑时段规则';
document.getElementById('ptmRuleName').value = name || '';
} else {
document.getElementById('ptmDrawerTitle').textContent = '添加时段规则';
document.getElementById('ptmRuleName').value = '';
document.getElementById('ptmTimeStart').value = '06:00';
document.getElementById('ptmTimeEnd').value = '10:00';
schedQuickDays('all');
var tog = document.getElementById('ptmToggle');
if (!tog.classList.contains('on')) tog.classList.add('on');
}
if (typeof lucide !== 'undefined') lucide.createIcons();
}
function closeScheduleDrawer() {
document.getElementById('ptmDrawerMask').classList.remove('open');
document.getElementById('ptmDrawer').classList.remove('open');
}
function toggleSchedDay(el) {
el.classList.toggle('active');
}
function schedQuickDays(mode) {
var days = document.querySelectorAll('#ptmDaySel .ptm-day');
days.forEach(function(d, i) {
if (mode === 'all') d.classList.add('active');
else if (mode === 'weekday') d.classList.toggle('active', i < 5);
else if (mode === 'weekend') d.classList.toggle('active', i >= 5);
});
}
function removeSchedProduct(el) {
el.closest('.ptm-prod-chip').remove();
}
function searchSchedules(keyword) {
var kw = keyword.trim().toLowerCase();
document.querySelectorAll('#ptmRuleList > .g-card').forEach(function(card) {
var name = (card.getAttribute('data-name') || '').toLowerCase();
card.style.display = (!kw || name.includes(kw)) ? '' : 'none';
});
}
function toggleSwitch(el) {
el.classList.toggle('on');
}
</script>

328
pages/product-specs.html Normal file
View File

@@ -0,0 +1,328 @@
<!-- 规格做法页 -->
<style>
.psp-toolbar { display:flex; align-items:center; gap:12px; margin-bottom:16px; box-shadow:var(--g-shadow-sm); border-radius:10px; padding:12px 16px; background:#fff; }
.psp-store-select { height:34px; border-radius:8px; border:1px solid #e5e7eb; padding:0 12px; font-size:13px; min-width:180px; outline:none; color:#1a1a2e; transition:var(--g-transition); }
.psp-store-select:focus { border-color:var(--primary); box-shadow:0 0 0 3px color-mix(in srgb,var(--primary) 12%,transparent); }
.psp-spacer { flex:1; }
.psp-search { width:200px; height:34px; border-radius:8px; border:1px solid #e5e7eb; padding:0 10px 0 32px; font-size:13px; outline:none; color:#1a1a2e; transition:var(--g-transition); background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%239ca3af' stroke-width='2'%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cpath d='m21 21-4.3-4.3'/%3E%3C/svg%3E") 10px center no-repeat; }
.psp-search:focus { border-color:var(--primary); box-shadow:0 0 0 3px color-mix(in srgb,var(--primary) 12%,transparent); }
/* 筛选标签 */
.psp-filter-tabs { display:flex; gap:4px; }
.psp-filter-tab { height:30px; padding:0 14px; border-radius:6px; font-size:12px; cursor:pointer; border:1px solid transparent; background:transparent; color:#4b5563; transition:var(--g-transition); font-weight:500; }
.psp-filter-tab:hover { background:#f3f4f6; }
.psp-filter-tab.active { background:color-mix(in srgb,var(--primary) 10%,transparent); color:var(--primary); border-color:color-mix(in srgb,var(--primary) 20%,transparent); font-weight:600; }
.psp-filter-tab .count { font-size:11px; margin-left:3px; opacity:0.7; }
/* 统计条 */
.psp-stats { display:flex; gap:12px; margin-bottom:16px; }
.psp-stat { display:flex; align-items:center; gap:8px; padding:10px 16px; background:#fff; border-radius:8px; box-shadow:var(--g-shadow-sm); font-size:13px; color:#4b5563; }
.psp-stat-num { font-size:18px; font-weight:700; color:#1a1a2e; }
.psp-stat-dot { width:8px; height:8px; border-radius:50%; flex-shrink:0; }
.psp-btn-dashed { border-style:dashed; width:100%; justify-content:center; margin-top:8px; color:#9ca3af; }
.psp-btn-dashed:hover { border-color:var(--primary); color:var(--primary); }
.psp-grid { display:grid; grid-template-columns:repeat(2,1fr); gap:16px; }
.psp-card {
background:#fff; border-radius:10px; border:none; box-shadow:var(--g-shadow-sm); padding:20px;
font-size:13px; display:flex; flex-direction:column; gap:10px; transition:var(--g-transition);
}
.psp-card:hover { box-shadow:var(--g-shadow-md); }
.psp-card.disabled { opacity:0.5; }
.psp-card-hd { display:flex; align-items:center; gap:8px; }
.psp-card-hd .name { font-weight:600; font-size:14px; color:#1a1a2e; }
.psp-card-meta { color:#9ca3af; font-size:12px; display:flex; align-items:center; gap:8px; }
.psp-pills { display:flex; flex-wrap:wrap; gap:6px; }
.psp-pill {
display:inline-block; padding:2px 10px; border-radius:12px;
background:#f8f9fb; font-size:12px; color:#1a1a2e;
}
.psp-pill .price { color:var(--primary); margin-left:2px; }
.psp-card-assoc { font-size:12px; color:#9ca3af; }
.psp-card-ft { display:flex; gap:16px; border-top:1px solid #f3f4f6; padding-top:10px; }
.psp-pill-group { display:flex; gap:8px; }
.psp-pill-btn {
height:34px; padding:0 16px; border-radius:8px; font-size:13px; cursor:pointer;
border:1px solid #e5e7eb; background:#fff; color:#1a1a2e; transition:var(--g-transition);
}
.psp-pill-btn:hover { border-color:var(--primary); color:var(--primary); }
.psp-pill-btn:focus { box-shadow:0 0 0 3px color-mix(in srgb, var(--primary) 12%, transparent); }
.psp-pill-btn.active { background:var(--primary); color:#fff; border-color:var(--primary); }
.psp-opt-list { display:flex; flex-direction:column; gap:8px; }
.psp-opt-row { display:flex; align-items:center; gap:8px; }
.psp-opt-row input[type="text"] {
flex:1; height:34px; padding:0 10px; border:1px solid #e5e7eb; border-radius:8px;
font-size:13px; outline:none; transition:var(--g-transition);
}
.psp-opt-row input[type="text"]:focus { border-color:var(--primary); box-shadow:0 0 0 3px color-mix(in srgb, var(--primary) 12%, transparent); }
.psp-opt-row .psp-price-wrap {
width:120px; display:flex; align-items:center; height:34px; border:1px solid #e5e7eb;
border-radius:8px; overflow:hidden; font-size:13px; flex-shrink:0; transition:var(--g-transition);
}
.psp-opt-row .psp-price-wrap span { padding:0 8px; color:#9ca3af; background:#f8f9fb; height:100%; display:flex; align-items:center; border-right:1px solid #e5e7eb; }
.psp-opt-row .psp-price-wrap input {
border:none; outline:none; width:100%; height:100%; padding:0 8px; font-size:13px;
}
.psp-opt-row .psp-price-wrap input:focus { box-shadow:none; }
.psp-opt-del {
width:34px; height:34px; border:1px solid #e5e7eb; border-radius:8px; background:#fff;
cursor:pointer; color:#9ca3af; display:flex; align-items:center; justify-content:center; font-size:14px; flex-shrink:0; transition:var(--g-transition);
}
.psp-opt-del:hover { border-color:#ef4444; color:#ef4444; background:#fef2f2; }
</style>
<!-- 工具栏 -->
<div class="psp-toolbar">
<select class="psp-store-select">
<option>老三家外卖 总店</option>
<option>老三家外卖 朝阳店</option>
<option>老三家外卖 海淀店</option>
<option>老三家外卖 丰台店</option>
<option>老三家外卖 通州店</option>
</select>
<div class="psp-filter-tabs">
<button class="psp-filter-tab active" onclick="filterSpecs('all',this)">全部<span class="count">6</span></button>
<button class="psp-filter-tab" onclick="filterSpecs('spec',this)">规格<span class="count">3</span></button>
<button class="psp-filter-tab" onclick="filterSpecs('method',this)">做法<span class="count">3</span></button>
</div>
<span class="psp-spacer"></span>
<input class="psp-search" placeholder="搜索模板名称…" oninput="searchSpecs(this.value)">
<button class="g-btn g-btn-primary" onclick="openSpecDrawer('add')">+ 添加模板</button>
</div>
<!-- 统计条 -->
<div class="psp-stats">
<div class="psp-stat"><span class="psp-stat-dot" style="background:#1890ff"></span>规格模板 <span class="psp-stat-num">3</span></div>
<div class="psp-stat"><span class="psp-stat-dot" style="background:#fa8c16"></span>做法模板 <span class="psp-stat-num">3</span></div>
<div class="psp-stat"><span class="psp-stat-dot" style="background:#52c41a"></span>总关联商品 <span class="psp-stat-num">79</span></div>
</div>
<!-- 卡片网格 -->
<div class="psp-grid" id="pspGrid">
<div class="psp-card" data-type="spec" data-name="杯型">
<div class="psp-card-hd"><span class="name">杯型</span><span class="g-tag g-tag-blue">规格</span></div>
<div class="psp-card-meta">单选 · 必选</div>
<div class="psp-pills">
<span class="psp-pill">大杯 <span class="price">+3元</span></span>
<span class="psp-pill">中杯</span>
<span class="psp-pill">小杯 <span class="price">-2元</span></span>
</div>
<div class="psp-card-assoc">已关联 12 个商品</div>
<div class="psp-card-ft">
<button class="g-action" onclick="openSpecDrawer('edit','杯型')">编辑</button>
<button class="g-action">复制</button>
<button class="g-action g-action-danger">删除</button>
</div>
</div>
<div class="psp-card" data-type="spec" data-name="温度">
<div class="psp-card-hd"><span class="name">温度</span><span class="g-tag g-tag-blue">规格</span></div>
<div class="psp-card-meta">单选 · 必选</div>
<div class="psp-pills">
<span class="psp-pill"></span>
<span class="psp-pill"></span>
<span class="psp-pill"></span>
<span class="psp-pill">去冰</span>
</div>
<div class="psp-card-assoc">已关联 20 个商品</div>
<div class="psp-card-ft">
<button class="g-action" onclick="openSpecDrawer('edit','温度')">编辑</button>
<button class="g-action">复制</button>
<button class="g-action g-action-danger">删除</button>
</div>
</div>
<div class="psp-card" data-type="spec" data-name="份量">
<div class="psp-card-hd"><span class="name">份量</span><span class="g-tag g-tag-blue">规格</span></div>
<div class="psp-card-meta">单选 · 可选</div>
<div class="psp-pills">
<span class="psp-pill">整份</span>
<span class="psp-pill">半份 <span class="price">-5元</span></span>
</div>
<div class="psp-card-assoc">已关联 8 个商品</div>
<div class="psp-card-ft">
<button class="g-action" onclick="openSpecDrawer('edit','份量')">编辑</button>
<button class="g-action">复制</button>
<button class="g-action g-action-danger">删除</button>
</div>
</div>
<div class="psp-card" data-type="method" data-name="辣度">
<div class="psp-card-hd"><span class="name">辣度</span><span class="g-tag g-tag-orange">做法</span></div>
<div class="psp-card-meta">单选 · 必选</div>
<div class="psp-pills">
<span class="psp-pill">不辣</span>
<span class="psp-pill">微辣</span>
<span class="psp-pill">中辣</span>
<span class="psp-pill">特辣</span>
</div>
<div class="psp-card-assoc">已关联 15 个商品</div>
<div class="psp-card-ft">
<button class="g-action" onclick="openSpecDrawer('edit','辣度')">编辑</button>
<button class="g-action">复制</button>
<button class="g-action g-action-danger">删除</button>
</div>
</div>
<div class="psp-card" data-type="method" data-name="甜度">
<div class="psp-card-hd"><span class="name">甜度</span><span class="g-tag g-tag-orange">做法</span></div>
<div class="psp-card-meta">单选 · 必选</div>
<div class="psp-pills">
<span class="psp-pill">全糖</span>
<span class="psp-pill">七分糖</span>
<span class="psp-pill">半糖</span>
<span class="psp-pill">三分糖</span>
<span class="psp-pill">无糖</span>
</div>
<div class="psp-card-assoc">已关联 18 个商品</div>
<div class="psp-card-ft">
<button class="g-action" onclick="openSpecDrawer('edit','甜度')">编辑</button>
<button class="g-action">复制</button>
<button class="g-action g-action-danger">删除</button>
</div>
</div>
<div class="psp-card disabled" data-type="method" data-name="烹饪方式">
<div class="psp-card-hd"><span class="name">烹饪方式</span><span class="g-tag g-tag-orange">做法</span><span class="g-tag g-tag-gray">已停用</span></div>
<div class="psp-card-meta">单选 · 可选</div>
<div class="psp-pills">
<span class="psp-pill">清蒸</span>
<span class="psp-pill">红烧</span>
<span class="psp-pill">干煸</span>
<span class="psp-pill">水煮</span>
</div>
<div class="psp-card-assoc">已关联 6 个商品</div>
<div class="psp-card-ft">
<button class="g-action" onclick="openSpecDrawer('edit','烹饪方式')">编辑</button>
<button class="g-action">复制</button>
<button class="g-action g-action-danger">删除</button>
</div>
</div>
</div>
<!-- 抽屉 -->
<div class="g-drawer-mask" id="pspDrawerMask" onclick="closeSpecDrawer()"></div>
<div class="g-drawer" id="pspDrawer" style="width:520px;">
<div class="g-drawer-hd">
<span class="g-drawer-title" id="pspDrawerTitle">添加模板</span>
<button class="g-drawer-close" onclick="closeSpecDrawer()">&times;</button>
</div>
<div class="g-drawer-bd">
<div class="g-form-group">
<label class="g-form-label required">模板名称</label>
<input type="text" class="g-input" id="pspName" placeholder="如:杯型、辣度、甜度">
</div>
<div class="g-form-group">
<label class="g-form-label required">模板类型</label>
<div class="psp-pill-group" id="pspTypeGroup">
<button class="psp-pill-btn active" onclick="selectSpecType(this)" data-val="spec">规格</button>
<button class="psp-pill-btn" onclick="selectSpecType(this)" data-val="method">做法</button>
</div>
<div class="g-hint">规格影响价格和库存,做法仅影响制作方式</div>
</div>
<div class="g-form-group">
<label class="g-form-label required">选择方式</label>
<div class="psp-pill-group" id="pspSelectGroup">
<button class="psp-pill-btn active" onclick="selectSpecSelect(this)" data-val="single">单选</button>
<button class="psp-pill-btn" onclick="selectSpecSelect(this)" data-val="multi">多选</button>
</div>
</div>
<div class="g-form-group">
<label class="g-form-label">是否必选</label>
<div class="g-toggle-wrap">
<label class="g-toggle-input"><input type="checkbox" id="pspRequired" checked><span class="g-toggle-sl"></span></label>
<span class="g-toggle-label" id="pspRequiredLabel"></span>
</div>
</div>
<div class="g-form-group">
<label class="g-form-label required">选项列表</label>
<div class="psp-opt-list" id="pspOptList">
<div class="psp-opt-row">
<input type="text" placeholder="选项名称,如:大杯" value="大杯">
<div class="psp-price-wrap"><span>&yen;</span><input type="number" placeholder="加价金额" value="3"></div>
<button class="psp-opt-del" onclick="removeSpecOption(this)">&times;</button>
</div>
<div class="psp-opt-row">
<input type="text" placeholder="选项名称,如:中杯" value="中杯">
<div class="psp-price-wrap"><span>&yen;</span><input type="number" placeholder="加价金额"></div>
<button class="psp-opt-del" onclick="removeSpecOption(this)">&times;</button>
</div>
<div class="psp-opt-row">
<input type="text" placeholder="选项名称,如:小杯" value="小杯">
<div class="psp-price-wrap"><span>&yen;</span><input type="number" placeholder="加价金额" value="-2"></div>
<button class="psp-opt-del" onclick="removeSpecOption(this)">&times;</button>
</div>
</div>
<button class="g-btn psp-btn-dashed" onclick="addSpecOption()">+ 添加选项</button>
</div>
</div>
<div class="g-drawer-ft">
<button class="g-btn" onclick="closeSpecDrawer()">取消</button>
<button class="g-btn g-btn-primary" id="pspSubmitBtn" onclick="closeSpecDrawer()">确认添加</button>
</div>
</div>
<script>
function openSpecDrawer(mode, name) {
document.getElementById('pspDrawerMask').classList.add('open');
document.getElementById('pspDrawer').classList.add('open');
if (mode === 'edit') {
document.getElementById('pspDrawerTitle').textContent = '编辑模板';
document.getElementById('pspSubmitBtn').textContent = '保存修改';
if (name) document.getElementById('pspName').value = name;
} else {
document.getElementById('pspDrawerTitle').textContent = '添加模板';
document.getElementById('pspSubmitBtn').textContent = '确认添加';
document.getElementById('pspName').value = '';
}
}
function closeSpecDrawer() {
document.getElementById('pspDrawerMask').classList.remove('open');
document.getElementById('pspDrawer').classList.remove('open');
}
function selectSpecType(el) {
el.parentElement.querySelectorAll('.psp-pill-btn').forEach(function(b){ b.classList.remove('active'); });
el.classList.add('active');
}
function selectSpecSelect(el) {
el.parentElement.querySelectorAll('.psp-pill-btn').forEach(function(b){ b.classList.remove('active'); });
el.classList.add('active');
}
function addSpecOption() {
var list = document.getElementById('pspOptList');
var row = document.createElement('div');
row.className = 'psp-opt-row';
row.innerHTML = '<input type="text" placeholder="选项名称">' +
'<div class="psp-price-wrap"><span>&yen;</span><input type="number" placeholder="加价金额"></div>' +
'<button class="psp-opt-del" onclick="removeSpecOption(this)">&times;</button>';
list.appendChild(row);
}
function removeSpecOption(el) {
var row = el.closest('.psp-opt-row');
if (row) row.remove();
}
/* 筛选 */
function filterSpecs(type, el) {
document.querySelectorAll('.psp-filter-tab').forEach(function(t){ t.classList.remove('active'); });
el.classList.add('active');
document.querySelectorAll('#pspGrid .psp-card').forEach(function(card) {
if (type === 'all') { card.style.display = ''; return; }
card.style.display = card.getAttribute('data-type') === type ? '' : 'none';
});
}
/* 搜索 */
function searchSpecs(keyword) {
keyword = keyword.trim().toLowerCase();
document.querySelectorAll('#pspGrid .psp-card').forEach(function(card) {
var name = card.getAttribute('data-name').toLowerCase();
card.style.display = (!keyword || name.indexOf(keyword) > -1) ? '' : 'none';
});
}
</script>

250
pages/store-delivery.html Normal file
View File

@@ -0,0 +1,250 @@
<!-- 配送设置页 -->
<style>
.page-delivery { max-width:960px; }
.pd-toolbar { display:flex; align-items:center; gap:12px; margin-bottom:16px; box-shadow:var(--g-shadow-sm); border-radius:10px; padding:10px 14px; background:#fff; }
.pd-toolbar select { height:34px; padding:0 10px; border:1px solid #e5e7eb; border-radius:8px; font-size:13px; outline:none; background:#fff; transition:var(--g-transition); }
.pd-toolbar select:focus { border-color:var(--primary); box-shadow:0 0 0 3px color-mix(in srgb, var(--primary) 12%, transparent); }
/* Mode Switch */
.mode-switch { display:flex; background:#f8f9fb; border-radius:8px; padding:3px; gap:2px; width:fit-content; margin-bottom:16px; }
.mode-switch-item { padding:6px 18px; border-radius:6px; font-size:13px; cursor:pointer; color:#4b5563; transition:var(--g-transition); border:none; background:none; }
.mode-switch-item.active { background:#fff; color:var(--primary); font-weight:600; box-shadow:var(--g-shadow-sm); }
/* Map Placeholder */
.map-area { width:100%; height:320px; background:#f0f5ff; border-radius:10px; border:1px dashed #adc6ff; display:flex; flex-direction:column; align-items:center; justify-content:center; color:#597ef7; margin-bottom:16px; position:relative; overflow:hidden; }
.map-area .map-icon { font-size:48px; margin-bottom:8px; opacity:0.5; }
.map-area .map-label { font-size:13px; opacity:0.7; }
.map-area .map-overlay { position:absolute; top:0; left:0; right:0; bottom:0; }
/* Fake map grid */
.map-area .grid-line { position:absolute; background:#d6e4ff; }
.map-area .grid-h { height:1px; left:0; right:0; }
.map-area .grid-v { width:1px; top:0; bottom:0; }
.map-area .map-pin { position:absolute; top:50%; left:50%; transform:translate(-50%,-100%); font-size:24px; z-index:2; }
/* Radius circles */
.map-area .radius-circle { position:absolute; top:50%; left:50%; border-radius:50%; border:2px dashed; transform:translate(-50%,-50%); }
.map-area .rc1 { width:100px; height:100px; border-color:#22c55e; background:rgba(34,197,94,0.06); }
.map-area .rc2 { width:180px; height:180px; border-color:#f59e0b; background:rgba(245,158,11,0.04); }
.map-area .rc3 { width:260px; height:260px; border-color:#ef4444; background:rgba(239,68,68,0.03); }
.map-area .rc-label { position:absolute; font-size:10px; font-weight:600; white-space:nowrap; }
.map-area .rc1 .rc-label { bottom:-16px; left:50%; transform:translateX(-50%); color:#22c55e; }
.map-area .rc2 .rc-label { bottom:-16px; left:50%; transform:translateX(-50%); color:#f59e0b; }
.map-area .rc3 .rc-label { bottom:-16px; left:50%; transform:translateX(-50%); color:#ef4444; }
/* Zone Table */
.zone-table { width:100%; border-collapse:collapse; font-size:13px; }
.zone-table th { background:#f8f9fb; padding:10px 12px; text-align:left; font-weight:600; color:#6b7280; border-bottom:1px solid #e5e7eb; }
.zone-table td { padding:10px 12px; border-bottom:1px solid #f3f4f6; color:#1a1a2e; }
.zone-table tr:last-child td { border-bottom:none; }
.zone-table tr:hover td { background:color-mix(in srgb, var(--primary) 3%, #fff); }
.zone-color { width:12px; height:12px; border-radius:3px; display:inline-block; vertical-align:middle; margin-right:6px; }
.z-link { color:var(--primary); cursor:pointer; font-size:12px; margin-right:8px; transition:var(--g-transition); }
.z-link:hover { text-decoration:underline; }
.z-link.danger { color:#ef4444; }
/* Radius Tier Cards */
.tier-list { display:flex; flex-direction:column; gap:10px; }
.tier-card { display:grid; grid-template-columns:40px 1fr 1fr 1fr 1fr auto; gap:12px; align-items:center; padding:12px 14px; background:#f8f9fb; border-radius:10px; border:none; box-shadow:var(--g-shadow-sm); transition:var(--g-transition); }
.tier-card:hover { box-shadow:var(--g-shadow-md); }
.tier-num { width:28px; height:28px; border-radius:50%; display:flex; align-items:center; justify-content:center; font-size:12px; font-weight:600; color:#fff; }
.tier-field label { font-size:11px; color:#9ca3af; display:block; margin-bottom:3px; }
.tier-field .val { font-size:13px; font-weight:500; color:#1a1a2e; }
.tier-actions { display:flex; gap:4px; }
.tier-edit { width:28px; height:28px; display:flex; align-items:center; justify-content:center; border-radius:8px; cursor:pointer; color:#9ca3af; border:none; background:none; transition:var(--g-transition); }
.tier-edit:hover { background:#fff; color:var(--primary); }
.pd-tip { font-size:12px; color:#9ca3af; margin-top:8px; display:flex; align-items:center; gap:4px; }
</style>
<div class="page-delivery">
<div class="pd-toolbar">
<select style="width:200px;">
<option>老三家外卖(朝阳店)</option>
<option>老三家外卖(海淀店)</option>
<option>老三家外卖(望京店)</option>
<option>老三家外卖(通州店)</option>
<option>老三家外卖(丰台店)</option>
</select>
<div style="flex:1;"></div>
<button class="g-btn" onclick="openCopyStoreModal('复制配送设置到其他门店')">
<i data-lucide="copy" style="width:14px;height:14px;"></i>复制到其他门店
</button>
</div>
<!-- 配送模式 -->
<div class="g-card" style="padding:0; margin-bottom:16px;">
<div class="g-card-title" style="padding:14px 18px; margin-bottom:0; border-bottom:1px solid #f5f5f5;">配送模式</div>
<div style="padding:16px 18px;">
<div class="mode-switch">
<button class="mode-switch-item active" onclick="switchDeliveryTab('radius')">按半径配送</button>
<button class="mode-switch-item" onclick="switchDeliveryTab('polygon')">按区域配送(多边形)</button>
</div>
<!-- 地图区域 -->
<div class="map-area">
<div class="grid-line grid-h" style="top:25%;"></div>
<div class="grid-line grid-h" style="top:50%;"></div>
<div class="grid-line grid-h" style="top:75%;"></div>
<div class="grid-line grid-v" style="left:25%;"></div>
<div class="grid-line grid-v" style="left:50%;"></div>
<div class="grid-line grid-v" style="left:75%;"></div>
<div class="map-pin"><i data-lucide="map-pin" style="width:16px;height:16px"></i></div>
<!-- Radius mode circles -->
<div id="radiusOverlay">
<div class="radius-circle rc3"><span class="rc-label">5km</span></div>
<div class="radius-circle rc2"><span class="rc-label">3km</span></div>
<div class="radius-circle rc1"><span class="rc-label">1km</span></div>
</div>
<!-- Polygon mode hint (hidden by default) -->
<div id="polygonOverlay" style="display:none; text-align:center;">
<div class="map-icon"><i data-lucide="map" style="width:16px;height:16px"></i></div>
<div class="map-label">点击地图绘制配送区域多边形</div>
</div>
</div>
</div>
</div>
<!-- 半径配送 - 距离梯度 -->
<div class="g-card" style="padding:0; margin-bottom:16px;" id="radiusSection">
<div class="g-card-title" style="padding:14px 18px; margin-bottom:0; border-bottom:1px solid #f5f5f5;">
距离梯度
<button class="g-btn g-btn-primary g-btn-sm">
<i data-lucide="plus" style="width:13px;height:13px;"></i>添加梯度
</button>
</div>
<div style="padding:16px 18px;">
<div class="tier-list">
<div class="tier-card">
<div class="tier-num" style="background:#52c41a;">1</div>
<div class="tier-field"><label>距离范围</label><div class="val">0 ~ 1 km</div></div>
<div class="tier-field"><label>配送费</label><div class="val">¥3.00</div></div>
<div class="tier-field"><label>预计送达</label><div class="val">20 分钟</div></div>
<div class="tier-field"><label>起送金额</label><div class="val">¥15.00</div></div>
<div class="tier-actions">
<button class="tier-edit"><i data-lucide="pencil" style="width:14px;height:14px;"></i></button>
<button class="tier-edit"><i data-lucide="trash-2" style="width:14px;height:14px;color:#ff4d4f;"></i></button>
</div>
</div>
<div class="tier-card">
<div class="tier-num" style="background:#faad14;">2</div>
<div class="tier-field"><label>距离范围</label><div class="val">1 ~ 3 km</div></div>
<div class="tier-field"><label>配送费</label><div class="val">¥5.00</div></div>
<div class="tier-field"><label>预计送达</label><div class="val">35 分钟</div></div>
<div class="tier-field"><label>起送金额</label><div class="val">¥20.00</div></div>
<div class="tier-actions">
<button class="tier-edit"><i data-lucide="pencil" style="width:14px;height:14px;"></i></button>
<button class="tier-edit"><i data-lucide="trash-2" style="width:14px;height:14px;color:#ff4d4f;"></i></button>
</div>
</div>
<div class="tier-card">
<div class="tier-num" style="background:#ff4d4f;">3</div>
<div class="tier-field"><label>距离范围</label><div class="val">3 ~ 5 km</div></div>
<div class="tier-field"><label>配送费</label><div class="val">¥8.00</div></div>
<div class="tier-field"><label>预计送达</label><div class="val">50 分钟</div></div>
<div class="tier-field"><label>起送金额</label><div class="val">¥25.00</div></div>
<div class="tier-actions">
<button class="tier-edit"><i data-lucide="pencil" style="width:14px;height:14px;"></i></button>
<button class="tier-edit"><i data-lucide="trash-2" style="width:14px;height:14px;color:#ff4d4f;"></i></button>
</div>
</div>
</div>
<div class="pd-tip"><i data-lucide="info" style="width:13px;height:13px;"></i>超出最大配送半径的地址将无法下单</div>
</div>
</div>
<!-- 多边形配送 - 区域列表 (hidden by default) -->
<div class="g-card" style="padding:0; margin-bottom:16px; display:none;" id="polygonSection">
<div class="g-card-title" style="padding:14px 18px; margin-bottom:0; border-bottom:1px solid #f5f5f5;">
配送区域
<button class="g-btn g-btn-primary g-btn-sm">
<i data-lucide="plus" style="width:13px;height:13px;"></i>绘制新区域
</button>
</div>
<div style="padding:0;">
<table class="zone-table">
<thead><tr><th>区域名称</th><th>配送费</th><th>起送金额</th><th>预计送达</th><th>优先级</th><th style="width:100px;">操作</th></tr></thead>
<tbody>
<tr>
<td><span class="zone-color" style="background:#52c41a;"></span>核心区域</td>
<td>¥3.00</td>
<td>¥15.00</td>
<td>20 分钟</td>
<td>1</td>
<td><a class="z-link">编辑</a><a class="z-link danger">删除</a></td>
</tr>
<tr>
<td><span class="zone-color" style="background:#1890ff;"></span>朝阳CBD</td>
<td>¥5.00</td>
<td>¥20.00</td>
<td>35 分钟</td>
<td>2</td>
<td><a class="z-link">编辑</a><a class="z-link danger">删除</a></td>
</tr>
<tr>
<td><span class="zone-color" style="background:#faad14;"></span>三里屯片区</td>
<td>¥6.00</td>
<td>¥25.00</td>
<td>40 分钟</td>
<td>3</td>
<td><a class="z-link">编辑</a><a class="z-link danger">删除</a></td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- 通用配送设置 -->
<div class="g-card" style="padding:0; margin-bottom:16px;">
<div class="g-card-title" style="padding:14px 18px; margin-bottom:0; border-bottom:1px solid #f5f5f5;">通用设置</div>
<div style="padding:16px 18px;">
<div style="display:grid; grid-template-columns:1fr 1fr; gap:16px 24px;">
<div>
<label style="font-size:12px; color:#666; display:block; margin-bottom:6px;">免配送费门槛</label>
<div style="display:flex; align-items:center; gap:6px;">
<input type="number" value="30" placeholder="如50" style="width:120px; height:32px; padding:0 10px; border:1px solid #d9d9d9; border-radius:6px; font-size:13px; outline:none;" />
<span style="font-size:13px; color:#666;"></span>
</div>
<div style="font-size:11px; color:#999; margin-top:4px;">订单满此金额免配送费,留空则不启用</div>
</div>
<div>
<label style="font-size:12px; color:#666; display:block; margin-bottom:6px;">最大配送距离</label>
<div style="display:flex; align-items:center; gap:6px;">
<input type="number" value="5" placeholder="如5" style="width:120px; height:32px; padding:0 10px; border:1px solid #d9d9d9; border-radius:6px; font-size:13px; outline:none;" />
<span style="font-size:13px; color:#666;">公里</span>
</div>
<div style="font-size:11px; color:#999; margin-top:4px;">仅半径模式生效</div>
</div>
<div>
<label style="font-size:12px; color:#666; display:block; margin-bottom:6px;">每小时最大配送单量</label>
<div style="display:flex; align-items:center; gap:6px;">
<input type="number" value="50" placeholder="如50" style="width:120px; height:32px; padding:0 10px; border:1px solid #d9d9d9; border-radius:6px; font-size:13px; outline:none;" />
<span style="font-size:13px; color:#666;"></span>
</div>
<div style="font-size:11px; color:#999; margin-top:4px;">达到上限后暂停接单</div>
</div>
<div>
<label style="font-size:12px; color:#666; display:block; margin-bottom:6px;">配送时间预估加成</label>
<div style="display:flex; align-items:center; gap:6px;">
<input type="number" value="10" placeholder="如30" style="width:120px; height:32px; padding:0 10px; border:1px solid #d9d9d9; border-radius:6px; font-size:13px; outline:none;" />
<span style="font-size:13px; color:#666;">分钟</span>
</div>
<div style="font-size:11px; color:#999; margin-top:4px;">高峰期自动增加预估时间</div>
</div>
</div>
<div style="margin-top:20px; display:flex; justify-content:flex-end; gap:8px;">
<button class="g-btn">重置</button>
<button class="g-btn g-btn-primary">保存设置</button>
</div>
</div>
</div>
</div>
<script>
function switchDeliveryTab(mode) {
document.querySelectorAll('.mode-switch-item').forEach(b => b.classList.remove('active'));
event.currentTarget.classList.add('active');
document.getElementById('radiusOverlay').style.display = mode === 'radius' ? '' : 'none';
document.getElementById('polygonOverlay').style.display = mode === 'polygon' ? '' : 'none';
document.getElementById('radiusSection').style.display = mode === 'radius' ? '' : 'none';
document.getElementById('polygonSection').style.display = mode === 'polygon' ? '' : 'none';
}
</script>

288
pages/store-dinein.html Normal file
View File

@@ -0,0 +1,288 @@
<!-- 堂食管理页 -->
<style>
.pdi-toolbar { display:flex; align-items:center; gap:12px; margin-bottom:16px; box-shadow:var(--g-shadow-sm); border-radius:10px; padding:12px 16px; background:#fff; }
.pdi-toolbar select { height:34px; padding:0 10px; border:1px solid #e5e7eb; border-radius:8px; font-size:13px; outline:none; background:#fff; cursor:pointer; transition:var(--g-transition); }
.pdi-toolbar select:focus { border-color:var(--primary); box-shadow:0 0 0 3px color-mix(in srgb, var(--primary) 12%, transparent); }
.pdi-card-hd { display:flex; align-items:center; justify-content:space-between; margin-bottom:16px; font-size:14px; font-weight:600; color:#1a1a2e; }
.pdi-card-hd .right { display:flex; gap:8px; }
.pdi-area-pills { display:flex; gap:8px; flex-wrap:wrap; margin-bottom:16px; }
.pdi-area-pill { padding:6px 16px; border-radius:20px; font-size:13px; cursor:pointer; border:1px solid #e5e7eb; background:#fff; color:#4b5563; transition:var(--g-transition); }
.pdi-area-pill:hover { border-color:var(--primary); color:var(--primary); }
.pdi-area-pill.active { background:var(--primary); color:#fff; border-color:var(--primary); }
.pdi-area-info { background:#f8f9fb; border-radius:8px; padding:14px 18px; display:flex; align-items:center; justify-content:space-between; font-size:13px; color:#4b5563; }
.pdi-area-info .actions { display:flex; gap:8px; }
.pdi-action-link { color:var(--primary); cursor:pointer; font-size:12px; text-decoration:none; transition:var(--g-transition); }
.pdi-action-link:hover { text-decoration:underline; }
.pdi-action-link.danger { color:#ef4444; }
.pdi-table-grid { display:grid; grid-template-columns:repeat(4,1fr); gap:14px; }
.pdi-table-card { background:#fff; border:none; border-radius:10px; padding:16px; box-shadow:var(--g-shadow-sm); transition:var(--g-transition); }
.pdi-table-card:hover { box-shadow:0 4px 16px rgba(0,0,0,0.10); }
.pdi-table-code { font-size:22px; font-weight:700; color:#1a1a2e; margin-bottom:8px; }
.pdi-table-cap { font-size:13px; color:#4b5563; display:flex; align-items:center; gap:5px; margin-bottom:8px; }
.pdi-table-status { display:inline-flex; align-items:center; gap:5px; font-size:12px; font-weight:600; margin-bottom:8px; }
.pdi-status-dot { width:7px; height:7px; border-radius:50%; display:inline-block; }
.pdi-s-free .pdi-status-dot { background:#22c55e; } .pdi-s-free { color:#22c55e; }
.pdi-s-dining .pdi-status-dot { background:#f59e0b; } .pdi-s-dining { color:#f59e0b; }
.pdi-s-reserved .pdi-status-dot { background:#1890ff; } .pdi-s-reserved { color:#1890ff; }
.pdi-s-disabled .pdi-status-dot { background:#9ca3af; } .pdi-s-disabled { color:#9ca3af; }
.pdi-table-tags { display:flex; gap:4px; flex-wrap:wrap; margin-bottom:10px; min-height:22px; }
.pdi-table-foot { display:flex; justify-content:flex-end; gap:8px; border-top:1px solid #f3f4f6; padding-top:10px; }
.pdi-icon-btn { width:28px; height:28px; border-radius:8px; border:1px solid #e5e7eb; background:#fff; cursor:pointer; display:inline-flex; align-items:center; justify-content:center; font-size:14px; color:#9ca3af; transition:var(--g-transition); }
.pdi-icon-btn:hover { border-color:var(--primary); color:var(--primary); }
.pdi-form-row { display:flex; align-items:center; gap:12px; margin-bottom:16px; font-size:13px; color:#1a1a2e; }
.pdi-form-row label { width:120px; text-align:right; flex-shrink:0; font-weight:500; color:#4b5563; }
.pdi-form-row input[type=number] { width:120px; height:34px; padding:0 10px; border:1px solid #e5e7eb; border-radius:8px; font-size:13px; outline:none; transition:var(--g-transition); }
.pdi-form-row input[type=number]:focus { border-color:var(--primary); box-shadow:0 0 0 3px color-mix(in srgb, var(--primary) 12%, transparent); }
.pdi-form-row .hint { font-size:12px; color:#9ca3af; }
.pdi-form-row .unit { color:#4b5563; }
.pdi-form-actions { display:flex; gap:8px; margin-left:132px; margin-top:8px; }
.pdi-tag-input-wrap { display:flex; flex-wrap:wrap; gap:6px; padding:6px 8px; border:1px solid #e5e7eb; border-radius:8px; min-height:36px; align-items:center; transition:var(--g-transition); }
.pdi-tag-item { display:inline-flex; align-items:center; gap:4px; padding:2px 8px; background:#f0f5ff; color:#597ef7; border-radius:6px; font-size:12px; }
.pdi-tag-item .remove { cursor:pointer; color:#aaa; font-size:14px; transition:var(--g-transition); }
.pdi-tag-item .remove:hover { color:#ef4444; }
.pdi-modal-mask { position:fixed; inset:0; background:rgba(0,0,0,0.4); z-index:2000; opacity:0; pointer-events:none; transition:var(--g-transition); display:flex; align-items:center; justify-content:center; }
.pdi-modal-mask.open { opacity:1; pointer-events:auto; }
.pdi-modal { background:#fff; border-radius:10px; width:480px; display:flex; flex-direction:column; box-shadow:0 8px 30px rgba(0,0,0,0.12); transform:translateY(12px);opacity:0; transition:var(--g-transition); }
.pdi-modal-mask.open .pdi-modal { transform:translateY(0);opacity:1; }
.pdi-modal-hd { padding:16px 20px; border-bottom:1px solid #e5e7eb; display:flex; align-items:center; justify-content:space-between; }
.pdi-modal-hd h3 { font-size:15px; font-weight:600; color:#1a1a2e; margin:0; }
.pdi-modal-bd { padding:20px; }
.pdi-modal-ft { padding:12px 20px; border-top:1px solid #e5e7eb; display:flex; justify-content:flex-end; gap:8px; background:#fafbfc; border-radius:0 0 10px 10px; }
.pdi-preview-box { background:#f8f9fb; border:1px solid #e5e7eb; border-radius:8px; padding:12px; margin-top:12px; }
.pdi-preview-box .pv-title { font-size:12px; color:#9ca3af; margin-bottom:8px; }
.pdi-preview-tags { display:flex; flex-wrap:wrap; gap:6px; }
.pdi-preview-tag { padding:4px 10px; background:#f0f5ff; color:#597ef7; border-radius:6px; font-size:12px; }
</style>
<div class="pdi-page">
<div class="pdi-toolbar">
<select>
<option>老三家外卖(中关村店)</option>
<option>老三家外卖(望京店)</option>
<option>老三家外卖(国贸店)</option>
<option>老三家外卖(西单店)</option>
<option>老三家外卖(五道口店)</option>
</select>
<button class="g-btn" onclick="openCopyStoreModal('复制堂食设置到其他门店')">复制到其他门店</button>
</div>
<div class="g-card" style="padding:20px;margin-bottom:16px;">
<div class="pdi-card-hd"><span>区域管理</span><div class="right"><button class="g-btn g-btn-primary g-btn-sm" onclick="openDiArea()">+ 添加区域</button></div></div>
<div class="pdi-area-pills">
<span class="pdi-area-pill active">大厅 (12桌)</span>
<span class="pdi-area-pill">包间 (4桌)</span>
<span class="pdi-area-pill">露台 (6桌)</span>
</div>
<div class="pdi-area-info">
<span>大厅 &mdash; 主要用餐区域共12张桌位可容纳约48人同时用餐</span>
<div class="actions"><a class="pdi-action-link" onclick="openDiArea('edit','大厅')">编辑</a><a class="pdi-action-link danger">删除</a></div>
</div>
</div>
<div class="g-card" style="padding:20px;margin-bottom:16px;">
<div class="pdi-card-hd"><span>桌位列表</span><div class="right"><button class="g-btn g-btn-sm" onclick="openDiBatch()">批量生成</button><button class="g-btn g-btn-primary g-btn-sm" onclick="openDiTable()">+ 添加桌位</button></div></div>
<div class="pdi-table-grid">
<div class="pdi-table-card">
<div class="pdi-table-code">A01</div>
<div class="pdi-table-cap"><i data-lucide="armchair" style="width:14px;height:14px"></i> 4人桌</div>
<div class="pdi-table-status pdi-s-free"><span class="pdi-status-dot"></span>空闲</div>
<div class="pdi-table-tags"><span class="g-tag g-tag-gray">靠窗</span></div>
<div class="pdi-table-foot"><button class="pdi-icon-btn" title="二维码"><i data-lucide="qr-code" style="width:12px;height:12px"></i></button><button class="pdi-icon-btn" title="编辑" onclick="openDiTable('edit',this.closest('.pdi-table-card').querySelector('.pdi-table-code').textContent)"><i data-lucide="pencil" style="width:12px;height:12px"></i></button><a class="g-action g-action-danger" style="font-size:12px;cursor:pointer">删除</a></div>
</div>
<div class="pdi-table-card">
<div class="pdi-table-code">A02</div>
<div class="pdi-table-cap"><i data-lucide="armchair" style="width:14px;height:14px"></i> 2人桌</div>
<div class="pdi-table-status pdi-s-dining"><span class="pdi-status-dot"></span>就餐中</div>
<div class="pdi-table-tags"></div>
<div class="pdi-table-foot"><button class="pdi-icon-btn" title="二维码"><i data-lucide="qr-code" style="width:12px;height:12px"></i></button><button class="pdi-icon-btn" title="编辑" onclick="openDiTable('edit',this.closest('.pdi-table-card').querySelector('.pdi-table-code').textContent)"><i data-lucide="pencil" style="width:12px;height:12px"></i></button><a class="g-action g-action-danger" style="font-size:12px;cursor:pointer">删除</a></div>
</div>
<div class="pdi-table-card">
<div class="pdi-table-code">A03</div>
<div class="pdi-table-cap"><i data-lucide="armchair" style="width:14px;height:14px"></i> 6人桌</div>
<div class="pdi-table-status pdi-s-free"><span class="pdi-status-dot"></span>空闲</div>
<div class="pdi-table-tags"><span class="g-tag g-tag-gray">VIP</span><span class="g-tag g-tag-gray">靠窗</span></div>
<div class="pdi-table-foot"><button class="pdi-icon-btn" title="二维码"><i data-lucide="qr-code" style="width:12px;height:12px"></i></button><button class="pdi-icon-btn" title="编辑" onclick="openDiTable('edit',this.closest('.pdi-table-card').querySelector('.pdi-table-code').textContent)"><i data-lucide="pencil" style="width:12px;height:12px"></i></button><a class="g-action g-action-danger" style="font-size:12px;cursor:pointer">删除</a></div>
</div>
<div class="pdi-table-card">
<div class="pdi-table-code">A04</div>
<div class="pdi-table-cap"><i data-lucide="armchair" style="width:14px;height:14px"></i> 4人桌</div>
<div class="pdi-table-status pdi-s-reserved"><span class="pdi-status-dot"></span>已预约</div>
<div class="pdi-table-tags"></div>
<div class="pdi-table-foot"><button class="pdi-icon-btn" title="二维码"><i data-lucide="qr-code" style="width:12px;height:12px"></i></button><button class="pdi-icon-btn" title="编辑" onclick="openDiTable('edit',this.closest('.pdi-table-card').querySelector('.pdi-table-code').textContent)"><i data-lucide="pencil" style="width:12px;height:12px"></i></button><a class="g-action g-action-danger" style="font-size:12px;cursor:pointer">删除</a></div>
</div>
<div class="pdi-table-card">
<div class="pdi-table-code">A05</div>
<div class="pdi-table-cap"><i data-lucide="armchair" style="width:14px;height:14px"></i> 8人桌</div>
<div class="pdi-table-status pdi-s-dining"><span class="pdi-status-dot"></span>就餐中</div>
<div class="pdi-table-tags"><span class="g-tag g-tag-gray">包厢</span></div>
<div class="pdi-table-foot"><button class="pdi-icon-btn" title="二维码"><i data-lucide="qr-code" style="width:12px;height:12px"></i></button><button class="pdi-icon-btn" title="编辑" onclick="openDiTable('edit',this.closest('.pdi-table-card').querySelector('.pdi-table-code').textContent)"><i data-lucide="pencil" style="width:12px;height:12px"></i></button><a class="g-action g-action-danger" style="font-size:12px;cursor:pointer">删除</a></div>
</div>
<div class="pdi-table-card">
<div class="pdi-table-code">A06</div>
<div class="pdi-table-cap"><i data-lucide="armchair" style="width:14px;height:14px"></i> 2人桌</div>
<div class="pdi-table-status pdi-s-free"><span class="pdi-status-dot"></span>空闲</div>
<div class="pdi-table-tags"><span class="g-tag g-tag-gray">靠窗</span></div>
<div class="pdi-table-foot"><button class="pdi-icon-btn" title="二维码"><i data-lucide="qr-code" style="width:12px;height:12px"></i></button><button class="pdi-icon-btn" title="编辑" onclick="openDiTable('edit',this.closest('.pdi-table-card').querySelector('.pdi-table-code').textContent)"><i data-lucide="pencil" style="width:12px;height:12px"></i></button><a class="g-action g-action-danger" style="font-size:12px;cursor:pointer">删除</a></div>
</div>
<div class="pdi-table-card" style="opacity:0.5;">
<div class="pdi-table-code">A07</div>
<div class="pdi-table-cap"><i data-lucide="armchair" style="width:14px;height:14px"></i> 4人桌</div>
<div class="pdi-table-status pdi-s-disabled"><span class="pdi-status-dot"></span>停用</div>
<div class="pdi-table-tags"></div>
<div class="pdi-table-foot"><button class="pdi-icon-btn" title="二维码"><i data-lucide="qr-code" style="width:12px;height:12px"></i></button><button class="pdi-icon-btn" title="编辑" onclick="openDiTable('edit',this.closest('.pdi-table-card').querySelector('.pdi-table-code').textContent)"><i data-lucide="pencil" style="width:12px;height:12px"></i></button><a class="g-action g-action-danger" style="font-size:12px;cursor:pointer">删除</a></div>
</div>
<div class="pdi-table-card">
<div class="pdi-table-code">A08</div>
<div class="pdi-table-cap"><i data-lucide="armchair" style="width:14px;height:14px"></i> 4人桌</div>
<div class="pdi-table-status pdi-s-free"><span class="pdi-status-dot"></span>空闲</div>
<div class="pdi-table-tags"><span class="g-tag g-tag-gray">VIP</span></div>
<div class="pdi-table-foot"><button class="pdi-icon-btn" title="二维码"><i data-lucide="qr-code" style="width:12px;height:12px"></i></button><button class="pdi-icon-btn" title="编辑" onclick="openDiTable('edit',this.closest('.pdi-table-card').querySelector('.pdi-table-code').textContent)"><i data-lucide="pencil" style="width:12px;height:12px"></i></button><a class="g-action g-action-danger" style="font-size:12px;cursor:pointer">删除</a></div>
</div>
</div>
</div>
<div class="g-card" style="padding:20px;margin-bottom:16px;">
<div class="pdi-card-hd"><span>堂食设置</span></div>
<div class="pdi-form-row"><label>是否开启堂食</label><button class="g-toggle on" onclick="this.classList.toggle('on')"></button></div>
<div class="pdi-form-row"><label>默认用餐时长</label><input type="number" value="90"><span class="unit">分钟</span></div>
<div class="pdi-form-row"><label>超时提醒</label><input type="number" value="10"><span class="unit">分钟</span><span class="hint">超过用餐时长后提醒</span></div>
<div class="pdi-form-actions"><button class="g-btn g-btn-primary">保存设置</button><button class="g-btn">重置</button></div>
</div>
</div>
<div class="g-drawer-mask" id="diDrawerMask" onclick="closeDiDrawer()"></div>
<!-- 添加/编辑区域 -->
<div class="g-drawer" id="diAreaDrawer" style="width:460px;">
<div class="g-drawer-hd"><span class="g-drawer-title" id="diAreaTitle">添加区域</span><button class="g-drawer-close" onclick="closeDiDrawer()"><i data-lucide="x" style="width:18px;height:18px;"></i></button></div>
<div class="g-drawer-bd">
<div class="g-form-group"><label class="g-form-label required">区域名称</label><input type="text" class="g-input" id="diAreaName" placeholder="如:大厅、包间、露台" /></div>
<div class="g-form-group"><label class="g-form-label">区域描述</label><textarea class="g-textarea" id="diAreaDesc" rows="3" placeholder="可选主要用餐区域可容纳约48人"></textarea></div>
<div class="g-form-group"><label class="g-form-label">排序</label><div style="display:flex;align-items:center;gap:6px;"><input type="number" class="g-input" id="diAreaSort" value="1" style="width:80px;" /><span style="font-size:12px;color:#999;">数字越小越靠前</span></div></div>
</div>
<div class="g-drawer-ft"><button class="g-btn" onclick="closeDiDrawer()">取消</button><button class="g-btn g-btn-primary" id="diAreaSubmit" onclick="closeDiDrawer()">确认添加</button></div>
</div>
<!-- 添加/编辑桌位 -->
<div class="g-drawer" id="diTableDrawer" style="width:460px;">
<div class="g-drawer-hd"><span class="g-drawer-title" id="diTableTitle">添加桌位</span><button class="g-drawer-close" onclick="closeDiDrawer()"><i data-lucide="x" style="width:18px;height:18px;"></i></button></div>
<div class="g-drawer-bd">
<div class="g-form-group"><label class="g-form-label required">桌位编号</label><input type="text" class="g-input" id="diTableCode" placeholder="如A09" /></div>
<div class="g-form-group"><label class="g-form-label">所属区域</label><select class="g-select" id="diTableArea"><option>大厅</option><option>包间</option><option>露台</option></select></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
<div class="g-form-group"><label class="g-form-label required">座位数</label><select class="g-select" id="diTableCap"><option>2人桌</option><option selected>4人桌</option><option>6人桌</option><option>8人桌</option><option>10人桌</option><option>12人桌</option></select></div>
<div class="g-form-group"><label class="g-form-label">状态</label><select class="g-select" id="diTableStatus"><option selected>空闲</option><option>停用</option></select></div>
</div>
<div class="g-form-group"><label class="g-form-label">标签</label>
<div class="pdi-tag-input-wrap" id="diTagWrap">
<span class="pdi-tag-item">靠窗 <span class="remove" onclick="this.parentElement.remove()"><i data-lucide="x" style="width:10px;height:10px"></i></span></span>
<input type="text" id="diTagInput" placeholder="输入后回车添加" style="border:none;outline:none;font-size:12px;flex:1;min-width:80px;" onkeydown="if(event.key==='Enter'){addDiTag();event.preventDefault();}" />
</div>
<div class="g-hint">输入标签名后按回车添加靠窗、VIP、包厢</div>
</div>
</div>
<div class="g-drawer-ft"><button class="g-btn" onclick="closeDiDrawer()">取消</button><button class="g-btn g-btn-primary" id="diTableSubmit" onclick="closeDiDrawer()">确认添加</button></div>
</div>
<!-- 批量生成弹窗 -->
<div class="pdi-modal-mask" id="diBatchMask" onclick="if(event.target===this)closeDiBatch()">
<div class="pdi-modal">
<div class="pdi-modal-hd"><h3>批量生成桌位</h3><button class="g-drawer-close" onclick="closeDiBatch()"><i data-lucide="x" style="width:16px;height:16px"></i></button></div>
<div class="pdi-modal-bd">
<div class="g-form-group"><label class="g-form-label">所属区域</label><select class="g-select" id="diBatchArea" onchange="updateBatchPreview()"><option>大厅</option><option>包间</option><option>露台</option></select></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
<div class="g-form-group"><label class="g-form-label">编号前缀</label><input type="text" class="g-input" id="diBatchPrefix" value="A" oninput="updateBatchPreview()" /></div>
<div class="g-form-group"><label class="g-form-label">起始编号</label><input type="number" class="g-input" id="diBatchStart" value="9" oninput="updateBatchPreview()" /></div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
<div class="g-form-group"><label class="g-form-label">生成数量</label><input type="number" class="g-input" id="diBatchCount" value="4" min="1" max="50" oninput="updateBatchPreview()" /></div>
<div class="g-form-group"><label class="g-form-label">座位数</label><select class="g-select" id="diBatchCap"><option>2人桌</option><option selected>4人桌</option><option>6人桌</option><option>8人桌</option></select></div>
</div>
<div class="pdi-preview-box">
<div class="pv-title">预览:将生成以下桌位</div>
<div class="pdi-preview-tags" id="diBatchPreview"></div>
</div>
</div>
<div class="pdi-modal-ft"><button class="g-btn" onclick="closeDiBatch()">取消</button><button class="g-btn g-btn-primary" onclick="closeDiBatch()">确认生成</button></div>
</div>
</div>
<script>
function openDiArea(mode, name) {
var t = document.getElementById('diAreaTitle');
var s = document.getElementById('diAreaSubmit');
if (mode === 'edit') {
t.textContent = '编辑区域';
s.textContent = '保存修改';
document.getElementById('diAreaName').value = name || '';
document.getElementById('diAreaDesc').value = '主要用餐区域可容纳约48人同时用餐';
document.getElementById('diAreaSort').value = '1';
} else {
t.textContent = '添加区域';
s.textContent = '确认添加';
document.getElementById('diAreaName').value = '';
document.getElementById('diAreaDesc').value = '';
document.getElementById('diAreaSort').value = '1';
}
document.getElementById('diDrawerMask').classList.add('open');
document.getElementById('diAreaDrawer').classList.add('open');
}
function openDiTable(mode, code) {
var t = document.getElementById('diTableTitle');
var s = document.getElementById('diTableSubmit');
if (mode === 'edit') {
t.textContent = '编辑桌位 - ' + code;
s.textContent = '保存修改';
document.getElementById('diTableCode').value = code || '';
} else {
t.textContent = '添加桌位';
s.textContent = '确认添加';
document.getElementById('diTableCode').value = '';
}
document.getElementById('diDrawerMask').classList.add('open');
document.getElementById('diTableDrawer').classList.add('open');
}
function closeDiDrawer() {
document.getElementById('diDrawerMask').classList.remove('open');
document.getElementById('diAreaDrawer').classList.remove('open');
document.getElementById('diTableDrawer').classList.remove('open');
}
function addDiTag() {
var inp = document.getElementById('diTagInput');
var v = inp.value.trim();
if (!v) return;
var span = document.createElement('span');
span.className = 'pdi-tag-item';
span.innerHTML = v + ' <span class="remove" onclick="this.parentElement.remove()"><i data-lucide="x" style="width:10px;height:10px"></i></span>';
document.getElementById('diTagWrap').insertBefore(span, inp);
inp.value = '';
}
function openDiBatch() {
document.getElementById('diBatchMask').classList.add('open');
updateBatchPreview();
}
function closeDiBatch() {
document.getElementById('diBatchMask').classList.remove('open');
}
function updateBatchPreview() {
var prefix = document.getElementById('diBatchPrefix').value || 'A';
var start = parseInt(document.getElementById('diBatchStart').value) || 1;
var count = Math.min(Math.max(parseInt(document.getElementById('diBatchCount').value) || 1, 1), 50);
var html = '';
for (var i = 0; i < count; i++) {
var num = (start + i).toString().padStart(2, '0');
html += '<span class="pdi-preview-tag">' + prefix + num + '</span>';
}
document.getElementById('diBatchPreview').innerHTML = html;
}
// init icons
if (typeof lucide !== 'undefined') lucide.createIcons();
</script>

131
pages/store-fees.html Normal file
View File

@@ -0,0 +1,131 @@
<!-- 费用设置页 -->
<style>
.page-fees { max-width:960px; }
.pf-toolbar { display:flex; align-items:center; gap:12px; margin-bottom:16px; box-shadow:var(--g-shadow-sm); border-radius:10px; padding:10px 14px; background:#fff; }
.pf-toolbar select { height:34px; padding:0 10px; border:1px solid #e5e7eb; border-radius:8px; font-size:13px; outline:none; background:#fff; transition:var(--g-transition); }
.pf-toolbar select:focus { border-color:var(--primary); box-shadow:0 0 0 3px color-mix(in srgb, var(--primary) 12%, transparent); }
.pf-grid { display:grid; grid-template-columns:1fr 1fr; gap:16px 24px; }
.pf-field label { font-size:12px; color:#6b7280; display:block; margin-bottom:6px; font-weight:500; }
.pf-field .pf-input-wrap { display:flex; align-items:center; gap:6px; }
.pf-field input[type=number] { width:140px; height:34px; padding:0 10px; border:1px solid #e5e7eb; border-radius:8px; font-size:13px; outline:none; transition:var(--g-transition); }
.pf-field input[type=number]:focus { border-color:var(--primary); box-shadow:0 0 0 3px color-mix(in srgb, var(--primary) 12%, transparent); }
.pf-field .pf-unit { font-size:13px; color:#4b5563; }
.pf-mode-switch { display:flex; background:#f8f9fb; border-radius:8px; padding:3px; gap:2px; width:fit-content; margin-bottom:16px; }
.pf-mode-switch-item { padding:6px 18px; border-radius:6px; font-size:13px; cursor:pointer; color:#4b5563; transition:var(--g-transition); border:none; background:none; }
.pf-mode-switch-item.active { background:#fff; color:var(--primary); font-weight:600; box-shadow:var(--g-shadow-sm); }
.pf-toggle-row { display:flex; align-items:center; gap:10px; margin-bottom:12px; }
.pf-toggle-label { font-size:13px; color:#1a1a2e; font-weight:500; }
.pf-tier-table { width:100%; border-collapse:collapse; font-size:13px; margin-top:10px; }
.pf-tier-table th { background:#f8f9fb; padding:10px 12px; text-align:left; font-weight:600; color:#6b7280; border-bottom:1px solid #e5e7eb; }
.pf-tier-table td { padding:10px 12px; border-bottom:1px solid #f3f4f6; color:#1a1a2e; }
.pf-tier-table tr:last-child td { border-bottom:none; }
.pf-tier-table tr:hover td { background:color-mix(in srgb, var(--primary) 3%, #fff); }
.pf-t-link { color:var(--primary); cursor:pointer; font-size:12px; margin-right:6px; transition:var(--g-transition); }
.pf-t-link:hover { text-decoration:underline; }
.pf-t-link.danger { color:#ef4444; }
.pf-other-row { display:flex; align-items:flex-start; gap:14px; padding:14px 0; border-bottom:1px solid #f3f4f6; }
.pf-other-row:last-child { border-bottom:none; }
.pf-other-info { flex:1; }
.pf-other-info .pf-other-name { font-size:13px; font-weight:500; color:#1a1a2e; margin-bottom:2px; }
.pf-other-info .pf-other-hint { font-size:11px; color:#9ca3af; }
.pf-other-input { display:flex; align-items:center; gap:6px; }
.pf-other-input input { width:100px; height:34px; padding:0 10px; border:1px solid #e5e7eb; border-radius:8px; font-size:13px; outline:none; transition:var(--g-transition); }
.pf-other-input input:disabled { background:#f8f9fb; color:#bbb; }
.pf-save-bar { margin-top:20px; display:flex; justify-content:flex-end; gap:8px; }
.pf-note { font-size:12px; color:#9ca3af; display:flex; align-items:center; gap:4px; }
</style>
<div class="page-fees">
<div class="pf-toolbar">
<select style="width:200px;">
<option>老三家外卖(朝阳店)</option>
<option>老三家外卖(海淀店)</option>
<option>老三家外卖(望京店)</option>
<option>老三家外卖(通州店)</option>
<option>老三家外卖(丰台店)</option>
</select>
<div style="flex:1;"></div>
<button class="g-btn" onclick="openCopyStoreModal('复制费用设置到其他门店')"><i data-lucide="copy" style="width:14px;height:14px;"></i>复制到其他门店</button>
</div>
<div class="g-card" style="padding:0; margin-bottom:16px;">
<div class="g-card-title" style="padding:14px 18px; margin-bottom:0; border-bottom:1px solid #f5f5f5;">起送与配送费</div>
<div style="padding:16px 18px;">
<div class="pf-grid">
<div class="pf-field"><label>起送金额</label><div class="pf-input-wrap"><span class="pf-unit">¥</span><input type="number" value="15.00" step="0.01" min="0" placeholder="如15.00" /></div><div class="g-hint">低于此金额的订单无法下单</div></div>
<div class="pf-field"><label>基础配送费</label><div class="pf-input-wrap"><span class="pf-unit">¥</span><input type="number" value="3.00" step="0.01" min="0" placeholder="如3.00" /></div><div class="g-hint">每笔订单默认收取的配送费</div></div>
<div class="pf-field"><label>免配送费门槛</label><div class="pf-input-wrap"><span class="pf-unit">¥</span><input type="number" value="30.00" step="0.01" min="0" placeholder="如30.00" /></div><div class="g-hint">订单满此金额免配送费,留空不启用</div></div>
</div>
<div class="pf-save-bar"><button class="g-btn">重置</button><button class="g-btn g-btn-primary">保存设置</button></div>
</div>
</div>
<div class="g-card" style="padding:0; margin-bottom:16px;">
<div class="g-card-title" style="padding:14px 18px; margin-bottom:0; border-bottom:1px solid #f5f5f5;">包装费设置</div>
<div style="padding:16px 18px;">
<div class="pf-mode-switch">
<button class="pf-mode-switch-item active" onclick="switchPkgMode('order')">按订单收取</button>
<button class="pf-mode-switch-item" onclick="switchPkgMode('item')">按商品收取</button>
</div>
<div id="pkgModeOrder">
<div class="pf-grid">
<div class="pf-field"><label>固定包装费</label><div class="pf-input-wrap"><span class="pf-unit">¥</span><input type="number" value="2.00" step="0.01" min="0" placeholder="如2.00" /></div><div class="g-hint">每笔订单统一收取的包装费</div></div>
</div>
<div style="margin-top:18px; padding-top:16px; border-top:1px solid #f5f5f5;">
<div class="pf-toggle-row"><label class="g-toggle-input"><input type="checkbox" id="tierToggle" onchange="toggleTiers()" checked /><span class="g-toggle-sl"></span></label><span class="pf-toggle-label">启用阶梯包装费</span></div>
<div id="tierSection">
<div class="pf-note" style="margin-bottom:10px;">启用后将按订单金额区间收取不同包装费,覆盖上方固定包装费</div>
<table class="pf-tier-table"><thead><tr><th>订单金额区间</th><th>包装费</th><th style="width:100px;">操作</th></tr></thead><tbody>
<tr><td>0 ~ 30 元</td><td>¥ 2.00</td><td><span class="pf-t-link">编辑</span><span class="pf-t-link danger">删除</span></td></tr>
<tr><td>30 ~ 60 元</td><td>¥ 3.00</td><td><span class="pf-t-link">编辑</span><span class="pf-t-link danger">删除</span></td></tr>
<tr><td>60 元以上</td><td>¥ 5.00</td><td><span class="pf-t-link">编辑</span><span class="pf-t-link danger">删除</span></td></tr>
</tbody></table>
<div style="margin-top:10px;"><button class="g-btn g-btn-sm"><i data-lucide="plus" style="width:13px;height:13px;"></i>添加阶梯</button></div>
</div>
</div>
</div>
<div id="pkgModeItem" style="display:none;">
<div style="padding:20px; background:#fafafa; border-radius:8px; text-align:center;">
<div style="font-size:28px; margin-bottom:8px; opacity:0.4;"><i data-lucide="package" style="width:16px;height:16px"></i></div>
<div style="font-size:13px; color:#666;">每个商品单独设置包装费,请在 <span style="color:var(--primary); cursor:pointer; font-weight:500;">商品管理</span> 中配置</div>
<div style="font-size:11px; color:#999; margin-top:6px;">系统将自动汇总订单内所有商品的包装费</div>
</div>
</div>
<div class="pf-save-bar"><button class="g-btn">重置</button><button class="g-btn g-btn-primary">保存设置</button></div>
</div>
</div>
<div class="g-card" style="padding:0; margin-bottom:16px;">
<div class="g-card-title" style="padding:14px 18px; margin-bottom:0; border-bottom:1px solid #f5f5f5;">其他费用<span style="font-size:11px; color:#999; font-weight:400; margin-left:8px;">可选,按需开启</span></div>
<div style="padding:16px 18px;">
<div class="pf-other-row">
<label class="g-toggle-input"><input type="checkbox" id="cutleryToggle" onchange="toggleOtherFee('cutlery')" /><span class="g-toggle-sl"></span></label>
<div class="pf-other-info"><div class="pf-other-name">餐具费</div><div class="pf-other-hint">向顾客收取一次性餐具费用,顾客可选择不需要餐具</div></div>
<div class="pf-other-input"><span class="pf-unit">¥</span><input type="number" id="cutleryInput" value="1.00" step="0.01" min="0" placeholder="如1.00" disabled /></div>
</div>
<div class="pf-other-row">
<label class="g-toggle-input"><input type="checkbox" id="rushToggle" onchange="toggleOtherFee('rush')" /><span class="g-toggle-sl"></span></label>
<div class="pf-other-info"><div class="pf-other-name">加急费</div><div class="pf-other-hint">顾客选择加急配送时额外收取的费用</div></div>
<div class="pf-other-input"><span class="pf-unit">¥</span><input type="number" id="rushInput" value="3.00" step="0.01" min="0" placeholder="如3.00" disabled /></div>
</div>
<div class="pf-save-bar"><button class="g-btn">重置</button><button class="g-btn g-btn-primary">保存设置</button></div>
</div>
</div>
</div>
<script>
function switchPkgMode(mode) {
document.querySelectorAll('.pf-mode-switch-item').forEach(function(b){b.classList.remove('active')});
event.currentTarget.classList.add('active');
document.getElementById('pkgModeOrder').style.display = mode === 'order' ? '' : 'none';
document.getElementById('pkgModeItem').style.display = mode === 'item' ? '' : 'none';
}
function toggleTiers() {
var on = document.getElementById('tierToggle').checked;
document.getElementById('tierSection').style.display = on ? '' : 'none';
}
function toggleOtherFee(type) {
if (type === 'cutlery') { document.getElementById('cutleryInput').disabled = !document.getElementById('cutleryToggle').checked; }
else if (type === 'rush') { document.getElementById('rushInput').disabled = !document.getElementById('rushToggle').checked; }
}
</script>

472
pages/store-hours.html Normal file
View File

@@ -0,0 +1,472 @@
<!-- 营业时间页 -->
<style>
.page-hours { max-width:960px; }
.ph-toolbar { display:flex; align-items:center; gap:12px; margin-bottom:16px; box-shadow:var(--g-shadow-sm); border-radius:10px; padding:12px 16px; background:#fff; }
.ph-card-header { padding:14px 18px; font-size:14px; font-weight:600; color:#1a1a2e; background:#f8f9fb; border-bottom:1px solid #e5e7eb; display:flex; align-items:center; justify-content:space-between; border-radius:10px 10px 0 0; }
.ph-card-body { padding:16px 18px; }
.week-grid { display:flex; flex-direction:column; gap:0; }
.week-row { display:grid; grid-template-columns:80px 1fr auto; align-items:center; padding:12px 0; border-bottom:1px solid #f3f4f6; gap:12px; transition:var(--g-transition); }
.week-row:last-child { border-bottom:none; }
.week-row:hover { background:color-mix(in srgb, var(--primary) 3%, #fff); }
.week-day { font-size:13px; font-weight:500; color:#1a1a2e; }
.week-day .day-en { font-size:11px; color:#9ca3af; font-weight:400; }
.week-slots { display:flex; flex-wrap:wrap; gap:8px; }
.time-slot { display:inline-flex; align-items:center; gap:6px; padding:4px 10px; border-radius:6px; font-size:12px; background:#f6ffed; border:1px solid #b7eb8f; color:#389e0d; }
.time-slot.delivery { background:#e6f7ff; border-color:#91d5ff; color:#1890ff; }
.time-slot.pickup { background:#fff7e6; border-color:#ffd591; color:#d46b08; }
.time-slot .slot-type { font-weight:600; }
.time-slot .slot-time { color:inherit; opacity:0.85; }
.time-slot .slot-cap { font-size:10px; opacity:0.7; }
.week-actions { display:flex; gap:4px; }
.week-edit { width:28px; height:28px; display:flex; align-items:center; justify-content:center; border-radius:8px; cursor:pointer; color:#9ca3af; border:none; background:none; transition:var(--g-transition); }
.week-edit:hover { background:#f8f9fb; color:var(--primary); }
.week-closed { font-size:12px; color:#9ca3af; font-style:italic; }
.holiday-table { width:100%; border-collapse:collapse; font-size:13px; }
.holiday-table th { background:#f8f9fb; padding:8px 12px; text-align:left; font-weight:600; color:#6b7280; border-bottom:1px solid #e5e7eb; }
.holiday-table td { padding:10px 12px; border-bottom:1px solid #f3f4f6; color:#1a1a2e; }
.holiday-table tr:last-child td { border-bottom:none; }
.holiday-table tr:hover td { background:color-mix(in srgb, var(--primary) 3%, #fff); }
.h-tag { display:inline-block; padding:1px 8px; border-radius:6px; font-size:11px; font-weight:600; }
.h-tag.closed { background:#fff2f0; color:#ef4444; }
.h-tag.special { background:#fff7e6; color:#f59e0b; }
.h-link { color:var(--primary); cursor:pointer; font-size:12px; margin-right:8px; transition:var(--g-transition); }
.h-link:hover { text-decoration:underline; }
.h-link.danger { color:#ef4444; }
.ph-legend { display:flex; gap:16px; font-size:12px; color:#9ca3af; margin-bottom:12px; }
.ph-legend span { display:flex; align-items:center; gap:4px; }
.ph-legend-dot { width:10px; height:10px; border-radius:2px; }
/* Type pills */
.ph-type-pills { display:flex; gap:8px; }
.ph-type-pill { padding:6px 16px; border-radius:8px; font-size:13px; cursor:pointer; border:1px solid #e5e7eb; background:#fff; color:#4b5563; transition:var(--g-transition); }
.ph-type-pill.tp-biz.active { background:#f6ffed; border-color:#b7eb8f; color:#389e0d; font-weight:600; }
.ph-type-pill.tp-del.active { background:#e6f7ff; border-color:#91d5ff; color:#1890ff; font-weight:600; }
.ph-type-pill.tp-pick.active { background:#fff7e6; border-color:#ffd591; color:#d46b08; font-weight:600; }
/* Day pills */
.ph-day-pills { display:flex; gap:6px; flex-wrap:wrap; }
.ph-day-pill { width:42px; height:34px; border-radius:8px; font-size:12px; cursor:pointer; border:1px solid #e5e7eb; background:#fff; color:#4b5563; display:flex; align-items:center; justify-content:center; transition:var(--g-transition); }
.ph-day-pill.selected { background:var(--primary); border-color:var(--primary); color:#fff; }
.ph-quick-btns { display:flex; gap:8px; margin-top:8px; }
.ph-quick-btn { font-size:11px; color:var(--primary); cursor:pointer; background:none; border:none; padding:0; transition:var(--g-transition); }
.ph-quick-btn:hover { text-decoration:underline; }
/* Edit drawer slot cards */
.ph-slot-card { background:#f8f9fb; border:1px solid #e5e7eb; border-radius:10px; padding:12px 14px; margin-bottom:10px; display:grid; grid-template-columns:auto 1fr auto; gap:12px; align-items:center; }
.ph-slot-tag { padding:3px 10px; border-radius:6px; font-size:12px; font-weight:600; white-space:nowrap; }
.ph-slot-tag.st-biz { background:#f6ffed; border:1px solid #b7eb8f; color:#389e0d; }
.ph-slot-tag.st-del { background:#e6f7ff; border:1px solid #91d5ff; color:#1890ff; }
.ph-slot-tag.st-pick { background:#fff7e6; border:1px solid #ffd591; color:#d46b08; }
.ph-slot-fields { display:flex; gap:10px; align-items:center; flex-wrap:wrap; }
.ph-slot-fields label { font-size:11px; color:#9ca3af; font-weight:500; }
.ph-slot-fields input { width:100px; }
.ph-slot-del { width:28px; height:28px; border:none; background:none; cursor:pointer; color:#bbb; display:flex; align-items:center; justify-content:center; border-radius:8px; transition:var(--g-transition); }
.ph-slot-del:hover { background:#fff2f0; color:#ef4444; }
.ph-add-dashed { width:100%; height:40px; border:1px dashed #e5e7eb; border-radius:10px; background:none; cursor:pointer; color:#9ca3af; font-size:13px; display:flex; align-items:center; justify-content:center; gap:6px; transition:var(--g-transition); }
.ph-add-dashed:hover { border-color:var(--primary); color:var(--primary); }
/* Toggle row */
.ph-toggle-row { display:flex; align-items:center; gap:10px; margin-bottom:16px; }
.ph-toggle-hint { font-size:12px; color:#ef4444; margin-left:8px; display:none; }
/* Holiday drawer */
.ph-hol-type-pills { display:flex; gap:8px; }
.ph-hol-type-pill { padding:6px 16px; border-radius:8px; font-size:13px; cursor:pointer; border:1px solid #e5e7eb; background:#fff; color:#4b5563; transition:var(--g-transition); }
.ph-hol-type-pill.ht-closed.active { background:#fff2f0; border-color:#ffa39e; color:#ef4444; font-weight:600; }
.ph-hol-type-pill.ht-special.active { background:#fff7e6; border-color:#ffd591; color:#f59e0b; font-weight:600; }
.ph-date-mode { display:flex; gap:8px; margin-bottom:10px; }
.ph-date-mode-pill { padding:4px 12px; border-radius:8px; font-size:12px; cursor:pointer; border:1px solid #e5e7eb; background:#fff; color:#4b5563; transition:var(--g-transition); }
.ph-date-mode-pill.active { background:var(--primary); border-color:var(--primary); color:#fff; }
</style>
<div class="page-hours">
<div class="ph-toolbar">
<select class="g-select" style="width:200px;">
<option>老三家外卖(朝阳店)</option>
<option>老三家外卖(海淀店)</option>
<option>老三家外卖(望京店)</option>
<option>老三家外卖(通州店)</option>
<option>老三家外卖(丰台店)</option>
</select>
<div style="flex:1;"></div>
<button class="g-btn" onclick="openCopyStoreModal('复制营业时间到其他门店')">
<i data-lucide="copy" style="width:14px;height:14px;"></i>复制到其他门店
</button>
</div>
<div class="g-card">
<div class="ph-card-header">
每周营业时间
<button class="g-btn g-btn-primary" style="height:28px;font-size:12px;" onclick="openHoursDrawer('add')">
<i data-lucide="plus" style="width:13px;height:13px;"></i>添加时段
</button>
</div>
<div class="ph-card-body">
<div class="ph-legend">
<span><span class="ph-legend-dot" style="background:#b7eb8f;"></span>营业</span>
<span><span class="ph-legend-dot" style="background:#91d5ff;"></span>配送</span>
<span><span class="ph-legend-dot" style="background:#ffd591;"></span>自提</span>
</div>
<div class="week-grid">
<div class="week-row">
<div class="week-day">周一<div class="day-en">Monday</div></div>
<div class="week-slots">
<div class="time-slot"><span class="slot-type">营业</span><span class="slot-time">09:00-22:00</span></div>
<div class="time-slot delivery"><span class="slot-type">配送</span><span class="slot-time">10:00-21:30</span><span class="slot-cap">50单/h</span></div>
<div class="time-slot pickup"><span class="slot-type">自提</span><span class="slot-time">09:00-21:00</span></div>
</div>
<div class="week-actions"><button class="week-edit" onclick="openHoursDrawer('editDay','周一')"><i data-lucide="pencil" style="width:14px;height:14px;"></i></button></div>
</div>
<div class="week-row">
<div class="week-day">周二<div class="day-en">Tuesday</div></div>
<div class="week-slots">
<div class="time-slot"><span class="slot-type">营业</span><span class="slot-time">09:00-22:00</span></div>
<div class="time-slot delivery"><span class="slot-type">配送</span><span class="slot-time">10:00-21:30</span><span class="slot-cap">50单/h</span></div>
<div class="time-slot pickup"><span class="slot-type">自提</span><span class="slot-time">09:00-21:00</span></div>
</div>
<div class="week-actions"><button class="week-edit" onclick="openHoursDrawer('editDay','周二')"><i data-lucide="pencil" style="width:14px;height:14px;"></i></button></div>
</div>
<div class="week-row">
<div class="week-day">周三<div class="day-en">Wednesday</div></div>
<div class="week-slots">
<div class="time-slot"><span class="slot-type">营业</span><span class="slot-time">09:00-22:00</span></div>
<div class="time-slot delivery"><span class="slot-type">配送</span><span class="slot-time">10:00-21:30</span><span class="slot-cap">50单/h</span></div>
<div class="time-slot pickup"><span class="slot-type">自提</span><span class="slot-time">09:00-21:00</span></div>
</div>
<div class="week-actions"><button class="week-edit" onclick="openHoursDrawer('editDay','周三')"><i data-lucide="pencil" style="width:14px;height:14px;"></i></button></div>
</div>
<div class="week-row">
<div class="week-day">周四<div class="day-en">Thursday</div></div>
<div class="week-slots">
<div class="time-slot"><span class="slot-type">营业</span><span class="slot-time">09:00-22:00</span></div>
<div class="time-slot delivery"><span class="slot-type">配送</span><span class="slot-time">10:00-21:30</span><span class="slot-cap">50单/h</span></div>
<div class="time-slot pickup"><span class="slot-type">自提</span><span class="slot-time">09:00-21:00</span></div>
</div>
<div class="week-actions"><button class="week-edit" onclick="openHoursDrawer('editDay','周四')"><i data-lucide="pencil" style="width:14px;height:14px;"></i></button></div>
</div>
<div class="week-row">
<div class="week-day">周五<div class="day-en">Friday</div></div>
<div class="week-slots">
<div class="time-slot"><span class="slot-type">营业</span><span class="slot-time">09:00-23:00</span></div>
<div class="time-slot delivery"><span class="slot-type">配送</span><span class="slot-time">10:00-22:30</span><span class="slot-cap">80单/h</span></div>
<div class="time-slot pickup"><span class="slot-type">自提</span><span class="slot-time">09:00-22:00</span></div>
</div>
<div class="week-actions"><button class="week-edit" onclick="openHoursDrawer('editDay','周五')"><i data-lucide="pencil" style="width:14px;height:14px;"></i></button></div>
</div>
<div class="week-row">
<div class="week-day">周六<div class="day-en">Saturday</div></div>
<div class="week-slots">
<div class="time-slot"><span class="slot-type">营业</span><span class="slot-time">09:00-23:00</span></div>
<div class="time-slot delivery"><span class="slot-type">配送</span><span class="slot-time">10:00-22:30</span><span class="slot-cap">80单/h</span></div>
<div class="time-slot pickup"><span class="slot-type">自提</span><span class="slot-time">09:00-22:00</span></div>
</div>
<div class="week-actions"><button class="week-edit" onclick="openHoursDrawer('editDay','周六')"><i data-lucide="pencil" style="width:14px;height:14px;"></i></button></div>
</div>
<div class="week-row">
<div class="week-day">周日<div class="day-en">Sunday</div></div>
<div class="week-slots">
<div class="time-slot"><span class="slot-type">营业</span><span class="slot-time">10:00-22:00</span></div>
<div class="time-slot delivery"><span class="slot-type">配送</span><span class="slot-time">10:30-21:30</span><span class="slot-cap">60单/h</span></div>
</div>
<div class="week-actions"><button class="week-edit" onclick="openHoursDrawer('editDay','周日')"><i data-lucide="pencil" style="width:14px;height:14px;"></i></button></div>
</div>
</div>
</div>
</div>
<div class="g-card">
<div class="ph-card-header">
节假日 / 特殊日期
<button class="g-btn g-btn-primary" style="height:28px;font-size:12px;" onclick="openHolidayDrawer('add')">
<i data-lucide="plus" style="width:13px;height:13px;"></i>添加日期
</button>
</div>
<div class="ph-card-body" style="padding:0;">
<table class="holiday-table">
<thead><tr><th>日期</th><th>类型</th><th>时间</th><th>原因</th><th style="width:100px;">操作</th></tr></thead>
<tbody>
<tr><td>2026-02-17 ~ 2026-02-19</td><td><span class="h-tag closed">休息</span></td><td>全天</td><td>春节假期</td><td><a class="h-link" onclick="openHolidayDrawer('edit',{date:'2026-02-17 ~ 2026-02-19',type:'closed',time:'',reason:'春节假期'})">编辑</a><a class="h-link danger">删除</a></td></tr>
<tr><td>2026-04-05</td><td><span class="h-tag closed">休息</span></td><td>全天</td><td>清明节</td><td><a class="h-link" onclick="openHolidayDrawer('edit',{date:'2026-04-05',type:'closed',time:'',reason:'清明节'})">编辑</a><a class="h-link danger">删除</a></td></tr>
<tr><td>2026-02-14</td><td><span class="h-tag special">特殊营业</span></td><td>09:00 - 23:30</td><td>情人节延长营业</td><td><a class="h-link" onclick="openHolidayDrawer('edit',{date:'2026-02-14',type:'special',time:'09:00-23:30',reason:'情人节延长营业'})">编辑</a><a class="h-link danger">删除</a></td></tr>
<tr><td>2026-05-01</td><td><span class="h-tag special">特殊营业</span></td><td>10:00 - 20:00</td><td>劳动节缩短营业</td><td><a class="h-link" onclick="openHolidayDrawer('edit',{date:'2026-05-01',type:'special',time:'10:00-20:00',reason:'劳动节缩短营业'})">编辑</a><a class="h-link danger">删除</a></td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- 添加时段抽屉 -->
<div class="g-drawer-mask" id="hoursDrawerMask" onclick="closeHoursDrawer()"></div>
<div class="g-drawer" id="addSlotDrawer" style="width:480px;">
<div class="g-drawer-hd">
<span class="g-drawer-title">添加时段</span>
<button class="g-drawer-close" onclick="closeHoursDrawer()"><i data-lucide="x" style="width:18px;height:18px;"></i></button>
</div>
<div class="g-drawer-bd">
<div class="g-form-group">
<label class="g-form-label required">时段类型</label>
<div class="ph-type-pills">
<button class="ph-type-pill tp-biz active" onclick="selectSlotType('biz',this)">营业</button>
<button class="ph-type-pill tp-del" onclick="selectSlotType('del',this)">配送</button>
<button class="ph-type-pill tp-pick" onclick="selectSlotType('pick',this)">自提</button>
</div>
</div>
<div class="g-form-group">
<label class="g-form-label required">适用星期</label>
<div class="ph-day-pills" id="addDayPills">
<button class="ph-day-pill selected" onclick="toggleDayPill(this)">周一</button>
<button class="ph-day-pill selected" onclick="toggleDayPill(this)">周二</button>
<button class="ph-day-pill selected" onclick="toggleDayPill(this)">周三</button>
<button class="ph-day-pill selected" onclick="toggleDayPill(this)">周四</button>
<button class="ph-day-pill selected" onclick="toggleDayPill(this)">周五</button>
<button class="ph-day-pill" onclick="toggleDayPill(this)">周六</button>
<button class="ph-day-pill" onclick="toggleDayPill(this)">周日</button>
</div>
<div class="ph-quick-btns">
<button class="ph-quick-btn" onclick="quickSelectDays('all')">全选</button>
<button class="ph-quick-btn" onclick="quickSelectDays('weekday')">工作日</button>
<button class="ph-quick-btn" onclick="quickSelectDays('weekend')">周末</button>
</div>
</div>
<div style="display:grid; grid-template-columns:1fr 1fr; gap:12px;">
<div class="g-form-group">
<label class="g-form-label required">开始时间</label>
<input type="time" class="g-input" value="09:00" style="width:100%;" />
</div>
<div class="g-form-group">
<label class="g-form-label required">结束时间</label>
<input type="time" class="g-input" value="22:00" style="width:100%;" />
</div>
</div>
<div class="g-form-group" id="addCapField" style="display:none;">
<label class="g-form-label">容量上限</label>
<div style="display:flex; align-items:center; gap:6px;">
<input type="number" class="g-input" value="50" style="width:120px;" />
<span style="font-size:13px; color:#666;">单/小时</span>
</div>
<div class="g-hint">该时段内每小时最大接单量</div>
</div>
<div class="g-form-group">
<label class="g-form-label">备注</label>
<textarea class="g-textarea" rows="2" placeholder="可选,如:午市高峰时段"></textarea>
</div>
</div>
<div class="g-drawer-ft">
<button class="g-btn" onclick="closeHoursDrawer()">取消</button>
<button class="g-btn g-btn-primary" onclick="closeHoursDrawer()">确认添加</button>
</div>
</div>
<!-- 编辑时段抽屉 -->
<div class="g-drawer" id="editDayDrawer" style="width:520px;">
<div class="g-drawer-hd">
<span class="g-drawer-title" id="editDayTitle">编辑时段 - 周一</span>
<button class="g-drawer-close" onclick="closeHoursDrawer()"><i data-lucide="x" style="width:18px;height:18px;"></i></button>
</div>
<div class="g-drawer-bd">
<div class="ph-toggle-row">
<button class="g-toggle on" id="dayOpenToggle" onclick="toggleDayOpen(this)"></button>
<span class="g-toggle-label">今日营业</span>
<span class="ph-toggle-hint" id="dayClosedHint">该日休息,不接单</span>
</div>
<div id="editSlotList">
<div class="ph-slot-card">
<span class="ph-slot-tag st-biz">营业</span>
<div class="ph-slot-fields">
<div><label>开始</label><input type="time" class="g-input" value="09:00" /></div>
<div><label>结束</label><input type="time" class="g-input" value="22:00" /></div>
</div>
<button class="ph-slot-del"><i data-lucide="trash-2" style="width:14px;height:14px;"></i></button>
</div>
<div class="ph-slot-card">
<span class="ph-slot-tag st-del">配送</span>
<div class="ph-slot-fields">
<div><label>开始</label><input type="time" class="g-input" value="10:00" /></div>
<div><label>结束</label><input type="time" class="g-input" value="21:30" /></div>
<div><label>容量</label><input type="number" class="g-input" value="50" style="width:70px;" /><span style="font-size:11px;color:#999;margin-left:3px;">单/h</span></div>
</div>
<button class="ph-slot-del"><i data-lucide="trash-2" style="width:14px;height:14px;"></i></button>
</div>
<div class="ph-slot-card">
<span class="ph-slot-tag st-pick">自提</span>
<div class="ph-slot-fields">
<div><label>开始</label><input type="time" class="g-input" value="09:00" /></div>
<div><label>结束</label><input type="time" class="g-input" value="21:00" /></div>
</div>
<button class="ph-slot-del"><i data-lucide="trash-2" style="width:14px;height:14px;"></i></button>
</div>
</div>
<button class="ph-add-dashed" style="margin-top:4px;" onclick="addSlotRow()">
<i data-lucide="plus" style="width:14px;height:14px;"></i>添加时段
</button>
</div>
<div class="g-drawer-ft">
<button class="g-btn" onclick="closeHoursDrawer()">取消</button>
<button class="g-btn g-btn-primary" onclick="closeHoursDrawer()">保存修改</button>
</div>
</div>
<!-- 节假日抽屉 -->
<div class="g-drawer" id="holidayDrawer" style="width:480px;">
<div class="g-drawer-hd">
<span class="g-drawer-title" id="holidayDrawerTitle">添加日期</span>
<button class="g-drawer-close" onclick="closeHoursDrawer()"><i data-lucide="x" style="width:18px;height:18px;"></i></button>
</div>
<div class="g-drawer-bd">
<div class="g-form-group">
<label class="g-form-label required">类型</label>
<div class="ph-hol-type-pills">
<button class="ph-hol-type-pill ht-closed active" onclick="selectHolType('closed',this)">休息</button>
<button class="ph-hol-type-pill ht-special" onclick="selectHolType('special',this)">特殊营业</button>
</div>
</div>
<div class="g-form-group">
<label class="g-form-label required">日期</label>
<div class="ph-date-mode">
<button class="ph-date-mode-pill active" onclick="switchDateMode('single',this)">单日</button>
<button class="ph-date-mode-pill" onclick="switchDateMode('range',this)">日期范围</button>
</div>
<div id="holDateSingle">
<input type="date" class="g-input" id="holDate1" value="2026-05-01" style="width:100%;" />
</div>
<div id="holDateRange" style="display:none;">
<div style="display:flex; align-items:center; gap:8px;">
<input type="date" class="g-input" id="holDateFrom" value="2026-05-01" style="flex:1;" />
<span style="color:#999;">~</span>
<input type="date" class="g-input" id="holDateTo" value="2026-05-03" style="flex:1;" />
</div>
</div>
</div>
<div class="g-form-group" id="holTimeGroup" style="display:none;">
<label class="g-form-label">营业时间</label>
<div style="display:grid; grid-template-columns:1fr 1fr; gap:12px;">
<div>
<label style="font-size:11px;color:#999;">开始</label>
<input type="time" class="g-input" id="holTimeStart" value="09:00" style="width:100%;" />
</div>
<div>
<label style="font-size:11px;color:#999;">结束</label>
<input type="time" class="g-input" id="holTimeEnd" value="22:00" style="width:100%;" />
</div>
</div>
<div class="g-hint">特殊营业日的营业时间段</div>
</div>
<div class="g-form-group">
<label class="g-form-label required">原因</label>
<input type="text" class="g-input" id="holReason" placeholder="如:春节假期、情人节延长营业" style="width:100%;" />
</div>
<div class="g-form-group">
<label class="g-form-label">备注</label>
<textarea class="g-textarea" rows="2" placeholder="可选"></textarea>
</div>
</div>
<div class="g-drawer-ft">
<button class="g-btn" onclick="closeHoursDrawer()">取消</button>
<button class="g-btn g-btn-primary" id="holSubmitBtn" onclick="closeHoursDrawer()">确认添加</button>
</div>
</div>
<script>
function openHoursDrawer(mode, dayName) {
document.getElementById('hoursDrawerMask').classList.add('open');
if (mode === 'add') {
document.getElementById('addSlotDrawer').classList.add('open');
} else if (mode === 'editDay') {
document.getElementById('editDayTitle').textContent = '编辑时段 - ' + dayName;
document.getElementById('editDayDrawer').classList.add('open');
}
if (typeof lucide !== 'undefined') lucide.createIcons();
}
function closeHoursDrawer() {
document.getElementById('hoursDrawerMask').classList.remove('open');
document.getElementById('addSlotDrawer').classList.remove('open');
document.getElementById('editDayDrawer').classList.remove('open');
document.getElementById('holidayDrawer').classList.remove('open');
}
function selectSlotType(type, el) {
el.parentElement.querySelectorAll('.ph-type-pill').forEach(function(b){ b.classList.remove('active'); });
el.classList.add('active');
document.getElementById('addCapField').style.display = type === 'del' ? '' : 'none';
}
function toggleDayPill(el) {
el.classList.toggle('selected');
}
function quickSelectDays(mode) {
var pills = document.querySelectorAll('#addDayPills .ph-day-pill');
var weekdays = ['周一','周二','周三','周四','周五'];
var weekends = ['周六','周日'];
pills.forEach(function(p) {
var day = p.textContent;
if (mode === 'all') p.classList.add('selected');
else if (mode === 'weekday') { weekdays.includes(day) ? p.classList.add('selected') : p.classList.remove('selected'); }
else if (mode === 'weekend') { weekends.includes(day) ? p.classList.add('selected') : p.classList.remove('selected'); }
});
}
function toggleDayOpen(el) {
el.classList.toggle('on');
var isOn = el.classList.contains('on');
document.getElementById('dayClosedHint').style.display = isOn ? 'none' : 'inline';
document.getElementById('editSlotList').style.opacity = isOn ? '1' : '0.4';
document.getElementById('editSlotList').style.pointerEvents = isOn ? '' : 'none';
}
function openHolidayDrawer(mode, data) {
document.getElementById('hoursDrawerMask').classList.add('open');
document.getElementById('holidayDrawer').classList.add('open');
if (mode === 'edit' && data) {
document.getElementById('holidayDrawerTitle').textContent = '编辑日期';
document.getElementById('holSubmitBtn').textContent = '保存修改';
document.getElementById('holReason').value = data.reason || '';
// set type pill
document.querySelectorAll('.ph-hol-type-pill').forEach(function(b){ b.classList.remove('active'); });
if (data.type === 'closed') document.querySelector('.ht-closed').classList.add('active');
else document.querySelector('.ht-special').classList.add('active');
document.getElementById('holTimeGroup').style.display = data.type === 'special' ? '' : 'none';
if (data.time) {
var parts = data.time.replace(/\s/g,'').split('-');
if (parts.length === 2) {
document.getElementById('holTimeStart').value = parts[0];
document.getElementById('holTimeEnd').value = parts[1];
}
}
// set date
if (data.date && data.date.includes('~')) {
switchDateMode('range', document.querySelectorAll('.ph-date-mode-pill')[1]);
var dp = data.date.split('~').map(function(s){ return s.trim(); });
document.getElementById('holDateFrom').value = dp[0];
document.getElementById('holDateTo').value = dp[1];
} else {
switchDateMode('single', document.querySelectorAll('.ph-date-mode-pill')[0]);
document.getElementById('holDate1').value = data.date || '';
}
} else {
document.getElementById('holidayDrawerTitle').textContent = '添加日期';
document.getElementById('holSubmitBtn').textContent = '确认添加';
document.getElementById('holReason').value = '';
document.querySelectorAll('.ph-hol-type-pill').forEach(function(b){ b.classList.remove('active'); });
document.querySelector('.ht-closed').classList.add('active');
document.getElementById('holTimeGroup').style.display = 'none';
switchDateMode('single', document.querySelectorAll('.ph-date-mode-pill')[0]);
}
if (typeof lucide !== 'undefined') lucide.createIcons();
}
function selectHolType(type, el) {
el.parentElement.querySelectorAll('.ph-hol-type-pill').forEach(function(b){ b.classList.remove('active'); });
el.classList.add('active');
document.getElementById('holTimeGroup').style.display = type === 'special' ? '' : 'none';
}
function switchDateMode(mode, el) {
document.querySelectorAll('.ph-date-mode-pill').forEach(function(b){ b.classList.remove('active'); });
if (el) el.classList.add('active');
document.getElementById('holDateSingle').style.display = mode === 'single' ? '' : 'none';
document.getElementById('holDateRange').style.display = mode === 'range' ? '' : 'none';
}
function addSlotRow() {
var list = document.getElementById('editSlotList');
if (!list) return;
var cards = list.querySelectorAll('.ph-slot-card');
if (cards.length > 0) {
var clone = cards[cards.length - 1].cloneNode(true);
clone.querySelectorAll('input').forEach(function(inp){ inp.value = ''; });
list.appendChild(clone);
if (typeof lucide !== 'undefined') lucide.createIcons();
}
}
</script>

366
pages/store-list.html Normal file
View File

@@ -0,0 +1,366 @@
<!-- 门店列表页 -->
<style>
.page-store-list .filter-bar { display:flex; gap:12px; flex-wrap:wrap; align-items:center; margin-bottom:16px; box-shadow:var(--g-shadow-sm); border-radius:10px; padding:10px 14px; background:#fff; }
.page-store-list .filter-bar input,
.page-store-list .filter-bar select {
height:34px; padding:0 10px; border:1px solid #e5e7eb; border-radius:8px;
font-size:13px; outline:none; transition:var(--g-transition); background:#fff;
}
.page-store-list .filter-bar input:focus,
.page-store-list .filter-bar select:focus { border-color:var(--primary); box-shadow:0 0 0 3px color-mix(in srgb, var(--primary) 12%, transparent); }
.page-store-list .filter-bar input { width:220px; }
.page-store-list .stat-cards { display:grid; grid-template-columns:repeat(4,1fr); gap:12px; margin-bottom:16px; }
.page-store-list .stat-card {
background:#fff; border-radius:10px; padding:16px 20px;
border:none; box-shadow:var(--g-shadow-sm); transition:var(--g-transition);
}
.page-store-list .stat-card:hover { box-shadow:var(--g-shadow-md); transform:translateY(-2px); }
.page-store-list .stat-card .label { font-size:13px; color:#9ca3af; margin-bottom:6px; }
.page-store-list .stat-card .value { font-size:24px; font-weight:700; color:#1a1a2e; }
.page-store-list .stat-card .value.green { color:#22c55e; }
.page-store-list .stat-card .value.orange { color:#f59e0b; }
.page-store-list .stat-card .value.red { color:#ef4444; }
.page-store-list .table-wrap {
background:#fff; border-radius:10px; border:none; box-shadow:var(--g-shadow-sm); overflow:hidden;
}
.page-store-list table { width:100%; border-collapse:collapse; font-size:13px; }
.page-store-list thead th {
background:#f8f9fb; padding:10px 12px; text-align:left;
font-weight:600; color:#6b7280; border-bottom:1px solid #e5e7eb;
white-space:nowrap;
}
.page-store-list tbody td {
padding:12px; border-bottom:1px solid #f3f4f6; color:#1a1a2e;
}
.page-store-list tbody tr:hover { background:color-mix(in srgb, var(--primary) 3%, #fff); }
.page-store-list tbody tr:last-child td { border-bottom:none; }
.page-store-list .status {
display:inline-flex; align-items:center; gap:5px; font-size:12px; font-weight:600;
}
.page-store-list .status-dot {
width:6px; height:6px; border-radius:50%;
}
.page-store-list .status-open .status-dot { background:#22c55e; }
.page-store-list .status-open { color:#22c55e; }
.page-store-list .status-closed .status-dot { background:#9ca3af; }
.page-store-list .status-closed { color:#9ca3af; }
.page-store-list .status-forced .status-dot { background:#ef4444; }
.page-store-list .status-forced { color:#ef4444; }
.page-store-list .audit-pass { color:#22c55e; font-weight:600; }
.page-store-list .audit-pending { color:#f59e0b; font-weight:600; }
.page-store-list .audit-reject { color:#ef4444; font-weight:600; }
.page-store-list .pagination {
display:flex; align-items:center; justify-content:flex-end;
padding:12px 16px; gap:6px; font-size:13px; color:#4b5563;
background:#fafbfc;
}
.page-store-list .page-btn {
min-width:34px; height:34px; border:1px solid #e5e7eb; border-radius:8px;
background:#fff; cursor:pointer; display:flex; align-items:center;
justify-content:center; font-size:13px; transition:var(--g-transition);
}
.page-store-list .page-btn:hover { border-color:var(--primary); color:var(--primary); }
.page-store-list .page-btn.active { background:var(--primary); color:#fff; border-color:var(--primary); box-shadow:0 0 0 3px color-mix(in srgb, var(--primary) 12%, transparent); }
.page-store-list .store-name-cell { display:flex; align-items:center; gap:10px; }
.page-store-list .store-avatar {
width:36px; height:36px; border-radius:8px; background:#f8f9fb;
display:flex; align-items:center; justify-content:center; flex-shrink:0;
overflow:hidden; font-size:14px; color:#9ca3af;
}
.page-store-list .store-info .name { font-weight:500; color:#1a1a2e; }
.page-store-list .store-info .code { font-size:11px; color:#9ca3af; margin-top:2px; }
</style>
<div class="page-store-list">
<!-- 筛选栏 -->
<div class="filter-bar">
<input type="text" placeholder="搜索门店名称 / 编号 / 电话" />
<select>
<option value="">营业状态</option>
<option>营业中</option>
<option>休息中</option>
<option>强制关闭</option>
</select>
<select>
<option value="">审核状态</option>
<option>待审核</option>
<option>已通过</option>
<option>已拒绝</option>
</select>
<select>
<option value="">服务方式</option>
<option>外卖</option>
<option>自提</option>
<option>堂食</option>
</select>
<button class="g-btn">查询</button>
<button class="g-btn">重置</button>
<div style="flex:1;"></div>
<button class="g-btn g-btn-primary" onclick="openSlDrawer('create')">
<i data-lucide="plus" style="width:14px;height:14px;"></i>新增门店
</button>
<button class="g-btn">
<i data-lucide="download" style="width:14px;height:14px;"></i>导出
</button>
</div>
<!-- 统计卡片 -->
<div class="stat-cards">
<div class="stat-card">
<div class="label">门店总数</div>
<div class="value">5</div>
</div>
<div class="stat-card">
<div class="label">营业中</div>
<div class="value green">3</div>
</div>
<div class="stat-card">
<div class="label">休息中</div>
<div class="value orange">1</div>
</div>
<div class="stat-card">
<div class="label">待审核</div>
<div class="value red">1</div>
</div>
</div>
<!-- 表格 -->
<div class="table-wrap">
<table>
<thead>
<tr>
<th style="width:220px;">门店信息</th>
<th>联系电话</th>
<th>店长</th>
<th>地址</th>
<th>服务方式</th>
<th>营业状态</th>
<th>审核状态</th>
<th>创建时间</th>
<th style="width:160px;">操作</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="store-name-cell">
<div class="store-avatar" style="background:#3b82f6"><i data-lucide="store" style="width:20px;height:20px;color:#fff"></i></div>
<div class="store-info">
<div class="name">老三家外卖(朝阳店)</div>
<div class="code">ST20250001</div>
</div>
</div>
</td>
<td>138****8001</td>
<td>张伟</td>
<td>北京市朝阳区建国路88号</td>
<td>
<span class="g-tag g-tag-blue">外卖</span>
<span class="g-tag g-tag-green">自提</span>
<span class="g-tag g-tag-orange">堂食</span>
</td>
<td><span class="status status-open"><span class="status-dot"></span>营业中</span></td>
<td><span class="audit-pass">已通过</span></td>
<td>2025-06-15</td>
<td>
<a class="g-action" onclick="openSlDrawer('edit',{name:'老三家外卖(朝阳店)',code:'ST20250001',phone:'13800008001',manager:'张伟'})">编辑</a>
<a class="g-action">歇业</a>
<a class="g-action g-action-danger">删除</a>
</td>
</tr>
<tr>
<td>
<div class="store-name-cell">
<div class="store-avatar" style="background:#f59e0b"><i data-lucide="store" style="width:20px;height:20px;color:#fff"></i></div>
<div class="store-info">
<div class="name">老三家外卖(海淀店)</div>
<div class="code">ST20250002</div>
</div>
</div>
</td>
<td>138****8002</td>
<td>李娜</td>
<td>北京市海淀区中关村大街66号</td>
<td>
<span class="g-tag g-tag-blue">外卖</span>
<span class="g-tag g-tag-green">自提</span>
</td>
<td><span class="status status-open"><span class="status-dot"></span>营业中</span></td>
<td><span class="audit-pass">已通过</span></td>
<td>2025-07-20</td>
<td>
<a class="g-action" onclick="openSlDrawer('edit',{name:'老三家外卖(海淀店)',code:'ST20250002',phone:'13800008002',manager:'李娜'})">编辑</a>
<a class="g-action">歇业</a>
<a class="g-action g-action-danger">删除</a>
</td>
</tr>
<tr>
<td>
<div class="store-name-cell">
<div class="store-avatar" style="background:#8b5cf6"><i data-lucide="store" style="width:20px;height:20px;color:#fff"></i></div>
<div class="store-info">
<div class="name">老三家外卖(望京店)</div>
<div class="code">ST20250003</div>
</div>
</div>
</td>
<td>138****8003</td>
<td>王磊</td>
<td>北京市朝阳区望京西路50号</td>
<td>
<span class="g-tag g-tag-blue">外卖</span>
</td>
<td><span class="status status-open"><span class="status-dot"></span>营业中</span></td>
<td><span class="audit-pass">已通过</span></td>
<td>2025-09-01</td>
<td>
<a class="g-action" onclick="openSlDrawer('edit',{name:'老三家外卖(望京店)',code:'ST20250003',phone:'13800008003',manager:'王磊'})">编辑</a>
<a class="g-action">歇业</a>
<a class="g-action g-action-danger">删除</a>
</td>
</tr>
<tr>
<td>
<div class="store-name-cell">
<div class="store-avatar" style="background:#ef4444"><i data-lucide="store" style="width:20px;height:20px;color:#fff"></i></div>
<div class="store-info">
<div class="name">老三家外卖(通州店)</div>
<div class="code">ST20250004</div>
</div>
</div>
</td>
<td>138****8004</td>
<td>赵敏</td>
<td>北京市通州区新华大街120号</td>
<td>
<span class="g-tag g-tag-blue">外卖</span>
<span class="g-tag g-tag-green">自提</span>
<span class="g-tag g-tag-orange">堂食</span>
</td>
<td><span class="status status-closed"><span class="status-dot"></span>休息中</span></td>
<td><span class="audit-pass">已通过</span></td>
<td>2025-10-12</td>
<td>
<a class="g-action" onclick="openSlDrawer('edit',{name:'老三家外卖(通州店)',code:'ST20250004',phone:'13800008004',manager:'赵敏'})">编辑</a>
<a class="g-action">开业</a>
<a class="g-action g-action-danger">删除</a>
</td>
</tr>
<tr>
<td>
<div class="store-name-cell">
<div class="store-avatar" style="background:#22c55e"><i data-lucide="store" style="width:20px;height:20px;color:#fff"></i></div>
<div class="store-info">
<div class="name">老三家外卖(丰台店)</div>
<div class="code">ST20250005</div>
</div>
</div>
</td>
<td>138****8005</td>
<td>刘洋</td>
<td>北京市丰台区丰台路18号</td>
<td>
<span class="g-tag g-tag-blue">外卖</span>
<span class="g-tag g-tag-green">自提</span>
</td>
<td><span class="status status-forced"><span class="status-dot"></span>待审核</span></td>
<td><span class="audit-pending">待审核</span></td>
<td>2026-01-28</td>
<td>
<a class="g-action" onclick="openSlDrawer('edit',{name:'老三家外卖(丰台店)',code:'ST20250005',phone:'13800008005',manager:'刘洋'})">编辑</a>
<a class="g-action g-action-danger">删除</a>
</td>
</tr>
</tbody>
</table>
<!-- 分页 -->
<div class="pagination">
<span>共 5 条</span>
<button class="page-btn">&lt;</button>
<button class="page-btn active">1</button>
<button class="page-btn">&gt;</button>
<span style="margin-left:8px;">10 条/页</span>
</div>
</div>
</div>
<!-- 门店添加/编辑抽屉 -->
<div class="g-drawer-mask" id="slDrawerMask" onclick="closeSlDrawer()"></div>
<div class="g-drawer" id="slDrawer" style="width:560px">
<div class="g-drawer-hd">
<div class="g-drawer-title" id="slDrawerTitle">添加门店</div>
<button class="g-drawer-close" onclick="closeSlDrawer()"><i data-lucide="x" style="width:18px;height:18px;"></i></button>
</div>
<div class="g-drawer-bd">
<div style="font-size:15px;font-weight:600;color:var(--g-text);padding-left:10px;border-left:3px solid var(--primary);margin-bottom:16px">基本信息</div>
<div class="g-form-group">
<label class="g-form-label required">门店名称</label>
<input class="g-input" placeholder="请输入门店名称">
</div>
<div class="g-form-group">
<label class="g-form-label required">门店编码</label>
<input class="g-input" placeholder="如ST20250006">
</div>
<div class="g-form-group">
<label class="g-form-label required">联系电话</label>
<input class="g-input" placeholder="请输入联系电话">
</div>
<div class="g-form-group">
<label class="g-form-label required">负责人</label>
<input class="g-input" placeholder="请输入负责人姓名">
</div>
<div class="g-form-group">
<label class="g-form-label required">门店地址</label>
<input class="g-input" placeholder="请输入详细地址">
</div>
<div class="g-form-group">
<label class="g-form-label">门店图片</label>
<div class="g-upload-zone" style="padding:16px">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
<span>点击上传门店图片</span>
</div>
</div>
<div style="font-size:15px;font-weight:600;color:var(--g-text);padding-left:10px;border-left:3px solid var(--primary);margin-bottom:16px;margin-top:24px">营业设置</div>
<div class="g-form-group">
<label class="g-form-label">营业状态</label>
<select class="g-select">
<option>营业中</option>
<option>歇业中</option>
<option>装修中</option>
</select>
</div>
<div class="g-form-group">
<label class="g-form-label">配送方式</label>
<div style="display:flex;gap:8px;flex-wrap:wrap">
<span class="g-pill checked" onclick="this.classList.toggle('checked')">外卖配送</span>
<span class="g-pill" onclick="this.classList.toggle('checked')">到店自提</span>
<span class="g-pill" onclick="this.classList.toggle('checked')">堂食</span>
</div>
</div>
</div>
<div class="g-drawer-ft">
<button class="g-btn" onclick="closeSlDrawer()">取消</button>
<button class="g-btn g-btn-primary" onclick="closeSlDrawer()">确认</button>
</div>
</div>
<script>
function openSlDrawer(mode) {
document.getElementById('slDrawerMask').classList.add('open');
document.getElementById('slDrawer').classList.add('open');
document.getElementById('slDrawerTitle').textContent = mode === 'edit' ? '编辑门店' : '添加门店';
}
function closeSlDrawer() {
document.getElementById('slDrawerMask').classList.remove('open');
document.getElementById('slDrawer').classList.remove('open');
}
</script>

245
pages/store-pickup.html Normal file
View File

@@ -0,0 +1,245 @@
<!-- 自提设置页 -->
<style>
.page-pickup { max-width:960px; }
.pp-toolbar { display:flex; align-items:center; gap:12px; margin-bottom:16px; box-shadow:var(--g-shadow-sm); border-radius:10px; padding:10px 14px; background:#fff; }
.pp-toolbar select { height:34px; padding:0 10px; border:1px solid #e5e7eb; border-radius:8px; font-size:13px; outline:none; background:#fff; transition:var(--g-transition); }
.pp-card { background:#fff; border-radius:10px; border:none; box-shadow:var(--g-shadow-sm); margin-bottom:16px; overflow:hidden; transition:var(--g-transition); }
.pp-card:hover { box-shadow:var(--g-shadow-md); }
.pp-card-header { padding:14px 18px; font-size:14px; font-weight:600; color:#1a1a2e; border-bottom:1px solid #f3f4f6; display:flex; align-items:center; justify-content:space-between; background:#f8f9fb; }
.pp-card-body { padding:16px 18px; }
.pp-form-row { display:flex; align-items:center; padding:12px 0; border-bottom:1px solid #f3f4f6; gap:12px; }
.pp-form-row:last-child { border-bottom:none; }
.pp-label { width:130px; font-size:13px; font-weight:500; color:#4b5563; flex-shrink:0; }
.pp-control { display:flex; align-items:center; gap:8px; flex:1; }
.pp-input { height:34px; padding:0 10px; border:1px solid #e5e7eb; border-radius:8px; font-size:13px; outline:none; background:#fff; width:120px; transition:var(--g-transition); }
.pp-input:focus { border-color:var(--primary); box-shadow:0 0 0 3px color-mix(in srgb, var(--primary) 12%, transparent); }
.pp-unit { font-size:12px; color:#9ca3af; }
.pp-hint { font-size:12px; color:#9ca3af; margin-left:8px; }
.pp-form-actions { display:flex; justify-content:flex-end; gap:8px; padding-top:14px; border-top:1px solid #f3f4f6; margin-top:4px; }
.pp-table { width:100%; border-collapse:collapse; font-size:13px; }
.pp-table th { background:#f8f9fb; padding:10px 12px; text-align:left; font-weight:600; color:#6b7280; border-bottom:1px solid #e5e7eb; white-space:nowrap; }
.pp-table td { padding:10px 12px; border-bottom:1px solid #f3f4f6; color:#1a1a2e; }
.pp-table tr:last-child td { border-bottom:none; }
.pp-table tr:hover td { background:color-mix(in srgb, var(--primary) 3%, #fff); }
.pp-progress { display:flex; align-items:center; gap:6px; }
.pp-progress-bar { width:60px; height:6px; background:#e5e7eb; border-radius:3px; overflow:hidden; }
.pp-progress-fill { height:100%; border-radius:3px; background:var(--primary); }
.pp-progress-text { font-size:11px; color:#9ca3af; white-space:nowrap; }
.pp-weekday { display:inline-block; padding:1px 8px; border-radius:6px; font-size:11px; font-weight:600; background:#f0f5ff; color:#597ef7; }
.pp-mode-switch { display:flex; background:#f8f9fb; border-radius:8px; padding:3px; gap:2px; width:fit-content; margin-bottom:16px; }
.pp-mode-item { padding:6px 18px; border-radius:6px; font-size:13px; cursor:pointer; color:#4b5563; border:none; background:none; transition:var(--g-transition); }
.pp-mode-item.active { background:#fff; color:var(--primary); font-weight:600; box-shadow:var(--g-shadow-sm); }
.pp-day-tabs { display:flex; gap:8px; margin-bottom:14px; }
.pp-day-tab { padding:8px 16px; border-radius:8px; font-size:13px; cursor:pointer; border:1px solid #e5e7eb; background:#fff; color:#4b5563; transition:var(--g-transition); }
.pp-day-tab.active { background:var(--primary); border-color:var(--primary); color:#fff; }
.pp-day-tab .dt-date { font-weight:500; }
.pp-day-tab .dt-sub { font-size:11px; opacity:0.8; }
.pp-slot-grid { display:flex; flex-wrap:wrap; gap:8px; }
.pp-slot-cell { width:82px; padding:8px 6px; border-radius:8px; text-align:center; border:1px solid #e5e7eb; transition:var(--g-transition); }
.pp-slot-cell .sc-time { font-size:13px; font-weight:500; }
.pp-slot-cell .sc-status { font-size:11px; margin-top:2px; }
.pp-slot-cell.expired { background:#f8f9fb; color:#bbb; border-color:#e5e7eb; }
.pp-slot-cell.available { background:#f0fdf4; color:#16a34a; border-color:#bbf7d0; }
.pp-slot-cell.almost { background:#fffbeb; color:#d97706; border-color:#fde68a; }
.pp-slot-cell.full { background:#fef2f2; color:#ef4444; border-color:#fecaca; }
.pp-legend { display:flex; gap:16px; font-size:12px; color:#9ca3af; margin-top:14px; }
.pp-legend span { display:flex; align-items:center; gap:4px; }
.pp-legend-dot { width:10px; height:10px; border-radius:2px; }
.pp-fg { margin-bottom:18px; }
.pp-fg-label { font-size:13px; font-weight:500; color:#4b5563; margin-bottom:6px; display:block; }
.pp-fg-hint { font-size:11px; color:#9ca3af; margin-top:4px; }
.pp-fg-input { height:34px; border:1px solid #e5e7eb; border-radius:8px; font-size:13px; padding:0 10px; outline:none; background:#fff; transition:var(--g-transition); }
.pp-fg-input:focus { border-color:var(--primary); box-shadow:0 0 0 3px color-mix(in srgb, var(--primary) 12%, transparent); }
.pp-day-pills { display:flex; gap:6px; }
.pp-day-pill { width:42px; height:34px; border-radius:8px; font-size:12px; cursor:pointer; border:1px solid #e5e7eb; background:#fff; color:#4b5563; display:flex; align-items:center; justify-content:center; transition:var(--g-transition); }
.pp-day-pill.selected { background:var(--primary); border-color:var(--primary); color:#fff; }
.pp-qk-btns { display:flex; gap:8px; margin-top:8px; }
.pp-qk-btn { font-size:11px; color:var(--primary); cursor:pointer; background:none; border:none; padding:0; transition:var(--g-transition); }
.pp-qk-btn:hover { text-decoration:underline; }
</style>
<div class="page-pickup">
<div class="pp-toolbar">
<select style="width:200px;">
<option>老三家外卖(朝阳店)</option>
<option>老三家外卖(海淀店)</option>
<option>老三家外卖(望京店)</option>
<option>老三家外卖(通州店)</option>
<option>老三家外卖(丰台店)</option>
</select>
<div style="flex:1;"></div>
<button class="g-btn" onclick="openCopyStoreModal('复制自提设置到其他门店')">
<i data-lucide="copy" style="width:14px;height:14px;"></i>复制到其他门店
</button>
</div>
<div class="pp-card">
<div class="pp-card-header">基本设置</div>
<div class="pp-card-body">
<div class="pp-form-row">
<div class="pp-label">允许当天自提</div>
<div class="pp-control"><label class="g-toggle-input"><input type="checkbox" checked><span class="g-toggle-sl"></span></label><span class="pp-hint">开启后顾客可选择当天自提</span></div>
</div>
<div class="pp-form-row">
<div class="pp-label">可预约天数</div>
<div class="pp-control"><input type="number" class="pp-input" value="3" style="width:80px;"><span class="pp-unit"></span><span class="pp-hint">顾客可提前预约的天数</span></div>
</div>
<div class="pp-form-row">
<div class="pp-label">单笔最大数量</div>
<div class="pp-control"><input type="number" class="pp-input" value="20" style="width:80px;"><span class="pp-unit"></span><span class="pp-hint">留空则不限制</span></div>
</div>
<div class="pp-form-actions"><button class="g-btn">重置</button><button class="g-btn g-btn-primary"><i data-lucide="save" style="width:14px;height:14px;"></i>保存设置</button></div>
</div>
</div>
<div class="pp-mode-switch">
<button class="pp-mode-item active" onclick="switchPickupMode('big')">大时段模式</button>
<button class="pp-mode-item" onclick="switchPickupMode('fine')">精细时段模式</button>
</div>
<div id="bigSlotSection">
<div class="pp-card">
<div class="pp-card-header">自提时段<button class="g-btn g-btn-primary g-btn-sm" onclick="openPickupDrawer('add')"><i data-lucide="plus" style="width:13px;height:13px;"></i>添加时段</button></div>
<div class="pp-card-body" style="padding:0;">
<table class="pp-table"><thead><tr><th>时段名称</th><th>时间范围</th><th>截止(分钟)</th><th>容量</th><th>已预约</th><th>适用星期</th><th>状态</th><th>操作</th></tr></thead>
<tbody>
<tr><td style="font-weight:500;">上午时段</td><td>09:00-11:30</td><td>30</td><td>20</td><td><div class="pp-progress"><div class="pp-progress-bar"><div class="pp-progress-fill" style="width:25%;"></div></div><span class="pp-progress-text">5/20</span></div></td><td><span class="pp-weekday">周一至周五</span></td><td><label class="g-toggle-input"><input type="checkbox" checked><span class="g-toggle-sl"></span></label></td><td><a class="g-action" onclick="openPickupDrawer('edit',{name:'上午时段',start:'09:00',end:'11:30',cutoff:30,cap:20,days:'weekday'})">编辑</a><a class="g-action g-action-danger">删除</a></td></tr>
<tr><td style="font-weight:500;">午间时段</td><td>11:30-14:00</td><td>20</td><td>30</td><td><div class="pp-progress"><div class="pp-progress-bar"><div class="pp-progress-fill" style="width:40%;"></div></div><span class="pp-progress-text">12/30</span></div></td><td><span class="pp-weekday">每天</span></td><td><label class="g-toggle-input"><input type="checkbox" checked><span class="g-toggle-sl"></span></label></td><td><a class="g-action" onclick="openPickupDrawer('edit',{name:'午间时段',start:'11:30',end:'14:00',cutoff:20,cap:30,days:'all'})">编辑</a><a class="g-action g-action-danger">删除</a></td></tr>
<tr><td style="font-weight:500;">下午时段</td><td>14:00-17:00</td><td>30</td><td>15</td><td><div class="pp-progress"><div class="pp-progress-bar"><div class="pp-progress-fill" style="width:20%;"></div></div><span class="pp-progress-text">3/15</span></div></td><td><span class="pp-weekday">周一至周五</span></td><td><label class="g-toggle-input"><input type="checkbox" checked><span class="g-toggle-sl"></span></label></td><td><a class="g-action" onclick="openPickupDrawer('edit',{name:'下午时段',start:'14:00',end:'17:00',cutoff:30,cap:15,days:'weekday'})">编辑</a><a class="g-action g-action-danger">删除</a></td></tr>
<tr><td style="font-weight:500;">晚间时段</td><td>17:00-20:30</td><td>30</td><td>25</td><td><div class="pp-progress"><div class="pp-progress-bar"><div class="pp-progress-fill" style="width:32%;"></div></div><span class="pp-progress-text">8/25</span></div></td><td><span class="pp-weekday">每天</span></td><td><label class="g-toggle-input"><input type="checkbox" checked><span class="g-toggle-sl"></span></label></td><td><a class="g-action" onclick="openPickupDrawer('edit',{name:'晚间时段',start:'17:00',end:'20:30',cutoff:30,cap:25,days:'all'})">编辑</a><a class="g-action g-action-danger">删除</a></td></tr>
<tr><td style="font-weight:500;">周末特惠</td><td>10:00-15:00</td><td>45</td><td>40</td><td><div class="pp-progress"><div class="pp-progress-bar"><div class="pp-progress-fill" style="width:45%;background:#faad14;"></div></div><span class="pp-progress-text">18/40</span></div></td><td><span class="pp-weekday">周六周日</span></td><td><label class="g-toggle-input"><input type="checkbox"><span class="g-toggle-sl"></span></label></td><td><a class="g-action" onclick="openPickupDrawer('edit',{name:'周末特惠',start:'10:00',end:'15:00',cutoff:45,cap:40,days:'weekend'})">编辑</a><a class="g-action g-action-danger">删除</a></td></tr>
</tbody></table>
</div>
</div>
</div>
<div id="fineSlotSection" style="display:none;">
<div class="pp-card">
<div class="pp-card-header">生成规则</div>
<div class="pp-card-body">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:14px 24px;">
<div class="pp-fg"><label class="pp-fg-label">时间间隔</label><select class="pp-input" style="width:100%;"><option>15 分钟</option><option>20 分钟</option><option>25 分钟</option><option selected>30 分钟</option><option>45 分钟</option><option>60 分钟</option></select></div>
<div class="pp-fg"><label class="pp-fg-label">每个时段容量</label><div style="display:flex;align-items:center;gap:6px;"><input type="number" class="pp-input" value="5" style="width:80px;"><span class="pp-unit"></span></div><div class="pp-fg-hint">每个时间窗口最大接单量</div></div>
<div class="pp-fg"><label class="pp-fg-label">每日开始时间</label><input type="time" class="pp-input" value="09:00" style="width:100%;"></div>
<div class="pp-fg"><label class="pp-fg-label">每日结束时间</label><input type="time" class="pp-input" value="20:30" style="width:100%;"></div>
<div class="pp-fg"><label class="pp-fg-label">最少提前预约</label><div style="display:flex;align-items:center;gap:6px;"><input type="number" class="pp-input" value="2" style="width:80px;"><span class="pp-unit">小时</span></div><div class="pp-fg-hint">下单时间距取餐时间的最小间隔</div></div>
</div>
<div class="pp-fg" style="margin-top:4px;"><label class="pp-fg-label">适用星期</label>
<div class="pp-day-pills" id="fineDayPills">
<button class="pp-day-pill selected" onclick="this.classList.toggle('selected')">周一</button>
<button class="pp-day-pill selected" onclick="this.classList.toggle('selected')">周二</button>
<button class="pp-day-pill selected" onclick="this.classList.toggle('selected')">周三</button>
<button class="pp-day-pill selected" onclick="this.classList.toggle('selected')">周四</button>
<button class="pp-day-pill selected" onclick="this.classList.toggle('selected')">周五</button>
<button class="pp-day-pill selected" onclick="this.classList.toggle('selected')">周六</button>
<button class="pp-day-pill selected" onclick="this.classList.toggle('selected')">周日</button>
</div>
<div class="pp-qk-btns"><button class="pp-qk-btn" onclick="ppQuickDays('all','fineDayPills')">全选</button><button class="pp-qk-btn" onclick="ppQuickDays('weekday','fineDayPills')">工作日</button><button class="pp-qk-btn" onclick="ppQuickDays('weekend','fineDayPills')">周末</button></div>
</div>
<div class="pp-form-actions"><button class="g-btn">重置</button><button class="g-btn g-btn-primary"><i data-lucide="save" style="width:14px;height:14px;"></i>保存规则</button></div>
</div>
</div>
<div class="pp-card">
<div class="pp-card-header">时段预览<span style="font-size:12px;font-weight:400;color:#999;">根据规则自动生成,以下为预览效果</span></div>
<div class="pp-card-body">
<div class="pp-day-tabs">
<button class="pp-day-tab active" onclick="switchPreviewDay(this)"><span class="dt-date">2/11</span> <span class="dt-sub">周三 今天</span></button>
<button class="pp-day-tab" onclick="switchPreviewDay(this)"><span class="dt-date">2/12</span> <span class="dt-sub">周四 明天</span></button>
<button class="pp-day-tab" onclick="switchPreviewDay(this)"><span class="dt-date">2/13</span> <span class="dt-sub">周五 后天</span></button>
</div>
<div class="pp-slot-grid"><div class="pp-slot-cell expired"><div class="sc-time">09:00</div><div class="sc-status">已过期</div></div><div class="pp-slot-cell expired"><div class="sc-time">09:30</div><div class="sc-status">已过期</div></div><div class="pp-slot-cell expired"><div class="sc-time">10:00</div><div class="sc-status">已过期</div></div><div class="pp-slot-cell expired"><div class="sc-time">10:30</div><div class="sc-status">已过期</div></div><div class="pp-slot-cell available"><div class="sc-time">11:00</div><div class="sc-status">剩余 3</div></div><div class="pp-slot-cell available"><div class="sc-time">11:30</div><div class="sc-status">剩余 5</div></div><div class="pp-slot-cell almost"><div class="sc-time">12:00</div><div class="sc-status">剩余 1</div></div><div class="pp-slot-cell full"><div class="sc-time">12:30</div><div class="sc-status">已满</div></div><div class="pp-slot-cell full"><div class="sc-time">13:00</div><div class="sc-status">已满</div></div><div class="pp-slot-cell available"><div class="sc-time">13:30</div><div class="sc-status">剩余 2</div></div><div class="pp-slot-cell available"><div class="sc-time">14:00</div><div class="sc-status">剩余 5</div></div><div class="pp-slot-cell available"><div class="sc-time">14:30</div><div class="sc-status">剩余 5</div></div><div class="pp-slot-cell available"><div class="sc-time">15:00</div><div class="sc-status">剩余 4</div></div><div class="pp-slot-cell available"><div class="sc-time">15:30</div><div class="sc-status">剩余 5</div></div><div class="pp-slot-cell available"><div class="sc-time">16:00</div><div class="sc-status">剩余 5</div></div><div class="pp-slot-cell available"><div class="sc-time">16:30</div><div class="sc-status">剩余 5</div></div><div class="pp-slot-cell available"><div class="sc-time">17:00</div><div class="sc-status">剩余 3</div></div><div class="pp-slot-cell available"><div class="sc-time">17:30</div><div class="sc-status">剩余 5</div></div><div class="pp-slot-cell almost"><div class="sc-time">18:00</div><div class="sc-status">剩余 1</div></div><div class="pp-slot-cell available"><div class="sc-time">18:30</div><div class="sc-status">剩余 5</div></div><div class="pp-slot-cell available"><div class="sc-time">19:00</div><div class="sc-status">剩余 5</div></div><div class="pp-slot-cell available"><div class="sc-time">19:30</div><div class="sc-status">剩余 4</div></div><div class="pp-slot-cell available"><div class="sc-time">20:00</div><div class="sc-status">剩余 5</div></div></div>
<div class="pp-legend">
<span><span class="pp-legend-dot" style="background:#d9d9d9;"></span>已过期</span>
<span><span class="pp-legend-dot" style="background:#b7eb8f;"></span>可预约</span>
<span><span class="pp-legend-dot" style="background:#ffd591;"></span>即将满</span>
<span><span class="pp-legend-dot" style="background:#ffa39e;"></span>已满</span>
</div>
</div>
</div>
</div>
</div>
<div class="g-drawer-mask" id="ppDrawerMask" onclick="closePickupDrawer()"></div>
<div class="g-drawer" id="ppSlotDrawer" style="width:480px;">
<div class="g-drawer-hd"><span class="g-drawer-title" id="ppDrawerTitle">添加时段</span><button class="g-drawer-close" onclick="closePickupDrawer()"><i data-lucide="x" style="width:18px;height:18px;"></i></button></div>
<div class="g-drawer-bd">
<div class="pp-fg"><label class="pp-fg-label">时段名称</label><input type="text" class="pp-fg-input" id="ppName" placeholder="如:上午时段" style="width:100%;" /></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
<div class="pp-fg"><label class="pp-fg-label">开始时间</label><input type="time" class="pp-fg-input" id="ppStart" value="09:00" style="width:100%;" /></div>
<div class="pp-fg"><label class="pp-fg-label">结束时间</label><input type="time" class="pp-fg-input" id="ppEnd" value="17:00" style="width:100%;" /></div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
<div class="pp-fg"><label class="pp-fg-label">截止时间</label><div style="display:flex;align-items:center;gap:6px;"><input type="number" class="pp-fg-input" id="ppCutoff" value="30" style="width:80px;"><span class="pp-unit">分钟</span></div><div class="pp-fg-hint">时段开始前停止接单</div></div>
<div class="pp-fg"><label class="pp-fg-label">容量上限</label><div style="display:flex;align-items:center;gap:6px;"><input type="number" class="pp-fg-input" id="ppCap" value="20" style="width:80px;"><span class="pp-unit"></span></div><div class="pp-fg-hint">该时段最大接单数</div></div>
</div>
<div class="pp-fg"><label class="pp-fg-label">适用星期</label>
<div class="pp-day-pills" id="ppDayPills">
<button class="pp-day-pill selected" onclick="this.classList.toggle('selected')">周一</button>
<button class="pp-day-pill selected" onclick="this.classList.toggle('selected')">周二</button>
<button class="pp-day-pill selected" onclick="this.classList.toggle('selected')">周三</button>
<button class="pp-day-pill selected" onclick="this.classList.toggle('selected')">周四</button>
<button class="pp-day-pill selected" onclick="this.classList.toggle('selected')">周五</button>
<button class="pp-day-pill" onclick="this.classList.toggle('selected')">周六</button>
<button class="pp-day-pill" onclick="this.classList.toggle('selected')">周日</button>
</div>
<div class="pp-qk-btns"><button class="pp-qk-btn" onclick="ppQuickDays('all','ppDayPills')">全选</button><button class="pp-qk-btn" onclick="ppQuickDays('weekday','ppDayPills')">工作日</button><button class="pp-qk-btn" onclick="ppQuickDays('weekend','ppDayPills')">周末</button></div>
</div>
<div class="pp-fg"><label class="pp-fg-label">启用状态</label><label class="g-toggle-input"><input type="checkbox" id="ppEnabled" checked><span class="g-toggle-sl"></span></label></div>
</div>
<div class="g-drawer-ft"><button class="g-btn" onclick="closePickupDrawer()">取消</button><button class="g-btn g-btn-primary" id="ppSubmitBtn" onclick="closePickupDrawer()">确认添加</button></div>
</div>
<script>
function switchPickupMode(mode) {
document.querySelectorAll('.pp-mode-item').forEach(function(b){ b.classList.remove('active'); });
event.currentTarget.classList.add('active');
document.getElementById('bigSlotSection').style.display = mode==='big' ? '' : 'none';
document.getElementById('fineSlotSection').style.display = mode==='fine' ? '' : 'none';
}
function openPickupDrawer(mode, data) {
document.getElementById('ppDrawerMask').classList.add('open');
document.getElementById('ppSlotDrawer').classList.add('open');
var weekdays=['周一','周二','周三','周四','周五'], weekends=['周六','周日'];
if (mode==='edit' && data) {
document.getElementById('ppDrawerTitle').textContent='编辑时段 - '+data.name;
document.getElementById('ppSubmitBtn').textContent='保存修改';
document.getElementById('ppName').value=data.name||'';
document.getElementById('ppStart').value=data.start||'';
document.getElementById('ppEnd').value=data.end||'';
document.getElementById('ppCutoff').value=data.cutoff||30;
document.getElementById('ppCap').value=data.cap||20;
document.querySelectorAll('#ppDayPills .pp-day-pill').forEach(function(p){
var d=p.textContent;
if(data.days==='all') p.classList.add('selected');
else if(data.days==='weekday') p.classList.toggle('selected',weekdays.includes(d));
else if(data.days==='weekend') p.classList.toggle('selected',weekends.includes(d));
});
} else {
document.getElementById('ppDrawerTitle').textContent='添加时段';
document.getElementById('ppSubmitBtn').textContent='确认添加';
document.getElementById('ppName').value='';
document.getElementById('ppStart').value='09:00';
document.getElementById('ppEnd').value='17:00';
document.getElementById('ppCutoff').value=30;
document.getElementById('ppCap').value=20;
ppQuickDays('weekday','ppDayPills');
}
if(typeof lucide!=='undefined') lucide.createIcons();
}
function closePickupDrawer() {
document.getElementById('ppDrawerMask').classList.remove('open');
document.getElementById('ppSlotDrawer').classList.remove('open');
}
function ppQuickDays(mode, containerId) {
var pills=document.querySelectorAll('#'+containerId+' .pp-day-pill');
var weekdays=['周一','周二','周三','周四','周五'], weekends=['周六','周日'];
pills.forEach(function(p){
var d=p.textContent;
if(mode==='all') p.classList.add('selected');
else if(mode==='weekday') p.classList.toggle('selected',weekdays.includes(d));
else if(mode==='weekend') p.classList.toggle('selected',weekends.includes(d));
});
}
function switchPreviewDay(el) {
document.querySelectorAll('.pp-day-tab').forEach(function(b){ b.classList.remove('active'); });
el.classList.add('active');
}
</script>

View File

@@ -0,0 +1,187 @@
<!-- 资质证照页 -->
<style>
.pq-top-bar{display:flex;align-items:center;gap:12px;margin-bottom:16px;flex-wrap:wrap;box-shadow:var(--g-shadow-sm);border-radius:10px;padding:12px 16px;background:#fff;}
.pq-top-bar select{height:34px;padding:0 10px;border:1px solid #e5e7eb;border-radius:8px;font-size:13px;outline:none;background:#fff;min-width:180px;cursor:pointer;transition:var(--g-transition);}
.pq-top-bar select:focus{border-color:var(--primary);box-shadow:0 0 0 3px color-mix(in srgb, var(--primary) 12%, transparent);}
.pq-card-hd{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;}
.pq-card-title{font-size:15px;font-weight:600;color:#1a1a2e;}
.pq-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;}
.pq-item{border:none;border-radius:10px;overflow:hidden;box-shadow:var(--g-shadow-sm);transition:var(--g-transition);}
.pq-item:hover{box-shadow:0 4px 16px rgba(0,0,0,0.10);}
.pq-thumb{height:160px;background:#f8f9fb;display:flex;align-items:center;justify-content:center;border-bottom:1px dashed #e5e7eb;}
.pq-thumb svg{width:48px;height:48px;color:#bbb;}
.pq-body{padding:12px 14px 14px;}
.pq-type-tag{display:inline-block;padding:2px 10px;border-radius:6px;font-size:12px;margin-bottom:8px;font-weight:600;}
.pq-tag-blue{background:#e6f7ff;color:#1890ff;}
.pq-tag-green{background:#f6ffed;color:#22c55e;}
.pq-tag-purple{background:#f9f0ff;color:#722ed1;}
.pq-tag-orange{background:#fff7e6;color:#f59e0b;}
.pq-tag-teal{background:#e6fffb;color:#13c2c2;}
.pq-tag-grey{background:#f8f9fb;color:#9ca3af;}
.pq-doc-no{font-size:13px;color:#1a1a2e;margin-bottom:6px;word-break:break-all;}
.pq-dates{font-size:12px;color:#9ca3af;margin-bottom:8px;}
.pq-status{display:inline-flex;align-items:center;gap:5px;font-size:12px;margin-bottom:10px;font-weight:600;}
.pq-status-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0;}
.pq-status-green .pq-status-dot{background:#22c55e;} .pq-status-green{color:#22c55e;}
.pq-status-orange .pq-status-dot{background:#f59e0b;} .pq-status-orange{color:#f59e0b;}
.pq-status-red .pq-status-dot{background:#ef4444;} .pq-status-red{color:#ef4444;}
.pq-actions{display:flex;gap:12px;border-top:1px solid #f3f4f6;padding-top:10px;}
.pq-remind-row{display:flex;align-items:center;gap:12px;margin-bottom:14px;font-size:13px;color:#1a1a2e;}
.pq-remind-row label{min-width:100px;text-align:right;font-weight:500;color:#4b5563;}
.pq-remind-row input[type=number]{width:80px;height:34px;padding:0 10px;border:1px solid #e5e7eb;border-radius:8px;font-size:13px;outline:none;text-align:center;transition:var(--g-transition);}
.pq-remind-row input[type=number]:focus{border-color:var(--primary);box-shadow:0 0 0 3px color-mix(in srgb, var(--primary) 12%, transparent);}
.pq-remind-row .unit{color:#9ca3af;font-size:12px;}
.pq-checks{display:flex;gap:18px;}
.pq-checks label{display:flex;align-items:center;gap:5px;font-size:13px;color:#1a1a2e;cursor:pointer;}
.pq-checks input[type=checkbox]{accent-color:var(--primary);cursor:pointer;}
.pq-item.pq-expired { border-left:3px solid #ef4444; background:#fff1f0; }
.pq-item.pq-expiring { border-left:3px solid #f59e0b; background:#fffbeb; }
@media(max-width:900px){.pq-grid{grid-template-columns:repeat(2,1fr);}}
@media(max-width:600px){.pq-grid{grid-template-columns:1fr;}}
</style>
<div class="pq-top-bar">
<select>
<option>老三家外卖(朝阳店)</option>
<option>老三家外卖(海淀店)</option>
<option>老三家外卖(丰台店)</option>
<option>老三家外卖(西城店)</option>
<option>老三家外卖(东城店)</option>
</select>
</div>
<div class="g-card" style="margin-bottom:16px;">
<div class="pq-card-hd">
<span class="pq-card-title">资质证照</span>
<button class="g-btn g-btn-primary" onclick="openSqDrawer()">+ 上传证照</button>
</div>
<div class="pq-grid">
<div class="pq-item">
<div class="pq-thumb"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg></div>
<div class="pq-body">
<span class="pq-type-tag pq-tag-blue">营业执照</span>
<div class="pq-doc-no">No.91110105MA01XXXX</div>
<div class="pq-dates">发证日期 2024-01-15 有效期至 2027-01-14</div>
<div class="pq-status pq-status-green"><span class="pq-status-dot"></span>有效</div>
<div class="pq-actions"><a class="g-action" href="javascript:;">编辑</a><a class="g-action g-action-danger" href="javascript:;">删除</a></div>
</div>
</div>
<div class="pq-item">
<div class="pq-thumb"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg></div>
<div class="pq-body">
<span class="pq-type-tag pq-tag-green">食品经营许可证</span>
<div class="pq-doc-no">No.JY11105XXXXXXX</div>
<div class="pq-dates">发证日期 2024-03-20 有效期至 2027-03-19</div>
<div class="pq-status pq-status-green"><span class="pq-status-dot"></span>有效</div>
<div class="pq-actions"><a class="g-action" href="javascript:;">编辑</a><a class="g-action g-action-danger" href="javascript:;">删除</a></div>
</div>
</div>
<div class="pq-item pq-expiring">
<div class="pq-thumb"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg></div>
<div class="pq-body">
<span class="pq-type-tag pq-tag-purple">食品安全管理员证</span>
<div class="pq-doc-no">No.AQGL2024XXXX</div>
<div class="pq-dates">发证日期 2024-06-01 有效期至 2025-05-31</div>
<div class="pq-status pq-status-orange"><span class="pq-status-dot"></span>即将过期</div>
<div class="pq-actions"><a class="g-action" href="javascript:;">编辑</a><a class="g-action g-action-danger" href="javascript:;">删除</a></div>
</div>
</div>
<div class="pq-item pq-expired">
<div class="pq-thumb"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg></div>
<div class="pq-body">
<span class="pq-type-tag pq-tag-orange">消防安全检查合格证</span>
<div class="pq-doc-no">No.XF2024XXXXX</div>
<div class="pq-dates">发证日期 2024-02-10 有效期至 2026-02-09</div>
<div class="pq-status pq-status-red"><span class="pq-status-dot"></span>已过期</div>
<div class="pq-actions"><a class="g-action" href="javascript:;">编辑</a><a class="g-action g-action-danger" href="javascript:;">删除</a></div>
</div>
</div>
<div class="pq-item">
<div class="pq-thumb"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg></div>
<div class="pq-body">
<span class="pq-type-tag pq-tag-teal">健康证(张伟)</span>
<div class="pq-doc-no">No.JK2024XXXXX</div>
<div class="pq-dates">发证日期 2024-08-15 有效期至 2025-08-14</div>
<div class="pq-status pq-status-green"><span class="pq-status-dot"></span>有效</div>
<div class="pq-actions"><a class="g-action" href="javascript:;">编辑</a><a class="g-action g-action-danger" href="javascript:;">删除</a></div>
</div>
</div>
<div class="pq-item">
<div class="pq-thumb"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg></div>
<div class="pq-body">
<span class="pq-type-tag pq-tag-grey">排污许可证</span>
<div class="pq-doc-no">No.PW2024XXXXX</div>
<div class="pq-dates">发证日期 2025-01-01 有效期至 2028-12-31</div>
<div class="pq-status pq-status-green"><span class="pq-status-dot"></span>有效</div>
<div class="pq-actions"><a class="g-action" href="javascript:;">编辑</a><a class="g-action g-action-danger" href="javascript:;">删除</a></div>
</div>
</div>
</div>
</div>
<div class="g-card">
<div class="pq-card-hd"><span class="pq-card-title">到期提醒设置</span></div>
<div class="pq-remind-row"><label>提前提醒天数</label><input type="number" value="30"><span class="unit"></span></div>
<div class="pq-remind-row"><label>提醒方式</label><div class="pq-checks"><label><input type="checkbox" checked>站内通知</label><label><input type="checkbox">短信通知</label><label><input type="checkbox">邮件通知</label></div></div>
<div class="pq-remind-row"><label></label><button class="g-btn g-btn-primary">保存设置</button></div>
</div>
<!-- 上传证照抽屉 -->
<div class="g-drawer-mask" id="sqDrawerMask" onclick="closeSqDrawer()"></div>
<div class="g-drawer" id="sqDrawer" style="width:480px">
<div class="g-drawer-hd">
<div class="g-drawer-title">上传证照</div>
<button class="g-drawer-close" onclick="closeSqDrawer()"><i data-lucide="x" style="width:18px;height:18px;"></i></button>
</div>
<div class="g-drawer-bd">
<div class="g-form-group">
<label class="g-form-label required">证照类型</label>
<select class="g-select">
<option value="">请选择证照类型</option>
<option>营业执照</option>
<option>食品经营许可证</option>
<option>卫生许可证</option>
<option>消防安全检查合格证</option>
<option>食品安全管理员证</option>
<option>健康证</option>
<option>排污许可证</option>
<option>其他</option>
</select>
</div>
<div class="g-form-group">
<label class="g-form-label required">证照编号</label>
<input class="g-input" placeholder="请输入证照编号">
</div>
<div class="g-form-group">
<label class="g-form-label required">有效期至</label>
<input type="date" class="g-input">
</div>
<div class="g-form-group">
<label class="g-form-label required">上传证照图片</label>
<div class="g-upload-zone">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
<p>点击或拖拽上传证照图片</p>
<p style="font-size:11px;color:#999;margin-top:4px">支持 JPG、PNG 格式,不超过 5MB</p>
</div>
</div>
<div class="g-form-group">
<label class="g-form-label">备注</label>
<textarea class="g-textarea" rows="3" placeholder="如有特殊说明请填写"></textarea>
</div>
</div>
<div class="g-drawer-ft">
<button class="g-btn" onclick="closeSqDrawer()">取消</button>
<button class="g-btn g-btn-primary" onclick="closeSqDrawer()">确认上传</button>
</div>
</div>
<script>
function openSqDrawer() {
document.getElementById('sqDrawerMask').classList.add('open');
document.getElementById('sqDrawer').classList.add('open');
}
function closeSqDrawer() {
document.getElementById('sqDrawerMask').classList.remove('open');
document.getElementById('sqDrawer').classList.remove('open');
}
</script>

406
pages/store-staff.html Normal file
View File

@@ -0,0 +1,406 @@
<!-- 员工排班页 -->
<style>
.ps-toolbar { display:flex; align-items:center; gap:12px; margin-bottom:16px; box-shadow:var(--g-shadow-sm); border-radius:10px; padding:12px 16px; background:#fff; }
.ps-filters { display:flex; gap:12px; margin-bottom:16px; flex-wrap:wrap; }
.ps-staff-info { display:flex; align-items:center; gap:10px; }
.ps-avatar { width:36px; height:36px; border-radius:50%; display:flex; align-items:center; justify-content:center; color:#fff; font-size:14px; font-weight:500; flex-shrink:0; }
.ps-staff-name { font-weight:500; color:#1a1a2e; }
.ps-staff-phone { color:#4b5563; font-size:12px; margin-top:2px; }
.ps-status { display:flex; align-items:center; gap:6px; }
.ps-dot { width:6px; height:6px; border-radius:50%; }
.ps-dot-green { background:#22c55e; }
.ps-dot-orange { background:#f59e0b; }
.ps-dot-grey { background:#9ca3af; }
.ps-perm-pill { display:inline-block; padding:1px 6px; background:#f0f5ff; color:#2f54eb; border-radius:6px; font-size:11px; margin:1px 2px; font-weight:600; }
.ps-actions a { color:var(--primary); text-decoration:none; margin-right:12px; font-size:13px; cursor:pointer; transition:var(--g-transition); }
.ps-actions a:hover { text-decoration:underline; }
.ps-actions a.ps-danger { color:#ef4444; }
.ps-pagination { display:flex; justify-content:flex-end; align-items:center; margin-top:16px; gap:4px; }
.ps-page-btn { min-width:32px; height:34px; border:1px solid #e5e7eb; border-radius:8px; background:#fff; cursor:pointer; display:flex; align-items:center; justify-content:center; font-size:13px; color:#1a1a2e; transition:var(--g-transition); }
.ps-page-btn.active { border-color:var(--primary); color:var(--primary); }
.ps-page-btn:hover { border-color:var(--primary); color:var(--primary); }
.ps-page-info { color:#4b5563; font-size:13px; margin-right:12px; }
.ps-schedule { width:100%; border-collapse:collapse; }
.ps-schedule th,.ps-schedule td { padding:10px 8px; text-align:center; border-bottom:1px solid #f3f4f6; font-size:13px; }
.ps-schedule th { background:#f8f9fb; font-weight:600; color:#6b7280; }
.ps-schedule td.ps-shift-morning { background:#e6f7ff; color:#096dd9; }
.ps-schedule td.ps-shift-evening { background:#fff7e6; color:#d46b08; }
.ps-schedule td.ps-shift-full { background:#f6ffed; color:#389e0d; }
.ps-schedule td.ps-shift-off { background:#f8f9fb; color:#9ca3af; }
.ps-schedule .ps-sched-name { text-align:left; font-weight:500; padding-left:12px; white-space:nowrap; color:#1a1a2e; }
.ps-schedule .ps-sched-role { font-size:11px; color:#4b5563; font-weight:normal; }
.ps-shift-label { font-size:11px; display:block; color:inherit; opacity:0.7; }
.ps-perm-grid { display:grid; grid-template-columns:1fr 1fr; gap:8px; }
.ps-perm-check { display:flex; align-items:center; gap:6px; font-size:13px; color:#1a1a2e; cursor:pointer; padding:6px 10px; border:1px solid #e5e7eb; border-radius:8px; transition:var(--g-transition); }
.ps-perm-check:hover { border-color:var(--primary); }
.ps-perm-check input { accent-color:var(--primary); cursor:pointer; }
.ps-perm-check.all { grid-column:1/-1; background:#f0f5ff; border-color:#d6e4ff; }
/* Schedule drawer */
.ps-sched-row { display:flex; align-items:center; gap:10px; padding:12px 0; border-bottom:1px solid #f3f4f6; }
.ps-sched-row:last-child { border-bottom:none; }
.ps-sched-day { width:40px; font-weight:600; color:#1a1a2e; flex-shrink:0; }
.ps-shift-pills { display:flex; gap:6px; flex-shrink:0; }
.ps-shift-pill { padding:4px 10px; border-radius:6px; font-size:12px; cursor:pointer; border:1px solid #e5e7eb; background:#fff; color:#4b5563; transition:var(--g-transition); }
.ps-shift-pill:hover { border-color:var(--primary); color:var(--primary); }
.ps-shift-pill.active { color:#fff; border-color:transparent; }
.ps-shift-pill.s-morning.active { background:#1890ff; }
.ps-shift-pill.s-evening.active { background:#fa8c16; }
.ps-shift-pill.s-full.active { background:#22c55e; }
.ps-shift-pill.s-off.active { background:#9ca3af; }
.ps-sched-times { display:flex; align-items:center; gap:4px; font-size:12px; color:#4b5563; }
.ps-sched-times input[type=time] { height:34px; border:1px solid #e5e7eb; border-radius:8px; font-size:12px; padding:0 4px; outline:none; transition:var(--g-transition); }
.ps-sched-times input[type=time]:focus { border-color:var(--primary); box-shadow:0 0 0 3px color-mix(in srgb, var(--primary) 12%, transparent); }
/* Week editor */
.ps-week-table { width:100%; border-collapse:collapse; }
.ps-week-table th { background:#f8f9fb; padding:8px 6px; text-align:center; font-weight:600; color:#6b7280; border:1px solid #f3f4f6; font-size:12px; }
.ps-week-table td { border:1px solid #f3f4f6; padding:0; text-align:center; height:52px; }
.ps-week-table .ps-wk-name { text-align:left; padding:8px 10px; font-weight:500; font-size:13px; white-space:nowrap; background:#fff; color:#1a1a2e; }
.ps-wk-cell { width:100%; height:100%; display:flex; flex-direction:column; align-items:center; justify-content:center; cursor:pointer; transition:var(--g-transition); user-select:none; font-size:12px; gap:2px; }
.ps-wk-cell:hover { filter:brightness(0.95); }
.ps-wk-cell .wk-time { font-size:11px; opacity:0.8; }
.ps-wk-cell[data-shift=morning] { background:#e6f7ff; color:#096dd9; }
.ps-wk-cell[data-shift=evening] { background:#fff7e6; color:#d46b08; }
.ps-wk-cell[data-shift=full] { background:#f6ffed; color:#389e0d; }
.ps-wk-cell[data-shift=off] { background:#f8f9fb; color:#9ca3af; }
.ps-week-legend { display:flex; gap:16px; margin-bottom:14px; font-size:12px; color:#4b5563; }
.ps-week-legend span { display:flex; align-items:center; gap:4px; }
.ps-week-legend i { width:12px; height:12px; border-radius:2px; display:inline-block; }
.ps-week-legend .lg-m { background:#e6f7ff; border:1px solid #91d5ff; }
.ps-week-legend .lg-e { background:#fff7e6; border:1px solid #ffd591; }
.ps-week-legend .lg-f { background:#f6ffed; border:1px solid #b7eb8f; }
.ps-week-legend .lg-o { background:#f8f9fb; border:1px solid #e5e7eb; }
/* Shift template */
.ps-tpl-row { display:flex; align-items:center; gap:14px; padding:10px 0; border-bottom:1px solid #f3f4f6; }
.ps-tpl-row:last-child { border-bottom:none; }
.ps-tpl-dot { width:10px; height:10px; border-radius:2px; flex-shrink:0; }
.ps-tpl-dot.m { background:#1890ff; }
.ps-tpl-dot.e { background:#fa8c16; }
.ps-tpl-dot.f { background:#22c55e; }
.ps-tpl-name { width:50px; font-weight:500; font-size:13px; color:#1a1a2e; }
.ps-tpl-times { display:flex; align-items:center; gap:6px; }
.ps-tpl-times input[type=time] { height:34px; border:1px solid #e5e7eb; border-radius:8px; font-size:13px; padding:0 8px; outline:none; width:110px; transition:var(--g-transition); }
.ps-tpl-times input[type=time]:focus { border-color:var(--primary); box-shadow:0 0 0 3px color-mix(in srgb, var(--primary) 12%, transparent); }
.ps-tpl-times .sep { color:#9ca3af; }
</style>
<div class="ps-page">
<div class="ps-toolbar">
<select class="g-select">
<option>老三家外卖(总店)</option>
<option>老三家外卖(朝阳店)</option>
<option>老三家外卖(海淀店)</option>
<option>老三家外卖(丰台店)</option>
<option>老三家外卖(通州店)</option>
</select>
<button class="g-btn" onclick="openCopyStoreModal('复制员工排班到其他门店')">复制到其他门店</button>
</div>
<div class="g-card">
<div class="g-card-hd"><span class="g-card-title">员工列表</span><button class="g-btn g-btn-primary" onclick="openStaffDrawer()">+ 添加员工</button></div>
<div class="ps-filters">
<input class="g-input" placeholder="搜索姓名/手机号" />
<select class="g-select"><option>全部角色</option><option>店长</option><option>收银员</option><option>配送员</option><option>厨师</option></select>
<select class="g-select"><option>全部状态</option><option>在职</option><option>休假</option><option>离职</option></select>
</div>
<table class="g-table"><thead><tr><th>员工信息</th><th>角色</th><th>邮箱</th><th>状态</th><th>权限</th><th>入职时间</th><th>操作</th></tr></thead>
<tbody><tr>
<td><div class="ps-staff-info"><div class="ps-avatar" style="background:#f56a00"></div><div><div class="ps-staff-name">张伟</div><div class="ps-staff-phone">138****8001</div></div></div></td>
<td><span class="g-tag g-tag-red">店长</span></td>
<td>zhangwei@example.com</td>
<td><span class="ps-status"><span class="ps-dot ps-dot-green"></span>在职</span></td>
<td><span class='ps-perm-pill'>全部权限</span></td>
<td>2024-01-15</td>
<td class="ps-actions"><a onclick="openStaffDrawer('edit','张伟')">编辑</a><a onclick="openSchedDrawer('张伟','店长')">排班</a><a class="ps-danger">删除</a></td>
</tr>
<tr>
<td><div class="ps-staff-info"><div class="ps-avatar" style="background:#7265e6"></div><div><div class="ps-staff-name">李娜</div><div class="ps-staff-phone">138****8002</div></div></div></td>
<td><span class="g-tag g-tag-blue">收银员</span></td>
<td>lina@example.com</td>
<td><span class="ps-status"><span class="ps-dot ps-dot-green"></span>在职</span></td>
<td><span class='ps-perm-pill'>收银</span><span class='ps-perm-pill'>退款</span></td>
<td>2024-03-20</td>
<td class="ps-actions"><a onclick="openStaffDrawer('edit','李娜')">编辑</a><a onclick="openSchedDrawer('李娜','收银员')">排班</a><a class="ps-danger">删除</a></td>
</tr>
<tr>
<td><div class="ps-staff-info"><div class="ps-avatar" style="background:#52c41a"></div><div><div class="ps-staff-name">王磊</div><div class="ps-staff-phone">138****8003</div></div></div></td>
<td><span class="g-tag g-tag-green">配送员</span></td>
<td><span style='color:#999'>-</span></td>
<td><span class="ps-status"><span class="ps-dot ps-dot-green"></span>在职</span></td>
<td><span class='ps-perm-pill'>配送管理</span></td>
<td>2024-06-01</td>
<td class="ps-actions"><a onclick="openStaffDrawer('edit','王磊')">编辑</a><a onclick="openSchedDrawer('王磊','配送员')">排班</a><a class="ps-danger">删除</a></td>
</tr>
<tr>
<td><div class="ps-staff-info"><div class="ps-avatar" style="background:#fa8c16"></div><div><div class="ps-staff-name">赵敏</div><div class="ps-staff-phone">138****8004</div></div></div></td>
<td><span class="g-tag g-tag-orange">厨师</span></td>
<td><span style='color:#999'>-</span></td>
<td><span class="ps-status"><span class="ps-dot ps-dot-green"></span>在职</span></td>
<td><span class='ps-perm-pill'>订单查看</span></td>
<td>2024-08-10</td>
<td class="ps-actions"><a onclick="openStaffDrawer('edit','赵敏')">编辑</a><a onclick="openSchedDrawer('赵敏','厨师')">排班</a><a class="ps-danger">删除</a></td>
</tr>
<tr>
<td><div class="ps-staff-info"><div class="ps-avatar" style="background:#1890ff"></div><div><div class="ps-staff-name">刘洋</div><div class="ps-staff-phone">138****8005</div></div></div></td>
<td><span class="g-tag g-tag-green">配送员</span></td>
<td>liuyang@example.com</td>
<td><span class="ps-status"><span class="ps-dot ps-dot-orange"></span>休假</span></td>
<td><span class='ps-perm-pill'>配送管理</span></td>
<td>2025-01-05</td>
<td class="ps-actions"><a onclick="openStaffDrawer('edit','刘洋')">编辑</a><a onclick="openSchedDrawer('刘洋','配送员')">排班</a><a class="ps-danger">删除</a></td>
</tr>
<tr style="opacity:0.5">
<td><div class="ps-staff-info"><div class="ps-avatar" style="background:#bbb"></div><div><div class="ps-staff-name">陈静</div><div class="ps-staff-phone">138****8006</div></div></div></td>
<td><span class="g-tag g-tag-blue">收银员</span></td>
<td><span style='color:#999'>-</span></td>
<td><span class="ps-status"><span class="ps-dot ps-dot-grey"></span>离职</span></td>
<td><span style='color:#999'>-</span></td>
<td>2024-11-20</td>
<td class="ps-actions"><a onclick="openStaffDrawer('edit','陈静')">编辑</a><a style="opacity:0.4;pointer-events:none;cursor:not-allowed">排班</a><a class="ps-danger">删除</a></td>
</tr>
</tbody></table>
<div class="ps-pagination"><span class="ps-page-info">共 6 条</span><button class="ps-page-btn">&lt;</button><button class="ps-page-btn active">1</button><button class="ps-page-btn">&gt;</button></div>
</div>
<div class="g-card">
<div class="g-card-hd"><span class="g-card-title">班次模板</span></div>
<div class="ps-tpl-row">
<span class="ps-tpl-dot m"></span><span class="ps-tpl-name">早班</span>
<div class="ps-tpl-times"><input type="time" id="psTplMs" value="09:00" onchange="syncShiftTpl()"><span class="sep">~</span><input type="time" id="psTplMe" value="14:00" onchange="syncShiftTpl()"></div>
</div>
<div class="ps-tpl-row">
<span class="ps-tpl-dot e"></span><span class="ps-tpl-name">晚班</span>
<div class="ps-tpl-times"><input type="time" id="psTplEs" value="14:00" onchange="syncShiftTpl()"><span class="sep">~</span><input type="time" id="psTplEe" value="21:00" onchange="syncShiftTpl()"></div>
</div>
<div class="ps-tpl-row">
<span class="ps-tpl-dot f"></span><span class="ps-tpl-name">全天</span>
<div class="ps-tpl-times"><input type="time" id="psTplFs" value="09:00" onchange="syncShiftTpl()"><span class="sep">~</span><input type="time" id="psTplFe" value="21:00" onchange="syncShiftTpl()"></div>
</div>
</div>
<div class="g-card"><button class="g-btn" onclick="openWeekEditor()">编辑排班</button></div>
<table class="ps-schedule"><thead><tr><th style="text-align:left;padding-left:12px">员工</th><th>周一</th><th>周二</th><th>周三</th><th>周四</th><th>周五</th><th>周六</th><th>周日</th></tr></thead>
<tbody><tr><td class="ps-sched-name">张伟 <span class="ps-sched-role">店长</span></td><td class="ps-shift-full">09:00-21:00<span class="ps-shift-label">全天</span></td><td class="ps-shift-full">09:00-21:00<span class="ps-shift-label">全天</span></td><td class="ps-shift-full">09:00-21:00<span class="ps-shift-label">全天</span></td><td class="ps-shift-full">09:00-21:00<span class="ps-shift-label">全天</span></td><td class="ps-shift-full">09:00-21:00<span class="ps-shift-label">全天</span></td><td class="ps-shift-morning">09:00-14:00<span class="ps-shift-label">早班</span></td><td class="ps-shift-off"></td></tr>
<tr><td class="ps-sched-name">李娜 <span class="ps-sched-role">收银员</span></td><td class="ps-shift-morning">09:00-14:00<span class="ps-shift-label">早班</span></td><td class="ps-shift-morning">09:00-14:00<span class="ps-shift-label">早班</span></td><td class="ps-shift-off"></td><td class="ps-shift-morning">09:00-14:00<span class="ps-shift-label">早班</span></td><td class="ps-shift-evening">14:00-21:00<span class="ps-shift-label">晚班</span></td><td class="ps-shift-full">09:00-21:00<span class="ps-shift-label">全天</span></td><td class="ps-shift-full">09:00-21:00<span class="ps-shift-label">全天</span></td></tr>
<tr><td class="ps-sched-name">王磊 <span class="ps-sched-role">配送员</span></td><td class="ps-shift-morning">09:00-14:00<span class="ps-shift-label">早班</span></td><td class="ps-shift-evening">14:00-21:00<span class="ps-shift-label">晚班</span></td><td class="ps-shift-morning">09:00-14:00<span class="ps-shift-label">早班</span></td><td class="ps-shift-evening">14:00-21:00<span class="ps-shift-label">晚班</span></td><td class="ps-shift-morning">09:00-14:00<span class="ps-shift-label">早班</span></td><td class="ps-shift-evening">14:00-21:00<span class="ps-shift-label">晚班</span></td><td class="ps-shift-off"></td></tr>
<tr><td class="ps-sched-name">赵敏 <span class="ps-sched-role">厨师</span></td><td class="ps-shift-full">09:00-21:00<span class="ps-shift-label">全天</span></td><td class="ps-shift-full">09:00-21:00<span class="ps-shift-label">全天</span></td><td class="ps-shift-evening">14:00-21:00<span class="ps-shift-label">晚班</span></td><td class="ps-shift-off"></td><td class="ps-shift-full">09:00-21:00<span class="ps-shift-label">全天</span></td><td class="ps-shift-full">09:00-21:00<span class="ps-shift-label">全天</span></td><td class="ps-shift-morning">09:00-14:00<span class="ps-shift-label">早班</span></td></tr>
<tr><td class="ps-sched-name">刘洋 <span class="ps-sched-role">配送员</span></td><td class="ps-shift-off"></td><td class="ps-shift-off"></td><td class="ps-shift-off"></td><td class="ps-shift-off"></td><td class="ps-shift-off"></td><td class="ps-shift-off"></td><td class="ps-shift-off"></td></tr>
</tbody></table>
</div>
</div>
<div class="g-drawer-mask" id="psDrawerMask" onclick="closeStaffDrawer()"></div>
<!-- 添加/编辑员工 -->
<div class="g-drawer" id="psStaffDrawer">
<div class="g-drawer-hd"><span class="g-drawer-title" id="psStaffTitle">添加员工</span><button class="g-drawer-close" onclick="closeStaffDrawer()"><i data-lucide="x" style="width:18px;height:18px;"></i></button></div>
<div class="g-drawer-bd">
<div class="g-form-group"><label class="g-form-label required">姓名</label><input type="text" class="g-input" id="psStaffName" placeholder="请输入员工姓名" /></div>
<div class="g-form-group"><label class="g-form-label required">手机号</label><input type="text" class="g-input" id="psStaffPhone" placeholder="请输入手机号" /></div>
<div class="g-form-group"><label class="g-form-label">邮箱</label><input type="text" class="g-input" id="psStaffEmail" placeholder="可选" /><div class="g-hint">用于接收系统通知</div></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
<div class="g-form-group"><label class="g-form-label required">角色</label><select class="g-select" id="psStaffRole"><option>店长</option><option>收银员</option><option>配送员</option><option>厨师</option></select></div>
<div class="g-form-group"><label class="g-form-label">状态</label><select class="g-select" id="psStaffStatus"><option>在职</option><option>休假</option><option>离职</option></select></div>
</div>
<div class="g-form-group"><label class="g-form-label">权限</label>
<div class="ps-perm-grid">
<label class="ps-perm-check all"><input type="checkbox" id="psPermAll" onchange="toggleAllPerms(this)"> 全部权限</label>
<label class="ps-perm-check"><input type="checkbox" class="ps-perm-cb"> 收银</label>
<label class="ps-perm-check"><input type="checkbox" class="ps-perm-cb"> 退款</label>
<label class="ps-perm-check"><input type="checkbox" class="ps-perm-cb"> 配送管理</label>
<label class="ps-perm-check"><input type="checkbox" class="ps-perm-cb"> 订单查看</label>
<label class="ps-perm-check"><input type="checkbox" class="ps-perm-cb"> 库存管理</label>
<label class="ps-perm-check"><input type="checkbox" class="ps-perm-cb"> 数据统计</label>
</div>
</div>
</div>
<div class="g-drawer-ft"><button class="g-btn" onclick="closeStaffDrawer()">取消</button><button class="g-btn g-btn-primary" id="psStaffSubmit" onclick="closeStaffDrawer()">确认添加</button></div>
</div>
<!-- 编辑排班 (个人) -->
<div class="g-drawer" id="psSchedDrawer" style="width:560px;">
<div class="g-drawer-hd"><span class="g-drawer-title" id="psSchedTitle">编辑排班 - 张伟</span><button class="g-drawer-close" onclick="closeStaffDrawer()"><i data-lucide="x" style="width:18px;height:18px;"></i></button></div>
<div class="g-drawer-bd">
<div style="background:#f0f5ff;border-radius:6px;padding:10px 14px;margin-bottom:16px;font-size:12px;color:#597ef7;">点击班次类型自动填充时间,也可手动修改。选择"休息"则该日不排班。</div>
<div id="psSchedRows"></div>
</div>
<div class="g-drawer-ft"><button class="g-btn" onclick="closeStaffDrawer()">取消</button><button class="g-btn g-btn-primary" onclick="closeStaffDrawer()">保存排班</button></div>
</div>
<!-- 编辑排班表 (全员周视图) -->
<div class="g-drawer" id="psWeekDrawer" style="width:640px;">
<div class="g-drawer-hd"><span class="g-drawer-title">编辑本周排班表</span><button class="g-drawer-close" onclick="closeStaffDrawer()"><i data-lucide="x" style="width:18px;height:18px;"></i></button></div>
<div class="g-drawer-bd">
<div class="ps-week-legend" id="psWeekLegend">
<span><i class="lg-m"></i><span class="lg-m-t">早班 09:00-14:00</span></span>
<span><i class="lg-e"></i><span class="lg-e-t">晚班 14:00-21:00</span></span>
<span><i class="lg-f"></i><span class="lg-f-t">全天 09:00-21:00</span></span>
<span><i class="lg-o"></i>休息</span>
<span style="margin-left:auto;color:#999;font-size:11px;">点击单元格切换班次</span>
</div>
<table class="ps-week-table">
<thead><tr><th style="text-align:left;padding-left:10px;width:90px;">员工</th><th>周一</th><th>周二</th><th>周三</th><th>周四</th><th>周五</th><th>周六</th><th>周日</th></tr></thead>
<tbody id="psWeekBody"></tbody>
</table>
</div>
<div class="g-drawer-ft"><button class="g-btn" onclick="closeStaffDrawer()">取消</button><button class="g-btn g-btn-primary" onclick="closeStaffDrawer()">保存排班</button></div>
</div>
<script>
var _dayNames = ['周一','周二','周三','周四','周五','周六','周日'];
var _defaultShifts = {
morning: ['09:00','14:00'],
evening: ['14:00','21:00'],
full: ['09:00','21:00'],
off: ['','']
};
var _shiftCycle = ['morning','evening','full','off'];
var _shiftLabels = {morning:'早班',evening:'晚班',full:'全天',off:'休息'};
function getShiftTimes() {
return {
morning: (document.getElementById('psTplMs').value||'09:00').slice(0,5) + '-' + (document.getElementById('psTplMe').value||'14:00').slice(0,5),
evening: (document.getElementById('psTplEs').value||'14:00').slice(0,5) + '-' + (document.getElementById('psTplEe').value||'21:00').slice(0,5),
full: (document.getElementById('psTplFs').value||'09:00').slice(0,5) + '-' + (document.getElementById('psTplFe').value||'21:00').slice(0,5),
off: ''
};
}
function syncShiftTpl() {
_defaultShifts.morning = [document.getElementById('psTplMs').value, document.getElementById('psTplMe').value];
_defaultShifts.evening = [document.getElementById('psTplEs').value, document.getElementById('psTplEe').value];
_defaultShifts.full = [document.getElementById('psTplFs').value, document.getElementById('psTplFe').value];
var t = getShiftTimes();
var lg = document.getElementById('psWeekLegend');
if (lg) {
lg.querySelector('.lg-m-t').textContent = '早班 ' + t.morning;
lg.querySelector('.lg-e-t').textContent = '晚班 ' + t.evening;
lg.querySelector('.lg-f-t').textContent = '全天 ' + t.full;
}
}
function openStaffDrawer(mode, name) {
var t = document.getElementById('psStaffTitle');
var s = document.getElementById('psStaffSubmit');
if (mode === 'edit') {
t.textContent = '编辑员工 - ' + name;
s.textContent = '保存修改';
document.getElementById('psStaffName').value = name || '';
document.getElementById('psStaffPhone').value = '138****8001';
document.getElementById('psStaffEmail').value = '';
} else {
t.textContent = '添加员工';
s.textContent = '确认添加';
document.getElementById('psStaffName').value = '';
document.getElementById('psStaffPhone').value = '';
document.getElementById('psStaffEmail').value = '';
document.querySelectorAll('.ps-perm-cb').forEach(function(c){c.checked=false;});
document.getElementById('psPermAll').checked = false;
}
document.getElementById('psDrawerMask').classList.add('open');
document.getElementById('psStaffDrawer').classList.add('open');
}
function openSchedDrawer(name, role) {
document.getElementById('psSchedTitle').textContent = '编辑排班 - ' + name + ' (' + role + ')';
var container = document.getElementById('psSchedRows');
var html = '';
for (var i = 0; i < 7; i++) {
var d = _dayNames[i];
var def = (i < 5) ? 'full' : (i === 5 ? 'morning' : 'off');
var st = _defaultShifts[def][0];
var et = _defaultShifts[def][1];
var hide = def === 'off' ? 'style="visibility:hidden"' : '';
html += '<div class="ps-sched-row">';
html += '<span class="ps-sched-day">' + d + '</span>';
html += '<div class="ps-shift-pills">';
['morning','evening','full','off'].forEach(function(typ){
var lb = {morning:'早班',evening:'晚班',full:'全天',off:'休息'}[typ];
var ac = (typ === def) ? ' active' : '';
html += '<span class="ps-shift-pill s-' + typ + ac + '" onclick="pickShift(this,\'' + typ + '\',' + i + ')">' + lb + '</span>';
});
html += '</div>';
html += '<div class="ps-sched-times" id="psTime' + i + '" ' + hide + '>';
html += '<input type="time" value="' + st + '" style="width:100px" /> <span>~</span> <input type="time" value="' + et + '" style="width:100px" />';
html += '</div></div>';
}
container.innerHTML = html;
document.getElementById('psDrawerMask').classList.add('open');
document.getElementById('psSchedDrawer').classList.add('open');
}
function closeStaffDrawer() {
document.getElementById('psDrawerMask').classList.remove('open');
document.getElementById('psStaffDrawer').classList.remove('open');
document.getElementById('psSchedDrawer').classList.remove('open');
document.getElementById('psWeekDrawer').classList.remove('open');
}
function pickShift(el, typ, dayIdx) {
var pills = el.parentElement.querySelectorAll('.ps-shift-pill');
pills.forEach(function(p){ p.classList.remove('active'); });
el.classList.add('active');
var timeDiv = document.getElementById('psTime' + dayIdx);
if (typ === 'off') {
timeDiv.style.visibility = 'hidden';
} else {
timeDiv.style.visibility = 'visible';
var inputs = timeDiv.querySelectorAll('input[type=time]');
inputs[0].value = _defaultShifts[typ][0];
inputs[1].value = _defaultShifts[typ][1];
}
}
function toggleAllPerms(el) {
var checked = el.checked;
document.querySelectorAll('.ps-perm-cb').forEach(function(c){ c.checked = checked; });
}
var _weekData = [
{name:'张伟',role:'店长',shifts:['full','full','full','full','full','morning','off']},
{name:'李娜',role:'收银员',shifts:['morning','morning','off','morning','evening','full','full']},
{name:'王磊',role:'配送员',shifts:['morning','evening','morning','evening','morning','evening','off']},
{name:'赵敏',role:'厨师',shifts:['full','full','evening','off','full','full','morning']},
{name:'刘洋',role:'配送员',shifts:['off','off','off','off','off','off','off']}
];
function openWeekEditor() {
syncShiftTpl();
var t = getShiftTimes();
var body = document.getElementById('psWeekBody');
var html = '';
for (var r = 0; r < _weekData.length; r++) {
var s = _weekData[r];
html += '<tr><td class="ps-wk-name">' + s.name + '<br><span style="font-size:11px;color:#999;font-weight:normal">' + s.role + '</span></td>';
for (var d = 0; d < 7; d++) {
var sh = s.shifts[d];
var tm = t[sh];
html += '<td><div class="ps-wk-cell" data-shift="' + sh + '" data-r="' + r + '" data-d="' + d + '" onclick="cycleShift(this)">';
html += '<span>' + _shiftLabels[sh] + '</span>';
if (tm) html += '<span class="wk-time">' + tm + '</span>';
html += '</div></td>';
}
html += '</tr>';
}
body.innerHTML = html;
document.getElementById('psDrawerMask').classList.add('open');
document.getElementById('psWeekDrawer').classList.add('open');
}
function cycleShift(el) {
var cur = el.getAttribute('data-shift');
var r = parseInt(el.getAttribute('data-r'));
var d = parseInt(el.getAttribute('data-d'));
var idx = (_shiftCycle.indexOf(cur) + 1) % _shiftCycle.length;
var next = _shiftCycle[idx];
el.setAttribute('data-shift', next);
var t = getShiftTimes();
var tm = t[next];
el.innerHTML = '<span>' + _shiftLabels[next] + '</span>' + (tm ? '<span class="wk-time">' + tm + '</span>' : '');
_weekData[r].shifts[d] = next;
}
if (typeof lucide !== 'undefined') lucide.createIcons();
</script>