feat: 页面背景预设与工作台增强,优化官网与登录体验

新增石米等淡色背景预设并设为默认,修复页面配置抽屉偶发不弹出问题,移除登录欢迎卡;同步 IDE 官网导航与背景样式,并扩展管理端工作台统计展示。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zzh 2026-08-28 10:59:10 +08:00
parent 7c08e83f38
commit b5edbb8447
52 changed files with 2219 additions and 1087 deletions

View File

@ -8,8 +8,8 @@
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
background: #f7f8fa;
color: #2c3e50;
background: #f6f1e7;
color: #1a2e38;
line-height: 1.7;
padding: 40px 20px;
}
@ -25,7 +25,7 @@
text-align: center;
margin-bottom: 36px;
padding-bottom: 24px;
border-bottom: 1px solid #eef0f3;
border-bottom: 1px solid #ebe4d8;
}
.header .brand {
display: inline-flex;
@ -33,8 +33,8 @@
gap: 8px;
font-size: 13px;
font-weight: 600;
color: #003860;
background: #E8F0F5;
color: #054f74;
background: #e0edf2;
padding: 4px 12px;
border-radius: 20px;
margin-bottom: 14px;
@ -57,26 +57,26 @@
color: #1a2332;
margin: 36px 0 18px;
padding-left: 12px;
border-left: 3px solid #003860;
border-left: 3px solid #054f74;
}
/* ========== 决策树 ========== */
.decision-tree {
background: linear-gradient(180deg, #f6f8fa 0%, #f9fafb 100%);
background: linear-gradient(180deg, #fbf8f2 0%, #f6f1e7 100%);
border-radius: 12px;
padding: 36px 28px 32px;
margin-bottom: 8px;
}
.q-card {
background: #fff;
border: 2px solid #e2e6eb;
border: 2px solid #e0d9cc;
border-radius: 10px;
padding: 16px 24px;
text-align: center;
transition: all 0.25s ease;
}
.q-card:hover {
border-color: #003860;
border-color: #054f74;
box-shadow: 0 4px 16px rgba(74,124,89,0.1);
}
.q-card .step-tag {
@ -84,7 +84,7 @@
font-size: 10px;
font-weight: 700;
color: #fff;
background: #003860;
background: #054f74;
padding: 2px 8px;
border-radius: 3px;
margin-bottom: 8px;
@ -101,7 +101,7 @@
border-color: #d4c8a8;
background: #fdfaf3;
}
.q-card.sub .step-tag { background: #1d7281; }
.q-card.sub .step-tag { background: #054f74; }
.connector {
display: flex;
justify-content: center;
@ -150,9 +150,9 @@
white-space: nowrap;
}
.option-label:hover { transform: scale(1.05); }
.option-label.yes { background: #E8F0F5; color: #003860; }
.option-label.yes { background: #e0edf2; color: #054f74; }
.option-label.no { background: #faf0e0; color: #8a5a20; }
.option-label.neutral { background: #eef0f3; color: #5a6472; }
.option-label.neutral { background: #ebe4d8; color: #4a6270; }
.sol-card {
width: 100%;
border-radius: 10px;
@ -166,7 +166,7 @@
box-shadow: 0 8px 24px rgba(0,0,0,0.08);
}
.sol-card.refund {
background: linear-gradient(135deg, #E8F0F5 0%, #DCE8F0 100%);
background: linear-gradient(135deg, #e0edf2 0%, #d4e4ec 100%);
border-color: #b0ccb8;
}
.sol-card.exempt {
@ -187,7 +187,7 @@
font-size: 14.5px;
font-weight: 700;
}
.sol-card.refund .sol-title { color: #003860; }
.sol-card.refund .sol-title { color: #054f74; }
.sol-card.exempt .sol-title { color: #8a5a20; }
.sol-card.basic .sol-title { color: #4a5568; }
.sol-card .sol-badge {
@ -198,8 +198,8 @@
color: #fff;
letter-spacing: 0.5px;
}
.sol-card.refund .sol-badge { background: #003860; }
.sol-card.exempt .sol-badge { background: #1d7281; }
.sol-card.refund .sol-badge { background: #054f74; }
.sol-card.exempt .sol-badge { background: #054f74; }
.sol-card.basic .sol-badge { background: #7a8494; }
.sol-card .sol-list {
list-style: none;
@ -221,8 +221,8 @@
height: 5px;
border-radius: 50%;
}
.sol-card.refund .sol-list li::before { background: #003860; }
.sol-card.exempt .sol-list li::before { background: #1d7281; }
.sol-card.refund .sol-list li::before { background: #054f74; }
.sol-card.exempt .sol-list li::before { background: #054f74; }
.sol-card.basic .sol-list li::before { background: #7a8494; }
.sol-card .sol-result {
padding-top: 10px;
@ -231,7 +231,7 @@
font-weight: 600;
line-height: 1.5;
}
.sol-card.refund .sol-result { border-color: #c0d4c6; color: #003860; }
.sol-card.refund .sol-result { border-color: #c0d4c6; color: #054f74; }
.sol-card.exempt .sol-result { border-color: #e0d0a8; color: #8a5a20; }
.sol-card.basic .sol-result { border-color: #d0d5dc; color: #4a5568; }
.sol-card .sol-tip {
@ -282,11 +282,11 @@
}
.compare-table td {
padding: 11px 14px;
border-bottom: 1px solid #eef0f3;
border-bottom: 1px solid #ebe4d8;
color: #4a5568;
}
.compare-table tr:last-child td { border-bottom: none; }
.compare-table .col-refund { color: #003860; }
.compare-table .col-refund { color: #054f74; }
.compare-table .col-exempt { color: #9a6a2a; }
/* ========== 我们的服务 ========== */
@ -313,7 +313,7 @@
width: 44px;
height: 44px;
border-radius: 10px;
background: linear-gradient(135deg, #E8F0F5, #e0ebe4);
background: linear-gradient(135deg, #e0edf2, #ebe4d8);
display: flex;
align-items: center;
justify-content: center;
@ -335,8 +335,8 @@
display: inline-block;
font-size: 10px;
font-weight: 600;
color: #003860;
background: #E8F0F5;
color: #054f74;
background: #e0edf2;
padding: 2px 8px;
border-radius: 3px;
margin-top: 10px;
@ -373,7 +373,7 @@
font-weight: 600;
margin-bottom: 6px;
}
.mode-card.single h4 { color: #003860; }
.mode-card.single h4 { color: #054f74; }
.mode-card.double h4 { color: #5a4a7a; }
.mode-card .mode-sub {
font-size: 12px;
@ -407,7 +407,7 @@
display: block;
margin-bottom: 4px;
}
.mode-card.single .mode-result strong { color: #003860; }
.mode-card.single .mode-result strong { color: #054f74; }
.mode-card.double .mode-result strong { color: #5a4a7a; }
.mode-note {
margin-top: 16px;
@ -451,7 +451,7 @@
left: 0;
width: 14px;
height: 14px;
background: #1d7281;
background: #054f74;
color: #fff;
border-radius: 50%;
font-size: 10px;
@ -464,7 +464,7 @@
/* ========== 底部CTA ========== */
.cta-box {
margin-top: 36px;
background: linear-gradient(135deg, #003860 0%, #003860 100%);
background: linear-gradient(135deg, #054f74 0%, #054f74 100%);
border-radius: 12px;
padding: 32px 36px;
text-align: center;
@ -516,7 +516,7 @@
text-decoration: none;
}
.site-topbar__brand {
color: #003860;
color: #054f74;
font-size: 15px;
font-weight: 700;
}
@ -528,7 +528,7 @@
height: 32px;
padding: 0 14px;
border-radius: 4px;
background: #1d7281;
background: #054f74;
color: #fff;
font-size: 13px;
font-weight: 700;

View File

@ -3,15 +3,15 @@
:root {
--ai-header-h: 72px;
--ai-ease: cubic-bezier(0.22, 1, 0.36, 1);
--ai-bg: #e9ecf1;
--ai-bg-soft: #f2f4f8;
--ai-bg: #f5f4f1;
--ai-bg-soft: #faf8f6;
--ai-surface: #ffffff;
--ai-text: #121820;
--ai-text-2: #3a4450;
--ai-text-3: #66707d;
--ai-border: #d2d8e0;
--ai-border-soft: #e2e7ee;
--ai-input-bg: #e4e8ef;
--ai-border: #e0d9cc;
--ai-border-soft: #ebe4d8;
--ai-input-bg: #f0ebe3;
--ai-radius: 16px;
--ai-radius-lg: 22px;
--ai-radius-sm: 12px;
@ -26,17 +26,18 @@
rgba(255, 255, 255, 0.9) 52%,
rgba(255, 255, 255, 0.95) 100%
);
--ai-glass-panel: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
--ai-glass-panel: linear-gradient(180deg, #ffffff 0%, var(--ai-bg-soft) 100%);
--ai-glass-shadow: none;
--ai-action: #1d7281;
--ai-action-hover: #186170;
--ai-action-rgb: 29, 114, 129;
--ai-primary-soft: #d2e9ec;
--ai-action: #054f74;
--ai-action-hover: #054b6e;
--ai-action-rgb: 5, 79, 116;
--ai-primary-soft: #e0edf2;
--ai-primary-mid: color-mix(in srgb, var(--ai-action) 16%, #fff);
--ai-primary-softer: color-mix(in srgb, var(--ai-action) 10%, #fff);
--ai-navy-rgb: var(--kc-primary-rgb, 0, 56, 96);
--ai-navy-rgb: var(--kc-primary-rgb, 5, 79, 116);
--ai-accent-rgb: var(--ai-action-rgb);
--ai-canvas: var(--ai-bg);
--ai-header-bg: color-mix(in srgb, var(--ai-bg) 94%, transparent);
--ai-ink: var(--ai-text);
--ai-muted: var(--ai-text-2);
--ai-line: var(--ai-border);
@ -44,6 +45,8 @@
--ai-gold: var(--ai-action);
--ai-content-max: 1120px;
--ai-guide-panel-w: 520px;
--ai-acrylic-bg: rgba(255, 255, 255, 0.32);
--ai-acrylic-bg-primary: rgba(255, 255, 255, 0.38);
}
body.command-home {
@ -57,10 +60,10 @@ body.command-home #header.header,
body.command-home #header.header.sticked {
display: flex !important;
padding: 10px 0;
background: color-mix(in srgb, var(--ai-surface) 98%, transparent);
background: var(--ai-header-bg);
backdrop-filter: blur(14px) saturate(165%);
-webkit-backdrop-filter: blur(14px) saturate(165%);
border-bottom: 1px solid var(--ai-border);
border-bottom: 1px solid color-mix(in srgb, var(--ai-border) 28%, transparent);
box-shadow: none;
}
@ -84,6 +87,19 @@ body.command-home .site-nav-dock {
flex-direction: column;
}
body.command-home #navbar .site-nav-dock a {
display: inline-flex;
align-items: center;
gap: 8px;
}
body.command-home #navbar .site-nav-dock a .site-nav-dock__icon {
font-size: 1.05rem;
line-height: 1;
color: var(--ai-text-3);
flex-shrink: 0;
}
@media (max-width: 1279px) {
body.command-home .site-nav-dock {
display: contents;
@ -115,22 +131,21 @@ body.command-home .site-nav-dock {
z-index: 3;
transform: translate(-50%, -50%);
flex-direction: row;
align-items: flex-end;
align-items: center;
justify-content: center;
gap: 2px;
padding: 5px 6px 6px;
gap: 4px;
padding: 6px 8px;
border-radius: 22px;
border: 1px solid var(--ai-border);
background: var(--ai-surface);
backdrop-filter: blur(14px) saturate(165%);
-webkit-backdrop-filter: blur(14px) saturate(165%);
box-shadow: var(--ai-shadow);
border: 1px solid rgba(255, 255, 255, 0.72);
background: var(--ai-acrylic-bg);
backdrop-filter: blur(28px) saturate(160%);
-webkit-backdrop-filter: blur(28px) saturate(160%);
box-shadow: none;
}
body.command-home #navbar li.site-nav-dock:hover > a,
body.command-home #navbar .site-nav-dock:hover > a {
color: var(--ai-text-2);
background: transparent;
}
body.command-home #navbar .site-nav-dock a,
@ -139,48 +154,47 @@ body.command-home .site-nav-dock {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 36px;
padding: 0 16px;
gap: 6px;
min-height: 40px;
padding: 8px 14px;
border-radius: 16px;
border: 1px solid transparent;
color: var(--ai-text-2);
font-size: 0.84rem;
font-weight: 600;
letter-spacing: 0.01em;
white-space: nowrap;
background: transparent;
transform-origin: 50% 100%;
transition: background 0.18s var(--ai-ease), color 0.18s var(--ai-ease), transform 0.18s var(--ai-ease);
transition:
border-color 0.2s var(--ai-ease),
background 0.2s var(--ai-ease),
color 0.2s var(--ai-ease);
}
body.command-home #navbar .site-nav-dock a .site-nav-dock__icon {
font-size: 1rem;
line-height: 1;
color: var(--ai-text-3);
transition: color 0.2s var(--ai-ease);
}
body.command-home #navbar .site-nav-dock a:hover,
body.command-home #navbar .site-nav-dock a.active,
body.command-home #navbar .site-nav-dock a.active:focus {
color: var(--ai-text);
background: var(--ai-primary-soft);
border-color: rgba(255, 255, 255, 0.92);
background: rgba(255, 255, 255, 0.44);
transform: none;
}
body.command-home #navbar .site-nav-dock a.active,
body.command-home #navbar .site-nav-dock a.active:focus {
padding-right: 22px;
color: var(--ai-action);
}
body.command-home #navbar .site-nav-dock a:hover {
transform: translateY(-3px) scale(1.06);
}
body.command-home #navbar .site-nav-dock a.active::after {
content: '';
position: absolute;
top: 50%;
right: 8px;
left: auto;
bottom: auto;
width: 5px;
height: 5px;
border-radius: 50%;
background: var(--ai-action);
transform: translateY(-50%);
box-shadow: 0 0 0 3px rgba(var(--ai-action-rgb), 0.16);
body.command-home #navbar .site-nav-dock a:hover .site-nav-dock__icon,
body.command-home #navbar .site-nav-dock a.active .site-nav-dock__icon {
color: var(--ai-action);
}
body.command-home #navbar > ul > li.site-nav-tools {
@ -204,7 +218,7 @@ body.command-home #header .logo img {
body.command-home #header .logo h1,
body.command-home #header .brand-lockup h1,
body.command-home #header .brand-lockup .brand-name {
color: var(--kc-primary, #003860);
color: var(--kc-primary, #054f74);
font-size: 1.45rem;
}
@ -247,16 +261,18 @@ body.command-home #navbar .language-toggle-link.active:focus {
height: 32px;
margin: 0;
padding: 0 10px 0 8px;
border: 1px solid var(--ai-border);
border: 1px solid rgba(255, 255, 255, 0.72);
border-radius: var(--ai-radius-sm);
background: var(--ai-surface);
color: var(--ai-text);
background: var(--ai-acrylic-bg);
backdrop-filter: blur(28px) saturate(160%);
-webkit-backdrop-filter: blur(28px) saturate(160%);
color: var(--ai-text-2);
box-shadow: none;
cursor: pointer;
transition:
border-color 0.2s var(--ai-ease),
background 0.2s var(--ai-ease),
box-shadow 0.2s var(--ai-ease);
color 0.2s var(--ai-ease);
}
body.command-home .language-toggle-icon {
@ -275,10 +291,12 @@ body.command-home .language-toggle__next {
color: var(--ai-action);
}
body.command-home #navbar .language-toggle-link:hover {
border-color: color-mix(in srgb, var(--ai-action) 35%, var(--ai-border));
background: var(--ai-surface);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--ai-action) 12%, transparent);
body.command-home #navbar .language-toggle-link:hover,
body.command-home #navbar .language-toggle-link.active,
body.command-home #navbar .language-toggle-link.active:focus {
border-color: rgba(255, 255, 255, 0.92);
background: rgba(255, 255, 255, 0.44);
box-shadow: none;
color: var(--ai-text);
}
@ -310,48 +328,6 @@ body.command-home #ai-advisor-shell {
background: var(--ai-bg);
}
.ai-shell-bg__blobs,
.ai-shell-bg__blobs::before,
.ai-shell-bg__blobs::after {
position: absolute;
border-radius: 50%;
filter: blur(68px);
transform: translateZ(0);
pointer-events: none;
}
.ai-shell-bg__blobs {
width: min(88vw, 900px);
height: min(72vh, 720px);
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: radial-gradient(circle, rgba(255, 255, 255, 0.9) 0%, rgba(255, 255, 255, 0.45) 50%, transparent 78%);
animation: ai-bg-drift-a 20s ease-in-out infinite;
}
.ai-shell-bg__blobs::before {
content: '';
width: min(64vw, 640px);
height: min(64vw, 640px);
top: 6%;
right: -8%;
left: auto;
transform: none;
background: radial-gradient(circle, rgba(255, 255, 255, 0.75) 0%, rgba(255, 255, 255, 0.28) 48%, transparent 78%);
animation: ai-bg-drift-b 24s ease-in-out infinite;
}
.ai-shell-bg__blobs::after {
content: '';
width: min(58vw, 580px);
height: min(58vw, 580px);
bottom: -6%;
left: -2%;
background: radial-gradient(circle, rgba(255, 255, 255, 0.7) 0%, rgba(255, 255, 255, 0.24) 46%, transparent 78%);
animation: ai-bg-drift-c 18s ease-in-out infinite;
}
.ai-shell-bg__veil {
display: none;
}
@ -387,7 +363,7 @@ body.command-home #ai-advisor-shell {
width: min(50vw, 720px);
height: min(58%, 760px);
margin: 0;
opacity: 0.62;
opacity: 0.24;
overflow: hidden;
transform: none;
-webkit-mask-image: linear-gradient(to right, #000 90%, transparent 100%),
@ -645,39 +621,8 @@ body.command-home .ai-stage-map--eu {
opacity: 1;
}
@keyframes ai-bg-drift-a {
0%,
100% {
transform: translate(-50%, -50%) scale(1);
}
50% {
transform: translate(calc(-50% + 2%), calc(-50% + 3%)) scale(1.05);
}
}
@keyframes ai-bg-drift-b {
0%,
100% {
transform: translate(0, 0) scale(1);
}
50% {
transform: translate(-5%, 4%) scale(1.1);
}
}
@keyframes ai-bg-drift-c {
0%,
100% {
transform: translate(0, 0) scale(1);
}
50% {
transform: translate(3%, -5%) scale(1.06);
}
}
/* iOS liquid glass — intent doors, reply cards */
.ai-intent-door,
.ai-card {
/* iOS liquid glass — intent doors */
.ai-intent-door {
position: relative;
isolation: isolate;
overflow: hidden;
@ -692,8 +637,7 @@ body.command-home .ai-stage-map--eu {
transition: transform 0.22s var(--ai-ease), box-shadow 0.22s var(--ai-ease), border-color 0.22s var(--ai-ease);
}
.ai-intent-door::before,
.ai-card::before {
.ai-intent-door::before {
content: '';
position: absolute;
inset: 0;
@ -707,8 +651,7 @@ body.command-home .ai-stage-map--eu {
opacity: 0.75;
}
.ai-intent-door::after,
.ai-card::after {
.ai-intent-door::after {
content: '';
position: absolute;
width: 120%;
@ -724,8 +667,7 @@ body.command-home .ai-stage-map--eu {
animation: ai-ios-glass-blob 10s ease-in-out infinite;
}
.ai-intent-door > *,
.ai-card > * {
.ai-intent-door > * {
position: relative;
z-index: 1;
}
@ -742,10 +684,7 @@ body.command-home .ai-stage-map--eu {
}
}
.ai-composer,
.ai-msg-row--assistant .ai-msg-bubble,
.ai-guide-panel,
.ai-history-sidebar {
.ai-msg-row--assistant .ai-msg-bubble {
position: relative;
isolation: isolate;
border: 1px solid var(--ai-border);
@ -758,13 +697,32 @@ body.command-home .ai-stage-map--eu {
}
.ai-composer {
position: relative;
isolation: isolate;
overflow: hidden;
min-height: 88px;
padding: 16px 16px 16px 24px;
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.72);
background: var(--ai-acrylic-bg);
backdrop-filter: blur(28px) saturate(160%);
-webkit-backdrop-filter: blur(28px) saturate(160%);
box-shadow: none;
transition:
border-color 0.2s var(--ai-ease),
background 0.2s var(--ai-ease);
}
.ai-composer::before,
.ai-msg-row--assistant .ai-msg-bubble:not(:has(.ai-card))::before,
.ai-guide-panel::before,
.ai-history-sidebar::before {
.ai-composer::before {
display: none;
}
.ai-composer:focus-within {
border-color: rgba(255, 255, 255, 0.92);
background: rgba(255, 255, 255, 0.44);
}
.ai-msg-row--assistant .ai-msg-bubble:not(:has(.ai-card))::before {
content: '';
position: absolute;
inset: 0;
@ -779,22 +737,13 @@ body.command-home .ai-stage-map--eu {
}
.ai-composer > .ai-composer-form,
.ai-msg-row--assistant .ai-msg-bubble > *,
.ai-guide-panel > *,
.ai-history-sidebar > * {
.ai-msg-row--assistant .ai-msg-bubble > * {
position: relative;
z-index: 1;
}
.ai-card-opt,
.ai-card-link,
.ai-followup-chip,
.ai-sidebar-toggle,
.ai-new-chat-ghost,
.ai-new-chat-btn,
.ai-guide-panel__close,
.ai-card__item,
.ai-history-item {
.ai-card-link {
position: relative;
isolation: isolate;
border: 1px solid var(--ai-border);
@ -809,13 +758,33 @@ body.command-home .ai-stage-map--eu {
}
.ai-card-opt::before,
.ai-card-link::before,
.ai-followup-chip::before,
.ai-card-link::before {
content: none;
}
/* 工具栏 / 快捷芯片 — 极简磨砂玻璃(与意图卡片、导航坞一致) */
.ai-sidebar-toggle,
.ai-new-chat-ghost,
.ai-new-chat-btn,
.ai-followup-chip,
.ai-history-item {
position: relative;
isolation: isolate;
border: 1px solid rgba(255, 255, 255, 0.72);
background: var(--ai-acrylic-bg);
backdrop-filter: blur(28px) saturate(160%);
-webkit-backdrop-filter: blur(28px) saturate(160%);
box-shadow: none;
transition:
border-color 0.2s var(--ai-ease),
background 0.2s var(--ai-ease),
color 0.2s var(--ai-ease);
}
.ai-sidebar-toggle::before,
.ai-new-chat-ghost::before,
.ai-new-chat-btn::before,
.ai-guide-panel__close::before,
.ai-card__item::before,
.ai-followup-chip::before,
.ai-history-item::before {
content: none;
}
@ -847,6 +816,16 @@ body.command-home .ai-stage-map--eu {
top: 8px;
left: 0;
z-index: 3;
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px;
border-radius: 18px;
border: 1px solid rgba(255, 255, 255, 0.72);
background: var(--ai-acrylic-bg);
backdrop-filter: blur(28px) saturate(160%);
-webkit-backdrop-filter: blur(28px) saturate(160%);
box-shadow: none;
}
.ai-sidebar-toggle,
@ -856,9 +835,13 @@ body.command-home .ai-stage-map--eu {
justify-content: center;
gap: 6px;
min-height: 34px;
padding: 0 10px;
border-radius: 12px;
color: var(--ai-muted);
padding: 0 12px;
border-radius: 14px;
border: 1px solid transparent;
background: transparent;
backdrop-filter: none;
-webkit-backdrop-filter: none;
color: var(--ai-text-2);
font-size: 0.78rem;
font-weight: 600;
cursor: pointer;
@ -867,14 +850,26 @@ body.command-home .ai-stage-map--eu {
.ai-sidebar-toggle {
width: 34px;
padding: 0;
margin-right: 6px;
}
.ai-sidebar-toggle i,
.ai-new-chat-ghost i {
font-size: 0.95rem;
line-height: 1;
color: var(--ai-text-3);
transition: color 0.2s var(--ai-ease);
}
.ai-sidebar-toggle:hover,
.ai-new-chat-ghost:hover {
color: var(--ai-action);
border-color: color-mix(in srgb, var(--ai-action) 35%, var(--ai-border));
background: var(--ai-primary-soft);
border-color: rgba(255, 255, 255, 0.92);
background: rgba(255, 255, 255, 0.44);
}
.ai-sidebar-toggle:hover i,
.ai-new-chat-ghost:hover i {
color: var(--ai-action);
}
.ai-history-sidebar {
@ -887,11 +882,20 @@ body.command-home .ai-stage-map--eu {
display: flex;
flex-direction: column;
padding: 16px 12px 20px;
border-right: 1px solid var(--ai-border);
border-right: 1px solid rgba(255, 255, 255, 0.55);
background: color-mix(in srgb, var(--ai-bg) 90%, var(--ai-surface));
backdrop-filter: blur(28px) saturate(160%);
-webkit-backdrop-filter: blur(28px) saturate(160%);
box-shadow: none;
transform: translateX(-105%);
transition: transform 0.28s var(--ai-ease);
}
.ai-history-sidebar > * {
position: relative;
z-index: 1;
}
#ai-advisor-shell.ai-history-open .ai-history-sidebar {
transform: translateX(0);
}
@ -907,7 +911,9 @@ body.command-home .ai-stage-map--eu {
inset: 0;
z-index: 19;
border: none;
background: rgba(14, 29, 52, 0.28);
background: color-mix(in srgb, var(--ai-bg) 42%, rgba(26, 46, 56, 0.08));
backdrop-filter: blur(8px) saturate(140%);
-webkit-backdrop-filter: blur(8px) saturate(140%);
cursor: pointer;
}
@ -921,12 +927,25 @@ body.command-home .ai-stage-map--eu {
display: flex;
flex-direction: column;
padding: 22px 20px 24px;
border-left: 1px solid var(--ai-border);
border-left: 1px solid rgba(255, 255, 255, 0.55);
background: color-mix(in srgb, var(--ai-bg) 90%, var(--ai-surface));
backdrop-filter: blur(28px) saturate(160%);
-webkit-backdrop-filter: blur(28px) saturate(160%);
box-shadow: none;
transform: translateX(105%);
transition: transform 0.28s var(--ai-ease);
overflow: hidden;
}
.ai-guide-panel::before {
display: none;
}
.ai-guide-panel > * {
position: relative;
z-index: 1;
}
#ai-advisor-shell.ai-guide-open .ai-guide-panel {
transform: translateX(0);
}
@ -964,15 +983,24 @@ body.command-home .ai-stage-map--eu {
justify-content: center;
width: 32px;
height: 32px;
border-radius: 10px;
color: var(--ai-muted);
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.72);
background: var(--ai-acrylic-bg);
backdrop-filter: blur(28px) saturate(160%);
-webkit-backdrop-filter: blur(28px) saturate(160%);
box-shadow: none;
color: var(--ai-text-2);
cursor: pointer;
transition:
border-color 0.2s var(--ai-ease),
background 0.2s var(--ai-ease),
color 0.2s var(--ai-ease);
}
.ai-guide-panel__close:hover {
color: var(--ai-action);
border-color: color-mix(in srgb, var(--ai-action) 35%, var(--ai-border));
background: var(--ai-primary-soft);
border-color: rgba(255, 255, 255, 0.92);
background: rgba(255, 255, 255, 0.44);
}
.ai-guide-panel__sub {
@ -988,7 +1016,7 @@ body.command-home .ai-stage-map--eu {
overflow-y: auto;
display: grid;
align-content: start;
gap: 8px;
gap: 10px;
padding-right: 0;
scrollbar-width: none;
-ms-overflow-style: none;
@ -1006,19 +1034,38 @@ body.command-home .ai-stage-map--eu {
column-gap: 12px;
min-height: 104px;
padding: 18px 16px 18px 14px;
border-radius: 16px;
border-radius: 20px;
text-align: left;
color: inherit;
cursor: pointer;
border: 1px solid var(--ai-border-soft);
background: var(--ai-surface);
transition: border-color 0.2s var(--ai-ease), background 0.2s var(--ai-ease);
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.72);
background: var(--ai-acrylic-bg);
backdrop-filter: blur(28px) saturate(160%);
-webkit-backdrop-filter: blur(28px) saturate(160%);
box-shadow: none;
transition:
border-color 0.2s var(--ai-ease),
background 0.2s var(--ai-ease),
color 0.2s var(--ai-ease);
}
.ai-guide-card::before,
.ai-guide-card::after {
display: none;
}
.ai-guide-card:hover,
.ai-guide-card:focus-visible {
border-color: color-mix(in srgb, var(--ai-action) 28%, var(--ai-border-soft));
background: color-mix(in srgb, var(--ai-primary-soft) 18%, var(--ai-surface));
border-color: rgba(255, 255, 255, 0.92);
background: rgba(255, 255, 255, 0.44);
box-shadow: none;
transform: none;
}
.ai-guide-card:hover .ai-guide-card__title,
.ai-guide-card:focus-visible .ai-guide-card__title {
color: var(--ai-action);
}
.ai-guide-card__icon {
@ -1061,7 +1108,8 @@ body.command-home .ai-stage-map--eu {
align-items: center;
padding: 1px 7px;
border-radius: 999px;
background: var(--ai-primary-soft);
border: 1px solid rgba(255, 255, 255, 0.55);
background: rgba(255, 255, 255, 0.28);
color: var(--ai-action);
font-size: 0.68rem;
font-weight: 700;
@ -1115,19 +1163,26 @@ body.command-home .ai-stage-map--eu {
margin-bottom: 12px;
padding: 0 12px;
border-radius: 14px;
color: var(--ai-ink);
color: var(--ai-text-2);
font-size: 0.84rem;
font-weight: 600;
cursor: pointer;
}
.ai-new-chat-btn i {
color: var(--ai-gold);
font-size: 0.95rem;
line-height: 1;
color: var(--ai-text-3);
transition: color 0.2s var(--ai-ease);
}
.ai-new-chat-btn:hover {
border-color: color-mix(in srgb, var(--ai-action) 35%, var(--ai-border));
background: var(--ai-primary-soft);
border-color: rgba(255, 255, 255, 0.92);
background: rgba(255, 255, 255, 0.44);
color: var(--ai-action);
}
.ai-new-chat-btn:hover i {
color: var(--ai-action);
}
@ -1137,7 +1192,7 @@ body.command-home .ai-stage-map--eu {
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 4px;
gap: 6px;
scrollbar-width: none;
-ms-overflow-style: none;
}
@ -1157,7 +1212,7 @@ body.command-home .ai-stage-map--eu {
align-items: flex-start;
gap: 10px;
width: 100%;
padding: 10px;
padding: 10px 12px;
border-radius: 14px;
text-align: left;
cursor: pointer;
@ -1166,8 +1221,12 @@ body.command-home .ai-stage-map--eu {
.ai-history-item:hover,
.ai-history-item.is-active {
border-color: color-mix(in srgb, var(--ai-action) 35%, var(--ai-border));
background: var(--ai-primary-soft);
border-color: rgba(255, 255, 255, 0.92);
background: rgba(255, 255, 255, 0.44);
}
.ai-history-item.is-active .ai-history-item__title {
color: var(--ai-action);
}
.ai-history-item__icon {
@ -1178,9 +1237,15 @@ body.command-home .ai-stage-map--eu {
width: 28px;
height: 28px;
border-radius: 8px;
background: var(--ai-primary-soft);
color: var(--ai-action);
background: transparent;
color: var(--ai-text-3);
font-size: 0.85rem;
transition: color 0.2s var(--ai-ease);
}
.ai-history-item:hover .ai-history-item__icon,
.ai-history-item.is-active .ai-history-item__icon {
color: var(--ai-action);
}
.ai-history-item__title {
@ -1342,7 +1407,7 @@ body.command-home .ai-stage-map--eu {
line-height: 1.22;
white-space: normal;
text-align: center;
color: var(--kc-primary, #003860);
color: var(--kc-primary, #054f74);
}
.ai-welcome-sub {
@ -1375,24 +1440,25 @@ body.command-home .ai-stage-map--eu {
}
.ai-welcome__command .ai-intent-grid {
gap: 6px 16px;
gap: 10px 14px;
margin-top: 4px;
padding-top: 18px;
border-top: 1px solid color-mix(in srgb, var(--ai-border) 50%, transparent);
}
/* 首页意图卡片 — 极简磨砂玻璃 */
.ai-welcome__command .ai-intent-door {
border: 1px solid color-mix(in srgb, var(--ai-border) 82%, transparent);
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.72);
border-radius: 20px;
padding: 14px 14px;
background: var(--ai-acrylic-bg);
backdrop-filter: blur(28px) saturate(160%);
-webkit-backdrop-filter: blur(28px) saturate(160%);
box-shadow: none;
background: color-mix(in srgb, var(--ai-surface) 72%, transparent);
backdrop-filter: none;
-webkit-backdrop-filter: none;
border-radius: 12px;
padding: 12px 10px;
transition:
background 0.2s var(--ai-ease),
border-color 0.2s var(--ai-ease),
color 0.2s var(--ai-ease);
background 0.2s var(--ai-ease);
}
.ai-welcome__command .ai-intent-door::before,
@ -1400,26 +1466,56 @@ body.command-home .ai-stage-map--eu {
display: none;
}
.ai-welcome__command .ai-intent-door.is-primary {
background: color-mix(in srgb, var(--ai-primary-soft) 38%, transparent);
.ai-welcome__command .ai-intent-door__icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
border: none;
background: transparent;
box-shadow: none;
border: 1px solid color-mix(in srgb, var(--ai-action) 32%, var(--ai-border));
border-left: 2px solid var(--ai-action);
padding-left: 14px;
border-radius: 12px;
}
.ai-welcome__command .ai-intent-door.is-primary {
padding: 18px 16px;
background: var(--ai-acrylic-bg-primary);
border-color: rgba(255, 255, 255, 0.8);
box-shadow: none;
}
.ai-welcome__command .ai-intent-door.is-primary .ai-intent-door__icon {
width: 44px;
height: 44px;
background: transparent;
border: none;
box-shadow: none;
}
.ai-welcome__command .ai-intent-door.is-primary .ai-intent-door__title {
color: var(--ai-action);
}
.ai-welcome__command .ai-intent-door:hover {
transform: none;
border-color: color-mix(in srgb, var(--ai-action) 38%, var(--ai-border));
background: color-mix(in srgb, var(--ai-primary-soft) 55%, transparent);
border-color: rgba(255, 255, 255, 0.92);
background: rgba(255, 255, 255, 0.44);
box-shadow: none;
transform: none;
}
.ai-welcome__command .ai-intent-door.is-primary:hover {
border-color: color-mix(in srgb, var(--ai-action) 45%, var(--ai-border));
border-left-color: var(--ai-action);
background: color-mix(in srgb, var(--ai-primary-soft) 65%, transparent);
background: rgba(255, 255, 255, 0.48);
box-shadow: none;
transform: none;
}
.ai-welcome__command .ai-intent-door__title {
font-weight: 700;
letter-spacing: -0.01em;
}
.ai-welcome__command .ai-intent-door__hint {
color: var(--ai-text-2);
}
.ai-welcome-plane {
@ -1633,7 +1729,7 @@ body.command-home .ai-stage-map--eu {
font-weight: 800;
letter-spacing: -0.02em;
line-height: 1.1;
color: var(--kc-primary, #003860);
color: var(--kc-primary, #054f74);
}
.ai-welcome-stats__label {
@ -1963,10 +2059,28 @@ body.command-home .ai-stage-map--eu {
40% { opacity: 1; transform: scale(1); }
}
/* Cards inside assistant replies */
/* Cards inside assistant replies — 极简磨砂玻璃 */
.ai-card {
position: relative;
isolation: isolate;
overflow: hidden;
padding: 18px 18px 16px;
border-radius: var(--ai-radius-lg);
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.72);
background: var(--ai-acrylic-bg);
backdrop-filter: blur(28px) saturate(160%);
-webkit-backdrop-filter: blur(28px) saturate(160%);
box-shadow: none;
}
.ai-card::before,
.ai-card::after {
display: none;
}
.ai-card > * {
position: relative;
z-index: 1;
}
.ai-card__kicker {
@ -2014,9 +2128,9 @@ body.command-home .ai-stage-map--eu {
padding: 10px 12px;
border-radius: var(--ai-radius-sm);
text-align: left;
border-color: var(--ai-border-soft);
background: var(--ai-primary-mid);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9);
border: 1px solid rgba(255, 255, 255, 0.55);
background: transparent;
box-shadow: none;
}
.ai-card__item strong {
@ -2143,13 +2257,11 @@ body.command-home .ai-stage-map--eu {
font-weight: 600;
cursor: pointer;
white-space: nowrap;
background: var(--ai-primary-mid);
border-color: color-mix(in srgb, var(--ai-action) 22%, var(--ai-border));
}
.ai-followup-chip:hover {
background: var(--ai-primary-soft);
border-color: color-mix(in srgb, var(--ai-action) 45%, var(--ai-border));
border-color: rgba(255, 255, 255, 0.92);
background: rgba(255, 255, 255, 0.44);
color: var(--ai-action);
}
@ -2177,9 +2289,9 @@ body.command-home .ai-stage-map--eu {
.ai-composer-wrap.is-welcome .ai-composer {
min-height: 72px;
padding: 12px 14px 12px 18px;
border-radius: 14px;
border: none;
background: color-mix(in srgb, var(--ai-input-bg) 72%, transparent);
border-radius: 16px;
border: 1px solid rgba(255, 255, 255, 0.72);
background: var(--ai-acrylic-bg);
box-shadow: none;
}
@ -2214,22 +2326,13 @@ body.command-home .ai-stage-map--eu {
.ai-composer-wrap.is-docked {
padding: 10px 0 0;
border-top: 1px solid color-mix(in srgb, var(--ai-border) 70%, transparent);
border-top: 1px solid color-mix(in srgb, var(--ai-border) 45%, transparent);
}
.ai-composer-wrap.is-docked .ai-composer {
box-shadow: none;
}
.ai-composer {
min-height: 88px;
padding: 16px 16px 16px 24px;
border-radius: var(--ai-radius-lg);
transition:
border-color 0.2s var(--ai-ease),
box-shadow 0.2s var(--ai-ease);
}
.ai-composer__caustic,
.ai-composer__sheen {
display: none;
@ -2341,11 +2444,7 @@ body.command-home .ai-stage-map--eu {
@media (prefers-reduced-motion: reduce) {
.ai-composer__sheen,
.ai-composer__caustic,
.ai-intent-door::after,
.ai-card::after,
.ai-shell-bg__blobs,
.ai-shell-bg__blobs::before,
.ai-shell-bg__blobs::after {
.ai-intent-door::after {
animation: none;
}
}
@ -2453,7 +2552,7 @@ body.command-home .ai-stage-map--eu {
width: min(54vw, 480px);
height: min(46%, 480px);
transform: none;
opacity: 0.48;
opacity: 0.38;
}
.ai-stage-map--am {
@ -2502,8 +2601,12 @@ body.command-home .ai-stage-map--eu {
}
.ai-welcome__command .ai-intent-door.is-primary {
padding-left: 10px;
border-radius: 12px;
padding: 16px 14px;
border-radius: 20px;
}
.ai-welcome__command .ai-intent-door {
border-radius: 20px;
}
.ai-welcome-plane img {

View File

@ -18,9 +18,9 @@
/* Colors */
:root {
--color-default: #0a0d13;
--color-primary: #0d42ff;
--color-secondary: #0e1d34;
--color-default: #1a2e38;
--color-primary: #054f74;
--color-secondary: #3d6678;
}
/* Smooth scroll behavior */
@ -139,7 +139,7 @@ section {
.breadcrumbs .page-header:before {
content: "";
background-color: rgba(14, 29, 52, 0.8);
background: color-mix(in srgb, var(--kc-page-bg, #f5f4f1) 88%, var(--kc-brand, #054f74));
position: absolute;
inset: 0;
}
@ -147,12 +147,12 @@ section {
.breadcrumbs .page-header h2 {
font-size: 56px;
font-weight: 500;
color: #fff;
color: var(--kc-primary, #054f74);
font-family: var(--font-secondary);
}
.breadcrumbs .page-header p {
color: rgba(255, 255, 255, 0.8);
color: var(--kc-text-muted, #4a6270);
}
.breadcrumbs nav {
@ -390,7 +390,7 @@ section {
.navbar .get-a-quote:hover,
.navbar .get-a-quote:focus:hover {
color: #fff;
background: #2756ff;
background: var(--kc-brand-hover, var(--color-primary));
}
.navbar .get-a-quote.wms-link,
@ -1723,7 +1723,7 @@ section {
}
.hero form .btn-primary:hover {
background-color: #2756ff;
background-color: var(--kc-brand-hover, var(--color-primary));
}
.hero .stats-item {
@ -1995,15 +1995,24 @@ section {
@import url('https://fonts.googleapis.com/css2?family=Fredoka:wght@400;600;700&display=swap');
:root {
--kc-primary: #003860;
--kc-primary-rgb: 0, 56, 96;
--kc-secondary: #4A7088;
--kc-tertiary: #1d7281;
--kc-tertiary-rgb: 29, 114, 129;
--kc-neutral: #F2F5F8;
--kc-page-bg: #F2F5F8;
--kc-surface: #FFFFFF;
--kc-on-primary: #FFFFFF;
/* IDE 品牌色 + 凝脂背景 — 与 OMS 主题 / 首页 command-home 一致 */
--kc-brand: #054f74;
--kc-brand-hover: #054b6e;
--kc-brand-rgb: 5, 79, 116;
--kc-brand-soft: #e0edf2;
--kc-primary: #054f74;
--kc-primary-rgb: 5, 79, 116;
--kc-secondary: #3d6678;
--kc-tertiary: #054f74;
--kc-tertiary-rgb: 5, 79, 116;
--kc-page-bg: #f5f4f1;
--kc-neutral: #faf8f6;
--kc-surface: #ffffff;
--kc-border: #e0d9cc;
--kc-border-soft: #ebe4d8;
--kc-text: #1a2e38;
--kc-text-muted: #4a6270;
--kc-on-primary: #ffffff;
--kc-radius-sm: 10px;
--kc-radius-md: 20px;
--kc-radius-lg: 32px;
@ -2024,7 +2033,7 @@ section {
body {
background: var(--kc-page-bg);
color: var(--kc-primary);
color: var(--kc-text);
font-size: 1.05rem;
line-height: 1.55;
}
@ -2055,8 +2064,9 @@ h2,
.header,
.header.sticked {
background: var(--kc-surface);
box-shadow: 0 8px 24px rgba(var(--kc-primary-rgb), 0.12);
background: color-mix(in srgb, var(--kc-page-bg) 94%, var(--kc-surface));
border-bottom: 1px solid color-mix(in srgb, var(--kc-border) 28%, transparent);
box-shadow: none;
}
.header .logo h1 {
@ -2113,11 +2123,11 @@ h2,
.horizontal-pricing .buy-btn:hover,
.contact .php-email-form button[type=submit]:hover,
.get-a-quote .php-email-form button[type=submit]:hover {
background: #f07a32;
border-color: #f07a32;
background: var(--kc-brand-hover);
border-color: var(--kc-brand-hover);
color: var(--kc-on-primary);
transform: translateY(-1px);
box-shadow: 0 8px 20px rgba(var(--kc-tertiary-rgb), 0.35);
box-shadow: none;
}
.language-toggle-link,
@ -2131,7 +2141,7 @@ h2,
margin-top: 0;
margin-left: 14px;
padding: 0 4px 0 0;
color: rgba(255, 255, 255, 0.65);
color: var(--kc-text-muted);
font-size: 1rem;
font-weight: 400;
letter-spacing: 0;
@ -2179,7 +2189,7 @@ h2,
line-height: 1.2;
letter-spacing: 0.05em;
text-align: center;
color: rgba(255, 255, 255, 0.42);
color: var(--kc-text-muted);
overflow: hidden;
max-width: 0;
opacity: 0;
@ -2191,14 +2201,14 @@ h2,
max-width: 40px;
padding: 3px 6px;
opacity: 1;
color: var(--kc-primary);
background: var(--kc-tertiary);
box-shadow: 0 1px 6px rgba(var(--kc-tertiary-rgb), 0.4);
color: var(--kc-on-primary);
background: var(--kc-brand);
box-shadow: none;
}
.language-toggle-link:hover {
color: #fff;
background: rgba(255, 255, 255, 0.08);
color: var(--kc-primary);
background: color-mix(in srgb, var(--kc-brand) 8%, var(--kc-surface));
}
.language-toggle-link:hover .language-toggle-icon {
@ -2206,9 +2216,9 @@ h2,
}
.hero {
background-color: #142637;
background-color: var(--kc-page-bg);
background-image: none;
color: #fff;
color: var(--kc-text);
min-height: 60vh;
}
@ -2216,7 +2226,7 @@ h2,
font-size: 4.5rem;
font-weight: 700;
letter-spacing: -0.02em;
color: #fff;
color: var(--kc-primary);
line-height: 1.08;
}
@ -2231,7 +2241,7 @@ h2,
font-weight: 700;
letter-spacing: -0.02em;
line-height: 1.08;
color: #fff;
color: var(--kc-primary);
}
@media (max-width: 768px) {
@ -2256,8 +2266,8 @@ h2,
.breadcrumbs nav {
background: var(--kc-surface);
border-radius: 12px;
border: 1px solid rgba(106, 117, 184, 0.22);
box-shadow: 0 8px 24px rgba(27, 42, 107, 0.08);
border: 1px solid var(--kc-border);
box-shadow: none;
}
.services .service-card.stat-card:hover {
@ -2311,7 +2321,7 @@ h2,
}
.section-header span {
color: rgba(27, 42, 107, 0.12);
color: color-mix(in srgb, var(--kc-primary) 10%, transparent);
}
.featured-services .service-item .icon i,
@ -2336,7 +2346,7 @@ h2,
}
.contact .info-item i {
background: rgba(27, 42, 107, 0.08);
background: rgba(var(--kc-brand-rgb), 0.08);
color: var(--kc-primary);
border-radius: var(--kc-radius-md);
width: 50px;
@ -2377,7 +2387,7 @@ h2,
}
.footer .social-links a {
border-color: rgba(106, 117, 184, 0.35);
border-color: rgba(var(--kc-brand-rgb), 0.35);
border-radius: var(--kc-radius-md);
width: 46px;
height: 46px;
@ -2444,7 +2454,7 @@ h2,
.hero-stats {
padding: 28px 0;
background: var(--kc-surface, #fff);
border-bottom: 1px solid rgba(27, 42, 107, 0.08);
border-bottom: 1px solid rgba(var(--kc-brand-rgb), 0.08);
}
.hero-stats .stats-item span {
@ -2458,7 +2468,7 @@ h2,
.hero-stats .stats-item p {
margin: 6px 0 0;
font-size: 0.9rem;
color: var(--kc-secondary, #6a75b8);
color: var(--kc-secondary, var(--kc-text-muted));
}
.pricing-more-link {
@ -2484,15 +2494,15 @@ h2,
pointer-events: none;
opacity: 0.35;
background-image:
linear-gradient(rgba(255, 180, 0, 0.08) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 180, 0, 0.08) 1px, transparent 1px);
linear-gradient(rgba(var(--kc-brand-rgb), 0.06) 1px, transparent 1px),
linear-gradient(90deg, rgba(var(--kc-brand-rgb), 0.06) 1px, transparent 1px);
background-size: 48px 48px;
mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.9), transparent 85%);
}
.hero .hero-main-title,
.hero h2 {
color: #fff;
color: var(--kc-primary);
}
.hero-desc {
@ -2500,7 +2510,7 @@ h2,
max-width: 560px;
font-size: 1rem;
line-height: 1.65;
color: rgba(255, 255, 255, 0.82);
color: var(--kc-text-muted);
}
.hero-cta-row {
@ -2525,26 +2535,26 @@ h2,
}
.btn-hero-primary {
background: var(--kc-tertiary);
color: var(--kc-primary);
box-shadow: 0 8px 20px rgba(255, 180, 0, 0.35);
background: var(--kc-brand);
color: var(--kc-on-primary);
box-shadow: none;
}
.btn-hero-primary:hover {
filter: brightness(1.05);
color: var(--kc-primary);
background: var(--kc-brand-hover);
color: var(--kc-on-primary);
}
.btn-hero-secondary {
background: transparent;
color: #fff;
border: 1px solid rgba(255, 255, 255, 0.35);
color: var(--kc-brand);
border: 1px solid color-mix(in srgb, var(--kc-brand) 35%, var(--kc-border));
}
.btn-hero-secondary:hover {
border-color: rgba(255, 255, 255, 0.75);
color: #fff;
background: rgba(255, 255, 255, 0.08);
border-color: var(--kc-brand);
color: var(--kc-brand-hover);
background: color-mix(in srgb, var(--kc-brand) 6%, var(--kc-surface));
}
.hero-track-form {
@ -2567,7 +2577,7 @@ h2,
padding: 6px 6px 6px 28px;
border-radius: 999px;
border: none !important;
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.2);
box-shadow: 0 8px 24px rgba(var(--kc-brand-rgb), 0.08);
background: #fff;
}
@ -2580,7 +2590,7 @@ h2,
}
.hero-track-stats .hero-track-form .form-control::placeholder {
color: rgba(106, 117, 184, 0.75);
color: rgba(var(--kc-brand-rgb), 0.75);
}
.hero-track-stats .hero-track-form .btn-primary {
@ -2635,10 +2645,10 @@ h2,
}
.stat-card {
--liquid-a: rgba(27, 42, 107, 0.1);
--liquid-b: rgba(106, 117, 184, 0.08);
--liquid-a: rgba(var(--kc-brand-rgb), 0.1);
--liquid-b: rgba(var(--kc-brand-rgb), 0.08);
--liquid-edge: rgba(255, 255, 255, 0.78);
--liquid-glow: rgba(27, 42, 107, 0.05);
--liquid-glow: rgba(var(--kc-brand-rgb), 0.05);
position: relative;
z-index: 0;
@ -2730,15 +2740,15 @@ h2,
}
.stat-card--primary {
--liquid-a: rgba(27, 42, 107, 0.12);
--liquid-b: rgba(106, 117, 184, 0.08);
--liquid-glow: rgba(27, 42, 107, 0.06);
--liquid-a: rgba(var(--kc-brand-rgb), 0.12);
--liquid-b: rgba(var(--kc-brand-rgb), 0.08);
--liquid-glow: rgba(var(--kc-brand-rgb), 0.06);
}
.stat-card--accent {
--liquid-a: rgba(255, 180, 0, 0.14);
--liquid-b: rgba(255, 210, 80, 0.1);
--liquid-glow: rgba(255, 180, 0, 0.07);
--liquid-a: rgba(var(--kc-brand-rgb), 0.12);
--liquid-b: rgba(var(--kc-brand-rgb), 0.08);
--liquid-glow: rgba(var(--kc-brand-rgb), 0.06);
}
.stat-card--success {
@ -2748,9 +2758,9 @@ h2,
}
.stat-card--neutral {
--liquid-a: rgba(106, 117, 184, 0.12);
--liquid-a: rgba(var(--kc-brand-rgb), 0.12);
--liquid-b: rgba(180, 190, 230, 0.08);
--liquid-glow: rgba(106, 117, 184, 0.05);
--liquid-glow: rgba(var(--kc-brand-rgb), 0.05);
}
.how-step.stat-card:hover,
@ -2802,22 +2812,22 @@ h2,
letter-spacing: 0.02em;
line-height: 1.2;
color: var(--kc-primary);
background: linear-gradient(135deg, rgba(255, 180, 0, 0.22) 0%, rgba(255, 180, 0, 0.08) 100%);
border: 1px solid rgba(255, 180, 0, 0.35);
background: color-mix(in srgb, var(--kc-brand) 10%, var(--kc-surface));
border: 1px solid color-mix(in srgb, var(--kc-brand) 18%, var(--kc-border));
clip-path: polygon(10px 0, 100% 0, calc(100% - 10px) 100%, 0 100%);
-webkit-clip-path: polygon(10px 0, 100% 0, calc(100% - 10px) 100%, 0 100%);
}
.poly-highlight--primary {
color: #fff;
background: linear-gradient(135deg, var(--kc-primary) 0%, #2a3d8f 100%);
border-color: rgba(27, 42, 107, 0.2);
background: linear-gradient(135deg, var(--kc-primary) 0%, color-mix(in srgb, var(--kc-brand) 75%, #1a3d52) 100%);
border-color: rgba(var(--kc-brand-rgb), 0.2);
}
.poly-highlight--soft {
color: var(--kc-secondary);
background: rgba(27, 42, 107, 0.06);
border-color: rgba(27, 42, 107, 0.1);
background: rgba(var(--kc-brand-rgb), 0.06);
border-color: rgba(var(--kc-brand-rgb), 0.1);
clip-path: polygon(8px 0, 100% 0, calc(100% - 8px) 100%, 0 100%);
-webkit-clip-path: polygon(8px 0, 100% 0, calc(100% - 8px) 100%, 0 100%);
}
@ -2954,7 +2964,7 @@ h2,
justify-content: center;
margin-bottom: 16px;
border-radius: 0;
background: rgba(27, 42, 107, 0.08);
background: rgba(var(--kc-brand-rgb), 0.08);
color: var(--kc-primary);
font-size: 1.5rem;
clip-path: polygon(15% 0, 100% 0, 85% 100%, 0 100%);
@ -3094,7 +3104,7 @@ h2,
.faq .faq-accordion .accordion-item {
background: transparent;
border: none;
border-bottom: 1px solid rgba(27, 42, 107, 0.08);
border-bottom: 1px solid rgba(var(--kc-brand-rgb), 0.08);
}
.faq .faq-accordion .accordion-item:last-child {
@ -3220,7 +3230,7 @@ h2,
}
.call-to-action .cta-btn-outline {
border-color: rgba(27, 42, 107, 0.25) !important;
border-color: rgba(var(--kc-brand-rgb), 0.25) !important;
color: var(--kc-primary) !important;
}
@ -3242,7 +3252,7 @@ h2,
border: 1px solid rgba(255, 255, 255, 0.78);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.9),
0 6px 18px rgba(27, 42, 107, 0.05),
0 6px 18px rgba(var(--kc-brand-rgb), 0.05),
0 1px 2px rgba(15, 23, 42, 0.03);
}
@ -3339,7 +3349,7 @@ h2,
border-radius: 12px;
background: linear-gradient(155deg, rgba(255, 255, 255, 0.92) 0%, rgba(255, 255, 255, 0.78) 100%);
border: 1px solid rgba(255, 255, 255, 0.78);
box-shadow: 0 4px 14px rgba(27, 42, 107, 0.05);
box-shadow: 0 4px 14px rgba(var(--kc-brand-rgb), 0.05);
}
.glass-panel,
@ -3361,7 +3371,7 @@ h2,
background: linear-gradient(155deg, rgba(255, 255, 255, 0.92) 0%, rgba(255, 255, 255, 0.78) 48%, rgba(255, 255, 255, 0.88) 100%);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.9),
0 6px 18px rgba(27, 42, 107, 0.05),
0 6px 18px rgba(var(--kc-brand-rgb), 0.05),
0 1px 2px rgba(15, 23, 42, 0.03);
backdrop-filter: blur(12px) saturate(1.15);
-webkit-backdrop-filter: blur(12px) saturate(1.15);
@ -3461,8 +3471,8 @@ h2,
padding: 16px;
margin-bottom: 12px;
border-radius: 12px;
background: rgba(27, 42, 107, 0.04);
border: 1px solid rgba(106, 117, 184, 0.12);
background: rgba(var(--kc-brand-rgb), 0.04);
border: 1px solid rgba(var(--kc-brand-rgb), 0.12);
}
.contact .php-email-form,

View File

@ -842,7 +842,6 @@
shell.id = 'ai-advisor-shell';
shell.innerHTML = `
<div class="ai-shell-bg" aria-hidden="true">
<div class="ai-shell-bg__blobs" aria-hidden="true"></div>
<div class="ai-shell-bg__veil" aria-hidden="true"></div>
</div>
<div class="ai-stage-maps" aria-hidden="true"></div>
@ -1468,7 +1467,7 @@
const heroCta = shell.querySelector('.ai-welcome-cta');
if (heroCta) {
heroCta.textContent = t('heroCta');
heroCta.href = registerUrl();
heroCta.href = loginPortalUrl();
}
renderWelcomeStats();
const mapHint = shell.querySelector('.ai-cover-panel__hint');
@ -1931,7 +1930,7 @@
renderStaticCopy();
},
open: () => {
if (!isHomePage()) window.location.href = 'index.html';
if (!isHomePage()) window.location.href = '/';
},
close: () => closeDrawers(),
};

View File

@ -7,6 +7,112 @@
document.addEventListener('DOMContentLoaded', () => {
"use strict";
const pageBackgroundStorageKey = 'app-page-background-color';
const themeColorStorageKey = 'app-theme-color';
const DEFAULT_THEME_COLOR = '#054f74';
function hexToRgb(hex) {
const normalized = hex.replace(/^#/, '');
if (!/^[a-f\d]{6}$/i.test(normalized)) return null;
return {
r: parseInt(normalized.slice(0, 2), 16),
g: parseInt(normalized.slice(2, 4), 16),
b: parseInt(normalized.slice(4, 6), 16),
};
}
function darkenHex(hex, ratio) {
const rgb = hexToRgb(hex);
if (!rgb) return hex;
const toHex = (value) => Math.round(value).toString(16).padStart(2, '0');
return (
'#' +
toHex(rgb.r * (1 - ratio)) +
toHex(rgb.g * (1 - ratio)) +
toHex(rgb.b * (1 - ratio))
);
}
function lightenHex(hex, mixWhite) {
const rgb = hexToRgb(hex);
if (!rgb) return hex;
const mix = typeof mixWhite === 'number' ? mixWhite : 0.92;
const toHex = (value) => Math.round(value).toString(16).padStart(2, '0');
return (
'#' +
toHex(rgb.r * (1 - mix) + 255 * mix) +
toHex(rgb.g * (1 - mix) + 255 * mix) +
toHex(rgb.b * (1 - mix) + 255 * mix)
);
}
function applyStoredThemeColor() {
let color = '';
try {
color = localStorage.getItem(themeColorStorageKey) || '';
} catch {
return;
}
if (!color) color = DEFAULT_THEME_COLOR;
if (!color.startsWith('#')) color = `#${color}`;
const root = document.documentElement;
const hover = darkenHex(color, 0.05);
const soft = lightenHex(color, 0.9);
const rgb = hexToRgb(color);
const brandTokens = ['--kc-brand', '--kc-primary', '--kc-tertiary', '--color-primary', '--ai-action'];
brandTokens.forEach((token) => root.style.setProperty(token, color));
root.style.setProperty('--kc-brand-hover', hover);
root.style.setProperty('--ai-action-hover', hover);
root.style.setProperty('--kc-brand-soft', soft);
root.style.setProperty('--ai-primary-soft', soft);
if (rgb) {
const rgbStr = `${rgb.r}, ${rgb.g}, ${rgb.b}`;
['--kc-brand-rgb', '--kc-primary-rgb', '--kc-tertiary-rgb', '--ai-action-rgb', '--ai-navy-rgb'].forEach(
(token) => root.style.setProperty(token, rgbStr),
);
}
}
function softenHex(hex, mixWhite) {
const normalized = hex.replace(/^#/, '');
if (!/^[a-f\d]{6}$/i.test(normalized)) return hex;
const r = parseInt(normalized.slice(0, 2), 16);
const g = parseInt(normalized.slice(2, 4), 16);
const b = parseInt(normalized.slice(4, 6), 16);
const mix = typeof mixWhite === 'number' ? mixWhite : 0.55;
const toHex = (value) => Math.round(value).toString(16).padStart(2, '0');
return (
'#' +
toHex(r * (1 - mix) + 255 * mix) +
toHex(g * (1 - mix) + 255 * mix) +
toHex(b * (1 - mix) + 255 * mix)
);
}
function applyStoredPageBackground() {
let color = '';
try {
color = localStorage.getItem(pageBackgroundStorageKey) || '';
} catch {
return;
}
if (!color) return;
if (!color.startsWith('#')) color = `#${color}`;
const root = document.documentElement;
const warmPageBgs = new Set(['#f6f1e7', '#f5f4f1', '#faf9f7']);
const soft = softenHex(color, warmPageBgs.has(color.toLowerCase()) ? 0.42 : 0.55);
root.style.setProperty('--ai-bg', color);
root.style.setProperty('--ai-canvas', color);
root.style.setProperty('--ai-bg-soft', soft);
root.style.setProperty('--ai-header-bg', `color-mix(in srgb, ${color} 94%, transparent)`);
root.style.setProperty('--kc-page-bg', color);
root.style.setProperty('--kc-neutral', soft);
}
applyStoredThemeColor();
applyStoredPageBackground();
const languageStorageKey = 'ide-language';
const pageTitleMap = {
'IDE|Faster & Simpler': 'IDE|更快 · 更简单',
@ -447,6 +553,9 @@ document.addEventListener('DOMContentLoaded', () => {
if (!element || element.hasAttribute('data-i18n-key')) {
return;
}
if (element.closest('.site-nav-dock')) {
return;
}
const key = resolveEnglishKey(element.textContent || '');
if (key) {
element.setAttribute('data-i18n-key', key);
@ -487,7 +596,7 @@ document.addEventListener('DOMContentLoaded', () => {
'.footer-info > p',
'.footer-links h4',
'.footer-contact h4',
'#navbar ul li > a:not(#language-toggle-btn)',
'#navbar ul > li:not(.site-nav-dock) > a:not(#language-toggle-btn)',
].forEach((selector) => {
document.querySelectorAll(selector).forEach(tagI18nElement);
});

View File

@ -44,11 +44,26 @@
<ul>
<li class="site-nav-dock">
<a href="/" class="active" data-i18n-key="Home">Home</a>
<a href="#services" data-i18n-key="Services">Services</a>
<a href="#pricing" data-i18n-key="Pricing">Pricing</a>
<a href="#about" data-i18n-key="About">About</a>
<a href="#contact" data-i18n-key="Contact">Contact</a>
<a href="/" class="active">
<i class="bi bi-house-door site-nav-dock__icon" aria-hidden="true"></i>
<span data-i18n-key="Home">Home</span>
</a>
<a href="#services">
<i class="bi bi-box-seam site-nav-dock__icon" aria-hidden="true"></i>
<span data-i18n-key="Services">Services</span>
</a>
<a href="#pricing">
<i class="bi bi-tag site-nav-dock__icon" aria-hidden="true"></i>
<span data-i18n-key="Pricing">Pricing</span>
</a>
<a href="#about">
<i class="bi bi-building site-nav-dock__icon" aria-hidden="true"></i>
<span data-i18n-key="About">About</span>
</a>
<a href="#contact">
<i class="bi bi-envelope site-nav-dock__icon" aria-hidden="true"></i>
<span data-i18n-key="Contact">Contact</span>
</a>
</li>
<li class="site-nav-tools">
<button type="button" id="language-toggle-btn" class="language-toggle-link" aria-label="Switch language">

View File

@ -87,7 +87,7 @@
*/
html {
scrollbar-gutter: stable;
background: #f0f3f4;
background: #f5f4f1;
}
html.app-booting,
html.app-booting body {
@ -99,7 +99,7 @@
#app {
margin: 0;
min-height: 100%;
background: #f0f3f4;
background: #f5f4f1;
}
#app-boot {
position: fixed;
@ -114,7 +114,7 @@
display: flex;
align-items: center;
justify-content: center;
background: #f0f3f4;
background: #f5f4f1;
/*
* 必须与 global.less 首屏后状态一致:
* Vue 加载后 html 会带上 antialiased,若 boot 首帧没有,中文会突然变细像「字号缩小」。
@ -187,7 +187,7 @@
height: 22px;
margin: 0;
border: 2px solid #e5e6eb;
border-top-color: var(--app-primary-color, #007979);
border-top-color: var(--app-primary-color, #054f74);
border-radius: 50%;
box-sizing: border-box;
transform: translate(-50%, -50%);
@ -216,8 +216,8 @@
white-space: nowrap;
}
#app-boot .boot-retry:hover {
border-color: var(--app-primary-color, #007979);
color: var(--app-primary-color, #007979);
border-color: var(--app-primary-color, #054f74);
color: var(--app-primary-color, #054f74);
}
#app-boot.is-timeout .boot-spinner {
opacity: 0;

View File

@ -55,7 +55,17 @@ server {
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
# 官网静态资源(/site/index.html 通过 base href 引用 /site/assets/...)
# 官网仅静态资源在 /site/;首页保持在 /
location = /site {
return 301 /;
}
location = /site/ {
return 301 /;
}
location = /site/index.html {
return 301 /;
}
location ^~ /site/ {
try_files $uri =404;
expires 7d;

View File

@ -22,6 +22,16 @@ server {
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
location = /site {
return 301 /;
}
location = /site/ {
return 301 /;
}
location = /site/index.html {
return 301 /;
}
location ^~ /site/ {
try_files $uri =404;
expires 7d;

View File

@ -26,9 +26,11 @@ function copyDirSync(src, dest, options = {}) {
}
}
function injectSiteBaseHref(html) {
if (html.includes('<base ')) return html;
return html.replace('<head>', '<head>\n <base href="/site/">');
/** 生产环境首页在 /,静态资源在 /site/assets/;勿用 base href(会把地址栏带到 /site/) */
function rewriteSiteAssetPaths(content) {
return content
.replace(/((?:href|src)=["'])assets\//g, '$1/site/assets/')
.replace(/(["'])assets\//g, '$1/site/assets/');
}
if (fs.existsSync(legacyLoginDir)) {
@ -49,10 +51,22 @@ function copyOfficialSite() {
skipDirNames: new Set(['.well-known']),
});
const siteIndex = path.join(siteDir, 'index.html');
if (fs.existsSync(siteIndex)) {
const html = fs.readFileSync(siteIndex, 'utf8');
fs.writeFileSync(siteIndex, injectSiteBaseHref(html), 'utf8');
for (const rel of ['index.html', '404.html', '9610.html']) {
const filePath = path.join(siteDir, rel);
if (!fs.existsSync(filePath)) continue;
const html = fs.readFileSync(filePath, 'utf8');
fs.writeFileSync(filePath, rewriteSiteAssetPaths(html), 'utf8');
}
const jsDir = path.join(siteDir, 'assets', 'js');
if (fs.existsSync(jsDir)) {
for (const name of fs.readdirSync(jsDir)) {
if (!name.endsWith('.js')) continue;
const filePath = path.join(jsDir, name);
const js = fs.readFileSync(filePath, 'utf8');
if (!js.includes('assets/')) continue;
fs.writeFileSync(filePath, rewriteSiteAssetPaths(js), 'utf8');
}
}
const src9610 = path.join(siteSrc, '9610.html');

View File

@ -1,52 +1,42 @@
<template>
<a-config-provider :locale="locale">
<router-view />
<AdminLoginCareToast v-if="showAdminCareToast" />
<GlobalSetting v-if="showGlobalSetting" />
</a-config-provider>
</template>
<script lang="ts" setup>
import { computed, defineAsyncComponent } from 'vue';
import { computed } from 'vue';
import { useRoute } from 'vue-router';
import enUS from '@arco-design/web-vue/es/locale/lang/en-us';
import zhCN from '@arco-design/web-vue/es/locale/lang/zh-cn';
import useLocale from '@/hooks/locale';
import { isLoginRouteName } from '@/utils/login-portal';
/** Settings drawer not needed on login; skip load to shrink first paint */
const GlobalSetting = defineAsyncComponent(
() => import('@/components/global-setting/index.vue'),
);
/** 管理端关怀 Toast:H5 不挂,避免无关模块进客户首屏 */
const AdminLoginCareToast = defineAsyncComponent(
() => import('@/views/login/components/AdminLoginCareToast.vue'),
);
import GlobalSetting from '@/components/global-setting/index.vue';
const route = useRoute();
function isH5LikePath(path: string) {
function isStandalonePortalPath(path: string) {
return (
path.startsWith('/h5') ||
path.startsWith('/wechat/') ||
path.startsWith('/track/')
path === '/track/query' ||
path.startsWith('/track/query/')
);
}
// route.name can be empty on first tick — do not load settings until we know it's not login
const showGlobalSetting = computed(() => {
const name = route.name ? String(route.name) : '';
if (!name) return false;
if (isLoginRouteName(name)) return false;
const path = String(route.path || '');
if (isH5LikePath(path)) return false;
return true;
});
function isLoginPath(path: string) {
return path === '/login' || path === '/admin' || path === '/h5/login';
}
const showAdminCareToast = computed(() => {
const showGlobalSetting = computed(() => {
const path = String(route.path || '');
if (!path) return false;
return !isH5LikePath(path);
if (isStandalonePortalPath(path)) return false;
if (isLoginPath(path)) return false;
const name = route.name ? String(route.name) : '';
if (name && isLoginRouteName(name)) return false;
return true;
});
const { currentLocale } = useLocale();

View File

@ -31,6 +31,20 @@ function unwrapData<T>(body: unknown): T {
return (payload.data ?? payload.Data ?? body) as T
}
export interface FulfillWorkplaceStats {
pendingBind: number
domesticInTransit: number
arrivedPendingPick: number
outboundDone: number
}
export interface PickupWorkplaceStats {
pendingPayment: number
pendingSchedule: number
pendingPickup: number
pendingInbound: number
}
export interface OrderWorkplaceStats {
total: number
counts: Record<string, number>
@ -44,6 +58,20 @@ export interface OrderWorkplaceStats {
claimed: number
storeCount: number
storeOrderPending: number
fulfill: FulfillWorkplaceStats
pickup: PickupWorkplaceStats
}
export interface OrderWorkplaceBusinessTrendPoint {
label: string
waybillCount: number
fulfillCount: number
pickupCount: number
}
export interface OrderWorkplaceBusinessTrend {
dimension: string
points: OrderWorkplaceBusinessTrendPoint[]
}
export interface OrderWorkplaceTrendPoint {
@ -72,6 +100,42 @@ export interface OrderWorkplaceSpendTrend {
points: OrderWorkplaceSpendTrendPoint[]
}
function normalizeFulfillStats(raw: unknown): FulfillWorkplaceStats {
const row =
typeof raw === 'object' && raw != null ? (raw as Record<string, unknown>) : {}
const num = (...keys: string[]) => {
for (const key of keys) {
const v = row[key]
if (v != null && Number.isFinite(Number(v))) return Number(v) || 0
}
return 0
}
return {
pendingBind: num('pendingBind', 'PendingBind'),
domesticInTransit: num('domesticInTransit', 'DomesticInTransit'),
arrivedPendingPick: num('arrivedPendingPick', 'ArrivedPendingPick'),
outboundDone: num('outboundDone', 'OutboundDone'),
}
}
function normalizePickupStats(raw: unknown): PickupWorkplaceStats {
const row =
typeof raw === 'object' && raw != null ? (raw as Record<string, unknown>) : {}
const num = (...keys: string[]) => {
for (const key of keys) {
const v = row[key]
if (v != null && Number.isFinite(Number(v))) return Number(v) || 0
}
return 0
}
return {
pendingPayment: num('pendingPayment', 'PendingPayment'),
pendingSchedule: num('pendingSchedule', 'PendingSchedule'),
pendingPickup: num('pendingPickup', 'PendingPickup'),
pendingInbound: num('pendingInbound', 'PendingInbound'),
}
}
function normalizeStats(raw: Record<string, unknown>): OrderWorkplaceStats {
const countsRaw = raw.counts ?? raw.Counts
const counts =
@ -100,6 +164,28 @@ function normalizeStats(raw: Record<string, unknown>): OrderWorkplaceStats {
Number(raw.claimed ?? raw.Claimed ?? countOf('claimed', 'Claimed') ?? 0) || 0,
storeCount: Number(raw.storeCount ?? raw.StoreCount ?? 0) || 0,
storeOrderPending: Number(raw.storeOrderPending ?? raw.StoreOrderPending ?? 0) || 0,
fulfill: normalizeFulfillStats(raw.fulfill ?? raw.Fulfill),
pickup: normalizePickupStats(raw.pickup ?? raw.Pickup),
}
}
function normalizeBusinessTrend(raw: Record<string, unknown>): OrderWorkplaceBusinessTrend {
const pointsRaw = raw.points ?? raw.Points
const points = Array.isArray(pointsRaw)
? pointsRaw.map((item) => {
const row = item as Record<string, unknown>
return {
label: String(row.label ?? row.Label ?? ''),
waybillCount: Number(row.waybillCount ?? row.WaybillCount ?? 0) || 0,
fulfillCount: Number(row.fulfillCount ?? row.FulfillCount ?? 0) || 0,
pickupCount: Number(row.pickupCount ?? row.PickupCount ?? 0) || 0,
}
})
: []
return {
dimension: String(raw.dimension ?? raw.Dimension ?? 'day'),
points,
}
}
@ -152,6 +238,21 @@ export async function getOrderWorkplaceStats(): Promise<OrderWorkplaceStats> {
return normalizeStats(data)
}
/** 工作台业务线趋势 GET /order/workplace-business-trend */
export async function getOrderWorkplaceBusinessTrend(params: {
dimension: 'day' | 'week' | 'month'
count?: number
}): Promise<OrderWorkplaceBusinessTrend> {
const res = await axiosInstance.get(orderUrl('/order/workplace-business-trend'), {
params: {
dimension: params.dimension,
count: params.count,
},
})
const data = unwrapData<Record<string, unknown>>(res.data)
return normalizeBusinessTrend(data)
}
/** 工作台订单趋势 GET /order/workplace-trend */
export async function getOrderWorkplaceTrend(params: {
dimension: 'day' | 'week' | 'month'

View File

@ -1,20 +1,26 @@
/* 主题色 CSS 变量(默认值,运行时由 themeColor 工具覆盖) */
:root {
--app-primary-color: #1d7281;
--app-primary-color-hover: #186170;
--app-primary-color-light: #e8f3f5;
--app-navbar-bg: #1d7281;
--app-sidebar-bg: #1d7281;
--app-page-bg: #f0f3f4;
--app-primary-color: #054f74;
--app-primary-color-hover: #054b6e;
--app-primary-color-light: #eef3f6;
--app-navbar-bg: #054f74;
--app-sidebar-bg: #054f74;
--app-page-bg: #f5f4f1;
--app-card-bg: #ffffff;
--app-card-border: #e5e6eb;
--app-elevated-bg: #ffffff;
/* 官网同款极简磨砂玻璃 */
--app-acrylic-bg: rgba(255, 255, 255, 0.32);
--app-acrylic-hover-bg: rgba(255, 255, 255, 0.44);
--app-acrylic-border: rgba(255, 255, 255, 0.72);
--app-acrylic-border-strong: rgba(255, 255, 255, 0.92);
--app-acrylic-blur: blur(28px) saturate(160%);
--app-button-gap: 8px; /* 相邻按钮间距,规范统一 */
/* 同步 Arco Design 主色(rgb 分量) */
--primary-6: 29, 114, 129;
--primary-5: 48, 135, 150;
--primary-7: 24, 97, 112;
--color-primary-light-1: #e8f3f5;
--primary-6: 5, 79, 116;
--primary-5: 38, 103, 134;
--primary-7: 5, 75, 110;
--color-primary-light-1: #eef3f6;
}
* {
@ -424,17 +430,23 @@ body .arco-button + .arco-btn {
}
.general-card {
border: none;
border-radius: 4px;
border: 1px solid var(--app-acrylic-border);
border-radius: 16px;
background: var(--app-acrylic-bg);
backdrop-filter: var(--app-acrylic-blur);
-webkit-backdrop-filter: var(--app-acrylic-blur);
box-shadow: none;
& > .arco-card-header {
height: auto;
padding: 20px;
border: none;
background: transparent;
}
& > .arco-card-body {
padding: 0 20px 20px;
background: transparent;
}
}

View File

@ -5,10 +5,10 @@
}
.stat-card {
--liquid-a: rgba(0, 121, 121, 0.1);
--liquid-b: rgba(125, 162, 255, 0.08);
--liquid-edge: rgba(255, 255, 255, 0.78);
--liquid-glow: rgba(0, 121, 121, 0.05);
--liquid-a: rgba(var(--primary-6), 0.1);
--liquid-b: rgba(var(--primary-6), 0.06);
--liquid-edge: var(--app-acrylic-border, rgba(255, 255, 255, 0.72));
--liquid-glow: transparent;
position: relative;
z-index: 0;
@ -22,23 +22,23 @@
padding: 14px 16px 12px;
border: 1px solid var(--liquid-edge);
border-radius: 18px;
background:
linear-gradient(155deg, rgba(255, 255, 255, 0.82) 0%, rgba(255, 255, 255, 0.55) 48%, rgba(255, 255, 255, 0.72) 100%);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.9),
inset 0 -1px 0 rgba(255, 255, 255, 0.18),
0 6px 18px var(--liquid-glow),
0 1px 2px rgba(15, 23, 42, 0.03);
backdrop-filter: blur(12px) saturate(1.15);
-webkit-backdrop-filter: blur(12px) saturate(1.15);
background: var(--app-acrylic-bg, rgba(255, 255, 255, 0.32));
box-shadow: none;
backdrop-filter: var(--app-acrylic-blur, blur(28px) saturate(160%));
-webkit-backdrop-filter: var(--app-acrylic-blur, blur(28px) saturate(160%));
text-align: left;
font: inherit;
color: inherit;
cursor: default;
transition:
border-color 0.35s ease,
box-shadow 0.35s ease,
transform 0.35s cubic-bezier(0.22, 1, 0.36, 1);
border-color 0.2s ease,
background 0.2s ease,
color 0.2s ease;
&__blob,
&__shine {
display: none;
}
&__blob,
&__shine,
@ -48,60 +48,19 @@
z-index: 1;
}
&__blob {
position: absolute;
z-index: 0;
inset: -40% -20% auto auto;
width: 78%;
height: 120%;
border-radius: 42% 58% 48% 52% / 48% 42% 58% 52%;
background:
radial-gradient(circle at 32% 28%, rgba(255, 255, 255, 0.55) 0%, transparent 42%),
radial-gradient(circle at 70% 65%, var(--liquid-a) 0%, transparent 58%),
radial-gradient(circle at 20% 80%, var(--liquid-b) 0%, transparent 55%);
filter: blur(12px);
opacity: 0.55;
pointer-events: none;
transform-origin: 60% 40%;
animation: liquid-drift 9s ease-in-out infinite;
}
&__shine {
position: absolute;
z-index: 0;
inset: 0;
border-radius: inherit;
background: linear-gradient(
115deg,
transparent 28%,
rgba(255, 255, 255, 0.05) 42%,
rgba(255, 255, 255, 0.22) 50%,
rgba(255, 255, 255, 0.05) 58%,
transparent 72%
);
background-size: 220% 100%;
background-position: 120% 0;
opacity: 0.35;
pointer-events: none;
transition: background-position 0.7s ease, opacity 0.35s ease;
}
&--primary {
--liquid-a: rgba(0, 121, 121, 0.12);
--liquid-b: rgba(64, 128, 255, 0.08);
--liquid-glow: rgba(0, 121, 121, 0.06);
--liquid-a: rgba(var(--primary-6), 0.12);
--liquid-b: rgba(var(--primary-6), 0.08);
}
&--warning {
--liquid-a: rgba(255, 125, 0, 0.12);
--liquid-b: rgba(255, 182, 93, 0.1);
--liquid-glow: rgba(255, 125, 0, 0.06);
--liquid-b: rgba(255, 182, 93, 0.08);
}
&--danger {
--liquid-a: rgba(245, 63, 63, 0.12);
--liquid-b: rgba(249, 137, 129, 0.09);
--liquid-glow: rgba(245, 63, 63, 0.06);
--liquid-b: rgba(249, 137, 129, 0.08);
&.stat-card--alert .stat-card__value {
color: #f98981;
@ -111,42 +70,25 @@
&--success {
--liquid-a: rgba(0, 180, 42, 0.1);
--liquid-b: rgba(76, 210, 99, 0.08);
--liquid-glow: rgba(0, 180, 42, 0.05);
}
&--neutral {
--liquid-a: rgba(114, 46, 209, 0.09);
--liquid-b: rgba(180, 126, 255, 0.07);
--liquid-glow: rgba(114, 46, 209, 0.04);
--liquid-a: rgba(var(--primary-6), 0.08);
--liquid-b: rgba(var(--primary-6), 0.05);
}
&--alert {
border-color: color-mix(in srgb, var(--liquid-a) 35%, white);
border-color: color-mix(in srgb, var(--app-primary-color) 22%, var(--app-acrylic-border));
}
&--clickable {
cursor: pointer;
&:hover {
border-color: rgba(255, 255, 255, 0.95);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.95),
inset 0 -1px 0 rgba(255, 255, 255, 0.22),
0 10px 24px var(--liquid-glow),
0 3px 8px rgba(15, 23, 42, 0.04);
transform: translateY(-2px);
.stat-card__blob {
animation-duration: 5.5s;
filter: blur(11px) saturate(1.1);
opacity: 0.72;
transform: scale(1.08) translate(-4%, 3%);
}
.stat-card__shine {
opacity: 0.55;
background-position: -40% 0;
}
border-color: var(--app-acrylic-border-strong, rgba(255, 255, 255, 0.92));
background: var(--app-acrylic-hover-bg, rgba(255, 255, 255, 0.44));
box-shadow: none;
transform: none;
.stat-card__hint {
opacity: 1;
@ -154,7 +96,7 @@
}
&:active {
transform: translateY(0);
transform: none;
}
&:focus-visible {
@ -183,7 +125,7 @@
&__avatar {
flex-shrink: 0;
background-color: transparent;
filter: drop-shadow(0 4px 8px rgba(15, 23, 42, 0.08));
filter: none;
:deep(img) {
width: 48px;
@ -196,7 +138,7 @@
display: inline-flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.88) !important;
background: transparent !important;
color: var(--app-primary-color, rgb(var(--primary-6)));
font-size: 22px;
}
@ -227,7 +169,7 @@
font-variant-numeric: tabular-nums;
line-height: 1;
letter-spacing: -0.02em;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.55);
text-shadow: none;
}
&__unit {
@ -275,36 +217,6 @@
}
}
@keyframes liquid-drift {
0%,
100% {
border-radius: 42% 58% 48% 52% / 48% 42% 58% 52%;
transform: translate(0, 0) rotate(0deg) scale(1);
}
33% {
border-radius: 58% 42% 55% 45% / 42% 58% 42% 58%;
transform: translate(-8%, 6%) rotate(8deg) scale(1.06);
}
66% {
border-radius: 48% 52% 40% 60% / 55% 45% 55% 45%;
transform: translate(6%, -4%) rotate(-6deg) scale(0.96);
}
}
@media (prefers-reduced-motion: reduce) {
.stat-card__blob {
animation: none;
}
.stat-card__shine {
transition: none;
}
.stat-card--clickable:hover {
transform: none;
}
}
@media (max-width: 1280px) {
.stat-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
@ -319,9 +231,4 @@
.stat-card__hint {
display: none;
}
.stat-card__blob {
animation: none;
opacity: 0.7;
}
}

View File

@ -804,7 +804,7 @@
el.style.setProperty('font-weight', '500', 'important');
el.style.setProperty('line-height', '22px', 'important');
el.style.setProperty('font-family', 'inherit', 'important');
el.style.setProperty('caret-color', 'var(--app-primary-color, #1d7281)', 'important');
el.style.setProperty('caret-color', 'var(--app-primary-color, #054f74)', 'important');
el.style.setProperty('-webkit-appearance', 'none', 'important');
el.style.setProperty('appearance', 'none', 'important');
if (el.tagName === 'BUTTON') {
@ -2759,7 +2759,7 @@
outline: none !important;
box-sizing: border-box !important;
text-align: left;
caret-color: var(--app-primary-color, #1d7281);
caret-color: var(--app-primary-color, #054f74);
-webkit-appearance: none !important;
appearance: none !important;
}

View File

@ -49,7 +49,7 @@
const colorValue = computed(() => {
const v = props.defaultValue as string;
if (!v || typeof v !== 'string') return '#1d7281';
if (!v || typeof v !== 'string') return '#054f74';
return v.startsWith('#') ? v : `#${v}`;
});

View File

@ -57,17 +57,19 @@
/>
<span class="color-swatch" :style="{ background: themeColor }" />
</div>
<div class="color-presets">
<div class="brand-presets">
<button
v-for="c in solidPresets"
:key="c"
v-for="item in brandPresets"
:key="item.color"
type="button"
class="color-preset"
:class="{ 'is-active': themeColor.toLowerCase() === c.toLowerCase() }"
:style="{ background: c }"
:title="c"
@click="setColor(c)"
/>
class="brand-preset"
:class="{ 'is-active': themeColor.toLowerCase() === item.color.toLowerCase() }"
:title="item.color"
@click="setColor(item.color)"
>
<span class="brand-preset__swatch" :style="{ background: item.color }" />
<span class="brand-preset__name">{{ $t(item.labelKey) }}</span>
</button>
</div>
<p class="settings-section__subhint">{{ $t('settings.themeColorLiquid') }}</p>
<div class="liquid-presets">
@ -88,15 +90,41 @@
</div>
</div>
<div class="settings-section">
<h5 class="settings-section__title">{{ $t('settings.pageBg') }}</h5>
<p class="settings-section__hint">{{ $t('settings.pageBgHint') }}</p>
<div class="brand-presets">
<button
v-for="item in pageBgPresets"
:key="item.color"
type="button"
class="brand-preset"
:class="{ 'is-active': pageBackgroundColor.toLowerCase() === item.color.toLowerCase() }"
:title="item.color"
@click="setPageBg(item.color)"
>
<span
class="brand-preset__swatch brand-preset__swatch--page"
:style="{ background: item.color }"
/>
<span class="brand-preset__name">{{ $t(item.labelKey) }}</span>
</button>
</div>
</div>
<p class="settings-persist-tip">{{ $t('settings.persistTip') }}</p>
</a-drawer>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { computed, onBeforeUnmount } from 'vue';
import { IconSun, IconMoon } from '@arco-design/web-vue/es/icon';
import { useAppStore } from '@/store';
import { DEFAULT_THEME_COLOR } from '@/utils/themeColor';
import {
DEFAULT_PAGE_BACKGROUND,
PAGE_BACKGROUND_PRESETS,
} from '@/utils/pageBackground';
const emit = defineEmits(['cancel']);
@ -104,16 +132,19 @@
const visible = computed(() => appStore.globalSettings);
const isDark = computed(() => appStore.theme === 'dark');
const themeColor = computed(() => appStore.themeColor || DEFAULT_THEME_COLOR);
const pageBackgroundColor = computed(
() => appStore.pageBackgroundColor || DEFAULT_PAGE_BACKGROUND,
);
// 经典纯色
const solidPresets = [
DEFAULT_THEME_COLOR,
'#007979',
'#2d8c8c',
'#165dff',
'#0fc6c2',
'#722ed1',
'#f77234',
const pageBgPresets = PAGE_BACKGROUND_PRESETS;
// 品牌主题色预设
const brandPresets = [
{ color: '#382c77', labelKey: 'settings.themeBrand.violet' },
{ color: '#004088', labelKey: 'settings.themeBrand.navy' },
{ color: '#054f74', labelKey: 'settings.themeBrand.teal' },
{ color: '#004ea2', labelKey: 'settings.themeBrand.blue' },
{ color: '#1e7fa0', labelKey: 'settings.themeBrand.cyan' },
];
// 液态暖色卡片(主色取自关怀卡片中调,样式复刻液态质感)
@ -135,6 +166,12 @@
emit('cancel');
};
onBeforeUnmount(() => {
if (appStore.globalSettings) {
appStore.updateSettings({ globalSettings: false });
}
});
const setMode = (dark: boolean) => {
appStore.toggleTheme(dark);
};
@ -143,6 +180,10 @@
appStore.updateSettings({ themeColor: hex });
};
const setPageBg = (hex: string) => {
appStore.updateSettings({ pageBackgroundColor: hex });
};
const onColorInput = (e: Event) => {
setColor((e.target as HTMLInputElement).value);
};
@ -268,6 +309,63 @@
margin-top: 12px;
}
.brand-presets {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
margin-top: 12px;
}
.brand-preset {
display: flex;
align-items: center;
gap: 8px;
min-height: 36px;
padding: 6px 10px;
border: 1px solid var(--color-border-2);
border-radius: 8px;
background: var(--color-fill-1);
cursor: pointer;
text-align: left;
transition: border-color 0.2s, background 0.2s, box-shadow 0.2s;
&:hover {
border-color: var(--color-border-3);
background: var(--color-fill-2);
}
&.is-active {
border-color: var(--app-primary-color);
background: var(--app-primary-color-light);
box-shadow: 0 0 0 1px var(--app-primary-color);
}
}
.brand-preset__swatch {
flex-shrink: 0;
width: 18px;
height: 18px;
border-radius: 50%;
border: 1px solid rgba(0, 0, 0, 0.08);
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.35);
}
.brand-preset__swatch--page {
border-radius: 6px;
}
.brand-preset__name {
font-size: 12px;
font-weight: 500;
color: var(--color-text-2);
line-height: 1.2;
}
.brand-preset.is-active .brand-preset__name {
color: var(--app-primary-color);
font-weight: 600;
}
.color-preset {
width: 24px;
height: 24px;

View File

@ -1,35 +1,39 @@
<template>
<a-dropdown trigger="hover" placement="bottomRight" popup-class="user-account-dropdown">
<a-dropdown
trigger="hover"
position="br"
@select="onDropdownSelect"
>
<div class="user-dropdown-trigger">
<span class="user-name">{{ displayName }}</span>
<IconSettings class="user-icon" />
</div>
<template #content>
<a-doption v-if="isCustomerPortal" @click="() => onMenuClick({ key: 'userinfo' })">
<a-doption v-if="isCustomerPortal" value="userinfo">
<span class="user-menu-item">
<span class="user-menu-item__icon"><i class="fa fa-user" /></span>
<span class="user-menu-item__label">{{ t('menu.user.profile') }}</span>
</span>
</a-doption>
<a-doption v-if="!isCustomerPortal" @click="() => onMenuClick({ key: 'settings' })">
<a-doption v-if="!isCustomerPortal" value="settings">
<span class="user-menu-item">
<span class="user-menu-item__icon"><i class="fa fa-cog" /></span>
<span class="user-menu-item__label">页面配置</span>
</span>
</a-doption>
<a-doption @click="() => onMenuClick({ key: 'changePassword' })">
<a-doption value="changePassword">
<span class="user-menu-item">
<span class="user-menu-item__icon"><i class="fa fa-lock" /></span>
<span class="user-menu-item__label">{{ t('profile.changePassword') }}</span>
</span>
</a-doption>
<a-doption v-if="isCustomerPortal" @click="() => onMenuClick({ key: 'settings' })">
<a-doption v-if="isCustomerPortal" value="settings">
<span class="user-menu-item">
<span class="user-menu-item__icon"><i class="fa fa-cog" /></span>
<span class="user-menu-item__label">页面配置</span>
</span>
</a-doption>
<a-doption @click="() => onMenuClick({ key: 'logout' })">
<a-doption value="logout">
<span class="user-menu-item is-danger">
<span class="user-menu-item__icon"><i class="fa fa-sign-in" /></span>
<span class="user-menu-item__label">退出登录</span>
@ -187,6 +191,11 @@ async function handleChangePasswordSubmit() {
}
}
const onDropdownSelect = (value: string | number | Record<string, unknown> | undefined) => {
if (value === undefined || value === null) return;
onMenuClick({ key: String(value) });
};
const onMenuClick = async (event: { key: string }) => {
const { key } = event;
@ -206,7 +215,7 @@ const onMenuClick = async (event: { key: string }) => {
} else if (key === 'changePassword') {
openChangePasswordModal();
} else if (key === 'settings') {
appStore.updateSettings({ globalSettings: true });
void appStore.openGlobalSettings();
}
};
</script>

View File

@ -7,7 +7,8 @@
"hideMenu": false,
"menuCollapse": false,
"footer": false,
"themeColor": "#1d7281",
"themeColor": "#054f74",
"pageBackgroundColor": "#f5f4f1",
"menuWidth": 220,
"globalSettings": false,
"device": "desktop",

View File

@ -2,6 +2,22 @@ export default {
'settings.title': 'Settings',
'settings.themeColor': 'Theme Color',
'settings.themeColorHint': 'Navbar, buttons, and selection states follow this color',
'settings.themeBrand.violet': 'Violet',
'settings.themeBrand.navy': 'Navy',
'settings.themeBrand.teal': 'Teal',
'settings.themeBrand.blue': 'Blue',
'settings.themeBrand.cyan': 'Cyan',
'settings.pageBg': 'Page Background',
'settings.pageBgHint': 'Admin content area and official site homepage',
'settings.pageBg.ningzhi': 'Cream',
'settings.pageBg.default': 'Default Gray',
'settings.pageBg.mist': 'Mist Blue',
'settings.pageBg.stone': 'Warm Stone',
'settings.pageBg.sage': 'Sage Mist',
'settings.pageBg.lilac': 'Soft Lilac',
'settings.pageBg.slate': 'Cool Slate',
'settings.pageBg.pearl': 'Pearl',
'settings.pageBg.white': 'White',
'settings.themeColorLiquid': 'Warm liquid textures',
'settings.themeLiquid.peach': 'Peach',
'settings.themeLiquid.apricot': 'Apricot',
@ -19,7 +35,7 @@ export default {
'settings.theme.lightDesc': 'Bright background',
'settings.theme.dark': 'Dark',
'settings.theme.darkDesc': 'Easier on the eyes',
'settings.persistTip': 'Theme color and appearance are saved on this device and restored next time.',
'settings.persistTip': 'Theme color, page background, and appearance are saved on this device and restored next time.',
'settings.search': 'Search',
'settings.language': 'Language',
'settings.navbar': 'Navbar',

View File

@ -2,6 +2,22 @@ export default {
'settings.title': '页面配置',
'settings.themeColor': '主题色',
'settings.themeColorHint': '导航栏、按钮、选中态等跟随此颜色',
'settings.themeBrand.violet': '深紫',
'settings.themeBrand.navy': '藏蓝',
'settings.themeBrand.teal': '墨青',
'settings.themeBrand.blue': '海蓝',
'settings.themeBrand.cyan': '青蓝',
'settings.pageBg': '页面背景',
'settings.pageBgHint': '管理端内容区与官网首页背景色',
'settings.pageBg.ningzhi': '凝脂',
'settings.pageBg.default': '默认灰',
'settings.pageBg.mist': '雾蓝',
'settings.pageBg.stone': '石米',
'settings.pageBg.sage': '苔雾',
'settings.pageBg.lilac': '薄紫',
'settings.pageBg.slate': '冷灰',
'settings.pageBg.pearl': '珍珠',
'settings.pageBg.white': '纯白',
'settings.themeColorLiquid': '暖色液态质感',
'settings.themeLiquid.peach': '蜜桃',
'settings.themeLiquid.apricot': '暖杏',
@ -19,7 +35,7 @@ export default {
'settings.theme.lightDesc': '浅色背景',
'settings.theme.dark': '夜间模式',
'settings.theme.darkDesc': '深色护眼',
'settings.persistTip': '主题色与外观模式会自动保存到本机,下次打开仍生效。',
'settings.persistTip': '主题色、页面背景与外观模式会自动保存到本机,下次打开仍生效。',
'settings.search': '搜索',
'settings.language': '语言',
'settings.navbar': '导航栏',

View File

@ -1,4 +1,5 @@
import { defineStore } from 'pinia';
import { nextTick } from 'vue';
import { Notification } from '@arco-design/web-vue';
import type { NotificationReturn } from '@arco-design/web-vue/es/notification/interface';
import type { RouteRecordNormalized } from 'vue-router';
@ -14,14 +15,21 @@ import {
setStoredThemeMode,
type ThemeMode,
} from '@/utils/themeColor';
import {
applyPageBackgroundColor,
getStoredPageBackground,
setStoredPageBackground,
} from '@/utils/pageBackground';
const useAppStore = defineStore('app', {
state: (): AppState => {
const storedColor = getStoredThemeColor();
const storedMode = getStoredThemeMode();
const themeColor = storedColor || defaultSettings.themeColor;
const pageBackgroundColor =
getStoredPageBackground() || defaultSettings.pageBackgroundColor;
const theme = (storedMode || defaultSettings.theme || 'light') as ThemeMode;
return { ...defaultSettings, themeColor, theme };
return { ...defaultSettings, themeColor, pageBackgroundColor, theme };
},
getters: {
@ -53,6 +61,19 @@ const useAppStore = defineStore('app', {
setStoredThemeMode(mode);
applyThemeMode(mode);
}
if (partial.pageBackgroundColor !== undefined) {
setStoredPageBackground(partial.pageBackgroundColor);
applyPageBackgroundColor(partial.pageBackgroundColor);
}
},
/** 打开页面配置抽屉;若状态已打开则先关再开,避免 v-if 卸载后无法再次弹出 */
async openGlobalSettings() {
if (this.globalSettings) {
this.globalSettings = false;
await nextTick();
}
this.globalSettings = true;
},
// Change theme color
@ -72,6 +93,7 @@ const useAppStore = defineStore('app', {
hydrateTheme() {
applyThemeColor(this.themeColor);
applyThemeMode(this.theme === 'dark' ? 'dark' : 'light');
applyPageBackgroundColor(this.pageBackgroundColor);
},
toggleDevice(device: string) {

View File

@ -10,6 +10,7 @@ export interface AppState {
menuCollapse: boolean;
footer: boolean;
themeColor: string;
pageBackgroundColor: string;
menuWidth: number;
globalSettings: boolean;
device: string;

View File

@ -4,7 +4,7 @@
*/
:root {
--app-page-bg: #f0f3f4;
--app-page-bg: #f5f4f1;
--app-card-bg: #ffffff;
--app-card-border: #e5e6eb;
--app-elevated-bg: #ffffff;

View File

@ -21,7 +21,7 @@ export type AdminCareSettings = {
export const DEFAULT_ADMIN_CARE_SETTINGS: AdminCareSettings = {
careEnabled: true,
loginBannerEnabled: true,
loginToastEnabled: true,
loginToastEnabled: false,
workplacePanelEnabled: true,
longAbsenceDays: 7,
lateNightThreshold: 3,

View File

@ -0,0 +1,80 @@
/**
* 页面背景色:OMS 与官网首页共用 localStorage
*/
export const PAGE_BACKGROUND_STORAGE_KEY = 'app-page-background-color';
export const DEFAULT_PAGE_BACKGROUND = '#f5f4f1';
export const PAGE_BACKGROUND_PRESETS = [
{ color: '#f0f3f4', labelKey: 'settings.pageBg.default' },
{ color: '#f6f1e7', labelKey: 'settings.pageBg.ningzhi' },
{ color: '#f4f6f9', labelKey: 'settings.pageBg.mist' },
{ color: '#f5f4f1', labelKey: 'settings.pageBg.stone' },
{ color: '#f1f4f2', labelKey: 'settings.pageBg.sage' },
{ color: '#f5f2f7', labelKey: 'settings.pageBg.lilac' },
{ color: '#f0f2f6', labelKey: 'settings.pageBg.slate' },
{ color: '#faf9f7', labelKey: 'settings.pageBg.pearl' },
{ color: '#ffffff', labelKey: 'settings.pageBg.white' },
] as const;
function hexToRgb(hex: string): { r: number; g: number; b: number } | null {
const m = hex.replace(/^#/, '').match(/^([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i);
if (!m) return null;
return {
r: parseInt(m[1], 16),
g: parseInt(m[2], 16),
b: parseInt(m[3], 16),
};
}
function rgbToHex(r: number, g: number, b: number): string {
return (
'#' +
[r, g, b]
.map((x) => Math.round(Math.max(0, Math.min(255, x))).toString(16).padStart(2, '0'))
.join('')
);
}
/** 生成略浅一档的背景色(用于官网辅助层) */
function softenHex(hex: string, mixWhite = 0.55): string {
const rgb = hexToRgb(hex);
if (!rgb) return hex;
const r = Math.round(rgb.r * (1 - mixWhite) + 255 * mixWhite);
const g = Math.round(rgb.g * (1 - mixWhite) + 255 * mixWhite);
const b = Math.round(rgb.b * (1 - mixWhite) + 255 * mixWhite);
return rgbToHex(r, g, b);
}
export function getStoredPageBackground(): string | null {
try {
return localStorage.getItem(PAGE_BACKGROUND_STORAGE_KEY);
} catch {
return null;
}
}
export function setStoredPageBackground(hex: string): void {
try {
localStorage.setItem(PAGE_BACKGROUND_STORAGE_KEY, hex);
} catch {
// ignore
}
}
/** 写入 OMS + 官网首页共用的背景 CSS 变量 */
export function applyPageBackgroundColor(hex: string): void {
const color = hex.startsWith('#') ? hex : `#${hex}`;
const root = document.documentElement;
const warmPageBgs = new Set(['#f6f1e7', '#f5f4f1', '#faf9f7']);
const soft = softenHex(color, warmPageBgs.has(color.toLowerCase()) ? 0.42 : 0.55);
root.style.setProperty('--app-page-bg', color);
root.style.setProperty('--kc-page-bg', color);
root.style.setProperty('--kc-neutral', soft);
root.style.setProperty('--ai-bg', color);
root.style.setProperty('--ai-canvas', color);
root.style.setProperty('--ai-bg-soft', soft);
root.style.setProperty('--ai-header-bg', `color-mix(in srgb, ${color} 94%, transparent)`);
}

View File

@ -13,9 +13,13 @@ const LEGACY_THEME_COLORS = new Set([
'#2d8c8c',
'#2D8C8C',
'#007979',
'#1d7281',
'#1D7281',
'#1e7fa0',
'#1E7FA0',
]);
export const DEFAULT_THEME_COLOR = '#1d7281';
export const DEFAULT_THEME_COLOR = '#054f74';
export type ThemeMode = 'light' | 'dark';
export function getStoredThemeColor(): string | null {

View File

@ -55,7 +55,7 @@ function clearPriceContext() {
<style lang="less" scoped>
.h5-assistant-page {
height: calc(100dvh - 56px - env(safe-area-inset-bottom, 0px));
height: calc(100dvh - var(--h5-dock-offset));
min-height: 360px;
padding: 10px 12px 8px;
box-sizing: border-box;

View File

@ -1,50 +1,51 @@
<template>
<nav class="h5-tabbar" aria-label="Customer center tabs">
<router-link
v-for="item in leftTabs"
:key="item.path"
:to="item.path"
replace
class="h5-tabbar__item"
:class="{ 'is-active': isActive(item.path) }"
>
<span class="h5-tabbar__icon-wrap">
<component :is="item.icon" class="h5-tabbar__icon" />
</span>
<span class="h5-tabbar__label">{{ t(item.labelKey) }}</span>
</router-link>
<button
v-if="!hideCreate"
type="button"
class="h5-tabbar__create"
:aria-label="t('h5.tab.createOrder')"
@click="emit('create-order')"
>
<span class="h5-tabbar__create-btn">
<icon-plus :size="28" class="h5-tabbar__create-plus" />
</span>
<span class="h5-tabbar__label">{{ t('h5.tab.createOrder') }}</span>
</button>
<div v-else class="h5-tabbar__create-spacer" aria-hidden="true" />
<router-link
v-for="item in rightTabs"
:key="item.path"
:to="item.path"
replace
class="h5-tabbar__item"
:class="{ 'is-active': isActive(item.path) }"
>
<span class="h5-tabbar__icon-wrap">
<component :is="item.icon" class="h5-tabbar__icon" />
<span v-if="item.badge && item.badge > 0" class="h5-tabbar__badge">
{{ item.badge > 99 ? '99+' : item.badge }}
<div class="h5-dock-shell">
<nav class="h5-dock" aria-label="Customer center tabs">
<router-link
v-for="item in leftTabs"
:key="item.path"
:to="item.path"
replace
class="h5-dock__item"
:class="{ 'is-active': isActive(item.path) }"
>
<span class="h5-dock__icon-wrap">
<component :is="item.icon" class="h5-dock__icon" />
</span>
</span>
<span class="h5-tabbar__label">{{ t(item.labelKey) }}</span>
</router-link>
</nav>
<span class="h5-dock__label">{{ t(item.labelKey) }}</span>
</router-link>
<button
v-if="!hideCreate"
type="button"
class="h5-dock__item h5-dock__item--create"
:aria-label="t('h5.tab.createOrder')"
@click="emit('create-order')"
>
<span class="h5-dock__icon-wrap h5-dock__icon-wrap--create">
<icon-plus :size="20" class="h5-dock__icon h5-dock__icon--plus" />
</span>
<span class="h5-dock__label">{{ t('h5.tab.createOrder') }}</span>
</button>
<router-link
v-for="item in rightTabs"
:key="item.path"
:to="item.path"
replace
class="h5-dock__item"
:class="{ 'is-active': isActive(item.path) }"
>
<span class="h5-dock__icon-wrap">
<component :is="item.icon" class="h5-dock__icon" />
<span v-if="item.badge && item.badge > 0" class="h5-dock__badge">
{{ item.badge > 99 ? '99+' : item.badge }}
</span>
</span>
<span class="h5-dock__label">{{ t(item.labelKey) }}</span>
</router-link>
</nav>
</div>
</template>
<script lang="ts" setup>
@ -103,124 +104,126 @@ function isActive(path: string) {
</script>
<style lang="less" scoped>
.h5-tabbar {
.h5-dock-shell {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 100;
display: flex;
align-items: flex-end;
height: calc(56px + env(safe-area-inset-bottom, 0px));
padding-bottom: env(safe-area-inset-bottom, 0px);
background: color-mix(in srgb, #fff 94%, transparent);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-top: 1px solid rgba(229, 230, 235, 0.9);
box-sizing: border-box;
box-shadow: 0 -4px 16px rgba(15, 23, 42, 0.04);
justify-content: center;
padding: 0 14px calc(6px + env(safe-area-inset-bottom, 0px));
pointer-events: none;
}
.h5-tabbar__item,
.h5-tabbar__create {
flex: 1;
.h5-dock {
pointer-events: auto;
display: flex;
align-items: center;
justify-content: center;
gap: 2px;
width: min(100%, 400px);
min-height: 48px;
padding: 4px 10px;
border-radius: 9999px;
background: color-mix(in srgb, #fff 72%, transparent);
backdrop-filter: blur(22px) saturate(185%);
-webkit-backdrop-filter: blur(22px) saturate(185%);
border: 1px solid rgba(255, 255, 255, 0.78);
box-shadow:
0 10px 36px rgba(15, 23, 42, 0.14),
0 2px 8px rgba(15, 23, 42, 0.06),
inset 0 1px 0 rgba(255, 255, 255, 0.92),
inset 0 -1px 0 rgba(15, 23, 42, 0.04);
box-sizing: border-box;
}
.h5-dock__item {
flex: 1 1 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 3px;
gap: 2px;
min-width: 0;
height: 56px;
padding: 0 2px;
color: #86909c;
text-decoration: none;
background: transparent;
border: none;
padding: 0;
font: inherit;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
transition: color 0.2s ease;
}
.h5-tabbar__item {
&.is-active {
color: var(--app-primary-color);
.h5-tabbar__icon-wrap {
.h5-dock__icon-wrap {
background: color-mix(in srgb, var(--app-primary-color) 14%, #fff);
color: var(--app-primary-color);
transform: translateY(-1px);
border-radius: 9999px;
}
.h5-tabbar__label {
.h5-dock__label {
font-weight: 600;
}
}
}
.h5-tabbar__create {
position: relative;
z-index: 1;
.h5-dock__item--create {
color: var(--app-primary-color);
justify-content: flex-end;
padding-bottom: 4px;
gap: 2px;
&:active .h5-tabbar__create-btn {
transform: translateY(-10px) scale(0.96);
.h5-dock__icon-wrap--create {
color: #fff;
background: var(--app-primary-color);
box-shadow:
0 4px 14px color-mix(in srgb, var(--app-primary-color) 38%, transparent),
inset 0 1px 0 rgba(255, 255, 255, 0.28);
}
&:active .h5-dock__icon-wrap--create {
filter: brightness(0.96);
}
.h5-dock__label {
color: var(--app-primary-color);
font-weight: 600;
}
}
.h5-tabbar__create-spacer {
flex: 0 0 8px;
height: 56px;
pointer-events: none;
}
.h5-tabbar__create-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 52px;
height: 52px;
margin-top: -22px;
border-radius: 50%;
color: #fff;
background: var(--app-primary-color);
box-shadow:
0 6px 16px color-mix(in srgb, var(--app-primary-color) 36%, transparent),
0 0 0 4px #fff;
transform: translateY(-10px);
transition: transform 0.15s ease;
}
.h5-tabbar__create-plus {
color: #fff;
pointer-events: none;
}
.h5-tabbar__icon-wrap {
.h5-dock__icon-wrap {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
width: 36px;
height: 28px;
border-radius: 14px;
width: 30px;
height: 24px;
border-radius: 0;
line-height: 1;
transition:
background 0.2s ease,
color 0.2s ease,
transform 0.2s ease;
color 0.2s ease;
}
.h5-tabbar__icon {
font-size: 20px;
.h5-dock__icon-wrap--create {
width: 32px;
height: 32px;
border-radius: 50%;
}
.h5-dock__icon {
font-size: 18px;
width: 1em;
height: 1em;
}
.h5-tabbar__badge {
.h5-dock__icon--plus {
color: #fff;
pointer-events: none;
}
.h5-dock__badge {
position: absolute;
top: -4px;
right: -2px;
@ -234,11 +237,16 @@ function isActive(path: string) {
line-height: 16px;
text-align: center;
box-sizing: border-box;
box-shadow: 0 0 0 2px #fff;
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.92);
}
.h5-tabbar__label {
font-size: 11px;
line-height: 1.2;
.h5-dock__label {
max-width: 100%;
font-size: 10px;
line-height: 1.15;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-align: center;
}
</style>

View File

@ -209,7 +209,7 @@ watch(
.h5-center-main {
flex: 1;
min-height: 0;
padding-bottom: calc(56px + var(--h5-safe-bottom));
padding-bottom: var(--h5-dock-offset);
box-sizing: border-box;
overflow: auto;
-webkit-overflow-scrolling: touch;

View File

@ -317,9 +317,7 @@ onMounted(async () => {
min-height: 100dvh;
display: flex;
flex-direction: column;
/* Stripe / Linear 常见:浅灰画布衬白卡片,突出登录区 */
background: #f5f6f8;
/* 独立路由壳:避免父级 overflow/safe-area 二次撑开导致首帧错位 */
background: var(--app-page-bg, #f0f3f4);
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
@ -340,12 +338,12 @@ onMounted(async () => {
.h5-login-oauth-screen__card {
width: min(320px, 100%);
padding: 36px 24px;
border-radius: 16px;
background: #fff;
border: 1px solid #e5e7eb;
box-shadow:
0 1px 2px rgba(15, 23, 42, 0.04),
0 8px 24px rgba(15, 23, 42, 0.06);
border-radius: 20px;
background: var(--app-acrylic-bg);
border: 1px solid var(--app-acrylic-border);
box-shadow: none;
backdrop-filter: var(--app-acrylic-blur);
-webkit-backdrop-filter: var(--app-acrylic-blur);
display: flex;
flex-direction: column;
align-items: center;
@ -380,8 +378,7 @@ onMounted(async () => {
justify-content: space-between;
height: 48px;
padding: 0 8px 0 4px;
background: #f5f6f8;
/* 卡内已有品牌标题,顶栏只留返回 + 语言,去掉底部分割避免「双标题」感 */
background: transparent;
border-bottom: none;
color: #4e5969;
}
@ -432,13 +429,13 @@ onMounted(async () => {
}
.h5-login-card {
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 16px;
background: var(--app-acrylic-bg);
border: 1px solid var(--app-acrylic-border);
border-radius: 20px;
padding: 28px 20px 22px;
box-shadow:
0 1px 2px rgba(15, 23, 42, 0.04),
0 8px 24px rgba(15, 23, 42, 0.06);
box-shadow: none;
backdrop-filter: var(--app-acrylic-blur);
-webkit-backdrop-filter: var(--app-acrylic-blur);
}
/* 品牌放进卡片:一个完整登录单元(Clerk / Vercel 鉴权卡常见) */
@ -496,19 +493,22 @@ onMounted(async () => {
.h5-login-form :deep(.arco-input-wrapper),
.h5-login-form :deep(.arco-input-password) {
border-radius: 10px;
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 12px;
background: var(--app-acrylic-bg);
border: 1px solid var(--app-acrylic-border);
min-height: 48px;
backdrop-filter: var(--app-acrylic-blur);
-webkit-backdrop-filter: var(--app-acrylic-blur);
transition:
border-color 0.2s var(--login-ease),
box-shadow 0.2s var(--login-ease);
box-shadow 0.2s var(--login-ease),
background 0.2s var(--login-ease);
&:hover,
&.arco-input-wrapper-focus,
&.arco-input-focus {
background: #fff;
border-color: var(--app-primary-color);
background: var(--app-acrylic-hover-bg);
border-color: var(--app-acrylic-border-strong);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--app-primary-color) 14%, transparent);
}
}
@ -516,11 +516,14 @@ onMounted(async () => {
.h5-login-error {
margin: 0 0 8px;
padding: 10px 12px;
border-radius: 10px;
background: #ffece8;
border-radius: 12px;
background: color-mix(in srgb, #ffece8 72%, var(--app-acrylic-bg));
border: 1px solid color-mix(in srgb, #ffb4a8 65%, var(--app-acrylic-border));
color: #f53f3f;
font-size: 13px;
line-height: 1.4;
backdrop-filter: var(--app-acrylic-blur);
-webkit-backdrop-filter: var(--app-acrylic-blur);
animation: h5-login-rise 0.28s var(--login-ease) both;
}

View File

@ -1754,7 +1754,7 @@ void bootstrap();
position: fixed;
left: 0;
right: 0;
bottom: calc(56px + env(safe-area-inset-bottom, 0px));
bottom: var(--h5-dock-offset);
z-index: 110;
display: flex;
align-items: center;
@ -2004,7 +2004,7 @@ void bootstrap();
position: fixed;
left: 12px;
right: 12px;
bottom: calc(56px + env(safe-area-inset-bottom, 0px));
bottom: var(--h5-dock-offset);
z-index: 110;
display: flex;
flex-direction: column;
@ -2164,7 +2164,7 @@ void bootstrap();
border-radius: 4px;
font-size: 11px;
font-weight: 600;
color: var(--app-primary-color, #1d7281);
color: var(--app-primary-color, #054f74);
background: rgba(29, 114, 129, 0.1);
}

View File

@ -23,6 +23,7 @@
--h5-primary-soft: color-mix(in srgb, var(--app-primary-color) 12%, #fff);
--h5-primary-softer: color-mix(in srgb, var(--app-primary-color) 8%, #fff);
--h5-safe-bottom: env(safe-area-inset-bottom, 0px);
--h5-dock-offset: calc(62px + var(--h5-safe-bottom));
background: var(--h5-bg);
color: var(--h5-text);

View File

@ -0,0 +1,297 @@
<template>
<section class="aw-trend" aria-label="business-trend">
<div class="aw-trend__head">
<p class="aw-trend__title">{{ t('workplace.admin.businessTrendTitle') }}</p>
<a-radio-group
v-model="dimension"
type="button"
size="small"
@change="handleDimensionChange"
>
<a-radio value="day">{{ t('workplace.orderAnalysis.day') }}</a-radio>
<a-radio value="week">{{ t('workplace.orderAnalysis.week') }}</a-radio>
<a-radio value="month">{{ t('workplace.orderAnalysis.month') }}</a-radio>
</a-radio-group>
</div>
<a-spin v-if="loading" class="aw-trend__loading" />
<div v-else-if="isEmpty" class="aw-trend__empty">
<p class="aw-trend__empty-title">{{ t('workplace.admin.businessTrendEmptyTitle') }}</p>
<p class="aw-trend__empty-desc">{{ t('workplace.admin.businessTrendEmptyDesc') }}</p>
</div>
<div v-else class="aw-trend__chart">
<Chart width="100%" height="320px" :options="chartOption" />
</div>
</section>
</template>
<script lang="ts" setup>
import { computed, onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import Chart from '@/components/chart/index.vue'
import { useAppStore } from '@/store'
import { getOrderWorkplaceBusinessTrend } from '@/api/workplace'
defineOptions({ name: 'AdminBusinessTrend' })
const { t } = useI18n()
const dimension = ref<'day' | 'week' | 'month'>('week')
const pointCount = ref(4)
const loading = ref(true)
const xAxisLabels = ref<string[]>([])
const waybillData = ref<number[]>([])
const fulfillData = ref<number[]>([])
const pickupData = ref<number[]>([])
const isEmpty = computed(() => {
if (xAxisLabels.value.length === 0) return true
const sum = (arr: number[]) => arr.reduce((acc, n) => acc + (Number(n) || 0), 0)
return sum(waybillData.value) === 0 && sum(fulfillData.value) === 0 && sum(pickupData.value) === 0
})
function resolvePointCount(dim: 'day' | 'week' | 'month') {
if (dim === 'week') return 4
if (dim === 'month') return 6
return 7
}
async function loadData() {
loading.value = true
try {
const trend = await getOrderWorkplaceBusinessTrend({
dimension: dimension.value,
count: pointCount.value,
})
xAxisLabels.value = trend.points.map((item) => item.label)
waybillData.value = trend.points.map((item) => item.waybillCount)
fulfillData.value = trend.points.map((item) => item.fulfillCount)
pickupData.value = trend.points.map((item) => item.pickupCount)
} catch {
xAxisLabels.value = []
waybillData.value = []
fulfillData.value = []
pickupData.value = []
} finally {
loading.value = false
}
}
function handleDimensionChange() {
pointCount.value = resolvePointCount(dimension.value)
void loadData()
}
const chartOption = computed(() => {
const appStore = useAppStore()
const isDark = appStore.theme === 'dark'
const labels = xAxisLabels.value || []
const primary = '#054f74'
const fulfillColor = isDark ? '#FFB65D' : '#D9924A'
const pickupColor = isDark ? '#36CFC9' : '#14B8A6'
return {
animationDuration: 280,
grid: {
left: 8,
right: 8,
top: 12,
bottom: 28,
containLabel: true,
},
legend: {
bottom: 0,
left: 'center',
icon: 'circle',
itemWidth: 8,
itemHeight: 8,
itemGap: 14,
textStyle: {
color: isDark ? 'rgba(255, 255, 255, 0.7)' : '#4E5969',
fontSize: 12,
},
},
tooltip: {
trigger: 'axis' as const,
confine: true,
axisPointer: { type: 'cross' as const },
},
xAxis: {
type: 'category' as const,
boundaryGap: false,
data: labels.length ? labels : ['—'],
axisLine: {
lineStyle: { color: isDark ? '#3f3f3f' : '#A9AEB8' },
},
axisTick: { show: false },
axisLabel: {
color: isDark ? 'rgba(255, 255, 255, 0.7)' : '#86909C',
hideOverlap: true,
},
},
yAxis: {
type: 'value' as const,
min: 0,
minInterval: 1,
max: (extent: { max: number }) => {
const m = Number(extent?.max) || 0
if (m <= 0) return 5
return Math.ceil(m * 1.15)
},
axisLine: { show: false },
axisLabel: {
color: isDark ? 'rgba(255, 255, 255, 0.7)' : '#86909C',
},
splitLine: {
lineStyle: { color: isDark ? '#3F3F3F' : '#E5E6EB' },
},
},
series: [
{
name: t('workplace.admin.businessTrend.waybill'),
type: 'line',
smooth: true,
data: waybillData.value,
symbol: 'circle',
symbolSize: 5,
lineStyle: { width: 2, color: primary },
itemStyle: { color: primary },
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: isDark ? 'rgba(5, 79, 116, 0.28)' : 'rgba(5, 79, 116, 0.18)',
},
{ offset: 1, color: isDark ? 'rgba(5, 79, 116, 0.04)' : 'rgba(5, 79, 116, 0.03)' },
],
},
},
},
{
name: t('workplace.admin.businessTrend.fulfill'),
type: 'line',
smooth: true,
data: fulfillData.value,
symbol: 'circle',
symbolSize: 5,
lineStyle: { width: 2, color: fulfillColor },
itemStyle: { color: fulfillColor },
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: isDark ? 'rgba(255, 182, 93, 0.22)' : 'rgba(217, 146, 74, 0.16)',
},
{ offset: 1, color: isDark ? 'rgba(255, 182, 93, 0.04)' : 'rgba(217, 146, 74, 0.03)' },
],
},
},
},
{
name: t('workplace.admin.businessTrend.pickup'),
type: 'line',
smooth: true,
data: pickupData.value,
symbol: 'circle',
symbolSize: 5,
lineStyle: { width: 2, color: pickupColor },
itemStyle: { color: pickupColor },
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: isDark ? 'rgba(54, 207, 201, 0.22)' : 'rgba(20, 184, 166, 0.16)',
},
{ offset: 1, color: isDark ? 'rgba(54, 207, 201, 0.04)' : 'rgba(20, 184, 166, 0.03)' },
],
},
},
},
],
}
})
onMounted(() => {
void loadData()
})
</script>
<style scoped lang="less">
.aw-trend {
--aw-ink: #1a2e38;
--aw-soft: rgba(26, 46, 56, 0.48);
--aw-chart-h: 320px;
padding: 12px 14px 14px;
}
.aw-trend__head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 8px;
flex-wrap: wrap;
}
.aw-trend__title {
margin: 0;
font-size: 13px;
font-weight: 700;
color: var(--aw-ink);
}
.aw-trend__loading {
display: flex;
justify-content: center;
align-items: center;
height: var(--aw-chart-h);
}
.aw-trend__empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: var(--aw-chart-h);
padding: 16px;
text-align: center;
}
.aw-trend__empty-title {
margin: 0 0 6px;
font-size: 14px;
font-weight: 600;
color: var(--aw-ink);
}
.aw-trend__empty-desc {
margin: 0;
font-size: 12px;
line-height: 1.45;
color: var(--aw-soft);
}
.aw-trend__chart {
width: 100%;
height: var(--aw-chart-h);
}
</style>

View File

@ -205,16 +205,16 @@ onMounted(() => {
@import '@/assets/style/stat-card.less';
.aw-ops {
--aw-ink: #5c3d2e;
--aw-soft: rgba(92, 61, 46, 0.48);
--aw-edge: rgba(220, 180, 140, 0.42);
--aw-shadow: 0 8px 20px rgba(160, 110, 70, 0.08);
--aw-ink: #1a2e38;
--aw-soft: rgba(26, 46, 56, 0.48);
--aw-edge: var(--app-acrylic-border);
--aw-shadow: none;
padding: 14px 16px 16px;
border-radius: 16px;
border: 1px solid var(--aw-edge);
background: #fff;
box-shadow: var(--aw-shadow);
padding: 0;
border: none;
border-radius: 0;
background: transparent;
box-shadow: none;
}
.aw-ops__head {
@ -248,15 +248,18 @@ onMounted(() => {
height: 28px;
padding: 0 10px;
border-radius: 8px;
border: 1px solid var(--aw-edge);
background: #fff;
border: 1px solid var(--app-acrylic-border);
background: var(--app-acrylic-bg);
backdrop-filter: var(--app-acrylic-blur);
-webkit-backdrop-filter: var(--app-acrylic-blur);
color: var(--aw-ink);
font-size: 12px;
font-weight: 600;
cursor: pointer;
&:hover:not(:disabled) {
border-color: rgba(193, 122, 82, 0.55);
border-color: var(--app-acrylic-border-strong);
background: var(--app-acrylic-hover-bg);
}
&:disabled {
@ -288,31 +291,11 @@ onMounted(() => {
}
.stat-card {
border: 1px solid var(--color-border-2, #e5e6eb);
border-radius: 12px;
background: #fff;
box-shadow: 0 2px 8px rgba(15, 23, 42, 0.04);
backdrop-filter: none;
-webkit-backdrop-filter: none;
min-height: 84px;
padding: 12px 14px 10px;
.stat-card__blob,
.stat-card__shine {
display: none;
}
&:hover {
border-color: color-mix(in srgb, #c17a52 28%, var(--color-border-2));
background: #fff;
box-shadow: 0 4px 14px rgba(160, 110, 70, 0.08);
transform: translateY(-1px);
}
&.is-primary {
border-color: color-mix(in srgb, #c45a3a 45%, white);
background: #fff;
box-shadow: 0 6px 16px rgba(196, 90, 58, 0.1);
border-color: color-mix(in srgb, var(--app-primary-color) 22%, var(--app-acrylic-border));
}
}
@ -323,7 +306,7 @@ onMounted(() => {
gap: 8px;
margin-top: 12px;
padding-top: 10px;
border-top: 1px dashed rgba(220, 180, 140, 0.4);
border-top: 1px dashed color-mix(in srgb, var(--app-acrylic-border) 65%, transparent);
min-width: 0;
}

View File

@ -0,0 +1,119 @@
<template>
<section
class="aw-stats-panel"
:class="`aw-stats-panel--${variant}`"
:aria-label="variant === 'fulfill' ? 'fulfill-stats' : 'pickup-stats'"
>
<p class="aw-stats-panel__title">{{ t(titleKey) }}</p>
<div v-if="loading" class="aw-stats-panel__loading">
<a-spin :size="20" />
</div>
<div v-else class="stat-grid stat-grid--inner">
<button
v-for="item in items"
:key="item.key"
type="button"
class="stat-card stat-card--clickable"
:class="[
`stat-card--${item.tone}`,
{ 'stat-card--alert': item.alert && item.value > 0 },
]"
@click="go(item)"
>
<div class="stat-card__main">
<div class="stat-card__meta">
<a-avatar :size="40" class="stat-card__avatar">
<img alt="" :src="item.icon" />
</a-avatar>
<span class="stat-card__label">{{ t(item.titleKey) }}</span>
</div>
<div class="stat-card__body">
<span class="stat-card__value">{{ formatValue(item.value) }}</span>
<span class="stat-card__unit">{{ t('workplace.pecs') }}</span>
</div>
</div>
</button>
</div>
</section>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { useWorkplaceFulfillPickupStats } from '../composables/useWorkplaceFulfillPickupStats'
defineOptions({ name: 'AdminStatsPanel' })
const props = defineProps<{
variant: 'fulfill' | 'pickup'
}>()
const { t } = useI18n()
const { loading, fulfillItems, pickupItems, formatValue, go } = useWorkplaceFulfillPickupStats()
const titleKey = computed(() =>
props.variant === 'fulfill'
? 'workplace.admin.fulfillTitle'
: 'workplace.admin.pickupTitle',
)
const items = computed(() =>
props.variant === 'fulfill' ? fulfillItems.value : pickupItems.value,
)
</script>
<style scoped lang="less">
@import '@/assets/style/stat-card.less';
.aw-stats-panel {
--aw-ink: #1a2e38;
height: 100%;
box-sizing: border-box;
padding: 12px 14px 14px;
}
.aw-stats-panel__title {
margin: 0 0 10px;
font-size: 13px;
font-weight: 700;
color: var(--aw-ink);
}
.aw-stats-panel__loading {
display: flex;
justify-content: center;
align-items: center;
min-height: 72px;
}
.stat-grid--inner {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 10px;
}
.stat-grid--inner .stat-card {
width: 100%;
min-height: 72px;
margin: 0;
padding: 10px 12px 8px;
border-radius: 14px;
}
.stat-grid--inner .stat-card__value {
font-size: 22px;
}
@media (max-width: 1280px) {
.stat-grid--inner {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 480px) {
.stat-grid--inner {
grid-template-columns: 1fr;
}
}
</style>

View File

@ -9,35 +9,49 @@
<AdminOpsQueue />
</section>
<div class="admin-left__row">
<div class="admin-left__body">
<div class="admin-left__stats">
<section class="aw-panel aw-panel--fulfill" aria-label="fulfill-stats">
<AdminStatsPanel variant="fulfill" />
</section>
<section class="aw-panel aw-panel--pickup" aria-label="pickup-stats">
<AdminStatsPanel variant="pickup" />
</section>
</div>
<section class="aw-panel aw-panel--care" aria-label="today-care">
<CareTodayPanel variant="hero" />
</section>
<section class="aw-panel aw-panel--links" aria-label="focus-links">
<p class="aw-panel__title">{{ t('workplace.admin.linksTitle') }}</p>
<div class="aw-link-grid">
<button
v-for="item in links"
:key="item.key"
type="button"
class="aw-link"
:class="`aw-tone--${item.tone}`"
@click="go(item.path, item.query)"
>
<span class="aw-link__icon" aria-hidden="true">
<component :is="item.icon" />
</span>
<span class="aw-link__body">
<span class="aw-link__label">{{ t(item.labelKey) }}</span>
<span class="aw-link__hint">{{ t(item.hintKey) }}</span>
</span>
</button>
</div>
</section>
</div>
<section class="aw-panel aw-panel--trend" aria-label="business-trend">
<AdminBusinessTrend />
</section>
</div>
<aside class="admin-right">
<section class="aw-panel aw-panel--links" aria-label="focus-links">
<p class="aw-panel__title">{{ t('workplace.admin.linksTitle') }}</p>
<div class="aw-link-grid">
<button
v-for="item in links"
:key="item.key"
type="button"
class="aw-link"
:class="`aw-tone--${item.tone}`"
@click="go(item.path, item.query)"
>
<span class="aw-link__icon" aria-hidden="true">
<component :is="item.icon" />
</span>
<span class="aw-link__body">
<span class="aw-link__label">{{ t(item.labelKey) }}</span>
<span class="aw-link__hint">{{ t(item.hintKey) }}</span>
</span>
</button>
</div>
</section>
<section class="aw-panel aw-panel--announce">
<Announcement notice-type="message" />
</section>
@ -61,6 +75,8 @@ import {
import Banner from './banner.vue'
import CareTodayPanel from './CareTodayPanel.vue'
import AdminOpsQueue from './AdminOpsQueue.vue'
import AdminStatsPanel from './AdminStatsPanel.vue'
import AdminBusinessTrend from './AdminBusinessTrend.vue'
import Announcement from './announcement.vue'
import NoticePopup from './notice-popup.vue'
import { useRbacUserStore } from '@/store/modules/rbac-user'
@ -137,13 +153,13 @@ function go(path: string, query?: Record<string, string>) {
<style scoped lang="less">
.admin-workplace {
--aw-ink: #5c3d2e;
--aw-muted: rgba(92, 61, 46, 0.68);
--aw-soft: rgba(92, 61, 46, 0.48);
--aw-edge: rgba(220, 180, 140, 0.42);
--aw-shadow: 0 8px 20px rgba(160, 110, 70, 0.06);
--aw-ink: #1a2e38;
--aw-muted: rgba(26, 46, 56, 0.68);
--aw-soft: rgba(26, 46, 56, 0.48);
--aw-edge: var(--app-acrylic-border);
--aw-shadow: none;
--aw-radius: 16px;
--aw-panel-bg: #fff;
--aw-panel-bg: var(--app-acrylic-bg);
width: 100%;
height: 100%;
@ -157,6 +173,7 @@ function go(path: string, query?: Record<string, string>) {
.admin-workplace__container {
display: flex;
flex: 1;
align-items: stretch;
gap: 14px;
min-height: 0;
width: min(1480px, 94%);
@ -169,28 +186,30 @@ function go(path: string, query?: Record<string, string>) {
.admin-left {
flex: 7;
min-width: 0;
min-height: 0;
display: flex;
flex-direction: column;
gap: 12px;
overflow: auto;
overflow: hidden;
}
.admin-left__row {
display: grid;
grid-template-columns: minmax(0, 1.15fr) minmax(260px, 0.85fr);
gap: 12px;
align-items: stretch;
.admin-left__body {
flex: 1 1 auto;
min-height: 220px;
min-height: 0;
display: flex;
flex-direction: column;
gap: 12px;
overflow-y: auto;
}
.admin-right {
flex: 3;
min-width: 280px;
max-width: 380px;
min-height: 0;
display: flex;
flex-direction: column;
min-height: 0;
gap: 12px;
overflow: hidden;
}
@ -199,6 +218,8 @@ function go(path: string, query?: Record<string, string>) {
border-radius: var(--aw-radius);
background: var(--aw-panel-bg);
box-shadow: var(--aw-shadow);
backdrop-filter: var(--app-acrylic-blur);
-webkit-backdrop-filter: var(--app-acrylic-blur);
min-width: 0;
}
@ -208,36 +229,61 @@ function go(path: string, query?: Record<string, string>) {
overflow: visible;
}
.aw-panel--care,
.aw-panel--links {
min-height: 0;
overflow: auto;
.admin-left__stats {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
gap: 12px;
align-items: start;
}
.aw-panel--fulfill,
.aw-panel--pickup {
flex: 0 0 auto;
padding: 0;
overflow: visible;
}
.aw-panel--pickup {
margin-top: 18px;
}
.aw-panel--trend {
flex: 0 0 auto;
padding: 0;
overflow: hidden;
margin-top: auto;
}
.aw-panel--care {
flex: 0 0 auto;
padding: 0;
overflow: hidden;
:deep(.care-today) {
width: 100%;
height: 100%;
min-height: 220px;
min-height: 148px;
box-sizing: border-box;
border: none;
box-shadow: none;
border-radius: var(--aw-radius);
background: #fff;
background: transparent;
}
:deep(.care-today.is-hero.is-collapsed) {
min-height: 88px;
}
}
.aw-panel--links {
flex: 0 0 auto;
padding: 14px 14px 16px;
overflow: auto;
}
.aw-panel--announce {
flex: 1 1 auto;
min-height: 0;
height: 100%;
margin-top: auto;
overflow: hidden;
display: flex;
flex-direction: column;
@ -262,7 +308,7 @@ function go(path: string, query?: Record<string, string>) {
:deep(.arco-card-header) {
flex-shrink: 0;
border-bottom: 1px solid rgba(220, 180, 140, 0.35);
border-bottom: 1px solid color-mix(in srgb, var(--app-acrylic-border) 55%, transparent);
}
:deep(.arco-card-body) {
@ -301,24 +347,29 @@ function go(path: string, query?: Record<string, string>) {
}
.aw-link {
--tone: #c17a52;
--tone-edge: rgba(193, 122, 82, 0.35);
--tone-strong: #b86b45;
--tone: var(--app-primary-color);
--tone-edge: var(--app-acrylic-border);
--tone-strong: var(--app-primary-color);
display: flex;
align-items: flex-start;
gap: 10px;
min-height: 64px;
padding: 10px 12px;
min-height: 56px;
padding: 8px 12px;
border-radius: 12px;
border: 1px solid var(--tone-edge);
background: #fff;
background: var(--app-acrylic-bg);
backdrop-filter: var(--app-acrylic-blur);
-webkit-backdrop-filter: var(--app-acrylic-blur);
box-shadow: none;
text-align: left;
cursor: pointer;
transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
transition: border-color 0.2s ease, background 0.2s ease;
&:hover {
box-shadow: 0 6px 14px rgba(160, 110, 70, 0.08);
border-color: var(--app-acrylic-border-strong);
background: var(--app-acrylic-hover-bg);
box-shadow: none;
}
&:focus-visible {
@ -361,14 +412,14 @@ function go(path: string, query?: Record<string, string>) {
display: inline-flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
width: 28px;
height: 28px;
border-radius: 10px;
flex-shrink: 0;
background: #fff;
background: transparent;
color: var(--tone-strong);
font-size: 15px;
border: 1px solid var(--tone-edge);
font-size: 14px;
border: none;
}
.aw-link__body {
@ -390,7 +441,6 @@ function go(path: string, query?: Record<string, string>) {
color: var(--aw-soft);
}
/* Ops 嵌在 hero 内时去掉外层重复边框 */
.aw-panel--hero :deep(.aw-ops.aw-surface) {
border: none;
box-shadow: none;
@ -410,14 +460,34 @@ function go(path: string, query?: Record<string, string>) {
flex: none;
max-width: none;
width: 100%;
min-height: auto;
overflow: visible;
}
.admin-left__body {
flex: none;
overflow: visible;
}
.aw-panel--trend,
.aw-panel--announce {
margin-top: 0;
}
.admin-left__stats {
grid-template-columns: 1fr;
}
.aw-panel--pickup {
margin-top: 0;
}
.admin-right {
min-height: 280px;
min-height: 360px;
}
.admin-left__row {
grid-template-columns: 1fr;
.aw-panel--links {
max-height: none;
}
}

View File

@ -383,8 +383,8 @@ onMounted(() => {
<style scoped lang="less">
.care-today {
--care-ink: #5c3d2e;
--care-muted: rgba(92, 61, 46, 0.72);
--care-ink: #1a2e38;
--care-muted: rgba(26, 46, 56, 0.72);
position: relative;
overflow: hidden;
@ -392,18 +392,20 @@ onMounted(() => {
flex: 0 0 auto;
padding: 12px 14px 12px;
border-radius: 16px;
border: 1px solid rgba(220, 180, 140, 0.42);
background: #fff;
box-shadow: 0 8px 20px rgba(160, 110, 70, 0.06);
transition: border-radius 0.6s ease, box-shadow 0.2s ease;
border: 1px solid var(--app-acrylic-border);
background: var(--app-acrylic-bg);
backdrop-filter: var(--app-acrylic-blur);
-webkit-backdrop-filter: var(--app-acrylic-blur);
box-shadow: none;
transition: border-radius 0.6s ease, background 0.2s ease, border-color 0.2s ease;
&.is-pressure {
border-color: rgba(230, 170, 140, 0.55);
border-color: color-mix(in srgb, var(--app-primary-color) 18%, var(--app-acrylic-border));
}
&.is-clocked-out {
border-color: rgba(220, 180, 140, 0.5);
background: #fff;
border-color: var(--app-acrylic-border);
background: var(--app-acrylic-bg);
}
&.is-collapsed {
@ -431,16 +433,16 @@ onMounted(() => {
}
.care-action--primary {
border-color: rgba(193, 122, 82, 0.35);
background: #fff;
border-color: var(--app-acrylic-border);
background: transparent;
em {
color: #b86b45;
color: var(--app-primary-color);
}
}
.care-today__clocked {
color: #5c3d2e;
color: var(--care-ink);
}
}
}
@ -477,8 +479,8 @@ onMounted(() => {
font-size: 11px;
font-weight: 600;
color: var(--care-ink);
background: #fff;
border: 1px solid rgba(220, 180, 140, 0.4);
background: transparent;
border: 1px solid color-mix(in srgb, var(--app-acrylic-border) 80%, transparent);
}
.care-today__head {
@ -505,8 +507,8 @@ onMounted(() => {
.care-today__toggle {
flex-shrink: 0;
border: 1px solid rgba(220, 180, 140, 0.4);
background: #fff;
border: 1px solid var(--app-acrylic-border);
background: transparent;
color: var(--care-muted);
font-size: 11px;
line-height: 1;
@ -516,7 +518,8 @@ onMounted(() => {
&:hover {
color: var(--care-ink);
border-color: rgba(193, 122, 82, 0.45);
border-color: var(--app-acrylic-border-strong);
background: var(--app-acrylic-hover-bg);
}
}
@ -565,21 +568,22 @@ onMounted(() => {
width: 100%;
padding: 8px 10px;
border-radius: 12px;
border: 1px solid rgba(220, 180, 140, 0.4);
background: #fff;
border: 1px solid var(--app-acrylic-border);
background: transparent;
text-align: left;
cursor: pointer;
transition: transform 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
transition: border-color 0.2s ease, background 0.2s ease;
&:hover {
transform: translateY(-1px);
background: #fff;
box-shadow: 0 6px 14px rgba(180, 110, 70, 0.08);
transform: none;
border-color: var(--app-acrylic-border-strong);
background: var(--app-acrylic-hover-bg);
box-shadow: none;
}
&--primary {
border-color: rgba(193, 122, 82, 0.4);
background: #fff;
border-color: color-mix(in srgb, var(--app-primary-color) 18%, var(--app-acrylic-border));
background: transparent;
}
}
@ -592,7 +596,7 @@ onMounted(() => {
margin-left: 4px;
font-style: normal;
font-variant-numeric: tabular-nums;
color: var(--app-primary-color, #1d7281);
color: var(--app-primary-color, #054f74);
}
}

View File

@ -44,16 +44,9 @@ defineOptions({ name: 'CustomerWorkplace' })
.workplace-container {
--wp-card-radius: 16px;
--wp-inner-radius: 12px;
--wp-card-border: 1px solid rgba(255, 255, 255, 0.78);
--wp-card-bg: linear-gradient(
155deg,
rgba(255, 255, 255, 0.94) 0%,
rgba(255, 255, 255, 0.78) 100%
);
--wp-card-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.9),
0 6px 18px rgba(0, 121, 121, 0.05),
0 2px 12px rgba(0, 0, 0, 0.04);
--wp-card-border: 1px solid var(--app-acrylic-border);
--wp-card-bg: var(--app-acrylic-bg);
--wp-card-shadow: none;
width: 100%;
height: 100%;
min-height: 100%;
@ -195,6 +188,8 @@ defineOptions({ name: 'CustomerWorkplace' })
border-radius: var(--wp-card-radius);
background: var(--wp-card-bg);
box-shadow: var(--wp-card-shadow);
backdrop-filter: var(--app-acrylic-blur);
-webkit-backdrop-filter: var(--app-acrylic-blur);
flex-shrink: 0;
:deep(.arco-card) {
@ -278,6 +273,8 @@ defineOptions({ name: 'CustomerWorkplace' })
border-radius: var(--wp-card-radius);
background: var(--wp-card-bg);
box-shadow: var(--wp-card-shadow);
backdrop-filter: var(--app-acrylic-blur);
-webkit-backdrop-filter: var(--app-acrylic-blur);
}
:deep(.chart-controls) {

View File

@ -231,16 +231,18 @@
gap: 10px;
height: 44px;
padding: 0 12px 0 14px;
border: 1px solid var(--color-border-2);
border: 1px solid var(--app-acrylic-border);
border-radius: 12px;
background: var(--color-fill-1);
background: var(--app-acrylic-bg);
backdrop-filter: var(--app-acrylic-blur);
-webkit-backdrop-filter: var(--app-acrylic-blur);
cursor: pointer;
transition: border-color 0.2s, background 0.2s, box-shadow 0.2s;
transition: border-color 0.2s, background 0.2s;
&:hover {
border-color: var(--app-primary-color, rgb(var(--primary-6)));
background: var(--color-primary-light-1, #e8fffb);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
border-color: var(--app-acrylic-border-strong);
background: var(--app-acrylic-hover-bg);
box-shadow: none;
.balance-chip__cta {
opacity: 1;

View File

@ -322,31 +322,10 @@ watch(showWorldTime, (val) => {
margin-top: 8px;
}
/* 与营销入口、右侧内层卡片统一 */
/* 首页统计卡与营销入口统一磨砂质感(继承 stat-card.less) */
.stat-card {
border: 1px solid rgba(255, 255, 255, 0.78);
border-radius: var(--wp-inner-radius, 12px);
background:
linear-gradient(155deg, rgba(255, 255, 255, 0.9) 0%, rgba(255, 255, 255, 0.68) 100%);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.9),
0 2px 8px rgba(0, 121, 121, 0.05);
backdrop-filter: none;
-webkit-backdrop-filter: none;
min-height: 84px;
padding: 12px 14px 10px;
.stat-card__blob,
.stat-card__shine {
display: none;
}
&:hover {
border-color: color-mix(in srgb, var(--app-primary-color) 28%, var(--color-border-2));
background: rgba(255, 255, 255, 0.96);
box-shadow: 0 4px 14px rgba(0, 121, 121, 0.1);
transform: translateY(-1px);
}
}
.stat-card--compact {

View File

@ -150,24 +150,23 @@ onMounted(async () => {
min-height: 64px;
margin: 0;
padding: 10px 12px;
border: 1px solid rgba(255, 255, 255, 0.78);
border: 1px solid var(--app-acrylic-border);
border-radius: var(--wp-inner-radius, 12px);
background:
linear-gradient(155deg, rgba(255, 255, 255, 0.88) 0%, rgba(255, 255, 255, 0.62) 100%);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.9),
0 2px 8px rgba(0, 121, 121, 0.05);
background: var(--app-acrylic-bg);
backdrop-filter: var(--app-acrylic-blur);
-webkit-backdrop-filter: var(--app-acrylic-blur);
box-shadow: none;
text-align: left;
font: inherit;
color: inherit;
cursor: pointer;
transition: border-color 0.2s, background 0.2s, box-shadow 0.2s, transform 0.2s;
transition: border-color 0.2s, background 0.2s;
&:hover {
border-color: color-mix(in srgb, var(--app-primary-color) 28%, var(--color-border-2));
background: rgba(255, 255, 255, 0.95);
box-shadow: 0 4px 14px rgba(0, 121, 121, 0.1);
transform: translateY(-1px);
border-color: var(--app-acrylic-border-strong);
background: var(--app-acrylic-hover-bg);
box-shadow: none;
transform: none;
}
&__badge {
@ -198,7 +197,7 @@ onMounted(async () => {
border-radius: 10px;
font-size: 15px;
color: var(--app-primary-color, rgb(var(--primary-6)));
background: color-mix(in srgb, var(--app-primary-color) 10%, #fff);
background: transparent;
}
&__body {

View File

@ -278,24 +278,26 @@
gap: 6px;
min-width: 0;
padding: 8px 10px;
border: 1px solid rgba(255, 255, 255, 0.78);
border: 1px solid var(--app-acrylic-border);
border-radius: var(--wp-inner-radius, 12px);
background: rgba(255, 255, 255, 0.55);
background: var(--app-acrylic-bg);
backdrop-filter: var(--app-acrylic-blur);
-webkit-backdrop-filter: var(--app-acrylic-blur);
color: var(--color-text-2);
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: border-color 0.25s, background 0.25s, box-shadow 0.25s, color 0.2s;
transition: border-color 0.2s, background 0.2s, color 0.2s;
&:hover {
border-color: rgba(255, 255, 255, 0.9);
background: rgba(255, 255, 255, 0.62);
border-color: var(--app-acrylic-border-strong);
background: var(--app-acrylic-hover-bg);
}
&.is-active {
border-color: rgba(0, 121, 121, 0.35);
background: linear-gradient(155deg, rgba(0, 121, 121, 0.12), rgba(255, 255, 255, 0.7));
box-shadow: 0 6px 16px rgba(0, 121, 121, 0.1);
border-color: color-mix(in srgb, var(--app-primary-color) 22%, var(--app-acrylic-border));
background: var(--app-acrylic-hover-bg);
box-shadow: none;
color: var(--app-primary-color, rgb(var(--primary-6)));
.biz-switch__icon {
@ -313,7 +315,7 @@
width: 24px;
height: 24px;
border-radius: 8px;
background: rgba(255, 255, 255, 0.8);
background: transparent;
color: var(--color-text-2);
font-size: 13px;
}
@ -325,16 +327,18 @@
}
.flow-step {
border: 1px solid rgba(255, 255, 255, 0.78);
border: 1px solid var(--app-acrylic-border);
border-radius: var(--wp-inner-radius, 12px);
background: rgba(255, 255, 255, 0.55);
background: var(--app-acrylic-bg);
backdrop-filter: var(--app-acrylic-blur);
-webkit-backdrop-filter: var(--app-acrylic-blur);
overflow: hidden;
transition: border-color 0.2s, background 0.2s, box-shadow 0.2s;
transition: border-color 0.2s, background 0.2s;
&:hover {
border-color: rgba(0, 121, 121, 0.28);
background: rgba(255, 255, 255, 0.62);
box-shadow: 0 4px 14px rgba(0, 121, 121, 0.08);
border-color: var(--app-acrylic-border-strong);
background: var(--app-acrylic-hover-bg);
box-shadow: none;
}
&__main {
@ -408,18 +412,18 @@
align-items: center;
gap: 6px;
padding: 4px 10px;
border: 1px solid var(--color-border-2);
border: 1px solid var(--app-acrylic-border);
border-radius: 14px;
background: rgba(255, 255, 255, 0.85);
background: transparent;
color: var(--color-text-2);
font-size: 12px;
cursor: pointer;
transition: border-color 0.2s, color 0.2s, background 0.2s;
&:hover {
border-color: var(--app-primary-color, rgb(var(--primary-6)));
border-color: var(--app-acrylic-border-strong);
color: var(--app-primary-color, rgb(var(--primary-6)));
background: var(--app-card-bg);
background: var(--app-acrylic-hover-bg);
}
}
@ -440,18 +444,18 @@
align-items: center;
gap: 6px;
padding: 5px 10px;
border: 1px solid rgba(255, 255, 255, 0.7);
border: 1px solid var(--app-acrylic-border);
border-radius: 16px;
background: rgba(255, 255, 255, 0.7);
background: transparent;
color: var(--color-text-2);
font-size: 12px;
cursor: pointer;
transition: border-color 0.2s, color 0.2s, background 0.2s;
&:hover {
border-color: var(--app-primary-color, rgb(var(--primary-6)));
border-color: var(--app-acrylic-border-strong);
color: var(--app-primary-color, rgb(var(--primary-6)));
background: var(--app-card-bg);
background: var(--app-acrylic-hover-bg);
}
&__icon {

View File

@ -135,20 +135,22 @@
min-width: 0;
padding: 10px 12px;
border-radius: 12px;
border: 1px solid rgba(220, 180, 140, 0.38);
background: #fff;
box-shadow: 0 2px 8px rgba(160, 110, 70, 0.05);
border: 1px solid var(--app-acrylic-border);
background: var(--app-acrylic-bg);
backdrop-filter: var(--app-acrylic-blur);
-webkit-backdrop-filter: var(--app-acrylic-blur);
box-shadow: none;
&--local {
border-color: rgba(193, 122, 82, 0.42);
background: #fff;
border-color: var(--app-acrylic-border-strong);
background: var(--app-acrylic-hover-bg);
}
}
.wt-card__label {
font-size: 11px;
font-weight: 600;
color: rgba(92, 61, 46, 0.62);
color: var(--color-text-3);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@ -159,7 +161,7 @@
font-weight: 700;
font-variant-numeric: tabular-nums;
letter-spacing: 0.03em;
color: #5c3d2e;
color: var(--color-text-1);
line-height: 1.15;
}
@ -169,7 +171,7 @@
.wt-card__meta {
font-size: 11px;
color: rgba(92, 61, 46, 0.5);
color: var(--color-text-3);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;

View File

@ -0,0 +1,171 @@
import { computed, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import {
getOrderWorkplaceStats,
type FulfillWorkplaceStats,
type PickupWorkplaceStats,
} from '@/api/workplace'
import iconStorePending from '@/assets/icons/workplace/order-store-pending.svg?url'
import iconPendingShip from '@/assets/icons/workplace/order-pending-ship.svg?url'
import iconPendingOnline from '@/assets/icons/workplace/order-pending-online.svg?url'
import iconShipped from '@/assets/icons/workplace/order-shipped.svg?url'
import iconNew from '@/assets/icons/workplace/order-new.svg?url'
export type WorkplaceStatTone = 'neutral' | 'primary' | 'warning' | 'danger' | 'success'
export type WorkplaceStatItem = {
key: string
titleKey: string
value: number
icon: string
tone: WorkplaceStatTone
alert?: boolean
path: string
query: Record<string, string>
}
const FULFILL_PATH = '/order/fulfillOrders'
const PICKUP_PATH = '/order/pickupOrder'
const defaultFulfill = (): FulfillWorkplaceStats => ({
pendingBind: 0,
domesticInTransit: 0,
arrivedPendingPick: 0,
outboundDone: 0,
})
const defaultPickup = (): PickupWorkplaceStats => ({
pendingPayment: 0,
pendingSchedule: 0,
pendingPickup: 0,
pendingInbound: 0,
})
const fulfill = ref<FulfillWorkplaceStats>(defaultFulfill())
const pickup = ref<PickupWorkplaceStats>(defaultPickup())
const loading = ref(false)
let loadPromise: Promise<void> | null = null
async function loadStats() {
if (loadPromise) return loadPromise
loading.value = true
loadPromise = (async () => {
try {
const stats = await getOrderWorkplaceStats()
fulfill.value = stats.fulfill
pickup.value = stats.pickup
} catch {
fulfill.value = defaultFulfill()
pickup.value = defaultPickup()
} finally {
loading.value = false
}
})()
return loadPromise
}
export function useWorkplaceFulfillPickupStats() {
const router = useRouter()
const fulfillItems = computed<WorkplaceStatItem[]>(() => [
{
key: 'pendingBind',
titleKey: 'workplace.admin.fulfill.pendingBind',
value: fulfill.value.pendingBind,
icon: iconStorePending,
tone: 'warning',
alert: true,
path: FULFILL_PATH,
query: { progress: 'pendingBind' },
},
{
key: 'domesticInTransit',
titleKey: 'workplace.admin.fulfill.domesticInTransit',
value: fulfill.value.domesticInTransit,
icon: iconPendingOnline,
tone: 'neutral',
path: FULFILL_PATH,
query: { progress: 'domesticInTransit' },
},
{
key: 'arrivedPendingPick',
titleKey: 'workplace.admin.fulfill.arrivedPendingPick',
value: fulfill.value.arrivedPendingPick,
icon: iconPendingShip,
tone: 'warning',
alert: true,
path: FULFILL_PATH,
query: { progress: 'arrivedPendingPick' },
},
{
key: 'outboundDone',
titleKey: 'workplace.admin.fulfill.outboundDone',
value: fulfill.value.outboundDone,
icon: iconShipped,
tone: 'success',
path: FULFILL_PATH,
query: { progress: 'outboundDone' },
},
])
const pickupItems = computed<WorkplaceStatItem[]>(() => [
{
key: 'pendingPayment',
titleKey: 'workplace.admin.pickup.pendingPayment',
value: pickup.value.pendingPayment,
icon: iconPendingOnline,
tone: 'warning',
alert: true,
path: PICKUP_PATH,
query: { status: 'pendingPayment' },
},
{
key: 'pendingSchedule',
titleKey: 'workplace.admin.pickup.pendingSchedule',
value: pickup.value.pendingSchedule,
icon: iconNew,
tone: 'neutral',
path: PICKUP_PATH,
query: { status: 'draft' },
},
{
key: 'pendingPickup',
titleKey: 'workplace.admin.pickup.pendingPickup',
value: pickup.value.pendingPickup,
icon: iconPendingShip,
tone: 'warning',
alert: true,
path: PICKUP_PATH,
query: { status: 'scheduled' },
},
{
key: 'pendingInbound',
titleKey: 'workplace.admin.pickup.pendingInbound',
value: pickup.value.pendingInbound,
icon: iconShipped,
tone: 'primary',
path: PICKUP_PATH,
query: { status: 'picked' },
},
])
function formatValue(n: number) {
return Math.round(Number(n) || 0).toLocaleString('zh-CN')
}
function go(item: WorkplaceStatItem) {
router.push({ path: item.path, query: item.query }).catch(() => undefined)
}
onMounted(() => {
void loadStats()
})
return {
loading,
fulfillItems,
pickupItems,
formatValue,
go,
}
}

View File

@ -233,4 +233,20 @@ export default {
'workplace.admin.link.pickupHint': 'Pickup and handoff',
'workplace.admin.link.price': 'Price check',
'workplace.admin.link.priceHint': 'Quote checks',
'workplace.admin.fulfillTitle': 'Fulfillment orders',
'workplace.admin.pickupTitle': 'Pickup orders',
'workplace.admin.fulfill.pendingBind': 'Pending bind',
'workplace.admin.fulfill.domesticInTransit': 'Domestic in transit',
'workplace.admin.fulfill.arrivedPendingPick': 'Pending pick',
'workplace.admin.fulfill.outboundDone': 'Outbound done',
'workplace.admin.pickup.pendingPayment': 'Pending payment',
'workplace.admin.pickup.pendingSchedule': 'Pending schedule',
'workplace.admin.pickup.pendingPickup': 'Pending pickup',
'workplace.admin.pickup.pendingInbound': 'Pending inbound',
'workplace.admin.businessTrendTitle': 'Business trends',
'workplace.admin.businessTrend.waybill': 'Waybills',
'workplace.admin.businessTrend.fulfill': 'Fulfillment',
'workplace.admin.businessTrend.pickup': 'Pickup',
'workplace.admin.businessTrendEmptyTitle': 'No trend data yet',
'workplace.admin.businessTrendEmptyDesc': 'Trends appear once waybills, fulfillment, or pickup orders are created.',
};

View File

@ -230,4 +230,20 @@ export default {
'workplace.admin.link.pickupHint': '揽收与交接',
'workplace.admin.link.price': '价格查询',
'workplace.admin.link.priceHint': '报价核对',
'workplace.admin.fulfillTitle': '代发订单',
'workplace.admin.pickupTitle': '揽收订单',
'workplace.admin.fulfill.pendingBind': '待绑定',
'workplace.admin.fulfill.domesticInTransit': '国内在途',
'workplace.admin.fulfill.arrivedPendingPick': '待拣货',
'workplace.admin.fulfill.outboundDone': '已出库',
'workplace.admin.pickup.pendingPayment': '待支付',
'workplace.admin.pickup.pendingSchedule': '待下发',
'workplace.admin.pickup.pendingPickup': '待取件',
'workplace.admin.pickup.pendingInbound': '待到仓',
'workplace.admin.businessTrendTitle': '业务趋势',
'workplace.admin.businessTrend.waybill': '运单',
'workplace.admin.businessTrend.fulfill': '代发',
'workplace.admin.businessTrend.pickup': '揽收',
'workplace.admin.businessTrendEmptyTitle': '还没有业务趋势数据',
'workplace.admin.businessTrendEmptyDesc': '运单、代发或揽收产生新增后,趋势会显示在这里。',
};

View File

@ -121,15 +121,11 @@ import {
IconEyeInvisible,
} from '@arco-design/web-vue/es/icon';
import { OmsLoginType } from '@/api/rbac';
import { getEmployeeByUser } from '@/api/system/employee';
import { useRbacUserStore } from '@/store/modules/rbac-user';
import { setToken } from '@/utils/auth';
import { getDefaultHomePathByType, resolveCustomerEntryPath } from '@/utils/login-portal';
import { enterSystemAfterLogin, applyRbacUserToUserStore } from '@/utils/enter-system';
import { ensureHeavyPlugins } from '@/plugins/heavy-plugins';
import { buildAdminLoginCarePayload } from '@/utils/admin-login-care';
import { openAdminCareToast } from '@/utils/admin-care-toast';
import { getAdminCareSettings } from '@/utils/admin-care-settings';
import { LOGIN_CHARACTERS_KEY, type LoginCharactersApi } from '../../composables/useLoginCharacters';
import { LOGIN_EXIT_KEY, type LoginExitApi } from '../../composables/useLoginExit';
import LoginLockLogo from '../LoginLockLogo.vue';
@ -336,106 +332,6 @@ function validateForm(): boolean {
return valid;
}
async function buildAdminLoginCareToast(userInfoData: {
id?: string;
name?: string | null;
account?: string | null;
}) {
const settings = getAdminCareSettings();
if (!settings.careEnabled || !settings.loginToastEnabled) return null;
const fallbackName =
userInfoData.name || userInfoData.account || loginForm.account.trim() || '同事';
let birthday: string | undefined;
let hireDate: string | undefined;
let displayName = fallbackName;
try {
if (userInfoData.id) {
const emp = await Promise.race([
getEmployeeByUser(userInfoData.id),
new Promise<null>((resolve) => {
window.setTimeout(() => resolve(null), 1200);
}),
]);
if (emp) {
birthday = emp.birthday;
hireDate = emp.hireDate;
if (emp.realName) displayName = emp.realName;
}
}
} catch {
// 员工档案缺失时仍给出时段关怀
}
const care = buildAdminLoginCarePayload({
displayName,
birthday,
hireDate,
userId: userInfoData.id,
longAbsenceDays: settings.longAbsenceDays,
lateNightThreshold: settings.lateNightThreshold,
});
const name = care.displayName || fallbackName;
const lines: string[] = [];
if (care.birthdayToday) {
lines.push(t('login.care.birthday', { name }));
} else if (care.tenureAnniversaryYears != null) {
lines.push(
t('login.care.tenureAnniversary', { name, years: care.tenureAnniversaryYears }),
);
} else if (care.holidayId) {
lines.push(t(`login.care.holidayToast.${care.holidayId}`, { name }));
} else if (care.birthdaySoon) {
lines.push(t('login.care.birthdaySoon', { name }));
}
if (care.longAbsence && care.daysSinceLastLogin != null) {
lines.push(
t('login.care.longAbsence', { name, days: care.daysSinceLastLogin }),
);
}
// 入职周年已单独祝福时,不再叠「并肩 N 天」
if (
care.tenureAnniversaryYears == null &&
care.tenureDays != null &&
care.tenureDays > 0
) {
const milestone =
care.tenureDays === 1 ||
care.tenureDays % 100 === 0 ||
care.tenureDays % 365 === 0;
lines.push(
t(milestone ? 'login.care.tenureMilestone' : 'login.care.tenure', {
name,
days: care.tenureDays,
}),
);
}
if (care.lateNightOverwork) {
lines.push(
t('login.care.lateNightOverwork', { count: care.lateNightStreak }),
);
} else if (care.afterHours) {
lines.push(t('login.care.afterHoursThanks'));
}
if (!lines.length) {
lines.push(t('login.care.defaultWelcome', { name }));
}
let title = t('login.care.notifyTitle');
if (care.birthdayToday) title = t('login.care.birthdayTitle');
else if (care.tenureAnniversaryYears != null) title = t('login.care.anniversaryTitle');
else if (care.holidayId) title = t('login.care.holidayTitle');
else if (care.longAbsence) title = t('login.care.longAbsenceTitle');
return { title, lines };
}
async function handleSubmit() {
if (loading.value) return;
@ -487,25 +383,12 @@ async function handleSubmit() {
}
Message.success(t('login.form.login.success'));
const careToastPromise =
props.loginType === OmsLoginType.User
? buildAdminLoginCareToast(userInfoData)
: null;
await nextTick();
await enterSystemAfterLogin({
target: redirectParams,
fallbackPath: defaultHomePath,
playExit: () => loginExit?.playExitAnimation() ?? Promise.resolve(),
});
if (careToastPromise) {
const toast = await careToastPromise;
if (toast) {
// 等进系统后再弹出,避免登录页卸载冲掉
window.setTimeout(() => {
openAdminCareToast(toast);
}, 420);
}
}
} else {
errorBox.value = t('login.form.login.failed');
Message.error(t('login.form.login.failed'));
@ -568,20 +451,22 @@ async function handleSubmit() {
min-width: 0;
height: 50px;
overflow: hidden;
background: rgba(248, 250, 252, 0.95);
border: 1px solid #d8dee8;
background: var(--app-acrylic-bg);
border: 1px solid var(--app-acrylic-border);
border-radius: 14px;
backdrop-filter: var(--app-acrylic-blur);
-webkit-backdrop-filter: var(--app-acrylic-blur);
transition: border-color 0.2s, box-shadow 0.2s, background 0.2s;
&:hover {
border-color: #14b8a6;
background: #fff;
border-color: var(--app-acrylic-border-strong);
background: var(--app-acrylic-hover-bg);
}
&:focus-within {
border-color: #0f766e;
box-shadow: 0 0 0 4px rgba(20, 184, 166, 0.15);
background: #fff;
border-color: var(--app-primary-color);
box-shadow: 0 0 0 4px color-mix(in srgb, var(--app-primary-color) 15%, transparent);
background: var(--app-acrylic-hover-bg);
}
}
@ -603,7 +488,7 @@ async function handleSubmit() {
}
.nova-login__input-wrapper:focus-within .nova-login__prefix-icon {
color: #0f766e;
color: var(--app-primary-color);
}
.nova-login__input {
@ -629,16 +514,16 @@ async function handleSubmit() {
&:-webkit-autofill:active {
-webkit-text-fill-color: #111827;
caret-color: #111827;
box-shadow: 0 0 0 1000px #f8fafc inset;
-webkit-box-shadow: 0 0 0 1000px #f8fafc inset;
box-shadow: 0 0 0 1000px rgba(255, 255, 255, 0.42) inset;
-webkit-box-shadow: 0 0 0 1000px rgba(255, 255, 255, 0.42) inset;
transition: background-color 99999s ease-out 0s;
}
}
.nova-login__input-wrapper:focus-within .nova-login__input:-webkit-autofill,
.nova-login__input-wrapper:hover .nova-login__input:-webkit-autofill {
box-shadow: 0 0 0 1000px #fff inset;
-webkit-box-shadow: 0 0 0 1000px #fff inset;
box-shadow: 0 0 0 1000px rgba(255, 255, 255, 0.52) inset;
-webkit-box-shadow: 0 0 0 1000px rgba(255, 255, 255, 0.52) inset;
}
.nova-login__eye-toggle {
@ -652,7 +537,7 @@ async function handleSubmit() {
border: none;
&:hover {
color: #0f766e;
color: var(--app-primary-color);
}
}
@ -673,10 +558,12 @@ async function handleSubmit() {
padding: 11px 14px;
font-size: 13px;
color: #b91c1c;
background: #fff1f2;
border: 1px solid #fecdd3;
background: color-mix(in srgb, #fff1f2 72%, var(--app-acrylic-bg));
border: 1px solid color-mix(in srgb, #fecdd3 80%, var(--app-acrylic-border));
border-radius: 12px;
margin-bottom: 16px;
backdrop-filter: var(--app-acrylic-blur);
-webkit-backdrop-filter: var(--app-acrylic-blur);
}
.nova-login__submit {
@ -686,19 +573,19 @@ async function handleSubmit() {
font-size: 15px;
font-weight: 600;
border-radius: 14px;
background: #007979;
border: none;
background: var(--app-primary-color);
border: 1px solid color-mix(in srgb, var(--app-primary-color) 80%, transparent);
color: #fff;
letter-spacing: 0.5px;
cursor: pointer;
box-shadow: 0 14px 26px rgba(0, 121, 121, 0.24);
transition: transform 0.2s, box-shadow 0.2s, background 0.2s, opacity 0.2s;
box-shadow: none;
transition: transform 0.2s, background 0.2s, opacity 0.2s;
font-family: inherit;
&:hover:not(:disabled) {
transform: translateY(-1px);
background: #008f8f;
box-shadow: 0 16px 28px rgba(0, 121, 121, 0.32);
background: var(--app-primary-color-hover);
box-shadow: none;
}
&:active:not(:disabled) {
@ -757,8 +644,17 @@ async function handleSubmit() {
}
}
:deep(.arco-checkbox-checked .arco-checkbox-icon) {
background-color: var(--app-primary-color);
border-color: var(--app-primary-color);
}
:deep(.arco-checkbox-icon-hover::before) {
background-color: color-mix(in srgb, var(--app-primary-color) 12%, transparent);
}
:deep(.agreement-line .arco-link) {
color: #0f766e;
color: var(--app-primary-color);
font-size: inherit;
font-weight: 600;
text-decoration: none;
@ -766,8 +662,8 @@ async function handleSubmit() {
padding: 0 2px;
&:hover {
color: #14b8a6;
background-color: rgba(20, 184, 166, 0.1);
color: var(--app-primary-color-hover);
background-color: color-mix(in srgb, var(--app-primary-color) 10%, transparent);
text-decoration: underline;
text-underline-offset: 2px;
}

View File

@ -377,10 +377,12 @@ onBeforeUnmount(() => {
width: 210px;
height: 210px;
padding: 10px;
background: #fff;
border: 1px solid #f0f0f0;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
background: var(--app-acrylic-bg);
border: 1px solid var(--app-acrylic-border);
border-radius: 14px;
box-shadow: none;
backdrop-filter: var(--app-acrylic-blur);
-webkit-backdrop-filter: var(--app-acrylic-blur);
display: flex;
align-items: center;
justify-content: center;
@ -503,17 +505,19 @@ onBeforeUnmount(() => {
width: 44px;
height: 44px;
padding: 0;
border: 1px solid #d8dee8;
border: 1px solid var(--app-acrylic-border);
border-radius: 50%;
background: rgba(248, 250, 252, 0.95);
background: var(--app-acrylic-bg);
backdrop-filter: var(--app-acrylic-blur);
-webkit-backdrop-filter: var(--app-acrylic-blur);
color: rgba(26, 45, 74, 0.72);
cursor: pointer;
line-height: 1;
transition: border-color 0.2s, background 0.2s, color 0.2s;
&:hover {
border-color: var(--app-primary-color, #007979);
background: #fff;
border-color: var(--app-acrylic-border-strong);
background: var(--app-acrylic-hover-bg);
color: var(--app-primary-color, #007979);
}
}

View File

@ -208,10 +208,13 @@ provide(LOGIN_EXIT_KEY, {
);
if (containerRef.value) {
const pageBg =
getComputedStyle(document.documentElement).getPropertyValue('--app-page-bg').trim() ||
'#f0f3f4';
tl.to(
containerRef.value,
{
backgroundColor: '#f0f3f4',
backgroundColor: pageBg,
duration: 0.28,
ease: 'power1.inOut',
},
@ -292,7 +295,7 @@ onBeforeUnmount(() => {
--login-split-top: 62%;
--login-split-bottom: 38%;
--login-left-bg: #142637;
--login-right-bg: #fff;
--login-right-bg: var(--app-page-bg, #f0f3f4);
box-sizing: border-box;
height: 100dvh;
@ -308,7 +311,7 @@ onBeforeUnmount(() => {
--entry-btn-bg-hover: rgba(0, 0, 0, 0.09);
--entry-account-color: rgba(26, 45, 74, 0.85);
--login-left-bg: #e8eef5;
--login-right-bg: #fff;
--login-right-bg: var(--app-page-bg, #f0f3f4);
}
.login-container--exit {
@ -475,7 +478,7 @@ onBeforeUnmount(() => {
.login-form-toolbar :deep(.contact-service-trigger--login:hover) {
border: none !important;
box-shadow: none !important;
background: rgba(15, 23, 42, 0.06) !important;
background: var(--app-acrylic-hover-bg) !important;
color: var(--app-primary-color) !important;
}
@ -492,19 +495,23 @@ onBeforeUnmount(() => {
max-width: 430px;
min-height: auto;
padding: 51px 32px 45px;
background: #fff;
border: 1px solid rgba(148, 163, 184, 0.22);
background: var(--app-acrylic-bg);
border: 1px solid var(--app-acrylic-border);
border-radius: 24px;
box-shadow: 0 16px 40px rgba(30, 41, 59, 0.08);
box-shadow: none;
backdrop-filter: var(--app-acrylic-blur);
-webkit-backdrop-filter: var(--app-acrylic-blur);
display: flex;
flex-direction: column;
opacity: 1;
transform: none;
transition: border-color 0.2s ease, background 0.2s ease;
}
.login-form:hover {
border-color: rgba(148, 163, 184, 0.32);
box-shadow: 0 20px 44px rgba(30, 41, 59, 0.1);
border-color: var(--app-acrylic-border-strong);
background: var(--app-acrylic-hover-bg);
box-shadow: none;
}
.login-form--ready {
@ -524,11 +531,15 @@ onBeforeUnmount(() => {
}
.login-form :deep(.account-entry-btn) {
background: rgba(248, 250, 252, 0.95);
background: var(--app-acrylic-bg);
border-color: var(--app-acrylic-border);
backdrop-filter: var(--app-acrylic-blur);
-webkit-backdrop-filter: var(--app-acrylic-blur);
}
.login-form :deep(.account-entry-btn:hover) {
background: #fff;
background: var(--app-acrylic-hover-bg);
border-color: var(--app-acrylic-border-strong);
}
@media screen and (max-width: 1024px) {

View File

@ -1,6 +1,7 @@
<script setup lang="ts">
import { computed, onMounted, reactive, ref } from 'vue'
import { computed, onMounted, reactive, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute } from 'vue-router'
import dayjs from 'dayjs'
import { Modal } from '@arco-design/web-vue'
import { IconInfoCircle } from '@arco-design/web-vue/es/icon'
@ -37,6 +38,7 @@ import {
defineOptions({ name: 'OrderPickupOrder' })
const { t } = useI18n()
const route = useRoute()
const { tableContainerRef, tableHeight } = useTableContainerHeight()
const {
isAdmin,
@ -47,7 +49,6 @@ const {
const keyword = ref('')
const STATUS_TAB_ALL = 'all'
const statusFilter = ref(STATUS_TAB_ALL)
const listLoading = ref(false)
const tableRows = ref<any[]>([])
const currentPage = ref(1)
@ -102,6 +103,19 @@ const statusTabs = [
{ value: 'cancelled', labelKey: 'order.pickupOrder.status.cancelled' },
]
const validStatusSet = new Set(statusTabs.map((tab) => tab.value))
function resolveStatusFromQuery(): string {
const raw = route.query.status
const status = Array.isArray(raw) ? raw[0] : raw
if (typeof status === 'string' && validStatusSet.has(status)) {
return status
}
return STATUS_TAB_ALL
}
const statusFilter = ref(resolveStatusFromQuery())
function canShowPay(row: { status?: string; payStatus?: string; receivableAmount?: unknown; Status?: string; PayStatus?: string; ReceivableAmount?: unknown }) {
const status = String(row?.status ?? row?.Status ?? '')
@ -667,6 +681,15 @@ const detailOrderColumns = computed(() => [
const detailStatus = computed(() => String(detail.value?.status ?? detail.value?.Status ?? ''))
const detailPayStatus = computed(() => String(detail.value?.payStatus ?? detail.value?.PayStatus ?? ''))
watch(
() => route.query.status,
() => {
statusFilter.value = resolveStatusFromQuery()
currentPage.value = 1
void loadList()
},
)
onMounted(async () => {
await ensureCarrierServiceOptions()
void loadList()

View File

@ -225,7 +225,7 @@ const noticeItems = [
</script>
<style lang="less" scoped>
@guide-accent: var(--app-primary-color, #1d7281);
@guide-accent: var(--app-primary-color, #054f74);
@guide-accent-soft: color-mix(in srgb, @guide-accent 12%, white);
@guide-accent-border: color-mix(in srgb, @guide-accent 35%, white);