UNPKG

@nitra/export-excel

Version:
251 lines (228 loc) 10.8 kB
import ExcelJS from 'exceljs' /** * Экспорт таблицы в XLSX * @param {Array<{label: string, field: string, width?: number, align?: string, wrapText?: boolean, excelType?: string}>} columns - Массив объектов с описанием колонок * @param {Array<object>} rows - Массив объектов с данными для экспорта * @param {'buffer'|'workbook'} [type] - Тип возвращаемого значения: 'buffer' для буфера или 'workbook' для объекта книги (по умолчанию: 'buffer') * @param {string} [filename] - Имя файла для листа Excel (по умолчанию: 'export') * @param {string} [creator] - Имя создателя документа (по умолчанию: 'exportToExcel') * @returns {Promise<ExcelJS.Workbook|ArrayBuffer>} Promise, который разрешается в буфер Excel или объект книги */ export const exportToExcel = async ( columns, rows, type = 'buffer', // workbook or buffer filename = 'export', creator = 'exportToExcel' ) => { // Создаем книгу const workbook = new ExcelJS.Workbook() workbook.creator = creator workbook.created = new Date() // Создаем лист в книге const worksheet = workbook.addWorksheet(filename, { pageSetup: { scale: 90 } }) // добавляем колонки const dateColumns = addColumnsToWorksheet(worksheet, columns) // добавляем данные addRowsToWorksheet(worksheet, rows, dateColumns) // применяем формат даты ко всем ячейкам с датами if (dateColumns.length > 0) { // Устанавливаем формат на уровне колонки для нативной даты Excel for (const column of worksheet.columns) { const columnKey = column.key || column._key const dateColumnInfo = dateColumns.find(dc => dc.field === columnKey) if (dateColumnInfo) { // Используем соответствующий формат в зависимости от типа if (dateColumnInfo.type === 'datetime') { column.numFmt = 'dd/mm/yyyy hh:mm' } else { column.numFmt = 'dd/mm/yyyy' } } } // Также применяем формат к каждой ячейке и убеждаемся, что значение является Date worksheet.eachRow((row, rowNumber) => { if (rowNumber > 1) { // пропускаем заголовок row.eachCell((cell, colNumber) => { const column = worksheet.getColumn(colNumber) const columnKey = column.key || column._key const dateColumnInfo = dateColumns.find(dc => dc.field === columnKey) if (dateColumnInfo) { // Убеждаемся, что значение является Date объектом для нативной даты Excel // ExcelJS автоматически конвертирует Date в нативную дату Excel (serial number) if (cell.value instanceof Date) { cell.numFmt = dateColumnInfo.type === 'datetime' ? 'dd/mm/yyyy hh:mm' : 'dd/mm/yyyy' } else if (cell.value !== null && cell.value !== undefined) { // Если значение не Date, пытаемся преобразовать const dateValue = new Date(cell.value) if (!isNaN(dateValue.getTime())) { cell.value = dateValue cell.numFmt = dateColumnInfo.type === 'datetime' ? 'dd/mm/yyyy hh:mm' : 'dd/mm/yyyy' } } } }) } }) } // добавляем нижнюю границу у таблицы worksheet.lastRow.eachCell({ includeEmpty: true }, function (cell) { cell.style.border = { right: { style: 'thin' }, bottom: { style: 'thin' } } }) // добавляем правую границу у колонок const numRows = rows.length worksheet.eachRow(function (row, rowNumber) { if (rowNumber <= numRows) { row.eachCell({ includeEmpty: true }, function (cell) { cell.style.border = { right: { style: 'thin' } } }) } }) if (type === 'workbook') { return workbook } if (type === 'buffer') { return await workbook.xlsx.writeBuffer() } } /** * Добавляет колонки в рабочий лист Excel * @param {ExcelJS.Worksheet} worksheet - Рабочий лист Excel * @param {Array<{label: string, field: string, width?: number, align?: string, wrapText?: boolean, excelType?: string}>} data - Массив объектов с описанием колонок * @returns {Array<{field: string, type: string}>} Массив объектов с информацией о колонках с датами */ function addColumnsToWorksheet(worksheet, data) { const columns = [] const dateColumns = [] // сохраняем поля колонок с датами и их типы for (const c of data) { // Определяем формат для даты let numFmt = c.excelType || '' if (c.excelType === 'date') { // Сохраняем поле для последующего форматирования dateColumns.push({ field: c.field, type: 'date' }) // Устанавливаем стандартный формат Excel для даты // Формат 'dd/mm/yyyy' - это стандартный формат Excel, который Excel распознает как тип "date" numFmt = 'dd/mm/yyyy' } else if (c.excelType === 'datetime') { // Сохраняем поле для последующего форматирования dateColumns.push({ field: c.field, type: 'datetime' }) // Устанавливаем стандартный формат Excel для даты с временем // Формат 'dd/mm/yyyy hh:mm' - это стандартный формат Excel, который Excel распознает как тип "date" numFmt = 'dd/mm/yyyy hh:mm' } columns.push({ header: c.label, key: c.field, width: c.width || 30, style: { numFmt: numFmt, // маска для форматирования чисел и дат alignment: { horizontal: c.align, vertical: 'middle', wrapText: c.wrapText } } }) } worksheet.columns = columns // форматирование колонок const firstRow = worksheet.getRow(1) firstRow.eachCell((cell) => { cell.alignment = { vertical: 'middle', horizontal: 'center', wrapText: true } cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'ffdddddd' } } cell.font = { size: 12, bold: true } cell.border = { top: { style: 'thin' }, left: { style: 'thin' }, bottom: { style: 'thin' }, right: { style: 'thin' } } }) return dateColumns } /** * Добавляет строки данных в рабочий лист Excel * @param {ExcelJS.Worksheet} worksheet - Рабочий лист Excel * @param {Array<object>} rows - Массив объектов с данными для добавления * @param {Array<{field: string, type: string}>} dateColumns - Массив объектов с информацией о колонках с датами * @returns {void} */ function addRowsToWorksheet(worksheet, rows, dateColumns) { const columns = worksheet.columns // Создаем Map для быстрой проверки типа колонки const dateColumnMap = new Map() if (dateColumns && dateColumns.length > 0) { for (const dc of dateColumns) { dateColumnMap.set(dc.field, dc.type) } } for (const row of rows) { const rowData = {} // берем ключ данных из столбца for (const column of columns) { // Используем key или _key в зависимости от версии ExcelJS const columnKey = column.key || column._key let value = row[columnKey] // если в колонке есть форматирование для чисел то данные преобразовываем в число if (column.style && column.style.numFmt && typeof column.style.numFmt === 'string' && column.style.numFmt.search('#') !== -1) { value = parseFloat(value || 0) } else if (dateColumnMap.has(columnKey)) { // если это колонка с датой, преобразуем значение в Date объект // ExcelJS автоматически конвертирует Date в нативную дату Excel if (value instanceof Date) { // уже Date объект, оставляем как есть - ExcelJS распознает это как нативную дату // value уже является Date, ничего не нужно делать } else if (typeof value === 'string' && value) { // пытаемся распарсить строку в дату // поддерживаем формат DD.MM.YYYY HH:mm и другие распространенные форматы const dateStr = value.trim() // пробуем разные форматы let parsedDate = null // DD.MM.YYYY HH:mm const match1 = dateStr.match(/(\d{2})\.(\d{2})\.(\d{4})\s+(\d{2}):(\d{2})/) if (match1) { parsedDate = new Date(parseInt(match1[3]), parseInt(match1[2]) - 1, parseInt(match1[1]), parseInt(match1[4]), parseInt(match1[5])) } else { // DD.MM.YYYY const match2 = dateStr.match(/(\d{2})\.(\d{2})\.(\d{4})/) if (match2) { parsedDate = new Date(parseInt(match2[3]), parseInt(match2[2]) - 1, parseInt(match2[1])) } else { // пробуем стандартный парсинг parsedDate = new Date(value) } } // проверяем, что дата валидна if (parsedDate && !isNaN(parsedDate.getTime())) { value = parsedDate } else { // если не удалось распарсить, оставляем как null value = null } } else if (!value) { value = null } } else { // иначе в строку value = value ? value.toString() : '' } rowData[columnKey] = value } worksheet.addRow(rowData) } }