From f5432ba8b02aa6ac82332a3874c3ead67cc94b78 Mon Sep 17 00:00:00 2001 From: zzh Date: Fri, 12 Dec 2025 13:17:42 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=99=BB=E5=BD=95=E7=95=8C?= =?UTF-8?q?=E9=9D=A2,=E4=BF=AE=E6=94=B9=E4=BB=AA=E8=A1=A8=E7=9B=98?= =?UTF-8?q?=E5=86=85=E5=AE=B9,=E4=BF=AE=E5=A4=8D=E5=AE=A1=E8=AE=A1?= =?UTF-8?q?=E8=B6=8B=E5=8A=BF=E6=98=BE=E7=A4=BA=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/assets/login.css | 546 +++++++++++++----------- frontend/assets/styles.css | 65 ++- frontend/assets/tuxi-station.png | Bin 0 -> 45857 bytes frontend/index.html | 299 ++++--------- frontend/js/app.js | 84 ++-- frontend/js/components/dashboard.js | 214 ++++++++-- frontend/js/components/product-intro.js | 78 ++++ frontend/login.html | 166 ++++--- server/app.py | 105 ++++- 9 files changed, 943 insertions(+), 614 deletions(-) create mode 100644 frontend/assets/tuxi-station.png create mode 100644 frontend/js/components/product-intro.js diff --git a/frontend/assets/login.css b/frontend/assets/login.css index 2a62e8b..fbe5bc3 100644 --- a/frontend/assets/login.css +++ b/frontend/assets/login.css @@ -10,7 +10,8 @@ body { overflow: hidden; } -.login-container { +/* 背景包装器 */ +.login-wrapper { position: relative; width: 100vw; height: 100vh; @@ -18,21 +19,10 @@ body { align-items: center; justify-content: center; overflow: hidden; + background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%); } -/* 背景层 - 工业风格渐变 + 几何图案 */ -.login-background { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: - linear-gradient(135deg, #1e3a8a 0%, #1e40af 25%, #3b82f6 50%, #60a5fa 75%, #93c5fd 100%); - z-index: 0; -} - -.login-background::before { +.login-wrapper::before { content: ''; position: absolute; top: 0; @@ -44,53 +34,82 @@ body { 0deg, transparent, transparent 2px, - rgba(255, 255, 255, 0.03) 2px, - rgba(255, 255, 255, 0.03) 4px + rgba(255, 255, 255, 0.02) 2px, + rgba(255, 255, 255, 0.02) 4px ), repeating-linear-gradient( 90deg, transparent, transparent 2px, - rgba(255, 255, 255, 0.03) 2px, - rgba(255, 255, 255, 0.03) 4px + rgba(255, 255, 255, 0.02) 2px, + rgba(255, 255, 255, 0.02) 4px ); - background-size: 50px 50px; + background-size: 60px 60px; } -.login-background::after { +.login-wrapper::after { content: ''; position: absolute; - top: -50%; - right: -20%; - width: 80%; - height: 80%; - background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, transparent 70%); + top: -30%; + left: -20%; + width: 70%; + height: 70%; + background: radial-gradient(circle, rgba(255, 255, 255, 0.25) 0%, rgba(255, 200, 255, 0.1) 40%, transparent 70%); border-radius: 50%; - animation: float 20s ease-in-out infinite; + animation: float-bg 10s ease-in-out infinite; } -@keyframes float { +/* 额外的动态光斑 */ +.login-wrapper .container::before { + content: ''; + position: absolute; + bottom: -40%; + right: -30%; + width: 60%; + height: 60%; + background: radial-gradient(circle, rgba(102, 126, 234, 0.3) 0%, rgba(118, 75, 162, 0.15) 50%, transparent 70%); + border-radius: 50%; + animation: float-bg2 12s ease-in-out infinite; + z-index: -2; +} + +@keyframes float-bg { 0%, 100% { transform: translate(0, 0) scale(1); + opacity: 0.8; } 50% { - transform: translate(-50px, 50px) scale(1.1); + transform: translate(80px, 60px) scale(1.2); + opacity: 1; } } -/* 登录卡片 */ -.login-card { +@keyframes float-bg2 { + 0%, 100% { + transform: translate(0, 0) scale(1); + opacity: 0.7; + } + 50% { + transform: translate(-60px, -50px) scale(1.15); + opacity: 1; + } +} + +/* 主容器 - Uiverse.io 风格 */ +.container { + display: flex; + width: 620px; + height: 520px; + max-width: 95%; + align-items: center; + justify-content: center; position: relative; - z-index: 1; - background: rgba(255, 255, 255, 0.95); - backdrop-filter: blur(20px); - border-radius: 24px; - box-shadow: - 0 20px 60px rgba(0, 0, 0, 0.3), - 0 0 0 1px rgba(255, 255, 255, 0.1) inset; - padding: 48px; - width: 90%; - max-width: 440px; + overflow: hidden; + background-color: rgba(255, 255, 255, 0.15); + border-radius: 20px; + box-shadow: 0px 0px 40px rgba(0, 0, 0, 0.08); + border: 1px solid rgba(255, 255, 255, 0.25); + backdrop-filter: blur(10px); animation: slideUp 0.6s ease-out; } @@ -105,176 +124,158 @@ body { } } -/* 登录头部 */ -.login-header { +.container::after { + position: absolute; + content: ""; + width: 80%; + height: 80%; + right: -40%; + background: rgb(157, 173, 203); + background: radial-gradient( + circle, + rgba(157, 173, 203, 0.6) 61%, + rgba(99, 122, 159, 0.4) 100% + ); + border-radius: 50%; + z-index: -1; +} + +/* 左侧表单区域 */ +.left { + width: 60%; + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + padding: 40px; +} + +/* 表单头部 */ +.form-header { text-align: center; - margin-bottom: 40px; -} - -.logo-container { - display: flex; - align-items: center; - justify-content: center; - gap: 12px; - margin-bottom: 8px; -} - -.logo-icon { - width: 64px; - height: 64px; - display: flex; - align-items: center; - justify-content: center; - animation: float-icon 3s ease-in-out infinite; -} - -.logo-icon svg { - width: 100%; - height: 100%; - filter: drop-shadow(0 4px 8px rgba(59, 130, 246, 0.3)); -} - -/* 比特币旋转硬币 */ -.logo-icon.bitcoin-coin { - perspective: 500px; - animation: none; -} - -.logo-icon.bitcoin-coin .coin-face { - position: absolute; - width: 100%; - height: 100%; - border-radius: 50%; - backface-visibility: hidden; -} - -.logo-icon.bitcoin-coin .front { - transform: translateZ(2px); - animation: rotate-bitcoin 7s infinite linear; - transform-style: preserve-3d; -} - -.logo-icon.bitcoin-coin .back { - transform: rotateY(180deg) translateZ(2px); - animation: rotate-bitcoin 7s infinite linear; - transform-style: preserve-3d; -} - -.logo-icon.bitcoin-coin::before { - content: ''; - position: absolute; - width: 100%; - height: 100%; - background: linear-gradient(90deg, #b36a00, #faa504, #b36a00); - border-radius: 50%; - animation: rotate-bitcoin 7s infinite linear; - transform-style: preserve-3d; -} - -@keyframes rotate-bitcoin { - 0% { transform: rotateY(0deg); } - 100% { transform: rotateY(360deg); } -} - -@keyframes float-icon { - 0%, 100% { - transform: translateY(0) scale(1); - } - 50% { - transform: translateY(-5px) scale(1.02); - } + margin-bottom: 30px; } .system-title { - font-size: 28px; + font-size: 22px; font-weight: 700; - color: #1e3a8a; - margin: 0; + color: #ffffff; + margin: 0 0 6px 0; + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); } .system-subtitle { - font-size: 13px; - color: #64748b; + font-size: 12px; + color: rgba(255, 255, 255, 0.85); letter-spacing: 1px; text-transform: uppercase; margin: 0; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.15); } -/* 表单 */ -.login-form { - margin-bottom: 32px; -} - -.form-group { - margin-bottom: 24px; -} - -.form-group label { - display: block; - font-size: 14px; - font-weight: 600; - color: #334155; - margin-bottom: 8px; -} - -.input-wrapper { - position: relative; +/* 表单样式 */ +.form { display: flex; - align-items: center; -} - -.input-icon { - position: absolute; - left: 16px; - width: 20px; - height: 20px; - display: flex; - align-items: center; + flex-direction: column; justify-content: center; + width: 100%; + position: relative; +} + +.form::before { + position: absolute; + content: ""; + width: 50%; + height: 50%; + right: -10%; + top: 10%; + z-index: -1; pointer-events: none; + background: radial-gradient( + circle, + rgba(194, 13, 170, 0.3) 20%, + rgba(26, 186, 235, 0.2) 60%, + rgba(26, 186, 235, 0.1) 100% + ); + filter: blur(60px); + border-radius: 50%; +} + +/* 输入框块 */ +.input-block { + position: relative; + margin-bottom: 8px; z-index: 1; - color: #64748b; - transition: all 0.3s ease; } -.input-icon svg { - width: 100%; - height: 100%; -} - -.form-input { - width: 100%; - padding: 14px 16px 14px 48px; - font-size: 15px; - font-family: inherit; - color: #1e293b; - background: #f8fafc; - border: 2px solid #e2e8f0; - border-radius: 12px; - transition: all 0.3s ease; +.input, +button { + background: rgba(255, 255, 255, 0.95); outline: none; + border: 1px solid rgba(200, 200, 200, 0.4); + border-radius: 0.5rem; + padding: 12px 14px; + margin: 8px auto; + width: 100%; + display: block; + color: #2d3748; + font-weight: 500; + font-size: 1em; + font-family: inherit; + transition: all 0.3s ease; } -.form-input::placeholder { - color: #94a3b8; +.input-block label { + position: absolute; + left: 14px; + top: 37%; + pointer-events: none; + color: #5a6a7a; + font-size: 0.95em; + transition: all 0.4s ease; } -.form-input:focus { - background: #ffffff; - border-color: #3b82f6; - box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.1); +.input:focus + label, +.input:valid + label { + transform: translateY(-120%) scale(0.9); + color: #ffffff; + font-weight: 600; + text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); } -.input-wrapper:focus-within .input-icon { - color: #3b82f6; - transform: scale(1.1); +.input:focus { + background: rgba(255, 255, 255, 1); + border-color: #5e7eb6; + box-shadow: 0 0 0 3px rgba(94, 126, 182, 0.15); } -/* 验证码 */ -.captcha-wrapper { +.input { + box-shadow: inset 2px 2px 4px rgba(165, 163, 163, 0.15), + 2px 2px 4px rgba(218, 218, 218, 0.1); +} + +/* 验证码区域 */ +.captcha-block { display: flex; - gap: 12px; align-items: center; + gap: 12px; + flex-wrap: nowrap; +} + +.captcha-block .captcha-input { + flex: 1; + width: auto; + min-width: 0; +} + +.captcha-block label { + left: 14px; + top: 37%; +} + +.captcha-block .input:focus + label, +.captcha-block .input:valid + label { + transform: translateY(-120%) scale(0.9); } .captcha-image-wrapper { @@ -282,21 +283,25 @@ body { display: flex; align-items: center; justify-content: center; - width: 120px; + width: 110px; height: 48px; - background: #f8fafc; - border: 2px solid #e2e8f0; - border-radius: 12px; + background: rgba(255, 255, 255, 0.95); + border: 1px solid rgba(200, 200, 200, 0.4); + border-radius: 0.5rem; overflow: hidden; flex-shrink: 0; cursor: pointer; transition: all 0.3s ease; + margin-top: 0; + box-shadow: inset 2px 2px 4px rgba(165, 163, 163, 0.15), + 2px 2px 6px rgba(218, 218, 218, 0.15); } .captcha-image-wrapper:hover { - border-color: #3b82f6; - box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.1); - transform: scale(1.02); + border-color: #5e7eb6; + box-shadow: 0 0 0 3px rgba(94, 126, 182, 0.15), + inset 2px 2px 4px rgba(165, 163, 163, 0.1); + transform: scale(1.03); } .captcha-image-wrapper:active { @@ -304,63 +309,57 @@ body { } .captcha-image-wrapper.refreshing { - animation: rotate 0.3s ease; + animation: captcha-refresh 0.4s ease; } -@keyframes rotate { - from { - transform: rotate(0deg); - } - to { - transform: rotate(180deg); - } +@keyframes captcha-refresh { + 0% { transform: scale(1); opacity: 1; } + 50% { transform: scale(0.95); opacity: 0.7; } + 100% { transform: scale(1); opacity: 1; } } .captcha-image { width: 100%; height: 100%; - object-fit: cover; + object-fit: contain; display: block; pointer-events: none; + background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); } -/* 错误消息 */ -.error-message { - background: #fef2f2; - border: 1px solid #fecaca; - color: #dc2626; - padding: 12px 16px; - border-radius: 10px; - font-size: 14px; - margin-bottom: 20px; - animation: shake 0.4s ease; +/* 底部链接 */ +.forgot { + display: block; + margin: 5px 0 10px 0; + color: rgba(255, 255, 255, 0.9); + font-size: 0.85em; } -@keyframes shake { - 0%, 100% { transform: translateX(0); } - 25% { transform: translateX(-10px); } - 75% { transform: translateX(10px); } +.forgot a { + color: rgba(255, 255, 255, 0.9); + text-decoration: none; + transition: color 0.3s ease; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.15); +} + +.forgot a:hover { + color: #ffffff; } /* 登录按钮 */ -.login-btn { - width: 100%; - padding: 16px; - font-size: 16px; +button { + background-color: #5e7eb6; + color: white; + font-size: 1em; font-weight: 600; - font-family: inherit; - color: #ffffff; - background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); - border: none; - border-radius: 12px; + box-shadow: 2px 4px 12px rgba(94, 126, 182, 0.3); cursor: pointer; - transition: all 0.3s ease; - box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3); + border: none; position: relative; overflow: hidden; } -.login-btn::before { +button::before { content: ''; position: absolute; top: 0; @@ -371,25 +370,27 @@ body { transition: left 0.5s ease; } -.login-btn:hover { +button:hover { + background-color: #4a6da8; transform: translateY(-2px); - box-shadow: 0 6px 20px rgba(59, 130, 246, 0.4); + box-shadow: 2px 6px 16px rgba(94, 126, 182, 0.4); } -.login-btn:hover::before { +button:hover::before { left: 100%; } -.login-btn:active { +button:active { transform: translateY(0); } -.login-btn:disabled { +button:disabled { opacity: 0.7; cursor: not-allowed; transform: none; } +/* 加载动画 */ .btn-loader { display: flex; gap: 6px; @@ -424,47 +425,86 @@ body { } } -/* 页脚 */ -.login-footer { - text-align: center; - padding-top: 24px; - border-top: 1px solid #e2e8f0; +/* 错误消息 */ +.error-message { + background: rgba(254, 242, 242, 0.95); + border: 1px solid #fecaca; + color: #dc2626; + padding: 10px 14px; + border-radius: 8px; + font-size: 13px; + margin-bottom: 10px; + animation: shake 0.4s ease; } -.login-footer p { - font-size: 13px; - color: #64748b; - margin: 0; +@keyframes shake { + 0%, 100% { transform: translateX(0); } + 25% { transform: translateX(-8px); } + 75% { transform: translateX(8px); } +} + +/* 右侧插图区域 */ +.right { + width: 40%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + padding: 20px; +} + +.img { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} + +.img svg { + max-width: 100%; + max-height: 100%; + filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.1)); } /* 响应式设计 */ @media (max-width: 768px) { - .login-card { - padding: 32px 24px; - max-width: 100%; - margin: 20px; + .container { + flex-direction: column; + width: 95%; + height: auto; + max-height: 90vh; } - .system-title { - font-size: 24px; + .left { + width: 100%; + padding: 30px 25px; } - .logo-icon { - font-size: 40px; - } -} - -@media (max-width: 480px) { - .login-card { - padding: 24px 20px; + .right { + display: none; } .system-title { font-size: 20px; } - .logo-container { - flex-direction: column; - gap: 8px; + .container::after { + display: none; + } +} + +@media (max-width: 480px) { + .left { + padding: 25px 20px; + } + + .system-title { + font-size: 18px; + } + + .input, button { + padding: 10px 12px; + font-size: 0.95em; } } diff --git a/frontend/assets/styles.css b/frontend/assets/styles.css index 021458d..adebfc0 100644 --- a/frontend/assets/styles.css +++ b/frontend/assets/styles.css @@ -1167,6 +1167,8 @@ input[type="date"]::-webkit-calendar-picker-indicator:hover{ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); border-radius: 20px; border: 1px solid var(--border); + display: flex; + flex-direction: column; } .metrics-title { display: flex; @@ -1205,7 +1207,8 @@ input[type="date"]::-webkit-calendar-picker-indicator:hover{ .metrics-data { display: flex; flex-direction: column; - justify-content: flex-start; + justify-content: space-between; + flex: 1; } .metrics-value { margin: 0.75rem 0; @@ -1378,3 +1381,63 @@ input[type="date"]::-webkit-calendar-picker-indicator:hover{ animation: truck-road-anim 1.4s linear infinite; fill: #282828; } + +/* ==================== 顶部导航栏样式 ==================== */ +#app{flex-direction:column} +.topbar{display:flex;align-items:center;justify-content:space-between;height:56px;padding:0 20px;background:linear-gradient(90deg,var(--surface),var(--surface-2));border-bottom:1px solid var(--border);flex-shrink:0;z-index:100} +.topbar-left{display:flex;align-items:center;gap:24px} +.topbar-right{display:flex;align-items:center;gap:16px} +.brand{display:flex;align-items:center;gap:10px} +.brand-name{font-size:16px;font-weight:700;color:var(--text);white-space:nowrap} +.topnav{display:flex;align-items:center;gap:4px} +.topnav-item{position:relative;display:flex;align-items:center;gap:4px;padding:8px 14px;color:var(--text-2);text-decoration:none;font-size:14px;font-weight:500;border-radius:6px;cursor:pointer;transition:all 0.2s ease;white-space:nowrap} +.topnav-item:hover,.topnav-item.active{background:rgba(79,140,255,0.1);color:var(--text)} +.topnav-item.active{color:var(--primary);background:rgba(79,140,255,0.15)} +.topnav-caret{font-size:10px;transition:transform 0.2s ease} +.topnav-item.has-dropdown:hover .topnav-caret,.topnav-item.has-dropdown.open .topnav-caret{transform:rotate(180deg)} +.topnav-dropdown{position:absolute;top:100%;left:0;min-width:180px;background:var(--surface);border:1px solid var(--border);border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,0.3);padding:6px 0;opacity:0;visibility:hidden;transform:translateY(8px);transition:all 0.2s ease;z-index:1000} +.topnav-item.has-dropdown:hover .topnav-dropdown,.topnav-item.has-dropdown.open .topnav-dropdown{opacity:1;visibility:visible;transform:translateY(0)} +.dropdown-item{display:block;padding:10px 16px;color:var(--text-2);text-decoration:none;font-size:13px;transition:all 0.15s ease} +.dropdown-item:hover{background:rgba(79,140,255,0.1);color:var(--text)} +.dropdown-item.active{color:var(--primary);background:rgba(79,140,255,0.08)} +.content.topbar-layout{flex:1;display:flex;flex-direction:column;overflow:hidden;height:calc(100vh - 56px)} +.content.topbar-layout .view{flex:1;overflow:auto;padding:20px} +[data-theme="light"] .topnav-dropdown{box-shadow:0 8px 24px rgba(0,0,0,0.1)} +.topbar .bb8-toggle{transform:scale(0.45);transform-origin:center} +.topbar .notification-bell{position:relative;background:none;border:none;cursor:pointer;font-size:20px;padding:8px} +.topbar .user-avatar-btn{display:flex;align-items:center;gap:8px;background:none;border:none;cursor:pointer;padding:4px 8px;border-radius:20px;transition:background 0.2s} +.topbar .user-avatar-btn:hover{background:rgba(79,140,255,0.1)} +.topbar .user-avatar-img{width:32px;height:32px;border-radius:50%;object-fit:cover} +.topbar .user-name-display{color:var(--text);font-size:14px;font-weight:500} +.topbar .user-dropdown{position:absolute;top:100%;right:0;margin-top:8px;min-width:150px;background:var(--surface);border:1px solid var(--border);border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,0.3);z-index:1000} +.topbar .user-menu-container{position:relative} +.topbar-icon-btn{display:flex;align-items:center;justify-content:center;width:36px;height:36px;border-radius:8px;color:var(--text-2);text-decoration:none;transition:all 0.2s ease} +.topbar-icon-btn:hover{background:rgba(79,140,255,0.1);color:var(--text)} +.topbar-icon-btn svg{width:20px;height:20px} + +/* ==================== 产品介绍页面 ==================== */ +.product-intro-page{max-width:1000px;margin:0 auto;padding:20px} +.product-intro-header{text-align:center;margin-bottom:40px} +.product-intro-header h1{font-size:28px;font-weight:700;color:var(--text);margin-bottom:8px} +.product-intro-header .subtitle{font-size:16px;color:var(--text-2)} +.product-cards{display:flex;flex-direction:column;gap:24px} +.product-card{background:var(--surface);border:1px solid var(--border);border-radius:16px;padding:32px;display:flex;gap:32px;align-items:center;flex-wrap:wrap} +.product-card.main-product{flex-direction:row} +.product-image-wrapper{flex:0 0 300px;display:flex;justify-content:center;align-items:center;background:linear-gradient(135deg,#f8fafc,#e2e8f0);border-radius:12px;padding:24px;min-height:300px} +[data-theme="dark"] .product-image-wrapper{background:linear-gradient(135deg,#1e293b,#334155)} +.product-image{max-width:100%;max-height:280px;object-fit:contain;border-radius:8px} +.product-info{flex:1;min-width:280px} +.product-info h2{font-size:24px;font-weight:600;color:var(--text);margin-bottom:12px} +.product-desc{font-size:15px;color:var(--text-2);line-height:1.7;margin-bottom:24px} +.product-features{display:grid;grid-template-columns:repeat(2,1fr);gap:16px} +.feature-item{display:flex;align-items:center;gap:10px;padding:12px 16px;background:var(--bg);border-radius:10px;transition:all 0.2s} +.feature-item:hover{background:rgba(79,140,255,0.1);transform:translateX(4px)} +.feature-icon{font-size:20px} +.feature-text{font-size:14px;color:var(--text);font-weight:500} +.product-specs{margin-top:32px;background:var(--surface);border:1px solid var(--border);border-radius:16px;padding:24px} +.product-specs h3{font-size:18px;font-weight:600;color:var(--text);margin-bottom:20px} +.specs-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px} +.spec-item{display:flex;justify-content:space-between;padding:12px 16px;background:var(--bg);border-radius:8px} +.spec-label{font-size:14px;color:var(--text-2)} +.spec-value{font-size:14px;color:var(--text);font-weight:500} +@media(max-width:768px){.product-card{flex-direction:column}.product-image-wrapper{flex:none;width:100%}.product-features{grid-template-columns:1fr}} diff --git a/frontend/assets/tuxi-station.png b/frontend/assets/tuxi-station.png new file mode 100644 index 0000000000000000000000000000000000000000..82768b25ccb1a62ab7db7554c881371d28e5b856 GIT binary patch literal 45857 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

}A@IQ|DXg}b3$O^y{T*|~YA0_X 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

3Suz(T|Z%^e#DlHl;2oD7pNZd011%2FdPs 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 GzyAk~(gmjg literal 0 HcmV?d00001 diff --git a/frontend/index.html b/frontend/index.html index d313784..11d77b8 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -13,201 +13,84 @@

- -
-
- -
- +
+ -
- - -
+
+ +
@@ -316,6 +198,7 @@ + \ No newline at end of file diff --git a/frontend/js/app.js b/frontend/js/app.js index f017659..31dcde2 100644 --- a/frontend/js/app.js +++ b/frontend/js/app.js @@ -32,27 +32,7 @@ // 根据用户角色控制菜单显示 function updateMenuVisibility(user) { - // 查找计划管理菜单组(包含标题"计划管理"的nav-group) - const navGroups = document.querySelectorAll('.nav-group'); - let planMgmtGroup = null; - - navGroups.forEach(group => { - const title = group.querySelector('.nav-group-title'); - if (title && title.textContent.trim() === '计划管理') { - planMgmtGroup = group; - } - }); - - if (planMgmtGroup) { - // 只有超级管理员可以看到计划管理菜单 - if (user && user.role === 'superadmin') { - planMgmtGroup.style.display = ''; - } else { - planMgmtGroup.style.display = 'none'; - } - } - - // 超级管理员专属菜单(系统管理) + // 超级管理员专属菜单 const superadminMenus = document.querySelectorAll('.superadmin-only'); superadminMenus.forEach(menu => { if (user && user.role === 'superadmin') { @@ -61,6 +41,37 @@ menu.style.display = 'none'; } }); + + // 计划管理菜单(顶部导航) + const planMenu = document.querySelector('[data-menu="plan"]'); + if (planMenu) { + if (user && user.role === 'superadmin') { + planMenu.style.display = ''; + } else { + planMenu.style.display = 'none'; + } + } + } + + // 高亮当前活动的导航项 + function updateActiveNav(path) { + // 移除所有活动状态 + document.querySelectorAll('.topnav-item').forEach(item => item.classList.remove('active')); + document.querySelectorAll('.dropdown-item').forEach(item => item.classList.remove('active')); + + // 查找匹配的导航项 + const route = path.replace('#', '').replace('/', ''); + const activeItem = document.querySelector(`.topnav-item[data-route="${route}"]`) || + document.querySelector(`.dropdown-item[data-route="${route}"]`); + + if (activeItem) { + activeItem.classList.add('active'); + // 如果是下拉菜单项,也高亮父菜单 + const parentDropdown = activeItem.closest('.topnav-item.has-dropdown'); + if (parentDropdown) { + parentDropdown.classList.add('active'); + } + } } // 创建水印 @@ -127,6 +138,9 @@ // 根据用户角色控制菜单显示 updateMenuVisibility(currentUser); + // 高亮当前导航项 + updateActiveNav(path); + // 初始化通知系统(超级管理员和管理员) if (currentUser && (currentUser.role === 'superadmin' || currentUser.role === 'admin') && window.NotificationSystem) { window.NotificationSystem.init(); @@ -186,11 +200,29 @@ .catch(() => {}); }); - // 菜单切换 - const toggleBtn = document.getElementById('menu-toggle'); - const sidebar = document.querySelector('.sidebar'); - toggleBtn?.addEventListener('click', () => { - sidebar.classList.toggle('open'); + // 顶部导航下拉菜单点击切换(移动端或点击常显示) + document.querySelectorAll('.topnav-item.has-dropdown').forEach(item => { + item.addEventListener('click', (e) => { + // 如果点击的是下拉菜单内的链接,不处理 + if (e.target.closest('.topnav-dropdown')) return; + + // 切换open状态 + const isOpen = item.classList.contains('open'); + // 关闭其他打开的菜单 + document.querySelectorAll('.topnav-item.has-dropdown.open').forEach(other => { + if (other !== item) other.classList.remove('open'); + }); + item.classList.toggle('open', !isOpen); + }); + }); + + // 点击页面其他地方关闭下拉菜单 + document.addEventListener('click', (e) => { + if (!e.target.closest('.topnav-item.has-dropdown')) { + document.querySelectorAll('.topnav-item.has-dropdown.open').forEach(item => { + item.classList.remove('open'); + }); + } }); // 主题切换 diff --git a/frontend/js/components/dashboard.js b/frontend/js/components/dashboard.js index e1f1a16..7f7c285 100644 --- a/frontend/js/components/dashboard.js +++ b/frontend/js/components/dashboard.js @@ -5,9 +5,68 @@ const Dashboard = (() => { '直通良品率': '', '良品率': '', '发货数量': truckIcon, - '不良数量': '' + '不良数量': '', + '良品/不良率': '', + '今日产量': '' }; + // 合并良品率/不良率卡片 + function rateCard(goodRate, badRate) { + const icon = metricsIcons['良品/不良率']; + const goodNum = parseFloat(String(goodRate).replace('%', '')) || 0; + const badNum = parseFloat(String(badRate).replace('%', '')) || 0; + + return `
+
+ + ${icon} + +

良品/不良率

+

+ + + +

+
+
+

${goodRate}/${badRate}

+
+
+
+
+
`; + } + + // 今日产量卡片(拼多多/圆通自动切换) + function todayProductionCard(todayPdd, todayYt, activePlatform) { + const isPdd = activePlatform === 'pdd'; + const platformName = isPdd ? '拼多多' : '圆通'; + const platformIcon = isPdd ? '' : ''; + const value = isPdd ? todayPdd : todayYt; + const color = isPdd ? { bg: '#e02e24', text: '#e02e24' } : { bg: '#1a6dd6', text: '#1a6dd6' }; + const fillWidth = Math.min((value / 10000) * 100, 100); + + return `
+
+ + ${platformIcon} + +

今日${platformName}

+

+ + + +

+
+
+

${value}

+
+
+
+
+
`; + } + function metricsCard(title, value, badgeClass) { const colors = { success: { bg: '#10B981', text: '#02972f' }, @@ -290,7 +349,10 @@ const Dashboard = (() => { const maxCount = Math.max(...pddCounts, ...ytCounts, 1); // 绘制参数(使用逻辑尺寸而非物理像素) - const padding = {left: 40, right: 20, top: 20, bottom: 30}; + // 根据最大值动态调整左边距,确保Y轴标签不被截断 + const maxDigits = Math.max(maxCount.toString().length, 2); + const dynamicLeftPadding = Math.max(50, 25 + maxDigits * 10); + const padding = {left: dynamicLeftPadding, right: 20, top: 20, bottom: 30}; const chartWidth = rect.width - padding.left - padding.right; const chartHeight = rect.height - padding.top - padding.bottom; @@ -340,52 +402,59 @@ const Dashboard = (() => { } }); - // 绘制折线 + // 绘制柔和曲线(智能避免标签重叠) const today = new Date().toISOString().split('T')[0]; - const drawLine = (counts, color) => { + + // 收集今日标签位置 + const todayLabels = []; + const drawLineWithLabels = (counts, color, labelKey) => { ctx.strokeStyle = color; - ctx.lineWidth = 2; + ctx.lineWidth = 2.5; + ctx.lineCap = 'round'; + ctx.lineJoin = 'round'; ctx.beginPath(); - counts.forEach((count, i) => { - const x = padding.left + (chartWidth / (timePoints.length - 1)) * i; - const y = padding.top + chartHeight - (count / maxCount) * chartHeight; - if(i === 0) ctx.moveTo(x, y); - else ctx.lineTo(x, y); - }); + + const points = counts.map((count, i) => ({ + x: padding.left + (chartWidth / (timePoints.length - 1)) * i, + y: padding.top + chartHeight - (count / maxCount) * chartHeight + })); + + ctx.moveTo(points[0].x, points[0].y); + for(let i = 1; i < points.length; i++) { + const prev = points[i - 1]; + const curr = points[i]; + const next = points[i + 1] || curr; + const prevPrev = points[i - 2] || prev; + const tension = 0.3; + const cp1x = prev.x + (curr.x - prevPrev.x) * tension; + const cp1y = prev.y + (curr.y - prevPrev.y) * tension; + const cp2x = curr.x - (next.x - prev.x) * tension; + const cp2y = curr.y - (next.y - prev.y) * tension; + ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, curr.x, curr.y); + } ctx.stroke(); - // 绘制数据点 + // 绘制数据点并收集今日标签 counts.forEach((count, i) => { const x = padding.left + (chartWidth / (timePoints.length - 1)) * i; const y = padding.top + chartHeight - (count / maxCount) * chartHeight; const isToday = timeRange === 'day' && timePoints[i] === today; if(isToday) { - // 今日数据点:更大的圆圈 + 外圈光晕 ctx.beginPath(); ctx.arc(x, y, 10, 0, Math.PI * 2); - ctx.fillStyle = color.replace(')', ', 0.2)').replace('rgb', 'rgba').replace('#', ''); - ctx.fillStyle = color + '33'; // 添加透明度 + ctx.fillStyle = color + '33'; ctx.fill(); - ctx.beginPath(); ctx.arc(x, y, 6, 0, Math.PI * 2); ctx.fillStyle = color; ctx.fill(); - - // 白色内圈 ctx.beginPath(); ctx.arc(x, y, 3, 0, Math.PI * 2); ctx.fillStyle = '#fff'; ctx.fill(); - - // 显示今日数值标签 - ctx.fillStyle = color; - ctx.font = 'bold 12px sans-serif'; - ctx.textAlign = 'center'; - ctx.fillText(count.toString(), x, y - 14); + todayLabels.push({x, y, count, color, key: labelKey}); } else { - // 普通数据点 ctx.beginPath(); ctx.arc(x, y, 3, 0, Math.PI * 2); ctx.fillStyle = color; @@ -394,8 +463,42 @@ const Dashboard = (() => { }); }; - drawLine(pddCounts, '#f59e0b'); - drawLine(ytCounts, '#3b82f6'); + drawLineWithLabels(pddCounts, '#f59e0b', 'pdd'); + drawLineWithLabels(ytCounts, '#3b82f6', 'yt'); + + // 智能绘制今日标签,显示在高亮点的左边(数字为0时不显示) + const nonZeroLabels = todayLabels.filter(label => label.count > 0); + if(nonZeroLabels.length === 2) { + const [a, b] = nonZeroLabels; + const yDiff = Math.abs(a.y - b.y); + + if(yDiff < 20) { + // 两个标签太近,垂直错开显示在左边 + const upper = a.y < b.y ? a : b; + const lower = a.y < b.y ? b : a; + ctx.font = 'bold 12px sans-serif'; + ctx.textAlign = 'right'; + ctx.fillStyle = upper.color; + ctx.fillText(upper.count.toString(), upper.x - 14, upper.y - 8); + ctx.fillStyle = lower.color; + ctx.fillText(lower.count.toString(), lower.x - 14, lower.y + 8); + } else { + // 正常显示在左边 + nonZeroLabels.forEach(label => { + ctx.font = 'bold 12px sans-serif'; + ctx.textAlign = 'right'; + ctx.fillStyle = label.color; + ctx.fillText(label.count.toString(), label.x - 14, label.y + 4); + }); + } + } else { + nonZeroLabels.forEach(label => { + ctx.font = 'bold 12px sans-serif'; + ctx.textAlign = 'right'; + ctx.fillStyle = label.color; + ctx.fillText(label.count.toString(), label.x - 14, label.y + 4); + }); + } // 绘制图例 ctx.font = '12px sans-serif'; @@ -639,7 +742,7 @@ const Dashboard = (() => { try { // 请求全部数据(不限制),传递AbortController信号 const signal = window.__auditAbortController.signal; - const [pddRes, ytRes] = await Promise.all([ + const [pddRes, ytRes, dashRes] = await Promise.all([ fetch('/api/audit/pdd', { headers: { 'Content-Type': 'application/json' }, credentials: 'include', @@ -655,9 +758,60 @@ const Dashboard = (() => { }).then(r => r.ok ? r.json() : {list:[]}).catch((e)=>{ if(e.name === 'AbortError') console.log('[Dashboard] YT请求被取消'); return {list:[]}; + }), + fetch('/api/dashboard', { + headers: { 'Content-Type': 'application/json' }, + credentials: 'include', + signal + }).then(r => r.ok ? r.json() : null).catch((e)=>{ + if(e.name === 'AbortError') console.log('[Dashboard] Dashboard请求被取消'); + return null; }) ]); + // 实时更新良品/不良率卡片和今日产量卡片 + if(dashRes) { + // 更新良品/不良率 + const goodRateEl = document.getElementById('good-rate-value'); + const badRateEl = document.getElementById('bad-rate-value'); + if(goodRateEl) goodRateEl.textContent = dashRes.goodRate || '—'; + if(badRateEl) badRateEl.textContent = dashRes.badRate || '—'; + + // 更新今日产量卡片 + const productionCard = document.getElementById('today-production-card'); + const platformNameEl = document.getElementById('today-platform-name'); + const productionValueEl = document.getElementById('today-production-value'); + const productionFillEl = document.getElementById('today-production-fill'); + + if(productionCard && platformNameEl && productionValueEl && productionFillEl) { + const currentPlatform = productionCard.dataset.platform; + const newPlatform = dashRes.activePlatform || 'pdd'; + const isPdd = newPlatform === 'pdd'; + const value = isPdd ? (dashRes.todayPdd || 0) : (dashRes.todayYt || 0); + const platformName = isPdd ? '拼多多' : '圆通'; + const color = isPdd ? '#e02e24' : '#1a6dd6'; + const fillWidth = Math.min((value / 10000) * 100, 100); + + // 如果平台切换了,更新图标 + if(currentPlatform !== newPlatform) { + productionCard.dataset.platform = newPlatform; + const iconEl = productionCard.querySelector('.metrics-icon'); + if(iconEl) { + iconEl.innerHTML = isPdd + ? '' + : ''; + } + const percentEl = productionCard.querySelector('.metrics-percent'); + if(percentEl) percentEl.style.color = color; + } + + platformNameEl.textContent = '今日' + platformName; + productionValueEl.textContent = value; + productionFillEl.style.width = fillWidth + '%'; + productionFillEl.style.backgroundColor = color; + } + } + clearTimeout(timeoutId); const duration = Date.now() - startTime; if(duration > 3000){ @@ -733,9 +887,9 @@ const Dashboard = (() => {
${metricsCard('直通良品率', data.fpyRate || '—', 'success')} - ${metricsCard('良品率', data.goodRate, 'success')} + ${rateCard(data.goodRate || '—', data.badRate || '—')} ${metricsCard('发货数量', data.shipments, 'warning')} - ${metricsCard('不良数量', data.badCount ?? data.defects, 'danger')} + ${todayProductionCard(data.todayPdd || 0, data.todayYt || 0, data.activePlatform || 'pdd')}
diff --git a/frontend/js/components/product-intro.js b/frontend/js/components/product-intro.js new file mode 100644 index 0000000..c24d34b --- /dev/null +++ b/frontend/js/components/product-intro.js @@ -0,0 +1,78 @@ +// 产品介绍页面 +Router.register('/product-intro', async () => { + setTimeout(() => { + if(window.ProductIntroComponent.afterRender) { + window.ProductIntroComponent.afterRender(); + } + }, 0); + return window.ProductIntroComponent.render(); +}); + +window.ProductIntroComponent = { + render: () => { + return ` +
+
+

产品介绍

+

兔喜驿站智能找灯系统

+
+ +
+
+
+ 兔喜基站 +
+
+

智能基站

+

兔喜驿站找灯条对应快递的基站设备,用于控制货架上的LED灯条,帮助快速定位快递包裹位置。

+
+
+ 📡 + 蓝牙信号覆盖 +
+
+ 💡 + 精准灯条控制 +
+
+ + 快速响应 +
+
+ 🔧 + 易于安装 +
+
+
+
+
+ +
+

技术规格

+
+
+ 品牌 + 兔喜生活 +
+
+ 类型 + 智能基站 +
+
+ 应用场景 + 快递驿站 +
+
+ 功能 + 灯条定位控制 +
+
+
+
+ `; + }, + + afterRender: () => { + // 页面渲染后的逻辑 + } +}; diff --git a/frontend/login.html b/frontend/login.html index 3190787..89c1b96 100644 --- a/frontend/login.html +++ b/frontend/login.html @@ -11,109 +11,87 @@ -