已合并 PR 74: PDF报告生成(基础功能)模块合并到开发分支
XP.ReportEngine 报告模块开发(仅按照实现基础功能开发设计,后期根据报告要求调整)。 完成基础类和接口设计,封装对外服务接口; 搭建数据处理层,实现模拟测试的数据绑定; PDF 模板设计,报表页眉页脚、适配模板调整分栏、页边距等排版逻辑渲染生成功能; 增加请求响应模型和引擎预热和后台线程处理; 更新 ReleaseFiles 文件。
This commit is contained in:
+33
-3
@@ -7,7 +7,10 @@
|
||||
<appSettings>
|
||||
<!-- 语言配置 可选值: ZhCN, ZhTW, EnUS| Language Configuration -->
|
||||
<add key="Language" value="ZhCN" />
|
||||
<add key="UserManual" value="D:\HMQProject\XplorePlane_CT\Code\XplorePlane\XP.App\bin\Debug\net8.0-windows7.0\UserManual.pdf" />
|
||||
<add key="XpData:RootPath" value="D:\XPData" />
|
||||
<add key="UserManual" value="UserManual.pdf" />
|
||||
<add key="DeviceId" value="PlanerCT001" />
|
||||
|
||||
|
||||
<!-- Serilog日志配置 -->
|
||||
<add key="Serilog:LogPath" value="D:\XplorePlane\Logs" />
|
||||
@@ -122,7 +125,6 @@
|
||||
<!-- 自动重连 | Auto Reconnection -->
|
||||
<add key="Plc:bReConnect" value="true" />
|
||||
|
||||
<!-- ==================== 运动控制配置 | Motion Control Configuration ==================== -->
|
||||
<!-- 直线轴配置(单位:mm)| Linear axis config (unit: mm) -->
|
||||
<add key="MotionControl:SourceZ:Min" value="-500" />
|
||||
<add key="MotionControl:SourceZ:Max" value="500" />
|
||||
@@ -148,7 +150,7 @@
|
||||
<add key="MotionControl:FixtureRotation:Min" value="-90" />
|
||||
<add key="MotionControl:FixtureRotation:Max" value="90" />
|
||||
<add key="MotionControl:FixtureRotation:Origin" value="0" />
|
||||
<add key="MotionControl:FixtureRotation:Enabled" value="true" />
|
||||
<add key="MotionControl:FixtureRotation:Enabled" value="false" />
|
||||
<!-- 几何原点(mm)| Geometry origins (mm) -->
|
||||
<add key="MotionControl:Geometry:SourceZOrigin" value="0" />
|
||||
<add key="MotionControl:Geometry:DetectorZOrigin" value="600" />
|
||||
@@ -161,6 +163,34 @@
|
||||
<!-- 运行参数 | Runtime parameters -->
|
||||
<add key="MotionControl:PollingInterval" value="500" />
|
||||
<add key="MotionControl:DefaultVelocity" value="500" />
|
||||
|
||||
<!-- 报告输出文件夹路径(绝对路径)| Report output directory (absolute path) -->
|
||||
<add key="Report:OutputDirectory" value="D:\XplorePlane\Report" />
|
||||
<!-- 报告模板文件路径(相对于应用程序目录或绝对路径)| Template file path (relative to app dir or absolute) -->
|
||||
<add key="Report:TemplatePath" value="Templates\StandardReportTemplate.json" />
|
||||
<!-- 输出文件名模式 | File name pattern -->
|
||||
<add key="Report:FileNamePattern" value="{Date}_{ProductName}_{WorkpieceSN}_{ReportId}" />
|
||||
<!-- 文件名重复时是否自动累加序号 | Auto-increment suffix when file name duplicates-->
|
||||
<add key="Report:AutoIncrementOnDuplicate" value="true" />
|
||||
<!-- 生成后是否自动打开 PDF 阅读器 | Auto-open PDF viewer after generation (true/false) -->
|
||||
<add key="Report:AutoOpenAfterGenerate" value="false" />
|
||||
<!-- 默认页面尺寸 | Default page size (A4) -->
|
||||
<add key="Report:DefaultPageSize" value="A4" />
|
||||
<!-- 默认页面方向 | Default orientation (Portrait / Landscape) -->
|
||||
<add key="Report:DefaultOrientation" value="Portrait" />
|
||||
<!-- 页面边距(mm),有效范围 0-100 | Page margins (mm), valid range 0-100 -->
|
||||
<add key="Report:MarginTop" value="20" />
|
||||
<add key="Report:MarginBottom" value="20" />
|
||||
<add key="Report:MarginLeft" value="20" />
|
||||
<add key="Report:MarginRight" value="20" />
|
||||
<!-- 报告中显示的公司名称 | Company name displayed in report -->
|
||||
<add key="Report:CompanyName" value="海克斯康制造智能技术(青岛)有限公司" />
|
||||
<!-- 公司 Logo 图片路径(可选,为空则不显示)| Company logo path (optional, empty = no logo) -->
|
||||
<add key="Report:CompanyLogo" value="Templates\Logo.png" />
|
||||
<!-- 软件信息配置 | Software Information Configuration -->
|
||||
<add key="Report:SoftwareName" value="XplorePlane" />
|
||||
<add key="Report:SoftwareLogo" value="Templates\Logo2.png" />
|
||||
|
||||
</appSettings>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,74 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||
<Config>
|
||||
<Group ID="WriteCommon" DBNumber="31">
|
||||
<Signal Name="SoftLive" Type="byte" StartAddr="0" IndexOrLength="" Remark="软件心跳,01周期变化表示PLC存活" />
|
||||
<Signal Name="EmergencyStop" Type="byte" StartAddr="5" IndexOrLength="" Remark="急停,0:缺省,1:触发急停" />
|
||||
<Signal Name="MC_SourceZ_Target" Type="single" StartAddr="10" IndexOrLength="" Remark="射线源Z目标位置" />
|
||||
<Signal Name="MC_SourceZ_Speed" Type="single" StartAddr="14" IndexOrLength="" Remark="射线源Z运动速度" />
|
||||
<Signal Name="MC_SourceZ_JogPos" Type="byte" StartAddr="18" IndexOrLength="" Remark="射线源Z正向Jog,0:缺省,1:正向点动" />
|
||||
<Signal Name="MC_SourceZ_JogNeg" Type="byte" StartAddr="19" IndexOrLength="" Remark="射线源Z反向Jog,0:缺省,1:反向点动" />
|
||||
<Signal Name="MC_SourceZ_Home" Type="byte" StartAddr="20" IndexOrLength="" Remark="射线源Z回零,0:缺省,1:触发回零" />
|
||||
<Signal Name="MC_SourceZ_Stop" Type="byte" StartAddr="21" IndexOrLength="" Remark="射线源Z停止,0:缺省,1:触发停止" />
|
||||
<Signal Name="MC_DetZ_Target" Type="single" StartAddr="22" IndexOrLength="" Remark="探测器Z目标位置" />
|
||||
<Signal Name="MC_DetZ_Speed" Type="single" StartAddr="26" IndexOrLength="" Remark="探测器Z运动速度" />
|
||||
<Signal Name="MC_DetZ_JogPos" Type="byte" StartAddr="30" IndexOrLength="" Remark="探测器Z正向Jog,0:缺省,1:正向点动" />
|
||||
<Signal Name="MC_DetZ_JogNeg" Type="byte" StartAddr="31" IndexOrLength="" Remark="探测器Z反向Jog,0:缺省,1:反向点动" />
|
||||
<Signal Name="MC_DetZ_Home" Type="byte" StartAddr="32" IndexOrLength="" Remark="探测器Z回零,0:缺省,1:触发回零" />
|
||||
<Signal Name="MC_DetZ_Stop" Type="byte" StartAddr="33" IndexOrLength="" Remark="探测器Z停止,0:缺省,1:触发停止" />
|
||||
<Signal Name="MC_StageX_Target" Type="single" StartAddr="34" IndexOrLength="" Remark="载物台X目标位置" />
|
||||
<Signal Name="MC_StageX_Speed" Type="single" StartAddr="38" IndexOrLength="" Remark="载物台X运动速度" />
|
||||
<Signal Name="MC_StageX_JogPos" Type="byte" StartAddr="42" IndexOrLength="" Remark="载物台X正向Jog,0:缺省,1:正向点动" />
|
||||
<Signal Name="MC_StageX_JogNeg" Type="byte" StartAddr="43" IndexOrLength="" Remark="载物台X反向Jog,0:缺省,1:反向点动" />
|
||||
<Signal Name="MC_StageX_Home" Type="byte" StartAddr="44" IndexOrLength="" Remark="载物台X回零,0:缺省,1:触发回零" />
|
||||
<Signal Name="MC_StageX_Stop" Type="byte" StartAddr="45" IndexOrLength="" Remark="载物台X停止,0:缺省,1:触发停止" />
|
||||
<Signal Name="MC_StageY_Target" Type="single" StartAddr="46" IndexOrLength="" Remark="载物台Y目标位置" />
|
||||
<Signal Name="MC_StageY_Speed" Type="single" StartAddr="50" IndexOrLength="" Remark="载物台Y运动速度" />
|
||||
<Signal Name="MC_StageY_JogPos" Type="byte" StartAddr="54" IndexOrLength="" Remark="载物台Y正向Jog,0:缺省,1:正向点动" />
|
||||
<Signal Name="MC_StageY_JogNeg" Type="byte" StartAddr="55" IndexOrLength="" Remark="载物台Y反向Jog,0:缺省,1:反向点动" />
|
||||
<Signal Name="MC_StageY_Home" Type="byte" StartAddr="56" IndexOrLength="" Remark="载物台Y回零,0:缺省,1:触发回零" />
|
||||
<Signal Name="MC_StageY_Stop" Type="byte" StartAddr="57" IndexOrLength="" Remark="载物台Y停止,0:缺省,1:触发停止" />
|
||||
<Signal Name="MC_DetSwing_Target" Type="single" StartAddr="58" IndexOrLength="" Remark="探测器摆动目标角度" />
|
||||
<Signal Name="MC_DetSwing_Speed" Type="single" StartAddr="62" IndexOrLength="" Remark="探测器摆动运动速度" />
|
||||
<Signal Name="MC_DetSwing_JogPos" Type="byte" StartAddr="66" IndexOrLength="" Remark="探测器摆动正向Jog,0:缺省,1:正向点动" />
|
||||
<Signal Name="MC_DetSwing_JogNeg" Type="byte" StartAddr="67" IndexOrLength="" Remark="探测器摆动反向Jog,0:缺省,1:反向点动" />
|
||||
<Signal Name="MC_DetSwing_Home" Type="byte" StartAddr="68" IndexOrLength="" Remark="探测器摆动回零,0:缺省,1:触发回零" />
|
||||
<Signal Name="MC_DetSwing_Stop" Type="byte" StartAddr="69" IndexOrLength="" Remark="探测器摆动停止,0:缺省,1:触发停止" />
|
||||
<Signal Name="MC_StageRot_Target" Type="single" StartAddr="70" IndexOrLength="" Remark="载物台旋转目标角度" />
|
||||
<Signal Name="MC_StageRot_Speed" Type="single" StartAddr="74" IndexOrLength="" Remark="载物台旋转运动速度" />
|
||||
<Signal Name="MC_StageRot_JogPos" Type="byte" StartAddr="78" IndexOrLength="" Remark="载物台旋转正向Jog,0:缺省,1:正向点动" />
|
||||
<Signal Name="MC_StageRot_JogNeg" Type="byte" StartAddr="79" IndexOrLength="" Remark="载物台旋转反向Jog,0:缺省,1:反向点动" />
|
||||
<Signal Name="MC_StageRot_Home" Type="byte" StartAddr="80" IndexOrLength="" Remark="载物台旋转回零,0:缺省,1:触发回零" />
|
||||
<Signal Name="MC_StageRot_Stop" Type="byte" StartAddr="81" IndexOrLength="" Remark="载物台旋转停止,0:缺省,1:触发停止" />
|
||||
<Signal Name="MC_FixRot_Target" Type="single" StartAddr="82" IndexOrLength="" Remark="夹具旋转目标角度" />
|
||||
<Signal Name="MC_FixRot_Speed" Type="single" StartAddr="86" IndexOrLength="" Remark="夹具旋转运动速度" />
|
||||
<Signal Name="MC_FixRot_JogPos" Type="byte" StartAddr="90" IndexOrLength="" Remark="夹具旋转正向Jog,0:缺省,1:正向点动" />
|
||||
<Signal Name="MC_FixRot_JogNeg" Type="byte" StartAddr="91" IndexOrLength="" Remark="夹具旋转反向Jog,0:缺省,1:反向点动" />
|
||||
<Signal Name="MC_FixRot_Home" Type="byte" StartAddr="92" IndexOrLength="" Remark="夹具旋转回零,0:缺省,1:触发回零" />
|
||||
<Signal Name="MC_FixRot_Stop" Type="byte" StartAddr="93" IndexOrLength="" Remark="夹具旋转停止,0:缺省,1:触发停止" />
|
||||
<Signal Name="MC_Door_Open" Type="byte" StartAddr="94" IndexOrLength="" Remark="安全门开门,0:缺省,1:触发开门" />
|
||||
<Signal Name="MC_Door_Close" Type="byte" StartAddr="95" IndexOrLength="" Remark="安全门关门,0:缺省,1:触发关门" />
|
||||
<Signal Name="MC_Door_Stop" Type="byte" StartAddr="96" IndexOrLength="" Remark="安全门停止,0:缺省,1:触发停止" />
|
||||
<Signal Name="MC_VirtualJoystick_Enable" Type="bool" StartAddr="111" IndexOrLength="" Remark="虚拟摇杆使能"/>
|
||||
<Signal Name="MC_SourceDetZ_Linkage_Enable" Type="bool" StartAddr="101" IndexOrLength="" Remark="联动使能"/>
|
||||
</Group>
|
||||
<Group ID="ReadCommon" DBNumber="31">
|
||||
<Signal Name="ProbeA" Type="single" StartAddr="228" IndexOrLength="" Remark="测座角度A" />
|
||||
<Signal Name="ProbeB" Type="string" StartAddr="225" IndexOrLength="20" Remark="测座角度B" />
|
||||
<Signal Name="test" Type="byte" StartAddr="222" IndexOrLength="" Remark="" />
|
||||
<Signal Name="PlcLive" Type="byte" StartAddr="200" IndexOrLength="" Remark="PLC心跳,01周期变化表示PLC存活" />
|
||||
<Signal Name="PlcAlarm" Type="byte" StartAddr="201" IndexOrLength="" Remark="系统报警,0:缺省,10:有报警" />
|
||||
<Signal Name="MC_SourceZ_Pos" Type="single" StartAddr="100" IndexOrLength="" Remark="射线源Z实际位置" />
|
||||
<Signal Name="MC_DetZ_Pos" Type="single" StartAddr="104" IndexOrLength="" Remark="探测器Z实际位置" />
|
||||
<Signal Name="MC_StageX_Pos" Type="single" StartAddr="108" IndexOrLength="" Remark="载物台X实际位置" />
|
||||
<Signal Name="MC_StageY_Pos" Type="single" StartAddr="112" IndexOrLength="" Remark="载物台Y实际位置" />
|
||||
<Signal Name="MC_DetSwing_Angle" Type="single" StartAddr="116" IndexOrLength="" Remark="探测器摆动实际角度" />
|
||||
<Signal Name="MC_StageRot_Angle" Type="single" StartAddr="120" IndexOrLength="" Remark="载物台旋转实际角度" />
|
||||
<Signal Name="MC_FixRot_Angle" Type="single" StartAddr="124" IndexOrLength="" Remark="夹具旋转实际角度" />
|
||||
<Signal Name="MC_Door_Status" Type="byte" StartAddr="128" IndexOrLength="" Remark="安全门状态,0:未知,1:正在开门,2:已开,3:正在关门,4:已关,5:已锁定,6:故障" />
|
||||
<Signal Name="MC_Door_Interlock" Type="byte" StartAddr="130" IndexOrLength="" Remark="安全门联锁信号,0:缺省(无联锁),10:联锁有效(禁止开门)" />
|
||||
<Signal Name="MC_Joystick_Active" Type="bool" StartAddr="110" IndexOrLength="" Remark="实体摇杆输入"/>
|
||||
</Group>
|
||||
<Group ID="Status" DBNumber="4100">
|
||||
<Signal Name="ScanMode" Type="byte" StartAddr="201" IndexOrLength="" Remark="扫描模式,0:缺省(空闲),具体值由PLC定义" />
|
||||
</Group>
|
||||
</Config>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 121 KiB |
@@ -0,0 +1,222 @@
|
||||
{
|
||||
"document": {
|
||||
"pageSize": "A4",
|
||||
"orientation": "Portrait",
|
||||
"margins": { "top": 40, "bottom": 20, "left": 20, "right": 20 },
|
||||
"header": {
|
||||
"enabled": true,
|
||||
"left": [
|
||||
"${loc:Report_Title}",
|
||||
"${loc:Report_Id}:${metadata.reportId} | ${loc:Report_Date}:${formatDate(metadata.inspectionDate)} | ${loc:Report_Sample}:${metadata.sampleName}"
|
||||
],
|
||||
"rightImageKey": "companyLogo",
|
||||
"fontSize": 7,
|
||||
"color": "#666666",
|
||||
"showLine": true
|
||||
},
|
||||
"footer": {
|
||||
"enabled": true,
|
||||
"left": ["${CompanyName}"],
|
||||
"right": ["{currentPage} / {totalPages}"],
|
||||
"fontSize": 8,
|
||||
"color": "#666666",
|
||||
"showLine": true
|
||||
}
|
||||
},
|
||||
"pages": [
|
||||
{
|
||||
"type": "homepage",
|
||||
"elements": [
|
||||
{
|
||||
"type": "row", "size": [170, 40], "positioning": "flow",
|
||||
"children": [
|
||||
{
|
||||
"type": "column", "align": "left",
|
||||
"children": [
|
||||
{ "type": "image", "dataKey": "companyLogo", "size": [50, 30], "align": "left" },
|
||||
{ "type": "text", "content": "${CompanyName}", "style": "companyName", "align": "left" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "column", "align": "right",
|
||||
"children": [
|
||||
{ "type": "image", "dataKey": "softwareLogo", "size": [15, 20], "align": "right" },
|
||||
{ "type": "text", "content": "${SoftwareName}", "style": "companyName", "align": "right" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{ "type": "spacer", "size": [170, 15], "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Report_Title}", "style": "title", "positioning": "flow" },
|
||||
{ "type": "spacer", "size": [170, 10], "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Report_Id}:${metadata.reportId}", "style": "homepageInfo", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Report_Sample}:${metadata.sampleName}", "style": "homepageInfo", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Report_Description}:${metadata.description}", "style": "homepageInfo", "positioning": "flow" },
|
||||
{ "type": "image", "dataKey": "workpieceImage", "size": [160, 110], "border": true, "align": "center", "style": "imageDefault", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Report_Operator}:${metadata.operatorName}", "style": "homepageFooter", "positioning": "flow" },
|
||||
{ "type": "text", "content": "${loc:Report_Date}:${formatDate(metadata.inspectionDate)}", "style": "homepageFooter", "positioning": "flow" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "summary",
|
||||
"elements": [
|
||||
{ "type": "text", "content": "${loc:Report_Summary}", "style": "homepageHeading", "positioning": "flow" },
|
||||
{
|
||||
"type": "table", "dataKey": "summaryTable", "positioning": "flow", "size": [170, 0], "style": "tableDefault",
|
||||
"columns": [
|
||||
{ "header": "${loc:Field_InspectionType}", "field": "inspectionType", "width": 50, "align": "left" },
|
||||
{ "header": "${loc:Field_Result}", "field": "classification", "width": 30, "align": "center", "colorRules": { "Pass": "#008000", "Fail": "#FF0000" } },
|
||||
{ "header": "${loc:Field_Status}", "field": "status", "width": 30, "align": "center", "colorRules": { "合格": "#008000", "PASS": "#008000", "不合格": "#FF0000", "FAIL": "#FF0000" } }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "metricData",
|
||||
"elements": [
|
||||
{ "type": "text", "content": "${loc:Page_MetricData}", "style": "heading", "positioning": "flow" },
|
||||
{
|
||||
"type": "row", "size": [170, 100], "widths": [6, 4], "positioning": "flow",
|
||||
"children": [
|
||||
{
|
||||
"type": "column", "align": "left",
|
||||
"children": [
|
||||
{ "type": "image", "dataKey": "lineMeasurementImage", "size": [110, 90], "border": true, "align": "left" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "column", "align": "left",
|
||||
"children": [
|
||||
{ "type": "text", "content": "${loc:Measurement_Type}:${measurementType}", "style": "body", "align": "left" },
|
||||
{ "type": "text", "content": "${loc:Field_Point1}:${point1}", "style": "body", "align": "left" },
|
||||
{ "type": "text", "content": "${loc:Field_Point2}:${point2}", "style": "body", "align": "left" },
|
||||
{ "type": "text", "content": "${loc:Measurement_Distance}:${actualDistance} ${unit}", "style": "body", "align": "left" },
|
||||
{ "type": "text", "content": "${loc:Measurement_Angle}:${angle}°", "style": "body", "align": "left" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "bgaInspection",
|
||||
"elements": [
|
||||
{ "type": "text", "content": "${loc:Page_BgaInspection}", "style": "heading", "positioning": "flow" },
|
||||
{
|
||||
"type": "row", "size": [170, 100], "widths": [6, 4], "positioning": "flow",
|
||||
"children": [
|
||||
{
|
||||
"type": "column", "align": "left",
|
||||
"children": [
|
||||
{ "type": "image", "dataKey": "bgaInspectionImage", "size": [110, 90], "border": true, "align": "left" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "column", "align": "left",
|
||||
"children": [
|
||||
{ "type": "text", "content": "${loc:Bga_Count}:${bgaCount}", "style": "body", "align": "left" },
|
||||
{ "type": "text", "content": "${loc:Measurement_VoidRate}:${formatPercent(voidRate)}", "style": "body", "align": "left" },
|
||||
{ "type": "text", "content": "${loc:Measurement_FillRate}:${formatPercent(fillRate)}", "style": "body", "align": "left" },
|
||||
{ "type": "text", "content": "${loc:Bga_TotalArea}:${formatNumber(totalBgaArea, 2)}", "style": "body", "align": "left" },
|
||||
{ "type": "text", "content": "${loc:Bga_TotalVoidArea}:${formatNumber(totalVoidArea, 2)}", "style": "body", "align": "left" },
|
||||
{ "type": "text", "content": "${loc:Bga_VoidLimit}:${formatPercent(voidLimit)}", "style": "body", "align": "left" },
|
||||
{ "type": "text", "content": "${loc:Table_Classification}:${classification}", "style": "conclusion", "align": "left", "colorRules": { "Pass": "#008000", "Fail": "#FF0000" } }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "table", "dataKey": "bgaBallsTable", "positioning": "flow", "size": [170, 0], "style": "tableDefault",
|
||||
"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", "colorRules": { "Pass": "#008000", "Fail": "#FF0000" } }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "voidInspection",
|
||||
"elements": [
|
||||
{ "type": "text", "content": "${loc:Page_VoidInspection}", "style": "heading", "positioning": "flow" },
|
||||
{
|
||||
"type": "row", "size": [170, 100], "widths": [6, 4], "positioning": "flow",
|
||||
"children": [
|
||||
{
|
||||
"type": "column", "align": "left",
|
||||
"children": [
|
||||
{ "type": "image", "dataKey": "voidInspectionImage", "size": [110, 90], "border": true, "align": "left" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "column", "align": "left",
|
||||
"children": [
|
||||
{ "type": "text", "content": "${loc:Void_RoiArea}:${formatNumber(roiArea, 2)}", "style": "body", "align": "left" },
|
||||
{ "type": "text", "content": "${loc:Void_TotalArea}:${formatNumber(totalVoidArea, 2)}", "style": "body", "align": "left" },
|
||||
{ "type": "text", "content": "${loc:Measurement_VoidRate}:${formatPercent(voidRate)}", "style": "body", "align": "left" },
|
||||
{ "type": "text", "content": "${loc:Void_Limit}:${formatPercent(voidLimit)}", "style": "body", "align": "left" },
|
||||
{ "type": "text", "content": "${loc:Void_Count}:${voidCount}", "style": "body", "align": "left" },
|
||||
{ "type": "text", "content": "${loc:Void_MaxArea}:${formatNumber(maxVoidArea, 2)}", "style": "body", "align": "left" },
|
||||
{ "type": "text", "content": "${loc:Table_Classification}:${classification}", "style": "conclusion", "align": "left", "colorRules": { "Pass": "#008000", "Fail": "#FF0000" } }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "table", "dataKey": "voidsTable", "positioning": "flow", "size": [170, 0], "style": "tableDefault",
|
||||
"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": "row", "size": [170, 100], "widths": [6, 4], "positioning": "flow",
|
||||
"children": [
|
||||
{
|
||||
"type": "column", "align": "left",
|
||||
"children": [
|
||||
{ "type": "image", "dataKey": "viaFillImage", "size": [110, 90], "border": true, "align": "left" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "column", "align": "left",
|
||||
"children": [
|
||||
{ "type": "text", "content": "${loc:Fill_Rate}:${formatPercent(fillRate)}", "style": "body", "align": "left" },
|
||||
{ "type": "text", "content": "${loc:Measurement_VoidRate}:${formatPercent(voidRate)}", "style": "body", "align": "left" },
|
||||
{ "type": "text", "content": "${loc:Fill_FullDistance}:${formatNumber(fullDistance, 2)}", "style": "body", "align": "left" },
|
||||
{ "type": "text", "content": "${loc:Fill_FillDistance}:${formatNumber(fillDistance, 2)}", "style": "body", "align": "left" },
|
||||
{ "type": "text", "content": "${loc:Fill_THTLimit}:${formatPercent(thtLimit)}", "style": "body", "align": "left" },
|
||||
{ "type": "text", "content": "${loc:Table_Classification}:${classification}", "style": "conclusion", "align": "left", "colorRules": { "Pass": "#008000", "Fail": "#FF0000" } }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"styles": {
|
||||
"title": { "font": "auto", "size": 28, "bold": true, "italic": false, "color": "#1a1a1a", "align": "center" },
|
||||
"companyName": { "font": "auto", "size": 10, "bold": false, "italic": false, "color": "#333333", "align": "center" },
|
||||
"homepageInfo": { "font": "auto", "size": 12, "bold": false, "italic": false, "color": "#333333", "align": "left", "paddingLeft": 10 },
|
||||
"homepageFooter": { "font": "auto", "size": 12, "bold": false, "italic": false, "color": "#333333", "align": "center" },
|
||||
"homepageHeading": { "font": "auto", "size": 14, "bold": true, "italic": false, "color": "#333333", "align": "left" },
|
||||
"heading": { "font": "auto", "size": 16, "bold": true, "italic": false, "color": "#333333", "align": "left", "marginBottom": 3 },
|
||||
"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" },
|
||||
"conclusion": { "font": "auto", "size": 12, "bold": true, "italic": false, "color": "#333333", "align": "left", "marginTop": 3 },
|
||||
"imageDefault": { "font": "auto", "size": 12, "bold": false, "italic": false, "color": "#333333", "align": "center", "marginTop": 5, "marginBottom": 5 },
|
||||
"tableHeader": { "font": "auto", "size": 10, "bold": true, "italic": false, "color": "#ffffff", "align": "center", "backgroundColor": "#4472C4" },
|
||||
"tableDefault": { "font": "auto", "size": 10, "bold": false, "italic": false, "color": "#333333", "align": "center", "marginTop": 5 },
|
||||
"tableCell": { "font": "auto", "size": 10, "bold": false, "italic": false, "color": "#333333", "align": "center" }
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+806
-1318
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -7,7 +7,10 @@
|
||||
<appSettings>
|
||||
<!-- 语言配置 可选值: ZhCN, ZhTW, EnUS| Language Configuration -->
|
||||
<add key="Language" value="ZhCN" />
|
||||
<add key="UserManual" value="D:\HMQProject\XplorePlane_CT\Code\XplorePlane\XP.App\bin\Debug\net8.0-windows7.0\UserManual.pdf" />
|
||||
<add key="XpData:RootPath" value="D:\XPData" />
|
||||
<add key="UserManual" value="UserManual.pdf" />
|
||||
<add key="DeviceId" value="PlanerCT001" />
|
||||
|
||||
|
||||
<!-- Serilog日志配置 -->
|
||||
<add key="Serilog:LogPath" value="D:\XplorePlane\Logs" />
|
||||
@@ -122,7 +125,6 @@
|
||||
<!-- 自动重连 | Auto Reconnection -->
|
||||
<add key="Plc:bReConnect" value="true" />
|
||||
|
||||
<!-- ==================== 运动控制配置 | Motion Control Configuration ==================== -->
|
||||
<!-- 直线轴配置(单位:mm)| Linear axis config (unit: mm) -->
|
||||
<add key="MotionControl:SourceZ:Min" value="-500" />
|
||||
<add key="MotionControl:SourceZ:Max" value="500" />
|
||||
@@ -148,7 +150,7 @@
|
||||
<add key="MotionControl:FixtureRotation:Min" value="-90" />
|
||||
<add key="MotionControl:FixtureRotation:Max" value="90" />
|
||||
<add key="MotionControl:FixtureRotation:Origin" value="0" />
|
||||
<add key="MotionControl:FixtureRotation:Enabled" value="true" />
|
||||
<add key="MotionControl:FixtureRotation:Enabled" value="false" />
|
||||
<!-- 几何原点(mm)| Geometry origins (mm) -->
|
||||
<add key="MotionControl:Geometry:SourceZOrigin" value="0" />
|
||||
<add key="MotionControl:Geometry:DetectorZOrigin" value="600" />
|
||||
@@ -161,6 +163,34 @@
|
||||
<!-- 运行参数 | Runtime parameters -->
|
||||
<add key="MotionControl:PollingInterval" value="500" />
|
||||
<add key="MotionControl:DefaultVelocity" value="500" />
|
||||
|
||||
<!-- 报告输出文件夹路径(绝对路径)| Report output directory (absolute path) -->
|
||||
<add key="Report:OutputDirectory" value="D:\XplorePlane\Report" />
|
||||
<!-- 报告模板文件路径(相对于应用程序目录或绝对路径)| Template file path (relative to app dir or absolute) -->
|
||||
<add key="Report:TemplatePath" value="Templates\StandardReportTemplate.json" />
|
||||
<!-- 输出文件名模式 | File name pattern -->
|
||||
<add key="Report:FileNamePattern" value="{Date}_{ProductName}_{WorkpieceSN}_{ReportId}" />
|
||||
<!-- 文件名重复时是否自动累加序号 | Auto-increment suffix when file name duplicates-->
|
||||
<add key="Report:AutoIncrementOnDuplicate" value="true" />
|
||||
<!-- 生成后是否自动打开 PDF 阅读器 | Auto-open PDF viewer after generation (true/false) -->
|
||||
<add key="Report:AutoOpenAfterGenerate" value="false" />
|
||||
<!-- 默认页面尺寸 | Default page size (A4) -->
|
||||
<add key="Report:DefaultPageSize" value="A4" />
|
||||
<!-- 默认页面方向 | Default orientation (Portrait / Landscape) -->
|
||||
<add key="Report:DefaultOrientation" value="Portrait" />
|
||||
<!-- 页面边距(mm),有效范围 0-100 | Page margins (mm), valid range 0-100 -->
|
||||
<add key="Report:MarginTop" value="20" />
|
||||
<add key="Report:MarginBottom" value="20" />
|
||||
<add key="Report:MarginLeft" value="20" />
|
||||
<add key="Report:MarginRight" value="20" />
|
||||
<!-- 报告中显示的公司名称 | Company name displayed in report -->
|
||||
<add key="Report:CompanyName" value="海克斯康制造智能技术(青岛)有限公司" />
|
||||
<!-- 公司 Logo 图片路径(可选,为空则不显示)| Company logo path (optional, empty = no logo) -->
|
||||
<add key="Report:CompanyLogo" value="Templates\Logo.png" />
|
||||
<!-- 软件信息配置 | Software Information Configuration -->
|
||||
<add key="Report:SoftwareName" value="XplorePlane" />
|
||||
<add key="Report:SoftwareLogo" value="Templates\Logo2.png" />
|
||||
|
||||
</appSettings>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -12,7 +12,8 @@
|
||||
}
|
||||
],
|
||||
"configProperties": {
|
||||
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": true
|
||||
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": true,
|
||||
"CSWINRT_USE_WINDOWS_UI_XAML_PROJECTIONS": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// Flag, ob die Trace-Meldungen asynchron zu protokolliern sind: 1 für asynchron, 0 für synchron.
|
||||
1
|
||||
// Kategorie Konfigurationen: <Kategorie-Name> <Enabled[0/1]>
|
||||
FXDriver 0
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,221 @@
|
||||
using System;
|
||||
using System.Configuration;
|
||||
using System.IO;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
|
||||
namespace XP.ReportEngine.Configs
|
||||
{
|
||||
/// <summary>
|
||||
/// 报告引擎配置加载器(读取 App.config)| Report engine configuration loader (reads from App.config)
|
||||
/// </summary>
|
||||
public class ConfigLoader
|
||||
{
|
||||
private readonly ILoggerService _logger;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数 | Constructor
|
||||
/// </summary>
|
||||
/// <param name="logger">日志服务 | Logger service</param>
|
||||
public ConfigLoader(ILoggerService logger)
|
||||
{
|
||||
_logger = logger.ForModule<ConfigLoader>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 App.config 加载报告引擎配置 | Load report engine configuration from App.config
|
||||
/// </summary>
|
||||
/// <param name="prefix">配置前缀,默认为 "Report" | Configuration prefix, default is "Report"</param>
|
||||
/// <returns>报告配置对象 | Report configuration object</returns>
|
||||
public ReportConfig LoadReportConfig(string prefix = "Report")
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Info("开始从 App.config 加载报告引擎配置,前缀: {Prefix} | Loading report config from App.config, prefix: {Prefix}", prefix);
|
||||
|
||||
var config = new ReportConfig();
|
||||
|
||||
// 读取输出目录 | Read output directory
|
||||
var outputDirectory = ConfigurationManager.AppSettings[$"{prefix}:OutputDirectory"];
|
||||
if (!string.IsNullOrEmpty(outputDirectory))
|
||||
{
|
||||
config.OutputDirectory = outputDirectory;
|
||||
}
|
||||
|
||||
// 读取模板路径 | Read template path
|
||||
var templatePath = ConfigurationManager.AppSettings[$"{prefix}:TemplatePath"];
|
||||
if (!string.IsNullOrEmpty(templatePath))
|
||||
{
|
||||
config.TemplatePath = templatePath;
|
||||
}
|
||||
|
||||
// 读取文件名模式 | Read file name pattern
|
||||
var fileNamePattern = ConfigurationManager.AppSettings[$"{prefix}:FileNamePattern"];
|
||||
if (!string.IsNullOrEmpty(fileNamePattern))
|
||||
{
|
||||
config.FileNamePattern = fileNamePattern;
|
||||
}
|
||||
|
||||
// 读取重复文件名自动累加设置 | Read auto-increment on duplicate setting
|
||||
var autoIncrement = ConfigurationManager.AppSettings[$"{prefix}:AutoIncrementOnDuplicate"];
|
||||
if (bool.TryParse(autoIncrement, out var autoIncrementValue))
|
||||
{
|
||||
config.AutoIncrementOnDuplicate = autoIncrementValue;
|
||||
}
|
||||
|
||||
// 读取自动打开设置 | Read auto-open setting
|
||||
var autoOpen = ConfigurationManager.AppSettings[$"{prefix}:AutoOpenAfterGenerate"];
|
||||
if (bool.TryParse(autoOpen, out var autoOpenValue))
|
||||
{
|
||||
config.AutoOpenAfterGenerate = autoOpenValue;
|
||||
}
|
||||
|
||||
// 读取页面尺寸 | Read page size
|
||||
var pageSize = ConfigurationManager.AppSettings[$"{prefix}:DefaultPageSize"];
|
||||
if (!string.IsNullOrEmpty(pageSize))
|
||||
{
|
||||
config.DefaultPageSize = pageSize;
|
||||
}
|
||||
|
||||
// 读取页面方向 | Read page orientation
|
||||
var orientation = ConfigurationManager.AppSettings[$"{prefix}:DefaultOrientation"];
|
||||
if (!string.IsNullOrEmpty(orientation))
|
||||
{
|
||||
config.DefaultOrientation = orientation;
|
||||
}
|
||||
|
||||
// 读取边距配置 | Read margin configuration
|
||||
var marginTop = ConfigurationManager.AppSettings[$"{prefix}:MarginTop"];
|
||||
if (float.TryParse(marginTop, out var marginTopValue))
|
||||
{
|
||||
config.MarginTop = marginTopValue;
|
||||
}
|
||||
|
||||
var marginBottom = ConfigurationManager.AppSettings[$"{prefix}:MarginBottom"];
|
||||
if (float.TryParse(marginBottom, out var marginBottomValue))
|
||||
{
|
||||
config.MarginBottom = marginBottomValue;
|
||||
}
|
||||
|
||||
var marginLeft = ConfigurationManager.AppSettings[$"{prefix}:MarginLeft"];
|
||||
if (float.TryParse(marginLeft, out var marginLeftValue))
|
||||
{
|
||||
config.MarginLeft = marginLeftValue;
|
||||
}
|
||||
|
||||
var marginRight = ConfigurationManager.AppSettings[$"{prefix}:MarginRight"];
|
||||
if (float.TryParse(marginRight, out var marginRightValue))
|
||||
{
|
||||
config.MarginRight = marginRightValue;
|
||||
}
|
||||
|
||||
// 读取公司名称 | Read company name
|
||||
var companyName = ConfigurationManager.AppSettings[$"{prefix}:CompanyName"];
|
||||
if (!string.IsNullOrEmpty(companyName))
|
||||
{
|
||||
config.CompanyName = companyName;
|
||||
}
|
||||
|
||||
// 读取公司 Logo 路径 | Read company logo path
|
||||
var companyLogo = ConfigurationManager.AppSettings[$"{prefix}:CompanyLogo"];
|
||||
if (!string.IsNullOrEmpty(companyLogo))
|
||||
{
|
||||
config.CompanyLogo = companyLogo;
|
||||
}
|
||||
|
||||
// 读取软件名称 | Read software name
|
||||
var softwareName = ConfigurationManager.AppSettings[$"{prefix}:SoftwareName"];
|
||||
if (!string.IsNullOrEmpty(softwareName))
|
||||
{
|
||||
config.SoftwareName = softwareName;
|
||||
}
|
||||
|
||||
// 读取软件 Logo 路径 | Read software logo path
|
||||
var softwareLogo = ConfigurationManager.AppSettings[$"{prefix}:SoftwareLogo"];
|
||||
if (!string.IsNullOrEmpty(softwareLogo))
|
||||
{
|
||||
config.SoftwareLogo = softwareLogo;
|
||||
}
|
||||
|
||||
// 验证配置 | Validate configuration
|
||||
ValidateConfig(config);
|
||||
|
||||
_logger.Info("报告引擎配置加载成功:输出目录={OutputDir}, 模板={Template} | Report config loaded: OutputDir={OutputDir}, Template={Template}",
|
||||
config.OutputDirectory, config.TemplatePath);
|
||||
|
||||
return config;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn("加载报告引擎配置失败,使用默认配置 | Failed to load report config, using defaults: {Message}", ex.Message);
|
||||
return new ReportConfig();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证配置参数 | Validate configuration parameters
|
||||
/// </summary>
|
||||
/// <param name="config">报告配置对象 | Report configuration object</param>
|
||||
private void ValidateConfig(ReportConfig config)
|
||||
{
|
||||
// 验证输出目录(不存在则尝试创建)| Validate output directory (create if not exists)
|
||||
if (string.IsNullOrWhiteSpace(config.OutputDirectory))
|
||||
{
|
||||
_logger.Warn("OutputDirectory 为空,使用默认值 | OutputDirectory is empty, using default");
|
||||
config.OutputDirectory = new ReportConfig().OutputDirectory;
|
||||
}
|
||||
|
||||
// 验证模板路径 | Validate template path
|
||||
if (string.IsNullOrWhiteSpace(config.TemplatePath))
|
||||
{
|
||||
_logger.Warn("TemplatePath 为空,使用默认值 | TemplatePath is empty, using default");
|
||||
config.TemplatePath = @"Templates\StandardReportTemplate.json";
|
||||
}
|
||||
|
||||
// 验证文件名模式 | Validate file name pattern
|
||||
if (string.IsNullOrWhiteSpace(config.FileNamePattern))
|
||||
{
|
||||
_logger.Warn("FileNamePattern 为空,使用默认值 | FileNamePattern is empty, using default");
|
||||
config.FileNamePattern = "{ReportId}";
|
||||
}
|
||||
|
||||
// 验证页面方向 | Validate page orientation
|
||||
if (config.DefaultOrientation != "Portrait" && config.DefaultOrientation != "Landscape")
|
||||
{
|
||||
_logger.Warn("DefaultOrientation 无效: {Value},使用默认值 Portrait | DefaultOrientation invalid: {Value}, using default Portrait", config.DefaultOrientation);
|
||||
config.DefaultOrientation = "Portrait";
|
||||
}
|
||||
|
||||
// 验证边距范围(0-100mm)| Validate margin range (0-100mm)
|
||||
config.MarginTop = ClampMargin(config.MarginTop, "MarginTop");
|
||||
config.MarginBottom = ClampMargin(config.MarginBottom, "MarginBottom");
|
||||
config.MarginLeft = ClampMargin(config.MarginLeft, "MarginLeft");
|
||||
config.MarginRight = ClampMargin(config.MarginRight, "MarginRight");
|
||||
|
||||
// 验证 Logo 路径(如果配置了则检查文件是否存在)| Validate logo path (check file exists if configured)
|
||||
if (!string.IsNullOrEmpty(config.CompanyLogo))
|
||||
{
|
||||
var logoPath = Path.IsPathRooted(config.CompanyLogo)
|
||||
? config.CompanyLogo
|
||||
: Path.Combine(AppDomain.CurrentDomain.BaseDirectory, config.CompanyLogo);
|
||||
|
||||
if (!File.Exists(logoPath))
|
||||
{
|
||||
_logger.Warn("公司 Logo 文件不存在: {Path},将不显示 Logo | Company logo file not found: {Path}, logo will not be displayed", logoPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将边距值限制在有效范围内 | Clamp margin value to valid range
|
||||
/// </summary>
|
||||
private float ClampMargin(float value, string name)
|
||||
{
|
||||
if (value < 0 || value > 100)
|
||||
{
|
||||
_logger.Warn("{Name} 超出有效范围 [0, 100]: {Value},使用默认值 20 | {Name} out of valid range [0, 100]: {Value}, using default 20", name, value);
|
||||
return 20f;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace XP.ReportEngine.Configs
|
||||
{
|
||||
/// <summary>
|
||||
/// 报告引擎配置模型 | Report engine configuration model
|
||||
/// </summary>
|
||||
public class ReportConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// 报告输出文件夹路径 | Report output directory path
|
||||
/// </summary>
|
||||
public string OutputDirectory { get; set; } = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
|
||||
"XplorePlane", "Reports");
|
||||
|
||||
/// <summary>
|
||||
/// 报告模板文件路径(相对或绝对)| Report template file path (relative or absolute)
|
||||
/// </summary>
|
||||
public string TemplatePath { get; set; } = @"Templates\StandardReportTemplate.json";
|
||||
|
||||
/// <summary>
|
||||
/// 输出文件名模式,支持占位符 | Output file name pattern, supports placeholders
|
||||
/// 支持的占位符 | Supported placeholders:
|
||||
/// {ReportId} - 报告编号(如 RPT-20250512-001)
|
||||
/// {CncProgram} - CNC 程序名称
|
||||
/// {ProductName} - 产品名称
|
||||
/// {ProductCode} - 产品类型码
|
||||
/// {WorkpieceSN} - 工件 SN 码
|
||||
/// {DeviceId} - 检测设备编号(本机)
|
||||
/// {MachineId} - 生产机台号
|
||||
/// {Date} - 日期(yyyyMMdd)
|
||||
/// {Time} - 时间(HHmmss)
|
||||
/// {Result} - 综合检测结论(Pass/Fail)
|
||||
/// </summary>
|
||||
public string FileNamePattern { get; set; } = "{ReportId}";
|
||||
|
||||
/// <summary>
|
||||
/// 文件名重复时是否自动累加序号 | Whether to auto-increment suffix when file name duplicates
|
||||
/// true: 重复时生成 filename(1).pdf, filename(2).pdf ...
|
||||
/// false: 直接覆盖同名文件
|
||||
/// </summary>
|
||||
public bool AutoIncrementOnDuplicate { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 生成后是否自动打开 PDF 阅读器 | Whether to auto-open PDF viewer after generation
|
||||
/// </summary>
|
||||
public bool AutoOpenAfterGenerate { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 默认页面尺寸 | Default page size
|
||||
/// </summary>
|
||||
public string DefaultPageSize { get; set; } = "A4";
|
||||
|
||||
/// <summary>
|
||||
/// 默认页面方向(Portrait / Landscape)| Default page orientation
|
||||
/// </summary>
|
||||
public string DefaultOrientation { get; set; } = "Portrait";
|
||||
|
||||
/// <summary>
|
||||
/// 默认上边距(mm)| Default top margin (mm)
|
||||
/// </summary>
|
||||
public float MarginTop { get; set; } = 20f;
|
||||
|
||||
/// <summary>
|
||||
/// 默认下边距(mm)| Default bottom margin (mm)
|
||||
/// </summary>
|
||||
public float MarginBottom { get; set; } = 20f;
|
||||
|
||||
/// <summary>
|
||||
/// 默认左边距(mm)| Default left margin (mm)
|
||||
/// </summary>
|
||||
public float MarginLeft { get; set; } = 20f;
|
||||
|
||||
/// <summary>
|
||||
/// 默认右边距(mm)| Default right margin (mm)
|
||||
/// </summary>
|
||||
public float MarginRight { get; set; } = 20f;
|
||||
|
||||
/// <summary>
|
||||
/// 报告中显示的公司名称 | Company name displayed in report
|
||||
/// </summary>
|
||||
public string CompanyName { get; set; } = "海克斯康制造智能技术(青岛)有限公司";
|
||||
|
||||
/// <summary>
|
||||
/// 公司 Logo 图片路径(可选,为空则不显示)| Company logo image path (optional, empty means no logo)
|
||||
/// </summary>
|
||||
public string CompanyLogo { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 报告中显示的软件名称 | Software name displayed in report
|
||||
/// </summary>
|
||||
public string SoftwareName { get; set; } = "XplorePlane";
|
||||
|
||||
/// <summary>
|
||||
/// 软件 Logo 图片路径(可选,为空则不显示)| Software logo image path (optional, empty means no logo)
|
||||
/// </summary>
|
||||
public string SoftwareLogo { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 获取解析后的模板绝对路径 | Get resolved absolute template path
|
||||
/// 如果 TemplatePath 是相对路径,则基于应用程序目录解析
|
||||
/// If TemplatePath is relative, resolves based on application directory
|
||||
/// </summary>
|
||||
public string GetResolvedTemplatePath()
|
||||
{
|
||||
if (Path.IsPathRooted(TemplatePath))
|
||||
{
|
||||
return TemplatePath;
|
||||
}
|
||||
return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, TemplatePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据文件名模式和上下文参数生成实际文件名 | Generate actual file name based on pattern and context parameters
|
||||
/// </summary>
|
||||
/// <param name="parameters">占位符参数字典 | Placeholder parameter dictionary</param>
|
||||
/// <returns>生成的文件名(不含扩展名)| Generated file name (without extension)</returns>
|
||||
public string ResolveFileName(Dictionary<string, string> parameters)
|
||||
{
|
||||
var fileName = FileNamePattern;
|
||||
|
||||
// 替换所有已知占位符 | Replace all known placeholders
|
||||
if (parameters != null)
|
||||
{
|
||||
foreach (var kvp in parameters)
|
||||
{
|
||||
fileName = fileName.Replace($"{{{kvp.Key}}}", SanitizeFileName(kvp.Value ?? ""));
|
||||
}
|
||||
}
|
||||
|
||||
// 替换日期和时间(始终可用)| Replace date and time (always available)
|
||||
fileName = fileName.Replace("{Date}", DateTime.Now.ToString("yyyyMMdd"));
|
||||
fileName = fileName.Replace("{Time}", DateTime.Now.ToString("HHmmss"));
|
||||
|
||||
// 清理未被替换的占位符(替换为空)| Clean up unreplaced placeholders
|
||||
fileName = System.Text.RegularExpressions.Regex.Replace(fileName, @"\{[^}]+\}", "");
|
||||
|
||||
// 移除连续的分隔符 | Remove consecutive separators
|
||||
fileName = System.Text.RegularExpressions.Regex.Replace(fileName, @"[_\-]{2,}", "_");
|
||||
fileName = fileName.Trim('_', '-');
|
||||
|
||||
return string.IsNullOrWhiteSpace(fileName) ? "Report" : fileName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析最终输出文件完整路径(含重复累加逻辑)| Resolve final output file full path (with duplicate increment logic)
|
||||
/// </summary>
|
||||
/// <param name="parameters">占位符参数字典 | Placeholder parameter dictionary</param>
|
||||
/// <param name="extension">文件扩展名(含点号,如 ".pdf")| File extension (with dot, e.g. ".pdf")</param>
|
||||
/// <returns>最终输出文件完整路径 | Final output file full path</returns>
|
||||
public string ResolveOutputFilePath(Dictionary<string, string> parameters, string extension = ".pdf")
|
||||
{
|
||||
var baseName = ResolveFileName(parameters);
|
||||
var outputDir = OutputDirectory;
|
||||
|
||||
// 确保输出目录存在 | Ensure output directory exists
|
||||
if (!Directory.Exists(outputDir))
|
||||
{
|
||||
Directory.CreateDirectory(outputDir);
|
||||
}
|
||||
|
||||
var filePath = Path.Combine(outputDir, baseName + extension);
|
||||
|
||||
// 重复累加逻辑 | Duplicate increment logic
|
||||
if (AutoIncrementOnDuplicate && File.Exists(filePath))
|
||||
{
|
||||
int counter = 1;
|
||||
string newPath;
|
||||
do
|
||||
{
|
||||
newPath = Path.Combine(outputDir, $"{baseName}({counter}){extension}");
|
||||
counter++;
|
||||
} while (File.Exists(newPath));
|
||||
|
||||
filePath = newPath;
|
||||
}
|
||||
|
||||
return filePath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理文件名中的非法字符 | Sanitize illegal characters in file name
|
||||
/// </summary>
|
||||
private static string SanitizeFileName(string name)
|
||||
{
|
||||
var invalidChars = Path.GetInvalidFileNameChars();
|
||||
foreach (var c in invalidChars)
|
||||
{
|
||||
name = name.Replace(c, '_');
|
||||
}
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<appSettings>
|
||||
<!-- ============================================================ -->
|
||||
<!-- 报告引擎配置 | Report Engine Configuration -->
|
||||
<!-- 前缀: Report -->
|
||||
<!-- ============================================================ -->
|
||||
|
||||
<!-- 报告输出文件夹路径(绝对路径)| Report output directory (absolute path) -->
|
||||
<!-- 默认值: {我的文档}\XplorePlane\Reports | Default: {MyDocuments}\XplorePlane\Reports -->
|
||||
<add key="Report:OutputDirectory" value="D:\Reports\XplorePlane" />
|
||||
|
||||
<!-- 报告模板文件路径(相对于应用程序目录或绝对路径)| Template file path (relative to app dir or absolute) -->
|
||||
<!-- 默认值: Templates\StandardReportTemplate.json -->
|
||||
<add key="Report:TemplatePath" value="Templates\StandardReportTemplate.json" />
|
||||
|
||||
<!--
|
||||
输出文件名模式 | File name pattern
|
||||
支持以下占位符 | Supported placeholders:
|
||||
{ReportId} - 报告编号(如 RPT-20250512-001)| Report ID (e.g. RPT-20250512-001)
|
||||
{CncProgram} - CNC 程序名称 | CNC program name
|
||||
{ProductName} - 产品名称 | Product name
|
||||
{ProductCode} - 产品类型码 | Product type code
|
||||
{WorkpieceSN} - 工件 SN 码 | Workpiece serial number
|
||||
{DeviceId} - 检测设备编号(本机)| Inspection device ID (local machine)
|
||||
{MachineId} - 生产机台号 | Production machine ID
|
||||
{Date} - 日期(yyyyMMdd)| Date (yyyyMMdd)
|
||||
{Time} - 时间(HHmmss)| Time (HHmmss)
|
||||
{Result} - 综合检测结论(Pass/Fail)| Overall inspection result (Pass/Fail)
|
||||
|
||||
示例 | Examples:
|
||||
"{ReportId}" → RPT-20250512-001.pdf
|
||||
"{Date}_{ProductName}_{WorkpieceSN}" → 20250512_PCB-A01_SN20250001.pdf
|
||||
"{ProductCode}_{WorkpieceSN}_{Result}" → PCBA-X100_SN20250001_Pass.pdf
|
||||
"{MachineId}_{CncProgram}_{Date}_{Time}" → MC01_Prog001_20250512_143025.pdf
|
||||
"{DeviceId}_{Date}_{ReportId}" → XP-CT-001_20250512_RPT-20250512-001.pdf
|
||||
-->
|
||||
<add key="Report:FileNamePattern" value="{Date}_{ProductName}_{WorkpieceSN}_{ReportId}" />
|
||||
|
||||
<!--
|
||||
文件名重复时是否自动累加序号 | Auto-increment suffix when file name duplicates
|
||||
true: 重复时生成 filename(1).pdf, filename(2).pdf ... 不覆盖已有文件
|
||||
false: 直接覆盖同名文件
|
||||
-->
|
||||
<add key="Report:AutoIncrementOnDuplicate" value="true" />
|
||||
|
||||
<!-- 生成后是否自动打开 PDF 阅读器 | Auto-open PDF viewer after generation (true/false) -->
|
||||
<add key="Report:AutoOpenAfterGenerate" value="false" />
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 页面布局配置 | Page Layout Configuration -->
|
||||
<!-- ============================================================ -->
|
||||
|
||||
<!-- 默认页面尺寸 | Default page size (A4) -->
|
||||
<add key="Report:DefaultPageSize" value="A4" />
|
||||
|
||||
<!-- 默认页面方向 | Default orientation (Portrait / Landscape) -->
|
||||
<add key="Report:DefaultOrientation" value="Portrait" />
|
||||
|
||||
<!-- 页面边距(mm),有效范围 0-100 | Page margins (mm), valid range 0-100 -->
|
||||
<add key="Report:MarginTop" value="20" />
|
||||
<add key="Report:MarginBottom" value="20" />
|
||||
<add key="Report:MarginLeft" value="20" />
|
||||
<add key="Report:MarginRight" value="20" />
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 公司信息配置 | Company Information Configuration -->
|
||||
<!-- ============================================================ -->
|
||||
|
||||
<!-- 报告中显示的公司名称 | Company name displayed in report -->
|
||||
<add key="Report:CompanyName" value="海克斯康制造智能技术(青岛)有限公司" />
|
||||
|
||||
<!-- 公司 Logo 图片路径(可选,为空则不显示)| Company logo path (optional, empty = no logo) -->
|
||||
<!-- 支持相对路径(基于应用程序目录)或绝对路径 | Supports relative (based on app dir) or absolute path -->
|
||||
<add key="Report:CompanyLogo" value="Resources\company_logo.png" />
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- 软件信息配置 | Software Information Configuration -->
|
||||
<!-- ============================================================ -->
|
||||
|
||||
<!-- 报告中显示的软件名称 | Software name displayed in report -->
|
||||
<add key="Report:SoftwareName" value="XplorePlane" />
|
||||
|
||||
<!-- 软件 Logo 图片路径(可选,为空则不显示)| Software logo path (optional, empty = no logo) -->
|
||||
<!-- 支持相对路径(基于应用程序目录)或绝对路径 | Supports relative (based on app dir) or absolute path -->
|
||||
<add key="Report:SoftwareLogo" value="Resources\software_logo.png" />
|
||||
</appSettings>
|
||||
</configuration>
|
||||
@@ -0,0 +1,73 @@
|
||||
# 字体方案说明 | Font Strategy
|
||||
|
||||
## 当前方案 | Current Approach
|
||||
|
||||
XP.ReportEngine 使用 **Windows 系统自带字体** 生成 PDF,无需额外下载或嵌入字体文件。
|
||||
|
||||
| 语言 | 字体 | 文件 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 简体中文 / 繁体中文 | 微软雅黑 | `C:\Windows\Fonts\msyh.ttc` | Windows 10+ 自带,支持简繁体 |
|
||||
| 西文(英文等) | Arial | `C:\Windows\Fonts\arial.ttf` | Windows 自带 |
|
||||
|
||||
## 为什么使用系统字体 | Why System Fonts
|
||||
|
||||
1. **Telerik RadPdfViewer 兼容性** — 系统字体生成的 PDF 在 Telerik RadPdfViewer 中显示正常,不会出现中文乱码
|
||||
2. **无需额外部署** — 不需要随应用程序分发字体文件,减小安装包体积
|
||||
3. **跨阅读器兼容** — 字体以子集嵌入 PDF 中(`PREFER_EMBEDDED`),在任何 PDF 阅读器中都能正确显示
|
||||
|
||||
## 后备机制 | Fallback Mechanism
|
||||
|
||||
`ITextPdfRenderer` 按以下顺序加载字体:
|
||||
|
||||
```
|
||||
微软雅黑 (msyh.ttc)
|
||||
↓ 加载失败
|
||||
宋体 (simsun.ttc)
|
||||
↓ 加载失败
|
||||
_cjkFont = null
|
||||
|
||||
Arial (arial.ttf)
|
||||
↓ 加载失败
|
||||
_westernFont = null
|
||||
|
||||
最终后备: iText 内置 Helvetica(不支持中文字符)
|
||||
```
|
||||
|
||||
语言选择逻辑:
|
||||
- zh-CN / zh-TW → 优先使用微软雅黑
|
||||
- en-US → 优先使用 Arial,后备微软雅黑(微软雅黑也支持西文)
|
||||
|
||||
## 字体子集化 | Font Subsetting
|
||||
|
||||
iText7 在 `document.Close()` 时执行字体子集化:
|
||||
- 分析文档中实际使用的字符
|
||||
- 从完整字体文件(微软雅黑约 15MB)中提取用到的字形子集
|
||||
- 仅嵌入子集到 PDF 中
|
||||
|
||||
这使得最终 PDF 文件大小合理(通常 200KB-2MB),但 `Close()` 操作需要约 1-1.5 秒。
|
||||
|
||||
## 性能说明 | Performance Notes
|
||||
|
||||
| 阶段 | 首次耗时 | 后续耗时 | 说明 |
|
||||
|------|---------|---------|------|
|
||||
| 字体加载 | ~5-6s | 0ms | 首次需从磁盘读取 TTC 文件并解析 |
|
||||
| 字体子集化 | ~1.2s | ~1.2s | 每次生成都需要,与字符数量相关 |
|
||||
|
||||
模块通过 `WarmUpAsync()` 预热机制在应用启动时后台完成首次字体加载,用户首次生成报告时不会感受到延迟。
|
||||
|
||||
## 系统要求 | System Requirements
|
||||
|
||||
- Windows 10 或更高版本(微软雅黑为系统预装字体)
|
||||
- 如果在精简版 Windows 上运行,需确保系统已安装微软雅黑字体
|
||||
|
||||
## 自定义字体扩展 | Custom Font Extension
|
||||
|
||||
如需添加自定义字体:
|
||||
|
||||
1. 将字体文件(.ttf / .ttc / .otf)放入项目 `Fonts/` 目录
|
||||
2. 在 `.csproj` 中取消注释嵌入资源配置:
|
||||
```xml
|
||||
<EmbeddedResource Include="Fonts\*.otf" />
|
||||
<EmbeddedResource Include="Fonts\*.ttf" />
|
||||
```
|
||||
3. 在 `ITextPdfRenderer.InitializeFonts()` 中扩展加载逻辑
|
||||
@@ -0,0 +1,269 @@
|
||||
# XP.ReportEngine 使用指南 | Usage Guidance
|
||||
|
||||
## 1. 概述
|
||||
|
||||
本文档说明如何在 XplorePlane 项目中使用 `XP.ReportEngine` 模块生成 PDF 检测报告。
|
||||
|
||||
模块提供两种调用方式:
|
||||
- **推荐**:通过 `IReportService` 门面接口(一行调用,自动处理所有细节)
|
||||
- **高级**:直接使用底层管线接口(`IReportGenerator`、`IReportDataAdapter` 等)
|
||||
|
||||
## 2. 前置条件
|
||||
|
||||
### 2.1 项目引用
|
||||
|
||||
在调用方项目的 `.csproj` 中添加项目引用:
|
||||
|
||||
```xml
|
||||
<ProjectReference Include="..\XP.ReportEngine\XP.ReportEngine.csproj" />
|
||||
```
|
||||
|
||||
### 2.2 模块注册
|
||||
|
||||
确保 `ReportEngineModule` 已在 `XP.App` 的模块目录中注册。模块初始化时会自动:
|
||||
- 注册所有服务到 DI 容器
|
||||
- 注册多语言资源到 Fallback Chain
|
||||
- 后台执行引擎预热(字体加载 + JIT 编译)
|
||||
|
||||
### 2.3 配置文件
|
||||
|
||||
在 `App.config` 中添加报告引擎配置项,参见 `Documents/App.config.example`。
|
||||
|
||||
## 3. 通过 IReportService 生成报告(推荐)
|
||||
|
||||
### 3.1 基本用法
|
||||
|
||||
```csharp
|
||||
using XP.ReportEngine.Interfaces;
|
||||
using XP.ReportEngine.Models;
|
||||
|
||||
public class InspectionService
|
||||
{
|
||||
private readonly IReportService _reportService;
|
||||
|
||||
public InspectionService(IReportService reportService)
|
||||
{
|
||||
_reportService = reportService;
|
||||
}
|
||||
|
||||
public async Task<string> GenerateInspectionReport(
|
||||
List<ProcessorOutput> processorOutputs,
|
||||
string productName,
|
||||
string operatorName)
|
||||
{
|
||||
var request = new ReportRequest
|
||||
{
|
||||
// 必填:处理器输出数据
|
||||
ProcessorOutputs = processorOutputs,
|
||||
|
||||
// 必填:报告元数据
|
||||
Metadata = new ReportMetadata
|
||||
{
|
||||
SampleName = productName,
|
||||
OperatorName = operatorName,
|
||||
InspectionDate = DateTime.Now,
|
||||
Description = "BGA 焊球气泡率检测"
|
||||
},
|
||||
|
||||
// 可选:文件名占位符参数(用于 FileNamePattern)
|
||||
FileNameParameters = new Dictionary<string, string>
|
||||
{
|
||||
["ProductName"] = productName,
|
||||
["ProductCode"] = "PCBA-X100",
|
||||
["WorkpieceSN"] = "SN20250001",
|
||||
["DeviceId"] = "XP-CT-001",
|
||||
["MachineId"] = "MC01",
|
||||
["Result"] = "Pass"
|
||||
}
|
||||
};
|
||||
|
||||
var result = await _reportService.GenerateAsync(request);
|
||||
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
return result.OutputFilePath; // PDF 文件路径
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"报告生成失败: {result.ErrorMessage}");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 指定输出路径
|
||||
|
||||
```csharp
|
||||
var request = new ReportRequest
|
||||
{
|
||||
ProcessorOutputs = outputs,
|
||||
Metadata = metadata,
|
||||
// 指定输出路径后,不再使用 ReportConfig 中的 OutputDirectory 和 FileNamePattern
|
||||
OutputFilePath = @"D:\CustomPath\MyReport.pdf"
|
||||
};
|
||||
```
|
||||
|
||||
### 3.3 注入额外图像
|
||||
|
||||
```csharp
|
||||
var request = new ReportRequest
|
||||
{
|
||||
ProcessorOutputs = outputs,
|
||||
Metadata = metadata,
|
||||
AdditionalImages = new Dictionary<string, ImageData>
|
||||
{
|
||||
// 工件整体图(首页显示)
|
||||
["workpieceImage"] = new ImageData
|
||||
{
|
||||
SourceType = ImageSourceType.FilePath,
|
||||
FilePath = @"D:\Images\workpiece.png"
|
||||
},
|
||||
// 自定义图像
|
||||
["customImage"] = new ImageData
|
||||
{
|
||||
SourceType = ImageSourceType.Bytes,
|
||||
Bytes = imageBytes
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 3.4 注入自定义属性
|
||||
|
||||
```csharp
|
||||
var request = new ReportRequest
|
||||
{
|
||||
ProcessorOutputs = outputs,
|
||||
Metadata = metadata,
|
||||
// 自定义属性会合并到 ReportContext.Properties,可在模板中通过 ${key} 绑定
|
||||
CustomProperties = new Dictionary<string, object>
|
||||
{
|
||||
["batchNumber"] = "BATCH-2025-001",
|
||||
["inspectionStation"] = "Station-A"
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## 4. ReportRequest 完整字段说明
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `ProcessorOutputs` | `List<ProcessorOutput>` | 是 | 处理器输出数据列表 |
|
||||
| `Metadata` | `ReportMetadata` | 是 | 报告元数据 |
|
||||
| `OutputFilePath` | `string` | 否 | 输出路径,为空时自动生成 |
|
||||
| `FileNameParameters` | `Dictionary<string, string>` | 否 | 文件名占位符参数 |
|
||||
| `AdditionalImages` | `Dictionary<string, ImageData>` | 否 | 额外图像数据 |
|
||||
| `CustomProperties` | `Dictionary<string, object>` | 否 | 自定义属性 |
|
||||
|
||||
### ReportMetadata 字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `ReportId` | `string` | 报告编号(为空时自动生成 RPT-yyyyMMdd-NNN) |
|
||||
| `InspectionDate` | `DateTime` | 检测日期 |
|
||||
| `SampleName` | `string` | 样品/产品名称 |
|
||||
| `OperatorName` | `string` | 操作员 |
|
||||
| `Description` | `string` | 描述信息 |
|
||||
|
||||
### ProcessorOutput 字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `ProcessorType` | `string` | 处理器类型标识 |
|
||||
| `OutputData` | `Dictionary<string, object>` | 输出数据字典 |
|
||||
| `AnnotatedImage` | `ImageData` | 关联的已标注图像 |
|
||||
|
||||
支持的 ProcessorType:
|
||||
- `LineMeasurementProcessor` — 线测量
|
||||
- `BgaVoidRateProcessor` — BGA 气泡率
|
||||
- `VoidMeasurementProcessor` — 空隙测量
|
||||
- `FillRateProcessor` — 通孔填锡率
|
||||
|
||||
## 5. ReportServiceResult 结果说明
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `IsSuccess` | `bool` | 是否成功 |
|
||||
| `OutputFilePath` | `string` | 输出文件路径(成功时有值) |
|
||||
| `ReportId` | `string` | 报告编号 |
|
||||
| `ErrorMessage` | `string` | 错误信息(失败时有值) |
|
||||
| `Exception` | `Exception` | 异常对象(失败时有值) |
|
||||
|
||||
## 6. 在 ViewModel 中使用(带 UI 交互)
|
||||
|
||||
如果需要进度条、文件对话框等 UI 交互,参考 `ReportDemoViewModel.cs` 的实现模式:
|
||||
|
||||
```csharp
|
||||
// 在后台线程执行,避免阻塞 UI
|
||||
var genResult = await Task.Run(() => _reportService.GenerateAsync(request));
|
||||
```
|
||||
|
||||
关键点:
|
||||
- `IReportService.GenerateAsync` 内部是 CPU 密集型操作(PDF 渲染)
|
||||
- 必须用 `Task.Run` 推到线程池,否则会阻塞 UI 线程
|
||||
- 进度条、对话框等 UI 逻辑留在 ViewModel 中
|
||||
|
||||
## 7. 高级用法:直接使用底层管线
|
||||
|
||||
适用于需要自定义管线某个阶段的场景:
|
||||
|
||||
```csharp
|
||||
// 1. 数据适配
|
||||
var adapter = container.Resolve<IReportDataAdapter>();
|
||||
var context = adapter.Adapt(processorOutputs, metadata);
|
||||
|
||||
// 2. 手动修改上下文
|
||||
context.Properties["customKey"] = "customValue";
|
||||
context.Images["myImage"] = new ImageData { ... };
|
||||
|
||||
// 3. 调用管线生成
|
||||
var generator = container.Resolve<IReportGenerator>();
|
||||
var options = new ReportGenerationOptions
|
||||
{
|
||||
TemplatePath = "Templates/StandardReportTemplate.json",
|
||||
OutputFilePath = @"D:\output.pdf",
|
||||
Format = ReportOutputFormat.Pdf
|
||||
};
|
||||
|
||||
var result = await generator.GenerateAsync(context, options);
|
||||
```
|
||||
|
||||
## 8. 预热机制
|
||||
|
||||
模块启动时自动在后台执行预热,无需手动调用。如果需要在特定时机手动预热:
|
||||
|
||||
```csharp
|
||||
var reportService = container.Resolve<IReportService>();
|
||||
await reportService.WarmUpAsync();
|
||||
```
|
||||
|
||||
预热内容:
|
||||
- iText7 程序集加载
|
||||
- BouncyCastle 加密提供程序注册
|
||||
- 微软雅黑字体文件读取和解析(约 5-6 秒)
|
||||
- JSON 模板反序列化 JIT 编译
|
||||
- 管线各阶段代码 JIT 编译
|
||||
|
||||
预热完成后,首次正式生成报告不会有额外延迟。
|
||||
|
||||
## 9. 线程安全
|
||||
|
||||
`IReportService` 内部使用 `SemaphoreSlim(1, 1)` 互斥锁,确保:
|
||||
- 预热与正式生成不会并发执行
|
||||
- 多个并发的 `GenerateAsync` 调用会串行执行
|
||||
|
||||
这是因为 `ITextPdfRenderer` 的字体字段在每次渲染时重置,并发渲染会导致 iText7 的 `"belongs to other PDF document"` 错误。
|
||||
|
||||
## 10. 常见问题
|
||||
|
||||
### Q: 首次生成报告很慢?
|
||||
A: 正常现象。首次需要加载字体(~5s)+ 字体子集化(~1.2s)。预热机制会在应用启动时后台完成字体加载,用户首次操作时只需等待子集化时间。
|
||||
|
||||
### Q: PDF 中图像显示为"无图像"占位矩形?
|
||||
A: 检查 `ProcessorOutput.AnnotatedImage` 或 `ReportRequest.AdditionalImages` 中的图像路径是否正确、文件是否存在。
|
||||
|
||||
### Q: 报告中某些字段为空?
|
||||
A: 检查 `ProcessorOutput.OutputData` 中的键名是否与 `ProcessorDataAdapter` 中的映射一致(区分大小写)。
|
||||
|
||||
### Q: 如何自定义报告模板?
|
||||
A: 修改 `Templates/StandardReportTemplate.json`,参考 `Documents/XP.ReportEngineModelDefine.md` 中的模板结构定义。
|
||||
@@ -0,0 +1,217 @@
|
||||
# XP.ReportEngine — PDF 报告生成引擎
|
||||
|
||||
## 概述
|
||||
|
||||
XP.ReportEngine 是 XplorePlane 平面 CT 检测系统的 PDF 报告生成模块。负责将检测分析结果(距离测量、BGA 气泡率、空隙测量、通孔填锡率)转换为结构化的 PDF 检测报告。
|
||||
|
||||
模块采用管线式架构(Pipeline),将报告生成过程分解为五个独立阶段:
|
||||
**模板加载 → 数据适配 → 数据绑定 → 排版计算 → PDF 渲染**
|
||||
|
||||
对外提供 `IReportService` 门面接口,外部模块只需构建 `ReportRequest` 即可一行调用完成报告生成。
|
||||
|
||||
## 技术栈
|
||||
|
||||
| 依赖 | 版本 | 用途 |
|
||||
|------|------|------|
|
||||
| .NET 8.0 | net8.0-windows7.0 | 运行时 |
|
||||
| iText7 | 8.0.5 | PDF 文档生成核心库 |
|
||||
| itext7.bouncy-castle-adapter | 8.0.5 | iText7 加密支持 |
|
||||
| Newtonsoft.Json | 13.0.3 | JSON 模板反序列化 |
|
||||
| Prism.Wpf | 9.0.537 | 模块化框架与 DI |
|
||||
| Telerik UI for WPF | 2024.1.408 | UI 控件(演示窗口) |
|
||||
| XP.Common | — | 日志、本地化、通用窗体、PDF 阅读器 |
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
XP.ReportEngine/
|
||||
├── Configs/ # 配置加载
|
||||
│ ├── ConfigLoader.cs
|
||||
│ └── ReportConfig.cs
|
||||
├── Interfaces/ # 核心接口定义
|
||||
│ ├── IReportService.cs ★ 门面接口(外部调用入口)
|
||||
│ ├── IReportGenerator.cs
|
||||
│ ├── IReportGeneratorFactory.cs
|
||||
│ ├── ITemplateEngine.cs
|
||||
│ ├── IDataBinder.cs
|
||||
│ ├── ILayoutEngine.cs
|
||||
│ ├── IPdfRenderer.cs
|
||||
│ └── IReportDataAdapter.cs
|
||||
├── Models/ # 数据模型
|
||||
│ ├── ReportRequest.cs ★ 报告生成请求
|
||||
│ ├── ReportServiceResult.cs ★ 报告服务结果
|
||||
│ ├── ReportContext.cs
|
||||
│ ├── ReportMetadata.cs
|
||||
│ ├── ImageData.cs
|
||||
│ ├── ReportTemplate.cs
|
||||
│ ├── TemplateElement.cs
|
||||
│ ├── LayoutPage.cs
|
||||
│ ├── ReportResult.cs
|
||||
│ ├── ReportGenerationOptions.cs
|
||||
│ ├── ProcessorOutput.cs
|
||||
│ └── TemplateValidationResult.cs
|
||||
├── Services/ # 服务实现
|
||||
│ ├── ReportService.cs ★ 门面服务实现
|
||||
│ ├── PdfReportGenerator.cs # 管线协调器
|
||||
│ ├── ReportGeneratorFactory.cs # 工厂
|
||||
│ ├── JsonTemplateEngine.cs # JSON 模板加载与验证
|
||||
│ ├── ExpressionDataBinder.cs # ${} 表达式数据绑定
|
||||
│ ├── PageLayoutEngine.cs # 分页与排版
|
||||
│ ├── ITextPdfRenderer.cs # iText7 PDF 渲染
|
||||
│ ├── ProcessorDataAdapter.cs # 处理器数据适配
|
||||
│ └── ReportIdGenerator.cs # 报告编号生成(RPT-yyyyMMdd-NNN)
|
||||
├── Templates/ # JSON 报告模板
|
||||
│ └── StandardReportTemplate.json
|
||||
├── Resources/ # 多语言资源文件
|
||||
│ ├── Resources.resx
|
||||
│ ├── Resources.zh-CN.resx
|
||||
│ ├── Resources.zh-TW.resx
|
||||
│ └── Resources.en-US.resx
|
||||
├── ViewModels/ # 演示窗口 ViewModel
|
||||
│ └── ReportDemoViewModel.cs
|
||||
├── Views/ # 演示窗口 View
|
||||
│ ├── ReportDemoWindow.xaml
|
||||
│ └── ReportDemoWindow.xaml.cs
|
||||
├── Documents/ # 项目文档
|
||||
│ ├── README.md ← 本文件
|
||||
│ ├── Guidance.md # 使用指南
|
||||
│ ├── App.config.example # 配置示例
|
||||
│ ├── FontFilesGuidance.md # 字体方案说明
|
||||
│ ├── XP.ReportEngineDesign.md # 架构设计文档
|
||||
│ └── XP.ReportEngineModelDefine.md # 模型定义文档
|
||||
├── ReportEngineModule.cs # Prism 模块入口
|
||||
└── XP.ReportEngine.csproj
|
||||
```
|
||||
|
||||
## 核心接口
|
||||
|
||||
| 接口 | 实现类 | 职责 |
|
||||
|------|--------|------|
|
||||
| **`IReportService`** | **`ReportService`** | **门面接口,外部模块调用入口** |
|
||||
| `IReportGenerator` | `PdfReportGenerator` | 协调管线各阶段,生成 PDF |
|
||||
| `IReportGeneratorFactory` | `ReportGeneratorFactory` | 根据格式创建对应生成器 |
|
||||
| `ITemplateEngine` | `JsonTemplateEngine` | JSON 模板加载、反序列化、验证 |
|
||||
| `IDataBinder` | `ExpressionDataBinder` | `${}` 表达式解析与数据绑定 |
|
||||
| `ILayoutEngine` | `PageLayoutEngine` | 分页、元素定位、表格跨页 |
|
||||
| `IPdfRenderer` | `ITextPdfRenderer` | 使用 iText7 渲染 PDF |
|
||||
| `IReportDataAdapter` | `ProcessorDataAdapter` | ProcessorOutput → ReportContext 转换 |
|
||||
|
||||
## 架构分层
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ 外部调用方(XP.App、其他模块) │
|
||||
│ 注入 IReportService,传入 ReportRequest │
|
||||
└──────────────────────┬──────────────────────────────┘
|
||||
│
|
||||
┌──────────────────────▼──────────────────────────────┐
|
||||
│ ReportService(门面层) │
|
||||
│ 生成报告ID → 解析输出路径 → 数据适配 → 注入配置数据 │
|
||||
└──────────────────────┬──────────────────────────────┘
|
||||
│
|
||||
┌──────────────────────▼──────────────────────────────┐
|
||||
│ PdfReportGenerator(管线协调层) │
|
||||
│ 模板加载 → 数据绑定 → 排版计算 → PDF 渲染 → 保存 │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 快速开始
|
||||
|
||||
详细使用指南请参阅 `Documents/Guidance.md`。
|
||||
|
||||
### 最简调用(通过 IReportService)
|
||||
|
||||
```csharp
|
||||
public class MyService
|
||||
{
|
||||
private readonly IReportService _reportService;
|
||||
|
||||
public MyService(IReportService reportService)
|
||||
{
|
||||
_reportService = reportService;
|
||||
}
|
||||
|
||||
public async Task GenerateReport(List<ProcessorOutput> outputs)
|
||||
{
|
||||
var request = new ReportRequest
|
||||
{
|
||||
ProcessorOutputs = outputs,
|
||||
Metadata = new ReportMetadata
|
||||
{
|
||||
SampleName = "PCB-001",
|
||||
OperatorName = "张三",
|
||||
InspectionDate = DateTime.Now
|
||||
}
|
||||
};
|
||||
|
||||
var result = await _reportService.GenerateAsync(request);
|
||||
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
// result.OutputFilePath — 生成的 PDF 路径
|
||||
// result.ReportId — 报告编号
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 预热机制
|
||||
|
||||
模块在 Prism 初始化时自动在后台执行一次预热(`WarmUpAsync`),触发 iText7 初始化、字体加载、JIT 编译等一次性开销,避免用户首次生成报告时卡顿。
|
||||
|
||||
预热与正式生成通过 `SemaphoreSlim` 互斥锁串行执行,不会出现并发冲突。
|
||||
|
||||
## 数据绑定表达式
|
||||
|
||||
模板中支持以下绑定语法:
|
||||
|
||||
| 语法 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| `${propertyName}` | 简单属性绑定 | `${sampleName}` |
|
||||
| `${object.property}` | 嵌套属性路径 | `${metadata.reportId}` |
|
||||
| `${list[index]}` | 列表索引访问 | `${bgaBalls[0].voidRate}` |
|
||||
| `${functionName(param)}` | 格式化函数 | `${formatDate(inspectionDate)}` |
|
||||
| `${loc:ResourceKey}` | 本地化键解析 | `${loc:Report_Title}` |
|
||||
|
||||
## 多语言支持
|
||||
|
||||
支持三种语言:简体中文(zh-CN)、繁体中文(zh-TW)、英文(en-US)。
|
||||
|
||||
通过 `ILocalizationService` 自动解析 `${loc:Key}` 表达式为当前语言文本。模块在初始化时注册资源源到 Fallback Chain。
|
||||
|
||||
## 模板页面类型
|
||||
|
||||
标准模板 `StandardReportTemplate.json` 包含以下页面类型:
|
||||
|
||||
| 页面类型 | 说明 |
|
||||
|---------|------|
|
||||
| `homepage` | 报告首页(公司信息 + 元数据 + 汇总表格) |
|
||||
| `summary` | 检测结果汇总页 |
|
||||
| `metricData` | 距离测量数据页 |
|
||||
| `bgaInspection` | BGA 焊球检测页(含数据表格) |
|
||||
| `voidInspection` | 空隙检测页(含数据表格) |
|
||||
| `viaFillInspection` | 通孔填锡检测页 |
|
||||
|
||||
## 错误处理
|
||||
|
||||
模块采用结果对象模式(Result Pattern):
|
||||
- `ReportServiceResult.Success(path, reportId)` — 成功
|
||||
- `ReportServiceResult.Failure(message, ex)` — 失败
|
||||
|
||||
非致命性问题(缺失属性、图像缺失)会记录警告日志并继续执行,不会中断报告生成。
|
||||
|
||||
## 构建
|
||||
|
||||
```bash
|
||||
cd XplorePlane
|
||||
dotnet build XP.ReportEngine/XP.ReportEngine.csproj
|
||||
```
|
||||
|
||||
## 相关文档
|
||||
|
||||
- `Documents/Guidance.md` — 详细使用指南(IReportService 调用方式)
|
||||
- `Documents/TemplateDevelopment.md` — 模板开发指南(新增/自定义模板)
|
||||
- `Documents/App.config.example` — 配置文件示例
|
||||
- `Documents/FontFilesGuidance.md` — 字体方案说明
|
||||
- `Documents/XP.ReportEngineDesign.md` — 架构设计文档
|
||||
- `Documents/XP.ReportEngineModelDefine.md` — 模型定义文档
|
||||
@@ -0,0 +1,403 @@
|
||||
# 报告模板开发指南 | Template Development Guide
|
||||
|
||||
## 1. 概述
|
||||
|
||||
XP.ReportEngine 使用 JSON 格式定义报告模板。模板描述了 PDF 报告的页面结构、元素布局、数据绑定和样式定义。
|
||||
|
||||
模板文件存放在 `XP.ReportEngine/Templates/` 目录下,通过 `App.config` 中的 `Report:TemplatePath` 配置项指定使用哪个模板。
|
||||
|
||||
## 2. 新增模板步骤
|
||||
|
||||
### 2.1 创建模板文件
|
||||
|
||||
1. 在 `Templates/` 目录下创建新的 JSON 文件,如 `CustomReportTemplate.json`
|
||||
2. 在 `.csproj` 中确认模板文件会被复制到输出目录(已有通配规则):
|
||||
```xml
|
||||
<Content Include="Templates\*.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
```
|
||||
3. 修改 `App.config` 中的 `Report:TemplatePath` 指向新模板:
|
||||
```xml
|
||||
<add key="Report:TemplatePath" value="Templates\CustomReportTemplate.json" />
|
||||
```
|
||||
|
||||
### 2.2 模板验证规则
|
||||
|
||||
模板加载后会自动验证,必须满足以下条件:
|
||||
- 包含 `document` 顶层字段(页面设置)
|
||||
- 包含 `pages` 顶层字段(至少一个页面定义)
|
||||
- 包含 `styles` 顶层字段(样式字典)
|
||||
|
||||
验证失败时报告生成会返回错误,不会生成 PDF。
|
||||
|
||||
## 3. 模板 JSON 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"document": { ... }, // 必需:文档级设置(页面尺寸、边距、页眉页脚)
|
||||
"pages": [ ... ], // 必需:页面定义数组
|
||||
"styles": { ... } // 必需:样式定义字典
|
||||
}
|
||||
```
|
||||
|
||||
## 4. document 配置
|
||||
|
||||
```json
|
||||
{
|
||||
"document": {
|
||||
"pageSize": "A4",
|
||||
"orientation": "Portrait",
|
||||
"margins": { "top": 40, "bottom": 20, "left": 20, "right": 20 },
|
||||
"header": {
|
||||
"enabled": true,
|
||||
"left": ["标题文本", "第二行文本"],
|
||||
"right": ["右侧文本"],
|
||||
"rightImageKey": "companyLogo",
|
||||
"leftImageKey": "softwareLogo",
|
||||
"fontSize": 7,
|
||||
"color": "#666666",
|
||||
"showLine": true
|
||||
},
|
||||
"footer": {
|
||||
"enabled": true,
|
||||
"left": ["公司名称"],
|
||||
"right": ["{currentPage} / {totalPages}"],
|
||||
"fontSize": 8,
|
||||
"color": "#666666",
|
||||
"showLine": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `pageSize` | string | `"A4"` | 页面尺寸 |
|
||||
| `orientation` | string | `"Portrait"` | 方向:Portrait / Landscape |
|
||||
| `margins` | object | `{top:20, bottom:20, left:20, right:20}` | 边距(mm) |
|
||||
| `header` | object | null | 页眉配置(首页不显示) |
|
||||
| `footer` | object | null | 页脚配置(首页不显示) |
|
||||
|
||||
### 页眉/页脚字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `enabled` | bool | 是否启用 |
|
||||
| `left` | string[] | 左侧文本行(支持 `${}` 绑定) |
|
||||
| `right` | string[] | 右侧文本行(支持 `${}` 绑定) |
|
||||
| `leftImageKey` | string | 左侧图像的 dataKey |
|
||||
| `rightImageKey` | string | 右侧图像的 dataKey |
|
||||
| `fontSize` | float | 字体大小 |
|
||||
| `color` | string | 字体颜色(十六进制) |
|
||||
| `showLine` | bool | 是否显示分隔线 |
|
||||
|
||||
页脚特殊占位符:
|
||||
- `{currentPage}` — 当前页码
|
||||
- `{totalPages}` — 总页数
|
||||
|
||||
## 5. pages 页面定义
|
||||
|
||||
```json
|
||||
{
|
||||
"pages": [
|
||||
{
|
||||
"type": "homepage",
|
||||
"elements": [ ... ]
|
||||
},
|
||||
{
|
||||
"type": "customPage",
|
||||
"elements": [ ... ]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `type` | string | 页面类型标识(`homepage` 类型不显示页眉页脚) |
|
||||
| `elements` | array | 页面内的元素列表 |
|
||||
|
||||
### 内置页面类型
|
||||
|
||||
| 类型 | 说明 | 特殊行为 |
|
||||
|------|------|---------|
|
||||
| `homepage` | 首页 | 不显示页眉页脚 |
|
||||
| `summary` | 汇总页 | 无 |
|
||||
| `metricData` | 距离测量页 | 无 |
|
||||
| `bgaInspection` | BGA 检测页 | 无 |
|
||||
| `voidInspection` | 空隙检测页 | 无 |
|
||||
| `viaFillInspection` | 通孔填锡页 | 无 |
|
||||
|
||||
你可以自定义任意 `type` 名称,只有 `homepage` 有特殊行为(不显示页眉页脚)。
|
||||
|
||||
## 6. 元素类型
|
||||
|
||||
### 6.1 text — 文本元素
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "text",
|
||||
"content": "${loc:Report_Title}",
|
||||
"style": "heading",
|
||||
"positioning": "flow",
|
||||
"align": "center",
|
||||
"colorRules": { "Pass": "#008000", "Fail": "#FF0000" }
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `content` | string | 文本内容,支持 `${}` 绑定表达式 |
|
||||
| `style` | string | 引用 `styles` 中定义的样式名 |
|
||||
| `align` | string | 对齐:left / center / right |
|
||||
| `colorRules` | object | 条件颜色规则(内容包含关键词时变色) |
|
||||
|
||||
### 6.2 image — 图像元素
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "image",
|
||||
"dataKey": "workpieceImage",
|
||||
"size": [160, 110],
|
||||
"border": true,
|
||||
"align": "center",
|
||||
"style": "imageDefault",
|
||||
"positioning": "flow"
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `dataKey` | string | 图像数据键(对应 `ReportContext.Images` 中的键) |
|
||||
| `size` | float[2] | [宽, 高](mm),图像会等比缩放适应 |
|
||||
| `border` | bool | 是否显示边框 |
|
||||
| `align` | string | 对齐:left / center / right |
|
||||
|
||||
图像缺失时会渲染灰色占位矩形,不会中断报告生成。
|
||||
|
||||
### 6.3 table — 表格元素
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "table",
|
||||
"dataKey": "bgaBallsTable",
|
||||
"positioning": "flow",
|
||||
"size": [170, 0],
|
||||
"style": "tableDefault",
|
||||
"columns": [
|
||||
{ "header": "序号", "field": "index", "width": 25, "align": "center" },
|
||||
{ "header": "气泡率", "field": "voidRate", "width": 40, "align": "center" },
|
||||
{ "header": "面积", "field": "area", "width": 40, "align": "center" },
|
||||
{
|
||||
"header": "分类",
|
||||
"field": "classification",
|
||||
"width": 35,
|
||||
"align": "center",
|
||||
"colorRules": { "Pass": "#008000", "Fail": "#FF0000" }
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `dataKey` | string | 表格数据键(对应 `ReportContext.Properties` 中的 `List<Dictionary<string, object>>` 数据) |
|
||||
| `size` | float[2] | [宽, 高](mm),高度为 0 表示自动 |
|
||||
| `columns` | array | 列定义数组 |
|
||||
|
||||
#### 列定义(ColumnDefinition)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `header` | string | 表头文本(支持 `${}` 绑定) |
|
||||
| `field` | string | 数据字段名(对应行数据字典中的键) |
|
||||
| `width` | float | 列宽(mm) |
|
||||
| `align` | string | 对齐:left / center / right |
|
||||
| `colorRules` | object | 条件颜色规则(单元格值匹配时变色) |
|
||||
|
||||
### 6.4 row — 水平布局容器
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "row",
|
||||
"size": [170, 100],
|
||||
"widths": [6, 4],
|
||||
"positioning": "flow",
|
||||
"children": [
|
||||
{
|
||||
"type": "column", "align": "left",
|
||||
"children": [
|
||||
{ "type": "image", "dataKey": "myImage", "size": [110, 90] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "column", "align": "left",
|
||||
"children": [
|
||||
{ "type": "text", "content": "文本内容", "style": "body" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `size` | float[2] | [总宽, 总高](mm) |
|
||||
| `widths` | float[] | 列宽比例数组(如 `[6, 4]` 表示 60%:40%) |
|
||||
| `children` | array | 子列元素(`type: "column"`) |
|
||||
|
||||
### 6.5 spacer — 间距元素
|
||||
|
||||
```json
|
||||
{ "type": "spacer", "size": [170, 10], "positioning": "flow" }
|
||||
```
|
||||
|
||||
用于在元素之间添加垂直间距。`size[1]` 为间距高度(mm)。
|
||||
|
||||
### 6.6 divider — 分隔线
|
||||
|
||||
```json
|
||||
{ "type": "divider", "positioning": "flow" }
|
||||
```
|
||||
|
||||
渲染一条水平分隔线。
|
||||
|
||||
### 6.7 pagebreak — 强制分页
|
||||
|
||||
```json
|
||||
{ "type": "pagebreak" }
|
||||
```
|
||||
|
||||
在当前位置强制插入分页符。
|
||||
|
||||
## 7. 样式定义
|
||||
|
||||
```json
|
||||
{
|
||||
"styles": {
|
||||
"heading": {
|
||||
"font": "auto",
|
||||
"size": 16,
|
||||
"bold": true,
|
||||
"italic": false,
|
||||
"color": "#333333",
|
||||
"align": "left",
|
||||
"marginTop": 0,
|
||||
"marginBottom": 3,
|
||||
"paddingLeft": 0,
|
||||
"lineHeight": 0,
|
||||
"backgroundColor": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `font` | string | `"auto"` | 字体(`auto` 根据语言自动选择) |
|
||||
| `size` | float | 12 | 字体大小(pt) |
|
||||
| `bold` | bool | false | 粗体 |
|
||||
| `italic` | bool | false | 斜体 |
|
||||
| `color` | string | `"#000000"` | 字体颜色(十六进制) |
|
||||
| `align` | string | `"left"` | 对齐:left / center / right |
|
||||
| `backgroundColor` | string | — | 背景色(十六进制) |
|
||||
| `marginTop` | float | 0 | 上边距(mm) |
|
||||
| `marginBottom` | float | 0 | 下边距(mm) |
|
||||
| `paddingLeft` | float | 0 | 左缩进(mm) |
|
||||
| `lineHeight` | float | 0 | 行高倍数(0 = 默认) |
|
||||
|
||||
元素引用未定义的样式名时,会使用默认样式(12pt、黑色、左对齐),不会报错。
|
||||
|
||||
## 8. 数据绑定表达式
|
||||
|
||||
模板中的 `content`、`header` 等文本字段支持 `${}` 绑定表达式:
|
||||
|
||||
| 语法 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| `${key}` | 从 Properties 中取值 | `${sampleName}` |
|
||||
| `${metadata.field}` | 从 Metadata 中取值 | `${metadata.reportId}` |
|
||||
| `${loc:Key}` | 多语言资源键 | `${loc:Report_Title}` |
|
||||
| `${formatDate(value)}` | 格式化日期 | `${formatDate(metadata.inspectionDate)}` |
|
||||
| `${formatNumber(value, decimals)}` | 格式化数字 | `${formatNumber(totalArea, 2)}` |
|
||||
| `${formatPercent(value)}` | 格式化百分比 | `${formatPercent(voidRate)}` |
|
||||
|
||||
### 数据来源
|
||||
|
||||
绑定表达式从 `ReportContext` 中查找数据:
|
||||
- `Properties` 字典 — 扁平化的键值对(由 `ProcessorDataAdapter` 从处理器输出转换而来)
|
||||
- `Metadata` — 报告元数据对象
|
||||
- `Images` 字典 — 图像数据(通过 `dataKey` 引用)
|
||||
|
||||
### ProcessorDataAdapter 输出的键名
|
||||
|
||||
| 处理器类型 | Properties 中的键 | Images 中的键 | 表格 dataKey |
|
||||
|-----------|------------------|--------------|-------------|
|
||||
| LineMeasurementProcessor | measurementType, point1, point2, pixelDistance, actualDistance, unit, angle | lineMeasurementImage | — |
|
||||
| BgaVoidRateProcessor | bgaCount, voidRate, fillRate, totalBgaArea, totalVoidArea, voidLimit, classification | bgaInspectionImage | bgaBallsTable |
|
||||
| VoidMeasurementProcessor | roiArea, totalVoidArea, voidRate, voidLimit, voidCount, maxVoidArea, classification | voidInspectionImage | voidsTable |
|
||||
| FillRateProcessor | fillRate, voidRate, fullDistance, fillDistance, thtLimit, classification, e1-e4 | viaFillImage | — |
|
||||
|
||||
### ReportService 自动注入的键
|
||||
|
||||
| 键 | 来源 | 说明 |
|
||||
|----|------|------|
|
||||
| `CompanyName` | ReportConfig | 公司名称 |
|
||||
| `SoftwareName` | ReportConfig | 软件名称 |
|
||||
| `companyLogo` | ReportConfig.CompanyLogo | 公司 Logo(Images) |
|
||||
| `softwareLogo` | ReportConfig.SoftwareLogo | 软件 Logo(Images) |
|
||||
| `summaryTable` | 自动生成 | 首页汇总表数据 |
|
||||
|
||||
## 9. 定位方式
|
||||
|
||||
| 值 | 说明 |
|
||||
|----|------|
|
||||
| `"flow"` | 流式布局,元素按顺序从上到下排列(推荐) |
|
||||
| `"absolute"` | 绝对定位,使用 `position` 坐标(不推荐,兼容性差) |
|
||||
|
||||
建议所有元素使用 `"positioning": "flow"`。
|
||||
|
||||
## 10. 完整模板示例(最小化)
|
||||
|
||||
```json
|
||||
{
|
||||
"document": {
|
||||
"pageSize": "A4",
|
||||
"orientation": "Portrait",
|
||||
"margins": { "top": 20, "bottom": 20, "left": 20, "right": 20 }
|
||||
},
|
||||
"pages": [
|
||||
{
|
||||
"type": "homepage",
|
||||
"elements": [
|
||||
{ "type": "text", "content": "检测报告", "style": "title", "positioning": "flow" },
|
||||
{ "type": "text", "content": "报告编号:${metadata.reportId}", "style": "body", "positioning": "flow" },
|
||||
{ "type": "text", "content": "检测日期:${formatDate(metadata.inspectionDate)}", "style": "body", "positioning": "flow" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dataPage",
|
||||
"elements": [
|
||||
{ "type": "text", "content": "检测数据", "style": "heading", "positioning": "flow" },
|
||||
{ "type": "image", "dataKey": "inspectionImage", "size": [150, 100], "positioning": "flow" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"styles": {
|
||||
"title": { "size": 24, "bold": true, "align": "center" },
|
||||
"heading": { "size": 16, "bold": true },
|
||||
"body": { "size": 12 }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 11. 注意事项
|
||||
|
||||
1. **尺寸单位**:所有尺寸(size、margins、width)单位为 **mm**(毫米)
|
||||
2. **颜色格式**:使用十六进制格式 `#RRGGBB`(如 `#FF0000` 为红色)
|
||||
3. **表格自动跨页**:当表格数据行超出当前页面剩余空间时,排版引擎会自动分页
|
||||
4. **图像缺失容错**:图像 dataKey 对应的数据不存在时,渲染占位矩形,不中断生成
|
||||
5. **样式缺失容错**:引用未定义的样式名时使用默认样式,不中断生成
|
||||
6. **绑定表达式缺失**:`${}` 表达式对应的数据不存在时,替换为空字符串
|
||||
7. **首页特殊处理**:`type: "homepage"` 的页面不显示页眉页脚
|
||||
8. **JSON 编码**:模板文件必须使用 **UTF-8** 编码保存
|
||||
@@ -0,0 +1,422 @@
|
||||
# XP.ReportEngine 项目规划
|
||||
|
||||
## 一、项目结构优化
|
||||
|
||||
**1. 完整目录结构**
|
||||
|
||||
```
|
||||
XP.ReportEngine/
|
||||
├── Interfaces/
|
||||
│ ├── IReportGenerator.cs // 报告生成器核心接口
|
||||
│ ├── ITemplateEngine.cs // 模板引擎接口
|
||||
│ └── IDataBinder.cs // 数据绑定接口
|
||||
├── Models/
|
||||
│ ├── ReportContext.cs // 报告上下文(包含测量数据、图片列表)
|
||||
│ ├── ReportTemplate.cs // 报告模板定义
|
||||
│ ├── TemplateElement.cs // 模板元素定义
|
||||
│ └── LayoutSettings.cs // 布局配置参数
|
||||
├── Services/
|
||||
│ ├── MeasurementReportBuilder.cs // 测量报告构建器
|
||||
│ ├── ImageLayoutService.cs // 图片排版服务(计算坐标、缩放)
|
||||
│ ├── TemplateEngine.cs // 模板引擎实现
|
||||
│ ├── DataBinder.cs // 数据绑定实现
|
||||
│ └── PdfGenerationService.cs // PDF生成核心服务
|
||||
├── Templates/
|
||||
│ └── StandardReportTemplate.json // 标准报告模板配置
|
||||
└── Extensions/
|
||||
└── ImageExtensions.cs // 图像处理扩展方法
|
||||
```
|
||||
|
||||
## 二、核心模块设计
|
||||
|
||||
### 1. 报告模板引擎
|
||||
|
||||
**1.1 模板定义规范**
|
||||
|
||||
- **JSON模板结构**:
|
||||
|
||||
```json
|
||||
{
|
||||
"document": {
|
||||
"pageSize": "A4",
|
||||
"margins": { "top": 20, "bottom": 20, "left": 20, "right": 20 }
|
||||
},
|
||||
"pages": [
|
||||
{
|
||||
"type": "title",
|
||||
"elements": [
|
||||
{ "type": "text", "content": "工业CT检测报告", "style": "title", "position": [10, 10] },
|
||||
{ "type": "text", "content": "报告日期: ${reportDate}", "style": "subtitle", "position": [10, 30] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "measurement",
|
||||
"elements": [
|
||||
{ "type": "table", "dataKey": "measurements", "position": [10, 10], "size": [190, 100] },
|
||||
{ "type": "image", "dataKey": "overviewImage", "position": [10, 120], "size": [190, 100] }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "defectDetail",
|
||||
"elements": [
|
||||
{ "type": "image", "dataKey": "defectImage", "position": [10, 10], "size": [90, 90] },
|
||||
{ "type": "text", "content": "缺陷类型: ${defectType}", "style": "normal", "position": [10, 100] }
|
||||
]
|
||||
}
|
||||
],
|
||||
"styles": {
|
||||
"title": { "font": "Arial", "size": 24, "bold": true, "color": "#000000" },
|
||||
"subtitle": { "font": "Arial", "size": 16, "italic": true, "color": "#666666" },
|
||||
"normal": { "font": "Arial", "size": 12, "color": "#000000" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**1.2 模板引擎实现**
|
||||
|
||||
- **模板加载与解析**:
|
||||
|
||||
```csharp
|
||||
public class TemplateEngine : ITemplateEngine
|
||||
{
|
||||
public ReportTemplate LoadTemplate(string templatePath)
|
||||
{
|
||||
var json = File.ReadAllText(templatePath);
|
||||
return JsonConvert.DeserializeObject<ReportTemplate>(json);
|
||||
}
|
||||
|
||||
public List<PageElement> ParseTemplate(ReportTemplate template, ReportContext context)
|
||||
{
|
||||
var elements = new List<PageElement>();
|
||||
|
||||
foreach (var page in template.Pages)
|
||||
{
|
||||
foreach (var element in page.Elements)
|
||||
{
|
||||
// 处理数据绑定
|
||||
var boundElement = BindData(element, context);
|
||||
elements.Add(boundElement);
|
||||
}
|
||||
}
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
private PageElement BindData(PageElement element, ReportContext context)
|
||||
{
|
||||
// 实现数据绑定逻辑
|
||||
if (element.Type == "text" && element.Content.Contains("${"))
|
||||
{
|
||||
element.Content = DataBinder.Bind(element.Content, context);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 数据绑定系统
|
||||
|
||||
**2.1 数据绑定机制**
|
||||
|
||||
- **支持的绑定语法**:
|
||||
- `${propertyName}` - 基本属性绑定
|
||||
- `${object.property}` - 对象属性绑定
|
||||
- `${list[index]}` - 列表索引访问
|
||||
- `${function(param)}` - 函数调用(如日期格式化)
|
||||
|
||||
**2.2 数据绑定实现**
|
||||
|
||||
```csharp
|
||||
public class DataBinder : IDataBinder
|
||||
{
|
||||
public string Bind(string template, ReportContext context)
|
||||
{
|
||||
// 使用正则表达式匹配绑定表达式
|
||||
var pattern = @"\$\{([^\}]+)\}";
|
||||
return Regex.Replace(template, pattern, match =>
|
||||
{
|
||||
var expression = match.Groups[1].Value;
|
||||
return EvaluateExpression(expression, context);
|
||||
});
|
||||
}
|
||||
|
||||
private string EvaluateExpression(string expression, ReportContext context)
|
||||
{
|
||||
// 解析表达式并获取值
|
||||
if (expression.Contains("."))
|
||||
{
|
||||
// 处理对象属性
|
||||
var parts = expression.Split('.');
|
||||
var obj = context.GetType().GetProperty(parts[0])?.GetValue(context);
|
||||
return obj?.GetType().GetProperty(parts[1])?.GetValue(obj)?.ToString() ?? string.Empty;
|
||||
}
|
||||
else if (expression.Contains("["))
|
||||
{
|
||||
// 处理列表索引
|
||||
var index = int.Parse(expression.Split('[')[1].TrimEnd(']'));
|
||||
return context.Images.Count > index ? "Image" : string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 处理基本属性
|
||||
return context.GetType().GetProperty(expression)?.GetValue(context)?.ToString() ?? string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 自动排版功能
|
||||
|
||||
**3.1 布局引擎设计**
|
||||
|
||||
- **核心功能**:
|
||||
- **页面分隔**: 当内容超出一页时自动创建新页
|
||||
- **元素定位**: 根据模板定义计算元素在页面上的精确位置
|
||||
- **尺寸计算**: 自动调整元素大小以适应内容
|
||||
- **响应式布局**: 根据页面尺寸动态调整元素位置
|
||||
|
||||
**3.2 排版服务实现**
|
||||
|
||||
```csharp
|
||||
public class ImageLayoutService
|
||||
{
|
||||
public LayoutResult CalculateLayout(ReportTemplate template, ReportContext context)
|
||||
{
|
||||
var result = new LayoutResult();
|
||||
var currentPage = 0;
|
||||
var currentY = template.Document.Margins.Top;
|
||||
|
||||
foreach (var page in template.Pages)
|
||||
{
|
||||
foreach (var element in page.Elements)
|
||||
{
|
||||
// 计算元素位置
|
||||
var position = new Point(
|
||||
template.Document.Margins.Left + element.Position[0],
|
||||
currentY + element.Position[1]
|
||||
);
|
||||
|
||||
// 计算元素尺寸
|
||||
var size = new Size(element.Size[0], element.Size[1]);
|
||||
|
||||
// 检查是否需要换页
|
||||
if (position.Y + size.Height > PageSize.A4.Height - template.Document.Margins.Bottom)
|
||||
{
|
||||
currentPage++;
|
||||
currentY = template.Document.Margins.Top;
|
||||
position = new Point(
|
||||
template.Document.Margins.Left + element.Position[0],
|
||||
currentY + element.Position[1]
|
||||
);
|
||||
}
|
||||
|
||||
// 添加到布局结果
|
||||
result.Elements.Add(new LayoutElement
|
||||
{
|
||||
Page = currentPage,
|
||||
Element = element,
|
||||
Position = position,
|
||||
Size = size
|
||||
});
|
||||
|
||||
// 更新当前Y位置
|
||||
currentY = position.Y + size.Height;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 图表嵌入实现
|
||||
|
||||
**4.1 WPF图表转PDF图像**
|
||||
|
||||
- **实现方案**:
|
||||
- 使用`RenderTargetBitmap`将WPF控件渲染为位图
|
||||
- 支持高质量图像输出(300 DPI)
|
||||
- 提供图像压缩选项,平衡质量与文件大小
|
||||
|
||||
**4.2 图像转换服务**
|
||||
|
||||
```csharp
|
||||
public static class ImageExtensions
|
||||
{
|
||||
public static byte[] ToPdfImage(this ImageSource image, double width, double height)
|
||||
{
|
||||
var renderTarget = new RenderTargetBitmap(
|
||||
(int)width, (int)height,
|
||||
96, 96, PixelFormats.Pbgra32);
|
||||
|
||||
renderTarget.Render(image);
|
||||
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
var encoder = new PngBitmapEncoder();
|
||||
encoder.Frames.Add(BitmapFrame.Create(renderTarget));
|
||||
encoder.Save(stream);
|
||||
return stream.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] ToPdfImage(this FrameworkElement element, double width, double height)
|
||||
{
|
||||
var renderTarget = new RenderTargetBitmap(
|
||||
(int)width, (int)height,
|
||||
96, 96, PixelFormats.Pbgra32);
|
||||
|
||||
renderTarget.Render(element);
|
||||
|
||||
using (var stream = new MemoryStream())
|
||||
{
|
||||
var encoder = new PngBitmapEncoder();
|
||||
encoder.Frames.Add(BitmapFrame.Create(renderTarget));
|
||||
encoder.Save(stream);
|
||||
return stream.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 三、PDF生成核心流程
|
||||
|
||||
### 1. 生成步骤
|
||||
|
||||
1. **加载模板**: 从Templates目录加载JSON模板
|
||||
2. **解析上下文**: 将MeasurementData和图片列表转换为ReportContext
|
||||
3. **数据绑定**: 将上下文数据绑定到模板中的占位符
|
||||
4. **计算布局**: 确定每个元素在PDF页面上的精确位置
|
||||
5. **生成PDF**: 使用iTextSharp绘制内容到PDF文档
|
||||
6. **输出结果**: 返回MemoryStream或保存为文件
|
||||
|
||||
### 2. 核心生成服务
|
||||
|
||||
```csharp
|
||||
public class PdfGenerationService
|
||||
{
|
||||
public MemoryStream GeneratePdf(ReportContext context, string templatePath)
|
||||
{
|
||||
// 1. 加载模板
|
||||
var templateEngine = new TemplateEngine();
|
||||
var template = templateEngine.LoadTemplate(templatePath);
|
||||
|
||||
// 2. 解析模板元素
|
||||
var elements = templateEngine.ParseTemplate(template, context);
|
||||
|
||||
// 3. 计算布局
|
||||
var layoutService = new ImageLayoutService();
|
||||
var layout = layoutService.CalculateLayout(template, context);
|
||||
|
||||
// 4. 创建PDF文档
|
||||
var document = new Document(PageSize.A4,
|
||||
template.Document.Margins.Left,
|
||||
template.Document.Margins.Right,
|
||||
template.Document.Margins.Top,
|
||||
template.Document.Margins.Bottom);
|
||||
|
||||
var stream = new MemoryStream();
|
||||
var writer = PdfWriter.GetInstance(document, stream);
|
||||
document.Open();
|
||||
|
||||
// 5. 绘制内容
|
||||
DrawContent(document, layout, context);
|
||||
|
||||
// 6. 关闭文档
|
||||
document.Close();
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
private void DrawContent(Document document, LayoutResult layout, ReportContext context)
|
||||
{
|
||||
// 实现内容绘制逻辑
|
||||
foreach (var element in layout.Elements)
|
||||
{
|
||||
switch (element.Element.Type)
|
||||
{
|
||||
case "text":
|
||||
DrawText(document, element);
|
||||
break;
|
||||
case "image":
|
||||
DrawImage(document, element, context);
|
||||
break;
|
||||
case "table":
|
||||
DrawTable(document, element, context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawText(Document document, LayoutElement element)
|
||||
{
|
||||
var paragraph = new Paragraph(element.Element.Content);
|
||||
paragraph.SetLocation(element.Position.X, document.PageSize.Height - element.Position.Y);
|
||||
document.Add(paragraph);
|
||||
}
|
||||
|
||||
private void DrawImage(Document document, LayoutElement element, ReportContext context)
|
||||
{
|
||||
// 获取图像数据
|
||||
var imageData = context.Images.FirstOrDefault();
|
||||
if (imageData == null) return;
|
||||
|
||||
// 创建iTextSharp图像对象
|
||||
var image = Image.GetInstance(imageData);
|
||||
image.SetAbsolutePosition(element.Position.X, document.PageSize.Height - element.Position.Y - element.Size.Height);
|
||||
image.ScaleToFit(element.Size.Width, element.Size.Height);
|
||||
|
||||
document.Add(image);
|
||||
}
|
||||
|
||||
private void DrawTable(Document document, LayoutElement element, ReportContext context)
|
||||
{
|
||||
// 创建表格
|
||||
var table = new PdfPTable(3); // 假设3列
|
||||
table.WidthPercentage = 100;
|
||||
|
||||
// 添加表头
|
||||
table.AddCell(new PdfPCell(new Phrase("参数", new Font(Font.FontFamily.HELVETICA, 12, Font.BOLD))));
|
||||
table.AddCell(new PdfPCell(new Phrase("测量值", new Font(Font.FontFamily.HELVETICA, 12, Font.BOLD))));
|
||||
table.AddCell(new PdfPCell(new Phrase("标准值", new Font(Font.FontFamily.HELVETICA, 12, Font.BOLD))));
|
||||
|
||||
// 添加数据行
|
||||
foreach (var measurement in context.Measurements)
|
||||
{
|
||||
table.AddCell(new PdfPCell(new Phrase(measurement.Parameter)));
|
||||
table.AddCell(new PdfPCell(new Phrase(measurement.Value.ToString())));
|
||||
table.AddCell(new PdfPCell(new Phrase(measurement.StandardValue.ToString())));
|
||||
}
|
||||
|
||||
document.Add(table);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 四、最佳实践与建议
|
||||
|
||||
### 1. 性能优化
|
||||
|
||||
- **图像处理**: 对大尺寸图像进行**压缩和缩放**,避免PDF文件过大
|
||||
- **内存管理**: 使用`using`语句确保资源及时释放
|
||||
- **批量处理**: 对大量数据采用**分批处理**策略,避免UI冻结
|
||||
|
||||
### 2. 错误处理
|
||||
|
||||
- 实现**完善的异常捕获**机制,特别是图像转换和PDF生成环节
|
||||
- 提供**友好的错误提示**,便于问题排查
|
||||
- 添加**日志记录**功能,跟踪报告生成过程
|
||||
|
||||
### 3. 样式管理
|
||||
|
||||
- 创建**样式库**,统一字体、颜色、间距等
|
||||
- 支持**主题切换**,适应不同客户的品牌要求
|
||||
- 确保**打印友好**,考虑黑白打印时的可读性
|
||||
|
||||
### 4. 扩展性考虑
|
||||
|
||||
- **插件架构**: 设计可扩展的插件系统,便于添加新功能
|
||||
- **多格式输出**: 基于相同架构,扩展支持Word、Excel等格式输出
|
||||
- **云服务集成**: 考虑与云存储集成,自动上传生成的报告
|
||||
|
||||
通过以上设计,XP.ReportEngine将成为一个**灵活、高效、可维护**的PDF报告生成系统,满足工业CT检测报告的专业需求,同时保持系统的扩展性和易用性。
|
||||
@@ -0,0 +1,328 @@
|
||||
# XP.ReportEngine 模板定义规范
|
||||
|
||||
## **一、模板结构总览**
|
||||
|
||||
模板使用JSON格式定义,分为以下几个核心部分:
|
||||
|
||||
1.
|
||||
|
||||
`document`:文档基础配置(页面尺寸、边距等)。
|
||||
|
||||
2.
|
||||
|
||||
`pages`:页面列表,每个页面包含元素列表。
|
||||
|
||||
3.
|
||||
|
||||
`elements`:页面中的具体元素(文本、表格、图片等)。
|
||||
|
||||
4.
|
||||
|
||||
`styles`:预定义的样式,可复用。
|
||||
|
||||
5.
|
||||
|
||||
`dataKey`:数据绑定字段,关联业务数据。
|
||||
|
||||
## **二、首页布局规范**
|
||||
|
||||
**模板路径**:`Templates/Homepage.json`
|
||||
|
||||
代码<title>图标/24_new/复制</title>
|
||||
|
||||
```
|
||||
{
|
||||
"document": {
|
||||
"pageSize": "A4",
|
||||
"orientation": "Portrait",
|
||||
"margins": { "top": 30, "right": 20, "bottom": 20, "left": 20 }
|
||||
},
|
||||
"pages": [
|
||||
{
|
||||
"type": "homepage",
|
||||
"elements": [
|
||||
{ "type": "text", "content": "平面CT检测报告", "style": "title", "position": [10, 10] },
|
||||
{ "type": "text", "content": "报告编号:${reportId}", "style": "subtitle", "position": [10, 40] },
|
||||
{ "type": "text", "content": "检测日期:${inspectionDate}", "style": "subtitle", "position": [10, 60] },
|
||||
{ "type": "text", "content": "样品名称:${sampleName}", "style": "subtitle", "position": [10, 80] },
|
||||
{ "type": "divider", "position": [10, 100], "width": 190 },
|
||||
{ "type": "table", "dataKey": "summaryData", "columns": [...], "position": [10, 120], "size": [190, 60] }
|
||||
]
|
||||
}
|
||||
],
|
||||
"styles": {
|
||||
// ... 样式定义(见下文)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**关键要素说明**:
|
||||
|
||||
1.
|
||||
|
||||
**标题与元数据**:顶部显示报告标题、编号、日期和样品名称。
|
||||
|
||||
2.
|
||||
|
||||
**摘要表格**:显示关键统计信息(如缺陷数量、合格率等),通过`summaryData`绑定数据。
|
||||
|
||||
3.
|
||||
|
||||
**分隔线**:视觉分隔标题与主体内容。
|
||||
|
||||
## **三、测量数据布局规范**
|
||||
|
||||
**模板路径**:`Templates/MetricData.json`
|
||||
|
||||
代码<title>图标/24_new/复制</title>
|
||||
|
||||
```
|
||||
{
|
||||
"pages": [
|
||||
{
|
||||
"type": "metricData",
|
||||
"elements": [
|
||||
{ "type": "text", "content": "测量数据详情", "style": "sectionTitle", "position": [10, 10] },
|
||||
{ "type": "table", "dataKey": "measurements", "columns": [
|
||||
{ "header": "参数名称", "field": "parameter" },
|
||||
{ "header": "测量值", "field": "value" },
|
||||
{ "header": "单位", "field": "unit" }
|
||||
], "position": [10, 30], "size": [190, 100] }
|
||||
]
|
||||
}
|
||||
],
|
||||
"styles": {
|
||||
// ... 样式定义(见下文)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**关键要素说明**:
|
||||
|
||||
●
|
||||
|
||||
表格绑定`measurements`数据,列通过`field`属性绑定数据字段。
|
||||
|
||||
●
|
||||
|
||||
支持动态行数,自动调整表格高度。
|
||||
|
||||
## **四、缺陷检测布局规范**
|
||||
|
||||
**模板路径**:`Templates/DefectDetails.json`
|
||||
|
||||
代码<title>图标/24_new/复制</title>
|
||||
|
||||
```
|
||||
{
|
||||
"pages": [
|
||||
{
|
||||
"type": "defectDetails",
|
||||
"elements": [
|
||||
{ "type": "text", "content": "缺陷检测结果", "style": "sectionTitle", "position": [10, 10] },
|
||||
{
|
||||
"type": "grid", "dataKey": "defects",
|
||||
"columns": [
|
||||
{ "type": "image", "dataKey": "imagePath", "size": [90, 90], "border": true },
|
||||
{ "type": "text", "content": "缺陷类型:${type}", "style": "gridText", "width": 100 },
|
||||
{ "type": "text", "content": "位置:X=${x}, Y=${y}", "style": "gridText", "width": 100 }
|
||||
],
|
||||
"position": [10, 30], "colWidth": 100, "rowHeight": 100
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"styles": {
|
||||
"gridText": { "font": "Arial", "size": 10, "align": "left" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**关键要素说明**:
|
||||
|
||||
1.
|
||||
|
||||
**网格布局**:每行显示缺陷图片、类型和位置信息。
|
||||
|
||||
2.
|
||||
|
||||
使用`grid`元素自动排版多缺陷数据,支持水平滚动或分页。
|
||||
|
||||
3.
|
||||
|
||||
图片路径通过`imagePath`字段绑定,文本使用数据绑定语法(如`X=${x}`)。
|
||||
|
||||
## **五、BGA检测布局规范**
|
||||
|
||||
**模板路径**:`Templates/BGAInspection.json`
|
||||
|
||||
代码<title>图标/24_new/复制</title>
|
||||
|
||||
```
|
||||
{
|
||||
"pages": [
|
||||
{
|
||||
"type": "bgaDetails",
|
||||
"elements": [
|
||||
{ "type": "text", "content": "BGA焊点检测结果", "style": "sectionTitle", "position": [10, 10] },
|
||||
{
|
||||
"type": "image", "dataKey": "bgaTopViewImage", "position": [10, 30], "size": [190, 150], "border": true,
|
||||
"annotations": [
|
||||
{ "type": "point", "x": ${defectX}, "y": ${defectY}, "color": "red", "size": 5 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "table", "dataKey": "bgaMetrics",
|
||||
"columns": [ "引脚编号", "焊锡高度", "偏移量" ],
|
||||
"position": [10, 190], "size": [190, 80]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**关键要素说明**:
|
||||
|
||||
1.
|
||||
|
||||
**图片标注**:在BGA俯视图上标记缺陷位置(通过`annotations`)。
|
||||
|
||||
2.
|
||||
|
||||
**表格显示参数**:焊锡高度、偏移量等数据。
|
||||
|
||||
3.
|
||||
|
||||
支持动态标注点,绑定缺陷坐标数据。
|
||||
|
||||
## **六、气泡检测布局规范**
|
||||
|
||||
**模板路径**:`Templates/VoidInspection.json`
|
||||
|
||||
代码<title>图标/24_new/复制</title>
|
||||
|
||||
```
|
||||
{
|
||||
"pages": [
|
||||
{
|
||||
"type": "voidDetails",
|
||||
"elements": [
|
||||
{ "type": "text", "content": "气泡检测结果", "style": "sectionTitle", "position": [10, 10] },
|
||||
{
|
||||
"type": "image", "dataKey": "voidImage", "position": [10, 30], "size": [190, 150], "border": true,
|
||||
"annotations": [
|
||||
{ "type": "circle", "x": ${bubbleX}, "y": ${bubbleY}, "radius": ${bubbleRadius}, "color": "blue" }
|
||||
]
|
||||
},
|
||||
{ "type": "text", "content": "总气泡数:${voidCount}", "style": "resultText", "position": [10, 190] },
|
||||
{ "type": "text", "content": "最大气泡体积:${maxVoidVolume} mm³", "style": "resultText", "position": [10, 210] }
|
||||
]
|
||||
}
|
||||
],
|
||||
"styles": {
|
||||
"resultText": { "font": "Arial", "size": 14, "bold": true }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**关键要素说明**:
|
||||
|
||||
1.
|
||||
|
||||
**气泡可视化**:在图像上用圆形标注气泡位置、半径。
|
||||
|
||||
2.
|
||||
|
||||
**统计结果**:显示总数量、最大体积等关键指标。
|
||||
|
||||
3.
|
||||
|
||||
支持多气泡标注,数据绑定气泡坐标和尺寸。
|
||||
|
||||
## **七、通孔填锡率检测布局规范**
|
||||
|
||||
**模板路径**:`Templates/ViaFillInspection.json`
|
||||
|
||||
代码<title>图标/24_new/复制</title>
|
||||
|
||||
```
|
||||
{
|
||||
"pages": [
|
||||
{
|
||||
"type": "viaFillDetails",
|
||||
"elements": [
|
||||
{ "type": "text", "content": "通孔填锡率检测结果", "style": "sectionTitle", "position": [10, 10] },
|
||||
{
|
||||
"type": "image", "dataKey": "viaImage", "position": [10, 30], "size": [190, 150], "border": true,
|
||||
"annotations": [
|
||||
{ "type": "rectangle", "x": ${viaX}, "y": ${viaY}, "width": ${viaWidth}, "height": ${viaHeight}, "color": "green" }
|
||||
]
|
||||
},
|
||||
{ "type": "text", "content": "平均填锡率:${avgFillRate}%", "style": "resultText", "position": [10, 190] },
|
||||
{ "type": "progressBar", "value": ${avgFillRate}, "position": [10, 220], "size": [190, 20], "color": "${fillRateColor(avgFillRate)}" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"functions": {
|
||||
"fillRateColor(value)": "return value >= 90 ? 'green' : value >= 70 ? 'orange' : 'red';"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**关键要素说明**:
|
||||
|
||||
1.
|
||||
|
||||
**填锡率图示**:用矩形标注通孔位置,进度条显示填锡率。
|
||||
|
||||
2.
|
||||
|
||||
**颜色条件绑定**:通过函数`fillRateColor`根据数值动态设置颜色。
|
||||
|
||||
3.
|
||||
|
||||
支持多通孔标注,绑定位置和尺寸数据。
|
||||
|
||||
## **八、样式定义示例**
|
||||
|
||||
代码<title>图标/24_new/复制</title>
|
||||
|
||||
```
|
||||
"styles": {
|
||||
"title": { "font": "Arial", "size": 24, "bold": true, "color": "#000000" },
|
||||
"subtitle": { "font": "Arial", "size": 16, "italic": true, "color": "#666666" },
|
||||
"gridText": { "font": "Arial", "size": 10, "align": "left" },
|
||||
"resultText": { "font": "Arial", "size": 14, "bold": true }
|
||||
}
|
||||
```
|
||||
|
||||
**关键要素说明**:
|
||||
|
||||
●
|
||||
|
||||
样式可复用,通过`style`属性引用。
|
||||
|
||||
●
|
||||
|
||||
支持字体、大小、颜色、对齐等属性配置。
|
||||
|
||||
**# 总结**
|
||||
|
||||
●
|
||||
|
||||
**模块化设计**:各检测模块独立为单独模板文件,可灵活组合。
|
||||
|
||||
●
|
||||
|
||||
**数据绑定**:通过`${}`语法绑定业务数据(如`measurements`、`defects`等对象)。
|
||||
|
||||
●
|
||||
|
||||
**动态布局**:支持表格自适应高度、图片标注、条件样式等高级功能。
|
||||
|
||||
●
|
||||
|
||||
**可扩展性**:新增检测类型只需定义对应模板文件即可。
|
||||
|
||||
**备注**:实际使用时,需根据具体业务数据模型调整`dataKey`和字段名称,确保模板与输入数据匹配。
|
||||
@@ -0,0 +1,18 @@
|
||||
using XP.ReportEngine.Models;
|
||||
|
||||
namespace XP.ReportEngine.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据绑定器接口 | Data binder interface
|
||||
/// </summary>
|
||||
public interface IDataBinder
|
||||
{
|
||||
/// <summary>
|
||||
/// 将上下文数据绑定到模板 | Bind context data to template
|
||||
/// </summary>
|
||||
/// <param name="template">报告模板 | Report template</param>
|
||||
/// <param name="context">报告上下文 | Report context</param>
|
||||
/// <returns>绑定后的模板(元素内容已替换)| Bound template with resolved content</returns>
|
||||
ReportTemplate Bind(ReportTemplate template, ReportContext context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Collections.Generic;
|
||||
using XP.ReportEngine.Models;
|
||||
|
||||
namespace XP.ReportEngine.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// 排版引擎接口 | Layout engine interface
|
||||
/// </summary>
|
||||
public interface ILayoutEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// 计算页面布局 | Calculate page layout
|
||||
/// </summary>
|
||||
/// <param name="template">绑定后的模板 | Bound template</param>
|
||||
/// <param name="options">生成选项 | Generation options</param>
|
||||
/// <returns>排版后的页面列表 | List of laid-out pages</returns>
|
||||
List<LayoutPage> CalculateLayout(ReportTemplate template, ReportGenerationOptions options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using XP.ReportEngine.Models;
|
||||
|
||||
namespace XP.ReportEngine.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// PDF 渲染器接口 | PDF renderer interface
|
||||
/// </summary>
|
||||
public interface IPdfRenderer
|
||||
{
|
||||
/// <summary>
|
||||
/// 将排版结果渲染为 PDF | Render layout result to PDF
|
||||
/// </summary>
|
||||
/// <param name="pages">排版后的页面列表 | Laid-out pages</param>
|
||||
/// <param name="options">生成选项 | Generation options</param>
|
||||
/// <param name="template">绑定后的模板(用于页眉页脚配置)| Bound template (for header/footer config)</param>
|
||||
/// <returns>PDF 内存流 | PDF memory stream</returns>
|
||||
MemoryStream Render(List<LayoutPage> pages, ReportGenerationOptions options, ReportTemplate template = null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Collections.Generic;
|
||||
using XP.ReportEngine.Models;
|
||||
|
||||
namespace XP.ReportEngine.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// 报告数据适配器接口 | Report data adapter interface
|
||||
/// 将 XP.ImageProcessing 的 OutputData 转换为 ReportContext
|
||||
/// </summary>
|
||||
public interface IReportDataAdapter
|
||||
{
|
||||
/// <summary>
|
||||
/// 将处理器输出数据适配为报告上下文 | Adapt processor output data to report context
|
||||
/// </summary>
|
||||
/// <param name="processorOutputs">处理器输出字典列表 | List of processor output dictionaries</param>
|
||||
/// <param name="metadata">报告元数据 | Report metadata</param>
|
||||
/// <returns>报告上下文 | Report context</returns>
|
||||
ReportContext Adapt(List<ProcessorOutput> processorOutputs, ReportMetadata metadata);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Threading.Tasks;
|
||||
using XP.ReportEngine.Models;
|
||||
|
||||
namespace XP.ReportEngine.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// 报告生成器接口(格式无关)| Report generator interface (format-agnostic)
|
||||
/// </summary>
|
||||
public interface IReportGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 异步生成报告 | Generate report asynchronously
|
||||
/// </summary>
|
||||
/// <param name="context">报告上下文数据 | Report context data</param>
|
||||
/// <param name="options">生成选项 | Generation options</param>
|
||||
/// <returns>生成结果 | Generation result</returns>
|
||||
Task<ReportResult> GenerateAsync(ReportContext context, ReportGenerationOptions options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using XP.ReportEngine.Models;
|
||||
|
||||
namespace XP.ReportEngine.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// 报告生成器工厂接口 | Report generator factory interface
|
||||
/// </summary>
|
||||
public interface IReportGeneratorFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据输出格式创建生成器 | Create generator by output format
|
||||
/// </summary>
|
||||
IReportGenerator Create(ReportOutputFormat format);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.Threading.Tasks;
|
||||
using XP.ReportEngine.Models;
|
||||
|
||||
namespace XP.ReportEngine.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// 报告服务接口(门面)| Report service interface (Facade)
|
||||
/// 提供完整的报告生成流程,外部模块通过此接口调用报告功能
|
||||
/// Provides complete report generation workflow for external modules
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 使用示例 | Usage example:
|
||||
/// <code>
|
||||
/// var request = new ReportRequest
|
||||
/// {
|
||||
/// ProcessorOutputs = processorOutputs,
|
||||
/// Metadata = new ReportMetadata
|
||||
/// {
|
||||
/// SampleName = "PCB-001",
|
||||
/// OperatorName = "Operator",
|
||||
/// InspectionDate = DateTime.Now
|
||||
/// }
|
||||
/// };
|
||||
/// var result = await _reportService.GenerateAsync(request);
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public interface IReportService
|
||||
{
|
||||
/// <summary>
|
||||
/// 生成报告 | Generate report
|
||||
/// 执行完整流程:生成报告ID → 数据适配 → 上下文组装 → 管线生成 → 文件保存
|
||||
/// Executes full workflow: generate report ID → data adaptation → context assembly → pipeline generation → file saving
|
||||
/// </summary>
|
||||
/// <param name="request">报告生成请求 | Report generation request</param>
|
||||
/// <returns>报告生成结果 | Report generation result</returns>
|
||||
Task<ReportServiceResult> GenerateAsync(ReportRequest request);
|
||||
|
||||
/// <summary>
|
||||
/// 预热报告引擎(建议在应用启动后台调用)| Warm up report engine (recommended to call in background on app startup)
|
||||
/// 触发 iText7 初始化、字体加载、JIT 编译等一次性开销,避免首次生成报告时卡顿
|
||||
/// Triggers iText7 initialization, font loading, JIT compilation to avoid first-run latency
|
||||
/// </summary>
|
||||
Task WarmUpAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using XP.ReportEngine.Models;
|
||||
|
||||
namespace XP.ReportEngine.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// 模板引擎接口 | Template engine interface
|
||||
/// </summary>
|
||||
public interface ITemplateEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// 加载并验证模板 | Load and validate template
|
||||
/// </summary>
|
||||
/// <param name="templatePath">模板文件路径 | Template file path</param>
|
||||
/// <returns>解析后的模板对象 | Parsed template object</returns>
|
||||
ReportTemplate LoadTemplate(string templatePath);
|
||||
|
||||
/// <summary>
|
||||
/// 验证模板结构完整性 | Validate template structure integrity
|
||||
/// </summary>
|
||||
TemplateValidationResult Validate(ReportTemplate template);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
namespace XP.ReportEngine.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// 图像数据封装 | Image data wrapper
|
||||
/// </summary>
|
||||
public class ImageData
|
||||
{
|
||||
/// <summary>
|
||||
/// 图像来源类型 | Image source type
|
||||
/// </summary>
|
||||
public ImageSourceType SourceType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 字节数组数据(当 SourceType 为 Bytes 时)| Byte array data
|
||||
/// </summary>
|
||||
public byte[] Bytes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文件路径(当 SourceType 为 FilePath 时)| File path
|
||||
/// </summary>
|
||||
public string FilePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// BitmapSource 引用(当 SourceType 为 BitmapSource 时)| BitmapSource reference
|
||||
/// </summary>
|
||||
public object BitmapSource { get; set; }
|
||||
}
|
||||
|
||||
public enum ImageSourceType
|
||||
{
|
||||
Bytes,
|
||||
FilePath,
|
||||
BitmapSource
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace XP.ReportEngine.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// 排版后的页面 | Laid-out page
|
||||
/// </summary>
|
||||
public class LayoutPage
|
||||
{
|
||||
public int PageNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 页面类型(来自模板定义:homepage / metricData / bgaInspection 等)
|
||||
/// Page type from template definition
|
||||
/// </summary>
|
||||
public string PageType { get; set; }
|
||||
|
||||
public List<LayoutElement> Elements { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 排版后的元素(含计算后的绝对坐标)| Laid-out element with computed absolute coordinates
|
||||
/// </summary>
|
||||
public class LayoutElement
|
||||
{
|
||||
public TemplateElement Source { get; set; }
|
||||
public float X { get; set; }
|
||||
public float Y { get; set; }
|
||||
public float Width { get; set; }
|
||||
public float Height { get; set; }
|
||||
public StyleDefinition ResolvedStyle { get; set; }
|
||||
public string ResolvedContent { get; set; }
|
||||
public List<Dictionary<string, object>> ResolvedTableData { get; set; }
|
||||
public ImageData ResolvedImage { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace XP.ReportEngine.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// 处理器输出数据封装 | Processor output data wrapper
|
||||
/// </summary>
|
||||
public class ProcessorOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 处理器类型名称 | Processor type name
|
||||
/// </summary>
|
||||
public string ProcessorType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 输出数据字典 | Output data dictionary
|
||||
/// </summary>
|
||||
public Dictionary<string, object> OutputData { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 关联的已标注图像 | Associated annotated image
|
||||
/// </summary>
|
||||
public ImageData AnnotatedImage { get; set; }
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user