Files
Henry_Du 573e168702 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 处手工精细化修正)
2026-09-19 08:30:59 +08:00

173 lines
5.0 KiB
Vue

<template>
<div
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-zinc-200 text-zinc-900 font-bold text-lg">
为歌曲 "{{ song?.title }}" 创建排期
</h3>
<form @submit.prevent="handleSubmit">
<div class="mb-4">
<label class="block mb-2 font-medium text-zinc-900" for="playDate">播放日期</label>
<input
id="playDate"
v-model="playDate"
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
type="date"
>
</div>
<!-- 播出时段选择 -->
<div v-if="playTimeEnabled" class="mb-4">
<CustomSelect
v-model="playTimeId"
:options="playTimeOptions"
label="播出时段"
placeholder="未指定"
class-name="w-full"
/>
<div
v-if="song?.preferredPlayTime"
class="mt-3 p-3 bg-white/5 rounded-lg text-sm flex items-start gap-2"
>
<Icon class="text-base text-yellow-400" name="lightbulb" size="16" />
<div>
用户期望的播出时段:
<span class="font-medium text-indigo-300">
{{ song.preferredPlayTime.name }}
<template v-if="song.preferredPlayTime.startTime || song.preferredPlayTime.endTime">
({{ formatPlayTimeRange(song.preferredPlayTime) }})
</template>
</span>
</div>
</div>
</div>
<div v-if="error" class="p-3 mt-4 bg-red-500/10 text-red-300 rounded-lg">
{{ error }}
</div>
<div class="flex justify-between gap-4 mt-6">
<button
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"
@click="$emit('cancel')"
>
取消
</button>
<button
:disabled="loading"
class="flex-1 p-3 border-none rounded-lg text-base cursor-pointer transition-all duration-200 bg-indigo-600 text-white hover:bg-indigo-700 hover:-translate-y-px disabled:bg-indigo-500/50 disabled:cursor-not-allowed disabled:transform-none"
type="submit"
>
{{ loading ? '创建中...' : '创建排期' }}
</button>
</div>
</form>
</div>
</template>
<script setup>
import { onMounted, ref, computed } from 'vue'
import { useSongs } from '~/composables/useSongs'
import CustomSelect from '~/components/UI/Common/CustomSelect.vue'
const props = defineProps({
song: {
type: Object,
required: true
},
loading: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['schedule', 'cancel'])
const playDate = ref('')
const playTimeId = ref('')
const error = ref('')
const playTimes = ref([])
const { playTimeEnabled } = useSongs()
// 转换播出时段为 CustomSelect 选项格式
const playTimeOptions = computed(() => {
const options = [{ label: '未指定', value: '' }]
if (playTimes.value && playTimes.value.length > 0) {
playTimes.value.forEach((pt) => {
options.push({
label: `${pt.name} (${pt.startTime} - ${pt.endTime})`,
value: pt.id
})
})
}
return options
})
// 初始化
onMounted(async () => {
await fetchPlayTimes()
// 如果歌曲有期望的播出时段,默认选择该时段
if (props.song?.preferredPlayTimeId) {
playTimeId.value = props.song.preferredPlayTimeId
}
})
// 获取播出时段
const fetchPlayTimes = async () => {
try {
// 使用useSongs中的方法获取播放时段
await useSongs().fetchPlayTimes()
const response = await fetch('/api/admin/play-times')
if (response.ok) {
const data = await response.json()
// 只显示启用的播放时段
playTimes.value = data.filter((pt) => pt.enabled)
}
} catch (err) {
console.error('获取播出时段失败:', err)
}
}
// 格式化播出时段时间范围
const formatPlayTimeRange = (playTime) => {
if (!playTime) return ''
if (playTime.startTime && playTime.endTime) {
return `${playTime.startTime} - ${playTime.endTime}`
} else if (playTime.startTime) {
return `${playTime.startTime} 开始`
} else if (playTime.endTime) {
return `${playTime.endTime} 结束`
}
return '不限时间'
}
const handleSubmit = () => {
error.value = ''
if (!playDate.value) {
error.value = '请选择播放日期'
return
}
const selectedDate = new Date(playDate.value)
// 播出时段ID需要转换为数字或null
const schedulePlayTimeId = playTimeId.value ? parseInt(playTimeId.value) : null
emit('schedule', {
songId: props.song.id,
playDate: selectedDate,
playTimeId: schedulePlayTimeId
})
}
</script>