import "server-only" import ExcelJS from "exceljs" export type ExcelColumn = { header: string key: string width?: number } export type ExcelSheet = { name: string columns: ExcelColumn[] rows: Record[] } export type TemplateColumn = ExcelColumn & { note?: string } export type TemplateSheet = { name: string columns: TemplateColumn[] sampleRows?: Record[] } export type ParsedSheet = { sheetName: string rows: Record[] } /** * 导出数据到 Excel Buffer */ export async function exportToExcel(params: { sheets: ExcelSheet[] }): Promise { const workbook = new ExcelJS.Workbook() for (const sheet of params.sheets) { const worksheet = workbook.addWorksheet(sheet.name, { views: [{ state: "frozen", ySplit: 1 }], }) worksheet.columns = sheet.columns.map((col) => ({ header: col.header, key: col.key, width: col.width ?? 20, })) // Header style const headerRow = worksheet.getRow(1) headerRow.font = { bold: true } headerRow.fill = { type: "pattern", pattern: "solid", fgColor: { argb: "FFE0E7FF" }, } headerRow.alignment = { vertical: "middle", horizontal: "left" } for (const row of sheet.rows) { worksheet.addRow(row) } worksheet.autoFilter = { from: { row: 1, column: 1 }, to: { row: 1, column: sheet.columns.length }, } } const arrayBuffer = await workbook.xlsx.writeBuffer() return Buffer.from(arrayBuffer) } /** * 从 Buffer 解析 Excel */ export async function parseExcel(buffer: Buffer): Promise { const workbook = new ExcelJS.Workbook() await workbook.xlsx.load(buffer as unknown as ArrayBuffer) const result: ParsedSheet[] = [] workbook.worksheets.forEach((worksheet) => { if (worksheet.rowCount === 0) { result.push({ sheetName: worksheet.name, rows: [] }) return } const headerRow = worksheet.getRow(1) const headers: string[] = [] const keys: string[] = [] headerRow.eachCell((cell, colNumber) => { const header = String(cell.value ?? "").trim() headers.push(header) keys.push(header || `column_${colNumber}`) }) const rows: Record[] = [] for (let rowNum = 2; rowNum <= worksheet.rowCount; rowNum++) { const row = worksheet.getRow(rowNum) const record: Record = {} let hasValue = false keys.forEach((key, idx) => { const cell = row.getCell(idx + 1) const value = cell.value if (value !== null && value !== undefined && value !== "") { record[key] = typeof value === "object" && "text" in value ? String((value as { text: string }).text) : value hasValue = true } else { record[key] = "" } }) if (hasValue) rows.push(record) } result.push({ sheetName: worksheet.name, rows }) }) return result } /** * 生成导入模板 Buffer */ export async function generateTemplate(params: { sheets: TemplateSheet[] }): Promise { const workbook = new ExcelJS.Workbook() for (const sheet of params.sheets) { const worksheet = workbook.addWorksheet(sheet.name) worksheet.columns = sheet.columns.map((col) => ({ header: col.header, key: col.key, width: col.width ?? 22, })) const headerRow = worksheet.getRow(1) headerRow.font = { bold: true } headerRow.fill = { type: "pattern", pattern: "solid", fgColor: { argb: "FFE0E7FF" }, } // Notes row (row 2) const noteRow = worksheet.getRow(2) sheet.columns.forEach((col, idx) => { const cell = noteRow.getCell(idx + 1) cell.value = col.note ?? "" cell.font = { italic: true, color: { argb: "FF6B7280" } } }) // Sample rows (starting from row 3) const sampleRows = sheet.sampleRows ?? [] sampleRows.forEach((row) => worksheet.addRow(row)) // Empty row for user input if (sampleRows.length === 0) { worksheet.addRow({}) } } const arrayBuffer = await workbook.xlsx.writeBuffer() return Buffer.from(arrayBuffer) }