修改登录界面,修改仪表盘内容,修复审计趋势显示错误

This commit is contained in:
zzh 2025-12-12 13:17:42 +08:00
parent 2152b13e97
commit f5432ba8b0
9 changed files with 943 additions and 614 deletions

View File

@ -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;
}
}

View File

@ -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}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -13,201 +13,84 @@
</head>
<body>
<div id="app">
<aside class="sidebar" id="sidebar">
<div class="brand-container">
<div class="loader-wrapper">
<!-- 顶部导航栏 -->
<header class="topbar" id="topbar">
<div class="topbar-left">
<div class="brand">
<div class="coin-wrapper">
<div class="coin">
<div class="side heads">
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="100%" height="100%" version="1.1" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd" viewBox="0 0 4091.27 4091.73">
<g id="Layer_x0020_1">
<path fill="#F7931A" fill-rule="nonzero" d="M4030.06 2540.77c-273.24,1096.01 -1383.32,1763.02 -2479.46,1489.71 -1095.68,-273.24 -1762.69,-1383.39 -1489.33,-2479.31 273.12,-1096.13 1383.2,-1763.19 2479,-1489.95 1096.06,273.24 1763.03,1383.51 1489.76,2479.57l0.02 -0.02z"></path>
<path fill="white" fill-rule="nonzero" d="M2947.77 1754.38c40.72,-272.26 -166.56,-418.61 -450,-516.24l91.95 -368.8 -224.5 -55.94 -89.51 359.09c-59.02,-14.72 -119.63,-28.59 -179.87,-42.34l90.16 -361.46 -224.36 -55.94 -92 368.68c-48.84,-11.12 -96.81,-22.11 -143.35,-33.69l0.26 -1.16 -309.59 -77.31 -59.72 239.78c0,0 166.56,38.18 163.05,40.53 90.91,22.69 107.35,82.87 104.62,130.57l-104.74 420.15c6.26,1.59 14.38,3.89 23.34,7.49 -7.49,-1.86 -15.46,-3.89 -23.73,-5.87l-146.81 588.57c-11.11,27.62 -39.31,69.07 -102.87,53.33 2.25,3.26 -163.17,-40.72 -163.17,-40.72l-111.46 256.98 292.15 72.83c54.35,13.63 107.61,27.89 160.06,41.3l-92.9 373.03 224.24 55.94 92 -369.07c61.26,16.63 120.71,31.97 178.91,46.43l-91.69 367.33 224.51 55.94 92.89 -372.33c382.82,72.45 670.67,43.24 791.83,-303.02 97.63,-278.78 -4.86,-439.58 -206.26,-544.44 146.69,-33.83 257.18,-130.31 286.64,-329.61l-0.07 -0.05zm-512.93 719.26c-69.38,278.78 -538.76,128.08 -690.94,90.29l123.28 -494.2c152.17,37.99 640.17,113.17 567.67,403.91zm69.43 -723.3c-63.29,253.58 -453.96,124.75 -580.69,93.16l111.77 -448.21c126.73,31.59 534.85,90.55 468.94,355.05l-0.02 0z"></path>
</g>
</svg>
</div>
<div class="side tails">
<svg xmlns="http://www.w3.org/2000/svg" class="svg_back" xml:space="preserve" width="100%" height="100%" version="1.1" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd" viewBox="0 0 4091.27 4091.73">
<g id="Layer_x0020_1">
<path fill="#F7931A" fill-rule="nonzero" d="M4030.06 2540.77c-273.24,1096.01 -1383.32,1763.02 -2479.46,1489.71 -1095.68,-273.24 -1762.69,-1383.39 -1489.33,-2479.31 273.12,-1096.13 1383.2,-1763.19 2479,-1489.95 1096.06,273.24 1763.03,1383.51 1489.76,2479.57l0.02 -0.02z"></path>
<path fill="white" fill-rule="nonzero" d="M2947.77 1754.38c40.72,-272.26 -166.56,-418.61 -450,-516.24l91.95 -368.8 -224.5 -55.94 -89.51 359.09c-59.02,-14.72 -119.63,-28.59 -179.87,-42.34l90.16 -361.46 -224.36 -55.94 -92 368.68c-48.84,-11.12 -96.81,-22.11 -143.35,-33.69l0.26 -1.16 -309.59 -77.31 -59.72 239.78c0,0 166.56,38.18 163.05,40.53 90.91,22.69 107.35,82.87 104.62,130.57l-104.74 420.15c6.26,1.59 14.38,3.89 23.34,7.49 -7.49,-1.86 -15.46,-3.89 -23.73,-5.87l-146.81 588.57c-11.11,27.62 -39.31,69.07 -102.87,53.33 2.25,3.26 -163.17,-40.72 -163.17,-40.72l-111.46 256.98 292.15 72.83c54.35,13.63 107.61,27.89 160.06,41.3l-92.9 373.03 224.24 55.94 92 -369.07c61.26,16.63 120.71,31.97 178.91,46.43l-91.69 367.33 224.51 55.94 92.89 -372.33c382.82,72.45 670.67,43.24 791.83,-303.02 97.63,-278.78 -4.86,-439.58 -206.26,-544.44 146.69,-33.83 257.18,-130.31 286.64,-329.61l-0.07 -0.05zm-512.93 719.26c-69.38,278.78 -538.76,128.08 -690.94,90.29l123.28 -494.2c152.17,37.99 640.17,113.17 567.67,403.91zm69.43 -723.3c-63.29,253.58 -453.96,124.75 -580.69,93.16l111.77 -448.21c126.73,31.59 534.85,90.55 468.94,355.05l-0.02 0z"></path>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 4091.27 4091.73" width="100%" height="100%">
<path fill="#F7931A" d="M4030.06 2540.77c-273.24,1096.01 -1383.32,1763.02 -2479.46,1489.71 -1095.68,-273.24 -1762.69,-1383.39 -1489.33,-2479.31 273.12,-1096.13 1383.2,-1763.19 2479,-1489.95 1096.06,273.24 1763.03,1383.51 1489.76,2479.57l0.02 -0.02z"></path>
<path fill="white" d="M2947.77 1754.38c40.72,-272.26 -166.56,-418.61 -450,-516.24l91.95 -368.8 -224.5 -55.94 -89.51 359.09c-59.02,-14.72 -119.63,-28.59 -179.87,-42.34l90.16 -361.46 -224.36 -55.94 -92 368.68c-48.84,-11.12 -96.81,-22.11 -143.35,-33.69l0.26 -1.16 -309.59 -77.31 -59.72 239.78c0,0 166.56,38.18 163.05,40.53 90.91,22.69 107.35,82.87 104.62,130.57l-104.74 420.15c6.26,1.59 14.38,3.89 23.34,7.49 -7.49,-1.86 -15.46,-3.89 -23.73,-5.87l-146.81 588.57c-11.11,27.62 -39.31,69.07 -102.87,53.33 2.25,3.26 -163.17,-40.72 -163.17,-40.72l-111.46 256.98 292.15 72.83c54.35,13.63 107.61,27.89 160.06,41.3l-92.9 373.03 224.24 55.94 92 -369.07c61.26,16.63 120.71,31.97 178.91,46.43l-91.69 367.33 224.51 55.94 92.89 -372.33c382.82,72.45 670.67,43.24 791.83,-303.02 97.63,-278.78 -4.86,-439.58 -206.26,-544.44 146.69,-33.83 257.18,-130.31 286.64,-329.61l-0.07 -0.05zm-512.93 719.26c-69.38,278.78 -538.76,128.08 -690.94,90.29l123.28 -494.2c152.17,37.99 640.17,113.17 567.67,403.91zm69.43 -723.3c-63.29,253.58 -453.96,124.75 -580.69,93.16l111.77 -448.21c126.73,31.59 534.85,90.55 468.94,355.05l-0.02 0z"></path>
</svg>
</div>
<div class="side tails">
<svg xmlns="http://www.w3.org/2000/svg" class="svg_back" viewBox="0 0 4091.27 4091.73" width="100%" height="100%">
<path fill="#F7931A" d="M4030.06 2540.77c-273.24,1096.01 -1383.32,1763.02 -2479.46,1489.71 -1095.68,-273.24 -1762.69,-1383.39 -1489.33,-2479.31 273.12,-1096.13 1383.2,-1763.19 2479,-1489.95 1096.06,273.24 1763.03,1383.51 1489.76,2479.57l0.02 -0.02z"></path>
<path fill="white" d="M2947.77 1754.38c40.72,-272.26 -166.56,-418.61 -450,-516.24l91.95 -368.8 -224.5 -55.94 -89.51 359.09c-59.02,-14.72 -119.63,-28.59 -179.87,-42.34l90.16 -361.46 -224.36 -55.94 -92 368.68c-48.84,-11.12 -96.81,-22.11 -143.35,-33.69l0.26 -1.16 -309.59 -77.31 -59.72 239.78c0,0 166.56,38.18 163.05,40.53 90.91,22.69 107.35,82.87 104.62,130.57l-104.74 420.15c6.26,1.59 14.38,3.89 23.34,7.49 -7.49,-1.86 -15.46,-3.89 -23.73,-5.87l-146.81 588.57c-11.11,27.62 -39.31,69.07 -102.87,53.33 2.25,3.26 -163.17,-40.72 -163.17,-40.72l-111.46 256.98 292.15 72.83c54.35,13.63 107.61,27.89 160.06,41.3l-92.9 373.03 224.24 55.94 92 -369.07c61.26,16.63 120.71,31.97 178.91,46.43l-91.69 367.33 224.51 55.94 92.89 -372.33c382.82,72.45 670.67,43.24 791.83,-303.02 97.63,-278.78 -4.86,-439.58 -206.26,-544.44 146.69,-33.83 257.18,-130.31 286.64,-329.61l-0.07 -0.05zm-512.93 719.26c-69.38,278.78 -538.76,128.08 -690.94,90.29l123.28 -494.2c152.17,37.99 640.17,113.17 567.67,403.91zm69.43 -723.3c-63.29,253.58 -453.96,124.75 -580.69,93.16l111.77 -448.21c126.73,31.59 534.85,90.55 468.94,355.05l-0.02 0z"></path>
</svg>
</div>
</div>
</div>
<span class="brand-name">韬智生产管理</span>
</div>
<div class="brand-text">
<span class="brand-title">韬智生产管理</span>
<span class="brand-subtitle">Production System</span>
<nav class="topnav" id="topnav">
<a href="#/dashboard" class="topnav-item" data-route="dashboard">仪表盘</a>
<div class="topnav-item has-dropdown" data-menu="upload">
<span class="topnav-text">上传</span>
<span class="topnav-caret"></span>
<div class="topnav-dropdown">
<a href="#/upload/mac" class="dropdown-item" data-route="upload-mac">MAC与批次</a>
<a href="#/upload/stats" class="dropdown-item" data-route="upload-stats">良/不良统计</a>
<a href="#/upload/repairs" class="dropdown-item" data-route="upload-repairs">返修记录</a>
<a href="#/upload/shipments" class="dropdown-item" data-route="upload-shipments">发货记录</a>
<a href="#/upload/sop" class="dropdown-item" data-route="upload-sop">SOP</a>
</div>
</div>
</div>
<button id="sidebar-toggle" class="sidebar-toggle" title="收起菜单">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="11 17 6 12 11 7"></polyline>
<polyline points="18 17 13 12 18 7"></polyline>
</svg>
</button>
<div class="topnav-item has-dropdown" data-menu="query">
<span class="topnav-text">出货查询</span>
<span class="topnav-caret"></span>
<div class="topnav-dropdown">
<a href="#/shipments/query" class="dropdown-item" data-route="shipments-query">详细记录查询</a>
<a href="#/shipments/summary" class="dropdown-item" data-route="shipments-summary">汇总信息查询</a>
</div>
</div>
<div class="topnav-item has-dropdown" data-menu="production">
<span class="topnav-text">生产管理</span>
<span class="topnav-caret"></span>
<div class="topnav-dropdown">
<a href="#/production-mgmt/work-order" class="dropdown-item" data-route="production-mgmt-work-order">生产工单下发中心</a>
</div>
</div>
<div class="topnav-item has-dropdown" data-menu="plan">
<span class="topnav-text">计划管理</span>
<span class="topnav-caret"></span>
<div class="topnav-dropdown">
<a href="#/plan-mgmt/bom" class="dropdown-item" data-route="plan-mgmt-bom">BOM物料清单</a>
<a href="#/plan-mgmt/initial-stock" class="dropdown-item" data-route="plan-mgmt-initial-stock">期初库存</a>
<a href="#/plan-mgmt/purchase-demand" class="dropdown-item" data-route="plan-mgmt-purchase-demand">采购需求清单</a>
<a href="#/plan-mgmt/customer-order" class="dropdown-item" data-route="plan-mgmt-customer-order">客户订单</a>
<a href="#/plan-mgmt/reconciliation" class="dropdown-item" data-route="plan-mgmt-reconciliation">对账单</a>
</div>
</div>
<div class="topnav-item has-dropdown" data-menu="collect">
<span class="topnav-text">采集</span>
<span class="topnav-caret"></span>
<div class="topnav-dropdown">
<a href="#/devices" class="dropdown-item" data-route="devices">设备状态</a>
<a href="#/environment" class="dropdown-item" data-route="environment">环境参数</a>
<a href="#/personnel" class="dropdown-item" data-route="personnel">人员信息</a>
<a href="#/qa" class="dropdown-item" data-route="qa">质检报告</a>
<a href="#/production" class="dropdown-item" data-route="production">时间记录</a>
</div>
</div>
<a href="#/export" class="topnav-item" data-route="export">导出</a>
<a href="#/product-intro" class="topnav-item" data-route="product-intro">产品</a>
<a href="#/system/operations-log" class="topnav-item superadmin-only" data-route="system-operations-log" style="display:none;">操作日志</a>
</nav>
</div>
<nav class="nav">
<div class="nav-group">
<div class="nav-group-title">概览</div>
<a href="#/dashboard" class="nav-item" data-route="dashboard">
<span class="icon icon-dashboard"></span>
<span class="text">仪表盘</span>
</a>
</div>
<div class="nav-group">
<div class="nav-group-title">数据上传</div>
<div class="nav-item has-children" data-expand="upload">
<button class="nav-item-btn">
<span class="icon">⬆️</span>
<span class="text">上传</span>
<span class="caret"></span>
</button>
<div class="nav-children" data-parent="upload">
<a href="#/upload/mac" class="nav-child" data-route="upload-mac">
<span class="child-icon">📱</span>
<span>MAC与批次</span>
</a>
<a href="#/upload/stats" class="nav-child" data-route="upload-stats">
<span class="child-icon">📈</span>
<span>良/不良统计</span>
</a>
<a href="#/upload/repairs" class="nav-child" data-route="upload-repairs">
<span class="child-icon">🔧</span>
<span>返修记录</span>
</a>
<a href="#/upload/shipments" class="nav-child" data-route="upload-shipments">
<span class="child-icon">📦</span>
<span>发货记录</span>
</a>
<a href="#/upload/sop" class="nav-child" data-route="upload-sop">
<span class="child-icon">📄</span>
<span>SOP</span>
</a>
</div>
</div>
</div>
<div class="nav-group">
<div class="nav-group-title">查询</div>
<div class="nav-item has-children" data-expand="query">
<button class="nav-item-btn">
<span class="icon">🔍</span>
<span class="text">出货查询</span>
<span class="caret"></span>
</button>
<div class="nav-children" data-parent="query">
<a href="#/shipments/query" class="nav-child" data-route="shipments-query">
<span class="child-icon">📋</span>
<span>详细记录查询</span>
</a>
<a href="#/shipments/summary" class="nav-child" data-route="shipments-summary">
<span class="child-icon">📊</span>
<span>汇总信息查询</span>
</a>
</div>
</div>
</div>
<div class="nav-group">
<div class="nav-group-title">生产管理</div>
<div class="nav-item has-children" data-expand="production-mgmt">
<button class="nav-item-btn">
<span class="icon">📋</span>
<span class="text">生产管理</span>
<span class="caret"></span>
</button>
<div class="nav-children" data-parent="production-mgmt">
<a href="#/production-mgmt/work-order" class="nav-child" data-route="production-mgmt-work-order">
<span class="child-icon">📝</span>
<span>生产工单下发中心</span>
</a>
</div>
</div>
</div>
<div class="nav-group">
<div class="nav-group-title">计划管理</div>
<div class="nav-item has-children" data-expand="plan-mgmt">
<button class="nav-item-btn">
<span class="icon">📅</span>
<span class="text">计划管理</span>
<span class="caret"></span>
</button>
<div class="nav-children" data-parent="plan-mgmt">
<a href="#/plan-mgmt/bom" class="nav-child" data-route="plan-mgmt-bom">
<span class="child-icon">📦</span>
<span>BOM物料清单</span>
</a>
<a href="#/plan-mgmt/initial-stock" class="nav-child" data-route="plan-mgmt-initial-stock">
<span class="child-icon">🏭</span>
<span>期初库存</span>
</a>
<a href="#/plan-mgmt/purchase-demand" class="nav-child" data-route="plan-mgmt-purchase-demand">
<span class="child-icon">🛒</span>
<span>采购需求清单</span>
</a>
<a href="#/plan-mgmt/customer-order" class="nav-child" data-route="plan-mgmt-customer-order">
<span class="child-icon">📋</span>
<span>客户订单</span>
</a>
<a href="#/plan-mgmt/reconciliation" class="nav-child" data-route="plan-mgmt-reconciliation">
<span class="child-icon">💰</span>
<span>对账单</span>
</a>
</div>
</div>
</div>
<div class="nav-group">
<div class="nav-group-title">扩展采集</div>
<div class="nav-item has-children" data-expand="collect">
<button class="nav-item-btn">
<span class="icon">🛠️</span>
<span class="text">采集</span>
<span class="caret"></span>
</button>
<div class="nav-children" data-parent="collect">
<a href="#/devices" class="nav-child" data-route="devices">
<span class="child-icon">💻</span>
<span>设备状态</span>
</a>
<a href="#/environment" class="nav-child" data-route="environment">
<span class="child-icon">🌡️</span>
<span>环境参数</span>
</a>
<a href="#/personnel" class="nav-child" data-route="personnel">
<span class="child-icon">👥</span>
<span>人员信息</span>
</a>
<a href="#/qa" class="nav-child" data-route="qa">
<span class="child-icon"></span>
<span>质检报告</span>
</a>
<a href="#/production" class="nav-child" data-route="production">
<span class="child-icon">⏱️</span>
<span>时间记录</span>
</a>
</div>
</div>
</div>
<div class="nav-group">
<div class="nav-group-title">导出与设置</div>
<a href="#/export" class="nav-item" data-route="export">
<span class="icon">📤</span>
<span class="text">导出</span>
</a>
<a href="#/settings" class="nav-item" data-route="settings">
<span class="icon">⚙️</span>
<span class="text">设置</span>
</a>
</div>
<div class="nav-group superadmin-only" style="display:none;">
<div class="nav-group-title">系统管理</div>
<a href="#/system/operations-log" class="nav-item" data-route="system-operations-log">
<span class="icon">📜</span>
<span class="text">操作日志</span>
</a>
</div>
</nav>
<div class="theme-toggle-container">
<div class="topbar-right">
<label class="bb8-toggle">
<input id="theme-toggle-checkbox" class="bb8-toggle__checkbox" type="checkbox">
<div class="bb8-toggle__container">
@ -219,6 +102,7 @@
<div class="bb8-toggle__star"></div>
<div class="bb8-toggle__star"></div>
<div class="bb8-toggle__star"></div>
<div class="bb8-toggle__star"></div>
<div class="tatto-1"></div>
<div class="tatto-2"></div>
<div class="gomrassen"></div>
@ -241,31 +125,29 @@
</div>
</div>
</label>
</div>
</aside>
<main class="content">
<header class="content-header" style="background: var(--bg) !important;">
<div id="breadcrumb"></div>
<div id="actions">
<button id="notification-bell" class="notification-bell" style="display:none;">
<span class="bell-icon">🔔</span>
<span id="notification-badge" class="notification-badge" style="display:none;">0</span>
<button id="notification-bell" class="notification-bell" style="display:none;">
<span class="bell-icon">🔔</span>
<span id="notification-badge" class="notification-badge" style="display:none;">0</span>
</button>
<div class="user-menu-container">
<button id="user-avatar-btn" class="user-avatar-btn">
<img id="user-avatar-img" src="./assets/user-avatar.svg" alt="用户头像" class="user-avatar-img" />
<span id="user-name-display" class="user-name-display">未登录</span>
</button>
<div class="user-menu-container">
<button id="user-avatar-btn" class="user-avatar-btn">
<img id="user-avatar-img" src="./assets/user-avatar.svg" alt="用户头像" class="user-avatar-img" />
<span id="user-name-display" class="user-name-display">未登录</span>
</button>
<div id="user-dropdown" class="user-dropdown" style="display:none;">
<div class="user-dropdown-item" id="user-dropdown-logout">
<span class="dropdown-icon">🚪</span>
<span>退出登录</span>
</div>
<div id="user-dropdown" class="user-dropdown" style="display:none;">
<a href="#/settings" class="user-dropdown-item" data-route="settings">
<span class="dropdown-icon">⚙️</span>
<span>设置</span>
</a>
<div class="user-dropdown-item" id="user-dropdown-logout">
<span class="dropdown-icon">🚪</span>
<span>退出登录</span>
</div>
</div>
<button id="menu-toggle" class="menu-toggle">菜单</button>
</div>
</header>
</div>
</header>
<main class="content topbar-layout">
<section id="view" class="view"></section>
</main>
@ -316,6 +198,7 @@
<script src="./js/components/settings.js"></script>
<script src="./js/components/notifications.js"></script>
<script src="./js/components/operations-log.js"></script>
<script src="./js/components/product-intro.js"></script>
<script src="./js/app.js"></script>
</body>
</html>

