perf(admin): 优化管理端页面切换性能与包体积

- 阶段 A: 增加 KeepAlive 缓存高频列表页,改用 onActivated 进行后台静默刷新,优化 tab 切换与 HomeEntry 闪屏

- 阶段 B: beforeEach 守卫在 TTL 内走同步快速路径,api 请求拦截器缓存 token 避免重复解析 JWT

- 阶段 C: 引入 unplugin 插件启用 Element Plus 组件按需加载,清理 App.vue 中 960+ 行冗余暗色主题 CSS

- i18n 拆包: 将三语文案包提取为独立懒加载 chunks,主包体积从 209KB 减少至 99KB (降低 53%)
This commit is contained in:
2026-06-18 15:30:27 +08:00
parent 4244c9e10b
commit 5baac41ce8
26 changed files with 549 additions and 603 deletions

View File

@@ -43,96 +43,10 @@ a { color: inherit; text-decoration: none; }
button { cursor: pointer; font-family: inherit; }
:root {
/* Monochrome admin — 克制、无渐变、无品牌色泛滥 */
--accent: #f5f5f5;
--accent-muted: #a3a3a3;
--accent-dim: #737373;
--accent-subtle: rgba(255, 255, 255, 0.06);
--accent-border: rgba(255, 255, 255, 0.1);
--accent-hover: rgba(255, 255, 255, 0.04);
--accent-focus: rgba(255, 255, 255, 0.14);
/* 语义色:仅 success/warning/danger 场景使用 */
--success-text: #7d9b8a;
--success-bg: rgba(255, 255, 255, 0.04);
--success-border: rgba(255, 255, 255, 0.08);
/* 兼容旧变量名 → 中性灰白 */
--gold-deep: var(--accent-dim);
--gold-mid: var(--accent);
--gold-bright: var(--accent);
--gold-glow: var(--accent);
--gold-surface: var(--accent-subtle);
--gold-border: var(--accent-border);
--gold-text: var(--accent-muted);
--green-deep: var(--accent-dim);
--green-mid: var(--accent);
--green-bright: var(--accent);
--green-glow: var(--accent);
--green-surface: var(--accent-subtle);
--green-border: var(--accent-border);
--green-text: var(--accent-muted);
--primary: var(--accent);
--primary-dark: #e5e5e5;
--primary-light: #ffffff;
--primary-link: var(--accent-muted);
--primary-on: #0a0a0a;
--primary-grad: var(--accent);
--primary-grad-hover: #ffffff;
--primary-shadow: none;
--bg-body: #0a0a0a;
--bg-card: #111111;
--bg-elevated: #161616;
--bg-hover: var(--accent-hover);
--text: #f5f5f5;
--text-muted: #737373;
--border: rgba(255, 255, 255, 0.08);
--border-soft: var(--accent-border);
--radius: 8px;
--radius-sm: 6px;
--shadow: none;
/* Element Plus dark overrides */
--el-bg-color: #111111;
--el-bg-color-page: #0a0a0a;
--el-bg-color-overlay: #161616;
--el-text-color-primary: #f5f5f5;
--el-text-color-regular: #d4d4d4;
--el-text-color-secondary: #737373;
--el-text-color-placeholder:#525252;
--el-border-color: rgba(255, 255, 255, 0.08);
--el-border-color-light: rgba(255, 255, 255, 0.06);
--el-border-color-lighter: rgba(255, 255, 255, 0.04);
--el-fill-color: #141414;
--el-fill-color-blank: #0a0a0a;
--el-fill-color-light: #111111;
--el-color-primary: #e5e5e5;
--el-color-primary-light-3: rgba(255, 255, 255, 0.22);
--el-color-primary-light-5: rgba(255, 255, 255, 0.12);
--el-color-primary-light-7: rgba(255, 255, 255, 0.07);
--el-color-primary-light-9: rgba(255, 255, 255, 0.04);
--el-color-primary-dark-2: #a3a3a3;
--el-color-success: #7d9b8a;
--el-color-success-light-3: rgba(125, 155, 138, 0.22);
--el-color-success-light-5: rgba(125, 155, 138, 0.12);
--el-color-success-light-7: rgba(125, 155, 138, 0.07);
--el-color-success-light-9: rgba(125, 155, 138, 0.04);
--el-color-success-dark-2: #5c7568;
--el-table-bg-color: transparent;
--el-table-tr-bg-color: transparent;
--el-table-header-bg-color: transparent;
--el-table-row-hover-bg-color: rgba(255, 255, 255, 0.03);
--el-table-border-color: rgba(255, 255, 255, 0.06);
--el-table-text-color: #d4d4d4;
--el-table-header-text-color: #737373;
--el-card-bg-color: #111111;
--el-card-border-color: rgba(255, 255, 255, 0.08);
/* Common variables placeholder if needed, layout rules follow */
}
html, body, #app {
height: 100%;
overflow: hidden;
}
/* 页面级隐藏滚动条;可滚动区域保留细滚动条便于发现溢出内容 */
/* 页面级隐藏滚动条 */
html, body {
scrollbar-width: none;
-ms-overflow-style: none;
@@ -143,33 +57,8 @@ body::-webkit-scrollbar {
height: 0;
display: none;
}
.admin-list-page .table-wrap,
.dashboard-page,
.page-scroll,
.settlement-page,
.nav {
scrollbar-width: thin;
scrollbar-color: rgba(255, 255, 255, 0.18) transparent;
}
.admin-list-page .table-wrap::-webkit-scrollbar,
.dashboard-page::-webkit-scrollbar,
.page-scroll::-webkit-scrollbar,
.settlement-page::-webkit-scrollbar,
.nav::-webkit-scrollbar {
width: 6px;
height: 6px;
display: block;
}
.admin-list-page .table-wrap::-webkit-scrollbar-thumb,
.dashboard-page::-webkit-scrollbar-thumb,
.page-scroll::-webkit-scrollbar-thumb,
.settlement-page::-webkit-scrollbar-thumb,
.nav::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.16);
border-radius: 3px;
}
/* 管理端列表页:占满主区域,表头固定、表体滚动,底部分页 */
/* 管理端列表页布局 */
.admin-list-page {
display: flex;
flex-direction: column;
@@ -211,8 +100,8 @@ body::-webkit-scrollbar {
flex-wrap: nowrap;
padding: 8px 10px;
border-radius: 8px;
background: rgba(255, 255, 255, 0.02);
border: 1px solid rgba(255, 255, 255, 0.05);
background: var(--bg-hover);
border: 1px solid var(--border-soft);
--list-chrome-control-h: 32px;
--el-component-size: 32px;
}
@@ -323,11 +212,11 @@ body::-webkit-scrollbar {
padding: 0 10px;
font-size: 13px;
font-weight: 600;
background: rgba(255, 255, 255, 0.02);
border-color: rgba(255, 255, 255, 0.05);
background: var(--bg-hover);
border-color: var(--border-soft);
}
.list-settings :deep(.el-collapse-item__wrap) {
border-color: rgba(255, 255, 255, 0.05);
border-color: var(--border-soft);
}
.list-settings :deep(.el-collapse-item__content) {
padding: 8px 10px 10px;
@@ -335,12 +224,12 @@ body::-webkit-scrollbar {
.list-settings-block + .list-settings-block {
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid rgba(255, 255, 255, 0.05);
border-top: 1px solid var(--border-soft);
}
.list-settings-title {
font-size: 12px;
font-weight: 600;
color: #aaa;
color: var(--text-muted);
margin-bottom: 6px;
}
.admin-list-page > .data-card,
@@ -388,7 +277,7 @@ body::-webkit-scrollbar {
margin: 0;
padding: 6px 0 8px;
font-size: 11px;
color: #666;
color: var(--text-muted);
line-height: 1.4;
}
.admin-list-page .pager {
@@ -399,7 +288,7 @@ body::-webkit-scrollbar {
padding-top: 0;
}
/* 控制台等非列表页:允许内部滚动(滚动条已全局隐藏) */
/* 控制台等非列表页 */
.dashboard-page,
.page-scroll {
height: 100%;
@@ -416,350 +305,12 @@ body {
-moz-osx-font-smoothing: grayscale;
}
/* ── Element Plus 全局暗色覆盖 ── */
.el-card {
background: var(--bg-card) !important;
border-color: var(--border) !important;
border-radius: var(--radius) !important;
box-shadow: none !important;
}
.el-card__header {
border-bottom-color: var(--border) !important;
color: var(--text-muted);
font-size: 12px;
font-weight: 500;
letter-spacing: 0;
text-transform: none;
}
.el-table { background: transparent !important; color: #ccc !important; }
.el-table::before { background-color: #222 !important; }
.el-table th.el-table__cell {
background: transparent !important;
color: var(--text-muted) !important;
font-size: 12px; font-weight: 500;
letter-spacing: 0; text-transform: none;
border-bottom-color: var(--border) !important;
}
.el-table td.el-table__cell { border-bottom-color: rgba(255, 255, 255, 0.04) !important; color: #d4d4d4 !important; }
.el-table--striped .el-table__body tr.el-table__row--striped td { background: rgba(255,255,255,0.015) !important; }
.el-table__body tr:hover > td { background: rgba(255, 255, 255, 0.03) !important; }
.el-input__wrapper {
background: var(--bg-body) !important;
box-shadow: 0 0 0 1px var(--border) inset !important;
border-radius: var(--radius-sm) !important;
}
.el-input__wrapper:hover { box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.14) inset !important; }
.el-input__wrapper.is-focus {
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.28) inset !important;
}
.el-input__inner { color: #fff !important; background: transparent !important; }
.el-input__inner:-webkit-autofill,
.el-input__inner:-webkit-autofill:focus {
-webkit-box-shadow: 0 0 0 1000px #0d0d0d inset !important;
-webkit-text-fill-color: #fff !important;
}
.el-button { background: transparent !important; border-color: var(--border) !important; color: #a3a3a3 !important; font-weight: 500 !important; transition: background 0.15s, border-color 0.15s, color 0.15s !important; }
.el-button:hover { background: var(--accent-hover) !important; border-color: rgba(255, 255, 255, 0.14) !important; color: #f5f5f5 !important; }
.el-button--primary {
background: var(--accent) !important;
border: 1px solid var(--accent) !important;
color: var(--primary-on) !important;
font-weight: 500 !important;
box-shadow: none !important;
}
.el-button--primary:hover {
background: #ffffff !important;
border-color: #ffffff !important;
color: var(--primary-on) !important;
box-shadow: none !important;
}
.el-button--success {
background: transparent !important;
border: 1px solid var(--success-border) !important;
color: var(--success-text) !important;
font-weight: 500 !important;
box-shadow: none !important;
}
.el-button--success:hover {
background: var(--success-bg) !important;
border-color: rgba(255, 255, 255, 0.12) !important;
color: #a8c4b4 !important;
box-shadow: none !important;
}
.el-button--warning {
background: transparent !important;
border: 1px solid rgba(255, 255, 255, 0.1) !important;
color: #d4a574 !important;
font-weight: 500 !important;
box-shadow: none !important;
}
.el-button--warning:hover {
background: rgba(255, 255, 255, 0.04) !important;
border-color: rgba(255, 255, 255, 0.14) !important;
color: #e0b888 !important;
box-shadow: none !important;
}
.el-button--danger { background: transparent !important; border-color: rgba(255,69,58,0.25) !important; color: #ef4444 !important; }
/* ── Disabled: muted ghost, clearly non-interactive ── */
.el-button.is-disabled,
.el-button.is-disabled:hover,
.el-button.is-disabled:focus,
.el-button:disabled {
cursor: not-allowed !important;
pointer-events: none;
transform: none !important;
filter: none;
box-shadow: none !important;
text-shadow: none !important;
opacity: 1 !important;
}
.el-button.is-disabled,
.el-button.is-disabled:hover,
.el-button:disabled {
background: rgba(255, 255, 255, 0.04) !important;
border-color: rgba(255, 255, 255, 0.08) !important;
color: rgba(255, 255, 255, 0.28) !important;
}
.el-button--primary.is-disabled,
.el-button--primary.is-disabled:hover,
.el-button--primary:disabled {
background: rgba(255, 255, 255, 0.06) !important;
border-color: rgba(255, 255, 255, 0.06) !important;
color: rgba(255, 255, 255, 0.28) !important;
}
.el-button--primary.is-plain.is-disabled,
.el-button--primary.is-plain.is-disabled:hover,
.el-button--primary.is-plain:disabled {
background: transparent !important;
border-color: rgba(255, 255, 255, 0.06) !important;
color: rgba(255, 255, 255, 0.24) !important;
}
.el-button--success.is-disabled,
.el-button--success.is-disabled:hover,
.el-button--success:disabled {
background: transparent !important;
border-color: rgba(255, 255, 255, 0.06) !important;
color: rgba(255, 255, 255, 0.24) !important;
}
.el-button--warning.is-disabled,
.el-button--warning.is-disabled:hover,
.el-button--warning:disabled {
background: rgba(196, 132, 18, 0.1) !important;
border-color: rgba(196, 132, 18, 0.14) !important;
color: rgba(251, 191, 36, 0.28) !important;
}
.el-button--danger.is-plain.is-disabled,
.el-button--danger.is-plain.is-disabled:hover,
.el-button--danger.is-plain:disabled {
background: rgba(255, 69, 58, 0.06) !important;
border-color: rgba(255, 69, 58, 0.1) !important;
color: rgba(255, 107, 98, 0.28) !important;
}
.el-button--danger.is-disabled,
.el-button--danger.is-disabled:hover,
.el-button--danger:disabled {
background: rgba(255, 69, 58, 0.06) !important;
border-color: rgba(255, 69, 58, 0.1) !important;
color: rgba(255, 107, 98, 0.28) !important;
}
.el-button--primary.is-plain {
background: transparent !important;
border-color: var(--border) !important;
color: #d4d4d4 !important;
box-shadow: none !important;
}
.el-button--primary.is-plain:hover {
background: var(--accent-hover) !important;
border-color: rgba(255, 255, 255, 0.14) !important;
color: #f5f5f5 !important;
}
.el-button--danger.is-plain {
background: rgba(255, 69, 58, 0.14) !important;
border-color: rgba(255, 69, 58, 0.45) !important;
color: #ff6b62 !important;
font-weight: 600 !important;
box-shadow: none !important;
}
.el-button--danger.is-plain:hover {
background: rgba(255, 69, 58, 0.24) !important;
border-color: rgba(255, 120, 110, 0.55) !important;
color: #ff9a92 !important;
}
.el-button.is-text,
.el-button.is-link.el-button--default {
color: var(--accent-muted) !important;
background: transparent !important;
border-color: transparent !important;
box-shadow: none !important;
}
.el-button.is-text:hover,
.el-button.is-link.el-button--default:hover {
color: #f5f5f5 !important;
background: transparent !important;
}
.el-tag { border-radius: 4px !important; font-size: 11px !important; font-weight: 500 !important; }
.el-tag--success {
background: var(--success-bg) !important;
border: 1px solid var(--success-border) !important;
color: var(--success-text) !important;
}
.el-tag--warning { background: rgba(251,191,36,0.1) !important; border-color: rgba(251,191,36,0.3) !important; color: #fbbf24 !important; }
.el-tag--danger { background: rgba(255,69,58,0.1) !important; border-color: rgba(255,69,58,0.3) !important; color: #ff453a !important; }
.el-tag--info { background: rgba(255,255,255,0.06) !important; border-color: #3a3a3a !important; color: #aaa !important; }
/* 表格操作:纯文字链 */
.el-button.is-link.el-button--primary,
.el-button.is-link.el-button--success {
color: var(--accent-muted) !important;
background: transparent !important;
border: none !important;
border-radius: 0 !important;
padding: 0 4px !important;
box-shadow: none !important;
font-weight: 500 !important;
}
.el-button.is-link.el-button--primary:hover,
.el-button.is-link.el-button--primary:focus,
.el-button.is-link.el-button--success:hover,
.el-button.is-link.el-button--success:focus {
color: #f5f5f5 !important;
background: transparent !important;
border: none !important;
box-shadow: none !important;
text-decoration: underline;
text-underline-offset: 3px;
}
.el-button.is-link.el-button--warning {
color: #a3a3a3 !important;
background: transparent !important;
border: none !important;
border-radius: 0 !important;
padding: 0 4px !important;
box-shadow: none !important;
font-weight: 500 !important;
}
.el-button.is-link.el-button--warning:hover,
.el-button.is-link.el-button--warning:focus {
color: #d4a574 !important;
background: transparent !important;
border: none !important;
box-shadow: none !important;
text-decoration: underline;
text-underline-offset: 3px;
}
/* 表格 link 操作按钮禁用:保留按钮形态,灰显不可点 */
.el-button.is-link.is-disabled,
.el-button.is-link.is-disabled:hover,
.el-button.is-link.is-disabled:focus,
.el-button.is-link:disabled {
cursor: not-allowed !important;
pointer-events: none;
background: rgba(255, 255, 255, 0.04) !important;
border: 1px solid rgba(255, 255, 255, 0.1) !important;
color: rgba(255, 255, 255, 0.28) !important;
box-shadow: none !important;
text-shadow: none !important;
opacity: 1 !important;
transform: none !important;
filter: none;
}
.el-button.is-link.el-button--primary.is-disabled,
.el-button.is-link.el-button--success.is-disabled {
background: transparent !important;
border: none !important;
color: rgba(255, 255, 255, 0.24) !important;
text-decoration: none !important;
}
.el-button.is-link.el-button--danger.is-disabled {
background: rgba(255, 69, 58, 0.06) !important;
border-color: rgba(255, 69, 58, 0.1) !important;
color: rgba(255, 107, 98, 0.26) !important;
}
.el-button.is-link.el-button--warning.is-disabled {
background: rgba(196, 132, 18, 0.08) !important;
border-color: rgba(196, 132, 18, 0.12) !important;
color: rgba(251, 191, 36, 0.26) !important;
}
.el-form-item__label { color: var(--text-muted) !important; font-size: 13px !important; font-weight: 500 !important; letter-spacing: 0 !important; }
/* ── Dialog / overlay实心背景避免噪点透底发糊 ── */
.el-overlay {
background-color: rgba(0, 0, 0, 0.72) !important;
backdrop-filter: none !important;
}
.el-dialog {
background: #141414 !important;
border: 1px solid var(--border) !important;
border-radius: var(--radius) !important;
box-shadow: 0 24px 48px rgba(0, 0, 0, 0.5) !important;
}
.el-dialog__header {
border-bottom: 1px solid #2a2a2a !important;
padding: 16px 20px 14px !important;
margin-right: 0 !important;
}
.el-dialog__title {
font-size: 16px !important;
font-weight: 700 !important;
color: #f0f0f0 !important;
letter-spacing: 0.02em !important;
}
.el-dialog__headerbtn .el-dialog__close {
color: #888 !important;
}
.el-dialog__headerbtn:hover .el-dialog__close {
color: #fff !important;
}
.el-dialog__body {
padding: 18px 20px !important;
font-size: 14px !important;
color: #ddd !important;
}
.el-dialog__footer {
border-top: 1px solid #2a2a2a !important;
padding: 12px 20px 16px !important;
}
.el-dialog .el-form-item__label {
font-size: 13px !important;
color: #aaa !important;
}
.el-dialog .el-descriptions__label {
color: #888 !important;
font-size: 12px !important;
font-weight: 600 !important;
background: #141414 !important;
}
.el-dialog .el-descriptions__content {
color: #e0e0e0 !important;
font-size: 13px !important;
background: #1a1a1a !important;
}
.el-dialog .el-descriptions__cell {
border-color: #2a2a2a !important;
}
/* 详情/编辑弹窗自定义布局 */
.user-edit-dialog .el-dialog__body,
.agent-edit-dialog .el-dialog__body {
max-height: min(70vh, 640px);
overflow-y: auto;
}
.user-edit-dialog .edit-meta,
.agent-edit-dialog .edit-meta {
display: flex;
@@ -767,7 +318,7 @@ body {
justify-content: space-between;
margin-bottom: 12px;
font-size: 12px;
color: #888;
color: var(--text-muted);
}
.user-edit-dialog .edit-form-section,
.agent-edit-dialog .edit-form-section {
@@ -781,7 +332,7 @@ body {
.agent-edit-dialog .section-title {
font-size: 11px;
font-weight: 700;
color: #888;
color: var(--text-muted);
letter-spacing: 0.04em;
margin-bottom: 8px;
}
@@ -792,7 +343,7 @@ body {
.user-edit-dialog .field-hint,
.agent-edit-dialog .field-hint {
font-size: 12px;
color: #888;
color: var(--text-muted);
margin-top: 4px;
line-height: 1.4;
}
@@ -804,15 +355,15 @@ body {
.agent-edit-dialog .password-mgmt-block {
margin: 0;
padding: 10px 12px;
border: 1px solid rgba(255, 255, 255, 0.08);
border: 1px solid var(--border-soft);
border-radius: 8px;
background: rgba(0, 0, 0, 0.15);
background: var(--bg-hover);
}
.user-edit-dialog .block-title,
.agent-edit-dialog .block-title {
font-size: 12px;
font-weight: 700;
color: #e8a84a;
color: var(--warning-text);
margin-bottom: 8px;
letter-spacing: 0.04em;
}
@@ -831,7 +382,7 @@ body {
.user-edit-dialog .password-field-label,
.agent-edit-dialog .password-field-label {
font-size: 12px;
color: #888;
color: var(--text-muted);
line-height: 1.35;
}
.user-edit-dialog .password-plain,
@@ -839,12 +390,12 @@ body {
font-family: ui-monospace, monospace;
font-size: 14px;
font-weight: 600;
color: #f0d090;
color: var(--warning-text);
letter-spacing: 0.06em;
}
.user-edit-dialog .password-empty,
.agent-edit-dialog .password-empty {
color: #666;
color: var(--text-muted);
}
.user-edit-dialog .block-hint,
.agent-edit-dialog .block-hint {
@@ -870,43 +421,22 @@ body {
.agent-edit-dialog .edit-stats-panel {
margin-top: 14px;
padding-top: 12px;
border-top: 1px solid rgba(255, 255, 255, 0.06);
border-top: 1px solid var(--border-soft);
}
.agent-edit-dialog .edit-stats {
margin-top: 4px;
}
.entity-detail-dialog .el-dialog__body {
padding: 12px 20px 16px !important;
max-height: none !important;
overflow: visible !important;
}
.detail-actions {
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid rgba(255, 255, 255, 0.06);
border-top: 1px solid var(--border-soft);
}
.el-statistic__head { color: #737373 !important; font-size: 12px !important; font-weight: 500 !important; letter-spacing: 0 !important; text-transform: none !important; }
.el-statistic__content .el-statistic__number { font-size: 24px !important; font-weight: 500 !important; color: #f5f5f5 !important; }
.el-input-number .el-input__wrapper { background: #0d0d0d !important; }
.el-date-editor .el-input__wrapper { background: #0d0d0d !important; }
.el-date-editor .el-input__inner { color: #fff !important; }
.el-picker-panel {
background: #1c1c1c !important;
border-color: #333 !important;
color: #ddd !important;
}
.el-picker-panel__footer { background: #1c1c1c !important; border-top-color: #333 !important; }
.el-date-picker__header-label,
.el-date-table th,
.el-date-table td .el-date-table-cell__text { color: #ccc !important; }
.el-time-panel { background: #1c1c1c !important; border-color: #333 !important; }
.el-time-spinner__item { color: #aaa !important; }
.el-time-spinner__item.is-active:not(.is-disabled) { color: #fff !important; }
/* ──────────────────────────────────────────────────────────────
Admin light refresh
The legacy admin theme was built as one dark surface. These