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
+16 -16
View File
@@ -1,5 +1,5 @@
<template> <template>
<div class="relative bg-zinc-900/50 border border-zinc-800/50 rounded-xl p-1 overflow-hidden"> <div class="relative bg-zinc-50/50 border border-zinc-200 rounded-xl p-1 overflow-hidden">
<!-- 简化版日期选择显示 --> <!-- 简化版日期选择显示 -->
<div class="flex items-center justify-between px-2.5 py-1.5" @click="toggleCalendar" style="cursor: pointer"> <div class="flex items-center justify-between px-2.5 py-1.5" @click="toggleCalendar" style="cursor: pointer">
<div class="flex items-center gap-2.5"> <div class="flex items-center gap-2.5">
@@ -8,18 +8,18 @@
<span class="text-base font-black leading-none my-0">{{ getDay(modelValue) }}</span> <span class="text-base font-black leading-none my-0">{{ getDay(modelValue) }}</span>
</div> </div>
<div class="flex flex-col"> <div class="flex flex-col">
<span class="text-xs font-bold text-zinc-200">{{ modelValue }}</span> <span class="text-xs font-bold text-zinc-700">{{ modelValue }}</span>
<span class="text-[10px] text-zinc-500 font-medium leading-tight">{{ isToday(modelValue) ? '今天' : '' }}</span> <span class="text-[10px] text-zinc-500 font-medium leading-tight">{{ isToday(modelValue) ? '今天' : '' }}</span>
</div> </div>
</div> </div>
<div class="flex items-center border-l border-zinc-800 pl-2"> <div class="flex items-center border-l border-zinc-200 pl-2">
<button class="p-1.5 text-zinc-500 hover:text-emerald-400 transition-colors" title="跳转到今天" @click.stop="scrollToToday"> <button class="p-1.5 text-zinc-500 hover:text-emerald-700 transition-colors" title="跳转到今天" @click.stop="scrollToToday">
<CircleDot class="w-4 h-4" /> <CircleDot class="w-4 h-4" />
</button> </button>
<button <button
class="p-1.5 text-zinc-500 hover:text-blue-400 transition-colors" class="p-1.5 text-zinc-500 hover:text-blue-700 transition-colors"
:class="{ 'text-blue-400': showCalendar }" :class="{ 'text-blue-700': showCalendar }"
title="打开月历" title="打开月历"
> >
<CalendarIcon class="w-4 h-4" /> <CalendarIcon class="w-4 h-4" />
@@ -29,7 +29,7 @@
<!-- 快捷选择区 + 月历网格 --> <!-- 快捷选择区 + 月历网格 -->
<Transition name="expand"> <Transition name="expand">
<div v-if="showCalendar" class="border-t border-zinc-800/60 mt-1 pt-2 px-2 pb-2"> <div v-if="showCalendar" class="border-t border-zinc-200/60 mt-1 pt-2 px-2 pb-2">
<!-- 快捷按钮 --> <!-- 快捷按钮 -->
<div class="flex flex-wrap gap-2 mb-3"> <div class="flex flex-wrap gap-2 mb-3">
<button <button
@@ -39,7 +39,7 @@
'px-3 py-1.5 rounded-lg text-[11px] font-bold transition-all border', 'px-3 py-1.5 rounded-lg text-[11px] font-bold transition-all border',
modelValue === q.value modelValue === q.value
? 'bg-blue-600 border-blue-500 text-white' ? 'bg-blue-600 border-blue-500 text-white'
: 'bg-zinc-900 border-zinc-800 text-zinc-400 hover:text-zinc-200 hover:border-zinc-700' : 'bg-white border-zinc-200 text-zinc-400 hover:text-zinc-700 hover:border-zinc-200'
]" ]"
@click="handleQuickSelect(q.value)" @click="handleQuickSelect(q.value)"
> >
@@ -48,34 +48,34 @@
</div> </div>
<!-- 月历网格 --> <!-- 月历网格 -->
<div class="bg-zinc-950/60 border border-zinc-800 rounded-xl p-3"> <div class="bg-white/80 border border-zinc-200 rounded-xl p-3">
<!-- 月份切换 --> <!-- 月份切换 -->
<div class="flex items-center justify-between mb-3 px-1"> <div class="flex items-center justify-between mb-3 px-1">
<button <button
class="p-1.5 text-zinc-500 hover:text-zinc-200 rounded-lg hover:bg-zinc-800 transition-colors" class="p-1.5 text-zinc-500 hover:text-zinc-700 rounded-lg hover:bg-zinc-100 transition-colors"
@click="calendarMonth--" @click="calendarMonth--"
> >
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><polyline points="15,18 9,12 15,6" /></svg> <svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><polyline points="15,18 9,12 15,6" /></svg>
</button> </button>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="text-sm font-black text-zinc-200">{{ calendarYear }}年</span> <span class="text-sm font-black text-zinc-700">{{ calendarYear }}年</span>
<select <select
v-model="calendarMonth" v-model="calendarMonth"
class="bg-zinc-900 border border-zinc-800 rounded-lg px-2 py-1 text-sm font-black text-zinc-200 focus:outline-none focus:border-blue-500/50" class="bg-white border border-zinc-200 rounded-lg px-2 py-1 text-sm font-black text-zinc-700 focus:outline-none focus:border-blue-500/50"
> >
<option v-for="m in 12" :key="m" :value="m - 1">{{ m }}月</option> <option v-for="m in 12" :key="m" :value="m - 1">{{ m }}月</option>
</select> </select>
</div> </div>
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<button <button
class="p-1.5 text-zinc-500 hover:text-zinc-200 rounded-lg hover:bg-zinc-800 transition-colors" class="p-1.5 text-zinc-500 hover:text-zinc-700 rounded-lg hover:bg-zinc-100 transition-colors"
@click="goToday" @click="goToday"
title="回到本月" title="回到本月"
> >
<CircleDot class="w-3.5 h-3.5" /> <CircleDot class="w-3.5 h-3.5" />
</button> </button>
<button <button
class="p-1.5 text-zinc-500 hover:text-zinc-200 rounded-lg hover:bg-zinc-800 transition-colors" class="p-1.5 text-zinc-500 hover:text-zinc-700 rounded-lg hover:bg-zinc-100 transition-colors"
@click="calendarMonth++" @click="calendarMonth++"
> >
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><polyline points="9,18 15,12 9,6" /></svg> <svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><polyline points="9,18 15,12 9,6" /></svg>
@@ -104,9 +104,9 @@
'relative aspect-square rounded-lg flex items-center justify-center text-sm font-bold transition-all', 'relative aspect-square rounded-lg flex items-center justify-center text-sm font-bold transition-all',
!cell.inMonth ? 'text-zinc-800 cursor-default' : '', !cell.inMonth ? 'text-zinc-800 cursor-default' : '',
cell.inMonth && modelValue !== cell.dateStr && !cell.isToday cell.inMonth && modelValue !== cell.dateStr && !cell.isToday
? 'text-zinc-400 hover:bg-zinc-800 hover:text-zinc-200' : '', ? 'text-zinc-400 hover:bg-zinc-100 hover:text-zinc-700' : '',
cell.inMonth && modelValue !== cell.dateStr && cell.isToday cell.inMonth && modelValue !== cell.dateStr && cell.isToday
? 'text-emerald-400 ring-1 ring-emerald-500/40 hover:bg-zinc-800' : '', ? 'text-emerald-700 ring-1 ring-emerald-500/40 hover:bg-zinc-100' : '',
modelValue === cell.dateStr modelValue === cell.dateStr
? 'bg-blue-600 text-white shadow-lg shadow-blue-900/20' : '' ? 'bg-blue-600 text-white shadow-lg shadow-blue-900/20' : ''
]" ]"
+17 -17
View File
@@ -1,5 +1,5 @@
<template> <template>
<div class="relative bg-zinc-900/50 border border-zinc-800/50 rounded-xl p-1 overflow-hidden"> <div class="relative bg-zinc-50/50 border border-zinc-200 rounded-xl p-1 overflow-hidden">
<!-- 简化版多选日期显示 --> <!-- 简化版多选日期显示 -->
<div class="flex items-center justify-between px-2.5 py-1.5" @click="toggleCalendar" style="cursor: pointer"> <div class="flex items-center justify-between px-2.5 py-1.5" @click="toggleCalendar" style="cursor: pointer">
<div class="flex items-center gap-2.5 min-w-0 flex-1"> <div class="flex items-center gap-2.5 min-w-0 flex-1">
@@ -10,7 +10,7 @@
<span class="text-[10px] font-bold leading-none opacity-80">天</span> <span class="text-[10px] font-bold leading-none opacity-80">天</span>
</div> </div>
<div class="flex flex-col min-w-0 flex-1"> <div class="flex flex-col min-w-0 flex-1">
<span class="text-xs font-bold text-zinc-200 truncate"> <span class="text-xs font-bold text-zinc-700 truncate">
{{ modelValue.length ? modelValue.slice(0, 3).join('、') + (modelValue.length > 3 ? ` 等${modelValue.length}天` : '') : '未选择日期' }} {{ modelValue.length ? modelValue.slice(0, 3).join('、') + (modelValue.length > 3 ? ` 等${modelValue.length}天` : '') : '未选择日期' }}
</span> </span>
<span class="text-[10px] text-zinc-500 font-medium leading-tight truncate"> <span class="text-[10px] text-zinc-500 font-medium leading-tight truncate">
@@ -19,13 +19,13 @@
</div> </div>
</div> </div>
<div class="flex items-center border-l border-zinc-800 pl-2 ml-2 shrink-0"> <div class="flex items-center border-l border-zinc-200 pl-2 ml-2 shrink-0">
<button class="p-1.5 text-zinc-500 hover:text-emerald-400 transition-colors" title="跳转到今天" @click.stop="scrollToToday"> <button class="p-1.5 text-zinc-500 hover:text-emerald-700 transition-colors" title="跳转到今天" @click.stop="scrollToToday">
<CircleDot class="w-4 h-4" /> <CircleDot class="w-4 h-4" />
</button> </button>
<button <button
class="p-1.5 text-zinc-500 hover:text-blue-400 transition-colors" class="p-1.5 text-zinc-500 hover:text-blue-700 transition-colors"
:class="{ 'text-blue-400': showCalendar }" :class="{ 'text-blue-700': showCalendar }"
title="打开月历" title="打开月历"
> >
<CalendarIcon class="w-4 h-4" /> <CalendarIcon class="w-4 h-4" />
@@ -35,19 +35,19 @@
<!-- 扩展面板:快捷批量 + 月历 --> <!-- 扩展面板:快捷批量 + 月历 -->
<Transition name="expand"> <Transition name="expand">
<div v-if="showCalendar" class="border-t border-zinc-800/60 mt-1 pt-2 px-2 pb-2 space-y-3"> <div v-if="showCalendar" class="border-t border-zinc-200/60 mt-1 pt-2 px-2 pb-2 space-y-3">
<!-- 批量操作按钮 --> <!-- 批量操作按钮 -->
<div class="flex flex-wrap items-center gap-2"> <div class="flex flex-wrap items-center gap-2">
<span class="text-[10px] font-black text-zinc-600 uppercase tracking-widest px-1">批量选择</span> <span class="text-[10px] font-black text-zinc-600 uppercase tracking-widest px-1">批量选择</span>
<button <button
v-for="b in batchActions" v-for="b in batchActions"
:key="b.label" :key="b.label"
class="px-2.5 py-1.5 rounded-lg text-[11px] font-bold bg-zinc-900 border border-zinc-800 text-zinc-400 hover:text-zinc-200 hover:border-zinc-700 transition-all" class="px-2.5 py-1.5 rounded-lg text-[11px] font-bold bg-white border border-zinc-200 text-zinc-400 hover:text-zinc-700 hover:border-zinc-200 transition-all"
@click="applyBatchAction(b.action)" @click="applyBatchAction(b.action)"
> >
{{ b.label }} {{ b.label }}
</button> </button>
<div class="w-px h-4 bg-zinc-800 mx-1" /> <div class="w-px h-4 bg-zinc-100 mx-1" />
<button <button
class="px-2.5 py-1.5 rounded-lg text-[11px] font-bold bg-red-500/10 border border-red-500/20 text-red-400 hover:text-red-300 hover:bg-red-500/15 transition-all" class="px-2.5 py-1.5 rounded-lg text-[11px] font-bold bg-red-500/10 border border-red-500/20 text-red-400 hover:text-red-300 hover:bg-red-500/15 transition-all"
@click="clearAll" @click="clearAll"
@@ -63,33 +63,33 @@
</div> </div>
<!-- 月历网格 --> <!-- 月历网格 -->
<div class="bg-zinc-950/60 border border-zinc-800 rounded-xl p-3"> <div class="bg-white/80 border border-zinc-200 rounded-xl p-3">
<div class="flex items-center justify-between mb-3 px-1"> <div class="flex items-center justify-between mb-3 px-1">
<button <button
class="p-1.5 text-zinc-500 hover:text-zinc-200 rounded-lg hover:bg-zinc-800 transition-colors" class="p-1.5 text-zinc-500 hover:text-zinc-700 rounded-lg hover:bg-zinc-100 transition-colors"
@click="calendarMonth--" @click="calendarMonth--"
> >
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><polyline points="15,18 9,12 15,6" /></svg> <svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><polyline points="15,18 9,12 15,6" /></svg>
</button> </button>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="text-sm font-black text-zinc-200">{{ calendarYear }}年</span> <span class="text-sm font-black text-zinc-700">{{ calendarYear }}年</span>
<select <select
v-model="calendarMonth" v-model="calendarMonth"
class="bg-zinc-900 border border-zinc-800 rounded-lg px-2 py-1 text-sm font-black text-zinc-200 focus:outline-none focus:border-blue-500/50" class="bg-white border border-zinc-200 rounded-lg px-2 py-1 text-sm font-black text-zinc-700 focus:outline-none focus:border-blue-500/50"
> >
<option v-for="m in 12" :key="m" :value="m - 1">{{ m }}月</option> <option v-for="m in 12" :key="m" :value="m - 1">{{ m }}月</option>
</select> </select>
</div> </div>
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<button <button
class="p-1.5 text-zinc-500 hover:text-zinc-200 rounded-lg hover:bg-zinc-800 transition-colors" class="p-1.5 text-zinc-500 hover:text-zinc-700 rounded-lg hover:bg-zinc-100 transition-colors"
@click="goToday" @click="goToday"
title="回到本月" title="回到本月"
> >
<CircleDot class="w-3.5 h-3.5" /> <CircleDot class="w-3.5 h-3.5" />
</button> </button>
<button <button
class="p-1.5 text-zinc-500 hover:text-zinc-200 rounded-lg hover:bg-zinc-800 transition-colors" class="p-1.5 text-zinc-500 hover:text-zinc-700 rounded-lg hover:bg-zinc-100 transition-colors"
@click="calendarMonth++" @click="calendarMonth++"
> >
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><polyline points="9,18 15,12 9,6" /></svg> <svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><polyline points="9,18 15,12 9,6" /></svg>
@@ -116,9 +116,9 @@
'relative aspect-square rounded-lg flex items-center justify-center text-sm font-bold transition-all', 'relative aspect-square rounded-lg flex items-center justify-center text-sm font-bold transition-all',
!cell.inMonth ? 'text-zinc-800 cursor-default' : '', !cell.inMonth ? 'text-zinc-800 cursor-default' : '',
cell.inMonth && !isSelected(cell.dateStr) && !cell.isToday cell.inMonth && !isSelected(cell.dateStr) && !cell.isToday
? 'text-zinc-400 hover:bg-zinc-800 hover:text-zinc-200' : '', ? 'text-zinc-400 hover:bg-zinc-100 hover:text-zinc-700' : '',
cell.inMonth && !isSelected(cell.dateStr) && cell.isToday cell.inMonth && !isSelected(cell.dateStr) && cell.isToday
? 'text-emerald-400 ring-1 ring-emerald-500/40 hover:bg-zinc-800' : '', ? 'text-emerald-700 ring-1 ring-emerald-500/40 hover:bg-zinc-100' : '',
isSelected(cell.dateStr) isSelected(cell.dateStr)
? 'bg-blue-600 text-white shadow-lg shadow-blue-900/20' : '', ? 'bg-blue-600 text-white shadow-lg shadow-blue-900/20' : '',
cell.isRangeMiddle && !isSelected(cell.dateStr) cell.isRangeMiddle && !isSelected(cell.dateStr)
@@ -5,15 +5,15 @@
class="fixed inset-0 z-[70] bg-black/70 backdrop-blur-sm px-4 py-6 flex items-end sm:items-center justify-center" class="fixed inset-0 z-[70] bg-black/70 backdrop-blur-sm px-4 py-6 flex items-end sm:items-center justify-center"
> >
<div <div
class="w-full max-w-xl rounded-2xl border border-zinc-800 bg-zinc-950 shadow-2xl overflow-hidden" class="w-full max-w-xl rounded-2xl border border-zinc-200 bg-white shadow-2xl overflow-hidden"
> >
<div class="px-5 py-4 border-b border-zinc-800 flex items-center justify-between gap-3"> <div class="px-5 py-4 border-b border-zinc-200 flex items-center justify-between gap-3">
<div> <div>
<p class="text-[10px] uppercase tracking-[0.2em] text-zinc-500 font-black">后台引导</p> <p class="text-[10px] uppercase tracking-[0.2em] text-zinc-500 font-black">后台引导</p>
<h3 class="text-lg font-black text-zinc-100 mt-1">{{ currentStep.title }}</h3> <h3 class="text-lg font-black text-zinc-900 mt-1">{{ currentStep.title }}</h3>
</div> </div>
<button <button
class="h-9 w-9 rounded-lg border border-zinc-700 text-zinc-400 hover:text-zinc-200 hover:bg-zinc-900 transition-colors" class="h-9 w-9 rounded-lg border border-zinc-200 text-zinc-400 hover:text-zinc-700 hover:bg-white transition-colors"
@click="handleClose" @click="handleClose"
> >
<X :size="16" class="mx-auto" /> <X :size="16" class="mx-auto" />
@@ -21,29 +21,29 @@
</div> </div>
<div class="px-5 py-5 space-y-4"> <div class="px-5 py-5 space-y-4">
<p class="text-sm leading-relaxed text-zinc-300">{{ currentStep.description }}</p> <p class="text-sm leading-relaxed text-zinc-600">{{ currentStep.description }}</p>
<div class="rounded-xl border border-blue-500/20 bg-blue-500/5 p-3"> <div class="rounded-xl border border-blue-200 bg-blue-500/5 p-3">
<p class="text-xs text-zinc-400"> <p class="text-xs text-zinc-400">
建议操作: 建议操作:
<span class="text-zinc-200 font-semibold">{{ currentStep.actionHint }}</span> <span class="text-zinc-700 font-semibold">{{ currentStep.actionHint }}</span>
</p> </p>
</div> </div>
</div> </div>
<div <div
class="px-5 py-4 border-t border-zinc-800 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3" class="px-5 py-4 border-t border-zinc-200 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3"
> >
<p class="text-xs text-zinc-500 font-semibold">{{ stepIndex + 1 }} / {{ steps.length }}</p> <p class="text-xs text-zinc-500 font-semibold">{{ stepIndex + 1 }} / {{ steps.length }}</p>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<button <button
v-if="stepIndex > 0" v-if="stepIndex > 0"
class="px-3.5 py-2 rounded-lg border border-zinc-700 text-zinc-300 hover:bg-zinc-900 text-sm" class="px-3.5 py-2 rounded-lg border border-zinc-200 text-zinc-600 hover:bg-white text-sm"
@click="prevStep" @click="prevStep"
> >
上一步 上一步
</button> </button>
<button <button
class="px-3.5 py-2 rounded-lg border border-zinc-700 text-zinc-200 hover:bg-zinc-900 text-sm" class="px-3.5 py-2 rounded-lg border border-zinc-200 text-zinc-700 hover:bg-white text-sm"
@click="goCurrentStep" @click="goCurrentStep"
> >
前往此功能 前往此功能
+59 -59
View File
@@ -3,7 +3,7 @@
<!-- 头部区域 --> <!-- 头部区域 -->
<div class="flex flex-col md:flex-row md:items-end justify-between gap-6"> <div class="flex flex-col md:flex-row md:items-end justify-between gap-6">
<div> <div>
<h2 class="text-2xl font-black text-zinc-100 tracking-tight">API密钥管理</h2> <h2 class="text-2xl font-black text-zinc-900 tracking-tight">API密钥管理</h2>
<p class="text-xs text-zinc-500 mt-1">管理开放API的访问密钥,控制第三方应用的访问权限</p> <p class="text-xs text-zinc-500 mt-1">管理开放API的访问密钥,控制第三方应用的访问权限</p>
</div> </div>
<button <button
@@ -16,7 +16,7 @@
<!-- 过滤器栏 --> <!-- 过滤器栏 -->
<div <div
class="bg-zinc-900/40 border border-zinc-800/60 rounded-2xl p-3 flex flex-col lg:flex-row gap-3 items-center" class="bg-zinc-50/40 border border-zinc-200/60 rounded-2xl p-3 flex flex-col lg:flex-row gap-3 items-center"
> >
<div class="relative flex-1 w-full"> <div class="relative flex-1 w-full">
<Search class="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-700" :size="16" /> <Search class="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-700" :size="16" />
@@ -24,7 +24,7 @@
v-model="filters.search" v-model="filters.search"
type="text" type="text"
placeholder="搜索API密钥名称或描述..." placeholder="搜索API密钥名称或描述..."
class="w-full bg-zinc-950 border border-zinc-800/80 rounded-xl pl-10 pr-4 py-2.5 text-xs focus:outline-none focus:border-blue-500/30 transition-all placeholder:text-zinc-800 text-zinc-200" class="w-full bg-white border border-zinc-200/80 rounded-xl pl-10 pr-4 py-2.5 text-xs focus:outline-none focus:border-blue-200 transition-all placeholder:text-zinc-500 text-zinc-700"
@input="debouncedSearch" @input="debouncedSearch"
> >
</div> </div>
@@ -42,12 +42,12 @@
v-model="filters.createdBy" v-model="filters.createdBy"
type="text" type="text"
placeholder="创建者用户名" placeholder="创建者用户名"
class="w-full bg-zinc-950 border border-zinc-800 rounded-xl pl-9 pr-4 py-2.5 text-xs focus:outline-none text-zinc-400 placeholder:text-zinc-800" class="w-full bg-white border border-zinc-200 rounded-xl pl-9 pr-4 py-2.5 text-xs focus:outline-none text-zinc-400 placeholder:text-zinc-500"
@input="debouncedSearch" @input="debouncedSearch"
> >
</div> </div>
<button <button
class="p-2.5 bg-zinc-950 border border-zinc-800 rounded-xl text-zinc-600 hover:text-blue-400 transition-all" class="p-2.5 bg-white border border-zinc-200 rounded-xl text-zinc-600 hover:text-blue-700 transition-all"
@click="loadApiKeys" @click="loadApiKeys"
> >
<RefreshCw :size="14" :class="{ 'animate-spin': loading }" /> <RefreshCw :size="14" :class="{ 'animate-spin': loading }" />
@@ -69,16 +69,16 @@
class="flex flex-col items-center justify-center py-20 px-4 text-center" class="flex flex-col items-center justify-center py-20 px-4 text-center"
> >
<div <div
class="w-20 h-20 rounded-3xl bg-zinc-900 border border-zinc-800 flex items-center justify-center mb-6 text-zinc-700 shadow-xl" class="w-20 h-20 rounded-3xl bg-white border border-zinc-200 flex items-center justify-center mb-6 text-zinc-700 shadow-xl"
> >
<Key :size="32" :stroke-width="1.5" /> <Key :size="32" :stroke-width="1.5" />
</div> </div>
<h3 class="text-lg font-bold text-zinc-200">暂无API密钥</h3> <h3 class="text-lg font-bold text-zinc-700">暂无API密钥</h3>
<p class="text-xs text-zinc-500 mt-2 max-w-xs leading-relaxed"> <p class="text-xs text-zinc-500 mt-2 max-w-xs leading-relaxed">
您还没有创建任何访问密钥。创建密钥后,您可以安全地将 VoiceHub 集成到第三方应用程序中。 您还没有创建任何访问密钥。创建密钥后,您可以安全地将 VoiceHub 集成到第三方应用程序中。
</p> </p>
<button <button
class="mt-8 flex items-center gap-2 px-6 py-3 bg-zinc-900 border border-zinc-800 hover:border-zinc-700 text-zinc-300 text-xs font-bold rounded-2xl transition-all" class="mt-8 flex items-center gap-2 px-6 py-3 bg-white border border-zinc-200 hover:border-zinc-200 text-zinc-600 text-xs font-bold rounded-2xl transition-all"
@click="openCreateModal" @click="openCreateModal"
> >
<Plus :size="16" /> 创建您的第一个密钥 <Plus :size="16" /> 创建您的第一个密钥
@@ -89,12 +89,12 @@
<div <div
v-for="apiKey in apiKeys" v-for="apiKey in apiKeys"
:key="apiKey.id" :key="apiKey.id"
class="bg-zinc-900/30 border border-zinc-800/60 rounded-2xl p-6 group hover:border-zinc-700 transition-all relative overflow-hidden" class="bg-zinc-50/30 border border-zinc-200/60 rounded-2xl p-6 group hover:border-zinc-200 transition-all relative overflow-hidden"
> >
<div class="flex items-start justify-between relative z-10"> <div class="flex items-start justify-between relative z-10">
<div class="space-y-1"> <div class="space-y-1">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<h4 class="text-sm font-black text-zinc-100 uppercase tracking-widest"> <h4 class="text-sm font-black text-zinc-900 uppercase tracking-widest">
{{ apiKey.name }} {{ apiKey.name }}
</h4> </h4>
<span <span
@@ -104,7 +104,7 @@
> >
<span <span
v-else-if="apiKey.status === 'inactive'" v-else-if="apiKey.status === 'inactive'"
class="inline-flex items-center gap-1.5 px-2 py-0.5 bg-zinc-800 text-zinc-500 text-[10px] font-black rounded uppercase border border-zinc-700/50" class="inline-flex items-center gap-1.5 px-2 py-0.5 bg-zinc-100 text-zinc-500 text-[10px] font-black rounded uppercase border border-zinc-200"
>非活跃</span >非活跃</span
> >
<span <span
@@ -119,13 +119,13 @@
class="flex items-center gap-1 opacity-100 lg:opacity-0 group-hover:opacity-100 transition-all" class="flex items-center gap-1 opacity-100 lg:opacity-0 group-hover:opacity-100 transition-all"
> >
<button <button
class="p-2 text-zinc-500 hover:text-blue-400 transition-colors" class="p-2 text-zinc-500 hover:text-blue-700 transition-colors"
@click="viewApiKey(apiKey)" @click="viewApiKey(apiKey)"
> >
<Eye :size="14" /> <Eye :size="14" />
</button> </button>
<button <button
class="p-2 text-zinc-500 hover:text-amber-400 transition-colors" class="p-2 text-zinc-500 hover:text-amber-700 transition-colors"
@click="editApiKey(apiKey)" @click="editApiKey(apiKey)"
> >
<Edit2 :size="14" /> <Edit2 :size="14" />
@@ -139,7 +139,7 @@
</div> </div>
</div> </div>
<div class="mt-8 grid grid-cols-2 gap-4 border-t border-zinc-800/40 pt-6 relative z-10"> <div class="mt-8 grid grid-cols-2 gap-4 border-t border-zinc-200 pt-6 relative z-10">
<div class="space-y-0.5"> <div class="space-y-0.5">
<span class="text-[9px] font-black text-zinc-600 uppercase tracking-widest" <span class="text-[9px] font-black text-zinc-600 uppercase tracking-widest"
>创建者</span >创建者</span
@@ -162,7 +162,7 @@
<span <span
v-for="perm in apiKey.permissions" v-for="perm in apiKey.permissions"
:key="perm" :key="perm"
class="text-[9px] font-mono bg-zinc-950 px-1.5 py-0.5 rounded text-zinc-500 border border-zinc-800/50" class="text-[9px] font-mono bg-white px-1.5 py-0.5 rounded text-zinc-500 border border-zinc-200"
> >
{{ perm }} {{ perm }}
</span> </span>
@@ -197,14 +197,14 @@
<!-- 创建/编辑模态框 --> <!-- 创建/编辑模态框 -->
<div <div
v-if="showCreateModal || showEditModal" v-if="showCreateModal || showEditModal"
class="relative w-full max-w-lg bg-zinc-900 border border-zinc-800 rounded-3xl shadow-2xl overflow-hidden animate-in fade-in zoom-in duration-200" class="relative w-full max-w-lg bg-white border border-zinc-200 rounded-3xl shadow-2xl overflow-hidden animate-in fade-in zoom-in duration-200"
> >
<div class="p-6 border-b border-zinc-800 flex items-center justify-between"> <div class="p-6 border-b border-zinc-200 flex items-center justify-between">
<h3 class="text-lg font-black text-zinc-100 uppercase tracking-widest"> <h3 class="text-lg font-black text-zinc-900 uppercase tracking-widest">
{{ showCreateModal ? '创建API密钥' : '编辑API密钥' }} {{ showCreateModal ? '创建API密钥' : '编辑API密钥' }}
</h3> </h3>
<button <button
class="text-zinc-500 hover:text-zinc-200 transition-colors" class="text-zinc-500 hover:text-zinc-700 transition-colors"
@click="closeModals" @click="closeModals"
> >
<X :size="20" /> <X :size="20" />
@@ -220,7 +220,7 @@
v-model="form.name" v-model="form.name"
type="text" type="text"
placeholder="输入API密钥名称" placeholder="输入API密钥名称"
class="w-full bg-zinc-950 border border-zinc-800 rounded-xl px-3 py-2.5 text-xs focus:outline-none focus:border-blue-500/30 text-zinc-200 placeholder:text-zinc-800" class="w-full bg-white border border-zinc-200 rounded-xl px-3 py-2.5 text-xs focus:outline-none focus:border-blue-200 text-zinc-700 placeholder:text-zinc-500"
> >
</div> </div>
<div class="space-y-1.5"> <div class="space-y-1.5">
@@ -230,7 +230,7 @@
<textarea <textarea
v-model="form.description" v-model="form.description"
placeholder="输入API密钥描述 (可选)" placeholder="输入API密钥描述 (可选)"
class="w-full bg-zinc-950 border border-zinc-800 rounded-xl px-3 py-2.5 text-xs focus:outline-none focus:border-blue-500/30 text-zinc-200 min-h-[80px] resize-none placeholder:text-zinc-800" class="w-full bg-white border border-zinc-200 rounded-xl px-3 py-2.5 text-xs focus:outline-none focus:border-blue-200 text-zinc-700 min-h-[80px] resize-none placeholder:text-zinc-500"
/> />
</div> </div>
<div class="space-y-1.5"> <div class="space-y-1.5">
@@ -259,22 +259,22 @@
<label <label
v-for="perm in availablePermissions" v-for="perm in availablePermissions"
:key="perm.value" :key="perm.value"
class="flex items-start gap-3 p-3 bg-zinc-950 border border-zinc-800 rounded-xl cursor-pointer hover:border-zinc-600 transition-all" class="flex items-start gap-3 p-3 bg-white border border-zinc-200 rounded-xl cursor-pointer hover:border-zinc-300 transition-all"
:class="{ :class="{
'border-blue-500/30 bg-blue-500/5': form.permissions.includes(perm.value) 'border-blue-200 bg-blue-500/5': form.permissions.includes(perm.value)
}" }"
> >
<input <input
v-model="form.permissions" v-model="form.permissions"
type="checkbox" type="checkbox"
:value="perm.value" :value="perm.value"
class="mt-1 w-3.5 h-3.5 rounded border-zinc-800 bg-zinc-900 accent-blue-600" class="mt-1 w-3.5 h-3.5 rounded border-zinc-200 bg-white accent-blue-600"
> >
<div> <div>
<p <p
class="text-xs font-bold" class="text-xs font-bold"
:class=" :class="
form.permissions.includes(perm.value) ? 'text-blue-400' : 'text-zinc-300' form.permissions.includes(perm.value) ? 'text-blue-700' : 'text-zinc-600'
" "
> >
{{ perm.label }} {{ perm.label }}
@@ -291,18 +291,18 @@
<input <input
v-model="form.isActive" v-model="form.isActive"
type="checkbox" type="checkbox"
class="w-3.5 h-3.5 rounded border-zinc-800 bg-zinc-900 accent-blue-600" class="w-3.5 h-3.5 rounded border-zinc-200 bg-white accent-blue-600"
> >
<span <span
class="text-xs font-bold text-zinc-300 group-hover:text-blue-400 transition-colors" class="text-xs font-bold text-zinc-600 group-hover:text-blue-700 transition-colors"
>启用此API密钥</span >启用此API密钥</span
> >
</label> </label>
</div> </div>
<div class="p-6 border-t border-zinc-800 flex gap-2 justify-end"> <div class="p-6 border-t border-zinc-200 flex gap-2 justify-end">
<button <button
class="px-4 py-2 text-xs font-bold text-zinc-500 hover:text-zinc-300" class="px-4 py-2 text-xs font-bold text-zinc-500 hover:text-zinc-600"
@click="closeModals" @click="closeModals"
> >
取消 取消
@@ -320,14 +320,14 @@
<!-- 成功模态框 --> <!-- 成功模态框 -->
<div <div
v-if="showSuccessModal" v-if="showSuccessModal"
class="relative w-full max-w-lg bg-zinc-900 border border-zinc-800 rounded-3xl shadow-2xl overflow-hidden animate-in fade-in zoom-in duration-200" class="relative w-full max-w-lg bg-white border border-zinc-200 rounded-3xl shadow-2xl overflow-hidden animate-in fade-in zoom-in duration-200"
> >
<div class="p-6 border-b border-zinc-800 flex items-center justify-between"> <div class="p-6 border-b border-zinc-200 flex items-center justify-between">
<h3 class="text-lg font-black text-zinc-100 uppercase tracking-widest"> <h3 class="text-lg font-black text-zinc-900 uppercase tracking-widest">
API密钥创建成功 API密钥创建成功
</h3> </h3>
<button <button
class="text-zinc-500 hover:text-zinc-200 transition-colors" class="text-zinc-500 hover:text-zinc-700 transition-colors"
@click="closeModals" @click="closeModals"
> >
<X :size="20" /> <X :size="20" />
@@ -343,7 +343,7 @@
> >
<Check :size="24" :stroke-width="3" /> <Check :size="24" :stroke-width="3" />
</div> </div>
<h4 class="text-lg font-black text-emerald-400">API密钥创建成功!</h4> <h4 class="text-lg font-black text-emerald-700">API密钥创建成功!</h4>
<p class="text-xs text-zinc-500 mt-2">请妥善保管以下API密钥,它只会显示这一次。</p> <p class="text-xs text-zinc-500 mt-2">请妥善保管以下API密钥,它只会显示这一次。</p>
</div> </div>
@@ -353,7 +353,7 @@
> >
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div <div
class="flex-1 bg-zinc-950 border border-zinc-800 rounded-xl px-4 py-3 font-mono text-xs text-blue-400 break-all select-all" class="flex-1 bg-white border border-zinc-200 rounded-xl px-4 py-3 font-mono text-xs text-blue-700 break-all select-all"
> >
{{ newApiKey?.apiKey }} {{ newApiKey?.apiKey }}
</div> </div>
@@ -362,7 +362,7 @@
:class=" :class="
copied copied
? 'bg-emerald-600 text-white' ? 'bg-emerald-600 text-white'
: 'bg-zinc-800 hover:bg-zinc-700 text-zinc-300' : 'bg-zinc-100 hover:bg-zinc-200 text-zinc-600'
" "
@click="copyToClipboard(newApiKey?.apiKey)" @click="copyToClipboard(newApiKey?.apiKey)"
> >
@@ -381,9 +381,9 @@
</div> </div>
</div> </div>
<div class="p-6 border-t border-zinc-800"> <div class="p-6 border-t border-zinc-200">
<button <button
class="w-full py-2.5 bg-zinc-950 border border-zinc-800 hover:border-zinc-700 text-zinc-200 text-xs font-black rounded-xl transition-all" class="w-full py-2.5 bg-white border border-zinc-200 hover:border-zinc-200 text-zinc-700 text-xs font-black rounded-xl transition-all"
@click="closeModals" @click="closeModals"
> >
我已保存,关闭窗口 我已保存,关闭窗口
@@ -394,12 +394,12 @@
<!-- 详情模态框 --> <!-- 详情模态框 -->
<div <div
v-if="showViewModal && selectedApiKey" v-if="showViewModal && selectedApiKey"
class="relative w-full max-w-2xl bg-zinc-900 border border-zinc-800 rounded-3xl shadow-2xl overflow-hidden animate-in fade-in zoom-in duration-200" class="relative w-full max-w-2xl bg-white border border-zinc-200 rounded-3xl shadow-2xl overflow-hidden animate-in fade-in zoom-in duration-200"
> >
<div class="p-6 border-b border-zinc-800 flex items-center justify-between"> <div class="p-6 border-b border-zinc-200 flex items-center justify-between">
<h3 class="text-lg font-black text-zinc-100 uppercase tracking-widest">API密钥详情</h3> <h3 class="text-lg font-black text-zinc-900 uppercase tracking-widest">API密钥详情</h3>
<button <button
class="text-zinc-500 hover:text-zinc-200 transition-colors" class="text-zinc-500 hover:text-zinc-700 transition-colors"
@click="closeModals" @click="closeModals"
> >
<X :size="20" /> <X :size="20" />
@@ -409,14 +409,14 @@
<div class="p-6 space-y-8 max-h-[80vh] overflow-y-auto custom-scrollbar"> <div class="p-6 space-y-8 max-h-[80vh] overflow-y-auto custom-scrollbar">
<section class="space-y-4"> <section class="space-y-4">
<h5 <h5
class="text-[10px] font-black text-zinc-600 uppercase tracking-widest border-b border-zinc-800 pb-2" class="text-[10px] font-black text-zinc-600 uppercase tracking-widest border-b border-zinc-200 pb-2"
> >
基本信息 基本信息
</h5> </h5>
<div class="grid grid-cols-2 gap-6"> <div class="grid grid-cols-2 gap-6">
<div class="space-y-1"> <div class="space-y-1">
<span class="text-[10px] text-zinc-600 font-bold">名称:</span> <span class="text-[10px] text-zinc-600 font-bold">名称:</span>
<p class="text-xs font-bold text-zinc-200">{{ selectedApiKey.name }}</p> <p class="text-xs font-bold text-zinc-700">{{ selectedApiKey.name }}</p>
</div> </div>
<div class="space-y-1"> <div class="space-y-1">
<span class="text-[10px] text-zinc-600 font-bold">状态:</span> <span class="text-[10px] text-zinc-600 font-bold">状态:</span>
@@ -428,7 +428,7 @@
> >
<span <span
v-else-if="selectedApiKey.status === 'inactive'" v-else-if="selectedApiKey.status === 'inactive'"
class="inline-flex items-center gap-1.5 px-2 py-0.5 bg-zinc-800 text-zinc-500 text-[10px] font-black rounded uppercase border border-zinc-700/50" class="inline-flex items-center gap-1.5 px-2 py-0.5 bg-zinc-100 text-zinc-500 text-[10px] font-black rounded uppercase border border-zinc-200"
>非活跃</span >非活跃</span
> >
<span <span
@@ -446,13 +446,13 @@
</div> </div>
<div class="space-y-1"> <div class="space-y-1">
<span class="text-[10px] text-zinc-600 font-bold">创建者:</span> <span class="text-[10px] text-zinc-600 font-bold">创建者:</span>
<p class="text-xs font-bold text-zinc-200"> <p class="text-xs font-bold text-zinc-700">
{{ selectedApiKey.creatorName || '未知' }} {{ selectedApiKey.creatorName || '未知' }}
</p> </p>
</div> </div>
<div class="space-y-1"> <div class="space-y-1">
<span class="text-[10px] text-zinc-600 font-bold">创建时间:</span> <span class="text-[10px] text-zinc-600 font-bold">创建时间:</span>
<p class="text-xs font-bold text-zinc-200"> <p class="text-xs font-bold text-zinc-700">
{{ formatDate(selectedApiKey.createdAt) }} {{ formatDate(selectedApiKey.createdAt) }}
</p> </p>
</div> </div>
@@ -460,7 +460,7 @@
<span class="text-[10px] text-zinc-600 font-bold">过期时间:</span> <span class="text-[10px] text-zinc-600 font-bold">过期时间:</span>
<p <p
class="text-xs font-bold" class="text-xs font-bold"
:class="selectedApiKey.isExpired ? 'text-red-400' : 'text-zinc-200'" :class="selectedApiKey.isExpired ? 'text-red-400' : 'text-zinc-700'"
> >
{{ formatDate(selectedApiKey.expiresAt) }} {{ formatDate(selectedApiKey.expiresAt) }}
</p> </p>
@@ -470,19 +470,19 @@
<section class="space-y-4"> <section class="space-y-4">
<h5 <h5
class="text-[10px] font-black text-zinc-600 uppercase tracking-widest border-b border-zinc-800 pb-2" class="text-[10px] font-black text-zinc-600 uppercase tracking-widest border-b border-zinc-200 pb-2"
> >
使用统计 使用统计
</h5> </h5>
<div class="grid grid-cols-2 gap-4"> <div class="grid grid-cols-2 gap-4">
<div <div
class="p-4 bg-zinc-950 border border-zinc-800 rounded-2xl flex items-center justify-between group hover:border-blue-500/30 transition-all" class="p-4 bg-white border border-zinc-200 rounded-2xl flex items-center justify-between group hover:border-blue-200 transition-all"
> >
<div class="space-y-0.5"> <div class="space-y-0.5">
<span class="text-[9px] font-black text-zinc-600 uppercase tracking-widest" <span class="text-[9px] font-black text-zinc-600 uppercase tracking-widest"
>总调用次数</span >总调用次数</span
> >
<p class="text-xl font-black text-zinc-100"> <p class="text-xl font-black text-zinc-900">
{{ selectedApiKey.usageCount || 0 }} {{ selectedApiKey.usageCount || 0 }}
</p> </p>
</div> </div>
@@ -493,13 +493,13 @@
</div> </div>
</div> </div>
<div <div
class="p-4 bg-zinc-950 border border-zinc-800 rounded-2xl flex items-center justify-between group hover:border-amber-500/30 transition-all" class="p-4 bg-white border border-zinc-200 rounded-2xl flex items-center justify-between group hover:border-amber-200 transition-all"
> >
<div class="space-y-0.5"> <div class="space-y-0.5">
<span class="text-[9px] font-black text-zinc-600 uppercase tracking-widest" <span class="text-[9px] font-black text-zinc-600 uppercase tracking-widest"
>最后使用时间</span >最后使用时间</span
> >
<p class="text-xs font-bold text-zinc-100"> <p class="text-xs font-bold text-zinc-900">
{{ {{
selectedApiKey.lastUsedAt selectedApiKey.lastUsedAt
? formatDate(selectedApiKey.lastUsedAt) ? formatDate(selectedApiKey.lastUsedAt)
@@ -517,7 +517,7 @@
</section> </section>
<section class="space-y-4"> <section class="space-y-4">
<div class="flex items-center justify-between border-b border-zinc-800 pb-2"> <div class="flex items-center justify-between border-b border-zinc-200 pb-2">
<h5 class="text-[10px] font-black text-zinc-600 uppercase tracking-widest"> <h5 class="text-[10px] font-black text-zinc-600 uppercase tracking-widest">
最近调用日志 最近调用日志
</h5> </h5>
@@ -538,7 +538,7 @@
</div> </div>
<div <div
v-else-if="apiLogs.length === 0" v-else-if="apiLogs.length === 0"
class="flex flex-col items-center justify-center py-10 bg-zinc-950/50 border border-zinc-800/50 rounded-2xl" class="flex flex-col items-center justify-center py-10 bg-white/70 border border-zinc-200 rounded-2xl"
> >
<History :size="24" class="text-zinc-800 mb-2" /> <History :size="24" class="text-zinc-800 mb-2" />
<span class="text-[10px] font-bold text-zinc-700 uppercase tracking-widest" <span class="text-[10px] font-bold text-zinc-700 uppercase tracking-widest"
@@ -549,7 +549,7 @@
v-for="log in apiLogs" v-for="log in apiLogs"
v-else v-else
:key="log.id" :key="log.id"
class="p-3 bg-zinc-950 border border-zinc-800/40 rounded-xl flex items-center justify-between group hover:border-zinc-700 transition-all" class="p-3 bg-white border border-zinc-200 rounded-xl flex items-center justify-between group hover:border-zinc-200 transition-all"
> >
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<div <div
@@ -1002,11 +1002,11 @@ const copyToClipboard = async (text) => {
const getMethodClass = (method) => { const getMethodClass = (method) => {
const map = { const map = {
GET: 'bg-emerald-500/10 text-emerald-500 border-emerald-500/20', GET: 'bg-emerald-500/10 text-emerald-500 border-emerald-500/20',
POST: 'bg-blue-500/10 text-blue-500 border-blue-500/20', POST: 'bg-blue-500/10 text-blue-500 border-blue-200',
PUT: 'bg-amber-500/10 text-amber-500 border-amber-500/20', PUT: 'bg-amber-500/10 text-amber-500 border-amber-500/20',
DELETE: 'bg-red-500/10 text-red-500 border-red-500/20' DELETE: 'bg-red-500/10 text-red-500 border-red-500/20'
} }
return map[method] || 'bg-zinc-500/10 text-zinc-500 border-zinc-500/20' return map[method] || 'bg-zinc-100 text-zinc-500 border-zinc-200'
} }
const getStatusColorClass = (code) => { const getStatusColorClass = (code) => {
@@ -1025,7 +1025,7 @@ onMounted(() => {
.loading-spinner { .loading-spinner {
width: 24px; width: 24px;
height: 24px; height: 24px;
border: 2px solid rgba(255, 255, 255, 0.1); border: 2px solid rgba(0, 0, 0, 0.08);
border-top-color: var(--border-secondary, #3b82f6); border-top-color: var(--border-secondary, #3b82f6);
border-radius: 50%; border-radius: 50%;
animation: spin 1s linear infinite; animation: spin 1s linear infinite;
+3 -3
View File
@@ -520,7 +520,7 @@ const formatFileSize = (bytes) => {
/* 主容器 */ /* 主容器 */
.backup-manager { .backup-manager {
min-height: 100vh; min-height: 100vh;
background: var(--bg-primary, #1a1a1a); background: var(--bg-primary, #ffffff);
color: var(--text-primary, #e5e5e5); color: var(--text-primary, #e5e5e5);
padding: 2rem; padding: 2rem;
position: relative; position: relative;
@@ -623,7 +623,7 @@ const formatFileSize = (bytes) => {
transition: all 0.2s ease; transition: all 0.2s ease;
display: block; display: block;
text-align: center; text-align: center;
color: white; color: var(--text-primary, #09090b);
box-sizing: border-box; box-sizing: border-box;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
} }
@@ -1043,7 +1043,7 @@ const formatFileSize = (bytes) => {
right: 1.5rem; right: 1.5rem;
padding: 1rem 1.5rem; padding: 1rem 1.5rem;
border-radius: 8px; border-radius: 8px;
color: white; color: var(--text-primary, #09090b);
font-weight: 500; font-weight: 500;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
z-index: 1001; z-index: 1001;
+39 -39
View File
@@ -13,13 +13,13 @@
@click="$emit('close')" @click="$emit('close')"
> >
<div <div
class="bg-zinc-900 border border-zinc-800 w-full max-w-2xl rounded-xl overflow-hidden shadow-2xl flex flex-col max-h-[90vh]" class="bg-white border border-zinc-200 w-full max-w-2xl rounded-xl overflow-hidden shadow-2xl flex flex-col max-h-[90vh]"
@click.stop @click.stop
> >
<!-- 头部 --> <!-- 头部 -->
<div class="p-8 pb-4 flex items-center justify-between border-b border-zinc-800/50"> <div class="p-8 pb-4 flex items-center justify-between border-b border-zinc-200">
<div> <div>
<h3 class="text-xl font-black text-zinc-100 tracking-tight flex items-center gap-3"> <h3 class="text-xl font-black text-zinc-900 tracking-tight flex items-center gap-3">
<div <div
class="w-10 h-10 rounded-xl bg-purple-600/10 flex items-center justify-center text-purple-500" class="w-10 h-10 rounded-xl bg-purple-600/10 flex items-center justify-center text-purple-500"
> >
@@ -32,7 +32,7 @@
</p> </p>
</div> </div>
<button <button
class="p-3 bg-zinc-800/50 hover:bg-zinc-800 text-zinc-500 hover:text-zinc-200 rounded-xl transition-all" class="p-3 bg-zinc-100/50 hover:bg-zinc-100 text-zinc-500 hover:text-zinc-700 rounded-xl transition-all"
@click="$emit('close')" @click="$emit('close')"
> >
<X :size="20" /> <X :size="20" />
@@ -47,7 +47,7 @@
'relative flex flex-col p-5 rounded-xl border-2 transition-all cursor-pointer group', 'relative flex flex-col p-5 rounded-xl border-2 transition-all cursor-pointer group',
updateType === 'grade-only' updateType === 'grade-only'
? 'bg-purple-500/5 border-purple-500/50 ring-4 ring-purple-500/10' ? 'bg-purple-500/5 border-purple-500/50 ring-4 ring-purple-500/10'
: 'bg-zinc-950 border-zinc-800 hover:border-zinc-700' : 'bg-white border-zinc-200 hover:border-zinc-200'
]" ]"
> >
<input v-model="updateType" type="radio" value="grade-only" class="sr-only" > <input v-model="updateType" type="radio" value="grade-only" class="sr-only" >
@@ -57,7 +57,7 @@
'w-10 h-10 rounded-lg flex items-center justify-center transition-colors', 'w-10 h-10 rounded-lg flex items-center justify-center transition-colors',
updateType === 'grade-only' updateType === 'grade-only'
? 'bg-purple-500 text-white' ? 'bg-purple-500 text-white'
: 'bg-zinc-800 text-zinc-500 group-hover:text-zinc-300' : 'bg-zinc-100 text-zinc-500 group-hover:text-zinc-600'
]" ]"
> >
<Calendar :size="18" /> <Calendar :size="18" />
@@ -69,7 +69,7 @@
<Check :size="12" class="text-white" /> <Check :size="12" class="text-white" />
</div> </div>
</div> </div>
<span class="text-sm font-black text-zinc-200 uppercase tracking-widest" <span class="text-sm font-black text-zinc-700 uppercase tracking-widest"
>仅更新年级</span >仅更新年级</span
> >
<span class="text-[10px] text-zinc-500 mt-1 font-medium leading-relaxed" <span class="text-[10px] text-zinc-500 mt-1 font-medium leading-relaxed"
@@ -82,7 +82,7 @@
'relative flex flex-col p-5 rounded-xl border-2 transition-all cursor-pointer group', 'relative flex flex-col p-5 rounded-xl border-2 transition-all cursor-pointer group',
updateType === 'excel-batch' updateType === 'excel-batch'
? 'bg-emerald-500/5 border-emerald-500/50 ring-4 ring-emerald-500/10' ? 'bg-emerald-500/5 border-emerald-500/50 ring-4 ring-emerald-500/10'
: 'bg-zinc-950 border-zinc-800 hover:border-zinc-700' : 'bg-white border-zinc-200 hover:border-zinc-200'
]" ]"
> >
<input v-model="updateType" type="radio" value="excel-batch" class="sr-only" > <input v-model="updateType" type="radio" value="excel-batch" class="sr-only" >
@@ -92,7 +92,7 @@
'w-10 h-10 rounded-lg flex items-center justify-center transition-colors', 'w-10 h-10 rounded-lg flex items-center justify-center transition-colors',
updateType === 'excel-batch' updateType === 'excel-batch'
? 'bg-emerald-500 text-white' ? 'bg-emerald-500 text-white'
: 'bg-zinc-800 text-zinc-500 group-hover:text-zinc-300' : 'bg-zinc-100 text-zinc-500 group-hover:text-zinc-600'
]" ]"
> >
<FileSpreadsheet :size="18" /> <FileSpreadsheet :size="18" />
@@ -104,7 +104,7 @@
<Check :size="12" class="text-white" /> <Check :size="12" class="text-white" />
</div> </div>
</div> </div>
<span class="text-sm font-black text-zinc-200 uppercase tracking-widest" <span class="text-sm font-black text-zinc-700 uppercase tracking-widest"
>Excel 批量更新</span >Excel 批量更新</span
> >
<span class="text-[10px] text-zinc-500 mt-1 font-medium leading-relaxed" <span class="text-[10px] text-zinc-500 mt-1 font-medium leading-relaxed"
@@ -117,7 +117,7 @@
'relative flex flex-col p-5 rounded-xl border-2 transition-all cursor-pointer group', 'relative flex flex-col p-5 rounded-xl border-2 transition-all cursor-pointer group',
updateType === 'status-batch' updateType === 'status-batch'
? 'bg-amber-500/5 border-amber-500/50 ring-4 ring-amber-500/10' ? 'bg-amber-500/5 border-amber-500/50 ring-4 ring-amber-500/10'
: 'bg-zinc-950 border-zinc-800 hover:border-zinc-700' : 'bg-white border-zinc-200 hover:border-zinc-200'
]" ]"
> >
<input v-model="updateType" type="radio" value="status-batch" class="sr-only" > <input v-model="updateType" type="radio" value="status-batch" class="sr-only" >
@@ -127,7 +127,7 @@
'w-10 h-10 rounded-lg flex items-center justify-center transition-colors', 'w-10 h-10 rounded-lg flex items-center justify-center transition-colors',
updateType === 'status-batch' updateType === 'status-batch'
? 'bg-amber-500 text-white' ? 'bg-amber-500 text-white'
: 'bg-zinc-800 text-zinc-500 group-hover:text-zinc-300' : 'bg-zinc-100 text-zinc-500 group-hover:text-zinc-600'
]" ]"
> >
<ShieldAlert :size="18" /> <ShieldAlert :size="18" />
@@ -139,7 +139,7 @@
<Check :size="12" class="text-white" /> <Check :size="12" class="text-white" />
</div> </div>
</div> </div>
<span class="text-sm font-black text-zinc-200 uppercase tracking-widest" <span class="text-sm font-black text-zinc-700 uppercase tracking-widest"
>设置账户状态</span >设置账户状态</span
> >
<span class="text-[10px] text-zinc-500 mt-1 font-medium leading-relaxed" <span class="text-[10px] text-zinc-500 mt-1 font-medium leading-relaxed"
@@ -153,7 +153,7 @@
v-if="['grade-only', 'status-batch'].includes(updateType)" v-if="['grade-only', 'status-batch'].includes(updateType)"
class="space-y-6 animate-in fade-in slide-in-from-bottom-2 duration-300" class="space-y-6 animate-in fade-in slide-in-from-bottom-2 duration-300"
> >
<div class="p-6 bg-zinc-950/50 border border-zinc-800/50 rounded-xl space-y-6"> <div class="p-6 bg-white/70 border border-zinc-200 rounded-xl space-y-6">
<div <div
class="flex items-center gap-2 text-xs font-black text-zinc-400 uppercase tracking-widest" class="flex items-center gap-2 text-xs font-black text-zinc-400 uppercase tracking-widest"
> >
@@ -203,7 +203,7 @@
</button> </button>
</div> </div>
<div <div
class="max-h-48 overflow-y-auto rounded-lg border border-zinc-800 bg-zinc-950 p-2 custom-scrollbar" class="max-h-48 overflow-y-auto rounded-lg border border-zinc-200 bg-white p-2 custom-scrollbar"
> >
<div <div
v-if="filteredStudents.length === 0" v-if="filteredStudents.length === 0"
@@ -215,17 +215,17 @@
<label <label
v-for="student in filteredStudents" v-for="student in filteredStudents"
:key="student.id" :key="student.id"
class="flex items-center gap-3 p-3 rounded-lg hover:bg-zinc-900/50 cursor-pointer transition-colors group" class="flex items-center gap-3 p-3 rounded-lg hover:bg-zinc-50/50 cursor-pointer transition-colors group"
> >
<input <input
v-model="selectedUserIds" v-model="selectedUserIds"
:value="student.id" :value="student.id"
type="checkbox" type="checkbox"
class="w-4 h-4 rounded-md border-zinc-700 bg-zinc-950 text-purple-600 focus:ring-purple-500/20" class="w-4 h-4 rounded-md border-zinc-200 bg-white text-purple-700 focus:ring-purple-500/30"
> >
<div class="flex flex-col"> <div class="flex flex-col">
<span <span
class="text-xs font-bold text-zinc-200 group-hover:text-purple-400 transition-colors" class="text-xs font-bold text-zinc-700 group-hover:text-purple-400 transition-colors"
>{{ student.name }}</span >{{ student.name }}</span
> >
<span class="text-[10px] text-zinc-600 font-mono">{{ <span class="text-[10px] text-zinc-600 font-mono">{{
@@ -261,21 +261,21 @@
/> />
</div> </div>
<label <label
class="flex items-center gap-3 p-3 bg-zinc-950 border border-zinc-800 rounded-lg cursor-pointer hover:border-zinc-700 transition-all" class="flex items-center gap-3 p-3 bg-white border border-zinc-200 rounded-lg cursor-pointer hover:border-zinc-200 transition-all"
> >
<input <input
v-model="keepClass" v-model="keepClass"
type="checkbox" type="checkbox"
class="w-5 h-5 rounded-md border-zinc-700 bg-zinc-950 text-purple-600 focus:ring-purple-500/20" class="w-5 h-5 rounded-md border-zinc-200 bg-white text-purple-700 focus:ring-purple-500/30"
> >
<span class="text-xs font-bold text-zinc-300">保持原有班级不变</span> <span class="text-xs font-bold text-zinc-600">保持原有班级不变</span>
</label> </label>
</div> </div>
</div> </div>
<!-- 目标状态设置 --> <!-- 目标状态设置 -->
<div v-if="updateType === 'status-batch'" class="p-6 bg-amber-500/5 border border-amber-500/20 rounded-xl space-y-6"> <div v-if="updateType === 'status-batch'" class="p-6 bg-amber-500/5 border border-amber-500/20 rounded-xl space-y-6">
<div class="flex items-center gap-2 text-xs font-black text-amber-400 uppercase tracking-widest"> <div class="flex items-center gap-2 text-xs font-black text-amber-700 uppercase tracking-widest">
<Save :size="14" /> <Save :size="14" />
目标状态设置 目标状态设置
</div> </div>
@@ -299,7 +299,7 @@
v-model="statusReason" v-model="statusReason"
rows="2" rows="2"
placeholder="例如: 2025届学生统一毕业" placeholder="例如: 2025届学生统一毕业"
class="w-full bg-zinc-950 border border-zinc-800 rounded-lg pl-11 pr-4 py-3 text-xs focus:outline-none focus:border-amber-500/30 transition-all text-zinc-200 resize-none" class="w-full bg-white border border-zinc-200 rounded-lg pl-11 pr-4 py-3 text-xs focus:outline-none focus:border-amber-200 transition-all text-zinc-700 resize-none"
></textarea> ></textarea>
</div> </div>
</div> </div>
@@ -336,19 +336,19 @@
'w-full py-12 border-2 border-dashed rounded-xl transition-all flex flex-col items-center justify-center gap-4', 'w-full py-12 border-2 border-dashed rounded-xl transition-all flex flex-col items-center justify-center gap-4',
isDragOver isDragOver
? 'border-emerald-500 bg-emerald-500/10' ? 'border-emerald-500 bg-emerald-500/10'
: 'border-zinc-800 hover:border-emerald-500/50 hover:bg-emerald-500/5' : 'border-zinc-200 hover:border-emerald-500/50 hover:bg-emerald-500/5'
]" ]"
> >
<div <div
:class="[ :class="[
'w-16 h-16 rounded-lg bg-zinc-900 flex items-center justify-center transition-colors shadow-xl', 'w-16 h-16 rounded-lg bg-white flex items-center justify-center transition-colors shadow-xl',
isDragOver ? 'text-emerald-500' : 'text-zinc-600 group-hover:text-emerald-500' isDragOver ? 'text-emerald-500' : 'text-zinc-600 group-hover:text-emerald-500'
]" ]"
> >
<Upload :size="32" /> <Upload :size="32" />
</div> </div>
<div class="text-center"> <div class="text-center">
<p class="text-base font-black text-zinc-200 tracking-tight"> <p class="text-base font-black text-zinc-700 tracking-tight">
拖拽 Excel 文件到此处 拖拽 Excel 文件到此处
</p> </p>
<p class="text-xs text-zinc-500 mt-1"> <p class="text-xs text-zinc-500 mt-1">
@@ -362,7 +362,7 @@
<!-- 模板与说明 --> <!-- 模板与说明 -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-4"> <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div <div
class="md:col-span-2 p-5 bg-zinc-950 border border-zinc-800 rounded-xl space-y-3" class="md:col-span-2 p-5 bg-white border border-zinc-200 rounded-xl space-y-3"
> >
<div <div
class="flex items-center gap-2 text-[10px] font-black text-zinc-500 uppercase tracking-widest" class="flex items-center gap-2 text-[10px] font-black text-zinc-500 uppercase tracking-widest"
@@ -372,15 +372,15 @@
</div> </div>
<ul class="text-[10px] text-zinc-600 space-y-1.5 font-medium leading-relaxed"> <ul class="text-[10px] text-zinc-600 space-y-1.5 font-medium leading-relaxed">
<li class="flex items-start gap-2"> <li class="flex items-start gap-2">
<div class="w-1 h-1 rounded-full bg-zinc-700 mt-1.5" /> <div class="w-1 h-1 rounded-full bg-zinc-200 mt-1.5" />
第一行为表头:用户名、姓名、年级、班级、新用户名 第一行为表头:用户名、姓名、年级、班级、新用户名
</li> </li>
<li class="flex items-start gap-2"> <li class="flex items-start gap-2">
<div class="w-1 h-1 rounded-full bg-zinc-700 mt-1.5" /> <div class="w-1 h-1 rounded-full bg-zinc-200 mt-1.5" />
<span class="text-zinc-400 font-bold">用户名</span> 列用于精确匹配现有账户 <span class="text-zinc-400 font-bold">用户名</span> 列用于精确匹配现有账户
</li> </li>
<li class="flex items-start gap-2"> <li class="flex items-start gap-2">
<div class="w-1 h-1 rounded-full bg-zinc-700 mt-1.5" /> <div class="w-1 h-1 rounded-full bg-zinc-200 mt-1.5" />
字段留空则保持原值不变,不进行覆盖更新 字段留空则保持原值不变,不进行覆盖更新
</li> </li>
</ul> </ul>
@@ -417,11 +417,11 @@
</div> </div>
</div> </div>
</div> </div>
<div class="rounded-xl border border-zinc-800 bg-zinc-950 overflow-hidden shadow-xl"> <div class="rounded-xl border border-zinc-200 bg-white overflow-hidden shadow-xl">
<div class="overflow-x-auto custom-scrollbar"> <div class="overflow-x-auto custom-scrollbar">
<table class="w-full text-left border-collapse"> <table class="w-full text-left border-collapse">
<thead <thead
class="bg-zinc-900/80 text-[10px] font-black text-zinc-500 uppercase tracking-widest border-b border-zinc-800" class="bg-zinc-50/80 text-[10px] font-black text-zinc-500 uppercase tracking-widest border-b border-zinc-200"
> >
<tr> <tr>
<th class="px-5 py-4 whitespace-nowrap">匹配用户</th> <th class="px-5 py-4 whitespace-nowrap">匹配用户</th>
@@ -435,7 +435,7 @@
v-for="(row, index) in excelPreviewData.slice(0, 10)" v-for="(row, index) in excelPreviewData.slice(0, 10)"
:key="index" :key="index"
:class="[ :class="[
row.error ? 'bg-red-500/5' : 'hover:bg-zinc-900/30 transition-colors' row.error ? 'bg-red-500/5' : 'hover:bg-zinc-50/30 transition-colors'
]" ]"
> >
<td class="px-5 py-4"> <td class="px-5 py-4">
@@ -443,7 +443,7 @@
<span <span
:class="[ :class="[
'text-xs font-bold', 'text-xs font-bold',
row.error ? 'text-red-400' : 'text-zinc-200' row.error ? 'text-red-400' : 'text-zinc-700'
]" ]"
>{{ row.username }}</span >{{ row.username }}</span
> >
@@ -459,11 +459,11 @@
</td> </td>
<td class="px-5 py-4"> <td class="px-5 py-4">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="text-xs font-bold text-emerald-400">{{ <span class="text-xs font-bold text-emerald-700">{{
row.newGrade || row.currentGrade || '-' row.newGrade || row.currentGrade || '-'
}}</span> }}</span>
<span class="text-[10px] text-zinc-700">/</span> <span class="text-[10px] text-zinc-700">/</span>
<span class="text-xs font-bold text-emerald-400">{{ <span class="text-xs font-bold text-emerald-700">{{
row.newClass || row.currentClass || '-' row.newClass || row.currentClass || '-'
}}</span> }}</span>
</div> </div>
@@ -488,7 +488,7 @@
</div> </div>
<div <div
v-if="excelPreviewData.length > 10" v-if="excelPreviewData.length > 10"
class="p-4 text-center border-t border-zinc-900 bg-zinc-900/20 text-[10px] text-zinc-600 font-bold uppercase tracking-widest" class="p-4 text-center border-t border-zinc-900 bg-zinc-50/20 text-[10px] text-zinc-600 font-bold uppercase tracking-widest"
> >
以及另外 {{ excelPreviewData.length - 10 }} 条记录已在队列中 以及另外 {{ excelPreviewData.length - 10 }} 条记录已在队列中
</div> </div>
@@ -507,9 +507,9 @@
</div> </div>
<!-- 底部按钮 --> <!-- 底部按钮 -->
<div class="p-8 pt-4 border-t border-zinc-800/50 bg-zinc-900/50 flex gap-3"> <div class="p-8 pt-4 border-t border-zinc-200 bg-zinc-50/50 flex gap-3">
<button <button
class="flex-1 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" class="flex-1 px-6 py-4 bg-zinc-100 hover:bg-zinc-200 text-zinc-600 text-xs font-black rounded-2xl transition-all uppercase tracking-widest"
@click="$emit('close')" @click="$emit('close')"
> >
取消操作 取消操作
+39 -38
View File
@@ -5,7 +5,7 @@
<!-- 页面标题 --> <!-- 页面标题 -->
<div class="flex flex-col md:flex-row md:items-end justify-between gap-6"> <div class="flex flex-col md:flex-row md:items-end justify-between gap-6">
<div> <div>
<h2 class="text-2xl font-black text-zinc-100 tracking-tight flex items-center gap-3"> <h2 class="text-2xl font-black text-zinc-900 tracking-tight flex items-center gap-3">
歌曲黑名单管理 歌曲黑名单管理
</h2> </h2>
<p class="text-xs text-zinc-500 mt-1 font-medium"> <p class="text-xs text-zinc-500 mt-1 font-medium">
@@ -15,7 +15,7 @@
</div> </div>
<!-- 添加黑名单项表单 --> <!-- 添加黑名单项表单 -->
<section class="bg-zinc-900/40 border border-zinc-800 rounded-xl p-8 shadow-xl"> <section class="bg-zinc-50/40 border border-zinc-200 rounded-xl p-8 shadow-xl">
<h3 class="text-[10px] font-black text-zinc-600 uppercase tracking-[0.2em] mb-6 px-1"> <h3 class="text-[10px] font-black text-zinc-600 uppercase tracking-[0.2em] mb-6 px-1">
添加黑名单项 添加黑名单项
</h3> </h3>
@@ -24,31 +24,31 @@
<label class="text-[10px] font-black text-zinc-600 uppercase tracking-widest px-1" <label class="text-[10px] font-black text-zinc-600 uppercase tracking-widest px-1"
>封禁类型</label >封禁类型</label
> >
<div class="flex p-1 bg-zinc-950 border border-zinc-800 rounded-lg gap-1"> <div class="flex p-1 bg-white border border-zinc-200 rounded-lg gap-1">
<button <button
class="flex-1 flex items-center justify-center gap-2 py-2 rounded-md text-[11px] font-bold transition-all" class="flex-1 flex items-center justify-center gap-2 py-2 rounded-md text-[11px] font-bold transition-all"
:class="newItem.type === 'SONG' ? 'bg-zinc-800 text-blue-400' : 'text-zinc-600'" :class="newItem.type === 'SONG' ? 'bg-zinc-100 text-blue-700' : 'text-zinc-600'"
@click="newItem.type = 'SONG'" @click="newItem.type = 'SONG'"
> >
<Music :size="12" /> 歌曲 <Music :size="12" /> 歌曲
</button> </button>
<button <button
class="flex-1 flex items-center justify-center gap-2 py-2 rounded-md text-[11px] font-bold transition-all" class="flex-1 flex items-center justify-center gap-2 py-2 rounded-md text-[11px] font-bold transition-all"
:class="newItem.type === 'KEYWORD' ? 'bg-zinc-800 text-purple-400' : 'text-zinc-600'" :class="newItem.type === 'KEYWORD' ? 'bg-zinc-100 text-purple-400' : 'text-zinc-600'"
@click="newItem.type = 'KEYWORD'" @click="newItem.type = 'KEYWORD'"
> >
<Type :size="12" /> 关键词 <Type :size="12" /> 关键词
</button> </button>
<button <button
class="flex-1 flex items-center justify-center gap-2 py-2 rounded-md text-[11px] font-bold transition-all" class="flex-1 flex items-center justify-center gap-2 py-2 rounded-md text-[11px] font-bold transition-all"
:class="newItem.type === 'LANGUAGE' ? 'bg-zinc-800 text-amber-400' : 'text-zinc-600'" :class="newItem.type === 'LANGUAGE' ? 'bg-zinc-100 text-amber-700' : 'text-zinc-600'"
@click="newItem.type = 'LANGUAGE'" @click="newItem.type = 'LANGUAGE'"
> >
<Languages :size="12" /> 语种 <Languages :size="12" /> 语种
</button> </button>
<button <button
class="flex-1 flex items-center justify-center gap-2 py-2 rounded-md text-[11px] font-bold transition-all" class="flex-1 flex items-center justify-center gap-2 py-2 rounded-md text-[11px] font-bold transition-all"
:class="newItem.type === 'GENRE' ? 'bg-zinc-800 text-emerald-400' : 'text-zinc-600'" :class="newItem.type === 'GENRE' ? 'bg-zinc-100 text-emerald-700' : 'text-zinc-600'"
@click="newItem.type = 'GENRE'" @click="newItem.type = 'GENRE'"
> >
<Disc3 :size="12" /> 曲风 <Disc3 :size="12" /> 曲风
@@ -64,17 +64,18 @@
v-model="newItem.value" v-model="newItem.value"
type="text" type="text"
:placeholder="typePlaceholder" :placeholder="typePlaceholder"
class="w-full bg-zinc-950 border border-zinc-800 rounded-lg px-5 py-3 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30 placeholder:text-zinc-700 transition-all" class="w-full bg-white border border-zinc-200 rounded-lg px-5 py-3 text-sm text-zinc-700 focus:outline-none focus:border-blue-200 placeholder:text-zinc-700 transition-all"
> >
</div> </div>
<div v-if="newItem.type === 'LANGUAGE' || newItem.type === 'GENRE'" class="xl:col-span-5 xl:col-start-auto space-y-1"> <div v-if="newItem.type === 'LANGUAGE' || newItem.type === 'GENRE'" class="xl:col-span-5 xl:col-start-auto space-y-1">
<p class="text-[10px] text-zinc-600 font-bold leading-relaxed px-1"> <p class="text-[10px] text-zinc-600 font-bold leading-relaxed px-1 flex items-start gap-1.5">
<Icon name="lightbulb" :size="13" class="text-amber-600 shrink-0 mt-[2px]" />
<template v-if="newItem.type === 'LANGUAGE'"> <template v-if="newItem.type === 'LANGUAGE'">
💡 支持多语种匹配:中文 / 英文 / 日文 / 韩文 / 俄文 / 阿拉伯文 等,支持逗号分隔,例如:<span class="text-amber-400">日文,韩文</span> 支持多语种匹配:中文 / 英文 / 日文 / 韩文 / 俄文 / 阿拉伯文 等,支持逗号分隔,例如:<span class="text-amber-700">日文,韩文</span>
</template> </template>
<template v-else> <template v-else>
💡 支持曲风关键词,可逗号分隔,例如:<span class="text-emerald-400">DJ,摇滚,rap</span> 支持曲风关键词,可逗号分隔,例如:<span class="text-emerald-700">DJ,摇滚,rap</span>
</template> </template>
</p> </p>
</div> </div>
@@ -87,7 +88,7 @@
v-model="newItem.reason" v-model="newItem.reason"
type="text" type="text"
placeholder="简述加入黑名单的原因" placeholder="简述加入黑名单的原因"
class="w-full bg-zinc-950 border border-zinc-800 rounded-lg px-5 py-3 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30 placeholder:text-zinc-700 transition-all" class="w-full bg-white border border-zinc-200 rounded-lg px-5 py-3 text-sm text-zinc-700 focus:outline-none focus:border-blue-200 placeholder:text-zinc-700 transition-all"
> >
</div> </div>
@@ -110,7 +111,7 @@
<!-- 筛选和搜索 --> <!-- 筛选和搜索 -->
<div <div
class="bg-zinc-900/20 border border-zinc-800 rounded-xl p-4 flex flex-col md:flex-row gap-4 items-center" class="bg-zinc-50/20 border border-zinc-200 rounded-xl p-4 flex flex-col md:flex-row gap-4 items-center"
> >
<div class="flex-1 relative w-full"> <div class="flex-1 relative w-full">
<Search class="absolute left-4 top-1/2 -translate-y-1/2 text-zinc-700" :size="18" /> <Search class="absolute left-4 top-1/2 -translate-y-1/2 text-zinc-700" :size="18" />
@@ -118,7 +119,7 @@
v-model="filters.search" v-model="filters.search"
type="text" type="text"
placeholder="在黑名单中搜索..." placeholder="在黑名单中搜索..."
class="w-full bg-zinc-950/50 border border-zinc-800 rounded-lg pl-12 pr-4 py-3 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30 placeholder:text-zinc-700 transition-all" class="w-full bg-white/70 border border-zinc-200 rounded-lg pl-12 pr-4 py-3 text-sm text-zinc-700 focus:outline-none focus:border-blue-200 placeholder:text-zinc-700 transition-all"
@input="debounceSearch" @input="debounceSearch"
> >
</div> </div>
@@ -131,7 +132,7 @@
@change="handleTypeFilterChange" @change="handleTypeFilterChange"
/> />
<button <button
class="p-3 bg-zinc-950 border border-zinc-800 rounded-lg text-zinc-600 hover:text-blue-400 transition-all active:scale-95" class="p-3 bg-white border border-zinc-200 rounded-lg text-zinc-600 hover:text-blue-700 transition-all active:scale-95"
@click="loadBlacklist" @click="loadBlacklist"
> >
<Filter :size="18" /> <Filter :size="18" />
@@ -159,8 +160,8 @@
<div <div
v-for="item in blacklist" v-for="item in blacklist"
:key="item.id" :key="item.id"
class="group flex flex-col lg:flex-row lg:items-center gap-6 p-6 bg-zinc-900/30 border rounded-xl transition-all hover:border-zinc-700" class="group flex flex-col lg:flex-row lg:items-center gap-6 p-6 bg-zinc-50/30 border rounded-xl transition-all hover:border-zinc-200"
:class="[item.isActive ? 'border-zinc-800' : 'border-zinc-800/40 opacity-60']" :class="[item.isActive ? 'border-zinc-200' : 'border-zinc-200 opacity-60']"
> >
<div class="flex-1 flex items-start gap-5"> <div class="flex-1 flex items-start gap-5">
<div <div
@@ -181,7 +182,7 @@
> >
{{ getTypeLabel(item.type) }} {{ getTypeLabel(item.type) }}
</span> </span>
<h5 class="text-base font-black text-zinc-100 truncate tracking-tight"> <h5 class="text-base font-black text-zinc-900 truncate tracking-tight">
{{ item.value }} {{ item.value }}
</h5> </h5>
</div> </div>
@@ -202,14 +203,14 @@
</div> </div>
<div <div
class="flex items-center justify-end gap-3 pt-4 lg:pt-0 border-t lg:border-t-0 border-zinc-800/50" class="flex items-center justify-end gap-3 pt-4 lg:pt-0 border-t lg:border-t-0 border-zinc-200"
> >
<button <button
:disabled="loading" :disabled="loading"
class="flex items-center gap-2 px-5 py-2.5 rounded-xl text-[10px] font-black uppercase tracking-widest transition-all" class="flex items-center gap-2 px-5 py-2.5 rounded-xl text-[10px] font-black uppercase tracking-widest transition-all"
:class="[ :class="[
item.isActive item.isActive
? 'bg-zinc-800 text-zinc-500 hover:text-amber-500 hover:bg-amber-500/5 hover:border-amber-500/10 border border-transparent' ? 'bg-zinc-100 text-zinc-500 hover:text-amber-500 hover:bg-amber-500/5 hover:border-amber-500/10 border border-transparent'
: 'bg-emerald-600 text-white shadow-lg shadow-emerald-900/20' : 'bg-emerald-600 text-white shadow-lg shadow-emerald-900/20'
]" ]"
@click="toggleItemStatus(item)" @click="toggleItemStatus(item)"
@@ -219,7 +220,7 @@
</button> </button>
<button <button
:disabled="loading" :disabled="loading"
class="p-3 bg-zinc-950 border border-zinc-800 rounded-xl text-zinc-700 hover:text-red-500 hover:border-red-500/30 transition-all active:scale-95" class="p-3 bg-white border border-zinc-200 rounded-xl text-zinc-700 hover:text-red-500 hover:border-red-200 transition-all active:scale-95"
@click="deleteItem(item)" @click="deleteItem(item)"
> >
<Trash2 :size="16" /> <Trash2 :size="16" />
@@ -234,7 +235,7 @@
class="py-20 flex flex-col items-center justify-center space-y-4" class="py-20 flex flex-col items-center justify-center space-y-4"
> >
<div <div
class="w-12 h-12 border-4 border-zinc-800 border-t-blue-500 rounded-full animate-spin" class="w-12 h-12 border-4 border-zinc-200 border-t-blue-500 rounded-full animate-spin"
/> />
<p class="text-zinc-500 text-xs font-bold uppercase tracking-widest">加载中...</p> <p class="text-zinc-500 text-xs font-bold uppercase tracking-widest">加载中...</p>
</div> </div>
@@ -242,10 +243,10 @@
<!-- 空状态 --> <!-- 空状态 -->
<div <div
v-else-if="blacklist.length === 0" v-else-if="blacklist.length === 0"
class="py-20 flex flex-col items-center justify-center text-center space-y-4 bg-zinc-900/10 border border-zinc-800/40 border-dashed rounded-xl" class="py-20 flex flex-col items-center justify-center text-center space-y-4 bg-zinc-50/10 border border-zinc-200 border-dashed rounded-xl"
> >
<div <div
class="w-16 h-16 rounded-lg bg-zinc-950 border border-zinc-800 flex items-center justify-center text-zinc-800 shadow-xl" class="w-16 h-16 rounded-lg bg-white border border-zinc-200 flex items-center justify-center text-zinc-800 shadow-xl"
> >
<Ban :size="32" /> <Ban :size="32" />
</div> </div>
@@ -262,19 +263,19 @@
<div v-if="pagination.pages > 1" class="flex items-center justify-center gap-4 mt-8"> <div v-if="pagination.pages > 1" class="flex items-center justify-center gap-4 mt-8">
<button <button
:disabled="pagination.page <= 1 || loading" :disabled="pagination.page <= 1 || loading"
class="p-3 bg-zinc-950 border border-zinc-800 rounded-lg text-zinc-400 hover:text-zinc-100 disabled:opacity-30 disabled:cursor-not-allowed transition-all active:scale-95" class="p-3 bg-white border border-zinc-200 rounded-lg text-zinc-400 hover:text-zinc-900 disabled:opacity-30 disabled:cursor-not-allowed transition-all active:scale-95"
@click="changePage(pagination.page - 1)" @click="changePage(pagination.page - 1)"
> >
<ChevronLeft :size="20" /> <ChevronLeft :size="20" />
</button> </button>
<div <div
class="px-6 py-3 bg-zinc-900/50 border border-zinc-800 rounded-lg text-[11px] font-black text-zinc-400 uppercase tracking-widest" class="px-6 py-3 bg-zinc-50/50 border border-zinc-200 rounded-lg text-[11px] font-black text-zinc-400 uppercase tracking-widest"
> >
第 {{ pagination.page }} 页 / 共 {{ pagination.pages }} 页 第 {{ pagination.page }} 页 / 共 {{ pagination.pages }} 页
</div> </div>
<button <button
:disabled="pagination.page >= pagination.pages || loading" :disabled="pagination.page >= pagination.pages || loading"
class="p-3 bg-zinc-950 border border-zinc-800 rounded-lg text-zinc-400 hover:text-zinc-100 disabled:opacity-30 disabled:cursor-not-allowed transition-all active:scale-95" class="p-3 bg-white border border-zinc-200 rounded-lg text-zinc-400 hover:text-zinc-900 disabled:opacity-30 disabled:cursor-not-allowed transition-all active:scale-95"
@click="changePage(pagination.page + 1)" @click="changePage(pagination.page + 1)"
> >
<ChevronRight :size="20" /> <ChevronRight :size="20" />
@@ -293,10 +294,10 @@
> >
<div <div
v-if="showDeleteDialog" v-if="showDeleteDialog"
class="fixed inset-0 z-[100] flex items-center justify-center p-4 bg-zinc-950/80 backdrop-blur-sm" class="fixed inset-0 z-[100] flex items-center justify-center p-4 bg-white/80 backdrop-blur-sm"
> >
<div <div
class="w-full max-w-md bg-zinc-900 border border-zinc-800 rounded-xl shadow-2xl overflow-hidden animate-in zoom-in-95 duration-300" class="w-full max-w-md bg-white border border-zinc-200 rounded-xl shadow-2xl overflow-hidden animate-in zoom-in-95 duration-300"
@click.stop @click.stop
> >
<div class="flex flex-col items-center py-8 space-y-6 text-center px-8"> <div class="flex flex-col items-center py-8 space-y-6 text-center px-8">
@@ -306,16 +307,16 @@
<Trash2 :size="28" /> <Trash2 :size="28" />
</div> </div>
<div class="space-y-2"> <div class="space-y-2">
<h4 class="text-lg font-bold text-zinc-100">确认从黑名单移除</h4> <h4 class="text-lg font-bold text-zinc-900">确认从黑名单移除</h4>
<p class="text-xs text-zinc-500 leading-relaxed"> <p class="text-xs text-zinc-500 leading-relaxed">
确定要移除 确定要移除
<span class="text-zinc-300 font-bold">"{{ deleteTargetItem?.value }}"</span> <span class="text-zinc-600 font-bold">"{{ deleteTargetItem?.value }}"</span>
吗?移除后,该项目将恢复为正常状态。 吗?移除后,该项目将恢复为正常状态。
</p> </p>
</div> </div>
<div class="flex gap-3 w-full pt-4"> <div class="flex gap-3 w-full pt-4">
<button <button
class="flex-1 px-4 py-4 bg-zinc-800 hover:bg-zinc-700 text-zinc-400 text-[10px] font-black rounded-lg transition-all uppercase tracking-widest" class="flex-1 px-4 py-4 bg-zinc-100 hover:bg-zinc-200 text-zinc-400 text-[10px] font-black rounded-lg transition-all uppercase tracking-widest"
@click="showDeleteDialog = false" @click="showDeleteDialog = false"
> >
取消 取消
@@ -422,7 +423,7 @@ const getTypeLabel = (type: string) => {
const getTypeIconClass = (type: string) => { const getTypeIconClass = (type: string) => {
switch (type) { switch (type) {
case 'SONG': case 'SONG':
return 'bg-blue-600/10 text-blue-500 border-blue-500/20 shadow-lg shadow-blue-900/5' return 'bg-blue-50 text-blue-500 border-blue-200 shadow-lg shadow-blue-900/5'
case 'KEYWORD': case 'KEYWORD':
return 'bg-purple-600/10 text-purple-500 border-purple-500/20 shadow-lg shadow-purple-900/5' return 'bg-purple-600/10 text-purple-500 border-purple-500/20 shadow-lg shadow-purple-900/5'
case 'LANGUAGE': case 'LANGUAGE':
@@ -430,22 +431,22 @@ const getTypeIconClass = (type: string) => {
case 'GENRE': case 'GENRE':
return 'bg-emerald-600/10 text-emerald-500 border-emerald-500/20 shadow-lg shadow-emerald-900/5' return 'bg-emerald-600/10 text-emerald-500 border-emerald-500/20 shadow-lg shadow-emerald-900/5'
default: default:
return 'bg-zinc-600/10 text-zinc-500 border-zinc-500/20' return 'bg-zinc-600/10 text-zinc-500 border-zinc-200'
} }
} }
const getTypeBadgeClass = (type: string) => { const getTypeBadgeClass = (type: string) => {
switch (type) { switch (type) {
case 'SONG': case 'SONG':
return 'bg-blue-600/10 text-blue-400 border-blue-500/20' return 'bg-blue-50 text-blue-700 border-blue-200'
case 'KEYWORD': case 'KEYWORD':
return 'bg-purple-600/10 text-purple-400 border-purple-500/20' return 'bg-purple-600/10 text-purple-400 border-purple-500/20'
case 'LANGUAGE': case 'LANGUAGE':
return 'bg-amber-600/10 text-amber-400 border-amber-500/20' return 'bg-amber-600/10 text-amber-700 border-amber-500/20'
case 'GENRE': case 'GENRE':
return 'bg-emerald-600/10 text-emerald-400 border-emerald-500/20' return 'bg-emerald-600/10 text-emerald-700 border-emerald-500/20'
default: default:
return 'bg-zinc-600/10 text-zinc-400 border-zinc-500/20' return 'bg-zinc-600/10 text-zinc-400 border-zinc-200'
} }
} }
+22 -22
View File
@@ -2,12 +2,12 @@
<div class="max-w-[1200px] mx-auto space-y-6 pb-24 px-2"> <div class="max-w-[1200px] mx-auto space-y-6 pb-24 px-2">
<div class="flex flex-col md:flex-row md:items-end justify-between gap-4"> <div class="flex flex-col md:flex-row md:items-end justify-between gap-4">
<div> <div>
<h2 class="text-2xl font-black text-zinc-100 tracking-tight">更新日志</h2> <h2 class="text-2xl font-black text-zinc-900 tracking-tight">更新日志</h2>
<p class="text-xs text-zinc-500 mt-1 font-medium">管理前台“更新日志”页面的发布内容</p> <p class="text-xs text-zinc-500 mt-1 font-medium">管理前台“更新日志”页面的发布内容</p>
</div> </div>
<div class="flex gap-3"> <div class="flex gap-3">
<button <button
class="flex items-center gap-2 px-5 py-2 bg-zinc-900 border border-zinc-800 hover:border-zinc-700 text-zinc-400 text-xs font-bold rounded-xl transition-all disabled:opacity-50 disabled:cursor-not-allowed" class="flex items-center gap-2 px-5 py-2 bg-white border border-zinc-200 hover:border-zinc-200 text-zinc-400 text-xs font-bold rounded-xl transition-all disabled:opacity-50 disabled:cursor-not-allowed"
:disabled="loading" :disabled="loading"
@click="load" @click="load"
> >
@@ -23,7 +23,7 @@
</div> </div>
<div v-if="loading" class="flex flex-col items-center justify-center py-20"> <div v-if="loading" class="flex flex-col items-center justify-center py-20">
<div class="w-8 h-8 border-4 border-zinc-800 border-t-blue-500 rounded-full animate-spin mb-4" /> <div class="w-8 h-8 border-4 border-zinc-200 border-t-blue-500 rounded-full animate-spin mb-4" />
<p class="text-zinc-500 text-sm">加载中...</p> <p class="text-zinc-500 text-sm">加载中...</p>
</div> </div>
@@ -31,20 +31,20 @@
<div <div
v-for="item in entries" v-for="item in entries"
:key="item.id" :key="item.id"
class="bg-zinc-900/40 border border-zinc-800 rounded-2xl p-5 flex flex-col md:flex-row md:items-center md:justify-between gap-4" class="bg-zinc-50/40 border border-zinc-200 rounded-2xl p-5 flex flex-col md:flex-row md:items-center md:justify-between gap-4"
> >
<div class="min-w-0"> <div class="min-w-0">
<div class="flex items-center gap-2 flex-wrap"> <div class="flex items-center gap-2 flex-wrap">
<span <span
class="px-2.5 py-1 rounded-lg text-[10px] font-black uppercase tracking-widest" class="px-2.5 py-1 rounded-lg text-[10px] font-black uppercase tracking-widest"
:class="item.published ? 'bg-emerald-500/10 text-emerald-300' : 'bg-zinc-800/50 text-zinc-400'" :class="item.published ? 'bg-emerald-500/10 text-emerald-700' : 'bg-zinc-100 text-zinc-600'"
> >
{{ item.published ? '已发布' : '草稿' }} {{ item.published ? '已发布' : '草稿' }}
</span> </span>
<span class="px-2.5 py-1 rounded-lg bg-blue-500/10 text-blue-300 text-[10px] font-black"> <span class="px-2.5 py-1 rounded-lg bg-blue-50 text-blue-700 text-[10px] font-black">
v{{ item.version }} v{{ item.version }}
</span> </span>
<p class="text-sm font-black text-zinc-100 truncate">{{ item.title }}</p> <p class="text-sm font-black text-zinc-900 truncate">{{ item.title }}</p>
</div> </div>
<p class="text-[10px] text-zinc-500 mt-2"> <p class="text-[10px] text-zinc-500 mt-2">
{{ formatDate(item.publishedAt || item.createdAt) }} {{ formatDate(item.publishedAt || item.createdAt) }}
@@ -53,13 +53,13 @@
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<button <button
class="px-3 py-2 rounded-lg border border-zinc-700 text-zinc-300 hover:bg-zinc-900 text-xs font-bold" class="px-3 py-2 rounded-lg border border-zinc-200 text-zinc-600 hover:bg-white text-xs font-bold"
@click="openEdit(item)" @click="openEdit(item)"
> >
<Pencil :size="14" class="inline-block mr-1" /> 编辑 <Pencil :size="14" class="inline-block mr-1" /> 编辑
</button> </button>
<button <button
class="px-3 py-2 rounded-lg border border-zinc-700 text-zinc-300 hover:bg-zinc-900 text-xs font-bold" class="px-3 py-2 rounded-lg border border-zinc-200 text-zinc-600 hover:bg-white text-xs font-bold"
:disabled="savingId === item.id" :disabled="savingId === item.id"
@click="togglePublish(item)" @click="togglePublish(item)"
> >
@@ -67,7 +67,7 @@
{{ item.published ? '取消发布' : '发布' }} {{ item.published ? '取消发布' : '发布' }}
</button> </button>
<button <button
class="px-3 py-2 rounded-lg border border-red-500/30 text-red-300 hover:bg-red-500/10 text-xs font-bold disabled:opacity-50" class="px-3 py-2 rounded-lg border border-red-200 text-red-300 hover:bg-red-500/10 text-xs font-bold disabled:opacity-50"
:disabled="savingId === item.id" :disabled="savingId === item.id"
@click="remove(item)" @click="remove(item)"
> >
@@ -83,11 +83,11 @@
<Teleport to="body"> <Teleport to="body">
<div v-if="showModal" class="fixed inset-0 z-[80] bg-black/60 backdrop-blur-sm p-4 flex items-end sm:items-center justify-center"> <div v-if="showModal" class="fixed inset-0 z-[80] bg-black/60 backdrop-blur-sm p-4 flex items-end sm:items-center justify-center">
<div class="w-full max-w-2xl bg-zinc-950 border border-zinc-800 rounded-2xl overflow-hidden"> <div class="w-full max-w-2xl bg-white border border-zinc-200 rounded-2xl overflow-hidden">
<div class="px-5 py-4 border-b border-zinc-800 flex items-center justify-between"> <div class="px-5 py-4 border-b border-zinc-200 flex items-center justify-between">
<p class="text-sm font-black text-zinc-100">{{ editingId ? '编辑更新日志' : '新建更新日志' }}</p> <p class="text-sm font-black text-zinc-900">{{ editingId ? '编辑更新日志' : '新建更新日志' }}</p>
<button <button
class="h-9 w-9 rounded-lg border border-zinc-700 text-zinc-400 hover:text-zinc-200 hover:bg-zinc-900 transition-colors" class="h-9 w-9 rounded-lg border border-zinc-200 text-zinc-400 hover:text-zinc-700 hover:bg-white transition-colors"
@click="closeModal" @click="closeModal"
> >
<X :size="16" class="mx-auto" /> <X :size="16" class="mx-auto" />
@@ -98,11 +98,11 @@
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3"> <div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div> <div>
<label class="text-[10px] font-black text-zinc-600 uppercase tracking-widest px-1 block mb-2">版本号</label> <label class="text-[10px] font-black text-zinc-600 uppercase tracking-widest px-1 block mb-2">版本号</label>
<input v-model="form.version" type="text" class="w-full bg-zinc-900 border border-zinc-800 rounded-xl px-4 py-2.5 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30 transition-all" placeholder="如:2.4.1" > <input v-model="form.version" type="text" class="w-full bg-white border border-zinc-200 rounded-xl px-4 py-2.5 text-sm text-zinc-700 focus:outline-none focus:border-blue-200 transition-all" placeholder="如:2.4.1" >
</div> </div>
<div> <div>
<label class="text-[10px] font-black text-zinc-600 uppercase tracking-widest px-1 block mb-2">标题</label> <label class="text-[10px] font-black text-zinc-600 uppercase tracking-widest px-1 block mb-2">标题</label>
<input v-model="form.title" type="text" class="w-full bg-zinc-900 border border-zinc-800 rounded-xl px-4 py-2.5 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30 transition-all" placeholder="如:投稿体验优化" > <input v-model="form.title" type="text" class="w-full bg-white border border-zinc-200 rounded-xl px-4 py-2.5 text-sm text-zinc-700 focus:outline-none focus:border-blue-200 transition-all" placeholder="如:投稿体验优化" >
</div> </div>
</div> </div>
@@ -111,23 +111,23 @@
<textarea <textarea
v-model="form.content" v-model="form.content"
:rows="10" :rows="10"
class="w-full bg-zinc-900 border border-zinc-800 rounded-xl px-4 py-3 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30 transition-all font-mono" class="w-full bg-white border border-zinc-200 rounded-xl px-4 py-3 text-sm text-zinc-700 focus:outline-none focus:border-blue-200 transition-all font-mono"
placeholder="支持纯文本(建议用分点)。" placeholder="支持纯文本(建议用分点)。"
/> />
</div> </div>
<div class="flex items-center justify-between gap-4 p-3 rounded-xl border border-zinc-800 bg-zinc-900/40"> <div class="flex items-center justify-between gap-4 p-3 rounded-xl border border-zinc-200 bg-zinc-50/40">
<div> <div>
<p class="text-xs font-bold text-zinc-200">立即发布</p> <p class="text-xs font-bold text-zinc-700">立即发布</p>
<p class="text-[10px] text-zinc-500 mt-0.5">发布后前台更新日志页面可见</p> <p class="text-[10px] text-zinc-500 mt-0.5">发布后前台更新日志页面可见</p>
</div> </div>
<input v-model="form.published" type="checkbox" class="w-5 h-5 rounded border-zinc-800 bg-zinc-900 accent-blue-600 cursor-pointer" > <input v-model="form.published" type="checkbox" class="w-5 h-5 rounded border-zinc-200 bg-white accent-blue-600 cursor-pointer" >
</div> </div>
</div> </div>
<div class="px-5 py-4 border-t border-zinc-800 flex items-center justify-end gap-3"> <div class="px-5 py-4 border-t border-zinc-200 flex items-center justify-end gap-3">
<button <button
class="px-4 py-2 rounded-lg border border-zinc-700 text-zinc-300 hover:bg-zinc-900 text-sm font-bold" class="px-4 py-2 rounded-lg border border-zinc-200 text-zinc-600 hover:bg-white text-sm font-bold"
@click="closeModal" @click="closeModal"
> >
取消 取消
+47 -47
View File
@@ -6,7 +6,7 @@
class="flex flex-col items-center justify-center min-h-[60vh] space-y-6" class="flex flex-col items-center justify-center min-h-[60vh] space-y-6"
> >
<div class="relative w-24 h-24"> <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 border-4 border-t-blue-500 rounded-full animate-spin" />
<div class="absolute inset-0 flex items-center justify-center"> <div class="absolute inset-0 flex items-center justify-center">
<Activity class="text-blue-500 animate-pulse" :size="32" /> <Activity class="text-blue-500 animate-pulse" :size="32" />
@@ -32,7 +32,7 @@
<h3 class="text-xl font-black text-white tracking-tight">数据加载失败</h3> <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> <p class="text-sm text-zinc-500 mt-2 max-w-md">{{ error }}</p>
<button <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" @click="refreshAllData"
> >
<RefreshCw :size="16" /> <RefreshCw :size="16" />
@@ -52,16 +52,16 @@
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<button <button
:disabled="isLoading" :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" @click="refreshAllData"
> >
<RefreshCw :size="18" :class="{ 'animate-spin': isLoading }" /> <RefreshCw :size="18" :class="{ 'animate-spin': isLoading }" />
</button> </button>
<div <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" /> <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 >实时模式</span
> >
</div> </div>
@@ -82,11 +82,11 @@
<div <div
v-for="(stat, i) in kpiStats" v-for="(stat, i) in kpiStats"
:key="i" :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="flex justify-between items-start">
<div <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" /> <component :is="stat.icon" :size="20" />
</div> </div>
@@ -103,7 +103,7 @@
</div> </div>
</div> </div>
<div class="mt-4"> <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) }} {{ formatNumber(stat.value) }}
</h4> </h4>
<p class="text-[10px] font-black text-zinc-600 uppercase tracking-[0.2em] mt-1"> <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="lg:col-span-12 grid grid-cols-1 md:grid-cols-2 gap-6">
<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 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" @mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave" @mouseleave="handleMouseLeave"
> >
@@ -142,7 +142,7 @@
</div> </div>
</div> </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"> <div class="relative z-10">
<span class="text-[10px] font-black text-zinc-600 uppercase tracking-widest" <span class="text-[10px] font-black text-zinc-600 uppercase tracking-widest"
@@ -166,7 +166,7 @@
<!-- 趋势分析图表卡片 --> <!-- 趋势分析图表卡片 -->
<div <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 class="flex items-center justify-between mb-10">
<div> <div>
@@ -196,7 +196,7 @@
class="flex-1 flex flex-col items-center justify-center space-y-4" class="flex-1 flex flex-col items-center justify-center space-y-4"
> >
<div <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"> <p class="text-xs font-black text-zinc-600 uppercase tracking-widest">
正在加载趋势数据... 正在加载趋势数据...
@@ -213,7 +213,7 @@
{{ panelStates.trends.error }} {{ panelStates.trends.error }}
</p> </p>
<button <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" @click="loadTrends"
> >
立即重试 立即重试
@@ -233,10 +233,10 @@
:style="{ :style="{
height: `${(item.count / Math.max(...trendData.map((d) => d.count), 1)) * 100}%` 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 <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="{ :style="{
bottom: `${(item.count / Math.max(...trendData.map((d) => d.count), 1)) * 100}%` bottom: `${(item.count / Math.max(...trendData.map((d) => d.count), 1)) * 100}%`
}" }"
@@ -259,7 +259,7 @@
<!-- 热门歌曲排行榜 --> <!-- 热门歌曲排行榜 -->
<div <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"> <div class="flex items-center justify-between mb-8">
<h3 class="text-xl font-bold flex items-center gap-3 text-white"> <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" v-if="panelStates.topSongs.loading && topSongs.length === 0"
class="flex-1 space-y-4" 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>
<div <div
v-else-if="panelStates.topSongs.error && topSongs.length === 0" v-else-if="panelStates.topSongs.error && topSongs.length === 0"
@@ -303,7 +303,7 @@
{{ panelStates.topSongs.error }} {{ panelStates.topSongs.error }}
</p> </p>
<button <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" @click="loadTopSongs"
> >
重试 重试
@@ -316,7 +316,7 @@
<div <div
v-for="(song, i) in topSongs.slice(0, 5)" v-for="(song, i) in topSongs.slice(0, 5)"
:key="i" :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 <div
:class="`w-8 h-8 rounded-lg flex items-center justify-center font-black text-xs ${ :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' ? 'bg-zinc-300 text-black'
: i === 2 : i === 2
? 'bg-amber-800 text-white' ? 'bg-amber-800 text-white'
: 'text-zinc-600 border border-zinc-800' : 'text-zinc-600 border border-zinc-200'
}`" }`"
> >
{{ i + 1 }} {{ i + 1 }}
</div> </div>
<div class="flex-1 min-w-0"> <div class="flex-1 min-w-0">
<h4 <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 }} {{ song.title }}
</h4> </h4>
@@ -360,7 +360,7 @@
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8"> <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<!-- 活跃用户排行榜 --> <!-- 活跃用户排行榜 -->
<div <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"> <div class="flex items-center justify-between mb-8">
<h3 class="text-xl font-bold flex items-center gap-3 text-white"> <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" v-if="panelStates.activeUsers.loading && activeUsers.length === 0"
class="flex-1 space-y-4" 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>
<div <div
v-else-if="panelStates.activeUsers.error && activeUsers.length === 0" v-else-if="panelStates.activeUsers.error && activeUsers.length === 0"
@@ -387,7 +387,7 @@
{{ panelStates.activeUsers.error }} {{ panelStates.activeUsers.error }}
</p> </p>
<button <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" @click="loadActiveUsers"
> >
重试 重试
@@ -401,31 +401,31 @@
<div <div
v-for="(user, i) in activeUsers.slice(0, 5)" v-for="(user, i) in activeUsers.slice(0, 5)"
:key="i" :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"> <div class="flex items-center gap-4 relative z-10">
<img <img
v-if="user.avatarUrl && !failedAvatars[user.id]" v-if="user.avatarUrl && !failedAvatars[user.id]"
:src="user.avatarUrl" :src="user.avatarUrl"
:alt="user.name" :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)" @error="handleAvatarError($event, user)"
> >
<div <div
v-else 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) }} {{ user.name.charAt(0) }}
</div> </div>
<div class="flex-1 min-w-0"> <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"> <p class="text-xs text-zinc-600 font-medium mt-1 truncate">
{{ user.contributions }}首投稿 · {{ user.likes }}次点赞 {{ user.contributions }}首投稿 · {{ user.likes }}次点赞
</p> </p>
</div> </div>
<div class="text-right shrink-0"> <div class="text-right shrink-0">
<span <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 >{{ user.activityScore }}</span
> >
<p class="text-[10px] font-black text-zinc-700 uppercase tracking-widest"> <p class="text-[10px] font-black text-zinc-700 uppercase tracking-widest">
@@ -451,7 +451,7 @@
<!-- 学期对比分析 --> <!-- 学期对比分析 -->
<div <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"> <div class="flex items-center justify-between mb-8">
<h3 class="text-xl font-bold flex items-center gap-3 text-white"> <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" v-if="panelStates.semesterComparison.loading && semesterComparison.length === 0"
class="flex-1 space-y-4" 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>
<div <div
v-else-if="panelStates.semesterComparison.error && semesterComparison.length === 0" v-else-if="panelStates.semesterComparison.error && semesterComparison.length === 0"
@@ -481,7 +481,7 @@
{{ panelStates.semesterComparison.error }} {{ panelStates.semesterComparison.error }}
</p> </p>
<button <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" @click="loadSemesterComparison"
> >
重试 重试
@@ -494,10 +494,10 @@
<div <div
v-for="(sem, i) in semesterComparison" v-for="(sem, i) in semesterComparison"
:key="i" :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"> <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 sem.semester
}}</span> }}</span>
<span <span
@@ -509,19 +509,19 @@
</div> </div>
<div class="grid grid-cols-3 gap-4"> <div class="grid grid-cols-3 gap-4">
<div> <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 class="text-[9px] font-black text-zinc-600 uppercase tracking-tighter mt-1">
歌曲总量 歌曲总量
</p> </p>
</div> </div>
<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 class="text-[9px] font-black text-zinc-600 uppercase tracking-tighter mt-1">
排期总数 排期总数
</p> </p>
</div> </div>
<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 class="text-[9px] font-black text-zinc-600 uppercase tracking-tighter mt-1">
获赞总数 获赞总数
</p> </p>
@@ -547,16 +547,16 @@
@mouseleave="handleTooltipMouseLeave" @mouseleave="handleTooltipMouseLeave"
> >
<div <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 <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"> <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> <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="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" /> <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 >{{ realtimeStats.activeUsers }} 在线</span
> >
</div> </div>
@@ -569,28 +569,28 @@
<div <div
v-for="user in realtimeStats.activeUsersList.slice(0, 5)" v-for="user in realtimeStats.activeUsersList.slice(0, 5)"
:key="user.id" :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 <img
v-if="user.avatarUrl && !failedAvatars[user.id]" v-if="user.avatarUrl && !failedAvatars[user.id]"
:src="user.avatarUrl" :src="user.avatarUrl"
:alt="user.name" :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)" @error="handleAvatarError($event, user)"
> >
<div <div
v-else 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) }} {{ user.name.charAt(0) }}
</div> </div>
<div class="flex-1 min-w-0"> <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"> <div class="text-[10px] text-zinc-500 font-medium truncate max-h-[14px] overflow-hidden">
@{{ user.username }} @{{ user.username }}
</div> </div>
</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 }} {{ user.lastActive }}
</div> </div>
</div> </div>
@@ -605,7 +605,7 @@
<p class="text-xs font-black uppercase tracking-widest truncate max-h-[16px] overflow-hidden">暂无在线用户</p> <p class="text-xs font-black uppercase tracking-widest truncate max-h-[16px] overflow-hidden">暂无在线用户</p>
</div> </div>
</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 class="text-[10px] font-black text-zinc-600 uppercase tracking-widest truncate max-h-[14px] overflow-hidden"
>实时数据同步中</span >实时数据同步中</span
> >
@@ -1147,7 +1147,7 @@ const formatDateShort = (dateStr) => {
width: 6px; width: 6px;
} }
.custom-scrollbar::-webkit-scrollbar-track { .custom-scrollbar::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.05); background: rgba(0, 0, 0, 0.04);
border-radius: 3px; border-radius: 3px;
} }
.custom-scrollbar::-webkit-scrollbar-thumb { .custom-scrollbar::-webkit-scrollbar-thumb {
+48 -48
View File
@@ -2,7 +2,7 @@
<div class="max-w-[1200px] mx-auto space-y-8 pb-20 px-2"> <div class="max-w-[1200px] mx-auto space-y-8 pb-20 px-2">
<!-- 页面标题 --> <!-- 页面标题 -->
<div class="space-y-1"> <div class="space-y-1">
<h2 class="text-2xl font-black text-zinc-100 tracking-tight">数据库操作</h2> <h2 class="text-2xl font-black text-zinc-900 tracking-tight">数据库操作</h2>
<p class="text-xs text-zinc-500 font-medium"> <p class="text-xs text-zinc-500 font-medium">
执行系统底层维护任务,包括备份、恢复及全局数据重置 执行系统底层维护任务,包括备份、恢复及全局数据重置
</p> </p>
@@ -14,7 +14,7 @@
v-for="card in cards" v-for="card in cards"
:key="card.id" :key="card.id"
:class="[ :class="[
'group relative bg-zinc-900/40 border border-zinc-800 rounded-2xl p-8 transition-all hover:border-zinc-700 hover:shadow-2xl hover:shadow-black/40', 'group relative bg-zinc-50/40 border border-zinc-200 rounded-2xl p-8 transition-all hover:border-zinc-200 hover:shadow-2xl hover:shadow-black/40',
card.isDanger ? 'hover:border-rose-500/20' : '' card.isDanger ? 'hover:border-rose-500/20' : ''
]" ]"
> >
@@ -22,7 +22,7 @@
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div <div
:class="[ :class="[
'p-3.5 rounded-2xl bg-zinc-950 border border-zinc-800 transition-all', 'p-3.5 rounded-2xl bg-white border border-zinc-200 transition-all',
card.isDanger card.isDanger
? 'text-rose-500 border-rose-500/10' ? 'text-rose-500 border-rose-500/10'
: `text-${card.color}-500 border-${card.color}-500/10 shadow-lg` : `text-${card.color}-500 border-${card.color}-500/10 shadow-lg`
@@ -38,7 +38,7 @@
</div> </div>
<div class="flex-1 space-y-2"> <div class="flex-1 space-y-2">
<h3 class="text-lg font-bold text-zinc-100 group-hover:text-blue-400 transition-colors"> <h3 class="text-lg font-bold text-zinc-900 group-hover:text-blue-700 transition-colors">
{{ card.title }} {{ card.title }}
</h3> </h3>
<p class="text-xs text-zinc-500 leading-relaxed font-medium"> <p class="text-xs text-zinc-500 leading-relaxed font-medium">
@@ -48,10 +48,10 @@
<button <button
:disabled="isLoading(card.id)" :disabled="isLoading(card.id)"
class="w-full py-3 rounded-xl text-xs font-black uppercase tracking-widest transition-all active:scale-95 bg-zinc-950 border border-zinc-800 text-zinc-400 hover:bg-zinc-800 hover:text-zinc-100 hover:border-zinc-700" class="w-full py-3 rounded-xl text-xs font-black uppercase tracking-widest transition-all active:scale-95 bg-white border border-zinc-200 text-zinc-400 hover:bg-zinc-100 hover:text-zinc-900 hover:border-zinc-200"
:class=" :class="
card.isDanger card.isDanger
? 'bg-zinc-950 border border-rose-900/30 text-rose-500 hover:bg-rose-600 hover:text-white hover:border-rose-600 shadow-lg shadow-rose-900/5' ? 'bg-white border border-rose-200 text-rose-600 hover:bg-rose-600 hover:text-zinc-900 hover:border-rose-600 shadow-lg shadow-rose-900/5'
: '' : ''
" "
@click="openModal(card.id)" @click="openModal(card.id)"
@@ -75,7 +75,7 @@
<div class="bg-blue-600/5 border border-blue-500/10 rounded-xl p-5 flex items-start gap-4"> <div class="bg-blue-600/5 border border-blue-500/10 rounded-xl p-5 flex items-start gap-4">
<AlertCircle class="text-blue-500 shrink-0 mt-0.5 w-[18px] h-[18px]" /> <AlertCircle class="text-blue-500 shrink-0 mt-0.5 w-[18px] h-[18px]" />
<div class="space-y-1"> <div class="space-y-1">
<p class="text-[11px] font-bold text-zinc-300">数据库维护建议</p> <p class="text-[11px] font-bold text-zinc-600">数据库维护建议</p>
<p class="text-[10px] text-zinc-500 leading-relaxed"> <p class="text-[10px] text-zinc-500 leading-relaxed">
建议每周进行一次全量备份。在执行“恢复备份”或“重置数据库”前,请务必先创建一份当前的数据备份,以免误操作造成不可挽回的损失。 建议每周进行一次全量备份。在执行“恢复备份”或“重置数据库”前,请务必先创建一份当前的数据备份,以免误操作造成不可挽回的损失。
</p> </p>
@@ -89,12 +89,12 @@
> >
<div class="absolute inset-0 bg-black/80 backdrop-blur-sm" @click="activeModal = 'none'" /> <div class="absolute inset-0 bg-black/80 backdrop-blur-sm" @click="activeModal = 'none'" />
<div <div
class="relative bg-zinc-900 border border-zinc-800 rounded-3xl w-full max-w-lg overflow-hidden shadow-2xl animate-in fade-in zoom-in duration-200" class="relative bg-white border border-zinc-200 rounded-3xl w-full max-w-lg overflow-hidden shadow-2xl animate-in fade-in zoom-in duration-200"
> >
<div class="px-8 py-6 border-b border-zinc-800 flex items-center justify-between"> <div class="px-8 py-6 border-b border-zinc-200 flex items-center justify-between">
<h3 class="text-xl font-black text-zinc-100 tracking-tight">创建数据库备份</h3> <h3 class="text-xl font-black text-zinc-900 tracking-tight">创建数据库备份</h3>
<button <button
class="p-2 hover:bg-zinc-800 rounded-xl transition-colors text-zinc-500 hover:text-zinc-200" class="p-2 hover:bg-zinc-100 rounded-xl transition-colors text-zinc-500 hover:text-zinc-700"
@click="activeModal = 'none'" @click="activeModal = 'none'"
> >
<X class="w-5 h-5" /> <X class="w-5 h-5" />
@@ -109,18 +109,18 @@
<label <label
v-for="(item, i) in backupOptions" v-for="(item, i) in backupOptions"
:key="i" :key="i"
class="flex items-start gap-4 p-4 bg-zinc-950/50 border border-zinc-800 rounded-xl cursor-pointer hover:border-zinc-700 transition-all group" class="flex items-start gap-4 p-4 bg-white/70 border border-zinc-200 rounded-xl cursor-pointer hover:border-zinc-200 transition-all group"
> >
<div class="shrink-0 mt-0.5"> <div class="shrink-0 mt-0.5">
<input <input
v-model="createForm[item.key]" v-model="createForm[item.key]"
type="checkbox" type="checkbox"
class="w-4 h-4 rounded border-zinc-800 bg-zinc-900 accent-blue-600" class="w-4 h-4 rounded border-zinc-200 bg-white accent-blue-600"
> >
</div> </div>
<div> <div>
<p <p
class="text-xs font-bold text-zinc-200 group-hover:text-blue-400 transition-colors" class="text-xs font-bold text-zinc-700 group-hover:text-blue-700 transition-colors"
> >
{{ item.label }} {{ item.label }}
</p> </p>
@@ -135,9 +135,9 @@
</p> </p>
</div> </div>
</div> </div>
<div class="px-8 py-6 bg-zinc-950/50 border-t border-zinc-800 flex gap-3 justify-end"> <div class="px-8 py-6 bg-white/70 border-t border-zinc-200 flex gap-3 justify-end">
<button <button
class="px-4 py-2 text-xs font-bold text-zinc-500 hover:text-zinc-300 transition-colors uppercase tracking-widest" class="px-4 py-2 text-xs font-bold text-zinc-500 hover:text-zinc-600 transition-colors uppercase tracking-widest"
@click="activeModal = 'none'" @click="activeModal = 'none'"
> >
取消 取消
@@ -160,12 +160,12 @@
> >
<div class="absolute inset-0 bg-black/80 backdrop-blur-sm" @click="activeModal = 'none'" /> <div class="absolute inset-0 bg-black/80 backdrop-blur-sm" @click="activeModal = 'none'" />
<div <div
class="relative bg-zinc-900 border border-zinc-800 rounded-3xl w-full max-w-lg overflow-hidden shadow-2xl animate-in fade-in zoom-in duration-200" class="relative bg-white border border-zinc-200 rounded-3xl w-full max-w-lg overflow-hidden shadow-2xl animate-in fade-in zoom-in duration-200"
> >
<div class="px-8 py-6 border-b border-zinc-800 flex items-center justify-between"> <div class="px-8 py-6 border-b border-zinc-200 flex items-center justify-between">
<h3 class="text-xl font-black text-zinc-100 tracking-tight">恢复数据库备份</h3> <h3 class="text-xl font-black text-zinc-900 tracking-tight">恢复数据库备份</h3>
<button <button
class="p-2 hover:bg-zinc-800 rounded-xl transition-colors text-zinc-500 hover:text-zinc-200" class="p-2 hover:bg-zinc-100 rounded-xl transition-colors text-zinc-500 hover:text-zinc-700"
@click="activeModal = 'none'" @click="activeModal = 'none'"
> >
<X class="w-5 h-5" /> <X class="w-5 h-5" />
@@ -173,7 +173,7 @@
</div> </div>
<div class="p-8 space-y-6"> <div class="p-8 space-y-6">
<div <div
class="border-2 border-dashed border-zinc-800 rounded-2xl p-10 flex flex-col items-center justify-center text-center group hover:border-emerald-500/50 hover:bg-emerald-500/5 transition-all cursor-pointer" class="border-2 border-dashed border-zinc-200 rounded-2xl p-10 flex flex-col items-center justify-center text-center group hover:border-emerald-500/50 hover:bg-emerald-500/5 transition-all cursor-pointer"
@click="$refs.fileInput.click()" @click="$refs.fileInput.click()"
@dragover.prevent @dragover.prevent
@drop.prevent="handleFileDrop" @drop.prevent="handleFileDrop"
@@ -181,7 +181,7 @@
<Upload <Upload
class="w-8 h-8 text-zinc-700 mb-4 group-hover:text-emerald-500 transition-colors" class="w-8 h-8 text-zinc-700 mb-4 group-hover:text-emerald-500 transition-colors"
/> />
<h5 class="text-sm font-bold text-zinc-300"> <h5 class="text-sm font-bold text-zinc-600">
{{ selectedFile ? selectedFile.name : '点击选择或拖拽备份文件' }} {{ selectedFile ? selectedFile.name : '点击选择或拖拽备份文件' }}
</h5> </h5>
<p class="text-[10px] text-zinc-600 font-bold uppercase mt-1 tracking-widest"> <p class="text-[10px] text-zinc-600 font-bold uppercase mt-1 tracking-widest">
@@ -205,15 +205,15 @@
:class="[ :class="[
'p-4 border rounded-xl text-left transition-all', 'p-4 border rounded-xl text-left transition-all',
restoreForm.mode === 'merge' restoreForm.mode === 'merge'
? 'bg-zinc-950 border-emerald-500/30' ? 'bg-white border-emerald-200'
: 'bg-zinc-950 border-zinc-800 hover:border-zinc-700' : 'bg-white border-zinc-200 hover:border-zinc-200'
]" ]"
@click="restoreForm.mode = 'merge'" @click="restoreForm.mode = 'merge'"
> >
<h6 <h6
:class="[ :class="[
'text-xs font-bold', 'text-xs font-bold',
restoreForm.mode === 'merge' ? 'text-emerald-400' : 'text-zinc-500' restoreForm.mode === 'merge' ? 'text-emerald-700' : 'text-zinc-500'
]" ]"
> >
增量模式 增量模式
@@ -224,15 +224,15 @@
:class="[ :class="[
'p-4 border rounded-xl text-left transition-all', 'p-4 border rounded-xl text-left transition-all',
restoreForm.mode === 'replace' restoreForm.mode === 'replace'
? 'bg-zinc-950 border-emerald-500/30' ? 'bg-white border-emerald-200'
: 'bg-zinc-950 border-zinc-800 hover:border-zinc-700' : 'bg-white border-zinc-200 hover:border-zinc-200'
]" ]"
@click="restoreForm.mode = 'replace'" @click="restoreForm.mode = 'replace'"
> >
<h6 <h6
:class="[ :class="[
'text-xs font-bold', 'text-xs font-bold',
restoreForm.mode === 'replace' ? 'text-emerald-400' : 'text-zinc-500' restoreForm.mode === 'replace' ? 'text-emerald-700' : 'text-zinc-500'
]" ]"
> >
覆盖模式 覆盖模式
@@ -244,7 +244,7 @@
<div <div
v-if="restoreForm.mode === 'replace' && hasSuperAdminInBackup" v-if="restoreForm.mode === 'replace' && hasSuperAdminInBackup"
class="p-4 bg-zinc-950 border border-zinc-800 rounded-xl" class="p-4 bg-white border border-zinc-200 rounded-xl"
> >
<label class="flex items-start gap-3 cursor-pointer"> <label class="flex items-start gap-3 cursor-pointer">
<input <input
@@ -253,7 +253,7 @@
class="mt-0.5 accent-emerald-500" class="mt-0.5 accent-emerald-500"
> >
<div> <div>
<p class="text-xs font-bold text-zinc-200">覆盖备份中的超级管理员账号数据</p> <p class="text-xs font-bold text-zinc-700">覆盖备份中的超级管理员账号数据</p>
<p class="text-[10px] text-zinc-500 mt-1"> <p class="text-[10px] text-zinc-500 mt-1">
关闭时将保留当前超级管理员账号及其第三方绑定、2FA等关联数据 关闭时将保留当前超级管理员账号及其第三方绑定、2FA等关联数据
</p> </p>
@@ -270,9 +270,9 @@
</p> </p>
</div> </div>
</div> </div>
<div class="px-8 py-6 bg-zinc-950/50 border-t border-zinc-800 flex gap-3 justify-end"> <div class="px-8 py-6 bg-white/70 border-t border-zinc-200 flex gap-3 justify-end">
<button <button
class="px-4 py-2 text-xs font-bold text-zinc-500 hover:text-zinc-300 transition-colors uppercase tracking-widest" class="px-4 py-2 text-xs font-bold text-zinc-500 hover:text-zinc-600 transition-colors uppercase tracking-widest"
@click="activeModal = 'none'" @click="activeModal = 'none'"
> >
取消 取消
@@ -295,12 +295,12 @@
> >
<div class="absolute inset-0 bg-black/80 backdrop-blur-sm" @click="activeModal = 'none'" /> <div class="absolute inset-0 bg-black/80 backdrop-blur-sm" @click="activeModal = 'none'" />
<div <div
class="relative bg-zinc-900 border border-zinc-800 rounded-3xl w-full max-w-lg overflow-hidden shadow-2xl animate-in fade-in zoom-in duration-200" class="relative bg-white border border-zinc-200 rounded-3xl w-full max-w-lg overflow-hidden shadow-2xl animate-in fade-in zoom-in duration-200"
> >
<div class="px-8 py-6 border-b border-zinc-800 flex items-center justify-between"> <div class="px-8 py-6 border-b border-zinc-200 flex items-center justify-between">
<h3 class="text-xl font-black text-zinc-100 tracking-tight">重置数据表序列</h3> <h3 class="text-xl font-black text-zinc-900 tracking-tight">重置数据表序列</h3>
<button <button
class="p-2 hover:bg-zinc-800 rounded-xl transition-colors text-zinc-500 hover:text-zinc-200" class="p-2 hover:bg-zinc-100 rounded-xl transition-colors text-zinc-500 hover:text-zinc-700"
@click="activeModal = 'none'" @click="activeModal = 'none'"
> >
<X class="w-5 h-5" /> <X class="w-5 h-5" />
@@ -318,14 +318,14 @@
/> />
</div> </div>
<div class="p-6 bg-zinc-950/50 border border-zinc-800 rounded-2xl space-y-4"> <div class="p-6 bg-white/70 border border-zinc-200 rounded-2xl space-y-4">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<div <div
class="w-8 h-8 rounded-lg bg-amber-500/10 text-amber-500 flex items-center justify-center" class="w-8 h-8 rounded-lg bg-amber-500/10 text-amber-500 flex items-center justify-center"
> >
<AlertCircle class="w-4 h-4" /> <AlertCircle class="w-4 h-4" />
</div> </div>
<h6 class="text-xs font-bold text-zinc-300 uppercase tracking-widest"> <h6 class="text-xs font-bold text-zinc-600 uppercase tracking-widest">
什么是重置序列? 什么是重置序列?
</h6> </h6>
</div> </div>
@@ -336,9 +336,9 @@
</p> </p>
</div> </div>
</div> </div>
<div class="px-8 py-6 bg-zinc-950/50 border-t border-zinc-800 flex gap-3 justify-end"> <div class="px-8 py-6 bg-white/70 border-t border-zinc-200 flex gap-3 justify-end">
<button <button
class="px-4 py-2 text-xs font-bold text-zinc-500 hover:text-zinc-300 transition-colors uppercase tracking-widest" class="px-4 py-2 text-xs font-bold text-zinc-500 hover:text-zinc-600 transition-colors uppercase tracking-widest"
@click="activeModal = 'none'" @click="activeModal = 'none'"
> >
取消 取消
@@ -361,12 +361,12 @@
> >
<div class="absolute inset-0 bg-black/80 backdrop-blur-sm" @click="activeModal = 'none'" /> <div class="absolute inset-0 bg-black/80 backdrop-blur-sm" @click="activeModal = 'none'" />
<div <div
class="relative bg-zinc-900 border border-zinc-800 rounded-3xl w-full max-w-lg overflow-hidden shadow-2xl animate-in fade-in zoom-in duration-200" class="relative bg-white border border-zinc-200 rounded-3xl w-full max-w-lg overflow-hidden shadow-2xl animate-in fade-in zoom-in duration-200"
> >
<div class="px-8 py-6 border-b border-zinc-800 flex items-center justify-between"> <div class="px-8 py-6 border-b border-zinc-200 flex items-center justify-between">
<h3 class="text-xl font-black text-rose-500 tracking-tight">危险操作:重置数据库</h3> <h3 class="text-xl font-black text-rose-500 tracking-tight">危险操作:重置数据库</h3>
<button <button
class="p-2 hover:bg-zinc-800 rounded-xl transition-colors text-zinc-500 hover:text-zinc-200" class="p-2 hover:bg-zinc-100 rounded-xl transition-colors text-zinc-500 hover:text-zinc-700"
@click="activeModal = 'none'" @click="activeModal = 'none'"
> >
<X class="w-5 h-5" /> <X class="w-5 h-5" />
@@ -382,7 +382,7 @@
</h4> </h4>
<p class="text-xs text-zinc-500 mt-2 font-medium leading-relaxed"> <p class="text-xs text-zinc-500 mt-2 font-medium leading-relaxed">
重置操作将永久删除系统中的所有 重置操作将永久删除系统中的所有
<span class="text-zinc-300 font-bold" <span class="text-zinc-600 font-bold"
>歌曲、投稿记录、排期文件、通知、日志及除您以外的用户账号</span >歌曲、投稿记录、排期文件、通知、日志及除您以外的用户账号</span
>。 >。
</p> </p>
@@ -395,7 +395,7 @@
请输入以下代码以确认操作 请输入以下代码以确认操作
</label> </label>
<div <div
class="bg-zinc-950 border border-rose-900/30 rounded-xl px-4 py-3 font-mono text-[10px] text-rose-400 text-center select-all" class="bg-white border border-rose-200 rounded-xl px-4 py-3 font-mono text-[10px] text-rose-700 text-center select-all"
> >
{{ CONFIRM_CODE }} {{ CONFIRM_CODE }}
</div> </div>
@@ -403,20 +403,20 @@
v-model="resetConfirmText" v-model="resetConfirmText"
type="text" type="text"
placeholder="在此输入上述代码..." placeholder="在此输入上述代码..."
class="w-full bg-zinc-950 border border-zinc-800 rounded-xl px-4 py-3 text-sm text-zinc-200 focus:outline-none focus:border-rose-500/40 text-center font-mono placeholder:text-zinc-700" class="w-full bg-white border border-zinc-200 rounded-xl px-4 py-3 text-sm text-zinc-700 focus:outline-none focus:border-rose-200 text-center font-mono placeholder:text-zinc-700"
> >
</div> </div>
<div class="grid grid-cols-2 gap-3 pt-2"> <div class="grid grid-cols-2 gap-3 pt-2">
<button <button
class="py-3 bg-zinc-900 hover:bg-zinc-800 text-zinc-500 text-xs font-black rounded-xl transition-all uppercase tracking-widest" class="py-3 bg-white hover:bg-zinc-100 text-zinc-500 text-xs font-black rounded-xl transition-all uppercase tracking-widest"
@click="activeModal = 'none'" @click="activeModal = 'none'"
> >
取消 取消
</button> </button>
<button <button
:disabled="resetConfirmText !== CONFIRM_CODE || resetLoading" :disabled="resetConfirmText !== CONFIRM_CODE || resetLoading"
class="py-3 rounded-xl text-xs font-black uppercase tracking-widest transition-all shadow-lg disabled:bg-zinc-800 disabled:text-zinc-600 disabled:cursor-not-allowed disabled:border-zinc-700" class="py-3 rounded-xl text-xs font-black uppercase tracking-widest transition-all shadow-lg disabled:bg-zinc-100 disabled:text-zinc-600 disabled:cursor-not-allowed disabled:border-zinc-200"
:class=" :class="
resetConfirmText === CONFIRM_CODE resetConfirmText === CONFIRM_CODE
? 'bg-rose-600 hover:bg-rose-500 text-white shadow-rose-900/20 active:scale-95' ? 'bg-rose-600 hover:bg-rose-500 text-white shadow-rose-900/20 active:scale-95'
+25 -25
View File
@@ -1,22 +1,22 @@
<template> <template>
<div class="bg-zinc-900/30 border border-zinc-800 rounded-[2rem] overflow-hidden"> <div class="bg-zinc-50/30 border border-zinc-200 rounded-[2rem] overflow-hidden">
<!-- 头部 --> <!-- 头部 -->
<div class="flex items-center justify-between p-6 border-b border-zinc-800 bg-zinc-900/20"> <div class="flex items-center justify-between p-6 border-b border-zinc-200 bg-zinc-50/20">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<div <div
class="w-10 h-10 rounded-2xl bg-blue-600/10 flex items-center justify-center border border-blue-500/20" class="w-10 h-10 rounded-2xl bg-blue-50 flex items-center justify-center border border-blue-200"
> >
<Mail :size="20" class="text-blue-500" /> <Mail :size="20" class="text-blue-500" />
</div> </div>
<div> <div>
<h3 class="text-sm font-black text-zinc-100 uppercase tracking-widest">邮件模板管理</h3> <h3 class="text-sm font-black text-zinc-900 uppercase tracking-widest">邮件模板管理</h3>
<p class="text-[10px] text-zinc-500 mt-0.5">自定义系统邮件的主题与内容,支持变量替换</p> <p class="text-[10px] text-zinc-500 mt-0.5">自定义系统邮件的主题与内容,支持变量替换</p>
</div> </div>
</div> </div>
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<button <button
v-if="selected" v-if="selected"
class="flex items-center gap-2 px-4 py-2 bg-zinc-900 border border-zinc-800 hover:border-zinc-700 text-zinc-400 text-[11px] font-bold rounded-xl transition-all" class="flex items-center gap-2 px-4 py-2 bg-white border border-zinc-200 hover:border-zinc-200 text-zinc-400 text-[11px] font-bold rounded-xl transition-all"
:disabled="saving" :disabled="saving"
@click="doPreview" @click="doPreview"
> >
@@ -35,14 +35,14 @@
<div class="grid grid-cols-1 lg:grid-cols-12 min-h-[650px]"> <div class="grid grid-cols-1 lg:grid-cols-12 min-h-[650px]">
<!-- 左侧:模板列表 --> <!-- 左侧:模板列表 -->
<div class="lg:col-span-3 border-r border-zinc-800 bg-zinc-900/10 flex flex-col"> <div class="lg:col-span-3 border-r border-zinc-200 bg-zinc-50/10 flex flex-col">
<div class="p-4 border-b border-zinc-800/50"> <div class="p-4 border-b border-zinc-200">
<div class="relative"> <div class="relative">
<Search :size="14" class="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-600" /> <Search :size="14" class="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-600" />
<input <input
type="text" type="text"
placeholder="搜索模板..." placeholder="搜索模板..."
class="w-full bg-zinc-950 border border-zinc-800 rounded-lg pl-9 pr-3 py-2 text-[10px] text-zinc-400 focus:outline-none focus:border-blue-500/30" class="w-full bg-white border border-zinc-200 rounded-lg pl-9 pr-3 py-2 text-[10px] text-zinc-400 focus:outline-none focus:border-blue-200"
> >
</div> </div>
</div> </div>
@@ -53,8 +53,8 @@
class="w-full group flex flex-col gap-1 p-3 rounded-2xl transition-all text-left border" class="w-full group flex flex-col gap-1 p-3 rounded-2xl transition-all text-left border"
:class="[ :class="[
selectedKey === t.key selectedKey === t.key
? 'bg-blue-600/10 border-blue-500/20' ? 'bg-blue-50 border-blue-200'
: 'bg-transparent border-transparent hover:bg-zinc-800/40' : 'bg-transparent border-transparent hover:bg-zinc-100/40'
]" ]"
@click="select(t)" @click="select(t)"
> >
@@ -63,8 +63,8 @@
class="text-[11px] font-black tracking-tight transition-colors" class="text-[11px] font-black tracking-tight transition-colors"
:class=" :class="
selectedKey === t.key selectedKey === t.key
? 'text-blue-400' ? 'text-blue-700'
: 'text-zinc-300 group-hover:text-zinc-100' : 'text-zinc-600 group-hover:text-zinc-900'
" "
> >
{{ t.name }} {{ t.name }}
@@ -72,7 +72,7 @@
<div class="flex gap-1"> <div class="flex gap-1">
<span <span
v-if="t.isBuiltin && !t.isOverridden" v-if="t.isBuiltin && !t.isOverridden"
class="px-1.5 py-0.5 rounded-md bg-zinc-800 text-[8px] font-black text-zinc-500 uppercase tracking-tighter" class="px-1.5 py-0.5 rounded-md bg-zinc-100 text-[8px] font-black text-zinc-500 uppercase tracking-tighter"
> >
内置 内置
</span> </span>
@@ -90,7 +90,7 @@
</div> </div>
<!-- 右侧:编辑器区域 --> <!-- 右侧:编辑器区域 -->
<div class="lg:col-span-9 flex flex-col bg-zinc-950/20 overflow-hidden"> <div class="lg:col-span-9 flex flex-col bg-white/50 overflow-hidden">
<div v-if="selected" class="flex-1 overflow-y-auto custom-scrollbar p-8"> <div v-if="selected" class="flex-1 overflow-y-auto custom-scrollbar p-8">
<div class="max-w-4xl mx-auto space-y-8"> <div class="max-w-4xl mx-auto space-y-8">
<!-- 基础信息与主题 --> <!-- 基础信息与主题 -->
@@ -102,7 +102,7 @@
<input <input
v-model="form.name" v-model="form.name"
type="text" type="text"
class="w-full bg-zinc-950 border border-zinc-800 rounded-xl px-4 py-3 text-xs text-zinc-200 focus:outline-none focus:border-blue-500/30" class="w-full bg-white border border-zinc-200 rounded-xl px-4 py-3 text-xs text-zinc-700 focus:outline-none focus:border-blue-200"
> >
</div> </div>
<div class="space-y-2"> <div class="space-y-2">
@@ -116,7 +116,7 @@
v-model="form.subject" v-model="form.subject"
type="text" type="text"
placeholder="请输入邮件主题..." placeholder="请输入邮件主题..."
class="w-full bg-zinc-950 border border-zinc-800 rounded-xl px-4 py-3 text-xs text-zinc-200 focus:outline-none focus:border-blue-500/30" class="w-full bg-white border border-zinc-200 rounded-xl px-4 py-3 text-xs text-zinc-700 focus:outline-none focus:border-blue-200"
> >
</div> </div>
</div> </div>
@@ -129,7 +129,7 @@
<Info :size="16" class="text-blue-500" /> <Info :size="16" class="text-blue-500" />
</div> </div>
<div class="space-y-1"> <div class="space-y-1">
<h4 class="text-[10px] font-black text-blue-400 uppercase tracking-widest"> <h4 class="text-[10px] font-black text-blue-700 uppercase tracking-widest">
可用变量 可用变量
</h4> </h4>
<p class="text-[11px] text-zinc-500 leading-relaxed font-mono"> <p class="text-[11px] text-zinc-500 leading-relaxed font-mono">
@@ -151,13 +151,13 @@
>HTML 内容</label >HTML 内容</label
> >
<span <span
class="px-2 py-0.5 rounded-md bg-zinc-900 text-[8px] font-black text-zinc-500 uppercase" class="px-2 py-0.5 rounded-md bg-white text-[8px] font-black text-zinc-500 uppercase"
>Handlebars</span >Handlebars</span
> >
</div> </div>
<button <button
v-if="selected.isOverridden" v-if="selected.isOverridden"
class="flex items-center gap-1.5 text-[10px] font-bold text-rose-500 hover:text-rose-400 transition-colors" class="flex items-center gap-1.5 text-[10px] font-bold text-rose-500 hover:text-rose-700 transition-colors"
@click="restore" @click="restore"
> >
<RotateCcw :size="12" /> 恢复默认配置 <RotateCcw :size="12" /> 恢复默认配置
@@ -165,14 +165,14 @@
</div> </div>
<div class="relative group"> <div class="relative group">
<div <div
class="absolute right-4 top-4 p-2 rounded-lg bg-zinc-900/50 border border-zinc-800 opacity-0 group-hover:opacity-100 transition-opacity" class="absolute right-4 top-4 p-2 rounded-lg bg-zinc-50/50 border border-zinc-200 opacity-0 group-hover:opacity-100 transition-opacity"
> >
<Code :size="14" class="text-zinc-500" /> <Code :size="14" class="text-zinc-500" />
</div> </div>
<textarea <textarea
v-model="form.html" v-model="form.html"
rows="16" rows="16"
class="w-full bg-zinc-950 border border-zinc-800 rounded-2xl p-6 text-[11px] text-zinc-300 font-mono leading-relaxed focus:outline-none focus:border-blue-500/30 custom-scrollbar" class="w-full bg-white border border-zinc-200 rounded-2xl p-6 text-[11px] text-zinc-600 font-mono leading-relaxed focus:outline-none focus:border-blue-200 custom-scrollbar"
placeholder="<html>..." placeholder="<html>..."
/> />
</div> </div>
@@ -184,7 +184,7 @@
enter-from-class="transform translate-y-4 opacity-0" enter-from-class="transform translate-y-4 opacity-0"
enter-to-class="transform translate-y-0 opacity-100" enter-to-class="transform translate-y-0 opacity-100"
> >
<div v-if="previewHtml" class="space-y-4 pt-4 border-t border-zinc-800"> <div v-if="previewHtml" class="space-y-4 pt-4 border-t border-zinc-200">
<div class="flex items-center justify-between px-1"> <div class="flex items-center justify-between px-1">
<label <label
class="text-[10px] font-black text-zinc-600 uppercase tracking-widest flex items-center gap-2" class="text-[10px] font-black text-zinc-600 uppercase tracking-widest flex items-center gap-2"
@@ -193,13 +193,13 @@
<span class="text-zinc-400 font-normal normal-case">{{ previewSubject }}</span> <span class="text-zinc-400 font-normal normal-case">{{ previewSubject }}</span>
</label> </label>
<button <button
class="text-[10px] font-bold text-zinc-500 hover:text-zinc-300" class="text-[10px] font-bold text-zinc-500 hover:text-zinc-600"
@click="previewHtml = ''" @click="previewHtml = ''"
> >
隐藏预览 隐藏预览
</button> </button>
</div> </div>
<div class="rounded-2xl overflow-hidden border border-zinc-800 bg-white shadow-2xl"> <div class="rounded-2xl overflow-hidden border border-zinc-200 bg-white shadow-2xl">
<iframe :srcdoc="previewHtml" class="w-full h-[500px] border-none" /> <iframe :srcdoc="previewHtml" class="w-full h-[500px] border-none" />
</div> </div>
</div> </div>
@@ -213,7 +213,7 @@
class="flex-1 flex flex-col items-center justify-center text-zinc-700 space-y-4" class="flex-1 flex flex-col items-center justify-center text-zinc-700 space-y-4"
> >
<div <div
class="w-16 h-16 rounded-3xl bg-zinc-900/50 flex items-center justify-center border border-zinc-800/50" class="w-16 h-16 rounded-3xl bg-zinc-50/50 flex items-center justify-center border border-zinc-200"
> >
<Mail :size="32" class="text-zinc-800" /> <Mail :size="32" class="text-zinc-800" />
</div> </div>
+35 -35
View File
@@ -1,8 +1,8 @@
<template> <template>
<div class="space-y-6"> <div class="space-y-6">
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<h3 class="text-lg font-bold text-zinc-100 flex items-center gap-2"> <h3 class="text-lg font-bold text-zinc-900 flex items-center gap-2">
<CalendarDays class="w-5 h-5 text-blue-400" /> <CalendarDays class="w-5 h-5 text-blue-700" />
期望播出时段设置 期望播出时段设置
</h3> </h3>
<button <button
@@ -17,39 +17,39 @@
<Loader2 class="w-6 h-6 animate-spin mx-auto text-zinc-500" /> <Loader2 class="w-6 h-6 animate-spin mx-auto text-zinc-500" />
</div> </div>
<div v-else-if="dates.length === 0" class="text-center py-10 text-zinc-500 bg-zinc-900/50 rounded-xl border border-zinc-800"> <div v-else-if="dates.length === 0" class="text-center py-10 text-zinc-500 bg-zinc-50/50 rounded-xl border border-zinc-200">
暂未设置任何开放日期 暂未设置任何开放日期
</div> </div>
<div v-else class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> <div v-else class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div v-for="date in dates" :key="date.id" class="bg-zinc-900 border border-zinc-800 rounded-xl p-4 flex flex-col gap-3"> <div v-for="date in dates" :key="date.id" class="bg-white border border-zinc-200 rounded-xl p-4 flex flex-col gap-3">
<div class="flex justify-between items-start"> <div class="flex justify-between items-start">
<div class="font-bold text-zinc-100 flex items-center gap-2"> <div class="font-bold text-zinc-900 flex items-center gap-2">
{{ date.date }} {{ date.date }}
<span class="text-xs font-normal text-zinc-500 bg-zinc-800 px-2 py-0.5 rounded">{{ date.weekday }}</span> <span class="text-xs font-normal text-zinc-500 bg-zinc-100 px-2 py-0.5 rounded">{{ date.weekday }}</span>
</div> </div>
<button @click="openDeleteDialog(date)" class="text-red-400 hover:text-red-300 p-1 transition-colors" title="删除"> <button @click="openDeleteDialog(date)" class="text-red-400 hover:text-red-300 p-1 transition-colors" title="删除">
<Trash2 class="w-4 h-4" /> <Trash2 class="w-4 h-4" />
</button> </button>
</div> </div>
<div class="flex flex-col gap-2.5 bg-zinc-950/50 p-3 rounded-lg border border-zinc-800/50"> <div class="flex flex-col gap-2.5 bg-white/70 p-3 rounded-lg border border-zinc-200">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="text-xs text-zinc-500">开放范围</div> <div class="text-xs text-zinc-500">开放范围</div>
<div class="text-xs font-mono text-blue-400 font-medium break-all text-right ml-4">{{ formatSequences(date.availableSequences) }}</div> <div class="text-xs font-mono text-blue-700 font-medium break-all text-right ml-4">{{ formatSequences(date.availableSequences) }}</div>
</div> </div>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="text-xs text-zinc-500">序号状态</div> <div class="text-xs text-zinc-500">序号状态</div>
<div class="text-xs flex gap-2"> <div class="text-xs flex gap-2">
<span class="text-emerald-500 bg-emerald-500/10 px-1.5 py-0.5 rounded border border-emerald-500/20">空闲 {{ getAvailableCount(date) }}</span> <span class="text-emerald-500 bg-emerald-500/10 px-1.5 py-0.5 rounded border border-emerald-500/20">空闲 {{ getAvailableCount(date) }}</span>
<span class="text-zinc-400 bg-zinc-800 px-1.5 py-0.5 rounded border border-zinc-700">共 {{ getTotalCount(date) }}</span> <span class="text-zinc-400 bg-zinc-100 px-1.5 py-0.5 rounded border border-zinc-200">共 {{ getTotalCount(date) }}</span>
</div> </div>
</div> </div>
</div> </div>
<!-- 新增详细占用情况列表 --> <!-- 新增详细占用情况列表 -->
<div v-if="date.takenSequences && date.takenSequences.length > 0" class="mt-1 pt-3 border-t border-zinc-800"> <div v-if="date.takenSequences && date.takenSequences.length > 0" class="mt-1 pt-3 border-t border-zinc-200">
<div class="text-xs font-bold text-zinc-500 mb-2 flex items-center justify-between"> <div class="text-xs font-bold text-zinc-500 mb-2 flex items-center justify-between">
<span>已占用明细</span> <span>已占用明细</span>
<span class="text-red-400">{{ date.takenSequences.length }} 个</span> <span class="text-red-400">{{ date.takenSequences.length }} 个</span>
@@ -61,12 +61,12 @@
class="flex items-center gap-2 text-[11px]" class="flex items-center gap-2 text-[11px]"
> >
<span class="w-5 h-5 rounded flex items-center justify-center bg-red-500/10 text-red-500 font-bold border border-red-500/20 shrink-0">{{ seq }}</span> <span class="w-5 h-5 rounded flex items-center justify-center bg-red-500/10 text-red-500 font-bold border border-red-500/20 shrink-0">{{ seq }}</span>
<span class="text-zinc-300 truncate" :title="date.takenDetails?.[seq]?.title">{{ date.takenDetails?.[seq]?.title || '未知歌曲' }}</span> <span class="text-zinc-600 truncate" :title="date.takenDetails?.[seq]?.title">{{ date.takenDetails?.[seq]?.title || '未知歌曲' }}</span>
<span class="text-zinc-500 truncate" :title="date.takenDetails?.[seq]?.artist">- {{ date.takenDetails?.[seq]?.artist || '未知歌手' }}</span> <span class="text-zinc-500 truncate" :title="date.takenDetails?.[seq]?.artist">- {{ date.takenDetails?.[seq]?.artist || '未知歌手' }}</span>
</div> </div>
</div> </div>
</div> </div>
<div v-else class="mt-2 pt-3 border-t border-zinc-800 text-xs text-emerald-500/70 font-bold"> <div v-else class="mt-2 pt-3 border-t border-zinc-200 text-xs text-emerald-500/70 font-bold">
所有序号目前空闲 所有序号目前空闲
</div> </div>
</div> </div>
@@ -74,22 +74,22 @@
<!-- 新增/编辑弹窗 --> <!-- 新增/编辑弹窗 -->
<div v-if="showModal" class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm"> <div v-if="showModal" class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm">
<div class="bg-zinc-900 border border-zinc-800 rounded-xl shadow-2xl w-full max-w-lg p-6"> <div class="bg-white border border-zinc-200 rounded-xl shadow-2xl w-full max-w-lg p-6">
<div class="flex justify-between items-center mb-6"> <div class="flex justify-between items-center mb-6">
<h3 class="text-lg font-bold text-zinc-100">新增开放日期</h3> <h3 class="text-lg font-bold text-zinc-900">新增开放日期</h3>
</div> </div>
<!-- Tabs --> <!-- Tabs -->
<div class="flex p-1 bg-zinc-950 rounded-lg mb-6"> <div class="flex p-1 bg-white rounded-lg mb-6">
<button <button
@click="activeTab = 'auto'" @click="activeTab = 'auto'"
:class="['flex-1 py-2 text-xs font-bold rounded-md transition-colors', activeTab === 'auto' ? 'bg-blue-600 text-white' : 'text-zinc-500 hover:text-zinc-300']" :class="['flex-1 py-2 text-xs font-bold rounded-md transition-colors', activeTab === 'auto' ? 'bg-blue-600 text-white' : 'text-zinc-500 hover:text-zinc-600']"
> >
批量自动分配 批量自动分配
</button> </button>
<button <button
@click="activeTab = 'manual'; nextTick(() => { setTimeout(() => { setStartSeq(manualFormData.startSeq); setEndSeq(manualFormData.endSeq) }, 100) })" @click="activeTab = 'manual'; nextTick(() => { setTimeout(() => { setStartSeq(manualFormData.startSeq); setEndSeq(manualFormData.endSeq) }, 100) })"
:class="['flex-1 py-2 text-xs font-bold rounded-md transition-colors', activeTab === 'manual' ? 'bg-blue-600 text-white' : 'text-zinc-500 hover:text-zinc-300']" :class="['flex-1 py-2 text-xs font-bold rounded-md transition-colors', activeTab === 'manual' ? 'bg-blue-600 text-white' : 'text-zinc-500 hover:text-zinc-600']"
> >
手动配置单日 手动配置单日
</button> </button>
@@ -104,22 +104,22 @@
</div> </div>
<div> <div>
<label class="block text-xs font-bold text-zinc-500 mb-1">总序号数量</label> <label class="block text-xs font-bold text-zinc-500 mb-1">总序号数量</label>
<input type="number" min="1" v-model="autoFormData.totalSequences" class="w-full bg-zinc-950 border border-zinc-800 rounded-lg px-3 py-2 text-zinc-200"> <input type="number" min="1" v-model="autoFormData.totalSequences" class="w-full bg-white border border-zinc-200 rounded-lg px-3 py-2 text-zinc-700">
<div class="flex items-center gap-2 mt-2"> <div class="flex items-center gap-2 mt-2">
<button @click="autoFormData.totalSequences = 21" class="text-[10px] px-2 py-1 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 rounded transition-colors">21</button> <button @click="autoFormData.totalSequences = 21" class="text-[10px] px-2 py-1 bg-zinc-100 hover:bg-zinc-200 text-zinc-600 rounded transition-colors">21</button>
<button @click="autoFormData.totalSequences = 28" class="text-[10px] px-2 py-1 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 rounded transition-colors">28</button> <button @click="autoFormData.totalSequences = 28" class="text-[10px] px-2 py-1 bg-zinc-100 hover:bg-zinc-200 text-zinc-600 rounded transition-colors">28</button>
<button @click="autoFormData.totalSequences = 35" class="text-[10px] px-2 py-1 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 rounded transition-colors">35</button> <button @click="autoFormData.totalSequences = 35" class="text-[10px] px-2 py-1 bg-zinc-100 hover:bg-zinc-200 text-zinc-600 rounded transition-colors">35</button>
</div> </div>
</div> </div>
<div class="p-3 bg-blue-500/10 border border-blue-500/20 rounded-lg"> <div class="p-3 bg-blue-500/10 border border-blue-200 rounded-lg">
<p class="text-xs text-blue-400 font-bold mb-1">自动分配预览:</p> <p class="text-xs text-blue-700 font-bold mb-1">自动分配预览:</p>
<p class="text-[10px] text-blue-300/80"> <p class="text-[10px] text-blue-300/80">
<span v-if="autoFormData.dates.length === 0">请先选择日期</span> <span v-if="autoFormData.dates.length === 0">请先选择日期</span>
<span v-else>将会把 1 - {{ autoFormData.totalSequences }} 号平均分配到选中的 {{ autoFormData.dates.length }} 天中。每天约 {{ Math.ceil(autoFormData.totalSequences / autoFormData.dates.length) }} 个序号。</span> <span v-else>将会把 1 - {{ autoFormData.totalSequences }} 号平均分配到选中的 {{ autoFormData.dates.length }} 天中。每天约 {{ Math.ceil(autoFormData.totalSequences / autoFormData.dates.length) }} 个序号。</span>
</p> </p>
</div> </div>
<div class="flex gap-3 pt-4"> <div class="flex gap-3 pt-4">
<button @click="showModal = false" class="flex-1 py-2 bg-zinc-800 text-zinc-300 rounded-lg text-sm font-bold">取消</button> <button @click="showModal = false" class="flex-1 py-2 bg-zinc-100 text-zinc-600 rounded-lg text-sm font-bold">取消</button>
<button @click="saveAuto" :disabled="saving || autoFormData.dates.length === 0" class="flex-1 py-2 bg-blue-600 text-white rounded-lg text-sm font-bold disabled:opacity-50">批量保存</button> <button @click="saveAuto" :disabled="saving || autoFormData.dates.length === 0" class="flex-1 py-2 bg-blue-600 text-white rounded-lg text-sm font-bold disabled:opacity-50">批量保存</button>
</div> </div>
</div> </div>
@@ -133,24 +133,24 @@
<div> <div>
<label class="block text-xs font-bold text-zinc-500 mb-1">开放的空闲序号</label> <label class="block text-xs font-bold text-zinc-500 mb-1">开放的空闲序号</label>
<div class="flex flex-col items-center bg-zinc-950 border border-zinc-800 rounded-xl p-4"> <div class="flex flex-col items-center bg-white border border-zinc-200 rounded-xl p-4">
<!-- 模拟 iOS 滚轮 (这里用简化的网格选择代替复杂的滚轮组件以保证稳定性和易用性) --> <!-- 模拟 iOS 滚轮 (这里用简化的网格选择代替复杂的滚轮组件以保证稳定性和易用性) -->
<div class="text-sm font-bold text-zinc-400 mb-3 w-full flex justify-between items-center"> <div class="text-sm font-bold text-zinc-400 mb-3 w-full flex justify-between items-center">
<span>选择开放范围</span> <span>选择开放范围</span>
<span class="text-blue-400">{{ manualFormData.startSeq }} - {{ manualFormData.endSeq }}</span> <span class="text-blue-700">{{ manualFormData.startSeq }} - {{ manualFormData.endSeq }}</span>
</div> </div>
<div class="flex gap-4 items-center w-full justify-center py-4"> <div class="flex gap-4 items-center w-full justify-center py-4">
<div class="flex flex-col items-center flex-1"> <div class="flex flex-col items-center flex-1">
<span class="text-[10px] text-zinc-500 mb-2">起始序号</span> <span class="text-[10px] text-zinc-500 mb-2">起始序号</span>
<div ref="startScrollRef" class="relative w-full h-32 bg-zinc-900 border border-zinc-700 rounded-lg overflow-hidden snap-y snap-mandatory hide-scrollbar" style="scroll-behavior: smooth;" @scroll="handleStartScroll"> <div ref="startScrollRef" class="relative w-full h-32 bg-white border border-zinc-200 rounded-lg overflow-hidden snap-y snap-mandatory hide-scrollbar" style="scroll-behavior: smooth;" @scroll="handleStartScroll">
<div class="absolute top-1/2 left-0 right-0 h-10 -mt-5 bg-blue-500/10 border-y border-blue-500/30 pointer-events-none z-10"></div> <div class="absolute top-1/2 left-0 right-0 h-10 -mt-5 bg-blue-500/10 border-y border-blue-200 pointer-events-none z-10"></div>
<div class="h-[44px]"></div> <!-- Padding top --> <div class="h-[44px]"></div> <!-- Padding top -->
<div <div
v-for="n in 100" v-for="n in 100"
:key="'start-'+n" :key="'start-'+n"
class="h-10 flex items-center justify-center text-lg font-bold snap-center cursor-pointer transition-colors" class="h-10 flex items-center justify-center text-lg font-bold snap-center cursor-pointer transition-colors"
:class="manualFormData.startSeq === n ? 'text-blue-400' : 'text-zinc-500 hover:text-zinc-300'" :class="manualFormData.startSeq === n ? 'text-blue-700' : 'text-zinc-500 hover:text-zinc-600'"
@click="setStartSeq(n)" @click="setStartSeq(n)"
:data-val="n" :data-val="n"
> >
@@ -164,14 +164,14 @@
<div class="flex flex-col items-center flex-1"> <div class="flex flex-col items-center flex-1">
<span class="text-[10px] text-zinc-500 mb-2">结束序号</span> <span class="text-[10px] text-zinc-500 mb-2">结束序号</span>
<div ref="endScrollRef" class="relative w-full h-32 bg-zinc-900 border border-zinc-700 rounded-lg overflow-hidden snap-y snap-mandatory hide-scrollbar" style="scroll-behavior: smooth;" @scroll="handleEndScroll"> <div ref="endScrollRef" class="relative w-full h-32 bg-white border border-zinc-200 rounded-lg overflow-hidden snap-y snap-mandatory hide-scrollbar" style="scroll-behavior: smooth;" @scroll="handleEndScroll">
<div class="absolute top-1/2 left-0 right-0 h-10 -mt-5 bg-blue-500/10 border-y border-blue-500/30 pointer-events-none z-10"></div> <div class="absolute top-1/2 left-0 right-0 h-10 -mt-5 bg-blue-500/10 border-y border-blue-200 pointer-events-none z-10"></div>
<div class="h-[44px]"></div> <!-- Padding top --> <div class="h-[44px]"></div> <!-- Padding top -->
<div <div
v-for="n in 100" v-for="n in 100"
:key="'end-'+n" :key="'end-'+n"
class="h-10 flex items-center justify-center text-lg font-bold snap-center cursor-pointer transition-colors" class="h-10 flex items-center justify-center text-lg font-bold snap-center cursor-pointer transition-colors"
:class="manualFormData.endSeq === n ? 'text-blue-400' : 'text-zinc-500 hover:text-zinc-300'" :class="manualFormData.endSeq === n ? 'text-blue-700' : 'text-zinc-500 hover:text-zinc-600'"
@click="setEndSeq(n)" @click="setEndSeq(n)"
:data-val="n" :data-val="n"
> >
@@ -185,7 +185,7 @@
<!-- 如果有特定跳过的序号,保留文本框以供进阶编辑 --> <!-- 如果有特定跳过的序号,保留文本框以供进阶编辑 -->
<div class="w-full mt-4"> <div class="w-full mt-4">
<div <div
class="flex justify-between items-center text-[10px] text-zinc-500 cursor-pointer py-2 hover:text-zinc-300 transition-colors" class="flex justify-between items-center text-[10px] text-zinc-500 cursor-pointer py-2 hover:text-zinc-600 transition-colors"
@click="showAdvancedManual = !showAdvancedManual" @click="showAdvancedManual = !showAdvancedManual"
> >
<span>进阶:自定义跳过某些序号</span> <span>进阶:自定义跳过某些序号</span>
@@ -193,7 +193,7 @@
</div> </div>
<div v-if="showAdvancedManual" class="mt-2"> <div v-if="showAdvancedManual" class="mt-2">
<textarea v-model="manualFormData.sequences" rows="2" placeholder="例如: 1,2,3,5 (跳过了4)" class="w-full bg-zinc-900 border border-zinc-800 rounded-lg px-3 py-2 text-xs text-zinc-300 resize-none focus:outline-none focus:border-blue-500"></textarea> <textarea v-model="manualFormData.sequences" rows="2" placeholder="例如: 1,2,3,5 (跳过了4)" class="w-full bg-white border border-zinc-200 rounded-lg px-3 py-2 text-xs text-zinc-600 resize-none focus:outline-none focus:border-blue-500"></textarea>
<p class="text-[10px] text-zinc-600 mt-1">手动编辑后将以此处文本框内容为准</p> <p class="text-[10px] text-zinc-600 mt-1">手动编辑后将以此处文本框内容为准</p>
</div> </div>
</div> </div>
@@ -202,7 +202,7 @@
<p class="text-[10px] text-zinc-600 mt-2">注意:手动配置会覆盖该日期原有的设置</p> <p class="text-[10px] text-zinc-600 mt-2">注意:手动配置会覆盖该日期原有的设置</p>
</div> </div>
<div class="flex gap-3 pt-4"> <div class="flex gap-3 pt-4">
<button @click="showModal = false" class="flex-1 py-2 bg-zinc-800 text-zinc-300 rounded-lg text-sm font-bold">取消</button> <button @click="showModal = false" class="flex-1 py-2 bg-zinc-100 text-zinc-600 rounded-lg text-sm font-bold">取消</button>
<button @click="saveManual" :disabled="saving || !manualFormData.date" class="flex-1 py-2 bg-blue-600 text-white rounded-lg text-sm font-bold disabled:opacity-50">保存</button> <button @click="saveManual" :disabled="saving || !manualFormData.date" class="flex-1 py-2 bg-blue-600 text-white rounded-lg text-sm font-bold disabled:opacity-50">保存</button>
</div> </div>
</div> </div>
+26 -26
View File
@@ -2,7 +2,7 @@
<div class="max-w-[1400px] mx-auto space-y-8 pb-20"> <div class="max-w-[1400px] mx-auto space-y-8 pb-20">
<div class="flex flex-col md:flex-row md:items-end justify-between gap-6"> <div class="flex flex-col md:flex-row md:items-end justify-between gap-6">
<div> <div>
<h2 class="text-2xl font-black text-zinc-100 tracking-tight">向用户发送通知</h2> <h2 class="text-2xl font-black text-zinc-900 tracking-tight">向用户发送通知</h2>
<p class="text-xs text-zinc-500 mt-1">即时推送系统公告、活动信息或重要提醒至指定群体</p> <p class="text-xs text-zinc-500 mt-1">即时推送系统公告、活动信息或重要提醒至指定群体</p>
</div> </div>
</div> </div>
@@ -10,7 +10,7 @@
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8 items-start"> <div class="grid grid-cols-1 lg:grid-cols-12 gap-8 items-start">
<!-- 左侧:编辑区 --> <!-- 左侧:编辑区 -->
<div class="lg:col-span-7 space-y-6"> <div class="lg:col-span-7 space-y-6">
<div class="bg-zinc-900/30 border border-zinc-800 rounded-[2rem] p-8 shadow-xl"> <div class="bg-zinc-50/30 border border-zinc-200 rounded-[2rem] p-8 shadow-xl">
<div class="space-y-6"> <div class="space-y-6">
<!-- 标题 --> <!-- 标题 -->
<div class="space-y-2"> <div class="space-y-2">
@@ -21,7 +21,7 @@
v-model="form.title" v-model="form.title"
type="text" type="text"
placeholder="请输入通知标题" placeholder="请输入通知标题"
class="w-full bg-zinc-950 border border-zinc-800 rounded-2xl px-5 py-3.5 text-sm focus:outline-none focus:border-blue-500/30 transition-all text-zinc-200 placeholder:text-zinc-800" class="w-full bg-white border border-zinc-200 rounded-2xl px-5 py-3.5 text-sm focus:outline-none focus:border-blue-200 transition-all text-zinc-700 placeholder:text-zinc-500"
> >
</div> </div>
@@ -33,12 +33,12 @@
<textarea <textarea
v-model="form.content" v-model="form.content"
placeholder="请输入通知详情内容..." placeholder="请输入通知详情内容..."
class="w-full bg-zinc-950 border border-zinc-800 rounded-2xl px-5 py-4 text-sm focus:outline-none focus:border-blue-500/30 transition-all text-zinc-200 placeholder:text-zinc-800 min-h-[160px] resize-none" class="w-full bg-white border border-zinc-200 rounded-2xl px-5 py-4 text-sm focus:outline-none focus:border-blue-200 transition-all text-zinc-700 placeholder:text-zinc-500 min-h-[160px] resize-none"
/> />
</div> </div>
<!-- 范围选择 --> <!-- 范围选择 -->
<div class="space-y-3 pt-4 border-t border-zinc-800/50"> <div class="space-y-3 pt-4 border-t border-zinc-200">
<label class="text-[10px] font-black text-zinc-600 uppercase tracking-[0.2em] px-1" <label class="text-[10px] font-black text-zinc-600 uppercase tracking-[0.2em] px-1"
>通知范围</label >通知范围</label
> >
@@ -50,7 +50,7 @@
'flex items-center gap-2 px-4 py-2.5 rounded-xl text-[11px] font-black transition-all border', 'flex items-center gap-2 px-4 py-2.5 rounded-xl text-[11px] font-black transition-all border',
form.scope === opt.id form.scope === opt.id
? 'bg-blue-600 border-blue-500 text-white shadow-lg shadow-blue-900/20' ? 'bg-blue-600 border-blue-500 text-white shadow-lg shadow-blue-900/20'
: 'bg-zinc-950 border-zinc-800 text-zinc-500 hover:border-zinc-700 hover:text-zinc-300' : 'bg-white border-zinc-200 text-zinc-500 hover:border-zinc-200 hover:text-zinc-600'
]" ]"
@click="form.scope = opt.id" @click="form.scope = opt.id"
> >
@@ -120,7 +120,7 @@
</div> </div>
<div v-else-if="form.scope === 'MULTI_CLASS'" key="multi" class="space-y-4"> <div v-else-if="form.scope === 'MULTI_CLASS'" key="multi" class="space-y-4">
<div class="p-6 bg-zinc-950/50 border border-zinc-800 border-dashed rounded-2xl"> <div class="p-6 bg-white/70 border border-zinc-200 border-dashed rounded-2xl">
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 mb-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-3 mb-4">
<CustomSelect <CustomSelect
v-model="multiClassForm.grade" v-model="multiClassForm.grade"
@@ -139,7 +139,7 @@
/> />
<button <button
:disabled="!canAddClass" :disabled="!canAddClass"
class="px-4 bg-zinc-800 hover:bg-zinc-700 disabled:opacity-50 disabled:cursor-not-allowed text-zinc-400 font-bold rounded-xl text-xs transition-all" class="px-4 bg-zinc-100 hover:bg-zinc-200 disabled:opacity-50 disabled:cursor-not-allowed text-zinc-400 font-bold rounded-xl text-xs transition-all"
@click="addClassToSelection" @click="addClassToSelection"
> >
添加 添加
@@ -151,7 +151,7 @@
<div <div
v-for="(cls, index) in form.selectedClasses" v-for="(cls, index) in form.selectedClasses"
:key="index" :key="index"
class="flex items-center gap-2 px-3 py-1.5 bg-zinc-900 border border-zinc-800 rounded-lg group" class="flex items-center gap-2 px-3 py-1.5 bg-white border border-zinc-200 rounded-lg group"
> >
<span class="text-[10px] font-bold text-zinc-400" <span class="text-[10px] font-bold text-zinc-400"
>{{ cls.grade }} {{ cls.class }}</span >{{ cls.grade }} {{ cls.class }}</span
@@ -188,7 +188,7 @@
v-model="userSearchQuery" v-model="userSearchQuery"
type="text" type="text"
placeholder="搜索并选择用户 (姓名、用户名或ID)..." placeholder="搜索并选择用户 (姓名、用户名或ID)..."
class="w-full bg-zinc-950 border border-zinc-800 rounded-2xl pl-12 pr-4 py-3 text-sm focus:outline-none focus:border-blue-500/30 transition-all text-zinc-200" class="w-full bg-white border border-zinc-200 rounded-2xl pl-12 pr-4 py-3 text-sm focus:outline-none focus:border-blue-200 transition-all text-zinc-700"
@input="onUserSearchInput" @input="onUserSearchInput"
> >
</div> </div>
@@ -196,9 +196,9 @@
<!-- 搜索结果 --> <!-- 搜索结果 -->
<div <div
v-if="showUserSearchResults && userSearchResults.length > 0" v-if="showUserSearchResults && userSearchResults.length > 0"
class="bg-zinc-950 border border-zinc-800 rounded-2xl overflow-hidden max-h-[300px] overflow-y-auto custom-scrollbar" class="bg-white border border-zinc-200 rounded-2xl overflow-hidden max-h-[300px] overflow-y-auto custom-scrollbar"
> >
<div class="px-4 py-2 border-b border-zinc-800/60 bg-zinc-900/40"> <div class="px-4 py-2 border-b border-zinc-200/60 bg-zinc-50/40">
<span class="text-[9px] font-black text-zinc-600 uppercase tracking-widest" <span class="text-[9px] font-black text-zinc-600 uppercase tracking-widest"
>搜索结果 ({{ userSearchResults.length }})</span >搜索结果 ({{ userSearchResults.length }})</span
> >
@@ -206,10 +206,10 @@
<div <div
v-for="user in userSearchResults" v-for="user in userSearchResults"
:key="user.id" :key="user.id"
class="flex items-center justify-between p-4 border-b border-zinc-800/30 last:border-0 hover:bg-zinc-800/30 transition-all" class="flex items-center justify-between p-4 border-b border-zinc-200/30 last:border-0 hover:bg-zinc-100/30 transition-all"
> >
<div> <div>
<h5 class="text-sm font-bold text-zinc-200"> <h5 class="text-sm font-bold text-zinc-700">
{{ user.name || user.username }} {{ user.name || user.username }}
</h5> </h5>
<div class="flex items-center gap-2 mt-0.5"> <div class="flex items-center gap-2 mt-0.5">
@@ -228,7 +228,7 @@
</div> </div>
<button <button
:disabled="isUserSelected(user.id)" :disabled="isUserSelected(user.id)"
class="px-3 py-1.5 bg-zinc-800 hover:bg-blue-600 hover:text-white disabled:opacity-50 disabled:cursor-not-allowed text-zinc-400 text-[10px] font-black rounded-lg transition-all uppercase" class="px-3 py-1.5 bg-zinc-100 hover:bg-blue-600 hover:text-zinc-900 disabled:opacity-50 disabled:cursor-not-allowed text-zinc-400 text-[10px] font-black rounded-lg transition-all uppercase"
@click="addUserToSelection(user)" @click="addUserToSelection(user)"
> >
{{ isUserSelected(user.id) ? '已选择' : '选择' }} {{ isUserSelected(user.id) ? '已选择' : '选择' }}
@@ -253,10 +253,10 @@
<div <div
v-for="(user, index) in form.selectedUsers" v-for="(user, index) in form.selectedUsers"
:key="user.id" :key="user.id"
class="flex items-center gap-2 px-3 py-1.5 bg-zinc-900 border border-zinc-800 rounded-lg group" class="flex items-center gap-2 px-3 py-1.5 bg-white border border-zinc-200 rounded-lg group"
> >
<div class="flex flex-col"> <div class="flex flex-col">
<span class="text-[10px] font-bold text-zinc-300 leading-none">{{ <span class="text-[10px] font-bold text-zinc-600 leading-none">{{
user.name || user.username user.name || user.username
}}</span> }}</span>
<span class="text-[8px] text-zinc-600">@{{ user.username }}</span> <span class="text-[8px] text-zinc-600">@{{ user.username }}</span>
@@ -302,10 +302,10 @@
<button <button
:disabled="loading || !isFormValid" :disabled="loading || !isFormValid"
:class="[ :class="[
'flex items-center gap-2 px-10 py-4 bg-zinc-950 border border-zinc-800 text-xs font-black rounded-2xl transition-all uppercase tracking-[0.2em] shadow-lg', 'flex items-center gap-2 px-10 py-4 bg-white border border-zinc-200 text-xs font-black rounded-2xl transition-all uppercase tracking-[0.2em] shadow-lg',
loading || !isFormValid loading || !isFormValid
? 'text-zinc-800 cursor-not-allowed' ? 'text-zinc-800 cursor-not-allowed'
: 'text-zinc-400 hover:text-zinc-100 hover:border-zinc-600 hover:bg-zinc-900' : 'text-zinc-400 hover:text-zinc-900 hover:border-zinc-300 hover:bg-white'
]" ]"
@click="sendNotification" @click="sendNotification"
> >
@@ -320,17 +320,17 @@
<!-- 右侧:预览区 --> <!-- 右侧:预览区 -->
<div class="lg:col-span-5 space-y-6 lg:sticky lg:top-8"> <div class="lg:col-span-5 space-y-6 lg:sticky lg:top-8">
<div <div
class="bg-zinc-900/30 border border-zinc-800 rounded-[2rem] p-8 shadow-xl flex flex-col h-full" class="bg-zinc-50/30 border border-zinc-200 rounded-[2rem] p-8 shadow-xl flex flex-col h-full"
> >
<h3 <h3
class="text-sm font-black text-zinc-100 uppercase tracking-widest mb-6 flex items-center gap-2" class="text-sm font-black text-zinc-900 uppercase tracking-widest mb-6 flex items-center gap-2"
> >
<Eye :size="16" class="text-blue-500" /> 通知预览 <Eye :size="16" class="text-blue-500" /> 通知预览
</h3> </h3>
<div class="flex-1 flex flex-col items-center justify-center p-4"> <div class="flex-1 flex flex-col items-center justify-center p-4">
<div <div
class="w-full max-w-[320px] bg-[#0c0c0e] border border-zinc-800 rounded-3xl overflow-hidden shadow-2xl relative" class="w-full max-w-[320px] bg-white border border-zinc-200 rounded-3xl overflow-hidden shadow-2xl relative"
> >
<!-- 顶部装饰 --> <!-- 顶部装饰 -->
<div class="h-1.5 bg-blue-600 w-full" /> <div class="h-1.5 bg-blue-600 w-full" />
@@ -339,7 +339,7 @@
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div <div
class="w-8 h-8 rounded-xl bg-blue-600/10 text-blue-500 flex items-center justify-center border border-blue-500/20" class="w-8 h-8 rounded-xl bg-blue-50 text-blue-500 flex items-center justify-center border border-blue-200"
> >
<Bell :size="14" /> <Bell :size="14" />
</div> </div>
@@ -356,7 +356,7 @@
<h4 <h4
:class="[ :class="[
'text-sm font-black transition-colors', 'text-sm font-black transition-colors',
form.title ? 'text-zinc-100' : 'text-zinc-800 italic' form.title ? 'text-zinc-900' : 'text-zinc-800 italic'
]" ]"
> >
{{ form.title || '通知标题' }} {{ form.title || '通知标题' }}
@@ -371,7 +371,7 @@
</p> </p>
</div> </div>
<div class="pt-4 border-t border-zinc-800/50 flex items-center justify-between"> <div class="pt-4 border-t border-zinc-200 flex items-center justify-between">
<div class="flex items-center gap-1.5"> <div class="flex items-center gap-1.5">
<Users :size="12" class="text-zinc-700" /> <Users :size="12" class="text-zinc-700" />
<span class="text-[9px] font-black text-zinc-600 uppercase tracking-wider"> <span class="text-[9px] font-black text-zinc-600 uppercase tracking-wider">
@@ -379,7 +379,7 @@
</span> </span>
</div> </div>
<button <button
class="p-1.5 text-blue-500 hover:bg-blue-600/10 rounded-lg transition-all" class="p-1.5 text-blue-500 hover:bg-blue-50 rounded-lg transition-all"
> >
<MessageSquare :size="14" /> <MessageSquare :size="14" />
</button> </button>
+18 -18
View File
@@ -5,19 +5,19 @@
<div <div
v-for="(stat, i) in statCards" v-for="(stat, i) in statCards"
:key="i" :key="i"
class="bg-zinc-900/40 border border-zinc-800 rounded-2xl p-6 relative overflow-hidden group hover:border-zinc-700 transition-all shadow-lg shadow-black/20" class="bg-zinc-50/40 border border-zinc-200 rounded-2xl p-6 relative overflow-hidden group hover:border-zinc-200 transition-all shadow-lg shadow-zinc-900/5"
> >
<div class="flex justify-between items-start mb-4"> <div class="flex justify-between items-start mb-4">
<div <div
:class="[ :class="[
'p-3 rounded-xl border', 'p-3 rounded-xl border',
stat.color === 'blue' stat.color === 'blue'
? 'bg-blue-500/10 text-blue-500 border-blue-500/20' ? 'bg-blue-500/10 text-blue-500 border-blue-200'
: stat.color === 'emerald' : stat.color === 'emerald'
? 'bg-emerald-500/10 text-emerald-500 border-emerald-500/20' ? 'bg-emerald-500/10 text-emerald-500 border-emerald-500/20'
: stat.color === 'pink' : stat.color === 'pink'
? 'bg-pink-500/10 text-pink-500 border-pink-500/20' ? 'bg-pink-500/10 text-pink-500 border-pink-500/20'
: 'bg-zinc-500/10 text-zinc-500 border-zinc-500/20' : 'bg-zinc-100 text-zinc-500 border-zinc-200'
]" ]"
> >
<component :is="stat.icon" :size="24" /> <component :is="stat.icon" :size="24" />
@@ -26,7 +26,7 @@
v-if="stat.trend" v-if="stat.trend"
:class="[ :class="[
'flex items-center gap-1 text-[11px] font-bold', 'flex items-center gap-1 text-[11px] font-bold',
stat.trendDown ? 'text-red-400' : 'text-emerald-400' stat.trendDown ? 'text-red-400' : 'text-emerald-700'
]" ]"
> >
<TrendingDown v-if="stat.trendDown" :size="12" /> <TrendingDown v-if="stat.trendDown" :size="12" />
@@ -36,7 +36,7 @@
</div> </div>
<div> <div>
<p class="text-zinc-500 text-sm font-medium">{{ stat.label }}</p> <p class="text-zinc-500 text-sm font-medium">{{ stat.label }}</p>
<h4 class="text-3xl font-bold tracking-tight text-zinc-100">{{ stat.value }}</h4> <h4 class="text-3xl font-bold tracking-tight text-zinc-900">{{ stat.value }}</h4>
</div> </div>
</div> </div>
</div> </div>
@@ -44,14 +44,14 @@
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8"> <div class="grid grid-cols-1 lg:grid-cols-12 gap-8">
<!-- 最近活动 --> <!-- 最近活动 -->
<div <div
class="lg:col-span-5 bg-zinc-900/40 border border-zinc-800 rounded-3xl overflow-hidden flex flex-col shadow-lg shadow-black/20" class="lg:col-span-5 bg-zinc-50/40 border border-zinc-200 rounded-3xl overflow-hidden flex flex-col shadow-lg shadow-zinc-900/5"
> >
<div class="px-6 py-5 border-b border-zinc-800 flex items-center justify-between"> <div class="px-6 py-5 border-b border-zinc-200 flex items-center justify-between">
<h3 class="text-lg font-bold flex items-center gap-2"> <h3 class="text-lg font-bold flex items-center gap-2">
<Activity :size="18" class="text-blue-500" /> 最近活动 <Activity :size="18" class="text-blue-500" /> 最近活动
</h3> </h3>
<button <button
class="p-2 text-zinc-500 hover:text-zinc-300 transition-colors" class="p-2 text-zinc-500 hover:text-zinc-600 transition-colors"
:class="{ 'animate-spin': loadingActivities }" :class="{ 'animate-spin': loadingActivities }"
@click="refreshActivities" @click="refreshActivities"
> >
@@ -79,7 +79,7 @@
<div <div
v-for="(activity, idx) in recentActivities" v-for="(activity, idx) in recentActivities"
:key="idx" :key="idx"
class="flex items-start gap-4 p-4 rounded-2xl hover:bg-zinc-800/40 transition-all cursor-pointer group" class="flex items-start gap-4 p-4 rounded-2xl hover:bg-zinc-100/40 transition-all cursor-pointer group"
> >
<div <div
:class="[ :class="[
@@ -91,7 +91,7 @@
</div> </div>
<div class="flex-1 min-w-0"> <div class="flex-1 min-w-0">
<h5 <h5
class="font-bold text-sm text-zinc-200 group-hover:text-blue-400 transition-colors" class="font-bold text-sm text-zinc-700 group-hover:text-blue-700 transition-colors"
> >
{{ activity.title }} {{ activity.title }}
</h5> </h5>
@@ -110,9 +110,9 @@
<!-- 系统状态 --> <!-- 系统状态 -->
<div <div
class="lg:col-span-4 bg-zinc-900/40 border border-zinc-800 rounded-xl overflow-hidden flex flex-col shadow-lg shadow-black/20" class="lg:col-span-4 bg-zinc-50/40 border border-zinc-200 rounded-xl overflow-hidden flex flex-col shadow-lg shadow-zinc-900/5"
> >
<div class="px-6 py-5 border-b border-zinc-800 flex items-center justify-between"> <div class="px-6 py-5 border-b border-zinc-200 flex items-center justify-between">
<h3 class="text-lg font-bold flex items-center gap-2"> <h3 class="text-lg font-bold flex items-center gap-2">
<ShieldCheck :size="18" class="text-emerald-500" /> 系统状态 <ShieldCheck :size="18" class="text-emerald-500" /> 系统状态
</h3> </h3>
@@ -144,16 +144,16 @@
/> />
<span class="text-xs font-semibold text-zinc-400">{{ status.label }}</span> <span class="text-xs font-semibold text-zinc-400">{{ status.label }}</span>
</div> </div>
<span class="text-xs font-bold text-zinc-200">{{ status.value }}</span> <span class="text-xs font-bold text-zinc-700">{{ status.value }}</span>
</div> </div>
</div> </div>
</div> </div>
<!-- 快速操作 --> <!-- 快速操作 -->
<div <div
class="lg:col-span-3 bg-zinc-900/40 border border-zinc-800 rounded-3xl overflow-hidden flex flex-col shadow-lg shadow-black/20" class="lg:col-span-3 bg-zinc-50/40 border border-zinc-200 rounded-3xl overflow-hidden flex flex-col shadow-lg shadow-zinc-900/5"
> >
<div class="px-6 py-5 border-b border-zinc-800"> <div class="px-6 py-5 border-b border-zinc-200">
<h3 class="text-lg font-bold flex items-center gap-2"> <h3 class="text-lg font-bold flex items-center gap-2">
<Zap :size="18" class="text-yellow-500" /> 快速操作 <Zap :size="18" class="text-yellow-500" /> 快速操作
</h3> </h3>
@@ -166,7 +166,7 @@
'w-full flex items-center gap-3 px-5 py-4 rounded-lg border font-bold text-sm transition-all text-left group', 'w-full flex items-center gap-3 px-5 py-4 rounded-lg border font-bold text-sm transition-all text-left group',
action.primary action.primary
? 'bg-blue-600 border-blue-500 text-white shadow-xl shadow-blue-900/20 hover:bg-blue-500' ? 'bg-blue-600 border-blue-500 text-white shadow-xl shadow-blue-900/20 hover:bg-blue-500'
: 'bg-zinc-950/40 border-zinc-800 text-zinc-400 hover:border-zinc-600 hover:text-zinc-200' : 'bg-white/80 border-zinc-200 text-zinc-400 hover:border-zinc-300 hover:text-zinc-700'
]" ]"
@click="navigateTo(action.id)" @click="navigateTo(action.id)"
> >
@@ -302,9 +302,9 @@ const getActivityStyle = (type) => {
const styles = { const styles = {
song: { icon: Music, bg: 'bg-purple-500/10 text-purple-500 border-purple-500/20' }, song: { icon: Music, bg: 'bg-purple-500/10 text-purple-500 border-purple-500/20' },
user: { icon: Users, bg: 'bg-pink-500/10 text-pink-500 border-pink-500/20' }, user: { icon: Users, bg: 'bg-pink-500/10 text-pink-500 border-pink-500/20' },
schedule: { icon: Calendar, bg: 'bg-blue-500/10 text-blue-500 border-blue-500/20' } schedule: { icon: Calendar, bg: 'bg-blue-500/10 text-blue-500 border-blue-200' }
} }
return styles[type] || { icon: Activity, bg: 'bg-zinc-500/10 text-zinc-500 border-zinc-500/20' } return styles[type] || { icon: Activity, bg: 'bg-zinc-100 text-zinc-500 border-zinc-200' }
} }
const loadStats = async () => { const loadStats = async () => {
+39 -39
View File
@@ -3,14 +3,14 @@
<!-- 页面标题 --> <!-- 页面标题 -->
<div class="flex flex-col md:flex-row md:items-end justify-between gap-6"> <div class="flex flex-col md:flex-row md:items-end justify-between gap-6">
<div> <div>
<h2 class="text-2xl font-black text-zinc-100 tracking-tight">播出时段管理</h2> <h2 class="text-2xl font-black text-zinc-900 tracking-tight">播出时段管理</h2>
<p class="text-xs text-zinc-500 mt-1"> <p class="text-xs text-zinc-500 mt-1">
定义校园广播的常规播放窗口,用户仅可在这些时间段内进行点歌或查看排期 定义校园广播的常规播放窗口,用户仅可在这些时间段内进行点歌或查看排期
</p> </p>
</div> </div>
<div class="flex flex-wrap items-center gap-3"> <div class="flex flex-wrap items-center gap-3">
<div <div
class="bg-zinc-900/40 border border-zinc-800 rounded-xl px-4 py-2 flex items-center gap-3" class="bg-zinc-50/40 border border-zinc-200 rounded-xl px-4 py-2 flex items-center gap-3"
> >
<span class="text-[10px] font-black text-zinc-500 uppercase tracking-widest" <span class="text-[10px] font-black text-zinc-500 uppercase tracking-widest"
>启用播出时段选择</span >启用播出时段选择</span
@@ -18,7 +18,7 @@
<button <button
:class="[ :class="[
'relative w-10 h-5 rounded-full transition-colors', 'relative w-10 h-5 rounded-full transition-colors',
enablePlayTimeSelection ? 'bg-blue-600' : 'bg-zinc-800' enablePlayTimeSelection ? 'bg-blue-600' : 'bg-zinc-100'
]" ]"
@click="toggleGlobalEnabled" @click="toggleGlobalEnabled"
> >
@@ -30,16 +30,16 @@
/> />
</button> </button>
</div> </div>
<div v-if="playTimes.length > 1" class="flex items-center gap-2 bg-zinc-900/40 border border-zinc-800 rounded-xl px-3 py-2"> <div v-if="playTimes.length > 1" class="flex items-center gap-2 bg-zinc-50/40 border border-zinc-200 rounded-xl px-3 py-2">
<span class="text-[10px] font-black text-zinc-500 uppercase tracking-widest">批量</span> <span class="text-[10px] font-black text-zinc-500 uppercase tracking-widest">批量</span>
<button <button
class="px-3 py-1.5 text-[10px] font-black bg-emerald-600/20 text-emerald-400 border border-emerald-500/30 rounded-lg hover:bg-emerald-600/30 transition-all" class="px-3 py-1.5 text-[10px] font-black bg-emerald-600/20 text-emerald-700 border border-emerald-200 rounded-lg hover:bg-emerald-600/30 transition-all"
@click="batchToggleAll(true)" @click="batchToggleAll(true)"
> >
全部启用 全部启用
</button> </button>
<button <button
class="px-3 py-1.5 text-[10px] font-black bg-zinc-800 text-zinc-400 border border-zinc-700 rounded-lg hover:bg-zinc-700 transition-all" class="px-3 py-1.5 text-[10px] font-black bg-zinc-100 text-zinc-400 border border-zinc-200 rounded-lg hover:bg-zinc-200 transition-all"
@click="batchToggleAll(false)" @click="batchToggleAll(false)"
> >
全部禁用 全部禁用
@@ -58,7 +58,7 @@
<div class="bg-blue-600/5 border border-blue-500/10 rounded-xl p-5 flex items-start gap-4"> <div class="bg-blue-600/5 border border-blue-500/10 rounded-xl p-5 flex items-start gap-4">
<Info class="text-blue-500 shrink-0 mt-0.5" :size="18" /> <Info class="text-blue-500 shrink-0 mt-0.5" :size="18" />
<div class="space-y-1"> <div class="space-y-1">
<p class="text-xs font-bold text-zinc-300">关于时段限制</p> <p class="text-xs font-bold text-zinc-600">关于时段限制</p>
<p class="text-[11px] text-zinc-500 leading-relaxed"> <p class="text-[11px] text-zinc-500 leading-relaxed">
启用全局时段选择后,系统将仅在定义的时段内开放点歌功能。如果未设置开始或结束时间,系统将视为该边界不设限。 启用全局时段选择后,系统将仅在定义的时段内开放点歌功能。如果未设置开始或结束时间,系统将视为该边界不设限。
</p> </p>
@@ -68,7 +68,7 @@
<!-- 加载状态 --> <!-- 加载状态 -->
<div <div
v-if="loading" v-if="loading"
class="flex flex-col items-center justify-center py-20 space-y-4 bg-zinc-900/20 border border-zinc-800/50 rounded-xl" class="flex flex-col items-center justify-center py-20 space-y-4 bg-zinc-50/20 border border-zinc-200 rounded-xl"
> >
<div <div
class="w-10 h-10 border-4 border-blue-600/20 border-t-blue-600 rounded-full animate-spin" class="w-10 h-10 border-4 border-blue-600/20 border-t-blue-600 rounded-full animate-spin"
@@ -83,7 +83,7 @@
> >
<AlertCircle class="text-red-500 shrink-0 mt-0.5" :size="18" /> <AlertCircle class="text-red-500 shrink-0 mt-0.5" :size="18" />
<div class="space-y-1"> <div class="space-y-1">
<p class="text-xs font-bold text-zinc-300">获取数据失败</p> <p class="text-xs font-bold text-zinc-600">获取数据失败</p>
<p class="text-[11px] text-zinc-500 leading-relaxed">{{ error }}</p> <p class="text-[11px] text-zinc-500 leading-relaxed">{{ error }}</p>
</div> </div>
</div> </div>
@@ -91,13 +91,13 @@
<!-- 空状态 --> <!-- 空状态 -->
<div <div
v-else-if="playTimes.length === 0" v-else-if="playTimes.length === 0"
class="flex flex-col items-center justify-center py-20 space-y-6 bg-zinc-900/20 border border-zinc-800/50 rounded-xl" class="flex flex-col items-center justify-center py-20 space-y-6 bg-zinc-50/20 border border-zinc-200 rounded-xl"
> >
<div class="p-6 rounded-xl bg-zinc-950 border border-zinc-800 text-zinc-700"> <div class="p-6 rounded-xl bg-white border border-zinc-200 text-zinc-700">
<Clock :size="48" /> <Clock :size="48" />
</div> </div>
<div class="text-center space-y-2"> <div class="text-center space-y-2">
<h3 class="text-lg font-bold text-zinc-100">暂无播出时段</h3> <h3 class="text-lg font-bold text-zinc-900">暂无播出时段</h3>
<p class="text-xs text-zinc-500">点击“添加播出时段”按钮创建第一个播出时段</p> <p class="text-xs text-zinc-500">点击“添加播出时段”按钮创建第一个播出时段</p>
</div> </div>
</div> </div>
@@ -109,15 +109,15 @@
v-for="playTime in playTimes" v-for="playTime in playTimes"
:key="playTime.id" :key="playTime.id"
:class="[ :class="[
'group relative bg-zinc-900/30 border rounded-xl p-8 transition-all hover:shadow-2xl hover:shadow-black/40', 'group relative bg-zinc-50/30 border rounded-xl p-8 transition-all hover:shadow-2xl hover:shadow-black/40',
playTime.enabled ? 'border-zinc-800/80' : 'border-zinc-800/40 opacity-60' playTime.enabled ? 'border-zinc-200/80' : 'border-zinc-200 opacity-60'
]" ]"
> >
<div class="flex items-start justify-between mb-6"> <div class="flex items-start justify-between mb-6">
<div <div
:class="[ :class="[
'p-3 rounded-xl bg-zinc-950 border border-zinc-800 transition-all', 'p-3 rounded-xl bg-white border border-zinc-200 transition-all',
playTime.enabled ? 'text-blue-500 border-blue-500/20' : 'text-zinc-700' playTime.enabled ? 'text-blue-500 border-blue-200' : 'text-zinc-700'
]" ]"
> >
<Clock :size="20" /> <Clock :size="20" />
@@ -128,7 +128,7 @@
'px-2 py-0.5 rounded text-[9px] font-black uppercase tracking-widest border transition-all', 'px-2 py-0.5 rounded text-[9px] font-black uppercase tracking-widest border transition-all',
playTime.enabled playTime.enabled
? 'bg-emerald-500/10 text-emerald-500 border-emerald-500/20' ? 'bg-emerald-500/10 text-emerald-500 border-emerald-500/20'
: 'bg-zinc-800/50 text-zinc-600 border-zinc-700/50' : 'bg-zinc-100/50 text-zinc-600 border-zinc-200'
]" ]"
> >
{{ playTime.enabled ? '已启用' : '已禁用' }} {{ playTime.enabled ? '已启用' : '已禁用' }}
@@ -142,7 +142,7 @@
<div class="space-y-4"> <div class="space-y-4">
<div> <div>
<h4 <h4
class="text-lg font-black text-zinc-100 group-hover:text-blue-400 transition-colors" class="text-lg font-black text-zinc-900 group-hover:text-blue-700 transition-colors"
> >
{{ playTime.name }} {{ playTime.name }}
</h4> </h4>
@@ -150,7 +150,7 @@
class="flex items-center gap-2 mt-1.5 text-blue-500/80 font-black tracking-tighter" class="flex items-center gap-2 mt-1.5 text-blue-500/80 font-black tracking-tighter"
> >
<span class="text-xl">{{ playTime.startTime || '不限' }}</span> <span class="text-xl">{{ playTime.startTime || '不限' }}</span>
<div class="w-4 h-[2px] bg-zinc-800" /> <div class="w-4 h-[2px] bg-zinc-100" />
<span class="text-xl">{{ playTime.endTime || '不限' }}</span> <span class="text-xl">{{ playTime.endTime || '不限' }}</span>
</div> </div>
</div> </div>
@@ -160,23 +160,23 @@
</p> </p>
</div> </div>
<div class="mt-8 pt-6 border-t border-zinc-800/50 flex items-center justify-between"> <div class="mt-8 pt-6 border-t border-zinc-200 flex items-center justify-between">
<div class="flex gap-2"> <div class="flex gap-2">
<button <button
class="p-2.5 bg-zinc-950 border border-zinc-800 rounded-lg text-zinc-500 hover:text-emerald-400 hover:border-emerald-500/30 transition-all" class="p-2.5 bg-white border border-zinc-200 rounded-lg text-zinc-500 hover:text-emerald-700 hover:border-emerald-200 transition-all"
title="复制此时段" title="复制此时段"
@click="duplicatePlayTime(playTime)" @click="duplicatePlayTime(playTime)"
> >
<Copy :size="14" /> <Copy :size="14" />
</button> </button>
<button <button
class="p-2.5 bg-zinc-950 border border-zinc-800 rounded-lg text-zinc-500 hover:text-blue-400 hover:border-blue-500/30 transition-all" class="p-2.5 bg-white border border-zinc-200 rounded-lg text-zinc-500 hover:text-blue-700 hover:border-blue-200 transition-all"
@click="editPlayTime(playTime)" @click="editPlayTime(playTime)"
> >
<Edit2 :size="14" /> <Edit2 :size="14" />
</button> </button>
<button <button
class="p-2.5 bg-zinc-950 border border-zinc-800 rounded-lg text-zinc-500 hover:text-red-400 hover:border-red-500/30 transition-all" class="p-2.5 bg-white border border-zinc-200 rounded-lg text-zinc-500 hover:text-red-400 hover:border-red-200 transition-all"
@click="confirmDelete(playTime)" @click="confirmDelete(playTime)"
> >
<Trash2 :size="14" /> <Trash2 :size="14" />
@@ -186,7 +186,7 @@
:class="[ :class="[
'flex items-center gap-2 px-4 py-2 rounded-lg text-[10px] font-black uppercase tracking-widest transition-all', 'flex items-center gap-2 px-4 py-2 rounded-lg text-[10px] font-black uppercase tracking-widest transition-all',
playTime.enabled playTime.enabled
? 'bg-zinc-800 text-zinc-400 hover:text-zinc-200' ? 'bg-zinc-100 text-zinc-400 hover:text-zinc-700'
: 'bg-emerald-600 text-white shadow-lg shadow-emerald-900/20' : 'bg-emerald-600 text-white shadow-lg shadow-emerald-900/20'
]" ]"
@click="togglePlayTimeStatus(playTime)" @click="togglePlayTimeStatus(playTime)"
@@ -207,15 +207,15 @@
> >
<div class="absolute inset-0 bg-black/80 backdrop-blur-sm" @click="cancelForm" /> <div class="absolute inset-0 bg-black/80 backdrop-blur-sm" @click="cancelForm" />
<div <div
class="relative w-full max-w-lg bg-zinc-900 border border-zinc-800 rounded-xl shadow-2xl overflow-hidden" class="relative w-full max-w-lg bg-white border border-zinc-200 rounded-xl shadow-2xl overflow-hidden"
> >
<div class="p-8"> <div class="p-8">
<div class="flex items-center justify-between mb-8"> <div class="flex items-center justify-between mb-8">
<h3 class="text-xl font-black text-zinc-100 tracking-tight"> <h3 class="text-xl font-black text-zinc-900 tracking-tight">
{{ editingPlayTime ? '编辑播出时段' : '添加播出时段' }} {{ editingPlayTime ? '编辑播出时段' : '添加播出时段' }}
</h3> </h3>
<button <button
class="p-2 text-zinc-500 hover:text-zinc-200 transition-colors" class="p-2 text-zinc-500 hover:text-zinc-700 transition-colors"
@click="cancelForm" @click="cancelForm"
> >
<X :size="20" /> <X :size="20" />
@@ -231,7 +231,7 @@
v-for="p in playTimePresets" v-for="p in playTimePresets"
:key="p.name" :key="p.name"
type="button" type="button"
class="px-3 py-2 bg-zinc-950 border border-zinc-800 rounded-lg text-[11px] font-bold text-zinc-400 hover:text-zinc-200 hover:border-zinc-700 hover:bg-zinc-900 transition-all" class="px-3 py-2 bg-white border border-zinc-200 rounded-lg text-[11px] font-bold text-zinc-400 hover:text-zinc-700 hover:border-zinc-200 hover:bg-white transition-all"
@click="applyPreset(p)" @click="applyPreset(p)"
> >
<span class="block">{{ p.name }}</span> <span class="block">{{ p.name }}</span>
@@ -248,7 +248,7 @@
v-model="formData.name" v-model="formData.name"
type="text" type="text"
placeholder="例如: 午间广播" placeholder="例如: 午间广播"
class="w-full bg-zinc-950 border border-zinc-800 rounded-lg px-5 py-3.5 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30" class="w-full bg-white border border-zinc-200 rounded-lg px-5 py-3.5 text-sm text-zinc-700 focus:outline-none focus:border-blue-200"
> >
</div> </div>
@@ -261,7 +261,7 @@
<input <input
v-model="formData.startTime" v-model="formData.startTime"
type="time" type="time"
class="w-full bg-zinc-950 border border-zinc-800 rounded-lg px-5 py-3.5 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30 appearance-none" class="w-full bg-white border border-zinc-200 rounded-lg px-5 py-3.5 text-sm text-zinc-700 focus:outline-none focus:border-blue-200 appearance-none"
> >
<Clock <Clock
class="absolute right-4 top-1/2 -translate-y-1/2 text-zinc-700 pointer-events-none" class="absolute right-4 top-1/2 -translate-y-1/2 text-zinc-700 pointer-events-none"
@@ -278,7 +278,7 @@
<input <input
v-model="formData.endTime" v-model="formData.endTime"
type="time" type="time"
class="w-full bg-zinc-950 border border-zinc-800 rounded-lg px-5 py-3.5 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30 appearance-none" class="w-full bg-white border border-zinc-200 rounded-lg px-5 py-3.5 text-sm text-zinc-700 focus:outline-none focus:border-blue-200 appearance-none"
> >
<Clock <Clock
class="absolute right-4 top-1/2 -translate-y-1/2 text-zinc-700 pointer-events-none" class="absolute right-4 top-1/2 -translate-y-1/2 text-zinc-700 pointer-events-none"
@@ -298,7 +298,7 @@
<textarea <textarea
v-model="formData.description" v-model="formData.description"
placeholder="请输入时段描述信息..." placeholder="请输入时段描述信息..."
class="w-full bg-zinc-950 border border-zinc-800 rounded-lg px-5 py-4 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30 min-h-[100px] resize-none" class="w-full bg-white border border-zinc-200 rounded-lg px-5 py-4 text-sm text-zinc-700 focus:outline-none focus:border-blue-200 min-h-[100px] resize-none"
/> />
</div> </div>
@@ -306,10 +306,10 @@
<input <input
v-model="formData.enabled" v-model="formData.enabled"
type="checkbox" type="checkbox"
class="w-4 h-4 rounded border-zinc-800 bg-zinc-950 accent-blue-600" class="w-4 h-4 rounded border-zinc-200 bg-white accent-blue-600"
> >
<span <span
class="text-xs font-bold text-zinc-300 group-hover:text-blue-400 transition-colors" class="text-xs font-bold text-zinc-600 group-hover:text-blue-700 transition-colors"
>启用此播出时段</span >启用此播出时段</span
> >
</label> </label>
@@ -323,9 +323,9 @@
</div> </div>
</div> </div>
<div class="px-8 py-6 bg-zinc-950/50 border-t border-zinc-800 flex justify-end gap-3"> <div class="px-8 py-6 bg-white/70 border-t border-zinc-200 flex justify-end gap-3">
<button <button
class="px-6 py-2.5 text-xs font-bold text-zinc-500 hover:text-zinc-300" class="px-6 py-2.5 text-xs font-bold text-zinc-500 hover:text-zinc-600"
@click="cancelForm" @click="cancelForm"
> >
取消 取消
@@ -350,7 +350,7 @@
@click="showDeleteConfirm = false" @click="showDeleteConfirm = false"
/> />
<div <div
class="relative w-full max-w-sm bg-zinc-900 border border-zinc-800 rounded-xl shadow-2xl overflow-hidden" class="relative w-full max-w-sm bg-white border border-zinc-200 rounded-xl shadow-2xl overflow-hidden"
> >
<div class="p-8"> <div class="p-8">
<div class="flex flex-col items-center py-4 space-y-6"> <div class="flex flex-col items-center py-4 space-y-6">
@@ -360,7 +360,7 @@
<AlertCircle :size="32" /> <AlertCircle :size="32" />
</div> </div>
<div class="text-center space-y-2 px-4"> <div class="text-center space-y-2 px-4">
<h4 class="text-lg font-bold text-zinc-100"> <h4 class="text-lg font-bold text-zinc-900">
确定要删除播出时段 "{{ playTimeToDelete?.name }}" 吗? 确定要删除播出时段 "{{ playTimeToDelete?.name }}" 吗?
</h4> </h4>
<p class="text-xs text-zinc-500 leading-relaxed"> <p class="text-xs text-zinc-500 leading-relaxed">
@@ -369,7 +369,7 @@
</div> </div>
<div class="flex gap-3 w-full pt-4"> <div class="flex gap-3 w-full pt-4">
<button <button
class="flex-1 px-4 py-3 bg-zinc-950 border border-zinc-800 text-zinc-500 text-xs font-black rounded-lg transition-all hover:bg-zinc-800" class="flex-1 px-4 py-3 bg-white border border-zinc-200 text-zinc-500 text-xs font-black rounded-lg transition-all hover:bg-zinc-100"
@click="showDeleteConfirm = false" @click="showDeleteConfirm = false"
> >
取消 取消
+10 -10
View File
@@ -8,13 +8,13 @@
<Transition name="scale"> <Transition name="scale">
<div <div
v-if="show" v-if="show"
class="bg-zinc-900 border border-zinc-800 rounded-2xl shadow-2xl w-full max-w-sm overflow-hidden flex flex-col" class="bg-white border border-zinc-200 rounded-2xl shadow-2xl w-full max-w-sm overflow-hidden flex flex-col"
@click.stop @click.stop
> >
<div class="flex items-center justify-between p-4 border-b border-zinc-800"> <div class="flex items-center justify-between p-4 border-b border-zinc-200">
<h3 class="text-sm font-black text-zinc-100 uppercase tracking-widest">生成歌单卡片</h3> <h3 class="text-sm font-black text-zinc-900 uppercase tracking-widest">生成歌单卡片</h3>
<button <button
class="text-zinc-500 hover:text-zinc-300 transition-colors" class="text-zinc-500 hover:text-zinc-600 transition-colors"
@click="$emit('close')" @click="$emit('close')"
> >
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M18 6L6 18M6 6l12 12"/></svg> <svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M18 6L6 18M6 6l12 12"/></svg>
@@ -29,22 +29,22 @@
v-model="cardTitle" v-model="cardTitle"
type="text" type="text"
placeholder="例如: 2026-03-21 校园午间广播" placeholder="例如: 2026-03-21 校园午间广播"
class="w-full bg-zinc-950 border border-zinc-800 rounded-lg px-3 py-2 text-sm text-zinc-300 focus:outline-none focus:border-blue-500/50 transition-colors" class="w-full bg-white border border-zinc-200 rounded-lg px-3 py-2 text-sm text-zinc-600 focus:outline-none focus:border-blue-500/50 transition-colors"
@input="drawCanvas" @input="drawCanvas"
> >
</div> </div>
<!-- Canvas 预览 --> <!-- Canvas 预览 -->
<div class="relative w-full aspect-[2/3] max-w-[280px] bg-zinc-950 rounded-xl overflow-hidden border border-zinc-800 shadow-inner flex items-center justify-center"> <div class="relative w-full aspect-[2/3] max-w-[280px] bg-white rounded-xl overflow-hidden border border-zinc-200 shadow-inner flex items-center justify-center">
<canvas ref="canvasRef" width="400" :height="canvasHeight" class="w-full h-full object-contain bg-white"></canvas> <canvas ref="canvasRef" width="400" :height="canvasHeight" class="w-full h-full object-contain bg-white"></canvas>
</div> </div>
<p class="text-[10px] text-zinc-500 text-center">宽度 400 像素,高度自适应,适合在手机端查看和分享</p> <p class="text-[10px] text-zinc-500 text-center">宽度 400 像素,高度自适应,适合在手机端查看和分享</p>
</div> </div>
<div class="p-4 border-t border-zinc-800 bg-zinc-900/50 flex gap-3"> <div class="p-4 border-t border-zinc-200 bg-zinc-50/50 flex gap-3">
<button <button
class="flex-1 py-2.5 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 text-xs font-bold rounded-xl transition-colors uppercase tracking-wider" class="flex-1 py-2.5 bg-zinc-100 hover:bg-zinc-200 text-zinc-600 text-xs font-bold rounded-xl transition-colors uppercase tracking-wider"
@click="$emit('close')" @click="$emit('close')"
> >
取消 取消
@@ -210,7 +210,7 @@ const drawCanvas = () => {
// 绘制副标题(日期) // 绘制副标题(日期)
ctx.font = '14px "Inter", "system-ui", sans-serif' ctx.font = '14px "Inter", "system-ui", sans-serif'
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)' ctx.fillStyle = 'rgba(0, 0, 0, 0.75)'
ctx.fillText(new Date().toLocaleDateString('zh-CN'), 200, 80) ctx.fillText(new Date().toLocaleDateString('zh-CN'), 200, 80)
// 绘制列表背景 // 绘制列表背景
@@ -278,7 +278,7 @@ const drawCanvas = () => {
// 绘制底部水印 // 绘制底部水印
if (bgImageObj.value) { if (bgImageObj.value) {
ctx.fillStyle = 'rgba(255, 255, 255, 0.6)' ctx.fillStyle = 'rgba(0, 0, 0, 0.55)'
} else { } else {
ctx.fillStyle = '#CBD5E1' ctx.fillStyle = '#CBD5E1'
} }
@@ -1,15 +1,15 @@
<template> <template>
<div class="flex flex-col space-y-8 pb-12 lg:pb-20 animate-in fade-in slide-in-from-bottom-4 duration-500"> <div class="flex flex-col space-y-8 pb-12 lg:pb-20 animate-in fade-in slide-in-from-bottom-4 duration-500">
<div class="flex flex-col space-y-2"> <div class="flex flex-col space-y-2">
<h2 class="text-2xl font-bold tracking-tight text-zinc-100">导入酷狗</h2> <h2 class="text-2xl font-bold tracking-tight text-zinc-900">导入酷狗</h2>
<p class="text-sm text-zinc-500">选择排期日期,生成对应的广播排期卡片,用于扫描导入酷狗音乐</p> <p class="text-sm text-zinc-500">选择排期日期,生成对应的广播排期卡片,用于扫描导入酷狗音乐</p>
</div> </div>
<div class="flex flex-col gap-8"> <div class="flex flex-col gap-8">
<!-- 设置面板 --> <!-- 设置面板 -->
<div class="w-full flex flex-col gap-6"> <div class="w-full flex flex-col gap-6">
<div class="bg-zinc-900/40 border border-zinc-800 rounded-xl p-6 space-y-6 shadow-lg"> <div class="bg-zinc-50/40 border border-zinc-200 rounded-xl p-6 space-y-6 shadow-lg">
<h3 class="text-lg font-bold flex items-center gap-2 text-zinc-100 shrink-0"> <h3 class="text-lg font-bold flex items-center gap-2 text-zinc-900 shrink-0">
<Layout class="w-4 h-4 text-blue-500" /> 选择日期 <Layout class="w-4 h-4 text-blue-500" /> 选择日期
</h3> </h3>
@@ -38,9 +38,9 @@
<!-- 预览区 --> <!-- 预览区 -->
<div class="w-full flex flex-col gap-4"> <div class="w-full flex flex-col gap-4">
<template v-if="Object.keys(schedulesByDate).length > 0"> <template v-if="Object.keys(schedulesByDate).length > 0">
<div v-for="(schedules, date) in schedulesByDate" :key="date" class="bg-zinc-900/40 border border-zinc-800 rounded-xl p-6 shadow-lg mb-4"> <div v-for="(schedules, date) in schedulesByDate" :key="date" class="bg-zinc-50/40 border border-zinc-200 rounded-xl p-6 shadow-lg mb-4">
<div class="flex items-center justify-between mb-4"> <div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-bold text-zinc-100">{{ date }} 排期预览 ({{ schedules.length }} 首)</h3> <h3 class="text-lg font-bold text-zinc-900">{{ date }} 排期预览 ({{ schedules.length }} 首)</h3>
<button <button
:disabled="schedules.length === 0" :disabled="schedules.length === 0"
class="px-4 py-2 bg-emerald-600 hover:bg-emerald-500 text-white text-xs font-bold rounded-xl shadow-lg shadow-emerald-900/20 transition-colors flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed" class="px-4 py-2 bg-emerald-600 hover:bg-emerald-500 text-white text-xs font-bold rounded-xl shadow-lg shadow-emerald-900/20 transition-colors flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
@@ -52,10 +52,10 @@
</div> </div>
<div v-if="schedules.length > 0" class="space-y-3"> <div v-if="schedules.length > 0" class="space-y-3">
<div v-for="(schedule, index) in schedules" :key="schedule.id || index" class="flex items-center gap-3 p-3 bg-zinc-950/50 border border-zinc-800/50 rounded-lg"> <div v-for="(schedule, index) in schedules" :key="schedule.id || index" class="flex items-center gap-3 p-3 bg-white/70 border border-zinc-200 rounded-lg">
<span class="text-blue-500 font-bold w-6">{{ index + 1 }}.</span> <span class="text-blue-500 font-bold w-6">{{ index + 1 }}.</span>
<div class="flex-1 overflow-hidden"> <div class="flex-1 overflow-hidden">
<p class="text-sm text-zinc-200 font-medium truncate">{{ schedule.song?.title || schedule.title }}</p> <p class="text-sm text-zinc-700 font-medium truncate">{{ schedule.song?.title || schedule.title }}</p>
<p class="text-xs text-zinc-500 truncate">{{ schedule.song?.artist || schedule.artist }}</p> <p class="text-xs text-zinc-500 truncate">{{ schedule.song?.artist || schedule.artist }}</p>
</div> </div>
</div> </div>
@@ -65,7 +65,7 @@
</div> </div>
</div> </div>
</template> </template>
<div v-else-if="!loading && selectedDates.length > 0" class="flex flex-col items-center justify-center py-20 bg-zinc-900/20 border border-zinc-800/50 rounded-xl border-dashed"> <div v-else-if="!loading && selectedDates.length > 0" class="flex flex-col items-center justify-center py-20 bg-zinc-50/20 border border-zinc-200 rounded-xl border-dashed">
<CalendarDays class="w-12 h-12 text-zinc-700 mb-4" /> <CalendarDays class="w-12 h-12 text-zinc-700 mb-4" />
<p class="text-zinc-500">所选日期均没有排期歌曲</p> <p class="text-zinc-500">所选日期均没有排期歌曲</p>
</div> </div>
+54 -54
View File
@@ -3,14 +3,14 @@
<!-- 顶部标题和全局开关 --> <!-- 顶部标题和全局开关 -->
<div class="flex flex-col xl:flex-row xl:items-end justify-between gap-6"> <div class="flex flex-col xl:flex-row xl:items-end justify-between gap-6">
<div class="space-y-1"> <div class="space-y-1">
<h2 class="text-2xl font-black text-zinc-100 tracking-tight">投稿开放管理</h2> <h2 class="text-2xl font-black text-zinc-900 tracking-tight">投稿开放管理</h2>
<p class="text-xs text-zinc-500">管理全校师生点歌投稿的开放时间、限制数量及全局状态控制</p> <p class="text-xs text-zinc-500">管理全校师生点歌投稿的开放时间、限制数量及全局状态控制</p>
</div> </div>
<div <div
class="flex flex-wrap items-center gap-4 bg-zinc-900/40 border border-zinc-800 p-2 rounded-3xl" class="flex flex-wrap items-center gap-4 bg-zinc-50/40 border border-zinc-200 p-2 rounded-3xl"
> >
<div class="flex items-center gap-3 px-3 border-r border-zinc-800/50 mr-1"> <div class="flex items-center gap-3 px-3 border-r border-zinc-200 mr-1">
<span class="text-[10px] font-black text-zinc-500 uppercase tracking-widest" <span class="text-[10px] font-black text-zinc-500 uppercase tracking-widest"
>当前状态</span >当前状态</span
> >
@@ -31,7 +31,7 @@
<button <button
:class="[ :class="[
'relative w-10 h-5 rounded-full transition-colors', 'relative w-10 h-5 rounded-full transition-colors',
enableRequest ? 'bg-blue-600' : 'bg-zinc-800' enableRequest ? 'bg-blue-600' : 'bg-zinc-100'
]" ]"
@click="toggleGlobalRequest" @click="toggleGlobalRequest"
> >
@@ -44,14 +44,14 @@
</button> </button>
</div> </div>
<div class="flex items-center gap-3 px-2 border-l border-zinc-800/50"> <div class="flex items-center gap-3 px-2 border-l border-zinc-200">
<span class="text-[10px] font-black text-zinc-400 uppercase tracking-widest" <span class="text-[10px] font-black text-zinc-400 uppercase tracking-widest"
>启用时段限制</span >启用时段限制</span
> >
<button <button
:class="[ :class="[
'relative w-10 h-5 rounded-full transition-colors', 'relative w-10 h-5 rounded-full transition-colors',
enableRequestTimeLimitation ? 'bg-purple-600' : 'bg-zinc-800' enableRequestTimeLimitation ? 'bg-purple-600' : 'bg-zinc-100'
]" ]"
@click="toggleTimeLimitation" @click="toggleTimeLimitation"
> >
@@ -70,17 +70,17 @@
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<!-- 添加时段按钮卡片 --> <!-- 添加时段按钮卡片 -->
<button <button
class="group relative h-full min-h-[220px] bg-zinc-900/20 border-2 border-dashed border-zinc-800 rounded-3xl flex flex-col items-center justify-center gap-4 hover:bg-blue-600/5 hover:border-blue-500/40 transition-all active:scale-95" class="group relative h-full min-h-[220px] bg-zinc-50/20 border-2 border-dashed border-zinc-200 rounded-3xl flex flex-col items-center justify-center gap-4 hover:bg-blue-600/5 hover:border-blue-500/40 transition-all active:scale-95"
@click="showAddForm = true" @click="showAddForm = true"
> >
<div <div
class="w-14 h-14 rounded-full bg-zinc-900 border border-zinc-800 flex items-center justify-center text-zinc-500 group-hover:text-blue-500 group-hover:bg-blue-600/10 group-hover:border-blue-500/20 transition-all" class="w-14 h-14 rounded-full bg-white border border-zinc-200 flex items-center justify-center text-zinc-500 group-hover:text-blue-500 group-hover:bg-blue-50 group-hover:border-blue-200 transition-all"
> >
<Plus :size="28" /> <Plus :size="28" />
</div> </div>
<div class="text-center"> <div class="text-center">
<h4 <h4
class="text-sm font-black text-zinc-400 group-hover:text-blue-400 transition-colors uppercase tracking-widest" class="text-sm font-black text-zinc-400 group-hover:text-blue-700 transition-colors uppercase tracking-widest"
> >
添加投稿时段 添加投稿时段
</h4> </h4>
@@ -101,16 +101,16 @@
v-for="slot in requestTimes" v-for="slot in requestTimes"
:key="slot.id" :key="slot.id"
:class="[ :class="[
'group bg-zinc-900/40 border rounded-3xl p-8 space-y-6 transition-all hover:shadow-2xl hover:shadow-black/40', 'group bg-zinc-50/40 border rounded-3xl p-8 space-y-6 transition-all hover:shadow-2xl hover:shadow-black/40',
slot.enabled && !slot.past ? 'border-zinc-800' : 'border-zinc-800/40 opacity-60' slot.enabled && !slot.past ? 'border-zinc-200' : 'border-zinc-200 opacity-60'
]" ]"
> >
<div class="flex items-start justify-between"> <div class="flex items-start justify-between">
<div <div
:class="[ :class="[
'p-3.5 rounded-2xl bg-zinc-950 border border-zinc-800 text-zinc-400 transition-colors', 'p-3.5 rounded-2xl bg-white border border-zinc-200 text-zinc-400 transition-colors',
slot.enabled && !slot.past slot.enabled && !slot.past
? 'text-blue-500 border-blue-500/20 shadow-lg shadow-blue-900/10' ? 'text-blue-500 border-blue-200 shadow-lg shadow-blue-900/10'
: '' : ''
]" ]"
> >
@@ -124,7 +124,7 @@
? 'bg-red-500/10 text-red-500 border-red-500/20' ? 'bg-red-500/10 text-red-500 border-red-500/20'
: slot.enabled : slot.enabled
? 'bg-emerald-500/10 text-emerald-500 border-emerald-500/20' ? 'bg-emerald-500/10 text-emerald-500 border-emerald-500/20'
: 'bg-zinc-800/50 text-zinc-600 border-zinc-700/50' : 'bg-zinc-100/50 text-zinc-600 border-zinc-200'
]" ]"
> >
{{ slot.past ? '已过期' : slot.enabled ? '已启用' : '已禁用' }} {{ slot.past ? '已过期' : slot.enabled ? '已启用' : '已禁用' }}
@@ -135,7 +135,7 @@
<div class="space-y-4"> <div class="space-y-4">
<div> <div>
<h4 <h4
class="text-lg font-black text-zinc-100 flex items-baseline gap-2 group-hover:text-blue-400 transition-colors" class="text-lg font-black text-zinc-900 flex items-baseline gap-2 group-hover:text-blue-700 transition-colors"
> >
{{ slot.name }} {{ slot.name }}
<span class="text-xs font-bold text-zinc-600 tracking-tight" <span class="text-xs font-bold text-zinc-600 tracking-tight"
@@ -150,7 +150,7 @@
}}</span> }}</span>
</div> </div>
<div class="flex items-center gap-2 text-zinc-500 font-bold"> <div class="flex items-center gap-2 text-zinc-500 font-bold">
<div class="w-3 h-[2px] bg-zinc-800 ml-1.5" /> <div class="w-3 h-[2px] bg-zinc-100 ml-1.5" />
<span class="text-[10px] uppercase tracking-tighter truncate">{{ <span class="text-[10px] uppercase tracking-tighter truncate">{{
slot.endTime || '不限' slot.endTime || '不限'
}}</span> }}</span>
@@ -179,7 +179,7 @@
> >
</div> </div>
<div <div
class="h-1.5 w-full bg-zinc-950 rounded-full overflow-hidden border border-zinc-800/50" class="h-1.5 w-full bg-white rounded-full overflow-hidden border border-zinc-200"
> >
<div <div
class="h-full transition-all duration-500" class="h-full transition-all duration-500"
@@ -196,17 +196,17 @@
</div> </div>
</div> </div>
<div class="pt-6 border-t border-zinc-800/50 flex items-center justify-between"> <div class="pt-6 border-t border-zinc-200 flex items-center justify-between">
<div class="flex gap-2"> <div class="flex gap-2">
<button <button
v-show="!slot.past" v-show="!slot.past"
class="p-2.5 bg-zinc-950 border border-zinc-800 rounded-xl text-zinc-500 hover:text-blue-400 hover:border-blue-500/30 transition-all" class="p-2.5 bg-white border border-zinc-200 rounded-xl text-zinc-500 hover:text-blue-700 hover:border-blue-200 transition-all"
@click="editRequestTime(slot)" @click="editRequestTime(slot)"
> >
<Edit2 :size="14" /> <Edit2 :size="14" />
</button> </button>
<button <button
class="p-2.5 bg-zinc-950 border border-zinc-800 rounded-xl text-zinc-500 hover:text-red-400 hover:border-red-500/30 transition-all" class="p-2.5 bg-white border border-zinc-200 rounded-xl text-zinc-500 hover:text-red-400 hover:border-red-200 transition-all"
@click="confirmDelete(slot)" @click="confirmDelete(slot)"
> >
<Trash2 :size="14" /> <Trash2 :size="14" />
@@ -217,7 +217,7 @@
:class="[ :class="[
'flex items-center gap-2 px-5 py-2.5 rounded-xl text-[10px] font-black uppercase tracking-widest transition-all', 'flex items-center gap-2 px-5 py-2.5 rounded-xl text-[10px] font-black uppercase tracking-widest transition-all',
slot.enabled slot.enabled
? 'bg-zinc-800 text-zinc-400 hover:text-zinc-200' ? 'bg-zinc-100 text-zinc-400 hover:text-zinc-700'
: 'bg-emerald-600 text-white shadow-lg shadow-emerald-900/20' : 'bg-emerald-600 text-white shadow-lg shadow-emerald-900/20'
]" ]"
@click="toggleRequestTimeStatus(slot)" @click="toggleRequestTimeStatus(slot)"
@@ -232,12 +232,12 @@
<!-- 统计概览 --> <!-- 统计概览 -->
<div <div
class="bg-zinc-900/20 border border-zinc-800 rounded-3xl p-8 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8" class="bg-zinc-50/20 border border-zinc-200 rounded-3xl p-8 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8"
> >
<div v-for="(stat, i) in stats" :key="i" class="flex items-center gap-4 group"> <div v-for="(stat, i) in stats" :key="i" class="flex items-center gap-4 group">
<div <div
:class="[ :class="[
'w-12 h-12 rounded-2xl bg-zinc-950 border border-zinc-800 flex items-center justify-center text-zinc-500 transition-colors', 'w-12 h-12 rounded-2xl bg-white border border-zinc-200 flex items-center justify-center text-zinc-500 transition-colors',
`group-hover:text-${stat.color}-500` `group-hover:text-${stat.color}-500`
]" ]"
> >
@@ -247,7 +247,7 @@
<p class="text-[10px] font-black text-zinc-600 uppercase tracking-widest"> <p class="text-[10px] font-black text-zinc-600 uppercase tracking-widest">
{{ stat.label }} {{ stat.label }}
</p> </p>
<h5 class="text-xl font-black text-zinc-200">{{ stat.value }}</h5> <h5 class="text-xl font-black text-zinc-700">{{ stat.value }}</h5>
</div> </div>
</div> </div>
</div> </div>
@@ -263,17 +263,17 @@
> >
<div <div
v-if="showAddForm || editingRequestTime" v-if="showAddForm || editingRequestTime"
class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-zinc-950/80 backdrop-blur-sm" class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-white/80 backdrop-blur-sm"
@click.self="cancelForm" @click.self="cancelForm"
> >
<div <div
class="w-full max-w-2xl bg-zinc-900 border border-zinc-800 rounded-3xl shadow-2xl overflow-hidden" class="w-full max-w-2xl bg-white border border-zinc-200 rounded-3xl shadow-2xl overflow-hidden"
> >
<div class="px-8 py-6 border-b border-zinc-800/50 flex items-center justify-between"> <div class="px-8 py-6 border-b border-zinc-200 flex items-center justify-between">
<h3 class="text-xl font-black text-zinc-100"> <h3 class="text-xl font-black text-zinc-900">
{{ editingRequestTime ? '编辑投稿开放时段' : '添加投稿开放时段' }} {{ editingRequestTime ? '编辑投稿开放时段' : '添加投稿开放时段' }}
</h3> </h3>
<button class="text-zinc-500 hover:text-zinc-300 transition-colors" @click="cancelForm"> <button class="text-zinc-500 hover:text-zinc-600 transition-colors" @click="cancelForm">
<X :size="20" /> <X :size="20" />
</button> </button>
</div> </div>
@@ -287,7 +287,7 @@
v-model="formData.name" v-model="formData.name"
type="text" type="text"
placeholder="例如: 2025冬季常规点歌" placeholder="例如: 2025冬季常规点歌"
class="w-full bg-zinc-950 border border-zinc-800 rounded-2xl px-5 py-3.5 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30" class="w-full bg-white border border-zinc-200 rounded-2xl px-5 py-3.5 text-sm text-zinc-700 focus:outline-none focus:border-blue-200"
> >
</div> </div>
@@ -301,7 +301,7 @@
v-for="p in requestTimeQuickPresets" v-for="p in requestTimeQuickPresets"
:key="p.label" :key="p.label"
type="button" type="button"
class="px-3 py-2 bg-zinc-950 border border-zinc-800 rounded-xl text-[11px] font-bold text-zinc-400 hover:text-zinc-200 hover:border-zinc-700 hover:bg-zinc-900 transition-all whitespace-nowrap" class="px-3 py-2 bg-white border border-zinc-200 rounded-xl text-[11px] font-bold text-zinc-400 hover:text-zinc-700 hover:border-zinc-200 hover:bg-white transition-all whitespace-nowrap"
@click="applyRequestTimePreset(p)" @click="applyRequestTimePreset(p)"
> >
{{ p.label }} {{ p.label }}
@@ -318,12 +318,12 @@
<input <input
v-model="formData.startDate" v-model="formData.startDate"
type="date" type="date"
class="w-full bg-zinc-950 border border-zinc-800 rounded-2xl px-4 py-3.5 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30" class="w-full bg-white border border-zinc-200 rounded-2xl px-4 py-3.5 text-sm text-zinc-700 focus:outline-none focus:border-blue-200"
> >
<input <input
v-model="formData.startTimeOfDay" v-model="formData.startTimeOfDay"
type="time" type="time"
class="w-full bg-zinc-950 border border-zinc-800 rounded-2xl px-4 py-3.5 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30" class="w-full bg-white border border-zinc-200 rounded-2xl px-4 py-3.5 text-sm text-zinc-700 focus:outline-none focus:border-blue-200"
> >
</div> </div>
<p class="text-[9px] text-zinc-600 px-1"> <p class="text-[9px] text-zinc-600 px-1">
@@ -338,12 +338,12 @@
<input <input
v-model="formData.endDate" v-model="formData.endDate"
type="date" type="date"
class="w-full bg-zinc-950 border border-zinc-800 rounded-2xl px-4 py-3.5 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30" class="w-full bg-white border border-zinc-200 rounded-2xl px-4 py-3.5 text-sm text-zinc-700 focus:outline-none focus:border-blue-200"
> >
<input <input
v-model="formData.endTimeOfDay" v-model="formData.endTimeOfDay"
type="time" type="time"
class="w-full bg-zinc-950 border border-zinc-800 rounded-2xl px-4 py-3.5 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30" class="w-full bg-white border border-zinc-200 rounded-2xl px-4 py-3.5 text-sm text-zinc-700 focus:outline-none focus:border-blue-200"
> >
</div> </div>
<p class="text-[9px] text-zinc-600 px-1"> <p class="text-[9px] text-zinc-600 px-1">
@@ -361,7 +361,7 @@
<input <input
v-model="formData.expected" v-model="formData.expected"
type="number" type="number"
class="w-full bg-zinc-950 border border-zinc-800 rounded-2xl px-5 py-3.5 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30" class="w-full bg-white border border-zinc-200 rounded-2xl px-5 py-3.5 text-sm text-zinc-700 focus:outline-none focus:border-blue-200"
> >
<div <div
class="absolute right-4 top-1/2 -translate-y-1/2 flex items-center gap-1.5 text-zinc-600 font-bold text-[10px]" class="absolute right-4 top-1/2 -translate-y-1/2 flex items-center gap-1.5 text-zinc-600 font-bold text-[10px]"
@@ -377,11 +377,11 @@
<input <input
v-model="formData.enabled" v-model="formData.enabled"
type="checkbox" type="checkbox"
class="w-4.5 h-4.5 rounded-lg border-zinc-800 bg-zinc-950 accent-blue-600" class="w-4.5 h-4.5 rounded-lg border-zinc-200 bg-white accent-blue-600"
> >
<div> <div>
<span <span
class="text-xs font-bold text-zinc-300 group-hover:text-blue-400 transition-colors" class="text-xs font-bold text-zinc-600 group-hover:text-blue-700 transition-colors"
>启用此投稿时段</span >启用此投稿时段</span
> >
<p class="text-[9px] text-zinc-600 font-medium"> <p class="text-[9px] text-zinc-600 font-medium">
@@ -399,7 +399,7 @@
<textarea <textarea
v-model="formData.description" v-model="formData.description"
placeholder="请输入针对此开放周期的特殊规则或介绍信息..." placeholder="请输入针对此开放周期的特殊规则或介绍信息..."
class="w-full bg-zinc-950 border border-zinc-800 rounded-2xl px-5 py-4 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30 min-h-[100px] resize-none" class="w-full bg-white border border-zinc-200 rounded-2xl px-5 py-4 text-sm text-zinc-700 focus:outline-none focus:border-blue-200 min-h-[100px] resize-none"
/> />
</div> </div>
@@ -412,9 +412,9 @@
</div> </div>
</div> </div>
<div class="px-8 py-6 bg-zinc-900/50 border-t border-zinc-800/50 flex gap-3 justify-end"> <div class="px-8 py-6 bg-zinc-50/50 border-t border-zinc-200 flex gap-3 justify-end">
<button <button
class="px-6 py-2.5 text-xs font-bold text-zinc-500 hover:text-zinc-300" class="px-6 py-2.5 text-xs font-bold text-zinc-500 hover:text-zinc-600"
@click="cancelForm" @click="cancelForm"
> >
取消 取消
@@ -442,11 +442,11 @@
> >
<div <div
v-if="showDeleteConfirm" v-if="showDeleteConfirm"
class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-zinc-950/80 backdrop-blur-sm" class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-white/80 backdrop-blur-sm"
@click.self="showDeleteConfirm = false" @click.self="showDeleteConfirm = false"
> >
<div <div
class="w-full max-w-md bg-zinc-900 border border-zinc-800 rounded-3xl shadow-2xl overflow-hidden p-8" class="w-full max-w-md bg-white border border-zinc-200 rounded-3xl shadow-2xl overflow-hidden p-8"
> >
<div class="flex flex-col items-center space-y-6"> <div class="flex flex-col items-center space-y-6">
<div <div
@@ -455,17 +455,17 @@
<Trash2 :size="28" /> <Trash2 :size="28" />
</div> </div>
<div class="text-center space-y-2 px-4"> <div class="text-center space-y-2 px-4">
<h4 class="text-lg font-bold text-zinc-100"> <h4 class="text-lg font-bold text-zinc-900">
确定要彻底移除 "{{ RequestTimeToDelete?.name }}" 吗? 确定要彻底移除 "{{ RequestTimeToDelete?.name }}" 吗?
</h4> </h4>
<p class="text-xs text-zinc-500 leading-relaxed"> <p class="text-xs text-zinc-500 leading-relaxed">
删除时段后,原本属于此周期的投稿关联记录可能失效。建议先尝试 删除时段后,原本属于此周期的投稿关联记录可能失效。建议先尝试
<span class="text-zinc-300 font-bold">禁用</span> 该时段。 <span class="text-zinc-600 font-bold">禁用</span> 该时段。
</p> </p>
</div> </div>
<div class="flex gap-3 w-full pt-4"> <div class="flex gap-3 w-full pt-4">
<button <button
class="flex-1 px-4 py-3 bg-zinc-900 hover:bg-zinc-800 text-zinc-500 text-xs font-black rounded-2xl transition-all" class="flex-1 px-4 py-3 bg-white hover:bg-zinc-100 text-zinc-500 text-xs font-black rounded-2xl transition-all"
@click="showDeleteConfirm = false" @click="showDeleteConfirm = false"
> >
保留时段 保留时段
@@ -485,9 +485,9 @@
<!-- 说明部分 --> <!-- 说明部分 -->
<div class="flex flex-col md:flex-row gap-6"> <div class="flex flex-col md:flex-row gap-6">
<div class="flex-1 bg-zinc-900/30 border border-zinc-800 rounded-[2rem] p-6 space-y-4"> <div class="flex-1 bg-zinc-50/30 border border-zinc-200 rounded-[2rem] p-6 space-y-4">
<h4 <h4
class="text-xs font-black text-zinc-100 uppercase tracking-widest flex items-center gap-2" class="text-xs font-black text-zinc-900 uppercase tracking-widest flex items-center gap-2"
> >
<CheckCircle2 :size="14" class="text-emerald-500" /> 逻辑优先级说明 <CheckCircle2 :size="14" class="text-emerald-500" /> 逻辑优先级说明
</h4> </h4>
@@ -495,16 +495,16 @@
<li class="flex gap-2"> <li class="flex gap-2">
<span class="text-zinc-700 font-black">1.</span> <span class="text-zinc-700 font-black">1.</span>
<span <span
><span class="text-zinc-300">全局开关</span> ><span class="text-zinc-600">全局开关</span>
具有最高优先级,关闭后所有投稿均不可用。</span 具有最高优先级,关闭后所有投稿均不可用。</span
> >
</li> </li>
<li class="flex gap-2"> <li class="flex gap-2">
<span class="text-zinc-700 font-black">2.</span> <span class="text-zinc-700 font-black">2.</span>
<span <span
>开启 <span class="text-zinc-300">时段限制</span> 后,系统会检查当前时间是否处于任一 >开启 <span class="text-zinc-600">时段限制</span> 后,系统会检查当前时间是否处于任一
<span class="text-zinc-300">已启用</span> 且未达到 <span class="text-zinc-600">已启用</span> 且未达到
<span class="text-zinc-300">计划数</span> 的时段内。</span <span class="text-zinc-600">计划数</span> 的时段内。</span
> >
</li> </li>
<li class="flex gap-2"> <li class="flex gap-2">
@@ -514,9 +514,9 @@
</ul> </ul>
</div> </div>
<div class="flex-1 bg-zinc-900/30 border border-zinc-800 rounded-[2rem] p-6 space-y-4"> <div class="flex-1 bg-zinc-50/30 border border-zinc-200 rounded-[2rem] p-6 space-y-4">
<h4 <h4
class="text-xs font-black text-zinc-100 uppercase tracking-widest flex items-center gap-2" class="text-xs font-black text-zinc-900 uppercase tracking-widest flex items-center gap-2"
> >
<XCircle :size="14" class="text-rose-500" /> 注意事项 <XCircle :size="14" class="text-rose-500" /> 注意事项
</h4> </h4>
+11 -11
View File
@@ -2,7 +2,7 @@
<div class="roles-manager"> <div class="roles-manager">
<div class="admin-section-header"> <div class="admin-section-header">
<div> <div>
<h2 class="text-xl font-black text-zinc-100 flex items-center gap-2"> <h2 class="text-xl font-black text-zinc-900 flex items-center gap-2">
<Shield class="w-6 h-6 text-indigo-400" /> <Shield class="w-6 h-6 text-indigo-400" />
权限组管理 权限组管理
</h2> </h2>
@@ -28,7 +28,7 @@
<div v-else-if="error" class="py-12 text-center text-red-400"> <div v-else-if="error" class="py-12 text-center text-red-400">
{{ error }} {{ error }}
<button class="mt-4 px-4 py-2 bg-zinc-800 rounded-lg hover:bg-zinc-700" @click="fetchRoles"> <button class="mt-4 px-4 py-2 bg-zinc-100 rounded-lg hover:bg-zinc-200" @click="fetchRoles">
重试 重试
</button> </button>
</div> </div>
@@ -37,15 +37,15 @@
<div <div
v-for="(roleConfig, roleKey) in roles" v-for="(roleConfig, roleKey) in roles"
:key="roleKey" :key="roleKey"
class="role-card p-6 bg-zinc-900/50 border border-zinc-800 rounded-2xl" class="role-card p-6 bg-zinc-50/50 border border-zinc-200 rounded-2xl"
> >
<div class="flex items-center justify-between mb-4"> <div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-xl bg-zinc-800 flex items-center justify-center text-zinc-400"> <div class="w-10 h-10 rounded-xl bg-zinc-100 flex items-center justify-center text-zinc-400">
<Shield class="w-5 h-5" /> <Shield class="w-5 h-5" />
</div> </div>
<div> <div>
<h3 class="font-bold text-zinc-200">{{ roleConfig.displayName }}</h3> <h3 class="font-bold text-zinc-700">{{ roleConfig.displayName }}</h3>
<p class="text-xs text-zinc-500 font-mono mt-0.5">{{ roleKey }}</p> <p class="text-xs text-zinc-500 font-mono mt-0.5">{{ roleKey }}</p>
</div> </div>
</div> </div>
@@ -56,7 +56,7 @@
class="hidden" class="hidden"
:disabled="roleKey === 'SUPER_ADMIN'" :disabled="roleKey === 'SUPER_ADMIN'"
/> />
<div class="w-10 h-6 bg-zinc-800 rounded-full relative transition-colors" :class="{ 'bg-indigo-500': roleConfig.canAccessAdmin }"> <div class="w-10 h-6 bg-zinc-100 rounded-full relative transition-colors" :class="{ 'bg-indigo-500': roleConfig.canAccessAdmin }">
<div class="absolute w-4 h-4 bg-white rounded-full top-1 transition-transform" :class="roleConfig.canAccessAdmin ? 'left-5' : 'left-1'" /> <div class="absolute w-4 h-4 bg-white rounded-full top-1 transition-transform" :class="roleConfig.canAccessAdmin ? 'left-5' : 'left-1'" />
</div> </div>
<span class="text-xs font-bold text-zinc-400">允许进后台</span> <span class="text-xs font-bold text-zinc-400">允许进后台</span>
@@ -67,20 +67,20 @@
<div class="flex items-center justify-between mb-4"> <div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<h4 class="text-xs font-bold text-zinc-500 uppercase tracking-wider">可访问页面</h4> <h4 class="text-xs font-bold text-zinc-500 uppercase tracking-wider">可访问页面</h4>
<span class="text-[10px] font-black px-2 py-0.5 rounded-md bg-zinc-800/60 text-zinc-400 border border-zinc-800"> <span class="text-[10px] font-black px-2 py-0.5 rounded-md bg-zinc-100/60 text-zinc-400 border border-zinc-200">
已启用 {{ roleConfig.pages.length }} 项 已启用 {{ roleConfig.pages.length }} 项
</span> </span>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<button <button
class="px-3 py-1.5 text-[11px] font-black rounded-lg bg-zinc-800/60 border border-zinc-800 text-zinc-300 hover:bg-zinc-800 transition-colors" class="px-3 py-1.5 text-[11px] font-black rounded-lg bg-zinc-100/60 border border-zinc-200 text-zinc-600 hover:bg-zinc-100 transition-colors"
:disabled="roleKey === 'SUPER_ADMIN'" :disabled="roleKey === 'SUPER_ADMIN'"
@click="selectAll(roleKey)" @click="selectAll(roleKey)"
> >
全选 全选
</button> </button>
<button <button
class="px-3 py-1.5 text-[11px] font-black rounded-lg bg-zinc-800/60 border border-zinc-800 text-zinc-300 hover:bg-zinc-800 transition-colors" class="px-3 py-1.5 text-[11px] font-black rounded-lg bg-zinc-100/60 border border-zinc-200 text-zinc-600 hover:bg-zinc-100 transition-colors"
:disabled="roleKey === 'SUPER_ADMIN'" :disabled="roleKey === 'SUPER_ADMIN'"
@click="clearAll(roleKey)" @click="clearAll(roleKey)"
> >
@@ -109,7 +109,7 @@
v-for="page in section.pages" v-for="page in section.pages"
:key="page.id" :key="page.id"
class="flex items-center gap-2 p-2 rounded-xl border cursor-pointer transition-all" class="flex items-center gap-2 p-2 rounded-xl border cursor-pointer transition-all"
:class="roleConfig.pages.includes(page.id) ? 'bg-indigo-500/10 border-indigo-500/30 text-indigo-200 shadow-[0_0_0_1px_rgba(99,102,241,0.15)]' : 'bg-zinc-800/30 border-zinc-800 text-zinc-500 hover:bg-zinc-800/70'" :class="roleConfig.pages.includes(page.id) ? 'bg-indigo-500/10 border-indigo-500/30 text-indigo-200 shadow-[0_0_0_1px_rgba(99,102,241,0.15)]' : 'bg-zinc-100/30 border-zinc-200 text-zinc-500 hover:bg-zinc-100/70'"
> >
<input <input
v-model="roleConfig.pages" v-model="roleConfig.pages"
@@ -125,7 +125,7 @@
</div> </div>
</div> </div>
</div> </div>
<div v-else class="mt-6 py-8 text-center border border-dashed border-zinc-800 rounded-xl"> <div v-else class="mt-6 py-8 text-center border border-dashed border-zinc-200 rounded-xl">
<p class="text-sm text-zinc-600">该角色不允许进入后台</p> <p class="text-sm text-zinc-600">该角色不允许进入后台</p>
</div> </div>
</div> </div>
+5 -5
View File
@@ -1,18 +1,18 @@
<template> <template>
<div <div
class="backdrop-blur-md p-6 rounded-xl border border-white/10 bg-slate-800/70 shadow-2xl max-w-[400px] mx-auto text-zinc-100 transition-transform duration-200 hover:-translate-y-0.5 hover:shadow-[0_15px_30px_rgba(0,0,0,0.2)]" class="backdrop-blur-md p-6 rounded-xl border border-zinc-200 bg-slate-800/70 shadow-2xl max-w-[400px] mx-auto text-zinc-900 transition-transform duration-200 hover:-translate-y-0.5 hover:shadow-[0_15px_30px_rgba(0,0,0,0.2)]"
> >
<h3 class="mb-6 pb-2 border-b border-white/10 text-zinc-100 font-bold text-lg"> <h3 class="mb-6 pb-2 border-b border-zinc-200 text-zinc-900 font-bold text-lg">
为歌曲 "{{ song?.title }}" 创建排期 为歌曲 "{{ song?.title }}" 创建排期
</h3> </h3>
<form @submit.prevent="handleSubmit"> <form @submit.prevent="handleSubmit">
<div class="mb-4"> <div class="mb-4">
<label class="block mb-2 font-medium text-zinc-100" for="playDate">播放日期</label> <label class="block mb-2 font-medium text-zinc-900" for="playDate">播放日期</label>
<input <input
id="playDate" id="playDate"
v-model="playDate" v-model="playDate"
class="w-full p-3 border border-white/10 rounded-lg text-base bg-[#0f172a99] text-zinc-100 outline-none transition-colors duration-150 focus:border-indigo-500 focus:shadow-[0_0_0_2px_rgba(99,102,241,0.25)]" class="w-full p-3 border border-zinc-200 rounded-lg text-base bg-white/90 text-zinc-900 outline-none transition-colors duration-150 focus:border-indigo-500 focus:shadow-[0_0_0_2px_rgba(99,102,241,0.25)]"
required required
type="date" type="date"
> >
@@ -51,7 +51,7 @@
<div class="flex justify-between gap-4 mt-6"> <div class="flex justify-between gap-4 mt-6">
<button <button
class="flex-1 p-3 border border-white/10 rounded-lg text-base cursor-pointer transition-all duration-200 bg-white/10 text-zinc-100 hover:bg-white/15" class="flex-1 p-3 border border-zinc-200 rounded-lg text-base cursor-pointer transition-all duration-200 bg-white/10 text-zinc-900 hover:bg-white/15"
type="button" type="button"
@click="$emit('cancel')" @click="$emit('cancel')"
> >
+74 -74
View File
@@ -9,7 +9,7 @@
<!-- 播出时段标签组 + 自动排期策略 --> <!-- 播出时段标签组 + 自动排期策略 -->
<div <div
class="space-y-3 bg-zinc-900/30 border border-zinc-800 rounded-xl p-3 md:p-4" class="space-y-3 bg-zinc-50/30 border border-zinc-200 rounded-xl p-3 md:p-4"
> >
<!-- 播出时段:标签按钮组(直接点击切换) --> <!-- 播出时段:标签按钮组(直接点击切换) -->
<div v-if="playTimeEnabled && playTimes.length > 0" class="space-y-2"> <div v-if="playTimeEnabled && playTimes.length > 0" class="space-y-2">
@@ -25,7 +25,7 @@
'px-4 py-2 rounded-xl text-xs font-black border transition-all whitespace-nowrap', 'px-4 py-2 rounded-xl text-xs font-black border transition-all whitespace-nowrap',
selectedPlayTime === '' selectedPlayTime === ''
? 'bg-blue-600 border-blue-500 text-white shadow-lg shadow-blue-900/20' ? 'bg-blue-600 border-blue-500 text-white shadow-lg shadow-blue-900/20'
: 'bg-zinc-950 border-zinc-800 text-zinc-400 hover:text-zinc-200 hover:border-zinc-700' : 'bg-white border-zinc-200 text-zinc-400 hover:text-zinc-700 hover:border-zinc-200'
]" ]"
@click="handlePlayTimeSelect('')" @click="handlePlayTimeSelect('')"
> >
@@ -43,10 +43,10 @@
selectedPlayTime === String(pt.id) selectedPlayTime === String(pt.id)
? pt.enabled ? pt.enabled
? 'bg-blue-600 border-blue-500 text-white shadow-lg shadow-blue-900/20' ? 'bg-blue-600 border-blue-500 text-white shadow-lg shadow-blue-900/20'
: 'bg-zinc-700 border-zinc-600 text-zinc-300' : 'bg-zinc-200 border-zinc-300 text-zinc-600'
: pt.enabled : pt.enabled
? 'bg-zinc-950 border-zinc-800 text-zinc-400 hover:text-zinc-200 hover:border-zinc-700' ? 'bg-white border-zinc-200 text-zinc-400 hover:text-zinc-700 hover:border-zinc-200'
: 'bg-zinc-950/40 border-zinc-800/40 text-zinc-700 cursor-not-allowed' : 'bg-white/80 border-zinc-200 text-zinc-700 cursor-not-allowed'
]" ]"
@click="pt.enabled && handlePlayTimeSelect(String(pt.id))" @click="pt.enabled && handlePlayTimeSelect(String(pt.id))"
> >
@@ -61,7 +61,7 @@
</span> </span>
<span <span
v-if="!pt.enabled" v-if="!pt.enabled"
class="ml-1 px-1.5 py-0.5 text-[9px] bg-zinc-800 rounded text-zinc-500" class="ml-1 px-1.5 py-0.5 text-[9px] bg-zinc-100 rounded text-zinc-500"
>已禁用</span> >已禁用</span>
</span> </span>
</button> </button>
@@ -82,27 +82,27 @@
<!-- 快速统计信息 --> <!-- 快速统计信息 -->
<div <div
v-if="playTimeEnabled && playTimes.length > 0" v-if="playTimeEnabled && playTimes.length > 0"
class="hidden md:flex items-center gap-3 bg-zinc-950/60 border border-zinc-800 rounded-xl px-4 py-2" class="hidden md:flex items-center gap-3 bg-white/80 border border-zinc-200 rounded-xl px-4 py-2"
> >
<div class="flex-1 flex items-center gap-2"> <div class="flex-1 flex items-center gap-2">
<div class="w-8 h-8 rounded-lg bg-blue-600/20 text-blue-400 flex items-center justify-center"> <div class="w-8 h-8 rounded-lg bg-blue-600/20 text-blue-700 flex items-center justify-center">
<ListMusic :size="14" /> <ListMusic :size="14" />
</div> </div>
<div class="flex flex-col leading-tight"> <div class="flex flex-col leading-tight">
<span class="text-[10px] font-black text-zinc-600 uppercase tracking-widest">当前列表</span> <span class="text-[10px] font-black text-zinc-600 uppercase tracking-widest">当前列表</span>
<span class="text-sm font-black text-zinc-200"> <span class="text-sm font-black text-zinc-700">
{{ localScheduledSongs.length }} 首 / {{ playTimeScheduledCount }} 首 {{ localScheduledSongs.length }} 首 / {{ playTimeScheduledCount }} 首
</span> </span>
</div> </div>
</div> </div>
<div class="w-px h-8 bg-zinc-800" /> <div class="w-px h-8 bg-zinc-100" />
<div class="flex-1 flex items-center gap-2"> <div class="flex-1 flex items-center gap-2">
<div class="w-8 h-8 rounded-lg bg-purple-600/20 text-purple-400 flex items-center justify-center"> <div class="w-8 h-8 rounded-lg bg-purple-600/20 text-purple-400 flex items-center justify-center">
<Inbox :size="14" /> <Inbox :size="14" />
</div> </div>
<div class="flex flex-col leading-tight"> <div class="flex flex-col leading-tight">
<span class="text-[10px] font-black text-zinc-600 uppercase tracking-widest">待排歌曲</span> <span class="text-[10px] font-black text-zinc-600 uppercase tracking-widest">待排歌曲</span>
<span class="text-sm font-black text-zinc-200"> <span class="text-sm font-black text-zinc-700">
{{ filteredUnscheduledSongs.length }} 首 {{ filteredUnscheduledSongs.length }} 首
</span> </span>
</div> </div>
@@ -119,7 +119,7 @@
<div v-else> <div v-else>
<div <div
class="lg:hidden sticky -top-4 -mx-4 -mt-4 z-20 flex gap-2 p-2 bg-zinc-950/90 backdrop-blur-md border-b border-zinc-800 shadow-xl mb-4 pt-4 min-h-[64px]" class="lg:hidden sticky -top-4 -mx-4 -mt-4 z-20 flex gap-2 p-2 bg-white/90 backdrop-blur-md border-b border-zinc-200 shadow-xl mb-4 pt-4 min-h-[64px]"
> >
<button <button
:class="[ :class="[
@@ -131,7 +131,7 @@
<ListMusic class="w-4 h-4" /> <ListMusic class="w-4 h-4" />
<span class="flex items-center gap-1.5" <span class="flex items-center gap-1.5"
>待排歌曲 >待排歌曲
<span class="px-1.5 py-0.5 bg-zinc-800 text-[10px] rounded text-zinc-400">{{ <span class="px-1.5 py-0.5 bg-zinc-100 text-[10px] rounded text-zinc-400">{{
filteredUnscheduledSongs.length filteredUnscheduledSongs.length
}}</span></span }}</span></span
> >
@@ -146,7 +146,7 @@
<PlaySquare class="w-4 h-4" /> <PlaySquare class="w-4 h-4" />
<span class="flex items-center gap-1.5" <span class="flex items-center gap-1.5"
>播放列表 >播放列表
<span class="px-1.5 py-0.5 bg-zinc-800 text-[10px] rounded text-zinc-400">{{ <span class="px-1.5 py-0.5 bg-zinc-100 text-[10px] rounded text-zinc-400">{{
localScheduledSongs.length localScheduledSongs.length
}}</span></span }}</span></span
> >
@@ -166,11 +166,11 @@
@drop.stop.prevent="handleReturnToDraggable" @drop.stop.prevent="handleReturnToDraggable"
> >
<div class="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-2 md:gap-3 px-1 min-h-[52px]"> <div class="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-2 md:gap-3 px-1 min-h-[52px]">
<h3 class="hidden lg:block text-lg font-black tracking-tight text-zinc-100 uppercase"> <h3 class="hidden lg:block text-lg font-black tracking-tight text-zinc-900 uppercase">
待排歌曲 待排歌曲
</h3> </h3>
<div <div
class="flex w-full lg:w-auto gap-2 p-1.5 bg-zinc-900/50 rounded-xl border border-zinc-800" class="flex w-full lg:w-auto gap-2 p-1.5 bg-zinc-50/50 rounded-xl border border-zinc-200"
> >
<button <button
v-for="tab in [ v-for="tab in [
@@ -182,7 +182,7 @@
:class="[ :class="[
'flex-1 lg:flex-none px-3 md:px-4 py-2 rounded-lg text-[10px] md:text-xs font-black uppercase tracking-widest transition-all', 'flex-1 lg:flex-none px-3 md:px-4 py-2 rounded-lg text-[10px] md:text-xs font-black uppercase tracking-widest transition-all',
activeTab === tab.id activeTab === tab.id
? 'bg-zinc-800 text-blue-400 shadow-md border border-blue-500/20' ? 'bg-zinc-100 text-blue-700 shadow-md border border-blue-200'
: 'text-zinc-600 hover:text-zinc-400' : 'text-zinc-600 hover:text-zinc-400'
]" ]"
@click="activeTab = tab.id" @click="activeTab = tab.id"
@@ -193,9 +193,9 @@
</div> </div>
<!-- 筛选区 - 移动端折叠 --> <!-- 筛选区 - 移动端折叠 -->
<div class="bg-zinc-900/40 border border-zinc-800 rounded-2xl shadow-xl"> <div class="bg-zinc-50/40 border border-zinc-200 rounded-2xl shadow-xl">
<div <div
class="p-3 md:p-4 flex items-center justify-between lg:hidden border-b border-zinc-800/50 rounded-t-2xl" class="p-3 md:p-4 flex items-center justify-between lg:hidden border-b border-zinc-200 rounded-t-2xl"
@click="mobileFiltersOpen = !mobileFiltersOpen" @click="mobileFiltersOpen = !mobileFiltersOpen"
> >
<div class="flex items-center gap-2 text-zinc-400"> <div class="flex items-center gap-2 text-zinc-400">
@@ -222,7 +222,7 @@
v-model="searchQuery" v-model="searchQuery"
type="text" type="text"
placeholder="搜索歌曲、艺术家..." placeholder="搜索歌曲、艺术家..."
class="w-full pl-9 pr-4 py-2 bg-zinc-950 border border-zinc-800 rounded-xl text-xs focus:outline-none focus:border-blue-500/30 transition-all text-zinc-200" class="w-full pl-9 pr-4 py-2 bg-white border border-zinc-200 rounded-xl text-xs focus:outline-none focus:border-blue-200 transition-all text-zinc-700"
> >
<button <button
v-if="searchQuery" v-if="searchQuery"
@@ -254,14 +254,14 @@
'draggable-songs flex-1 border-2 border-dashed rounded-[2rem] p-2 md:p-3 min-h-[400px] transition-colors duration-200', 'draggable-songs flex-1 border-2 border-dashed rounded-[2rem] p-2 md:p-3 min-h-[400px] transition-colors duration-200',
isDraggableOver isDraggableOver
? 'border-blue-500 bg-blue-500/5' ? 'border-blue-500 bg-blue-500/5'
: 'border-zinc-800/80 bg-zinc-900/20' : 'border-zinc-200/80 bg-zinc-50/20'
]" ]"
> >
<div class="space-y-2"> <div class="space-y-2">
<div <div
v-for="song in filteredUnscheduledSongs" v-for="song in filteredUnscheduledSongs"
:key="song.id" :key="song.id"
class="draggable-song relative group bg-zinc-900 border border-zinc-800/50 rounded-xl p-3 hover:border-zinc-700 transition-all select-none" class="draggable-song relative group bg-white border border-zinc-200 rounded-xl p-3 hover:border-zinc-200 transition-all select-none"
draggable="true" draggable="true"
@dragend="dragEnd" @dragend="dragEnd"
@dragstart="dragStart($event, song)" @dragstart="dragStart($event, song)"
@@ -273,7 +273,7 @@
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<!-- 封面图片 --> <!-- 封面图片 -->
<div <div
class="relative w-12 h-12 rounded-lg overflow-hidden bg-zinc-800 flex-shrink-0 border border-zinc-700/50 cursor-pointer hover:opacity-80 transition-opacity" class="relative w-12 h-12 rounded-lg overflow-hidden bg-zinc-100 flex-shrink-0 border border-zinc-200 cursor-pointer hover:opacity-80 transition-opacity"
@click.stop="playSong(song)" @click.stop="playSong(song)"
> >
<img <img
@@ -294,10 +294,10 @@
<div class="flex-1 min-w-0 flex flex-col gap-0.5"> <div class="flex-1 min-w-0 flex flex-col gap-0.5">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<h4 class="font-bold text-zinc-100 text-sm truncate"> <h4 class="font-bold text-zinc-900 text-sm truncate">
<span <span
v-if="false" v-if="false"
class="text-zinc-100 flex items-center gap-1 w-full text-left" class="text-zinc-900 flex items-center gap-1 w-full text-left"
> >
<span class="truncate">{{ song.title }}</span> <span class="truncate">{{ song.title }}</span>
</span> </span>
@@ -305,7 +305,7 @@
</h4> </h4>
<button <button
v-if="song.hasSubmissionNote && song.submissionNote" v-if="song.hasSubmissionNote && song.submissionNote"
class="inline-flex items-center justify-center w-5 h-5 rounded-full border border-blue-500/30 bg-blue-500/10 text-blue-400 hover:bg-blue-500/20 transition-all flex-shrink-0" class="inline-flex items-center justify-center w-5 h-5 rounded-full border border-blue-200 bg-blue-500/10 text-blue-700 hover:bg-blue-500/20 transition-all flex-shrink-0"
title="查看备注留言" title="查看备注留言"
@click.stop="openSubmissionRemark(song)" @click.stop="openSubmissionRemark(song)"
> >
@@ -342,7 +342,7 @@
<!-- 普通模式:投票数 --> <!-- 普通模式:投票数 -->
<div <div
v-if="activeTab !== 'replay'" v-if="activeTab !== 'replay'"
class="flex items-center gap-1 text-[10px] font-bold text-zinc-500 bg-zinc-950/50 px-2 py-1 rounded-md border border-zinc-800/50" class="flex items-center gap-1 text-[10px] font-bold text-zinc-500 bg-white/70 px-2 py-1 rounded-md border border-zinc-200"
> >
<Heart class="w-3 h-3 text-red-500/50" /> <Heart class="w-3 h-3 text-red-500/50" />
{{ song.voteCount || 0 }} {{ song.voteCount || 0 }}
@@ -351,7 +351,7 @@
<!-- 重播模式:查看按钮 --> <!-- 重播模式:查看按钮 -->
<button <button
v-if="activeTab === 'replay'" v-if="activeTab === 'replay'"
class="px-3 py-1.5 rounded-lg bg-blue-500/10 hover:bg-blue-500/20 text-blue-400 border border-blue-500/20 text-[10px] font-bold transition-colors" class="px-3 py-1.5 rounded-lg bg-blue-500/10 hover:bg-blue-500/20 text-blue-700 border border-blue-200 text-[10px] font-bold transition-colors"
@click.stop="openReplayModal(song)" @click.stop="openReplayModal(song)"
> >
查看 查看
@@ -369,7 +369,7 @@
<!-- 移动端添加按钮 --> <!-- 移动端添加按钮 -->
<button <button
class="lg:hidden p-2 rounded-full bg-blue-600/20 text-blue-400 hover:bg-blue-600/30 active:scale-95 transition-all flex-shrink-0" class="lg:hidden p-2 rounded-full bg-blue-600/20 text-blue-700 hover:bg-blue-600/30 active:scale-95 transition-all flex-shrink-0"
@click.stop="addSongToSchedule(song)" @click.stop="addSongToSchedule(song)"
> >
<Plus class="w-5 h-5" /> <Plus class="w-5 h-5" />
@@ -377,7 +377,7 @@
<!-- 菜单按钮 --> <!-- 菜单按钮 -->
<div <div
class="p-1.5 rounded-lg bg-zinc-950 border border-zinc-800 text-zinc-600 cursor-grab active:cursor-grabbing hover:text-zinc-400 transition-colors" class="p-1.5 rounded-lg bg-white border border-zinc-200 text-zinc-600 cursor-grab active:cursor-grabbing hover:text-zinc-400 transition-colors"
> >
<MoreVertical class="w-4 h-4" /> <MoreVertical class="w-4 h-4" />
</div> </div>
@@ -422,81 +422,81 @@
<div <div
class="hidden lg:flex flex-col xl:flex-row xl:items-center justify-between gap-3 md:gap-4 px-1 min-h-[56px]" class="hidden lg:flex flex-col xl:flex-row xl:items-center justify-between gap-3 md:gap-4 px-1 min-h-[56px]"
> >
<h3 class="text-lg font-black tracking-tight text-zinc-100 uppercase">播放顺序</h3> <h3 class="text-lg font-black tracking-tight text-zinc-900 uppercase">播放顺序</h3>
<div <div
class="flex flex-wrap items-center gap-1.5 md:gap-2 p-1.5 bg-zinc-900/50 border border-zinc-800/50 rounded-2xl" class="flex flex-wrap items-center gap-1.5 md:gap-2 p-1.5 bg-zinc-50/50 border border-zinc-200 rounded-2xl"
> >
<div class="flex gap-1 md:gap-1.5"> <div class="flex gap-1 md:gap-1.5">
<button <button
:disabled=" :disabled="
!hasChanges && localScheduledSongs.length === 0 && !hasUnpublishedDrafts !hasChanges && localScheduledSongs.length === 0 && !hasUnpublishedDrafts
" "
class="p-2 bg-zinc-950 border border-zinc-800 hover:bg-zinc-800 text-zinc-500 hover:text-zinc-200 rounded-xl transition-all group relative disabled:opacity-50 disabled:cursor-not-allowed" class="p-2 bg-white border border-zinc-200 hover:bg-zinc-100 text-zinc-500 hover:text-zinc-700 rounded-xl transition-all group relative disabled:opacity-50 disabled:cursor-not-allowed"
@click="saveDraft" @click="saveDraft"
> >
<Save class="w-3.5 h-3.5" /> <Save class="w-3.5 h-3.5" />
<span <span
class="absolute -top-10 left-1/2 -translate-x-1/2 px-2 py-1 bg-zinc-800 text-[9px] text-zinc-300 rounded opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none whitespace-nowrap border border-zinc-700" class="absolute -top-10 left-1/2 -translate-x-1/2 px-2 py-1 bg-zinc-100 text-[9px] text-zinc-600 rounded opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none whitespace-nowrap border border-zinc-200"
>保存草稿</span >保存草稿</span
> >
</button> </button>
<button <button
:disabled="localScheduledSongs.length === 0" :disabled="localScheduledSongs.length === 0"
class="p-2 bg-zinc-950 border border-zinc-800 hover:bg-zinc-800 text-zinc-500 hover:text-zinc-200 rounded-xl transition-all group relative disabled:opacity-50 disabled:cursor-not-allowed" class="p-2 bg-white border border-zinc-200 hover:bg-zinc-100 text-zinc-500 hover:text-zinc-700 rounded-xl transition-all group relative disabled:opacity-50 disabled:cursor-not-allowed"
@click="openDownloadDialog" @click="openDownloadDialog"
> >
<Download class="w-3.5 h-3.5" /> <Download class="w-3.5 h-3.5" />
<span <span
class="absolute -top-10 left-1/2 -translate-x-1/2 px-2 py-1 bg-zinc-800 text-[9px] text-zinc-300 rounded opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none whitespace-nowrap border border-zinc-700" class="absolute -top-10 left-1/2 -translate-x-1/2 px-2 py-1 bg-zinc-100 text-[9px] text-zinc-600 rounded opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none whitespace-nowrap border border-zinc-200"
>下载歌曲</span >下载歌曲</span
> >
</button> </button>
<button <button
:disabled="localScheduledSongs.length === 0" :disabled="localScheduledSongs.length === 0"
class="p-2 bg-zinc-950 border border-zinc-800 hover:bg-zinc-800 text-zinc-500 hover:text-blue-400 rounded-xl transition-all group relative disabled:opacity-50 disabled:cursor-not-allowed" class="p-2 bg-white border border-zinc-200 hover:bg-zinc-100 text-zinc-500 hover:text-blue-700 rounded-xl transition-all group relative disabled:opacity-50 disabled:cursor-not-allowed"
@click="showCardGenerator = true" @click="showCardGenerator = true"
> >
<ImageIcon class="w-3.5 h-3.5" /> <ImageIcon class="w-3.5 h-3.5" />
<span <span
class="absolute -top-10 left-1/2 -translate-x-1/2 px-2 py-1 bg-zinc-800 text-[9px] text-zinc-300 rounded opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none whitespace-nowrap border border-zinc-700" class="absolute -top-10 left-1/2 -translate-x-1/2 px-2 py-1 bg-zinc-100 text-[9px] text-zinc-600 rounded opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none whitespace-nowrap border border-zinc-200"
>生成图片</span >生成图片</span
> >
</button> </button>
<button <button
:disabled="localScheduledSongs.length === 0" :disabled="localScheduledSongs.length === 0"
class="p-2 bg-zinc-950 border border-zinc-800 hover:bg-zinc-800 text-zinc-500 hover:text-emerald-500 rounded-xl transition-all group relative disabled:opacity-50 disabled:cursor-not-allowed" class="p-2 bg-white border border-zinc-200 hover:bg-zinc-100 text-zinc-500 hover:text-emerald-500 rounded-xl transition-all group relative disabled:opacity-50 disabled:cursor-not-allowed"
@click="markAllAsPlayed" @click="markAllAsPlayed"
> >
<CheckCircle2 class="w-3.5 h-3.5" /> <CheckCircle2 class="w-3.5 h-3.5" />
<span <span
class="absolute -top-10 left-1/2 -translate-x-1/2 px-2 py-1 bg-zinc-800 text-[9px] text-zinc-300 rounded opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none whitespace-nowrap border border-zinc-700" class="absolute -top-10 left-1/2 -translate-x-1/2 px-2 py-1 bg-zinc-100 text-[9px] text-zinc-600 rounded opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none whitespace-nowrap border border-zinc-200"
>全部已播放</span >全部已播放</span
> >
</button> </button>
<button <button
:disabled="localScheduledSongs.length === 0" :disabled="localScheduledSongs.length === 0"
class="p-2 bg-zinc-950 border border-zinc-800 hover:bg-zinc-800 text-zinc-500 hover:text-cyan-400 rounded-xl transition-all group relative disabled:opacity-50 disabled:cursor-not-allowed" class="p-2 bg-white border border-zinc-200 hover:bg-zinc-100 text-zinc-500 hover:text-cyan-400 rounded-xl transition-all group relative disabled:opacity-50 disabled:cursor-not-allowed"
title="构建播放队列" title="构建播放队列"
@click="confirmBuildPlayQueue" @click="confirmBuildPlayQueue"
> >
<Music2 class="w-3.5 h-3.5" /> <Music2 class="w-3.5 h-3.5" />
<span <span
class="absolute -top-10 left-1/2 -translate-x-1/2 px-2 py-1 bg-zinc-800 text-[9px] text-zinc-300 rounded opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none whitespace-nowrap border border-zinc-700" class="absolute -top-10 left-1/2 -translate-x-1/2 px-2 py-1 bg-zinc-100 text-[9px] text-zinc-600 rounded opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none whitespace-nowrap border border-zinc-200"
>构建播放队列</span >构建播放队列</span
> >
</button> </button>
<button <button
class="p-2 bg-zinc-950 border border-zinc-800 hover:bg-zinc-800 text-zinc-500 hover:text-purple-400 rounded-xl transition-all group relative" class="p-2 bg-white border border-zinc-200 hover:bg-zinc-100 text-zinc-500 hover:text-purple-400 rounded-xl transition-all group relative"
@click="openMoveDateDialog" @click="openMoveDateDialog"
> >
<ArrowRight class="w-3.5 h-3.5" /> <ArrowRight class="w-3.5 h-3.5" />
<span <span
class="absolute -top-10 left-1/2 -translate-x-1/2 px-2 py-1 bg-zinc-800 text-[9px] text-zinc-300 rounded opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none whitespace-nowrap border border-zinc-700" class="absolute -top-10 left-1/2 -translate-x-1/2 px-2 py-1 bg-zinc-100 text-[9px] text-zinc-600 rounded opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none whitespace-nowrap border border-zinc-200"
>迁移日期</span >迁移日期</span
> >
</button> </button>
</div> </div>
<div class="h-6 w-[1px] bg-zinc-800 mx-1" /> <div class="h-6 w-[1px] bg-zinc-100 mx-1" />
<button <button
class="flex items-center gap-1.5 px-3 md:px-4 py-2 bg-yellow-500/10 hover:bg-yellow-500/20 text-yellow-500 text-[10px] md:text-xs font-black rounded-xl border border-yellow-500/20 transition-all uppercase tracking-widest active:scale-95 disabled:opacity-50" class="flex items-center gap-1.5 px-3 md:px-4 py-2 bg-yellow-500/10 hover:bg-yellow-500/20 text-yellow-500 text-[10px] md:text-xs font-black rounded-xl border border-yellow-500/20 transition-all uppercase tracking-widest active:scale-95 disabled:opacity-50"
title="一键将当前待排歌曲自动排入" title="一键将当前待排歌曲自动排入"
@@ -525,7 +525,7 @@
ref="sequenceList" ref="sequenceList"
:class="[ :class="[
'sequence-list flex-1 border-2 border-dashed rounded-[2rem] p-2 md:p-3 min-h-[400px] transition-colors duration-200', 'sequence-list flex-1 border-2 border-dashed rounded-[2rem] p-2 md:p-3 min-h-[400px] transition-colors duration-200',
isSequenceOver ? 'border-blue-500 bg-blue-500/5' : 'border-zinc-800/80 bg-zinc-900/20' isSequenceOver ? 'border-blue-500 bg-blue-500/5' : 'border-zinc-200/80 bg-zinc-50/20'
]" ]"
@dragleave="handleSequenceDragLeave" @dragleave="handleSequenceDragLeave"
@dragover.prevent="handleDragOver" @dragover.prevent="handleDragOver"
@@ -545,9 +545,9 @@
v-for="(schedule, index) in localScheduledSongs" v-for="(schedule, index) in localScheduledSongs"
:key="schedule.id" :key="schedule.id"
:class="[ :class="[
'scheduled-song relative group bg-zinc-900 border border-zinc-800/50 rounded-xl p-3 hover:border-zinc-700 transition-all select-none', 'scheduled-song relative group bg-white border border-zinc-200 rounded-xl p-3 hover:border-zinc-200 transition-all select-none',
dragOverIndex === index ? 'border-t-2 border-t-blue-500' : '', dragOverIndex === index ? 'border-t-2 border-t-blue-500' : '',
schedule.isDraft ? 'border-amber-500/30 bg-amber-500/5' : '' schedule.isDraft ? 'border-amber-200 bg-amber-500/5' : ''
]" ]"
:data-schedule-id="schedule.id" :data-schedule-id="schedule.id"
draggable="true" draggable="true"
@@ -563,17 +563,17 @@
> >
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<div <div
class="flex flex-col items-center justify-center w-10 h-10 rounded-lg bg-zinc-950/50 border border-zinc-800 text-zinc-500 font-black text-xs flex-shrink-0" class="flex flex-col items-center justify-center w-10 h-10 rounded-lg bg-white/70 border border-zinc-200 text-zinc-500 font-black text-xs flex-shrink-0"
> >
<span class="text-[8px] text-zinc-600 uppercase leading-none mb-0.5">POS</span> <span class="text-[8px] text-zinc-600 uppercase leading-none mb-0.5">POS</span>
<span class="text-sm text-zinc-300 leading-none">{{ <span class="text-sm text-zinc-600 leading-none">{{
index + 1 < 10 ? '0' + (index + 1) : index + 1 index + 1 < 10 ? '0' + (index + 1) : index + 1
}}</span> }}</span>
</div> </div>
<!-- 封面图片 --> <!-- 封面图片 -->
<div <div
class="relative w-10 h-10 rounded-lg overflow-hidden bg-zinc-800 flex-shrink-0 border border-zinc-700/50 cursor-pointer hover:opacity-80 transition-opacity" class="relative w-10 h-10 rounded-lg overflow-hidden bg-zinc-100 flex-shrink-0 border border-zinc-200 cursor-pointer hover:opacity-80 transition-opacity"
@click.stop="playSong(schedule.song)" @click.stop="playSong(schedule.song)"
> >
<img <img
@@ -594,12 +594,12 @@
<div class="flex-1 min-w-0 flex flex-col gap-0.5"> <div class="flex-1 min-w-0 flex flex-col gap-0.5">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<h4 class="font-bold text-zinc-200 text-sm truncate"> <h4 class="font-bold text-zinc-700 text-sm truncate">
{{ schedule.song.title }} {{ schedule.song.title }}
</h4> </h4>
<button <button
v-if="schedule.song.hasSubmissionNote && schedule.song.submissionNote" v-if="schedule.song.hasSubmissionNote && schedule.song.submissionNote"
class="inline-flex items-center justify-center w-5 h-5 rounded-full border border-blue-500/30 bg-blue-500/10 text-blue-400 hover:bg-blue-500/20 transition-all flex-shrink-0" class="inline-flex items-center justify-center w-5 h-5 rounded-full border border-blue-200 bg-blue-500/10 text-blue-700 hover:bg-blue-500/20 transition-all flex-shrink-0"
title="查看备注留言" title="查看备注留言"
@click.stop="openSubmissionRemark(schedule.song)" @click.stop="openSubmissionRemark(schedule.song)"
> >
@@ -608,7 +608,7 @@
<!-- 重播标识 --> <!-- 重播标识 -->
<span <span
v-if="schedule.song.replayRequestCount > 0" v-if="schedule.song.replayRequestCount > 0"
class="px-1.5 py-0.5 rounded text-[9px] font-bold bg-blue-500/10 text-blue-500 border border-blue-500/20 uppercase tracking-wider flex items-center gap-1" class="px-1.5 py-0.5 rounded text-[9px] font-bold bg-blue-500/10 text-blue-500 border border-blue-200 uppercase tracking-wider flex items-center gap-1"
title="重播歌曲" title="重播歌曲"
> >
<Icon name="repeat" :size="10" /> <Icon name="repeat" :size="10" />
@@ -682,7 +682,7 @@
</button> </button>
<div <div
class="p-1.5 rounded-lg bg-zinc-950 border border-zinc-800 text-zinc-600 cursor-grab active:cursor-grabbing hover:text-zinc-400 transition-colors" class="p-1.5 rounded-lg bg-white border border-zinc-200 text-zinc-600 cursor-grab active:cursor-grabbing hover:text-zinc-400 transition-colors"
> >
<MoreVertical class="w-4 h-4" /> <MoreVertical class="w-4 h-4" />
</div> </div>
@@ -696,42 +696,42 @@
<!-- 移动端底部操作栏 --> <!-- 移动端底部操作栏 -->
<div <div
class="lg:hidden fixed bottom-0 left-0 right-0 z-40 p-3 bg-zinc-950/90 backdrop-blur-xl border-t border-zinc-800 flex items-center gap-3 pb-6" class="lg:hidden fixed bottom-0 left-0 right-0 z-40 p-3 bg-white/90 backdrop-blur-xl border-t border-zinc-200 flex items-center gap-3 pb-6"
> >
<div class="w-[240px] overflow-x-auto scrollbar-hide"> <div class="w-[240px] overflow-x-auto scrollbar-hide">
<div class="flex items-center gap-2 w-max"> <div class="flex items-center gap-2 w-max">
<button <button
class="w-11 h-11 shrink-0 bg-zinc-900 border border-zinc-800 text-zinc-400 rounded-xl flex items-center justify-center active:scale-95 transition-all" class="w-11 h-11 shrink-0 bg-white border border-zinc-200 text-zinc-400 rounded-xl flex items-center justify-center active:scale-95 transition-all"
@click="openDownloadDialog" @click="openDownloadDialog"
> >
<Download class="w-5 h-5" /> <Download class="w-5 h-5" />
</button> </button>
<button <button
class="w-11 h-11 shrink-0 bg-zinc-900 border border-zinc-800 text-zinc-400 rounded-xl flex items-center justify-center active:scale-95 transition-all" class="w-11 h-11 shrink-0 bg-white border border-zinc-200 text-zinc-400 rounded-xl flex items-center justify-center active:scale-95 transition-all"
@click="showCardGenerator = true" @click="showCardGenerator = true"
> >
<ImageIcon class="w-5 h-5" /> <ImageIcon class="w-5 h-5" />
</button> </button>
<button <button
class="w-11 h-11 shrink-0 bg-zinc-900 border border-zinc-800 text-zinc-400 rounded-xl flex items-center justify-center active:scale-95 transition-all" class="w-11 h-11 shrink-0 bg-white border border-zinc-200 text-zinc-400 rounded-xl flex items-center justify-center active:scale-95 transition-all"
@click="saveDraft" @click="saveDraft"
> >
<Save class="w-5 h-5" /> <Save class="w-5 h-5" />
</button> </button>
<button <button
class="w-11 h-11 shrink-0 bg-zinc-900 border border-zinc-800 text-emerald-500 rounded-xl flex items-center justify-center active:scale-95 transition-all" class="w-11 h-11 shrink-0 bg-white border border-zinc-200 text-emerald-500 rounded-xl flex items-center justify-center active:scale-95 transition-all"
@click="markAllAsPlayed" @click="markAllAsPlayed"
> >
<CheckCircle2 class="w-5 h-5" /> <CheckCircle2 class="w-5 h-5" />
</button> </button>
<button <button
class="w-11 h-11 shrink-0 bg-zinc-900 border border-zinc-800 text-purple-400 rounded-xl flex items-center justify-center active:scale-95 transition-all" class="w-11 h-11 shrink-0 bg-white border border-zinc-200 text-purple-400 rounded-xl flex items-center justify-center active:scale-95 transition-all"
@click="openMoveDateDialog" @click="openMoveDateDialog"
> >
<ArrowRight class="w-5 h-5" /> <ArrowRight class="w-5 h-5" />
</button> </button>
<button <button
class="w-11 h-11 shrink-0 bg-zinc-900 border border-zinc-800 text-blue-500 rounded-xl flex items-center justify-center active:scale-95 transition-all" class="w-11 h-11 shrink-0 bg-white border border-zinc-200 text-blue-500 rounded-xl flex items-center justify-center active:scale-95 transition-all"
title="仅发布排期" title="仅发布排期"
@click="publishSchedule" @click="publishSchedule"
> >
@@ -785,13 +785,13 @@
class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm" class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm"
> >
<div <div
class="bg-zinc-900 border border-zinc-800 rounded-xl shadow-2xl w-full max-w-sm overflow-hidden" class="bg-white border border-zinc-200 rounded-xl shadow-2xl w-full max-w-sm overflow-hidden"
@click.stop @click.stop
> >
<div class="flex items-center justify-between p-4 border-b border-zinc-800"> <div class="flex items-center justify-between p-4 border-b border-zinc-200">
<h3 class="text-sm font-black text-zinc-100 uppercase tracking-widest">迁移排期日期</h3> <h3 class="text-sm font-black text-zinc-900 uppercase tracking-widest">迁移排期日期</h3>
<button <button
class="text-zinc-500 hover:text-zinc-300 transition-colors" class="text-zinc-500 hover:text-zinc-600 transition-colors"
@click="showMoveDateDialog = false" @click="showMoveDateDialog = false"
> >
<CloseIcon class="w-5 h-5" /> <CloseIcon class="w-5 h-5" />
@@ -801,12 +801,12 @@
<div class="text-xs text-zinc-500">当前日期:{{ selectedDate }}</div> <div class="text-xs text-zinc-500">当前日期:{{ selectedDate }}</div>
<input <input
v-model="moveTargetDate" v-model="moveTargetDate"
class="w-full bg-zinc-950 border border-zinc-800 rounded-xl px-4 py-3 text-zinc-200 focus:outline-none focus:border-purple-500 transition-colors" class="w-full bg-white border border-zinc-200 rounded-xl px-4 py-3 text-zinc-700 focus:outline-none focus:border-purple-500 transition-colors"
type="date" type="date"
> >
<div class="flex gap-3"> <div class="flex gap-3">
<button <button
class="flex-1 py-3 bg-zinc-800 hover:bg-zinc-700 text-zinc-300 text-xs font-bold rounded-xl transition-colors uppercase tracking-wider" class="flex-1 py-3 bg-zinc-100 hover:bg-zinc-200 text-zinc-600 text-xs font-bold rounded-xl transition-colors uppercase tracking-wider"
@click="showMoveDateDialog = false" @click="showMoveDateDialog = false"
> >
取消 取消
@@ -829,11 +829,11 @@
@click="closeReplayModal" @click="closeReplayModal"
> >
<div <div
class="bg-zinc-900 border border-zinc-800 rounded-2xl shadow-2xl w-full max-w-md overflow-hidden" class="bg-white border border-zinc-200 rounded-2xl shadow-2xl w-full max-w-md overflow-hidden"
@click.stop @click.stop
> >
<div class="flex items-center justify-between p-4 border-b border-zinc-800"> <div class="flex items-center justify-between p-4 border-b border-zinc-200">
<h3 class="text-sm font-black text-zinc-100 uppercase tracking-widest"> <h3 class="text-sm font-black text-zinc-900 uppercase tracking-widest">
{{ replayModalTitle }} - 重播申请详情 {{ replayModalTitle }} - 重播申请详情
</h3> </h3>
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
@@ -847,7 +847,7 @@
拒绝申请 拒绝申请
</button> </button>
<button <button
class="text-zinc-500 hover:text-zinc-300 transition-colors" class="text-zinc-500 hover:text-zinc-600 transition-colors"
@click="closeReplayModal" @click="closeReplayModal"
> >
<CloseIcon class="w-5 h-5" /> <CloseIcon class="w-5 h-5" />
@@ -863,12 +863,12 @@
> >
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<div <div
class="w-8 h-8 rounded-lg bg-zinc-900 border border-zinc-800 flex items-center justify-center text-zinc-500 group-hover:text-blue-400 transition-colors" class="w-8 h-8 rounded-lg bg-white border border-zinc-200 flex items-center justify-center text-zinc-500 group-hover:text-blue-700 transition-colors"
> >
<User class="w-3.5 h-3.5" /> <User class="w-3.5 h-3.5" />
</div> </div>
<div class="flex flex-col"> <div class="flex flex-col">
<span class="text-sm font-bold text-zinc-200">{{ req.name }}</span> <span class="text-sm font-bold text-zinc-700">{{ req.name }}</span>
<span v-if="req.grade" class="text-[10px] text-zinc-500" <span v-if="req.grade" class="text-[10px] text-zinc-500"
>{{ req.grade }}{{ req.class ? ` ${req.class}` : '' }}</span >{{ req.grade }}{{ req.class ? ` ${req.class}` : '' }}</span
> >
+23 -23
View File
@@ -3,14 +3,14 @@
class="flex flex-col space-y-8 pb-12 lg:pb-20 animate-in fade-in slide-in-from-bottom-4 duration-500" class="flex flex-col space-y-8 pb-12 lg:pb-20 animate-in fade-in slide-in-from-bottom-4 duration-500"
> >
<div class="flex flex-col space-y-2"> <div class="flex flex-col space-y-2">
<h2 class="text-2xl font-bold tracking-tight text-zinc-100">打印排期</h2> <h2 class="text-2xl font-bold tracking-tight text-zinc-900">打印排期</h2>
<p class="text-sm text-zinc-500">自定义打印设置,预览并导出排期表</p> <p class="text-sm text-zinc-500">自定义打印设置,预览并导出排期表</p>
</div> </div>
<!-- 权限检查 --> <!-- 权限检查 -->
<div <div
v-if="!canPrintSchedule" v-if="!canPrintSchedule"
class="flex flex-col items-center justify-center py-20 bg-zinc-900/50 rounded-xl border border-zinc-800" class="flex flex-col items-center justify-center py-20 bg-zinc-50/50 rounded-xl border border-zinc-200"
> >
<div <div
class="w-16 h-16 bg-red-500/10 rounded-full flex items-center justify-center text-red-500 mb-4" class="w-16 h-16 bg-red-500/10 rounded-full flex items-center justify-center text-red-500 mb-4"
@@ -21,7 +21,7 @@
<path d="m9 9 6 6" /> <path d="m9 9 6 6" />
</svg> </svg>
</div> </div>
<h3 class="text-lg font-bold text-zinc-100">权限不足</h3> <h3 class="text-lg font-bold text-zinc-900">权限不足</h3>
<p class="text-zinc-500 mt-2">您没有打印排期的权限,请联系管理员获取相应权限。</p> <p class="text-zinc-500 mt-2">您没有打印排期的权限,请联系管理员获取相应权限。</p>
</div> </div>
@@ -29,9 +29,9 @@
<!-- 设置面板 --> <!-- 设置面板 -->
<div class="lg:col-span-4 flex flex-col gap-6 h-auto"> <div class="lg:col-span-4 flex flex-col gap-6 h-auto">
<div <div
class="bg-zinc-900/40 border border-zinc-800 rounded-xl p-6 space-y-6 shadow-lg flex flex-col h-auto" class="bg-zinc-50/40 border border-zinc-200 rounded-xl p-6 space-y-6 shadow-lg flex flex-col h-auto"
> >
<h3 class="text-lg font-bold flex items-center gap-2 text-zinc-100 shrink-0"> <h3 class="text-lg font-bold flex items-center gap-2 text-zinc-900 shrink-0">
<Layout class="w-4 h-4 text-blue-500" /> 打印设置 <Layout class="w-4 h-4 text-blue-500" /> 打印设置
</h3> </h3>
@@ -59,8 +59,8 @@
:class="[ :class="[
'py-2.5 rounded-lg text-sm font-bold transition-all', 'py-2.5 rounded-lg text-sm font-bold transition-all',
settings.orientation === 'portrait' settings.orientation === 'portrait'
? 'border border-blue-500/30 bg-blue-600/10 text-blue-400 shadow-sm' ? 'border border-blue-200 bg-blue-50 text-blue-700 shadow-sm'
: 'border border-zinc-800 bg-zinc-950 text-zinc-500 hover:text-zinc-300' : 'border border-zinc-200 bg-white text-zinc-500 hover:text-zinc-600'
]" ]"
@click="settings.orientation = 'portrait'" @click="settings.orientation = 'portrait'"
> >
@@ -70,8 +70,8 @@
:class="[ :class="[
'py-2.5 rounded-lg text-sm font-bold transition-all', 'py-2.5 rounded-lg text-sm font-bold transition-all',
settings.orientation === 'landscape' settings.orientation === 'landscape'
? 'border border-blue-500/30 bg-blue-600/10 text-blue-400 shadow-sm' ? 'border border-blue-200 bg-blue-50 text-blue-700 shadow-sm'
: 'border border-zinc-800 bg-zinc-950 text-zinc-500 hover:text-zinc-300' : 'border border-zinc-200 bg-white text-zinc-500 hover:text-zinc-600'
]" ]"
@click="settings.orientation = 'landscape'" @click="settings.orientation = 'landscape'"
> >
@@ -89,13 +89,13 @@
<input <input
v-model="settings.startDate" v-model="settings.startDate"
type="date" type="date"
class="bg-zinc-950 border border-zinc-800 rounded-lg px-3 py-2 text-sm text-zinc-300 focus:outline-none focus:border-blue-500/50 transition-colors" class="bg-white border border-zinc-200 rounded-lg px-3 py-2 text-sm text-zinc-600 focus:outline-none focus:border-blue-500/50 transition-colors"
max="9999-12-31" max="9999-12-31"
> >
<input <input
v-model="settings.endDate" v-model="settings.endDate"
type="date" type="date"
class="bg-zinc-950 border border-zinc-800 rounded-lg px-3 py-2 text-sm text-zinc-300 focus:outline-none focus:border-blue-500/50 transition-colors" class="bg-white border border-zinc-200 rounded-lg px-3 py-2 text-sm text-zinc-600 focus:outline-none focus:border-blue-500/50 transition-colors"
max="9999-12-31" max="9999-12-31"
> >
</div> </div>
@@ -103,7 +103,7 @@
<button <button
v-for="range in dateRanges" v-for="range in dateRanges"
:key="range.value" :key="range.value"
class="px-2 py-1 bg-zinc-800/50 hover:bg-zinc-800 text-[10px] text-zinc-400 rounded-md transition-colors border border-zinc-800" class="px-2 py-1 bg-zinc-100/50 hover:bg-zinc-100 text-[10px] text-zinc-400 rounded-md transition-colors border border-zinc-200"
@click="setDateRange(range.value)" @click="setDateRange(range.value)"
> >
{{ range.label }} {{ range.label }}
@@ -127,7 +127,7 @@
'w-5 h-5 rounded flex items-center justify-center border transition-all', 'w-5 h-5 rounded flex items-center justify-center border transition-all',
settings[option.key] settings[option.key]
? 'bg-blue-600 border-blue-600' ? 'bg-blue-600 border-blue-600'
: 'bg-zinc-950 border-zinc-800 group-hover:border-zinc-700' : 'bg-white border-zinc-200 group-hover:border-zinc-200'
]" ]"
> >
<CheckCircle2 v-if="settings[option.key]" class="w-3 h-3 text-white" /> <CheckCircle2 v-if="settings[option.key]" class="w-3 h-3 text-white" />
@@ -137,7 +137,7 @@
:class="[ :class="[
'text-sm font-medium transition-colors', 'text-sm font-medium transition-colors',
settings[option.key] settings[option.key]
? 'text-zinc-200' ? 'text-zinc-700'
: 'text-zinc-500 group-hover:text-zinc-400' : 'text-zinc-500 group-hover:text-zinc-400'
]" ]"
>{{ option.label }}</span >{{ option.label }}</span
@@ -153,7 +153,7 @@
'w-5 h-5 rounded flex items-center justify-center border transition-all', 'w-5 h-5 rounded flex items-center justify-center border transition-all',
settings.showSchoolLogo settings.showSchoolLogo
? 'bg-blue-600 border-blue-600' ? 'bg-blue-600 border-blue-600'
: 'bg-zinc-950 border-zinc-800 group-hover:border-zinc-700' : 'bg-white border-zinc-200 group-hover:border-zinc-200'
]" ]"
> >
<CheckCircle2 v-if="settings.showSchoolLogo" class="w-3 h-3 text-white" /> <CheckCircle2 v-if="settings.showSchoolLogo" class="w-3 h-3 text-white" />
@@ -163,7 +163,7 @@
:class="[ :class="[
'text-sm font-medium transition-colors', 'text-sm font-medium transition-colors',
settings.showSchoolLogo settings.showSchoolLogo
? 'text-zinc-200' ? 'text-zinc-700'
: 'text-zinc-500 group-hover:text-zinc-400' : 'text-zinc-500 group-hover:text-zinc-400'
]" ]"
>学校Logo</span >学校Logo</span
@@ -180,15 +180,15 @@
<textarea <textarea
v-model="settings.remark" v-model="settings.remark"
placeholder="请输入备注信息(可选)" placeholder="请输入备注信息(可选)"
class="w-full bg-zinc-950 border border-zinc-800 rounded-lg px-4 py-3 text-sm focus:outline-none text-zinc-300 min-h-[80px] resize-none focus:border-blue-500/50 transition-colors" class="w-full bg-white border border-zinc-200 rounded-lg px-4 py-3 text-sm focus:outline-none text-zinc-600 min-h-[80px] resize-none focus:border-blue-500/50 transition-colors"
/> />
</div> </div>
</div> </div>
<!-- 操作按钮 --> <!-- 操作按钮 -->
<div class="space-y-2 pt-4 border-t border-zinc-800 shrink-0"> <div class="space-y-2 pt-4 border-t border-zinc-200 shrink-0">
<button <button
class="w-full flex items-center justify-center gap-2 py-3 bg-zinc-800/80 hover:bg-zinc-700 text-zinc-300 text-sm font-bold rounded-lg border border-zinc-700 transition-all" class="w-full flex items-center justify-center gap-2 py-3 bg-zinc-100/80 hover:bg-zinc-200 text-zinc-600 text-sm font-bold rounded-lg border border-zinc-200 transition-all"
@click="refreshPreview" @click="refreshPreview"
> >
<RefreshCw class="w-4 h-4" /> 刷新预览 <RefreshCw class="w-4 h-4" /> 刷新预览
@@ -223,10 +223,10 @@
<!-- 预览区域 --> <!-- 预览区域 -->
<div class="lg:col-span-8 flex flex-col gap-4 h-[700px] lg:h-0 lg:min-h-full mb-8 lg:mb-0"> <div class="lg:col-span-8 flex flex-col gap-4 h-[700px] lg:h-0 lg:min-h-full mb-8 lg:mb-0">
<div <div
class="bg-zinc-900/40 border border-zinc-800 rounded-xl overflow-hidden flex flex-col h-full shadow-lg" class="bg-zinc-50/40 border border-zinc-200 rounded-xl overflow-hidden flex flex-col h-full shadow-lg"
> >
<div class="px-6 py-5 border-b border-zinc-800 flex items-center justify-between"> <div class="px-6 py-5 border-b border-zinc-200 flex items-center justify-between">
<h3 class="text-lg font-bold flex items-center gap-2 text-zinc-100"> <h3 class="text-lg font-bold flex items-center gap-2 text-zinc-900">
<AlignLeft class="w-4.5 h-4.5 text-zinc-500" /> 预览区域 <AlignLeft class="w-4.5 h-4.5 text-zinc-500" /> 预览区域
</h3> </h3>
<div class="flex items-center gap-2 text-xs font-bold"> <div class="flex items-center gap-2 text-xs font-bold">
@@ -245,7 +245,7 @@
</div> </div>
<div <div
class="flex-1 bg-zinc-950/50 p-6 md:p-12 overflow-auto custom-scrollbar flex items-start" class="flex-1 bg-white/70 p-6 md:p-12 overflow-auto custom-scrollbar flex items-start"
> >
<!-- 纸张预览 --> <!-- 纸张预览 -->
<div <div
+18 -18
View File
@@ -3,7 +3,7 @@
<!-- 顶部标题栏 --> <!-- 顶部标题栏 -->
<div class="flex flex-col md:flex-row md:items-end justify-between gap-6"> <div class="flex flex-col md:flex-row md:items-end justify-between gap-6">
<div> <div>
<h2 class="text-2xl font-black text-zinc-100 tracking-tight">学期管理</h2> <h2 class="text-2xl font-black text-zinc-900 tracking-tight">学期管理</h2>
<p class="text-xs text-zinc-500 mt-1"> <p class="text-xs text-zinc-500 mt-1">
定义系统的教学周期,点歌、排期及统计数据将基于当前活跃学期进行归档 定义系统的教学周期,点歌、排期及统计数据将基于当前活跃学期进行归档
</p> </p>
@@ -134,7 +134,7 @@
<div <div
v-else v-else
class="bg-zinc-900/30 border border-zinc-800 border-dashed rounded-3xl p-20 flex flex-col items-center justify-center text-center gap-4" class="bg-zinc-50/30 border border-zinc-200 border-dashed rounded-3xl p-20 flex flex-col items-center justify-center text-center gap-4"
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -158,7 +158,7 @@
</button> </button>
</div> </div>
<div class="p-6 bg-zinc-900/20 border border-zinc-800 rounded-3xl flex items-start gap-4"> <div class="p-6 bg-zinc-50/20 border border-zinc-200 rounded-3xl flex items-start gap-4">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="18" width="18"
@@ -214,7 +214,7 @@
<div class="space-y-4"> <div class="space-y-4">
<div <div
v-if="loading && semesters.length === 0" v-if="loading && semesters.length === 0"
class="text-center py-20 bg-zinc-900/10 border border-zinc-800 border-dashed rounded-[2rem]" class="text-center py-20 bg-zinc-50/10 border border-zinc-200 border-dashed rounded-[2rem]"
> >
<p class="text-xs font-bold text-zinc-600">加载中...</p> <p class="text-xs font-bold text-zinc-600">加载中...</p>
</div> </div>
@@ -225,8 +225,8 @@
class="group flex items-center justify-between p-6 rounded-[2rem] border transition-all" class="group flex items-center justify-between p-6 rounded-[2rem] border transition-all"
:class=" :class="
sem.isActive sem.isActive
? 'bg-blue-600/5 border-blue-500/30' ? 'bg-blue-600/5 border-blue-200'
: 'bg-zinc-900/40 border-zinc-800 hover:border-zinc-700' : 'bg-zinc-50/40 border-zinc-200 hover:border-zinc-200'
" "
> >
<div class="flex items-center gap-5"> <div class="flex items-center gap-5">
@@ -235,7 +235,7 @@
:class=" :class="
sem.isActive sem.isActive
? 'bg-blue-600 text-white shadow-lg shadow-blue-900/20' ? 'bg-blue-600 text-white shadow-lg shadow-blue-900/20'
: 'bg-zinc-800 text-zinc-600 border border-zinc-700' : 'bg-zinc-100 text-zinc-600 border border-zinc-200'
" "
> >
<svg <svg
@@ -258,7 +258,7 @@
<h5 <h5
class="font-bold transition-colors" class="font-bold transition-colors"
:class=" :class="
sem.isActive ? 'text-blue-400' : 'text-zinc-200 group-hover:text-blue-400' sem.isActive ? 'text-blue-700' : 'text-zinc-700 group-hover:text-blue-700'
" "
> >
{{ sem.name }} {{ sem.name }}
@@ -293,7 +293,7 @@
<template v-else> <template v-else>
<button <button
:disabled="loading" :disabled="loading"
class="flex items-center gap-2 px-4 py-2 bg-zinc-800 hover:bg-zinc-700 text-zinc-400 hover:text-white text-[10px] font-black rounded-xl border border-zinc-700 transition-all uppercase tracking-widest disabled:opacity-50 disabled:cursor-not-allowed" class="flex items-center gap-2 px-4 py-2 bg-zinc-100 hover:bg-zinc-200 text-zinc-400 hover:text-zinc-900 text-[10px] font-black rounded-xl border border-zinc-200 transition-all uppercase tracking-widest disabled:opacity-50 disabled:cursor-not-allowed"
@click="setActive(sem.id)" @click="setActive(sem.id)"
> >
<svg <svg
@@ -342,7 +342,7 @@
<div <div
v-if="semesters.length === 0 && !loading" v-if="semesters.length === 0 && !loading"
class="text-center py-20 bg-zinc-900/10 border border-zinc-800 border-dashed rounded-[2rem]" class="text-center py-20 bg-zinc-50/10 border border-zinc-200 border-dashed rounded-[2rem]"
> >
<p class="text-xs font-bold text-zinc-600">暂无学期数据</p> <p class="text-xs font-bold text-zinc-600">暂无学期数据</p>
</div> </div>
@@ -356,14 +356,14 @@
class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm" class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm"
> >
<div <div
class="w-full max-w-md bg-zinc-900 rounded-3xl border border-zinc-800 shadow-2xl overflow-hidden" class="w-full max-w-md bg-white rounded-3xl border border-zinc-200 shadow-2xl overflow-hidden"
@click.stop @click.stop
> >
<div class="p-8 space-y-6"> <div class="p-8 space-y-6">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<h3 class="text-xl font-black text-white tracking-tight">添加新学期</h3> <h3 class="text-xl font-black text-white tracking-tight">添加新学期</h3>
<button <button
class="p-2 text-zinc-500 hover:text-white transition-colors" class="p-2 text-zinc-500 hover:text-zinc-900 transition-colors"
@click="closeAddModal" @click="closeAddModal"
> >
<svg <svg
@@ -392,7 +392,7 @@
v-model="newSemester.name" v-model="newSemester.name"
type="text" type="text"
placeholder="例如: 2025学年第一学期" placeholder="例如: 2025学年第一学期"
class="w-full bg-zinc-950 border border-zinc-800 rounded-2xl px-5 py-4 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30 font-bold transition-all" class="w-full bg-white border border-zinc-200 rounded-2xl px-5 py-4 text-sm text-zinc-700 focus:outline-none focus:border-blue-200 font-bold transition-all"
> >
</div> </div>
@@ -400,11 +400,11 @@
<input <input
v-model="newSemester.isActive" v-model="newSemester.isActive"
type="checkbox" type="checkbox"
class="w-4.5 h-4.5 rounded-lg border-zinc-800 bg-zinc-950 accent-blue-600 transition-all" class="w-4.5 h-4.5 rounded-lg border-zinc-200 bg-white accent-blue-600 transition-all"
> >
<div> <div>
<span <span
class="text-xs font-bold text-zinc-300 group-hover:text-blue-400 transition-colors" class="text-xs font-bold text-zinc-600 group-hover:text-blue-700 transition-colors"
>设为当前活跃学期</span >设为当前活跃学期</span
> >
<p class="text-[10px] text-zinc-600 font-medium"> <p class="text-[10px] text-zinc-600 font-medium">
@@ -413,7 +413,7 @@
</div> </div>
</label> </label>
<div class="p-4 bg-zinc-950 border border-zinc-800 rounded-2xl flex items-start gap-3"> <div class="p-4 bg-white border border-zinc-200 rounded-2xl flex items-start gap-3">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="14" width="14"
@@ -437,9 +437,9 @@
</div> </div>
</div> </div>
<div class="p-6 bg-zinc-800/50 border-t border-zinc-800 flex gap-3 justify-end"> <div class="p-6 bg-zinc-100/50 border-t border-zinc-200 flex gap-3 justify-end">
<button <button
class="px-6 py-2.5 text-xs font-bold text-zinc-500 hover:text-zinc-300 transition-colors" class="px-6 py-2.5 text-xs font-bold text-zinc-500 hover:text-zinc-600 transition-colors"
@click="closeAddModal" @click="closeAddModal"
> >
取消 取消
+12 -12
View File
@@ -2,7 +2,7 @@
<template> <template>
<aside <aside
:class="[ :class="[
'fixed inset-y-0 left-0 z-50 w-64 bg-[#09090b] border-r border-zinc-800 transform transition-transform duration-300 ease-in-out lg:translate-x-0', 'fixed inset-y-0 left-0 z-50 w-64 bg-[#ffffff] border-r border-zinc-200 shadow-sm transform transition-transform duration-300 ease-in-out lg:translate-x-0',
isOpen ? 'translate-x-0' : '-translate-x-full' isOpen ? 'translate-x-0' : '-translate-x-full'
]" ]"
> >
@@ -24,7 +24,7 @@
</div> </div>
<!-- 品牌文字 --> <!-- 品牌文字 -->
<div class="flex flex-col justify-center"> <div class="flex flex-col justify-center">
<h1 class="font-bold text-lg text-zinc-100 leading-none tracking-tight">VoiceHub</h1> <h1 class="font-bold text-lg text-zinc-900 leading-none tracking-tight">VoiceHub</h1>
<p <p
class="text-[10px] text-zinc-500 mt-1.5 uppercase tracking-widest font-bold leading-none" class="text-[10px] text-zinc-500 mt-1.5 uppercase tracking-widest font-bold leading-none"
> >
@@ -49,8 +49,8 @@
:class="[ :class="[
'w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-[13px] font-bold transition-all group border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/80 focus-visible:ring-inset', 'w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-[13px] font-bold transition-all group border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/80 focus-visible:ring-inset',
activeTab === item.id activeTab === item.id
? 'bg-blue-600/10 text-blue-400 border-blue-500/20' ? 'bg-blue-50 text-blue-700 border-blue-200'
: 'text-zinc-500 hover:text-zinc-200 hover:bg-zinc-800/40 border-transparent', : 'text-zinc-600 hover:text-zinc-900 hover:bg-zinc-100 border-transparent',
getTourClass(item.id) getTourClass(item.id)
]" ]"
@click="onNavigate(item.id)" @click="onNavigate(item.id)"
@@ -61,8 +61,8 @@
:size="18" :size="18"
:class=" :class="
activeTab === item.id activeTab === item.id
? 'text-blue-400' ? 'text-blue-700'
: 'text-zinc-500 group-hover:text-zinc-300' : 'text-zinc-500 group-hover:text-zinc-700'
" "
/> />
<span class="truncate">{{ item.label }}</span> <span class="truncate">{{ item.label }}</span>
@@ -78,26 +78,26 @@
</nav> </nav>
<!-- 用户信息及退出登录 --> <!-- 用户信息及退出登录 -->
<div class="mt-4 pt-4 border-t border-zinc-800"> <div class="mt-4 pt-4 border-t border-zinc-200">
<div <div
class="flex items-center gap-3 p-3 rounded-lg bg-zinc-900/50 border border-zinc-800/50 hover:bg-zinc-800/30 transition-colors" class="flex items-center gap-3 p-3 rounded-lg bg-zinc-50 border border-zinc-200 hover:bg-zinc-100 transition-colors"
> >
<!-- 用户头像/首字母 --> <!-- 用户头像/首字母 -->
<img <img
v-if="currentUser?.avatar && !avatarError" v-if="currentUser?.avatar && !avatarError"
:src="currentUser.avatar" :src="currentUser.avatar"
class="w-10 h-10 rounded-lg object-cover border border-zinc-700 shrink-0" class="w-10 h-10 rounded-lg object-cover border border-zinc-200 shrink-0"
@error="avatarError = true" @error="avatarError = true"
> >
<div <div
v-else v-else
class="w-10 h-10 rounded-lg bg-zinc-800 flex items-center justify-center text-zinc-400 font-bold border border-zinc-700 shrink-0" class="w-10 h-10 rounded-lg bg-zinc-100 flex items-center justify-center text-zinc-600 font-bold border border-zinc-200 shrink-0"
> >
{{ (currentUser?.name || '管').charAt(0) }} {{ (currentUser?.name || '管').charAt(0) }}
</div> </div>
<!-- 用户详细信息 --> <!-- 用户详细信息 -->
<div class="flex-1 min-w-0"> <div class="flex-1 min-w-0">
<p class="text-xs font-black truncate text-zinc-100"> <p class="text-xs font-black truncate text-zinc-900">
{{ currentUser?.name || '管理员' }} {{ currentUser?.name || '管理员' }}
</p> </p>
<p <p
@@ -108,7 +108,7 @@
</div> </div>
<!-- 退出按钮 --> <!-- 退出按钮 -->
<button <button
class="p-2 text-zinc-600 hover:text-red-400 hover:bg-red-400/10 rounded-lg transition-all" class="p-2 text-zinc-500 hover:text-red-600 hover:bg-red-50 rounded-lg transition-all"
title="退出登录" title="退出登录"
@click="$emit('logout')" @click="$emit('logout')"
> >
+33 -33
View File
@@ -3,7 +3,7 @@
<!-- 顶部标题栏 --> <!-- 顶部标题栏 -->
<div class="flex flex-col md:flex-row md:items-end justify-between gap-4"> <div class="flex flex-col md:flex-row md:items-end justify-between gap-4">
<div> <div>
<h2 class="text-2xl font-black text-zinc-100 tracking-tight">站点配置</h2> <h2 class="text-2xl font-black text-zinc-900 tracking-tight">站点配置</h2>
<p class="text-xs text-zinc-500 mt-1 font-medium"> <p class="text-xs text-zinc-500 mt-1 font-medium">
管理站点全局属性、视觉识别、点歌逻辑及系统安全策略 管理站点全局属性、视觉识别、点歌逻辑及系统安全策略
</p> </p>
@@ -11,7 +11,7 @@
<div class="flex gap-3"> <div class="flex gap-3">
<button <button
:disabled="loading || saving" :disabled="loading || saving"
class="flex items-center gap-2 px-5 py-2 bg-zinc-900 border border-zinc-800 hover:border-zinc-700 text-zinc-400 text-xs font-bold rounded-xl transition-all disabled:opacity-50 disabled:cursor-not-allowed" class="flex items-center gap-2 px-5 py-2 bg-white border border-zinc-200 hover:border-zinc-200 text-zinc-400 text-xs font-bold rounded-xl transition-all disabled:opacity-50 disabled:cursor-not-allowed"
@click="resetForm" @click="resetForm"
> >
<RotateCcw :size="14" /> 重置 <RotateCcw :size="14" /> 重置
@@ -35,7 +35,7 @@
<div v-if="loading" class="flex flex-col items-center justify-center py-20"> <div v-if="loading" class="flex flex-col items-center justify-center py-20">
<div <div
class="w-8 h-8 border-4 border-zinc-800 border-t-blue-500 rounded-full animate-spin mb-4" class="w-8 h-8 border-4 border-zinc-200 border-t-blue-500 rounded-full animate-spin mb-4"
/> />
<p class="text-zinc-500 text-sm">加载配置中...</p> <p class="text-zinc-500 text-sm">加载配置中...</p>
</div> </div>
@@ -44,7 +44,7 @@
<!-- 基础信息 --> <!-- 基础信息 -->
<section :class="cardClass"> <section :class="cardClass">
<h3 <h3
class="text-sm font-black text-zinc-100 uppercase tracking-widest flex items-center gap-2 border-b border-zinc-800 pb-4" class="text-sm font-black text-zinc-900 uppercase tracking-widest flex items-center gap-2 border-b border-zinc-200 pb-4"
> >
<Globe :size="16" class="text-blue-500" /> 基础信息 <Globe :size="16" class="text-blue-500" /> 基础信息
</h3> </h3>
@@ -100,7 +100,7 @@
<!-- 视觉识别 --> <!-- 视觉识别 -->
<section :class="cardClass"> <section :class="cardClass">
<h3 <h3
class="text-sm font-black text-zinc-100 uppercase tracking-widest flex items-center gap-2 border-b border-zinc-800 pb-4" class="text-sm font-black text-zinc-900 uppercase tracking-widest flex items-center gap-2 border-b border-zinc-200 pb-4"
> >
<ImageIcon :size="16" class="text-purple-500" /> 视觉识别 <ImageIcon :size="16" class="text-purple-500" /> 视觉识别
</h3> </h3>
@@ -137,10 +137,10 @@
<!-- 投稿须知 --> <!-- 投稿须知 -->
<section <section
class="lg:col-span-2 bg-zinc-900/40 border border-zinc-800 rounded-2xl p-6 space-y-6" class="lg:col-span-2 bg-zinc-50/40 border border-zinc-200 rounded-2xl p-6 space-y-6"
> >
<h3 <h3
class="text-sm font-black text-zinc-100 uppercase tracking-widest flex items-center gap-2 border-b border-zinc-800 pb-4" class="text-sm font-black text-zinc-900 uppercase tracking-widest flex items-center gap-2 border-b border-zinc-200 pb-4"
> >
<FileText :size="16" class="text-emerald-500" /> 投稿须知 <FileText :size="16" class="text-emerald-500" /> 投稿须知
</h3> </h3>
@@ -155,75 +155,75 @@
<!-- 投稿逻辑设置 --> <!-- 投稿逻辑设置 -->
<section :class="cardClass"> <section :class="cardClass">
<h3 <h3
class="text-sm font-black text-zinc-100 uppercase tracking-widest flex items-center gap-2 border-b border-zinc-800 pb-4" class="text-sm font-black text-zinc-900 uppercase tracking-widest flex items-center gap-2 border-b border-zinc-200 pb-4"
> >
<Settings2 :size="16" class="text-amber-500" /> 投稿逻辑设置 <Settings2 :size="16" class="text-amber-500" /> 投稿逻辑设置
</h3> </h3>
<div class="space-y-6"> <div class="space-y-6">
<div <div
class="flex items-center justify-between p-3 bg-zinc-950/50 border border-zinc-800 rounded-xl" class="flex items-center justify-between p-3 bg-white/70 border border-zinc-200 rounded-xl"
> >
<div> <div>
<p class="text-xs font-bold text-zinc-200">启用联合投稿</p> <p class="text-xs font-bold text-zinc-700">启用联合投稿</p>
<p class="text-[10px] text-zinc-500 mt-0.5">允许用户添加联合投稿人并发起协作投稿</p> <p class="text-[10px] text-zinc-500 mt-0.5">允许用户添加联合投稿人并发起协作投稿</p>
</div> </div>
<input <input
v-model="formData.enableCollaborativeSubmission" v-model="formData.enableCollaborativeSubmission"
type="checkbox" type="checkbox"
class="w-5 h-5 rounded border-zinc-800 bg-zinc-900 accent-blue-600 cursor-pointer" class="w-5 h-5 rounded border-zinc-200 bg-white accent-blue-600 cursor-pointer"
> >
</div> </div>
<div <div
class="flex items-center justify-between p-3 bg-zinc-950/50 border border-zinc-800 rounded-xl" class="flex items-center justify-between p-3 bg-white/70 border border-zinc-200 rounded-xl"
> >
<div> <div>
<p class="text-xs font-bold text-zinc-200">启用投稿备注留言</p> <p class="text-xs font-bold text-zinc-700">启用投稿备注留言</p>
<p class="text-[10px] text-zinc-500 mt-0.5">允许用户在投稿时附加公开或仅管理员可见的备注</p> <p class="text-[10px] text-zinc-500 mt-0.5">允许用户在投稿时附加公开或仅管理员可见的备注</p>
</div> </div>
<input <input
v-model="formData.enableSubmissionRemarks" v-model="formData.enableSubmissionRemarks"
type="checkbox" type="checkbox"
class="w-5 h-5 rounded border-zinc-800 bg-zinc-900 accent-blue-600 cursor-pointer" class="w-5 h-5 rounded border-zinc-200 bg-white accent-blue-600 cursor-pointer"
> >
</div> </div>
<div <div
class="flex items-center justify-between p-3 bg-zinc-950/50 border border-zinc-800 rounded-xl" class="flex items-center justify-between p-3 bg-white/70 border border-zinc-200 rounded-xl"
> >
<div> <div>
<p class="text-xs font-bold text-zinc-200">启用重播申请</p> <p class="text-xs font-bold text-zinc-700">启用重播申请</p>
<p class="text-[10px] text-zinc-500 mt-0.5">允许用户对本学期已播放过的歌曲再次申请</p> <p class="text-[10px] text-zinc-500 mt-0.5">允许用户对本学期已播放过的歌曲再次申请</p>
</div> </div>
<input <input
v-model="formData.enableReplayRequests" v-model="formData.enableReplayRequests"
type="checkbox" type="checkbox"
class="w-5 h-5 rounded border-zinc-800 bg-zinc-900 accent-blue-600 cursor-pointer" class="w-5 h-5 rounded border-zinc-200 bg-white accent-blue-600 cursor-pointer"
> >
</div> </div>
<div class="space-y-4"> <div class="space-y-4">
<div <div
class="flex items-center justify-between p-3 bg-zinc-950/50 border border-zinc-800 rounded-xl" class="flex items-center justify-between p-3 bg-white/70 border border-zinc-200 rounded-xl"
> >
<div> <div>
<p class="text-xs font-bold text-zinc-200">启用投稿限额</p> <p class="text-xs font-bold text-zinc-700">启用投稿限额</p>
<p class="text-[10px] text-zinc-500 mt-0.5">限制单个用户的点歌频率</p> <p class="text-[10px] text-zinc-500 mt-0.5">限制单个用户的点歌频率</p>
</div> </div>
<input <input
v-model="formData.enableSubmissionLimit" v-model="formData.enableSubmissionLimit"
type="checkbox" type="checkbox"
class="w-5 h-5 rounded border-zinc-800 bg-zinc-900 accent-blue-600 cursor-pointer" class="w-5 h-5 rounded border-zinc-200 bg-white accent-blue-600 cursor-pointer"
> >
</div> </div>
<div v-if="formData.enableSubmissionLimit" class="space-y-4"> <div v-if="formData.enableSubmissionLimit" class="space-y-4">
<div class="grid grid-cols-3 gap-2 p-1 bg-zinc-950 border border-zinc-800 rounded-xl"> <div class="grid grid-cols-3 gap-2 p-1 bg-white border border-zinc-200 rounded-xl">
<button <button
:class="[ :class="[
'py-2 rounded-lg text-[10px] font-black uppercase tracking-widest transition-all', 'py-2 rounded-lg text-[10px] font-black uppercase tracking-widest transition-all',
currentLimitType === 'daily' currentLimitType === 'daily'
? 'bg-zinc-800 text-blue-400 shadow-sm' ? 'bg-zinc-100 text-blue-700 shadow-sm'
: 'text-zinc-600 hover:text-zinc-400' : 'text-zinc-600 hover:text-zinc-400'
]" ]"
@click="handleLimitTypeChange('daily')" @click="handleLimitTypeChange('daily')"
@@ -234,7 +234,7 @@
:class="[ :class="[
'py-2 rounded-lg text-[10px] font-black uppercase tracking-widest transition-all', 'py-2 rounded-lg text-[10px] font-black uppercase tracking-widest transition-all',
currentLimitType === 'weekly' currentLimitType === 'weekly'
? 'bg-zinc-800 text-blue-400 shadow-sm' ? 'bg-zinc-100 text-blue-700 shadow-sm'
: 'text-zinc-600 hover:text-zinc-400' : 'text-zinc-600 hover:text-zinc-400'
]" ]"
@click="handleLimitTypeChange('weekly')" @click="handleLimitTypeChange('weekly')"
@@ -245,7 +245,7 @@
:class="[ :class="[
'py-2 rounded-lg text-[10px] font-black uppercase tracking-widest transition-all', 'py-2 rounded-lg text-[10px] font-black uppercase tracking-widest transition-all',
currentLimitType === 'monthly' currentLimitType === 'monthly'
? 'bg-zinc-800 text-blue-400 shadow-sm' ? 'bg-zinc-100 text-blue-700 shadow-sm'
: 'text-zinc-600 hover:text-zinc-400' : 'text-zinc-600 hover:text-zinc-400'
]" ]"
@click="handleLimitTypeChange('monthly')" @click="handleLimitTypeChange('monthly')"
@@ -279,23 +279,23 @@
<!-- 安全与隐私设置 --> <!-- 安全与隐私设置 -->
<section :class="cardClass"> <section :class="cardClass">
<h3 <h3
class="text-sm font-black text-zinc-100 uppercase tracking-widest flex items-center gap-2 border-b border-zinc-800 pb-4" class="text-sm font-black text-zinc-900 uppercase tracking-widest flex items-center gap-2 border-b border-zinc-200 pb-4"
> >
<Shield :size="16" class="text-rose-500" /> 安全与隐私设置 <Shield :size="16" class="text-rose-500" /> 安全与隐私设置
</h3> </h3>
<div class="space-y-4"> <div class="space-y-4">
<div class="p-4 bg-zinc-950/50 border border-zinc-800 rounded-xl space-y-4"> <div class="p-4 bg-white/70 border border-zinc-200 rounded-xl space-y-4">
<div class="flex items-start gap-4"> <div class="flex items-start gap-4">
<div class="shrink-0 pt-0.5"> <div class="shrink-0 pt-0.5">
<input <input
id="show-keywords" id="show-keywords"
v-model="formData.showBlacklistKeywords" v-model="formData.showBlacklistKeywords"
type="checkbox" type="checkbox"
class="w-4 h-4 rounded border-zinc-800 bg-zinc-900 accent-blue-600 cursor-pointer" class="w-4 h-4 rounded border-zinc-200 bg-white accent-blue-600 cursor-pointer"
> >
</div> </div>
<label for="show-keywords" class="cursor-pointer"> <label for="show-keywords" class="cursor-pointer">
<p class="text-xs font-bold text-zinc-200">显示黑名单具体关键词</p> <p class="text-xs font-bold text-zinc-700">显示黑名单具体关键词</p>
<p class="text-[10px] text-zinc-500 mt-1 leading-relaxed"> <p class="text-[10px] text-zinc-500 mt-1 leading-relaxed">
开启后,在投稿命中黑名单时将明确提示冲突关键词;关闭则仅提示“包含关键词”。 开启后,在投稿命中黑名单时将明确提示冲突关键词;关闭则仅提示“包含关键词”。
</p> </p>
@@ -303,18 +303,18 @@
</div> </div>
</div> </div>
<div class="p-4 bg-zinc-950/50 border border-zinc-800 rounded-xl space-y-4"> <div class="p-4 bg-white/70 border border-zinc-200 rounded-xl space-y-4">
<div class="flex items-start gap-4"> <div class="flex items-start gap-4">
<div class="shrink-0 pt-0.5"> <div class="shrink-0 pt-0.5">
<input <input
id="hide-students" id="hide-students"
v-model="formData.hideStudentInfo" v-model="formData.hideStudentInfo"
type="checkbox" type="checkbox"
class="w-4 h-4 rounded border-zinc-800 bg-zinc-900 accent-blue-600 cursor-pointer" class="w-4 h-4 rounded border-zinc-200 bg-white accent-blue-600 cursor-pointer"
> >
</div> </div>
<label for="hide-students" class="cursor-pointer"> <label for="hide-students" class="cursor-pointer">
<p class="text-xs font-bold text-zinc-200">隐藏学生详细信息</p> <p class="text-xs font-bold text-zinc-700">隐藏学生详细信息</p>
<p class="text-[10px] text-zinc-500 mt-1 leading-relaxed"> <p class="text-[10px] text-zinc-500 mt-1 leading-relaxed">
开启后,非管理员用户在前端点歌列表、排期预览中将无法查看投稿学生的完整学号与真实姓名。 开启后,非管理员用户在前端点歌列表、排期预览中将无法查看投稿学生的完整学号与真实姓名。
</p> </p>
@@ -359,9 +359,9 @@ const saveSuccess = ref(false)
// 样式类常量 // 样式类常量
const inputClass = const inputClass =
'w-full bg-zinc-950 border border-zinc-800 rounded-xl px-4 py-2.5 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30 transition-all placeholder:text-zinc-800' 'w-full bg-white border border-zinc-200 rounded-xl px-4 py-2.5 text-sm text-zinc-700 focus:outline-none focus:border-blue-200 transition-all placeholder:text-zinc-500'
const labelClass = 'text-[10px] font-black text-zinc-600 uppercase tracking-widest px-1 block mb-2' const labelClass = 'text-[10px] font-black text-zinc-600 uppercase tracking-widest px-1 block mb-2'
const cardClass = 'bg-zinc-900/40 border border-zinc-800 rounded-2xl p-6 shadow-xl space-y-6' const cardClass = 'bg-zinc-50/40 border border-zinc-200 rounded-2xl p-6 shadow-xl space-y-6'
const defaultSubmissionGuidelines = `1. 投稿时无需加入书名号 const defaultSubmissionGuidelines = `1. 投稿时无需加入书名号
2. 除DJ外,其他类型歌曲均接收(包括小语种) 2. 除DJ外,其他类型歌曲均接收(包括小语种)
+15 -15
View File
@@ -3,14 +3,14 @@
<!-- 头部区域 --> <!-- 头部区域 -->
<div class="flex flex-col md:flex-row md:items-end justify-between gap-6"> <div class="flex flex-col md:flex-row md:items-end justify-between gap-6">
<div> <div>
<h2 class="text-2xl font-black text-zinc-100 tracking-tight">邮件服务配置</h2> <h2 class="text-2xl font-black text-zinc-900 tracking-tight">邮件服务配置</h2>
<p class="text-xs text-zinc-500 mt-1"> <p class="text-xs text-zinc-500 mt-1">
配置 SMTP 服务以发送系统验证码、投稿通知及其他重要提醒 配置 SMTP 服务以发送系统验证码、投稿通知及其他重要提醒
</p> </p>
</div> </div>
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<button <button
class="flex items-center gap-2 px-6 py-2 bg-zinc-900 border border-zinc-800 hover:border-zinc-700 text-zinc-300 text-xs font-bold rounded-xl transition-all active:scale-95 disabled:opacity-50" class="flex items-center gap-2 px-6 py-2 bg-white border border-zinc-200 hover:border-zinc-200 text-zinc-600 text-xs font-bold rounded-xl transition-all active:scale-95 disabled:opacity-50"
:disabled="reloading" :disabled="reloading"
@click="reloadSmtpConfig" @click="reloadSmtpConfig"
> >
@@ -31,16 +31,16 @@
<!-- 左侧栏:SMTP 设置与测试 --> <!-- 左侧栏:SMTP 设置与测试 -->
<div class="xl:col-span-4 space-y-8"> <div class="xl:col-span-4 space-y-8">
<!-- SMTP 核心设置 --> <!-- SMTP 核心设置 -->
<section class="bg-zinc-900/30 border border-zinc-800 rounded-[2rem] p-6 space-y-6"> <section class="bg-zinc-50/30 border border-zinc-200 rounded-[2rem] p-6 space-y-6">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<h3 <h3
class="text-sm font-black text-zinc-100 uppercase tracking-widest flex items-center gap-2" class="text-sm font-black text-zinc-900 uppercase tracking-widest flex items-center gap-2"
> >
<Server :size="16" class="text-blue-500" /> 服务配置 <Server :size="16" class="text-blue-500" /> 服务配置
</h3> </h3>
<button <button
class="relative w-10 h-5 rounded-full transition-colors" class="relative w-10 h-5 rounded-full transition-colors"
:class="config.smtpEnabled ? 'bg-blue-600' : 'bg-zinc-800'" :class="config.smtpEnabled ? 'bg-blue-600' : 'bg-zinc-100'"
@click="config.smtpEnabled = !config.smtpEnabled" @click="config.smtpEnabled = !config.smtpEnabled"
> >
<div <div
@@ -59,7 +59,7 @@
v-model="config.smtpHost" v-model="config.smtpHost"
type="text" type="text"
placeholder="smtp.example.com" placeholder="smtp.example.com"
class="w-full bg-zinc-950 border border-zinc-800 rounded-xl px-4 py-2.5 text-xs text-zinc-200 focus:outline-none focus:border-blue-500/30" class="w-full bg-white border border-zinc-200 rounded-xl px-4 py-2.5 text-xs text-zinc-700 focus:outline-none focus:border-blue-200"
> >
</div> </div>
<div class="grid grid-cols-2 gap-3"> <div class="grid grid-cols-2 gap-3">
@@ -71,7 +71,7 @@
v-model.number="config.smtpPort" v-model.number="config.smtpPort"
type="number" type="number"
placeholder="587" placeholder="587"
class="w-full bg-zinc-950 border border-zinc-800 rounded-xl px-4 py-2.5 text-xs text-zinc-200 focus:outline-none focus:border-blue-500/30" class="w-full bg-white border border-zinc-200 rounded-xl px-4 py-2.5 text-xs text-zinc-700 focus:outline-none focus:border-blue-200"
> >
</div> </div>
<div class="space-y-1.5"> <div class="space-y-1.5">
@@ -94,7 +94,7 @@
v-model="config.smtpUsername" v-model="config.smtpUsername"
type="text" type="text"
placeholder="your-email@example.com" placeholder="your-email@example.com"
class="w-full bg-zinc-950 border border-zinc-800 rounded-xl px-4 py-2.5 text-xs text-zinc-200 focus:outline-none focus:border-blue-500/30" class="w-full bg-white border border-zinc-200 rounded-xl px-4 py-2.5 text-xs text-zinc-700 focus:outline-none focus:border-blue-200"
> >
</div> </div>
<div class="space-y-1.5"> <div class="space-y-1.5">
@@ -105,7 +105,7 @@
v-model="config.smtpFromEmail" v-model="config.smtpFromEmail"
type="text" type="text"
:placeholder="config.smtpUsername || 'your-email@example.com'" :placeholder="config.smtpUsername || 'your-email@example.com'"
class="w-full bg-zinc-950 border border-zinc-800 rounded-xl px-4 py-2.5 text-xs text-zinc-200 focus:outline-none focus:border-blue-500/30" class="w-full bg-white border border-zinc-200 rounded-xl px-4 py-2.5 text-xs text-zinc-700 focus:outline-none focus:border-blue-200"
> >
<p class="text-[9px] text-zinc-500 px-1 italic"> <p class="text-[9px] text-zinc-500 px-1 italic">
通常与发件人账号一致,部分服务商强制要求一致。 通常与发件人账号一致,部分服务商强制要求一致。
@@ -119,7 +119,7 @@
v-model="config.smtpPassword" v-model="config.smtpPassword"
type="password" type="password"
placeholder="••••••••••••" placeholder="••••••••••••"
class="w-full bg-zinc-950 border border-zinc-800 rounded-xl px-4 py-2.5 text-xs text-zinc-200 focus:outline-none focus:border-blue-500/30" class="w-full bg-white border border-zinc-200 rounded-xl px-4 py-2.5 text-xs text-zinc-700 focus:outline-none focus:border-blue-200"
> >
</div> </div>
<div class="space-y-1.5"> <div class="space-y-1.5">
@@ -130,16 +130,16 @@
v-model="config.smtpFromName" v-model="config.smtpFromName"
type="text" type="text"
placeholder="校园广播站" placeholder="校园广播站"
class="w-full bg-zinc-950 border border-zinc-800 rounded-xl px-4 py-2.5 text-xs text-zinc-200 focus:outline-none focus:border-blue-500/30" class="w-full bg-white border border-zinc-200 rounded-xl px-4 py-2.5 text-xs text-zinc-700 focus:outline-none focus:border-blue-200"
> >
</div> </div>
</div> </div>
</section> </section>
<!-- SMTP 测试模块 --> <!-- SMTP 测试模块 -->
<section class="bg-zinc-900/30 border border-zinc-800 rounded-[2rem] p-6 space-y-6"> <section class="bg-zinc-50/30 border border-zinc-200 rounded-[2rem] p-6 space-y-6">
<h3 <h3
class="text-sm font-black text-zinc-100 uppercase tracking-widest flex items-center gap-2" class="text-sm font-black text-zinc-900 uppercase tracking-widest flex items-center gap-2"
> >
<Send :size="16" class="text-emerald-500" /> 服务测试 <Send :size="16" class="text-emerald-500" /> 服务测试
</h3> </h3>
@@ -154,10 +154,10 @@
v-model="testEmail" v-model="testEmail"
type="email" type="email"
placeholder="输入测试邮箱地址" placeholder="输入测试邮箱地址"
class="flex-1 bg-zinc-950 border border-zinc-800 rounded-xl px-4 py-2.5 text-xs text-zinc-200 focus:outline-none focus:border-blue-500/30" class="flex-1 bg-white border border-zinc-200 rounded-xl px-4 py-2.5 text-xs text-zinc-700 focus:outline-none focus:border-blue-200"
> >
<button <button
class="flex items-center gap-2 px-4 py-2 bg-zinc-900 border border-zinc-800 hover:border-zinc-700 text-zinc-400 text-xs font-bold rounded-xl transition-all disabled:opacity-50" class="flex items-center gap-2 px-4 py-2 bg-white border border-zinc-200 hover:border-zinc-200 text-zinc-400 text-xs font-bold rounded-xl transition-all disabled:opacity-50"
:disabled="testing || !testEmail || !config.smtpEnabled" :disabled="testing || !testEmail || !config.smtpEnabled"
@click="sendTestEmail" @click="sendTestEmail"
> >
+21 -21
View File
@@ -3,7 +3,7 @@
<div class="space-y-6"> <div class="space-y-6">
<div class="flex flex-col md:flex-row md:items-end justify-between gap-6 px-1"> <div class="flex flex-col md:flex-row md:items-end justify-between gap-6 px-1">
<div class="space-y-1"> <div class="space-y-1">
<h2 class="text-3xl font-black text-zinc-100 tracking-tight">歌曲审核</h2> <h2 class="text-3xl font-black text-zinc-900 tracking-tight">歌曲审核</h2>
<div class="flex items-center gap-6 mt-4"> <div class="flex items-center gap-6 mt-4">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="text-[10px] font-black text-zinc-600 uppercase tracking-widest">待审核</span> <span class="text-[10px] font-black text-zinc-600 uppercase tracking-widest">待审核</span>
@@ -15,7 +15,7 @@
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<button <button
:disabled="loading" :disabled="loading"
class="flex items-center gap-2 px-5 py-2.5 bg-blue-600/10 hover:bg-blue-600/20 text-blue-400 text-xs font-bold rounded-lg border border-blue-500/20 transition-all disabled:opacity-50" class="flex items-center gap-2 px-5 py-2.5 bg-blue-50 hover:bg-blue-600/20 text-blue-700 text-xs font-bold rounded-lg border border-blue-200 transition-all disabled:opacity-50"
@click="refreshSongs(true)" @click="refreshSongs(true)"
> >
<RotateCcw :size="16" :class="{ 'animate-spin': loading }" /> 刷新 <RotateCcw :size="16" :class="{ 'animate-spin': loading }" /> 刷新
@@ -25,15 +25,15 @@
</div> </div>
<!-- 歌曲表格 --> <!-- 歌曲表格 -->
<div class="bg-zinc-900/10 border border-zinc-800/40 rounded-xl overflow-hidden shadow-2xl relative"> <div class="bg-zinc-50/10 border border-zinc-200 rounded-xl overflow-hidden shadow-2xl relative">
<div v-if="loading" class="absolute inset-0 bg-zinc-950/40 backdrop-blur-sm z-10 flex items-center justify-center"> <div v-if="loading" class="absolute inset-0 bg-white/80 backdrop-blur-sm z-10 flex items-center justify-center">
<div class="flex flex-col items-center gap-3"> <div class="flex flex-col items-center gap-3">
<div class="w-8 h-8 border-2 border-blue-500/20 border-t-blue-500 rounded-full animate-spin" /> <div class="w-8 h-8 border-2 border-blue-200 border-t-blue-500 rounded-full animate-spin" />
<span class="text-xs font-bold text-zinc-400 tracking-widest uppercase">正在加载...</span> <span class="text-xs font-bold text-zinc-400 tracking-widest uppercase">正在加载...</span>
</div> </div>
</div> </div>
<div class="hidden lg:grid grid-cols-12 gap-4 px-8 py-5 border-b border-zinc-800/60 bg-zinc-900/40"> <div class="hidden lg:grid grid-cols-12 gap-4 px-8 py-5 border-b border-zinc-200/60 bg-zinc-50/40">
<div class="col-span-4 text-[10px] font-black text-zinc-600 uppercase tracking-widest">歌曲信息</div> <div class="col-span-4 text-[10px] font-black text-zinc-600 uppercase tracking-widest">歌曲信息</div>
<div class="col-span-2 text-[10px] font-black text-zinc-600 uppercase tracking-widest">投稿人</div> <div class="col-span-2 text-[10px] font-black text-zinc-600 uppercase tracking-widest">投稿人</div>
<div class="col-span-3 text-[10px] font-black text-zinc-600 uppercase tracking-widest">期望排期</div> <div class="col-span-3 text-[10px] font-black text-zinc-600 uppercase tracking-widest">期望排期</div>
@@ -49,17 +49,17 @@
<div <div
v-for="song in pendingAuditSongs" v-for="song in pendingAuditSongs"
:key="song.id" :key="song.id"
class="grid grid-cols-1 lg:grid-cols-12 gap-4 px-6 lg:px-8 py-5 transition-all items-center hover:bg-zinc-800/20 group relative" class="grid grid-cols-1 lg:grid-cols-12 gap-4 px-6 lg:px-8 py-5 transition-all items-center hover:bg-zinc-100/20 group relative"
> >
<div class="col-span-12 lg:col-span-4 flex items-center gap-4"> <div class="col-span-12 lg:col-span-4 flex items-center gap-4">
<div class="w-12 h-12 rounded-xl flex items-center justify-center shrink-0 border transition-all overflow-hidden bg-zinc-800/40 border-zinc-700/30 cursor-pointer hover:opacity-80" @click.stop="playSong(song)"> <div class="w-12 h-12 rounded-xl flex items-center justify-center shrink-0 border transition-all overflow-hidden bg-zinc-100/40 border-zinc-200/30 cursor-pointer hover:opacity-80" @click.stop="playSong(song)">
<img v-if="song.cover" :src="convertToHttps(song.cover)" class="w-full h-full object-cover" referrerpolicy="no-referrer"> <img v-if="song.cover" :src="convertToHttps(song.cover)" class="w-full h-full object-cover" referrerpolicy="no-referrer">
<Music v-else :size="20" class="text-zinc-600" /> <Music v-else :size="20" class="text-zinc-600" />
</div> </div>
<div class="min-w-0"> <div class="min-w-0">
<h4 class="font-bold transition-colors flex items-center text-zinc-100 group-hover:text-blue-400"> <h4 class="font-bold transition-colors flex items-center text-zinc-900 group-hover:text-blue-700">
<span class="truncate">{{ song.title }}</span> <span class="truncate">{{ song.title }}</span>
<button v-if="song.hasSubmissionNote && song.submissionNote" class="ml-2 inline-flex items-center justify-center w-5 h-5 rounded-full border border-blue-500/30 bg-blue-500/10 text-blue-400 hover:bg-blue-500/20 transition-all" title="查看备注留言" @click.stop="openSubmissionRemark(song)"> <button v-if="song.hasSubmissionNote && song.submissionNote" class="ml-2 inline-flex items-center justify-center w-5 h-5 rounded-full border border-blue-200 bg-blue-500/10 text-blue-700 hover:bg-blue-500/20 transition-all" title="查看备注留言" @click.stop="openSubmissionRemark(song)">
<MessageSquare :size="12" /> <MessageSquare :size="12" />
</button> </button>
</h4> </h4>
@@ -80,10 +80,10 @@
</div> </div>
<div class="col-span-12 lg:col-span-3 flex items-center justify-end gap-2"> <div class="col-span-12 lg:col-span-3 flex items-center justify-end gap-2">
<button class="px-4 py-2 bg-emerald-600/10 hover:bg-emerald-600/20 text-emerald-500 hover:text-emerald-400 text-xs font-bold rounded-lg border border-emerald-500/20 transition-all" @click="auditSong(song.id, 'approved')"> <button class="px-4 py-2 bg-emerald-600/10 hover:bg-emerald-600/20 text-emerald-500 hover:text-emerald-700 text-xs font-bold rounded-lg border border-emerald-500/20 transition-all" @click="auditSong(song.id, 'approved')">
通过 通过
</button> </button>
<button class="px-4 py-2 bg-red-600/10 hover:bg-red-600/20 text-red-500 hover:text-red-400 text-xs font-bold rounded-lg border border-red-500/20 transition-all" @click="rejectSong(song.id)"> <button class="px-4 py-2 bg-red-600/10 hover:bg-red-600/20 text-red-600 hover:text-red-400 text-xs font-bold rounded-lg border border-red-500/20 transition-all" @click="rejectSong(song.id)">
未过审 (释放排期) 未过审 (释放排期)
</button> </button>
</div> </div>
@@ -93,20 +93,20 @@
<!-- 驳回歌曲对话框 --> <!-- 驳回歌曲对话框 -->
<Transition enter-active-class="transition duration-300 ease-out" enter-from-class="opacity-0" enter-to-class="opacity-100" leave-active-class="transition duration-200 ease-in" leave-from-class="opacity-100" leave-to-class="opacity-0"> <Transition enter-active-class="transition duration-300 ease-out" enter-from-class="opacity-0" enter-to-class="opacity-100" leave-active-class="transition duration-200 ease-in" leave-from-class="opacity-100" leave-to-class="opacity-0">
<div v-if="showRejectDialog" class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-zinc-950/80 backdrop-blur-sm"> <div v-if="showRejectDialog" class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-white/80 backdrop-blur-sm">
<div class="w-full max-w-lg bg-zinc-900 border border-zinc-800 rounded-xl shadow-2xl overflow-hidden" @click.stop> <div class="w-full max-w-lg bg-white border border-zinc-200 rounded-xl shadow-2xl overflow-hidden" @click.stop>
<div class="px-8 py-6 border-b border-zinc-800/50 flex items-center justify-between"> <div class="px-8 py-6 border-b border-zinc-200 flex items-center justify-between">
<h3 class="text-xl font-black text-zinc-100">未过审处理</h3> <h3 class="text-xl font-black text-zinc-900">未过审处理</h3>
<button class="text-zinc-500 hover:text-zinc-300 transition-colors" @click="showRejectDialog = false"><X :size="20" /></button> <button class="text-zinc-500 hover:text-zinc-600 transition-colors" @click="showRejectDialog = false"><X :size="20" /></button>
</div> </div>
<div class="p-8 space-y-6"> <div class="p-8 space-y-6">
<div class="p-5 bg-zinc-950 border border-zinc-800 rounded-lg"> <div class="p-5 bg-white border border-zinc-200 rounded-lg">
<p class="text-xs text-zinc-400 mb-2">此操作会将歌曲标记为未过审,并<span class="text-red-400 font-bold">立即释放其占用的排期序号</span>,供其他同学选择。歌曲不会被删除。</p> <p class="text-xs text-zinc-400 mb-2">此操作会将歌曲标记为未过审,并<span class="text-red-400 font-bold">立即释放其占用的排期序号</span>,供其他同学选择。歌曲不会被删除。</p>
<textarea v-model="rejectReason" placeholder="请输入未过审原因 (可选),将通知投稿人..." class="w-full bg-zinc-900 border border-zinc-800 rounded-lg px-4 py-3 text-sm text-zinc-200 focus:outline-none focus:border-red-500/30 min-h-[100px] resize-none transition-all" /> <textarea v-model="rejectReason" placeholder="请输入未过审原因 (可选),将通知投稿人..." class="w-full bg-white border border-zinc-200 rounded-lg px-4 py-3 text-sm text-zinc-700 focus:outline-none focus:border-red-200 min-h-[100px] resize-none transition-all" />
</div> </div>
</div> </div>
<div class="px-8 py-6 bg-zinc-900/50 border-t border-zinc-800/50 flex gap-3 justify-end"> <div class="px-8 py-6 bg-zinc-50/50 border-t border-zinc-200 flex gap-3 justify-end">
<button class="px-6 py-2.5 text-xs font-bold text-zinc-500 hover:text-zinc-300" @click="showRejectDialog = false">取消</button> <button class="px-6 py-2.5 text-xs font-bold text-zinc-500 hover:text-zinc-600" @click="showRejectDialog = false">取消</button>
<button :disabled="auditLoading" class="px-8 py-2.5 bg-red-600 hover:bg-red-500 text-white text-xs font-black rounded-lg transition-all disabled:opacity-50" @click="confirmReject"> <button :disabled="auditLoading" class="px-8 py-2.5 bg-red-600 hover:bg-red-500 text-white text-xs font-black rounded-lg transition-all disabled:opacity-50" @click="confirmReject">
{{ auditLoading ? '处理中...' : '确认释放排期' }} {{ auditLoading ? '处理中...' : '确认释放排期' }}
</button> </button>
+46 -46
View File
@@ -8,14 +8,14 @@
<Transition name="scale"> <Transition name="scale">
<div <div
v-if="show" v-if="show"
class="bg-zinc-900 border border-zinc-800 rounded-2xl shadow-2xl w-full max-w-lg overflow-hidden flex flex-col max-h-[90vh]" class="bg-white border border-zinc-200 rounded-2xl shadow-2xl w-full max-w-lg overflow-hidden flex flex-col max-h-[90vh]"
@click.stop @click.stop
> >
<!-- 头部 --> <!-- 头部 -->
<div class="flex items-center justify-between p-4 border-b border-zinc-800 shrink-0"> <div class="flex items-center justify-between p-4 border-b border-zinc-200 shrink-0">
<h3 class="text-sm font-black text-zinc-100 uppercase tracking-widest">下载歌曲</h3> <h3 class="text-sm font-black text-zinc-900 uppercase tracking-widest">下载歌曲</h3>
<button <button
class="text-zinc-500 hover:text-zinc-300 transition-colors" class="text-zinc-500 hover:text-zinc-600 transition-colors"
@click="closeDialog" @click="closeDialog"
> >
<CloseIcon class="w-5 h-5" /> <CloseIcon class="w-5 h-5" />
@@ -36,15 +36,15 @@
class="flex flex-col p-4 rounded-2xl border text-left transition-all relative overflow-hidden group" class="flex flex-col p-4 rounded-2xl border text-left transition-all relative overflow-hidden group"
:class="[ :class="[
selectedQuality === option.value selectedQuality === option.value
? 'bg-blue-600/10 border-blue-500 shadow-sm' ? 'bg-blue-50 border-blue-500 shadow-sm'
: 'bg-zinc-950 border-zinc-800 hover:border-zinc-700' : 'bg-white border-zinc-200 hover:border-zinc-200'
]" ]"
@click="selectedQuality = option.value" @click="selectedQuality = option.value"
> >
<div class="flex items-center justify-between mb-1 relative z-10"> <div class="flex items-center justify-between mb-1 relative z-10">
<span <span
class="text-xs font-bold transition-colors" class="text-xs font-bold transition-colors"
:class="selectedQuality === option.value ? 'text-blue-400' : 'text-zinc-200'" :class="selectedQuality === option.value ? 'text-blue-700' : 'text-zinc-700'"
>{{ option.label }}</span >{{ option.label }}</span
> >
<div <div
@@ -69,16 +69,16 @@
> >
</div> </div>
<div class="bg-zinc-950 border border-zinc-800 rounded-2xl p-4 space-y-4"> <div class="bg-white border border-zinc-200 rounded-2xl p-4 space-y-4">
<!-- 合并开关 --> <!-- 合并开关 -->
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex flex-col"> <div class="flex flex-col">
<span class="text-xs font-bold text-zinc-200">合并为一个文件</span> <span class="text-xs font-bold text-zinc-700">合并为一个文件</span>
<span class="text-[10px] text-zinc-500">将选中歌曲按顺序合并为单个音频</span> <span class="text-[10px] text-zinc-500">将选中歌曲按顺序合并为单个音频</span>
</div> </div>
<button <button
class="w-10 h-6 rounded-full transition-colors relative" class="w-10 h-6 rounded-full transition-colors relative"
:class="mergeSongs ? 'bg-blue-600' : 'bg-zinc-700'" :class="mergeSongs ? 'bg-blue-600' : 'bg-zinc-200'"
@click="mergeSongs = !mergeSongs" @click="mergeSongs = !mergeSongs"
> >
<div <div
@@ -90,20 +90,20 @@
<!-- 合并设置区域 --> <!-- 合并设置区域 -->
<Transition name="expand"> <Transition name="expand">
<div v-if="mergeSongs" class="space-y-4 pt-4 border-t border-zinc-800/50"> <div v-if="mergeSongs" class="space-y-4 pt-4 border-t border-zinc-200">
<!-- 导出格式选择 --> <!-- 导出格式选择 -->
<div class="space-y-2"> <div class="space-y-2">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<Music class="w-3 h-3 text-zinc-500" /> <Music class="w-3 h-3 text-zinc-500" />
<span class="text-xs font-bold text-zinc-200">导出格式</span> <span class="text-xs font-bold text-zinc-700">导出格式</span>
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
<button <button
class="flex-1 py-1.5 px-3 rounded-lg border text-[10px] font-bold transition-all" class="flex-1 py-1.5 px-3 rounded-lg border text-[10px] font-bold transition-all"
:class=" :class="
exportFormat === 'mp3' exportFormat === 'mp3'
? 'bg-blue-600/10 border-blue-500 text-blue-400' ? 'bg-blue-50 border-blue-500 text-blue-700'
: 'bg-zinc-900 border-zinc-800 text-zinc-400 hover:border-zinc-700' : 'bg-white border-zinc-200 text-zinc-400 hover:border-zinc-200'
" "
@click="exportFormat = 'mp3'" @click="exportFormat = 'mp3'"
> >
@@ -113,8 +113,8 @@
class="flex-1 py-1.5 px-3 rounded-lg border text-[10px] font-bold transition-all" class="flex-1 py-1.5 px-3 rounded-lg border text-[10px] font-bold transition-all"
:class=" :class="
exportFormat === 'wav' exportFormat === 'wav'
? 'bg-blue-600/10 border-blue-500 text-blue-400' ? 'bg-blue-50 border-blue-500 text-blue-700'
: 'bg-zinc-900 border-zinc-800 text-zinc-400 hover:border-zinc-700' : 'bg-white border-zinc-200 text-zinc-400 hover:border-zinc-200'
" "
@click="exportFormat = 'wav'" @click="exportFormat = 'wav'"
> >
@@ -128,10 +128,10 @@
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<Edit3 class="w-3 h-3 text-zinc-500" /> <Edit3 class="w-3 h-3 text-zinc-500" />
<span class="text-xs font-bold text-zinc-200">自定义文件名</span> <span class="text-xs font-bold text-zinc-700">自定义文件名</span>
</div> </div>
<button <button
class="text-[10px] text-blue-400 hover:text-blue-300 transition-colors flex items-center gap-1" class="text-[10px] text-blue-700 hover:text-blue-300 transition-colors flex items-center gap-1"
:class="{ 'opacity-50 cursor-not-allowed': !customFilename }" :class="{ 'opacity-50 cursor-not-allowed': !customFilename }"
title="保存为默认预设" title="保存为默认预设"
@click="saveFilenamePreset" @click="saveFilenamePreset"
@@ -145,19 +145,19 @@
v-model="customFilename" v-model="customFilename"
type="text" type="text"
placeholder="例如: 第XX期 - {songs}" placeholder="例如: 第XX期 - {songs}"
class="w-full bg-zinc-900 border border-zinc-800 rounded-xl px-3 py-2 text-xs text-zinc-200 focus:outline-none focus:border-blue-500/50 transition-colors pr-8" class="w-full bg-white border border-zinc-200 rounded-xl px-3 py-2 text-xs text-zinc-700 focus:outline-none focus:border-blue-500/50 transition-colors pr-8"
> >
<!-- 快速插入占位符按钮 --> <!-- 快速插入占位符按钮 -->
<div class="absolute right-2 top-1/2 -translate-y-1/2 flex gap-1"> <div class="absolute right-2 top-1/2 -translate-y-1/2 flex gap-1">
<button <button
class="px-1.5 py-0.5 rounded bg-zinc-800 text-[9px] text-zinc-400 hover:text-zinc-200 hover:bg-zinc-700 transition-colors" class="px-1.5 py-0.5 rounded bg-zinc-100 text-[9px] text-zinc-400 hover:text-zinc-700 hover:bg-zinc-200 transition-colors"
title="插入所有歌名" title="插入所有歌名"
@click="insertPlaceholder('{songs}')" @click="insertPlaceholder('{songs}')"
> >
{songs} {songs}
</button> </button>
<button <button
class="px-1.5 py-0.5 rounded bg-zinc-800 text-[9px] text-zinc-400 hover:text-zinc-200 hover:bg-zinc-700 transition-colors" class="px-1.5 py-0.5 rounded bg-zinc-100 text-[9px] text-zinc-400 hover:text-zinc-700 hover:bg-zinc-200 transition-colors"
title="插入当前日期" title="插入当前日期"
@click="insertPlaceholder('{date}')" @click="insertPlaceholder('{date}')"
> >
@@ -168,13 +168,13 @@
<p class="text-[9px] text-zinc-600"> <p class="text-[9px] text-zinc-600">
可用占位符: 可用占位符:
<code <code
class="bg-zinc-800 px-1 rounded text-zinc-400 cursor-pointer hover:text-blue-400" class="bg-zinc-100 px-1 rounded text-zinc-400 cursor-pointer hover:text-blue-700"
@click="insertPlaceholder('{songs}')" @click="insertPlaceholder('{songs}')"
>{songs}</code >{songs}</code
> >
(所有歌名), (所有歌名),
<code <code
class="bg-zinc-800 px-1 rounded text-zinc-400 cursor-pointer hover:text-blue-400" class="bg-zinc-100 px-1 rounded text-zinc-400 cursor-pointer hover:text-blue-700"
@click="insertPlaceholder('{date}')" @click="insertPlaceholder('{date}')"
>{date}</code >{date}</code
> >
@@ -184,14 +184,14 @@
<!-- 标准化选项 (仅在合并模式或需要时显示,这里允许单独使用) --> <!-- 标准化选项 (仅在合并模式或需要时显示,这里允许单独使用) -->
<div <div
class="flex items-center justify-between pt-3 border-t border-zinc-800/50" class="flex items-center justify-between pt-3 border-t border-zinc-200"
> >
<div class="flex flex-col"> <div class="flex flex-col">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="text-xs font-bold text-zinc-200">统一音量 (标准化)</span> <span class="text-xs font-bold text-zinc-700">统一音量 (标准化)</span>
<span <span
v-if="normalizeAudio" v-if="normalizeAudio"
class="text-[10px] bg-blue-500/10 text-blue-400 px-1.5 py-0.5 rounded border border-blue-500/20" class="text-[10px] bg-blue-500/10 text-blue-700 px-1.5 py-0.5 rounded border border-blue-200"
>Peak {{ targetDb }}dB</span >Peak {{ targetDb }}dB</span
> >
</div> </div>
@@ -201,7 +201,7 @@
</div> </div>
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<button <button
class="text-[10px] text-blue-400 hover:text-blue-300 transition-colors flex items-center gap-1" class="text-[10px] text-blue-700 hover:text-blue-300 transition-colors flex items-center gap-1"
title="保存当前音量设置(包括开启状态)为默认" title="保存当前音量设置(包括开启状态)为默认"
@click="saveDbPreset" @click="saveDbPreset"
> >
@@ -210,7 +210,7 @@
</button> </button>
<button <button
class="w-10 h-6 rounded-full transition-colors relative" class="w-10 h-6 rounded-full transition-colors relative"
:class="normalizeAudio ? 'bg-blue-600' : 'bg-zinc-700'" :class="normalizeAudio ? 'bg-blue-600' : 'bg-zinc-200'"
@click="normalizeAudio = !normalizeAudio" @click="normalizeAudio = !normalizeAudio"
> >
<div <div
@@ -232,9 +232,9 @@
min="-10" min="-10"
max="0" max="0"
step="0.5" step="0.5"
class="flex-1 h-1.5 bg-zinc-800 rounded-lg appearance-none cursor-pointer [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-blue-500" class="flex-1 h-1.5 bg-zinc-100 rounded-lg appearance-none cursor-pointer [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-blue-500"
> >
<span class="text-xs font-mono text-zinc-300 w-12 text-right" <span class="text-xs font-mono text-zinc-600 w-12 text-right"
>{{ targetDb }} dB</span >{{ targetDb }} dB</span
> >
</div> </div>
@@ -255,7 +255,7 @@
> >
<div <div
v-if="estimatedTotalDuration.count > 0" v-if="estimatedTotalDuration.count > 0"
class="flex items-center gap-1.5 text-[10px] text-blue-400 font-bold bg-blue-500/10 px-2 py-0.5 rounded-full border border-blue-500/20" class="flex items-center gap-1.5 text-[10px] text-blue-700 font-bold bg-blue-500/10 px-2 py-0.5 rounded-full border border-blue-200"
> >
<Clock class="w-3 h-3" /> <Clock class="w-3 h-3" />
<span>预估总时长: {{ formatDuration(estimatedTotalDuration.total) }}</span> <span>预估总时长: {{ formatDuration(estimatedTotalDuration.total) }}</span>
@@ -264,7 +264,7 @@
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<button <button
v-if="selectedSongs.size > 0" v-if="selectedSongs.size > 0"
class="text-[10px] font-bold text-zinc-500 hover:text-zinc-300 transition-colors flex items-center gap-1" class="text-[10px] font-bold text-zinc-500 hover:text-zinc-600 transition-colors flex items-center gap-1"
title="预下载选中歌曲到浏览器缓存" title="预下载选中歌曲到浏览器缓存"
@click="preloadSelectedSongs" @click="preloadSelectedSongs"
> >
@@ -272,7 +272,7 @@
预下载选中 预下载选中
</button> </button>
<button <button
class="text-[10px] font-bold text-blue-500/80 hover:text-blue-400 transition-colors" class="text-[10px] font-bold text-blue-500/80 hover:text-blue-700 transition-colors"
@click="toggleSelectAll" @click="toggleSelectAll"
> >
{{ isAllSelected ? '取消全选' : '全选' }} {{ isAllSelected ? '取消全选' : '全选' }}
@@ -281,12 +281,12 @@
</div> </div>
<div <div
class="bg-zinc-950/50 border border-zinc-800/50 rounded-2xl overflow-hidden max-h-[300px] overflow-y-auto custom-scrollbar" class="bg-white/70 border border-zinc-200 rounded-2xl overflow-hidden max-h-[300px] overflow-y-auto custom-scrollbar"
> >
<div <div
v-for="song in songs" v-for="song in songs"
:key="song.id" :key="song.id"
class="w-full flex items-center gap-3 p-3.5 hover:bg-zinc-800/30 transition-all text-left border-b border-zinc-800/30 last:border-0 group relative" class="w-full flex items-center gap-3 p-3.5 hover:bg-zinc-100/30 transition-all text-left border-b border-zinc-200/30 last:border-0 group relative"
> >
<!-- 预下载进度条背景 --> <!-- 预下载进度条背景 -->
<div <div
@@ -310,7 +310,7 @@
:class="[ :class="[
selectedSongs.has(song.song.id) selectedSongs.has(song.song.id)
? 'bg-blue-600 border-blue-600 shadow-sm' ? 'bg-blue-600 border-blue-600 shadow-sm'
: 'bg-zinc-900 border-zinc-800 group-hover:border-zinc-700' : 'bg-white border-zinc-200 group-hover:border-zinc-200'
]" ]"
@click="toggleSongSelection(song.song.id)" @click="toggleSongSelection(song.song.id)"
> >
@@ -326,7 +326,7 @@
@click="toggleSongSelection(song.song.id)" @click="toggleSongSelection(song.song.id)"
> >
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<p class="text-xs font-bold text-zinc-300 truncate">{{ song.song.title }}</p> <p class="text-xs font-bold text-zinc-600 truncate">{{ song.song.title }}</p>
<!-- 预下载标记 --> <!-- 预下载标记 -->
<div <div
v-if=" v-if="
@@ -360,7 +360,7 @@
</button> </button>
<button <button
v-else v-else
class="p-1.5 rounded-lg hover:bg-blue-500/10 text-zinc-600 hover:text-blue-400 transition-colors" class="p-1.5 rounded-lg hover:bg-blue-500/10 text-zinc-600 hover:text-blue-700 transition-colors"
title="预下载此歌曲" title="预下载此歌曲"
@click.stop="preloadSong(song.song)" @click.stop="preloadSong(song.song)"
> >
@@ -378,16 +378,16 @@
<!-- 进度条 --> <!-- 进度条 -->
<section <section
v-if="downloading || downloadedCount > 0" v-if="downloading || downloadedCount > 0"
class="space-y-3 pt-4 border-t border-zinc-800/50" class="space-y-3 pt-4 border-t border-zinc-200"
> >
<div <div
class="flex items-center justify-between text-[10px] font-bold uppercase tracking-wider" class="flex items-center justify-between text-[10px] font-bold uppercase tracking-wider"
> >
<span class="text-zinc-400">{{ mergeSongs ? '处理进度' : '下载进度' }}</span> <span class="text-zinc-400">{{ mergeSongs ? '处理进度' : '下载进度' }}</span>
<span class="text-blue-400">{{ downloadedCount }} / {{ totalCount }}</span> <span class="text-blue-700">{{ downloadedCount }} / {{ totalCount }}</span>
</div> </div>
<div <div
class="h-2 bg-zinc-950 rounded-full overflow-hidden border border-zinc-800/50 relative" class="h-2 bg-white rounded-full overflow-hidden border border-zinc-200 relative"
> >
<div <div
class="h-full bg-gradient-to-r from-blue-600 to-indigo-500 transition-all duration-300 ease-out relative overflow-hidden" class="h-full bg-gradient-to-r from-blue-600 to-indigo-500 transition-all duration-300 ease-out relative overflow-hidden"
@@ -402,7 +402,7 @@
</div> </div>
<div class="text-[10px] text-zinc-500 truncate"> <div class="text-[10px] text-zinc-500 truncate">
<template v-if="downloading"> <template v-if="downloading">
<span v-if="processingStatus" class="text-blue-400 animate-pulse">{{ <span v-if="processingStatus" class="text-blue-700 animate-pulse">{{
processingStatus processingStatus
}}</span> }}</span>
<span v-else>{{ <span v-else>{{
@@ -438,14 +438,14 @@
<!-- 底部按钮 --> <!-- 底部按钮 -->
<div <div
class="p-4 border-t border-zinc-800 bg-zinc-900/50 flex items-center justify-between shrink-0" class="p-4 border-t border-zinc-200 bg-zinc-50/50 flex items-center justify-between shrink-0"
> >
<div class="text-[11px] font-black text-zinc-500 uppercase tracking-widest"> <div class="text-[11px] font-black text-zinc-500 uppercase tracking-widest">
已选择 <span class="text-blue-400">{{ selectedSongs.size }}</span> 首歌曲 已选择 <span class="text-blue-700">{{ selectedSongs.size }}</span> 首歌曲
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<button <button
class="px-4 py-2 text-xs font-bold text-zinc-500 hover:text-zinc-300 transition-colors uppercase tracking-wider" class="px-4 py-2 text-xs font-bold text-zinc-500 hover:text-zinc-600 transition-colors uppercase tracking-wider"
:disabled="downloading" :disabled="downloading"
@click="closeDialog" @click="closeDialog"
> >
@@ -453,7 +453,7 @@
</button> </button>
<button <button
v-if="!downloading && downloadedCount > 0" v-if="!downloading && downloadedCount > 0"
class="px-6 py-2.5 bg-zinc-800 hover:bg-zinc-700 text-zinc-200 text-xs font-bold rounded-xl border border-zinc-700 transition-all uppercase tracking-wider" class="px-6 py-2.5 bg-zinc-100 hover:bg-zinc-200 text-zinc-700 text-xs font-bold rounded-xl border border-zinc-200 transition-all uppercase tracking-wider"
@click="closeDialog" @click="closeDialog"
> >
关闭 关闭
+81 -81
View File
@@ -4,13 +4,13 @@
<div class="space-y-6"> <div class="space-y-6">
<div class="flex flex-col md:flex-row md:items-end justify-between gap-6 px-1"> <div class="flex flex-col md:flex-row md:items-end justify-between gap-6 px-1">
<div class="space-y-1"> <div class="space-y-1">
<h2 class="text-3xl font-black text-zinc-100 tracking-tight">歌曲管理</h2> <h2 class="text-3xl font-black text-zinc-900 tracking-tight">歌曲管理</h2>
<div class="flex items-center gap-6 mt-4"> <div class="flex items-center gap-6 mt-4">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="text-[10px] font-black text-zinc-600 uppercase tracking-widest" <span class="text-[10px] font-black text-zinc-600 uppercase tracking-widest"
>总计</span >总计</span
> >
<span class="text-sm font-bold text-zinc-300">{{ filteredSongs.length }}</span> <span class="text-sm font-bold text-zinc-600">{{ filteredSongs.length }}</span>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="text-[10px] font-black text-zinc-600 uppercase tracking-widest" <span class="text-[10px] font-black text-zinc-600 uppercase tracking-widest"
@@ -39,10 +39,10 @@
> >
<div <div
v-if="selectedSongs.length > 0" v-if="selectedSongs.length > 0"
class="flex items-center gap-2 p-1.5 bg-zinc-900/90 border border-zinc-800 rounded-lg backdrop-blur-xl shadow-2xl" class="flex items-center gap-2 p-1.5 bg-zinc-50/90 border border-zinc-200 rounded-lg backdrop-blur-xl shadow-2xl"
> >
<button <button
class="flex items-center gap-2 px-4 py-2 bg-zinc-800 hover:bg-zinc-700 text-emerald-400 text-[11px] font-black rounded-lg border border-zinc-700/50 transition-all active:scale-95 uppercase tracking-widest" class="flex items-center gap-2 px-4 py-2 bg-zinc-100 hover:bg-zinc-200 text-emerald-700 text-[11px] font-black rounded-lg border border-zinc-200 transition-all active:scale-95 uppercase tracking-widest"
@click="openDownloadDialog" @click="openDownloadDialog"
> >
<Download :size="14" class="text-emerald-500/70" /> 下载 ({{ <Download :size="14" class="text-emerald-500/70" /> 下载 ({{
@@ -50,25 +50,25 @@
}}) }})
</button> </button>
<button <button
class="flex items-center gap-2 px-4 py-2 bg-zinc-800 hover:bg-zinc-700 text-emerald-400 text-[11px] font-black rounded-lg border border-zinc-700/50 transition-all active:scale-95 uppercase tracking-widest" class="flex items-center gap-2 px-4 py-2 bg-zinc-100 hover:bg-zinc-200 text-emerald-700 text-[11px] font-black rounded-lg border border-zinc-200 transition-all active:scale-95 uppercase tracking-widest"
@click="batchMarkPlayed" @click="batchMarkPlayed"
> >
<Check :size="14" class="text-emerald-500/70" /> 已播放 ({{ selectedSongs.length }}) <Check :size="14" class="text-emerald-500/70" /> 已播放 ({{ selectedSongs.length }})
</button> </button>
<button <button
class="flex items-center gap-2 px-4 py-2 bg-zinc-800 hover:bg-zinc-700 text-blue-400 text-[11px] font-black rounded-lg border border-zinc-700/50 transition-all active:scale-95 uppercase tracking-widest" class="flex items-center gap-2 px-4 py-2 bg-zinc-100 hover:bg-zinc-200 text-blue-700 text-[11px] font-black rounded-lg border border-zinc-200 transition-all active:scale-95 uppercase tracking-widest"
@click="batchMarkUnplayed" @click="batchMarkUnplayed"
> >
<RotateCcw :size="14" class="text-blue-500/70" /> 未播放 ({{ selectedSongs.length }}) <RotateCcw :size="14" class="text-blue-500/70" /> 未播放 ({{ selectedSongs.length }})
</button> </button>
<button <button
class="flex items-center gap-2 px-4 py-2 bg-zinc-800 hover:bg-zinc-700 text-amber-400 text-[11px] font-black rounded-lg border border-zinc-700/50 transition-all active:scale-95 uppercase tracking-widest" class="flex items-center gap-2 px-4 py-2 bg-zinc-100 hover:bg-zinc-200 text-amber-700 text-[11px] font-black rounded-lg border border-zinc-200 transition-all active:scale-95 uppercase tracking-widest"
@click="batchReject" @click="batchReject"
> >
<X :size="14" class="text-amber-500/70" /> 驳回 ({{ selectedSongs.length }}) <X :size="14" class="text-amber-500/70" /> 驳回 ({{ selectedSongs.length }})
</button> </button>
<button <button
class="flex items-center gap-2 px-4 py-2 bg-zinc-800 hover:bg-zinc-700 text-red-400 text-[11px] font-black rounded-lg border border-zinc-700/50 transition-all active:scale-95 uppercase tracking-widest" class="flex items-center gap-2 px-4 py-2 bg-zinc-100 hover:bg-zinc-200 text-red-400 text-[11px] font-black rounded-lg border border-zinc-200 transition-all active:scale-95 uppercase tracking-widest"
@click="batchDelete" @click="batchDelete"
> >
<Trash2 :size="14" class="text-red-500/70" /> 删除 ({{ selectedSongs.length }}) <Trash2 :size="14" class="text-red-500/70" /> 删除 ({{ selectedSongs.length }})
@@ -77,14 +77,14 @@
</Transition> </Transition>
<button <button
class="flex items-center gap-2 px-5 py-2.5 bg-zinc-900 border border-zinc-800 hover:border-zinc-700 text-zinc-300 text-xs font-bold rounded-lg transition-all active:scale-95" class="flex items-center gap-2 px-5 py-2.5 bg-white border border-zinc-200 hover:border-zinc-200 text-zinc-600 text-xs font-bold rounded-lg transition-all active:scale-95"
@click="openAddSongModal" @click="openAddSongModal"
> >
<Plus :size="16" /> 手动添加 <Plus :size="16" /> 手动添加
</button> </button>
<button <button
:disabled="loading" :disabled="loading"
class="flex items-center gap-2 px-5 py-2.5 bg-blue-600/10 hover:bg-blue-600/20 text-blue-400 text-xs font-bold rounded-lg border border-blue-500/20 transition-all disabled:opacity-50" class="flex items-center gap-2 px-5 py-2.5 bg-blue-50 hover:bg-blue-600/20 text-blue-700 text-xs font-bold rounded-lg border border-blue-200 transition-all disabled:opacity-50"
@click="refreshSongs(true)" @click="refreshSongs(true)"
> >
<RotateCcw :size="16" :class="{ 'animate-spin': loading }" /> 刷新 <RotateCcw :size="16" :class="{ 'animate-spin': loading }" /> 刷新
@@ -94,7 +94,7 @@
<!-- 过滤器栏 --> <!-- 过滤器栏 -->
<div <div
class="bg-zinc-900/30 border border-zinc-800/60 rounded-xl p-4 flex flex-col lg:flex-row gap-4 items-stretch lg:items-center" class="bg-zinc-50/30 border border-zinc-200/60 rounded-xl p-4 flex flex-col lg:flex-row gap-4 items-stretch lg:items-center"
> >
<div class="relative flex-1 group w-full lg:w-auto"> <div class="relative flex-1 group w-full lg:w-auto">
<Search <Search
@@ -105,7 +105,7 @@
v-model="searchQuery" v-model="searchQuery"
type="text" type="text"
placeholder="搜索歌曲标题、艺术家或投稿人..." placeholder="搜索歌曲标题、艺术家或投稿人..."
class="w-full bg-zinc-950/50 border border-zinc-800/80 rounded-lg pl-12 pr-4 py-3 text-sm focus:outline-none focus:border-blue-500/30 transition-all placeholder:text-zinc-800 text-zinc-200" class="w-full bg-white/70 border border-zinc-200/80 rounded-lg pl-12 pr-4 py-3 text-sm focus:outline-none focus:border-blue-200 transition-all placeholder:text-zinc-500 text-zinc-700"
> >
</div> </div>
<div class="grid grid-cols-2 md:grid-cols-3 lg:flex lg:items-center gap-3 w-full lg:w-auto"> <div class="grid grid-cols-2 md:grid-cols-3 lg:flex lg:items-center gap-3 w-full lg:w-auto">
@@ -138,16 +138,16 @@
<!-- 歌曲表格 --> <!-- 歌曲表格 -->
<div <div
class="bg-zinc-900/10 border border-zinc-800/40 rounded-xl overflow-hidden shadow-2xl relative" class="bg-zinc-50/10 border border-zinc-200 rounded-xl overflow-hidden shadow-2xl relative"
> >
<!-- 加载遮罩 --> <!-- 加载遮罩 -->
<div <div
v-if="loading" v-if="loading"
class="absolute inset-0 bg-zinc-950/40 backdrop-blur-sm z-10 flex items-center justify-center" class="absolute inset-0 bg-white/80 backdrop-blur-sm z-10 flex items-center justify-center"
> >
<div class="flex flex-col items-center gap-3"> <div class="flex flex-col items-center gap-3">
<div <div
class="w-8 h-8 border-2 border-blue-500/20 border-t-blue-500 rounded-full animate-spin" class="w-8 h-8 border-2 border-blue-200 border-t-blue-500 rounded-full animate-spin"
/> />
<span class="text-xs font-bold text-zinc-400 tracking-widest uppercase">正在加载...</span> <span class="text-xs font-bold text-zinc-400 tracking-widest uppercase">正在加载...</span>
</div> </div>
@@ -155,13 +155,13 @@
<!-- 表头 --> <!-- 表头 -->
<div <div
class="hidden lg:grid grid-cols-12 gap-4 px-8 py-5 border-b border-zinc-800/60 bg-zinc-900/40" class="hidden lg:grid grid-cols-12 gap-4 px-8 py-5 border-b border-zinc-200/60 bg-zinc-50/40"
> >
<div class="col-span-1 flex items-center"> <div class="col-span-1 flex items-center">
<input <input
type="checkbox" type="checkbox"
:checked="isAllSelected" :checked="isAllSelected"
class="w-5 h-5 rounded-lg border-2 border-zinc-800 bg-zinc-950 accent-blue-600 transition-all cursor-pointer" class="w-5 h-5 rounded-lg border-2 border-zinc-200 bg-white accent-blue-600 transition-all cursor-pointer"
@change="toggleSelectAll" @change="toggleSelectAll"
> >
</div> </div>
@@ -204,14 +204,14 @@
:key="song.id" :key="song.id"
:class="[ :class="[
'grid grid-cols-1 lg:grid-cols-12 gap-4 px-6 lg:px-8 py-5 transition-all items-center group relative', 'grid grid-cols-1 lg:grid-cols-12 gap-4 px-6 lg:px-8 py-5 transition-all items-center group relative',
selectedSongs.includes(song.id) ? 'bg-blue-600/5' : 'hover:bg-zinc-800/20' selectedSongs.includes(song.id) ? 'bg-blue-600/5' : 'hover:bg-zinc-100/20'
]" ]"
> >
<div class="hidden lg:flex col-span-1 items-center"> <div class="hidden lg:flex col-span-1 items-center">
<input <input
type="checkbox" type="checkbox"
:checked="selectedSongs.includes(song.id)" :checked="selectedSongs.includes(song.id)"
class="w-5 h-5 rounded-lg border-2 border-zinc-800 bg-zinc-950 accent-blue-600 transition-all cursor-pointer" class="w-5 h-5 rounded-lg border-2 border-zinc-200 bg-white accent-blue-600 transition-all cursor-pointer"
@change="toggleSelectSong(song.id)" @change="toggleSelectSong(song.id)"
> >
</div> </div>
@@ -219,7 +219,7 @@
<div class="col-span-12 lg:col-span-4 flex items-center gap-4"> <div class="col-span-12 lg:col-span-4 flex items-center gap-4">
<div <div
:class="[ :class="[
'w-12 h-12 rounded-xl flex items-center justify-center shrink-0 border transition-all overflow-hidden bg-zinc-800/40 border-zinc-700/30 group-hover:border-zinc-600 cursor-pointer hover:opacity-80', 'w-12 h-12 rounded-xl flex items-center justify-center shrink-0 border transition-all overflow-hidden bg-zinc-100/40 border-zinc-200/30 group-hover:border-zinc-300 cursor-pointer hover:opacity-80',
selectedSongs.includes(song.id) ? 'border-blue-500/50' : '' selectedSongs.includes(song.id) ? 'border-blue-500/50' : ''
]" ]"
@click.stop="playSong(song)" @click.stop="playSong(song)"
@@ -237,8 +237,8 @@
:class="[ :class="[
'font-bold transition-colors flex items-center', 'font-bold transition-colors flex items-center',
selectedSongs.includes(song.id) selectedSongs.includes(song.id)
? 'text-blue-400' ? 'text-blue-700'
: 'text-zinc-100 group-hover:text-blue-400' : 'text-zinc-900 group-hover:text-blue-700'
]" ]"
> >
<span <span
@@ -250,7 +250,7 @@
<span v-else class="truncate">{{ song.title }}</span> <span v-else class="truncate">{{ song.title }}</span>
<button <button
v-if="song.hasSubmissionNote && song.submissionNote" v-if="song.hasSubmissionNote && song.submissionNote"
class="ml-2 inline-flex items-center justify-center w-5 h-5 rounded-full border border-blue-500/30 bg-blue-500/10 text-blue-400 hover:bg-blue-500/20 transition-all" class="ml-2 inline-flex items-center justify-center w-5 h-5 rounded-full border border-blue-200 bg-blue-500/10 text-blue-700 hover:bg-blue-500/20 transition-all"
title="查看备注留言" title="查看备注留言"
@click.stop="openSubmissionRemark(song)" @click.stop="openSubmissionRemark(song)"
> >
@@ -261,7 +261,7 @@
<!-- 期望播出日期标签 --> <!-- 期望播出日期标签 -->
<div v-if="song.expectedPlayDate" class="mt-1 flex items-center"> <div v-if="song.expectedPlayDate" class="mt-1 flex items-center">
<span class="inline-flex items-center gap-1 px-1.5 py-0.5 bg-blue-500/10 text-blue-400 rounded border border-blue-500/20 text-[10px] font-medium"> <span class="inline-flex items-center gap-1 px-1.5 py-0.5 bg-blue-500/10 text-blue-700 rounded border border-blue-200 text-[10px] font-medium">
<CalendarDays :size="10" /> <CalendarDays :size="10" />
期望: {{ song.expectedPlayDate }} 期望: {{ song.expectedPlayDate }}
<template v-if="song.expectedPlaySequence"> <template v-if="song.expectedPlaySequence">
@@ -295,7 +295,7 @@
'inline-flex items-center gap-1.5 text-xs font-bold px-2 py-1 rounded-lg transition-all', 'inline-flex items-center gap-1.5 text-xs font-bold px-2 py-1 rounded-lg transition-all',
song.voteCount > 0 song.voteCount > 0
? 'bg-pink-500/10 text-pink-500 hover:bg-pink-500/20 cursor-pointer' ? 'bg-pink-500/10 text-pink-500 hover:bg-pink-500/20 cursor-pointer'
: 'bg-zinc-950/30 text-zinc-700 cursor-default' : 'bg-white/60 text-zinc-700 cursor-default'
]" ]"
@click="showVoters(song.id)" @click="showVoters(song.id)"
> >
@@ -312,7 +312,7 @@
? 'bg-emerald-500/10 text-emerald-500' ? 'bg-emerald-500/10 text-emerald-500'
: song.scheduled : song.scheduled
? 'bg-blue-500/10 text-blue-500' ? 'bg-blue-500/10 text-blue-500'
: 'bg-zinc-800 text-zinc-500' : 'bg-zinc-100 text-zinc-500'
]" ]"
> >
{{ getStatusText(song) }} {{ getStatusText(song) }}
@@ -323,7 +323,7 @@
class="col-span-12 lg:col-span-2 flex items-center justify-end gap-2 opacity-100 lg:opacity-0 group-hover:opacity-100 transition-all" class="col-span-12 lg:col-span-2 flex items-center justify-end gap-2 opacity-100 lg:opacity-0 group-hover:opacity-100 transition-all"
> >
<button <button
class="p-2 bg-zinc-800/50 text-blue-400 hover:bg-blue-600 hover:text-white rounded-xl transition-all border border-zinc-700/30" class="p-2 bg-zinc-100/50 text-blue-700 hover:bg-blue-600 hover:text-zinc-900 rounded-xl transition-all border border-zinc-200/30"
title="编辑歌曲" title="编辑歌曲"
@click="editSong(song)" @click="editSong(song)"
> >
@@ -332,7 +332,7 @@
<button <button
v-if="!song.played" v-if="!song.played"
class="p-2 bg-zinc-800/50 text-emerald-400 hover:bg-emerald-600 hover:text-white rounded-xl transition-all border border-zinc-700/30" class="p-2 bg-zinc-100/50 text-emerald-700 hover:bg-emerald-600 hover:text-zinc-900 rounded-xl transition-all border border-zinc-200/30"
title="标记为已播放" title="标记为已播放"
@click="markAsPlayed(song.id)" @click="markAsPlayed(song.id)"
> >
@@ -340,7 +340,7 @@
</button> </button>
<button <button
v-else v-else
class="p-2 bg-zinc-800/50 text-zinc-400 hover:bg-zinc-600 hover:text-white rounded-xl transition-all border border-zinc-700/30" class="p-2 bg-zinc-100/50 text-zinc-400 hover:bg-zinc-600 hover:text-zinc-900 rounded-xl transition-all border border-zinc-200/30"
title="标记为未播放" title="标记为未播放"
@click="markAsUnplayed(song.id)" @click="markAsUnplayed(song.id)"
> >
@@ -348,14 +348,14 @@
</button> </button>
<button <button
class="p-2 bg-zinc-800/50 text-amber-500 hover:bg-amber-600 hover:text-white rounded-xl transition-all border border-zinc-700/30" class="p-2 bg-zinc-100/50 text-amber-500 hover:bg-amber-600 hover:text-zinc-900 rounded-xl transition-all border border-zinc-200/30"
title="驳回歌曲" title="驳回歌曲"
@click="rejectSong(song.id)" @click="rejectSong(song.id)"
> >
<X :size="14" /> <X :size="14" />
</button> </button>
<button <button
class="p-2 bg-zinc-800/50 text-red-400 hover:bg-red-600 hover:text-white rounded-xl transition-all border border-zinc-700/30" class="p-2 bg-zinc-100/50 text-red-400 hover:bg-red-600 hover:text-zinc-900 rounded-xl transition-all border border-zinc-200/30"
title="删除歌曲" title="删除歌曲"
@click="deleteSong(song.id)" @click="deleteSong(song.id)"
> >
@@ -407,16 +407,16 @@
> >
<div <div
v-if="showRejectDialog" v-if="showRejectDialog"
class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-zinc-950/80 backdrop-blur-sm" class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-white/80 backdrop-blur-sm"
> >
<div <div
class="w-full max-w-lg bg-zinc-900 border border-zinc-800 rounded-xl shadow-2xl overflow-hidden" class="w-full max-w-lg bg-white border border-zinc-200 rounded-xl shadow-2xl overflow-hidden"
@click.stop @click.stop
> >
<div class="px-8 py-6 border-b border-zinc-800/50 flex items-center justify-between"> <div class="px-8 py-6 border-b border-zinc-200 flex items-center justify-between">
<h3 class="text-xl font-black text-zinc-100">{{ isBatchReject ? `批量驳回歌曲 (${selectedSongs.length} 首)` : '驳回歌曲' }}</h3> <h3 class="text-xl font-black text-zinc-900">{{ isBatchReject ? `批量驳回歌曲 (${selectedSongs.length} 首)` : '驳回歌曲' }}</h3>
<button <button
class="text-zinc-500 hover:text-zinc-300 transition-colors" class="text-zinc-500 hover:text-zinc-600 transition-colors"
@click="cancelReject" @click="cancelReject"
> >
<X :size="20" /> <X :size="20" />
@@ -424,25 +424,25 @@
</div> </div>
<div class="p-8 space-y-6"> <div class="p-8 space-y-6">
<div v-if="!isBatchReject" class="p-5 bg-zinc-950 border border-zinc-800 rounded-lg flex items-center gap-4"> <div v-if="!isBatchReject" class="p-5 bg-white border border-zinc-200 rounded-lg flex items-center gap-4">
<div <div
class="w-10 h-10 rounded-lg bg-red-500/10 text-red-500 flex items-center justify-center border border-red-500/10" class="w-10 h-10 rounded-lg bg-red-500/10 text-red-500 flex items-center justify-center border border-red-500/10"
> >
<Music :size="18" /> <Music :size="18" />
</div> </div>
<div> <div>
<h4 class="font-bold text-zinc-100 text-sm">{{ rejectSongInfo.title }}</h4> <h4 class="font-bold text-zinc-900 text-sm">{{ rejectSongInfo.title }}</h4>
<p class="text-xs text-zinc-500">投稿人: {{ rejectSongInfo.requester }}</p> <p class="text-xs text-zinc-500">投稿人: {{ rejectSongInfo.requester }}</p>
</div> </div>
</div> </div>
<div v-else class="p-5 bg-zinc-950 border border-zinc-800 rounded-lg flex items-center gap-4"> <div v-else class="p-5 bg-white border border-zinc-200 rounded-lg flex items-center gap-4">
<div <div
class="w-10 h-10 rounded-lg bg-amber-500/10 text-amber-500 flex items-center justify-center border border-amber-500/10" class="w-10 h-10 rounded-lg bg-amber-500/10 text-amber-500 flex items-center justify-center border border-amber-500/10"
> >
<ListMusic :size="18" /> <ListMusic :size="18" />
</div> </div>
<div> <div>
<h4 class="font-bold text-zinc-100 text-sm">即将批量驳回 {{ selectedSongs.length }} 首歌曲</h4> <h4 class="font-bold text-zinc-900 text-sm">即将批量驳回 {{ selectedSongs.length }} 首歌曲</h4>
<p class="text-xs text-zinc-500">驳回后将一并清理投票、排期记录并发送通知给对应投稿人</p> <p class="text-xs text-zinc-500">驳回后将一并清理投票、排期记录并发送通知给对应投稿人</p>
</div> </div>
</div> </div>
@@ -454,7 +454,7 @@
<textarea <textarea
v-model="rejectReason" v-model="rejectReason"
placeholder="请输入驳回原因,将通过系统通知发送给投稿人..." placeholder="请输入驳回原因,将通过系统通知发送给投稿人..."
class="w-full bg-zinc-950 border border-zinc-800 rounded-lg px-4 py-4 text-sm text-zinc-200 focus:outline-none focus:border-red-500/30 min-h-[120px] resize-none transition-all" class="w-full bg-white border border-zinc-200 rounded-lg px-4 py-4 text-sm text-zinc-700 focus:outline-none focus:border-red-200 min-h-[120px] resize-none transition-all"
/> />
</div> </div>
@@ -462,11 +462,11 @@
<input <input
v-model="addToBlacklist" v-model="addToBlacklist"
type="checkbox" type="checkbox"
class="w-4 h-4 rounded border-zinc-800 bg-zinc-950 accent-red-600" class="w-4 h-4 rounded border-zinc-200 bg-white accent-red-600"
> >
<div> <div>
<span <span
class="text-xs font-bold text-zinc-300 group-hover:text-red-400 transition-colors" class="text-xs font-bold text-zinc-600 group-hover:text-red-400 transition-colors"
>同时将此歌曲加入黑名单</span >同时将此歌曲加入黑名单</span
> >
<p class="text-[10px] text-zinc-600 font-medium"> <p class="text-[10px] text-zinc-600 font-medium">
@@ -476,9 +476,9 @@
</label> </label>
</div> </div>
<div class="px-8 py-6 bg-zinc-900/50 border-t border-zinc-800/50 flex gap-3 justify-end"> <div class="px-8 py-6 bg-zinc-50/50 border-t border-zinc-200 flex gap-3 justify-end">
<button <button
class="px-6 py-2.5 text-xs font-bold text-zinc-500 hover:text-zinc-300" class="px-6 py-2.5 text-xs font-bold text-zinc-500 hover:text-zinc-600"
@click="cancelReject" @click="cancelReject"
> >
取消 取消
@@ -516,18 +516,18 @@
> >
<div <div
v-if="showEditModal || showAddSongModal" v-if="showEditModal || showAddSongModal"
class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-zinc-950/80 backdrop-blur-sm overflow-y-auto" class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-white/80 backdrop-blur-sm overflow-y-auto"
> >
<div <div
class="w-full max-w-2xl bg-zinc-900 border border-zinc-800 rounded-xl shadow-2xl overflow-hidden my-auto" class="w-full max-w-2xl bg-white border border-zinc-200 rounded-xl shadow-2xl overflow-hidden my-auto"
@click.stop @click.stop
> >
<div class="px-8 py-6 border-b border-zinc-800/50 flex items-center justify-between"> <div class="px-8 py-6 border-b border-zinc-200 flex items-center justify-between">
<h3 class="text-xl font-black text-zinc-100"> <h3 class="text-xl font-black text-zinc-900">
{{ showEditModal ? '编辑歌曲' : '手动添加歌曲' }} {{ showEditModal ? '编辑歌曲' : '手动添加歌曲' }}
</h3> </h3>
<button <button
class="text-zinc-500 hover:text-zinc-300 transition-colors" class="text-zinc-500 hover:text-zinc-600 transition-colors"
@click="showEditModal ? cancelEditSong() : cancelAddSong()" @click="showEditModal ? cancelEditSong() : cancelAddSong()"
> >
<X :size="20" /> <X :size="20" />
@@ -545,14 +545,14 @@
v-model="editForm.title" v-model="editForm.title"
type="text" type="text"
placeholder="输入歌曲标题" placeholder="输入歌曲标题"
class="w-full bg-zinc-950 border border-zinc-800 rounded-lg px-4 py-3 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30 transition-all" class="w-full bg-white border border-zinc-200 rounded-lg px-4 py-3 text-sm text-zinc-700 focus:outline-none focus:border-blue-200 transition-all"
> >
<input <input
v-else v-else
v-model="addForm.title" v-model="addForm.title"
type="text" type="text"
placeholder="输入歌曲标题" placeholder="输入歌曲标题"
class="w-full bg-zinc-950 border border-zinc-800 rounded-lg px-4 py-3 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30 transition-all" class="w-full bg-white border border-zinc-200 rounded-lg px-4 py-3 text-sm text-zinc-700 focus:outline-none focus:border-blue-200 transition-all"
> >
</div> </div>
<div class="space-y-2"> <div class="space-y-2">
@@ -564,14 +564,14 @@
v-model="editForm.artist" v-model="editForm.artist"
type="text" type="text"
placeholder="输入歌手姓名" placeholder="输入歌手姓名"
class="w-full bg-zinc-950 border border-zinc-800 rounded-lg px-4 py-3 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30 transition-all" class="w-full bg-white border border-zinc-200 rounded-lg px-4 py-3 text-sm text-zinc-700 focus:outline-none focus:border-blue-200 transition-all"
> >
<input <input
v-else v-else
v-model="addForm.artist" v-model="addForm.artist"
type="text" type="text"
placeholder="输入歌手姓名" placeholder="输入歌手姓名"
class="w-full bg-zinc-950 border border-zinc-800 rounded-lg px-4 py-3 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30 transition-all" class="w-full bg-white border border-zinc-200 rounded-lg px-4 py-3 text-sm text-zinc-700 focus:outline-none focus:border-blue-200 transition-all"
> >
</div> </div>
</div> </div>
@@ -587,7 +587,7 @@
v-model="editUserSearchQuery" v-model="editUserSearchQuery"
type="text" type="text"
placeholder="搜索用户姓名或用户名" placeholder="搜索用户姓名或用户名"
class="w-full bg-zinc-950 border border-zinc-800 rounded-lg px-4 py-3 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30 transition-all" class="w-full bg-white border border-zinc-200 rounded-lg px-4 py-3 text-sm text-zinc-700 focus:outline-none focus:border-blue-200 transition-all"
@focus="showEditUserDropdown = true" @focus="showEditUserDropdown = true"
@input="searchEditUsers()" @input="searchEditUsers()"
> >
@@ -596,7 +596,7 @@
v-model="userSearchQuery" v-model="userSearchQuery"
type="text" type="text"
placeholder="搜索用户姓名或用户名" placeholder="搜索用户姓名或用户名"
class="w-full bg-zinc-950 border border-zinc-800 rounded-lg px-4 py-3 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30 transition-all" class="w-full bg-white border border-zinc-200 rounded-lg px-4 py-3 text-sm text-zinc-700 focus:outline-none focus:border-blue-200 transition-all"
@focus="showUserDropdown = true" @focus="showUserDropdown = true"
@input="searchUsers()" @input="searchUsers()"
> >
@@ -605,7 +605,7 @@
class="absolute right-4 top-1/2 -translate-y-1/2" class="absolute right-4 top-1/2 -translate-y-1/2"
> >
<div <div
class="w-4 h-4 border-2 border-blue-500/20 border-t-blue-500 rounded-full animate-spin" class="w-4 h-4 border-2 border-blue-200 border-t-blue-500 rounded-full animate-spin"
/> />
</div> </div>
</div> </div>
@@ -617,18 +617,18 @@
? showEditUserDropdown && filteredEditUsers.length > 0 ? showEditUserDropdown && filteredEditUsers.length > 0
: showUserDropdown && filteredUsers.length > 0 : showUserDropdown && filteredUsers.length > 0
" "
class="absolute z-10 w-full mt-2 bg-zinc-900 border border-zinc-800 rounded-lg shadow-2xl overflow-hidden max-h-48 overflow-y-auto" class="absolute z-10 w-full mt-2 bg-white border border-zinc-200 rounded-lg shadow-2xl overflow-hidden max-h-48 overflow-y-auto"
> >
<div <div
v-for="user in showEditModal v-for="user in showEditModal
? filteredEditUsers.slice(0, 10) ? filteredEditUsers.slice(0, 10)
: filteredUsers.slice(0, 10)" : filteredUsers.slice(0, 10)"
:key="user.id" :key="user.id"
class="px-4 py-3 hover:bg-zinc-800 cursor-pointer transition-colors flex items-center justify-between group" class="px-4 py-3 hover:bg-zinc-100 cursor-pointer transition-colors flex items-center justify-between group"
@click="showEditModal ? selectEditUser(user) : selectUser(user)" @click="showEditModal ? selectEditUser(user) : selectUser(user)"
> >
<div class="flex flex-col"> <div class="flex flex-col">
<span class="text-sm font-bold text-zinc-200">{{ user.name }}</span> <span class="text-sm font-bold text-zinc-700">{{ user.name }}</span>
<span class="text-[10px] text-zinc-500">@{{ user.username }}</span> <span class="text-[10px] text-zinc-500">@{{ user.username }}</span>
</div> </div>
<Check <Check
@@ -674,7 +674,7 @@
<span <span
v-for="collaborator in selectedEditCollaborators" v-for="collaborator in selectedEditCollaborators"
:key="collaborator.id" :key="collaborator.id"
class="inline-flex items-center gap-2 px-3 py-1.5 rounded-lg bg-zinc-950 border border-zinc-800 text-xs text-zinc-300" class="inline-flex items-center gap-2 px-3 py-1.5 rounded-lg bg-white border border-zinc-200 text-xs text-zinc-600"
> >
{{ getCollaboratorDisplayName(collaborator) }} {{ getCollaboratorDisplayName(collaborator) }}
<button <button
@@ -690,7 +690,7 @@
v-model="editCollaboratorSearchQuery" v-model="editCollaboratorSearchQuery"
type="text" type="text"
placeholder="搜索并添加联合投稿人" placeholder="搜索并添加联合投稿人"
class="w-full bg-zinc-950 border border-zinc-800 rounded-lg px-4 py-3 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30 transition-all" class="w-full bg-white border border-zinc-200 rounded-lg px-4 py-3 text-sm text-zinc-700 focus:outline-none focus:border-blue-200 transition-all"
@focus="showEditCollaboratorDropdown = true" @focus="showEditCollaboratorDropdown = true"
@input="searchEditCollaborators()" @input="searchEditCollaborators()"
> >
@@ -699,21 +699,21 @@
class="absolute right-4 top-1/2 -translate-y-1/2" class="absolute right-4 top-1/2 -translate-y-1/2"
> >
<div <div
class="w-4 h-4 border-2 border-blue-500/20 border-t-blue-500 rounded-full animate-spin" class="w-4 h-4 border-2 border-blue-200 border-t-blue-500 rounded-full animate-spin"
/> />
</div> </div>
<div <div
v-if="showEditCollaboratorDropdown && filteredEditCollaborators.length > 0" v-if="showEditCollaboratorDropdown && filteredEditCollaborators.length > 0"
class="absolute z-10 w-full mt-2 bg-zinc-900 border border-zinc-800 rounded-lg shadow-2xl overflow-hidden max-h-48 overflow-y-auto" class="absolute z-10 w-full mt-2 bg-white border border-zinc-200 rounded-lg shadow-2xl overflow-hidden max-h-48 overflow-y-auto"
> >
<div <div
v-for="user in filteredEditCollaborators.slice(0, 10)" v-for="user in filteredEditCollaborators.slice(0, 10)"
:key="user.id" :key="user.id"
class="px-4 py-3 hover:bg-zinc-800 cursor-pointer transition-colors flex items-center justify-between group" class="px-4 py-3 hover:bg-zinc-100 cursor-pointer transition-colors flex items-center justify-between group"
@click="selectEditCollaborator(user)" @click="selectEditCollaborator(user)"
> >
<div class="flex flex-col"> <div class="flex flex-col">
<span class="text-sm font-bold text-zinc-200">{{ user.name }}</span> <span class="text-sm font-bold text-zinc-700">{{ user.name }}</span>
<span class="text-[10px] text-zinc-500">@{{ user.username }}</span> <span class="text-[10px] text-zinc-500">@{{ user.username }}</span>
</div> </div>
<Plus :size="14" class="text-blue-500 opacity-70 group-hover:opacity-100" /> <Plus :size="14" class="text-blue-500 opacity-70 group-hover:opacity-100" />
@@ -759,8 +759,8 @@
class="inline-flex items-center gap-2 px-3 py-1.5 rounded-lg border text-[10px] font-black tracking-widest uppercase transition-all disabled:opacity-40 disabled:cursor-not-allowed" class="inline-flex items-center gap-2 px-3 py-1.5 rounded-lg border text-[10px] font-black tracking-widest uppercase transition-all disabled:opacity-40 disabled:cursor-not-allowed"
:class=" :class="
submissionNoteClearRequested submissionNoteClearRequested
? 'border-zinc-700 bg-zinc-950 text-zinc-300 hover:border-zinc-600' ? 'border-zinc-200 bg-white text-zinc-600 hover:border-zinc-300'
: 'border-amber-500/20 bg-amber-500/10 text-amber-400 hover:bg-amber-500/20' : 'border-amber-500/20 bg-amber-500/10 text-amber-700 hover:bg-amber-500/20'
" "
@click=" @click="
submissionNoteClearRequested submissionNoteClearRequested
@@ -775,7 +775,7 @@
v-model="editForm.submissionNote" v-model="editForm.submissionNote"
:disabled="submissionNoteClearRequested" :disabled="submissionNoteClearRequested"
placeholder="填写歌曲备注" placeholder="填写歌曲备注"
class="w-full bg-zinc-950 border border-zinc-800 rounded-lg px-4 py-4 text-sm text-zinc-200 focus:outline-none focus:border-blue-500/30 min-h-[120px] resize-none transition-all disabled:opacity-50 disabled:cursor-not-allowed" class="w-full bg-white border border-zinc-200 rounded-lg px-4 py-4 text-sm text-zinc-700 focus:outline-none focus:border-blue-200 min-h-[120px] resize-none transition-all disabled:opacity-50 disabled:cursor-not-allowed"
/> />
<div <div
v-if="submissionNoteClearRequested" v-if="submissionNoteClearRequested"
@@ -787,16 +787,16 @@
<textarea <textarea
v-model="submissionNoteClearReason" v-model="submissionNoteClearReason"
placeholder="可选:请输入清空备注的理由,例如:备注内容违规" placeholder="可选:请输入清空备注的理由,例如:备注内容违规"
class="w-full bg-zinc-950 border border-zinc-800 rounded-lg px-4 py-3 text-sm text-zinc-200 focus:outline-none focus:border-amber-500/30 min-h-[96px] resize-none transition-all" class="w-full bg-white border border-zinc-200 rounded-lg px-4 py-3 text-sm text-zinc-700 focus:outline-none focus:border-amber-200 min-h-[96px] resize-none transition-all"
/> />
<label class="flex items-center gap-3 cursor-pointer group"> <label class="flex items-center gap-3 cursor-pointer group">
<input <input
v-model="notifyOnSubmissionNoteClear" v-model="notifyOnSubmissionNoteClear"
type="checkbox" type="checkbox"
class="w-4 h-4 rounded border-zinc-800 bg-zinc-950 accent-amber-500" class="w-4 h-4 rounded border-zinc-200 bg-white accent-amber-500"
> >
<div> <div>
<span class="text-xs font-bold text-zinc-300 group-hover:text-amber-300 transition-colors" <span class="text-xs font-bold text-zinc-600 group-hover:text-amber-300 transition-colors"
>清空后发送通知</span >清空后发送通知</span
> >
<p class="text-[10px] text-zinc-600 font-medium"> <p class="text-[10px] text-zinc-600 font-medium">
@@ -806,7 +806,7 @@
</label> </label>
</div> </div>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-5 pt-4 border-t border-zinc-800/50"> <div class="grid grid-cols-1 md:grid-cols-2 gap-5 pt-4 border-t border-zinc-200">
<div class="space-y-2"> <div class="space-y-2">
<label class="text-[10px] font-black text-zinc-600 uppercase tracking-widest px-1" <label class="text-[10px] font-black text-zinc-600 uppercase tracking-widest px-1"
>音乐平台 (可选)</label >音乐平台 (可选)</label
@@ -841,14 +841,14 @@
v-model="editForm.musicId" v-model="editForm.musicId"
type="text" type="text"
placeholder="输入平台唯一标识符" placeholder="输入平台唯一标识符"
class="w-full bg-zinc-950 border border-zinc-800 rounded-lg px-4 py-3 text-sm text-zinc-200 focus:outline-none transition-all" class="w-full bg-white border border-zinc-200 rounded-lg px-4 py-3 text-sm text-zinc-700 focus:outline-none transition-all"
> >
<input <input
v-else v-else
v-model="addForm.musicId" v-model="addForm.musicId"
type="text" type="text"
placeholder="输入平台唯一标识符" placeholder="输入平台唯一标识符"
class="w-full bg-zinc-950 border border-zinc-800 rounded-lg px-4 py-3 text-sm text-zinc-200 focus:outline-none transition-all" class="w-full bg-white border border-zinc-200 rounded-lg px-4 py-3 text-sm text-zinc-700 focus:outline-none transition-all"
> >
</div> </div>
</div> </div>
@@ -862,14 +862,14 @@
v-model="editForm.cover" v-model="editForm.cover"
type="text" type="text"
placeholder="http://..." placeholder="http://..."
class="w-full bg-zinc-950 border border-zinc-800 rounded-lg px-4 py-3 text-sm text-zinc-200 focus:outline-none transition-all" class="w-full bg-white border border-zinc-200 rounded-lg px-4 py-3 text-sm text-zinc-700 focus:outline-none transition-all"
> >
<input <input
v-else v-else
v-model="addForm.cover" v-model="addForm.cover"
type="text" type="text"
placeholder="http://..." placeholder="http://..."
class="w-full bg-zinc-950 border border-zinc-800 rounded-lg px-4 py-3 text-sm text-zinc-200 focus:outline-none transition-all" class="w-full bg-white border border-zinc-200 rounded-lg px-4 py-3 text-sm text-zinc-700 focus:outline-none transition-all"
> >
<p <p
v-if=" v-if="
@@ -901,14 +901,14 @@
v-model="editForm.playUrl" v-model="editForm.playUrl"
type="text" type="text"
placeholder="http://..." placeholder="http://..."
class="w-full bg-zinc-950 border border-zinc-800 rounded-lg px-4 py-3 text-sm text-zinc-200 focus:outline-none transition-all" class="w-full bg-white border border-zinc-200 rounded-lg px-4 py-3 text-sm text-zinc-700 focus:outline-none transition-all"
> >
<input <input
v-else v-else
v-model="addForm.playUrl" v-model="addForm.playUrl"
type="text" type="text"
placeholder="http://..." placeholder="http://..."
class="w-full bg-zinc-950 border border-zinc-800 rounded-lg px-4 py-3 text-sm text-zinc-200 focus:outline-none transition-all" class="w-full bg-white border border-zinc-200 rounded-lg px-4 py-3 text-sm text-zinc-700 focus:outline-none transition-all"
> >
<p <p
v-if=" v-if="
@@ -932,9 +932,9 @@
</div> </div>
</div> </div>
<div class="px-8 py-6 bg-zinc-900/50 border-t border-zinc-800/50 flex gap-3 justify-end"> <div class="px-8 py-6 bg-zinc-50/50 border-t border-zinc-200 flex gap-3 justify-end">
<button <button
class="px-6 py-2.5 text-xs font-bold text-zinc-500 hover:text-zinc-300" class="px-6 py-2.5 text-xs font-bold text-zinc-500 hover:text-zinc-600"
@click="showEditModal ? cancelEditSong() : cancelAddSong()" @click="showEditModal ? cancelEditSong() : cancelAddSong()"
> >
取消 取消
@@ -9,16 +9,16 @@
> >
<div <div
v-if="show" v-if="show"
class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-zinc-950/80 backdrop-blur-sm" class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-white/80 backdrop-blur-sm"
@click="close" @click="close"
> >
<div <div
class="w-full max-w-lg bg-zinc-900 border border-zinc-800 rounded-xl shadow-2xl overflow-hidden" class="w-full max-w-lg bg-white border border-zinc-200 rounded-xl shadow-2xl overflow-hidden"
@click.stop @click.stop
> >
<div class="px-8 py-6 border-b border-zinc-800/50 flex items-center justify-between"> <div class="px-8 py-6 border-b border-zinc-200 flex items-center justify-between">
<h3 class="text-xl font-black text-zinc-100">投稿备注留言</h3> <h3 class="text-xl font-black text-zinc-900">投稿备注留言</h3>
<button class="text-zinc-500 hover:text-zinc-300 transition-colors" @click="close"> <button class="text-zinc-500 hover:text-zinc-600 transition-colors" @click="close">
<X :size="20" /> <X :size="20" />
</button> </button>
</div> </div>
@@ -29,15 +29,15 @@
:class="[ :class="[
'px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider border', 'px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider border',
isPublic isPublic
? 'bg-blue-500/10 text-blue-400 border-blue-500/20' ? 'bg-blue-500/10 text-blue-700 border-blue-200'
: 'bg-amber-500/10 text-amber-500 border-amber-500/20' : 'bg-amber-500/10 text-amber-500 border-amber-500/20'
]" ]"
> >
{{ isPublic ? '公开备注' : '仅管理员可见' }} {{ isPublic ? '公开备注' : '仅管理员可见' }}
</span> </span>
</div> </div>
<div class="bg-zinc-950/50 border border-zinc-800/50 rounded-xl p-4"> <div class="bg-white/70 border border-zinc-200 rounded-xl p-4">
<p class="text-sm text-zinc-200 leading-relaxed whitespace-pre-wrap"> <p class="text-sm text-zinc-700 leading-relaxed whitespace-pre-wrap">
{{ content }} {{ content }}
</p> </p>
</div> </div>
+105 -105
View File
@@ -3,7 +3,7 @@
<!-- Header --> <!-- Header -->
<div class="flex flex-col md:flex-row md:items-end justify-between gap-6 mt-4"> <div class="flex flex-col md:flex-row md:items-end justify-between gap-6 mt-4">
<div> <div>
<h2 class="text-2xl font-black text-zinc-100 tracking-tight">用户管理</h2> <h2 class="text-2xl font-black text-zinc-900 tracking-tight">用户管理</h2>
<p class="text-xs text-zinc-500 mt-1">系统共有 {{ totalUsers }} 位成员 · 权限与账户管理</p> <p class="text-xs text-zinc-500 mt-1">系统共有 {{ totalUsers }} 位成员 · 权限与账户管理</p>
</div> </div>
<div class="flex flex-wrap items-center gap-2"> <div class="flex flex-wrap items-center gap-2">
@@ -15,14 +15,14 @@
添加 添加
</button> </button>
<button <button
class="flex-1 md:flex-none flex items-center justify-center gap-2 px-4 py-2.5 bg-zinc-900 border border-zinc-800 text-zinc-300 text-xs font-black rounded-lg transition-all uppercase tracking-widest" class="flex-1 md:flex-none flex items-center justify-center gap-2 px-4 py-2.5 bg-white border border-zinc-200 text-zinc-600 text-xs font-black rounded-lg transition-all uppercase tracking-widest"
@click="showImportModal = true" @click="showImportModal = true"
> >
<FileSpreadsheet class="text-emerald-500" :size="14" /> <FileSpreadsheet class="text-emerald-500" :size="14" />
导入 导入
</button> </button>
<button <button
class="flex-1 md:flex-none flex items-center justify-center gap-2 px-4 py-2.5 bg-zinc-900 border border-zinc-800 text-zinc-300 text-xs font-black rounded-lg transition-all uppercase tracking-widest" class="flex-1 md:flex-none flex items-center justify-center gap-2 px-4 py-2.5 bg-white border border-zinc-200 text-zinc-600 text-xs font-black rounded-lg transition-all uppercase tracking-widest"
@click="showBatchUpdateModal = true" @click="showBatchUpdateModal = true"
> >
<Layers class="text-purple-500" :size="14" /> <Layers class="text-purple-500" :size="14" />
@@ -33,7 +33,7 @@
<!-- Filter Bar --> <!-- Filter Bar -->
<div <div
class="bg-zinc-900/40 border border-zinc-800/60 rounded-xl p-3 flex flex-col lg:flex-row gap-3 items-center" class="bg-zinc-50/40 border border-zinc-200/60 rounded-xl p-3 flex flex-col lg:flex-row gap-3 items-center"
> >
<div class="relative flex-1 w-full group"> <div class="relative flex-1 w-full group">
<Search <Search
@@ -42,7 +42,7 @@
/> />
<input <input
v-model="searchQuery" v-model="searchQuery"
class="w-full bg-zinc-950 border border-zinc-800/80 rounded-lg pl-11 pr-4 py-2.5 text-xs focus:outline-none focus:border-blue-500/30 transition-all text-zinc-200" class="w-full bg-white border border-zinc-200/80 rounded-lg pl-11 pr-4 py-2.5 text-xs focus:outline-none focus:border-blue-200 transition-all text-zinc-700"
placeholder="通过姓名或学号搜索..." placeholder="通过姓名或学号搜索..."
type="text" type="text"
> >
@@ -84,7 +84,7 @@
/> />
<button <button
class="p-3 bg-zinc-950 border border-zinc-800 rounded-lg text-zinc-600 hover:text-blue-400 transition-all shadow-sm" class="p-3 bg-white border border-zinc-200 rounded-lg text-zinc-600 hover:text-blue-700 transition-all shadow-sm"
@click="loadUsers(1)" @click="loadUsers(1)"
> >
<RefreshCw :size="14" /> <RefreshCw :size="14" />
@@ -96,17 +96,17 @@
<div class="space-y-4"> <div class="space-y-4">
<div v-if="loading" class="flex flex-col items-center justify-center py-20 text-zinc-500"> <div v-if="loading" class="flex flex-col items-center justify-center py-20 text-zinc-500">
<div <div
class="w-8 h-8 border-2 border-blue-500/20 border-t-blue-500 rounded-full animate-spin mb-4" class="w-8 h-8 border-2 border-blue-200 border-t-blue-500 rounded-full animate-spin mb-4"
/> />
<div class="text-xs font-black uppercase tracking-widest">正在加载用户...</div> <div class="text-xs font-black uppercase tracking-widest">正在加载用户...</div>
</div> </div>
<div <div
v-else-if="users.length === 0" v-else-if="users.length === 0"
class="flex flex-col items-center justify-center py-20 bg-zinc-900/20 border border-zinc-800/50 rounded-xl" class="flex flex-col items-center justify-center py-20 bg-zinc-50/20 border border-zinc-200 rounded-xl"
> >
<div <div
class="w-16 h-16 rounded-lg bg-zinc-800/50 flex items-center justify-center text-zinc-600 mb-4" class="w-16 h-16 rounded-lg bg-zinc-100/50 flex items-center justify-center text-zinc-600 mb-4"
> >
<Search :size="32" /> <Search :size="32" />
</div> </div>
@@ -118,12 +118,12 @@
<template v-else> <template v-else>
<!-- 桌面端表格 --> <!-- 桌面端表格 -->
<div <div
class="hidden lg:block bg-zinc-900/20 border border-zinc-800/50 rounded-xl overflow-hidden shadow-lg custom-scrollbar" class="hidden lg:block bg-zinc-50/20 border border-zinc-200 rounded-xl overflow-hidden shadow-lg custom-scrollbar"
> >
<table class="w-full"> <table class="w-full">
<thead> <thead>
<tr <tr
class="bg-zinc-900/60 border-b border-zinc-800 text-[10px] font-black text-zinc-600 uppercase tracking-widest" class="bg-zinc-50/60 border-b border-zinc-200 text-[10px] font-black text-zinc-600 uppercase tracking-widest"
> >
<th class="px-6 py-5 text-left">用户详情</th> <th class="px-6 py-5 text-left">用户详情</th>
<th class="px-6 py-5 text-left">角色权限</th> <th class="px-6 py-5 text-left">角色权限</th>
@@ -137,7 +137,7 @@
<tr <tr
v-for="user in users" v-for="user in users"
:key="user.id" :key="user.id"
class="group hover:bg-zinc-800/30 transition-all text-xs cursor-pointer" class="group hover:bg-zinc-100/30 transition-all text-xs cursor-pointer"
@click="showUserDetail(user, $event)" @click="showUserDetail(user, $event)"
> >
<td class="px-6 py-5"> <td class="px-6 py-5">
@@ -145,17 +145,17 @@
<img <img
v-if="user.avatar && !failedImages[user.id]" v-if="user.avatar && !failedImages[user.id]"
:src="user.avatar" :src="user.avatar"
class="w-10 h-10 rounded-xl object-cover border border-zinc-700/50" class="w-10 h-10 rounded-xl object-cover border border-zinc-200"
@error="handleImageError(user.id)" @error="handleImageError(user.id)"
> >
<div <div
v-else 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-zinc-300 transition-colors border border-zinc-700/50" class="w-10 h-10 rounded-xl bg-zinc-100 flex items-center justify-center font-black text-zinc-500 group-hover:text-zinc-600 transition-colors border border-zinc-200"
> >
{{ (user.name || user.username || '?').charAt(0).toUpperCase() }} {{ (user.name || user.username || '?').charAt(0).toUpperCase() }}
</div> </div>
<div> <div>
<p class="font-black text-zinc-100">{{ user.name || user.username }}</p> <p class="font-black text-zinc-900">{{ user.name || user.username }}</p>
<p class="text-[10px] text-zinc-600 font-mono mt-0.5"> <p class="text-[10px] text-zinc-600 font-mono mt-0.5">
ID: {{ user.username }} ID: {{ user.username }}
</p> </p>
@@ -167,9 +167,9 @@
class="px-2 py-0.5 text-[10px] font-black rounded border uppercase tracking-widest" class="px-2 py-0.5 text-[10px] font-black rounded border uppercase tracking-widest"
:class="{ :class="{
'bg-orange-500/10 text-orange-400 border-orange-500/20': user.role?.toUpperCase() === 'SUPER_ADMIN', 'bg-orange-500/10 text-orange-400 border-orange-500/20': user.role?.toUpperCase() === 'SUPER_ADMIN',
'bg-blue-500/10 text-blue-400 border-blue-500/20': user.role?.toUpperCase() === 'ADMIN' || user.role?.toUpperCase() === 'MINISTER', 'bg-blue-500/10 text-blue-700 border-blue-200': user.role?.toUpperCase() === 'ADMIN' || user.role?.toUpperCase() === 'MINISTER',
'bg-purple-500/10 text-purple-400 border-purple-500/20': user.role?.toUpperCase() === 'SONG_ADMIN' || user.role?.toUpperCase() === 'STAFF' || user.role?.toUpperCase() === 'VICE_MINISTER', 'bg-purple-500/10 text-purple-400 border-purple-500/20': user.role?.toUpperCase() === 'SONG_ADMIN' || user.role?.toUpperCase() === 'STAFF' || user.role?.toUpperCase() === 'VICE_MINISTER',
'bg-zinc-800 text-zinc-500 border-zinc-700/50': !['SUPER_ADMIN', 'ADMIN', 'MINISTER', 'SONG_ADMIN', 'STAFF', 'VICE_MINISTER'].includes(user.role?.toUpperCase()) 'bg-zinc-100 text-zinc-500 border-zinc-200': !['SUPER_ADMIN', 'ADMIN', 'MINISTER', 'SONG_ADMIN', 'STAFF', 'VICE_MINISTER'].includes(user.role?.toUpperCase())
}" }"
> >
{{ getRoleDisplayName(user.role?.toUpperCase()) }} {{ getRoleDisplayName(user.role?.toUpperCase()) }}
@@ -222,14 +222,14 @@
> >
<button <button
:disabled="isSelf(user) || !canManageTargetUser(user)" :disabled="isSelf(user) || !canManageTargetUser(user)"
class="p-2 bg-zinc-950 border border-zinc-800 rounded-xl text-zinc-500 hover:text-blue-400 transition-colors disabled:opacity-20 disabled:cursor-not-allowed action-btn" class="p-2 bg-white border border-zinc-200 rounded-xl text-zinc-500 hover:text-blue-700 transition-colors disabled:opacity-20 disabled:cursor-not-allowed action-btn"
:title="!canManageTargetUser(user) ? '权限不足:无法编辑该用户' : '编辑用户'" :title="!canManageTargetUser(user) ? '权限不足:无法编辑该用户' : '编辑用户'"
@click="editUser(user)" @click="editUser(user)"
> >
<Edit2 :size="13" /> <Edit2 :size="13" />
</button> </button>
<button <button
class="p-2 bg-zinc-950 border border-zinc-800 rounded-xl text-zinc-500 hover:text-purple-400 transition-colors action-btn" class="p-2 bg-white border border-zinc-200 rounded-xl text-zinc-500 hover:text-purple-400 transition-colors action-btn"
title="查看歌曲" title="查看歌曲"
@click="viewUserSongs(user)" @click="viewUserSongs(user)"
> >
@@ -237,7 +237,7 @@
</button> </button>
<button <button
:disabled="isSelf(user) || !canResetPasswordFor(user)" :disabled="isSelf(user) || !canResetPasswordFor(user)"
class="p-2 bg-zinc-950 border border-zinc-800 rounded-xl text-zinc-500 hover:text-amber-400 transition-colors disabled:opacity-20 disabled:cursor-not-allowed action-btn" class="p-2 bg-white border border-zinc-200 rounded-xl text-zinc-500 hover:text-amber-700 transition-colors disabled:opacity-20 disabled:cursor-not-allowed action-btn"
:title="!canResetPasswordFor(user) ? '权限不足:无法重置该用户的密码' : '重置密码'" :title="!canResetPasswordFor(user) ? '权限不足:无法重置该用户的密码' : '重置密码'"
@click="resetPassword(user)" @click="resetPassword(user)"
> >
@@ -245,7 +245,7 @@
</button> </button>
<button <button
:disabled="isSelf(user) || !canManageTargetUser(user)" :disabled="isSelf(user) || !canManageTargetUser(user)"
class="p-2 bg-zinc-950 border border-zinc-800 rounded-xl text-zinc-500 hover:text-red-400 transition-colors disabled:opacity-20 disabled:cursor-not-allowed action-btn" class="p-2 bg-white border border-zinc-200 rounded-xl text-zinc-500 hover:text-red-400 transition-colors disabled:opacity-20 disabled:cursor-not-allowed action-btn"
:title="!canManageTargetUser(user) ? '权限不足:无法删除该用户' : '删除用户'" :title="!canManageTargetUser(user) ? '权限不足:无法删除该用户' : '删除用户'"
@click="confirmDeleteUser(user)" @click="confirmDeleteUser(user)"
> >
@@ -263,7 +263,7 @@
<div <div
v-for="user in users" v-for="user in users"
:key="user.id" :key="user.id"
class="bg-zinc-900/40 border border-zinc-800 rounded-xl p-5 space-y-5 shadow-lg shadow-black/20" class="bg-zinc-50/40 border border-zinc-200 rounded-xl p-5 space-y-5 shadow-lg shadow-zinc-900/5"
@click="showUserDetail(user, $event)" @click="showUserDetail(user, $event)"
> >
<div class="flex items-start justify-between"> <div class="flex items-start justify-between">
@@ -271,17 +271,17 @@
<img <img
v-if="user.avatar && !failedImages[user.id]" v-if="user.avatar && !failedImages[user.id]"
:src="user.avatar" :src="user.avatar"
class="w-12 h-12 rounded-lg object-cover border border-zinc-700" class="w-12 h-12 rounded-lg object-cover border border-zinc-200"
@error="handleImageError(user.id)" @error="handleImageError(user.id)"
> >
<div <div
v-else v-else
class="w-12 h-12 rounded-lg bg-zinc-800 flex items-center justify-center font-black text-lg text-zinc-500 border border-zinc-700" class="w-12 h-12 rounded-lg bg-zinc-100 flex items-center justify-center font-black text-lg text-zinc-500 border border-zinc-200"
> >
{{ (user.name || user.username || '?').charAt(0).toUpperCase() }} {{ (user.name || user.username || '?').charAt(0).toUpperCase() }}
</div> </div>
<div> <div>
<h4 class="text-base font-black text-zinc-100">{{ user.name || user.username }}</h4> <h4 class="text-base font-black text-zinc-900">{{ user.name || user.username }}</h4>
<p class="text-xs text-zinc-500 font-mono">@{{ user.username }}</p> <p class="text-xs text-zinc-500 font-mono">@{{ user.username }}</p>
</div> </div>
</div> </div>
@@ -320,7 +320,7 @@
</div> </div>
</div> </div>
<div class="grid grid-cols-2 gap-4 py-4 border-y border-zinc-800/50"> <div class="grid grid-cols-2 gap-4 py-4 border-y border-zinc-200">
<div class="space-y-1"> <div class="space-y-1">
<p class="text-[9px] font-black text-zinc-600 uppercase tracking-widest"> <p class="text-[9px] font-black text-zinc-600 uppercase tracking-widest">
角色/等级 角色/等级
@@ -330,9 +330,9 @@
class="px-2 py-0.5 text-[10px] font-black rounded border uppercase tracking-widest" class="px-2 py-0.5 text-[10px] font-black rounded border uppercase tracking-widest"
:class="{ :class="{
'bg-orange-500/10 text-orange-400 border-orange-500/20': user.role?.toUpperCase() === 'SUPER_ADMIN', 'bg-orange-500/10 text-orange-400 border-orange-500/20': user.role?.toUpperCase() === 'SUPER_ADMIN',
'bg-blue-500/10 text-blue-400 border-blue-500/20': user.role?.toUpperCase() === 'ADMIN' || user.role?.toUpperCase() === 'MINISTER', 'bg-blue-500/10 text-blue-700 border-blue-200': user.role?.toUpperCase() === 'ADMIN' || user.role?.toUpperCase() === 'MINISTER',
'bg-purple-500/10 text-purple-400 border-purple-500/20': user.role?.toUpperCase() === 'SONG_ADMIN' || user.role?.toUpperCase() === 'STAFF' || user.role?.toUpperCase() === 'VICE_MINISTER', 'bg-purple-500/10 text-purple-400 border-purple-500/20': user.role?.toUpperCase() === 'SONG_ADMIN' || user.role?.toUpperCase() === 'STAFF' || user.role?.toUpperCase() === 'VICE_MINISTER',
'bg-zinc-800 text-zinc-500 border-zinc-700/50': !['SUPER_ADMIN', 'ADMIN', 'MINISTER', 'SONG_ADMIN', 'STAFF', 'VICE_MINISTER'].includes(user.role?.toUpperCase()) 'bg-zinc-100 text-zinc-500 border-zinc-200': !['SUPER_ADMIN', 'ADMIN', 'MINISTER', 'SONG_ADMIN', 'STAFF', 'VICE_MINISTER'].includes(user.role?.toUpperCase())
}" }"
> >
{{ getRoleDisplayName(user.role?.toUpperCase()) }} {{ getRoleDisplayName(user.role?.toUpperCase()) }}
@@ -343,7 +343,7 @@
<p class="text-[9px] font-black text-zinc-600 uppercase tracking-widest"> <p class="text-[9px] font-black text-zinc-600 uppercase tracking-widest">
所在班级 所在班级
</p> </p>
<p class="text-xs font-bold text-zinc-300"> <p class="text-xs font-bold text-zinc-600">
{{ user.grade || '-' }} {{ user.class || '-' }} {{ user.grade || '-' }} {{ user.class || '-' }}
</p> </p>
</div> </div>
@@ -364,21 +364,21 @@
<div class="flex gap-2 action-buttons"> <div class="flex gap-2 action-buttons">
<button <button
:disabled="isSelf(user) || !canManageTargetUser(user)" :disabled="isSelf(user) || !canManageTargetUser(user)"
class="flex-1 py-2.5 bg-zinc-950 border border-zinc-800 rounded-lg text-zinc-400 flex items-center justify-center gap-2 text-[10px] font-black uppercase tracking-widest active:bg-blue-600 active:text-white transition-colors disabled:opacity-20 action-btn" class="flex-1 py-2.5 bg-white border border-zinc-200 rounded-lg text-zinc-400 flex items-center justify-center gap-2 text-[10px] font-black uppercase tracking-widest active:bg-blue-600 active:text-white transition-colors disabled:opacity-20 action-btn"
:title="!canManageTargetUser(user) ? '权限不足:无法编辑该用户' : '编辑用户'" :title="!canManageTargetUser(user) ? '权限不足:无法编辑该用户' : '编辑用户'"
@click="editUser(user)" @click="editUser(user)"
> >
<Edit2 :size="12" /> 编辑 <Edit2 :size="12" /> 编辑
</button> </button>
<button <button
class="flex-1 py-2.5 bg-zinc-950 border border-zinc-800 rounded-lg text-zinc-400 flex items-center justify-center gap-2 text-[10px] font-black uppercase tracking-widest active:bg-purple-600 active:text-white transition-colors action-btn" class="flex-1 py-2.5 bg-white border border-zinc-200 rounded-lg text-zinc-400 flex items-center justify-center gap-2 text-[10px] font-black uppercase tracking-widest active:bg-purple-600 active:text-white transition-colors action-btn"
@click="viewUserSongs(user)" @click="viewUserSongs(user)"
> >
<Music :size="12" /> 记录 <Music :size="12" /> 记录
</button> </button>
<button <button
:disabled="isSelf(user) || !canResetPasswordFor(user)" :disabled="isSelf(user) || !canResetPasswordFor(user)"
class="flex-1 py-2.5 bg-zinc-950 border border-zinc-800 rounded-lg text-zinc-400 flex items-center justify-center gap-2 text-[10px] font-black uppercase tracking-widest active:bg-amber-600 active:text-white transition-colors disabled:opacity-20 action-btn" class="flex-1 py-2.5 bg-white border border-zinc-200 rounded-lg text-zinc-400 flex items-center justify-center gap-2 text-[10px] font-black uppercase tracking-widest active:bg-amber-600 active:text-white transition-colors disabled:opacity-20 action-btn"
:title="!canResetPasswordFor(user) ? '权限不足:无法重置该用户的密码' : '重置密码'" :title="!canResetPasswordFor(user) ? '权限不足:无法重置该用户的密码' : '重置密码'"
@click="resetPassword(user)" @click="resetPassword(user)"
> >
@@ -386,7 +386,7 @@
</button> </button>
<button <button
:disabled="isSelf(user) || !canManageTargetUser(user)" :disabled="isSelf(user) || !canManageTargetUser(user)"
class="px-3 py-2.5 bg-zinc-950 border border-zinc-800 rounded-lg text-zinc-400 active:bg-red-600 active:text-white transition-colors disabled:opacity-20 action-btn" class="px-3 py-2.5 bg-white border border-zinc-200 rounded-lg text-zinc-400 active:bg-red-600 active:text-white transition-colors disabled:opacity-20 action-btn"
:title="!canManageTargetUser(user) ? '权限不足:无法删除该用户' : '删除用户'" :title="!canManageTargetUser(user) ? '权限不足:无法删除该用户' : '删除用户'"
@click="confirmDeleteUser(user)" @click="confirmDeleteUser(user)"
> >
@@ -422,15 +422,15 @@
@click="closeModal" @click="closeModal"
> >
<div <div
class="bg-zinc-900 border border-zinc-800 w-full max-w-lg rounded-3xl overflow-hidden shadow-2xl" class="bg-white border border-zinc-200 w-full max-w-lg rounded-3xl overflow-hidden shadow-2xl"
@click.stop @click.stop
> >
<div class="p-8"> <div class="p-8">
<div class="flex items-center justify-between mb-8"> <div class="flex items-center justify-between mb-8">
<div> <div>
<h3 class="text-xl font-black text-zinc-100 tracking-tight flex items-center gap-3"> <h3 class="text-xl font-black text-zinc-900 tracking-tight flex items-center gap-3">
<div <div
class="w-10 h-10 rounded-2xl bg-blue-600/10 flex items-center justify-center text-blue-500" class="w-10 h-10 rounded-2xl bg-blue-50 flex items-center justify-center text-blue-500"
> >
<UserPlus v-if="!editingUser" :size="20" /> <UserPlus v-if="!editingUser" :size="20" />
<Edit2 v-else :size="20" /> <Edit2 v-else :size="20" />
@@ -440,7 +440,7 @@
<p class="text-xs text-zinc-500 mt-1 ml-13">请填写以下账户详细信息以继续</p> <p class="text-xs text-zinc-500 mt-1 ml-13">请填写以下账户详细信息以继续</p>
</div> </div>
<button <button
class="p-3 bg-zinc-800/50 hover:bg-zinc-800 text-zinc-500 hover:text-zinc-200 rounded-2xl transition-all" class="p-3 bg-zinc-100/50 hover:bg-zinc-100 text-zinc-500 hover:text-zinc-700 rounded-2xl transition-all"
@click="closeModal" @click="closeModal"
> >
<X :size="20" /> <X :size="20" />
@@ -460,7 +460,7 @@
/> />
<input <input
v-model="userForm.name" v-model="userForm.name"
class="w-full bg-zinc-950 border border-zinc-800 rounded-2xl pl-11 pr-4 py-3 text-xs focus:outline-none focus:border-blue-500/30 transition-all text-zinc-200" class="w-full bg-white border border-zinc-200 rounded-2xl pl-11 pr-4 py-3 text-xs focus:outline-none focus:border-blue-200 transition-all text-zinc-700"
placeholder="请输入真实姓名" placeholder="请输入真实姓名"
type="text" type="text"
> >
@@ -478,7 +478,7 @@
<input <input
v-model="userForm.username" v-model="userForm.username"
:disabled="!!editingUser" :disabled="!!editingUser"
class="w-full bg-zinc-950 border border-zinc-800 rounded-2xl pl-11 pr-4 py-3 text-xs focus:outline-none focus:border-blue-500/30 transition-all text-zinc-200 disabled:opacity-50" class="w-full bg-white border border-zinc-200 rounded-2xl pl-11 pr-4 py-3 text-xs focus:outline-none focus:border-blue-200 transition-all text-zinc-700 disabled:opacity-50"
placeholder="登录唯一标识" placeholder="登录唯一标识"
type="text" type="text"
> >
@@ -497,7 +497,7 @@
/> />
<input <input
v-model="userForm.password" v-model="userForm.password"
class="w-full bg-zinc-950 border border-zinc-800 rounded-2xl pl-11 pr-4 py-3 text-xs focus:outline-none focus:border-blue-500/30 transition-all text-zinc-200" class="w-full bg-white border border-zinc-200 rounded-2xl pl-11 pr-4 py-3 text-xs focus:outline-none focus:border-blue-200 transition-all text-zinc-700"
placeholder="设置安全访问密码" placeholder="设置安全访问密码"
type="password" type="password"
> >
@@ -571,7 +571,7 @@
<div class="flex gap-3 mt-10"> <div class="flex gap-3 mt-10">
<button <button
class="flex-1 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" class="flex-1 px-6 py-4 bg-zinc-100 hover:bg-zinc-200 text-zinc-600 text-xs font-black rounded-2xl transition-all uppercase tracking-widest"
@click="closeModal" @click="closeModal"
> >
取消操作 取消操作
@@ -606,7 +606,7 @@
@click="closeResetPassword" @click="closeResetPassword"
> >
<div <div
class="bg-zinc-900 border border-zinc-800 w-full max-w-md rounded-3xl overflow-hidden shadow-2xl" class="bg-white border border-zinc-200 w-full max-w-md rounded-3xl overflow-hidden shadow-2xl"
@click.stop @click.stop
> >
<div class="p-8 text-center"> <div class="p-8 text-center">
@@ -615,10 +615,10 @@
> >
<Lock :size="32" /> <Lock :size="32" />
</div> </div>
<h3 class="text-xl font-black text-zinc-100 tracking-tight">重置访问密码</h3> <h3 class="text-xl font-black text-zinc-900 tracking-tight">重置访问密码</h3>
<p class="text-xs text-zinc-500 mt-2 mb-8"> <p class="text-xs text-zinc-500 mt-2 mb-8">
正在为 正在为
<span class="text-zinc-200 font-bold">{{ resetPasswordUser.name }}</span> 修改登录凭据 <span class="text-zinc-700 font-bold">{{ resetPasswordUser.name }}</span> 修改登录凭据
</p> </p>
<div class="space-y-4 text-left"> <div class="space-y-4 text-left">
@@ -633,7 +633,7 @@
/> />
<input <input
v-model="passwordForm.password" v-model="passwordForm.password"
class="w-full bg-zinc-950 border border-zinc-800 rounded-2xl pl-11 pr-4 py-3 text-xs focus:outline-none focus:border-amber-500/30 transition-all text-zinc-200" class="w-full bg-white border border-zinc-200 rounded-2xl pl-11 pr-4 py-3 text-xs focus:outline-none focus:border-amber-200 transition-all text-zinc-700"
placeholder="设置高强度新密码" placeholder="设置高强度新密码"
type="password" type="password"
> >
@@ -650,7 +650,7 @@
/> />
<input <input
v-model="passwordForm.confirmPassword" v-model="passwordForm.confirmPassword"
class="w-full bg-zinc-950 border border-zinc-800 rounded-2xl pl-11 pr-4 py-3 text-xs focus:outline-none focus:border-amber-500/30 transition-all text-zinc-200" class="w-full bg-white border border-zinc-200 rounded-2xl pl-11 pr-4 py-3 text-xs focus:outline-none focus:border-amber-200 transition-all text-zinc-700"
placeholder="请再次输入以确认" placeholder="请再次输入以确认"
type="password" type="password"
> >
@@ -668,7 +668,7 @@
<div class="flex gap-3 mt-8"> <div class="flex gap-3 mt-8">
<button <button
class="flex-1 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" class="flex-1 px-6 py-4 bg-zinc-100 hover:bg-zinc-200 text-zinc-600 text-xs font-black rounded-2xl transition-all uppercase tracking-widest"
@click="closeResetPassword" @click="closeResetPassword"
> >
取消 取消
@@ -703,13 +703,13 @@
@click="closeImportModal" @click="closeImportModal"
> >
<div <div
class="bg-zinc-900 border border-zinc-800 w-full max-w-2xl rounded-3xl overflow-hidden shadow-2xl" class="bg-white border border-zinc-200 w-full max-w-2xl rounded-3xl overflow-hidden shadow-2xl"
@click.stop @click.stop
> >
<div class="p-8"> <div class="p-8">
<div class="flex items-center justify-between mb-8"> <div class="flex items-center justify-between mb-8">
<div> <div>
<h3 class="text-xl font-black text-zinc-100 tracking-tight flex items-center gap-3"> <h3 class="text-xl font-black text-zinc-900 tracking-tight flex items-center gap-3">
<div <div
class="w-10 h-10 rounded-2xl bg-emerald-600/10 flex items-center justify-center text-emerald-500" class="w-10 h-10 rounded-2xl bg-emerald-600/10 flex items-center justify-center text-emerald-500"
> >
@@ -722,7 +722,7 @@
</p> </p>
</div> </div>
<button <button
class="p-3 bg-zinc-800/50 hover:bg-zinc-800 text-zinc-500 hover:text-zinc-200 rounded-2xl transition-all" class="p-3 bg-zinc-100/50 hover:bg-zinc-100 text-zinc-500 hover:text-zinc-700 rounded-2xl transition-all"
@click="closeImportModal" @click="closeImportModal"
> >
<X :size="20" /> <X :size="20" />
@@ -730,9 +730,9 @@
</div> </div>
<div class="space-y-6 max-h-[60vh] overflow-y-auto pr-2 custom-scrollbar"> <div class="space-y-6 max-h-[60vh] overflow-y-auto pr-2 custom-scrollbar">
<div class="p-5 bg-zinc-950/50 border border-zinc-800/50 rounded-3xl space-y-4"> <div class="p-5 bg-white/70 border border-zinc-200 rounded-3xl space-y-4">
<div class="flex items-center gap-2 text-zinc-300 font-bold text-sm mb-2"> <div class="flex items-center gap-2 text-zinc-600 font-bold text-sm mb-2">
<Info :size="16" class="text-blue-400" /> <Info :size="16" class="text-blue-700" />
导入说明 导入说明
</div> </div>
<p class="text-xs text-zinc-500 leading-relaxed"> <p class="text-xs text-zinc-500 leading-relaxed">
@@ -740,16 +740,16 @@
注意:第一行可以是标题行(会自动跳过),角色字段必须匹配系统定义的角色。 注意:第一行可以是标题行(会自动跳过),角色字段必须匹配系统定义的角色。
</p> </p>
<div class="overflow-hidden rounded-2xl border border-zinc-800/80"> <div class="overflow-hidden rounded-2xl border border-zinc-200/80">
<table class="w-full text-[10px] text-left"> <table class="w-full text-[10px] text-left">
<thead class="bg-zinc-900 text-zinc-400 uppercase tracking-tighter"> <thead class="bg-white text-zinc-400 uppercase tracking-tighter">
<tr> <tr>
<th class="px-3 py-2 border-b border-zinc-800">姓名</th> <th class="px-3 py-2 border-b border-zinc-200">姓名</th>
<th class="px-3 py-2 border-b border-zinc-800">账号名</th> <th class="px-3 py-2 border-b border-zinc-200">账号名</th>
<th class="px-3 py-2 border-b border-zinc-800">密码</th> <th class="px-3 py-2 border-b border-zinc-200">密码</th>
<th class="px-3 py-2 border-b border-zinc-800">角色</th> <th class="px-3 py-2 border-b border-zinc-200">角色</th>
<th class="px-3 py-2 border-b border-zinc-800">年级</th> <th class="px-3 py-2 border-b border-zinc-200">年级</th>
<th class="px-3 py-2 border-b border-zinc-800">班级</th> <th class="px-3 py-2 border-b border-zinc-200">班级</th>
</tr> </tr>
</thead> </thead>
<tbody class="text-zinc-500"> <tbody class="text-zinc-500">
@@ -767,7 +767,7 @@
<div class="flex items-center justify-between gap-4"> <div class="flex items-center justify-between gap-4">
<div <div
class="p-3 bg-blue-500/5 border border-blue-500/10 rounded-2xl text-[10px] text-blue-400/80 leading-relaxed flex-1" class="p-3 bg-blue-500/5 border border-blue-500/10 rounded-2xl text-[10px] text-blue-700/80 leading-relaxed flex-1"
> >
<strong>支持的角色:</strong> <strong>支持的角色:</strong>
<span v-if="isSuperAdmin" <span v-if="isSuperAdmin"
@@ -804,15 +804,15 @@
@change="handleFileUpload" @change="handleFileUpload"
> >
<div <div
class="w-full py-10 border-2 border-dashed border-zinc-800 group-hover:border-emerald-500/50 group-hover:bg-emerald-500/5 rounded-3xl transition-all flex flex-col items-center justify-center gap-3" class="w-full py-10 border-2 border-dashed border-zinc-200 group-hover:border-emerald-500/50 group-hover:bg-emerald-500/5 rounded-3xl transition-all flex flex-col items-center justify-center gap-3"
> >
<div <div
class="w-12 h-12 rounded-2xl bg-zinc-900 flex items-center justify-center text-zinc-600 group-hover:text-emerald-500 transition-colors" class="w-12 h-12 rounded-2xl bg-white flex items-center justify-center text-zinc-600 group-hover:text-emerald-500 transition-colors"
> >
<Upload :size="24" /> <Upload :size="24" />
</div> </div>
<div class="text-center"> <div class="text-center">
<p class="text-sm font-bold text-zinc-300">点击或拖拽上传 Excel 文件</p> <p class="text-sm font-bold text-zinc-600">点击或拖拽上传 Excel 文件</p>
<p class="text-xs text-zinc-500 mt-1">仅支持 .xlsx 格式</p> <p class="text-xs text-zinc-500 mt-1">仅支持 .xlsx 格式</p>
</div> </div>
</div> </div>
@@ -829,7 +829,7 @@
<div <div
v-if="importSuccess" v-if="importSuccess"
class="p-4 bg-emerald-500/10 border border-emerald-500/20 rounded-2xl flex items-center gap-3 text-emerald-400 text-xs" class="p-4 bg-emerald-500/10 border border-emerald-500/20 rounded-2xl flex items-center gap-3 text-emerald-700 text-xs"
> >
<CheckCircle2 :size="16" /> <CheckCircle2 :size="16" />
{{ importSuccess }} {{ importSuccess }}
@@ -841,9 +841,9 @@
>预览数据 ({{ previewData.length }}条记录)</label >预览数据 ({{ previewData.length }}条记录)</label
> >
</div> </div>
<div class="overflow-hidden rounded-3xl border border-zinc-800 bg-zinc-950/30"> <div class="overflow-hidden rounded-3xl border border-zinc-200 bg-white/60">
<table class="w-full text-xs text-left"> <table class="w-full text-xs text-left">
<thead class="bg-zinc-900/50 text-zinc-500"> <thead class="bg-zinc-50/50 text-zinc-500">
<tr> <tr>
<th class="px-4 py-3 font-medium">姓名</th> <th class="px-4 py-3 font-medium">姓名</th>
<th class="px-4 py-3 font-medium">账号</th> <th class="px-4 py-3 font-medium">账号</th>
@@ -855,13 +855,13 @@
<tr <tr
v-for="(row, index) in previewData.slice(0, 5)" v-for="(row, index) in previewData.slice(0, 5)"
:key="index" :key="index"
class="text-zinc-300" class="text-zinc-600"
> >
<td class="px-4 py-3">{{ row.name }}</td> <td class="px-4 py-3">{{ row.name }}</td>
<td class="px-4 py-3">{{ row.username }}</td> <td class="px-4 py-3">{{ row.username }}</td>
<td class="px-4 py-3"> <td class="px-4 py-3">
<span <span
class="px-2 py-0.5 bg-zinc-800 rounded-md text-[10px] text-zinc-400 uppercase" class="px-2 py-0.5 bg-zinc-100 rounded-md text-[10px] text-zinc-400 uppercase"
>{{ row.role }}</span >{{ row.role }}</span
> >
</td> </td>
@@ -883,7 +883,7 @@
<div class="flex gap-3 mt-8"> <div class="flex gap-3 mt-8">
<button <button
class="flex-1 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" class="flex-1 px-6 py-4 bg-zinc-100 hover:bg-zinc-200 text-zinc-600 text-xs font-black rounded-2xl transition-all uppercase tracking-widest"
@click="closeImportModal" @click="closeImportModal"
> >
取消 取消
@@ -945,15 +945,15 @@
@click="closeUserDetailModal" @click="closeUserDetailModal"
> >
<div <div
class="bg-zinc-900 border border-zinc-800 w-full max-w-3xl rounded-3xl overflow-hidden shadow-2xl" class="bg-white border border-zinc-200 w-full max-w-3xl rounded-3xl overflow-hidden shadow-2xl"
@click.stop @click.stop
> >
<div class="p-8"> <div class="p-8">
<div class="flex items-center justify-between mb-8"> <div class="flex items-center justify-between mb-8">
<div> <div>
<h3 class="text-xl font-black text-zinc-100 tracking-tight flex items-center gap-3"> <h3 class="text-xl font-black text-zinc-900 tracking-tight flex items-center gap-3">
<div <div
class="w-10 h-10 rounded-2xl bg-blue-600/10 flex items-center justify-center text-blue-500" class="w-10 h-10 rounded-2xl bg-blue-50 flex items-center justify-center text-blue-500"
> >
<User :size="20" /> <User :size="20" />
</div> </div>
@@ -962,7 +962,7 @@
<p class="text-xs text-zinc-500 mt-1 ml-13">查看完整的账户资料与操作记录</p> <p class="text-xs text-zinc-500 mt-1 ml-13">查看完整的账户资料与操作记录</p>
</div> </div>
<button <button
class="p-3 bg-zinc-800/50 hover:bg-zinc-800 text-zinc-500 hover:text-zinc-200 rounded-2xl transition-all" class="p-3 bg-zinc-100/50 hover:bg-zinc-100 text-zinc-500 hover:text-zinc-700 rounded-2xl transition-all"
@click="closeUserDetailModal" @click="closeUserDetailModal"
> >
<X :size="20" /> <X :size="20" />
@@ -982,27 +982,27 @@
基本信息 基本信息
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div class="p-4 bg-zinc-950/50 border border-zinc-800/50 rounded-2xl space-y-1"> <div class="p-4 bg-white/70 border border-zinc-200 rounded-2xl space-y-1">
<div class="text-[10px] font-black text-zinc-600 uppercase tracking-tighter"> <div class="text-[10px] font-black text-zinc-600 uppercase tracking-tighter">
用户 ID 用户 ID
</div> </div>
<div class="text-sm font-bold text-zinc-300">{{ selectedUserDetail.id }}</div> <div class="text-sm font-bold text-zinc-600">{{ selectedUserDetail.id }}</div>
</div> </div>
<div class="p-4 bg-zinc-950/50 border border-zinc-800/50 rounded-2xl space-y-1"> <div class="p-4 bg-white/70 border border-zinc-200 rounded-2xl space-y-1">
<div class="text-[10px] font-black text-zinc-600 uppercase tracking-tighter"> <div class="text-[10px] font-black text-zinc-600 uppercase tracking-tighter">
姓名 姓名
</div> </div>
<div class="text-sm font-bold text-zinc-300">{{ selectedUserDetail.name }}</div> <div class="text-sm font-bold text-zinc-600">{{ selectedUserDetail.name }}</div>
</div> </div>
<div class="p-4 bg-zinc-950/50 border border-zinc-800/50 rounded-2xl space-y-1"> <div class="p-4 bg-white/70 border border-zinc-200 rounded-2xl space-y-1">
<div class="text-[10px] font-black text-zinc-600 uppercase tracking-tighter"> <div class="text-[10px] font-black text-zinc-600 uppercase tracking-tighter">
用户名 用户名
</div> </div>
<div class="text-sm font-bold text-zinc-300"> <div class="text-sm font-bold text-zinc-600">
{{ selectedUserDetail.username }} {{ selectedUserDetail.username }}
</div> </div>
</div> </div>
<div class="p-4 bg-zinc-950/50 border border-zinc-800/50 rounded-2xl space-y-1"> <div class="p-4 bg-white/70 border border-zinc-200 rounded-2xl space-y-1">
<div class="text-[10px] font-black text-zinc-600 uppercase tracking-tighter"> <div class="text-[10px] font-black text-zinc-600 uppercase tracking-tighter">
角色权限 角色权限
</div> </div>
@@ -1017,19 +1017,19 @@
</span> </span>
</div> </div>
</div> </div>
<div class="p-4 bg-zinc-950/50 border border-zinc-800/50 rounded-2xl space-y-1"> <div class="p-4 bg-white/70 border border-zinc-200 rounded-2xl space-y-1">
<div class="text-[10px] font-black text-zinc-600 uppercase tracking-tighter"> <div class="text-[10px] font-black text-zinc-600 uppercase tracking-tighter">
年级 年级
</div> </div>
<div class="text-sm font-bold text-zinc-300"> <div class="text-sm font-bold text-zinc-600">
{{ selectedUserDetail.grade || '未设置' }} {{ selectedUserDetail.grade || '未设置' }}
</div> </div>
</div> </div>
<div class="p-4 bg-zinc-950/50 border border-zinc-800/50 rounded-2xl space-y-1"> <div class="p-4 bg-white/70 border border-zinc-200 rounded-2xl space-y-1">
<div class="text-[10px] font-black text-zinc-600 uppercase tracking-tighter"> <div class="text-[10px] font-black text-zinc-600 uppercase tracking-tighter">
班级 班级
</div> </div>
<div class="text-sm font-bold text-zinc-300"> <div class="text-sm font-bold text-zinc-600">
{{ selectedUserDetail.class || '未设置' }} {{ selectedUserDetail.class || '未设置' }}
</div> </div>
</div> </div>
@@ -1046,7 +1046,7 @@
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div <div
class="p-4 bg-zinc-950/50 border border-zinc-800/50 rounded-2xl flex items-center justify-between" class="p-4 bg-white/70 border border-zinc-200 rounded-2xl flex items-center justify-between"
> >
<div class="space-y-1"> <div class="space-y-1">
<div class="text-[10px] font-black text-zinc-600 uppercase tracking-tighter"> <div class="text-[10px] font-black text-zinc-600 uppercase tracking-tighter">
@@ -1089,7 +1089,7 @@
</div> </div>
</div> </div>
<div <div
class="p-4 bg-zinc-950/50 border border-zinc-800/50 rounded-2xl flex items-center justify-between" class="p-4 bg-white/70 border border-zinc-200 rounded-2xl flex items-center justify-between"
> >
<div class="space-y-1"> <div class="space-y-1">
<div class="text-[10px] font-black text-zinc-600 uppercase tracking-tighter"> <div class="text-[10px] font-black text-zinc-600 uppercase tracking-tighter">
@@ -1113,7 +1113,7 @@
'w-8 h-8 rounded-xl flex items-center justify-center', 'w-8 h-8 rounded-xl flex items-center justify-center',
selectedUserDetail.meowNickname selectedUserDetail.meowNickname
? 'bg-emerald-500/10 text-emerald-500' ? 'bg-emerald-500/10 text-emerald-500'
: 'bg-zinc-800 text-zinc-600' : 'bg-zinc-100 text-zinc-600'
]" ]"
> >
<AtSign :size="16" /> <AtSign :size="16" />
@@ -1133,18 +1133,18 @@
</div> </div>
<div class="space-y-3"> <div class="space-y-3">
<div <div
class="flex items-center justify-between text-xs p-3 bg-zinc-950/30 rounded-xl border border-zinc-800/30" class="flex items-center justify-between text-xs p-3 bg-white/60 rounded-xl border border-zinc-200/30"
> >
<span class="text-zinc-500">最后登录</span> <span class="text-zinc-500">最后登录</span>
<span class="text-zinc-300 font-medium">{{ <span class="text-zinc-600 font-medium">{{
formatDate(selectedUserDetail.lastLogin) formatDate(selectedUserDetail.lastLogin)
}}</span> }}</span>
</div> </div>
<div <div
class="flex items-center justify-between text-xs p-3 bg-zinc-950/30 rounded-xl border border-zinc-800/30" class="flex items-center justify-between text-xs p-3 bg-white/60 rounded-xl border border-zinc-200/30"
> >
<span class="text-zinc-500">登录 IP</span> <span class="text-zinc-500">登录 IP</span>
<span class="text-zinc-300 font-medium">{{ <span class="text-zinc-600 font-medium">{{
selectedUserDetail.lastLoginIp || '未知' selectedUserDetail.lastLoginIp || '未知'
}}</span> }}</span>
</div> </div>
@@ -1159,18 +1159,18 @@
</div> </div>
<div class="space-y-3"> <div class="space-y-3">
<div <div
class="flex items-center justify-between text-xs p-3 bg-zinc-950/30 rounded-xl border border-zinc-800/30" class="flex items-center justify-between text-xs p-3 bg-white/60 rounded-xl border border-zinc-200/30"
> >
<span class="text-zinc-500">创建时间</span> <span class="text-zinc-500">创建时间</span>
<span class="text-zinc-300 font-medium">{{ <span class="text-zinc-600 font-medium">{{
formatDate(selectedUserDetail.createdAt) formatDate(selectedUserDetail.createdAt)
}}</span> }}</span>
</div> </div>
<div <div
class="flex items-center justify-between text-xs p-3 bg-zinc-950/30 rounded-xl border border-zinc-800/30" class="flex items-center justify-between text-xs p-3 bg-white/60 rounded-xl border border-zinc-200/30"
> >
<span class="text-zinc-500">最近更新</span> <span class="text-zinc-500">最近更新</span>
<span class="text-zinc-300 font-medium">{{ <span class="text-zinc-600 font-medium">{{
formatDate(selectedUserDetail.updatedAt) formatDate(selectedUserDetail.updatedAt)
}}</span> }}</span>
</div> </div>
@@ -1201,21 +1201,21 @@
<div <div
v-else-if="statusLogs.length === 0" v-else-if="statusLogs.length === 0"
class="py-12 text-center bg-zinc-950/30 border border-zinc-800/50 rounded-3xl" class="py-12 text-center bg-white/60 border border-zinc-200 rounded-3xl"
> >
<p class="text-xs text-zinc-600">暂无状态变更记录</p> <p class="text-xs text-zinc-600">暂无状态变更记录</p>
</div> </div>
<div v-else class="space-y-4"> <div v-else class="space-y-4">
<div <div
class="relative pl-6 space-y-6 before:absolute before:left-[11px] before:top-2 before:bottom-2 before:w-0.5 before:bg-zinc-800" class="relative pl-6 space-y-6 before:absolute before:left-[11px] before:top-2 before:bottom-2 before:w-0.5 before:bg-zinc-100"
> >
<div v-for="log in statusLogs" :key="log.id" class="relative"> <div v-for="log in statusLogs" :key="log.id" class="relative">
<div <div
class="absolute -left-[23px] top-1.5 w-3 h-3 rounded-full bg-zinc-900 border-2 border-zinc-700 ring-4 ring-zinc-900" class="absolute -left-[23px] top-1.5 w-3 h-3 rounded-full bg-white border-2 border-zinc-200 ring-4 ring-zinc-900"
/> />
<div <div
class="p-4 bg-zinc-950/50 border border-zinc-800/50 rounded-2xl space-y-3" class="p-4 bg-white/70 border border-zinc-200 rounded-2xl space-y-3"
> >
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
@@ -1244,7 +1244,7 @@
<div <div
v-if="log.reason" v-if="log.reason"
class="text-xs text-zinc-400 bg-zinc-900/50 p-2.5 rounded-xl border border-zinc-800/30 leading-relaxed" class="text-xs text-zinc-400 bg-zinc-50/50 p-2.5 rounded-xl border border-zinc-200/30 leading-relaxed"
> >
<span class="text-zinc-600 font-bold mr-1">原因:</span> <span class="text-zinc-600 font-bold mr-1">原因:</span>
{{ log.reason }} {{ log.reason }}
@@ -1252,12 +1252,12 @@
<div class="flex items-center gap-2 text-[10px]"> <div class="flex items-center gap-2 text-[10px]">
<div <div
class="w-4 h-4 rounded-full bg-zinc-800 flex items-center justify-center text-zinc-500" class="w-4 h-4 rounded-full bg-zinc-100 flex items-center justify-center text-zinc-500"
> >
<User :size="8" /> <User :size="8" />
</div> </div>
<span class="text-zinc-500">操作者:</span> <span class="text-zinc-500">操作者:</span>
<span class="text-zinc-300 font-bold">{{ <span class="text-zinc-600 font-bold">{{
log.operator?.name || '系统' log.operator?.name || '系统'
}}</span> }}</span>
</div> </div>
@@ -1277,7 +1277,7 @@
<div class="mt-8"> <div class="mt-8">
<button <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" class="w-full px-6 py-4 bg-zinc-100 hover:bg-zinc-200 text-zinc-600 text-xs font-black rounded-2xl transition-all uppercase tracking-widest"
@click="closeUserDetailModal" @click="closeUserDetailModal"
> >
关闭窗口 关闭窗口
+15 -15
View File
@@ -26,16 +26,16 @@
> >
<div <div
v-if="show" v-if="show"
class="relative w-full max-w-4xl max-h-[85vh] flex flex-col bg-zinc-900 border border-zinc-800 rounded-xl shadow-2xl overflow-hidden" class="relative w-full max-w-4xl max-h-[85vh] flex flex-col bg-white border border-zinc-200 rounded-xl shadow-2xl overflow-hidden"
@click.stop @click.stop
> >
<!-- 头部 --> <!-- 头部 -->
<div <div
class="flex items-center justify-between px-6 py-4 border-b border-zinc-800 bg-zinc-900/50 backdrop-blur-sm" class="flex items-center justify-between px-6 py-4 border-b border-zinc-200 bg-zinc-50/50 backdrop-blur-sm"
> >
<h3 class="text-lg font-semibold text-white">用户歌曲信息</h3> <h3 class="text-lg font-semibold text-white">用户歌曲信息</h3>
<button <button
class="p-2 text-zinc-400 hover:text-white hover:bg-zinc-800 rounded-lg transition-colors" class="p-2 text-zinc-400 hover:text-zinc-900 hover:bg-zinc-100 rounded-lg transition-colors"
@click="$emit('close')" @click="$emit('close')"
> >
<svg <svg
@@ -56,7 +56,7 @@
</div> </div>
<!-- 主体内容 --> <!-- 主体内容 -->
<div class="flex-1 flex flex-col min-h-0 overflow-hidden bg-zinc-900"> <div class="flex-1 flex flex-col min-h-0 overflow-hidden bg-white">
<!-- 加载状态 --> <!-- 加载状态 -->
<div v-if="loading" class="flex-1 flex flex-col items-center justify-center"> <div v-if="loading" class="flex-1 flex flex-col items-center justify-center">
<LoadingState message="加载数据中..." spinner-type="circle" /> <LoadingState message="加载数据中..." spinner-type="circle" />
@@ -80,7 +80,7 @@
<!-- 内容 --> <!-- 内容 -->
<div v-else-if="userSongs" class="flex-1 flex flex-col min-h-0"> <div v-else-if="userSongs" class="flex-1 flex flex-col min-h-0">
<!-- 用户信息头部 --> <!-- 用户信息头部 -->
<div class="px-6 py-4 bg-zinc-900 border-b border-zinc-800"> <div class="px-6 py-4 bg-white border-b border-zinc-200">
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<h4 class="text-base font-medium text-white">{{ userSongs.user.name }}</h4> <h4 class="text-base font-medium text-white">{{ userSongs.user.name }}</h4>
@@ -99,7 +99,7 @@
<!-- 工具栏 (标签页 + 过滤) --> <!-- 工具栏 (标签页 + 过滤) -->
<div <div
class="flex flex-col sm:flex-row items-center justify-between border-b border-zinc-800 bg-zinc-900/50" class="flex flex-col sm:flex-row items-center justify-between border-b border-zinc-200 bg-zinc-50/50"
> >
<!-- 标签页 --> <!-- 标签页 -->
<div class="flex w-full sm:w-auto overflow-x-auto no-scrollbar"> <div class="flex w-full sm:w-auto overflow-x-auto no-scrollbar">
@@ -110,7 +110,7 @@
:class=" :class="
activeTab === tab.id activeTab === tab.id
? 'text-blue-500' ? 'text-blue-500'
: 'text-zinc-400 hover:text-white hover:bg-zinc-800/50' : 'text-zinc-400 hover:text-zinc-900 hover:bg-zinc-100/50'
" "
@click="activeTab = tab.id" @click="activeTab = tab.id"
> >
@@ -120,7 +120,7 @@
:class=" :class="
activeTab === tab.id activeTab === tab.id
? 'bg-blue-500/10 text-blue-500' ? 'bg-blue-500/10 text-blue-500'
: 'bg-zinc-800 text-zinc-500' : 'bg-zinc-100 text-zinc-500'
" "
> >
{{ getTabCount(tab.id) }} {{ getTabCount(tab.id) }}
@@ -135,7 +135,7 @@
<!-- 学期过滤器 --> <!-- 学期过滤器 -->
<div <div
class="w-full sm:w-auto px-4 py-2 border-t sm:border-t-0 sm:border-l border-zinc-800 flex items-center gap-3 bg-zinc-900/30" class="w-full sm:w-auto px-4 py-2 border-t sm:border-t-0 sm:border-l border-zinc-200 flex items-center gap-3 bg-zinc-50/30"
> >
<CustomSelect <CustomSelect
v-model="selectedSemester" v-model="selectedSemester"
@@ -160,7 +160,7 @@
<div <div
v-for="song in filteredSongs" v-for="song in filteredSongs"
:key="song.id" :key="song.id"
class="group flex flex-col sm:flex-row sm:items-center justify-between gap-4 p-4 rounded-lg border border-zinc-800 bg-zinc-800/20 hover:bg-zinc-800/50 hover:border-zinc-700 transition-all duration-200" class="group flex flex-col sm:flex-row sm:items-center justify-between gap-4 p-4 rounded-lg border border-zinc-200 bg-zinc-100/20 hover:bg-zinc-100/50 hover:border-zinc-200 transition-all duration-200"
> >
<!-- 歌曲信息 --> <!-- 歌曲信息 -->
<div class="flex-1 min-w-0"> <div class="flex-1 min-w-0">
@@ -168,7 +168,7 @@
<h5 class="text-sm font-medium text-white truncate">{{ song.title }}</h5> <h5 class="text-sm font-medium text-white truncate">{{ song.title }}</h5>
<span <span
v-if="song.semester" v-if="song.semester"
class="px-1.5 py-0.5 rounded text-[10px] font-medium bg-zinc-800 text-zinc-400 border border-zinc-700" class="px-1.5 py-0.5 rounded text-[10px] font-medium bg-zinc-100 text-zinc-400 border border-zinc-200"
> >
{{ song.semester }} {{ song.semester }}
</span> </span>
@@ -214,7 +214,7 @@
<!-- Submitter Info (if available) --> <!-- Submitter Info (if available) -->
<div v-if="song.requester" class="hidden sm:block text-right min-w-[80px]"> <div v-if="song.requester" class="hidden sm:block text-right min-w-[80px]">
<div class="text-xs text-zinc-300">{{ song.requester.name }}</div> <div class="text-xs text-zinc-600">{{ song.requester.name }}</div>
<div class="text-[10px] text-zinc-500"> <div class="text-[10px] text-zinc-500">
{{ song.requester.grade }}{{ song.requester.class }} {{ song.requester.grade }}{{ song.requester.class }}
</div> </div>
@@ -224,10 +224,10 @@
<!-- 投稿人信息 (移动端) --> <!-- 投稿人信息 (移动端) -->
<div <div
v-if="song.requester" v-if="song.requester"
class="sm:hidden pt-3 mt-1 border-t border-zinc-800/50 flex justify-between items-center text-xs" class="sm:hidden pt-3 mt-1 border-t border-zinc-200 flex justify-between items-center text-xs"
> >
<span class="text-zinc-500">投稿人:</span> <span class="text-zinc-500">投稿人:</span>
<span class="text-zinc-300"> <span class="text-zinc-600">
{{ song.requester.name }} {{ song.requester.name }}
<span class="text-zinc-500 ml-1"> <span class="text-zinc-500 ml-1">
{{ song.requester.grade }}{{ song.requester.class }} {{ song.requester.grade }}{{ song.requester.class }}
@@ -422,7 +422,7 @@ const getStatusClasses = (song) => {
return 'bg-emerald-500/10 text-emerald-500 border-emerald-500/20' return 'bg-emerald-500/10 text-emerald-500 border-emerald-500/20'
} }
if (song.scheduled) { if (song.scheduled) {
return 'bg-blue-500/10 text-blue-500 border-blue-500/20' return 'bg-blue-500/10 text-blue-500 border-blue-200'
} }
return 'bg-amber-500/10 text-amber-500 border-amber-500/20' return 'bg-amber-500/10 text-amber-500 border-amber-500/20'
} }
+3 -3
View File
@@ -202,7 +202,7 @@ watch(
} }
.modal-content { .modal-content {
background: var(--surface-primary, var(--bg-primary, #1a1a1a)); background: var(--surface-primary, var(--bg-primary, #ffffff));
border-radius: 16px; border-radius: 16px;
border: 1px solid var(--border-primary, #2a2a2a); border: 1px solid var(--border-primary, #2a2a2a);
width: 100%; width: 100%;
@@ -342,7 +342,7 @@ watch(
.retry-btn { .retry-btn {
background: #667eea; background: #667eea;
color: white; color: var(--text-primary, #09090b);
border: none; border: none;
padding: 8px 16px; padding: 8px 16px;
border-radius: 8px; border-radius: 8px;
@@ -414,7 +414,7 @@ watch(
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
color: white; color: var(--text-primary, #09090b);
font-weight: 600; font-weight: 600;
font-size: 14px; font-size: 14px;
} }
+28 -28
View File
@@ -1,36 +1,36 @@
<template> <template>
<div class="space-y-6 max-w-5xl mx-auto pb-24 md:pb-8 animate-in fade-in slide-in-from-bottom-4 duration-500"> <div class="space-y-6 max-w-5xl mx-auto pb-24 md:pb-8 animate-in fade-in slide-in-from-bottom-4 duration-500">
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<h2 class="text-2xl md:text-3xl font-bold tracking-tight text-zinc-100">工作流与使用教程</h2> <h2 class="text-2xl md:text-3xl font-bold tracking-tight text-zinc-900">工作流与使用教程</h2>
<p class="text-zinc-400">了解校园广播站点歌系统的标准操作流程和功能说明,帮助您更高效地管理点歌与排期。</p> <p class="text-zinc-400">了解校园广播站点歌系统的标准操作流程和功能说明,帮助您更高效地管理点歌与排期。</p>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6"> <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- 歌曲管理流程 --> <!-- 歌曲管理流程 -->
<div class="bg-zinc-900/50 border border-zinc-800 rounded-xl p-6 hover:border-zinc-700 transition-colors"> <div class="bg-zinc-50/50 border border-zinc-200 rounded-xl p-6 hover:border-zinc-200 transition-colors">
<div class="flex items-center gap-3 mb-4 text-blue-400"> <div class="flex items-center gap-3 mb-4 text-blue-700">
<CheckCircle2 class="w-6 h-6" /> <CheckCircle2 class="w-6 h-6" />
<h3 class="text-xl font-semibold">1. 歌曲管理与审核</h3> <h3 class="text-xl font-semibold">1. 歌曲管理与审核</h3>
</div> </div>
<ol class="space-y-4 text-zinc-300"> <ol class="space-y-4 text-zinc-600">
<li class="flex gap-3"> <li class="flex gap-3">
<span class="flex-shrink-0 w-6 h-6 rounded-full bg-blue-500/20 text-blue-400 flex items-center justify-center text-sm font-bold">1</span> <span class="flex-shrink-0 w-6 h-6 rounded-full bg-blue-500/20 text-blue-700 flex items-center justify-center text-sm font-bold">1</span>
<div> <div>
<p class="font-medium text-zinc-100">查看所有歌曲</p> <p class="font-medium text-zinc-900">查看所有歌曲</p>
<p class="text-sm text-zinc-500 mt-1">进入“歌曲管理”页面,系统会列出所有用户提交的歌曲。</p> <p class="text-sm text-zinc-500 mt-1">进入“歌曲管理”页面,系统会列出所有用户提交的歌曲。</p>
</div> </div>
</li> </li>
<li class="flex gap-3"> <li class="flex gap-3">
<span class="flex-shrink-0 w-6 h-6 rounded-full bg-blue-500/20 text-blue-400 flex items-center justify-center text-sm font-bold">2</span> <span class="flex-shrink-0 w-6 h-6 rounded-full bg-blue-500/20 text-blue-700 flex items-center justify-center text-sm font-bold">2</span>
<div> <div>
<p class="font-medium text-zinc-100">试听与检查</p> <p class="font-medium text-zinc-900">试听与检查</p>
<p class="text-sm text-zinc-500 mt-1">点击播放按钮试听歌曲质量,检查留言内容是否合规,确保没有不当言论。</p> <p class="text-sm text-zinc-500 mt-1">点击播放按钮试听歌曲质量,检查留言内容是否合规,确保没有不当言论。</p>
</div> </div>
</li> </li>
<li class="flex gap-3"> <li class="flex gap-3">
<span class="flex-shrink-0 w-6 h-6 rounded-full bg-blue-500/20 text-blue-400 flex items-center justify-center text-sm font-bold">3</span> <span class="flex-shrink-0 w-6 h-6 rounded-full bg-blue-500/20 text-blue-700 flex items-center justify-center text-sm font-bold">3</span>
<div> <div>
<p class="font-medium text-zinc-100">拒绝或修改状态</p> <p class="font-medium text-zinc-900">拒绝或修改状态</p>
<p class="text-sm text-zinc-500 mt-1">如不符合要求,可以在歌曲管理中拒绝该歌曲,并填写退回原因;审核通过的歌曲可直接用于排期。</p> <p class="text-sm text-zinc-500 mt-1">如不符合要求,可以在歌曲管理中拒绝该歌曲,并填写退回原因;审核通过的歌曲可直接用于排期。</p>
</div> </div>
</li> </li>
@@ -38,30 +38,30 @@
</div> </div>
<!-- 排期流程 --> <!-- 排期流程 -->
<div class="bg-zinc-900/50 border border-zinc-800 rounded-xl p-6 hover:border-zinc-700 transition-colors"> <div class="bg-zinc-50/50 border border-zinc-200 rounded-xl p-6 hover:border-zinc-200 transition-colors">
<div class="flex items-center gap-3 mb-4 text-emerald-400"> <div class="flex items-center gap-3 mb-4 text-emerald-700">
<CalendarDays class="w-6 h-6" /> <CalendarDays class="w-6 h-6" />
<h3 class="text-xl font-semibold">2. 节目排期流程</h3> <h3 class="text-xl font-semibold">2. 节目排期流程</h3>
</div> </div>
<ol class="space-y-4 text-zinc-300"> <ol class="space-y-4 text-zinc-600">
<li class="flex gap-3"> <li class="flex gap-3">
<span class="flex-shrink-0 w-6 h-6 rounded-full bg-emerald-500/20 text-emerald-400 flex items-center justify-center text-sm font-bold">1</span> <span class="flex-shrink-0 w-6 h-6 rounded-full bg-emerald-500/20 text-emerald-700 flex items-center justify-center text-sm font-bold">1</span>
<div> <div>
<p class="font-medium text-zinc-100">选择日期与时段</p> <p class="font-medium text-zinc-900">选择日期与时段</p>
<p class="text-sm text-zinc-500 mt-1">在“排期管理”中,选择目标播放日期和播出时段。</p> <p class="text-sm text-zinc-500 mt-1">在“排期管理”中,选择目标播放日期和播出时段。</p>
</div> </div>
</li> </li>
<li class="flex gap-3"> <li class="flex gap-3">
<span class="flex-shrink-0 w-6 h-6 rounded-full bg-emerald-500/20 text-emerald-400 flex items-center justify-center text-sm font-bold">2</span> <span class="flex-shrink-0 w-6 h-6 rounded-full bg-emerald-500/20 text-emerald-700 flex items-center justify-center text-sm font-bold">2</span>
<div> <div>
<p class="font-medium text-zinc-100">拖拽编排</p> <p class="font-medium text-zinc-900">拖拽编排</p>
<p class="text-sm text-zinc-500 mt-1">将左侧“待排歌曲”拖拽至右侧的“播放列表”中,可上下拖拽调整播放顺序。</p> <p class="text-sm text-zinc-500 mt-1">将左侧“待排歌曲”拖拽至右侧的“播放列表”中,可上下拖拽调整播放顺序。</p>
</div> </div>
</li> </li>
<li class="flex gap-3"> <li class="flex gap-3">
<span class="flex-shrink-0 w-6 h-6 rounded-full bg-emerald-500/20 text-emerald-400 flex items-center justify-center text-sm font-bold">3</span> <span class="flex-shrink-0 w-6 h-6 rounded-full bg-emerald-500/20 text-emerald-700 flex items-center justify-center text-sm font-bold">3</span>
<div> <div>
<p class="font-medium text-zinc-100">发布排期</p> <p class="font-medium text-zinc-900">发布排期</p>
<p class="text-sm text-zinc-500 mt-1">排期完成后,点击“发布排期”。发布后用户即可在前端页面看到该日期的节目单。</p> <p class="text-sm text-zinc-500 mt-1">排期完成后,点击“发布排期”。发布后用户即可在前端页面看到该日期的节目单。</p>
</div> </div>
</li> </li>
@@ -69,23 +69,23 @@
</div> </div>
<!-- 播音流程 --> <!-- 播音流程 -->
<div class="bg-zinc-900/50 border border-zinc-800 rounded-xl p-6 hover:border-zinc-700 transition-colors"> <div class="bg-zinc-50/50 border border-zinc-200 rounded-xl p-6 hover:border-zinc-200 transition-colors">
<div class="flex items-center gap-3 mb-4 text-purple-400"> <div class="flex items-center gap-3 mb-4 text-purple-400">
<Mic2 class="w-6 h-6" /> <Mic2 class="w-6 h-6" />
<h3 class="text-xl font-semibold">3. 播音与打印</h3> <h3 class="text-xl font-semibold">3. 播音与打印</h3>
</div> </div>
<ol class="space-y-4 text-zinc-300"> <ol class="space-y-4 text-zinc-600">
<li class="flex gap-3"> <li class="flex gap-3">
<span class="flex-shrink-0 w-6 h-6 rounded-full bg-purple-500/20 text-purple-400 flex items-center justify-center text-sm font-bold">1</span> <span class="flex-shrink-0 w-6 h-6 rounded-full bg-purple-500/20 text-purple-400 flex items-center justify-center text-sm font-bold">1</span>
<div> <div>
<p class="font-medium text-zinc-100">生成歌单图片/打印</p> <p class="font-medium text-zinc-900">生成歌单图片/打印</p>
<p class="text-sm text-zinc-500 mt-1">播音前,前往“导入酷狗”生成今日节目单图片用于宣传,或在“打印排期”中生成播音员用的纸质稿。</p> <p class="text-sm text-zinc-500 mt-1">播音前,前往“导入酷狗”生成今日节目单图片用于宣传,或在“打印排期”中生成播音员用的纸质稿。</p>
</div> </div>
</li> </li>
<li class="flex gap-3"> <li class="flex gap-3">
<span class="flex-shrink-0 w-6 h-6 rounded-full bg-purple-500/20 text-purple-400 flex items-center justify-center text-sm font-bold">2</span> <span class="flex-shrink-0 w-6 h-6 rounded-full bg-purple-500/20 text-purple-400 flex items-center justify-center text-sm font-bold">2</span>
<div> <div>
<p class="font-medium text-zinc-100">标记已播</p> <p class="font-medium text-zinc-900">标记已播</p>
<p class="text-sm text-zinc-500 mt-1">播音结束后,系统可自动/手动将排期内的歌曲标记为“已播”,方便统计数据。</p> <p class="text-sm text-zinc-500 mt-1">播音结束后,系统可自动/手动将排期内的歌曲标记为“已播”,方便统计数据。</p>
</div> </div>
</li> </li>
@@ -93,22 +93,22 @@
</div> </div>
<!-- 常见问题 --> <!-- 常见问题 -->
<div class="bg-zinc-900/50 border border-zinc-800 rounded-xl p-6 hover:border-zinc-700 transition-colors"> <div class="bg-zinc-50/50 border border-zinc-200 rounded-xl p-6 hover:border-zinc-200 transition-colors">
<div class="flex items-center gap-3 mb-4 text-amber-400"> <div class="flex items-center gap-3 mb-4 text-amber-700">
<HelpCircle class="w-6 h-6" /> <HelpCircle class="w-6 h-6" />
<h3 class="text-xl font-semibold">常见问题 (FAQ)</h3> <h3 class="text-xl font-semibold">常见问题 (FAQ)</h3>
</div> </div>
<div class="space-y-4"> <div class="space-y-4">
<div> <div>
<h4 class="text-sm font-medium text-zinc-200">Q: 为什么找不到某些待排歌曲?</h4> <h4 class="text-sm font-medium text-zinc-700">Q: 为什么找不到某些待排歌曲?</h4>
<p class="text-sm text-zinc-500 mt-1">A: 请检查是否在排期页面的左侧选错了标签(如普通投稿/重播申请),或者该歌曲还未通过审核。</p> <p class="text-sm text-zinc-500 mt-1">A: 请检查是否在排期页面的左侧选错了标签(如普通投稿/重播申请),或者该歌曲还未通过审核。</p>
</div> </div>
<div> <div>
<h4 class="text-sm font-medium text-zinc-200">Q: 什么是选曲日期?</h4> <h4 class="text-sm font-medium text-zinc-700">Q: 什么是选曲日期?</h4>
<p class="text-sm text-zinc-500 mt-1">A: 选曲日期是用户在点歌时可选择的期望播放日期。请在“选曲日期”设置中提前开放可用的日期。</p> <p class="text-sm text-zinc-500 mt-1">A: 选曲日期是用户在点歌时可选择的期望播放日期。请在“选曲日期”设置中提前开放可用的日期。</p>
</div> </div>
<div> <div>
<h4 class="text-sm font-medium text-zinc-200">Q: 权限不足怎么办?</h4> <h4 class="text-sm font-medium text-zinc-700">Q: 权限不足怎么办?</h4>
<p class="text-sm text-zinc-500 mt-1">A: 不同的功能需要不同的管理员角色(如歌曲管理员、超级管理员),请联系超级管理员调整您的角色权限。</p> <p class="text-sm text-zinc-500 mt-1">A: 不同的功能需要不同的管理员角色(如歌曲管理员、超级管理员),请联系超级管理员调整您的角色权限。</p>
</div> </div>
</div> </div>
+5
View File
@@ -162,6 +162,11 @@
<path d="M12 3l1.8 5.4L19 9.7l-4.2 3.6L16.6 19 12 15.8 7.4 19l1.8-5.7L5 9.7l5.2-1.3L12 3z" /> <path d="M12 3l1.8 5.4L19 9.7l-4.2 3.6L16.6 19 12 15.8 7.4 19l1.8-5.7L5 9.7l5.2-1.3L12 3z" />
</g> </g>
<!-- 灯泡图标 (提示) -->
<g v-else-if="name === 'lightbulb'">
<path d="M9 18h6M10 22h4M12 2a7 7 0 0 0-4 12.7c.6.5 1 1.2 1 2V17h6v-.3c0-.8.4-1.5 1-2A7 7 0 0 0 12 2z" />
</g>
<!-- 用户图标 --> <!-- 用户图标 -->
<g v-else-if="name === 'user'"> <g v-else-if="name === 'user'">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" /> <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
+5 -5
View File
@@ -1,5 +1,5 @@
<template> <template>
<div class="admin-body"> <div>
<ClientOnly> <ClientOnly>
<div class="admin-layout" @touchend="handleTouchEnd" @touchstart="handleTouchStart"> <div class="admin-layout" @touchend="handleTouchEnd" @touchstart="handleTouchStart">
<!-- 左侧导航栏 --> <!-- 左侧导航栏 -->
@@ -23,14 +23,14 @@
<!-- 主内容区域 --> <!-- 主内容区域 -->
<main <main
class="flex-1 flex flex-col h-screen overflow-hidden lg:ml-64 relative bg-[#09090b] text-zinc-100" class="flex-1 flex flex-col h-screen overflow-hidden lg:ml-64 relative bg-[#f8fafc] text-zinc-900"
> >
<header <header
class="admin-main-header h-16 shrink-0 flex items-center justify-between px-4 md:px-8 border-b border-zinc-800 bg-zinc-950/60 backdrop-blur-xl z-30" class="admin-main-header h-16 shrink-0 flex items-center justify-between px-4 md:px-8 border-b border-zinc-200 bg-white/80 backdrop-blur-xl z-30"
> >
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<button <button
class="admin-mobile-menu-btn lg:hidden p-2 text-zinc-400 hover:bg-zinc-800 rounded-lg transition-colors" class="admin-mobile-menu-btn lg:hidden p-2 text-zinc-600 hover:bg-zinc-100 rounded-lg transition-colors"
@click="toggleSidebar" @click="toggleSidebar"
> >
<Menu :size="20" /> <Menu :size="20" />
@@ -39,7 +39,7 @@
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<button <button
class="px-3 py-1.5 text-xs font-semibold rounded-lg border border-zinc-700 text-zinc-300 hover:bg-zinc-800 transition-colors" class="px-3 py-1.5 text-xs font-semibold rounded-lg border border-zinc-200 text-zinc-600 hover:bg-zinc-100 transition-colors"
@click="openOnboardingTutorial" @click="openOnboardingTutorial"
> >
新手引导 新手引导