// 1) 安全地把对象数组转成 CSV 文本 export function toCSV>( rows: T[], opts?: { headers?: string[] // 指定列顺序与表头;不传则用 rows[0] 的键 delimiter?: string // 默认逗号 "," includeHeader?: boolean // 默认 true }, ): string { const delimiter = opts?.delimiter ?? ',' const includeHeader = opts?.includeHeader ?? true if (!rows?.length) return '' const headers = opts?.headers ?? Object.keys(rows[0]) // 处理每个单元格:转为字符串、转义双引号、若含分隔符/换行/引号则整体再包一层引号 const esc = (val: any) => { let s = val == null ? '' : String(val) s = s.replace(/"/g, '""') if (s.includes('"') || s.includes(delimiter) || s.includes('\n') || s.includes('\r')) { s = `"${s}"` } return s } const lines: string[] = [] if (includeHeader) { lines.push(headers.map((h) => esc(h)).join(delimiter)) } for (const row of rows) { lines.push(headers.map((h) => esc(row[h])).join(delimiter)) } // Windows/Excel 友好:使用 \r\n 作为换行 return lines.join('\r\n') } // 2) 触发浏览器下载(含 UTF-8 BOM,Excel 更稳) export function downloadCSV(filename: string, csv: string) { // 加 BOM,避免 Excel 中文乱码 const BOM = '\uFEFF' const blob = new Blob([BOM + csv], { type: 'text/csv;charset=utf-8;' }) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = filename.endsWith('.csv') ? filename : `${filename}.csv` document.body.appendChild(a) a.click() document.body.removeChild(a) URL.revokeObjectURL(url) } // 3) 使用示例 // type Row = { id: number; name: string; note?: string }; // const data: Row[] = [ // { id: 1, name: "张三", note: "带,逗号" }, // { id: 2, name: "李四", note: "含\"引号\"与换行\n第二行" }, // ]; // const csv = toCSV(data, { // headers: ["id", "name", "note"], // 指定列顺序(可选) // includeHeader: true, // }); // downloadCSV("示例数据.csv", csv);