feat: align with upstream 1.5.9.9

This commit is contained in:
2026-09-12 18:21:11 +08:00
commit 34f0679117
805 changed files with 376275 additions and 0 deletions
+85
View File
@@ -0,0 +1,85 @@
import { db } from '~/drizzle/db'
import { cardCodes } from '~/drizzle/schema'
import { and, desc, eq, ilike, or, inArray } from 'drizzle-orm'
import { setHeader } from 'h3'
export default defineEventHandler(async (event) => {
const user = event.context.user
if (!user) {
throw createError({ statusCode: 401, message: '未授权访问' })
}
if (!['SONG_ADMIN', 'ADMIN', 'SUPER_ADMIN'].includes(user.role)) {
throw createError({ statusCode: 403, message: '权限不足' })
}
try {
const query = getQuery(event)
const idsParam = typeof query.ids === 'string' ? query.ids.trim() : ''
const status = typeof query.status === 'string' ? query.status.trim().toUpperCase() : ''
const keyword = typeof query.q === 'string' ? query.q.trim() : ''
const conditions: any[] = []
if (status) conditions.push(eq(cardCodes.status, status as any))
if (keyword) {
conditions.push(
or(
ilike(cardCodes.code, `%${keyword}%`),
ilike(cardCodes.note, `%${keyword}%`)
)
)
}
let rows
if (idsParam) {
const ids = idsParam.split(',').map((s) => Number(s)).filter((id) => Number.isInteger(id) && id > 0)
if (ids.length === 0) {
throw createError({ statusCode: 400, message: '无效的点歌券ID' })
}
rows = await db.select().from(cardCodes).where(inArray(cardCodes.id, ids)).orderBy(desc(cardCodes.createdAt))
} else {
let qb = db.select().from(cardCodes)
if (conditions.length) qb = qb.where(and(...conditions))
rows = await qb.orderBy(desc(cardCodes.createdAt))
}
const statusLabelMap: Record<string, string> = {
AVAILABLE: '可用',
LOCKED: '已锁定',
REDEEMED: '已核销',
INVALID: '已作废'
}
const header = ['ID', '点歌券', '状态', '备注', '创建时间', '更新时间', '锁定用户ID', '锁定时间', '核销用户ID', '核销时间']
const csvRows = [header.join(',')]
for (const r of rows) {
const esc = (v: any) => {
if (v === null || typeof v === 'undefined') return ''
let s = v instanceof Date ? v.toISOString() : String(v)
if (/^[=+\-@]/.test(s)) {
s = `\t${s}`
}
return s.includes(',') || s.includes('\n') || s.includes('"') ? '"' + s.replace(/"/g, '""') + '"' : s
}
csvRows.push([
esc(r.id),
esc(r.code),
esc(statusLabelMap[r.status] || r.status),
esc(r.note),
esc(r.createdAt),
esc(r.updatedAt),
esc(r.lockedBy),
esc(r.lockedAt),
esc(r.redeemedBy),
esc(r.redeemedAt)
].join(','))
}
const csv = '\ufeff' + csvRows.join('\n')
setHeader(event, 'Content-Type', 'text/csv; charset=utf-8')
setHeader(event, 'Content-Disposition', 'attachment; filename="card-codes.csv"')
return csv
} catch (err: any) {
console.error('导出点歌券失败', err)
throw createError({ statusCode: err.statusCode || 500, message: err.message || '导出点歌券失败' })
}
})