Files

1501 lines
79 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TakeoutSaaS - 租户管理系统</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/lucide@latest"></script>
<style>
:root {
--sidebar-width: 224px;
--sidebar-collapsed-width: 64px;
--header-height: 50px;
--tabbar-height: 38px;
--primary: hsl(212, 100%, 45%);
--primary-light: hsl(212, 100%, 95%);
--sidebar-bg: #001529;
--sidebar-bg-light: #000c17;
--sidebar-text: rgba(255,255,255,0.65);
--sidebar-text-active: #ffffff;
--content-bg: #f0f2f5;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; overflow: hidden; height: 100vh; }
/* Sidebar */
.sidebar {
width: var(--sidebar-width);
min-width: var(--sidebar-width);
background: var(--sidebar-bg);
height: 100vh;
transition: all 0.3s cubic-bezier(0.2, 0, 0, 1);
display: flex;
flex-direction: column;
overflow: hidden;
z-index: 100;
}
.sidebar.collapsed {
width: var(--sidebar-collapsed-width);
min-width: var(--sidebar-collapsed-width);
}
.sidebar-logo {
height: var(--header-height);
display: flex;
align-items: center;
padding: 0 16px;
gap: 10px;
border-bottom: 1px solid rgba(255,255,255,0.08);
flex-shrink: 0;
}
.sidebar-logo .logo-icon {
width: 32px; height: 32px;
background: var(--primary);
border-radius: 6px;
display: flex; align-items: center; justify-content: center;
flex-shrink: 0;
}
.sidebar-logo .logo-text {
color: #fff; font-size: 16px; font-weight: 600;
white-space: nowrap; overflow: hidden;
transition: opacity 0.2s;
}
.sidebar.collapsed .logo-text { opacity: 0; width: 0; }
.sidebar.collapsed .sidebar-logo { justify-content: center; padding: 0; }
/* Menu */
.sidebar-menu {
flex: 1; overflow-y: auto; overflow-x: hidden;
padding: 4px 0;
}
.sidebar-menu::-webkit-scrollbar { width: 4px; }
.sidebar-menu::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.15); border-radius: 2px; }
.menu-item {
display: flex; align-items: center;
padding: 0 16px; height: 40px;
color: var(--sidebar-text);
cursor: pointer; transition: all 0.2s;
white-space: nowrap; position: relative;
gap: 10px; font-size: 14px;
text-decoration: none;
}
.menu-item:hover { color: var(--sidebar-text-active); }
.menu-item.active {
color: var(--sidebar-text-active);
background: var(--primary);
border-radius: 0;
}
.menu-item .menu-icon { width: 16px; height: 16px; flex-shrink: 0; }
.menu-item .menu-label { overflow: hidden; transition: opacity 0.2s; }
.sidebar.collapsed .menu-label { opacity: 0; width: 0; }
.sidebar.collapsed .menu-item { justify-content: center; padding: 0; }
.sidebar.collapsed .menu-arrow { display: none; }
.menu-group-title {
padding: 8px 16px 4px;
font-size: 11px; color: rgba(255,255,255,0.3);
text-transform: uppercase; letter-spacing: 0.5px;
white-space: nowrap; overflow: hidden;
}
.sidebar.collapsed .menu-group-title { text-align: center; padding: 8px 4px 4px; font-size: 0; }
.sidebar.collapsed .menu-group-title::after { content: '···'; font-size: 11px; }
.menu-arrow {
margin-left: auto; width: 14px; height: 14px;
transition: transform 0.2s; flex-shrink: 0;
}
.menu-arrow.expanded { transform: rotate(90deg); }
.menu-sub {
max-height: 0; overflow: hidden;
transition: max-height 0.3s cubic-bezier(0.2, 0, 0, 1);
background: var(--sidebar-bg-light);
}
.menu-sub.open { max-height: 500px; }
.menu-sub .menu-item { padding-left: 48px; height: 36px; font-size: 13px; }
.sidebar.collapsed .menu-sub .menu-item { padding-left: 0; }
/* Header */
.header {
height: var(--header-height);
background: #fff;
border-bottom: 1px solid #f0f0f0;
display: flex; align-items: center;
padding: 0 16px;
flex-shrink: 0;
}
.header-left { display: flex; align-items: center; gap: 8px; }
.header-right { margin-left: auto; display: flex; align-items: center; gap: 4px; }
.header-btn {
width: 36px; height: 36px;
display: flex; align-items: center; justify-content: center;
border-radius: 6px; cursor: pointer;
color: #666; transition: all 0.2s; border: none; background: none;
}
.header-btn:hover { background: #f5f5f5; color: #333; }
.breadcrumb { display: flex; align-items: center; gap: 6px; font-size: 13px; color: #999; margin-left: 4px; }
.breadcrumb a { color: #666; text-decoration: none; }
.breadcrumb a:hover { color: var(--primary); }
.breadcrumb .current { color: #333; }
.breadcrumb .sep { color: #d9d9d9; }
.avatar {
width: 28px; height: 28px; border-radius: 50%;
background: var(--primary); color: #fff;
display: flex; align-items: center; justify-content: center;
font-size: 12px; font-weight: 600; cursor: pointer;
}
.user-dropdown-trigger {
display: flex; align-items: center; gap: 6px;
padding: 4px 8px; border-radius: 6px; cursor: pointer; transition: all 0.2s;
}
.user-dropdown-trigger:hover { background: #f5f5f5; }
.user-name { font-size: 13px; color: #333; }
/* Tabbar */
.tabbar {
height: var(--tabbar-height);
background: #fff;
border-bottom: 1px solid #f0f0f0;
display: flex; align-items: flex-end;
padding: 0 12px;
flex-shrink: 0;
overflow-x: auto;
gap: 2px;
}
.tabbar::-webkit-scrollbar { height: 0; }
.tab {
display: flex; align-items: center; gap: 6px;
padding: 6px 12px;
font-size: 12px; color: #666;
cursor: pointer; white-space: nowrap;
border-radius: 6px 6px 0 0;
transition: all 0.2s;
background: transparent;
border: 1px solid transparent;
border-bottom: none;
position: relative;
height: 30px;
}
.tab:hover { color: var(--primary); }
.tab.active {
color: var(--primary);
background: var(--content-bg);
border-color: #f0f0f0;
}
.tab .tab-close {
width: 14px; height: 14px;
display: flex; align-items: center; justify-content: center;
border-radius: 50%; font-size: 10px;
opacity: 0; transition: all 0.15s;
}
.tab:hover .tab-close { opacity: 1; }
.tab .tab-close:hover { background: rgba(0,0,0,0.1); }
.tab .tab-pin { width: 12px; height: 12px; color: var(--primary); }
/* Content */
.content-area {
flex: 1; background: var(--content-bg);
padding: 16px; overflow-y: auto;
}
/* Dropdown */
.dropdown {
position: absolute; top: 100%; right: 0;
background: #fff; border-radius: 8px;
box-shadow: 0 6px 16px rgba(0,0,0,0.12);
padding: 4px; min-width: 160px;
opacity: 0; visibility: hidden;
transform: translateY(4px);
transition: all 0.2s; z-index: 200;
}
.dropdown.show { opacity: 1; visibility: visible; transform: translateY(0); }
.dropdown-item {
display: flex; align-items: center; gap: 8px;
padding: 8px 12px; font-size: 13px; color: #333;
border-radius: 4px; cursor: pointer; transition: all 0.15s;
}
.dropdown-item:hover { background: #f5f5f5; }
.dropdown-item.danger { color: #ff4d4f; }
.dropdown-divider { height: 1px; background: #f0f0f0; margin: 4px 0; }
/* Notification badge */
.badge {
position: relative;
}
.badge::after {
content: ''; position: absolute; top: 6px; right: 6px;
width: 8px; height: 8px; background: #ff4d4f;
border-radius: 50%; border: 2px solid #fff;
}
/* Drawer */
/* Copy Store Modal */
.csm-mask { position:fixed; inset:0; background:rgba(0,0,0,0.4); z-index:2000; opacity:0; pointer-events:none; transition:opacity 0.25s; display:flex; align-items:center; justify-content:center; }
.csm-mask.open { opacity:1; pointer-events:auto; }
.csm-modal { background:#fff; border-radius:10px; width:460px; max-height:80vh; display:flex; flex-direction:column; box-shadow:0 8px 30px rgba(0,0,0,0.12); transform:scale(0.92); transition:transform 0.25s; }
.csm-mask.open .csm-modal { transform:scale(1); }
.csm-header { padding:16px 20px; border-bottom:1px solid #f0f0f0; display:flex; align-items:center; justify-content:space-between; }
.csm-header h3 { font-size:15px; font-weight:600; color:#333; margin:0; }
.csm-close { width:28px; height:28px; border:none; background:none; cursor:pointer; color:#999; display:flex; align-items:center; justify-content:center; border-radius:6px; font-size:16px; }
.csm-close:hover { background:#f5f5f5; color:#333; }
.csm-body { padding:16px 20px; overflow-y:auto; flex:1; }
.csm-warn { display:flex; align-items:center; gap:6px; padding:8px 12px; background:#fff7e6; border:1px solid #ffe58f; border-radius:6px; font-size:12px; color:#d46b08; margin-bottom:14px; }
.csm-all { display:flex; align-items:center; gap:8px; padding-bottom:10px; border-bottom:1px solid #f0f0f0; margin-bottom:6px; font-size:13px; color:#333; cursor:pointer; }
.csm-item { display:flex; align-items:center; gap:10px; padding:10px 0; border-bottom:1px solid #f5f5f5; cursor:pointer; }
.csm-item:last-child { border-bottom:none; }
.csm-item:hover { background:#fafafa; margin:0 -20px; padding:10px 20px; }
.csm-cb { width:16px; height:16px; border-radius:4px; border:1px solid #d9d9d9; flex-shrink:0; display:flex; align-items:center; justify-content:center; transition:all 0.15s; }
.csm-cb.checked { background:var(--primary); border-color:var(--primary); }
.csm-cb.checked::after { content:'✓'; color:#fff; font-size:11px; }
.csm-store-name { font-size:13px; font-weight:500; color:#333; }
.csm-store-addr { font-size:11px; color:#999; margin-top:2px; }
.csm-footer { padding:12px 20px; border-top:1px solid #f0f0f0; display:flex; justify-content:flex-end; gap:8px; }
.csm-btn { height:32px; padding:0 16px; border-radius:6px; font-size:13px; cursor:pointer; border:1px solid #d9d9d9; background:#fff; color:#333; transition:all 0.2s; }
.csm-btn:hover { border-color:var(--primary); color:var(--primary); }
.csm-btn-primary { background:var(--primary); color:#fff; border-color:var(--primary); }
.csm-btn-primary:hover { opacity:0.85; color:#fff; }
.drawer-mask { position:fixed; inset:0; background:rgba(0,0,0,0.45); z-index:1000; opacity:0; visibility:hidden; transition:all 0.3s; }
.drawer-mask.open { opacity:1; visibility:visible; }
.drawer-panel { position:fixed; top:0; right:0; bottom:0; width:620px; background:#fff; z-index:1001; transform:translateX(100%); transition:transform 0.3s cubic-bezier(0.2,0,0,1); display:flex; flex-direction:column; box-shadow:-6px 0 16px rgba(0,0,0,0.08); }
.drawer-panel.open { transform:translateX(0); }
.drawer-header { height:50px; padding:0 20px; display:flex; align-items:center; justify-content:space-between; border-bottom:1px solid #f0f0f0; flex-shrink:0; }
.drawer-header .title { font-size:15px; font-weight:600; color:#333; }
.drawer-close { width:32px; height:32px; display:flex; align-items:center; justify-content:center; border:none; background:none; cursor:pointer; border-radius:6px; color:#999; }
.drawer-close:hover { background:#f5f5f5; color:#333; }
.drawer-body { flex:1; overflow-y:auto; padding:20px; }
.drawer-footer { padding:12px 20px; border-top:1px solid #f0f0f0; display:flex; justify-content:flex-end; gap:10px; flex-shrink:0; }
.df-section { margin-bottom:20px; }
.df-section-title { font-size:13px; font-weight:600; color:#333; margin-bottom:12px; padding-bottom:8px; border-bottom:1px solid #f5f5f5; }
.df-grid { display:grid; grid-template-columns:1fr 1fr; gap:14px 16px; }
.df-item { display:flex; flex-direction:column; gap:5px; }
.df-item.full { grid-column:1/-1; }
.df-label { font-size:12px; color:#666; }
.df-label .req { color:#ff4d4f; margin-right:2px; }
.df-input, .df-select { height:32px; padding:0 10px; border:1px solid #d9d9d9; border-radius:6px; font-size:13px; outline:none; transition:border-color 0.2s; background:#fff; font-family:inherit; width:100%; box-sizing:border-box; }
.df-input:focus, .df-select:focus { border-color:var(--primary); }
.df-hint { font-size:11px; color:#999; }
.df-radio-group { display:flex; border:1px solid #d9d9d9; border-radius:6px; overflow:hidden; }
.df-radio-btn { flex:1; height:32px; display:flex; align-items:center; justify-content:center; font-size:13px; cursor:pointer; background:#fff; color:#666; border:none; border-right:1px solid #d9d9d9; transition:all 0.2s; }
.df-radio-btn:last-child { border-right:none; }
.df-radio-btn.active { background:var(--primary); color:#fff; }
.df-delivery-panel { margin-top:12px; padding:14px; background:#fafafa; border-radius:6px; border:1px solid #f0f0f0; }
.df-map-placeholder { height:180px; background:#e8e8e8; border-radius:6px; display:flex; align-items:center; justify-content:center; color:#999; font-size:13px; margin-top:8px; border:1px dashed #d9d9d9; }
.df-switch-group { display:flex; gap:20px; flex-wrap:wrap; }
.df-switch-item { display:flex; align-items:center; gap:8px; font-size:13px; color:#333; }
.df-switch { width:36px; height:20px; border-radius:10px; background:#d9d9d9; position:relative; cursor:pointer; transition:background 0.2s; }
.df-switch.on { background:var(--primary); }
.df-switch::after { content:''; position:absolute; top:2px; left:2px; width:16px; height:16px; border-radius:50%; background:#fff; transition:left 0.2s; box-shadow:0 1px 3px rgba(0,0,0,0.15); }
.df-switch.on::after { left:18px; }
.df-upload-box { width:90px; height:90px; border:1px dashed #d9d9d9; border-radius:6px; display:flex; flex-direction:column; align-items:center; justify-content:center; cursor:pointer; color:#999; font-size:11px; gap:4px; }
.df-upload-box:hover { border-color:var(--primary); color:var(--primary); }
.df-addr-row { display:grid; grid-template-columns:1fr 1fr 1fr; gap:8px; }
.df-coord-row { display:grid; grid-template-columns:1fr 1fr auto; gap:8px; align-items:end; }
.df-btn { height:32px; padding:0 16px; border-radius:6px; font-size:13px; cursor:pointer; border:1px solid #d9d9d9; background:#fff; color:#333; display:inline-flex; align-items:center; gap:6px; }
.df-btn:hover { border-color:var(--primary); color:var(--primary); }
.df-btn-primary { background:var(--primary); color:#fff; border-color:var(--primary); }
.df-btn-primary:hover { opacity:0.85; color:#fff; }
/* ================================================================
GLOBAL COMPONENT DESIGN SYSTEM (.g- prefix)
All page fragments inherit these styles automatically.
================================================================ */
/* --- Tokens --- */
:root {
--g-radius: 8px;
--g-radius-sm: 6px;
--g-radius-lg: 12px;
--g-shadow-sm: 0 1px 2px rgba(0,0,0,.04);
--g-shadow: 0 1px 3px rgba(0,0,0,.06), 0 1px 2px rgba(0,0,0,.04);
--g-shadow-md: 0 4px 12px rgba(0,0,0,.07), 0 1px 3px rgba(0,0,0,.04);
--g-shadow-lg: 0 8px 24px rgba(0,0,0,.08), 0 2px 6px rgba(0,0,0,.04);
--g-border: #f0f0f0;
--g-border-hover: #d9d9d9;
--g-bg-subtle: #fafafa;
--g-bg-tint: color-mix(in srgb, var(--primary) 4%, #fff);
--g-text: #1f1f1f;
--g-text-secondary: #666;
--g-text-muted: #999;
--g-text-placeholder: #bbb;
--g-danger: #f5222d;
--g-success: #52c41a;
--g-warning: #fa8c16;
--g-transition: 200ms cubic-bezier(.4,0,.2,1);
}
/* --- Card --- */
.g-card {
background: #fff; border-radius: var(--g-radius); border: 1px solid var(--g-border);
padding: 20px 24px; transition: box-shadow var(--g-transition);
}
.g-card:hover { box-shadow: var(--g-shadow); }
.g-card-title {
font-size: 15px; font-weight: 600; color: var(--g-text); margin-bottom: 16px;
padding-bottom: 12px; border-bottom: 1px solid #f5f5f5;
}
.g-card-hd {
display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px;
}
.g-card-hd-title { font-size: 15px; font-weight: 600; color: var(--g-text); }
/* --- Button --- */
.g-btn {
height: 32px; padding: 0 16px; border-radius: var(--g-radius-sm); font-size: 13px;
cursor: pointer; border: 1px solid var(--g-border-hover); background: #fff; color: var(--g-text);
display: inline-flex; align-items: center; justify-content: center; gap: 5px;
transition: all var(--g-transition); white-space: nowrap; user-select: none;
box-shadow: var(--g-shadow-sm);
}
.g-btn:hover { border-color: var(--primary); color: var(--primary); box-shadow: var(--g-shadow); }
.g-btn:active { transform: translateY(1px); }
.g-btn:focus-visible { outline: none; box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 20%, transparent); }
.g-btn-primary { background: var(--primary); color: #fff; border-color: var(--primary); }
.g-btn-primary:hover { opacity: .88; color: #fff; }
.g-btn-danger { color: var(--g-danger); border-color: var(--g-danger); }
.g-btn-danger:hover { background: var(--g-danger); color: #fff; }
.g-btn-sm { height: 28px; padding: 0 10px; font-size: 12px; }
.g-btn-lg { height: 36px; padding: 0 20px; font-size: 14px; }
/* --- Form --- */
.g-form-group { margin-bottom: 16px; }
.g-form-label {
display: block; font-size: 13px; color: var(--g-text); margin-bottom: 6px; font-weight: 500;
}
.g-form-label.required::before { content: '*'; color: var(--g-danger); margin-right: 3px; }
.g-form-row { display: flex; align-items: flex-start; margin-bottom: 14px; gap: 12px; }
.g-form-row-label { width: 80px; flex-shrink: 0; font-size: 13px; color: var(--g-text-secondary); line-height: 32px; text-align: right; }
.g-form-row-label.required::before { content: '*'; color: var(--g-danger); margin-right: 2px; }
.g-form-ctrl { flex: 1; min-width: 0; }
.g-input, .g-select {
width: 100%; height: 34px; padding: 0 11px; border: 1px solid #d9d9d9; border-radius: var(--g-radius-sm);
font-size: 13px; outline: none; background: #fff; color: var(--g-text);
box-sizing: border-box; transition: all var(--g-transition);
}
.g-input:focus, .g-select:focus {
border-color: var(--primary); box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 10%, transparent);
}
.g-input::placeholder { color: var(--g-text-placeholder); }
.g-select { cursor: pointer; }
.g-textarea {
width: 100%; padding: 8px 11px; border: 1px solid #d9d9d9; border-radius: var(--g-radius-sm);
font-size: 13px; outline: none; background: #fff; color: var(--g-text);
box-sizing: border-box; resize: vertical; font-family: inherit; transition: all var(--g-transition);
}
.g-textarea:focus {
border-color: var(--primary); box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 10%, transparent);
}
.g-hint { font-size: 11px; color: var(--g-text-muted); margin-top: 4px; }
.g-form-inline { display: flex; gap: 8px; align-items: center; }
.g-form-inline .g-input { width: auto; flex: 1; }
.g-form-unit { font-size: 12px; color: var(--g-text-muted); white-space: nowrap; }
/* --- Drawer --- */
.g-drawer-mask {
position: fixed; inset: 0; background: rgba(0,0,0,.35); z-index: 1000;
opacity: 0; pointer-events: none; transition: opacity .3s;
}
.g-drawer-mask.open { opacity: 1; pointer-events: auto; }
.g-drawer {
position: fixed; top: 0; right: 0; height: 100%; background: #fff; z-index: 1001;
transform: translateX(100%); transition: transform .3s cubic-bezier(.4,0,.2,1);
display: flex; flex-direction: column; box-shadow: var(--g-shadow-lg);
}
.g-drawer.open { transform: translateX(0); }
.g-drawer-hd {
height: 56px; display: flex; align-items: center; padding: 0 24px;
border-bottom: 1px solid var(--g-border); flex-shrink: 0;
}
.g-drawer-title { font-size: 16px; font-weight: 600; color: var(--g-text); flex: 1; }
.g-drawer-close {
width: 32px; height: 32px; border: none; background: none; font-size: 20px;
cursor: pointer; color: #999; display: flex; align-items: center; justify-content: center;
border-radius: var(--g-radius-sm); transition: all var(--g-transition);
}
.g-drawer-close:hover { background: #f5f5f5; color: var(--g-text); }
.g-drawer-bd { flex: 1; overflow-y: auto; padding: 20px 24px; }
.g-drawer-ft {
padding: 14px 24px; border-top: 1px solid var(--g-border);
display: flex; justify-content: flex-end; gap: 10px; flex-shrink: 0;
}
/* --- Toggle --- */
.g-toggle {
position: relative; width: 40px; height: 22px; border-radius: 11px;
background: #ccc; cursor: pointer; transition: background var(--g-transition); flex-shrink: 0;
}
.g-toggle::after {
content: ''; position: absolute; top: 2px; left: 2px;
width: 18px; height: 18px; border-radius: 50%; background: #fff;
transition: transform var(--g-transition); box-shadow: 0 1px 3px rgba(0,0,0,.15);
}
.g-toggle.on { background: var(--primary); }
.g-toggle.on::after { transform: translateX(18px); }
.g-toggle-wrap { display: flex; align-items: center; gap: 10px; }
.g-toggle-label { font-size: 13px; color: var(--g-text); }
/* Also support <label><input type=checkbox><span class=g-toggle-sl></span></label> pattern */
.g-toggle-input { position: relative; width: 40px; height: 22px; flex-shrink: 0; }
.g-toggle-input input { opacity: 0; width: 0; height: 0; }
.g-toggle-sl {
position: absolute; inset: 0; background: #ccc; border-radius: 11px;
cursor: pointer; transition: background var(--g-transition);
}
.g-toggle-sl::before {
content: ''; position: absolute; width: 18px; height: 18px; border-radius: 50%;
background: #fff; left: 2px; top: 2px; transition: transform var(--g-transition);
box-shadow: 0 1px 3px rgba(0,0,0,.15);
}
.g-toggle-input input:checked + .g-toggle-sl { background: var(--primary); }
.g-toggle-input input:checked + .g-toggle-sl::before { transform: translateX(18px); }
/* --- Pill / Chip --- */
.g-pill {
display: inline-flex; align-items: center; padding: 5px 14px; border-radius: 16px;
border: 1px solid #d9d9d9; font-size: 12px; cursor: pointer;
transition: all var(--g-transition); user-select: none; background: #fff;
}
.g-pill:hover { border-color: var(--primary); }
.g-pill.checked {
background: color-mix(in srgb, var(--primary) 10%, transparent);
border-color: var(--primary); color: var(--primary); font-weight: 500;
}
/* --- Tag / Badge --- */
.g-tag {
display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 12px; font-weight: 500;
}
.g-tag-blue { background: #e6f7ff; color: #1890ff; }
.g-tag-green { background: #f6ffed; color: #52c41a; }
.g-tag-orange { background: #fff7e6; color: #fa8c16; }
.g-tag-red { background: #fff1f0; color: #f5222d; }
.g-tag-purple { background: #f9f0ff; color: #722ed1; }
.g-tag-gray { background: #f5f5f5; color: #999; }
.g-tag-solid { color: #fff; }
/* --- Segmented Control --- */
.g-seg {
display: flex; border-radius: var(--g-radius); overflow: hidden;
border: 1px solid #e8e8e8; background: #f5f5f5;
}
.g-seg-item {
flex: 1; text-align: center; padding: 8px 0; font-size: 13px; cursor: pointer;
transition: all var(--g-transition); color: var(--g-text-muted); user-select: none;
border-right: 1px solid #e8e8e8; position: relative;
}
.g-seg-item:last-child { border-right: none; }
.g-seg-item:hover { color: var(--g-text-secondary); background: #f0f0f0; }
.g-seg-item.active {
background: #fff; color: var(--primary); font-weight: 600;
box-shadow: 0 1px 4px rgba(0,0,0,.06);
}
/* --- Upload Zone --- */
.g-upload-zone {
border: 2px dashed #d9d9d9; border-radius: var(--g-radius-lg); padding: 24px;
text-align: center; cursor: pointer; transition: all var(--g-transition);
color: var(--g-text-muted); font-size: 13px; background: var(--g-bg-subtle);
}
.g-upload-zone:hover {
border-color: var(--primary); color: var(--primary);
background: var(--g-bg-tint); box-shadow: var(--g-shadow);
}
.g-upload-zone svg { display: block; margin: 0 auto 8px; }
/* --- Action Link --- */
.g-action {
font-size: 13px; color: var(--primary); cursor: pointer; text-decoration: none;
transition: opacity var(--g-transition);
}
.g-action:hover { opacity: .75; }
.g-action-danger { color: var(--g-danger); }
/* --- Section Label (small caps) --- */
.g-section-label {
font-size: 11px; color: var(--g-text-muted); font-weight: 600;
letter-spacing: 1px; text-transform: uppercase; margin-bottom: 10px;
}
/* --- Divider --- */
.g-divider {
height: 1px; background: linear-gradient(90deg, transparent, #e8e8e8, transparent);
margin: 20px 0;
}
/* --- Table --- */
.g-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.g-table th {
text-align: left; padding: 10px 12px; font-size: 12px; font-weight: 500;
color: var(--g-text-muted); background: var(--g-bg-subtle); border-bottom: 1px solid var(--g-border);
}
.g-table td {
padding: 12px; border-bottom: 1px solid #f5f5f5; color: var(--g-text);
}
.g-table tr:hover td { background: color-mix(in srgb, var(--primary) 2%, #fff); }
/* --- Empty State --- */
.g-empty {
text-align: center; padding: 48px 20px; color: var(--g-text-muted); font-size: 14px;
}
.g-empty svg { display: block; margin: 0 auto 12px; color: #ddd; }
/* --- Pagination --- */
.g-pagination {
display: flex; align-items: center; justify-content: flex-end;
gap: 6px; margin-top: 18px; font-size: 13px; color: var(--g-text-secondary);
}
.g-page-btn {
min-width: 32px; height: 32px; border-radius: var(--g-radius-sm);
border: 1px solid #d9d9d9; background: #fff; cursor: pointer; font-size: 13px;
display: inline-flex; align-items: center; justify-content: center;
transition: all var(--g-transition);
}
.g-page-btn:hover { border-color: var(--primary); color: var(--primary); }
.g-page-btn.active { background: var(--primary); color: #fff; border-color: var(--primary); }
.g-page-btn:disabled { opacity: .4; cursor: not-allowed; }
</style>
</head>
<body>
<div style="display:flex; height:100vh;">
<!-- Sidebar -->
<aside class="sidebar" id="sidebar">
<div class="sidebar-logo">
<div class="logo-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9h18v10a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V9Z"/><path d="m3 9 2.45-4.9A2 2 0 0 1 7.24 3h9.52a2 2 0 0 1 1.8 1.1L21 9"/><path d="M12 3v6"/></svg>
</div>
<span class="logo-text">TakeoutSaaS</span>
</div>
<nav class="sidebar-menu">
<!-- ===== 核心业务 ===== -->
<!-- 1. 仪表盘 -->
<div class="menu-parent" data-menu="dashboard">
<div class="menu-item active" data-tab="工作台" onclick="switchTab(this, '工作台')">
<i data-lucide="layout-dashboard" class="menu-icon"></i>
<span class="menu-label">工作台</span>
</div>
</div>
<!-- 2. 门店管理 -->
<div class="menu-parent" data-menu="store">
<div class="menu-item" onclick="toggleMenu('store')">
<i data-lucide="store" class="menu-icon"></i>
<span class="menu-label">门店管理</span>
<i data-lucide="chevron-right" class="menu-arrow"></i>
</div>
<div class="menu-sub" id="menu-store">
<div class="menu-item" data-tab="门店列表" onclick="switchTab(this, '门店列表')"><span class="menu-label">门店列表</span></div>
<div class="menu-item" data-tab="营业时间" onclick="switchTab(this, '营业时间')"><span class="menu-label">营业时间</span></div>
<div class="menu-item" data-tab="配送设置" onclick="switchTab(this, '配送设置')"><span class="menu-label">配送设置</span></div>
<div class="menu-item" data-tab="自提设置" onclick="switchTab(this, '自提设置')"><span class="menu-label">自提设置</span></div>
<div class="menu-item" data-tab="堂食管理" onclick="switchTab(this, '堂食管理')"><span class="menu-label">堂食管理</span></div>
<div class="menu-item" data-tab="费用设置" onclick="switchTab(this, '费用设置')"><span class="menu-label">费用设置</span></div>
<div class="menu-item" data-tab="员工排班" onclick="switchTab(this, '员工排班')"><span class="menu-label">员工排班</span></div>
</div>
</div>
<!-- 3. 商品管理 -->
<div class="menu-parent" data-menu="product">
<div class="menu-item" onclick="toggleMenu('product')">
<i data-lucide="package" class="menu-icon"></i>
<span class="menu-label">商品管理</span>
<i data-lucide="chevron-right" class="menu-arrow"></i>
</div>
<div class="menu-sub" id="menu-product">
<div class="menu-item" data-tab="商品列表" onclick="switchTab(this, '商品列表')"><span class="menu-label">商品列表</span></div>
<div class="menu-item" data-tab="分类管理" onclick="switchTab(this, '分类管理')"><span class="menu-label">分类管理</span></div>
<div class="menu-item" data-tab="规格做法" onclick="switchTab(this, '规格做法')"><span class="menu-label">规格做法</span></div>
<div class="menu-item" data-tab="加料管理" onclick="switchTab(this, '加料管理')"><span class="menu-label">加料管理</span></div>
<div class="menu-item" data-tab="商品标签" onclick="switchTab(this, '商品标签')"><span class="menu-label">商品标签</span></div>
<div class="menu-item" data-tab="时段供应" onclick="switchTab(this, '时段供应')"><span class="menu-label">时段供应</span></div>
<div class="menu-item" data-tab="批量工具" onclick="switchTab(this, '批量工具')"><span class="menu-label">批量工具</span></div>
</div>
</div>
<!-- 4. 订单管理 -->
<div class="menu-parent" data-menu="order">
<div class="menu-item" onclick="toggleMenu('order')">
<i data-lucide="clipboard-list" class="menu-icon"></i>
<span class="menu-label">订单管理</span>
<i data-lucide="chevron-right" class="menu-arrow"></i>
</div>
<div class="menu-sub" id="menu-order">
<div class="menu-item" data-tab="订单大厅" onclick="switchTab(this, '订单大厅')"><span class="menu-label">订单大厅</span></div>
<div class="menu-item" data-tab="全部订单" onclick="switchTab(this, '全部订单')"><span class="menu-label">全部订单</span></div>
<div class="menu-item" data-tab="退款售后" onclick="switchTab(this, '退款售后')"><span class="menu-label">退款售后</span></div>
<div class="menu-item" data-tab="订单设置" onclick="switchTab(this, '订单设置')"><span class="menu-label">订单设置</span></div>
</div>
</div>
<!-- 5. 评价管理 -->
<div class="menu-parent" data-menu="review">
<div class="menu-item" data-tab="评价管理" onclick="switchTab(this, '评价管理')">
<i data-lucide="message-square-text" class="menu-icon"></i>
<span class="menu-label">评价管理</span>
</div>
</div>
<!-- 5.5 客户管理 -->
<div class="menu-parent" data-menu="customer">
<div class="menu-item" onclick="toggleMenu('customer')">
<i data-lucide="users" class="menu-icon"></i>
<span class="menu-label">客户管理</span>
<i data-lucide="chevron-right" class="menu-arrow"></i>
</div>
<div class="menu-sub" id="menu-customer">
<div class="menu-item" data-tab="客户列表" onclick="switchTab(this, '客户列表')"><span class="menu-label">客户列表</span></div>
<div class="menu-item" data-tab="客户画像" onclick="switchTab(this, '客户画像')"><span class="menu-label">客户画像</span></div>
<div class="menu-item" data-tab="客户分析" onclick="switchTab(this, '客户分析')"><span class="menu-label">客户分析</span></div>
</div>
</div>
<!-- 6. 营销中心 -->
<div class="menu-parent" data-menu="marketing">
<div class="menu-item" onclick="toggleMenu('marketing')">
<i data-lucide="megaphone" class="menu-icon"></i>
<span class="menu-label">营销中心</span>
<i data-lucide="chevron-right" class="menu-arrow"></i>
</div>
<div class="menu-sub" id="menu-marketing">
<div class="menu-item" data-tab="营销日历" onclick="switchTab(this, '营销日历')"><span class="menu-label">营销日历</span></div>
<div class="menu-item" data-tab="优惠券" onclick="switchTab(this, '优惠券')"><span class="menu-label">优惠券</span></div>
<div class="menu-item" data-tab="满减活动" onclick="switchTab(this, '满减活动')"><span class="menu-label">满减活动</span></div>
<div class="menu-item" data-tab="限时折扣" onclick="switchTab(this, '限时折扣')"><span class="menu-label">限时折扣</span></div>
<div class="menu-item" data-tab="秒杀活动" onclick="switchTab(this, '秒杀活动')"><span class="menu-label">秒杀活动</span></div>
<div class="menu-item" data-tab="新客有礼" onclick="switchTab(this, '新客有礼')"><span class="menu-label">新客有礼</span></div>
<div class="menu-item" data-tab="次卡管理" onclick="switchTab(this, '次卡管理')"><span class="menu-label">次卡管理</span></div>
</div>
</div>
<!-- 7. 会员中心 -->
<div class="menu-parent" data-menu="member">
<div class="menu-item" onclick="toggleMenu('member')">
<i data-lucide="crown" class="menu-icon"></i>
<span class="menu-label">会员中心</span>
<i data-lucide="chevron-right" class="menu-arrow"></i>
</div>
<div class="menu-sub" id="menu-member">
<div class="menu-item" data-tab="会员管理" onclick="switchTab(this, '会员管理')"><span class="menu-label">会员管理</span></div>
<div class="menu-item" data-tab="储值卡" onclick="switchTab(this, '储值卡')"><span class="menu-label">储值卡</span></div>
<div class="menu-item" data-tab="积分商城" onclick="switchTab(this, '积分商城')"><span class="menu-label">积分商城</span></div>
<div class="menu-item" data-tab="消息触达" onclick="switchTab(this, '消息触达')"><span class="menu-label">消息触达</span></div>
</div>
</div>
<!-- 6. 财务中心 -->
<div class="menu-parent" data-menu="finance">
<div class="menu-item" onclick="toggleMenu('finance')">
<i data-lucide="wallet" class="menu-icon"></i>
<span class="menu-label">财务中心</span>
<i data-lucide="chevron-right" class="menu-arrow"></i>
</div>
<div class="menu-sub" id="menu-finance">
<div class="menu-item" data-tab="财务概览" onclick="switchTab(this, '财务概览')"><span class="menu-label">财务概览</span></div>
<div class="menu-item" data-tab="交易流水" onclick="switchTab(this, '交易流水')"><span class="menu-label">交易流水</span></div>
<div class="menu-item" data-tab="到账查询" onclick="switchTab(this, '到账查询')"><span class="menu-label">到账查询</span></div>
<div class="menu-item" data-tab="成本管理" onclick="switchTab(this, '成本管理')"><span class="menu-label">成本管理</span></div>
<div class="menu-item" data-tab="发票管理" onclick="switchTab(this, '发票管理')"><span class="menu-label">发票管理</span></div>
<div class="menu-item" data-tab="经营报表" onclick="switchTab(this, '经营报表')"><span class="menu-label">经营报表</span></div>
</div>
</div>
<!-- 7. 数据统计 -->
<div class="menu-parent" data-menu="statistics">
<div class="menu-item" onclick="toggleMenu('statistics')">
<i data-lucide="bar-chart-3" class="menu-icon"></i>
<span class="menu-label">数据统计</span>
<i data-lucide="chevron-right" class="menu-arrow"></i>
</div>
<div class="menu-sub" id="menu-statistics">
<div class="menu-item" data-tab="商品分析" onclick="switchTab(this, '商品分析')"><span class="menu-label">商品分析</span></div>
<div class="menu-item" data-tab="订单分析" onclick="switchTab(this, '订单分析')"><span class="menu-label">订单分析</span></div>
<div class="menu-item" data-tab="营销分析" onclick="switchTab(this, '营销分析')"><span class="menu-label">营销分析</span></div>
</div>
</div>
<!-- 8. 库存管理 -->
<div class="menu-parent" data-menu="inventory">
<div class="menu-item" onclick="toggleMenu('inventory')">
<i data-lucide="warehouse" class="menu-icon"></i>
<span class="menu-label">库存管理</span>
<i data-lucide="chevron-right" class="menu-arrow"></i>
</div>
<div class="menu-sub" id="menu-inventory">
<div class="menu-item" data-tab="库存总览" onclick="switchTab(this, '库存总览')"><span class="menu-label">库存总览</span></div>
<div class="menu-item" data-tab="出入库管理" onclick="switchTab(this, '出入库管理')"><span class="menu-label">出入库管理</span></div>
<div class="menu-item" data-tab="采购管理" onclick="switchTab(this, '采购管理')"><span class="menu-label">采购管理</span></div>
<div class="menu-item" data-tab="估清管理" onclick="switchTab(this, '估清管理')"><span class="menu-label">估清管理</span></div>
<div class="menu-item" data-tab="效期管理" onclick="switchTab(this, '效期管理')"><span class="menu-label">效期管理</span></div>
</div>
</div>
<!-- ===== 高级功能 ===== -->
<div class="menu-group-title">高级功能</div>
<!-- 9. 多渠道整合 -->
<div class="menu-parent" data-menu="channel">
<div class="menu-item" onclick="toggleMenu('channel')">
<i data-lucide="git-merge" class="menu-icon"></i>
<span class="menu-label">多渠道整合</span>
<i data-lucide="chevron-right" class="menu-arrow"></i>
</div>
<div class="menu-sub" id="menu-channel">
<div class="menu-item" data-tab="渠道管理" onclick="switchTab(this, '渠道管理')"><span class="menu-label">渠道管理</span></div>
<div class="menu-item" data-tab="订单聚合" onclick="switchTab(this, '订单聚合')"><span class="menu-label">订单聚合</span></div>
<div class="menu-item" data-tab="菜单同步" onclick="switchTab(this, '菜单同步')"><span class="menu-label">菜单同步</span></div>
<div class="menu-item" data-tab="配送调度" onclick="switchTab(this, '配送调度')"><span class="menu-label">配送调度</span></div>
</div>
</div>
<!-- 10. 增值服务 -->
<div class="menu-parent" data-menu="valueadded">
<div class="menu-item" onclick="toggleMenu('valueadded')">
<i data-lucide="sparkles" class="menu-icon"></i>
<span class="menu-label">增值服务</span>
<i data-lucide="chevron-right" class="menu-arrow"></i>
</div>
<div class="menu-sub" id="menu-valueadded">
<div class="menu-item" data-tab="小程序商城" onclick="switchTab(this, '小程序商城')"><span class="menu-label">小程序商城</span></div>
<div class="menu-item" data-tab="店铺装修" onclick="switchTab(this, '店铺装修')"><span class="menu-label">店铺装修</span></div>
<div class="menu-item" data-tab="连锁管理" onclick="switchTab(this, '连锁管理')"><span class="menu-label">连锁管理</span></div>
<div class="menu-item" data-tab="统一采购" onclick="switchTab(this, '统一采购')"><span class="menu-label">统一采购</span></div>
<div class="menu-item" data-tab="跨店调拨" onclick="switchTab(this, '跨店调拨')"><span class="menu-label">跨店调拨</span></div>
<div class="menu-item" data-tab="AI助手" onclick="switchTab(this, 'AI助手')"><span class="menu-label">AI助手</span></div>
<div class="menu-item" data-tab="智能客服" onclick="switchTab(this, '智能客服')"><span class="menu-label">智能客服</span></div>
<div class="menu-item" data-tab="智能定价" onclick="switchTab(this, '智能定价')"><span class="menu-label">智能定价</span></div>
<div class="menu-item" data-tab="销量预测" onclick="switchTab(this, '销量预测')"><span class="menu-label">销量预测</span></div>
</div>
</div>
<!-- 11. 合规安全 -->
<div class="menu-parent" data-menu="compliance">
<div class="menu-item" onclick="toggleMenu('compliance')">
<i data-lucide="shield-check" class="menu-icon"></i>
<span class="menu-label">合规安全</span>
<i data-lucide="chevron-right" class="menu-arrow"></i>
</div>
<div class="menu-sub" id="menu-compliance">
<div class="menu-item" data-tab="资质证照" onclick="switchTab(this, '资质证照')"><span class="menu-label">资质证照</span></div>
<div class="menu-item" data-tab="溯源管理" onclick="switchTab(this, '溯源管理')"><span class="menu-label">溯源管理</span></div>
<div class="menu-item" data-tab="资质到期提醒" onclick="switchTab(this, '资质到期提醒')"><span class="menu-label">资质到期提醒</span></div>
<div class="menu-item" data-tab="电子发票" onclick="switchTab(this, '电子发票')"><span class="menu-label">电子发票</span></div>
<div class="menu-item" data-tab="税务报表" onclick="switchTab(this, '税务报表')"><span class="menu-label">税务报表</span></div>
</div>
</div>
<!-- ===== 系统管理 ===== -->
<div class="menu-group-title">系统管理</div>
<!-- 12. 设置中心 -->
<div class="menu-parent" data-menu="settings">
<div class="menu-item" onclick="toggleMenu('settings')">
<i data-lucide="settings" class="menu-icon"></i>
<span class="menu-label">设置中心</span>
<i data-lucide="chevron-right" class="menu-arrow"></i>
</div>
<div class="menu-sub" id="menu-settings">
<div class="menu-item" data-tab="基本信息" onclick="switchTab(this, '基本信息')"><span class="menu-label">基本信息</span></div>
<div class="menu-item" data-tab="支付设置" onclick="switchTab(this, '支付设置')"><span class="menu-label">支付设置</span></div>
<div class="menu-item" data-tab="打印设置" onclick="switchTab(this, '打印设置')"><span class="menu-label">打印设置</span></div>
<div class="menu-item" data-tab="通知设置" onclick="switchTab(this, '通知设置')"><span class="menu-label">通知设置</span></div>
</div>
</div>
<!-- 13. 账号管理 -->
<div class="menu-parent" data-menu="account">
<div class="menu-item" onclick="toggleMenu('account')">
<i data-lucide="users" class="menu-icon"></i>
<span class="menu-label">账号管理</span>
<i data-lucide="chevron-right" class="menu-arrow"></i>
</div>
<div class="menu-sub" id="menu-account">
<div class="menu-item" data-tab="子账号管理" onclick="switchTab(this, '子账号管理')"><span class="menu-label">子账号管理</span></div>
<div class="menu-item" data-tab="角色权限" onclick="switchTab(this, '角色权限')"><span class="menu-label">角色权限</span></div>
<div class="menu-item" data-tab="操作日志" onclick="switchTab(this, '操作日志')"><span class="menu-label">操作日志</span></div>
</div>
</div>
<!-- 14. 订阅服务 -->
<div class="menu-parent" data-menu="subscription">
<div class="menu-item" onclick="toggleMenu('subscription')">
<i data-lucide="credit-card" class="menu-icon"></i>
<span class="menu-label">订阅服务</span>
<i data-lucide="chevron-right" class="menu-arrow"></i>
</div>
<div class="menu-sub" id="menu-subscription">
<div class="menu-item" data-tab="当前套餐" onclick="switchTab(this, '当前套餐')"><span class="menu-label">当前套餐</span></div>
<div class="menu-item" data-tab="续费升级" onclick="switchTab(this, '续费升级')"><span class="menu-label">续费升级</span></div>
<div class="menu-item" data-tab="账单记录" onclick="switchTab(this, '账单记录')"><span class="menu-label">账单记录</span></div>
</div>
</div>
</nav>
</aside>
<!-- Main -->
<div style="flex:1; display:flex; flex-direction:column; overflow:hidden;">
<!-- Header -->
<header class="header">
<div class="header-left">
<button class="header-btn" onclick="toggleSidebar()" title="收起菜单">
<i data-lucide="menu" style="width:18px;height:18px;"></i>
</button>
<div class="breadcrumb">
<a href="#">首页</a>
<span class="sep">/</span>
<span class="current" id="breadcrumb-current">工作台</span>
</div>
</div>
<div class="header-right">
<button class="header-btn" title="搜索">
<i data-lucide="search" style="width:16px;height:16px;"></i>
</button>
<button class="header-btn badge" title="通知">
<i data-lucide="bell" style="width:16px;height:16px;"></i>
</button>
<button class="header-btn" title="全屏" onclick="toggleFullscreen()">
<i data-lucide="maximize" style="width:16px;height:16px;"></i>
</button>
<div style="position:relative;">
<div class="user-dropdown-trigger" onclick="toggleDropdown()">
<div class="avatar"></div>
<span class="user-name">管理员</span>
<i data-lucide="chevron-down" style="width:12px;height:12px;color:#999;"></i>
</div>
<div class="dropdown" id="userDropdown">
<div class="dropdown-item"><i data-lucide="user" style="width:14px;height:14px;"></i>个人中心</div>
<div class="dropdown-item"><i data-lucide="store" style="width:14px;height:14px;"></i>商户中心</div>
<div class="dropdown-divider"></div>
<div class="dropdown-item danger"><i data-lucide="log-out" style="width:14px;height:14px;"></i>退出登录</div>
</div>
</div>
</div>
</header>
<!-- Tabbar -->
<div class="tabbar" id="tabbar">
<div class="tab active" data-tab="工作台">
<i data-lucide="pin" class="tab-pin"></i>
<span>工作台</span>
</div>
</div>
<!-- Content -->
<main class="content-area" id="contentArea">
<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#999;font-size:14px;">
<div style="text-align:center;">
<i data-lucide="layout-dashboard" style="width:48px;height:48px;color:#d9d9d9;margin-bottom:12px;"></i>
<p>工作台 - 内容区域</p>
<p style="font-size:12px;margin-top:4px;color:#bbb;">点击左侧菜单切换页面</p>
</div>
</div>
</main>
</div>
</div>
<!-- Store Drawer -->
<div class="drawer-mask" id="drawerMask" onclick="closeDrawer()"></div>
<div class="drawer-panel" id="drawerPanel">
<div class="drawer-header">
<span class="title" id="drawerTitle">新增门店</span>
<button class="drawer-close" onclick="closeDrawer()"><i data-lucide="x" style="width:18px;height:18px;"></i></button>
</div>
<div class="drawer-body">
<!-- 基本信息 -->
<div class="df-section">
<div class="df-section-title">基本信息</div>
<div class="df-grid">
<div class="df-item"><label class="df-label"><span class="req">*</span>门店名称</label><input class="df-input" id="df-name" placeholder="请输入门店名称"/></div>
<div class="df-item"><label class="df-label">门店编号</label><input class="df-input" id="df-code" placeholder="系统自动生成" disabled style="background:#f5f5f5;"/></div>
<div class="df-item"><label class="df-label"><span class="req">*</span>联系电话</label><input class="df-input" id="df-phone" placeholder="请输入联系电话"/></div>
<div class="df-item"><label class="df-label">店长姓名</label><input class="df-input" id="df-manager" placeholder="请输入店长姓名"/></div>
<div class="df-item"><label class="df-label">门店分类</label><select class="df-select" id="df-category"><option value="">请选择</option><option>中餐</option><option>西餐</option><option>快餐</option><option>饮品</option><option>烘焙</option><option>火锅</option></select></div>
<div class="df-item"><label class="df-label">所有权类型</label><select class="df-select" id="df-ownership"><option>直营</option><option>加盟</option></select></div>
</div>
</div>
<!-- 地址信息 -->
<div class="df-section">
<div class="df-section-title">地址信息</div>
<div class="df-grid">
<div class="df-item full"><label class="df-label"><span class="req">*</span>所在地区</label><div class="df-addr-row"><select class="df-select"><option>请选择省份</option><option>北京市</option><option>上海市</option><option>广东省</option></select><select class="df-select"><option>请选择城市</option></select><select class="df-select"><option>请选择区县</option></select></div></div>
<div class="df-item full"><label class="df-label"><span class="req">*</span>详细地址</label><input class="df-input" id="df-address" placeholder="街道、门牌号等"/></div>
<div class="df-item full"><label class="df-label">经纬度</label><div class="df-coord-row"><input class="df-input" placeholder="经度" id="df-lng"/><input class="df-input" placeholder="纬度" id="df-lat"/><button class="df-btn"><i data-lucide="map-pin" style="width:14px;height:14px;"></i>地图选点</button></div></div>
</div>
</div>
<!-- 配送设置 -->
<div class="df-section">
<div class="df-section-title">配送设置</div>
<div class="df-grid">
<div class="df-item full">
<label class="df-label">配送范围模式</label>
<div class="df-radio-group">
<button class="df-radio-btn active" onclick="switchDeliveryMode('radius',this)">半径模式</button>
<button class="df-radio-btn" onclick="switchDeliveryMode('polygon',this)">多边形围栏</button>
</div>
</div>
<div class="df-item full" id="deliveryRadius">
<label class="df-label">配送半径(公里)</label>
<input class="df-input" type="number" placeholder="例如5" value="5" style="width:200px;"/>
<div class="df-hint">以门店地址为圆心,设定配送覆盖半径</div>
</div>
<div class="df-item full" id="deliveryPolygon" style="display:none;">
<label class="df-label">在地图上绘制配送范围</label>
<div class="df-map-placeholder"><i data-lucide="pentagon" style="width:24px;height:24px;margin-right:8px;"></i>点击此处打开地图,绘制多边形配送范围</div>
<div class="df-hint">在地图上点击多个点围出配送区域,支持多个不规则区域</div>
</div>
</div>
</div>
<!-- 服务与时间 -->
<div class="df-section">
<div class="df-section-title">服务与时间</div>
<div class="df-grid">
<div class="df-item full"><label class="df-label">支持的服务方式</label><div class="df-switch-group"><div class="df-switch-item"><div class="df-switch on" onclick="this.classList.toggle('on')"></div>外卖配送</div><div class="df-switch-item"><div class="df-switch on" onclick="this.classList.toggle('on')"></div>到店自提</div><div class="df-switch-item"><div class="df-switch" onclick="this.classList.toggle('on')"></div>堂食</div><div class="df-switch-item"><div class="df-switch" onclick="this.classList.toggle('on')"></div>预约</div><div class="df-switch-item"><div class="df-switch" onclick="this.classList.toggle('on')"></div>排队</div></div></div>
<div class="df-item full"><label class="df-label">默认配送时间</label><input class="df-input" placeholder="例如09:00-22:00" id="df-hours" style="width:300px;"/><div class="df-hint">门店默认的配送服务时间段,详细时段可在"营业时间"菜单中设置</div></div>
</div>
</div>
<!-- 门店展示 -->
<div class="df-section">
<div class="df-section-title">门店展示</div>
<div class="df-grid">
<div class="df-item"><label class="df-label">门店封面</label><div class="df-upload-box"><i data-lucide="image-plus" style="width:22px;height:22px;"></i>上传封面</div><div class="df-hint">建议 750x420不超过 2MB</div></div>
<div class="df-item"><label class="df-label">门店招牌</label><div class="df-upload-box"><i data-lucide="image-plus" style="width:22px;height:22px;"></i>上传招牌</div><div class="df-hint">门头实拍照片,用于审核</div></div>
</div>
</div>
</div>
<div class="drawer-footer">
<button class="df-btn" onclick="closeDrawer()">取消</button>
<button class="df-btn df-btn-primary"><i data-lucide="check" style="width:14px;height:14px;"></i>保存</button>
</div>
</div>
<!-- 复制到其他门店弹窗 -->
<!-- 通用商品选择器弹窗 -->
<style>
.pp-mask{position:fixed;inset:0;background:rgba(0,0,0,.35);z-index:2000;display:none;align-items:center;justify-content:center;backdrop-filter:blur(2px)}
.pp-mask.open{display:flex}
.pp-modal{background:#fff;border-radius:12px;box-shadow:0 12px 40px rgba(0,0,0,.12);width:720px;max-height:80vh;display:flex;flex-direction:column;animation:ppSlideUp 200ms cubic-bezier(.4,0,.2,1)}
@keyframes ppSlideUp{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}
.pp-header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid #f0f0f0;flex-shrink:0}
.pp-header h3{font-size:16px;font-weight:600;color:#1a1a2e;margin:0}
.pp-header-sub{font-size:12px;color:#9ca3af;font-weight:400;margin-left:8px}
.pp-close{width:28px;height:28px;border-radius:6px;border:none;background:transparent;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#999;transition:200ms}
.pp-close:hover{background:#f3f4f6;color:#333}
.pp-toolbar{display:flex;gap:8px;padding:12px 20px;border-bottom:1px solid #f3f4f6;flex-shrink:0;flex-wrap:wrap;align-items:center}
.pp-search{position:relative;flex:1;min-width:180px}
.pp-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:200ms}
.pp-search input:focus{border-color:hsl(212,100%,45%);box-shadow:0 0 0 3px color-mix(in srgb,hsl(212,100%,45%) 12%,transparent)}
.pp-search i{position:absolute;left:9px;top:50%;transform:translateY(-50%);color:#bbb;pointer-events:none}
.pp-cat-select{height:32px;border-radius:8px;border:1px solid #e5e7eb;padding:0 10px;font-size:12px;outline:none;color:#1a1a2e;min-width:120px}
.pp-selected-count{font-size:12px;color:hsl(212,100%,45%);font-weight:600;margin-left:auto}
.pp-body{flex:1;overflow-y:auto;padding:0}
.pp-body::-webkit-scrollbar{width:4px}
.pp-body::-webkit-scrollbar-thumb{background:#e5e7eb;border-radius:2px}
.pp-table{width:100%;border-collapse:collapse;font-size:13px}
.pp-table th{text-align:left;padding:8px 16px;font-size:11px;font-weight:500;color:#6b7280;background:#f8f9fb;position:sticky;top:0;z-index:1}
.pp-table td{padding:8px 16px;border-bottom:1px solid #f3f4f6;color:#1a1a2e}
.pp-table tr:hover td{background:color-mix(in srgb,hsl(212,100%,45%) 3%,#fff)}
.pp-table tr.pp-checked td{background:color-mix(in srgb,hsl(212,100%,45%) 8%,#fff)}
.pp-cb{width:16px;height:16px;border-radius:4px;border:1.5px solid #d1d5db;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;transition:200ms;flex-shrink:0;vertical-align:middle}
.pp-cb.checked{background:hsl(212,100%,45%);border-color:hsl(212,100%,45%)}
.pp-cb svg{display:none}
.pp-cb.checked svg{display:block}
.pp-prod-cell{display:flex;align-items:center;gap:10px}
.pp-prod-thumb{width:36px;height:36px;border-radius:6px;background:#f3f4f6;display:flex;align-items:center;justify-content:center;flex-shrink:0;color:#ccc}
.pp-prod-name{font-weight:500}
.pp-prod-spu{font-size:11px;color:#9ca3af;margin-top:1px}
.pp-prod-price{font-weight:600}
.pp-prod-cat{font-size:12px;color:#6b7280}
.pp-prod-status{display:inline-block;padding:1px 8px;border-radius:4px;font-size:11px;font-weight:500}
.pp-prod-status.on{background:#dcfce7;color:#22c55e}
.pp-prod-status.off{background:#f3f4f6;color:#9ca3af}
.pp-footer{display:flex;align-items:center;justify-content:space-between;padding:14px 20px;border-top:1px solid #f0f0f0;flex-shrink:0}
.pp-footer-info{font-size:12px;color:#6b7280}
.pp-footer-info strong{color:hsl(212,100%,45%);font-weight:600}
.pp-footer-btns{display:flex;gap:8px}
</style>
<div class="pp-mask" id="ppMask" onclick="if(event.target===this)closeProductPicker()">
<div class="pp-modal">
<div class="pp-header">
<div style="display:flex;align-items:baseline">
<h3 id="ppTitle">选择商品</h3>
<span class="pp-header-sub" id="ppSubtitle"></span>
</div>
<button class="pp-close" onclick="closeProductPicker()"><i data-lucide="x" style="width:16px;height:16px"></i></button>
</div>
<div class="pp-toolbar">
<div class="pp-search">
<i data-lucide="search" style="width:14px;height:14px"></i>
<input type="text" id="ppSearchInput" placeholder="搜索商品名称或编码…" oninput="filterPpProducts()">
</div>
<select class="pp-cat-select" id="ppCatFilter" onchange="filterPpProducts()">
<option value="">全部分类</option>
<option>热销推荐</option>
<option>主食</option>
<option>小吃凉菜</option>
<option>汤羹粥品</option>
<option>饮品</option>
<option>酒水</option>
</select>
<span class="pp-selected-count" id="ppSelectedCount">已选 0 个</span>
</div>
<div class="pp-body">
<table class="pp-table">
<thead>
<tr>
<th style="width:36px"><span class="pp-cb" id="ppAllCb" onclick="togglePpAll()"><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></th>
<th>商品</th>
<th>分类</th>
<th>售价</th>
<th>库存</th>
<th>状态</th>
</tr>
</thead>
<tbody id="ppProductList"></tbody>
</table>
</div>
<div class="pp-footer">
<div class="pp-footer-info">已选择 <strong id="ppFooterCount">0</strong> 个商品</div>
<div class="pp-footer-btns">
<button class="g-btn" onclick="closeProductPicker()">取消</button>
<button class="g-btn g-btn-primary" onclick="confirmProductPicker()">确认选择</button>
</div>
</div>
</div>
</div>
<!-- 复制到其他门店弹窗 -->
<div class="csm-mask" id="csmMask" onclick="if(event.target===this)closeCopyModal()">
<div class="csm-modal">
<div class="csm-header">
<h3 id="csmTitle">复制到其他门店</h3>
<button class="csm-close" onclick="closeCopyModal()"></button>
</div>
<div class="csm-body">
<div class="csm-warn">
<i data-lucide="alert-triangle" style="width:14px;height:14px;flex-shrink:0;"></i>
<span>将覆盖目标门店的现有设置,请谨慎操作</span>
</div>
<label class="csm-all" onclick="toggleCsmAll()">
<span class="csm-cb" id="csmAllCb"></span> 全选
</label>
<div id="csmStoreList"></div>
</div>
<div class="csm-footer">
<button class="csm-btn" onclick="closeCopyModal()">取消</button>
<button class="csm-btn csm-btn-primary" onclick="closeCopyModal()">确认复制</button>
</div>
</div>
</div>
<script>
// Init Lucide icons
lucide.createIcons();
// Drawer functions
function openDrawer(mode, data) {
document.getElementById('drawerMask').classList.add('open');
document.getElementById('drawerPanel').classList.add('open');
const title = document.getElementById('drawerTitle');
// Reset form
document.querySelectorAll('#drawerPanel .df-input').forEach(el => { if(!el.disabled) el.value=''; });
if (mode === 'edit' && data) {
title.textContent = '编辑门店 - ' + (data.name || '');
if(data.name) document.getElementById('df-name').value = data.name;
if(data.code) document.getElementById('df-code').value = data.code;
if(data.phone) document.getElementById('df-phone').value = data.phone;
if(data.manager) document.getElementById('df-manager').value = data.manager;
} else {
title.textContent = '新增门店';
}
lucide.createIcons();
}
function closeDrawer() {
document.getElementById('drawerMask').classList.remove('open');
document.getElementById('drawerPanel').classList.remove('open');
}
function switchDeliveryMode(mode, btn) {
btn.parentElement.querySelectorAll('.df-radio-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
document.getElementById('deliveryRadius').style.display = mode==='radius' ? '' : 'none';
document.getElementById('deliveryPolygon').style.display = mode==='polygon' ? '' : 'none';
}
// Sidebar toggle
function toggleSidebar() {
document.getElementById('sidebar').classList.toggle('collapsed');
}
// Menu toggle
function toggleMenu(id) {
const sub = document.getElementById('menu-' + id);
const arrow = sub.previousElementSibling.querySelector('.menu-arrow');
sub.classList.toggle('open');
if (arrow) arrow.classList.toggle('expanded');
}
// Tab & menu switching
function switchTab(menuItem, label) {
// Update active menu
document.querySelectorAll('.menu-sub .menu-item').forEach(el => el.classList.remove('active'));
menuItem.classList.add('active');
// Update breadcrumb
document.getElementById('breadcrumb-current').textContent = label;
// Check if tab exists
const tabbar = document.getElementById('tabbar');
let existingTab = tabbar.querySelector(`.tab[data-tab="${label}"]`);
if (!existingTab) {
// Create new tab
const tab = document.createElement('div');
tab.className = 'tab';
tab.dataset.tab = label;
tab.innerHTML = `<span>${label}</span><span class="tab-close" onclick="event.stopPropagation();closeTab(this.parentElement)">✕</span>`;
tab.onclick = function() { activateTab(this); };
tabbar.appendChild(tab);
existingTab = tab;
}
activateTab(existingTab);
}
// Page mapping - 有独立页面的菜单项
const pageMap = {
'工作台': 'pages/dashboard.html',
'门店列表': 'pages/store-list.html',
'营业时间': 'pages/store-hours.html',
'配送设置': 'pages/store-delivery.html',
'自提设置': 'pages/store-pickup.html',
'堂食管理': 'pages/store-dinein.html',
'费用设置': 'pages/store-fees.html',
'资质证照': 'pages/store-qualifications.html',
'员工排班': 'pages/store-staff.html',
'商品列表': 'pages/product-list.html',
'分类管理': 'pages/product-category.html',
'规格做法': 'pages/product-specs.html',
'加料管理': 'pages/product-addons.html',
'商品标签': 'pages/product-labels.html',
'时段供应': 'pages/product-schedule.html',
'批量工具': 'pages/product-batch.html',
'商品详情': 'pages/product-detail.html',
'订单大厅': 'pages/order-board.html',
'全部订单': 'pages/order-list.html',
'退款售后': 'pages/order-refund.html',
'订单设置': 'pages/order-settings.html',
'评价管理': 'pages/reviews.html',
'优惠券': 'pages/mkt-coupon.html',
'满减活动': 'pages/mkt-reduction.html',
'限时折扣': 'pages/mkt-flash-sale.html',
'秒杀活动': 'pages/mkt-seckill.html',
'新客有礼': 'pages/mkt-new-customer.html',
'次卡管理': 'pages/mkt-pass-card.html',
'营销日历': 'pages/mkt-calendar.html',
'会员管理': 'pages/mbr-members.html',
'储值卡': 'pages/mbr-prepaid.html',
'积分商城': 'pages/mbr-points.html',
'消息触达': 'pages/mbr-messaging.html',
'客户列表': 'pages/cust-list.html',
'客户画像': 'pages/cust-profile.html',
'客户分析': 'pages/cust-analysis.html',
'财务概览': 'pages/fin-overview.html',
'交易流水': 'pages/fin-transactions.html',
'到账查询': 'pages/fin-settlement.html',
'成本管理': 'pages/fin-cost.html',
'发票管理': 'pages/fin-invoice.html',
'经营报表': 'pages/fin-reports.html',
'商品分析': 'pages/stat-product.html',
'订单分析': 'pages/stat-order.html',
'营销分析': 'pages/stat-marketing.html',
'库存总览': 'pages/inv-overview.html',
'出入库管理': 'pages/inv-inout.html',
'采购管理': 'pages/inv-purchase.html',
'估清管理': 'pages/inv-soldout.html',
'效期管理': 'pages/inv-expiry.html',
'渠道管理': 'pages/ch-platform.html',
'订单聚合': 'pages/ch-orders.html',
'菜单同步': 'pages/ch-menu-sync.html',
'配送调度': 'pages/ch-delivery.html',
};
// Page cache
const pageCache = {};
// 设置页面HTML并执行其中的script标签
function setPageHtml(html) {
const area = document.getElementById('contentArea');
area.innerHTML = html;
area.querySelectorAll('script').forEach(old => {
const s = document.createElement('script');
s.textContent = old.textContent;
old.replaceWith(s);
});
lucide.createIcons();
}
// Navigate to a virtual page (not in sidebar menu)
function navigateTo(label, options) {
window._pageOptions = options || {};
const tabbar = document.getElementById('tabbar');
let tab = tabbar.querySelector(`.tab[data-tab="${label}"]`);
if (!tab) {
tab = document.createElement('div');
tab.className = 'tab';
tab.dataset.tab = label;
tab.innerHTML = `<span>${label}</span><span class="tab-close" onclick="event.stopPropagation();closeTab(this.parentElement)">✕</span>`;
tab.onclick = function() { activateTab(this); };
tabbar.appendChild(tab);
}
activateTab(tab);
}
function activateTab(tab) {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
tab.classList.add('active');
const label = tab.dataset.tab;
// Sync menu active state
document.querySelectorAll('.menu-sub .menu-item').forEach(el => {
el.classList.toggle('active', el.dataset.tab === label);
});
document.getElementById('breadcrumb-current').textContent = label;
// Load page content
if (pageMap[label]) {
if (pageCache[label]) {
setPageHtml(pageCache[label]);
} else {
document.getElementById('contentArea').innerHTML = '<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#999;">加载中...</div>';
fetch(pageMap[label] + '?_t=' + Date.now())
.then(r => r.text())
.then(html => {
pageCache[label] = html;
if (tab.classList.contains('active')) { setPageHtml(html); }
});
}
} else {
document.getElementById('contentArea').innerHTML = `
<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#999;font-size:14px;">
<div style="text-align:center;">
<i data-lucide="layout-dashboard" style="width:48px;height:48px;color:#d9d9d9;margin-bottom:12px;"></i>
<p>${label} - 内容区域</p>
<p style="font-size:12px;margin-top:4px;color:#bbb;">此处将放置 ${label} 的具体内容</p>
</div>
</div>`;
lucide.createIcons();
}
}
function closeTab(tab) {
const wasActive = tab.classList.contains('active');
const prev = tab.previousElementSibling;
tab.remove();
if (wasActive && prev) activateTab(prev);
}
// User dropdown
function toggleDropdown() {
document.getElementById('userDropdown').classList.toggle('show');
}
document.addEventListener('click', (e) => {
if (!e.target.closest('.user-dropdown-trigger') && !e.target.closest('.dropdown')) {
document.getElementById('userDropdown').classList.remove('show');
}
});
// Fullscreen
function toggleFullscreen() {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen();
} else {
document.exitFullscreen();
}
}
// Auto-load dashboard on init
const allStores = [
{ name:'老三家外卖(朝阳店)', addr:'北京市朝阳区建国路88号' },
{ name:'老三家外卖(海淀店)', addr:'北京市海淀区中关村大街66号' },
{ name:'老三家外卖(望京店)', addr:'北京市朝阳区望京西路50号' },
{ name:'老三家外卖(通州店)', addr:'北京市通州区新华大街120号' },
{ name:'老三家外卖(丰台店)', addr:'北京市丰台区丰台路18号' },
];
function openCopyStoreModal(title) {
document.getElementById('csmTitle').textContent = title || '复制到其他门店';
// find current store from any visible selector on the page
let current = '';
const sel = document.querySelector('#contentArea select');
if (sel) current = sel.value;
const list = document.getElementById('csmStoreList');
list.innerHTML = allStores.filter(s => s.name !== current).map(s =>
`<label class="csm-item" onclick="toggleCsmItem(this)">
<span class="csm-cb"></span>
<div><div class="csm-store-name">${s.name}</div><div class="csm-store-addr">${s.addr}</div></div>
</label>`
).join('');
document.getElementById('csmAllCb').classList.remove('checked');
document.getElementById('csmMask').classList.add('open');
lucide.createIcons();
}
function closeCopyModal() {
document.getElementById('csmMask').classList.remove('open');
}
function toggleCsmItem(el) {
el.querySelector('.csm-cb').classList.toggle('checked');
syncCsmAll();
}
function toggleCsmAll() {
const allCb = document.getElementById('csmAllCb');
const items = document.querySelectorAll('#csmStoreList .csm-cb');
const shouldCheck = !allCb.classList.contains('checked');
allCb.classList.toggle('checked', shouldCheck);
items.forEach(cb => cb.classList.toggle('checked', shouldCheck));
}
function syncCsmAll() {
const items = document.querySelectorAll('#csmStoreList .csm-cb');
const allChecked = [...items].every(cb => cb.classList.contains('checked'));
document.getElementById('csmAllCb').classList.toggle('checked', allChecked);
}
const initTab = document.querySelector('.tab[data-tab="工作台"]');
if (initTab) activateTab(initTab);
// ========== 通用商品选择器 ==========
var _ppProducts = [
{name:'宫保鸡丁',spu:'SPU20240001',cat:'主食',price:32,stock:86,status:'on'},
{name:'鱼香肉丝盖饭',spu:'SPU20240002',cat:'主食',price:22,stock:45,status:'on'},
{name:'招牌红烧肉饭',spu:'SPU20240003',cat:'热销推荐',price:28,stock:60,status:'on'},
{name:'番茄牛腩面',spu:'SPU20240004',cat:'主食',price:26,stock:38,status:'on'},
{name:'香辣鸡腿堡套餐',spu:'SPU20240005',cat:'热销推荐',price:35,stock:25,status:'on'},
{name:'麻婆豆腐',spu:'SPU20240006',cat:'小吃凉菜',price:18,stock:70,status:'on'},
{name:'酸辣土豆丝',spu:'SPU20240007',cat:'小吃凉菜',price:14,stock:55,status:'on'},
{name:'皮蛋豆腐',spu:'SPU20240008',cat:'小吃凉菜',price:12,stock:40,status:'on'},
{name:'紫菜蛋花汤',spu:'SPU20240009',cat:'汤羹粥品',price:8,stock:90,status:'on'},
{name:'西湖牛肉羹',spu:'SPU20240010',cat:'汤羹粥品',price:16,stock:30,status:'on'},
{name:'冰柠檬红茶',spu:'SPU20240011',cat:'饮品',price:8,stock:120,status:'on'},
{name:'珍珠奶茶',spu:'SPU20240012',cat:'饮品',price:12,stock:80,status:'on'},
{name:'美式咖啡',spu:'SPU20240013',cat:'饮品',price:15,stock:60,status:'on'},
{name:'青岛啤酒',spu:'SPU20240014',cat:'酒水',price:10,stock:100,status:'on'},
{name:'可乐鸡翅',spu:'SPU20240015',cat:'小吃凉菜',price:22,stock:0,status:'off'},
{name:'红烧排骨',spu:'SPU20240016',cat:'热销推荐',price:38,stock:20,status:'on'},
{name:'蛋炒饭',spu:'SPU20240017',cat:'主食',price:12,stock:99,status:'on'},
{name:'酸梅汤',spu:'SPU20240018',cat:'饮品',price:6,stock:50,status:'on'},
];
var _ppCallback = null;
var _ppSelected = [];
var _ppExclude = [];
function openProductPicker(opts) {
opts = opts || {};
_ppCallback = opts.onConfirm || null;
_ppSelected = (opts.selected || []).slice();
_ppExclude = opts.exclude || [];
document.getElementById('ppTitle').textContent = opts.title || '选择商品';
document.getElementById('ppSubtitle').textContent = opts.subtitle || '';
document.getElementById('ppSearchInput').value = '';
document.getElementById('ppCatFilter').value = '';
renderPpProducts();
updatePpCount();
document.getElementById('ppMask').classList.add('open');
lucide.createIcons();
}
function closeProductPicker() {
document.getElementById('ppMask').classList.remove('open');
_ppCallback = null;
}
function renderPpProducts() {
var kw = (document.getElementById('ppSearchInput').value || '').trim().toLowerCase();
var cat = document.getElementById('ppCatFilter').value;
var tbody = document.getElementById('ppProductList');
var html = '';
_ppProducts.forEach(function(p) {
if (_ppExclude.indexOf(p.spu) > -1) return;
if (kw && p.name.toLowerCase().indexOf(kw) === -1 && p.spu.toLowerCase().indexOf(kw) === -1) return;
if (cat && p.cat !== cat) return;
var checked = _ppSelected.indexOf(p.spu) > -1;
html += '<tr class="' + (checked ? 'pp-checked' : '') + '" onclick="togglePpRow(this,\'' + p.spu + '\')" style="cursor:pointer">'
+ '<td><span class="pp-cb' + (checked ? ' checked' : '') + '"><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></td>'
+ '<td><div class="pp-prod-cell"><div class="pp-prod-thumb"><i data-lucide="image" style="width:16px;height:16px"></i></div><div><div class="pp-prod-name">' + p.name + '</div><div class="pp-prod-spu">' + p.spu + '</div></div></div></td>'
+ '<td class="pp-prod-cat">' + p.cat + '</td>'
+ '<td class="pp-prod-price">&yen;' + p.price.toFixed(2) + '</td>'
+ '<td>' + p.stock + '</td>'
+ '<td><span class="pp-prod-status ' + p.status + '">' + (p.status === 'on' ? '在售' : '下架') + '</span></td>'
+ '</tr>';
});
if (!html) {
html = '<tr><td colspan="6" style="text-align:center;padding:40px;color:#9ca3af">没有找到匹配的商品</td></tr>';
}
tbody.innerHTML = html;
syncPpAllCb();
lucide.createIcons();
}
function filterPpProducts() {
renderPpProducts();
}
function togglePpRow(tr, spu) {
var idx = _ppSelected.indexOf(spu);
if (idx > -1) {
_ppSelected.splice(idx, 1);
} else {
_ppSelected.push(spu);
}
var cb = tr.querySelector('.pp-cb');
cb.classList.toggle('checked');
tr.classList.toggle('pp-checked');
syncPpAllCb();
updatePpCount();
}
function togglePpAll() {
var allCb = document.getElementById('ppAllCb');
var rows = document.querySelectorAll('#ppProductList tr[onclick]');
var shouldCheck = !allCb.classList.contains('checked');
rows.forEach(function(tr) {
var spu = tr.getAttribute('onclick').match(/'([^']+)'/)[1];
var cb = tr.querySelector('.pp-cb');
var idx = _ppSelected.indexOf(spu);
if (shouldCheck && idx === -1) {
_ppSelected.push(spu);
cb.classList.add('checked');
tr.classList.add('pp-checked');
} else if (!shouldCheck && idx > -1) {
_ppSelected.splice(idx, 1);
cb.classList.remove('checked');
tr.classList.remove('pp-checked');
}
});
allCb.classList.toggle('checked', shouldCheck);
updatePpCount();
}
function syncPpAllCb() {
var rows = document.querySelectorAll('#ppProductList tr[onclick]');
var allChecked = rows.length > 0 && [...rows].every(function(tr) { return tr.classList.contains('pp-checked'); });
document.getElementById('ppAllCb').classList.toggle('checked', allChecked);
}
function updatePpCount() {
var n = _ppSelected.length;
document.getElementById('ppSelectedCount').textContent = '已选 ' + n + ' 个';
document.getElementById('ppFooterCount').textContent = n;
}
function confirmProductPicker() {
if (_ppCallback) {
var selected = _ppProducts.filter(function(p) { return _ppSelected.indexOf(p.spu) > -1; });
_ppCallback(selected);
}
closeProductPicker();
}
</script>
</body>
</html>