master
parent
d11f282e4a
commit
1725b16a41
@ -0,0 +1,87 @@
|
||||
import * as XLSX from 'xlsx'
|
||||
import { formatDateTimeZh, formatDateZh } from './datetime'
|
||||
import { bookingTypeLabel } from './bookingType'
|
||||
import { reservationStatusLabel } from './reservationStatus'
|
||||
|
||||
export type ActivityRegistrationExportRow = {
|
||||
id: number
|
||||
visitor_name: string
|
||||
visitor_phone?: string
|
||||
booking_type?: string | null
|
||||
ticket_count?: number
|
||||
status: 'pending' | 'verified' | 'cancelled' | 'expired'
|
||||
qr_token: string
|
||||
created_at: string
|
||||
verified_at?: string | null
|
||||
venue?: { id: number; name: string }
|
||||
activity?: { id: number; title: string }
|
||||
activity_day?: {
|
||||
activity_date: string
|
||||
session_name?: string
|
||||
session_start_at?: string
|
||||
session_end_at?: string
|
||||
} | null
|
||||
}
|
||||
|
||||
function formatActivitySessionTime(ad: ActivityRegistrationExportRow['activity_day']) {
|
||||
if (!ad) return '-'
|
||||
const name = (ad.session_name || '').trim()
|
||||
if (ad.session_start_at && ad.session_end_at) {
|
||||
const s = new Date(String(ad.session_start_at).replace(' ', 'T'))
|
||||
const e = new Date(String(ad.session_end_at).replace(' ', 'T'))
|
||||
if (Number.isNaN(s.getTime()) || Number.isNaN(e.getTime())) {
|
||||
return [name, ad.activity_date ? formatDateZh(ad.activity_date) : ''].filter(Boolean).join(' ')
|
||||
}
|
||||
const y = s.getFullYear()
|
||||
const m = String(s.getMonth() + 1).padStart(2, '0')
|
||||
const d = String(s.getDate()).padStart(2, '0')
|
||||
const pad2 = (n: number) => String(n).padStart(2, '0')
|
||||
const hm = (t: Date) => `${pad2(t.getHours())}:${pad2(t.getMinutes())}`
|
||||
if (s.toDateString() === e.toDateString()) {
|
||||
const timePart = `${y}年${m}月${d}日 ${hm(s)}-${hm(e)}`
|
||||
return name ? `${name} ${timePart}` : timePart
|
||||
}
|
||||
return [name, `${ad.session_start_at} ~ ${ad.session_end_at}`].filter(Boolean).join(' ')
|
||||
}
|
||||
return [name, ad.activity_date ? formatDateZh(ad.activity_date) : ''].filter(Boolean).join(' ') || '-'
|
||||
}
|
||||
|
||||
const COLS = [
|
||||
'编号',
|
||||
'活动',
|
||||
'场馆',
|
||||
'报名人',
|
||||
'手机号',
|
||||
'预约类型',
|
||||
'预约票数',
|
||||
'场次名称',
|
||||
'活动时间',
|
||||
'状态',
|
||||
'下单时间',
|
||||
'核销时间',
|
||||
'二维码 token',
|
||||
] as const
|
||||
|
||||
export function downloadActivityRegistrationsListXlsx(rows: ActivityRegistrationExportRow[]) {
|
||||
const exportRows = rows.map((r) => ({
|
||||
编号: r.id,
|
||||
活动: r.activity?.title ?? '',
|
||||
场馆: r.venue?.name ?? '',
|
||||
报名人: r.visitor_name,
|
||||
手机号: r.visitor_phone ?? '',
|
||||
预约类型: bookingTypeLabel(r.booking_type, r.ticket_count),
|
||||
预约票数: r.ticket_count ?? 1,
|
||||
场次名称: (r.activity_day?.session_name || '').trim() || '-',
|
||||
活动时间: formatActivitySessionTime(r.activity_day),
|
||||
状态: reservationStatusLabel(r.status),
|
||||
下单时间: formatDateTimeZh(r.created_at),
|
||||
核销时间: formatDateTimeZh(r.verified_at),
|
||||
'二维码 token': r.qr_token,
|
||||
}))
|
||||
const ws = XLSX.utils.json_to_sheet(exportRows, { header: [...COLS] })
|
||||
const wb = XLSX.utils.book_new()
|
||||
XLSX.utils.book_append_sheet(wb, ws, '报名管理')
|
||||
const now = new Date()
|
||||
const dateText = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
|
||||
XLSX.writeFile(wb, `报名管理-${dateText}.xlsx`)
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
import * as XLSX from 'xlsx'
|
||||
import { formatDateTimeZh, formatDateZh } from './datetime'
|
||||
import { bookingTypeLabel } from './bookingType'
|
||||
import { reservationStatusLabel } from './reservationStatus'
|
||||
|
||||
export type ActivityVerifyExportRow = {
|
||||
visitor_name?: string
|
||||
visitor_phone?: string
|
||||
booking_type?: string | null
|
||||
ticket_count?: number
|
||||
status?: string
|
||||
qr_token?: string
|
||||
created_at?: string | null
|
||||
verified_at?: string | null
|
||||
venue?: { id?: number; name?: string } | null
|
||||
activity?: { id?: number; title?: string } | null
|
||||
activity_day?: {
|
||||
activity_date?: string
|
||||
session_name?: string
|
||||
session_start_at?: string
|
||||
session_end_at?: string
|
||||
time_range_text?: string
|
||||
} | null
|
||||
}
|
||||
|
||||
function formatActivitySessionTime(ad: ActivityVerifyExportRow['activity_day']) {
|
||||
if (!ad) return '-'
|
||||
const tr = (ad.time_range_text || '').trim()
|
||||
if (tr) return tr
|
||||
const name = ((ad.session_name ?? '') as string).trim()
|
||||
if (ad.session_start_at && ad.session_end_at) {
|
||||
const s = new Date(String(ad.session_start_at).replace(' ', 'T'))
|
||||
const e = new Date(String(ad.session_end_at).replace(' ', 'T'))
|
||||
if (Number.isNaN(s.getTime()) || Number.isNaN(e.getTime())) {
|
||||
return [name, ad.activity_date ? formatDateZh(ad.activity_date) : ''].filter(Boolean).join(' ')
|
||||
}
|
||||
const y = s.getFullYear()
|
||||
const m = String(s.getMonth() + 1).padStart(2, '0')
|
||||
const d = String(s.getDate()).padStart(2, '0')
|
||||
const pad2 = (n: number) => String(n).padStart(2, '0')
|
||||
const hm = (t: Date) => `${pad2(t.getHours())}:${pad2(t.getMinutes())}`
|
||||
if (s.toDateString() === e.toDateString()) {
|
||||
const timePart = `${y}年${m}月${d}日 ${hm(s)}-${hm(e)}`
|
||||
return name ? `${name} ${timePart}` : timePart
|
||||
}
|
||||
return [name, `${ad.session_start_at} ~ ${ad.session_end_at}`].filter(Boolean).join(' ')
|
||||
}
|
||||
return [name, ad.activity_date ? formatDateZh(ad.activity_date) : ''].filter(Boolean).join(' ') || '-'
|
||||
}
|
||||
|
||||
const COLS = [
|
||||
'活动',
|
||||
'场馆',
|
||||
'报名人',
|
||||
'手机号',
|
||||
'预约类型',
|
||||
'预约场次',
|
||||
'场次时间',
|
||||
'状态',
|
||||
'预约时间',
|
||||
'核销时间',
|
||||
'二维码 token',
|
||||
] as const
|
||||
|
||||
export function downloadActivityVerifyListXlsx(rows: ActivityVerifyExportRow[]) {
|
||||
const exportRows = rows.map((r) => ({
|
||||
活动: r.activity?.title ?? '',
|
||||
场馆: r.venue?.name ?? '',
|
||||
报名人: r.visitor_name ?? '',
|
||||
手机号: r.visitor_phone ?? '',
|
||||
预约类型: bookingTypeLabel(r.booking_type, r.ticket_count),
|
||||
预约场次: (((r.activity_day?.session_name ?? '') as string).trim() || '-') as string,
|
||||
场次时间: formatActivitySessionTime(r.activity_day ?? null),
|
||||
状态: reservationStatusLabel(r.status ?? ''),
|
||||
预约时间: formatDateTimeZh(r.created_at),
|
||||
核销时间: formatDateTimeZh(r.verified_at),
|
||||
'二维码 token': r.qr_token ?? '',
|
||||
}))
|
||||
const ws = XLSX.utils.json_to_sheet(exportRows, { header: [...COLS] })
|
||||
const wb = XLSX.utils.book_new()
|
||||
XLSX.utils.book_append_sheet(wb, ws, '现场核销')
|
||||
const now = new Date()
|
||||
const dateText = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
|
||||
XLSX.writeFile(wb, `现场核销-${dateText}.xlsx`)
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
import * as XLSX from 'xlsx'
|
||||
import { formatDateTimeZh, formatDateZh } from './datetime'
|
||||
import { bookingTypeLabel } from './bookingType'
|
||||
import { reservationStatusLabel } from './reservationStatus'
|
||||
|
||||
export type TicketGrabRegistrationExportRow = {
|
||||
visitor_name: string
|
||||
visitor_phone?: string | null
|
||||
id_card?: string | null
|
||||
booking_type?: string | null
|
||||
ticket_count?: number
|
||||
status: 'pending' | 'verified' | 'cancelled' | 'expired'
|
||||
qr_token: string
|
||||
created_at: string
|
||||
verified_at?: string | null
|
||||
entry_date?: string | null
|
||||
venue?: { id: number; name: string } | null
|
||||
ticket_grab_event?: { id: number; title: string } | null
|
||||
}
|
||||
|
||||
const COLS = [
|
||||
'抢票活动',
|
||||
'场馆',
|
||||
'姓名',
|
||||
'身份证',
|
||||
'入馆日',
|
||||
'预约类型',
|
||||
'票数',
|
||||
'状态',
|
||||
'下单时间',
|
||||
'核销时间',
|
||||
'核销 Token',
|
||||
] as const
|
||||
|
||||
export function downloadTicketGrabRegistrationsListXlsx(rows: TicketGrabRegistrationExportRow[]) {
|
||||
const exportRows = rows.map((r) => ({
|
||||
抢票活动: r.ticket_grab_event?.title ?? '',
|
||||
场馆: r.venue?.name ?? '',
|
||||
姓名: r.visitor_name ?? '',
|
||||
身份证: r.id_card ?? '',
|
||||
入馆日: r.entry_date ? formatDateZh(String(r.entry_date)) : '-',
|
||||
预约类型: bookingTypeLabel(r.booking_type, r.ticket_count),
|
||||
票数: r.ticket_count ?? 1,
|
||||
状态: reservationStatusLabel(r.status),
|
||||
下单时间: formatDateTimeZh(r.created_at),
|
||||
核销时间: r.verified_at ? formatDateTimeZh(String(r.verified_at)) : '-',
|
||||
'核销 Token': r.qr_token ?? '',
|
||||
}))
|
||||
const ws = XLSX.utils.json_to_sheet(exportRows, { header: [...COLS] })
|
||||
const wb = XLSX.utils.book_new()
|
||||
XLSX.utils.book_append_sheet(wb, ws, '抢票报名')
|
||||
const now = new Date()
|
||||
const dateText = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
|
||||
XLSX.writeFile(wb, `抢票报名-${dateText}.xlsx`)
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
import * as XLSX from 'xlsx'
|
||||
|
||||
export function cellValueForExport(v: unknown): string | number | boolean {
|
||||
if (v === null || v === undefined) return ''
|
||||
if (typeof v === 'number' && !Number.isFinite(v)) return ''
|
||||
if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') return v
|
||||
return JSON.stringify(v)
|
||||
}
|
||||
|
||||
/** 将接口原始记录铺开为表格行:顶层键并列,值为对象或数组时用 JSON 字符串落格 */
|
||||
export function recordsToExportRows(
|
||||
records: Record<string, unknown>[],
|
||||
): Record<string, string | number | boolean>[] {
|
||||
const keySet = new Set<string>()
|
||||
for (const row of records) {
|
||||
if (!row || typeof row !== 'object' || Array.isArray(row)) continue
|
||||
for (const k of Object.keys(row)) keySet.add(k)
|
||||
}
|
||||
const keys = [...keySet].sort((a, b) => a.localeCompare(b))
|
||||
return records.map((row) => {
|
||||
const out: Record<string, string | number | boolean> = {}
|
||||
if (!row || typeof row !== 'object' || Array.isArray(row)) {
|
||||
for (const k of keys) out[k] = ''
|
||||
return out
|
||||
}
|
||||
const r = row as Record<string, unknown>
|
||||
for (const k of keys) {
|
||||
out[k] = cellValueForExport(r[k])
|
||||
}
|
||||
return out
|
||||
})
|
||||
}
|
||||
|
||||
export function downloadXlsxJsonRecords(filenameStem: string, sheetName: string, records: Record<string, unknown>[]) {
|
||||
const safeName = sheetName.replace(/[:\\/?*[\]]/g, '').slice(0, 31) || 'Sheet1'
|
||||
const exportRows = recordsToExportRows(records)
|
||||
const ws = XLSX.utils.json_to_sheet(exportRows)
|
||||
const wb = XLSX.utils.book_new()
|
||||
XLSX.utils.book_append_sheet(wb, ws, safeName)
|
||||
const now = new Date()
|
||||
const dateText = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
|
||||
XLSX.writeFile(wb, `${filenameStem}-${dateText}.xlsx`)
|
||||
}
|
||||
Loading…
Reference in new issue