/** * CSV 导出工具 * * 纯前端生成 CSV 文件并触发浏览器下载,无需后端参与。 * 自动处理中文编码(UTF-8 BOM),确保 Excel 能正确显示中文。 */ import { formatDateStatic } from './dateUtils' /** * 将数据导出为 CSV 文件并触发下载 * * @param filename - 下载文件名(不含扩展名) * @param headers - CSV 表头行 * @param rows - CSV 数据行(二维数组) */ export function exportToCSV( filename: string, headers: string[], rows: string[][], ): void { // BOM 确保 Excel 正确识别 UTF-8 中文 const BOM = '\uFEFF' // 转义单元格:如果包含逗号、引号或换行,用引号包裹 const escapeCell = (cell: string): string => { const str = String(cell ?? '') if (str.includes(',') || str.includes('"') || str.includes('\n') || str.includes('\r')) { return `"${str.replace(/"/g, '""')}"` } return str } const headerLine = headers.map(escapeCell).join(',') const dataLines = rows.map(row => row.map(escapeCell).join(',')) const csvContent = BOM + [headerLine, ...dataLines].join('\n') const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = `${filename}.csv` document.body.appendChild(a) a.click() a.remove() URL.revokeObjectURL(url) } /** 患者导出所需的简要字段 */ export interface ExportPatient { animal_name: string species: string breed: string sex: string age: string owner_name: string weight?: string created_at: string } /** * 导出患者列表为 CSV */ export function exportPatientsToCSV(patients: ExportPatient[], locale: string = 'zh'): void { const headers = ['动物名', '物种', '品种', '性别', '年龄', '主人', '体重(kg)', '创建时间'] const rows = patients.map(p => [ p.animal_name ?? '', p.species ?? '', p.breed ?? '', p.sex ?? '', p.age ?? '', p.owner_name ?? '', p.weight ?? '', p.created_at ? formatDateStatic(p.created_at, locale) : '', ]) const dateStr = new Date().toISOString().slice(0, 10) exportToCSV(`患者档案_${dateStr}`, headers, rows) } /** 问诊导出所需的简要字段 */ export interface ExportConsultation { animal_name: string species: string breed: string owner_name: string status: string doctor_name: string serial_number: string created_at: string } /** * 导出问诊列表为 CSV */ export function exportConsultationsToCSV(consultations: ExportConsultation[], locale: string = 'zh'): void { const statusLabel = (s: string): string => { const map: Record = { 'draft': '草稿', 'final': '终稿', 'recording': '录音中', 'transcribing': '转写中', 'processing': '处理中', 'cancelled': '已取消', } return map[s] ?? s } const headers = ['动物名', '物种', '品种', '主人', '状态', '医师', '编号', '创建时间'] const rows = consultations.map(c => [ c.animal_name ?? '', c.species ?? '', c.breed ?? '', c.owner_name ?? '', statusLabel(c.status), c.doctor_name ?? '', c.serial_number ?? '', c.created_at ? formatDateStatic(c.created_at, locale) : '', ]) const dateStr = new Date().toISOString().slice(0, 10) exportToCSV(`问诊记录_${dateStr}`, headers, rows) } /** * 导出图表数据为 CSV(通用) * * 适用于饼图、柱状图等简单 key-value 数据的导出。 * @param filename - 文件名(不含扩展名) * @param labelHeader - 标签列标题 * @param valueHeader - 数值列标题 * @param data - 键值对数组 */ export function exportChartDataToCSV( filename: string, labelHeader: string, valueHeader: string, data: Array<{ name: string; value: number }>, ): void { const headers = [labelHeader, valueHeader] const rows = data.map(d => [d.name, String(d.value)]) const dateStr = new Date().toISOString().slice(0, 10) exportToCSV(`${filename}_${dateStr}`, headers, rows) }