818 lines
24 KiB
TypeScript
818 lines
24 KiB
TypeScript
import { computed, ref } from 'vue'
|
|
import { useAuth } from './useAuth'
|
|
import { extractErrorCode, useServerErrors } from './useLocaleText'
|
|
import { getGlobalDedup } from './useRequestDedup'
|
|
import { useLocale } from '~/utils/locale'
|
|
import type { PlayTime, Schedule, Song } from '~/types'
|
|
|
|
export const useSongs = () => {
|
|
const { isAuthenticated, user, getAuthConfig } = useAuth()
|
|
const { songs: songsLocale } = useLocale()
|
|
const { localize: localizeServerError } = useServerErrors()
|
|
const dedup = getGlobalDedup()
|
|
|
|
const songs = ref<Song[]>([])
|
|
const publicSchedules = ref<Schedule[]>([])
|
|
const publicSongs = ref<Song[]>([])
|
|
const loading = ref(false)
|
|
const error = ref('')
|
|
const notification = ref({ show: false, message: '', type: '' })
|
|
const playTimes = ref<PlayTime[]>([])
|
|
const playTimeEnabled = ref(false)
|
|
const songCount = ref(0)
|
|
const requestFormLocale = computed(() => songsLocale.value?.requestForm)
|
|
const actionLocale = computed(() => songsLocale.value.actions)
|
|
|
|
let songsRequestVersion = 0
|
|
let publicSchedulesRequestVersion = 0
|
|
|
|
// 显示通知
|
|
const showNotification = (message: string, type: 'success' | 'error' | 'info' = 'info') => {
|
|
// 使用全局通知
|
|
if (window.$showNotification) {
|
|
window.$showNotification(message, type)
|
|
} else {
|
|
// 备用方案
|
|
notification.value = {
|
|
show: true,
|
|
message,
|
|
type
|
|
}
|
|
|
|
// 3秒后自动关闭
|
|
setTimeout(() => {
|
|
notification.value.show = false
|
|
}, 3000)
|
|
}
|
|
}
|
|
|
|
// 获取播放时段列表
|
|
const fetchPlayTimes = async () => {
|
|
loading.value = true
|
|
error.value = ''
|
|
|
|
try {
|
|
const response = await $fetch('/api/play-times')
|
|
playTimeEnabled.value = response.enabled
|
|
|
|
// 确保类型兼容性
|
|
if (response.playTimes && Array.isArray(response.playTimes)) {
|
|
playTimes.value = response.playTimes.map((pt) => ({
|
|
id: pt.id,
|
|
name: pt.name,
|
|
startTime: pt.startTime || undefined,
|
|
endTime: pt.endTime || undefined,
|
|
enabled: pt.enabled,
|
|
description: pt.description || undefined
|
|
}))
|
|
} else {
|
|
playTimes.value = []
|
|
}
|
|
|
|
return response
|
|
} catch (err: any) {
|
|
error.value = err.message || '获取播放时段失败'
|
|
return { enabled: false, playTimes: [] }
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
// 获取歌曲列表
|
|
const fetchSongs = async (silent = false, semester?: string, forceRefresh = false) => {
|
|
const requestVersion = ++songsRequestVersion
|
|
if (!silent) {
|
|
loading.value = true
|
|
}
|
|
error.value = ''
|
|
|
|
try {
|
|
const requestUserId = user.value?.id ?? null
|
|
const requestParams = {
|
|
semester: semester || '',
|
|
audience: requestUserId ? `user:${requestUserId}` : 'guest',
|
|
role: user.value?.role || ''
|
|
}
|
|
const request = async () => {
|
|
// 构建URL参数
|
|
const params = new URLSearchParams()
|
|
if (semester) {
|
|
params.append('semester', semester)
|
|
}
|
|
const url = `/api/songs${params.toString() ? '?' + params.toString() : ''}`
|
|
|
|
// API请求
|
|
return await $fetch(url, {
|
|
...getAuthConfig()
|
|
})
|
|
}
|
|
const response = forceRefresh
|
|
? await request()
|
|
: await dedup.dedupedRequest('songs', request, requestParams)
|
|
|
|
if (
|
|
requestVersion !== songsRequestVersion ||
|
|
(user.value?.id ?? null) !== requestUserId ||
|
|
(user.value?.role ?? '') !== (requestParams.role || '')
|
|
)
|
|
return
|
|
|
|
// 正确解析API返回的数据结构
|
|
if (response && response.success && response.data && Array.isArray(response.data.songs)) {
|
|
songs.value = response.data.songs as Song[]
|
|
} else {
|
|
songs.value = []
|
|
console.warn('API返回的数据格式不正确:', response)
|
|
}
|
|
} catch (err: any) {
|
|
if (requestVersion === songsRequestVersion) {
|
|
error.value = err.message || '获取歌曲列表失败'
|
|
}
|
|
} finally {
|
|
if (!silent && requestVersion === songsRequestVersion) {
|
|
loading.value = false
|
|
}
|
|
}
|
|
}
|
|
|
|
// 静默刷新歌曲列表 - 不显示加载状态
|
|
const refreshSongsSilent = async () => {
|
|
return fetchSongs(true)
|
|
}
|
|
|
|
// 从排期数据中提取歌曲信息
|
|
const extractSongsFromSchedules = (schedules: Schedule[]): Song[] => {
|
|
const songsMap = new Map<string, Song>()
|
|
|
|
schedules.forEach((schedule) => {
|
|
if (schedule.song) {
|
|
const songId = String(schedule.song.id)
|
|
if (!songsMap.has(songId)) {
|
|
// 将排期中的歌曲信息转换为完整的Song对象
|
|
const completeSong: Song = {
|
|
id: schedule.song.id,
|
|
title: schedule.song.title,
|
|
artist: schedule.song.artist,
|
|
requester: schedule.song.requester,
|
|
requesterGrade: schedule.song.requesterGrade || null,
|
|
requesterClass: schedule.song.requesterClass || null,
|
|
requesterId: 0, // 默认值,公共API不提供这个信息
|
|
voteCount: schedule.song.voteCount,
|
|
played: schedule.song.played || false,
|
|
playedAt: schedule.song.playedAt || null,
|
|
semester: schedule.song.semester || null,
|
|
requestedAt: schedule.song.requestedAt || new Date().toISOString(),
|
|
cover: schedule.song.cover || null,
|
|
musicPlatform: schedule.song.musicPlatform || null,
|
|
musicId: schedule.song.musicId || null,
|
|
durationSeconds: schedule.song.durationSeconds || null,
|
|
usedCardCode: schedule.song.usedCardCode === true,
|
|
hasSubmissionNote: schedule.song.hasSubmissionNote === true,
|
|
submissionNote: schedule.song.submissionNote || null,
|
|
submissionNotePublic: schedule.song.submissionNotePublic === true
|
|
}
|
|
songsMap.set(songId, completeSong)
|
|
}
|
|
}
|
|
})
|
|
|
|
return Array.from(songsMap.values())
|
|
}
|
|
|
|
// 获取公共排期(无需登录)
|
|
const fetchPublicSchedules = async (silent = false, semester?: string, forceRefresh = false) => {
|
|
const requestVersion = ++publicSchedulesRequestVersion
|
|
if (!silent) {
|
|
loading.value = true
|
|
}
|
|
error.value = ''
|
|
|
|
try {
|
|
const requestUserId = user.value?.id ?? null
|
|
const requestParams = {
|
|
semester: semester || '',
|
|
audience: requestUserId ? `user:${requestUserId}` : 'guest',
|
|
role: user.value?.role || ''
|
|
}
|
|
const request = async () => {
|
|
// 构建URL参数
|
|
const params = new URLSearchParams()
|
|
if (semester) {
|
|
params.append('semester', semester)
|
|
}
|
|
const url = `/api/songs/public${params.toString() ? '?' + params.toString() : ''}`
|
|
|
|
const response = await $fetch(url, {
|
|
...getAuthConfig()
|
|
})
|
|
return response
|
|
}
|
|
const data = forceRefresh
|
|
? await request()
|
|
: await dedup.dedupedRequest('public-schedules', request, requestParams)
|
|
|
|
if (
|
|
requestVersion !== publicSchedulesRequestVersion ||
|
|
(user.value?.id ?? null) !== requestUserId ||
|
|
(user.value?.role ?? '') !== (requestParams.role || '')
|
|
)
|
|
return
|
|
|
|
// 确保每个排期的歌曲都有played属性,并处理null/undefined转换
|
|
const processedData = data.map((schedule: any) => {
|
|
// 处理歌曲属性
|
|
if (schedule.song && schedule.song.played === undefined) {
|
|
schedule.song.played = false
|
|
}
|
|
|
|
// 处理播放时间属性
|
|
let processedPlayTime = null
|
|
if (schedule.playTime) {
|
|
processedPlayTime = {
|
|
...schedule.playTime,
|
|
startTime: schedule.playTime.startTime || undefined,
|
|
endTime: schedule.playTime.endTime || undefined
|
|
}
|
|
}
|
|
|
|
// 返回符合Schedule类型的对象
|
|
return {
|
|
...schedule,
|
|
playTime: processedPlayTime
|
|
} as Schedule
|
|
})
|
|
|
|
publicSchedules.value = processedData
|
|
|
|
// 直接从排期数据中提取歌曲信息,避免重复请求
|
|
publicSongs.value = extractSongsFromSchedules(processedData)
|
|
} catch (err: any) {
|
|
if (requestVersion === publicSchedulesRequestVersion) {
|
|
error.value = err.message || '获取排期失败'
|
|
}
|
|
} finally {
|
|
if (!silent && requestVersion === publicSchedulesRequestVersion) {
|
|
loading.value = false
|
|
}
|
|
}
|
|
}
|
|
|
|
// 静默刷新公共排期 - 不显示加载状态
|
|
const refreshSchedulesSilent = async () => {
|
|
return fetchPublicSchedules(true)
|
|
}
|
|
|
|
// 请求歌曲
|
|
const requestSong = async (songData: {
|
|
title: string
|
|
artist: string
|
|
preferredPlayTimeId?: number | null
|
|
cover?: string | null
|
|
musicPlatform?: string | null
|
|
musicId?: string | null
|
|
submissionNote?: string | null
|
|
submissionNotePublic?: boolean
|
|
cardCode?: string | null
|
|
}) => {
|
|
if (!isAuthenticated.value) {
|
|
showNotification(actionLocale.value.loginRequest, 'error')
|
|
return null
|
|
}
|
|
|
|
loading.value = true
|
|
error.value = ''
|
|
|
|
try {
|
|
// 使用认证配置
|
|
const authConfig = getAuthConfig()
|
|
|
|
const data = await $fetch('/api/songs/request', {
|
|
method: 'POST',
|
|
body: songData,
|
|
...authConfig
|
|
})
|
|
|
|
// 绕过去重,从数据库重新校准写入后的列表
|
|
await fetchSongs(true, undefined, true)
|
|
|
|
return data
|
|
} catch (err: any) {
|
|
const errorMsg = localizeServerError(err) || '点歌失败'
|
|
// 如果是重复投稿错误,只显示通知而不设置全局错误
|
|
if (errorMsg.includes('已经在列表中') || errorMsg.includes('不能重复投稿')) {
|
|
showNotification(errorMsg, 'info')
|
|
} else {
|
|
error.value = errorMsg
|
|
showNotification(errorMsg, 'error')
|
|
}
|
|
return null
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
// 投票
|
|
const voteSong = async (songId: number | { id: number; unvote?: boolean }) => {
|
|
if (!isAuthenticated.value) {
|
|
showNotification(actionLocale.value.loginVote, 'error')
|
|
return null
|
|
}
|
|
|
|
loading.value = true
|
|
error.value = ''
|
|
|
|
// 处理传入的可能是对象的情况
|
|
const actualSongId = typeof songId === 'object' ? songId.id : songId
|
|
const isUnvote = typeof songId === 'object' && songId.unvote === true
|
|
|
|
try {
|
|
// 检查是否已经投过票(只在正常投票时检查)
|
|
const targetSong = songs.value.find((s) => s.id === actualSongId)
|
|
|
|
if (!isUnvote && targetSong && targetSong.voted) {
|
|
showNotification(actionLocale.value.alreadyVoted, 'info')
|
|
loading.value = false
|
|
return null
|
|
}
|
|
|
|
// 使用认证配置
|
|
const authConfig = getAuthConfig()
|
|
|
|
try {
|
|
const data = await $fetch('/api/songs/vote', {
|
|
method: 'POST',
|
|
body: {
|
|
songId: actualSongId,
|
|
unvote: isUnvote // 添加取消投票参数
|
|
},
|
|
...authConfig
|
|
})
|
|
|
|
// 立即更新本地歌曲的投票状态
|
|
if (targetSong) {
|
|
if (isUnvote) {
|
|
// 取消投票
|
|
targetSong.voted = false
|
|
targetSong.voteCount = Math.max(0, (targetSong.voteCount || 1) - 1)
|
|
showNotification(actionLocale.value.voteCancelled(targetSong.title), 'success')
|
|
} else {
|
|
// 正常投票
|
|
targetSong.voted = true
|
|
targetSong.voteCount = (targetSong.voteCount || 0) + 1
|
|
showNotification(actionLocale.value.voteSucceeded(targetSong.title), 'success')
|
|
}
|
|
}
|
|
|
|
await fetchSongs(true, undefined, true)
|
|
|
|
return data
|
|
} catch (fetchErr: any) {
|
|
// 处理API返回的错误
|
|
const errorMsg = fetchErr.data?.message || fetchErr.message || '投票失败'
|
|
|
|
// 如果是已投票错误,只显示通知,不抛出异常
|
|
if (errorMsg.includes('已经为这首歌投过票')) {
|
|
showNotification(actionLocale.value.alreadyVoted, 'info')
|
|
|
|
// 如果API返回已投票错误,也更新本地状态
|
|
if (targetSong && !targetSong.voted) {
|
|
targetSong.voted = true
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
// 其他错误则抛出
|
|
throw fetchErr
|
|
}
|
|
} catch (err: any) {
|
|
const errorMsg = err.data?.message || err.message || '投票失败'
|
|
error.value = errorMsg
|
|
showNotification(errorMsg, 'error')
|
|
return null
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
// 撤回歌曲(只能撤回自己的投稿)
|
|
const withdrawSong = async (songId: number) => {
|
|
if (!isAuthenticated.value) {
|
|
showNotification(actionLocale.value.loginWithdraw, 'error')
|
|
return null
|
|
}
|
|
|
|
loading.value = true
|
|
error.value = ''
|
|
|
|
try {
|
|
// 查找歌曲信息用于通知
|
|
const targetSong = songs.value.find((s) => s.id === songId)
|
|
const songTitle = targetSong ? targetSong.title : actionLocale.value.fallbackSongTitle
|
|
|
|
// 使用认证配置
|
|
const authConfig = getAuthConfig()
|
|
|
|
const data = await $fetch('/api/songs/withdraw', {
|
|
method: 'POST',
|
|
body: { songId },
|
|
...authConfig
|
|
})
|
|
|
|
// 绕过去重,从数据库重新校准写入后的列表
|
|
await fetchSongs(true, undefined, true)
|
|
|
|
// 显示成功通知
|
|
let message = ''
|
|
if (data.action === 'leave') {
|
|
message = actionLocale.value.leaveCollaborationSucceeded(songTitle)
|
|
} else if (data.message) {
|
|
// 如果后端返回了特定消息,优先使用(除了撤回投稿的情况,我们要保留带标题的格式)
|
|
message = data.message
|
|
} else {
|
|
message = data.quotaReturned
|
|
? actionLocale.value.withdrawSucceededQuota(songTitle)
|
|
: actionLocale.value.withdrawSucceeded(songTitle)
|
|
}
|
|
|
|
if (data.action !== 'leave') {
|
|
message = data.quotaReturned
|
|
? actionLocale.value.withdrawSucceededQuota(songTitle)
|
|
: actionLocale.value.withdrawSucceeded(songTitle)
|
|
}
|
|
|
|
showNotification(message, 'success')
|
|
return data
|
|
} catch (err: any) {
|
|
const errorMsg = err.data?.message || err.message || '撤回歌曲失败'
|
|
error.value = errorMsg
|
|
showNotification(errorMsg, 'error')
|
|
return null
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
// 删除歌曲(管理员专用)
|
|
const deleteSong = async (songId: number) => {
|
|
if (!isAuthenticated.value) {
|
|
showNotification(actionLocale.value.loginDelete, 'error')
|
|
return null
|
|
}
|
|
|
|
loading.value = true
|
|
error.value = ''
|
|
|
|
try {
|
|
// 使用认证配置
|
|
const authConfig = getAuthConfig()
|
|
|
|
const data = await $fetch('/api/admin/songs/delete', {
|
|
method: 'POST',
|
|
body: { songId },
|
|
...authConfig
|
|
})
|
|
|
|
// 绕过去重,从数据库重新校准写入后的列表
|
|
await fetchSongs(true, undefined, true)
|
|
|
|
showNotification(actionLocale.value.deleteSucceeded, 'success')
|
|
return data
|
|
} catch (err: any) {
|
|
const errorMsg = err.data?.message || err.message || '删除歌曲失败'
|
|
error.value = errorMsg
|
|
showNotification(errorMsg, 'error')
|
|
return null
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
// 标记歌曲为已播放(管理员专用)
|
|
const markPlayed = async (songId: number) => {
|
|
if (!isAuthenticated.value) {
|
|
showNotification(actionLocale.value.loginMarkPlayed, 'error')
|
|
return null
|
|
}
|
|
|
|
loading.value = true
|
|
error.value = ''
|
|
|
|
try {
|
|
// 使用认证配置
|
|
const authConfig = getAuthConfig()
|
|
|
|
const data = await $fetch('/api/admin/songs/mark-played', {
|
|
method: 'POST',
|
|
body: { songId },
|
|
...authConfig
|
|
})
|
|
|
|
// 绕过去重,从数据库重新校准写入后的列表
|
|
await fetchSongs(true, undefined, true)
|
|
|
|
showNotification(actionLocale.value.markPlayedSucceeded, 'success')
|
|
return data
|
|
} catch (err: any) {
|
|
const errorMsg = err.data?.message || err.message || '标记歌曲失败'
|
|
error.value = errorMsg
|
|
showNotification(errorMsg, 'error')
|
|
return null
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
// 撤回歌曲已播放状态(管理员专用)
|
|
const unmarkPlayed = async (songId: number) => {
|
|
if (!isAuthenticated.value) {
|
|
showNotification(actionLocale.value.loginUnmarkPlayed, 'error')
|
|
return null
|
|
}
|
|
|
|
loading.value = true
|
|
error.value = ''
|
|
|
|
try {
|
|
// 使用认证配置
|
|
const authConfig = getAuthConfig()
|
|
|
|
const data = await $fetch('/api/admin/songs/mark-played', {
|
|
method: 'POST',
|
|
body: { songId, unmark: true },
|
|
...authConfig
|
|
})
|
|
|
|
// 绕过去重,从数据库重新校准写入后的列表
|
|
await fetchSongs(true, undefined, true)
|
|
|
|
showNotification(actionLocale.value.unmarkPlayedSucceeded, 'success')
|
|
return data
|
|
} catch (err: any) {
|
|
const errorMsg = err.data?.message || err.message || '撤回歌曲已播放状态失败'
|
|
error.value = errorMsg
|
|
showNotification(errorMsg, 'error')
|
|
return null
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
// 申请重播
|
|
const requestReplay = async (
|
|
songId: number,
|
|
options: {
|
|
preferredPlayTimeId?: number | null
|
|
submissionNote?: string | null
|
|
submissionNotePublic?: boolean
|
|
} = {}
|
|
) => {
|
|
if (!isAuthenticated.value) {
|
|
showNotification(actionLocale.value.loginRequestReplay, 'error')
|
|
return null
|
|
}
|
|
|
|
loading.value = true
|
|
error.value = ''
|
|
|
|
try {
|
|
const authConfig = getAuthConfig()
|
|
const data = await $fetch('/api/songs/replay', {
|
|
method: 'POST',
|
|
body: { songId, ...options },
|
|
...authConfig
|
|
})
|
|
|
|
// 更新本地数据状态
|
|
const songIndex = songs.value.findIndex((s) => s.id === songId)
|
|
if (songIndex !== -1) {
|
|
songs.value[songIndex].replayRequested = true
|
|
}
|
|
|
|
await fetchSongs(true, undefined, true)
|
|
|
|
showNotification(actionLocale.value.replaySucceeded, 'success')
|
|
return data
|
|
} catch (err: any) {
|
|
// 按错误码本地化展示,重复申请降级为提示
|
|
if (extractErrorCode(err) === 'SONG_REPLAY_ALREADY_REQUESTED') {
|
|
showNotification(actionLocale.value.replayAlreadyRequested, 'info')
|
|
} else {
|
|
showNotification(localizeServerError(err) || '申请重播失败', 'error')
|
|
}
|
|
return null
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 撤回重播申请
|
|
* @param songId 歌曲ID
|
|
*/
|
|
const withdrawReplay = async (songId: number) => {
|
|
const { user } = useAuth()
|
|
if (!user.value) {
|
|
showNotification(actionLocale.value.loginCancelReplay, 'error')
|
|
return null
|
|
}
|
|
|
|
loading.value = true
|
|
error.value = ''
|
|
|
|
try {
|
|
const authConfig = getAuthConfig()
|
|
const data = await $fetch('/api/songs/replay', {
|
|
method: 'DELETE',
|
|
body: { songId },
|
|
...authConfig
|
|
})
|
|
|
|
// 更新本地数据状态
|
|
const songIndex = songs.value.findIndex((s) => s.id === songId)
|
|
if (songIndex !== -1) {
|
|
songs.value[songIndex].replayRequested = false
|
|
}
|
|
|
|
await fetchSongs(true, undefined, true)
|
|
|
|
showNotification(actionLocale.value.replayCancelled, 'success')
|
|
return data
|
|
} catch (err: any) {
|
|
showNotification(localizeServerError(err) || '取消重播申请失败', 'error')
|
|
return null
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
// 按热度排序的歌曲
|
|
const songsByPopularity = computed(() => {
|
|
return [...songs.value].sort((a, b) => b.voteCount - a.voteCount)
|
|
})
|
|
|
|
// 按创建时间排序的歌曲
|
|
const songsByDate = computed(() => {
|
|
return [...songs.value].sort(
|
|
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
|
)
|
|
})
|
|
|
|
// 已播放的歌曲
|
|
const playedSongs = computed(() => {
|
|
return songs.value.filter((song) => song.played)
|
|
})
|
|
|
|
// 未播放的歌曲
|
|
const unplayedSongs = computed(() => {
|
|
return songs.value.filter((song) => !song.played)
|
|
})
|
|
|
|
// 我的投稿歌曲
|
|
const mySongs = computed(() => {
|
|
if (!user.value) return []
|
|
return songs.value.filter((song) => song.requesterId === user.value?.id)
|
|
})
|
|
|
|
// 我的重播申请歌曲
|
|
const myReplaySongs = computed(() => {
|
|
if (!user.value) return []
|
|
return songs.value.filter((song) => song.replayRequested)
|
|
})
|
|
|
|
// 所有可见的歌曲(登录用户看到的 + 公共歌曲)
|
|
const visibleSongs = computed(() => {
|
|
return isAuthenticated.value ? songs.value : publicSongs.value
|
|
})
|
|
|
|
const clearPrivateSongs = () => {
|
|
songsRequestVersion++
|
|
songs.value = []
|
|
}
|
|
|
|
const clearPublicSongs = () => {
|
|
publicSchedulesRequestVersion++
|
|
publicSongs.value = []
|
|
publicSchedules.value = []
|
|
}
|
|
|
|
// 根据播放时间段过滤歌曲排期
|
|
const filterSchedulesByPlayTime = (schedules: Schedule[], playTimeId: number | null) => {
|
|
if (playTimeId === null) {
|
|
return schedules.filter((s) => s.playTimeId === null)
|
|
}
|
|
return schedules.filter((s) => s.playTimeId === playTimeId)
|
|
}
|
|
|
|
// 获取播放时间名称
|
|
const getPlayTimeName = (playTimeId: number | null) => {
|
|
if (playTimeId === null) {
|
|
return '未指定时段'
|
|
}
|
|
|
|
const foundTime = playTimes.value.find((pt) => pt.id === playTimeId)
|
|
return foundTime ? foundTime.name : '未知时段'
|
|
}
|
|
|
|
// 格式化播放时间显示
|
|
const formatPlayTimeDisplay = (playTime: PlayTime | null) => {
|
|
if (!playTime) {
|
|
return '全天'
|
|
}
|
|
|
|
let displayText = playTime.name
|
|
|
|
// 如果有开始和结束时间,添加时间信息
|
|
if (playTime.startTime && playTime.endTime) {
|
|
displayText += ` (${playTime.startTime}-${playTime.endTime})`
|
|
}
|
|
// 如果只有开始时间
|
|
else if (playTime.startTime) {
|
|
displayText += ` (${playTime.startTime}起)`
|
|
}
|
|
// 如果只有结束时间
|
|
else if (playTime.endTime) {
|
|
displayText += ` (至${playTime.endTime})`
|
|
}
|
|
|
|
return displayText
|
|
}
|
|
|
|
// 获取歌曲总数
|
|
const fetchSongCount = async (forceRefresh = false) => {
|
|
try {
|
|
const request = async () => {
|
|
const response = await $fetch('/api/songs/count')
|
|
return response
|
|
}
|
|
const response = forceRefresh
|
|
? await request()
|
|
: await dedup.dedupedRequest('song-count', request)
|
|
|
|
// 正确解析API返回的数据结构
|
|
if (response && typeof response.count === 'number') {
|
|
songCount.value = response.count
|
|
return response.count
|
|
} else {
|
|
console.warn('歌曲总数API返回的数据格式不正确:', response)
|
|
songCount.value = 0
|
|
return 0
|
|
}
|
|
} catch (err: any) {
|
|
console.error('获取歌曲总数失败:', err)
|
|
return 0
|
|
}
|
|
}
|
|
|
|
// 初始化加载
|
|
const initialize = async () => {
|
|
await fetchPlayTimes()
|
|
if (isAuthenticated.value) {
|
|
await fetchSongs()
|
|
} else {
|
|
await fetchPublicSchedules()
|
|
}
|
|
}
|
|
|
|
return {
|
|
songs,
|
|
publicSongs,
|
|
publicSchedules,
|
|
visibleSongs,
|
|
songCount,
|
|
loading,
|
|
error,
|
|
notification,
|
|
playTimes,
|
|
playTimeEnabled,
|
|
showNotification,
|
|
fetchSongs,
|
|
fetchPublicSchedules,
|
|
fetchPlayTimes,
|
|
fetchSongCount,
|
|
clearPrivateSongs,
|
|
clearPublicSongs,
|
|
refreshSongsSilent,
|
|
refreshSchedulesSilent,
|
|
requestSong,
|
|
voteSong,
|
|
withdrawSong,
|
|
deleteSong,
|
|
markPlayed,
|
|
unmarkPlayed,
|
|
requestReplay,
|
|
withdrawReplay,
|
|
filterSchedulesByPlayTime,
|
|
getPlayTimeName,
|
|
formatPlayTimeDisplay,
|
|
extractSongsFromSchedules,
|
|
initialize,
|
|
songsByPopularity,
|
|
songsByDate,
|
|
playedSongs,
|
|
unplayedSongs,
|
|
mySongs,
|
|
myReplaySongs
|
|
}
|
|
}
|