3662 lines
98 KiB
Vue
3662 lines
98 KiB
Vue
<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>
|