feat(schedule)+fix(schedule)+feat(player): 排期管理5项功能 - 紧凑布局/投稿人优先算法/localStorage持久化/备选池修复/播放队列按钮
* fix+style(schedule): ScheduleManager 标签页紧凑布局,头部 min-height 防抖,内容区统间距与移动端适配 * feat(schedule): 新增自动排期策略「按投稿人数优先覆盖」,按 requester+collaborators 数排序分配好时段 * feat(schedule): localStorage 键 voicehub:schedule:auto-config 持久化策略/时段/学期/过滤等7项,onMounted 恢复,watch debounce 500ms 保存 * fix(schedule): songs/index.get.ts 新增 use_pool 查询参数,备选池过滤条件 status=PENDING + song.played=false + song未在任何排期中;ScheduleManager/draft-bulk 创建排期时将池记录 UPDATE 为 ASSIGNED * feat(player): ScheduleManager 工具栏新增「构建播放队列」按钮,按 playDate+playTimeId+sequence 排序后调用 useAudioPlayer.playSong 加载播放列表,支持 confirm 确认与 toast 反馈 * refactor(useSongs): fetchSongs 增加第5参数 usePool,透传 use_pool=true 到 /api/songs
This commit is contained in:
@@ -1,5 +1,13 @@
|
||||
import { db } from '~/drizzle/db'
|
||||
import { playTimes, schedules, songs, users, votes, songReplayRequests } from '~/drizzle/schema'
|
||||
import {
|
||||
playTimes,
|
||||
schedules,
|
||||
songs,
|
||||
users,
|
||||
votes,
|
||||
songReplayRequests,
|
||||
scheduleSongPool
|
||||
} from '~/drizzle/schema'
|
||||
import { and, asc, count, desc, eq, gte, lte } from 'drizzle-orm'
|
||||
import { createSongSelectedNotification } from '../../services/notificationService'
|
||||
import { cacheService } from '~~/server/services/cacheService'
|
||||
@@ -126,6 +134,19 @@ export default defineEventHandler(async (event) => {
|
||||
// 获取客户端IP地址
|
||||
const clientIP = getClientIP(event)
|
||||
|
||||
await tx
|
||||
.update(scheduleSongPool)
|
||||
.set({
|
||||
status: 'ASSIGNED',
|
||||
targetPlayDate: playDate
|
||||
})
|
||||
.where(
|
||||
and(
|
||||
eq(scheduleSongPool.songId, body.songId),
|
||||
eq(scheduleSongPool.status, 'PENDING')
|
||||
)
|
||||
)
|
||||
|
||||
// 只有在非草稿模式下才创建通知
|
||||
if (!isDraft) {
|
||||
await createSongSelectedNotification(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { db } from '~/drizzle/db'
|
||||
import { schedules, songReplayRequests } from '~/drizzle/schema'
|
||||
import { schedules, songReplayRequests, scheduleSongPool } from '~/drizzle/schema'
|
||||
import { and, eq, gte, inArray, lte } from 'drizzle-orm'
|
||||
import { cacheService } from '~~/server/services/cacheService'
|
||||
|
||||
@@ -80,6 +80,21 @@ export default defineEventHandler(async (event) => {
|
||||
|
||||
const inserted = await tx.insert(schedules).values(rows).returning({ id: schedules.id })
|
||||
|
||||
if (songIds.length > 0) {
|
||||
await tx
|
||||
.update(scheduleSongPool)
|
||||
.set({
|
||||
status: 'ASSIGNED',
|
||||
targetPlayDate: startOfDay
|
||||
})
|
||||
.where(
|
||||
and(
|
||||
inArray(scheduleSongPool.songId, songIds),
|
||||
eq(scheduleSongPool.status, 'PENDING')
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return { deletedCount: existingSchedules.length, insertedCount: inserted.length }
|
||||
})
|
||||
|
||||
|
||||
@@ -8,9 +8,10 @@ import {
|
||||
songs,
|
||||
systemSettings,
|
||||
users,
|
||||
votes
|
||||
votes,
|
||||
scheduleSongPool
|
||||
} from '~/drizzle/schema'
|
||||
import { and, asc, count, desc, eq, inArray, like, or } from 'drizzle-orm'
|
||||
import { and, asc, count, desc, eq, inArray, like, or, isNull, notInArray } from 'drizzle-orm'
|
||||
import { cacheService } from '~~/server/services/cacheService'
|
||||
import { formatDateTime } from '~/utils/timeUtils'
|
||||
import { maskSongsInfo, MaskableSong, MaskableUser } from '~~/server/utils/studentMask'
|
||||
@@ -67,6 +68,7 @@ export default defineEventHandler(async (event) => {
|
||||
const sortBy = (query.sortBy as string) || 'createdAt'
|
||||
const sortOrder = (query.sortOrder as string) || 'desc'
|
||||
const bypassCache = query.bypass_cache === 'true'
|
||||
const usePool = query.use_pool === 'true' || query.usePool === 'true'
|
||||
|
||||
// 获取用户身份
|
||||
const user = event.context.user || null
|
||||
@@ -203,6 +205,35 @@ export default defineEventHandler(async (event) => {
|
||||
conditions.push(eq(songs.requesterId, user.id))
|
||||
}
|
||||
|
||||
if (usePool) {
|
||||
const poolRows = await db
|
||||
.select({ songId: scheduleSongPool.songId })
|
||||
.from(scheduleSongPool)
|
||||
.where(
|
||||
and(
|
||||
eq(scheduleSongPool.status, 'PENDING'),
|
||||
isNull(scheduleSongPool.targetPlayDate)
|
||||
)
|
||||
)
|
||||
.groupBy(scheduleSongPool.songId)
|
||||
|
||||
const allScheduledSongIdsQuery = await db
|
||||
.select({ songId: schedules.songId })
|
||||
.from(schedules)
|
||||
|
||||
const scheduledIdsSet = new Set(allScheduledSongIdsQuery.map((r) => r.songId))
|
||||
const candidateFromPool = new Set(
|
||||
poolRows.map((r) => r.songId).filter((id) => !scheduledIdsSet.has(id))
|
||||
)
|
||||
|
||||
if (candidateFromPool.size === 0) {
|
||||
conditions.push(eq(songs.id, -1))
|
||||
} else {
|
||||
conditions.push(inArray(songs.id, Array.from(candidateFromPool)))
|
||||
}
|
||||
conditions.push(eq(songs.played, false))
|
||||
}
|
||||
|
||||
const whereCondition = conditions.length > 0 ? and(...conditions) : undefined
|
||||
|
||||
// 查询歌曲总数
|
||||
|
||||
Reference in New Issue
Block a user