修复epplus api
This commit is contained in:
@@ -15,266 +15,153 @@ namespace XericUI.XTable.Rendering.Component
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class XericUIActionTable
|
public partial class XericUIActionTable
|
||||||
{
|
{
|
||||||
#region 导入
|
#region 导入(公开入口)
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 从 Excel .xlsx 文件加载数据到表格。
|
/// 从 Excel .xlsx 文件加载数据到表格。
|
||||||
/// 支持文本、合并单元格、列宽、行高的导入。
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="filePath">Excel 文件路径</param>
|
/// <param name="filePath">Excel 文件路径</param>
|
||||||
/// <param name="sheetIndex">工作表索引(0-based),默认 0 即第一个工作表</param>
|
/// <param name="sheetIndex">工作表索引(0-based),默认 0 即第一个工作表</param>
|
||||||
public void LoadFromExcelFile(string filePath, int sheetIndex = 0)
|
public void LoadFromExcelFile(string filePath, int sheetIndex = 0)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(filePath))
|
if (!TryOpenPackage(filePath, out ExcelPackage package)) return;
|
||||||
|
using (package)
|
||||||
{
|
{
|
||||||
Debug.LogWarning("[XTable] LoadFromExcelFile: 文件路径为空");
|
if (sheetIndex < 0 || sheetIndex >= package.Workbook.Worksheets.Count)
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!File.Exists(filePath))
|
|
||||||
{
|
|
||||||
Debug.LogWarning($"[XTable] LoadFromExcelFile: 文件不存在: {filePath}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using (var package = new ExcelPackage(new FileInfo(filePath)))
|
|
||||||
{
|
{
|
||||||
if (sheetIndex < 0 || sheetIndex >= package.Workbook.Worksheets.Count)
|
Debug.LogWarning($"[XTable] LoadFromExcelFile: 工作表索引 {sheetIndex} 超出范围(共 {package.Workbook.Worksheets.Count} 个工作表)");
|
||||||
{
|
return;
|
||||||
Debug.LogWarning($"[XTable] LoadFromExcelFile: 工作表索引 {sheetIndex} 超出范围(共 {package.Workbook.Worksheets.Count} 个工作表)");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ExcelWorksheet ws = package.Workbook.Worksheets[sheetIndex];
|
|
||||||
if (ws == null)
|
|
||||||
{
|
|
||||||
Debug.LogWarning("[XTable] LoadFromExcelFile: 工作簿中没有工作表");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确定行列数
|
|
||||||
int rows = 0, cols = 0;
|
|
||||||
if (ws.Dimension != null)
|
|
||||||
{
|
|
||||||
rows = ws.Dimension.Rows;
|
|
||||||
cols = ws.Dimension.Columns;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rows <= 0 || cols <= 0)
|
|
||||||
{
|
|
||||||
Debug.LogWarning("[XTable] LoadFromExcelFile: Excel 文件为空或无法识别行列数");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确保数据层存在
|
|
||||||
if (m_TableData == null)
|
|
||||||
m_TableData = new XTableData();
|
|
||||||
|
|
||||||
// 设置行高
|
|
||||||
m_RowHeights = new float[rows];
|
|
||||||
for (int r = 0; r < rows; r++)
|
|
||||||
{
|
|
||||||
float h = (float)ws.Row(r + 1).Height;
|
|
||||||
m_RowHeights[r] = h > 0 ? h : Config.DefaultCellHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置列宽
|
|
||||||
m_ColWidths = new float[cols];
|
|
||||||
for (int c = 0; c < cols; c++)
|
|
||||||
{
|
|
||||||
float w = (float)ws.Column(c + 1).Width;
|
|
||||||
m_ColWidths[c] = w > 0 ? w : Config.DefaultCellWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 读取单元格文本
|
|
||||||
for (int r = 0; r < rows; r++)
|
|
||||||
{
|
|
||||||
for (int c = 0; c < cols; c++)
|
|
||||||
{
|
|
||||||
object val = ws.Cells[r + 1, c + 1].Value;
|
|
||||||
if (val != null)
|
|
||||||
{
|
|
||||||
string text = val.ToString();
|
|
||||||
if (!string.IsNullOrEmpty(text))
|
|
||||||
{
|
|
||||||
var cell = m_TableData.GetOrCreateCell(r, c);
|
|
||||||
cell.Text = text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 读取合并单元格
|
|
||||||
if (ws.MergedCells.Count > 0)
|
|
||||||
{
|
|
||||||
m_TableData.ClearAllMerges();
|
|
||||||
foreach (string mergeAddr in ws.MergedCells)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var addr = new ExcelAddressBase(mergeAddr);
|
|
||||||
int startR = addr.Start.Row - 1; // 1-based → 0-based
|
|
||||||
int startC = addr.Start.Column - 1;
|
|
||||||
int endR = addr.End.Row - 1;
|
|
||||||
int endC = addr.End.Column - 1;
|
|
||||||
int rowSpan = endR - startR + 1;
|
|
||||||
int colSpan = endC - startC + 1;
|
|
||||||
|
|
||||||
if (rowSpan > 1 || colSpan > 1)
|
|
||||||
{
|
|
||||||
if (endR < rows && endC < cols)
|
|
||||||
m_TableData.MergeCells(startR, startC, rowSpan, colSpan);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// 忽略无法解析的合并地址
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 读取冻结窗格
|
|
||||||
string freezePanes = ws.View.FreezePanes;
|
|
||||||
if (!string.IsNullOrEmpty(freezePanes))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var addr = new ExcelAddressBase(freezePanes);
|
|
||||||
int freezeRows = addr.Start.Row - 1; // 1-based → 冻结行数
|
|
||||||
int freezeCols = addr.Start.Column - 1; // 1-based → 冻结列数
|
|
||||||
if (freezeRows > 0 || freezeCols > 0)
|
|
||||||
SetFreezeCount(freezeRows, freezeCols);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// 忽略解析失败的冻结地址
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 标记脏并刷新
|
|
||||||
MarkDirty(TableDirtyType.DataChanged | TableDirtyType.LayoutChanged);
|
|
||||||
RecalculateTotalSize();
|
|
||||||
RefreshView();
|
|
||||||
}
|
}
|
||||||
|
ExcelWorksheet ws = package.Workbook.Worksheets[sheetIndex];
|
||||||
|
if (!TryGetWorksheetDimension(ws, out int rows, out int cols)) return;
|
||||||
|
EnsureTableData();
|
||||||
|
LoadFromExcelFileCore(ws, rows, cols);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从 Excel .xlsx 文件加载数据到表格,按工作表名称选择。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath">Excel 文件路径</param>
|
||||||
|
/// <param name="sheetName">工作表名称</param>
|
||||||
|
public void LoadFromExcelFile(string filePath, string sheetName)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(sheetName))
|
||||||
{
|
{
|
||||||
Debug.LogError($"[XTable] LoadFromExcelFile 失败: {ex.Message}\n{ex.StackTrace}");
|
Debug.LogWarning("[XTable] LoadFromExcelFile: 工作表名称为空");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!TryOpenPackage(filePath, out ExcelPackage package)) return;
|
||||||
|
using (package)
|
||||||
|
{
|
||||||
|
ExcelWorksheet ws = package.Workbook.Worksheets[sheetName];
|
||||||
|
if (ws == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[XTable] LoadFromExcelFile: 找不到工作表 \"{sheetName}\"");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!TryGetWorksheetDimension(ws, out int rows, out int cols)) return;
|
||||||
|
EnsureTableData();
|
||||||
|
LoadFromExcelFileCore(ws, rows, cols);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 导出
|
#region 导出(公开入口)
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 将表格数据导出为 Excel .xlsx 文件。
|
/// 将表格数据导出为 Excel .xlsx 文件,写入指定名称的工作表。
|
||||||
/// 支持文本、合并单元格、列宽、行高、样式(颜色/字体/填充)、边框缓存的导出。
|
/// 文件不存在时自动创建;已存在时查找或追加同名工作表。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="filePath">Excel 文件路径</param>
|
/// <param name="filePath">Excel 文件路径</param>
|
||||||
/// <param name="sheetName">工作表名称,默认 "Sheet1"</param>
|
/// <param name="sheetName">工作表名称,默认 "Sheet1"</param>
|
||||||
public void SaveToExcelFile(string filePath, string sheetName = "Sheet1")
|
public void SaveToExcelFile(string filePath, string sheetName = "Sheet1")
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(filePath))
|
if (string.IsNullOrEmpty(sheetName)) sheetName = "Sheet1";
|
||||||
{
|
if (!EnsureDirectory(filePath)) return;
|
||||||
Debug.LogWarning("[XTable] SaveToExcelFile: 文件路径为空");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确保目录存在
|
|
||||||
string dir = Path.GetDirectoryName(filePath);
|
|
||||||
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
|
|
||||||
Directory.CreateDirectory(dir);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var package = new ExcelPackage())
|
if (File.Exists(filePath))
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(sheetName))
|
// 追加到已有文件
|
||||||
sheetName = "Sheet1";
|
using (var package = new ExcelPackage(new FileInfo(filePath)))
|
||||||
ExcelWorksheet ws = package.Workbook.Worksheets.Add(sheetName);
|
|
||||||
|
|
||||||
int rows = m_RowHeights?.Length ?? 0;
|
|
||||||
int cols = m_ColWidths?.Length ?? 0;
|
|
||||||
|
|
||||||
if (rows == 0 || cols == 0)
|
|
||||||
{
|
{
|
||||||
// 空表格:创建空文件
|
ExcelWorksheet ws = package.Workbook.Worksheets[sheetName]
|
||||||
|
?? package.Workbook.Worksheets.Add(sheetName);
|
||||||
|
SaveToExcelFileCore(ws);
|
||||||
|
package.Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 创建新文件
|
||||||
|
using (var package = new ExcelPackage())
|
||||||
|
{
|
||||||
|
ExcelWorksheet ws = package.Workbook.Worksheets.Add(sheetName);
|
||||||
|
SaveToExcelFileCore(ws);
|
||||||
package.SaveAs(new FileInfo(filePath));
|
package.SaveAs(new FileInfo(filePath));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogError($"[XTable] SaveToExcelFile 失败: {ex.Message}\n{ex.StackTrace}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 设置列宽
|
/// <summary>
|
||||||
for (int c = 0; c < cols; c++)
|
/// 将表格数据导出到 Excel .xlsx 文件,按索引覆盖指定工作表。
|
||||||
ws.Column(c + 1).Width = m_ColWidths[c];
|
/// 文件不存在时自动创建。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath">Excel 文件路径</param>
|
||||||
|
/// <param name="sheetIndex">工作表索引(0-based)</param>
|
||||||
|
public void SaveToExcelFile(string filePath, int sheetIndex)
|
||||||
|
{
|
||||||
|
if (sheetIndex < 0)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[XTable] SaveToExcelFile: 工作表索引不能为负数");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!EnsureDirectory(filePath)) return;
|
||||||
|
|
||||||
// 设置行高
|
try
|
||||||
for (int r = 0; r < rows; r++)
|
{
|
||||||
|
if (File.Exists(filePath))
|
||||||
|
{
|
||||||
|
// 写入已有文件的指定 sheet
|
||||||
|
using (var package = new ExcelPackage(new FileInfo(filePath)))
|
||||||
{
|
{
|
||||||
float h = m_RowHeights[r];
|
int count = package.Workbook.Worksheets.Count;
|
||||||
if (h > 0)
|
ExcelWorksheet ws;
|
||||||
ws.Row(r + 1).Height = h;
|
if (sheetIndex < count)
|
||||||
}
|
|
||||||
|
|
||||||
// 写入冻结窗格
|
|
||||||
if (m_FreezeRowCount > 0 || m_FreezeColCount > 0)
|
|
||||||
{
|
|
||||||
int freezeRow = m_FreezeRowCount + 1; // 1-based: 冻结行数的下一行
|
|
||||||
int freezeCol = m_FreezeColCount + 1; // 1-based: 冻结列数的下一列
|
|
||||||
ws.View.FreezePanes = ExcelCellBase.GetAddress(freezeRow, freezeCol);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 写入单元格数据和样式
|
|
||||||
for (int r = 0; r < rows; r++)
|
|
||||||
{
|
|
||||||
for (int c = 0; c < cols; c++)
|
|
||||||
{
|
{
|
||||||
// 跳过被合并覆盖的单元格(稍后通过合并区域统一处理)
|
ws = package.Workbook.Worksheets[sheetIndex];
|
||||||
if (m_TableData != null && m_TableData.IsCellMerged(r, c))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var cell = m_TableData?.GetCell(r, c);
|
|
||||||
if (cell == null) continue;
|
|
||||||
|
|
||||||
var excelCell = ws.Cells[r + 1, c + 1];
|
|
||||||
|
|
||||||
// 写入文本
|
|
||||||
if (!string.IsNullOrEmpty(cell.Text))
|
|
||||||
excelCell.Value = cell.Text;
|
|
||||||
|
|
||||||
// 应用样式
|
|
||||||
string ns = !string.IsNullOrEmpty(cell.StyleNamespace)
|
|
||||||
? cell.StyleNamespace
|
|
||||||
: m_DefaultStyleNamespace;
|
|
||||||
if (!string.IsNullOrEmpty(ns))
|
|
||||||
ApplyStyleToExcelCell(excelCell, ns);
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 超出范围则追加,中间空缺补空白 sheet
|
||||||
|
while (package.Workbook.Worksheets.Count <= sheetIndex)
|
||||||
|
package.Workbook.Worksheets.Add("Sheet" + (package.Workbook.Worksheets.Count + 1));
|
||||||
|
ws = package.Workbook.Worksheets[sheetIndex];
|
||||||
|
}
|
||||||
|
SaveToExcelFileCore(ws);
|
||||||
|
package.Save();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// 写入合并单元格
|
else
|
||||||
if (m_TableData != null)
|
{
|
||||||
|
// 创建新文件
|
||||||
|
using (var package = new ExcelPackage())
|
||||||
{
|
{
|
||||||
WriteMergedCellsToExcel(ws, rows, cols);
|
// 补齐到目标索引
|
||||||
|
while (package.Workbook.Worksheets.Count <= sheetIndex)
|
||||||
|
package.Workbook.Worksheets.Add("Sheet" + (package.Workbook.Worksheets.Count + 1));
|
||||||
|
ExcelWorksheet ws = package.Workbook.Worksheets[sheetIndex];
|
||||||
|
SaveToExcelFileCore(ws);
|
||||||
|
package.SaveAs(new FileInfo(filePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 写入边框缓存
|
|
||||||
if (m_TableData != null)
|
|
||||||
{
|
|
||||||
WriteBorderCacheToExcel(ws, rows, cols);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 写入表格外边框
|
|
||||||
if (m_TableData != null && m_TableData.OuterBorderNs != null && m_TableData.OuterBorderNs.Count > 0)
|
|
||||||
{
|
|
||||||
var outerRange = ws.Cells[1, 1, rows, cols];
|
|
||||||
var border = outerRange.Style.Border;
|
|
||||||
border.BorderAround(ExcelBorderStyle.Thin,
|
|
||||||
System.Drawing.Color.FromArgb(180, 180, 180));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存
|
|
||||||
package.SaveAs(new FileInfo(filePath));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -285,18 +172,255 @@ namespace XericUI.XTable.Rendering.Component
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region 导入核心
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从已打开的工作表读取数据填入表格。
|
||||||
|
/// </summary>
|
||||||
|
private void LoadFromExcelFileCore(ExcelWorksheet ws, int rows, int cols)
|
||||||
|
{
|
||||||
|
// 设置行高
|
||||||
|
m_RowHeights = new float[rows];
|
||||||
|
for (int r = 0; r < rows; r++)
|
||||||
|
{
|
||||||
|
float h = (float)ws.Row(r + 1).Height;
|
||||||
|
m_RowHeights[r] = h > 0 ? h : Config.DefaultRowHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置列宽
|
||||||
|
m_ColWidths = new float[cols];
|
||||||
|
for (int c = 0; c < cols; c++)
|
||||||
|
{
|
||||||
|
float w = (float)ws.Column(c + 1).Width;
|
||||||
|
m_ColWidths[c] = w > 0 ? w : Config.DefaultColumnWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取单元格文本
|
||||||
|
for (int r = 0; r < rows; r++)
|
||||||
|
{
|
||||||
|
for (int c = 0; c < cols; c++)
|
||||||
|
{
|
||||||
|
object val = ws.Cells[r + 1, c + 1].Value;
|
||||||
|
if (val != null)
|
||||||
|
{
|
||||||
|
string text = val.ToString();
|
||||||
|
if (!string.IsNullOrEmpty(text))
|
||||||
|
{
|
||||||
|
var cell = m_TableData.GetOrCreateCell(r, c);
|
||||||
|
cell.Text = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取合并单元格
|
||||||
|
if (ws.MergedCells.Count > 0)
|
||||||
|
{
|
||||||
|
m_TableData.ClearAllMerges();
|
||||||
|
foreach (string mergeAddr in ws.MergedCells)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var addr = new ExcelAddressBase(mergeAddr);
|
||||||
|
int startR = addr.Start.Row - 1;
|
||||||
|
int startC = addr.Start.Column - 1;
|
||||||
|
int endR = addr.End.Row - 1;
|
||||||
|
int endC = addr.End.Column - 1;
|
||||||
|
int rowSpan = endR - startR + 1;
|
||||||
|
int colSpan = endC - startC + 1;
|
||||||
|
|
||||||
|
if ((rowSpan > 1 || colSpan > 1) && endR < rows && endC < cols)
|
||||||
|
m_TableData.MergeCells(startR, startC, rowSpan, colSpan);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 忽略无法解析的合并地址
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取冻结窗格
|
||||||
|
// FreezePanes 是 EPPlus 的写入方法,读取需通过工作表 XML 中的 d:pane 节点
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var nsMgr = new System.Xml.XmlNamespaceManager(ws.WorksheetXml.NameTable);
|
||||||
|
nsMgr.AddNamespace("d", "http://schemas.openxmlformats.org/spreadsheetml/2006/main");
|
||||||
|
var paneNode = ws.WorksheetXml.SelectSingleNode("//d:pane[@state='frozen']", nsMgr);
|
||||||
|
if (paneNode?.Attributes != null)
|
||||||
|
{
|
||||||
|
string xSplit = paneNode.Attributes["xSplit"]?.Value;
|
||||||
|
string ySplit = paneNode.Attributes["ySplit"]?.Value;
|
||||||
|
int freezeRows = int.TryParse(ySplit, out var fr) ? fr : 0;
|
||||||
|
int freezeCols = int.TryParse(xSplit, out var fc) ? fc : 0;
|
||||||
|
if (freezeRows > 0 || freezeCols > 0)
|
||||||
|
SetFreezeCount(freezeRows, freezeCols);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 忽略解析失败的冻结信息
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记脏并刷新
|
||||||
|
MarkDirty(TableDirtyType.DataChanged | TableDirtyType.LayoutChanged);
|
||||||
|
RecalculateTotalSize();
|
||||||
|
RefreshView();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 导出核心
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将表格数据写入已准备好的工作表。
|
||||||
|
/// </summary>
|
||||||
|
private void SaveToExcelFileCore(ExcelWorksheet ws)
|
||||||
|
{
|
||||||
|
int rows = m_RowHeights?.Length ?? 0;
|
||||||
|
int cols = m_ColWidths?.Length ?? 0;
|
||||||
|
|
||||||
|
if (rows == 0 || cols == 0) return;
|
||||||
|
|
||||||
|
// 设置列宽
|
||||||
|
for (int c = 0; c < cols; c++)
|
||||||
|
ws.Column(c + 1).Width = m_ColWidths[c];
|
||||||
|
|
||||||
|
// 设置行高
|
||||||
|
for (int r = 0; r < rows; r++)
|
||||||
|
{
|
||||||
|
float h = m_RowHeights[r];
|
||||||
|
if (h > 0) ws.Row(r + 1).Height = h;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入冻结窗格
|
||||||
|
if (m_FreezeRowCount > 0 || m_FreezeColCount > 0)
|
||||||
|
{
|
||||||
|
int freezeRow = m_FreezeRowCount + 1;
|
||||||
|
int freezeCol = m_FreezeColCount + 1;
|
||||||
|
ws.View.FreezePanes(freezeRow, freezeCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入单元格数据和样式
|
||||||
|
for (int r = 0; r < rows; r++)
|
||||||
|
{
|
||||||
|
for (int c = 0; c < cols; c++)
|
||||||
|
{
|
||||||
|
if (m_TableData != null && m_TableData.IsCellMerged(r, c))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var cell = m_TableData?.GetCell(r, c);
|
||||||
|
if (cell == null) continue;
|
||||||
|
|
||||||
|
var excelCell = ws.Cells[r + 1, c + 1];
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(cell.Text))
|
||||||
|
excelCell.Value = cell.Text;
|
||||||
|
|
||||||
|
string ns = !string.IsNullOrEmpty(cell.StyleNamespace)
|
||||||
|
? cell.StyleNamespace
|
||||||
|
: m_DefaultStyleNamespace;
|
||||||
|
if (!string.IsNullOrEmpty(ns))
|
||||||
|
ApplyStyleToExcelCell(excelCell, ns);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入合并单元格
|
||||||
|
if (m_TableData != null)
|
||||||
|
WriteMergedCellsToExcel(ws, rows, cols);
|
||||||
|
|
||||||
|
// 写入边框缓存
|
||||||
|
if (m_TableData != null)
|
||||||
|
WriteBorderCacheToExcel(ws, rows, cols);
|
||||||
|
|
||||||
|
// 写入表格外边框
|
||||||
|
if (m_TableData?.OuterBorderNs != null && m_TableData.OuterBorderNs.Count > 0)
|
||||||
|
{
|
||||||
|
var outerRange = ws.Cells[1, 1, rows, cols];
|
||||||
|
outerRange.Style.Border.BorderAround(ExcelBorderStyle.Thin,
|
||||||
|
System.Drawing.Color.FromArgb(180, 180, 180));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region 辅助方法
|
#region 辅助方法
|
||||||
|
|
||||||
|
/// <summary>打开 Excel 包,失败时 LogWarning 并返回 false。</summary>
|
||||||
|
private static bool TryOpenPackage(string filePath, out ExcelPackage package)
|
||||||
|
{
|
||||||
|
package = null;
|
||||||
|
if (string.IsNullOrEmpty(filePath))
|
||||||
|
{
|
||||||
|
Debug.LogWarning("[XTable] LoadFromExcelFile: 文件路径为空");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!File.Exists(filePath))
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[XTable] LoadFromExcelFile: 文件不存在: {filePath}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
package = new ExcelPackage(new FileInfo(filePath));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogError($"[XTable] 无法打开 Excel 文件: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>获取工作表的有效维度,空表返回 false。</summary>
|
||||||
|
private static bool TryGetWorksheetDimension(ExcelWorksheet ws, out int rows, out int cols)
|
||||||
|
{
|
||||||
|
rows = 0; cols = 0;
|
||||||
|
if (ws == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("[XTable] 工作表为空");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ws.Dimension != null)
|
||||||
|
{
|
||||||
|
rows = ws.Dimension.Rows;
|
||||||
|
cols = ws.Dimension.Columns;
|
||||||
|
}
|
||||||
|
if (rows <= 0 || cols <= 0)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("[XTable] Excel 文件为空或无法识别行列数");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>确保数据层存在。</summary>
|
||||||
|
private void EnsureTableData()
|
||||||
|
{
|
||||||
|
if (m_TableData == null)
|
||||||
|
m_TableData = new XTableData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>确保导出目录存在,路径为空时返回 false。</summary>
|
||||||
|
private static bool EnsureDirectory(string filePath)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(filePath))
|
||||||
|
{
|
||||||
|
Debug.LogWarning("[XTable] SaveToExcelFile: 文件路径为空");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
string dir = Path.GetDirectoryName(filePath);
|
||||||
|
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
|
||||||
|
Directory.CreateDirectory(dir);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 将 XTable 的 StyleNamespace 映射为 Excel 单元格样式(字体颜色、背景、字号、对齐)。
|
/// 将 XTable 的 StyleNamespace 映射为 Excel 单元格样式(字体颜色、背景、字号、对齐)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void ApplyStyleToExcelCell(ExcelRange excelCell, string ns)
|
private void ApplyStyleToExcelCell(ExcelRange excelCell, string ns)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(ns)) return;
|
// ns 已确保非空
|
||||||
|
|
||||||
var style = excelCell.Style;
|
var style = excelCell.Style;
|
||||||
|
|
||||||
// 字体颜色
|
|
||||||
Color fgColor = GetCellFgColor(ns);
|
Color fgColor = GetCellFgColor(ns);
|
||||||
style.Font.Color.SetColor(System.Drawing.Color.FromArgb(
|
style.Font.Color.SetColor(System.Drawing.Color.FromArgb(
|
||||||
(byte)(fgColor.a * 255),
|
(byte)(fgColor.a * 255),
|
||||||
@@ -304,7 +428,6 @@ namespace XericUI.XTable.Rendering.Component
|
|||||||
(byte)(fgColor.g * 255),
|
(byte)(fgColor.g * 255),
|
||||||
(byte)(fgColor.b * 255)));
|
(byte)(fgColor.b * 255)));
|
||||||
|
|
||||||
// 背景填充
|
|
||||||
Color bgColor = GetCellBgColor(ns);
|
Color bgColor = GetCellBgColor(ns);
|
||||||
style.Fill.PatternType = ExcelFillStyle.Solid;
|
style.Fill.PatternType = ExcelFillStyle.Solid;
|
||||||
style.Fill.BackgroundColor.SetColor(System.Drawing.Color.FromArgb(
|
style.Fill.BackgroundColor.SetColor(System.Drawing.Color.FromArgb(
|
||||||
@@ -313,12 +436,10 @@ namespace XericUI.XTable.Rendering.Component
|
|||||||
(byte)(bgColor.g * 255),
|
(byte)(bgColor.g * 255),
|
||||||
(byte)(bgColor.b * 255)));
|
(byte)(bgColor.b * 255)));
|
||||||
|
|
||||||
// 字体大小
|
|
||||||
int fontSize = GetCellFontSize(ns);
|
int fontSize = GetCellFontSize(ns);
|
||||||
if (fontSize > 0)
|
if (fontSize > 0)
|
||||||
style.Font.Size = fontSize;
|
style.Font.Size = fontSize;
|
||||||
|
|
||||||
// 对齐
|
|
||||||
style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
|
style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
|
||||||
style.VerticalAlignment = ExcelVerticalAlignment.Center;
|
style.VerticalAlignment = ExcelVerticalAlignment.Center;
|
||||||
}
|
}
|
||||||
@@ -337,18 +458,15 @@ namespace XericUI.XTable.Rendering.Component
|
|||||||
|
|
||||||
foreach (var desc in block.MergeDescriptors)
|
foreach (var desc in block.MergeDescriptors)
|
||||||
{
|
{
|
||||||
if (desc.IsCrossBlock) continue; // 跨块合并暂不处理
|
if (desc.IsCrossBlock) continue;
|
||||||
|
|
||||||
// 计算全局坐标
|
|
||||||
int blockRow = block.BlockRow;
|
int blockRow = block.BlockRow;
|
||||||
int blockCol = block.BlockCol;
|
int blockCol = block.BlockCol;
|
||||||
int globalStartRow = blockRow * m_TableData.BlockSizeY + desc.LocalStartRow;
|
int globalStartRow = blockRow * m_TableData.BlockSizeY + desc.LocalStartRow;
|
||||||
int globalStartCol = blockCol * m_TableData.BlockSizeX + desc.LocalStartCol;
|
int globalStartCol = blockCol * m_TableData.BlockSizeX + desc.LocalStartCol;
|
||||||
|
|
||||||
int endR = globalStartRow + desc.MergeRowSpan - 1;
|
int endR = globalStartRow + desc.MergeRowSpan - 1;
|
||||||
int endC = globalStartCol + desc.MergeColSpan - 1;
|
int endC = globalStartCol + desc.MergeColSpan - 1;
|
||||||
|
|
||||||
// 检查边界
|
|
||||||
if (endR >= rows || endC >= cols) continue;
|
if (endR >= rows || endC >= cols) continue;
|
||||||
|
|
||||||
var range = ws.Cells[globalStartRow + 1, globalStartCol + 1, endR + 1, endC + 1];
|
var range = ws.Cells[globalStartRow + 1, globalStartCol + 1, endR + 1, endC + 1];
|
||||||
@@ -368,12 +486,10 @@ namespace XericUI.XTable.Rendering.Component
|
|||||||
{
|
{
|
||||||
for (int c = 0; c < cols; c++)
|
for (int c = 0; c < cols; c++)
|
||||||
{
|
{
|
||||||
// 跳过被合并覆盖的单元格
|
|
||||||
if (m_TableData.IsCellMerged(r, c)) continue;
|
if (m_TableData.IsCellMerged(r, c)) continue;
|
||||||
|
|
||||||
var excelCell = ws.Cells[r + 1, c + 1];
|
var excelCell = ws.Cells[r + 1, c + 1];
|
||||||
|
|
||||||
// 右侧边框
|
|
||||||
string rightNs = m_TableData.GetBorderRightNs(r, c);
|
string rightNs = m_TableData.GetBorderRightNs(r, c);
|
||||||
if (!string.IsNullOrEmpty(rightNs))
|
if (!string.IsNullOrEmpty(rightNs))
|
||||||
{
|
{
|
||||||
@@ -382,13 +498,10 @@ namespace XericUI.XTable.Rendering.Component
|
|||||||
excelCell.Style.Border.Right.Style = BorderWidthToStyle(width);
|
excelCell.Style.Border.Right.Style = BorderWidthToStyle(width);
|
||||||
excelCell.Style.Border.Right.Color.SetColor(
|
excelCell.Style.Border.Right.Color.SetColor(
|
||||||
System.Drawing.Color.FromArgb(
|
System.Drawing.Color.FromArgb(
|
||||||
(byte)(color.a * 255),
|
(byte)(color.a * 255), (byte)(color.r * 255),
|
||||||
(byte)(color.r * 255),
|
(byte)(color.g * 255), (byte)(color.b * 255)));
|
||||||
(byte)(color.g * 255),
|
|
||||||
(byte)(color.b * 255)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 底部边框
|
|
||||||
string bottomNs = m_TableData.GetBorderBottomNs(r, c);
|
string bottomNs = m_TableData.GetBorderBottomNs(r, c);
|
||||||
if (!string.IsNullOrEmpty(bottomNs))
|
if (!string.IsNullOrEmpty(bottomNs))
|
||||||
{
|
{
|
||||||
@@ -397,10 +510,8 @@ namespace XericUI.XTable.Rendering.Component
|
|||||||
excelCell.Style.Border.Bottom.Style = BorderWidthToStyle(width);
|
excelCell.Style.Border.Bottom.Style = BorderWidthToStyle(width);
|
||||||
excelCell.Style.Border.Bottom.Color.SetColor(
|
excelCell.Style.Border.Bottom.Color.SetColor(
|
||||||
System.Drawing.Color.FromArgb(
|
System.Drawing.Color.FromArgb(
|
||||||
(byte)(color.a * 255),
|
(byte)(color.a * 255), (byte)(color.r * 255),
|
||||||
(byte)(color.r * 255),
|
(byte)(color.g * 255), (byte)(color.b * 255)));
|
||||||
(byte)(color.g * 255),
|
|
||||||
(byte)(color.b * 255)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -409,36 +520,28 @@ namespace XericUI.XTable.Rendering.Component
|
|||||||
for (int c = 0; c < cols; c++)
|
for (int c = 0; c < cols; c++)
|
||||||
{
|
{
|
||||||
string topNs = m_TableData.GetBorderBottomNs(-1, c);
|
string topNs = m_TableData.GetBorderBottomNs(-1, c);
|
||||||
if (!string.IsNullOrEmpty(topNs))
|
if (string.IsNullOrEmpty(topNs)) continue;
|
||||||
{
|
Color color = GetCellBorderColor(topNs);
|
||||||
Color color = GetCellBorderColor(topNs);
|
float width = GetCellBorderWidth(topNs);
|
||||||
float width = GetCellBorderWidth(topNs);
|
ws.Cells[1, c + 1].Style.Border.Top.Style = BorderWidthToStyle(width);
|
||||||
ws.Cells[1, c + 1].Style.Border.Top.Style = BorderWidthToStyle(width);
|
ws.Cells[1, c + 1].Style.Border.Top.Color.SetColor(
|
||||||
ws.Cells[1, c + 1].Style.Border.Top.Color.SetColor(
|
System.Drawing.Color.FromArgb(
|
||||||
System.Drawing.Color.FromArgb(
|
(byte)(color.a * 255), (byte)(color.r * 255),
|
||||||
(byte)(color.a * 255),
|
(byte)(color.g * 255), (byte)(color.b * 255)));
|
||||||
(byte)(color.r * 255),
|
|
||||||
(byte)(color.g * 255),
|
|
||||||
(byte)(color.b * 255)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 左侧外框(第一列左侧)
|
// 左侧外框(第一列左侧)
|
||||||
for (int r = 0; r < rows; r++)
|
for (int r = 0; r < rows; r++)
|
||||||
{
|
{
|
||||||
string leftNs = m_TableData.GetBorderRightNs(r, -1);
|
string leftNs = m_TableData.GetBorderRightNs(r, -1);
|
||||||
if (!string.IsNullOrEmpty(leftNs))
|
if (string.IsNullOrEmpty(leftNs)) continue;
|
||||||
{
|
Color color = GetCellBorderColor(leftNs);
|
||||||
Color color = GetCellBorderColor(leftNs);
|
float width = GetCellBorderWidth(leftNs);
|
||||||
float width = GetCellBorderWidth(leftNs);
|
ws.Cells[r + 1, 1].Style.Border.Left.Style = BorderWidthToStyle(width);
|
||||||
ws.Cells[r + 1, 1].Style.Border.Left.Style = BorderWidthToStyle(width);
|
ws.Cells[r + 1, 1].Style.Border.Left.Color.SetColor(
|
||||||
ws.Cells[r + 1, 1].Style.Border.Left.Color.SetColor(
|
System.Drawing.Color.FromArgb(
|
||||||
System.Drawing.Color.FromArgb(
|
(byte)(color.a * 255), (byte)(color.r * 255),
|
||||||
(byte)(color.a * 255),
|
(byte)(color.g * 255), (byte)(color.b * 255)));
|
||||||
(byte)(color.r * 255),
|
|
||||||
(byte)(color.g * 255),
|
|
||||||
(byte)(color.b * 255)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 20fe94b5450de624a9198b851207193a
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Reference in New Issue
Block a user