127 lines
5.5 KiB
JavaScript
127 lines
5.5 KiB
JavaScript
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, '"')
|
||
.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);
|
||
};
|
||
|
||
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 = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
|
||
+ '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">'
|
||
+ '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>'
|
||
+ '<Default Extension="xml" ContentType="application/xml"/>'
|
||
+ '<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>'
|
||
+ '<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>'
|
||
+ '</Types>';
|
||
|
||
const ROOT_RELS_XML = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
|
||
+ '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
|
||
+ '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>'
|
||
+ '</Relationships>';
|
||
|
||
const WORKBOOK_RELS_XML = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
|
||
+ '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
|
||
+ '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>'
|
||
+ '</Relationships>';
|
||
|
||
const workbookXml = (sheetName) => '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
|
||
+ '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" '
|
||
+ 'xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">'
|
||
+ `<sheets><sheet name="${escapeXmlText(sheetName)}" sheetId="1" r:id="rId1"/></sheets>`
|
||
+ '</workbook>';
|
||
|
||
const cellXml = (value, rowIndex, columnIndex) => {
|
||
const ref = `${spreadsheetColumnName(columnIndex)}${rowIndex + 1}`;
|
||
if (value === null || value === undefined || value === '') {
|
||
return `<c r="${ref}"/>`;
|
||
}
|
||
if (typeof value === 'number' && Number.isFinite(value)) {
|
||
return `<c r="${ref}"><v>${value}</v></c>`;
|
||
}
|
||
return `<c r="${ref}" t="inlineStr"><is><t xml:space="preserve">${escapeXmlText(value)}</t></is></c>`;
|
||
};
|
||
|
||
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 '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
|
||
+ '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">'
|
||
+ `<sheetData>${lines.map((row) => `<row>${row}</row>`).join('')}</sheetData>`
|
||
+ '</worksheet>';
|
||
};
|
||
|
||
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);
|
||
};
|