import JSZip from 'jszip';
// 通用表格导出:CSV(带 BOM,Excel 直接打开中文不乱码)与 XLSX(基于 jszip 生成最小 OOXML 工作簿)。
// columns 复用 antd Table 列定义(title/dataIndex),可选 exportValue(value, row) 提供导出值格式化(与 antd render 的 value 语义一致)。
export const escapeXmlText = (value) => String(value)
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
export const spreadsheetColumnName = (index) => {
let name = '';
let value = index + 1;
while (value > 0) {
const remainder = (value - 1) % 26;
name = String.fromCharCode(65 + remainder) + name;
value = Math.floor((value - 1) / 26);
}
return name;
};
const resolveCell = (column, row) => {
const value = row?.[column?.dataIndex];
return typeof column?.exportValue === 'function' ? column.exportValue(value, row) : value;
};
export const resolveExportRows = (columns, rows) => (
(rows || []).map((row) => columns.map((column) => resolveCell(column, row)))
);
const saveBlob = (blob, fileName) => {
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
link.remove();
URL.revokeObjectURL(url);
};
// 服务端直接返回文件的场景(/export 接口,responseType: 'blob')在这里统一落盘
export const downloadBlobAsFile = (blob, fileName) => saveBlob(blob, fileName);
// 导出接口出错时,错误响应体同样是 Blob(内含 JSON),这里统一解出可读文案
export const readBlobErrorMessage = async (error, fallback = '导出失败') => {
const data = error?.response?.data;
if (typeof Blob !== 'undefined' && data instanceof Blob) {
try {
const text = await data.text();
try {
const parsed = JSON.parse(text);
return parsed?.error || parsed?.message || fallback;
} catch {
return text.slice(0, 300) || fallback;
}
} catch {
return fallback;
}
}
return error?.response?.data?.error || error?.message || fallback;
};
const quoteCsvCell = (value) => {
const text = value === null || value === undefined ? '' : String(value);
return /[",\r\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
};
export const buildCsvContent = (columns, rows) => {
const header = columns.map((column) => quoteCsvCell(column?.title ?? '')).join(',');
const body = resolveExportRows(columns, rows)
.map((cells) => cells.map((cell) => quoteCsvCell(cell)).join(','))
.join('\r\n');
return body ? `${header}\r\n${body}` : header;
};
export const exportTableToCsv = (fileName, columns, rows) => {
const content = buildCsvContent(columns, rows);
saveBlob(new Blob([`\ufeff${content}`], { type: 'text/csv;charset=utf-8;' }), `${fileName}.csv`);
};
const CONTENT_TYPES_XML = ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ '';
const ROOT_RELS_XML = ''
+ ''
+ ''
+ '';
const WORKBOOK_RELS_XML = ''
+ ''
+ ''
+ '';
const workbookXml = (sheetName) => ''
+ ''
+ ``
+ '';
const cellXml = (value, rowIndex, columnIndex) => {
const ref = `${spreadsheetColumnName(columnIndex)}${rowIndex + 1}`;
if (value === null || value === undefined || value === '') {
return ``;
}
if (typeof value === 'number' && Number.isFinite(value)) {
return `${value}`;
}
return `${escapeXmlText(value)}`;
};
export const buildWorksheetXml = (columns, rows) => {
const lines = [columns.map((column, columnIndex) => cellXml(column?.title ?? '', 0, columnIndex)).join('')];
resolveExportRows(columns, rows).forEach((cells, rowIndex) => {
lines.push(cells.map((cell, columnIndex) => cellXml(cell, rowIndex + 1, columnIndex)).join(''));
});
return ''
+ ''
+ `${lines.map((row) => `${row}
`).join('')}`
+ '';
};
export const exportTableToXlsx = async (fileName, columns, rows, sheetName = '数据') => {
const zip = new JSZip();
zip.file('[Content_Types].xml', CONTENT_TYPES_XML);
zip.file('_rels/.rels', ROOT_RELS_XML);
zip.file('xl/workbook.xml', workbookXml(sheetName));
zip.file('xl/_rels/workbook.xml.rels', WORKBOOK_RELS_XML);
zip.file('xl/worksheets/sheet1.xml', buildWorksheetXml(columns, rows));
const blob = await zip.generateAsync({ type: 'blob', compression: 'DEFLATE' });
saveBlob(blob, `${fileName}.xlsx`);
};
export const exportTableFile = async (format, fileName, columns, rows, sheetName) => {
if (format === 'xlsx') {
await exportTableToXlsx(fileName, columns, rows, sheetName);
return;
}
exportTableToCsv(fileName, columns, rows);
};