cangku/frontend/src/utils/tableExport.js

127 lines
5.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;');
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);
};