chore: 首次簽入 Thinkyu ASP.NET 專案

- 加入 Visual Studio / ASP.NET .gitignore
- 排除建置輸出、IDE 設定、NuGet packages、大型 MSI 安裝檔
This commit is contained in:
2026-09-10 09:42:37 +08:00
commit 577060bc78
2496 changed files with 501389 additions and 0 deletions
+442
View File
@@ -0,0 +1,442 @@
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<Education.Services.PrintData> 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<string, string>
{
{ "[領據編號]", 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<string, string>
{
{ "[領據編號]", 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);
}
/// <summary>
/// 複製工作表到另一個 workbook 中
/// </summary>
private ISheet CopySheetToWorkbook(XSSFWorkbook targetWorkbook, ISheet sourceSheet, string newSheetName)
{
ISheet newSheet = targetWorkbook.CreateSheet(newSheetName);
// 建立樣式快取和字體快取
Dictionary<int, ICellStyle> styleCache = new Dictionary<int, ICellStyle>();
Dictionary<string, IFont> fontCache = new Dictionary<string, IFont>();
// 複製所有行
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;
}
/// <summary>
/// 複製列印設定(帶驗證)
/// </summary>
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));
}
}
/// <summary>
/// 複製合併儲存格區域
/// </summary>
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
));
}
}
}
/// <summary>
/// 複製行及其樣式到目標工作簿(使用快取避免重複建立樣式)
/// </summary>
private void CopyRow(XSSFWorkbook targetWorkbook, IRow sourceRow, IRow targetRow,
Dictionary<int, ICellStyle> styleCache, Dictionary<string, IFont> 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);
}
}
/// <summary>
/// 建立儲存格樣式(包含字體)
/// </summary>
private ICellStyle CreateCellStyle(XSSFWorkbook targetWorkbook, ICellStyle sourceStyle, Dictionary<string, IFont> 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;
}
/// <summary>
/// 複製儲存格的值
/// </summary>
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;
}
}
/// <summary>
/// 在工作表中執行字串取代
/// </summary>
private void ReplaceInSheet(ISheet sheet, Dictionary<string, string> 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);
}
}
}
}
}
}