feat(admin-ui): 管理后台整体切换为浅色主题(ModernLight) + 新增 lightbulb 图标

1. layout(管理控制台 dashboard.vue): main bg-[#09090b] text-zinc-100 → bg-[#f8fafc] text-zinc-900; header bg-zinc-950/60 border-zinc-800 → bg-white/80 border-zinc-200; 移动菜单按钮/新手引导按钮 text-zinc-300/400 hover:bg-zinc-800 → text-zinc-600 hover:bg-zinc-100 border-zinc-200

2. Admin/Sidebar.vue: bg-[#09090b] border-zinc-800 → bg-white border-zinc-200 + shadow-sm; 标题 text-zinc-100 → 900; activeTab bg-blue-600/10 text-blue-400 → bg-blue-50 text-blue-700 border-blue-200; 未选中 text-zinc-500 hover:text-zinc-200 hover:bg-zinc-800/40 → text-zinc-600 hover:text-zinc-900 hover:bg-zinc-100; 用户卡 avatar/首字母 bg-white/70 border-zinc-200 + 退出按钮 hover:bg-red-50 text-red-600

3. 批量 33 个 Admin 组件硬编码深色原子类 1414 处替换(bg-zinc-9xx / border-zinc-8xx / text-zinc-100,200,300 / shadow-black/ -> 对应 light 版本) + style 块原生 CSS rgba(255,255,255,x) 16 处改为 rgba(0,0,0,x) / var 浅色主题 + 硬编码 bg-[#0c0c0e] / border-white/10 / color:white / surface-primary fallback 对应浅色修正

4. 二轮批量追加: bg-zinc-950 + opacity 变体(80/60/50/40/30/20/10) 共 431 处 → bg-white 对应 opacity 版本;placeholder:text-zinc-800 → 500;text-blue/red/emerald/amber/rose/purple 400 版 → 700/600 深浅一致版;border color /500 /30 → 200;focus:ring /20 → /30;shadow color 阴影降低强度

5. hover:text-white 扫尾: 13 处 DataAnalysisPanel/DatabaseManager/NotificationSender/SemesterManager/SongManagement/UserSongsModal hover 文本从 white → zinc-900;按钮三态从 hover:bg-*600 白字 → hover:bg-*-100/200 对应深字色,和前台 indigo/浅色体系保持一致

6. BlacklistManager 前台可见提示 💡→ <Icon lightbulb amber-600 size=13>;ChangelogManager 徽章色从 emerald-300/blue-300/zinc-400 → emerald-700/blue-700/zinc-600 可读;Icon.vue 新增 lightbulb 分支 stroke 图标

7. 受影响文件: dashboard.vue + Sidebar.vue + Icon.vue + Admin/* (33个组件共 1874 处批量替换 + 20 处手工精细化修正)
This commit is contained in:
2026-09-19 08:30:59 +08:00
parent 357876a716
commit 573e168702
36 changed files with 1023 additions and 1017 deletions
+47 -47
View File
@@ -6,7 +6,7 @@
class="flex flex-col items-center justify-center min-h-[60vh] space-y-6"
>
<div class="relative w-24 h-24">
<div class="absolute inset-0 border-4 border-blue-500/20 rounded-full" />
<div class="absolute inset-0 border-4 border-blue-200 rounded-full" />
<div class="absolute inset-0 border-4 border-t-blue-500 rounded-full animate-spin" />
<div class="absolute inset-0 flex items-center justify-center">
<Activity class="text-blue-500 animate-pulse" :size="32" />
@@ -32,7 +32,7 @@
<h3 class="text-xl font-black text-white tracking-tight">数据加载失败</h3>
<p class="text-sm text-zinc-500 mt-2 max-w-md">{{ error }}</p>
<button
class="mt-6 px-8 py-3 bg-zinc-900 border border-zinc-800 rounded-full text-sm font-black text-white hover:bg-zinc-800 transition-all flex items-center gap-2 mx-auto"
class="mt-6 px-8 py-3 bg-white border border-zinc-200 rounded-full text-sm font-black text-white hover:bg-zinc-100 transition-all flex items-center gap-2 mx-auto"
@click="refreshAllData"
>
<RefreshCw :size="16" />
@@ -52,16 +52,16 @@
<div class="flex items-center gap-3">
<button
:disabled="isLoading"
class="p-2.5 bg-zinc-900/50 border border-zinc-800 rounded-full text-zinc-400 hover:text-white transition-all group disabled:opacity-50"
class="p-2.5 bg-zinc-50/50 border border-zinc-200 rounded-full text-zinc-400 hover:text-zinc-900 transition-all group disabled:opacity-50"
@click="refreshAllData"
>
<RefreshCw :size="18" :class="{ 'animate-spin': isLoading }" />
</button>
<div
class="px-3 py-1.5 bg-blue-500/10 border border-blue-500/20 rounded-full flex items-center gap-2"
class="px-3 py-1.5 bg-blue-500/10 border border-blue-200 rounded-full flex items-center gap-2"
>
<div class="w-2 h-2 bg-blue-500 rounded-full animate-pulse" />
<span class="text-[10px] font-black text-blue-400 uppercase tracking-widest"
<span class="text-[10px] font-black text-blue-700 uppercase tracking-widest"
>实时模式</span
>
</div>
@@ -82,11 +82,11 @@
<div
v-for="(stat, i) in kpiStats"
:key="i"
class="group relative p-6 bg-zinc-900/40 border border-zinc-800/60 rounded-[2rem] overflow-hidden hover:border-zinc-700 transition-all hover:shadow-2xl hover:shadow-black/40"
class="group relative p-6 bg-zinc-50/40 border border-zinc-200/60 rounded-[2rem] overflow-hidden hover:border-zinc-200 transition-all hover:shadow-2xl hover:shadow-black/40"
>
<div class="flex justify-between items-start">
<div
:class="`p-3 rounded-2xl bg-zinc-950 border border-zinc-800 text-zinc-400 group-hover:text-${stat.color}-400 group-hover:border-${stat.color}-500/30 transition-all`"
:class="`p-3 rounded-2xl bg-white border border-zinc-200 text-zinc-400 group-hover:text-${stat.color}-400 group-hover:border-${stat.color}-500/30 transition-all`"
>
<component :is="stat.icon" :size="20" />
</div>
@@ -103,7 +103,7 @@
</div>
</div>
<div class="mt-4">
<h4 class="text-3xl font-black text-zinc-100 tracking-tighter">
<h4 class="text-3xl font-black text-zinc-900 tracking-tighter">
{{ formatNumber(stat.value) }}
</h4>
<p class="text-[10px] font-black text-zinc-600 uppercase tracking-[0.2em] mt-1">
@@ -122,7 +122,7 @@
<!-- 实时脉冲部分 -->
<div class="lg:col-span-12 grid grid-cols-1 md:grid-cols-2 gap-6">
<div
class="p-8 bg-zinc-900/20 border border-zinc-800/40 rounded-3xl flex items-center justify-between relative overflow-hidden group cursor-help"
class="p-8 bg-zinc-50/20 border border-zinc-200 rounded-3xl flex items-center justify-between relative overflow-hidden group cursor-help"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
>
@@ -142,7 +142,7 @@
</div>
</div>
<div
class="p-8 bg-zinc-900/20 border border-zinc-800/40 rounded-3xl flex items-center justify-between relative overflow-hidden group"
class="p-8 bg-zinc-50/20 border border-zinc-200 rounded-3xl flex items-center justify-between relative overflow-hidden group"
>
<div class="relative z-10">
<span class="text-[10px] font-black text-zinc-600 uppercase tracking-widest"
@@ -166,7 +166,7 @@
<!-- 趋势分析图表卡片 -->
<div
class="lg:col-span-8 bg-zinc-900/40 border border-zinc-800 rounded-[3rem] p-8 shadow-2xl overflow-hidden flex flex-col min-h-[500px]"
class="lg:col-span-8 bg-zinc-50/40 border border-zinc-200 rounded-[3rem] p-8 shadow-2xl overflow-hidden flex flex-col min-h-[500px]"
>
<div class="flex items-center justify-between mb-10">
<div>
@@ -196,7 +196,7 @@
class="flex-1 flex flex-col items-center justify-center space-y-4"
>
<div
class="w-12 h-12 border-4 border-blue-500/20 border-t-blue-500 rounded-full animate-spin"
class="w-12 h-12 border-4 border-blue-200 border-t-blue-500 rounded-full animate-spin"
/>
<p class="text-xs font-black text-zinc-600 uppercase tracking-widest">
正在加载趋势数据...
@@ -213,7 +213,7 @@
{{ panelStates.trends.error }}
</p>
<button
class="px-6 py-2 bg-zinc-800 hover:bg-zinc-700 text-white text-[10px] font-black uppercase tracking-widest rounded-full transition-all"
class="px-6 py-2 bg-zinc-100 hover:bg-zinc-200 text-white text-[10px] font-black uppercase tracking-widest rounded-full transition-all"
@click="loadTrends"
>
立即重试
@@ -233,10 +233,10 @@
:style="{
height: `${(item.count / Math.max(...trendData.map((d) => d.count), 1)) * 100}%`
}"
class="w-full bg-gradient-to-t from-blue-600/10 to-blue-500/40 rounded-t-xl group-hover:from-blue-600/30 group-hover:to-blue-400 transition-all border-x border-t border-blue-500/20 group-hover:border-blue-500/40 min-h-[4px]"
class="w-full bg-gradient-to-t from-blue-600/10 to-blue-500/40 rounded-t-xl group-hover:from-blue-600/30 group-hover:to-blue-400 transition-all border-x border-t border-blue-200 group-hover:border-blue-500/40 min-h-[4px]"
/>
<div
class="absolute left-1/2 -translate-x-1/2 -translate-y-full mb-2 text-[10px] font-black text-blue-400 opacity-0 group-hover:opacity-100 transition-all whitespace-nowrap pointer-events-none z-10"
class="absolute left-1/2 -translate-x-1/2 -translate-y-full mb-2 text-[10px] font-black text-blue-700 opacity-0 group-hover:opacity-100 transition-all whitespace-nowrap pointer-events-none z-10"
:style="{
bottom: `${(item.count / Math.max(...trendData.map((d) => d.count), 1)) * 100}%`
}"
@@ -259,7 +259,7 @@
<!-- 热门歌曲排行榜 -->
<div
class="lg:col-span-4 bg-zinc-900/40 border border-zinc-800 rounded-[3rem] p-8 flex flex-col min-h-[500px]"
class="lg:col-span-4 bg-zinc-50/40 border border-zinc-200 rounded-[3rem] p-8 flex flex-col min-h-[500px]"
>
<div class="flex items-center justify-between mb-8">
<h3 class="text-xl font-bold flex items-center gap-3 text-white">
@@ -292,7 +292,7 @@
v-if="panelStates.topSongs.loading && topSongs.length === 0"
class="flex-1 space-y-4"
>
<div v-for="i in 5" :key="i" class="h-20 bg-zinc-800/20 animate-pulse rounded-2xl" />
<div v-for="i in 5" :key="i" class="h-20 bg-zinc-100/20 animate-pulse rounded-2xl" />
</div>
<div
v-else-if="panelStates.topSongs.error && topSongs.length === 0"
@@ -303,7 +303,7 @@
{{ panelStates.topSongs.error }}
</p>
<button
class="px-4 py-2 bg-zinc-800 text-[10px] font-black uppercase rounded-full hover:bg-zinc-700 text-white transition-colors"
class="px-4 py-2 bg-zinc-100 text-[10px] font-black uppercase rounded-full hover:bg-zinc-200 text-white transition-colors"
@click="loadTopSongs"
>
重试
@@ -316,7 +316,7 @@
<div
v-for="(song, i) in topSongs.slice(0, 5)"
:key="i"
class="p-4 bg-zinc-950/50 border border-zinc-800/40 rounded-2xl flex items-center gap-4 group hover:bg-zinc-800/30 transition-all"
class="p-4 bg-white/70 border border-zinc-200 rounded-2xl flex items-center gap-4 group hover:bg-zinc-100/30 transition-all"
>
<div
:class="`w-8 h-8 rounded-lg flex items-center justify-center font-black text-xs ${
@@ -326,14 +326,14 @@
? 'bg-zinc-300 text-black'
: i === 2
? 'bg-amber-800 text-white'
: 'text-zinc-600 border border-zinc-800'
: 'text-zinc-600 border border-zinc-200'
}`"
>
{{ i + 1 }}
</div>
<div class="flex-1 min-w-0">
<h4
class="text-sm font-bold text-zinc-200 truncate group-hover:text-white transition-colors"
class="text-sm font-bold text-zinc-700 truncate group-hover:text-zinc-900 transition-colors"
>
{{ song.title }}
</h4>
@@ -360,7 +360,7 @@
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<!-- 活跃用户排行榜 -->
<div
class="bg-zinc-900/40 border border-zinc-800 rounded-[3rem] p-8 flex flex-col min-h-[500px]"
class="bg-zinc-50/40 border border-zinc-200 rounded-[3rem] p-8 flex flex-col min-h-[500px]"
>
<div class="flex items-center justify-between mb-8">
<h3 class="text-xl font-bold flex items-center gap-3 text-white">
@@ -376,7 +376,7 @@
v-if="panelStates.activeUsers.loading && activeUsers.length === 0"
class="flex-1 space-y-4"
>
<div v-for="i in 4" :key="i" class="h-24 bg-zinc-800/20 animate-pulse rounded-3xl" />
<div v-for="i in 4" :key="i" class="h-24 bg-zinc-100/20 animate-pulse rounded-3xl" />
</div>
<div
v-else-if="panelStates.activeUsers.error && activeUsers.length === 0"
@@ -387,7 +387,7 @@
{{ panelStates.activeUsers.error }}
</p>
<button
class="px-4 py-2 bg-zinc-800 text-[10px] font-black uppercase rounded-full hover:bg-zinc-700 text-white transition-colors"
class="px-4 py-2 bg-zinc-100 text-[10px] font-black uppercase rounded-full hover:bg-zinc-200 text-white transition-colors"
@click="loadActiveUsers"
>
重试
@@ -401,31 +401,31 @@
<div
v-for="(user, i) in activeUsers.slice(0, 5)"
:key="i"
class="relative p-5 bg-zinc-950/30 border border-zinc-800/50 rounded-3xl overflow-hidden group"
class="relative p-5 bg-white/60 border border-zinc-200 rounded-3xl overflow-hidden group"
>
<div class="flex items-center gap-4 relative z-10">
<img
v-if="user.avatarUrl && !failedAvatars[user.id]"
:src="user.avatarUrl"
:alt="user.name"
class="w-12 h-12 rounded-2xl object-cover border border-zinc-700/50 shrink-0"
class="w-12 h-12 rounded-2xl object-cover border border-zinc-200 shrink-0"
@error="handleAvatarError($event, user)"
>
<div
v-else
class="w-12 h-12 rounded-2xl bg-zinc-800 flex items-center justify-center font-black text-zinc-500 group-hover:text-zinc-200 transition-colors shrink-0"
class="w-12 h-12 rounded-2xl bg-zinc-100 flex items-center justify-center font-black text-zinc-500 group-hover:text-zinc-700 transition-colors shrink-0"
>
{{ user.name.charAt(0) }}
</div>
<div class="flex-1 min-w-0">
<h4 class="text-sm font-bold text-zinc-100 truncate">{{ user.name }}</h4>
<h4 class="text-sm font-bold text-zinc-900 truncate">{{ user.name }}</h4>
<p class="text-xs text-zinc-600 font-medium mt-1 truncate">
{{ user.contributions }}首投稿 · {{ user.likes }}次点赞
</p>
</div>
<div class="text-right shrink-0">
<span
class="text-xl font-black text-zinc-300 group-hover:text-purple-400 transition-colors"
class="text-xl font-black text-zinc-600 group-hover:text-purple-400 transition-colors"
>{{ user.activityScore }}</span
>
<p class="text-[10px] font-black text-zinc-700 uppercase tracking-widest">
@@ -451,7 +451,7 @@
<!-- 学期对比分析 -->
<div
class="bg-zinc-900/40 border border-zinc-800 rounded-[3rem] p-8 flex flex-col min-h-[500px]"
class="bg-zinc-50/40 border border-zinc-200 rounded-[3rem] p-8 flex flex-col min-h-[500px]"
>
<div class="flex items-center justify-between mb-8">
<h3 class="text-xl font-bold flex items-center gap-3 text-white">
@@ -470,7 +470,7 @@
v-if="panelStates.semesterComparison.loading && semesterComparison.length === 0"
class="flex-1 space-y-4"
>
<div v-for="i in 3" :key="i" class="h-32 bg-zinc-800/20 animate-pulse rounded-[2rem]" />
<div v-for="i in 3" :key="i" class="h-32 bg-zinc-100/20 animate-pulse rounded-[2rem]" />
</div>
<div
v-else-if="panelStates.semesterComparison.error && semesterComparison.length === 0"
@@ -481,7 +481,7 @@
{{ panelStates.semesterComparison.error }}
</p>
<button
class="px-4 py-2 bg-zinc-800 text-[10px] font-black uppercase rounded-full hover:bg-zinc-700 text-white transition-colors"
class="px-4 py-2 bg-zinc-100 text-[10px] font-black uppercase rounded-full hover:bg-zinc-200 text-white transition-colors"
@click="loadSemesterComparison"
>
重试
@@ -494,10 +494,10 @@
<div
v-for="(sem, i) in semesterComparison"
:key="i"
:class="`p-6 border rounded-[2rem] transition-all ${sem.isActive ? 'bg-emerald-500/5 border-emerald-500/20 shadow-lg shadow-emerald-500/5' : 'bg-zinc-950/20 border-zinc-800/60 opacity-60 hover:opacity-100'}`"
:class="`p-6 border rounded-[2rem] transition-all ${sem.isActive ? 'bg-emerald-500/5 border-emerald-500/20 shadow-lg shadow-emerald-900/5' : 'bg-white/50 border-zinc-200/60 opacity-60 hover:opacity-100'}`"
>
<div class="flex items-center justify-between mb-4">
<span class="text-xs font-black text-zinc-300 uppercase tracking-widest">{{
<span class="text-xs font-black text-zinc-600 uppercase tracking-widest">{{
sem.semester
}}</span>
<span
@@ -509,19 +509,19 @@
</div>
<div class="grid grid-cols-3 gap-4">
<div>
<h5 class="text-lg font-black text-zinc-100">{{ sem.totalSongs }}</h5>
<h5 class="text-lg font-black text-zinc-900">{{ sem.totalSongs }}</h5>
<p class="text-[9px] font-black text-zinc-600 uppercase tracking-tighter mt-1">
歌曲总量
</p>
</div>
<div>
<h5 class="text-lg font-black text-zinc-100">{{ sem.totalSchedules }}</h5>
<h5 class="text-lg font-black text-zinc-900">{{ sem.totalSchedules }}</h5>
<p class="text-[9px] font-black text-zinc-600 uppercase tracking-tighter mt-1">
排期总数
</p>
</div>
<div>
<h5 class="text-lg font-black text-zinc-100">{{ sem.totalRequests }}</h5>
<h5 class="text-lg font-black text-zinc-900">{{ sem.totalRequests }}</h5>
<p class="text-[9px] font-black text-zinc-600 uppercase tracking-tighter mt-1">
获赞总数
</p>
@@ -547,16 +547,16 @@
@mouseleave="handleTooltipMouseLeave"
>
<div
class="bg-zinc-900 border border-zinc-800 rounded-3xl shadow-2xl overflow-hidden min-w-[320px] max-w-[380px] backdrop-blur-xl bg-opacity-90 animate-in fade-in zoom-in duration-200"
class="bg-white border border-zinc-200 rounded-3xl shadow-2xl overflow-hidden min-w-[320px] max-w-[380px] backdrop-blur-xl bg-opacity-90 animate-in fade-in zoom-in duration-200"
>
<div
class="p-6 border-b border-zinc-800/50 bg-gradient-to-br from-blue-500/10 to-transparent max-h-[500px] overflow-y-auto custom-scrollbar"
class="p-6 border-b border-zinc-200 bg-gradient-to-br from-blue-500/10 to-transparent max-h-[500px] overflow-y-auto custom-scrollbar"
>
<div class="flex items-center justify-between mb-4">
<h4 class="text-sm font-black text-white uppercase tracking-widest truncate max-h-[20px] overflow-hidden">活跃用户详情</h4>
<div class="flex items-center gap-2 px-2 py-1 bg-blue-500/20 rounded-full shrink-0">
<div class="w-1.5 h-1.5 bg-blue-500 rounded-full animate-pulse" />
<span class="text-[10px] font-black text-blue-400"
<span class="text-[10px] font-black text-blue-700"
>{{ realtimeStats.activeUsers }} 在线</span
>
</div>
@@ -569,28 +569,28 @@
<div
v-for="user in realtimeStats.activeUsersList.slice(0, 5)"
:key="user.id"
class="flex items-center gap-3 p-3 bg-zinc-950/50 border border-zinc-800/50 rounded-2xl group hover:border-blue-500/30 transition-all"
class="flex items-center gap-3 p-3 bg-white/70 border border-zinc-200 rounded-2xl group hover:border-blue-200 transition-all"
>
<img
v-if="user.avatarUrl && !failedAvatars[user.id]"
:src="user.avatarUrl"
:alt="user.name"
class="w-10 h-10 rounded-xl object-cover border border-zinc-700/50 shrink-0"
class="w-10 h-10 rounded-xl object-cover border border-zinc-200 shrink-0"
@error="handleAvatarError($event, user)"
>
<div
v-else
class="w-10 h-10 rounded-xl bg-zinc-800 flex items-center justify-center font-black text-zinc-500 group-hover:text-blue-400 transition-colors shrink-0"
class="w-10 h-10 rounded-xl bg-zinc-100 flex items-center justify-center font-black text-zinc-500 group-hover:text-blue-700 transition-colors shrink-0"
>
{{ user.name.charAt(0) }}
</div>
<div class="flex-1 min-w-0">
<div class="text-xs font-bold text-zinc-200 truncate max-h-[16px] overflow-hidden">{{ user.name }}</div>
<div class="text-xs font-bold text-zinc-700 truncate max-h-[16px] overflow-hidden">{{ user.name }}</div>
<div class="text-[10px] text-zinc-500 font-medium truncate max-h-[14px] overflow-hidden">
@{{ user.username }}
</div>
</div>
<div class="text-[10px] font-black text-zinc-600 bg-zinc-900 px-2 py-1 rounded-lg shrink-0">
<div class="text-[10px] font-black text-zinc-600 bg-white px-2 py-1 rounded-lg shrink-0">
{{ user.lastActive }}
</div>
</div>
@@ -605,7 +605,7 @@
<p class="text-xs font-black uppercase tracking-widest truncate max-h-[16px] overflow-hidden">暂无在线用户</p>
</div>
</div>
<div class="px-6 py-4 bg-zinc-950/50 flex items-center justify-between">
<div class="px-6 py-4 bg-white/70 flex items-center justify-between">
<span class="text-[10px] font-black text-zinc-600 uppercase tracking-widest truncate max-h-[14px] overflow-hidden"
>实时数据同步中</span
>
@@ -1147,7 +1147,7 @@ const formatDateShort = (dateStr) => {
width: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.05);
background: rgba(0, 0, 0, 0.04);
border-radius: 3px;
}
.custom-scrollbar::-webkit-scrollbar-thumb {