using NPOI.SS.UserModel; using NPOI.SS.Util; using NPOI.XSSF.UserModel; using System; using System.Collections.Generic; using System.IO; using System.Xml.Linq; using WebLib; namespace Education.Report { public class 領據列印 { private const string TEMPLATE_FILE_NAME = "領據列印模版.xlsx"; private const string LABOR_SHEET_NAME = "勞務費"; private const string TRAVEL_SHEET_NAME = "差旅費"; public System.IO.MemoryStream DoReport(List printDataList) { // 載入模版檔案 string templatePath = GetTemplatePath(); if (!File.Exists(templatePath)) { throw new FileNotFoundException(string.Format("模版檔案不存在: {0}", templatePath)); } XSSFWorkbook templateWorkbook = null; XSSFWorkbook outputWorkbook = null; System.IO.MemoryStream ms = new System.IO.MemoryStream(); try { // 從模版載入 workbook (唯讀) using (FileStream templateStream = new FileStream(templatePath, FileMode.Open, FileAccess.Read)) { templateWorkbook = new XSSFWorkbook(templateStream); } // 建立產出工作簿 outputWorkbook = new XSSFWorkbook(); // 取得模版工作表 ISheet laborTemplateSheet = templateWorkbook.GetSheet(LABOR_SHEET_NAME); ISheet travelTemplateSheet = templateWorkbook.GetSheet(TRAVEL_SHEET_NAME); if (laborTemplateSheet == null) { throw new Exception(string.Format("模版檔案中找不到工作表: {0}", LABOR_SHEET_NAME)); } if (travelTemplateSheet == null) { throw new Exception(string.Format("模版檔案中找不到工作表: {0}", TRAVEL_SHEET_NAME)); } // 處理每筆領據 int sheetCount = 0; foreach (var data in printDataList) { // 勞務費單據 if (data.Labor != null && !data.Labor.IsEmpty) { ProcessLaborSheet(outputWorkbook, laborTemplateSheet, data.Main, data.Labor, data.講師, data.非講師, ref sheetCount); } // 差旅費單據 if (data.Travel != null && !data.Travel.IsEmpty) { ProcessTravelSheet(outputWorkbook, travelTemplateSheet, data.Main, data.Travel, data.講師, data.非講師, ref sheetCount); } } // 寫入 MemoryStream outputWorkbook.Write(ms); return ms; } finally { if (templateWorkbook != null) { templateWorkbook.Close(); } } } private string GetTemplatePath() { // 模版檔案應位於應用程式根目錄下的 Template 資料夾 string appPath = System.AppDomain.CurrentDomain.BaseDirectory; string templatesFolder = Path.Combine(appPath, "Template"); return Path.Combine(templatesFolder, TEMPLATE_FILE_NAME); } private void ProcessLaborSheet(XSSFWorkbook outputWorkbook, ISheet templateSheet, 領據Item mainData, 勞務費Item laborData, 講師 講師, 非講師Item 非講師, ref int sheetCount) { // 複製模版工作表到產出工作簿 ISheet newSheet = CopySheetToWorkbook(outputWorkbook, templateSheet, string.Format("{0}_講師勞務費_{1}", mainData.領款人姓名, laborData.編號)); sheetCount++; // 建立替換字典 var replacements = new Dictionary { { "[領據編號]", laborData.編號 }, { "[領款人姓名]", mainData.領款人姓名 }, { "[計畫名稱]", mainData.計畫名稱 }, { "[費用別]", laborData.費用別 }, { "[單價]", string.Format("{0:###,##0}", laborData.單價) }, { "[數量]", string.Format("{0:###,##0.##}", laborData.數量) }, { "[單位]", laborData.單位 }, { "[身分證字號]", mainData.身分證字號 }, { "[聯絡電話]", mainData.身分別 == "講師" ? 講師.手機 : 非講師.聯絡電話 }, { "[戶籍地址]", mainData.身分別 == "講師" ? 講師.戶籍地址 : 非講師.戶籍地址 }, { "[電子郵件]", mainData.身分別 == "講師" ? 講師.電子郵件 : 非講師.電子郵件 }, { "[扣繳類別]", 扣繳類別.GetName(laborData.扣繳類別) }, { "[合計]", string.Format("{0:###,##0}", laborData.合計金額) }, { "[應扣繳稅額]", string.Format("{0:###,##0}", laborData.應扣繳稅額) }, { "[活動名稱事由]", mainData.活動名稱事由 }, { "[活動日期年]", (mainData.活動日期.Year - 1911).ToString() }, { "[活動日期月]", mainData.活動日期.Month.ToString() }, { "[活動日期日]", mainData.活動日期.Day.ToString() } }; // 執行字串取代 ReplaceInSheet(newSheet, replacements); } private void ProcessTravelSheet(XSSFWorkbook outputWorkbook, ISheet templateSheet, 領據Item mainData, 差旅費Item travelData, 講師 講師, 非講師Item 非講師, ref int sheetCount) { // 複製模版工作表到產出工作簿 ISheet newSheet = CopySheetToWorkbook(outputWorkbook, templateSheet, string.Format("{0}_差旅費_{1}", mainData.領款人姓名, travelData.編號)); sheetCount++; // 建立替換字典 var replacements = new Dictionary { { "[領據編號]", travelData.編號 }, { "[計畫名稱]", mainData.計畫名稱 }, { "[領款人姓名]", mainData.領款人姓名 }, { "[起點]", travelData.起點 }, { "[訖點]", travelData.訖點 }, { "[捷公]", string.Format("{0:###,##0}", travelData.捷運公車) }, { "[火車]", string.Format("{0:###,##0}", travelData.火車) }, { "[計程]", string.Format("{0:###,##0}", travelData.計程車) }, { "[高飛]", string.Format("{0:###,##0}", travelData.高鐵飛機) }, { "[開車]", string.Format("{0:###,##0}", travelData.自行開車) }, { "[過停]", string.Format("{0:###,##0}", travelData.過路費停車費) }, { "[住宿]", string.Format("{0:###,##0}", travelData.住宿費) }, { "[合計]", string.Format("{0:###,##0}", travelData.合計) }, { "[活動名稱事由]", mainData.活動名稱事由 }, { "[活動日期年]", (mainData.活動日期.Year - 1911).ToString() }, { "[活動日期月]", mainData.活動日期.Month.ToString() }, { "[活動日期日]", mainData.活動日期.Day.ToString() } }; // 執行字串取代 ReplaceInSheet(newSheet, replacements); } /// /// 複製工作表到另一個 workbook 中 /// private ISheet CopySheetToWorkbook(XSSFWorkbook targetWorkbook, ISheet sourceSheet, string newSheetName) { ISheet newSheet = targetWorkbook.CreateSheet(newSheetName); // 建立樣式快取和字體快取 Dictionary styleCache = new Dictionary(); Dictionary fontCache = new Dictionary(); // 複製所有行 for (int rowIndex = 0; rowIndex <= sourceSheet.LastRowNum; rowIndex++) { IRow sourceRow = sourceSheet.GetRow(rowIndex); if (sourceRow == null) continue; IRow newRow = newSheet.CreateRow(rowIndex); CopyRow(targetWorkbook, sourceRow, newRow, styleCache, fontCache); } // 複製列寬(只複製來源工作表實際有內容的欄位) int maxCol = 0; for (int r = 0; r <= sourceSheet.LastRowNum; r++) { var row = sourceSheet.GetRow(r); if (row != null && row.LastCellNum > maxCol) maxCol = row.LastCellNum; } for (int colIndex = 0; colIndex < maxCol; colIndex++) { newSheet.SetColumnWidth(colIndex, sourceSheet.GetColumnWidth(colIndex)); } // 複製合併儲存格 CopyMergedRegions(sourceSheet, newSheet); // 複製列印設定(加上驗證) CopyPrintSetup(sourceSheet, newSheet); // 複製頁面邊界: 天地上下左右 newSheet.SetMargin(MarginType.TopMargin, sourceSheet.GetMargin(MarginType.TopMargin)); newSheet.SetMargin(MarginType.BottomMargin, sourceSheet.GetMargin(MarginType.BottomMargin)); newSheet.SetMargin(MarginType.LeftMargin, sourceSheet.GetMargin(MarginType.LeftMargin)); newSheet.SetMargin(MarginType.RightMargin, sourceSheet.GetMargin(MarginType.RightMargin)); newSheet.SetMargin(MarginType.HeaderMargin, sourceSheet.GetMargin(MarginType.HeaderMargin)); newSheet.SetMargin(MarginType.FooterMargin, sourceSheet.GetMargin(MarginType.FooterMargin)); // 複製顯示格線設定 newSheet.DisplayGridlines = sourceSheet.DisplayGridlines; newSheet.IsPrintGridlines = sourceSheet.IsPrintGridlines; return newSheet; } /// /// 複製列印設定(帶驗證) /// private void CopyPrintSetup(ISheet sourceSheet, ISheet targetSheet) { if (sourceSheet.PrintSetup == null) return; try { IPrintSetup sourcePrintSetup = sourceSheet.PrintSetup; IPrintSetup targetPrintSetup = targetSheet.PrintSetup; // 複製方向 targetPrintSetup.Landscape = sourcePrintSetup.Landscape; // 複製紙張大小 targetPrintSetup.PaperSize = sourcePrintSetup.PaperSize; // 複製符合頁面設定 // targetPrintSetup.FitWidth = sourcePrintSetup.FitWidth; // targetPrintSetup.FitHeight = sourcePrintSetup.FitHeight; // 複製水平置中與垂直置中 targetSheet.HorizontallyCenter = sourceSheet.HorizontallyCenter; targetSheet.VerticallyCenter = sourceSheet.VerticallyCenter; // 將工作表放入單一頁面 // targetSheet.FitToPage = true; } catch (Exception ex) { // 如果複製列印設定失敗,記錄但不中斷流程 System.Diagnostics.Debug.WriteLine(string.Format("複製列印設定失敗: {0}", ex.Message)); } } /// /// 複製合併儲存格區域 /// private void CopyMergedRegions(ISheet sourceSheet, ISheet targetSheet) { int mergedRegionCount = sourceSheet.NumMergedRegions; for (int i = 0; i < mergedRegionCount; i++) { CellRangeAddress mergedRegion = sourceSheet.GetMergedRegion(i); if (mergedRegion != null) { targetSheet.AddMergedRegion(new CellRangeAddress( mergedRegion.FirstRow, mergedRegion.LastRow, mergedRegion.FirstColumn, mergedRegion.LastColumn )); } } } /// /// 複製行及其樣式到目標工作簿(使用快取避免重複建立樣式) /// private void CopyRow(XSSFWorkbook targetWorkbook, IRow sourceRow, IRow targetRow, Dictionary styleCache, Dictionary fontCache) { targetRow.Height = sourceRow.Height; for (int colIndex = 0; colIndex < sourceRow.LastCellNum; colIndex++) { ICell sourceCell = sourceRow.GetCell(colIndex); if (sourceCell == null) continue; ICell targetCell = targetRow.CreateCell(colIndex); // 複製樣式(使用快取) if (sourceCell.CellStyle != null) { short styleIndex = sourceCell.CellStyle.Index; ICellStyle targetStyle; if (!styleCache.TryGetValue(styleIndex, out targetStyle)) { // 第一次遇到這個樣式,建立並快取 targetStyle = CreateCellStyle(targetWorkbook, sourceCell.CellStyle, fontCache); styleCache[styleIndex] = targetStyle; } targetCell.CellStyle = targetStyle; } // 複製值 CopyCellValue(sourceCell, targetCell); } } /// /// 建立儲存格樣式(包含字體) /// private ICellStyle CreateCellStyle(XSSFWorkbook targetWorkbook, ICellStyle sourceStyle, Dictionary fontCache) { ICellStyle newStyle = targetWorkbook.CreateCellStyle(); // 先複製基本樣式(對齊、邊框、背景等) newStyle.Alignment = sourceStyle.Alignment; newStyle.VerticalAlignment = sourceStyle.VerticalAlignment; newStyle.WrapText = sourceStyle.WrapText; newStyle.BorderTop = sourceStyle.BorderTop; newStyle.BorderBottom = sourceStyle.BorderBottom; newStyle.BorderLeft = sourceStyle.BorderLeft; newStyle.BorderRight = sourceStyle.BorderRight; newStyle.TopBorderColor = sourceStyle.TopBorderColor; newStyle.BottomBorderColor = sourceStyle.BottomBorderColor; newStyle.LeftBorderColor = sourceStyle.LeftBorderColor; newStyle.RightBorderColor = sourceStyle.RightBorderColor; newStyle.FillPattern = sourceStyle.FillPattern; newStyle.FillForegroundColor = sourceStyle.FillForegroundColor; newStyle.FillBackgroundColor = sourceStyle.FillBackgroundColor; newStyle.DataFormat = sourceStyle.DataFormat; // 複製字體(使用快取,但不包含底線) XSSFCellStyle xssfSourceStyle = sourceStyle as XSSFCellStyle; if (xssfSourceStyle != null) { XSSFFont sourceFont = xssfSourceStyle.GetFont() as XSSFFont; if (sourceFont != null) { // 建立字體的唯一鍵 string fontKey = string.Format("{0}_{1}_{2}_{3}_{4}_{5}", sourceFont.FontName ?? "", sourceFont.FontHeightInPoints, sourceFont.IsBold, sourceFont.IsItalic, sourceFont.IsStrikeout, sourceFont.Color); IFont targetFont; if (!fontCache.TryGetValue(fontKey, out targetFont)) { // 第一次遇到這個字體,建立並快取 targetFont = targetWorkbook.CreateFont(); targetFont.FontName = sourceFont.FontName; targetFont.FontHeightInPoints = sourceFont.FontHeightInPoints; targetFont.IsBold = sourceFont.IsBold; targetFont.IsItalic = sourceFont.IsItalic; targetFont.IsStrikeout = sourceFont.IsStrikeout; targetFont.Color = sourceFont.Color; // 不複製底線:targetFont.Underline = sourceFont.Underline; fontCache[fontKey] = targetFont; } newStyle.SetFont(targetFont); } } return newStyle; } /// /// 複製儲存格的值 /// private void CopyCellValue(ICell sourceCell, ICell targetCell) { switch (sourceCell.CellType) { case CellType.String: targetCell.SetCellValue(sourceCell.StringCellValue); break; case CellType.Numeric: if (DateUtil.IsCellDateFormatted(sourceCell)) { targetCell.SetCellValue(sourceCell.DateCellValue); } else { targetCell.SetCellValue(sourceCell.NumericCellValue); } break; case CellType.Boolean: targetCell.SetCellValue(sourceCell.BooleanCellValue); break; case CellType.Formula: targetCell.SetCellFormula(sourceCell.CellFormula); break; case CellType.Blank: targetCell.SetCellType(CellType.Blank); break; default: break; } } /// /// 在工作表中執行字串取代 /// private void ReplaceInSheet(ISheet sheet, Dictionary replacements) { for (int rowIndex = 0; rowIndex <= sheet.LastRowNum; rowIndex++) { IRow row = sheet.GetRow(rowIndex); if (row == null) continue; for (int colIndex = 0; colIndex < row.LastCellNum; colIndex++) { ICell cell = row.GetCell(colIndex); if (cell == null || cell.CellType != CellType.String) continue; string cellValue = cell.StringCellValue; string newValue = cellValue; // 逐個替換 foreach (var kvp in replacements) { newValue = newValue.Replace(kvp.Key, kvp.Value ?? ""); } // 如果有變化,更新單元格 if (newValue != cellValue) { cell.SetCellValue(newValue); } } } } } }