@nitra/export-excel
Version:
export-excel helper
251 lines (228 loc) • 10.8 kB
JavaScript
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)
}
}