View File

@ -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');
});
}
});
// 主题切换

View File

@ -5,9 +5,68 @@ const Dashboard = (() => {
'直通良品率': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>',
'良品率': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>',
'发货数量': truckIcon,
'不良数量': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>'
'不良数量': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>',
'良品/不良率': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 20V10"/><path d="M12 20V4"/><path d="M6 20v-6"/></svg>',
'今日产量': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2v4"/><path d="M12 18v4"/><path d="m4.93 4.93 2.83 2.83"/><path d="m16.24 16.24 2.83 2.83"/><path d="M2 12h4"/><path d="M18 12h4"/><path d="m4.93 19.07 2.83-2.83"/><path d="m16.24 7.76 2.83-2.83"/></svg>'
};
// 合并良品率/不良率卡片
function rateCard(goodRate, badRate) {
const icon = metricsIcons['良品/不良率'];
const goodNum = parseFloat(String(goodRate).replace('%', '')) || 0;
const badNum = parseFloat(String(badRate).replace('%', '')) || 0;
return `<div class="metrics-card" id="rate-card">
<div class="metrics-title">
<span class="metrics-icon" style="background-color:#10B981">
${icon}
</span>
<p class="metrics-title-text">良品/不良率</p>
<p class="metrics-percent" style="color:#02972f">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1792 1792" fill="currentColor" height="16" width="16">
<path d="M1408 1216q0 26-19 45t-45 19h-896q-26 0-45-19t-19-45 19-45l448-448q19-19 45-19t45 19l448 448q19 19 19 45z"></path>
</svg>
</p>
</div>
<div class="metrics-data">
<p class="metrics-value"><span style="color:#10B981" id="good-rate-value">${goodRate}</span><span style="color:var(--text-2);font-size:14px;font-weight:400;margin:0 4px">/</span><span style="color:#EF4444" id="bad-rate-value">${badRate}</span></p>
<div class="metrics-range">
<div class="metrics-fill" style="width:${goodNum}%;background-color:#10B981"></div>
</div>
</div>
</div>`;
}
// 今日产量卡片(拼多多/圆通自动切换)
function todayProductionCard(todayPdd, todayYt, activePlatform) {
const isPdd = activePlatform === 'pdd';
const platformName = isPdd ? '拼多多' : '圆通';
const platformIcon = isPdd ? '<img src="assets/pdd.svg" style="width:20px;height:20px" />' : '<img src="assets/yt.svg" style="width:20px;height:20px" />';
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 `<div class="metrics-card" id="today-production-card" data-platform="${activePlatform}">
<div class="metrics-title">
<span class="metrics-icon" style="background-color:#f5f5f5;padding:4px">
${platformIcon}
</span>
<p class="metrics-title-text" id="today-platform-name">今日${platformName}</p>
<p class="metrics-percent" style="color:${color.text}">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1792 1792" fill="currentColor" height="16" width="16">
<path d="M1408 1216q0 26-19 45t-45 19h-896q-26 0-45-19t-19-45 19-45l448-448q19-19 45-19t45 19l448 448q19 19 19 45z"></path>
</svg>
</p>
</div>
<div class="metrics-data">
<p class="metrics-value" id="today-production-value">${value}</p>
<div class="metrics-range">
<div class="metrics-fill" id="today-production-fill" style="width:${fillWidth}%;background-color:${color.bg}"></div>
</div>
</div>
</div>`;
}
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
? '<img src="assets/pdd.svg" style="width:20px;height:20px" />'
: '<img src="assets/yt.svg" style="width:20px;height:20px" />';
}
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 = (() => {
<div>
<div class="dashboard-metrics-4col">
${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')}
</div>
<div class="card" style="margin-top:12px">
<div style="font-weight:600;margin-bottom:8px;display:flex;justify-content:space-between;align-items:center">

View File

@ -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 `
<div class="product-intro-page">
<div class="product-intro-header">
<h1>产品介绍</h1>
<p class="subtitle">兔喜驿站智能找灯系统</p>
</div>
<div class="product-cards">
<div class="product-card main-product">
<div class="product-image-wrapper">
<img src="./assets/tuxi-station.png" alt="兔喜基站" class="product-image rotated" />
</div>
<div class="product-info">
<h2>智能基站</h2>
<p class="product-desc">兔喜驿站找灯条对应快递的基站设备用于控制货架上的LED灯条帮助快速定位快递包裹位置</p>
<div class="product-features">
<div class="feature-item">
<span class="feature-icon">📡</span>
<span class="feature-text">蓝牙信号覆盖</span>
</div>
<div class="feature-item">
<span class="feature-icon">💡</span>
<span class="feature-text">精准灯条控制</span>
</div>
<div class="feature-item">
<span class="feature-icon"></span>
<span class="feature-text">快速响应</span>
</div>
<div class="feature-item">
<span class="feature-icon">🔧</span>
<span class="feature-text">易于安装</span>
</div>
</div>
</div>
</div>
</div>
<div class="product-specs">
<h3>技术规格</h3>
<div class="specs-grid">
<div class="spec-item">
<span class="spec-label">品牌</span>
<span class="spec-value">兔喜生活</span>
</div>
<div class="spec-item">
<span class="spec-label">类型</span>
<span class="spec-value">智能基站</span>
</div>
<div class="spec-item">
<span class="spec-label">应用场景</span>
<span class="spec-value">快递驿站</span>
</div>
<div class="spec-item">
<span class="spec-label">功能</span>
<span class="spec-value">灯条定位控制</span>
</div>
</div>
</div>
</div>
`;
},
afterRender: () => {
// 页面渲染后的逻辑
}
};

View File

@ -11,109 +11,87 @@
<link rel="stylesheet" href="./assets/login.css" />
</head>
<body>
<div class="login-container">
<div class="login-background"></div>
<div class="login-card">
<div class="login-header">
<div class="logo-container">
<div class="logo-icon bitcoin-coin">
<div class="coin-face front">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 4091.27 4091.73" width="100%" height="100%">
<path fill="#F7931A" d="M4030.06 2540.77c-273.24,1096.01 -1383.32,1763.02 -2479.46,1489.71 -1095.68,-273.24 -1762.69,-1383.39 -1489.33,-2479.31 273.12,-1096.13 1383.2,-1763.19 2479,-1489.95 1096.06,273.24 1763.03,1383.51 1489.76,2479.57l0.02 -0.02z"/>
<path fill="white" d="M2947.77 1754.38c40.72,-272.26 -166.56,-418.61 -450,-516.24l91.95 -368.8 -224.5 -55.94 -89.51 359.09c-59.02,-14.72 -119.63,-28.59 -179.87,-42.34l90.16 -361.46 -224.36 -55.94 -92 368.68c-48.84,-11.12 -96.81,-22.11 -143.35,-33.69l0.26 -1.16 -309.59 -77.31 -59.72 239.78c0,0 166.56,38.18 163.05,40.53 90.91,22.69 107.35,82.87 104.62,130.57l-104.74 420.15c6.26,1.59 14.38,3.89 23.34,7.49 -7.49,-1.86 -15.46,-3.89 -23.73,-5.87l-146.81 588.57c-11.11,27.62 -39.31,69.07 -102.87,53.33 2.25,3.26 -163.17,-40.72 -163.17,-40.72l-111.46 256.98 292.15 72.83c54.35,13.63 107.61,27.89 160.06,41.3l-92.9 373.03 224.24 55.94 92 -369.07c61.26,16.63 120.71,31.97 178.91,46.43l-91.69 367.33 224.51 55.94 92.89 -372.33c382.82,72.45 670.67,43.24 791.83,-303.02 97.63,-278.78 -4.86,-439.58 -206.26,-544.44 146.69,-33.83 257.18,-130.31 286.64,-329.61l-0.07 -0.05zm-512.93 719.26c-69.38,278.78 -538.76,128.08 -690.94,90.29l123.28 -494.2c152.17,37.99 640.17,113.17 567.67,403.91zm69.43 -723.3c-63.29,253.58 -453.96,124.75 -580.69,93.16l111.77 -448.21c126.73,31.59 534.85,90.55 468.94,355.05l-0.02 0z"/>
</svg>
</div>
<div class="coin-face back">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 4091.27 4091.73" width="100%" height="100%" style="transform:scaleX(-1)">
<path fill="#F7931A" d="M4030.06 2540.77c-273.24,1096.01 -1383.32,1763.02 -2479.46,1489.71 -1095.68,-273.24 -1762.69,-1383.39 -1489.33,-2479.31 273.12,-1096.13 1383.2,-1763.19 2479,-1489.95 1096.06,273.24 1763.03,1383.51 1489.76,2479.57l0.02 -0.02z"/>
<path fill="white" d="M2947.77 1754.38c40.72,-272.26 -166.56,-418.61 -450,-516.24l91.95 -368.8 -224.5 -55.94 -89.51 359.09c-59.02,-14.72 -119.63,-28.59 -179.87,-42.34l90.16 -361.46 -224.36 -55.94 -92 368.68c-48.84,-11.12 -96.81,-22.11 -143.35,-33.69l0.26 -1.16 -309.59 -77.31 -59.72 239.78c0,0 166.56,38.18 163.05,40.53 90.91,22.69 107.35,82.87 104.62,130.57l-104.74 420.15c6.26,1.59 14.38,3.89 23.34,7.49 -7.49,-1.86 -15.46,-3.89 -23.73,-5.87l-146.81 588.57c-11.11,27.62 -39.31,69.07 -102.87,53.33 2.25,3.26 -163.17,-40.72 -163.17,-40.72l-111.46 256.98 292.15 72.83c54.35,13.63 107.61,27.89 160.06,41.3l-92.9 373.03 224.24 55.94 92 -369.07c61.26,16.63 120.71,31.97 178.91,46.43l-91.69 367.33 224.51 55.94 92.89 -372.33c382.82,72.45 670.67,43.24 791.83,-303.02 97.63,-278.78 -4.86,-439.58 -206.26,-544.44 146.69,-33.83 257.18,-130.31 286.64,-329.61l-0.07 -0.05zm-512.93 719.26c-69.38,278.78 -538.76,128.08 -690.94,90.29l123.28 -494.2c152.17,37.99 640.17,113.17 567.67,403.91zm69.43 -723.3c-63.29,253.58 -453.96,124.75 -580.69,93.16l111.77 -448.21c126.73,31.59 534.85,90.55 468.94,355.05l-0.02 0z"/>
</svg>
</div>
</div>
<div class="login-wrapper">
<div class="container">
<div class="left">
<div class="form-header">
<h1 class="system-title">韬智生产管理系统</h1>
<p class="system-subtitle">Production Management System</p>
</div>
<p class="system-subtitle">Production Management System</p>
</div>
<div class="login-form">
<div class="form-group">
<label for="username">用户名</label>
<div class="input-wrapper">
<span class="input-icon">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="8" r="4" stroke="currentColor" stroke-width="2"/>
<path d="M6 21c0-3.314 2.686-6 6-6s6 2.686 6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
</span>
<input
type="text"
id="username"
class="form-input"
placeholder="请输入用户名"
autocomplete="username"
/>
<form class="form" onsubmit="return false;">
<div class="input-block">
<input class="input" type="text" id="username" required autocomplete="username">
<label for="username">用户名</label>
</div>
</div>
<div class="form-group">
<label for="password">密码</label>
<div class="input-wrapper">
<span class="input-icon">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="5" y="11" width="14" height="10" rx="2" stroke="currentColor" stroke-width="2"/>
<path d="M8 11V7a4 4 0 0 1 8 0v4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
<circle cx="12" cy="16" r="1.5" fill="currentColor"/>
</svg>
</span>
<input
type="password"
id="password"
class="form-input"
placeholder="请输入密码"
autocomplete="current-password"
/>
<div class="input-block">
<input class="input" type="password" id="password" required autocomplete="current-password">
<label for="password">密码</label>
</div>
</div>
<div class="form-group">
<label for="captcha">验证码</label>
<div class="captcha-wrapper">
<div class="input-wrapper" style="flex: 1;">
<span class="input-icon">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="3" y="5" width="18" height="14" rx="2" stroke="currentColor" stroke-width="2"/>
<path d="M7 9h2m3 0h2m3 0h2M7 13h3m4 0h3" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
</span>
<input
type="text"
id="captcha"
class="form-input"
placeholder="请输入验证码"
maxlength="4"
autocomplete="off"
/>
</div>
<div class="input-block captcha-block">
<input class="input captcha-input" type="text" id="captcha" required maxlength="4" autocomplete="off">
<label for="captcha">验证码</label>
<div class="captcha-image-wrapper" id="captcha-image-wrapper" title="点击刷新验证码">
<img id="captcha-image" class="captcha-image" alt="验证码" />
</div>
</div>
</div>
<div id="error-message" class="error-message" style="display:none;"></div>
<button id="login-btn" class="login-btn">
<span id="login-text">登录</span>
<span id="login-loader" class="btn-loader" style="display:none;">
<span class="dot"></span>
<span class="dot"></span>
<span class="dot"></span>
</span>
</button>
<div id="error-message" class="error-message" style="display:none;"></div>
<div class="input-block">
<span class="forgot"><a href="#">© 2025 韬智科技</a></span>
<button type="button" id="login-btn">
<span id="login-text">登录</span>
<span id="login-loader" class="btn-loader" style="display:none;">
<span class="dot"></span>
<span class="dot"></span>
<span class="dot"></span>
</span>
</button>
</div>
</form>
</div>
<div class="login-footer">
<p>© 2025 韬智科技 · 智能制造</p>
<div class="right">
<div class="img">
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 731.67004 550.61784" xmlns:xlink="http://www.w3.org/1999/xlink">
<path d="M0,334.13393c0,.66003,.53003,1.19,1.19006,1.19H730.48004c.65997,0,1.19-.52997,1.19-1.19,0-.65997-.53003-1.19-1.19-1.19H1.19006c-.66003,0-1.19006,.53003-1.19006,1.19Z" fill="#3f3d56"></path>
<polygon points="466.98463 81.60598 470.81118 130.55703 526.26809 107.39339 494.98463 57.60598 466.98463 81.60598" fill="#a0616a"></polygon>
<circle cx="465.32321" cy="55.18079" r="41.33858" fill="#a0616a"></circle>
<polygon points="387.98463 440.60598 394.98463 503.39339 345.98463 496.60598 361.98463 438.60598 387.98463 440.60598" fill="#a0616a"></polygon>
<polygon points="578.98463 449.60598 585.98463 512.39339 536.98463 505.60598 552.98463 447.60598 578.98463 449.60598" fill="#a0616a"></polygon>
<path d="M462.48463,260.10598c-.66897,0-54.14584,2.68515-89.47714,4.46286-16.72275,.84141-29.45202,15.31527-28.15459,32.00884l12.63173,162.5283,36,1,.87795-131,71.12205,4-3-73Z" fill="#2f2e41"></path>
<path d="M619.48463,259.10598s9,69,2,76c-7,7-226.5-5.5-226.5-5.5,0,0,48.15354-69.53704,56.82677-71.51852,8.67323-1.98148,146.67323-8.98148,146.67323-8.98148l21,10Z" fill="#2f2e41"></path>
<path id="uuid-91047c5b-47d7-4179-8a16-40bd6d529b28-203" d="M335.12666,172.23337c-8.35907-11.69074-9.10267-25.48009-1.66174-30.79863,7.44093-5.31854,20.24665-.15219,28.60713,11.54383,3.40375,4.62627,5.65012,10.00041,6.55111,15.67279l34.79215,49.9814-19.8001,13.70807-35.7745-48.83421c-5.07753-2.68845-9.43721-6.55406-12.71405-11.27326Z" fill="#a0616a"></path>
<path d="M464.98463,112.60598l51-21,96,148s-67,15-90,18c-23,3-49-9-49-9l-8-136Z" fill="#5e7eb6"></path>
<path d="M526.98463,137.60598l-18.5-57.70866,24,18.20866s68,45,68,64c0,19,21,77,21,77,0,0,23.5,19.5,15.5,37.5-8,18,10.5,15.5,12.5,28.5,2,13-28.5,30.5-28.5,30.5,0,0-7.5-73.5-31.5-73.5-24,0-62.5-124.5-62.5-124.5Z" fill="#3f3d56"></path>
<path d="M468.56831,111.13035l-25.08368,9.97563s4,70,8,76c4,6,18,38,18,38v10.42913s-28,8.57087-27,13.57087c1,5,66,19,66,19,0,0-13-40-21-53-8-13-18.91632-113.97563-18.91632-113.97563Z" fill="#3f3d56"></path>
<path d="M452.48463,121.10598s-29-4-34,30c-5,34-1.82283,38.5-1.82283,38.5l-8.17717,19.5-27-30-26,17s47,76,66,74c19-2,47-57,47-57l-16-92Z" fill="#3f3d56"></path>
<path d="M597.32321,270.14478l-14.83858,209.96121-38.5-1.5s-8.5-198.5-8.5-201.5c0-3,4-20,29-21,25-1,32.83858,14.03879,32.83858,14.03879Z" fill="#2f2e41"></path>
<path d="M541.48463,484.10598s20-6,23-2c3,4,20,6,20,6l5,49s-14,10-16,12-55,4-56-8c-1-12,14-27,14-27l10-30Z" fill="#2f2e41"></path>
<path d="M394.48463,470.10598s6-5,8,9c2,14,9,37-1,40-10,3-110,4-110-5v-9l9-7,18.00394-2.869s34.99606-32.131,38.99606-32.131c4,0,17,13,17,13l20-6Z" fill="#2f2e41"></path>
<path d="M505.98463,77.60598s-20-24-28-22-3,5-3,5l-20-22s-16-6-31,13c0,0-9-16,0-25,9-9,12-8,14-13,2-5,16-9,16-9,0,0-.80315-7.19685,3.59843-3.59843s15.3937,3.59843,15.3937,3.59843c0,0,.06299-4,4.53543,0,4.47244,4,9.47244,2,9.47244,2,0,0,0,6.92126,3.5,6.96063,3.5,.03937,9.5-4.96063,10.5-.96063,1,4,8,6,9,18,1,12-4,47-4,47Z" fill="#2f2e41"></path>
<g>
<path d="M342.99463,178.84874l-114.2362,78.82694c-3.94205,2.72015-9.36214,1.72624-12.08229-2.21581l-32.16176-46.60891c-2.72015-3.94205-1.7259-9.36208,2.21615-12.08223l114.2362-78.82694c3.94205-2.72015,9.36214-1.72624,12.08229,2.21581l32.16176,46.60891c2.72015,3.94205,1.7259,9.36208-2.21615,12.08223Z" fill="#fff"></path>
<path d="M312.83914,120.30274l32.16148,46.6085c2.64627,3.83499,1.68408,9.08121-2.15091,11.72749l-56.06388,38.68602c-14.78562-4.04015-28.2774-13.11486-37.66263-26.71596-6.14766-8.9092-9.85314-18.77211-11.26649-28.80885l63.25494-43.6481c3.83499-2.64627,9.08121-1.68408,11.72749,2.15091Z" fill="#e6e6e6"></path>
<path d="M223.84012,260.20913c-3.0791,0-6.10938-1.46094-7.9873-4.18066l-32.16211-46.60938c-1.4668-2.12695-2.01758-4.7002-1.5498-7.24805,.4668-2.54785,1.89551-4.75879,4.02246-6.22559l114.23535-78.82715c4.39746-3.03223,10.44043-1.92285,13.47363,2.4707l32.16211,46.60938c1.4668,2.12695,2.01758,4.7002,1.5498,7.24805-.4668,2.54688-1.89551,4.75879-4.02148,6.22559l-114.23633,78.82715c-1.67578,1.15527-3.59082,1.70996-5.48633,1.70996Zm82.04785-142.80176c-1.50391,0-3.02344,.44043-4.35254,1.35742l-114.23633,78.82715c-1.6875,1.16309-2.82031,2.91797-3.19141,4.94043-.37109,2.02148,.06543,4.06445,1.22949,5.75l32.16211,46.60938c2.40625,3.48633,7.20215,4.36816,10.69043,1.96094l114.2373-78.82715c1.68652-1.16309,2.81934-2.91797,3.19043-4.94043,.37109-2.02148-.06543-4.06445-1.22949-5.75l-32.16211-46.60938c-1.48926-2.1582-3.89453-3.31836-6.33789-3.31836Z" fill="#3f3d56"></path>
<path d="M224.6666,236.93718c-2.89521,1.9978-3.6253,5.97848-1.6275,8.87369,1.9978,2.89521,5.97848,3.6253,8.87369,1.6275l11.76134-8.11573c2.89521-1.9978,3.6253-5.97848,1.6275-8.87369-1.9978-2.89521-5.97848-3.6253-8.87369-1.6275l-11.76134,8.11573Z" fill="#5e7eb6"></path>
<path d="M232.63862,171.91114c-4.56802,3.15209-5.71978,9.43286-2.56769,14.00088,3.15209,4.56802,9.43252,5.71972,14.00054,2.56763l18.29546-12.6245c4.56802-3.15209,5.72007-9.43245,2.56797-14.00047-3.15209-4.56802-9.4328-5.72013-14.00082-2.56804l-18.29546,12.6245Z" fill="#5e7eb6"></path>
</g>
<g>
<path d="M340.25926,185.80874H201.4659c-4.78947,0-8.68608-3.89636-8.68608-8.68583v-56.62834c0-4.78947,3.89661-8.68583,8.68608-8.68583h138.79336c4.78947,0,8.68608,3.89636,8.68608,8.68583v56.62834c0,4.78947-3.89661,8.68583-8.68608,8.68583Z" fill="#fff"></path>
<path d="M348.69017,120.49482v56.62784c0,4.65939-3.77152,8.43091-8.43091,8.43091h-68.11583c-9.87497-11.72273-15.82567-26.8544-15.82567-43.37931,0-10.82439,2.55172-21.04674,7.08876-30.11034h76.85275c4.65939,0,8.43091,3.77152,8.43091,8.43091Z" fill="#e6e6e6"></path>
<path d="M340.25907,186.80874H201.4661c-5.34082,0-9.68652-4.34473-9.68652-9.68555v-56.62891c0-5.34082,4.3457-9.68555,9.68652-9.68555h138.79297c5.34082,0,9.68652,4.34473,9.68652,9.68555v56.62891c0,5.34082-4.3457,9.68555-9.68652,9.68555ZM201.4661,112.80874c-4.23828,0-7.68652,3.44727-7.68652,7.68555v56.62891c0,4.23828,3.44824,7.68555,7.68652,7.68555h138.79297c4.23828,0,7.68652-3.44727,7.68652-7.68555v-56.62891c0-4.23828-3.44824-7.68555-7.68652-7.68555H201.4661Z" fill="#3f3d56"></path>
<path d="M209.87637,166.41564c-3.51759,0-6.37931,2.86172-6.37931,6.37931s2.86172,6.37931,6.37931,6.37931h14.28966c3.51759,0,6.37931-2.86172,6.37931-6.37931s-2.86172-6.37931-6.37931-6.37931h-14.28966Z" fill="#5e7eb6"></path>
<path d="M253.36907,117.42253c-5.55,0-10.06511,4.51536-10.06511,10.06536s4.51511,10.06486,10.06511,10.06486h22.22841c5.55,0,10.06511-4.51486,10.06511-10.06486s-4.51511-10.06536-10.06511-10.06536h-22.22841Z" fill="#5e7eb6"></path>
</g>
<g>
<path d="M456.25926,381.80874h-138.79336c-4.78947,0-8.68608-3.89636-8.68608-8.68583v-56.62834c0-4.78947,3.89661-8.68583,8.68608-8.68583h138.79336c4.78947,0,8.68608,3.89636,8.68608,8.68583v56.62834c0,4.78947-3.89661,8.68583-8.68608,8.68583Z" fill="#fff"></path>
<path d="M464.69017,316.49482v56.62784c0,4.65939-3.77152,8.43091-8.43091,8.43091h-68.11583c-9.87497-11.72273-15.82567-26.8544-15.82567-43.37931,0-10.82439,2.55172-21.04674,7.08876-30.11034h76.85275c4.65939,0,8.43091,3.77152,8.43091,8.43091Z" fill="#e6e6e6"></path>
<path d="M456.25907,382.80874h-138.79297c-5.34082,0-9.68652-4.34473-9.68652-9.68555v-56.62891c0-5.34082,4.3457-9.68555,9.68652-9.68555h138.79297c5.34082,0,9.68652,4.34473,9.68652,9.68555v56.62891c0,5.34082-4.3457,9.68555-9.68652,9.68555Zm-138.79297-74c-4.23828,0-7.68652,3.44727-7.68652,7.68555v56.62891c0,4.23828,3.44824,7.68555,7.68652,7.68555h138.79297c4.23828,0,7.68652-3.44727,7.68652-7.68555v-56.62891c0-4.23828-3.44824-7.68555-7.68652-7.68555h-138.79297Z" fill="#3f3d56"></path>
<path d="M325.87637,362.41564c-3.51759,0-6.37931,2.86172-6.37931,6.37931s2.86172,6.37931,6.37931,6.37931h14.28966c3.51759,0,6.37931-2.86172,6.37931-6.37931s-2.86172-6.37931-6.37931-6.37931h-14.28966Z" fill="#5e7eb6"></path>
<path d="M369.36907,313.42253c-5.55,0-10.06511,4.51536-10.06511,10.06536s4.51511,10.06486,10.06511,10.06486h22.22841c5.55,0,10.06511-4.51486,10.06511-10.06486s-4.51511-10.06536-10.06511-10.06536h-22.22841Z" fill="#5e7eb6"></path>
</g>
<path id="uuid-c026fd96-7d81-4b34-bb39-0646c0e08e96-204" d="M465.67391,331.01678c-12.74718,6.63753-26.5046,5.44058-30.72743-2.67249-4.22283-8.11308,2.6878-20.06802,15.44041-26.70621,5.05777-2.72156,10.69376-4.19231,16.43644-4.28916l54.36547-27.44139,10.79681,21.52636-53.36733,28.57487c-3.37375,4.65048-7.81238,8.42516-12.94437,11.00803Z" fill="#a0616a"></path>
<path d="M527.48463,97.10598s56-3,68,27c12,30,22,128,22,128l-122,66.37402-21-32.37402,82-64-29-125Z" fill="#3f3d56"></path>
</svg>
</div>
</div>
</div>
</div>

View File

@ -824,7 +824,6 @@ def dashboard():
s = c.fetchone()
c.execute('SELECT COUNT(1) AS total FROM defects')
defects = c.fetchone()
conn.close()
good = s['good_total'] if s else 0
bad = s['bad_total'] if s else 0
@ -833,10 +832,108 @@ def dashboard():
# 计算总良品率
rate = "{}%".format(round((good/(good+bad)) * 100, 2)) if (good+bad) > 0 else u''
# 计算不良率
bad_rate = "{}%".format(round((bad/(good+bad)) * 100, 2)) if (good+bad) > 0 else u''
# 计算直通良品率FPY = First Pass Yield
total_produced = good + bad
fpy_rate = "{}%".format(round((fpy_good/total_produced) * 100, 2)) if total_produced > 0 else u''
conn.close()
# 获取今日北京时间日期
from datetime import timezone, timedelta
beijing_tz = timezone(timedelta(hours=8))
now_bj = datetime.now(beijing_tz)
today_bj = now_bj.strftime('%Y-%m-%d')
ten_min_ago = now_bj - timedelta(minutes=10)
# 从 Redis 获取审计数据来计算今日产量和活跃状态
today_pdd = 0
today_yt = 0
pdd_active = False
yt_active = False
try:
r = get_redis()
# 获取拼多多审计数据
pdd_items = []
for key in ['mac_batch_audit_pdd', 'audit:pdd', 'pdd:audit']:
if r.exists(key) and r.type(key) == 'list':
pdd_items = r.lrange(key, 0, -1)
break
# 获取圆通审计数据
yt_items = []
for key in ['mac_batch_audit_yt', 'audit:yt', 'yt:audit']:
if r.exists(key) and r.type(key) == 'list':
yt_items = r.lrange(key, 0, -1)
break
# 解析拼多多数据(使用 parse_audit_line 解析原始数据)
for item in pdd_items:
try:
parsed = parse_audit_line(item)
ts_str = parsed.get('ts_cn') or ''
if ts_str.startswith(today_bj):
today_pdd += 1
# 检查是否在最近10分钟内ts_cn 格式为 YYYY-MM-DD HH:MM:SS
try:
item_time = datetime.strptime(ts_str, '%Y-%m-%d %H:%M:%S')
item_time = item_time.replace(tzinfo=beijing_tz)
if item_time >= ten_min_ago:
pdd_active = True
except:
pass
except:
pass
# 解析圆通数据(使用 parse_audit_line 解析原始数据)
for item in yt_items:
try:
parsed = parse_audit_line(item)
ts_str = parsed.get('ts_cn') or ''
if ts_str.startswith(today_bj):
today_yt += 1
# 检查是否在最近10分钟内ts_cn 格式为 YYYY-MM-DD HH:MM:SS
try:
item_time = datetime.strptime(ts_str, '%Y-%m-%d %H:%M:%S')
item_time = item_time.replace(tzinfo=beijing_tz)
if item_time >= ten_min_ago:
yt_active = True
except:
pass
except:
pass
except Exception as e:
log('dashboard_audit_error', str(e))
# 调试日志
log('dashboard_platform_debug', json.dumps({
'today_pdd': today_pdd,
'today_yt': today_yt,
'pdd_active': pdd_active,
'yt_active': yt_active,
'today_bj': today_bj
}))
# 自动切换逻辑:优先显示今日活跃的平台
# 1. 如果拼多多活跃最近10分钟有数据显示拼多多
# 2. 如果圆通活跃最近10分钟有数据显示圆通
# 3. 如果都不活跃,显示今日有数据的平台
# 4. 如果都没数据,默认拼多多
if pdd_active:
active_platform = 'pdd'
elif yt_active:
active_platform = 'yt'
elif today_pdd > 0:
active_platform = 'pdd'
elif today_yt > 0:
active_platform = 'yt'
else:
active_platform = 'pdd' # 都没数据时默认拼多多
# 从 Redis 获取发货数量SN 记录数)
shipments_count = 0
try:
@ -856,9 +953,13 @@ def dashboard():
return jsonify({
'fpyRate': fpy_rate,
'goodRate': rate,
'badRate': bad_rate,
'shipments': shipments_count,
'defects': (defects['total'] or 0) if defects else 0,
'badCount': bad
'badCount': bad,
'todayPdd': today_pdd,
'todayYt': today_yt,
'activePlatform': active_platform
})