Files
BYW-VoiceHub/patches/0056-chore-remove-temporary-files.patch
T

5841 lines
168 KiB
Diff
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
From d8060147ed53bc624a142520ccbd5d5c0b80d9a6 Mon Sep 17 00:00:00 2001
From: HenryDu8133 <813367384@qq.com>
Date: Sat, 12 Sep 2026 15:57:27 +0800
Subject: [PATCH 56/58] chore: remove temporary files
---
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 -------
6 files changed, 5781 deletions(-)
delete mode 100644 fix-colors.mjs
delete mode 100644 old-index.vue
delete mode 100644 old-nuxt.config.ts
delete mode 100644 old-nuxt2.config.ts
delete mode 100644 old-variables.css
delete mode 100644 old-variables2.css
diff --git a/fix-colors.mjs b/fix-colors.mjs
deleted file mode 100644
index 003e18b..0000000
--- a/fix-colors.mjs
+++ /dev/null
@@ -1,128 +0,0 @@
-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 <style> blocks.
- const styleBlocks = content.match(/<style[^>]*>[\s\S]*?<\/style>/gi);
- if (styleBlocks) {
- for (const block of styleBlocks) {
- let newBlock = block;
-
- // Match CSS properties like background: #1a1a1a;
- newBlock = newBlock.replace(/(background(-color)?|color|border(-color)?|border-(top|right|bottom|left)(-color)?|fill|stroke):\s*([^;}]*?)(#[0-9a-fA-F]{3,6})/gi, (match, p1, p2, p3, p4, p5, p6, hex) => {
- const hexLower = hex.toLowerCase();
-
- // Don't replace if it's already inside a var()
- if (p6.includes('var(')) return match;
-
- let replacement = hex;
-
- if (grayscales[hexLower]) {
- // Determine if it's a background, border, or text color
- if (p1.startsWith('border')) {
- replacement = `var(--border-secondary, ${hexLower})`;
- } else if (p1.startsWith('background')) {
- if (hexLower === '#ffffff' || hexLower === '#fff') {
- replacement = `var(--card-bg, ${hexLower})`; // White background is usually a card
- } else {
- replacement = grayscales[hexLower].replace('text', 'bg'); // Force bg variables for background
- }
- } else if (p1 === 'color') {
- if (['#000000', '#000', '#111111', '#111', '#1a1a1a', '#222222', '#222', '#2a2a2a'].includes(hexLower)) {
- replacement = `var(--text-primary, ${hexLower})`;
- } else {
- replacement = grayscales[hexLower].replace('bg', 'text'); // Force text variables for color
- }
- } else {
- replacement = grayscales[hexLower];
- }
- } else {
- // Heuristic for unknown grayscales or dark colors
- // Check if it's a dark shade (e.g. #040e15, #242f38)
- if (p1.startsWith('background') && match.match(/#0[0-9a-f]{5}/i)) {
- replacement = `var(--bg-secondary, ${hexLower})`;
- } else if (p1.startsWith('background') && match.match(/#1[0-9a-f]{5}/i)) {
- replacement = `var(--bg-secondary, ${hexLower})`;
- } else if (p1.startsWith('background') && match.match(/#2[0-9a-f]{5}/i)) {
- replacement = `var(--bg-tertiary, ${hexLower})`;
- } else if (p1.startsWith('border') && match.match(/#[1-4][0-9a-f]{5}/i)) {
- replacement = `var(--border-secondary, ${hexLower})`;
- }
- }
-
- return `${p1}: ${p6}${replacement}`;
- });
-
- content = content.replace(block, newBlock);
- }
- }
-
- if (content !== original) {
- await fs.writeFile(file, content, 'utf-8');
- console.log('Fixed', file);
- }
- }
-}
-
-fixColors().catch(console.error);
diff --git a/old-index.vue b/old-index.vue
deleted file mode 100644
index 778186b..0000000
--- a/old-index.vue
+++ /dev/null
@@ -1,3661 +0,0 @@
-<template>
- <div class="home">
- <div class="ellipse-effect" />
- <div class="main-content">
- <div class="top-bar">
- <div class="logo-section">
- <NuxtLink class="logo-link" to="/">
- <img
- alt="VoiceHub Logo"
- class="logo-image"
- :src="logo"
- width="90"
- height="90"
- decoding="async"
- fetchpriority="high"
- >
- </NuxtLink>
- <!-- 妯嚎鍜屽鏍ogo -->
- <div v-if="schoolLogoHomeUrl && schoolLogoHomeUrl.trim()" class="logo-divider-container">
- <div class="logo-divider" />
- <img
- :src="proxiedSchoolLogoUrl"
- alt="瀛︽牎Logo"
- class="school-logo"
- width="200"
- height="80"
- decoding="async"
- fetchpriority="high"
- >
- </div>
- </div>
-
- <!-- 鐢ㄦ埛淇℃伅鍖哄煙 -->
- <div class="user-section">
- <ClientOnly>
- <!-- 涓婚鍒囨崲鎸夐挳 -->
- <button
- class="utility-btn theme-toggle-btn"
- type="button"
- aria-label="涓婚"
- title="鍒囨崲涓婚"
- @click="handleToggleTheme"
- >
- <Icon :name="isDark ? 'sun' : 'moon'" :size="18" />
- <span class="utility-btn-label">涓婚</span>
- </button>
-
- <!-- 璇█鍒囨崲鎸夐挳锛堝崰浣嶆寜閽紝褰撳墠鍗曡绉嶏級 -->
- <button
- class="utility-btn lang-toggle-btn"
- type="button"
- aria-label="璇█"
- title="璇█"
- @click="handleLanguageClick"
- >
- <Icon name="globe" :size="18" />
- <span class="utility-btn-label">璇█</span>
- </button>
-
- <div v-if="isClientAuthenticated" class="user-info">
- <div class="user-details-desktop">
- <span class="user-name">{{ user?.name || '鐢ㄦ埛' }}</span>
- <span v-if="isAdmin" class="user-badge admin">{{ roleName }}</span>
- <span v-else class="user-badge">{{ userClassInfo }}</span>
- </div>
-
- <NuxtLink v-if="isAdmin" class="enter-admin-btn" to="/dashboard">
- <Icon name="settings" :size="16" />
- <span>杩涘叆鍚庡彴</span>
- </NuxtLink>
-
- <div class="user-avatar-wrapper" @click="toggleUserActions">
- <img
- v-if="user?.avatar && !avatarError"
- :src="user.avatar"
- class="user-avatar"
- @error="avatarError = true"
- >
- <div v-else class="user-avatar-placeholder">
- {{ user?.name?.[0] || 'U' }}
- </div>
- </div>
-
- <Transition name="dropdown-fade">
- <div v-if="showUserActions" class="user-actions-dropdown">
- <NuxtLink class="action-item" to="/account">
- <Icon name="user" :size="16" />
- <span>璐﹀彿绠$悊</span>
- </NuxtLink>
- <NuxtLink v-if="isAdmin" class="action-item" to="/dashboard">
- <Icon name="settings" :size="16" />
- <span>绠$悊鍚庡彴</span>
- </NuxtLink>
- <button class="action-item logout" @click="handleLogout">
- <Icon name="logout" :size="16" />
- <span>閫€鍑虹櫥褰?/span>
- </button>
- </div>
- </Transition>
- </div>
-
- <div v-else class="login-options">
- <NuxtLink class="login-btn" to="/login">
- <Icon name="user" :size="16" />
- <span>鐧诲綍/娉ㄥ唽</span>
- </NuxtLink>
- </div>
- </ClientOnly>
- </div>
- </div>
-
- <div v-if="siteTitle" class="site-title">
- <div class="title-container">
- <h2 class="main-title">{{ siteTitle }}</h2>
- <div class="title-divider" />
- <span class="sub-title">VoiceHub 鏍″洯骞挎挱绯荤粺</span>
- </div>
- </div>
-
- <!-- 涓棿涓讳綋鍐呭鍖哄煙 -->
- <div class="content-area">
- <!-- 閫夐」鍗″尯鍩?-->
- <div class="tabs-row">
- <div
- :class="{ active: activeTab === 'schedule' }"
- class="section-tab schedule-tab"
- @click="handleTabClick('schedule')"
- >
- <Icon class="tab-icon" name="calendar" :size="20" />
- <span class="tab-text">鎾嚭鎺掓湡</span>
- </div>
- <div
- :class="{ active: activeTab === 'songs' }"
- class="section-tab songs-tab"
- @click="handleTabClick('songs')"
- >
- <Icon class="tab-icon" name="music" :size="20" />
- <span class="tab-text">姝屾洸鍒楄〃</span>
- </div>
- <div
- :class="{ active: activeTab === 'request' }"
- class="section-tab request-tab"
- @click="handleTabClick('request')"
- >
- <Icon class="tab-icon" name="search" :size="20" />
- <span class="tab-text">鎶曠姝屾洸</span>
- </div>
- <ClientOnly>
- <div
- ref="notificationTabRef"
- :class="{ active: activeTab === 'notification', disabled: !isClientAuthenticated }"
- class="section-tab"
- data-tab="notification"
- @click="isClientAuthenticated ? handleTabClick('notification') : showLoginNotice()"
- >
- <div class="icon-wrapper">
- <Icon class="tab-icon" name="message-circle" :size="20" />
- <span
- v-if="isClientAuthenticated && hasUnreadNotifications"
- class="notification-badge-tab"
- />
- </div>
- <span class="tab-text">
- 娑堟伅
- <span
- v-if="isClientAuthenticated && hasUnreadNotifications"
- class="notification-badge-desktop"
- />
- </span>
- </div>
- <template #fallback>
- <div class="section-tab disabled" data-tab="notification">
- <Icon class="tab-icon" name="message-circle" :size="20" />
- <span class="tab-text">娑堟伅</span>
- </div>
- </template>
- </ClientOnly>
- </div>
-
- <!-- 鍐呭鍖哄煙 -->
- <div class="tab-content-container">
- <ClientOnly>
- <!-- 鎾嚭鎺掓湡鍐呭 -->
- <div v-if="activeTab === 'schedule'" key="schedule" class="tab-pane schedule-tab-pane">
- <div class="full-width">
- <SongsScheduleList
- :error="error"
- :loading="loading"
- :schedules="publicSchedules"
- @semester-change="handleSemesterChange"
- />
- </div>
-
- <!-- 鐢ㄦ埛浣跨敤鎸囧崡缁勪欢 -->
- <UserTutorial />
- </div>
-
- <!-- 姝屾洸鍒楄〃鍐呭 -->
- <div v-else-if="activeTab === 'songs'" key="songs" class="tab-pane">
- <div class="song-list-container">
- <SongsSongList
- :error="error"
- :is-admin="isAdmin"
- :loading="loading"
- :songs="filteredSongs"
- @refresh="refreshSongs"
- @vote="handleVote"
- @withdraw="handleWithdraw"
- @cancel-replay="handleCancelReplay"
- @request-replay="handleRequestReplay"
- @semester-change="handleSemesterChange"
- />
- </div>
- </div>
-
- <!-- 鎶曠姝屾洸鍐呭 -->
- <div v-else-if="activeTab === 'request'" key="request" class="tab-pane request-pane">
- <SongsRequestForm
- ref="requestFormRef"
- :loading="loading"
- @request="handleRequest"
- @vote="handleVote"
- />
- </div>
-
- <!-- 閫氱煡鍐呭 -->
- <div
- v-else-if="activeTab === 'notification'"
- key="notification"
- class="tab-pane notification-pane"
- >
- <div v-if="!isClientAuthenticated" class="login-required-container">
- <div class="login-required-content">
- <Lock class="login-icon" :size="48" />
- <h3>闇€瑕佺櫥褰?/h3>
- <p>鎮ㄩ渶瑕佺櫥褰曟墠鑳芥煡鐪嬮€氱煡</p>
- <button class="login-button" @click="navigateToLogin">绔嬪嵆鐧诲綍</button>
- </div>
- </div>
- <div v-else class="notification-container">
- <!-- 鏍囬鍜岃缃寜閽?-->
- <div class="notification-header">
- <h2 class="notification-title">閫氱煡涓績</h2>
- <button class="settings-icon" @click="toggleNotificationSettings">
- <svg
- fill="none"
- height="20"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"
- viewBox="0 0 24 24"
- width="20"
- xmlns="http://www.w3.org/2000/svg"
- >
- <circle cx="12" cy="12" r="3" />
- <path
- d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"
- />
- </svg>
- </button>
- </div>
-
- <!-- 閫氱煡鍒楄〃 -->
- <div class="notification-list">
- <div v-if="notificationsLoading" class="loading-indicator">
- <div class="loading-spinner" />
- <span>鍔犺浇涓?..</span>
- </div>
-
- <div v-else-if="userNotifications.length === 0" class="empty-notification">
- <div class="empty-icon">
- <Icon :size="48" color="#6b7280" name="bell" />
- </div>
- <p>鏆傛棤閫氱煡</p>
- </div>
-
- <Transition mode="out-in" name="notification-list-fade">
- <div
- v-if="userNotifications.length > 0"
- :key="notificationsService.currentPage.value"
- class="notification-items"
- >
- <div
- v-for="(notification, index) in userNotifications"
- :key="notification.id"
- :class="{ unread: !notification.read }"
- :style="{ '--animation-delay': index * 0.1 + 's' }"
- class="notification-card"
- @click="viewNotification(notification)"
- >
- <div class="notification-card-header">
- <div class="notification-icon-type">
- <Icon
- v-if="notification.type === 'SONG_SELECTED'"
- :size="20"
- color="#4f46e5"
- name="check"
- />
- <Icon
- v-else-if="notification.type === 'SONG_PLAYED'"
- :size="20"
- color="#10b981"
- name="play"
- />
- <Icon
- v-else-if="notification.type === 'SONG_VOTED'"
- :size="20"
- color="#f59e0b"
- name="thumbs-up"
- />
- <Icon
- v-else-if="notification.type === 'SONG_REJECTED'"
- :size="20"
- color="#ef4444"
- name="x-circle"
- />
- <Icon
- v-else-if="notification.type === 'COLLABORATION_INVITE'"
- :size="20"
- color="#0B5AFE"
- name="users"
- />
- <Icon
- v-else-if="notification.type === 'COLLABORATION_RESPONSE'"
- :size="20"
- color="#8b5cf6"
- name="message-circle"
- />
- <Icon v-else :size="20" color="#6b7280" name="bell" />
- </div>
- <div class="notification-title-row">
- <div class="notification-title">
- <span v-if="notification.type === 'SONG_SELECTED'">姝屾洸宸查€変腑</span>
- <span v-else-if="notification.type === 'SONG_PLAYED'"
- >姝屾洸宸叉挱鏀?/span
- >
- <span v-else-if="notification.type === 'SONG_VOTED'">鏀跺埌鏂版姇绁?/span>
- <span v-else-if="notification.type === 'SONG_REJECTED'"
- >姝屾洸琚┏鍥?/span
- >
- <span v-else-if="notification.type === 'COLLABORATION_INVITE'">
- 鑱斿悎鎶曠閭€璇? <span
- v-if="notification.handled"
- :class="[
- 'status-tag',
- notification.status === 'ACCEPTED'
- ? 'accepted'
- : notification.status === 'INVALID'
- ? 'invalid'
- : 'rejected'
- ]"
- >
- {{
- notification.status === 'ACCEPTED'
- ? '- 宸叉帴鍙?
- : notification.status === 'INVALID'
- ? '- 宸插け鏁?
- : '- 宸叉嫆缁?
- }}
- </span>
- </span>
- <span v-else-if="notification.type === 'COLLABORATION_RESPONSE'"
- >鑱斿悎鎶曠鍥炲</span
- >
- <span v-else>绯荤粺閫氱煡</span>
- <span v-if="!notification.read" class="unread-indicator" />
- </div>
- <div class="notification-time">
- {{ formatNotificationTime(notification.createdAt) }}
- </div>
- </div>
- </div>
- <div class="notification-card-body">
- <div class="notification-text">{{ notification.message }}</div>
-
- <!-- 鑱斿悎鎶曠閭€璇锋搷浣滄寜閽?-->
- <div
- v-if="
- notification.type === 'COLLABORATION_INVITE' && !notification.handled
- "
- class="invite-actions"
- >
- <button
- :disabled="notification.processing"
- class="action-button accept-btn"
- @click.stop="handleCollaborationReply(notification, true)"
- >
- {{ notification.processing ? '澶勭悊涓?..' : '鎺ュ彈閭€璇? }}
- </button>
- <button
- :disabled="notification.processing"
- class="action-button reject-btn"
- @click.stop="handleCollaborationReply(notification, false)"
- >
- 鎷掔粷
- </button>
- </div>
- </div>
- <div class="notification-card-actions">
- <button
- class="action-button delete"
- title="鍒犻櫎"
- @click.stop="deleteNotification(notification.id)"
- >
- <svg
- fill="none"
- height="16"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"
- viewBox="0 0 24 24"
- width="16"
- xmlns="http://www.w3.org/2000/svg"
- >
- <polyline points="3 6 5 6 21 6" />
- <path
- d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"
- />
- </svg>
- <span>鍒犻櫎</span>
- </button>
- </div>
- </div>
- </div>
- </Transition>
- </div>
-
- <!-- 鍒嗛〉鎺т欢 -->
- <div
- v-if="notificationsService.totalCount.value > 0"
- class="notification-pagination"
- >
- <div class="pagination-info">
- <span class="pagination-text">
- 鍏?{{ notificationsService.totalCount.value }} 鏉¢€氱煡锛?绗? {{ notificationsService.currentPage.value }} /
- {{ notificationsService.totalPages.value }} 椤? </span>
- </div>
-
- <div class="pagination-controls">
- <!-- 姣忛〉鏄剧ず鏁伴噺閫夋嫨鍣?-->
- <div class="page-size-selector">
- <label for="pageSize">姣忛〉鏄剧ず锛?/label>
- <CustomSelect
- id="pageSize"
- :model-value="notificationsService.pageSize.value"
- :options="pageSizeOptions"
- class="page-size-custom-select"
- @update:model-value="handlePageSizeChange"
- />
- </div>
-
- <!-- 椤电爜瀵艰埅 -->
- <div class="page-navigation">
- <button
- :disabled="
- !notificationsService.hasPrevPage.value ||
- notificationsService.isPaginationLoading.value
- "
- class="page-nav-button"
- title="涓婁竴椤?
- @click="notificationsService.prevPage()"
- >
- <svg
- fill="none"
- height="16"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"
- viewBox="0 0 24 24"
- width="16"
- xmlns="http://www.w3.org/2000/svg"
- >
- <polyline points="15 18 9 12 15 6" />
- </svg>
- </button>
-
- <!-- 椤电爜鎸夐挳 -->
- <div class="page-numbers">
- <template v-for="page in getVisiblePages()" :key="page">
- <button
- v-if="page !== '...'"
- :class="[
- 'page-number-button',
- { active: page === notificationsService.currentPage.value }
- ]"
- :disabled="notificationsService.isPaginationLoading.value"
- @click="notificationsService.goToPage(page)"
- >
- {{ page }}
- </button>
- <span v-else class="page-ellipsis">...</span>
- </template>
- </div>
-
- <button
- :disabled="
- !notificationsService.hasNextPage.value ||
- notificationsService.isPaginationLoading.value
- "
- class="page-nav-button"
- title="涓嬩竴椤?
- @click="notificationsService.nextPage()"
- >
- <svg
- fill="none"
- height="16"
- stroke="currentColor"
- stroke-linecap="round"
- stroke-linejoin="round"
- stroke-width="2"
- viewBox="0 0 24 24"
- width="16"
- xmlns="http://www.w3.org/2000/svg"
- >
- <polyline points="9 18 15 12 9 6" />
- </svg>
- </button>
- </div>
- </div>
-
- <!-- 鍒嗛〉鍔犺浇鐘舵€?-->
- <div
- v-if="notificationsService.isPaginationLoading.value"
- class="pagination-loading"
- >
- <div class="loading-spinner" />
- <span>鍔犺浇涓?..</span>
- </div>
- </div>
-
- <!-- 搴曢儴鎿嶄綔鎸夐挳 -->
- <div v-if="userNotifications.length > 0" class="notification-actions-bar">
- <button
- :class="{ disabled: !hasUnreadNotifications }"
- :disabled="!hasUnreadNotifications"
- class="action-button-large"
- @click="markAllNotificationsAsRead"
- >
- 鍏ㄩ儴鏍囪涓哄凡璇? </button>
- <button class="action-button-large danger" @click="clearAllNotifications">
- 娓呯┖鎵€鏈夋秷鎭? </button>
- </div>
-
- <!-- 纭瀵硅瘽妗?-->
- <ConfirmDialog
- v-model:show="showConfirmDialog"
- :cancel-text="confirmDialogConfig.cancelText"
- :confirm-text="confirmDialogConfig.confirmText"
- :message="confirmDialogConfig.message"
- :title="confirmDialogConfig.title"
- :type="confirmDialogConfig.type"
- @cancel="handleCancelAction"
- @confirm="handleConfirmAction"
- />
- </div>
- </div>
- </ClientOnly>
- </div>
- </div>
-
- <!-- 椤佃剼淇℃伅鏄剧ず -->
- <SiteFooter />
- </div>
-
- <!-- 瑙勫垯寮圭獥 -->
- <Teleport to="body">
- <Transition
- enter-active-class="transition duration-300 ease-out"
- enter-from-class="opacity-0 scale-95"
- enter-to-class="opacity-100 scale-100"
- leave-active-class="transition duration-200 ease-in"
- leave-from-class="opacity-100 scale-100"
- leave-to-class="opacity-0 scale-95"
- >
- <div
- v-if="showRules"
- class="fixed inset-0 z-[70] flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm"
- @click.self="showRules = false"
- >
- <div
- class="bg-zinc-900 border border-zinc-800 w-full max-w-lg rounded-3xl overflow-hidden shadow-2xl flex flex-col"
- >
- <div class="p-8 pb-4 flex items-center justify-between">
- <div>
- <h3 class="text-xl font-black text-zinc-100 tracking-tight flex items-center gap-3">
- <div
- class="w-10 h-10 rounded-2xl bg-blue-600/10 flex items-center justify-center text-blue-500"
- >
- <Icon name="bell" :size="20" />
- </div>
- 鐐规瓕瑙勫垯
- </h3>
- <p class="text-xs text-zinc-500 mt-1 ml-13">鎶曠鍓嶈浠旂粏闃呰浠ヤ笅瑙勫垯</p>
- </div>
- <button
- class="p-3 bg-zinc-800/50 hover:bg-zinc-800 text-zinc-500 hover:text-zinc-200 rounded-2xl transition-all"
- @click="showRules = false"
- >
- <Icon name="x" :size="20" />
- </button>
- </div>
-
- <div class="p-8 pt-4 space-y-8">
- <div class="rules-group space-y-4">
- <h4
- class="text-[10px] font-black text-zinc-500 uppercase tracking-widest flex items-center gap-2"
- >
- <Icon name="message-circle" :size="12" />
- 鎶曠椤荤煡
- </h4>
- <div
- v-if="submissionGuidelines"
- class="text-sm text-zinc-400 leading-relaxed font-medium bg-zinc-950/50 p-6 rounded-3xl border border-zinc-800/50"
- v-html="submissionGuidelines.replace(/\n/g, '<br>')"
- />
- <div
- v-else
- class="space-y-3 bg-zinc-950/50 p-6 rounded-3xl border border-zinc-800/50"
- >
- <div class="flex gap-3 text-sm text-zinc-400 font-medium">
- <span class="text-blue-500 font-black">01</span>
- <p>鎶曠鏃舵棤闇€鍔犲叆涔﹀悕鍙?/p>
- </div>
- <div class="flex gap-3 text-sm text-zinc-400 font-medium">
- <span class="text-blue-500 font-black">02</span>
- <p>闄J澶栵紝鍏朵粬绫诲瀷姝屾洸鍧囨帴鏀讹紙鍖呮嫭灏忚绉嶏級</p>
- </div>
- <div class="flex gap-3 text-sm text-zinc-400 font-medium">
- <span class="text-blue-500 font-black">03</span>
- <p>绂佹鎶曢€掑惈鏈夎繚瑙勫唴瀹圭殑姝屾洸</p>
- </div>
- </div>
- </div>
-
- <div class="rules-group space-y-4">
- <h4
- class="text-[10px] font-black text-zinc-500 uppercase tracking-widest flex items-center gap-2"
- >
- <Icon name="calendar" :size="12" />
- 鎾斁鏃堕棿
- </h4>
- <div
- class="bg-blue-600/10 border border-blue-500/20 p-6 rounded-3xl flex items-center gap-4"
- >
- <div
- class="w-12 h-12 rounded-2xl bg-blue-600 flex items-center justify-center text-white shadow-lg shadow-blue-900/40"
- >
- <Icon name="clock" :size="24" />
- </div>
- <div>
- <p class="text-sm font-black text-zinc-100">姣忓ぉ澶滆嚜淇潤鐝墠</p>
- <p class="text-[10px] text-zinc-500 font-bold uppercase tracking-widest mt-0.5">
- PLAYBACK TIME
- </p>
- </div>
- </div>
- </div>
- </div>
-
- <div class="p-8 pt-0">
- <button
- class="w-full px-6 py-4 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 text-xs font-black rounded-2xl transition-all uppercase tracking-widest shadow-lg active:scale-95"
- @click="showRules = false"
- >
- 鎴戠煡閬撲簡
- </button>
- </div>
- </div>
- </div>
- </Transition>
- </Teleport>
-
- <div
- v-if="statisticsCodeEnabled && statisticsCode"
- id="site-statistics-code"
- class="hidden"
- v-html="statisticsCode"
- />
- </div>
-</template>
-
-<script setup>
-import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
-import { Lock } from '@lucide/vue'
-import { useRouter } from 'vue-router'
-
-import logo from '~~/public/images/logo.png'
-import Icon from '~/components/UI/Icon.vue'
-import ConfirmDialog from '~/components/UI/ConfirmDialog.vue'
-
-import { useNotifications } from '~/composables/useNotifications'
-import { useSiteConfig } from '~/composables/useSiteConfig'
-import { useTheme } from '~/composables/useTheme'
-import CustomSelect from '~/components/UI/Common/CustomSelect.vue'
-
-// 鑾峰彇杩愯鏃堕厤缃?const config = useRuntimeConfig()
-const router = useRouter()
-
-// 涓婚
-const { isDark, toggleTheme } = useTheme()
-const handleToggleTheme = () => {
- toggleTheme()
-}
-const handleLanguageClick = () => {
- if (typeof window !== 'undefined' && window.$showNotification) {
- window.$showNotification('褰撳墠鐗堟湰鏆備笉鏀寔澶氳瑷€鍒囨崲', 'info')
- }
-}
-
-// 绔欑偣閰嶇疆
-const {
- siteTitle,
- description: siteDescription,
- guidelines: submissionGuidelines,
- icp: icpNumber,
- schoolLogoHomeUrl,
- statisticsCode,
- statisticsCodeEnabled,
- initSiteConfig
-} = useSiteConfig()
-
-const auth = useAuth()
-
-const isClientAuthenticated = computed(() => auth?.isAuthenticated?.value || false)
-const isAdmin = computed(() => auth?.isAdmin?.value || false)
-const user = computed(() => auth?.user?.value || null)
-
-const roleName = computed(() => {
- const role = user.value?.role
- const map = {
- ADMIN: '閮ㄩ暱',
- SUPER_ADMIN: '鎶€鏈繍缁?,
- SONG_ADMIN: '宸ヤ綔浜哄憳',
- VICE_MINISTER: '鍓儴',
- RESIDENT: '甯搁┗',
- TEACHER: '鏁欏笀',
- USER: '瀛︾敓锛堟櫘閫氱敤鎴凤級'
- }
- return map[role] || '绠$悊鍛?
-})
-
-const userClassInfo = computed(() => {
- if (user.value?.grade && user.value?.class) {
- return `${user.value.grade} ${user.value.class}`
- }
- return '鍚屽'
-})
-
-const songs = useSongs()
-// 绔嬪嵆鍒濆鍖栭€氱煡鏈嶅姟锛岄伩鍏嶆椂搴忛棶棰?const notificationsService = useNotifications()
-const unreadNotificationCount = ref(0)
-
-// 妯℃嫙鏁版嵁鍒濆鍊?const songCount = ref(0)
-const scheduleCount = ref(0)
-const isRequestOpen = ref(true)
-
-// 寮圭獥鐘舵€?const showRequestModal = ref(false)
-const showRules = ref(false)
-const showUserActions = ref(false)
-const avatarError = ref(false)
-
-const toggleUserActions = (event) => {
- event.stopPropagation()
- showUserActions.value = !showUserActions.value
-}
-
-// 鐩戝惉鐢ㄦ埛澶村儚鍙樺寲锛岄噸缃敊璇姸鎬?watch(
- () => user.value?.avatar,
- () => {
- avatarError.value = false
- }
-)
-
-// 鐐瑰嚮澶栭儴鍏抽棴涓嬫媺鑿滃崟
-const handleClickOutside = (event) => {
- if (showUserActions.value) {
- const dropdown = document.querySelector('.user-actions-dropdown')
- const avatar = document.querySelector('.user-avatar-wrapper')
- if (dropdown && !dropdown.contains(event.target) && !avatar.contains(event.target)) {
- showUserActions.value = false
- }
- }
-}
-
-onMounted(() => {
- window.addEventListener('click', handleClickOutside)
-})
-
-onUnmounted(() => {
- window.removeEventListener('click', handleClickOutside)
-})
-
-// 鏍囩椤电姸鎬?const activeTab = ref('schedule') // 榛樿鏄剧ず鎾嚭鎺掓湡
-
-const tabOrder = ['schedule', 'songs', 'request', 'notification']
-const activeIndex = computed(() => {
- const index = tabOrder.indexOf(activeTab.value)
- return index === -1 ? 0 : index
-})
-
-// 閫氱煡鎸夐挳寮哄埗鏇存柊鐩稿叧
-const notificationTabRef = ref(null)
-const hasInitializedAuthData = ref(isClientAuthenticated.value)
-
-let refreshInterval = null
-let lastDataRevision = ''
-
-const fetchDataRevision = async () => {
- try {
- const url = isClientAuthenticated.value ? '/api/songs/revision' : '/api/songs/public-revision'
- const authConfig = isClientAuthenticated.value ? auth.getAuthConfig() : {}
- const result = await $fetch(url, {
- ...authConfig,
- query: { t: Date.now() }
- })
- return result?.revision || ''
- } catch (e) {
- console.error('鑾峰彇鏁版嵁淇鍙峰け璐?', e)
- return ''
- }
-}
-
-const setupRefreshInterval = async () => {
- if (refreshInterval) {
- clearInterval(refreshInterval)
- }
-
- const intervalSeconds = Math.max(5, Math.min(30, notificationSettings.value.refreshInterval || 10))
- const intervalMs = intervalSeconds * 1000
-
- lastDataRevision = await fetchDataRevision()
-
- refreshInterval = setInterval(async () => {
- try {
- const nextRevision = await fetchDataRevision()
- if (!nextRevision || nextRevision === lastDataRevision) return
- lastDataRevision = nextRevision
-
- if (isClientAuthenticated.value) {
- await Promise.allSettled([
- songs.fetchSongs(false, undefined, true, true),
- songs.fetchPublicSchedules(false, undefined, true, true),
- loadNotifications()
- ])
- } else {
- await Promise.allSettled([songs.fetchPublicSchedules(false, undefined, true, true), songs.fetchSongCount()])
- }
-
- await updateSongCounts()
- } catch (error) {
- console.error('瀹氭湡鍒锋柊澶辫触:', error)
- }
- }, intervalMs)
-}
-
-// 娣诲姞閫氱煡鐩稿叧鍙橀噺
-const userNotifications = computed(() => notificationsService?.notifications?.value || [])
-const notificationsLoading = computed(() => notificationsService?.loading?.value || false)
-const hasUnreadNotifications = computed(() => {
- // 纭繚notificationsService宸插垵濮嬪寲涓旀湁unreadCount
- if (!notificationsService || !notificationsService.unreadCount) {
- return false
- }
- const unreadCount = notificationsService.unreadCount.value
- return unreadCount > 0
-})
-const showNotificationSettings = ref(false)
-
-const pageSizeOptions = [
- { label: '5鏉?, value: 5 },
- { label: '10鏉?, value: 10 },
- { label: '20鏉?, value: 20 },
- { label: '50鏉?, value: 50 }
-]
-
-const notificationSettings = ref({
- songSelectedNotify: true,
- songPlayedNotify: true,
- songVotedNotify: true,
- songVotedThreshold: 1,
- systemNotify: true,
- refreshInterval: 60
-})
-
-// 璺宠浆鍒伴€氱煡璁剧疆椤甸潰
-const toggleNotificationSettings = () => {
- navigateTo('/notification-settings')
-}
-
-// 鑾峰彇閫氱煡璁剧疆
-const fetchNotificationSettings = async () => {
- if (notificationsService) {
- await notificationsService.fetchNotificationSettings()
- if (notificationsService.settings.value) {
- notificationSettings.value = {
- songSelectedNotify: notificationsService.settings.value.songSelectedNotify,
- songPlayedNotify: notificationsService.settings.value.songPlayedNotify,
- songVotedNotify: notificationsService.settings.value.songVotedNotify,
- songVotedThreshold: notificationsService.settings.value.songVotedThreshold || 1,
- systemNotify: notificationsService.settings.value.systemNotify,
- refreshInterval: notificationsService.settings.value.refreshInterval || 60
- }
- }
- }
-}
-
-// 淇濆瓨閫氱煡璁剧疆
-const saveNotificationSettings = async () => {
- if (notificationsService) {
- await notificationsService.updateNotificationSettings(notificationSettings.value)
-
- // 濡傛灉鍦ㄩ椤碉紝鏇存柊鍒锋柊闂撮殧
- if (typeof setupRefreshInterval === 'function') {
- setupRefreshInterval()
- }
- }
-}
-
-// 鍔犺浇閫氱煡
-const loadNotifications = async () => {
- if (isClientAuthenticated.value && notificationsService) {
- try {
- await notificationsService.fetchNotifications()
- } catch (error) {
- console.error('[閫氱煡鑾峰彇] 鍔犺浇閫氱煡澶辫触:', error)
- }
- }
-}
-
-// 鏍囪閫氱煡涓哄凡璇?const markNotificationAsRead = async (id) => {
- if (notificationsService) {
- await notificationsService.markAsRead(id)
- }
-}
-
-// 鏍囪鎵€鏈夐€氱煡涓哄凡璇?const markAllNotificationsAsRead = async () => {
- try {
- if (notificationsService) {
- await notificationsService.markAllAsRead()
- }
- } catch (error) {
- console.error('[閫氱煡鎿嶄綔] 鏍囪鎵€鏈夐€氱煡涓哄凡璇诲け璐?', error)
- }
-}
-
-// 鍒犻櫎閫氱煡
-const deleteNotification = async (id) => {
- pendingAction.value = 'delete'
- pendingId.value = id
- confirmDialogConfig.value = {
- title: '鍒犻櫎娑堟伅',
- message: '纭畾瑕佸垹闄ゆ娑堟伅鍚楋紵',
- type: 'warning',
- confirmText: '鍒犻櫎',
- cancelText: '鍙栨秷'
- }
- showConfirmDialog.value = true
-}
-
-// 娓呯┖鎵€鏈夐€氱煡
-const clearAllNotifications = async () => {
- pendingAction.value = 'clearAll'
- confirmDialogConfig.value = {
- title: '娓呯┖鎵€鏈夋秷鎭?,
- message: '纭畾瑕佹竻绌烘墍鏈夋秷鎭悧锛熸鎿嶄綔涓嶅彲鎾ら攢銆?,
- type: 'danger',
- confirmText: '娓呯┖',
- cancelText: '鍙栨秷'
- }
- showConfirmDialog.value = true
-}
-
-// 纭瀵硅瘽妗嗙浉鍏崇姸鎬?const showConfirmDialog = ref(false)
-const confirmDialogConfig = ref({
- title: '',
- message: '',
- type: 'warning',
- confirmText: '纭畾',
- cancelText: '鍙栨秷'
-})
-const pendingAction = ref('')
-const pendingId = ref(null)
-
-// 澶勭悊纭鎿嶄綔
-const handleConfirmAction = async () => {
- if (notificationsService) {
- if (pendingAction.value === 'delete') {
- await notificationsService.deleteNotification(pendingId.value)
- pendingId.value = null
- } else if (pendingAction.value === 'clearAll') {
- await notificationsService.clearAllNotifications()
- }
- }
- showConfirmDialog.value = false
- pendingAction.value = ''
-}
-
-// 澶勭悊鍙栨秷鎿嶄綔
-const handleCancelAction = () => {
- showConfirmDialog.value = false
- pendingAction.value = ''
- pendingId.value = null
-}
-
-// 鍒嗛〉鐩稿叧鏂规硶
-const handlePageSizeChange = async (newSize) => {
- const size = parseInt(newSize)
- if (notificationsService) {
- await notificationsService.changePageSize(size)
- }
-}
-
-// 鑾峰彇鍙鐨勯〉鐮佸垪琛?const getVisiblePages = () => {
- if (!notificationsService) return []
-
- const currentPage = notificationsService.currentPage.value
- const totalPages = notificationsService.totalPages.value
- const pages = []
-
- if (totalPages <= 7) {
- // 鎬婚〉鏁板皯浜庣瓑浜?椤碉紝鏄剧ず鎵€鏈夐〉鐮? for (let i = 1; i <= totalPages; i++) {
- pages.push(i)
- }
- } else {
- // 鎬婚〉鏁板ぇ浜?椤碉紝鏄剧ず鐪佺暐鍙? if (currentPage <= 4) {
- // 褰撳墠椤靛湪鍓嶉潰
- for (let i = 1; i <= 5; i++) {
- pages.push(i)
- }
- pages.push('...')
- pages.push(totalPages)
- } else if (currentPage >= totalPages - 3) {
- // 褰撳墠椤靛湪鍚庨潰
- pages.push(1)
- pages.push('...')
- for (let i = totalPages - 4; i <= totalPages; i++) {
- pages.push(i)
- }
- } else {
- // 褰撳墠椤靛湪涓棿
- pages.push(1)
- pages.push('...')
- for (let i = currentPage - 1; i <= currentPage + 1; i++) {
- pages.push(i)
- }
- pages.push('...')
- pages.push(totalPages)
- }
- }
-
- return pages
-}
-
-// 鏍煎紡鍖栭€氱煡鏃堕棿
-const formatNotificationTime = (timeString) => {
- const date = new Date(timeString)
- const now = new Date()
- const diff = now.getTime() - date.getTime()
-
- // 灏忎簬1鍒嗛挓
- if (diff < 60000) {
- return '鍒氬垰'
- }
-
- // 灏忎簬1灏忔椂
- if (diff < 3600000) {
- return `${Math.floor(diff / 60000)}鍒嗛挓鍓峘
- }
-
- // 灏忎簬24灏忔椂
- if (diff < 86400000) {
- return `${Math.floor(diff / 3600000)}灏忔椂鍓峘
- }
-
- // 灏忎簬30澶? if (diff < 2592000000) {
- return `${Math.floor(diff / 86400000)}澶╁墠`
- }
-
- // 澶т簬30澶╋紝鏄剧ず鍏蜂綋鏃ユ湡
- return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`
-}
-
-// 鐩戝惉鏍囩椤靛垏鎹紝濡傛灉鍒囨崲鍒伴€氱煡鏍囩椤碉紝鍔犺浇閫氱煡
-watch(activeTab, (newTab) => {
- if (newTab === 'notification') {
- loadNotifications()
- }
-})
-
-watch(
- () => auth?.isAuthenticated?.value,
- async (newAuthState, oldAuthState) => {
- if (newAuthState && !oldAuthState) {
- hasInitializedAuthData.value = true
- await Promise.allSettled([loadNotifications(), fetchNotificationSettings(), songs.fetchSongs()])
- await updateSongCounts()
- return
- }
-
- if (!newAuthState && oldAuthState) {
- hasInitializedAuthData.value = false
- await Promise.allSettled([songs.fetchSongCount(), songs.fetchPublicSchedules()])
- unreadNotificationCount.value = 0
- await updateSongCounts()
- }
- },
- { flush: 'post' }
-)
-
-// 鍒濆鍖栨椂濡傛灉宸茬粡鍦ㄩ€氱煡鏍囩椤碉紝鍒欏姞杞介€氱煡
-onMounted(() => {
- if (activeTab.value === 'notification') {
- loadNotifications()
- }
-})
-
-// 鑾峰彇褰撳墠鏃ユ湡鍜屾槦鏈?const getCurrentDate = () => {
- const now = new Date()
- const year = now.getFullYear()
- const month = now.getMonth() + 1
- const date = now.getDate()
- const weekDays = ['鏃?, '涓€', '浜?, '涓?, '鍥?, '浜?, '鍏?]
- const weekDay = weekDays[now.getDay()]
-
- return `${year}骞?{month}鏈?{date}鏃?鍛?{weekDay}`
-}
-
-// RequestForm缁勪欢寮曠敤
-const requestFormRef = ref(null)
-
-// 鏃х殑showNotification鍑芥暟宸茬Щ闄わ紝浣跨敤鍏ㄥ眬閫氱煡绯荤粺
-
-// 鏇存柊姝屾洸鏁伴噺缁熻锛堜紭鍖栫増鏈紝閬垮厤閲嶅璇锋眰锛?const updateSongCounts = async (semester = null) => {
- try {
- // 鏇存柊鎺掓湡姝屾洸鏁伴噺
- const schedules = songs?.publicSchedules?.value || []
- scheduleCount.value = schedules.length
-
- // 鏇存柊鎬绘瓕鏇叉暟閲? if (isClientAuthenticated.value && songs?.songs?.value) {
- // 宸茬櫥褰曠敤鎴凤細浣跨敤瀹屾暣姝屾洸鍒楄〃
- songCount.value = songs.songs.value.length
- } else {
- // 鏈櫥褰曠敤鎴凤細浣跨敤缂撳瓨鐨勬瓕鏇叉€绘暟
- songCount.value = songs?.songCount?.value || 0
- }
- } catch (e) {
- console.error('鏇存柊姝屾洸缁熻澶辫触', e)
- }
-}
-
-// 鐩戝惉siteTitle鍙樺寲锛岀‘淇濋椤祎itle姝g‘璁剧疆
-watch(
- siteTitle,
- (newSiteTitle) => {
- if (typeof document !== 'undefined' && newSiteTitle) {
- document.title = `棣栭〉 | ${newSiteTitle}`
- }
- },
- { immediate: true }
-)
-
-// 鍦ㄧ粍浠舵寕杞藉悗鍒濆鍖栬璇佸拰姝屾洸锛堝彧浼氬湪瀹㈡埛绔墽琛岋級
-onMounted(async () => {
- try {
- await initSiteConfig()
-
- const currentUser = await auth.initAuth()
-
- if (typeof document !== 'undefined' && siteTitle.value) {
- document.title = `棣栭〉 | ${siteTitle.value}`
- }
-
- if (isClientAuthenticated.value) {
- hasInitializedAuthData.value = true
- await Promise.allSettled([
- songs.fetchSongs(),
- songs.fetchPublicSchedules(),
- loadNotifications(),
- fetchNotificationSettings()
- ])
- await checkPasswordChangeRequired(currentUser)
- } else {
- hasInitializedAuthData.value = false
- await Promise.allSettled([songs.fetchSongCount(), songs.fetchPublicSchedules()])
- }
-
- await updateSongCounts()
-
- await setupRefreshInterval()
-
- if (songs.notification) {
- watch(songs.notification, (newVal) => {
- if (newVal.show) {
- showNotification(newVal.message, newVal.type)
- }
- })
- }
-
- // 娉ㄥ叆绔欑偣缁熻浠g爜锛堝厹搴曟柟妗堬細鍔ㄦ€佹彃鍏?script 鍒?body 鏈熬锛? if (statisticsCodeEnabled.value && statisticsCode.value) {
- try {
- injectStatisticsCode(statisticsCode.value)
- } catch (e) {
- console.warn('缁熻浠g爜娉ㄥ叆澶辫触:', e)
- }
- }
- } catch (error) {
- console.error('棣栭〉鍒濆鍖栧け璐?', error)
- await Promise.allSettled([songs.fetchPublicSchedules(), songs.fetchSongCount()])
- await updateSongCounts()
- }
-})
-
-// 缁勪欢鍗歌浇鏃舵竻闄ゅ畾鏃跺櫒
-onUnmounted(() => {
- if (refreshInterval) {
- clearInterval(refreshInterval)
- }
-})
-
-// 瀹炴椂璁$畻姝屾洸鎬绘暟
-const realSongCount = computed(() => {
- return songs?.visibleSongs?.value?.length || 0
-})
-
-// 浣跨敤璁$畻灞炴€у畨鍏ㄥ湴璁块棶鏁版嵁
-const publicSchedules = computed(() => songs?.publicSchedules?.value || [])
-const allSongs = computed(() => songs?.visibleSongs?.value || [])
-const filteredSongs = computed(() => {
- // 杩斿洖鎵€鏈夋瓕鏇诧紝浣嗗皢宸叉挱鏀剧殑姝屾洸鎺掑湪鏈€鍚? if (allSongs.value && allSongs.value.length > 0) {
- const unplayedSongs = allSongs.value.filter((song) => !song.played)
- const playedSongs = allSongs.value.filter((song) => song.played)
- return [...unplayedSongs, ...playedSongs]
- }
- return []
-})
-const loading = computed(() => songs?.loading?.value || false)
-const error = computed(() => songs?.error?.value || '')
-
-// 澶勭悊瀛︽牎logo鐨凥TTP/HTTPS浠g悊
-const proxiedSchoolLogoUrl = computed(() => {
- if (!schoolLogoHomeUrl.value || !schoolLogoHomeUrl.value.trim()) {
- return ''
- }
-
- const logoUrl = schoolLogoHomeUrl.value.trim()
-
- // 濡傛灉鏄疕TTP閾炬帴锛岄€氳繃浠g悊璁块棶
- if (logoUrl.startsWith('http://')) {
- return `/api/proxy/image?url=${encodeURIComponent(logoUrl)}`
- }
-
- // HTTPS閾炬帴鎴栫浉瀵硅矾寰勭洿鎺ヨ繑鍥? return logoUrl
-})
-
-// 澶勭悊鎶曠璇锋眰
-const handleRequest = async (songData, done) => {
- if (!auth || !isClientAuthenticated.value) {
- if (window.$showNotification) {
- window.$showNotification('闇€瑕佺櫥褰曟墠鑳芥姇绋挎瓕鏇?, 'error')
- }
- showRequestModal.value = false
- if (typeof done === 'function') done(false)
- return false
- }
-
- try {
- console.log('澶勭悊姝屾洸璇锋眰:', songData.title)
- // 鐩存帴浼犻€掓暣涓猻ongData瀵硅薄锛岀‘淇滼SON鏍煎紡姝g‘
- const result = await songs.requestSong(songData)
- if (result) {
- // 鏄剧ず鎶曠鎴愬姛閫氱煡
- if (window.$showNotification) {
- window.$showNotification(`銆?{songData.title} - ${songData.artist}銆嬫姇绋挎垚鍔燂紒`, 'success')
- }
-
- // 寮哄埗鍒锋柊姝屾洸鍒楄〃
- console.log('鎶曠鎴愬姛锛屽埛鏂版瓕鏇插垪琛?)
- await refreshSongs()
-
- // 鍒锋柊鎶曠鐘舵€? if (requestFormRef.value && requestFormRef.value.refreshSubmissionStatus) {
- await requestFormRef.value.refreshSubmissionStatus()
- }
-
- // 濡傛灉褰撳墠鍦ㄦ瓕鏇插垪琛ㄩ〉锛岃嚜鍔ㄥ垏鎹㈠埌璇ラ〉闈? if (activeTab.value !== 'songs') {
- setTimeout(() => {
- handleTabClick('songs')
- }, 500)
- }
-
- if (typeof done === 'function') done(true)
- return true
- }
- if (typeof done === 'function') done(false)
- return false
- } catch (err) {
- if (window.$showNotification) {
- window.$showNotification(err.message || '鐐规瓕澶辫触', 'error')
- }
- if (typeof done === 'function') done(false)
- return false
- }
-}
-
-// 澶勭悊鎶曠エ
-const handleVote = async (song) => {
- if (!isClientAuthenticated.value) {
- showNotification('璇峰厛鐧诲綍鍚庡啀鎶曠エ', 'error')
- return
- }
-
- try {
- if (!songs) return
-
- // 璋冪敤鎶曠エAPI - 閫氱煡宸插湪composable涓鐞? // 妫€鏌ユ槸鍚︽槸鍙栨秷鎶曠エ璇锋眰
- if (song.unvote) {
- // 浼犻€掑畬鏁村璞′互鏀寔鎾ら攢鎶曠エ鍔熻兘
- await songs.voteSong(song)
- } else {
- // 淇濇寔鍚戝悗鍏煎锛屼紶閫扞D
- await songs.voteSong(song.id)
- }
-
- // 闈欓粯鍒锋柊姝屾洸鍒楄〃浠ヨ幏鍙栨渶鏂扮姸鎬侊紝浣嗕笉褰卞搷褰撳墠瑙嗗浘
- setTimeout(() => {
- songs.refreshSongsSilent().catch((err) => {
- console.error('鍒锋柊姝屾洸鍒楄〃澶辫触', err)
- })
- }, 500)
- } catch (err) {
- // 涓嶅仛浠讳綍澶勭悊锛屽洜涓簎seSongs涓凡缁忓鐞嗕簡閿欒鎻愮ず
- console.log('API閿欒宸插湪useSongs涓鐞?)
- }
-}
-
-const handleCancelReplay = async (song) => {
- if (!isClientAuthenticated.value) {
- showNotification('璇峰厛鐧诲綍鎵嶈兘鍙栨秷閲嶆挱鐢宠', 'error')
- return
- }
-
- try {
- if (!songs) return
- await songs.withdrawReplay(song.id)
- updateSongCounts()
- } catch (err) {
- console.log('API閿欒宸插湪useSongs涓鐞?)
- }
-}
-
-const handleRequestReplay = async (song) => {
- if (!isClientAuthenticated.value) {
- showNotification('璇峰厛鐧诲綍鎵嶈兘鐢宠閲嶆挱', 'error')
- return
- }
-
- try {
- if (!songs) return
- await songs.requestReplay(song.id)
- updateSongCounts()
- } catch (err) {
- console.log('API閿欒宸插湪useSongs涓鐞?)
- }
-}
-
-// 澶勭悊鎾ゅ洖鎶曠
-const handleWithdraw = async (song) => {
- if (!isClientAuthenticated.value) {
- showNotification('璇峰厛鐧诲綍鎵嶈兘鎾ゅ洖鎶曠', 'error')
- return
- }
-
- try {
- if (!songs) return
-
- // 璋冪敤鎾ゅ洖API - 閫氱煡宸插湪composable涓鐞? await songs.withdrawSong(song.id)
- // 鏇存柊璁℃暟
- updateSongCounts()
- } catch (err) {
- // 涓嶅仛浠讳綍澶勭悊锛屽洜涓簎seSongs涓凡缁忓鐞嗕簡閿欒鎻愮ず
- console.log('API閿欒宸插湪useSongs涓鐞?)
- }
-}
-
-// 鍒锋柊姝屾洸鍒楄〃锛堜紭鍖栫増鏈級
-const refreshSongs = async () => {
- try {
- if (isClientAuthenticated.value) {
- await songs.fetchSongs(false, undefined, true) // forceRefresh=true
- } else {
- await songs.fetchPublicSchedules(false, undefined, true) // forceRefresh=true
- }
-
- updateSongCounts()
- } catch (err) {
- console.error('鍒锋柊姝屾洸鍒楄〃澶辫触', err)
- }
-}
-
-// 澶勭悊瀛︽湡鍙樺寲锛堝墠绔繃婊ょ増鏈級
-const handleSemesterChange = async (semester) => {
- try {
- // 閫氳繃浜嬩欢鎬荤嚎閫氱煡SongList缁勪欢杩涜鍓嶇杩囨护
- // 浣跨敤nextTick纭繚浜嬩欢鍦―OM鏇存柊鍚庤Е鍙? await nextTick()
-
- // 瑙﹀彂鑷畾涔変簨浠讹紝閫氱煡鎵€鏈夌洃鍚殑缁勪欢
- const event = new CustomEvent('semester-filter-change', {
- detail: { semester }
- })
- window.dispatchEvent(event)
-
- console.log('瀛︽湡鍒囨崲浜嬩欢宸插彂閫?', semester)
-
- // 鏇存柊姝屾洸璁℃暟锛堝熀浜庡綋鍓嶅凡鏈夋暟鎹級
- await updateSongCounts(semester)
- } catch (err) {
- console.error('鍒囨崲瀛︽湡澶辫触', err)
- }
-}
-
-// 鏇存柊閫氱煡鏁伴噺 - 鍙互淇濈暀杩欎釜鍑芥暟浣嗕笉鍐嶈皟鐢?const updateNotificationCount = async () => {
- // 鍑芥暟淇濈暀浣嗕笉鍐嶄娇鐢?}
-
-// 澶勭悊鐧诲嚭
-const handleLogout = () => {
- if (auth) {
- auth.logout()
- }
-}
-
-// 澶勭悊杩涘叆鍚庡彴鐨勭偣鍑诲姩鐢?const handleDashboardClick = (event) => {
- const button = event.currentTarget
- button.classList.add('clicking')
-
- // 娣诲姞娑熸吉鏁堟灉
- const ripple = document.createElement('span')
- ripple.classList.add('ripple')
- button.appendChild(ripple)
-
- setTimeout(() => {
- button.classList.remove('clicking')
- ripple.remove()
- }, 300)
-}
-
-// 娣诲姞鏌ョ湅閫氱煡骞舵爣璁颁负宸茶
-const viewNotification = async (notification) => {
- if (!notification.read) {
- await notificationsService.markAsRead(notification.id)
- }
-}
-
-// 澶勭悊鑱斿悎鎶曠鍥炲
-const handleCollaborationReply = async (notification, accept) => {
- if (notification.processing) return
- notification.processing = true
-
- try {
- await $fetch('/api/songs/collaborators/reply', {
- method: 'POST',
- body: {
- songId: notification.songId,
- accept
- }
- })
-
- // 鏍囪涓哄凡澶勭悊
- notification.handled = true
- notification.status = accept ? 'ACCEPTED' : 'REJECTED'
- notification.repliedAt = new Date()
- // notification.message += accept ? ' (宸叉帴鍙?' : ' (宸叉嫆缁?'
-
- if (window.$showNotification) {
- window.$showNotification(accept ? '宸叉帴鍙楄仈鍚堟姇绋块個璇? : '宸叉嫆缁濊仈鍚堟姇绋块個璇?, 'success')
- }
-
- // 鏍囪閫氱煡涓哄凡璇? await markNotificationAsRead(notification.id)
-
- // 鍒锋柊姝屾洸鍒楄〃
- refreshSongs()
-
- // 鍒锋柊閫氱煡鍒楄〃
- await loadNotifications()
- } catch (error) {
- console.error('澶勭悊鑱斿悎鎶曠閭€璇峰け璐?', error)
- if (window.$showNotification) {
- window.$showNotification(error.statusMessage || '鎿嶄綔澶辫触', 'error')
- }
- } finally {
- notification.processing = false
- }
-}
-
-// 鏍煎紡鍖栧埛鏂伴棿闅?const formatRefreshInterval = (seconds) => {
- if (seconds < 60) {
- return `${seconds}绉抈
- } else {
- const minutes = Math.floor(seconds / 60)
- const remainingSeconds = seconds % 60
- return remainingSeconds > 0 ? `${minutes}鍒?{remainingSeconds}绉抈 : `${minutes}鍒嗛挓`
- }
-}
-
-// 娉㈢汗鏁堟灉鎸囦护
-const vRipple = {
- mounted(el) {
- el.addEventListener('click', (e) => {
- const rect = el.getBoundingClientRect()
- const x = e.clientX - rect.left
- const y = e.clientY - rect.top
-
- const ripple = document.createElement('span')
- ripple.className = 'ripple-effect'
- ripple.style.left = `${x}px`
- ripple.style.top = `${y}px`
-
- el.appendChild(ripple)
-
- setTimeout(() => {
- ripple.remove()
- }, 600) // 涓嶤SS鍔ㄧ敾鏃堕棿涓€鑷? })
- }
-}
-
-// 澶勭悊鏍囩鐐瑰嚮浜嬩欢锛屾坊鍔犲姩鐢绘晥鏋?const handleTabClick = (tab) => {
- activeTab.value = tab
-}
-
-// 娣诲姞瀵艰埅鍒扮櫥褰曢〉闈㈢殑鏂规硶
-const navigateToLogin = () => {
- router.push('/login')
-}
-
-// 鏄剧ず鐧诲綍鎻愮ず
-const showLoginNotice = () => {
- if (window.$showNotification) {
- window.$showNotification('闇€瑕佺櫥褰曟墠鑳芥煡鐪嬮€氱煡', 'info')
- }
-}
-
-// 娉ㄥ叆绔欑偣缁熻浠g爜锛堝厹搴曟柟妗堬細鍔ㄦ€佽В鏋愬苟鎻掑叆 script 鍏冪礌鍒?body 鏈熬锛?const injectStatisticsCode = (code) => {
- if (typeof document === 'undefined' || !code) return
-
- // 鍏堝皾璇曠敤涓存椂瀹瑰櫒瑙f瀽 HTML锛屾彁鍙栧叾涓殑 script 鏍囩
- const tempDiv = document.createElement('div')
- tempDiv.innerHTML = code
-
- // 灏嗛潪 script 鐨勫厓绱犲厛闄勫姞鍒板鍣ㄤ腑
- const container = document.getElementById('site-statistics-code')
- if (container) {
- Array.from(tempDiv.childNodes).forEach((node) => {
- if (node.nodeType === 1 && node.tagName && node.tagName.toLowerCase() !== 'script') {
- container.appendChild(node.cloneNode(true))
- }
- })
- }
-
- // 鍗曠嫭澶勭悊 script 鏍囩锛氬姩鎬佸垱寤哄苟鎻掑叆鍒?body 鏈熬锛岀‘淇濊兘姝g‘鎵ц
- const scripts = tempDiv.querySelectorAll('script')
- scripts.forEach((oldScript) => {
- const newScript = document.createElement('script')
- Array.from(oldScript.attributes).forEach((attr) => {
- newScript.setAttribute(attr.name, attr.value)
- })
- if (oldScript.textContent) {
- newScript.textContent = oldScript.textContent
- }
- document.body.appendChild(newScript)
- })
-}
-
-// 妫€鏌ョ敤鎴锋槸鍚﹂渶瑕佷慨鏀瑰瘑鐮?const checkPasswordChangeRequired = async (user = null) => {
- try {
- // 浣跨敤浼犲叆鐨勭敤鎴蜂俊鎭垨褰撳墠璁よ瘉鐘舵€佷腑鐨勭敤鎴蜂俊鎭? const currentUser = user || auth?.user?.value
-
- if (currentUser && currentUser.requirePasswordChange) {
- // 寤惰繜1绉掓樉绀洪€氱煡锛岀‘淇濋〉闈㈠姞杞藉畬鎴? setTimeout(() => {
- if (window.$showNotification) {
- window.$showNotification(
- '涓轰簡鎮ㄧ殑璐︽埛瀹夊叏锛屽缓璁偍淇敼瀵嗙爜銆傛偍鍙互鐐瑰嚮鍙充笂瑙掔殑"淇敼瀵嗙爜"鎸夐挳杩涜淇敼銆?,
- 'info',
- true,
- 8000 // 鏄剧ず8绉? )
- }
- }, 1000)
- }
- } catch (error) {
- console.error('妫€鏌ュ瘑鐮佷慨鏀圭姸鎬佸け璐?', error)
- }
-}
-
-// 鏃х殑showToast鍑芥暟宸茬Щ闄わ紝浣跨敤鍏ㄥ眬閫氱煡绯荤粺
-
-// 娣诲姞鏈閫氱煡璁℃暟
-// 涔嬪墠宸插0鏄庝簡unreadNotificationCount锛岃繖閲屽鍏惰繘琛屽寮?if (
- notificationsService &&
- notificationsService.unreadCount &&
- notificationsService.unreadCount.value
-) {
- const count = notificationsService.unreadCount.value
- unreadNotificationCount.value = count
-}
-</script>
-
-<style scoped>
-.home {
- width: 100%;
- flex: 1;
- background-color: #121318;
- padding: 1.5rem;
- color: #ffffff;
- display: flex;
- flex-direction: column;
- min-height: 100vh; /* 纭繚鑷冲皯鍗犳弧瑙嗗彛 */
-}
-
-.main-content {
- width: 100%;
- max-width: 1440px;
- margin: 0 auto;
- position: relative;
- display: flex;
- flex-direction: column;
- height: 100%; /* 鏀逛负鍗犳弧鐖跺鍣ㄩ珮搴﹁€屼笉鏄鍙i珮搴?*/
-}
-
-/* 娣诲姞椤堕儴Ellipse 1鏁堟灉 */
-.ellipse-effect {
- position: absolute;
- top: -165px;
- left: 50%;
- transform: translateX(-50%) perspective(500px) rotateX(10deg);
- width: 1110px;
- height: 309px;
- background: radial-gradient(
- ellipse at center,
- rgba(11, 90, 254, 0.3) 0%,
- rgba(11, 90, 254, 0.15) 30%,
- rgba(11, 90, 254, 0) 70%
- );
- z-index: 0;
- pointer-events: none;
-}
-
-/* 椤堕儴鍖哄煙鏍峰紡 */
-.top-bar {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 2rem;
- margin-top: -2rem;
-}
-
-.logo-section {
- display: flex;
- align-items: center;
- gap: 20px;
- min-height: 160px;
-}
-
-.logo-link {
- display: block;
- text-decoration: none;
-}
-
-.logo-image {
- width: 80px;
- height: auto;
- object-fit: contain;
-}
-
-/* 妯嚎鍜屽鏍ogo瀹瑰櫒 */
-.logo-divider-container {
- display: flex;
- align-items: center;
- gap: 30px;
-}
-
-/* 妯嚎鏍峰紡 */
-.logo-divider {
- width: 2px;
- height: 100px;
- background: linear-gradient(
- to bottom,
- rgba(255, 255, 255, 0.3),
- rgba(255, 255, 255, 0.8),
- rgba(255, 255, 255, 0.3)
- );
- border-radius: 1px;
-}
-
-/* 瀛︽牎logo鏍峰紡 */
-.school-logo {
- max-width: 200px;
- max-height: 80px;
- width: auto;
- height: auto;
- object-fit: contain;
-}
-
-.user-section {
- position: relative;
- z-index: 100;
- display: flex;
- align-items: center;
- gap: 10px;
-}
-
-/* 閫氱敤宸ュ叿鎸夐挳锛堜富棰?璇█锛?*/
-.utility-btn {
- display: inline-flex;
- align-items: center;
- gap: 6px;
- padding: 7px 12px;
- background: var(--utility-btn-bg, rgba(255, 255, 255, 0.06));
- color: var(--utility-btn-color, rgba(255, 255, 255, 0.78));
- border: 1px solid var(--utility-btn-border, rgba(255, 255, 255, 0.12));
- border-radius: 10px;
- font-size: 13px;
- font-weight: 500;
- line-height: 1;
- cursor: pointer;
- transition:
- background 0.2s ease,
- color 0.2s ease,
- border-color 0.2s ease,
- transform 0.15s ease;
- backdrop-filter: blur(6px);
-}
-
-.utility-btn:hover {
- background: var(--utility-btn-hover-bg, rgba(255, 255, 255, 0.12));
- color: var(--utility-btn-hover-color, #ffffff);
- border-color: var(--utility-btn-hover-border, rgba(255, 255, 255, 0.22));
-}
-
-.utility-btn:active {
- transform: scale(0.97);
-}
-
-.utility-btn-label {
- white-space: nowrap;
-}
-
-@media (max-width: 768px) {
- .utility-btn-label {
- display: none;
- }
- .utility-btn {
- padding: 7px 8px;
- border-radius: 9px;
- }
-}
-
-.user-info {
- display: flex;
- align-items: center;
- gap: 12px;
- cursor: pointer;
-}
-
-.login-options {
- display: flex;
- align-items: center;
- gap: 12px;
-}
-
-.enter-admin-btn {
- display: flex;
- align-items: center;
- gap: 6px;
- padding: 6px 12px;
- background: rgba(59, 130, 246, 0.15);
- color: #60a5fa;
- border: 1px solid rgba(59, 130, 246, 0.3);
- border-radius: 8px;
- font-size: 0.85rem;
- font-weight: 600;
- transition: all 0.2s ease;
- cursor: pointer;
- text-decoration: none;
- margin-right: 8px;
-}
-
-.enter-admin-btn:hover {
- background: rgba(59, 130, 246, 0.25);
- transform: translateY(-1px);
-}
-
-.user-details-desktop {
- display: flex;
- flex-direction: column;
- align-items: flex-end;
-}
-
-.user-name {
- font-size: 14px;
- font-weight: 600;
- color: rgba(255, 255, 255, 0.9);
-}
-
-.user-badge {
- font-size: 10px;
- padding: 1px 6px;
- background: rgba(255, 255, 255, 0.1);
- color: rgba(255, 255, 255, 0.5);
- border-radius: 4px;
- margin-top: 2px;
-}
-
-.user-badge.admin {
- background: rgba(59, 130, 246, 0.2);
- color: #3b82f6;
-}
-
-.user-avatar-wrapper {
- width: 36px;
- height: 36px;
- border-radius: 50%;
- overflow: hidden;
- background: #2a2a2a;
- border: 1px solid rgba(255, 255, 255, 0.1);
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.user-avatar {
- width: 100%;
- height: 100%;
- object-fit: cover;
-}
-
-.user-avatar-placeholder {
- font-size: 16px;
- font-weight: 700;
- color: var(--primary);
-}
-
-.user-actions-dropdown {
- position: absolute;
- top: calc(100% + 12px);
- right: 0;
- background: #1a1a1f;
- border: 1px solid rgba(255, 255, 255, 0.1);
- border-radius: 12px;
- padding: 8px;
- min-width: 160px;
- box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
- display: flex;
- flex-direction: column;
- gap: 4px;
-}
-
-.action-item {
- display: flex;
- align-items: center;
- gap: 10px;
- padding: 10px 12px;
- border-radius: 8px;
- color: rgba(255, 255, 255, 0.7);
- font-size: 14px;
- transition: all 0.2s;
- text-decoration: none;
- background: transparent;
- width: 100%;
- text-align: left;
-}
-
-.action-item:hover {
- background: rgba(255, 255, 255, 0.05);
- color: white;
-}
-
-.action-item.logout:hover {
- background: rgba(239, 68, 68, 0.1);
- color: #ef4444;
-}
-
-.notification-badge {
- position: absolute;
- top: -5px;
- right: -5px;
- min-width: 18px;
- height: 18px;
- border-radius: 9px;
- background-color: #f44336;
- color: white;
- font-size: 10px;
- font-weight: bold;
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 0 4px;
-}
-
-/* 鐧诲綍鎸夐挳 - 妗岄潰绔?*/
-.login-options .login-btn {
- display: flex;
- align-items: center;
- gap: 10px;
- padding: 10px 24px;
- border-radius: 14px;
- font-size: 14px;
- font-weight: 600;
- background: rgba(255, 255, 255, 0.04);
- backdrop-filter: blur(12px);
- -webkit-backdrop-filter: blur(12px);
- color: #ffffff;
- border: 1px solid rgba(255, 255, 255, 0.1);
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
- transition: all 0.4s cubic-bezier(0.23, 1, 0.32, 1);
- text-decoration: none;
- position: relative;
- overflow: hidden;
-}
-
-.login-options .login-btn:hover {
- background: rgba(255, 255, 255, 0.08);
- border-color: rgba(59, 130, 246, 0.5);
- transform: translateY(-2px);
- box-shadow: 0 8px 25px rgba(59, 130, 246, 0.2);
- color: white;
-}
-
-.login-options .login-btn :deep(.icon),
-.login-options .login-btn i {
- color: #3b82f6;
- transition: all 0.3s ease;
-}
-
-.login-options .login-btn:hover :deep(.icon),
-.login-options .login-btn:hover i {
- transform: scale(1.1);
- filter: drop-shadow(0 0 5px rgba(59, 130, 246, 0.5));
-}
-
-.login-options .login-btn:active {
- transform: translateY(0) scale(0.96);
- transition: all 0.1s;
-}
-
-/* 绔欑偣鏍囬 */
-.site-title {
- text-align: center;
- margin: 3rem 0;
- padding: 0 1rem;
-}
-
-.title-container {
- display: inline-flex;
- flex-direction: column;
- align-items: center;
- gap: 0.75rem;
-}
-
-.main-title {
- font-family: 'MiSans', sans-serif;
- font-weight: 800;
- font-size: 42px;
- letter-spacing: -0.02em;
- background: linear-gradient(135deg, #ffffff 0%, rgba(255, 255, 255, 0.7) 100%);
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- text-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
- margin: 0;
- line-height: 1.2;
-}
-
-.title-divider {
- width: 40px;
- height: 4px;
- background: #0b5afe;
- border-radius: 2px;
- box-shadow: 0 0 15px rgba(11, 90, 254, 0.6);
-}
-
-.sub-title {
- font-family: 'MiSans', sans-serif;
- font-weight: 500;
- font-size: 14px;
- color: rgba(255, 255, 255, 0.4);
- letter-spacing: 0.2em;
- text-transform: uppercase;
-}
-
-/* 鍐呭鍖哄煙缁撴瀯 */
-.content-area {
- display: flex;
- flex-direction: column;
- gap: 0;
- flex: 1; /* 鍗犳嵁鍓╀綑绌洪棿 */
- min-height: 0; /* 鍏佽 flex 瀛愬厓绱犳敹缂?*/
- width: 100%; /* 纭繚瀹藉害鍗犳弧 */
-}
-
-/* 閫夐」鍗℃牱寮?- 妗岄潰绔?*/
-.tabs-row {
- display: flex;
- gap: 5px;
- margin-bottom: 0;
- overflow-x: auto;
- white-space: nowrap;
- scrollbar-width: none;
- -ms-overflow-style: none;
- padding-bottom: 2px;
-}
-
-.tabs-row::-webkit-scrollbar {
- display: none;
-}
-
-.section-tab {
- background: #1a1b24;
- border-radius: 15px 15px 0 0;
- padding: 15px 24px;
- font-family: 'MiSans', sans-serif;
- font-weight: 600;
- font-size: 16px;
- color: rgba(255, 255, 255, 0.8);
- border: 2px solid #282830;
- border-bottom: none;
- cursor: pointer;
- flex: 0 0 auto;
-}
-
-.section-tab.active {
- background: #21242d;
- color: #ffffff;
- position: relative;
- z-index: 1;
-}
-
-@keyframes tab-pane-enter {
- 0% {
- opacity: 0;
- transform: translateY(20px);
- }
- 100% {
- opacity: 1;
- transform: translateY(0);
- }
-}
-
-/* 閫氱煡鍒楄〃杩囨浮鍔ㄧ敾 */
-.notification-list-fade-enter-active,
-.notification-list-fade-leave-active {
- transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
-}
-
-.notification-list-fade-enter-from {
- opacity: 0;
- transform: translateY(30px) scale(0.95);
-}
-
-.notification-list-fade-leave-to {
- opacity: 0;
- transform: translateY(-20px) scale(1.02);
-}
-
-/* 閫氱煡椤逛氦閿欒繘鍏ュ姩鐢?*/
-.notification-card {
- animation: notification-item-enter 0.6s cubic-bezier(0.4, 0, 0.2, 1) forwards;
- animation-delay: var(--animation-delay, 0s);
- opacity: 0;
- transform: translateY(20px);
- will-change: transform, opacity;
- transition:
- transform 0.2s cubic-bezier(0.4, 0, 0.2, 1),
- box-shadow 0.2s ease;
-}
-
-.notification-card:hover {
- transform: translateY(-2px) scale(1.02);
- box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
-}
-
-@keyframes notification-item-enter {
- 0% {
- opacity: 0;
- transform: translateY(20px) scale(0.95);
- }
- 100% {
- opacity: 1;
- transform: translateY(0) scale(1);
- }
-}
-
-/* 閫夐」鍗″垏鎹㈠姩鐢?*/
-.section-tab {
- position: relative;
- transition: all 0.3s ease;
- overflow: hidden;
- padding: 0.75rem 1.5rem;
- z-index: 10; /* 纭繚鍦ㄥ唴瀹逛箣涓?*/
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 8px;
-}
-
-.section-tab .tab-icon {
- display: none; /* PC绔粯璁ら殣钘忓浘鏍?*/
-}
-
-.section-tab .icon-wrapper {
- display: none; /* PC绔粯璁ら殣钘忓浘鏍囧鍣紝閬垮厤闂磋窛闂 */
-}
-
-.section-tab .tab-text {
- display: inline;
- position: relative;
-}
-
-/* PC绔€氱煡灏忓渾鐐?*/
-.notification-badge-desktop {
- position: absolute;
- top: -2px;
- right: -8px;
- width: 6px;
- height: 6px;
- background: #0b5afe;
- border-radius: 50%;
- box-shadow: 0 0 5px rgba(11, 90, 254, 0.5);
-}
-
-.section-tab::after {
- content: '';
- position: absolute;
- bottom: 0;
- left: 50%;
- width: 0;
- height: 2px;
- background: #0b5afe;
- transition: all 0.3s ease;
- transform: translateX(-50%);
-}
-
-.section-tab:hover::after {
- width: 50%;
-}
-
-.section-tab.active::after {
- width: 100%;
-}
-
-/* 绉婚櫎涓婃诞鏁堟灉 */
-.section-tab:hover {
- transform: none; /* 绉婚櫎涓婃诞鏁堟灉 */
- background-color: transparent; /* 绉婚櫎鑳屾櫙鑹?*/
- box-shadow: none; /* 绉婚櫎鍐呴槾褰?*/
- color: rgba(255, 255, 255, 0.9);
-}
-
-.section-tab.active:hover {
- background-color: transparent;
- box-shadow: none;
- color: #ffffff;
-}
-
-/* 鍐呭瀹瑰櫒 */
-.tab-content-container {
- background: #1a1b24;
- border: 2px solid #282830;
- border-radius: 0 15px 15px 15px;
- padding: 1.5rem;
- margin-top: -2px; /* 浣垮唴瀹瑰鍣ㄤ笌鏍囩杩炴帴 */
- box-sizing: border-box;
- width: 100%;
- display: flex;
- flex-direction: column;
- flex: 1; /* 鍗犳嵁鍓╀綑绌洪棿 */
- min-height: 0; /* 鍏佽 flex 瀛愬厓绱犳敹缂?*/
- overflow: hidden; /* 闃叉鍐呭婧㈠嚭 */
-}
-
-@media (max-width: 768px) {
- .tab-content-container {
- padding: 1rem;
- width: 100%;
- max-width: 100%;
- box-sizing: border-box;
- }
-}
-
-.tab-pane {
- width: 100%;
- box-sizing: border-box;
- animation: tab-pane-enter 0.45s ease;
-}
-
-/* 閽堝鎺掓湡鏍囩椤电殑鐗规畩鏍峰紡 */
-.schedule-tab-pane {
- width: 100%;
- box-sizing: border-box;
- padding: 0;
- display: flex;
- flex-direction: column;
- flex: 1; /* 鍗犳弧鐖跺鍣ㄩ珮搴?*/
- min-height: 0; /* 鍏佽 flex 瀛愬厓绱犳敹缂?*/
- overflow: hidden; /* 闃叉鍐呭婧㈠嚭 */
-}
-
-.schedule-tab-pane .full-width {
- flex: 1; /* 鍗犳嵁鍓╀綑绌洪棿 */
- display: flex;
- flex-direction: column;
- min-height: 0; /* 鍏佽 flex 瀛愬厓绱犳敹缂?*/
- overflow: hidden; /* 闃叉鍐呭婧㈠嚭 */
-}
-
-@media (max-width: 768px) {
- .tab-pane {
- padding: 0.5rem;
- }
-
- .schedule-tab-pane {
- padding: 0;
- }
-
- /* 绉诲姩绔垎椤垫帶浠舵牱寮?*/
- .pagination-controls {
- flex-direction: column;
- gap: 10px;
- }
-
- .page-size-selector {
- justify-content: center;
- }
-
- .page-navigation {
- justify-content: center;
- flex-wrap: wrap;
- }
-
- .page-numbers {
- flex-wrap: wrap;
- justify-content: center;
- }
-}
-
-.song-list-container {
- width: 100%;
- padding: 1rem 0;
-}
-
-.date-info p {
- font-family: 'MiSans', sans-serif;
- font-weight: 400;
- font-size: 16px;
- letter-spacing: 4%;
-}
-
-/* 姝屾洸鏃舵 */
-.time-label {
- font-family: 'MiSans', sans-serif;
- font-weight: 400;
- font-size: 16px;
- letter-spacing: 4%;
- color: rgba(255, 255, 255, 0.6);
- margin: 1.5rem 0 1rem;
-}
-
-.song-cards {
- display: flex;
- flex-direction: column;
- gap: 1rem;
-}
-
-/* 璇锋眰琛ㄥ崟甯冨眬 */
-.request-pane {
- display: flex;
- gap: 2rem;
-}
-
-/* 绌虹姸鎬?*/
-.empty-state {
- text-align: center;
- padding: 2rem;
- color: rgba(255, 255, 255, 0.6);
-}
-
-/* 涓嬫媺鑿滃崟鍔ㄧ敾 */
-.dropdown-fade-enter-active,
-.dropdown-fade-leave-active {
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
-}
-
-.dropdown-fade-enter-from,
-.dropdown-fade-leave-to {
- opacity: 0;
- transform: translateY(-10px) scale(0.95);
-}
-
-/* 閫氱煡闈㈡澘 */
-.notification-pane {
- height: 100%;
- display: flex;
- flex-direction: column;
-}
-
-.notification-container {
- display: flex;
- flex-direction: column;
- height: 100%;
- width: 100%;
- position: relative;
-}
-
-/* 閫氱煡澶撮儴 */
-.notification-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 1.25rem 0;
- margin-bottom: 1.5rem;
- border-bottom: 1px solid rgba(255, 255, 255, 0.08);
-}
-
-.notification-title {
- font-size: 1.25rem;
- font-weight: 700;
- color: #ffffff;
- margin: 0;
- display: flex;
- align-items: center;
- gap: 0.75rem;
-}
-
-.settings-icon {
- background: rgba(255, 255, 255, 0.05);
- border: 1px solid rgba(255, 255, 255, 0.1);
- color: rgba(255, 255, 255, 0.6);
- width: 36px;
- height: 36px;
- border-radius: 10px;
- display: flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- transition: all 0.2s ease;
-}
-
-.settings-icon:hover {
- background-color: rgba(255, 255, 255, 0.1);
- color: #ffffff;
- transform: rotate(30deg);
-}
-
-/* 閫氱煡鍒楄〃 */
-.notification-list {
- flex: 1;
- display: flex;
- flex-direction: column;
- overflow-y: auto;
- padding: 0.5rem;
- margin-bottom: 1.5rem;
- min-height: 400px;
-}
-
-.loading-indicator,
-.empty-notification {
- flex: 1;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding: 3rem 0;
- color: rgba(255, 255, 255, 0.4);
- gap: 1.25rem;
-}
-
-.empty-icon {
- width: 80px;
- height: 80px;
- background: rgba(255, 255, 255, 0.03);
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- margin-bottom: 0.5rem;
-}
-
-.loading-spinner {
- width: 32px;
- height: 32px;
- border: 3px solid rgba(255, 255, 255, 0.1);
- border-top-color: #3b82f6;
- border-radius: 50%;
- animation: spin 0.8s linear infinite;
-}
-
-@keyframes spin {
- to {
- transform: rotate(360deg);
- }
-}
-
-.notification-items {
- display: flex;
- flex-direction: column;
- gap: 1rem;
-}
-
-.notification-card {
- background: rgba(255, 255, 255, 0.03);
- backdrop-filter: blur(20px);
- border: 1px solid rgba(255, 255, 255, 0.06);
- border-radius: 20px;
- padding: 1.25rem;
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
- position: relative;
- cursor: pointer;
-}
-
-.notification-card:hover {
- background: rgba(255, 255, 255, 0.06);
- border-color: rgba(255, 255, 255, 0.12);
- transform: translateY(-2px);
-}
-
-.notification-card.unread {
- background: rgba(59, 130, 246, 0.05);
- border-color: rgba(59, 130, 246, 0.2);
-}
-
-.notification-card-header {
- display: flex;
- align-items: flex-start;
- margin-bottom: 1rem;
-}
-
-.notification-icon-type {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 40px;
- height: 40px;
- background: rgba(59, 130, 246, 0.1);
- color: #3b82f6;
- border-radius: 12px;
- margin-right: 1rem;
- flex-shrink: 0;
- font-size: 1.25rem;
-}
-
-.notification-title-row {
- flex: 1;
- min-width: 0;
-}
-
-.notification-title {
- font-size: 0.95rem;
- font-weight: 600;
- color: #ffffff;
- margin-bottom: 0.25rem;
-}
-
-.notification-time {
- font-size: 0.75rem;
- color: rgba(255, 255, 255, 0.4);
-}
-
-.notification-card-body {
- padding: 0 0 0 3.5rem;
-}
-
-.notification-text {
- color: rgba(255, 255, 255, 0.7);
- font-size: 0.875rem;
- line-height: 1.6;
-}
-
-.notification-card-actions {
- display: flex;
- justify-content: flex-end;
- margin-top: 1rem;
- padding-left: 3.5rem;
-}
-
-.action-button.delete {
- background: rgba(239, 68, 68, 0.05);
- color: #ef4444;
- border: 1px solid rgba(239, 68, 68, 0.1);
- padding: 0.4rem 0.75rem;
- border-radius: 8px;
- font-size: 0.75rem;
- display: flex;
- align-items: center;
- gap: 0.4rem;
- transition: all 0.2s;
-}
-
-.action-button.delete:hover {
- background: rgba(239, 68, 68, 0.1);
- border-color: rgba(239, 68, 68, 0.2);
-}
-
-.notification-actions-bar {
- display: flex;
- gap: 1rem;
- padding: 1rem 0;
- border-top: 1px solid rgba(255, 255, 255, 0.08);
-}
-
-.action-button-large {
- flex: 1;
- background: rgba(255, 255, 255, 0.05);
- border: 1px solid rgba(255, 255, 255, 0.1);
- color: #ffffff;
- padding: 0.75rem;
- border-radius: 12px;
- font-size: 0.875rem;
- font-weight: 600;
- cursor: pointer;
- transition: all 0.2s;
-}
-
-.action-button-large:hover:not(.disabled) {
- background: rgba(255, 255, 255, 0.1);
-}
-
-.action-button-large.danger {
- color: #ef4444;
-}
-
-.action-button-large.danger:hover {
- background: rgba(239, 68, 68, 0.1);
-}
-
-.action-button-large.disabled {
- opacity: 0.5;
- cursor: not-allowed;
-}
-
-/* 鍒嗛〉鎺т欢鏍峰紡 */
-.notification-pagination {
- padding: 15px 0;
- border-top: 1px solid rgba(255, 255, 255, 0.1);
- border-bottom: 1px solid rgba(255, 255, 255, 0.1);
-}
-
-.pagination-info {
- text-align: center;
- margin-bottom: 15px;
-}
-
-.pagination-text {
- color: rgba(255, 255, 255, 0.7);
- font-size: 0.85rem;
-}
-
-.pagination-controls {
- display: flex;
- justify-content: space-between;
- align-items: center;
- gap: 15px;
-}
-
-.page-size-selector {
- display: flex;
- align-items: center;
- gap: 8px;
-}
-
-.page-size-selector label {
- color: rgba(255, 255, 255, 0.7);
- font-size: 0.85rem;
- white-space: nowrap;
-}
-
-.page-size-custom-select {
- width: 90px;
-}
-
-.page-navigation {
- display: flex;
- align-items: center;
- gap: 8px;
-}
-
-.page-nav-button {
- background-color: rgba(255, 255, 255, 0.1);
- border: 1px solid rgba(255, 255, 255, 0.2);
- color: var(--light);
- width: 32px;
- height: 32px;
- border-radius: 6px;
- display: flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
- will-change: transform, background-color;
-}
-
-.page-nav-button:hover:not(:disabled) {
- background-color: rgba(255, 255, 255, 0.2);
- border-color: rgba(255, 255, 255, 0.3);
- transform: translateY(-1px);
-}
-
-.page-nav-button:active:not(:disabled) {
- transform: translateY(0) scale(0.95);
- transition: all 0.1s ease;
-}
-
-.page-nav-button:disabled {
- background-color: rgba(255, 255, 255, 0.05);
- border-color: rgba(255, 255, 255, 0.1);
- color: rgba(255, 255, 255, 0.3);
- cursor: not-allowed;
-}
-
-.page-numbers {
- display: flex;
- align-items: center;
- gap: 4px;
-}
-
-.page-number-button {
- background-color: rgba(255, 255, 255, 0.1);
- border: 1px solid rgba(255, 255, 255, 0.2);
- color: var(--light);
- width: 32px;
- height: 32px;
- border-radius: 6px;
- display: flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- font-size: 0.85rem;
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
- will-change: transform, background-color;
- position: relative;
- overflow: hidden;
-}
-
-.page-number-button:hover:not(:disabled) {
- background-color: rgba(255, 255, 255, 0.2);
- border-color: rgba(255, 255, 255, 0.3);
- transform: translateY(-1px);
-}
-
-.page-number-button:active:not(:disabled) {
- transform: translateY(0) scale(0.95);
- transition: all 0.1s ease;
-}
-
-.page-number-button.active {
- background-color: var(--primary);
- border-color: var(--primary);
- color: white;
-}
-
-.page-number-button.active:hover {
- background-color: #0952e8;
- border-color: #0952e8;
-}
-
-.page-number-button:disabled {
- background-color: rgba(255, 255, 255, 0.05);
- border-color: rgba(255, 255, 255, 0.1);
- color: rgba(255, 255, 255, 0.3);
- cursor: not-allowed;
-}
-
-.page-ellipsis {
- color: rgba(255, 255, 255, 0.5);
- padding: 0 4px;
- font-size: 0.85rem;
-}
-
-/* 鍒嗛〉鍔犺浇鐘舵€?*/
-.pagination-loading {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 8px;
- padding: 10px 0;
- color: rgba(255, 255, 255, 0.7);
- font-size: 0.85rem;
- animation: fade-in 0.3s ease;
-}
-
-.loading-spinner {
- width: 16px;
- height: 16px;
- border: 2px solid rgba(255, 255, 255, 0.2);
- border-top: 2px solid var(--primary);
- border-radius: 50%;
- animation: spin 0.8s cubic-bezier(0.4, 0, 0.2, 1) infinite;
- will-change: transform;
-}
-
-@keyframes spin {
- 0% {
- transform: rotate(0deg);
- }
- 100% {
- transform: rotate(360deg);
- }
-}
-
-@keyframes fade-in {
- 0% {
- opacity: 0;
- transform: translateY(10px);
- }
- 100% {
- opacity: 1;
- transform: translateY(0);
- }
-}
-
-/* 搴曢儴鎿嶄綔鏍?*/
-.notification-actions-bar {
- display: flex;
- justify-content: center;
- gap: 10px;
- padding: 15px 0;
- border-top: 1px solid rgba(255, 255, 255, 0.1);
-}
-
-.action-button-large {
- background-color: rgba(255, 255, 255, 0.1);
- border: none;
- color: var(--light);
- padding: 8px 16px;
- border-radius: 6px;
- cursor: pointer;
- font-size: 0.9rem;
- transition: all 0.2s ease;
-}
-
-.action-button-large:hover {
- background-color: rgba(255, 255, 255, 0.2);
-}
-
-.action-button-large.danger {
- color: #ef4444;
-}
-
-.action-button-large.danger:hover {
- background-color: rgba(239, 68, 68, 0.2);
-}
-
-.action-button-large.disabled {
- background-color: rgba(255, 255, 255, 0.05);
- color: rgba(255, 255, 255, 0.3);
- cursor: not-allowed;
- opacity: 0.5;
-}
-
-.action-button-large.disabled:hover {
- background-color: rgba(255, 255, 255, 0.05);
-}
-
-/* ==================== 绉诲姩绔璁?==================== */
-
-/* 鍩虹绉诲姩绔€傞厤 */
-@media (max-width: 768px) {
- .home {
- padding: 0;
- background-color: #0a0a0f;
- }
-
- .main-content {
- padding: 0;
- }
-
- /* 闅愯棌鍘熸湁鐨別llipse鏁堟灉锛屼娇鐢ㄦ洿寰鐨勮儗鏅?*/
- .ellipse-effect {
- display: none;
- }
-
- /* 椤堕儴鍖哄煙 */
- .top-bar {
- flex-direction: row;
- justify-content: space-between;
- align-items: center;
- padding: 10px 12px;
- margin: 0;
- background: linear-gradient(180deg, rgba(11, 90, 254, 0.08) 0%, transparent 100%);
- border-bottom: 1px solid rgba(255, 255, 255, 0.05);
- }
-
- .logo-section {
- min-height: auto;
- gap: 8px;
- }
-
- .logo-image {
- width: 76px;
- height: auto;
- }
-
- .logo-divider-container {
- gap: 8px;
- }
-
- .logo-divider {
- height: 28px;
- width: 1px;
- background: rgba(255, 255, 255, 0.15);
- }
-
- /* 绉诲姩绔殑涓婚〉闈㈤噷涓嶉渶瑕佸啓瀛︽牎鍚嶏紝淇濇寔绠€娲?*/
- .site-title {
- display: none;
- }
-
- .school-logo {
- max-width: 120px;
- max-height: 36px;
- }
-
- /* 鐢ㄦ埛鍖哄煙绠€鍖?*/
- .user-section {
- width: auto;
- }
-
- .user-details-desktop {
- display: none; /* 绉诲姩绔粎鏄剧ず澶村儚 */
- }
-
- .user-avatar-wrapper {
- width: 32px;
- height: 32px;
- background: rgba(255, 255, 255, 0.05);
- border-color: rgba(255, 255, 255, 0.1);
- }
-
- .user-avatar-placeholder {
- font-size: 14px;
- }
-
- .user-actions-dropdown {
- top: calc(100% + 10px);
- min-width: 140px;
- padding: 6px;
- }
-
- .action-item {
- padding: 8px 10px;
- font-size: 13px;
- }
-
- /* 鐧诲綍鎸夐挳 */
- .login-options .login-btn {
- display: flex;
- align-items: center;
- gap: 8px;
- padding: 8px 18px;
- border-radius: 12px;
- font-size: 14px;
- font-weight: 600;
- background: rgba(255, 255, 255, 0.08);
- backdrop-filter: blur(10px);
- -webkit-backdrop-filter: blur(10px);
- color: white;
- border: 1px solid rgba(255, 255, 255, 0.1);
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
- }
-
- .login-options .login-btn :deep(.icon) {
- color: #3b82f6;
- }
-
- .login-options .login-btn:active {
- transform: scale(0.95);
- background: rgba(255, 255, 255, 0.15);
- border-color: rgba(59, 130, 246, 0.4);
- }
-
- /* Footer 闂磋窛浼樺寲 */
- :deep(.site-footer) {
- padding-bottom: calc(80px + env(safe-area-inset-bottom, 0px));
- }
-
- /* 鍐呭鍖哄煙 - 鍏ㄥ鏃犺竟妗?*/
- .content-area {
- min-height: auto;
- overflow-x: hidden; /* 闃叉妯悜婧㈠嚭 */
- max-width: 100vw; /* 纭繚涓嶈秴杩囪鍙e搴?*/
- box-sizing: border-box; /* 纭繚padding璁″叆鎬诲搴?*/
- }
-
- .tabs-row {
- position: fixed;
- bottom: calc(1rem + env(safe-area-inset-bottom, 0px));
- left: 1rem;
- right: 1rem;
- margin: 0 auto;
- max-width: 500px;
- display: flex;
- justify-content: space-around;
- align-items: stretch; /* 淇敼涓?stretch 浠ラ厤鍚堝瓙鍏冪礌 height: 100% */
- gap: 0;
- padding: 0 0.5rem;
- height: 64px;
- background: rgba(28, 28, 30, 0.9);
- backdrop-filter: blur(20px) saturate(180%);
- -webkit-backdrop-filter: blur(20px) saturate(180%);
- border: 1px solid rgba(255, 255, 255, 0.1);
- border-radius: 9999px;
- z-index: 1000;
- box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6);
- }
-
- .section-tab {
- flex: 1;
- height: 100%; /* 纭繚濉弧瀹瑰櫒楂樺害 */
- background: transparent;
- border: none;
- border-radius: 0;
- padding: 0; /* 绉婚櫎鍥哄畾鍐呰竟璺濓紝鏀圭敤 flex 灞呬腑 */
- font-size: 10px;
- font-weight: 500;
- color: #71717a; /* text-zinc-500 */
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center; /* 鍨傜洿灞呬腑鍐呭 */
- gap: 4px;
- position: relative;
- transition: all 0.2s ease;
- }
-
- .section-tab .tab-icon {
- display: block;
- margin-bottom: 2px;
- transition: all 0.2s ease;
- color: currentColor;
- }
-
- .section-tab .tab-text {
- font-size: 9px;
- font-weight: 700;
- letter-spacing: 0.02em;
- transition: all 0.2s ease;
- }
-
- .section-tab.active {
- color: #3b82f6 !important; /* text-blue-500 - Force blue */
- background: transparent !important;
- transform: none !important;
- text-shadow: 0 0 12px rgba(59, 130, 246, 0.6); /* Text Glow */
- }
-
- /* Prevent hover from turning it white on mobile */
- .section-tab.active:hover {
- color: #3b82f6 !important;
- background: transparent !important;
- box-shadow: none !important;
- }
-
- .section-tab.active .tab-icon {
- opacity: 1;
- color: currentColor;
- transform: none;
- filter: drop-shadow(0 0 8px rgba(59, 130, 246, 0.5)); /* Icon Glow */
- }
-
- .section-tab.active .tab-text {
- font-weight: 700;
- }
-
- /* 绉婚櫎鍘熸湁鐨勪吉鍏冪礌鍥炬爣 */
- .section-tab::before {
- display: none;
- }
-
- /* 绉婚櫎搴曢儴鎸囩ず鍣紙妯潯锛? 褰诲簳闅愯棌 */
- .section-tab::after {
- display: none !important;
- }
-
- @keyframes dot-pop-in {
- 0% {
- transform: scale(0);
- opacity: 0;
- }
- 100% {
- transform: scale(1);
- opacity: 1;
- }
- }
-
- .icon-wrapper {
- position: relative;
- display: flex;
- align-items: center;
- justify-content: center;
- width: 24px;
- height: 24px;
- margin: 0 auto;
- }
-
- .section-tab .icon-wrapper {
- display: flex; /* 绉诲姩绔樉绀哄浘鏍囧鍣?*/
- }
-
- /* 閫氱煡寰界珷 - 鍥炲綊钃濊壊椋庢牸 */
- .notification-badge-tab {
- position: absolute;
- top: 0;
- right: 0;
- width: 8px;
- height: 8px;
- background: #0b5afe;
- border-radius: 50%;
- border: 1.5px solid #0a0a0f;
- box-shadow: 0 0 5px rgba(11, 90, 254, 0.4);
- z-index: 2;
- }
-
- .notification-badge-desktop {
- display: none; /* 绉诲姩绔殣钘忔闈㈢増寰界珷 */
- }
-
- @keyframes badge-pulse {
- /* 绉婚櫎瀵艰嚧浣嶇Щ鐨勫姩鐢?*/
- }
-
- .section-tab.disabled {
- opacity: 0.3;
- filter: grayscale(1);
- }
-
- /* 鍐呭瀹瑰櫒 */
- .tab-content-container {
- background: transparent;
- border: none;
- border-radius: 0;
- padding: 0 6px calc(80px + env(safe-area-inset-bottom, 0px));
- margin: 0;
- min-height: calc(100vh - 120px);
- }
-
- .tab-pane {
- padding: 0;
- overflow-x: hidden; /* 闃叉妯悜婧㈠嚭 */
- max-width: 100%; /* 纭繚涓嶈秴杩囪鍙e搴?*/
- }
-
- /* 鎺掓湡鏍囩椤典紭鍖?*/
- .schedule-tab-pane {
- padding: 0;
- min-height: auto;
- }
-
- /* 璇锋眰琛ㄥ崟鍖哄煙 */
- .request-pane {
- flex-direction: column;
- gap: 16px;
- overflow-x: hidden; /* 闃叉妯悜婧㈠嚭 */
- max-width: 100%; /* 纭繚涓嶈秴杩囪鍙e搴?*/
- padding: 0 0.5rem; /* 娣诲姞宸﹀彸鍐呰竟璺濓紝闃叉鍐呭璐磋竟 */
- box-sizing: border-box; /* 纭繚padding璁″叆鎬诲搴?*/
- }
-
- /* 鐧诲綍閫夐」 */
- .login-options {
- display: flex;
- align-items: center;
- }
-
- .login-options .btn-outline {
- padding: 6px 14px;
- font-size: 12px;
- border-radius: 6px;
- background: rgba(11, 90, 254, 0.15);
- border: 1px solid rgba(11, 90, 254, 0.3);
- }
-}
-
-/* 灏忓睆骞曡澶囬澶栦紭鍖?*/
-@media (max-width: 480px) {
- .top-bar {
- padding: 8px 10px;
- }
-
- .logo-image {
- width: 90px;
- }
-
- .school-logo {
- max-width: 100px;
- max-height: 32px;
- }
-
- .action-button {
- padding: 5px 8px;
- font-size: 10px;
- }
-
- .site-title {
- margin: 6px 0 10px;
- padding: 0 12px;
- }
-
- .site-title h2 {
- font-size: 16px;
- }
-
- .tab-content-container {
- padding: 0 4px calc(80px + env(safe-area-inset-bottom, 0px));
- }
-}
-
-/* 瓒呭皬灞忓箷璁惧 */
-@media (max-width: 360px) {
- .logo-image {
- width: 60px;
- }
-
- .school-logo {
- max-width: 80px;
- max-height: 28px;
- }
-
- .action-button {
- padding: 4px 6px;
- font-size: 9px;
- }
-
- .section-tab {
- font-size: 9px;
- }
-}
-
-/* 寮圭獥閬僵灞?*/
-.modal-overlay {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: rgba(0, 0, 0, 0.6);
- backdrop-filter: blur(8px);
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 1000;
- animation: overlay-fade-in 0.4s ease-out;
-}
-
-@keyframes overlay-fade-in {
- from {
- opacity: 0;
- }
- to {
- opacity: 1;
- }
-}
-
-/* 寮圭獥鍐呭 */
-.modal-content {
- background: linear-gradient(135deg, #1a1b24 0%, #121318 100%);
- border-radius: 20px;
- border: 1px solid rgba(255, 255, 255, 0.1);
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
- max-width: 420px;
- width: 90%;
- overflow: hidden;
- animation: modal-slide-up 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
-}
-
-@keyframes modal-slide-up {
- from {
- opacity: 0;
- transform: translateY(30px) scale(0.95);
- }
- to {
- opacity: 1;
- transform: translateY(0) scale(1);
- }
-}
-
-/* 寮圭獥澶撮儴 */
-.modal-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 24px 28px 16px;
- border-bottom: 1px solid rgba(255, 255, 255, 0.08);
-}
-
-.modal-header h2 {
- font-family:
- 'MiSans',
- system-ui,
- -apple-system,
- sans-serif;
- font-size: 20px;
- font-weight: 700;
- color: #ffffff;
- margin: 0;
- letter-spacing: 0.02em;
-}
-
-/* 鍏抽棴鎸夐挳 */
-.close-button {
- width: 32px;
- height: 32px;
- border-radius: 50%;
- background: rgba(255, 255, 255, 0.08);
- border: none;
- color: rgba(255, 255, 255, 0.6);
- font-size: 24px;
- line-height: 1;
- cursor: pointer;
- transition: all 0.2s ease;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.close-button:hover {
- background: rgba(255, 255, 255, 0.15);
- color: #ffffff;
- transform: rotate(90deg);
-}
-
-/* 寮圭獥涓讳綋 */
-.modal-body {
- padding: 24px 28px 28px;
-}
-
-.rules-container {
- display: flex;
- flex-direction: column;
- gap: 1.5rem;
-}
-
-.rules-group {
- display: flex;
- flex-direction: column;
- gap: 0.75rem;
-}
-
-.rules-subtitle {
- display: flex;
- align-items: center;
- gap: 0.5rem;
- font-size: 16px;
- font-weight: 700;
- color: #ffffff;
- margin: 0;
-}
-
-.rules-icon {
- color: #3b82f6;
-}
-
-.rules-text {
- font-size: 14px;
- color: rgba(255, 255, 255, 0.6);
- line-height: 1.6;
- margin: 0;
-}
-
-.guidelines-content {
- font-size: 14px;
- color: rgba(255, 255, 255, 0.6);
- line-height: 1.6;
-}
-
-.default-rules {
- display: flex;
- flex-direction: column;
- gap: 0.5rem;
-}
-
-.rule-item {
- display: flex;
- font-size: 14px;
- color: rgba(255, 255, 255, 0.6);
- line-height: 1.5;
-}
-
-.rule-item span {
- margin-right: 0.5rem;
- color: rgba(255, 255, 255, 0.3);
- font-weight: 600;
-}
-
-/* 骞村害鎶ュ憡寮圭獥 */
-.year-review-overlay {
- backdrop-filter: blur(8px);
- background: rgba(0, 0, 0, 0.4);
-}
-
-.year-review-card {
- position: relative;
- width: 90%;
- max-width: 400px;
- background: #12121a;
- border-radius: 32px;
- overflow: hidden;
- border: 1px solid rgba(255, 255, 255, 0.1);
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
- animation: card-appear 0.6s cubic-bezier(0.16, 1, 0.3, 1);
-}
-
-.card-glow {
- position: absolute;
- top: -50%;
- left: -50%;
- width: 200%;
- height: 200%;
- background: radial-gradient(circle, rgba(139, 92, 246, 0.15) 0%, transparent 70%);
- pointer-events: none;
-}
-
-.card-pattern {
- position: absolute;
- inset: 0;
- background-image: radial-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px);
- background-size: 20px 20px;
- opacity: 0.5;
-}
-
-.card-close {
- position: absolute;
- top: 20px;
- right: 20px;
- width: 36px;
- height: 36px;
- border-radius: 12px;
- background: rgba(255, 255, 255, 0.05);
- border: 1px solid rgba(255, 255, 255, 0.1);
- color: rgba(255, 255, 255, 0.4);
- display: flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- z-index: 10;
- transition: all 0.2s ease;
-}
-
-.card-close:hover {
- background: rgba(255, 255, 255, 0.1);
- color: #fff;
- transform: translateY(2px);
-}
-
-.card-content {
- position: relative;
- padding: 40px 32px;
- text-align: center;
- z-index: 5;
-}
-
-.brand-badge {
- display: inline-block;
- padding: 4px 12px;
- background: rgba(139, 92, 246, 0.1);
- border: 1px solid rgba(139, 92, 246, 0.2);
- border-radius: 99px;
- color: #a78bfa;
- font-size: 12px;
- font-weight: 700;
- letter-spacing: 0.1em;
- margin-bottom: 32px;
-}
-
-.visual-container {
- position: relative;
- height: 120px;
- display: flex;
- align-items: center;
- justify-content: center;
- margin-bottom: 24px;
-}
-
-.main-icon {
- width: 80px;
- height: 80px;
- background: linear-gradient(135deg, #8b5cf6 0%, #6d28d9 100%);
- border-radius: 24px;
- display: flex;
- align-items: center;
- justify-content: center;
- color: #fff;
- box-shadow: 0 10px 25px -5px rgba(139, 92, 246, 0.5);
- z-index: 2;
- transform: rotate(-5deg);
-}
-
-.music-bars {
- position: absolute;
- display: flex;
- align-items: flex-end;
- gap: 4px;
- height: 40px;
- opacity: 0.3;
-}
-
-.bar {
- width: 4px;
- background: #8b5cf6;
- border-radius: 2px;
- animation: bar-dance 1.2s ease-in-out infinite;
-}
-
-.bar:nth-child(1) {
- height: 20px;
- animation-delay: 0.1s;
-}
-.bar:nth-child(2) {
- height: 35px;
- animation-delay: 0.3s;
-}
-.bar:nth-child(3) {
- height: 25px;
- animation-delay: 0.2s;
-}
-.bar:nth-child(4) {
- height: 40px;
- animation-delay: 0.4s;
-}
-.bar:nth-child(5) {
- height: 30px;
- animation-delay: 0.2s;
-}
-
-@keyframes bar-dance {
- 0%,
- 100% {
- transform: scaleY(1);
- }
- 50% {
- transform: scaleY(0.6);
- }
-}
-
-.card-title {
- font-size: 24px;
- font-weight: 800;
- color: #fff;
- margin-bottom: 12px;
- letter-spacing: -0.01em;
-}
-
-.card-description {
- font-size: 15px;
- color: rgba(255, 255, 255, 0.5);
- line-height: 1.6;
- margin-bottom: 32px;
-}
-
-.card-actions {
- display: flex;
- flex-direction: column;
- gap: 12px;
-}
-
-.btn-primary {
- width: 100%;
- padding: 16px;
- background: #fff;
- border: none;
- border-radius: 16px;
- color: #000;
- font-size: 16px;
- font-weight: 700;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 8px;
- cursor: pointer;
- transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1);
-}
-
-.btn-primary:hover {
- transform: translateY(-2px);
- box-shadow: 0 10px 20px rgba(255, 255, 255, 0.1);
- background: #f4f4f4;
-}
-
-.btn-primary:active {
- transform: translateY(0);
-}
-
-.btn-secondary {
- width: 100%;
- padding: 14px;
- background: transparent;
- border: 1px solid rgba(255, 255, 255, 0.1);
- border-radius: 16px;
- color: rgba(255, 255, 255, 0.6);
- font-size: 14px;
- font-weight: 600;
- cursor: pointer;
- transition: all 0.2s ease;
-}
-
-.btn-secondary:hover {
- background: rgba(255, 255, 255, 0.05);
- color: #fff;
- border-color: rgba(255, 255, 255, 0.2);
-}
-
-@keyframes card-appear {
- from {
- opacity: 0;
- transform: translateY(30px) scale(0.95);
- }
- to {
- opacity: 1;
- transform: translateY(0) scale(1);
- }
-}
-
-/* 瑕嗙洊鏃у姩鐢?*/
-.modal-animation-enter-active,
-.modal-animation-leave-active {
- transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1);
-}
-
-.modal-animation-enter-from,
-.modal-animation-leave-to {
- opacity: 0;
- backdrop-filter: blur(0);
-}
-
-.modal-animation-enter-from .year-review-card,
-.modal-animation-leave-to .year-review-card {
- transform: translateY(40px) scale(0.9);
-}
-
-/* 娉㈢汗鏁堟灉 */
-.section-tab {
- position: relative;
- overflow: hidden;
- transition: all 0.3s ease;
- cursor: pointer;
-}
-
-.ripple-effect {
- position: absolute;
- border-radius: 50%;
- background-color: rgba(255, 255, 255, 0.3);
- transform: scale(0);
- animation: ripple 0.6s linear;
- pointer-events: none;
- width: 100px;
- height: 100px;
- margin-left: -50px; /* 灞呬腑瀹氫綅 */
- margin-top: -50px; /* 灞呬腑瀹氫綅 */
-}
-
-@keyframes ripple {
- to {
- transform: scale(4);
- opacity: 0;
- }
-}
-
-/* 纭繚鍏ㄥ鏄剧ず */
-.full-width,
-.full-width > div {
- width: 100% !important;
- max-width: 100% !important;
- box-sizing: border-box !important;
- padding: 0 !important;
- margin: 0 !important;
- display: block !important;
-}
-
-/* 閫氱煡鏍囩涓婄殑鏈寰界珷 */
-.notification-badge-tab {
- position: absolute; /* 淇敼涓虹粷瀵瑰畾浣嶏紝闃叉鎸ゅ帇鍥炬爣 */
- top: 0;
- right: 0;
- width: 8px;
- height: 8px;
- border-radius: 50%;
- background-color: #0b5afe;
- display: inline-block;
- z-index: 2;
-}
-
-@keyframes pulse {
- 0% {
- transform: scale(1);
- box-shadow: 0 0 0 0 rgba(0, 122, 255, 0.4);
- }
- 70% {
- transform: scale(1.05);
- box-shadow: 0 0 0 5px rgba(0, 122, 255, 0);
- }
- 100% {
- transform: scale(1);
- box-shadow: 0 0 0 0 rgba(0, 122, 255, 0);
- }
-}
-
-/* 绂佺敤鐨勬爣绛鹃〉鏍峰紡 */
-.section-tab.disabled {
- opacity: 0.6;
- cursor: not-allowed;
-}
-
-/* 鏈櫥褰曟彁绀烘牱寮?*/
-.login-required-container {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- height: 100%;
- padding: 40px 0;
-}
-
-.login-required-content {
- text-align: center;
- max-width: 400px;
- padding: 30px;
- background-color: rgba(30, 41, 59, 0.5);
- border-radius: 12px;
- border: 1px solid rgba(255, 255, 255, 0.1);
- box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2);
-}
-
-.login-icon {
- font-size: 3rem;
- margin-bottom: 15px;
-}
-
-.login-required-content h3 {
- font-size: 1.5rem;
- margin-bottom: 10px;
- color: var(--light);
-}
-
-.login-required-content p {
- margin-bottom: 20px;
- color: rgba(255, 255, 255, 0.7);
-}
-
-.login-button {
- background: linear-gradient(180deg, #0043f8 0%, #0075f8 100%);
- border: none;
- color: white;
- padding: 10px 20px;
- border-radius: 6px;
- font-weight: 600;
- cursor: pointer;
- transition: all 0.2s ease;
-}
-
-.login-button:hover {
- transform: translateY(-2px);
- box-shadow: 0 5px 15px rgba(0, 67, 248, 0.3);
-}
-</style>
diff --git a/old-nuxt.config.ts b/old-nuxt.config.ts
deleted file mode 100644
index b5c3581..0000000
--- a/old-nuxt.config.ts
+++ /dev/null
@@ -1,447 +0,0 @@
-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<string, unknown> | 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<string, unknown>
- 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
deleted file mode 100644
index 41bc534..0000000
--- a/old-nuxt2.config.ts
+++ /dev/null
@@ -1,433 +0,0 @@
-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<string, unknown> | 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<string, unknown>
- 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
deleted file mode 100644
index 3fe2829..0000000
--- a/old-variables.css
+++ /dev/null
@@ -1,556 +0,0 @@
-/* 鍏ㄥ眬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
deleted file mode 100644
index 3fe2829..0000000
--- a/old-variables2.css
+++ /dev/null
@@ -1,556 +0,0 @@
-/* 鍏ㄥ眬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;
-}
--
2.45.1.windows.1