|
|
@@ -0,0 +1,235 @@
|
|
|
+/**
|
|
|
+ * Excel导出工具类
|
|
|
+ * @fileoverview 核心Excel文件生成功能,独立可复用
|
|
|
+ * @description 专门处理Excel文件生成和样式设置,无业务逻辑依赖
|
|
|
+ */
|
|
|
+
|
|
|
+import ExcelJS from 'exceljs';
|
|
|
+
|
|
|
+/**
|
|
|
+ * Excel导出工具类
|
|
|
+ * 负责生成多Sheet Excel文件,支持样式设置和格式化
|
|
|
+ */
|
|
|
+class ExcelExportUtil {
|
|
|
+ constructor() {
|
|
|
+ this.defaultStyles = {
|
|
|
+ header: {
|
|
|
+ fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF165DFF' } },
|
|
|
+ font: { color: { argb: 'FFFFFFFF' }, bold: true, size: 11 },
|
|
|
+ alignment: { horizontal: 'center', vertical: 'middle' }
|
|
|
+ },
|
|
|
+ data: {
|
|
|
+ fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFFFFFFF' } },
|
|
|
+ font: { size: 11 },
|
|
|
+ alignment: { horizontal: 'left', vertical: 'middle' }
|
|
|
+ },
|
|
|
+ total: {
|
|
|
+ fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFFFF7E6' } },
|
|
|
+ font: { size: 11, bold: true },
|
|
|
+ alignment: { horizontal: 'right', vertical: 'middle' }
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成多Sheet Excel文件
|
|
|
+ * @param {Array} ordersData 处理后的订单数据
|
|
|
+ * @param {Object} options 导出选项
|
|
|
+ */
|
|
|
+ async exportToMultiSheets(ordersData, options = {}) {
|
|
|
+ const workbook = new ExcelJS.Workbook();
|
|
|
+
|
|
|
+ // 设置工作簿属性
|
|
|
+ workbook.creator = 'Gubersail工厂订单系统';
|
|
|
+ workbook.lastModifiedBy = '系统';
|
|
|
+ workbook.created = new Date();
|
|
|
+ workbook.modified = new Date();
|
|
|
+
|
|
|
+ // 为每个订单创建工作表
|
|
|
+ for (let i = 0; i < ordersData.length; i++) {
|
|
|
+ const orderData = ordersData[i];
|
|
|
+ const worksheet = workbook.addWorksheet(this.sanitizeSheetName(orderData.orderCode));
|
|
|
+
|
|
|
+ // 生成订单Sheet内容
|
|
|
+ this.generateOrderSheet(worksheet, orderData);
|
|
|
+
|
|
|
+ // 更新进度回调
|
|
|
+ if (options.progressCallback) {
|
|
|
+ options.progressCallback(i + 1, ordersData.length, '生成Excel工作表');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 生成Excel文件缓冲区
|
|
|
+ const buffer = await workbook.xlsx.writeBuffer();
|
|
|
+ return buffer;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成单个订单的工作表
|
|
|
+ * @param {ExcelJS.Worksheet} worksheet 工作表对象
|
|
|
+ * @param {Object} orderData 订单数据
|
|
|
+ */
|
|
|
+ generateOrderSheet(worksheet, orderData) {
|
|
|
+ let rowIndex = 1;
|
|
|
+ const maxColumns = 7; // 7个字段
|
|
|
+
|
|
|
+ // 1. 设置列宽
|
|
|
+ worksheet.columns = [
|
|
|
+ { width: 15 }, // 订单号
|
|
|
+ { width: 20 }, // 项目名称
|
|
|
+ { width: 15 }, // 供应商
|
|
|
+ { width: 12 }, // 采购数量
|
|
|
+ { width: 12 }, // 采购单价
|
|
|
+ { width: 15 }, // 采购金额
|
|
|
+ { width: 15 } // 付款金额
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 2. 标题行
|
|
|
+ const titleRow = worksheet.getRow(rowIndex++);
|
|
|
+ titleRow.values = [`订单采购汇总 - ${orderData.orderCode}`];
|
|
|
+ titleRow.font = { size: 14, bold: true };
|
|
|
+ titleRow.alignment = { horizontal: 'center', vertical: 'middle' };
|
|
|
+ titleRow.height = 40;
|
|
|
+ worksheet.mergeCells(1, 1, 1, maxColumns);
|
|
|
+
|
|
|
+ // 3. 空行
|
|
|
+ rowIndex++;
|
|
|
+
|
|
|
+ // 4. 表头行
|
|
|
+ const headerRow = worksheet.getRow(rowIndex++);
|
|
|
+ headerRow.values = ['订单号', '项目名称', '供应商', '采购数量', '采购单价', '采购金额', '付款金额'];
|
|
|
+ this.applyHeaderStyle(headerRow);
|
|
|
+
|
|
|
+ // 5. 订单主信息行
|
|
|
+ const mainInfoRow = worksheet.getRow(rowIndex++);
|
|
|
+ mainInfoRow.values = [
|
|
|
+ orderData.orderCode,
|
|
|
+ orderData.orgName,
|
|
|
+ orderData.supplierName,
|
|
|
+ orderData.totalQuantity,
|
|
|
+ orderData.unitPrice.toFixed(2),
|
|
|
+ orderData.totalAmount.toFixed(2),
|
|
|
+ orderData.paymentAmount.toFixed(2)
|
|
|
+ ];
|
|
|
+ this.applyDataStyle(mainInfoRow);
|
|
|
+
|
|
|
+ // 6. 空行
|
|
|
+ rowIndex++;
|
|
|
+
|
|
|
+ // 7. 明细标题行
|
|
|
+ const detailTitleRow = worksheet.getRow(rowIndex++);
|
|
|
+ detailTitleRow.values = ['订单明细信息'];
|
|
|
+ detailTitleRow.font = { size: 12, bold: true };
|
|
|
+ detailTitleRow.height = 30;
|
|
|
+ worksheet.mergeCells(rowIndex - 1, 1, rowIndex - 1, maxColumns);
|
|
|
+
|
|
|
+ // 8. 空行
|
|
|
+ rowIndex++;
|
|
|
+
|
|
|
+ // 9. 明细表头行
|
|
|
+ const detailHeaderRow = worksheet.getRow(rowIndex++);
|
|
|
+ detailHeaderRow.values = ['物料编码', '物料名称', '规格', '数量', '单价', '金额'];
|
|
|
+ this.applyHeaderStyle(detailHeaderRow);
|
|
|
+
|
|
|
+ // 10. 明细数据行
|
|
|
+ orderData.orderItems.forEach(item => {
|
|
|
+ const dataRow = worksheet.getRow(rowIndex++);
|
|
|
+ dataRow.values = [
|
|
|
+ item.itemCode,
|
|
|
+ item.itemName,
|
|
|
+ item.specs,
|
|
|
+ item.quantity,
|
|
|
+ item.unitPrice.toFixed(2),
|
|
|
+ item.totalAmount.toFixed(2)
|
|
|
+ ];
|
|
|
+ this.applyDataStyle(dataRow);
|
|
|
+
|
|
|
+ // 设置数字格式
|
|
|
+ dataRow.getCell(4).numFmt = '#,##0'; // 数量
|
|
|
+ dataRow.getCell(5).numFmt = '#,##0.00'; // 单价
|
|
|
+ dataRow.getCell(6).numFmt = '#,##0.00'; // 金额
|
|
|
+ });
|
|
|
+
|
|
|
+ // 11. 合计行
|
|
|
+ const totalRow = worksheet.getRow(rowIndex++);
|
|
|
+ const totalQuantity = orderData.orderItems.reduce((sum, item) => sum + item.quantity, 0);
|
|
|
+ const totalAmount = orderData.orderItems.reduce((sum, item) => sum + item.totalAmount, 0);
|
|
|
+
|
|
|
+ totalRow.values = ['合计', '', '', totalQuantity, '', totalAmount.toFixed(2), ''];
|
|
|
+ this.applyTotalStyle(totalRow);
|
|
|
+ totalRow.getCell(4).numFmt = '#,##0';
|
|
|
+ totalRow.getCell(6).numFmt = '#,##0.00';
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 应用表头样式
|
|
|
+ * @param {ExcelJS.Row} row 行对象
|
|
|
+ */
|
|
|
+ applyHeaderStyle(row) {
|
|
|
+ row.height = 30;
|
|
|
+ row.eachCell(cell => {
|
|
|
+ cell.fill = this.defaultStyles.header.fill;
|
|
|
+ cell.font = this.defaultStyles.header.font;
|
|
|
+ cell.alignment = this.defaultStyles.header.alignment;
|
|
|
+ cell.border = this.getStandardBorder();
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 应用数据行样式
|
|
|
+ * @param {ExcelJS.Row} row 行对象
|
|
|
+ */
|
|
|
+ applyDataStyle(row) {
|
|
|
+ row.height = 25;
|
|
|
+ row.eachCell(cell => {
|
|
|
+ cell.fill = this.defaultStyles.data.fill;
|
|
|
+ cell.font = this.defaultStyles.data.font;
|
|
|
+ cell.alignment = this.defaultStyles.data.alignment;
|
|
|
+ cell.border = this.getStandardBorder();
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 应用合计行样式
|
|
|
+ * @param {ExcelJS.Row} row 行对象
|
|
|
+ */
|
|
|
+ applyTotalStyle(row) {
|
|
|
+ row.height = 30;
|
|
|
+ row.eachCell(cell => {
|
|
|
+ cell.fill = this.defaultStyles.total.fill;
|
|
|
+ cell.font = this.defaultStyles.total.font;
|
|
|
+ cell.alignment = this.defaultStyles.total.alignment;
|
|
|
+ cell.border = this.getStandardBorder();
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取标准边框样式
|
|
|
+ */
|
|
|
+ getStandardBorder() {
|
|
|
+ return {
|
|
|
+ top: { style: 'thin', color: { argb: 'FFD0D7E3' } },
|
|
|
+ left: { style: 'thin', color: { argb: 'FFD0D7E3' } },
|
|
|
+ bottom: { style: 'thin', color: { argb: 'FFD0D7E3' } },
|
|
|
+ right: { style: 'thin', color: { argb: 'FFD0D7E3' } }
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 清理Sheet名称,确保Excel兼容性
|
|
|
+ * @param {string} name 原始名称
|
|
|
+ */
|
|
|
+ sanitizeSheetName(name) {
|
|
|
+ // Excel Sheet名称不能包含特殊字符,且长度不超过31
|
|
|
+ return name
|
|
|
+ .replace(/[\\/:*?"<>|]/g, '_') // 替换非法字符
|
|
|
+ .substring(0, 31) // 限制长度
|
|
|
+ .trim();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 导出单例实例
|
|
|
+export const excelExportUtil = new ExcelExportUtil();
|
|
|
+
|
|
|
+// 导出类
|
|
|
+export default ExcelExportUtil;
|