1501 lines
79 KiB
HTML
1501 lines
79 KiB
HTML
<!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">¥' + 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>
|