报告XP.ReportEngine模块:根据项目设计搭建数据处理层,数据绑定器实现,PDF渲染和生成功能开发实现。
This commit is contained in:
@@ -1,19 +1,71 @@
|
||||
using Prism.Ioc;
|
||||
using Prism.Modularity;
|
||||
using XP.ReportEngine.Views;
|
||||
using System.Resources;
|
||||
using XP.Common.Localization;
|
||||
using XP.Common.Localization.Interfaces;
|
||||
using XP.ReportEngine.Interfaces;
|
||||
using XP.ReportEngine.Services;
|
||||
|
||||
namespace XP.ReportEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// 报告引擎模块 | Report Engine Module
|
||||
/// Prism 模块入口,注册报告生成相关服务到 DI 容器
|
||||
/// Prism module entry, registers report generation services to DI container
|
||||
/// </summary>
|
||||
[Module(ModuleName = "ReportEngineModule")]
|
||||
public class ReportEngineModule : IModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 模块初始化 | Module initialization
|
||||
/// 注册模块级多语言资源源 | Register module-level localization resource source
|
||||
/// </summary>
|
||||
public void OnInitialized(IContainerProvider containerProvider)
|
||||
{
|
||||
// 注册模块级多语言资源到 Fallback Chain | Register module-level localization resources to Fallback Chain
|
||||
var localizationService = containerProvider.Resolve<ILocalizationService>();
|
||||
var resourceManager = new ResourceManager(
|
||||
"XP.ReportEngine.Resources.Resources",
|
||||
typeof(ReportEngineModule).Assembly);
|
||||
localizationService.RegisterResourceSource("XP.ReportEngine", resourceManager);
|
||||
|
||||
// 初始化 LocalizationHelper,使其通过 ILocalizationService 获取字符串(支持 Fallback Chain)
|
||||
// Initialize LocalizationHelper to use ILocalizationService for string lookup (supports Fallback Chain)
|
||||
LocalizationHelper.Initialize(localizationService);
|
||||
|
||||
System.Console.WriteLine("[ReportEngineModule] 模块已初始化 | Module initialized");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册类型到 DI 容器 | Register types to DI container
|
||||
/// </summary>
|
||||
public void RegisterTypes(IContainerRegistry containerRegistry)
|
||||
{
|
||||
// 注册报告生成器(瞬态)| Register report generator (transient)
|
||||
containerRegistry.Register<IReportGenerator, PdfReportGenerator>();
|
||||
|
||||
// 注册报告生成器工厂(单例)| Register report generator factory (singleton)
|
||||
containerRegistry.RegisterSingleton<IReportGeneratorFactory, ReportGeneratorFactory>();
|
||||
|
||||
// 注册模板引擎(瞬态)| Register template engine (transient)
|
||||
containerRegistry.Register<ITemplateEngine, JsonTemplateEngine>();
|
||||
|
||||
// 注册数据绑定器(瞬态)| Register data binder (transient)
|
||||
containerRegistry.Register<IDataBinder, ExpressionDataBinder>();
|
||||
|
||||
// 注册排版引擎(瞬态)| Register layout engine (transient)
|
||||
containerRegistry.Register<ILayoutEngine, PageLayoutEngine>();
|
||||
|
||||
// 注册 PDF 渲染器(瞬态)| Register PDF renderer (transient)
|
||||
containerRegistry.Register<IPdfRenderer, ITextPdfRenderer>();
|
||||
|
||||
// 注册数据适配器(瞬态)| Register data adapter (transient)
|
||||
containerRegistry.Register<IReportDataAdapter, ProcessorDataAdapter>();
|
||||
|
||||
// 注册报告编号生成器(单例,维护每日计数器状态)| Register report ID generator (singleton, maintains daily counter state)
|
||||
containerRegistry.RegisterSingleton<ReportIdGenerator>();
|
||||
|
||||
System.Console.WriteLine("[ReportEngineModule] 类型注册完成 | Type registration completed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,4 +58,184 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<!-- Report metadata keys -->
|
||||
<data name="Report_Title" xml:space="preserve">
|
||||
<value>Inspection Report</value>
|
||||
</data>
|
||||
<data name="Report_Date" xml:space="preserve">
|
||||
<value>Inspection Date</value>
|
||||
</data>
|
||||
<data name="Report_Sample" xml:space="preserve">
|
||||
<value>Sample Name</value>
|
||||
</data>
|
||||
<data name="Report_Operator" xml:space="preserve">
|
||||
<value>Operator</value>
|
||||
</data>
|
||||
<data name="Report_Summary" xml:space="preserve">
|
||||
<value>Inspection Summary</value>
|
||||
</data>
|
||||
<data name="Report_Id" xml:space="preserve">
|
||||
<value>Report ID</value>
|
||||
</data>
|
||||
<data name="Report_Description" xml:space="preserve">
|
||||
<value>Description</value>
|
||||
</data>
|
||||
<!-- Classification keys -->
|
||||
<data name="Classification_Pass" xml:space="preserve">
|
||||
<value>PASS</value>
|
||||
</data>
|
||||
<data name="Classification_Fail" xml:space="preserve">
|
||||
<value>FAIL</value>
|
||||
</data>
|
||||
<!-- Inspection type keys -->
|
||||
<data name="Inspection_LineMeasurement" xml:space="preserve">
|
||||
<value>Line Measurement</value>
|
||||
</data>
|
||||
<data name="Inspection_BgaVoidRate" xml:space="preserve">
|
||||
<value>BGA Void Rate</value>
|
||||
</data>
|
||||
<data name="Inspection_VoidMeasurement" xml:space="preserve">
|
||||
<value>Void Measurement</value>
|
||||
</data>
|
||||
<data name="Inspection_FillRate" xml:space="preserve">
|
||||
<value>Via Fill Rate</value>
|
||||
</data>
|
||||
<!-- Table header keys -->
|
||||
<data name="Table_Index" xml:space="preserve">
|
||||
<value>No.</value>
|
||||
</data>
|
||||
<data name="Table_VoidRate" xml:space="preserve">
|
||||
<value>Void Rate</value>
|
||||
</data>
|
||||
<data name="Table_Classification" xml:space="preserve">
|
||||
<value>Classification</value>
|
||||
</data>
|
||||
<data name="Table_Area" xml:space="preserve">
|
||||
<value>Area</value>
|
||||
</data>
|
||||
<data name="Table_AreaPercent" xml:space="preserve">
|
||||
<value>Area %</value>
|
||||
</data>
|
||||
<data name="Table_CenterX" xml:space="preserve">
|
||||
<value>Center X</value>
|
||||
</data>
|
||||
<data name="Table_CenterY" xml:space="preserve">
|
||||
<value>Center Y</value>
|
||||
</data>
|
||||
<!-- Measurement keys -->
|
||||
<data name="Measurement_Type" xml:space="preserve">
|
||||
<value>Measurement Type</value>
|
||||
</data>
|
||||
<data name="Measurement_Distance" xml:space="preserve">
|
||||
<value>Distance</value>
|
||||
</data>
|
||||
<data name="Measurement_Unit" xml:space="preserve">
|
||||
<value>Unit</value>
|
||||
</data>
|
||||
<data name="Measurement_Angle" xml:space="preserve">
|
||||
<value>Angle</value>
|
||||
</data>
|
||||
<data name="Measurement_FillRate" xml:space="preserve">
|
||||
<value>Fill Rate</value>
|
||||
</data>
|
||||
<data name="Measurement_VoidRate" xml:space="preserve">
|
||||
<value>Void Rate</value>
|
||||
</data>
|
||||
<data name="Measurement_Limit" xml:space="preserve">
|
||||
<value>Limit</value>
|
||||
</data>
|
||||
<!-- Summary keys -->
|
||||
<data name="Summary_TotalDefects" xml:space="preserve">
|
||||
<value>Total Defects</value>
|
||||
</data>
|
||||
<data name="Summary_PassCount" xml:space="preserve">
|
||||
<value>Pass Count</value>
|
||||
</data>
|
||||
<data name="Summary_FailCount" xml:space="preserve">
|
||||
<value>Fail Count</value>
|
||||
</data>
|
||||
<data name="Summary_OverallResult" xml:space="preserve">
|
||||
<value>Overall Result</value>
|
||||
</data>
|
||||
<!-- Image placeholder key -->
|
||||
<data name="Image_NoImage" xml:space="preserve">
|
||||
<value>No Image</value>
|
||||
</data>
|
||||
<!-- Page title keys -->
|
||||
<data name="Page_Homepage" xml:space="preserve">
|
||||
<value>Inspection Report</value>
|
||||
</data>
|
||||
<data name="Page_MetricData" xml:space="preserve">
|
||||
<value>Measurement Data</value>
|
||||
</data>
|
||||
<data name="Page_BgaInspection" xml:space="preserve">
|
||||
<value>BGA Solder Ball Inspection</value>
|
||||
</data>
|
||||
<data name="Page_VoidInspection" xml:space="preserve">
|
||||
<value>Void Inspection</value>
|
||||
</data>
|
||||
<data name="Page_ViaFillInspection" xml:space="preserve">
|
||||
<value>Via Fill Rate Inspection</value>
|
||||
</data>
|
||||
<!-- BGA inspection keys -->
|
||||
<data name="Bga_Count" xml:space="preserve">
|
||||
<value>Ball Count</value>
|
||||
</data>
|
||||
<data name="Bga_TotalArea" xml:space="preserve">
|
||||
<value>Total Ball Area</value>
|
||||
</data>
|
||||
<data name="Bga_TotalVoidArea" xml:space="preserve">
|
||||
<value>Total Void Area</value>
|
||||
</data>
|
||||
<data name="Bga_VoidLimit" xml:space="preserve">
|
||||
<value>Void Rate Limit</value>
|
||||
</data>
|
||||
<data name="Bga_BallIndex" xml:space="preserve">
|
||||
<value>Ball No.</value>
|
||||
</data>
|
||||
<!-- Void inspection keys -->
|
||||
<data name="Void_RoiArea" xml:space="preserve">
|
||||
<value>ROI Area</value>
|
||||
</data>
|
||||
<data name="Void_TotalArea" xml:space="preserve">
|
||||
<value>Total Void Area</value>
|
||||
</data>
|
||||
<data name="Void_Count" xml:space="preserve">
|
||||
<value>Void Count</value>
|
||||
</data>
|
||||
<data name="Void_MaxArea" xml:space="preserve">
|
||||
<value>Max Void Area</value>
|
||||
</data>
|
||||
<data name="Void_Limit" xml:space="preserve">
|
||||
<value>Void Rate Limit</value>
|
||||
</data>
|
||||
<!-- Via fill rate keys -->
|
||||
<data name="Fill_Rate" xml:space="preserve">
|
||||
<value>Fill Rate</value>
|
||||
</data>
|
||||
<data name="Fill_FullDistance" xml:space="preserve">
|
||||
<value>Full Distance</value>
|
||||
</data>
|
||||
<data name="Fill_FillDistance" xml:space="preserve">
|
||||
<value>Fill Distance</value>
|
||||
</data>
|
||||
<data name="Fill_THTLimit" xml:space="preserve">
|
||||
<value>THT Limit</value>
|
||||
</data>
|
||||
<!-- Common field keys -->
|
||||
<data name="Field_Point1" xml:space="preserve">
|
||||
<value>Point 1</value>
|
||||
</data>
|
||||
<data name="Field_Point2" xml:space="preserve">
|
||||
<value>Point 2</value>
|
||||
</data>
|
||||
<data name="Field_Result" xml:space="preserve">
|
||||
<value>Result</value>
|
||||
</data>
|
||||
<data name="Field_InspectionType" xml:space="preserve">
|
||||
<value>Inspection Type</value>
|
||||
</data>
|
||||
<data name="Field_Status" xml:space="preserve">
|
||||
<value>Status</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -1,64 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
@@ -117,4 +58,184 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<!-- 报告元数据键 | Report metadata keys -->
|
||||
<data name="Report_Title" xml:space="preserve">
|
||||
<value>检测报告</value>
|
||||
</data>
|
||||
<data name="Report_Date" xml:space="preserve">
|
||||
<value>检测日期</value>
|
||||
</data>
|
||||
<data name="Report_Sample" xml:space="preserve">
|
||||
<value>样品名称</value>
|
||||
</data>
|
||||
<data name="Report_Operator" xml:space="preserve">
|
||||
<value>操作员</value>
|
||||
</data>
|
||||
<data name="Report_Summary" xml:space="preserve">
|
||||
<value>检测摘要</value>
|
||||
</data>
|
||||
<data name="Report_Id" xml:space="preserve">
|
||||
<value>报告编号</value>
|
||||
</data>
|
||||
<data name="Report_Description" xml:space="preserve">
|
||||
<value>描述</value>
|
||||
</data>
|
||||
<!-- 分类结果键 | Classification keys -->
|
||||
<data name="Classification_Pass" xml:space="preserve">
|
||||
<value>通过</value>
|
||||
</data>
|
||||
<data name="Classification_Fail" xml:space="preserve">
|
||||
<value>不通过</value>
|
||||
</data>
|
||||
<!-- 检测类型键 | Inspection type keys -->
|
||||
<data name="Inspection_LineMeasurement" xml:space="preserve">
|
||||
<value>距离测量</value>
|
||||
</data>
|
||||
<data name="Inspection_BgaVoidRate" xml:space="preserve">
|
||||
<value>BGA 气泡率</value>
|
||||
</data>
|
||||
<data name="Inspection_VoidMeasurement" xml:space="preserve">
|
||||
<value>空隙测量</value>
|
||||
</data>
|
||||
<data name="Inspection_FillRate" xml:space="preserve">
|
||||
<value>通孔填锡率</value>
|
||||
</data>
|
||||
<!-- 表格列头键 | Table header keys -->
|
||||
<data name="Table_Index" xml:space="preserve">
|
||||
<value>序号</value>
|
||||
</data>
|
||||
<data name="Table_VoidRate" xml:space="preserve">
|
||||
<value>气泡率</value>
|
||||
</data>
|
||||
<data name="Table_Classification" xml:space="preserve">
|
||||
<value>分类结果</value>
|
||||
</data>
|
||||
<data name="Table_Area" xml:space="preserve">
|
||||
<value>面积</value>
|
||||
</data>
|
||||
<data name="Table_AreaPercent" xml:space="preserve">
|
||||
<value>面积百分比</value>
|
||||
</data>
|
||||
<data name="Table_CenterX" xml:space="preserve">
|
||||
<value>中心 X</value>
|
||||
</data>
|
||||
<data name="Table_CenterY" xml:space="preserve">
|
||||
<value>中心 Y</value>
|
||||
</data>
|
||||
<!-- 测量键 | Measurement keys -->
|
||||
<data name="Measurement_Type" xml:space="preserve">
|
||||
<value>测量类型</value>
|
||||
</data>
|
||||
<data name="Measurement_Distance" xml:space="preserve">
|
||||
<value>距离</value>
|
||||
</data>
|
||||
<data name="Measurement_Unit" xml:space="preserve">
|
||||
<value>单位</value>
|
||||
</data>
|
||||
<data name="Measurement_Angle" xml:space="preserve">
|
||||
<value>角度</value>
|
||||
</data>
|
||||
<data name="Measurement_FillRate" xml:space="preserve">
|
||||
<value>填锡率</value>
|
||||
</data>
|
||||
<data name="Measurement_VoidRate" xml:space="preserve">
|
||||
<value>气泡率</value>
|
||||
</data>
|
||||
<data name="Measurement_Limit" xml:space="preserve">
|
||||
<value>限值</value>
|
||||
</data>
|
||||
<!-- 摘要键 | Summary keys -->
|
||||
<data name="Summary_TotalDefects" xml:space="preserve">
|
||||
<value>总缺陷数</value>
|
||||
</data>
|
||||
<data name="Summary_PassCount" xml:space="preserve">
|
||||
<value>通过数量</value>
|
||||
</data>
|
||||
<data name="Summary_FailCount" xml:space="preserve">
|
||||
<value>不通过数量</value>
|
||||
</data>
|
||||
<data name="Summary_OverallResult" xml:space="preserve">
|
||||
<value>总体结果</value>
|
||||
</data>
|
||||
<!-- 图像占位符键 | Image placeholder key -->
|
||||
<data name="Image_NoImage" xml:space="preserve">
|
||||
<value>无图像</value>
|
||||
</data>
|
||||
<!-- 页面标题键 | Page title keys -->
|
||||
<data name="Page_Homepage" xml:space="preserve">
|
||||
<value>检测报告首页</value>
|
||||
</data>
|
||||
<data name="Page_MetricData" xml:space="preserve">
|
||||
<value>测量数据</value>
|
||||
</data>
|
||||
<data name="Page_BgaInspection" xml:space="preserve">
|
||||
<value>BGA 焊球检测</value>
|
||||
</data>
|
||||
<data name="Page_VoidInspection" xml:space="preserve">
|
||||
<value>空隙检测</value>
|
||||
</data>
|
||||
<data name="Page_ViaFillInspection" xml:space="preserve">
|
||||
<value>通孔填锡检测</value>
|
||||
</data>
|
||||
<!-- BGA 检测键 | BGA inspection keys -->
|
||||
<data name="Bga_Count" xml:space="preserve">
|
||||
<value>焊球数量</value>
|
||||
</data>
|
||||
<data name="Bga_TotalArea" xml:space="preserve">
|
||||
<value>焊球总面积</value>
|
||||
</data>
|
||||
<data name="Bga_TotalVoidArea" xml:space="preserve">
|
||||
<value>气泡总面积</value>
|
||||
</data>
|
||||
<data name="Bga_VoidLimit" xml:space="preserve">
|
||||
<value>气泡率限值</value>
|
||||
</data>
|
||||
<data name="Bga_BallIndex" xml:space="preserve">
|
||||
<value>焊球序号</value>
|
||||
</data>
|
||||
<!-- 空隙检测键 | Void inspection keys -->
|
||||
<data name="Void_RoiArea" xml:space="preserve">
|
||||
<value>ROI 面积</value>
|
||||
</data>
|
||||
<data name="Void_TotalArea" xml:space="preserve">
|
||||
<value>空隙总面积</value>
|
||||
</data>
|
||||
<data name="Void_Count" xml:space="preserve">
|
||||
<value>空隙数量</value>
|
||||
</data>
|
||||
<data name="Void_MaxArea" xml:space="preserve">
|
||||
<value>最大空隙面积</value>
|
||||
</data>
|
||||
<data name="Void_Limit" xml:space="preserve">
|
||||
<value>空隙率限值</value>
|
||||
</data>
|
||||
<!-- 通孔填锡键 | Via fill rate keys -->
|
||||
<data name="Fill_Rate" xml:space="preserve">
|
||||
<value>填锡率</value>
|
||||
</data>
|
||||
<data name="Fill_FullDistance" xml:space="preserve">
|
||||
<value>满填距离</value>
|
||||
</data>
|
||||
<data name="Fill_FillDistance" xml:space="preserve">
|
||||
<value>填充距离</value>
|
||||
</data>
|
||||
<data name="Fill_THTLimit" xml:space="preserve">
|
||||
<value>THT 限值</value>
|
||||
</data>
|
||||
<!-- 通用字段键 | Common field keys -->
|
||||
<data name="Field_Point1" xml:space="preserve">
|
||||
<value>起点</value>
|
||||
</data>
|
||||
<data name="Field_Point2" xml:space="preserve">
|
||||
<value>终点</value>
|
||||
</data>
|
||||
<data name="Field_Result" xml:space="preserve">
|
||||
<value>结果</value>
|
||||
</data>
|
||||
<data name="Field_InspectionType" xml:space="preserve">
|
||||
<value>检测类型</value>
|
||||
</data>
|
||||
<data name="Field_Status" xml:space="preserve">
|
||||
<value>状态</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -58,4 +58,184 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<!-- 报告元数据键 | Report metadata keys -->
|
||||
<data name="Report_Title" xml:space="preserve">
|
||||
<value>检测报告</value>
|
||||
</data>
|
||||
<data name="Report_Date" xml:space="preserve">
|
||||
<value>检测日期</value>
|
||||
</data>
|
||||
<data name="Report_Sample" xml:space="preserve">
|
||||
<value>样品名称</value>
|
||||
</data>
|
||||
<data name="Report_Operator" xml:space="preserve">
|
||||
<value>操作员</value>
|
||||
</data>
|
||||
<data name="Report_Summary" xml:space="preserve">
|
||||
<value>检测摘要</value>
|
||||
</data>
|
||||
<data name="Report_Id" xml:space="preserve">
|
||||
<value>报告编号</value>
|
||||
</data>
|
||||
<data name="Report_Description" xml:space="preserve">
|
||||
<value>描述</value>
|
||||
</data>
|
||||
<!-- 分类结果键 | Classification keys -->
|
||||
<data name="Classification_Pass" xml:space="preserve">
|
||||
<value>通过</value>
|
||||
</data>
|
||||
<data name="Classification_Fail" xml:space="preserve">
|
||||
<value>不通过</value>
|
||||
</data>
|
||||
<!-- 检测类型键 | Inspection type keys -->
|
||||
<data name="Inspection_LineMeasurement" xml:space="preserve">
|
||||
<value>距离测量</value>
|
||||
</data>
|
||||
<data name="Inspection_BgaVoidRate" xml:space="preserve">
|
||||
<value>BGA 气泡率</value>
|
||||
</data>
|
||||
<data name="Inspection_VoidMeasurement" xml:space="preserve">
|
||||
<value>空隙测量</value>
|
||||
</data>
|
||||
<data name="Inspection_FillRate" xml:space="preserve">
|
||||
<value>通孔填锡率</value>
|
||||
</data>
|
||||
<!-- 表格列头键 | Table header keys -->
|
||||
<data name="Table_Index" xml:space="preserve">
|
||||
<value>序号</value>
|
||||
</data>
|
||||
<data name="Table_VoidRate" xml:space="preserve">
|
||||
<value>气泡率</value>
|
||||
</data>
|
||||
<data name="Table_Classification" xml:space="preserve">
|
||||
<value>分类结果</value>
|
||||
</data>
|
||||
<data name="Table_Area" xml:space="preserve">
|
||||
<value>面积</value>
|
||||
</data>
|
||||
<data name="Table_AreaPercent" xml:space="preserve">
|
||||
<value>面积百分比</value>
|
||||
</data>
|
||||
<data name="Table_CenterX" xml:space="preserve">
|
||||
<value>中心 X</value>
|
||||
</data>
|
||||
<data name="Table_CenterY" xml:space="preserve">
|
||||
<value>中心 Y</value>
|
||||
</data>
|
||||
<!-- 测量键 | Measurement keys -->
|
||||
<data name="Measurement_Type" xml:space="preserve">
|
||||
<value>测量类型</value>
|
||||
</data>
|
||||
<data name="Measurement_Distance" xml:space="preserve">
|
||||
<value>距离</value>
|
||||
</data>
|
||||
<data name="Measurement_Unit" xml:space="preserve">
|
||||
<value>单位</value>
|
||||
</data>
|
||||
<data name="Measurement_Angle" xml:space="preserve">
|
||||
<value>角度</value>
|
||||
</data>
|
||||
<data name="Measurement_FillRate" xml:space="preserve">
|
||||
<value>填锡率</value>
|
||||
</data>
|
||||
<data name="Measurement_VoidRate" xml:space="preserve">
|
||||
<value>气泡率</value>
|
||||
</data>
|
||||
<data name="Measurement_Limit" xml:space="preserve">
|
||||
<value>限值</value>
|
||||
</data>
|
||||
<!-- 摘要键 | Summary keys -->
|
||||
<data name="Summary_TotalDefects" xml:space="preserve">
|
||||
<value>总缺陷数</value>
|
||||
</data>
|
||||
<data name="Summary_PassCount" xml:space="preserve">
|
||||
<value>通过数量</value>
|
||||
</data>
|
||||
<data name="Summary_FailCount" xml:space="preserve">
|
||||
<value>不通过数量</value>
|
||||
</data>
|
||||
<data name="Summary_OverallResult" xml:space="preserve">
|
||||
<value>总体结果</value>
|
||||
</data>
|
||||
<!-- 图像占位符键 | Image placeholder key -->
|
||||
<data name="Image_NoImage" xml:space="preserve">
|
||||
<value>无图像</value>
|
||||
</data>
|
||||
<!-- 页面标题键 | Page title keys -->
|
||||
<data name="Page_Homepage" xml:space="preserve">
|
||||
<value>检测报告首页</value>
|
||||
</data>
|
||||
<data name="Page_MetricData" xml:space="preserve">
|
||||
<value>测量数据</value>
|
||||
</data>
|
||||
<data name="Page_BgaInspection" xml:space="preserve">
|
||||
<value>BGA 焊球检测</value>
|
||||
</data>
|
||||
<data name="Page_VoidInspection" xml:space="preserve">
|
||||
<value>空隙检测</value>
|
||||
</data>
|
||||
<data name="Page_ViaFillInspection" xml:space="preserve">
|
||||
<value>通孔填锡检测</value>
|
||||
</data>
|
||||
<!-- BGA 检测键 | BGA inspection keys -->
|
||||
<data name="Bga_Count" xml:space="preserve">
|
||||
<value>焊球数量</value>
|
||||
</data>
|
||||
<data name="Bga_TotalArea" xml:space="preserve">
|
||||
<value>焊球总面积</value>
|
||||
</data>
|
||||
<data name="Bga_TotalVoidArea" xml:space="preserve">
|
||||
<value>气泡总面积</value>
|
||||
</data>
|
||||
<data name="Bga_VoidLimit" xml:space="preserve">
|
||||
<value>气泡率限值</value>
|
||||
</data>
|
||||
<data name="Bga_BallIndex" xml:space="preserve">
|
||||
<value>焊球序号</value>
|
||||
</data>
|
||||
<!-- 空隙检测键 | Void inspection keys -->
|
||||
<data name="Void_RoiArea" xml:space="preserve">
|
||||
<value>ROI 面积</value>
|
||||
</data>
|
||||
<data name="Void_TotalArea" xml:space="preserve">
|
||||
<value>空隙总面积</value>
|
||||
</data>
|
||||
<data name="Void_Count" xml:space="preserve">
|
||||
<value>空隙数量</value>
|
||||
</data>
|
||||
<data name="Void_MaxArea" xml:space="preserve">
|
||||
<value>最大空隙面积</value>
|
||||
</data>
|
||||
<data name="Void_Limit" xml:space="preserve">
|
||||
<value>空隙率限值</value>
|
||||
</data>
|
||||
<!-- 通孔填锡键 | Via fill rate keys -->
|
||||
<data name="Fill_Rate" xml:space="preserve">
|
||||
<value>填锡率</value>
|
||||
</data>
|
||||
<data name="Fill_FullDistance" xml:space="preserve">
|
||||
<value>满填距离</value>
|
||||
</data>
|
||||
<data name="Fill_FillDistance" xml:space="preserve">
|
||||
<value>填充距离</value>
|
||||
</data>
|
||||
<data name="Fill_THTLimit" xml:space="preserve">
|
||||
<value>THT 限值</value>
|
||||
</data>
|
||||
<!-- 通用字段键 | Common field keys -->
|
||||
<data name="Field_Point1" xml:space="preserve">
|
||||
<value>起点</value>
|
||||
</data>
|
||||
<data name="Field_Point2" xml:space="preserve">
|
||||
<value>终点</value>
|
||||
</data>
|
||||
<data name="Field_Result" xml:space="preserve">
|
||||
<value>结果</value>
|
||||
</data>
|
||||
<data name="Field_InspectionType" xml:space="preserve">
|
||||
<value>检测类型</value>
|
||||
</data>
|
||||
<data name="Field_Status" xml:space="preserve">
|
||||
<value>状态</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -58,4 +58,184 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<!-- 報告元數據鍵 | Report metadata keys -->
|
||||
<data name="Report_Title" xml:space="preserve">
|
||||
<value>檢測報告</value>
|
||||
</data>
|
||||
<data name="Report_Date" xml:space="preserve">
|
||||
<value>檢測日期</value>
|
||||
</data>
|
||||
<data name="Report_Sample" xml:space="preserve">
|
||||
<value>樣品名稱</value>
|
||||
</data>
|
||||
<data name="Report_Operator" xml:space="preserve">
|
||||
<value>操作員</value>
|
||||
</data>
|
||||
<data name="Report_Summary" xml:space="preserve">
|
||||
<value>檢測摘要</value>
|
||||
</data>
|
||||
<data name="Report_Id" xml:space="preserve">
|
||||
<value>報告編號</value>
|
||||
</data>
|
||||
<data name="Report_Description" xml:space="preserve">
|
||||
<value>描述</value>
|
||||
</data>
|
||||
<!-- 分類結果鍵 | Classification keys -->
|
||||
<data name="Classification_Pass" xml:space="preserve">
|
||||
<value>通過</value>
|
||||
</data>
|
||||
<data name="Classification_Fail" xml:space="preserve">
|
||||
<value>不通過</value>
|
||||
</data>
|
||||
<!-- 檢測類型鍵 | Inspection type keys -->
|
||||
<data name="Inspection_LineMeasurement" xml:space="preserve">
|
||||
<value>距離測量</value>
|
||||
</data>
|
||||
<data name="Inspection_BgaVoidRate" xml:space="preserve">
|
||||
<value>BGA 氣泡率</value>
|
||||
</data>
|
||||
<data name="Inspection_VoidMeasurement" xml:space="preserve">
|
||||
<value>空隙測量</value>
|
||||
</data>
|
||||
<data name="Inspection_FillRate" xml:space="preserve">
|
||||
<value>通孔填錫率</value>
|
||||
</data>
|
||||
<!-- 表格列頭鍵 | Table header keys -->
|
||||
<data name="Table_Index" xml:space="preserve">
|
||||
<value>序號</value>
|
||||
</data>
|
||||
<data name="Table_VoidRate" xml:space="preserve">
|
||||
<value>氣泡率</value>
|
||||
</data>
|
||||
<data name="Table_Classification" xml:space="preserve">
|
||||
<value>分類結果</value>
|
||||
</data>
|
||||
<data name="Table_Area" xml:space="preserve">
|
||||
<value>面積</value>
|
||||
</data>
|
||||
<data name="Table_AreaPercent" xml:space="preserve">
|
||||
<value>面積百分比</value>
|
||||
</data>
|
||||
<data name="Table_CenterX" xml:space="preserve">
|
||||
<value>中心 X</value>
|
||||
</data>
|
||||
<data name="Table_CenterY" xml:space="preserve">
|
||||
<value>中心 Y</value>
|
||||
</data>
|
||||
<!-- 測量鍵 | Measurement keys -->
|
||||
<data name="Measurement_Type" xml:space="preserve">
|
||||
<value>測量類型</value>
|
||||
</data>
|
||||
<data name="Measurement_Distance" xml:space="preserve">
|
||||
<value>距離</value>
|
||||
</data>
|
||||
<data name="Measurement_Unit" xml:space="preserve">
|
||||
<value>單位</value>
|
||||
</data>
|
||||
<data name="Measurement_Angle" xml:space="preserve">
|
||||
<value>角度</value>
|
||||
</data>
|
||||
<data name="Measurement_FillRate" xml:space="preserve">
|
||||
<value>填錫率</value>
|
||||
</data>
|
||||
<data name="Measurement_VoidRate" xml:space="preserve">
|
||||
<value>氣泡率</value>
|
||||
</data>
|
||||
<data name="Measurement_Limit" xml:space="preserve">
|
||||
<value>限值</value>
|
||||
</data>
|
||||
<!-- 摘要鍵 | Summary keys -->
|
||||
<data name="Summary_TotalDefects" xml:space="preserve">
|
||||
<value>總缺陷數</value>
|
||||
</data>
|
||||
<data name="Summary_PassCount" xml:space="preserve">
|
||||
<value>通過數量</value>
|
||||
</data>
|
||||
<data name="Summary_FailCount" xml:space="preserve">
|
||||
<value>不通過數量</value>
|
||||
</data>
|
||||
<data name="Summary_OverallResult" xml:space="preserve">
|
||||
<value>總體結果</value>
|
||||
</data>
|
||||
<!-- 圖像佔位符鍵 | Image placeholder key -->
|
||||
<data name="Image_NoImage" xml:space="preserve">
|
||||
<value>無圖像</value>
|
||||
</data>
|
||||
<!-- 頁面標題鍵 | Page title keys -->
|
||||
<data name="Page_Homepage" xml:space="preserve">
|
||||
<value>檢測報告首頁</value>
|
||||
</data>
|
||||
<data name="Page_MetricData" xml:space="preserve">
|
||||
<value>測量數據</value>
|
||||
</data>
|
||||
<data name="Page_BgaInspection" xml:space="preserve">
|
||||
<value>BGA 焊球檢測</value>
|
||||
</data>
|
||||
<data name="Page_VoidInspection" xml:space="preserve">
|
||||
<value>空隙檢測</value>
|
||||
</data>
|
||||
<data name="Page_ViaFillInspection" xml:space="preserve">
|
||||
<value>通孔填錫檢測</value>
|
||||
</data>
|
||||
<!-- BGA 檢測鍵 | BGA inspection keys -->
|
||||
<data name="Bga_Count" xml:space="preserve">
|
||||
<value>焊球數量</value>
|
||||
</data>
|
||||
<data name="Bga_TotalArea" xml:space="preserve">
|
||||
<value>焊球總面積</value>
|
||||
</data>
|
||||
<data name="Bga_TotalVoidArea" xml:space="preserve">
|
||||
<value>氣泡總面積</value>
|
||||
</data>
|
||||
<data name="Bga_VoidLimit" xml:space="preserve">
|
||||
<value>氣泡率限值</value>
|
||||
</data>
|
||||
<data name="Bga_BallIndex" xml:space="preserve">
|
||||
<value>焊球序號</value>
|
||||
</data>
|
||||
<!-- 空隙檢測鍵 | Void inspection keys -->
|
||||
<data name="Void_RoiArea" xml:space="preserve">
|
||||
<value>ROI 面積</value>
|
||||
</data>
|
||||
<data name="Void_TotalArea" xml:space="preserve">
|
||||
<value>空隙總面積</value>
|
||||
</data>
|
||||
<data name="Void_Count" xml:space="preserve">
|
||||
<value>空隙數量</value>
|
||||
</data>
|
||||
<data name="Void_MaxArea" xml:space="preserve">
|
||||
<value>最大空隙面積</value>
|
||||
</data>
|
||||
<data name="Void_Limit" xml:space="preserve">
|
||||
<value>空隙率限值</value>
|
||||
</data>
|
||||
<!-- 通孔填錫鍵 | Via fill rate keys -->
|
||||
<data name="Fill_Rate" xml:space="preserve">
|
||||
<value>填錫率</value>
|
||||
</data>
|
||||
<data name="Fill_FullDistance" xml:space="preserve">
|
||||
<value>滿填距離</value>
|
||||
</data>
|
||||
<data name="Fill_FillDistance" xml:space="preserve">
|
||||
<value>填充距離</value>
|
||||
</data>
|
||||
<data name="Fill_THTLimit" xml:space="preserve">
|
||||
<value>THT 限值</value>
|
||||
</data>
|
||||
<!-- 通用字段鍵 | Common field keys -->
|
||||
<data name="Field_Point1" xml:space="preserve">
|
||||
<value>起點</value>
|
||||
</data>
|
||||
<data name="Field_Point2" xml:space="preserve">
|
||||
<value>終點</value>
|
||||
</data>
|
||||
<data name="Field_Result" xml:space="preserve">
|
||||
<value>結果</value>
|
||||
</data>
|
||||
<data name="Field_InspectionType" xml:space="preserve">
|
||||
<value>檢測類型</value>
|
||||
</data>
|
||||
<data name="Field_Status" xml:space="preserve">
|
||||
<value>狀態</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XP.ReportEngine.Interfaces;
|
||||
using XP.ReportEngine.Models;
|
||||
|
||||
namespace XP.ReportEngine.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// PDF 报告生成器实现 | PDF report generator implementation
|
||||
/// 协调管线各阶段:模板加载 → 数据绑定 → 排版 → 渲染 → 保存
|
||||
/// Orchestrates pipeline phases: template loading → data binding → layout → rendering → saving
|
||||
/// </summary>
|
||||
public class PdfReportGenerator : IReportGenerator
|
||||
{
|
||||
private readonly ILoggerService _logger;
|
||||
private readonly ITemplateEngine _templateEngine;
|
||||
private readonly IDataBinder _dataBinder;
|
||||
private readonly ILayoutEngine _layoutEngine;
|
||||
private readonly IPdfRenderer _pdfRenderer;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数 | Constructor
|
||||
/// </summary>
|
||||
/// <param name="logger">日志服务 | Logger service</param>
|
||||
/// <param name="templateEngine">模板引擎 | Template engine</param>
|
||||
/// <param name="dataBinder">数据绑定器 | Data binder</param>
|
||||
/// <param name="layoutEngine">排版引擎 | Layout engine</param>
|
||||
/// <param name="pdfRenderer">PDF 渲染器 | PDF renderer</param>
|
||||
public PdfReportGenerator(
|
||||
ILoggerService logger,
|
||||
ITemplateEngine templateEngine,
|
||||
IDataBinder dataBinder,
|
||||
ILayoutEngine layoutEngine,
|
||||
IPdfRenderer pdfRenderer)
|
||||
{
|
||||
_logger = logger?.ForModule<PdfReportGenerator>() ?? throw new ArgumentNullException(nameof(logger));
|
||||
_templateEngine = templateEngine ?? throw new ArgumentNullException(nameof(templateEngine));
|
||||
_dataBinder = dataBinder ?? throw new ArgumentNullException(nameof(dataBinder));
|
||||
_layoutEngine = layoutEngine ?? throw new ArgumentNullException(nameof(layoutEngine));
|
||||
_pdfRenderer = pdfRenderer ?? throw new ArgumentNullException(nameof(pdfRenderer));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步生成 PDF 报告 | Generate PDF report asynchronously
|
||||
/// 执行完整管线:模板加载 → 验证 → 数据绑定 → 排版计算 → PDF 渲染 → 文件保存(可选)
|
||||
/// Executes full pipeline: template load → validate → data bind → layout → PDF render → save (optional)
|
||||
/// </summary>
|
||||
/// <param name="context">报告上下文数据 | Report context data</param>
|
||||
/// <param name="options">生成选项 | Generation options</param>
|
||||
/// <returns>生成结果 | Generation result</returns>
|
||||
public async Task<ReportResult> GenerateAsync(ReportContext context, ReportGenerationOptions options)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Info("报告生成管线开始 | Report generation pipeline started");
|
||||
|
||||
// 阶段 1:加载模板 | Phase 1: Load template
|
||||
_logger.Info("阶段 1:加载模板 | Phase 1: Loading template");
|
||||
var template = _templateEngine.LoadTemplate(options.TemplatePath);
|
||||
if (template == null)
|
||||
{
|
||||
var errorMsg = $"模板文件未找到: {options.TemplatePath}";
|
||||
_logger.Error(null, "模板加载失败: {Path} | Template loading failed: {Path}", options.TemplatePath);
|
||||
return ReportResult.Failure(errorMsg);
|
||||
}
|
||||
|
||||
var validation = _templateEngine.Validate(template);
|
||||
if (!validation.IsValid)
|
||||
{
|
||||
var errorMsg = $"模板验证失败: {validation.ErrorMessage}";
|
||||
_logger.Error(null, "模板验证失败: {Message} | Template validation failed: {Message}", validation.ErrorMessage);
|
||||
return ReportResult.Failure(errorMsg);
|
||||
}
|
||||
_logger.Info("阶段 1 完成:模板加载成功 | Phase 1 completed: Template loaded successfully");
|
||||
|
||||
// 阶段 2:数据绑定 | Phase 2: Data binding
|
||||
_logger.Info("阶段 2:数据绑定 | Phase 2: Data binding");
|
||||
var boundTemplate = _dataBinder.Bind(template, context);
|
||||
_logger.Info("阶段 2 完成:数据绑定成功 | Phase 2 completed: Data binding successful");
|
||||
|
||||
// 阶段 3:排版计算 | Phase 3: Layout calculation
|
||||
_logger.Info("阶段 3:排版计算 | Phase 3: Layout calculation");
|
||||
var pages = _layoutEngine.CalculateLayout(boundTemplate, options);
|
||||
_logger.Info("阶段 3 完成:排版计算成功,共 {PageCount} 页 | Phase 3 completed: Layout calculated, {PageCount} pages", pages.Count);
|
||||
|
||||
// 阶段 4:PDF 渲染 | Phase 4: PDF rendering
|
||||
_logger.Info("阶段 4:PDF 渲染 | Phase 4: PDF rendering");
|
||||
var stream = _pdfRenderer.Render(pages, options);
|
||||
_logger.Info("阶段 4 完成:PDF 渲染成功 | Phase 4 completed: PDF rendering successful");
|
||||
|
||||
// 阶段 5:保存文件(可选)| Phase 5: Save file (optional)
|
||||
if (!string.IsNullOrEmpty(options.OutputFilePath))
|
||||
{
|
||||
_logger.Info("阶段 5:保存文件 | Phase 5: Saving file");
|
||||
await SaveToFileAsync(stream, options.OutputFilePath);
|
||||
_logger.Info("阶段 5 完成:文件保存成功 {Path} | Phase 5 completed: File saved successfully {Path}", options.OutputFilePath);
|
||||
}
|
||||
|
||||
_logger.Info("报告生成管线完成 | Report generation pipeline completed");
|
||||
return ReportResult.Success(stream);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "报告生成失败 | Report generation failed: {Message}", ex.Message);
|
||||
return ReportResult.Failure($"报告生成过程中发生错误: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 MemoryStream 保存到文件 | Save MemoryStream to file
|
||||
/// </summary>
|
||||
/// <param name="stream">PDF 内存流 | PDF memory stream</param>
|
||||
/// <param name="filePath">输出文件路径 | Output file path</param>
|
||||
private async Task SaveToFileAsync(MemoryStream stream, string filePath)
|
||||
{
|
||||
// 确保输出目录存在 | Ensure output directory exists
|
||||
var directory = Path.GetDirectoryName(filePath);
|
||||
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
// 重置流位置后写入文件 | Reset stream position before writing to file
|
||||
stream.Position = 0;
|
||||
using var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
await stream.CopyToAsync(fileStream);
|
||||
|
||||
// 重置流位置以便后续使用 | Reset stream position for subsequent use
|
||||
stream.Position = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,472 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XP.ReportEngine.Interfaces;
|
||||
using XP.ReportEngine.Models;
|
||||
|
||||
namespace XP.ReportEngine.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 处理器数据适配器实现 | Processor data adapter implementation
|
||||
/// 将 XP.ImageProcessing 的 ProcessorOutput 转换为 ReportContext
|
||||
/// Converts XP.ImageProcessing ProcessorOutput to ReportContext
|
||||
/// </summary>
|
||||
public class ProcessorDataAdapter : IReportDataAdapter
|
||||
{
|
||||
private readonly ILoggerService _logger;
|
||||
|
||||
// 处理器类型常量 | Processor type constants
|
||||
private const string LineMeasurementProcessor = "LineMeasurementProcessor";
|
||||
private const string BgaVoidRateProcessor = "BgaVoidRateProcessor";
|
||||
private const string VoidMeasurementProcessor = "VoidMeasurementProcessor";
|
||||
private const string FillRateProcessor = "FillRateProcessor";
|
||||
|
||||
public ProcessorDataAdapter(ILoggerService logger)
|
||||
{
|
||||
_logger = logger?.ForModule<ProcessorDataAdapter>() ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将处理器输出数据适配为报告上下文 | Adapt processor output data to report context
|
||||
/// </summary>
|
||||
public ReportContext Adapt(List<ProcessorOutput> processorOutputs, ReportMetadata metadata)
|
||||
{
|
||||
if (processorOutputs == null) throw new ArgumentNullException(nameof(processorOutputs));
|
||||
if (metadata == null) throw new ArgumentNullException(nameof(metadata));
|
||||
|
||||
_logger.Info("开始数据适配,处理器数量: {Count} | Starting data adaptation, processor count: {Count}", processorOutputs.Count);
|
||||
|
||||
var context = new ReportContext
|
||||
{
|
||||
Metadata = metadata,
|
||||
ResultGroups = new List<InspectionResultGroup>(),
|
||||
Images = new Dictionary<string, ImageData>(),
|
||||
Properties = new Dictionary<string, object>()
|
||||
};
|
||||
|
||||
// 多处理器输出聚合逻辑:每个 ProcessorOutput 生成一个 InspectionResultGroup | Aggregation: each ProcessorOutput becomes one InspectionResultGroup
|
||||
for (var i = 0; i < processorOutputs.Count; i++)
|
||||
{
|
||||
var output = processorOutputs[i];
|
||||
if (output == null)
|
||||
{
|
||||
_logger.Warn("处理器输出为 null,索引: {Index},已跳过 | Processor output is null at index: {Index}, skipped", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
var group = AdaptProcessorOutput(output, i);
|
||||
context.ResultGroups.Add(group);
|
||||
|
||||
// 关联标注图像 | Associate annotated image
|
||||
if (output.AnnotatedImage != null)
|
||||
{
|
||||
var imageKey = $"{output.ProcessorType}_{i}_annotated";
|
||||
context.Images[imageKey] = output.AnnotatedImage;
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Info("数据适配完成,生成 {Count} 个结果分组 | Data adaptation completed, generated {Count} result groups", context.ResultGroups.Count);
|
||||
return context;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据处理器类型分发适配逻辑 | Dispatch adaptation logic by processor type
|
||||
/// </summary>
|
||||
private InspectionResultGroup AdaptProcessorOutput(ProcessorOutput output, int index)
|
||||
{
|
||||
var sourceId = $"{output.ProcessorType}_{index}";
|
||||
|
||||
return output.ProcessorType switch
|
||||
{
|
||||
LineMeasurementProcessor => AdaptLineMeasurement(output, sourceId),
|
||||
BgaVoidRateProcessor => AdaptBgaVoidRate(output, sourceId),
|
||||
VoidMeasurementProcessor => AdaptVoidMeasurement(output, sourceId),
|
||||
FillRateProcessor => AdaptFillRate(output, sourceId),
|
||||
_ => AdaptGeneric(output, sourceId)
|
||||
};
|
||||
}
|
||||
|
||||
#region LineMeasurementProcessor 适配 | LineMeasurementProcessor Adaptation
|
||||
|
||||
/// <summary>
|
||||
/// 适配线测量处理器输出 | Adapt line measurement processor output
|
||||
/// 提取 MeasurementType、Point1、Point2、PixelDistance、ActualDistance、Unit、Angle
|
||||
/// </summary>
|
||||
private InspectionResultGroup AdaptLineMeasurement(ProcessorOutput output, string sourceId)
|
||||
{
|
||||
_logger.Debug("适配 LineMeasurementProcessor 输出,SourceId: {SourceId} | Adapting LineMeasurementProcessor output, SourceId: {SourceId}", sourceId);
|
||||
|
||||
var data = output.OutputData ?? new Dictionary<string, object>();
|
||||
var group = new InspectionResultGroup
|
||||
{
|
||||
ProcessorType = LineMeasurementProcessor,
|
||||
SourceId = sourceId,
|
||||
Classification = string.Empty,
|
||||
Data = new Dictionary<string, object>
|
||||
{
|
||||
["measurementType"] = GetValueOrDefault<string>(data, "MeasurementType", string.Empty),
|
||||
["point1"] = GetValueOrDefault<object>(data, "Point1", null),
|
||||
["point2"] = GetValueOrDefault<object>(data, "Point2", null),
|
||||
["pixelDistance"] = GetValueOrDefault<double>(data, "PixelDistance", 0.0),
|
||||
["actualDistance"] = GetValueOrDefault<double>(data, "ActualDistance", 0.0),
|
||||
["unit"] = GetValueOrDefault<string>(data, "Unit", string.Empty),
|
||||
["angle"] = GetValueOrDefault<double>(data, "Angle", 0.0)
|
||||
},
|
||||
TableRows = new List<Dictionary<string, object>>()
|
||||
};
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region BgaVoidRateProcessor 适配 | BgaVoidRateProcessor Adaptation
|
||||
|
||||
/// <summary>
|
||||
/// 适配 BGA 气泡率处理器输出 | Adapt BGA void rate processor output
|
||||
/// 提取 BgaCount、BgaBalls 列表转 TableRows、VoidRate、FillRate、TotalBgaArea、TotalVoidArea、Classification、VoidLimit
|
||||
/// </summary>
|
||||
private InspectionResultGroup AdaptBgaVoidRate(ProcessorOutput output, string sourceId)
|
||||
{
|
||||
_logger.Debug("适配 BgaVoidRateProcessor 输出,SourceId: {SourceId} | Adapting BgaVoidRateProcessor output, SourceId: {SourceId}", sourceId);
|
||||
|
||||
var data = output.OutputData ?? new Dictionary<string, object>();
|
||||
var group = new InspectionResultGroup
|
||||
{
|
||||
ProcessorType = BgaVoidRateProcessor,
|
||||
SourceId = sourceId,
|
||||
Classification = GetValueOrDefault<string>(data, "Classification", string.Empty),
|
||||
Data = new Dictionary<string, object>
|
||||
{
|
||||
["bgaCount"] = GetValueOrDefault<int>(data, "BgaCount", 0),
|
||||
["voidRate"] = GetValueOrDefault<double>(data, "VoidRate", 0.0),
|
||||
["fillRate"] = GetValueOrDefault<double>(data, "FillRate", 0.0),
|
||||
["totalBgaArea"] = GetValueOrDefault<double>(data, "TotalBgaArea", 0.0),
|
||||
["totalVoidArea"] = GetValueOrDefault<double>(data, "TotalVoidArea", 0.0),
|
||||
["voidLimit"] = GetValueOrDefault<double>(data, "VoidLimit", 0.0)
|
||||
},
|
||||
TableRows = ConvertBgaBallsToTableRows(data)
|
||||
};
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 BgaBalls 列表转换为表格行 | Convert BgaBalls list to table rows
|
||||
/// 每个焊球一行,包含 index、voidRate、classification
|
||||
/// </summary>
|
||||
private List<Dictionary<string, object>> ConvertBgaBallsToTableRows(Dictionary<string, object> data)
|
||||
{
|
||||
var tableRows = new List<Dictionary<string, object>>();
|
||||
var bgaBalls = GetListValue(data, "BgaBalls");
|
||||
|
||||
if (bgaBalls == null || bgaBalls.Count == 0)
|
||||
{
|
||||
return tableRows;
|
||||
}
|
||||
|
||||
for (var i = 0; i < bgaBalls.Count; i++)
|
||||
{
|
||||
var ball = bgaBalls[i];
|
||||
var row = new Dictionary<string, object>
|
||||
{
|
||||
["index"] = i + 1,
|
||||
["voidRate"] = GetNestedValue<double>(ball, "VoidRate", 0.0),
|
||||
["classification"] = GetNestedValue<string>(ball, "Classification", string.Empty)
|
||||
};
|
||||
tableRows.Add(row);
|
||||
}
|
||||
|
||||
return tableRows;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region VoidMeasurementProcessor 适配 | VoidMeasurementProcessor Adaptation
|
||||
|
||||
/// <summary>
|
||||
/// 适配空隙测量处理器输出 | Adapt void measurement processor output
|
||||
/// 提取 RoiArea、TotalVoidArea、VoidRate、VoidLimit、VoidCount、MaxVoidArea、Classification、Voids 列表转 TableRows
|
||||
/// </summary>
|
||||
private InspectionResultGroup AdaptVoidMeasurement(ProcessorOutput output, string sourceId)
|
||||
{
|
||||
_logger.Debug("适配 VoidMeasurementProcessor 输出,SourceId: {SourceId} | Adapting VoidMeasurementProcessor output, SourceId: {SourceId}", sourceId);
|
||||
|
||||
var data = output.OutputData ?? new Dictionary<string, object>();
|
||||
var group = new InspectionResultGroup
|
||||
{
|
||||
ProcessorType = VoidMeasurementProcessor,
|
||||
SourceId = sourceId,
|
||||
Classification = GetValueOrDefault<string>(data, "Classification", string.Empty),
|
||||
Data = new Dictionary<string, object>
|
||||
{
|
||||
["roiArea"] = GetValueOrDefault<double>(data, "RoiArea", 0.0),
|
||||
["totalVoidArea"] = GetValueOrDefault<double>(data, "TotalVoidArea", 0.0),
|
||||
["voidRate"] = GetValueOrDefault<double>(data, "VoidRate", 0.0),
|
||||
["voidLimit"] = GetValueOrDefault<double>(data, "VoidLimit", 0.0),
|
||||
["voidCount"] = GetValueOrDefault<int>(data, "VoidCount", 0),
|
||||
["maxVoidArea"] = GetValueOrDefault<double>(data, "MaxVoidArea", 0.0)
|
||||
},
|
||||
TableRows = ConvertVoidsToTableRows(data)
|
||||
};
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 Voids 列表转换为表格行 | Convert Voids list to table rows
|
||||
/// 每个空隙一行,包含 index、area、areaPercent、centerX、centerY
|
||||
/// </summary>
|
||||
private List<Dictionary<string, object>> ConvertVoidsToTableRows(Dictionary<string, object> data)
|
||||
{
|
||||
var tableRows = new List<Dictionary<string, object>>();
|
||||
var voids = GetListValue(data, "Voids");
|
||||
|
||||
if (voids == null || voids.Count == 0)
|
||||
{
|
||||
return tableRows;
|
||||
}
|
||||
|
||||
for (var i = 0; i < voids.Count; i++)
|
||||
{
|
||||
var voidItem = voids[i];
|
||||
var row = new Dictionary<string, object>
|
||||
{
|
||||
["index"] = i + 1,
|
||||
["area"] = GetNestedValue<double>(voidItem, "Area", 0.0),
|
||||
["areaPercent"] = GetNestedValue<double>(voidItem, "AreaPercent", 0.0),
|
||||
["centerX"] = GetNestedValue<double>(voidItem, "CenterX", 0.0),
|
||||
["centerY"] = GetNestedValue<double>(voidItem, "CenterY", 0.0)
|
||||
};
|
||||
tableRows.Add(row);
|
||||
}
|
||||
|
||||
return tableRows;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region FillRateProcessor 适配 | FillRateProcessor Adaptation
|
||||
|
||||
/// <summary>
|
||||
/// 适配填锡率处理器输出 | Adapt fill rate processor output
|
||||
/// 提取 FillRate、VoidRate、FullDistance、FillDistance、THTLimit、Classification、E1-E4 椭圆几何数据
|
||||
/// </summary>
|
||||
private InspectionResultGroup AdaptFillRate(ProcessorOutput output, string sourceId)
|
||||
{
|
||||
_logger.Debug("适配 FillRateProcessor 输出,SourceId: {SourceId} | Adapting FillRateProcessor output, SourceId: {SourceId}", sourceId);
|
||||
|
||||
var data = output.OutputData ?? new Dictionary<string, object>();
|
||||
var group = new InspectionResultGroup
|
||||
{
|
||||
ProcessorType = FillRateProcessor,
|
||||
SourceId = sourceId,
|
||||
Classification = GetValueOrDefault<string>(data, "Classification", string.Empty),
|
||||
Data = new Dictionary<string, object>
|
||||
{
|
||||
["fillRate"] = GetValueOrDefault<double>(data, "FillRate", 0.0),
|
||||
["voidRate"] = GetValueOrDefault<double>(data, "VoidRate", 0.0),
|
||||
["fullDistance"] = GetValueOrDefault<double>(data, "FullDistance", 0.0),
|
||||
["fillDistance"] = GetValueOrDefault<double>(data, "FillDistance", 0.0),
|
||||
["thtLimit"] = GetValueOrDefault<double>(data, "THTLimit", 0.0),
|
||||
["e1"] = GetValueOrDefault<object>(data, "E1", null),
|
||||
["e2"] = GetValueOrDefault<object>(data, "E2", null),
|
||||
["e3"] = GetValueOrDefault<object>(data, "E3", null),
|
||||
["e4"] = GetValueOrDefault<object>(data, "E4", null)
|
||||
},
|
||||
TableRows = new List<Dictionary<string, object>>()
|
||||
};
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 通用适配 | Generic Adaptation
|
||||
|
||||
/// <summary>
|
||||
/// 通用处理器适配(未知类型)| Generic processor adaptation (unknown type)
|
||||
/// 将所有 OutputData 键值对直接映射到 Data 字典
|
||||
/// </summary>
|
||||
private InspectionResultGroup AdaptGeneric(ProcessorOutput output, string sourceId)
|
||||
{
|
||||
_logger.Warn("未知处理器类型: {ProcessorType},使用通用适配 | Unknown processor type: {ProcessorType}, using generic adaptation", output.ProcessorType);
|
||||
|
||||
var data = output.OutputData ?? new Dictionary<string, object>();
|
||||
var group = new InspectionResultGroup
|
||||
{
|
||||
ProcessorType = output.ProcessorType ?? "Unknown",
|
||||
SourceId = sourceId,
|
||||
Classification = GetValueOrDefault<string>(data, "Classification", string.Empty),
|
||||
Data = new Dictionary<string, object>(),
|
||||
TableRows = new List<Dictionary<string, object>>()
|
||||
};
|
||||
|
||||
// 将所有键值对转为小驼峰命名映射 | Map all key-value pairs with camelCase naming
|
||||
foreach (var kvp in data)
|
||||
{
|
||||
var camelKey = ToCamelCase(kvp.Key);
|
||||
group.Data[camelKey] = kvp.Value;
|
||||
}
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 辅助方法 | Helper Methods
|
||||
|
||||
/// <summary>
|
||||
/// 从字典中获取值,缺失时使用默认值并记录警告 | Get value from dictionary, use default and log warning if missing
|
||||
/// </summary>
|
||||
private T GetValueOrDefault<T>(Dictionary<string, object> data, string key, T defaultValue)
|
||||
{
|
||||
if (data == null || !data.TryGetValue(key, out var value) || value == null)
|
||||
{
|
||||
_logger.Warn("处理器输出缺少键: {Key},使用默认值: {Default} | Processor output missing key: {Key}, using default: {Default}", key, defaultValue);
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return ConvertValue<T>(value);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn("键 {Key} 的值类型转换失败: {Message},使用默认值 | Value type conversion failed for key {Key}: {Message}, using default", key, ex.Message);
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从字典中获取列表值 | Get list value from dictionary
|
||||
/// </summary>
|
||||
private IList GetListValue(Dictionary<string, object> data, string key)
|
||||
{
|
||||
if (data == null || !data.TryGetValue(key, out var value) || value == null)
|
||||
{
|
||||
_logger.Warn("处理器输出缺少列表键: {Key},返回空列表 | Processor output missing list key: {Key}, returning empty list", key);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value is IList list)
|
||||
{
|
||||
return list;
|
||||
}
|
||||
|
||||
_logger.Warn("键 {Key} 的值不是列表类型 | Value for key {Key} is not a list type", key);
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从嵌套对象中获取属性值 | Get property value from nested object
|
||||
/// </summary>
|
||||
private T GetNestedValue<T>(object obj, string propertyName, T defaultValue)
|
||||
{
|
||||
if (obj == null) return defaultValue;
|
||||
|
||||
// 字典访问 | Dictionary access
|
||||
if (obj is IDictionary<string, object> dict)
|
||||
{
|
||||
if (dict.TryGetValue(propertyName, out var dictValue) && dictValue != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
return ConvertValue<T>(dictValue);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// 反射访问 | Reflection access
|
||||
var type = obj.GetType();
|
||||
var propInfo = type.GetProperty(propertyName,
|
||||
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.IgnoreCase);
|
||||
if (propInfo != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var value = propInfo.GetValue(obj);
|
||||
if (value == null) return defaultValue;
|
||||
return ConvertValue<T>(value);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 类型转换辅助方法 | Type conversion helper
|
||||
/// </summary>
|
||||
private T ConvertValue<T>(object value)
|
||||
{
|
||||
if (value == null) return default;
|
||||
|
||||
var targetType = typeof(T);
|
||||
|
||||
// 直接类型匹配 | Direct type match
|
||||
if (value is T typedValue)
|
||||
{
|
||||
return typedValue;
|
||||
}
|
||||
|
||||
// 处理 nullable 类型 | Handle nullable types
|
||||
var underlyingType = Nullable.GetUnderlyingType(targetType) ?? targetType;
|
||||
|
||||
// object 类型直接返回 | Return directly for object type
|
||||
if (underlyingType == typeof(object))
|
||||
{
|
||||
return (T)value;
|
||||
}
|
||||
|
||||
// 数值类型转换 | Numeric type conversion
|
||||
if (underlyingType == typeof(double))
|
||||
{
|
||||
return (T)(object)Convert.ToDouble(value);
|
||||
}
|
||||
if (underlyingType == typeof(int))
|
||||
{
|
||||
return (T)(object)Convert.ToInt32(value);
|
||||
}
|
||||
if (underlyingType == typeof(float))
|
||||
{
|
||||
return (T)(object)Convert.ToSingle(value);
|
||||
}
|
||||
if (underlyingType == typeof(long))
|
||||
{
|
||||
return (T)(object)Convert.ToInt64(value);
|
||||
}
|
||||
|
||||
// 字符串转换 | String conversion
|
||||
if (underlyingType == typeof(string))
|
||||
{
|
||||
return (T)(object)value.ToString();
|
||||
}
|
||||
|
||||
// 通用转换 | General conversion
|
||||
return (T)Convert.ChangeType(value, underlyingType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 PascalCase 转换为 camelCase | Convert PascalCase to camelCase
|
||||
/// </summary>
|
||||
private string ToCamelCase(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input)) return input;
|
||||
if (input.Length == 1) return input.ToLowerInvariant();
|
||||
return char.ToLowerInvariant(input[0]) + input.Substring(1);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XP.ReportEngine.Interfaces;
|
||||
using XP.ReportEngine.Models;
|
||||
|
||||
namespace XP.ReportEngine.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 报告生成器工厂实现 | Report generator factory implementation
|
||||
/// 根据输出格式创建对应的报告生成器实例
|
||||
/// Creates report generator instances based on output format
|
||||
/// </summary>
|
||||
public class ReportGeneratorFactory : IReportGeneratorFactory
|
||||
{
|
||||
private readonly ILoggerService _logger;
|
||||
private readonly IReportGenerator _pdfReportGenerator;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数 | Constructor
|
||||
/// </summary>
|
||||
/// <param name="logger">日志服务 | Logger service</param>
|
||||
/// <param name="pdfReportGenerator">PDF 报告生成器 | PDF report generator</param>
|
||||
public ReportGeneratorFactory(ILoggerService logger, IReportGenerator pdfReportGenerator)
|
||||
{
|
||||
_logger = logger?.ForModule<ReportGeneratorFactory>() ?? throw new ArgumentNullException(nameof(logger));
|
||||
_pdfReportGenerator = pdfReportGenerator ?? throw new ArgumentNullException(nameof(pdfReportGenerator));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据输出格式创建生成器 | Create generator by output format
|
||||
/// 当前仅支持 PDF 格式,未来可扩展 Excel/CSV
|
||||
/// Currently only supports PDF format, extensible for Excel/CSV in the future
|
||||
/// </summary>
|
||||
/// <param name="format">输出格式 | Output format</param>
|
||||
/// <returns>对应格式的报告生成器 | Report generator for the specified format</returns>
|
||||
/// <exception cref="NotSupportedException">当请求不支持的格式时抛出 | Thrown when unsupported format is requested</exception>
|
||||
public IReportGenerator Create(ReportOutputFormat format)
|
||||
{
|
||||
_logger.Info("创建报告生成器,格式: {Format} | Creating report generator, format: {Format}", format);
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case ReportOutputFormat.Pdf:
|
||||
return _pdfReportGenerator;
|
||||
|
||||
default:
|
||||
var message = $"不支持的报告输出格式: {format} | Unsupported report output format: {format}";
|
||||
_logger.Error(null, message);
|
||||
throw new NotSupportedException(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
|
||||
namespace XP.ReportEngine.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 报告编号生成器 | Report ID generator
|
||||
/// 生成格式为 RPT-yyyyMMdd-NNN 的唯一报告编号
|
||||
/// Generates unique report IDs in format: RPT-yyyyMMdd-NNN
|
||||
/// 线程安全,每日自动重置计数器
|
||||
/// Thread-safe with daily counter reset
|
||||
/// </summary>
|
||||
public class ReportIdGenerator
|
||||
{
|
||||
private readonly object _lock = new();
|
||||
private int _dailyCounter;
|
||||
private string _currentDate;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数 | Constructor
|
||||
/// </summary>
|
||||
public ReportIdGenerator()
|
||||
{
|
||||
_currentDate = DateTime.Now.ToString("yyyyMMdd");
|
||||
_dailyCounter = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成下一个唯一报告编号 | Generate next unique report ID
|
||||
/// 格式:RPT-yyyyMMdd-NNN(如 RPT-20250101-001)
|
||||
/// Format: RPT-yyyyMMdd-NNN (e.g., RPT-20250101-001)
|
||||
/// </summary>
|
||||
/// <returns>唯一报告编号 | Unique report ID</returns>
|
||||
public string GenerateNext()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var today = DateTime.Now.ToString("yyyyMMdd");
|
||||
|
||||
// 日期变更时重置计数器 | Reset counter when date changes
|
||||
if (today != _currentDate)
|
||||
{
|
||||
_currentDate = today;
|
||||
_dailyCounter = 0;
|
||||
}
|
||||
|
||||
_dailyCounter++;
|
||||
return $"RPT-{_currentDate}-{_dailyCounter:D3}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据指定时间戳生成报告编号 | Generate report ID with specified timestamp
|
||||
/// 用于需要指定日期的场景(如补录报告)
|
||||
/// Used for scenarios requiring specific dates (e.g., backfilling reports)
|
||||
/// </summary>
|
||||
/// <param name="timestamp">指定的时间戳 | Specified timestamp</param>
|
||||
/// <returns>唯一报告编号 | Unique report ID</returns>
|
||||
public string GenerateForDate(DateTime timestamp)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var dateStr = timestamp.ToString("yyyyMMdd");
|
||||
|
||||
// 如果指定日期与当前日期相同,使用当前计数器
|
||||
// If specified date matches current date, use current counter
|
||||
if (dateStr == _currentDate)
|
||||
{
|
||||
_dailyCounter++;
|
||||
return $"RPT-{_currentDate}-{_dailyCounter:D3}";
|
||||
}
|
||||
|
||||
// 如果指定日期与当前日期不同,更新日期并重置计数器
|
||||
// If specified date differs from current date, update date and reset counter
|
||||
_currentDate = dateStr;
|
||||
_dailyCounter = 1;
|
||||
return $"RPT-{_currentDate}-{_dailyCounter:D3}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
{
|
||||
"document": {
|
||||
"pageSize": "A4",
|
||||
"orientation": "Portrait",
|
||||
"margins": { "top": 20, "bottom": 20, "left": 20, "right": 20 }
|
||||
},
|
||||
"pages": [
|
||||
{
|
||||
"type": "homepage",
|
||||
"elements": [
|
||||
{ "type": "text", "content": "${loc:Report_Title}", "style": "title", "position": [60, 40], "positioning": "absolute" },
|
||||
{ "type": "divider", "position": [0, 60], "size": [170, 1], "positioning": "absolute" },
|
||||
{ "type": "text", "content": "${loc:Report_Id}:${metadata.reportId}", "style": "body", "position": [0, 75], "positioning": "absolute" },
|
||||
{ "type": "text", "content": "${loc:Report_Date}:${formatDate(metadata.inspectionDate)}", "style": "body", "position": [0, 85], "positioning": "absolute" },
|
||||
{ "type": "text", "content": "${loc:Report_Sample}:${metadata.sampleName}", "style": "body", "position": [0, 95], "positioning": "absolute" },
|
||||
{ "type": "text", "content": "${loc:Report_Operator}:${metadata.operatorName}", "style": "body", "position": [0, 105], "positioning": "absolute" },
|
||||
{ "type": "text", "content": "${loc:Report_Description}:${metadata.description}", "style": "body", "position": [0, 115], "positioning": "absolute" },
|
||||
{ "type": "divider", "position": [0, 130], "size": [170, 1], "positioning": "absolute" },
|
||||
{ "type": "text", "content": "${loc:Report_Summary}", "style": "heading", "position": [0, 140], "positioning": "absolute" },
|
||||
{
|
||||
"type": "table", "dataKey": "summaryTable", "position": [0, 150], "size": [170, 60], "positioning": "absolute",
|
||||
"columns": [
|
||||
{ "header": "${loc:Field_InspectionType}", "field": "inspectionType", "width": 50, "align": "left" },
|
||||
{ "header": "${loc:Field_Result}", "field": "classification", "width": 30, "align": "center" },
|
||||
{ "header": "${loc:Field_Status}", "field": "status", "width": 30, "align": "center" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "metricData",
|
||||
"elements": [
|
||||
{ "type": "text", "content": "${loc:Page_MetricData}", "style": "heading", "positioning": "flow" },
|
||||
{ "type": "divider", "size": [170, 1], "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Measurement_Type}:${measurementType}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Field_Point1}:${point1}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Field_Point2}:${point2}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Measurement_Distance}:${actualDistance} ${unit}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Measurement_Angle}:${angle}°", "style": "body", "positioning": "flow" },
|
||||
{ "type": "image", "dataKey": "lineMeasurementImage", "size": [150, 100], "border": true, "positioning": "flow" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "bgaInspection",
|
||||
"elements": [
|
||||
{ "type": "text", "content": "${loc:Page_BgaInspection}", "style": "heading", "positioning": "flow" },
|
||||
{ "type": "divider", "size": [170, 1], "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Bga_Count}:${bgaCount}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Measurement_VoidRate}:${formatPercent(voidRate)}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Measurement_FillRate}:${formatPercent(fillRate)}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Bga_TotalArea}:${formatNumber(totalBgaArea, 2)}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Bga_TotalVoidArea}:${formatNumber(totalVoidArea, 2)}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Bga_VoidLimit}:${formatPercent(voidLimit)}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Table_Classification}:${classification}", "style": "bodyBold", "positioning": "flow" },
|
||||
{ "type": "image", "dataKey": "bgaInspectionImage", "size": [150, 100], "border": true, "positioning": "flow" },
|
||||
{
|
||||
"type": "table", "dataKey": "bgaBallsTable", "positioning": "flow", "size": [170, 0],
|
||||
"columns": [
|
||||
{ "header": "${loc:Bga_BallIndex}", "field": "index", "width": 25, "align": "center" },
|
||||
{ "header": "${loc:Table_VoidRate}", "field": "voidRate", "width": 40, "align": "center" },
|
||||
{ "header": "${loc:Table_Area}", "field": "area", "width": 40, "align": "center" },
|
||||
{ "header": "${loc:Table_Classification}", "field": "classification", "width": 35, "align": "center" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "voidInspection",
|
||||
"elements": [
|
||||
{ "type": "text", "content": "${loc:Page_VoidInspection}", "style": "heading", "positioning": "flow" },
|
||||
{ "type": "divider", "size": [170, 1], "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Void_RoiArea}:${formatNumber(roiArea, 2)}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Void_TotalArea}:${formatNumber(totalVoidArea, 2)}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Measurement_VoidRate}:${formatPercent(voidRate)}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Void_Limit}:${formatPercent(voidLimit)}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Void_Count}:${voidCount}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Void_MaxArea}:${formatNumber(maxVoidArea, 2)}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Table_Classification}:${classification}", "style": "bodyBold", "positioning": "flow" },
|
||||
{ "type": "image", "dataKey": "voidInspectionImage", "size": [150, 100], "border": true, "positioning": "flow" },
|
||||
{
|
||||
"type": "table", "dataKey": "voidsTable", "positioning": "flow", "size": [170, 0],
|
||||
"columns": [
|
||||
{ "header": "${loc:Table_Index}", "field": "index", "width": 20, "align": "center" },
|
||||
{ "header": "${loc:Table_Area}", "field": "area", "width": 35, "align": "center" },
|
||||
{ "header": "${loc:Table_AreaPercent}", "field": "areaPercent", "width": 35, "align": "center" },
|
||||
{ "header": "${loc:Table_CenterX}", "field": "centerX", "width": 30, "align": "center" },
|
||||
{ "header": "${loc:Table_CenterY}", "field": "centerY", "width": 30, "align": "center" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "viaFillInspection",
|
||||
"elements": [
|
||||
{ "type": "text", "content": "${loc:Page_ViaFillInspection}", "style": "heading", "positioning": "flow" },
|
||||
{ "type": "divider", "size": [170, 1], "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Fill_Rate}:${formatPercent(fillRate)}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Measurement_VoidRate}:${formatPercent(voidRate)}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Fill_FullDistance}:${formatNumber(fullDistance, 2)}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Fill_FillDistance}:${formatNumber(fillDistance, 2)}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Fill_THTLimit}:${formatPercent(thtLimit)}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Table_Classification}:${classification}", "style": "bodyBold", "positioning": "flow" },
|
||||
{ "type": "image", "dataKey": "viaFillImage", "size": [150, 100], "border": true, "positioning": "flow" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"styles": {
|
||||
"title": { "font": "auto", "size": 24, "bold": true, "italic": false, "color": "#1a1a1a", "align": "center" },
|
||||
"heading": { "font": "auto", "size": 16, "bold": true, "italic": false, "color": "#333333", "align": "left" },
|
||||
"body": { "font": "auto", "size": 11, "bold": false, "italic": false, "color": "#333333", "align": "left" },
|
||||
"bodyBold": { "font": "auto", "size": 11, "bold": true, "italic": false, "color": "#1a1a1a", "align": "left" },
|
||||
"tableHeader": { "font": "auto", "size": 10, "bold": true, "italic": false, "color": "#ffffff", "align": "center", "backgroundColor": "#4472C4" },
|
||||
"tableCell": { "font": "auto", "size": 10, "bold": false, "italic": false, "color": "#333333", "align": "center" }
|
||||
}
|
||||
}
|
||||
@@ -22,4 +22,10 @@
|
||||
<EmbeddedResource Include="Fonts\*.otf" />
|
||||
<EmbeddedResource Include="Fonts\*.ttf" />
|
||||
</ItemGroup>
|
||||
<!-- 模板文件复制到输出目录 | Copy template files to output directory -->
|
||||
<ItemGroup>
|
||||
<Content Include="Templates\*.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user