From 9fc5e3e607f7083a3fdede858a401eb4453397c1 Mon Sep 17 00:00:00 2001
From: HenryDu8133 <813367384@qq.com>
Date: Sat, 12 Sep 2026 15:56:10 +0800
Subject: [PATCH] =?UTF-8?q?fix(ui):=20=E6=89=B9=E9=87=8F=E6=9B=BF=E6=8D=A2?=
=?UTF-8?q?=E6=89=80=E6=9C=89=E7=BB=84=E4=BB=B6=E4=B8=AD=E6=AE=8B=E7=95=99?=
=?UTF-8?q?=E7=9A=84=E7=A1=AC=E7=BC=96=E7=A0=81=E6=B7=B1=E8=89=B2=E5=80=BC?=
=?UTF-8?q?=E4=BB=A5=E5=BD=BB=E5=BA=95=E6=94=AF=E6=8C=81=E6=B5=85=E8=89=B2?=
=?UTF-8?q?=E4=B8=BB=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/components/Admin/ApiKeyManager.vue | 4 +-
app/components/Admin/BackupManager.vue | 54 +-
app/components/Admin/BlacklistManager.vue | 2 +-
app/components/Admin/EmailTemplateManager.vue | 2 +-
app/components/Admin/NotificationSender.vue | 2 +-
app/components/Admin/OverviewDashboard.vue | 2 +-
app/components/Admin/RequestTimeManager.vue | 2 +-
app/components/Admin/ScheduleItemPrint.vue | 26 +-
app/components/Admin/SchedulePrinter.vue | 34 +-
app/components/Admin/Sidebar.vue | 2 +-
app/components/Admin/SongDownloadDialog.vue | 2 +-
app/components/Admin/SongManagement.vue | 2 +-
app/components/Admin/VotersModal.vue | 46 +-
app/components/Auth/OAuthButtons.vue | 16 +-
app/components/Auth/RegisterForm.vue | 2 +-
.../Notifications/NotificationSettings.vue | 4 +-
app/components/Player/PlayerLyric/AMLyric.vue | 2 +-
.../Player/PlayerLyric/DefaultLyric.vue | 2 +-
app/components/Songs/RequestForm.vue | 100 +-
app/components/Songs/ScheduleList.vue | 76 +-
app/components/Songs/SongList.vue | 58 +-
app/components/UI/AudioPlayer.vue | 50 +-
app/components/UI/Common/CustomSelect.vue | 2 +-
app/components/UI/LyricsModal.vue | 18 +-
app/components/UI/Notification.vue | 12 +-
app/components/UI/NotificationContainer.vue | 12 +-
app/components/UserTutorial.vue | 12 +-
app/components/year-review/ArtistSlide.vue | 2 +-
app/components/year-review/FirstSongSlide.vue | 8 +-
app/components/year-review/IntroSlide.vue | 2 +-
app/components/year-review/MiscSlide.vue | 4 +-
app/components/year-review/OutroSlide.vue | 10 +-
app/components/year-review/ShareCard.vue | 4 +-
app/pages/change-password.vue | 2 +-
app/pages/index.vue | 12 +-
app/pages/year-review.vue | 10 +-
fix-colors.mjs | 128 +
old-index.vue | 3661 +++++++++++++++++
old-nuxt.config.ts | 447 ++
old-nuxt2.config.ts | 433 ++
old-variables.css | 556 +++
old-variables2.css | 556 +++
42 files changed, 6081 insertions(+), 300 deletions(-)
create mode 100644 fix-colors.mjs
create mode 100644 old-index.vue
create mode 100644 old-nuxt.config.ts
create mode 100644 old-nuxt2.config.ts
create mode 100644 old-variables.css
create mode 100644 old-variables2.css
diff --git a/app/components/Admin/ApiKeyManager.vue b/app/components/Admin/ApiKeyManager.vue
index 8a21229..b20a430 100644
--- a/app/components/Admin/ApiKeyManager.vue
+++ b/app/components/Admin/ApiKeyManager.vue
@@ -1026,7 +1026,7 @@ onMounted(() => {
width: 24px;
height: 24px;
border: 2px solid rgba(255, 255, 255, 0.1);
- border-top-color: #3b82f6;
+ border-top-color: var(--border-secondary, #3b82f6);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@@ -1044,7 +1044,7 @@ onMounted(() => {
background: transparent;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
- background: #27272a;
+ background: var(--bg-tertiary, #27272a);
border-radius: 10px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
diff --git a/app/components/Admin/BackupManager.vue b/app/components/Admin/BackupManager.vue
index efe60d0..3d78253 100644
--- a/app/components/Admin/BackupManager.vue
+++ b/app/components/Admin/BackupManager.vue
@@ -556,8 +556,8 @@ const formatFileSize = (bytes) => {
/* 操作卡片 */
.action-card {
- background: #2a2a2a;
- border: 1px solid #404040;
+ background: var(--bg-tertiary, #2a2a2a);
+ border: 1px solid var(--border-secondary, #404040);
border-radius: 12px;
padding: 2rem;
text-align: center;
@@ -566,7 +566,7 @@ const formatFileSize = (bytes) => {
.action-card:hover {
border-color: #606060;
- background: #2f2f2f;
+ background: var(--bg-tertiary, #2f2f2f);
}
.action-card .card-content {
@@ -587,7 +587,7 @@ const formatFileSize = (bytes) => {
display: flex;
align-items: center;
justify-content: center;
- color: #ffffff;
+ color: var(--text-primary, #ffffff);
}
.action-icon svg {
@@ -599,7 +599,7 @@ const formatFileSize = (bytes) => {
.action-card h4 {
font-size: 1.5rem;
font-weight: 600;
- color: #ffffff;
+ color: var(--text-primary, #ffffff);
margin: 0 0 0.75rem 0;
}
@@ -672,8 +672,8 @@ const formatFileSize = (bytes) => {
}
.modal {
- background: #2a2a2a;
- border: 1px solid #404040;
+ background: var(--bg-tertiary, #2a2a2a);
+ border: 1px solid var(--border-secondary, #404040);
border-radius: 12px;
max-width: 600px;
width: 100%;
@@ -724,7 +724,7 @@ const formatFileSize = (bytes) => {
.progress-details h5,
.progress-errors h5 {
margin: 0 0 10px 0;
- color: #ffffff;
+ color: var(--text-primary, #ffffff);
font-size: 14px;
font-weight: 600;
}
@@ -736,14 +736,14 @@ const formatFileSize = (bytes) => {
border: 1px solid #505050;
border-radius: 6px;
padding: 10px;
- background: #333333;
+ background: var(--bg-quaternary, #333333);
}
.detail-item {
padding: 4px 0;
color: #a0a0a0;
font-size: 13px;
- border-bottom: 1px solid #404040;
+ border-bottom: 1px solid var(--border-secondary, #404040);
}
.detail-item:last-child {
@@ -754,7 +754,7 @@ const formatFileSize = (bytes) => {
padding: 4px 0;
color: #ff6b6b;
font-size: 13px;
- border-bottom: 1px solid #4a2a2a;
+ border-bottom: 1px solid var(--border-secondary, #4a2a2a);
}
.error-item:last-child {
@@ -772,7 +772,7 @@ const formatFileSize = (bytes) => {
.modal-title {
font-size: 1.75rem;
font-weight: 600;
- color: #ffffff;
+ color: var(--text-primary, #ffffff);
margin: 0;
}
@@ -788,7 +788,7 @@ const formatFileSize = (bytes) => {
.close-btn:hover {
background: #505050;
- color: #ffffff;
+ color: var(--text-primary, #ffffff);
}
.close-btn svg {
@@ -802,11 +802,11 @@ const formatFileSize = (bytes) => {
.modal-footer {
padding: 1.5rem 2rem;
- border-top: 1px solid #404040;
+ border-top: 1px solid var(--border-secondary, #404040);
display: flex;
gap: 1rem;
justify-content: flex-end;
- background: #252525;
+ background: var(--bg-tertiary, #252525);
}
.modal-footer button {
@@ -822,7 +822,7 @@ const formatFileSize = (bytes) => {
display: block;
font-size: 1.125rem;
font-weight: 600;
- color: #ffffff;
+ color: var(--text-primary, #ffffff);
margin-bottom: 1rem;
}
@@ -838,7 +838,7 @@ const formatFileSize = (bytes) => {
align-items: flex-start;
gap: 1rem;
padding: 1.25rem;
- background: #333333;
+ background: var(--bg-quaternary, #333333);
border: 1px solid #505050;
border-radius: 8px;
cursor: pointer;
@@ -870,7 +870,7 @@ const formatFileSize = (bytes) => {
display: block;
font-size: 1rem;
font-weight: 500;
- color: #ffffff;
+ color: var(--text-primary, #ffffff);
margin-bottom: 0.25rem;
}
@@ -907,12 +907,12 @@ const formatFileSize = (bytes) => {
text-align: center;
cursor: pointer;
transition: all 0.2s ease;
- background: #333333;
+ background: var(--bg-quaternary, #333333);
}
.upload-area:hover,
.upload-area.drag-over {
- border-color: #4a90e2;
+ border-color: var(--border-secondary, #4a90e2);
background: #383838;
}
@@ -926,7 +926,7 @@ const formatFileSize = (bytes) => {
.upload-area h4 {
font-size: 1.25rem;
font-weight: 600;
- color: #ffffff;
+ color: var(--text-primary, #ffffff);
margin: 0 0 0.5rem 0;
}
@@ -941,7 +941,7 @@ const formatFileSize = (bytes) => {
align-items: center;
justify-content: space-between;
padding: 1.25rem;
- background: #333333;
+ background: var(--bg-quaternary, #333333);
border: 1px solid #505050;
border-radius: 8px;
margin-top: 1rem;
@@ -969,7 +969,7 @@ const formatFileSize = (bytes) => {
display: block;
font-size: 1rem;
font-weight: 500;
- color: #ffffff;
+ color: var(--text-primary, #ffffff);
margin-bottom: 0.25rem;
}
@@ -1056,8 +1056,8 @@ const formatFileSize = (bytes) => {
}
.notification.success {
- background: #2e7d32;
- border: 1px solid #4caf50;
+ background: var(--bg-tertiary, #2e7d32);
+ border: 1px solid var(--border-secondary, #4caf50);
}
.notification.error {
@@ -1071,8 +1071,8 @@ const formatFileSize = (bytes) => {
}
.notification.info {
- background: #1976d2;
- border: 1px solid #2196f3;
+ background: var(--bg-secondary, #1976d2);
+ border: 1px solid var(--border-secondary, #2196f3);
}
@keyframes slideInRight {
diff --git a/app/components/Admin/BlacklistManager.vue b/app/components/Admin/BlacklistManager.vue
index a31371f..b5ec8a4 100644
--- a/app/components/Admin/BlacklistManager.vue
+++ b/app/components/Admin/BlacklistManager.vue
@@ -645,7 +645,7 @@ onMounted(() => {
background: transparent;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
- background: #27272a;
+ background: var(--bg-tertiary, #27272a);
border-radius: 10px;
}
diff --git a/app/components/Admin/EmailTemplateManager.vue b/app/components/Admin/EmailTemplateManager.vue
index 89fda18..a995f31 100644
--- a/app/components/Admin/EmailTemplateManager.vue
+++ b/app/components/Admin/EmailTemplateManager.vue
@@ -336,7 +336,7 @@ onMounted(() => {
background: transparent;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
- background: #27272a;
+ background: var(--bg-tertiary, #27272a);
border-radius: 10px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
diff --git a/app/components/Admin/NotificationSender.vue b/app/components/Admin/NotificationSender.vue
index de4cd46..23bd005 100644
--- a/app/components/Admin/NotificationSender.vue
+++ b/app/components/Admin/NotificationSender.vue
@@ -742,7 +742,7 @@ onUnmounted(() => {
background: transparent;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
- background: #27272a;
+ background: var(--bg-tertiary, #27272a);
border-radius: 10px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
diff --git a/app/components/Admin/OverviewDashboard.vue b/app/components/Admin/OverviewDashboard.vue
index 7ac7086..148318c 100644
--- a/app/components/Admin/OverviewDashboard.vue
+++ b/app/components/Admin/OverviewDashboard.vue
@@ -366,7 +366,7 @@ onMounted(() => {
background: transparent;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
- background: #27272a;
+ background: var(--bg-tertiary, #27272a);
border-radius: 10px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
diff --git a/app/components/Admin/RequestTimeManager.vue b/app/components/Admin/RequestTimeManager.vue
index bf35bb6..762281f 100644
--- a/app/components/Admin/RequestTimeManager.vue
+++ b/app/components/Admin/RequestTimeManager.vue
@@ -842,7 +842,7 @@ const cancelForm = () => {
background: transparent;
}
::-webkit-scrollbar-thumb {
- background: #27272a;
+ background: var(--bg-tertiary, #27272a);
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
diff --git a/app/components/Admin/ScheduleItemPrint.vue b/app/components/Admin/ScheduleItemPrint.vue
index 51b2c1b..703e42a 100644
--- a/app/components/Admin/ScheduleItemPrint.vue
+++ b/app/components/Admin/ScheduleItemPrint.vue
@@ -112,7 +112,7 @@ const handleImageError = (event) => {
justify-content: center;
font-weight: bold;
font-size: 14px;
- color: #333;
+ color: var(--text-quaternary, #333333);
flex-shrink: 0;
}
@@ -141,7 +141,7 @@ const handleImageError = (event) => {
display: none;
align-items: center;
justify-content: center;
- color: #999;
+ color: var(--text-tertiary, #999999);
}
.cover-placeholder.show {
@@ -161,7 +161,7 @@ const handleImageError = (event) => {
.song-title {
font-weight: bold;
font-size: 16px;
- color: #333;
+ color: var(--text-quaternary, #333333);
margin-bottom: 2px;
white-space: nowrap;
overflow: hidden;
@@ -176,7 +176,7 @@ const handleImageError = (event) => {
display: inline-block;
padding: 1px 4px;
background: #e3f2fd;
- border: 1px solid #2196f3;
+ border: 1px solid var(--border-secondary, #2196f3);
border-radius: 3px;
color: #1976d2;
font-size: 10px;
@@ -186,7 +186,7 @@ const handleImageError = (event) => {
.song-artist {
font-size: 14px;
- color: #666;
+ color: var(--text-quaternary, #666666);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@@ -197,7 +197,7 @@ const handleImageError = (event) => {
display: flex;
align-items: center;
font-size: 12px;
- color: #666;
+ color: var(--text-quaternary, #666666);
white-space: nowrap;
}
@@ -207,14 +207,14 @@ const handleImageError = (event) => {
}
.value {
- color: #333;
+ color: var(--text-quaternary, #333333);
}
/* 打印样式 */
@media print {
.schedule-item-print {
- color: #000 !important;
- background: #fff !important;
+ color: var(--text-primary, #000) !important;
+ background: var(--card-bg, #fff) !important;
width: 100% !important;
max-width: none !important;
box-sizing: border-box !important;
@@ -230,7 +230,7 @@ const handleImageError = (event) => {
.sequence-number {
background: #f0f0f0 !important;
- color: #000 !important;
+ color: var(--text-primary, #000) !important;
width: 30px !important;
height: 30px !important;
flex-shrink: 0 !important;
@@ -264,7 +264,7 @@ const handleImageError = (event) => {
}
.song-title {
- color: #000 !important;
+ color: var(--text-primary, #000) !important;
font-size: 16px !important;
white-space: nowrap !important;
overflow: hidden !important;
@@ -274,11 +274,11 @@ const handleImageError = (event) => {
.song-artist,
.label,
.value {
- color: #333 !important;
+ color: var(--text-quaternary, #333333) !important;
}
.time-range {
- color: #666 !important;
+ color: var(--text-quaternary, #666666) !important;
}
.requester-info,
diff --git a/app/components/Admin/SchedulePrinter.vue b/app/components/Admin/SchedulePrinter.vue
index 71bf445..9b26695 100644
--- a/app/components/Admin/SchedulePrinter.vue
+++ b/app/components/Admin/SchedulePrinter.vue
@@ -1383,15 +1383,15 @@ watch(
/* 预览区域和打印样式的核心CSS - 保持原生CSS以确保精确控制 */
.preview-content {
- background: #ffffff;
+ background: var(--card-bg, #ffffff);
padding: 0;
position: relative;
}
/* 打印页面样式 */
.print-page {
- background: #ffffff;
- color: #000000;
+ background: var(--card-bg, #ffffff);
+ color: var(--text-primary, #000000);
width: 100%;
margin: 0 auto;
padding: 30px;
@@ -1426,7 +1426,7 @@ watch(
.logo-divider {
width: 2px;
height: 60px;
- background: linear-gradient(to bottom, #ddd, #999, #ddd);
+ background: linear-gradient(to bottom, var(--border-secondary, #dddddd), #999, #ddd);
border-radius: 1px;
margin: 0 4px;
}
@@ -1445,19 +1445,19 @@ watch(
font-size: 24px;
font-weight: normal;
margin: 0 0 4px 0;
- color: #000;
+ color: var(--text-primary, #000);
}
.title-section h2 {
font-size: 18px;
font-weight: 500;
margin: 0;
- color: #666;
+ color: var(--text-quaternary, #666666);
}
.date-info {
font-size: 14px;
- color: #666;
+ color: var(--text-quaternary, #666666);
text-align: right;
display: flex;
align-items: flex-start;
@@ -1477,14 +1477,14 @@ watch(
.no-data-message {
text-align: center;
padding: 60px 20px;
- color: #666;
+ color: var(--text-quaternary, #666666);
}
.no-data-icon {
width: 64px;
height: 64px;
margin: 0 auto 20px;
- color: #ccc;
+ color: var(--text-secondary, #cccccc);
}
.no-data-icon svg {
@@ -1496,7 +1496,7 @@ watch(
font-size: 18px;
font-weight: 500;
margin: 0 0 12px 0;
- color: #333;
+ color: var(--text-quaternary, #333333);
}
.no-data-message p {
@@ -1512,15 +1512,15 @@ watch(
font-weight: bold;
margin: 24px 0 12px 0;
padding: 8px 12px;
- border-bottom: 2px solid #ddd;
+ border-bottom: 2px solid var(--border-secondary, #ddd);
background: #f8f9fa;
- color: #333;
+ color: var(--text-quaternary, #333333);
border-radius: 4px 4px 0 0;
}
.playtime-group .group-title {
background: #e3f2fd;
- border-bottom-color: #2196f3;
+ border-bottom-color: var(--border-secondary, #2196f3);
color: #1565c0;
}
@@ -1533,7 +1533,7 @@ watch(
.group-count {
font-size: 14px;
font-weight: normal;
- color: #666;
+ color: var(--text-quaternary, #666666);
margin-left: 8px;
}
@@ -1547,7 +1547,7 @@ watch(
margin: 16px 0 8px 0;
padding: 6px 10px;
background: #f0f8ff;
- border-left: 3px solid #2196f3;
+ border-left: 3px solid var(--border-secondary, #2196f3);
color: #1565c0;
border-radius: 0 4px 4px 0;
}
@@ -1555,7 +1555,7 @@ watch(
.playtime-count {
font-size: 12px;
font-weight: normal;
- color: #666;
+ color: var(--text-quaternary, #666666);
margin-left: 6px;
}
@@ -1570,7 +1570,7 @@ watch(
padding-top: 16px;
border-top: 1px solid #e5e5e5;
font-size: 12px;
- color: #666;
+ color: var(--text-quaternary, #666666);
}
.footer-left {
diff --git a/app/components/Admin/Sidebar.vue b/app/components/Admin/Sidebar.vue
index d4dc22a..369f26c 100644
--- a/app/components/Admin/Sidebar.vue
+++ b/app/components/Admin/Sidebar.vue
@@ -279,7 +279,7 @@ const getRoleDisplayName = (role) => {
background: transparent;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
- background: #27272a;
+ background: var(--bg-tertiary, #27272a);
border-radius: 10px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
diff --git a/app/components/Admin/SongDownloadDialog.vue b/app/components/Admin/SongDownloadDialog.vue
index bb81bf2..9268c5a 100644
--- a/app/components/Admin/SongDownloadDialog.vue
+++ b/app/components/Admin/SongDownloadDialog.vue
@@ -1228,7 +1228,7 @@ onUnmounted(() => {
background: transparent;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
- background: #27272a;
+ background: var(--bg-tertiary, #27272a);
border-radius: 10px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
diff --git a/app/components/Admin/SongManagement.vue b/app/components/Admin/SongManagement.vue
index fce2995..a630cc2 100644
--- a/app/components/Admin/SongManagement.vue
+++ b/app/components/Admin/SongManagement.vue
@@ -2179,7 +2179,7 @@ onUnmounted(() => {
background: transparent;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
- background: #27272a;
+ background: var(--bg-tertiary, #27272a);
border-radius: 10px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
diff --git a/app/components/Admin/VotersModal.vue b/app/components/Admin/VotersModal.vue
index d7730f9..52fe766 100644
--- a/app/components/Admin/VotersModal.vue
+++ b/app/components/Admin/VotersModal.vue
@@ -223,14 +223,14 @@ watch(
.modal-title {
font-size: 20px;
font-weight: 600;
- color: #ffffff;
+ color: var(--text-primary, #ffffff);
margin: 0;
}
.close-btn {
background: none;
border: none;
- color: #888;
+ color: var(--text-tertiary, #888888);
cursor: pointer;
padding: 8px;
border-radius: 8px;
@@ -238,8 +238,8 @@ watch(
}
.close-btn:hover {
- background: #2a2a2a;
- color: #ffffff;
+ background: var(--bg-tertiary, #2a2a2a);
+ color: var(--text-primary, #ffffff);
}
.close-btn svg {
@@ -254,22 +254,22 @@ watch(
}
.song-info {
- background: #2a2a2a;
+ background: var(--bg-tertiary, #2a2a2a);
border-radius: 12px;
padding: 20px;
margin-bottom: 24px;
- border: 1px solid #3a3a3a;
+ border: 1px solid var(--border-secondary, #3a3a3a);
}
.song-title {
font-size: 18px;
font-weight: 600;
- color: #ffffff;
+ color: var(--text-primary, #ffffff);
margin: 0 0 8px 0;
}
.song-artist {
- color: #888;
+ color: var(--text-tertiary, #888888);
margin: 0 0 16px 0;
font-size: 14px;
}
@@ -304,7 +304,7 @@ watch(
.spinner {
width: 32px;
height: 32px;
- border: 3px solid #3a3a3a;
+ border: 3px solid var(--border-secondary, #3a3a3a);
border-top: 3px solid #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
@@ -366,12 +366,12 @@ watch(
align-items: center;
justify-content: space-between;
padding-bottom: 12px;
- border-bottom: 1px solid #3a3a3a;
+ border-bottom: 1px solid var(--border-secondary, #3a3a3a);
}
.voters-title {
font-weight: 600;
- color: #ffffff;
+ color: var(--text-primary, #ffffff);
font-size: 16px;
}
@@ -388,15 +388,15 @@ watch(
align-items: center;
justify-content: space-between;
padding: 12px 16px;
- background: #2a2a2a;
+ background: var(--bg-tertiary, #2a2a2a);
border-radius: 8px;
- border: 1px solid #3a3a3a;
+ border: 1px solid var(--border-secondary, #3a3a3a);
transition: all 0.2s ease;
}
.voter-item:hover {
- background: #333333;
- border-color: #4a4a4a;
+ background: var(--bg-quaternary, #333333);
+ border-color: var(--border-secondary, #4a4a4a);
}
.voter-info {
@@ -426,18 +426,18 @@ watch(
}
.voter-name {
- color: #ffffff;
+ color: var(--text-primary, #ffffff);
font-weight: 500;
font-size: 14px;
}
.vote-time {
- color: #888;
+ color: var(--text-tertiary, #888888);
font-size: 12px;
}
.voter-number {
- color: #888;
+ color: var(--text-tertiary, #888888);
font-size: 12px;
font-weight: 500;
}
@@ -454,11 +454,11 @@ watch(
.empty-icon {
width: 48px;
height: 48px;
- color: #666;
+ color: var(--text-quaternary, #666666);
}
.empty-state p {
- color: #888;
+ color: var(--text-tertiary, #888888);
margin: 0;
}
@@ -469,8 +469,8 @@ watch(
}
.close-button {
- background: #3a3a3a;
- color: #ffffff;
+ background: var(--bg-quaternary, #3a3a3a);
+ color: var(--text-primary, #ffffff);
border: none;
padding: 12px 24px;
border-radius: 8px;
@@ -490,7 +490,7 @@ watch(
}
.voters-container::-webkit-scrollbar-track {
- background: #2a2a2a;
+ background: var(--bg-tertiary, #2a2a2a);
border-radius: 3px;
}
diff --git a/app/components/Auth/OAuthButtons.vue b/app/components/Auth/OAuthButtons.vue
index 77e08f1..d7a169f 100644
--- a/app/components/Auth/OAuthButtons.vue
+++ b/app/components/Auth/OAuthButtons.vue
@@ -141,21 +141,21 @@ const loginWith = (provider: string) => {
}
.oauth-btn-github:hover {
- background: #24292e;
- color: #ffffff;
- border-color: #24292e;
+ background: var(--bg-tertiary, #24292e);
+ color: var(--text-primary, #ffffff);
+ border-color: var(--border-secondary, #24292e);
}
.oauth-btn-google:hover {
- background: #ffffff;
- color: #111111;
+ background: var(--card-bg, #ffffff);
+ color: var(--text-primary, #111111);
border-color: #dadce0;
}
.oauth-btn-aggregate:hover {
- background: #12b7f5;
- color: #ffffff;
- border-color: #12b7f5;
+ background: var(--bg-secondary, #12b7f5);
+ color: var(--text-primary, #ffffff);
+ border-color: var(--border-secondary, #12b7f5);
}
.oauth-empty-hint {
diff --git a/app/components/Auth/RegisterForm.vue b/app/components/Auth/RegisterForm.vue
index c9f0dc0..ee8fbf2 100644
--- a/app/components/Auth/RegisterForm.vue
+++ b/app/components/Auth/RegisterForm.vue
@@ -575,7 +575,7 @@ const handleRegister = async () => {
width: 100%;
padding: 16px;
background: var(--primary);
- color: #ffffff;
+ color: var(--text-primary, #ffffff);
border: none;
border-radius: var(--radius-lg);
font-size: 16px;
diff --git a/app/components/Notifications/NotificationSettings.vue b/app/components/Notifications/NotificationSettings.vue
index e585ebd..b6137f7 100644
--- a/app/components/Notifications/NotificationSettings.vue
+++ b/app/components/Notifications/NotificationSettings.vue
@@ -209,7 +209,7 @@ input[type='range']::-webkit-slider-thumb {
-webkit-appearance: none;
width: 18px;
height: 18px;
- background: #2563eb; /* blue-600 */
+ background: var(--bg-tertiary, #2563eb); /* blue-600 */
border-radius: 50%;
cursor: pointer;
transition: all 0.2s ease;
@@ -218,7 +218,7 @@ input[type='range']::-webkit-slider-thumb {
input[type='range']::-moz-range-thumb {
width: 18px;
height: 18px;
- background: #2563eb;
+ background: var(--bg-tertiary, #2563eb);
border-radius: 50%;
cursor: pointer;
border: none;
diff --git a/app/components/Player/PlayerLyric/AMLyric.vue b/app/components/Player/PlayerLyric/AMLyric.vue
index 58ba74a..93ee383 100644
--- a/app/components/Player/PlayerLyric/AMLyric.vue
+++ b/app/components/Player/PlayerLyric/AMLyric.vue
@@ -160,7 +160,7 @@ const jumpSeek = (line: LyricLineMouseEvent) => {
:deep(.am-lyric .is-active),
:deep(.am-lyric .lyric-line.current),
:deep(.am-lyric .lyric-line.is-current) {
- color: #ffffff;
+ color: var(--text-primary, #ffffff);
text-shadow: 0 4px 18px rgba(0, 0, 0, 0.35);
}
}
diff --git a/app/components/Player/PlayerLyric/DefaultLyric.vue b/app/components/Player/PlayerLyric/DefaultLyric.vue
index 156fec0..259724f 100644
--- a/app/components/Player/PlayerLyric/DefaultLyric.vue
+++ b/app/components/Player/PlayerLyric/DefaultLyric.vue
@@ -824,6 +824,6 @@ onBeforeUnmount(() => {
align-items: center;
justify-content: center;
font-size: 22px;
- color: #fff;
+ color: var(--text-primary, #ffffff);
}
diff --git a/app/components/Songs/RequestForm.vue b/app/components/Songs/RequestForm.vue
index 538f4f5..8c494ae 100644
--- a/app/components/Songs/RequestForm.vue
+++ b/app/components/Songs/RequestForm.vue
@@ -2912,7 +2912,7 @@ defineExpose({
diff --git a/app/components/year-review/ArtistSlide.vue b/app/components/year-review/ArtistSlide.vue
index 41a2cd4..1b060d8 100644
--- a/app/components/year-review/ArtistSlide.vue
+++ b/app/components/year-review/ArtistSlide.vue
@@ -43,7 +43,7 @@ defineProps({
.bg-overlay {
position: absolute;
inset: 0;
- background: linear-gradient(to top, #000, transparent, transparent);
+ background: linear-gradient(to top, var(--bg-primary, #111111), transparent, transparent);
opacity: 0.8;
z-index: 10;
}
diff --git a/app/components/year-review/FirstSongSlide.vue b/app/components/year-review/FirstSongSlide.vue
index 630ef15..9ab57af 100644
--- a/app/components/year-review/FirstSongSlide.vue
+++ b/app/components/year-review/FirstSongSlide.vue
@@ -74,7 +74,7 @@ const formatDate = (dateStr) => {
align-items: center;
justify-content: center;
overflow: hidden;
- background-color: #000;
+ background-color: var(--bg-primary, #111111);
}
.bg-blur {
@@ -120,7 +120,7 @@ const formatDate = (dateStr) => {
}
.highlight {
- color: #fff;
+ color: var(--text-primary, #ffffff);
font-weight: 700;
margin: 0 0.5rem;
}
@@ -141,7 +141,7 @@ const formatDate = (dateStr) => {
overflow: hidden;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
border: 1px solid rgba(255, 255, 255, 0.1);
- background: #111827;
+ background: var(--bg-secondary, #111827);
display: flex;
align-items: center;
justify-content: center;
@@ -181,7 +181,7 @@ const formatDate = (dateStr) => {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
- color: #fff;
+ color: var(--text-primary, #ffffff);
}
.song-artist {
diff --git a/app/components/year-review/IntroSlide.vue b/app/components/year-review/IntroSlide.vue
index 266bbb7..4c5e496 100644
--- a/app/components/year-review/IntroSlide.vue
+++ b/app/components/year-review/IntroSlide.vue
@@ -31,7 +31,7 @@ defineProps({
align-items: center;
justify-content: center;
overflow: hidden;
- background: radial-gradient(circle at center, #111827, #000000);
+ background: radial-gradient(circle at center, var(--bg-secondary, #111827), #000000);
}
.blob {
diff --git a/app/components/year-review/MiscSlide.vue b/app/components/year-review/MiscSlide.vue
index cc65ec1..6221775 100644
--- a/app/components/year-review/MiscSlide.vue
+++ b/app/components/year-review/MiscSlide.vue
@@ -54,7 +54,7 @@ const formatPlatform = (platform) => {
align-items: center;
justify-content: center;
overflow: hidden;
- background-color: #111827; /* gray-900 */
+ background-color: var(--bg-secondary, #111827); /* gray-900 */
}
.bg-gradient {
@@ -63,7 +63,7 @@ const formatPlatform = (platform) => {
left: 0;
width: 100%;
height: 50%;
- background: linear-gradient(to bottom, #1f2937, transparent);
+ background: linear-gradient(to bottom, var(--bg-secondary, #1f2937), transparent);
opacity: 0.2;
}
diff --git a/app/components/year-review/OutroSlide.vue b/app/components/year-review/OutroSlide.vue
index 0fe78e7..f62251c 100644
--- a/app/components/year-review/OutroSlide.vue
+++ b/app/components/year-review/OutroSlide.vue
@@ -56,13 +56,13 @@ defineEmits(['share'])
align-items: center;
justify-content: center;
overflow: hidden;
- background-color: #000;
+ background-color: var(--bg-primary, #111111);
}
.bg-gradient {
position: absolute;
inset: 0;
- background: radial-gradient(circle at center, #1f2937, #000, #000);
+ background: radial-gradient(circle at center, var(--bg-secondary, #1f2937), #000, #000);
opacity: 0.5;
}
@@ -132,8 +132,8 @@ defineEmits(['share'])
width: 100%;
padding: 1rem 2rem;
border-radius: 9999px;
- background: #fff;
- color: #000;
+ background: var(--card-bg, #fff);
+ color: var(--text-primary, #000);
font-weight: 700;
font-size: 1.125rem;
overflow: hidden;
@@ -173,7 +173,7 @@ defineEmits(['share'])
border-radius: 9999px;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
- color: #fff;
+ color: var(--text-primary, #ffffff);
font-weight: 700;
font-size: 1.125rem;
backdrop-filter: blur(12px);
diff --git a/app/components/year-review/ShareCard.vue b/app/components/year-review/ShareCard.vue
index b1f2e91..bd33395 100644
--- a/app/components/year-review/ShareCard.vue
+++ b/app/components/year-review/ShareCard.vue
@@ -63,7 +63,7 @@ defineExpose({
height: 100%;
min-height: 600px;
overflow: hidden;
- background: linear-gradient(135deg, #0f0f23 0%, #1a1a2e 50%, #16213e 100%);
+ background: linear-gradient(135deg, var(--bg-secondary, #0f0f23) 0%, #1a1a2e 50%, #16213e 100%);
}
.card-background {
@@ -127,7 +127,7 @@ defineExpose({
.main-title {
font-size: 3.5rem;
font-weight: 900;
- background: linear-gradient(135deg, #ffffff 0%, #e5e7eb 50%, #9ca3af 100%);
+ background: linear-gradient(135deg, var(--card-bg, #ffffff) 0%, #e5e7eb 50%, #9ca3af 100%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
diff --git a/app/pages/change-password.vue b/app/pages/change-password.vue
index 506d5e6..7cad842 100644
--- a/app/pages/change-password.vue
+++ b/app/pages/change-password.vue
@@ -195,7 +195,7 @@ onMounted(async () => {
font-size: 36px;
font-weight: 700;
margin: 0;
- background: linear-gradient(135deg, #ffffff 0%, #f0f0f0 100%);
+ background: linear-gradient(135deg, var(--card-bg, #ffffff) 0%, #f0f0f0 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
diff --git a/app/pages/index.vue b/app/pages/index.vue
index 8d450c0..7ec75dd 100644
--- a/app/pages/index.vue
+++ b/app/pages/index.vue
@@ -1910,7 +1910,7 @@ if (
height: 18px;
border-radius: 9px;
background-color: var(--error, #ef4444);
- color: #ffffff;
+ color: var(--text-primary, #ffffff);
font-size: 10px;
font-weight: 700;
display: flex;
@@ -3405,7 +3405,7 @@ if (
display: flex;
align-items: center;
justify-content: center;
- color: #ffffff;
+ color: var(--text-primary, #ffffff);
box-shadow: 0 10px 25px -5px rgba(139, 92, 246, 0.35);
z-index: 2;
transform: rotate(-5deg);
@@ -3485,7 +3485,7 @@ if (
background: var(--primary, #0b5afe);
border: none;
border-radius: 16px;
- color: #ffffff;
+ color: var(--text-primary, #ffffff);
font-size: 16px;
font-weight: 700;
display: flex;
@@ -3600,7 +3600,7 @@ if (
width: 8px;
height: 8px;
border-radius: 50%;
- background-color: #0b5afe;
+ background-color: var(--bg-secondary, #0b5afe);
display: inline-block;
z-index: 2;
}
@@ -3664,9 +3664,9 @@ if (
}
.login-button {
- background: linear-gradient(180deg, #0043f8 0%, #0075f8 100%);
+ background: linear-gradient(180deg, var(--bg-secondary, #0043f8) 0%, #0075f8 100%);
border: none;
- color: #ffffff;
+ color: var(--text-primary, #ffffff);
padding: 10px 20px;
border-radius: 6px;
font-weight: 600;
diff --git a/app/pages/year-review.vue b/app/pages/year-review.vue
index aae3219..398dd2c 100644
--- a/app/pages/year-review.vue
+++ b/app/pages/year-review.vue
@@ -211,8 +211,8 @@ const handleTouchEnd = (e) => {
.review-page {
position: fixed;
inset: 0;
- background-color: #000;
- color: #fff;
+ background-color: var(--bg-primary, #111111);
+ color: var(--text-primary, #ffffff);
z-index: 9999;
font-family:
'MiSans',
@@ -230,7 +230,7 @@ const handleTouchEnd = (e) => {
flex-direction: column;
align-items: center;
justify-content: center;
- background-color: #000;
+ background-color: var(--bg-primary, #111111);
z-index: 50;
}
@@ -285,7 +285,7 @@ const handleTouchEnd = (e) => {
.empty .empty-icon-circle {
width: 5rem;
height: 5rem;
- background: #111827;
+ background: var(--bg-secondary, #111827);
border-radius: 50%;
display: flex;
align-items: center;
@@ -364,7 +364,7 @@ const handleTouchEnd = (e) => {
.indicator-dot.active {
height: 1.5rem;
- background: #fff;
+ background: var(--card-bg, #fff);
}
/* 滚动提示 */
diff --git a/fix-colors.mjs b/fix-colors.mjs
new file mode 100644
index 0000000..003e18b
--- /dev/null
+++ b/fix-colors.mjs
@@ -0,0 +1,128 @@
+import fs from 'fs/promises';
+import path from 'path';
+
+async function walk(dir) {
+ const files = await fs.readdir(dir, { withFileTypes: true });
+ let results = [];
+ for (const file of files) {
+ const res = path.resolve(dir, file.name);
+ if (file.isDirectory()) {
+ results = results.concat(await walk(res));
+ } else if (file.name.endsWith('.vue')) {
+ results.push(res);
+ }
+ }
+ return results;
+}
+
+async function fixColors() {
+ const dirs = [path.resolve('app', 'components'), path.resolve('app', 'pages'), path.resolve('app', 'layouts')];
+ let files = [];
+ for (const dir of dirs) {
+ try {
+ files = files.concat(await walk(dir));
+ } catch (e) {
+ // Ignore if dir doesn't exist
+ }
+ }
+
+ for (const file of files) {
+ let content = await fs.readFile(file, 'utf-8');
+ let original = content;
+
+ // Generic regex for hardcoded grayscales in styles
+ const grayscales = {
+ '#000000': 'var(--bg-primary, #111111)',
+ '#000': 'var(--bg-primary, #111111)',
+ '#111111': 'var(--bg-primary, #111111)',
+ '#111': 'var(--bg-primary, #111111)',
+ '#1a1a1a': 'var(--bg-secondary, #1a1a1a)',
+ '#222222': 'var(--bg-tertiary, #222222)',
+ '#222': 'var(--bg-tertiary, #222222)',
+ '#2a2a2a': 'var(--bg-tertiary, #2a2a2a)',
+ '#333333': 'var(--bg-quaternary, #333333)',
+ '#333': 'var(--bg-quaternary, #333333)',
+ '#3a3a3a': 'var(--bg-quaternary, #3a3a3a)',
+ '#444444': 'var(--text-disabled, #444444)',
+ '#444': 'var(--text-disabled, #444444)',
+ '#666666': 'var(--text-quaternary, #666666)',
+ '#666': 'var(--text-quaternary, #666666)',
+ '#888888': 'var(--text-tertiary, #888888)',
+ '#888': 'var(--text-tertiary, #888888)',
+ '#999999': 'var(--text-tertiary, #999999)',
+ '#999': 'var(--text-tertiary, #999999)',
+ '#aaaaaa': 'var(--text-secondary, #aaaaaa)',
+ '#aaa': 'var(--text-secondary, #aaaaaa)',
+ '#cccccc': 'var(--text-secondary, #cccccc)',
+ '#ccc': 'var(--text-secondary, #cccccc)',
+ '#dddddd': 'var(--border-secondary, #dddddd)',
+ '#ddd': 'var(--border-secondary, #dddddd)',
+ '#eeeeee': 'var(--border-primary, #eeeeee)',
+ '#eee': 'var(--border-primary, #eeeeee)',
+ '#ffffff': 'var(--text-primary, #ffffff)',
+ '#fff': 'var(--text-primary, #ffffff)'
+ };
+
+ // We only want to replace inside
diff --git a/old-nuxt.config.ts b/old-nuxt.config.ts
new file mode 100644
index 0000000..b5c3581
--- /dev/null
+++ b/old-nuxt.config.ts
@@ -0,0 +1,447 @@
+import wasm from 'vite-plugin-wasm'
+import glsl from 'vite-plugin-glsl'
+import { fileURLToPath } from 'url'
+import { index, uniqueIndex } from 'drizzle-orm'
+
+const customSeoConfig: Record | null = (() => {
+ const raw = process.env.NUXT_PUBLIC_SEO_CONFIG
+ if (!raw) return null
+ try {
+ const parsed = JSON.parse(raw)
+ if (parsed && typeof parsed === 'object') return parsed as Record
+ return null
+ } catch {
+ return null
+ }
+})()
+
+function readNumberEnv(name: string, defaultValue: number): number {
+ const raw = process.env[name]
+ if (raw === undefined || raw === '') return defaultValue
+ const num = Number(raw)
+ return Number.isFinite(num) ? num : defaultValue
+}
+
+// https://nuxt.com/docs/api/configuration/nuxt-config
+export default defineNuxtConfig({
+ compatibilityDate: '2026-01-30',
+ future: {
+ compatibilityVersion: 4
+ },
+ srcDir: 'app',
+ serverDir: fileURLToPath(new URL('./server', import.meta.url)),
+ dir: {
+ public: fileURLToPath(new URL('./public', import.meta.url))
+ },
+ devtools: { enabled: process.env.NODE_ENV === 'development' },
+ devServer: {
+ host: '0.0.0.0',
+ port: 3000
+ },
+ modules: [
+ '@nuxtjs/tailwindcss',
+ ...((process.env.NODE_ENV === 'development' || process.env.npm_lifecycle_event?.includes('lint'))
+ ? ['@nuxt/eslint']
+ : []),
+ ...((process.env.PWA_ENABLED || 'false') === 'true'
+ ? ['@vite-pwa/nuxt']
+ : [])
+ ],
+
+ pwa: (process.env.PWA_ENABLED || 'false') === 'true'
+ ? {
+ registerType: 'autoUpdate',
+ manifest: {
+ name: process.env.NUXT_PUBLIC_SITE_TITLE || '鏍″洯骞挎挱绔欑偣姝岀郴缁?,
+ short_name: process.env.NUXT_PUBLIC_SITE_TITLE || 'VoiceHub',
+ description: process.env.NUXT_PUBLIC_SITE_DESCRIPTION || '鏍″洯骞挎挱绔欑偣姝岀郴缁?,
+ display: (process.env.PWA_DISPLAY as any) || 'standalone',
+ background_color: process.env.PWA_BACKGROUND_COLOR || '#000000',
+ theme_color: process.env.PWA_THEME_COLOR || '#111111',
+ icons: [
+ {
+ src: '/images/logo.png',
+ sizes: '192x192',
+ type: 'image/png'
+ },
+ {
+ src: '/images/logo.png',
+ sizes: '512x512',
+ type: 'image/png'
+ }
+ ]
+ },
+ workbox: {
+ navigateFallback: '/',
+ globPatterns: ['**/*.{js,css,html,png,jpg,jpeg,svg,ico,webp,woff2}']
+ },
+ client: {
+ installPrompt: true,
+ periodicSyncForUpdates: 3600
+ },
+ devOptions: {
+ enabled: process.env.NODE_ENV === 'development'
+ }
+ }
+ : {},
+
+ // 寮曞叆鍏ㄥ眬CSS
+ css: [
+ '~/assets/css/variables.css',
+ '~/assets/css/components.css',
+ '~/assets/css/main.css',
+ '~/assets/css/transitions.css',
+ '~/assets/css/mobile-admin.css',
+ '~/assets/css/print-fix.css',
+ '~/assets/css/sf-pro-icons.css'
+ ],
+
+ build: {
+ transpile: []
+ },
+
+ // 瀹為獙鎬т紭鍖栧姛鑳? experimental: {
+ payloadExtraction: true,
+ renderJsonPayloads: true,
+ viewTransition: true
+ },
+
+ // 閰嶇疆杩愯鏃堕厤缃? runtimeConfig: {
+ // 鏈嶅姟鍣ㄧ鏈夐敭锛堜笉浼氭毚闇插埌瀹㈡埛绔級
+ jwtSecret: process.env.JWT_SECRET || 'your-secret-key',
+ // Redis閰嶇疆锛堝彲閫夛級
+ redisUrl: process.env.REDIS_URL || '',
+ redisKeyPrefix: process.env.REDIS_KEY_PREFIX || '',
+ // Sentry 鏈嶅姟绔厤缃紙涓嶄細鏆撮湶鍒板鎴风锛? sentry: {
+ serverDsn: process.env.SENTRY_SERVER_DSN || process.env.SENTRY_DSN || '',
+ dsn: process.env.SENTRY_DSN || '',
+ errorSampleRate: readNumberEnv('SENTRY_ERROR_SAMPLE_RATE', 1.0),
+ tracesSampleRate: readNumberEnv('SENTRY_TRACES_SAMPLE_RATE', 1.0),
+ replaySessionSampleRate: readNumberEnv('SENTRY_REPLAY_SESSION_SAMPLE_RATE', 0),
+ replayOnErrorSampleRate: readNumberEnv('SENTRY_REPLAY_ON_ERROR_SAMPLE_RATE', 0),
+ release: process.env.SENTRY_RELEASE || '',
+ environment: process.env.SENTRY_ENVIRONMENT || process.env.NODE_ENV || 'production'
+ },
+ // 鍏叡閿紙浼氭毚闇插埌瀹㈡埛绔級
+ public: {
+ host: process.env.NUXT_PUBLIC_HOST || '',
+ apiBase: '/api',
+ oauth: {
+ github: !!process.env.GITHUB_CLIENT_ID,
+ casdoor: !!process.env.CASDOOR_CLIENT_ID,
+ google: !!process.env.GOOGLE_CLIENT_ID,
+ aggregate: !!process.env.AGGREGATE_OAUTH_APP_ID
+ },
+ siteTitle: process.env.NUXT_PUBLIC_SITE_TITLE || '鏍″洯骞挎挱绔欑偣姝岀郴缁?,
+ siteLogo: process.env.NUXT_PUBLIC_SITE_LOGO || '',
+ siteDescription:
+ process.env.NUXT_PUBLIC_SITE_DESCRIPTION || '鏍″洯骞挎挱绔欑偣姝岀郴缁?- 璁╀綘鐨勫0闊宠鍚',
+ isNetlify: process.env.NETLIFY === 'true',
+ // SEO 鑷畾涔夐厤缃紙閫氳繃 NUXT_PUBLIC_SEO_CONFIG 娉ㄥ叆 JSON 瀛楃涓诧級
+ seoConfig: customSeoConfig ?? {},
+ // 鍥惧舰楠岃瘉鐮佺浉鍏? captcha: {
+ enabled: (process.env.CAPTCHA_ENABLED || 'false') === 'true',
+ provider: process.env.CAPTCHA_PROVIDER || 'turnstile',
+ siteKey: process.env.CAPTCHA_SITE_KEY || ''
+ },
+ // PWA 鐩稿叧缁撴瀯锛堟湰鍦颁笉鍚敤妯″潡锛屼絾淇濈暀閰嶇疆椤逛互渚夸笂灞傚簲鐢ㄤ娇鐢級
+ pwa: {
+ enabled: (process.env.PWA_ENABLED || 'false') === 'true',
+ display: process.env.PWA_DISPLAY || 'standalone',
+ backgroundColor: process.env.PWA_BACKGROUND_COLOR || '#000000',
+ themeColor: process.env.PWA_THEME_COLOR || '#111111'
+ },
+ // 涓婚閰嶇疆
+ defaultTheme: process.env.NUXT_PUBLIC_DEFAULT_THEME || 'ClassicLight',
+ enabledThemes: (process.env.NUXT_PUBLIC_ENABLED_THEMES || 'ClassicLight,ModernLight')
+ .split(',')
+ .map((s) => s.trim())
+ .filter(Boolean),
+ // Sentry 瀹㈡埛绔厤缃紙浠呭叕寮€ DSN 涓庨噰鏍风巼锛? sentry: {
+ dsn: process.env.SENTRY_DSN || '',
+ tracesSampleRate: readNumberEnv('SENTRY_TRACES_SAMPLE_RATE', 1.0),
+ replaySessionSampleRate: readNumberEnv('SENTRY_REPLAY_SESSION_SAMPLE_RATE', 0),
+ replayOnErrorSampleRate: readNumberEnv('SENTRY_REPLAY_ON_ERROR_SAMPLE_RATE', 0)
+ }
+ }
+ },
+
+ // 閰嶇疆鐜鍙橀噺
+ app: {
+ head: {
+ title: process.env.NUXT_PUBLIC_SITE_TITLE || '鏍″洯骞挎挱绔欑偣姝岀郴缁?,
+ htmlAttrs: { lang: 'zh-CN' },
+ meta: [
+ { charset: 'utf-8' },
+ {
+ name: 'viewport',
+ content:
+ 'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover'
+ },
+ {
+ name: 'description',
+ content:
+ process.env.NUXT_PUBLIC_SITE_DESCRIPTION || '鏍″洯骞挎挱绔欑偣姝岀郴缁?- 璁╀綘鐨勫0闊宠鍚'
+ },
+ // 绉诲姩绔紭鍖? { name: 'theme-color', content: '#111111' },
+ {
+ name: 'x-default-theme',
+ content: process.env.NUXT_PUBLIC_DEFAULT_THEME || 'ClassicLight'
+ },
+ {
+ name: 'x-enabled-themes',
+ content: process.env.NUXT_PUBLIC_ENABLED_THEMES || 'ClassicLight,ModernLight'
+ },
+ { name: 'apple-mobile-web-app-capable', content: 'yes' },
+ { name: 'apple-mobile-web-app-status-bar-style', content: 'black-translucent' },
+ { name: 'apple-mobile-web-app-title', content: 'VoiceHub绠$悊' },
+ { name: 'mobile-web-app-capable', content: 'yes' },
+ { name: 'format-detection', content: 'telephone=no' }
+ ],
+ script: [
+ {
+ type: 'text/javascript',
+ tagPosition: 'head',
+ children: [
+ `(function(){try{`,
+ ` var k='voicehub:theme', d=document, r=d.documentElement, s=null, eff=null, def=null, list=[];`,
+ ` try{`,
+ ` var mt=d.querySelector('meta[name="x-enabled-themes"]');`,
+ ` if(mt && mt.content){list=String(mt.content).split(',').map(function(x){return x.replace(/^\\s+|\\s+$/g,'')}).filter(Boolean)}`,
+ ` }catch(e){list=[]}`,
+ ` try{`,
+ ` var md=d.querySelector('meta[name="x-default-theme"]');`,
+ ` if(md && md.content){def=md.content}`,
+ ` }catch(e){def=null}`,
+ ` if(!def){def='ClassicLight'}`,
+ ` if(list.length===0){list=['ClassicLight','ModernLight']}`,
+ ` function valid(t){`,
+ ` if(list.indexOf(t)!==-1){return t}`,
+ ` if(list.indexOf(def)!==-1){return def}`,
+ ` return list[0]`,
+ ` }`,
+ ` try{s=localStorage.getItem(k)}catch(e){s=null}`,
+ ` if(!s){s=def}`,
+ ` else{s=valid(s)}`,
+ ` if(typeof s==='string' && (s==='System'||s==='system')){eff=window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light'}`,
+ ` else if(typeof s==='string'){eff=s.endsWith('Dark')||s==='dark'?'dark':(s.endsWith('Light')||s==='light'?'light':s)}`,
+ ` else{eff='light'}`,
+ ` r.setAttribute('data-theme', (typeof s==='string'?s:eff));`,
+ ` if(eff==='dark'){r.classList.add('dark'); r.classList.remove('light'); r.style.colorScheme='dark'}`,
+ ` else if(eff==='light'){r.classList.add('light'); r.classList.remove('dark'); r.style.colorScheme='light'}`,
+ ` else{r.classList.remove('dark','light'); r.style.colorScheme='light dark'}`,
+ ` try{`,
+ ` var tm=d.querySelector('meta[name="theme-color"]');`,
+ ` if(tm){tm.content=(eff==='light'?'#f8fafc':'#111111')}`,
+ ` }catch(e){}`,
+ `}catch(e){}})();`
+ ].join('')
+ }
+ ],
+ link: [
+ { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
+ { rel: 'preload', as: 'image', href: '/images/logo.png' },
+ { rel: 'preload', as: 'image', href: '/images/site-logo.png' },
+ { rel: 'preload', as: 'image', href: '/images/school-logo-white.png' },
+ // 浼樺厛鍔犺浇甯歌瀛椾綋锛岀‘淇濋〉闈㈠揩閫熸樉绀? {
+ rel: 'preload',
+ as: 'style',
+ href: 'https://cdn.jsdelivr.net/npm/misans@4.1.0/lib/Normal/MiSans-Regular.min.css'
+ },
+ {
+ rel: 'stylesheet',
+ href: 'https://cdn.jsdelivr.net/npm/misans@4.1.0/lib/Normal/MiSans-Regular.min.css'
+ },
+ // 寤惰繜鍔犺浇鍏朵粬瀛楅噸锛岄伩鍏嶉樆濉為〉闈㈡覆鏌? {
+ rel: 'preload',
+ as: 'style',
+ href: 'https://cdn.jsdelivr.net/npm/misans@4.1.0/lib/Normal/MiSans-Medium.min.css',
+ onload: "this.onload=null;this.rel='stylesheet'"
+ },
+ {
+ rel: 'preload',
+ as: 'style',
+ href: 'https://cdn.jsdelivr.net/npm/misans@4.1.0/lib/Normal/MiSans-Semibold.min.css',
+ onload: "this.onload=null;this.rel='stylesheet'"
+ },
+ {
+ rel: 'preload',
+ as: 'style',
+ href: 'https://cdn.jsdelivr.net/npm/misans@4.1.0/lib/Normal/MiSans-Bold.min.css',
+ onload: "this.onload=null;this.rel='stylesheet'"
+ }
+ ]
+ }
+ },
+
+ features: {
+ inlineStyles: false
+ },
+
+ // TypeScript閰嶇疆
+ typescript: {
+ strict: true
+ },
+
+ // 鏈嶅姟鍣ㄧ閰嶇疆
+ nitro: {
+ compressPublicAssets: true,
+ preset: process.env.VERCEL
+ ? 'vercel'
+ : process.env.NETLIFY
+ ? 'netlify'
+ : process.env.NITRO_PRESET || 'node-server',
+ // 澧炲己閿欒澶勭悊鍜岀ǔ瀹氭€? experimental: {
+ wasm: true,
+ asyncContext: true
+ },
+ timing: true,
+ // 澧炲姞璇锋眰瓒呮椂鏃堕棿
+ routeRules: {
+ // 瀹屽叏绂佺敤鎵€鏈堿PI璺敱鐨勭紦瀛橈紝纭繚姣忔閮借姹傛暟鎹簱
+ '/api/**': {
+ headers: {
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
+ Pragma: 'no-cache',
+ Expires: '0',
+ Connection: 'keep-alive'
+ }
+ },
+ // 闈欐€佽祫婧愭枃浠剁紦瀛橀厤缃? '/_nuxt/**': {
+ headers: {
+ 'Cache-Control': 'public, max-age=31536000, immutable'
+ }
+ },
+ '/assets/**': {
+ headers: {
+ 'Cache-Control': 'public, max-age=31536000, immutable'
+ }
+ },
+ '/favicon.ico': {
+ headers: {
+ 'Cache-Control': 'public, max-age=86400'
+ }
+ },
+ // 鍥剧墖銆丆SS銆丣S绛夐潤鎬佽祫婧愮紦瀛? '/**/*.{png,jpg,jpeg,gif,webp,svg,ico}': {
+ headers: {
+ 'Cache-Control': 'public, max-age=31536000, immutable'
+ }
+ },
+ '/**/*.{css,js}': {
+ headers: {
+ 'Cache-Control': 'public, max-age=31536000, immutable'
+ }
+ },
+ // 璁よ瘉鐩稿叧椤甸潰涓嶇紦瀛? '/login': {
+ headers: {
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
+ Pragma: 'no-cache',
+ Expires: '0'
+ }
+ },
+ '/dashboard': {
+ headers: {
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
+ Pragma: 'no-cache',
+ Expires: '0'
+ }
+ },
+ '/change-password': {
+ headers: {
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
+ Pragma: 'no-cache',
+ Expires: '0'
+ }
+ },
+ '/auth/**': {
+ headers: {
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
+ Pragma: 'no-cache',
+ Expires: '0'
+ }
+ },
+ '/notification-settings': {
+ headers: {
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
+ Pragma: 'no-cache',
+ Expires: '0'
+ }
+ }
+ },
+ // 鏍规嵁閮ㄧ讲鐜璋冩暣閰嶇疆
+ ...(process.env.VERCEL
+ ? {
+ // Vercel 鐜锛氫娇鐢ㄦ爣鍑嗛厤缃? }
+ : process.env.NETLIFY
+ ? {
+ // Netlify 鐜锛氱‘淇?Drizzle 姝g‘鎵撳寘
+ experimental: {
+ wasm: true
+ }
+ }
+ : {
+ // 鍏朵粬鐜锛氫娇鐢ㄦ爣鍑嗛厤缃? })
+ },
+
+ // Vite 閰嶇疆
+ vite: {
+ build: {
+ chunkSizeWarningLimit: 2000,
+ cssCodeSplit: true,
+ rollupOptions: {
+ maxParallelFileOps: 2,
+ output: {
+ manualChunks(id) {
+ if (id.includes('node_modules/lucide-vue-next')) {
+ return 'lucide'
+ }
+ }
+ }
+ }
+ },
+ resolve: {
+ alias: [
+ {
+ find: '@applemusic-like-lyrics/core/style.css',
+ replacement: fileURLToPath(
+ new URL('./vendor/amll-core/src/styles/index.css', import.meta.url)
+ )
+ },
+ {
+ find: '@applemusic-like-lyrics/core',
+ replacement: fileURLToPath(new URL('./vendor/amll-core/src/index.ts', import.meta.url))
+ }
+ ]
+ },
+ plugins: [wasm(), glsl()],
+ optimizeDeps: {
+ include: ['drizzle-orm'],
+ exclude: ['@applemusic-like-lyrics/vue', '@applemusic-like-lyrics/lyric']
+ },
+ build: {
+ sourcemap: false,
+ rollupOptions: {
+ output: {
+ manualChunks(id) {
+ if (!id.includes('node_modules')) return
+ if (id.includes('lucide-vue-next')) return 'icons'
+ if (id.includes('@pixi')) return 'pixi'
+ if (id.includes('@applemusic-like-lyrics')) return 'lyric-engine'
+ if (id.includes('drizzle-orm') || id.includes('postgres')) return 'database'
+ if (id.includes('xlsx') || id.includes('jspdf') || id.includes('jszip')) return 'office'
+ }
+ }
+ }
+ },
+ // 娣诲姞 WASM 鏀寔閰嶇疆
+ assetsInclude: ['**/*.wasm'],
+ // SSR閰嶇疆
+ ssr: {
+ noExternal: process.env.NETLIFY
+ ? ['drizzle-orm', 'postgres']
+ : process.env.VERCEL
+ ? []
+ : ['drizzle-orm', 'postgres']
+ }
+ }
+})
diff --git a/old-nuxt2.config.ts b/old-nuxt2.config.ts
new file mode 100644
index 0000000..41bc534
--- /dev/null
+++ b/old-nuxt2.config.ts
@@ -0,0 +1,433 @@
+import wasm from 'vite-plugin-wasm'
+import glsl from 'vite-plugin-glsl'
+import { fileURLToPath } from 'url'
+import { index, uniqueIndex } from 'drizzle-orm'
+
+const customSeoConfig: Record | null = (() => {
+ const raw = process.env.NUXT_PUBLIC_SEO_CONFIG
+ if (!raw) return null
+ try {
+ const parsed = JSON.parse(raw)
+ if (parsed && typeof parsed === 'object') return parsed as Record
+ return null
+ } catch {
+ return null
+ }
+})()
+
+function readNumberEnv(name: string, defaultValue: number): number {
+ const raw = process.env[name]
+ if (raw === undefined || raw === '') return defaultValue
+ const num = Number(raw)
+ return Number.isFinite(num) ? num : defaultValue
+}
+
+// https://nuxt.com/docs/api/configuration/nuxt-config
+export default defineNuxtConfig({
+ compatibilityDate: '2026-01-30',
+ future: {
+ compatibilityVersion: 4
+ },
+ srcDir: 'app',
+ serverDir: fileURLToPath(new URL('./server', import.meta.url)),
+ dir: {
+ public: fileURLToPath(new URL('./public', import.meta.url))
+ },
+ devtools: { enabled: process.env.NODE_ENV === 'development' },
+ devServer: {
+ host: '0.0.0.0',
+ port: 3000
+ },
+ modules: [
+ '@nuxtjs/tailwindcss',
+ ...((process.env.NODE_ENV === 'development' || process.env.npm_lifecycle_event?.includes('lint'))
+ ? ['@nuxt/eslint']
+ : []),
+ ...((process.env.PWA_ENABLED || 'false') === 'true'
+ ? ['@vite-pwa/nuxt']
+ : [])
+ ],
+
+ pwa: (process.env.PWA_ENABLED || 'false') === 'true'
+ ? {
+ registerType: 'autoUpdate',
+ manifest: {
+ name: process.env.NUXT_PUBLIC_SITE_TITLE || '鏍″洯骞挎挱绔欑偣姝岀郴缁?,
+ short_name: process.env.NUXT_PUBLIC_SITE_TITLE || 'VoiceHub',
+ description: process.env.NUXT_PUBLIC_SITE_DESCRIPTION || '鏍″洯骞挎挱绔欑偣姝岀郴缁?,
+ display: (process.env.PWA_DISPLAY as any) || 'standalone',
+ background_color: process.env.PWA_BACKGROUND_COLOR || '#000000',
+ theme_color: process.env.PWA_THEME_COLOR || '#111111',
+ icons: [
+ {
+ src: '/images/logo.png',
+ sizes: '192x192',
+ type: 'image/png'
+ },
+ {
+ src: '/images/logo.png',
+ sizes: '512x512',
+ type: 'image/png'
+ }
+ ]
+ },
+ workbox: {
+ navigateFallback: '/',
+ globPatterns: ['**/*.{js,css,html,png,jpg,jpeg,svg,ico,webp,woff2}']
+ },
+ client: {
+ installPrompt: true,
+ periodicSyncForUpdates: 3600
+ },
+ devOptions: {
+ enabled: process.env.NODE_ENV === 'development'
+ }
+ }
+ : {},
+
+ // 寮曞叆鍏ㄥ眬CSS
+ css: [
+ '~/assets/css/variables.css',
+ '~/assets/css/components.css',
+ '~/assets/css/main.css',
+ '~/assets/css/transitions.css',
+ '~/assets/css/mobile-admin.css',
+ '~/assets/css/print-fix.css',
+ '~/assets/css/sf-pro-icons.css'
+ ],
+
+ build: {
+ transpile: []
+ },
+
+ // 瀹為獙鎬т紭鍖栧姛鑳? experimental: {
+ payloadExtraction: true,
+ renderJsonPayloads: true,
+ viewTransition: true
+ },
+
+ // 閰嶇疆杩愯鏃堕厤缃? runtimeConfig: {
+ // 鏈嶅姟鍣ㄧ鏈夐敭锛堜笉浼氭毚闇插埌瀹㈡埛绔級
+ jwtSecret: process.env.JWT_SECRET || 'your-secret-key',
+ // Redis閰嶇疆锛堝彲閫夛級
+ redisUrl: process.env.REDIS_URL || '',
+ redisKeyPrefix: process.env.REDIS_KEY_PREFIX || '',
+ // Sentry 鏈嶅姟绔厤缃紙涓嶄細鏆撮湶鍒板鎴风锛? sentry: {
+ serverDsn: process.env.SENTRY_SERVER_DSN || process.env.SENTRY_DSN || '',
+ dsn: process.env.SENTRY_DSN || '',
+ errorSampleRate: readNumberEnv('SENTRY_ERROR_SAMPLE_RATE', 1.0),
+ tracesSampleRate: readNumberEnv('SENTRY_TRACES_SAMPLE_RATE', 1.0),
+ replaySessionSampleRate: readNumberEnv('SENTRY_REPLAY_SESSION_SAMPLE_RATE', 0),
+ replayOnErrorSampleRate: readNumberEnv('SENTRY_REPLAY_ON_ERROR_SAMPLE_RATE', 0),
+ release: process.env.SENTRY_RELEASE || '',
+ environment: process.env.SENTRY_ENVIRONMENT || process.env.NODE_ENV || 'production'
+ },
+ // 鍏叡閿紙浼氭毚闇插埌瀹㈡埛绔級
+ public: {
+ host: process.env.NUXT_PUBLIC_HOST || '',
+ apiBase: '/api',
+ oauth: {
+ github: !!process.env.GITHUB_CLIENT_ID,
+ casdoor: !!process.env.CASDOOR_CLIENT_ID,
+ google: !!process.env.GOOGLE_CLIENT_ID,
+ aggregate: !!process.env.AGGREGATE_OAUTH_APP_ID
+ },
+ siteTitle: process.env.NUXT_PUBLIC_SITE_TITLE || '鏍″洯骞挎挱绔欑偣姝岀郴缁?,
+ siteLogo: process.env.NUXT_PUBLIC_SITE_LOGO || '',
+ siteDescription:
+ process.env.NUXT_PUBLIC_SITE_DESCRIPTION || '鏍″洯骞挎挱绔欑偣姝岀郴缁?- 璁╀綘鐨勫0闊宠鍚',
+ isNetlify: process.env.NETLIFY === 'true',
+ // SEO 鑷畾涔夐厤缃紙閫氳繃 NUXT_PUBLIC_SEO_CONFIG 娉ㄥ叆 JSON 瀛楃涓诧級
+ seoConfig: customSeoConfig ?? {},
+ // 鍥惧舰楠岃瘉鐮佺浉鍏? captcha: {
+ enabled: (process.env.CAPTCHA_ENABLED || 'false') === 'true',
+ provider: process.env.CAPTCHA_PROVIDER || 'turnstile',
+ siteKey: process.env.CAPTCHA_SITE_KEY || ''
+ },
+ // PWA 鐩稿叧缁撴瀯锛堟湰鍦颁笉鍚敤妯″潡锛屼絾淇濈暀閰嶇疆椤逛互渚夸笂灞傚簲鐢ㄤ娇鐢級
+ pwa: {
+ enabled: (process.env.PWA_ENABLED || 'false') === 'true',
+ display: process.env.PWA_DISPLAY || 'standalone',
+ backgroundColor: process.env.PWA_BACKGROUND_COLOR || '#000000',
+ themeColor: process.env.PWA_THEME_COLOR || '#111111'
+ },
+ // 涓婚閰嶇疆
+ defaultTheme: process.env.NUXT_PUBLIC_DEFAULT_THEME || 'ClassicLight',
+ enabledThemes: (process.env.NUXT_PUBLIC_ENABLED_THEMES || 'ClassicLight,ModernLight')
+ .split(',')
+ .map((s) => s.trim())
+ .filter(Boolean),
+ // Sentry 瀹㈡埛绔厤缃紙浠呭叕寮€ DSN 涓庨噰鏍风巼锛? sentry: {
+ dsn: process.env.SENTRY_DSN || '',
+ tracesSampleRate: readNumberEnv('SENTRY_TRACES_SAMPLE_RATE', 1.0),
+ replaySessionSampleRate: readNumberEnv('SENTRY_REPLAY_SESSION_SAMPLE_RATE', 0),
+ replayOnErrorSampleRate: readNumberEnv('SENTRY_REPLAY_ON_ERROR_SAMPLE_RATE', 0)
+ }
+ }
+ },
+
+ // 閰嶇疆鐜鍙橀噺
+ app: {
+ head: {
+ title: process.env.NUXT_PUBLIC_SITE_TITLE || '鏍″洯骞挎挱绔欑偣姝岀郴缁?,
+ htmlAttrs: { lang: 'zh-CN' },
+ meta: [
+ { charset: 'utf-8' },
+ {
+ name: 'viewport',
+ content:
+ 'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover'
+ },
+ {
+ name: 'description',
+ content:
+ process.env.NUXT_PUBLIC_SITE_DESCRIPTION || '鏍″洯骞挎挱绔欑偣姝岀郴缁?- 璁╀綘鐨勫0闊宠鍚'
+ },
+ // 绉诲姩绔紭鍖? { name: 'theme-color', content: '#111111' },
+ {
+ name: 'x-default-theme',
+ content: process.env.NUXT_PUBLIC_DEFAULT_THEME || 'System'
+ },
+ { name: 'apple-mobile-web-app-capable', content: 'yes' },
+ { name: 'apple-mobile-web-app-status-bar-style', content: 'black-translucent' },
+ { name: 'apple-mobile-web-app-title', content: 'VoiceHub绠$悊' },
+ { name: 'mobile-web-app-capable', content: 'yes' },
+ { name: 'format-detection', content: 'telephone=no' }
+ ],
+ script: [
+ {
+ type: 'text/javascript',
+ tagPosition: 'head',
+ children: [
+ `(function(){try{`,
+ ` var k='voicehub:theme', d=document, r=d.documentElement, s=null, eff=null;`,
+ ` try{s=localStorage.getItem(k)}catch(e){s=null}`,
+ ` if(!s){`,
+ ` try{`,
+ ` var meta=document.querySelector('meta[name="x-default-theme"]');`,
+ ` if(meta && meta.content){s=meta.content}`,
+ ` }catch(e){s=null}`,
+ ` if(!s){s='System'}`,
+ ` }`,
+ ` if(s==='System'||s==='system'){eff=window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light'}`,
+ ` else if(typeof s==='string'){eff=s.endsWith('Dark')||s==='dark'?'dark':(s.endsWith('Light')||s==='light'?'light':s)}`,
+ ` else{eff='dark'}`,
+ ` r.setAttribute('data-theme', (typeof s==='string'?s:eff));`,
+ ` if(eff==='dark'){r.classList.add('dark'); r.classList.remove('light'); r.style.colorScheme='dark'}`,
+ ` else if(eff==='light'){r.classList.add('light'); r.classList.remove('dark'); r.style.colorScheme='light'}`,
+ ` else{r.classList.remove('dark','light'); r.style.colorScheme='light dark'}`,
+ ` try{`,
+ ` var tm=d.querySelector('meta[name="theme-color"]');`,
+ ` if(tm){tm.content=(eff==='light'?'#f8fafc':'#111111')}`,
+ ` }catch(e){}`,
+ `}catch(e){}})();`
+ ].join('')
+ }
+ ],
+ link: [
+ { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
+ { rel: 'preload', as: 'image', href: '/images/logo.png' },
+ { rel: 'preload', as: 'image', href: '/images/site-logo.png' },
+ { rel: 'preload', as: 'image', href: '/images/school-logo-white.png' },
+ // 浼樺厛鍔犺浇甯歌瀛椾綋锛岀‘淇濋〉闈㈠揩閫熸樉绀? {
+ rel: 'preload',
+ as: 'style',
+ href: 'https://cdn.jsdelivr.net/npm/misans@4.1.0/lib/Normal/MiSans-Regular.min.css'
+ },
+ {
+ rel: 'stylesheet',
+ href: 'https://cdn.jsdelivr.net/npm/misans@4.1.0/lib/Normal/MiSans-Regular.min.css'
+ },
+ // 寤惰繜鍔犺浇鍏朵粬瀛楅噸锛岄伩鍏嶉樆濉為〉闈㈡覆鏌? {
+ rel: 'preload',
+ as: 'style',
+ href: 'https://cdn.jsdelivr.net/npm/misans@4.1.0/lib/Normal/MiSans-Medium.min.css',
+ onload: "this.onload=null;this.rel='stylesheet'"
+ },
+ {
+ rel: 'preload',
+ as: 'style',
+ href: 'https://cdn.jsdelivr.net/npm/misans@4.1.0/lib/Normal/MiSans-Semibold.min.css',
+ onload: "this.onload=null;this.rel='stylesheet'"
+ },
+ {
+ rel: 'preload',
+ as: 'style',
+ href: 'https://cdn.jsdelivr.net/npm/misans@4.1.0/lib/Normal/MiSans-Bold.min.css',
+ onload: "this.onload=null;this.rel='stylesheet'"
+ }
+ ]
+ }
+ },
+
+ features: {
+ inlineStyles: false
+ },
+
+ // TypeScript閰嶇疆
+ typescript: {
+ strict: true
+ },
+
+ // 鏈嶅姟鍣ㄧ閰嶇疆
+ nitro: {
+ compressPublicAssets: true,
+ preset: process.env.VERCEL
+ ? 'vercel'
+ : process.env.NETLIFY
+ ? 'netlify'
+ : process.env.NITRO_PRESET || 'node-server',
+ // 澧炲己閿欒澶勭悊鍜岀ǔ瀹氭€? experimental: {
+ wasm: true,
+ asyncContext: true
+ },
+ timing: true,
+ // 澧炲姞璇锋眰瓒呮椂鏃堕棿
+ routeRules: {
+ // 瀹屽叏绂佺敤鎵€鏈堿PI璺敱鐨勭紦瀛橈紝纭繚姣忔閮借姹傛暟鎹簱
+ '/api/**': {
+ headers: {
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
+ Pragma: 'no-cache',
+ Expires: '0',
+ Connection: 'keep-alive'
+ }
+ },
+ // 闈欐€佽祫婧愭枃浠剁紦瀛橀厤缃? '/_nuxt/**': {
+ headers: {
+ 'Cache-Control': 'public, max-age=31536000, immutable'
+ }
+ },
+ '/assets/**': {
+ headers: {
+ 'Cache-Control': 'public, max-age=31536000, immutable'
+ }
+ },
+ '/favicon.ico': {
+ headers: {
+ 'Cache-Control': 'public, max-age=86400'
+ }
+ },
+ // 鍥剧墖銆丆SS銆丣S绛夐潤鎬佽祫婧愮紦瀛? '/**/*.{png,jpg,jpeg,gif,webp,svg,ico}': {
+ headers: {
+ 'Cache-Control': 'public, max-age=31536000, immutable'
+ }
+ },
+ '/**/*.{css,js}': {
+ headers: {
+ 'Cache-Control': 'public, max-age=31536000, immutable'
+ }
+ },
+ // 璁よ瘉鐩稿叧椤甸潰涓嶇紦瀛? '/login': {
+ headers: {
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
+ Pragma: 'no-cache',
+ Expires: '0'
+ }
+ },
+ '/dashboard': {
+ headers: {
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
+ Pragma: 'no-cache',
+ Expires: '0'
+ }
+ },
+ '/change-password': {
+ headers: {
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
+ Pragma: 'no-cache',
+ Expires: '0'
+ }
+ },
+ '/auth/**': {
+ headers: {
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
+ Pragma: 'no-cache',
+ Expires: '0'
+ }
+ },
+ '/notification-settings': {
+ headers: {
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
+ Pragma: 'no-cache',
+ Expires: '0'
+ }
+ }
+ },
+ // 鏍规嵁閮ㄧ讲鐜璋冩暣閰嶇疆
+ ...(process.env.VERCEL
+ ? {
+ // Vercel 鐜锛氫娇鐢ㄦ爣鍑嗛厤缃? }
+ : process.env.NETLIFY
+ ? {
+ // Netlify 鐜锛氱‘淇?Drizzle 姝g‘鎵撳寘
+ experimental: {
+ wasm: true
+ }
+ }
+ : {
+ // 鍏朵粬鐜锛氫娇鐢ㄦ爣鍑嗛厤缃? })
+ },
+
+ // Vite 閰嶇疆
+ vite: {
+ build: {
+ chunkSizeWarningLimit: 2000,
+ cssCodeSplit: true,
+ rollupOptions: {
+ maxParallelFileOps: 2,
+ output: {
+ manualChunks(id) {
+ if (id.includes('node_modules/lucide-vue-next')) {
+ return 'lucide'
+ }
+ }
+ }
+ }
+ },
+ resolve: {
+ alias: [
+ {
+ find: '@applemusic-like-lyrics/core/style.css',
+ replacement: fileURLToPath(
+ new URL('./vendor/amll-core/src/styles/index.css', import.meta.url)
+ )
+ },
+ {
+ find: '@applemusic-like-lyrics/core',
+ replacement: fileURLToPath(new URL('./vendor/amll-core/src/index.ts', import.meta.url))
+ }
+ ]
+ },
+ plugins: [wasm(), glsl()],
+ optimizeDeps: {
+ include: ['drizzle-orm'],
+ exclude: ['@applemusic-like-lyrics/vue', '@applemusic-like-lyrics/lyric']
+ },
+ build: {
+ sourcemap: false,
+ rollupOptions: {
+ output: {
+ manualChunks(id) {
+ if (!id.includes('node_modules')) return
+ if (id.includes('lucide-vue-next')) return 'icons'
+ if (id.includes('@pixi')) return 'pixi'
+ if (id.includes('@applemusic-like-lyrics')) return 'lyric-engine'
+ if (id.includes('drizzle-orm') || id.includes('postgres')) return 'database'
+ if (id.includes('xlsx') || id.includes('jspdf') || id.includes('jszip')) return 'office'
+ }
+ }
+ }
+ },
+ // 娣诲姞 WASM 鏀寔閰嶇疆
+ assetsInclude: ['**/*.wasm'],
+ // SSR閰嶇疆
+ ssr: {
+ noExternal: process.env.NETLIFY
+ ? ['drizzle-orm', 'postgres']
+ : process.env.VERCEL
+ ? []
+ : ['drizzle-orm', 'postgres']
+ }
+ }
+})
diff --git a/old-variables.css b/old-variables.css
new file mode 100644
index 0000000..3fe2829
--- /dev/null
+++ b/old-variables.css
@@ -0,0 +1,556 @@
+/* 鍏ㄥ眬CSS鍙橀噺 - 缁熶竴閰嶈壊鏂规 */
+:root {
+ /* 涓昏壊璋?*/
+ --primary: #3b82f6;
+ --primary-hover: #2563eb;
+ --primary-light: rgba(59, 130, 246, 0.1);
+ --primary-border: rgba(59, 130, 246, 0.2);
+
+ /* 鎴愬姛鑹?*/
+ --success: #10b981;
+ --success-hover: #059669;
+ --success-light: rgba(16, 185, 129, 0.1);
+ --success-border: rgba(16, 185, 129, 0.2);
+
+ /* 璀﹀憡鑹?*/
+ --warning: #f59e0b;
+ --warning-hover: #d97706;
+ --warning-light: rgba(245, 158, 11, 0.1);
+ --warning-border: rgba(245, 158, 11, 0.2);
+
+ /* 閿欒鑹?*/
+ --error: #ef4444;
+ --error-hover: #dc2626;
+ --error-light: rgba(239, 68, 68, 0.1);
+ --error-border: rgba(239, 68, 68, 0.2);
+
+ /* 淇℃伅鑹?*/
+ --info: #3b82f6;
+ --info-hover: #2563eb;
+ --info-light: rgba(59, 130, 246, 0.1);
+ --info-border: rgba(59, 130, 246, 0.2);
+
+ /* 鑳屾櫙鑹?*/
+ --bg-primary: #111111;
+ --bg-secondary: #1a1a1a;
+ --bg-tertiary: #2a2a2a;
+ --bg-quaternary: #3a3a3a;
+ --bg-hover: #1f1f1f;
+
+ /* 鏂囧瓧棰滆壊 */
+ --text-primary: #ffffff;
+ --text-secondary: #cccccc;
+ --text-tertiary: #888888;
+ --text-quaternary: #666666;
+ --text-disabled: #444444;
+
+ /* 杈规棰滆壊 */
+ --border-primary: #1f1f1f;
+ --border-secondary: #2a2a2a;
+ --border-tertiary: #3a3a3a;
+ --border-quaternary: #4a4a4a;
+
+ /* 闃村奖 */
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
+ --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
+ --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
+ --shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.1);
+
+ /* 鍦嗚 */
+ --radius-sm: 4px;
+ --radius-md: 6px;
+ --radius-lg: 8px;
+ --radius-xl: 12px;
+ --radius-2xl: 16px;
+
+ /* 闂磋窛 */
+ --spacing-xs: 4px;
+ --spacing-sm: 8px;
+ --spacing-md: 12px;
+ --spacing-lg: 16px;
+ --spacing-xl: 20px;
+ --spacing-2xl: 24px;
+ --spacing-3xl: 32px;
+
+ /* 瀛椾綋澶у皬 */
+ --text-xs: 12px;
+ --text-sm: 14px;
+ --text-base: 16px;
+ --text-lg: 18px;
+ --text-xl: 20px;
+ --text-2xl: 24px;
+ --text-3xl: 30px;
+
+ /* 瀛椾綋绮楃粏 */
+ --font-normal: 400;
+ --font-medium: 500;
+ --font-semibold: 600;
+ --font-bold: 700;
+
+ /* 杩囨浮鍔ㄧ敾 */
+ --transition-fast: 0.15s ease;
+ --transition-normal: 0.2s ease;
+ --transition-slow: 0.3s ease;
+
+ /* Z-index灞傜骇 */
+ --z-dropdown: 100;
+ --z-sticky: 200;
+ --z-fixed: 300;
+ --z-modal-backdrop: 400;
+ --z-modal: 500;
+ --z-popover: 600;
+ --z-tooltip: 700;
+ --z-toast: 800;
+
+ /* 鐗规畩鐘舵€侀鑹?*/
+ --status-pending: #fbbf24;
+ --status-pending-light: rgba(251, 191, 36, 0.1);
+ --status-approved: #10b981;
+ --status-approved-light: rgba(16, 185, 129, 0.1);
+ --status-played: #6b7280;
+ --status-played-light: rgba(107, 114, 128, 0.1);
+
+ /* 鎶曠エ鐩稿叧棰滆壊 */
+ --vote-heart: #ef4444;
+ --vote-heart-hover: #dc2626;
+ --vote-count: var(--text-tertiary);
+
+ /* 鎷栨嫿鐘舵€侀鑹?*/
+ --drag-over: var(--primary-light);
+ --drag-over-border: var(--primary);
+ --dragging-opacity: 0.5;
+
+ /* 琛ㄦ牸棰滆壊 */
+ --table-header-bg: var(--bg-tertiary);
+ --table-row-hover: var(--bg-hover);
+ --table-border: var(--border-secondary);
+ --table-selected: var(--primary-light);
+
+ /* 鍗$墖棰滆壊 */
+ --card-bg: var(--bg-secondary);
+ --card-border: var(--border-secondary);
+ --card-hover-border: var(--border-tertiary);
+ --card-shadow: var(--shadow-md);
+
+ /* 鎸夐挳棰滆壊 */
+ --btn-primary-bg: var(--primary);
+ --btn-primary-hover: var(--primary-hover);
+ --btn-primary-text: var(--text-primary);
+ --btn-primary-border: var(--primary-border);
+
+ --btn-secondary-bg: #3a3a3a;
+ --btn-secondary-hover: #4a4a4a;
+ --btn-secondary-text: var(--text-primary);
+ --btn-secondary-border: #555555;
+
+ --btn-success-bg: var(--success);
+ --btn-success-hover: var(--success-hover);
+ --btn-success-text: var(--text-primary);
+ --btn-success-border: var(--success-border);
+
+ --btn-warning-bg: var(--warning);
+ --btn-warning-hover: var(--warning-hover);
+ --btn-warning-text: var(--text-primary);
+ --btn-warning-border: var(--warning-border);
+
+ --btn-error-bg: var(--error);
+ --btn-error-hover: var(--error-hover);
+ --btn-error-text: var(--text-primary);
+ --btn-error-border: var(--error-border);
+
+ --btn-disabled-bg: var(--bg-tertiary);
+ --btn-disabled-text: var(--text-disabled);
+
+ /* 杈撳叆妗嗛鑹?*/
+ --input-bg: var(--bg-tertiary);
+ --input-border: var(--border-tertiary);
+ --input-border-focus: var(--primary);
+ --input-text: var(--text-primary);
+ --input-placeholder: var(--text-quaternary);
+ --input-shadow-focus: 0 0 0 3px var(--primary-light);
+
+ /* 妯℃€佹棰滆壊 */
+ --modal-backdrop: rgba(0, 0, 0, 0.7);
+ --modal-bg: var(--bg-secondary);
+ --modal-border: var(--border-secondary);
+ --modal-header-border: var(--border-secondary);
+ --modal-footer-border: var(--border-secondary);
+
+ /* 閫氱煡棰滆壊 */
+ --notification-success-bg: var(--success-light);
+ --notification-success-border: var(--success-border);
+ --notification-success-text: var(--success);
+
+ --notification-warning-bg: var(--warning-light);
+ --notification-warning-border: var(--warning-border);
+ --notification-warning-text: var(--warning);
+
+ --notification-error-bg: var(--error-light);
+ --notification-error-border: var(--error-border);
+ --notification-error-text: var(--error);
+
+ --notification-info-bg: var(--info-light);
+ --notification-info-border: var(--info-border);
+ --notification-info-text: var(--info);
+
+ /* 鍔犺浇鍔ㄧ敾棰滆壊 */
+ --spinner-primary: var(--primary);
+ --spinner-secondary: var(--bg-tertiary);
+
+ /* 婊氬姩鏉¢鑹?*/
+ --scrollbar-track: var(--bg-tertiary);
+ --scrollbar-thumb: var(--bg-quaternary);
+ --scrollbar-thumb-hover: #4a4a4a;
+}
+
+:root[data-theme='light'],
+:root[data-theme='ClassicLight'],
+:root[data-theme='ModernLight'] {
+ --bg-primary: #f8fafc;
+ --bg-secondary: #f1f5f9;
+ --bg-tertiary: #e2e8f0;
+ --bg-quaternary: #cbd5e1;
+ --bg-hover: #e2e8f0;
+
+ --text-primary: #0f172a;
+ --text-secondary: #334155;
+ --text-tertiary: #475569;
+ --text-quaternary: #64748b;
+ --text-disabled: #94a3b8;
+
+ --border-primary: #e2e8f0;
+ --border-secondary: #cbd5e1;
+ --border-tertiary: #94a3b8;
+ --border-quaternary: #64748b;
+
+ --modal-backdrop: rgba(0, 0, 0, 0.4);
+ --scrollbar-thumb-hover: #94a3b8;
+
+ --btn-secondary-bg: #f1f5f9;
+ --btn-secondary-hover: #e2e8f0;
+ --btn-secondary-text: #0f172a;
+ --btn-secondary-border: #cbd5e1;
+
+ --card-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
+}
+
+:root[data-theme='ClassicLight'] {
+ --primary: #1e40af;
+ --primary-hover: #1e3a8a;
+ --primary-light: rgba(30, 64, 175, 0.08);
+ --primary-border: rgba(30, 64, 175, 0.15);
+
+ --bg-primary: #ffffff;
+ --bg-secondary: #f5f7fa;
+ --bg-tertiary: #edf0f5;
+ --bg-quaternary: #d9dee7;
+ --bg-hover: #f0f3f8;
+
+ --success: #059669;
+ --success-hover: #047857;
+ --success-light: rgba(5, 150, 105, 0.08);
+ --success-border: rgba(5, 150, 105, 0.15);
+
+ --warning: #d97706;
+ --warning-hover: #b45309;
+ --warning-light: rgba(217, 119, 6, 0.08);
+ --warning-border: rgba(217, 119, 6, 0.15);
+
+ --error: #dc2626;
+ --error-hover: #b91c1c;
+ --error-light: rgba(220, 38, 38, 0.08);
+ --error-border: rgba(220, 38, 38, 0.15);
+
+ --info: #2563eb;
+ --info-hover: #1d4ed8;
+ --info-light: rgba(37, 99, 235, 0.08);
+ --info-border: rgba(37, 99, 235, 0.15);
+
+ --text-primary: #1a2332;
+ --text-secondary: #4a5568;
+ --text-tertiary: #6b7280;
+ --text-quaternary: #9ca3af;
+ --text-disabled: #c5cbd3;
+
+ --border-primary: #e5e7eb;
+ --border-secondary: #d1d5db;
+ --border-tertiary: #9ca3af;
+ --border-quaternary: #6b7280;
+
+ --input-bg: #ffffff;
+ --input-border: #d1d5db;
+ --input-border-focus: #1e40af;
+ --input-text: #1a2332;
+ --input-placeholder: #9ca3af;
+ --input-shadow-focus: 0 0 0 3px rgba(30, 64, 175, 0.1);
+
+ --card-bg: #ffffff;
+ --card-border: #e5e7eb;
+ --card-hover-border: #d1d5db;
+
+ --table-header-bg: #f5f7fa;
+ --table-row-hover: #f9fafb;
+ --table-border: #e5e7eb;
+ --table-selected: rgba(30, 64, 175, 0.06);
+
+ --scrollbar-track: #f1f5f9;
+ --scrollbar-thumb: #cbd5e1;
+ --scrollbar-thumb-hover: #94a3b8;
+
+ --status-pending: #ca8a04;
+ --status-pending-light: rgba(202, 138, 4, 0.08);
+ --status-approved: #059669;
+ --status-approved-light: rgba(5, 150, 105, 0.08);
+ --status-played: #6b7280;
+ --status-played-light: rgba(107, 114, 128, 0.08);
+
+ --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.04);
+ --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.06), 0 2px 4px -2px rgba(0, 0, 0, 0.04);
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.06), 0 4px 6px -4px rgba(0, 0, 0, 0.04);
+ --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.06), 0 8px 10px -6px rgba(0, 0, 0, 0.04);
+
+ --radius-sm: 3px;
+ --radius-md: 5px;
+ --radius-lg: 6px;
+ --radius-xl: 8px;
+ --radius-2xl: 10px;
+}
+
+:root[data-theme='ModernLight'] {
+ --primary: #4f46e5;
+ --primary-hover: #4338ca;
+ --primary-light: rgba(79, 70, 229, 0.1);
+ --primary-border: rgba(79, 70, 229, 0.2);
+
+ --bg-primary: #fafafa;
+ --bg-secondary: #f4f4f5;
+ --bg-tertiary: #e4e4e7;
+ --bg-quaternary: #d4d4d8;
+ --bg-hover: #eeeeef;
+
+ --success: #16a34a;
+ --success-hover: #15803d;
+ --success-light: rgba(22, 163, 74, 0.1);
+ --success-border: rgba(22, 163, 74, 0.2);
+
+ --warning: #ea580c;
+ --warning-hover: #c2410c;
+ --warning-light: rgba(234, 88, 12, 0.1);
+ --warning-border: rgba(234, 88, 12, 0.2);
+
+ --error: #e11d48;
+ --error-hover: #be123c;
+ --error-light: rgba(225, 29, 72, 0.1);
+ --error-border: rgba(225, 29, 72, 0.2);
+
+ --info: #0ea5e9;
+ --info-hover: #0284c7;
+ --info-light: rgba(14, 165, 233, 0.1);
+ --info-border: rgba(14, 165, 233, 0.2);
+
+ --text-primary: #09090b;
+ --text-secondary: #3f3f46;
+ --text-tertiary: #71717a;
+ --text-quaternary: #a1a1aa;
+ --text-disabled: #d4d4d8;
+
+ --border-primary: #e4e4e7;
+ --border-secondary: #d4d4d8;
+ --border-tertiary: #a1a1aa;
+ --border-quaternary: #71717a;
+
+ --input-bg: #ffffff;
+ --input-border: #e4e4e7;
+ --input-border-focus: #4f46e5;
+ --input-text: #09090b;
+ --input-placeholder: #a1a1aa;
+ --input-shadow-focus: 0 0 0 3px rgba(79, 70, 229, 0.12);
+
+ --card-bg: #ffffff;
+ --card-border: #e4e4e7;
+ --card-hover-border: #d4d4d8;
+
+ --table-header-bg: #f4f4f5;
+ --table-row-hover: #fafafa;
+ --table-border: #e4e4e7;
+ --table-selected: rgba(79, 70, 229, 0.08);
+
+ --scrollbar-track: #f4f4f5;
+ --scrollbar-thumb: #d4d4d8;
+ --scrollbar-thumb-hover: #a1a1aa;
+
+ --status-pending: #eab308;
+ --status-pending-light: rgba(234, 179, 8, 0.12);
+ --status-approved: #22c55e;
+ --status-approved-light: rgba(34, 197, 94, 0.12);
+ --status-played: #71717a;
+ --status-played-light: rgba(113, 113, 122, 0.12);
+
+ --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.03);
+ --shadow-md: 0 2px 8px -2px rgba(0, 0, 0, 0.05), 0 1px 3px 0 rgba(0, 0, 0, 0.03);
+ --shadow-lg: 0 8px 24px -4px rgba(0, 0, 0, 0.06), 0 2px 8px -4px rgba(0, 0, 0, 0.04);
+ --shadow-xl: 0 16px 40px -8px rgba(0, 0, 0, 0.08), 0 4px 16px -8px rgba(0, 0, 0, 0.05);
+
+ --radius-sm: 4px;
+ --radius-md: 6px;
+ --radius-lg: 8px;
+ --radius-xl: 10px;
+ --radius-2xl: 12px;
+}
+
+/* 娣辫壊涓婚鐗瑰畾璋冩暣 */
+@media (prefers-color-scheme: dark) {
+ :root {
+ /* 鍦ㄦ繁鑹叉ā寮忎笅鍙互杩涜寰皟 */
+ }
+}
+
+/* 楂樺姣斿害妯″紡鏀寔 */
+@media (prefers-contrast: high) {
+ :root {
+ --border-primary: #444444;
+ --border-secondary: #555555;
+ --border-tertiary: #666666;
+ --text-tertiary: #aaaaaa;
+ --text-quaternary: #888888;
+ }
+}
+
+/* 鍑忓皯鍔ㄧ敾妯″紡鏀寔 */
+@media (prefers-reduced-motion: reduce) {
+ :root {
+ --transition-fast: 0s;
+ --transition-normal: 0s;
+ --transition-slow: 0s;
+ }
+}
+
+/* 鍏ㄥ眬婊氬姩鏉℃牱寮?*/
+* {
+ scrollbar-width: thin;
+ scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
+}
+
+*::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+*::-webkit-scrollbar-track {
+ background: var(--scrollbar-track);
+}
+
+*::-webkit-scrollbar-thumb {
+ background: var(--scrollbar-thumb);
+ border-radius: var(--radius-md);
+}
+
+*::-webkit-scrollbar-thumb:hover {
+ background: var(--scrollbar-thumb-hover);
+}
+
+/* 鍏ㄥ眬閫夋嫨鏂囨湰鏍峰紡 */
+::selection {
+ background: var(--primary-light);
+ color: var(--text-primary);
+}
+
+/* 鍏ㄥ眬鐒︾偣鏍峰紡 */
+*:focus {
+ outline: 2px solid var(--primary);
+ outline-offset: 2px;
+}
+
+*:focus:not(:focus-visible) {
+ outline: none;
+}
+
+/* 鍏ㄥ眬绂佺敤鐘舵€佹牱寮?*/
+*:disabled {
+ cursor: not-allowed;
+ opacity: 0.6;
+}
+
+/* 鍏ㄥ眬閾炬帴鏍峰紡 */
+a {
+ color: var(--primary);
+ text-decoration: none;
+ transition: color var(--transition-normal);
+}
+
+a:hover {
+ color: var(--primary-hover);
+}
+
+/* 鍏ㄥ眬鎸夐挳閲嶇疆 */
+button {
+ border: none;
+ background: none;
+ cursor: pointer;
+ font-family: inherit;
+}
+
+* {
+ -webkit-tap-highlight-color: transparent;
+}
+
+button,
+a,
+input,
+textarea {
+ -webkit-tap-highlight-color: transparent;
+}
+
+@media (hover: none) and (pointer: coarse) {
+ *:focus {
+ outline: none !important;
+ }
+ button,
+ a {
+ -webkit-user-select: none;
+ user-select: none;
+ }
+}
+
+/* 鍏ㄥ眬杈撳叆妗嗛噸缃?*/
+input,
+textarea,
+select {
+ font-family: inherit;
+ font-size: inherit;
+}
+
+/* 鍏ㄥ眬鍥剧墖鏍峰紡 */
+img {
+ max-width: 100%;
+ height: auto;
+}
+
+/* 鍏ㄥ眬琛ㄦ牸鏍峰紡 */
+table {
+ border-collapse: collapse;
+ width: 100%;
+}
+
+/* 鍏ㄥ眬浠g爜鏍峰紡 */
+code,
+pre {
+ font-family: 'Fira Code', 'Monaco', 'Consolas', monospace;
+ background: var(--bg-tertiary);
+ border-radius: var(--radius-sm);
+}
+
+code {
+ padding: 2px 4px;
+ font-size: 0.875em;
+}
+
+pre {
+ padding: var(--spacing-lg);
+ overflow-x: auto;
+}
+
+pre code {
+ padding: 0;
+ background: none;
+}
diff --git a/old-variables2.css b/old-variables2.css
new file mode 100644
index 0000000..3fe2829
--- /dev/null
+++ b/old-variables2.css
@@ -0,0 +1,556 @@
+/* 鍏ㄥ眬CSS鍙橀噺 - 缁熶竴閰嶈壊鏂规 */
+:root {
+ /* 涓昏壊璋?*/
+ --primary: #3b82f6;
+ --primary-hover: #2563eb;
+ --primary-light: rgba(59, 130, 246, 0.1);
+ --primary-border: rgba(59, 130, 246, 0.2);
+
+ /* 鎴愬姛鑹?*/
+ --success: #10b981;
+ --success-hover: #059669;
+ --success-light: rgba(16, 185, 129, 0.1);
+ --success-border: rgba(16, 185, 129, 0.2);
+
+ /* 璀﹀憡鑹?*/
+ --warning: #f59e0b;
+ --warning-hover: #d97706;
+ --warning-light: rgba(245, 158, 11, 0.1);
+ --warning-border: rgba(245, 158, 11, 0.2);
+
+ /* 閿欒鑹?*/
+ --error: #ef4444;
+ --error-hover: #dc2626;
+ --error-light: rgba(239, 68, 68, 0.1);
+ --error-border: rgba(239, 68, 68, 0.2);
+
+ /* 淇℃伅鑹?*/
+ --info: #3b82f6;
+ --info-hover: #2563eb;
+ --info-light: rgba(59, 130, 246, 0.1);
+ --info-border: rgba(59, 130, 246, 0.2);
+
+ /* 鑳屾櫙鑹?*/
+ --bg-primary: #111111;
+ --bg-secondary: #1a1a1a;
+ --bg-tertiary: #2a2a2a;
+ --bg-quaternary: #3a3a3a;
+ --bg-hover: #1f1f1f;
+
+ /* 鏂囧瓧棰滆壊 */
+ --text-primary: #ffffff;
+ --text-secondary: #cccccc;
+ --text-tertiary: #888888;
+ --text-quaternary: #666666;
+ --text-disabled: #444444;
+
+ /* 杈规棰滆壊 */
+ --border-primary: #1f1f1f;
+ --border-secondary: #2a2a2a;
+ --border-tertiary: #3a3a3a;
+ --border-quaternary: #4a4a4a;
+
+ /* 闃村奖 */
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
+ --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
+ --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
+ --shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.1);
+
+ /* 鍦嗚 */
+ --radius-sm: 4px;
+ --radius-md: 6px;
+ --radius-lg: 8px;
+ --radius-xl: 12px;
+ --radius-2xl: 16px;
+
+ /* 闂磋窛 */
+ --spacing-xs: 4px;
+ --spacing-sm: 8px;
+ --spacing-md: 12px;
+ --spacing-lg: 16px;
+ --spacing-xl: 20px;
+ --spacing-2xl: 24px;
+ --spacing-3xl: 32px;
+
+ /* 瀛椾綋澶у皬 */
+ --text-xs: 12px;
+ --text-sm: 14px;
+ --text-base: 16px;
+ --text-lg: 18px;
+ --text-xl: 20px;
+ --text-2xl: 24px;
+ --text-3xl: 30px;
+
+ /* 瀛椾綋绮楃粏 */
+ --font-normal: 400;
+ --font-medium: 500;
+ --font-semibold: 600;
+ --font-bold: 700;
+
+ /* 杩囨浮鍔ㄧ敾 */
+ --transition-fast: 0.15s ease;
+ --transition-normal: 0.2s ease;
+ --transition-slow: 0.3s ease;
+
+ /* Z-index灞傜骇 */
+ --z-dropdown: 100;
+ --z-sticky: 200;
+ --z-fixed: 300;
+ --z-modal-backdrop: 400;
+ --z-modal: 500;
+ --z-popover: 600;
+ --z-tooltip: 700;
+ --z-toast: 800;
+
+ /* 鐗规畩鐘舵€侀鑹?*/
+ --status-pending: #fbbf24;
+ --status-pending-light: rgba(251, 191, 36, 0.1);
+ --status-approved: #10b981;
+ --status-approved-light: rgba(16, 185, 129, 0.1);
+ --status-played: #6b7280;
+ --status-played-light: rgba(107, 114, 128, 0.1);
+
+ /* 鎶曠エ鐩稿叧棰滆壊 */
+ --vote-heart: #ef4444;
+ --vote-heart-hover: #dc2626;
+ --vote-count: var(--text-tertiary);
+
+ /* 鎷栨嫿鐘舵€侀鑹?*/
+ --drag-over: var(--primary-light);
+ --drag-over-border: var(--primary);
+ --dragging-opacity: 0.5;
+
+ /* 琛ㄦ牸棰滆壊 */
+ --table-header-bg: var(--bg-tertiary);
+ --table-row-hover: var(--bg-hover);
+ --table-border: var(--border-secondary);
+ --table-selected: var(--primary-light);
+
+ /* 鍗$墖棰滆壊 */
+ --card-bg: var(--bg-secondary);
+ --card-border: var(--border-secondary);
+ --card-hover-border: var(--border-tertiary);
+ --card-shadow: var(--shadow-md);
+
+ /* 鎸夐挳棰滆壊 */
+ --btn-primary-bg: var(--primary);
+ --btn-primary-hover: var(--primary-hover);
+ --btn-primary-text: var(--text-primary);
+ --btn-primary-border: var(--primary-border);
+
+ --btn-secondary-bg: #3a3a3a;
+ --btn-secondary-hover: #4a4a4a;
+ --btn-secondary-text: var(--text-primary);
+ --btn-secondary-border: #555555;
+
+ --btn-success-bg: var(--success);
+ --btn-success-hover: var(--success-hover);
+ --btn-success-text: var(--text-primary);
+ --btn-success-border: var(--success-border);
+
+ --btn-warning-bg: var(--warning);
+ --btn-warning-hover: var(--warning-hover);
+ --btn-warning-text: var(--text-primary);
+ --btn-warning-border: var(--warning-border);
+
+ --btn-error-bg: var(--error);
+ --btn-error-hover: var(--error-hover);
+ --btn-error-text: var(--text-primary);
+ --btn-error-border: var(--error-border);
+
+ --btn-disabled-bg: var(--bg-tertiary);
+ --btn-disabled-text: var(--text-disabled);
+
+ /* 杈撳叆妗嗛鑹?*/
+ --input-bg: var(--bg-tertiary);
+ --input-border: var(--border-tertiary);
+ --input-border-focus: var(--primary);
+ --input-text: var(--text-primary);
+ --input-placeholder: var(--text-quaternary);
+ --input-shadow-focus: 0 0 0 3px var(--primary-light);
+
+ /* 妯℃€佹棰滆壊 */
+ --modal-backdrop: rgba(0, 0, 0, 0.7);
+ --modal-bg: var(--bg-secondary);
+ --modal-border: var(--border-secondary);
+ --modal-header-border: var(--border-secondary);
+ --modal-footer-border: var(--border-secondary);
+
+ /* 閫氱煡棰滆壊 */
+ --notification-success-bg: var(--success-light);
+ --notification-success-border: var(--success-border);
+ --notification-success-text: var(--success);
+
+ --notification-warning-bg: var(--warning-light);
+ --notification-warning-border: var(--warning-border);
+ --notification-warning-text: var(--warning);
+
+ --notification-error-bg: var(--error-light);
+ --notification-error-border: var(--error-border);
+ --notification-error-text: var(--error);
+
+ --notification-info-bg: var(--info-light);
+ --notification-info-border: var(--info-border);
+ --notification-info-text: var(--info);
+
+ /* 鍔犺浇鍔ㄧ敾棰滆壊 */
+ --spinner-primary: var(--primary);
+ --spinner-secondary: var(--bg-tertiary);
+
+ /* 婊氬姩鏉¢鑹?*/
+ --scrollbar-track: var(--bg-tertiary);
+ --scrollbar-thumb: var(--bg-quaternary);
+ --scrollbar-thumb-hover: #4a4a4a;
+}
+
+:root[data-theme='light'],
+:root[data-theme='ClassicLight'],
+:root[data-theme='ModernLight'] {
+ --bg-primary: #f8fafc;
+ --bg-secondary: #f1f5f9;
+ --bg-tertiary: #e2e8f0;
+ --bg-quaternary: #cbd5e1;
+ --bg-hover: #e2e8f0;
+
+ --text-primary: #0f172a;
+ --text-secondary: #334155;
+ --text-tertiary: #475569;
+ --text-quaternary: #64748b;
+ --text-disabled: #94a3b8;
+
+ --border-primary: #e2e8f0;
+ --border-secondary: #cbd5e1;
+ --border-tertiary: #94a3b8;
+ --border-quaternary: #64748b;
+
+ --modal-backdrop: rgba(0, 0, 0, 0.4);
+ --scrollbar-thumb-hover: #94a3b8;
+
+ --btn-secondary-bg: #f1f5f9;
+ --btn-secondary-hover: #e2e8f0;
+ --btn-secondary-text: #0f172a;
+ --btn-secondary-border: #cbd5e1;
+
+ --card-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
+}
+
+:root[data-theme='ClassicLight'] {
+ --primary: #1e40af;
+ --primary-hover: #1e3a8a;
+ --primary-light: rgba(30, 64, 175, 0.08);
+ --primary-border: rgba(30, 64, 175, 0.15);
+
+ --bg-primary: #ffffff;
+ --bg-secondary: #f5f7fa;
+ --bg-tertiary: #edf0f5;
+ --bg-quaternary: #d9dee7;
+ --bg-hover: #f0f3f8;
+
+ --success: #059669;
+ --success-hover: #047857;
+ --success-light: rgba(5, 150, 105, 0.08);
+ --success-border: rgba(5, 150, 105, 0.15);
+
+ --warning: #d97706;
+ --warning-hover: #b45309;
+ --warning-light: rgba(217, 119, 6, 0.08);
+ --warning-border: rgba(217, 119, 6, 0.15);
+
+ --error: #dc2626;
+ --error-hover: #b91c1c;
+ --error-light: rgba(220, 38, 38, 0.08);
+ --error-border: rgba(220, 38, 38, 0.15);
+
+ --info: #2563eb;
+ --info-hover: #1d4ed8;
+ --info-light: rgba(37, 99, 235, 0.08);
+ --info-border: rgba(37, 99, 235, 0.15);
+
+ --text-primary: #1a2332;
+ --text-secondary: #4a5568;
+ --text-tertiary: #6b7280;
+ --text-quaternary: #9ca3af;
+ --text-disabled: #c5cbd3;
+
+ --border-primary: #e5e7eb;
+ --border-secondary: #d1d5db;
+ --border-tertiary: #9ca3af;
+ --border-quaternary: #6b7280;
+
+ --input-bg: #ffffff;
+ --input-border: #d1d5db;
+ --input-border-focus: #1e40af;
+ --input-text: #1a2332;
+ --input-placeholder: #9ca3af;
+ --input-shadow-focus: 0 0 0 3px rgba(30, 64, 175, 0.1);
+
+ --card-bg: #ffffff;
+ --card-border: #e5e7eb;
+ --card-hover-border: #d1d5db;
+
+ --table-header-bg: #f5f7fa;
+ --table-row-hover: #f9fafb;
+ --table-border: #e5e7eb;
+ --table-selected: rgba(30, 64, 175, 0.06);
+
+ --scrollbar-track: #f1f5f9;
+ --scrollbar-thumb: #cbd5e1;
+ --scrollbar-thumb-hover: #94a3b8;
+
+ --status-pending: #ca8a04;
+ --status-pending-light: rgba(202, 138, 4, 0.08);
+ --status-approved: #059669;
+ --status-approved-light: rgba(5, 150, 105, 0.08);
+ --status-played: #6b7280;
+ --status-played-light: rgba(107, 114, 128, 0.08);
+
+ --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.04);
+ --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.06), 0 2px 4px -2px rgba(0, 0, 0, 0.04);
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.06), 0 4px 6px -4px rgba(0, 0, 0, 0.04);
+ --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.06), 0 8px 10px -6px rgba(0, 0, 0, 0.04);
+
+ --radius-sm: 3px;
+ --radius-md: 5px;
+ --radius-lg: 6px;
+ --radius-xl: 8px;
+ --radius-2xl: 10px;
+}
+
+:root[data-theme='ModernLight'] {
+ --primary: #4f46e5;
+ --primary-hover: #4338ca;
+ --primary-light: rgba(79, 70, 229, 0.1);
+ --primary-border: rgba(79, 70, 229, 0.2);
+
+ --bg-primary: #fafafa;
+ --bg-secondary: #f4f4f5;
+ --bg-tertiary: #e4e4e7;
+ --bg-quaternary: #d4d4d8;
+ --bg-hover: #eeeeef;
+
+ --success: #16a34a;
+ --success-hover: #15803d;
+ --success-light: rgba(22, 163, 74, 0.1);
+ --success-border: rgba(22, 163, 74, 0.2);
+
+ --warning: #ea580c;
+ --warning-hover: #c2410c;
+ --warning-light: rgba(234, 88, 12, 0.1);
+ --warning-border: rgba(234, 88, 12, 0.2);
+
+ --error: #e11d48;
+ --error-hover: #be123c;
+ --error-light: rgba(225, 29, 72, 0.1);
+ --error-border: rgba(225, 29, 72, 0.2);
+
+ --info: #0ea5e9;
+ --info-hover: #0284c7;
+ --info-light: rgba(14, 165, 233, 0.1);
+ --info-border: rgba(14, 165, 233, 0.2);
+
+ --text-primary: #09090b;
+ --text-secondary: #3f3f46;
+ --text-tertiary: #71717a;
+ --text-quaternary: #a1a1aa;
+ --text-disabled: #d4d4d8;
+
+ --border-primary: #e4e4e7;
+ --border-secondary: #d4d4d8;
+ --border-tertiary: #a1a1aa;
+ --border-quaternary: #71717a;
+
+ --input-bg: #ffffff;
+ --input-border: #e4e4e7;
+ --input-border-focus: #4f46e5;
+ --input-text: #09090b;
+ --input-placeholder: #a1a1aa;
+ --input-shadow-focus: 0 0 0 3px rgba(79, 70, 229, 0.12);
+
+ --card-bg: #ffffff;
+ --card-border: #e4e4e7;
+ --card-hover-border: #d4d4d8;
+
+ --table-header-bg: #f4f4f5;
+ --table-row-hover: #fafafa;
+ --table-border: #e4e4e7;
+ --table-selected: rgba(79, 70, 229, 0.08);
+
+ --scrollbar-track: #f4f4f5;
+ --scrollbar-thumb: #d4d4d8;
+ --scrollbar-thumb-hover: #a1a1aa;
+
+ --status-pending: #eab308;
+ --status-pending-light: rgba(234, 179, 8, 0.12);
+ --status-approved: #22c55e;
+ --status-approved-light: rgba(34, 197, 94, 0.12);
+ --status-played: #71717a;
+ --status-played-light: rgba(113, 113, 122, 0.12);
+
+ --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.03);
+ --shadow-md: 0 2px 8px -2px rgba(0, 0, 0, 0.05), 0 1px 3px 0 rgba(0, 0, 0, 0.03);
+ --shadow-lg: 0 8px 24px -4px rgba(0, 0, 0, 0.06), 0 2px 8px -4px rgba(0, 0, 0, 0.04);
+ --shadow-xl: 0 16px 40px -8px rgba(0, 0, 0, 0.08), 0 4px 16px -8px rgba(0, 0, 0, 0.05);
+
+ --radius-sm: 4px;
+ --radius-md: 6px;
+ --radius-lg: 8px;
+ --radius-xl: 10px;
+ --radius-2xl: 12px;
+}
+
+/* 娣辫壊涓婚鐗瑰畾璋冩暣 */
+@media (prefers-color-scheme: dark) {
+ :root {
+ /* 鍦ㄦ繁鑹叉ā寮忎笅鍙互杩涜寰皟 */
+ }
+}
+
+/* 楂樺姣斿害妯″紡鏀寔 */
+@media (prefers-contrast: high) {
+ :root {
+ --border-primary: #444444;
+ --border-secondary: #555555;
+ --border-tertiary: #666666;
+ --text-tertiary: #aaaaaa;
+ --text-quaternary: #888888;
+ }
+}
+
+/* 鍑忓皯鍔ㄧ敾妯″紡鏀寔 */
+@media (prefers-reduced-motion: reduce) {
+ :root {
+ --transition-fast: 0s;
+ --transition-normal: 0s;
+ --transition-slow: 0s;
+ }
+}
+
+/* 鍏ㄥ眬婊氬姩鏉℃牱寮?*/
+* {
+ scrollbar-width: thin;
+ scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
+}
+
+*::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+*::-webkit-scrollbar-track {
+ background: var(--scrollbar-track);
+}
+
+*::-webkit-scrollbar-thumb {
+ background: var(--scrollbar-thumb);
+ border-radius: var(--radius-md);
+}
+
+*::-webkit-scrollbar-thumb:hover {
+ background: var(--scrollbar-thumb-hover);
+}
+
+/* 鍏ㄥ眬閫夋嫨鏂囨湰鏍峰紡 */
+::selection {
+ background: var(--primary-light);
+ color: var(--text-primary);
+}
+
+/* 鍏ㄥ眬鐒︾偣鏍峰紡 */
+*:focus {
+ outline: 2px solid var(--primary);
+ outline-offset: 2px;
+}
+
+*:focus:not(:focus-visible) {
+ outline: none;
+}
+
+/* 鍏ㄥ眬绂佺敤鐘舵€佹牱寮?*/
+*:disabled {
+ cursor: not-allowed;
+ opacity: 0.6;
+}
+
+/* 鍏ㄥ眬閾炬帴鏍峰紡 */
+a {
+ color: var(--primary);
+ text-decoration: none;
+ transition: color var(--transition-normal);
+}
+
+a:hover {
+ color: var(--primary-hover);
+}
+
+/* 鍏ㄥ眬鎸夐挳閲嶇疆 */
+button {
+ border: none;
+ background: none;
+ cursor: pointer;
+ font-family: inherit;
+}
+
+* {
+ -webkit-tap-highlight-color: transparent;
+}
+
+button,
+a,
+input,
+textarea {
+ -webkit-tap-highlight-color: transparent;
+}
+
+@media (hover: none) and (pointer: coarse) {
+ *:focus {
+ outline: none !important;
+ }
+ button,
+ a {
+ -webkit-user-select: none;
+ user-select: none;
+ }
+}
+
+/* 鍏ㄥ眬杈撳叆妗嗛噸缃?*/
+input,
+textarea,
+select {
+ font-family: inherit;
+ font-size: inherit;
+}
+
+/* 鍏ㄥ眬鍥剧墖鏍峰紡 */
+img {
+ max-width: 100%;
+ height: auto;
+}
+
+/* 鍏ㄥ眬琛ㄦ牸鏍峰紡 */
+table {
+ border-collapse: collapse;
+ width: 100%;
+}
+
+/* 鍏ㄥ眬浠g爜鏍峰紡 */
+code,
+pre {
+ font-family: 'Fira Code', 'Monaco', 'Consolas', monospace;
+ background: var(--bg-tertiary);
+ border-radius: var(--radius-sm);
+}
+
+code {
+ padding: 2px 4px;
+ font-size: 0.875em;
+}
+
+pre {
+ padding: var(--spacing-lg);
+ overflow-x: auto;
+}
+
+pre code {
+ padding: 0;
+ background: none;
+}