@@ -0,0 +1,149 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using OfficeOpenXml;
|
||||
using OfficeOpenXml.Style;
|
||||
|
||||
namespace YhCore.Service.Excel
|
||||
{
|
||||
/// <summary>
|
||||
/// EPPlus 多 Sheet 导出(移植自旧 Bsch.Common/DocPrint/ExcelHelper,
|
||||
/// 保留 RenderToExcelBytes + ExportDynamicSheet:动态列、大标题、自定义列宽、打印优化)。
|
||||
/// 注意:EPPlus 实际产出 xlsx 字节流,旧系统文件名沿用 .xls,Excel 可正常打开,保持原契约。
|
||||
/// </summary>
|
||||
public class ExcelHelper
|
||||
{
|
||||
/// <summary>导出多 Sheet Excel 字节流(listdt/listsheetname/listtitle 按下标一一对应)</summary>
|
||||
public static byte[] RenderToExcelBytes(string fileName, List<DataTable> listdt, List<string> listsheetname, List<string> listtitle, List<double> listcolwidth = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var package = new ExcelPackage();
|
||||
int n = 0;
|
||||
foreach (var dt in listdt)
|
||||
{
|
||||
string sheetname = "Sheet" + (n + 1).ToString();
|
||||
if (n < listsheetname.Count) sheetname = listsheetname[n];
|
||||
string title = "";
|
||||
if (n < listtitle.Count) title = listtitle[n];
|
||||
var ws1 = package.Workbook.Worksheets.Add(sheetname);
|
||||
ExportDynamicSheet(ws1, dt, title, "A", listcolwidth);
|
||||
n++;
|
||||
}
|
||||
return package.GetAsByteArray();
|
||||
}
|
||||
catch (Exception) { return Array.Empty<byte>(); }
|
||||
}
|
||||
|
||||
#region 动态列 Sheet 导出(通用方法)
|
||||
|
||||
/// <summary>
|
||||
/// 导出 动态列 + 自定义表头 + 自定义页码 + 打印优化
|
||||
/// </summary>
|
||||
public static void ExportDynamicSheet(ExcelWorksheet ws, DataTable dt, string sheetTitle, string printMark, List<double> listcolwidth = null)
|
||||
{
|
||||
// -------------------------- 行定义 --------------------------
|
||||
int titleRow = 1; // 大标题行
|
||||
int headerRow = 2; // 列标题行
|
||||
int dataStartRow = 3; // 数据开始行
|
||||
|
||||
int colCount = dt.Columns.Count;
|
||||
int rowCount = dt.Rows.Count;
|
||||
|
||||
// -------------------------- 1. 自定义大标题(合并行) --------------------------
|
||||
if (sheetTitle != "")
|
||||
{
|
||||
ws.Cells[titleRow, 1].Value = sheetTitle;
|
||||
ws.Cells[titleRow, 1, titleRow, colCount].Merge = true; // 合并单元格
|
||||
|
||||
// 标题样式
|
||||
var titleStyle = ws.Cells[titleRow, 1].Style;
|
||||
titleStyle.Font.Size = 12;
|
||||
titleStyle.Font.Bold = true;
|
||||
titleStyle.HorizontalAlignment = ExcelHorizontalAlignment.Center;
|
||||
titleStyle.VerticalAlignment = ExcelVerticalAlignment.Center;
|
||||
ws.Row(titleRow).Height = 32;
|
||||
}
|
||||
else
|
||||
{
|
||||
headerRow = 1; // 列标题行
|
||||
dataStartRow = 2; // 数据开始行
|
||||
}
|
||||
|
||||
// -------------------------- 2. 动态列 自动填充表头 --------------------------
|
||||
for (int c = 0; c < colCount; c++)
|
||||
{
|
||||
ws.Cells[headerRow, c + 1].Value = dt.Columns[c].ColumnName;
|
||||
}
|
||||
|
||||
// -------------------------- 3. 动态列 自动填充数据 --------------------------
|
||||
for (int r = 0; r < rowCount; r++)
|
||||
{
|
||||
for (int c = 0; c < colCount; c++)
|
||||
{
|
||||
ws.Cells[dataStartRow + r, c + 1].Value = dt.Rows[r][c];
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------- 4. 自动换行 + 行高自适应(关键) --------------------------
|
||||
var dataRange = ws.Cells[headerRow, 1, dataStartRow + rowCount - 1, colCount];
|
||||
dataRange.Style.Font.Size = 10;
|
||||
dataRange.Style.WrapText = true;
|
||||
dataRange.Style.VerticalAlignment = ExcelVerticalAlignment.Center;
|
||||
dataRange.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
|
||||
// 加细边框
|
||||
dataRange.Style.Border.Top.Style = ExcelBorderStyle.Thin;
|
||||
dataRange.Style.Border.Bottom.Style = ExcelBorderStyle.Thin;
|
||||
dataRange.Style.Border.Left.Style = ExcelBorderStyle.Thin;
|
||||
dataRange.Style.Border.Right.Style = ExcelBorderStyle.Thin;
|
||||
|
||||
// 自动计算:所有列平均撑满 A4 宽度
|
||||
if (listcolwidth != null)
|
||||
{
|
||||
int colnum = listcolwidth.Count > colCount ? colCount : listcolwidth.Count;
|
||||
for (int i = 1; i <= colnum; i++)
|
||||
{
|
||||
ws.Column(i).Width = listcolwidth[i - 1];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
double totalWidth = 58;
|
||||
double perColWidth = (double)totalWidth / colCount;
|
||||
for (int i = 1; i <= colCount; i++)
|
||||
{
|
||||
ws.Column(i).Width = perColWidth;
|
||||
}
|
||||
}
|
||||
|
||||
// 行高自适应(EPPlus 4.x 兼容)
|
||||
for (int r = headerRow; r <= headerRow + rowCount - 1; r++)
|
||||
{
|
||||
ws.Row(r).CustomHeight = true;
|
||||
}
|
||||
|
||||
// -------------------------- 5. 表头样式 --------------------------
|
||||
var headerRange = ws.Cells[headerRow, 1, headerRow, colCount];
|
||||
headerRange.Style.Font.Bold = true;
|
||||
ws.Row(headerRow).Height = 25;
|
||||
|
||||
// -------------------------- 6. 打印:每页重复标题行 --------------------------
|
||||
ws.PrinterSettings.RepeatRows = new ExcelAddress($"{headerRow}:{headerRow}");
|
||||
|
||||
// -------------------------- 7. 自定义页码(中文样式 + 字体大小) --------------------------
|
||||
// &P=当前页 &N=总页 12号字体 居中
|
||||
ws.HeaderFooter.OddFooter.CenteredText = "&14 第 &P 页 / 共 &N 页";
|
||||
|
||||
// -------------------------- 8. 打印格式优化 --------------------------
|
||||
ws.PrinterSettings.PaperSize = ePaperSize.A4;
|
||||
ws.PrinterSettings.Orientation = eOrientation.Portrait;
|
||||
|
||||
ws.PrinterSettings.LeftMargin = 0.4m;
|
||||
ws.PrinterSettings.RightMargin = 0.4m;
|
||||
ws.PrinterSettings.TopMargin = 0.8m;
|
||||
ws.PrinterSettings.BottomMargin = 1.2m;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,404 @@
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using NPOI.HSSF.UserModel;
|
||||
using NPOI.SS.Formula.Eval;
|
||||
using NPOI.SS.UserModel;
|
||||
using NPOI.XSSF.UserModel;
|
||||
|
||||
namespace YhCore.Service.Excel
|
||||
{
|
||||
/// <summary>
|
||||
/// 使用 NPOI 操作 Excel,无需 Office COM 组件(移植自旧 Bsch.Common/DocPrint/ExcelRender,
|
||||
/// 仅保留 6 个 WYSHRole Excel 接口用到的导出/导入成员;控制器用 File() 包装下载)。
|
||||
/// </summary>
|
||||
public class ExcelRender
|
||||
{
|
||||
static ExcelRender()
|
||||
{
|
||||
// ExportDT 列宽按 GB2312 字节长计算:net5 需注册 CodePages 提供程序后才能 GetEncoding(936)
|
||||
Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
|
||||
}
|
||||
|
||||
#region DataTable 导出为 .xls 字节流
|
||||
|
||||
/// <summary>
|
||||
/// DataTable 导出为 Excel 的 MemoryStream(HSSF .xls;65535 行自动分 sheet;列宽按 GB2312 字节长,上限 50)
|
||||
/// </summary>
|
||||
static MemoryStream ExportDT(DataTable dtSource, string strHeaderText)
|
||||
{
|
||||
HSSFWorkbook workbook = new HSSFWorkbook();
|
||||
HSSFSheet sheet = workbook.CreateSheet() as HSSFSheet;
|
||||
|
||||
HSSFCellStyle dateStyle = workbook.CreateCellStyle() as HSSFCellStyle;
|
||||
HSSFDataFormat format = workbook.CreateDataFormat() as HSSFDataFormat;
|
||||
dateStyle.DataFormat = format.GetFormat("yyyy-mm-dd");
|
||||
|
||||
//取得列宽
|
||||
int[] arrColWidth = new int[dtSource.Columns.Count];
|
||||
foreach (DataColumn item in dtSource.Columns)
|
||||
{
|
||||
arrColWidth[item.Ordinal] = Encoding.GetEncoding(936).GetBytes(item.ColumnName.ToString()).Length;
|
||||
}
|
||||
for (int i = 0; i < dtSource.Rows.Count; i++)
|
||||
{
|
||||
for (int j = 0; j < dtSource.Columns.Count; j++)
|
||||
{
|
||||
int intTemp = Encoding.GetEncoding(936).GetBytes(dtSource.Rows[i][j].ToString()).Length;
|
||||
if (intTemp > arrColWidth[j])
|
||||
{
|
||||
arrColWidth[j] = intTemp;
|
||||
if (intTemp >= 50)
|
||||
arrColWidth[j] = 50;
|
||||
}
|
||||
}
|
||||
}
|
||||
int rowIndex = 0;
|
||||
|
||||
foreach (DataRow row in dtSource.Rows)
|
||||
{
|
||||
#region 新建表,填充表头,填充列头,样式
|
||||
|
||||
if (rowIndex == 65535 || rowIndex == 0)
|
||||
{
|
||||
if (rowIndex != 0)
|
||||
{
|
||||
sheet = workbook.CreateSheet() as HSSFSheet;
|
||||
}
|
||||
|
||||
#region 列头及样式
|
||||
|
||||
{
|
||||
HSSFRow headerRow = sheet.CreateRow(0) as HSSFRow;
|
||||
|
||||
HSSFCellStyle headStyle = workbook.CreateCellStyle() as HSSFCellStyle;
|
||||
headStyle.Alignment = NPOI.SS.UserModel.HorizontalAlignment.Center;
|
||||
HSSFFont font = workbook.CreateFont() as HSSFFont;
|
||||
font.FontHeightInPoints = 10;
|
||||
font.Boldweight = 700;
|
||||
headStyle.SetFont(font);
|
||||
|
||||
foreach (DataColumn column in dtSource.Columns)
|
||||
{
|
||||
headerRow.CreateCell(column.Ordinal).SetCellValue(column.ColumnName);
|
||||
headerRow.GetCell(column.Ordinal).CellStyle = headStyle;
|
||||
|
||||
//设置列宽
|
||||
sheet.SetColumnWidth(column.Ordinal, (arrColWidth[column.Ordinal] + 1) * 256);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
rowIndex = 1;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 填充内容
|
||||
|
||||
HSSFRow dataRow = sheet.CreateRow(rowIndex) as HSSFRow;
|
||||
foreach (DataColumn column in dtSource.Columns)
|
||||
{
|
||||
HSSFCell newCell = dataRow.CreateCell(column.Ordinal) as HSSFCell;
|
||||
|
||||
string drValue = row[column].ToString();
|
||||
|
||||
switch (column.DataType.ToString())
|
||||
{
|
||||
case "System.String": //字符串类型
|
||||
newCell.SetCellValue(drValue);
|
||||
break;
|
||||
case "System.DateTime": //日期类型
|
||||
DateTime dateV;
|
||||
DateTime.TryParse(drValue, out dateV);
|
||||
newCell.SetCellValue(dateV);
|
||||
newCell.CellStyle = dateStyle; //格式化显示
|
||||
break;
|
||||
case "System.Boolean": //布尔型
|
||||
bool boolV = false;
|
||||
bool.TryParse(drValue, out boolV);
|
||||
newCell.SetCellValue(boolV);
|
||||
break;
|
||||
case "System.Int16": //整型
|
||||
case "System.Int32":
|
||||
case "System.Int64":
|
||||
case "System.Byte":
|
||||
int intV = 0;
|
||||
int.TryParse(drValue, out intV);
|
||||
newCell.SetCellValue(intV);
|
||||
break;
|
||||
case "System.Decimal": //浮点型
|
||||
case "System.Double":
|
||||
double doubV = 0;
|
||||
double.TryParse(drValue, out doubV);
|
||||
newCell.SetCellValue(doubV);
|
||||
break;
|
||||
case "System.DBNull": //空值处理
|
||||
newCell.SetCellValue("");
|
||||
break;
|
||||
default:
|
||||
newCell.SetCellValue("");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
rowIndex++;
|
||||
}
|
||||
using (MemoryStream ms = new MemoryStream())
|
||||
{
|
||||
workbook.Write(ms);
|
||||
ms.Flush();
|
||||
ms.Position = 0;
|
||||
|
||||
return ms;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>导出 Excel 字节流(供 Service 层返回 byte[],控制器包 File 下载)</summary>
|
||||
public static byte[] RenderToExcelBytes(DataTable table, string fileName)
|
||||
{
|
||||
try { return ExportDT(table, null).ToArray(); }
|
||||
catch (Exception) { return Array.Empty<byte>(); }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 读取 excel 到 DataTable
|
||||
|
||||
/// <summary>Excel 文档流是否有数据(默认第一个 sheet)</summary>
|
||||
public static bool HasData(Stream excelFileStream, string fileExt)
|
||||
{
|
||||
return HasData(excelFileStream, 0, fileExt);
|
||||
}
|
||||
|
||||
public static bool HasData(Stream excelFileStream, int sheetIndex, string fileExt)
|
||||
{
|
||||
using (excelFileStream)
|
||||
{
|
||||
IWorkbook workbook = null;
|
||||
if (fileExt == ".xls")
|
||||
{
|
||||
workbook = new HSSFWorkbook(excelFileStream);
|
||||
}
|
||||
else if (fileExt == ".xlsx")
|
||||
{
|
||||
workbook = new XSSFWorkbook(excelFileStream);
|
||||
}
|
||||
|
||||
{
|
||||
if (workbook.NumberOfSheets > 0)
|
||||
{
|
||||
if (sheetIndex < workbook.NumberOfSheets)
|
||||
{
|
||||
ISheet sheet = workbook.GetSheetAt(sheetIndex);
|
||||
{
|
||||
return sheet.PhysicalNumberOfRows > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Excel 文档流转换成 DataTable
|
||||
/// </summary>
|
||||
/// <param name="excelFileStream">Excel 文档流</param>
|
||||
/// <param name="sheetIndex">表索引号,如第一个表为 0</param>
|
||||
/// <param name="headerRowIndex">标题行索引号,如第一行为 0</param>
|
||||
public static DataTable RenderFromExcel(Stream excelFileStream, int sheetIndex, int headerRowIndex, string fileExt)
|
||||
{
|
||||
DataTable table = null;
|
||||
|
||||
using (excelFileStream)
|
||||
{
|
||||
IWorkbook workbook = null;
|
||||
if (fileExt == ".xls")
|
||||
{
|
||||
workbook = new HSSFWorkbook(excelFileStream);
|
||||
}
|
||||
else if (fileExt == ".xlsx")
|
||||
{
|
||||
workbook = new XSSFWorkbook(excelFileStream);
|
||||
}
|
||||
|
||||
ISheet sheet = workbook.GetSheetAt(sheetIndex);
|
||||
{
|
||||
table = ImportDt(sheet, headerRowIndex, true);
|
||||
}
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
/// <summary>将指定 sheet 中的数据导出到 datatable 中(第一行为列头)</summary>
|
||||
static DataTable ImportDt(ISheet sheet, int HeaderRowIndex, bool needHeader)
|
||||
{
|
||||
DataTable table = new DataTable();
|
||||
IRow headerRow;
|
||||
int cellCount;
|
||||
try
|
||||
{
|
||||
if (HeaderRowIndex < 0 || !needHeader)
|
||||
{
|
||||
headerRow = sheet.GetRow(0);
|
||||
cellCount = headerRow.LastCellNum;
|
||||
|
||||
for (int i = headerRow.FirstCellNum; i <= cellCount; i++)
|
||||
{
|
||||
DataColumn column = new DataColumn(Convert.ToString(i));
|
||||
table.Columns.Add(column);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
headerRow = sheet.GetRow(HeaderRowIndex);
|
||||
cellCount = headerRow.LastCellNum;
|
||||
|
||||
for (int i = headerRow.FirstCellNum; i < cellCount; i++)
|
||||
{
|
||||
if (headerRow.GetCell(i) == null)
|
||||
{
|
||||
if (table.Columns.IndexOf(Convert.ToString(i)) > 0)
|
||||
{
|
||||
DataColumn column = new DataColumn(Convert.ToString("重复列名" + i));
|
||||
table.Columns.Add(column);
|
||||
}
|
||||
else
|
||||
{
|
||||
DataColumn column = new DataColumn(Convert.ToString(i));
|
||||
table.Columns.Add(column);
|
||||
}
|
||||
}
|
||||
else if (table.Columns.IndexOf(headerRow.GetCell(i).ToString()) > 0)
|
||||
{
|
||||
DataColumn column = new DataColumn(Convert.ToString("重复列名" + i));
|
||||
table.Columns.Add(column);
|
||||
}
|
||||
else
|
||||
{
|
||||
DataColumn column = new DataColumn(headerRow.GetCell(i).ToString());
|
||||
table.Columns.Add(column);
|
||||
}
|
||||
}
|
||||
}
|
||||
int rowCount = sheet.LastRowNum;
|
||||
for (int i = (HeaderRowIndex + 1); i <= sheet.LastRowNum; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
IRow row;
|
||||
if (sheet.GetRow(i) == null)
|
||||
{
|
||||
row = sheet.CreateRow(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
row = sheet.GetRow(i);
|
||||
}
|
||||
|
||||
DataRow dataRow = table.NewRow();
|
||||
|
||||
for (int j = row.FirstCellNum; j < cellCount; j++)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (row.GetCell(j) != null)
|
||||
{
|
||||
switch (row.GetCell(j).CellType)
|
||||
{
|
||||
case CellType.String:
|
||||
string str = row.GetCell(j).StringCellValue;
|
||||
if (str != null && str.Length > 0)
|
||||
{
|
||||
dataRow[j] = str.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
dataRow[j] = null;
|
||||
}
|
||||
break;
|
||||
case CellType.Numeric:
|
||||
if (DateUtil.IsCellDateFormatted(row.GetCell(j)))
|
||||
{
|
||||
dataRow[j] = DateTime.FromOADate(row.GetCell(j).NumericCellValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
dataRow[j] = Convert.ToDouble(row.GetCell(j).NumericCellValue);
|
||||
}
|
||||
break;
|
||||
case CellType.Boolean:
|
||||
dataRow[j] = Convert.ToString(row.GetCell(j).BooleanCellValue);
|
||||
break;
|
||||
case CellType.Error:
|
||||
dataRow[j] = ErrorEval.GetText(row.GetCell(j).ErrorCellValue);
|
||||
break;
|
||||
case CellType.Formula:
|
||||
switch (row.GetCell(j).CachedFormulaResultType)
|
||||
{
|
||||
case CellType.String:
|
||||
string strFORMULA = row.GetCell(j).StringCellValue;
|
||||
if (strFORMULA != null && strFORMULA.Length > 0)
|
||||
{
|
||||
dataRow[j] = strFORMULA.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
dataRow[j] = null;
|
||||
}
|
||||
break;
|
||||
case CellType.Numeric:
|
||||
dataRow[j] = Convert.ToString(row.GetCell(j).NumericCellValue);
|
||||
break;
|
||||
case CellType.Boolean:
|
||||
dataRow[j] = Convert.ToString(row.GetCell(j).BooleanCellValue);
|
||||
break;
|
||||
case CellType.Error:
|
||||
dataRow[j] = ErrorEval.GetText(row.GetCell(j).ErrorCellValue);
|
||||
break;
|
||||
default:
|
||||
dataRow[j] = "";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
dataRow[j] = "";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
table.Rows.Add(dataRow);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public static byte[] StreamToBytes(Stream stream)
|
||||
{
|
||||
byte[] bytes = new byte[stream.Length];
|
||||
stream.Read(bytes, 0, bytes.Length);
|
||||
// 设置当前流的位置为流的开始
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
using System;
|
||||
|
||||
namespace YhCore.Service.Pdf
|
||||
{
|
||||
/// <summary>数字转中文大写金额(移植自旧 Bsch.Common/DocPrint/NumberToChineseConverter,审批单合计金额用)。</summary>
|
||||
public class NumberToChineseConverter
|
||||
{
|
||||
// 中文大写数字
|
||||
private static readonly char[] ChineseNumbers = { '零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖' };
|
||||
// 中文大写单位
|
||||
private static readonly string[] ChineseUnits = { "分", "角", "元", "拾", "佰", "仟", "万", "拾", "佰", "仟", "亿", "拾", "佰", "仟", "兆" };
|
||||
|
||||
/// <summary>
|
||||
/// 将数字转换为中文大写金额
|
||||
/// </summary>
|
||||
/// <param name="number">需要转换的数字(支持整数和小数,建议保留两位小数)</param>
|
||||
/// <returns>中文大写金额字符串</returns>
|
||||
public static string ToChineseUppercase(decimal number)
|
||||
{
|
||||
// 处理负数
|
||||
bool isNegative = number < 0;
|
||||
if (isNegative)
|
||||
{
|
||||
number = Math.Abs(number);
|
||||
}
|
||||
|
||||
// 四舍五入保留两位小数
|
||||
number = Math.Round(number, 2);
|
||||
long integerPart = (long)number; // 整数部分
|
||||
int decimalPart = (int)((number - integerPart) * 100); // 小数部分(角分)
|
||||
|
||||
// 处理零值
|
||||
if (integerPart == 0 && decimalPart == 0)
|
||||
{
|
||||
return "零元整";
|
||||
}
|
||||
|
||||
string result = string.Empty;
|
||||
|
||||
// 处理整数部分
|
||||
if (integerPart > 0)
|
||||
{
|
||||
string integerStr = integerPart.ToString();
|
||||
int length = integerStr.Length;
|
||||
|
||||
// 标记是否需要加零
|
||||
bool needZero = false;
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
int digit = int.Parse(integerStr[i].ToString());
|
||||
int unitIndex = length - i - 1 + 2; // 对应到ChineseUnits的索引(从元开始)
|
||||
|
||||
if (digit == 0)
|
||||
{
|
||||
needZero = true;
|
||||
// 处理万、亿等单位,避免漏单位
|
||||
if (unitIndex == 6 || unitIndex == 10)
|
||||
{
|
||||
result += ChineseUnits[unitIndex];
|
||||
needZero = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (needZero)
|
||||
{
|
||||
result += ChineseNumbers[0];
|
||||
needZero = false;
|
||||
}
|
||||
result += ChineseNumbers[digit] + ChineseUnits[unitIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理小数部分(角分)
|
||||
if (decimalPart > 0)
|
||||
{
|
||||
int jiao = decimalPart / 10; // 角
|
||||
int fen = decimalPart % 10; // 分
|
||||
|
||||
if (jiao > 0)
|
||||
{
|
||||
result += ChineseNumbers[jiao] + ChineseUnits[1];
|
||||
}
|
||||
else if (integerPart > 0) // 有整数部分但角为0,需要加零
|
||||
{
|
||||
result += ChineseNumbers[0];
|
||||
}
|
||||
|
||||
if (fen > 0)
|
||||
{
|
||||
result += ChineseNumbers[fen] + ChineseUnits[0];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 无小数部分,加"整"
|
||||
result += "整";
|
||||
}
|
||||
|
||||
// 处理负数
|
||||
if (isNegative)
|
||||
{
|
||||
result = "负" + result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 重载方法,支持double类型
|
||||
public static string ToChineseUppercase(double number)
|
||||
{
|
||||
return ToChineseUppercase((decimal)number);
|
||||
}
|
||||
|
||||
// 重载方法,支持int类型
|
||||
public static string ToChineseUppercase(int number)
|
||||
{
|
||||
return ToChineseUppercase((decimal)number);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
// PDF helper(移植自旧 Bsch.Common/DocPrint/PdfHelper):
|
||||
// HTML→PDF 由 wkhtmltopdf 外部进程完成(原 iTextSharp XMLWorker 无 Core 版),
|
||||
// 多页合并用 iTextSharp.LGPLv2.Core;Base64→PNG 直接落盘。
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using iTextSharp.text;
|
||||
using iTextSharp.text.pdf;
|
||||
using Yh.Platform.Abstractions;
|
||||
using YhCore.Common.Config;
|
||||
|
||||
namespace YhCore.Service.Pdf
|
||||
{
|
||||
public class PdfHelper
|
||||
{
|
||||
private static string WkHtmlToPdfPath =>
|
||||
string.IsNullOrEmpty(Config.WkHtmlToPdfPath) ? "wkhtmltopdf" : Config.WkHtmlToPdfPath;
|
||||
|
||||
// --enable-local-file-access 为 wkhtmltopdf 0.12.6 新增开关(签名图片用本地绝对路径 <img> 必需);
|
||||
// 0.12.5 及以下(如 Debian 10 源)不认识该参数会报 Unknown long argument,按版本探测决定是否追加
|
||||
private static readonly Lazy<bool> NeedsLocalFileAccessFlag = new Lazy<bool>(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
using var probe = Process.Start(new ProcessStartInfo(WkHtmlToPdfPath, "--version")
|
||||
{
|
||||
RedirectStandardOutput = true,
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false,
|
||||
});
|
||||
if (probe == null) return true;
|
||||
string output = probe.StandardOutput.ReadToEnd();
|
||||
probe.WaitForExit(10000);
|
||||
var m = Regex.Match(output, @"(\d+)\.(\d+)\.(\d+)");
|
||||
if (!m.Success) return true;
|
||||
var version = new Version(int.Parse(m.Groups[1].Value), int.Parse(m.Groups[2].Value), int.Parse(m.Groups[3].Value));
|
||||
return version >= new Version(0, 12, 6);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
/// <summary>逐页 HTML→PDF 后合并为单个 PDF 字节流(DwonWYSQD 用)</summary>
|
||||
public static byte[] ConvertHtmlTextToPdfByList(List<string> htmlText, string HorV = "V")
|
||||
=> MergePdf(htmlText.Select(HtmlToPdf).ToList());
|
||||
|
||||
private static byte[] HtmlToPdf(string html)
|
||||
{
|
||||
var tmpIn = Path.GetTempFileName() + ".html";
|
||||
var tmpOut = Path.GetTempFileName() + ".pdf";
|
||||
try
|
||||
{
|
||||
File.WriteAllText(tmpIn, html, new UTF8Encoding(false));
|
||||
string localFlag = NeedsLocalFileAccessFlag.Value ? " --enable-local-file-access" : "";
|
||||
var psi = new ProcessStartInfo(WkHtmlToPdfPath,
|
||||
$" --encoding utf-8{localFlag} --disable-smart-shrinking \"{tmpIn}\" \"{tmpOut}\"")
|
||||
{ CreateNoWindow = true, UseShellExecute = false };
|
||||
using var proc = Process.Start(psi);
|
||||
if (proc == null || !proc.WaitForExit(60000) || !File.Exists(tmpOut))
|
||||
throw new DException("审批单 PDF 生成失败,请检查服务器是否已安装 wkhtmltopdf(或配置 wkhtmltopdf_path 指向其可执行文件)!");
|
||||
return File.ReadAllBytes(tmpOut);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(tmpIn)) File.Delete(tmpIn);
|
||||
if (File.Exists(tmpOut)) File.Delete(tmpOut);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] MergePdf(List<byte[]> pdfBytes)
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
var doc = new Document();
|
||||
var copy = new PdfCopy(doc, ms);
|
||||
doc.Open();
|
||||
foreach (var b in pdfBytes)
|
||||
{
|
||||
if (b == null || b.Length == 0) continue;
|
||||
var reader = new PdfReader(b);
|
||||
for (int i = 1; i <= reader.NumberOfPages; i++)
|
||||
copy.AddPage(copy.GetImportedPage(reader, i));
|
||||
copy.FreeReader(reader);
|
||||
reader.Close();
|
||||
}
|
||||
doc.Close();
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>签名 base64 → PNG 落盘(审批单 HTML 内嵌本地路径图片用)</summary>
|
||||
public static void ConvertBase64ToPng(string base64String, string outputFilePath)
|
||||
{
|
||||
var bytes = Convert.FromBase64String(base64String);
|
||||
File.WriteAllBytes(outputFilePath, bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user