新增分析报告自动生成(可编辑存在问题),新增效率刻度界面(功能未实现)

This commit is contained in:
anxinglong 2026-06-29 18:50:15 +08:00
parent eae91f1813
commit 9d1164e336
14 changed files with 1857 additions and 6 deletions

14
src/EfficiencyScale.cpp Normal file
View File

@ -0,0 +1,14 @@
#include "EfficiencyScale.h"
#include "ui_EfficiencyScale.h"
EfficiencyScale::EfficiencyScale(QWidget *parent)
: QDialog(parent)
, ui(new Ui::EfficiencyScale)
{
ui->setupUi(this);
}
EfficiencyScale::~EfficiencyScale()
{
delete ui;
}

22
src/EfficiencyScale.h Normal file
View File

@ -0,0 +1,22 @@
#ifndef EFFICIENCYSCALE_H
#define EFFICIENCYSCALE_H
#include <QDialog>
namespace Ui {
class EfficiencyScale;
}
class EfficiencyScale : public QDialog
{
Q_OBJECT
public:
explicit EfficiencyScale(QWidget *parent = nullptr);
~EfficiencyScale();
private:
Ui::EfficiencyScale *ui;
};
#endif // EFFICIENCYSCALE_H

342
src/EfficiencyScale.ui Normal file
View File

@ -0,0 +1,342 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>EfficiencyScale</class>
<widget class="QDialog" name="EfficiencyScale">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1173</width>
<height>709</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="2,7">
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>效率刻度管理列表</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QListWidget" name="listWidget"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_9">
<item>
<widget class="QPushButton" name="pBtn_Add_File">
<property name="text">
<string>添加</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pBtn_Delete_File">
<property name="text">
<string>删除</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_6" stretch="3,5">
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,4">
<item>
<layout class="QVBoxLayout" name="verticalLayout_3" stretch="3,1,5,0,1,1">
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>效率刻度信息</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QGridLayout" name="gridLayout" columnstretch="2,3" rowminimumheight="2,3" columnminimumwidth="0,0">
<item row="0" column="0">
<widget class="QLabel" name="label_5">
<property name="minimumSize">
<size>
<width>85</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>效率刻度命名:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit_name"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_6">
<property name="minimumSize">
<size>
<width>85</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>备注说明:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPlainTextEdit" name="plainTextEdit_description">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QPushButton" name="pBtn_Save">
<property name="text">
<string>保存</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pBtn_SaveAs">
<property name="text">
<string>另存为</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>公式拟合</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>拟合公式:</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QComboBox" name="comboBox_fit_type">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pBtn_fitting">
<property name="text">
<string>拟合</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="label_fit_type_text">
<property name="minimumSize">
<size>
<width>287</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>y = a + bx</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_fit_result">
<property name="minimumSize">
<size>
<width>287</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>a = 1.21823, b = -5.0542</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>效率刻度拟合数据</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QPushButton" name="pBtn_Add">
<property name="text">
<string>新增</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pBtn_Delete">
<property name="text">
<string>删除</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QWidget" name="widget" native="true">
<property name="styleSheet">
<string notr="true"/>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QVBoxLayout" name="layout_fittingCurve">
<property name="spacing">
<number>0</number>
</property>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTableWidget" name="tablew_scale_data">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<attribute name="horizontalHeaderCascadingSectionResizes">
<bool>true</bool>
</attribute>
<column>
<property name="text">
<string>序号</string>
</property>
</column>
<column>
<property name="text">
<string>能量(key)</string>
</property>
</column>
<column>
<property name="text">
<string>净计数率</string>
</property>
</column>
<column>
<property name="text">
<string>探测效率</string>
</property>
</column>
<column>
<property name="text">
<string>拟合效率</string>
</property>
</column>
<column>
<property name="text">
<string>误差</string>
</property>
</column>
<column>
<property name="text">
<string>刻度日期</string>
</property>
</column>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -36,6 +36,7 @@
#include "EnergyScaleDataModel.h" #include "EnergyScaleDataModel.h"
#include "EnergyCountPlotView.h" #include "EnergyCountPlotView.h"
#include "ParticleCountPlotView.h" #include "ParticleCountPlotView.h"
#include "reportwidget.h"
#include <array> #include <array>
#include "csv.h" #include "csv.h"
#include <fstream> #include <fstream>
@ -869,3 +870,26 @@ void MainWindow::onParticleEnergyDataUpdated(const QString &energySpectrumFilena
// 更新数据表格 // 更新数据表格
// updataTable(); // updataTable();
} }
void MainWindow::on_action_analysisReport_triggered()
{
ReportWidget *widget = new ReportWidget();
widget->show();
}
#include "EfficiencyScale.h"
void MainWindow::on_action_efficiency_scale_mrg_triggered()
{
// QDialog energy_scale_mrg_dlg;
// energy_scale_mrg_dlg.setObjectName("EfficiencyScaleFormDlg");
// energy_scale_mrg_dlg.setWindowTitle(QStringLiteral(u"效率刻度管理"));
// energy_scale_mrg_dlg.setWindowFlags(energy_scale_mrg_dlg.windowFlags() | Qt::WindowMaximizeButtonHint);
EfficiencyScale* efficiency_scale_mrg_form = new EfficiencyScale();
efficiency_scale_mrg_form->setWindowTitle(QStringLiteral(u"效率刻度管理"));
efficiency_scale_mrg_form->setWindowFlags(efficiency_scale_mrg_form->windowFlags() | Qt::WindowMaximizeButtonHint);
// QHBoxLayout* hlayout = new QHBoxLayout;
// hlayout->addWidget(efficiency_scale_mrg_form);
// efficiency_scale_mrg_form.setLayout(hlayout);
efficiency_scale_mrg_form->exec();
}

View File

@ -106,6 +106,10 @@ private slots:
void onMeasureStopped(bool success, const QString& message); void onMeasureStopped(bool success, const QString& message);
void onDeviceStatusMessage(const QString& msg); void onDeviceStatusMessage(const QString& msg);
void on_action_analysisReport_triggered();
void on_action_efficiency_scale_mrg_triggered();
private: private:
QMutex _mutex_info_output; QMutex _mutex_info_output;
QPlainTextEdit* _plain_edit_info_output; QPlainTextEdit* _plain_edit_info_output;

View File

@ -20,7 +20,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1079</width> <width>1079</width>
<height>22</height> <height>21</height>
</rect> </rect>
</property> </property>
<widget class="QMenu" name="menu_measurement_analysis"> <widget class="QMenu" name="menu_measurement_analysis">
@ -112,6 +112,7 @@
<addaction name="action_roi"/> <addaction name="action_roi"/>
<addaction name="action_efficiency_scale_mrg"/> <addaction name="action_efficiency_scale_mrg"/>
<addaction name="action_nuclideLib"/> <addaction name="action_nuclideLib"/>
<addaction name="action_analysisReport"/>
</widget> </widget>
<widget class="QToolBar" name="toolBar"> <widget class="QToolBar" name="toolBar">
<property name="windowTitle"> <property name="windowTitle">
@ -230,6 +231,14 @@
<enum>QAction::TextHeuristicRole</enum> <enum>QAction::TextHeuristicRole</enum>
</property> </property>
</action> </action>
<action name="action_analysisReport">
<property name="text">
<string>分析报告自动生成</string>
</property>
<property name="menuRole">
<enum>QAction::NoRole</enum>
</property>
</action>
</widget> </widget>
<resources/> <resources/>
<connections/> <connections/>

View File

@ -321,6 +321,8 @@ void NuclideAnalysisView::onActionPlotClear()
m_allFitResults.clear(); m_allFitResults.clear();
m_nuclideTable->setRowCount(0);
m_bSelecting = false; m_bSelecting = false;
m_bSelectHS = false; m_bSelectHS = false;

View File

@ -0,0 +1,687 @@
#include "reportgenerator.h"
#include <QFile>
#include <QTextStream>
#include <QLocale>
ReportGenerator::ReportGenerator()
{
}
void ReportGenerator::setData(const SpectrumReportData &data)
{
m_data = data;
}
QString ReportGenerator::formatScientific(double value, int precision) const
{
if (value == 0.0) {
return QString("0.0000E+00");
}
// 格式化为科学计数法,类似 2.7056E-01
QString result = QString::number(value, 'E', precision);
// 确保指数部分有两位
QStringList parts = result.split('E');
if (parts.size() == 2) {
QString mantissa = parts[0];
QString exponent = parts[1];
// 处理指数符号和位数
QChar sign = exponent[0];
QString num = exponent.mid(1);
bool ok;
int expNum = num.toInt(&ok);
if (ok) {
exponent = QString("%1%2").arg(sign).arg(QString::number(expNum).rightJustified(2, '0'));
}
result = QString("%1E%2").arg(mantissa).arg(exponent);
}
return result;
}
QString ReportGenerator::formatDecimal(double value, int precision) const
{
return QString::number(value, 'f', precision);
}
QString ReportGenerator::formatDateTime(const QDateTime &dt) const
{
return dt.toString("yyyy/M/d hh:mm:ss");
}
QString ReportGenerator::generateHtmlHeader() const
{
return R"(
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title></title>
<style>
body {
font-family: "SimSun", "宋体", serif;
font-size: 12px;
margin: 20px;
line-height: 1.6;
}
.report-title {
text-align: center;
font-size: 18px;
font-weight: bold;
margin-bottom: 20px;
}
.section-title {
font-weight: bold;
font-size: 14px;
margin-top: 15px;
margin-bottom: 10px;
border-bottom: 1px solid #333;
padding-bottom: 5px;
}
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 10px;
}
table, th, td {
border: 1px solid #333;
}
th, td {
padding: 6px 8px;
text-align: left;
font-size: 12px;
}
th {
background-color: #f0f0f0;
font-weight: bold;
}
.param-table td:first-child {
width: 150px;
font-weight: bold;
}
.peak-table {
font-size: 11px;
}
.peak-table th {
text-align: center;
background-color: #e8e8e8;
}
.peak-table td {
text-align: center;
}
.signature-section {
margin-top: 30px;
text-align: right;
}
.signature-line {
margin-bottom: 8px;
}
.spectrum-file-header {
text-align: center;
font-weight: bold;
margin-bottom: 10px;
}
.legend {
margin-top: 10px;
font-size: 11px;
color: #555;
}
.legend-item {
margin-bottom: 3px;
}
</style>
</head>
<body>
)";
}
QString ReportGenerator::generateMeasurementSection() const
{
QString html;
html += QString("<div class='spectrum-file-header'>能谱文件:%1</div>")
.arg(m_data.measurement.spectrumFile);
html += "<div class='section-title'>测量参数</div>";
html += "<table class='param-table'>";
html += QString("<tr><td>开始时间:</td><td>%1</td>"
"<td>活时间S</td><td>%2</td></tr>")
.arg(formatDateTime(m_data.measurement.startTime))
.arg(formatDecimal(m_data.measurement.liveTime, 0));
html += QString("<tr><td>实时间S</td><td>%1</td>"
"<td>死时间:</td><td>%2 %</td></tr>")
.arg(formatDecimal(m_data.measurement.realTime, 0))
.arg(formatDecimal(m_data.measurement.deadTime, 2));
html += "</table>";
return html;
}
QString ReportGenerator::generateCalibrationSection() const
{
QString html;
html += "<div class='section-title'>刻度</div>";
html += "<table class='param-table'>";
html += QString("<tr><td>文件:</td><td>%1</td>"
"<td>刻度标准源:</td><td>%2</td></tr>")
.arg(m_data.calibration.fileName)
.arg(m_data.calibration.standardSource);
html += "</table>";
// 能量刻度
html += "<div style='margin-left: 20px; margin-top: 10px;'>";
html += "<div style='font-weight: bold;'>能量刻度</div>";
html += "<table class='param-table'>";
html += QString("<tr><td>生成时间:</td><td>%1</td>"
"<td>截距:</td><td>%2 keV</td></tr>")
.arg(formatDateTime(m_data.calibration.energy.generateTime))
.arg(formatDecimal(m_data.calibration.energy.intercept, 3));
html += QString("<tr><td>斜率:</td><td>%1 keV/ch</td>"
"<td>二次项系数:</td><td>%2 keV/(ch^2)</td></tr>")
.arg(formatDecimal(m_data.calibration.energy.slope, 3))
.arg(formatScientific(m_data.calibration.energy.quadratic, 3));
html += "</table>";
html += "</div>";
// 效率刻度
html += "<div style='margin-left: 20px; margin-top: 10px;'>";
html += "<div style='font-weight: bold;'>效率刻度</div>";
html += "<table class='param-table'>";
html += QString("<tr><td>生成时间:</td><td>%1</td>"
"<td>拐点能量keV</td><td>%2</td></tr>")
.arg(formatDateTime(m_data.calibration.efficiency.generateTime))
.arg(formatDecimal(m_data.calibration.efficiency.inflectionEnergy, 2));
html += QString("<tr><td>拐点以上:</td><td>%1</td>"
"<td>误差:</td><td>%2%</td></tr>")
.arg(m_data.calibration.efficiency.aboveInflection)
.arg(formatDecimal(m_data.calibration.efficiency.aboveError, 2));
html += QString("<tr><td>拐点以下:</td><td>%1</td>"
"<td>误差:</td><td>%2%</td></tr>")
.arg(m_data.calibration.efficiency.belowInflection)
.arg(formatDecimal(m_data.calibration.efficiency.belowError, 2));
html += QString("<tr><td>效率对数方程式:</td><td colspan='3'>%1</td></tr>")
.arg(m_data.calibration.efficiency.logEquation);
html += "</table>";
html += "</div>";
return html;
}
QString ReportGenerator::generateNuclideLibSection() const
{
QString html;
html += "<div class='section-title'>核素库</div>";
html += "<table class='param-table'>";
html += QString("<tr><td>文件:</td><td>%1</td>"
"<td>峰匹配宽度keV</td><td>%2 * FWHM</td></tr>")
.arg(m_data.nuclideLib.fileName)
.arg(formatDecimal(m_data.nuclideLib.peakMatchWidth, 3));
html += "</table>";
return html;
}
QString ReportGenerator::generateAnalysisSection() const
{
QString html;
html += "<div class='section-title'>分析参数</div>";
html += "<table class='param-table'>";
html += QString("<tr><td>起始能量(keV)</td><td>%1</td>"
"<td>终止能量keV</td><td>%2</td></tr>")
.arg(formatDecimal(m_data.analysis.startEnergy, 2))
.arg(formatDecimal(m_data.analysis.endEnergy, 2));
html += QString("<tr><td>统计误差限值:</td><td>%1%</td>"
"<td>活度比例系数:</td><td>%2</td></tr>")
.arg(formatDecimal(m_data.analysis.statErrorLimit, 3))
.arg(formatScientific(m_data.analysis.activityRatio, 4));
html += QString("<tr><td>探测限计算方法:</td><td colspan='3'>%1</td></tr>")
.arg(m_data.analysis.mdaMethod);
html += "</table>";
return html;
}
QString ReportGenerator::generatePeakTableSection() const
{
QString html;
html += "<div class='section-title'>能 谱 峰 一 览 表</div>";
html += "<table class='peak-table'>";
// 表头 - 第一行
html += "<tr>";
html += "<th rowspan='2'>核素</th>";
html += "<th rowspan='2'>平均活度贝克Bq</th>";
html += "<th rowspan='2'>不确定度</th>";
html += "<th colspan='4'>峰</th>";
html += "<th rowspan='2'>备注</th>";
html += "</tr>";
// 表头 - 第二行
html += "<tr>";
html += "<th>能量keV</th>"; // 峰子项1
html += "<th>活度贝克Bq</th>"; // 峰子项2
html += "<th>不确定度</th>"; // 峰子项3
html += "<th>最小可探测活度MDA贝克Bq</th>"; // 峰子项4
html += "</tr>";
// 数据行8列与表头对应
for (const auto& peak : m_data.peaks) {
html += "<tr>";
html += QString("<td>%1</td>").arg(peak.nuclide);
html += QString("<td>%1</td>").arg(formatScientific(peak.avgActivity, 4));
html += QString("<td>%1%</td>").arg(formatDecimal(peak.avgUncertainty, 2));
html += QString("<td>%1</td>").arg(formatDecimal(peak.energy, 2));
html += QString("<td>%1</td>").arg(formatScientific(peak.peakActivity, 3));
html += QString("<td>%1%</td>").arg(formatDecimal(peak.peakUncertainty, 2));
html += QString("<td>%1</td>").arg(formatScientific(peak.mda, 3));
html += QString("<td>%1</td>").arg(peak.remark);
html += "</tr>";
}
html += "</table>";
// 峰提示码说明
html += "<div class='legend'>";
html += "<div style='font-weight: bold; margin-bottom: 5px;'>峰提示码说明:</div>";
html += "<div class='legend-item'>G - γ射线</div>";
html += "<div class='legend-item'>X - X射线</div>";
html += "<div class='legend-item'>ec - 内转换电子</div>";
html += "</div>";
return html;
}
QString ReportGenerator::generateSignatureSection() const
{
QString html;
html += "<div class='signature-section'>";
html += QString("<div class='signature-line'>分析员:%1</div>")
.arg(m_data.signature.analyst);
html += QString("<div class='signature-line'>审查员:%1</div>")
.arg(m_data.signature.reviewer);
html += QString("<div class='signature-line'>实验室:%1</div>")
.arg(m_data.signature.laboratory);
html += QString("<div class='signature-line'>日 期:%1</div>")
.arg(m_data.signature.reportDate.toString("yyyy年MM月dd日"));
html += "</div>";
return html;
}
QString ReportGenerator::generateHtmlFooter() const
{
return "</body>\n</html>";
}
QString ReportGenerator::generateHtmlReport() const
{
QString html;
html += generateHtmlHeader();
html += "<div class='report-title'>符合能谱分析报告</div>";
html += generateMeasurementSection();
html += generateCalibrationSection();
html += generateNuclideLibSection();
html += generateAnalysisSection();
html += generatePeakTableSection();
html += generateSignatureSection();
html += generateHtmlFooter();
return html;
}
QString ReportGenerator::generateTextReport() const
{
QString text;
QTextStream stream(&text);
stream << QStringLiteral(u"符合能谱分析报告\n");
stream << "========================================\n\n";
stream << QStringLiteral(u"能谱文件: ") << m_data.measurement.spectrumFile << "\n\n";
stream << QStringLiteral(u"测量参数\n");
stream << "----------------------------------------\n";
stream << QStringLiteral(u" 开始时间: ") << formatDateTime(m_data.measurement.startTime) << "\n";
stream << QStringLiteral(u" 活时间: ") << formatDecimal(m_data.measurement.liveTime, 0) << "\n";
stream << QStringLiteral(u" 实时间: ") << formatDecimal(m_data.measurement.realTime, 0) << "\n";
stream << QStringLiteral(u" 死时间: ") << formatDecimal(m_data.measurement.deadTime, 2) << " %\n\n";
stream << QStringLiteral(u"刻度\n");
stream << "----------------------------------------\n";
stream << QStringLiteral(u" 文件: ") << m_data.calibration.fileName << "\n\n";
stream << QStringLiteral(u" 能量刻度\n");
stream << QStringLiteral(u" 生成时间: ") << formatDateTime(m_data.calibration.energy.generateTime) << "\n";
stream << QStringLiteral(u" 截距: ") << formatDecimal(m_data.calibration.energy.intercept, 3) << " keV\n";
stream << QStringLiteral(u" 斜率: ") << formatDecimal(m_data.calibration.energy.slope, 3) << " keV/ch\n";
stream << QStringLiteral(u" 二次项系数: ") << formatScientific(m_data.calibration.energy.quadratic, 3) << " keV/(ch^2)\n\n";
stream << QStringLiteral(u" 效率刻度\n");
stream << QStringLiteral(u" 生成时间: ") << formatDateTime(m_data.calibration.efficiency.generateTime) << "\n";
stream << QStringLiteral(u" 拐点能量: " )<< formatDecimal(m_data.calibration.efficiency.inflectionEnergy, 2) << " keV\n";
stream << QStringLiteral(u" 拐点以上: ") << m_data.calibration.efficiency.aboveInflection
<< QStringLiteral(u" 误差 = ") << formatDecimal(m_data.calibration.efficiency.aboveError, 2) << " %\n";
stream << QStringLiteral(u" 拐点以下: ") << m_data.calibration.efficiency.belowInflection
<< QStringLiteral(u" 误差 = ") << formatDecimal(m_data.calibration.efficiency.belowError, 2) << " %\n";
stream << QStringLiteral(u" 效率对数方程式: ") << m_data.calibration.efficiency.logEquation << "\n\n";
stream << QStringLiteral(u"核素库\n");
stream << "----------------------------------------\n";
stream << QStringLiteral(u" 文件名: ") << m_data.nuclideLib.fileName << "\n";
stream << QStringLiteral(u" 峰匹配宽度: ") << formatDecimal(m_data.nuclideLib.peakMatchWidth, 3) << " * FWHM (keV)\n\n";
stream << QStringLiteral(u"分析参数\n");
stream << QStringLiteral(u"----------------------------------------\n");
stream << QStringLiteral(u" 起始能量: " )<< formatDecimal(m_data.analysis.startEnergy, 2) << " keV\n";
stream << QStringLiteral(u" 终止能量: ") << formatDecimal(m_data.analysis.endEnergy, 2) << " keV\n";
stream << QStringLiteral(u" 统计误差限值: ") << formatDecimal(m_data.analysis.statErrorLimit, 3) << "%\n";
stream << QStringLiteral(u" 活度比例系数: ") << formatScientific(m_data.analysis.activityRatio, 4) << "\n";
stream << QStringLiteral(u" 探测限计算方法: " )<< m_data.analysis.mdaMethod << "\n\n";
stream << QStringLiteral(u"********************* 能 谱 峰 一 览 表 ******************\n\n");
stream << QString("%1 %2 %3 %4 %5 %6 %7\n")
.arg("核素", -10)
.arg("平均活度(Bq)", -15)
.arg("能量(keV)", -12)
.arg("峰活度(Bq)", -15)
.arg("MDA(Bq)", -15)
.arg("提示码", -8)
.arg("备注", -6);
stream << "------------------------------------------------------------------------\n";
for (const auto& peak : m_data.peaks) {
stream << QString("%1 %2 %3 %4 %5 %6 %7\n")
.arg(peak.nuclide, -10)
.arg(formatScientific(peak.avgActivity, 4), -15)
.arg(formatDecimal(peak.energy, 2), -12)
.arg(formatScientific(peak.peakActivity, 3), -15)
.arg(formatScientific(peak.mda, 3), -15)
.arg(peak.peakCode, -8)
.arg(peak.remark, -6);
}
stream << "\n\n";
stream << QStringLiteral(u"分析员: " )<< m_data.signature.analyst << "\n";
stream << QStringLiteral(u"审查员: " )<< m_data.signature.reviewer << "\n";
stream << QStringLiteral(u"实验室: " )<< m_data.signature.laboratory << "\n";
stream << QStringLiteral(u"日 期: ") << m_data.signature.reportDate.toString("yyyy年MM月dd日") << "\n";
return text;
}
bool ReportGenerator::saveToFile(const QString &filePath) const
{
QFile file(filePath);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
return false;
}
QTextStream out(&file);
out.setCodec("UTF-8");
if (filePath.endsWith(".html", Qt::CaseInsensitive) ||
filePath.endsWith(".htm", Qt::CaseInsensitive)) {
out << generateHtmlReport();
} else {
out << generateTextReport();
}
file.close();
return true;
}
QString ReportGenerator::generateWordReport() const
{
QString html;
QTextStream stream(&html);
stream << "<html xmlns:o='urn:schemas-microsoft-com:office:office'\n";
stream << " xmlns:w='urn:schemas-microsoft-com:office:word'\n";
stream << " xmlns='http://www.w3.org/TR/REC-html40'>\n";
stream << "<head>\n";
stream << "<meta charset='UTF-8'>\n";
stream << "<title>符合能谱分析报告</title>\n";
stream << "<!--[if gte mso 9]>\n";
stream << "<xml>\n";
stream << "<w:WordDocument>\n";
stream << "<w:View>Print</w:View>\n";
stream << "<w:Zoom>100</w:Zoom>\n";
stream << "<w:DoNotOptimizeForBrowser/>\n";
stream << "</w:WordDocument>\n";
stream << "</xml>\n";
stream << "<![endif]-->\n";
stream << "<style>\n";
stream << " body {\n";
stream << " font-family: 'SimSun', '宋体', serif;\n";
stream << " font-size: 12pt;\n";
stream << " line-height: 1.5;\n";
stream << " margin: 2.54cm 3.17cm;\n";
stream << " }\n";
stream << " .report-title {\n";
stream << " text-align: center;\n";
stream << " font-size: 18pt;\n";
stream << " font-weight: bold;\n";
stream << " margin-bottom: 20pt;\n";
stream << " }\n";
stream << " .section-title {\n";
stream << " font-weight: bold;\n";
stream << " font-size: 14pt;\n";
stream << " margin-top: 15pt;\n";
stream << " margin-left: 0pt;\n";
stream << " margin-bottom: 8pt;\n";
stream << " border-bottom: 1px solid #000;\n";
stream << " padding-bottom: 3pt;\n";
stream << " }\n";
stream << " table {\n";
stream << " width: 100%;\n";
stream << " border-collapse: collapse;\n";
stream << " margin-bottom: 8pt;\n";
stream << " font-size: 11pt;\n";
stream << " }\n";
stream << " table, th, td {\n";
stream << " border: 1px solid #000;\n";
stream << " }\n";
stream << " th, td {\n";
stream << " padding: 5pt 8pt;\n";
stream << " text-align: left;\n";
stream << " }\n";
stream << " th {\n";
stream << " background-color: #f0f0f0;\n";
stream << " font-weight: bold;\n";
stream << " text-align: center;\n";
stream << " }\n";
stream << " .param-table td:first-child {\n";
stream << " width: 140pt;\n";
stream << " font-weight: bold;\n";
stream << " }\n";
stream << " .peak-table {\n";
stream << " font-size: 10pt;\n";
stream << " }\n";
stream << " .peak-table td {\n";
stream << " text-align: center;\n";
stream << " }\n";
stream << " .signature-section {\n";
stream << " margin-top: 30pt;\n";
stream << " text-align: right;\n";
stream << " font-size: 12pt;\n";
stream << " }\n";
stream << " .signature-line {\n";
stream << " margin-bottom: 8pt;\n";
stream << " }\n";
stream << " .spectrum-file-header {\n";
stream << " text-align: center;\n";
stream << " font-weight: bold;\n";
stream << " font-size: 13pt;\n";
stream << " margin-bottom: 10pt;\n";
stream << " }\n";
stream << " .calibration-sub {\n";
stream << " margin-left: 20pt;\n";
stream << " margin-top: 8pt;\n";
stream << " }\n";
stream << " .calibration-sub-title {\n";
stream << " font-weight: bold;\n";
stream << " margin-bottom: 5pt;\n";
stream << " }\n";
stream << " .legend {\n";
stream << " margin-top: 8pt;\n";
stream << " font-size: 10pt;\n";
stream << " color: #333;\n";
stream << " }\n";
stream << " .legend-title {\n";
stream << " font-weight: bold;\n";
stream << " margin-bottom: 3pt;\n";
stream << " }\n";
stream << "</style>\n";
stream << "</head>\n";
stream << "<body>\n";
// 报告标题
stream << QStringLiteral(u"<div class='report-title'>符合能谱分析报告</div>\n");
// 能谱文件
stream << QStringLiteral(u"<div class='spectrum-file-header'>能谱文件:%1</div>\n")
.arg(m_data.measurement.spectrumFile);
// 测量参数
stream << QStringLiteral(u"<div class='section-title'>测量参数</div>\n");
stream << "<table class='param-table'>\n";
stream << QStringLiteral(u"<tr><td>开始时间:</td><td>%1</td><td>活时间S</td><td>%2</td></tr>\n")
.arg(formatDateTime(m_data.measurement.startTime))
.arg(formatDecimal(m_data.measurement.liveTime, 0));
stream << QStringLiteral(u"<tr><td>实时间S</td><td>%1</td><td>死时间:</td><td>%2 %</td></tr>\n")
.arg(formatDecimal(m_data.measurement.realTime, 0))
.arg(formatDecimal(m_data.measurement.deadTime, 2));
stream << "</table>\n";
// 刻度
stream << QStringLiteral(u"<div class='section-title'>刻度</div>\n");
stream << "<table class='param-table'>\n";
stream << QStringLiteral(u"<tr><td>文件:</td><td>%1</td><td>刻度标准源:</td><td>%2</td></tr>\n")
.arg(m_data.calibration.fileName)
.arg(m_data.calibration.standardSource);
stream << "</table>\n";
// 能量刻度
stream << "<div class='calibration-sub'>\n";
stream << QStringLiteral(u"<div class='calibration-sub-title'>能量刻度</div>\n");
stream << "<table class='param-table'>\n";
stream << QStringLiteral(u"<tr><td>生成时间:</td><td>%1</td><td>截距:</td><td>%2 keV</td></tr>\n")
.arg(formatDateTime(m_data.calibration.energy.generateTime))
.arg(formatDecimal(m_data.calibration.energy.intercept, 3));
stream << QStringLiteral(u"<tr><td>斜率:</td><td>%1 keV/ch</td><td>二次项系数:</td><td>%2 keV/(ch^2)</td></tr>\n")
.arg(formatDecimal(m_data.calibration.energy.slope, 3))
.arg(formatScientific(m_data.calibration.energy.quadratic, 3));
stream << "</table>\n";
stream << "</div>\n";
// 效率刻度
stream << "<div class='calibration-sub'>\n";
stream << QStringLiteral(u"<div class='calibration-sub-title'>效率刻度</div>\n");
stream << "<table class='param-table'>\n";
stream << QStringLiteral(u"<tr><td>生成时间:</td><td>%1</td><td>拐点能量keV</td><td>%2</td></tr>\n")
.arg(formatDateTime(m_data.calibration.efficiency.generateTime))
.arg(formatDecimal(m_data.calibration.efficiency.inflectionEnergy, 2));
stream << QStringLiteral(u"<tr><td>拐点以上:</td><td>%1</td><td>误差:</td><td>%2%</td></tr>\n")
.arg(m_data.calibration.efficiency.aboveInflection)
.arg(formatDecimal(m_data.calibration.efficiency.aboveError, 2));
stream << QStringLiteral(u"<tr><td>拐点以下:</td><td>%1</td><td>误差:</td><td>%2%</td></tr>\n")
.arg(m_data.calibration.efficiency.belowInflection)
.arg(formatDecimal(m_data.calibration.efficiency.belowError, 2));
stream << QStringLiteral(u"<tr><td>效率对数方程式:</td><td colspan='3'>%1</td></tr>\n")
.arg(m_data.calibration.efficiency.logEquation);
stream << "</table>\n";
stream << "</div>\n";
// 核素库
stream << QStringLiteral(u"<div class='section-title'>核素库</div>\n");
stream << "<table class='param-table'>\n";
stream << QStringLiteral(u"<tr><td>文件:</td><td>%1</td><td>峰匹配宽度keV</td><td>%2 * FWHM</td></tr>\n")
.arg(m_data.nuclideLib.fileName)
.arg(formatDecimal(m_data.nuclideLib.peakMatchWidth, 3));
stream << "</table>\n";
// 分析参数
stream << QStringLiteral(u"<div class='section-title'>分析参数</div>\n");
stream << "<table class='param-table'>\n";
stream << QStringLiteral(u"<tr><td>起始能量(keV)</td><td>%1</td><td>终止能量keV</td><td>%2</td></tr>\n")
.arg(formatDecimal(m_data.analysis.startEnergy, 2))
.arg(formatDecimal(m_data.analysis.endEnergy, 2));
stream << QStringLiteral(u"<tr><td>统计误差限值:</td><td>%1%</td><td>活度比例系数:</td><td>%2</td></tr>\n")
.arg(formatDecimal(m_data.analysis.statErrorLimit, 3))
.arg(formatScientific(m_data.analysis.activityRatio, 4));
stream << QStringLiteral(u"<tr><td>探测限计算方法:</td><td colspan='3'>%1</td></tr>\n")
.arg(m_data.analysis.mdaMethod);
stream << "</table>\n";
// 能谱峰一览表
stream << QStringLiteral(u"<div class='section-title'>能 谱 峰 一 览 表</div>\n");
stream << QStringLiteral(u"<table class='peak-table'>\n");
stream << QStringLiteral(u"<tr>\n");
stream << QStringLiteral(u"<th rowspan='2'>核素</th>\n");
stream << QStringLiteral(u"<th rowspan='2' colspan='1'>平均活度贝克Bq</th>\n");
stream << QStringLiteral(u"<th rowspan='2' colspan='1'>不确定度</th>\n");
stream << QStringLiteral(u"<th colspan='4'>峰</th>\n");
stream << QStringLiteral(u"<th rowspan='2'>备注</th>\n");
stream << QStringLiteral(u"</tr>\n");
stream << QStringLiteral(u"<tr>\n");
stream << QStringLiteral(u"<th>能量keV</th>\n");
stream << QStringLiteral(u"<th>活度贝克Bq</th>\n");
stream << QStringLiteral(u"<th>不确定度</th>\n");
stream << QStringLiteral(u"<th>最小可探测活度MDA贝克Bq</th>\n");
stream << QStringLiteral(u"</tr>\n");
for (const auto& peak : m_data.peaks) {
stream << QStringLiteral(u"<tr>\n");
stream << QStringLiteral(u"<td>%1</td>\n").arg(peak.nuclide);
stream << QStringLiteral(u"<td>%1</td>\n").arg(formatScientific(peak.avgActivity, 4));
stream << QStringLiteral(u"<td>%1%</td>\n").arg(formatDecimal(peak.avgUncertainty, 2));
stream << QStringLiteral(u"<td>%1</td>\n").arg(formatDecimal(peak.energy, 2));
stream << QStringLiteral(u"<td>%1</td>\n").arg(formatScientific(peak.peakActivity, 3));
stream << QStringLiteral(u"<td>%1%</td>\n").arg(formatDecimal(peak.peakUncertainty, 2));
stream << QStringLiteral(u"<td>%1</td>\n").arg(formatScientific(peak.mda, 3));
stream << QStringLiteral(u"<td>%1</td>\n").arg(peak.remark);
stream << QStringLiteral(u"</tr>\n");
}
stream << QStringLiteral(u"</table>\n");
// 峰提示码说明
stream << QStringLiteral(u"<div class='legend'>\n");
stream << QStringLiteral(u"<div class='legend-title'>峰提示码说明:</div>\n");
stream << QStringLiteral(u"<div>G - γ射线</div>\n");
stream << QStringLiteral(u"<div>X - X射线</div>\n");
stream << QStringLiteral(u"<div>ec - 内转换电子</div>\n");
stream << QStringLiteral(u"</div>\n");
// 签名
stream << "<div class='signature-section'>\n";
stream << QStringLiteral(u"<div class='signature-line'>分析员:%1</div>\n")
.arg(m_data.signature.analyst);
stream << QStringLiteral(u"<div class='signature-line'>审查员:%1</div>\n")
.arg(m_data.signature.reviewer);
stream << QStringLiteral(u"<div class='signature-line'>实验室:%1</div>\n")
.arg(m_data.signature.laboratory);
stream << QStringLiteral(u"<div class='signature-line'>日 期:%1</div>\n")
.arg(m_data.signature.reportDate.toString("yyyy年MM月dd日"));
stream << "</div>\n";
stream << "</body>\n";
stream << "</html>\n";
return html;
}
bool ReportGenerator::saveToWordFile(const QString &filePath) const
{
QFile file(filePath);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
return false;
}
QTextStream out(&file);
out.setCodec("UTF-8");
out << generateWordReport();
file.close();
return true;
}

View File

@ -0,0 +1,68 @@
#ifndef REPORTGENERATOR_H
#define REPORTGENERATOR_H
#include "spectrumdata.h"
#include <QString>
class ReportGenerator
{
public:
ReportGenerator();
// 设置报告数据
void setData(const SpectrumReportData& data);
// 生成HTML格式报告
QString generateHtmlReport() const;
// 生成Word格式报告
QString generateWordReport() const;
// 生成简化版文本报告
QString generateTextReport() const;
// 保存报告到文件
bool saveToFile(const QString& filePath) const;
// 保存为Word文档
bool saveToWordFile(const QString& filePath) const;
private:
SpectrumReportData m_data;
// 格式化数字为科学计数法字符串
QString formatScientific(double value, int precision = 4) const;
// 格式化浮点数
QString formatDecimal(double value, int precision = 2) const;
// 格式化日期时间
QString formatDateTime(const QDateTime& dt) const;
// 生成HTML头部
QString generateHtmlHeader() const;
// 生成测量参数部分
QString generateMeasurementSection() const;
// 生成刻度部分
QString generateCalibrationSection() const;
// 生成核素库部分
QString generateNuclideLibSection() const;
// 生成分析参数部分
QString generateAnalysisSection() const;
// 生成能谱峰一览表
QString generatePeakTableSection() const;
// 生成签名部分
QString generateSignatureSection() const;
// 生成HTML尾部
QString generateHtmlFooter() const;
};
#endif // REPORTGENERATOR_H

View File

@ -0,0 +1,345 @@
#include "reportwidget.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QFormLayout>
#include <QGroupBox>
#include <QHeaderView>
#include <QFileDialog>
#include <QMessageBox>
#include <QDateTime>
#include <QTextBrowser>
#include <QTextEdit>
#include <QPushButton>
#include <QLineEdit>
#include <QTableWidget>
ReportWidget::ReportWidget(QWidget *parent)
: QWidget(parent)
{
setupUi();
setupConnections();
loadSampleData();
}
void ReportWidget::setupUi()
{
auto* mainLayout = new QVBoxLayout(this);
// 顶部按钮栏
auto* btnLayout = new QHBoxLayout();
m_sampleBtn = new QPushButton("加载示例数据", this);
m_refreshBtn = new QPushButton("刷新报告", this);
m_exportHtmlBtn = new QPushButton("导出HTML", this);
m_exportWordBtn = new QPushButton("导出Word", this);
m_exportTextBtn = new QPushButton("导出文本", this);
btnLayout->addWidget(m_sampleBtn);
btnLayout->addStretch();
btnLayout->addWidget(m_refreshBtn);
btnLayout->addWidget(m_exportHtmlBtn);
btnLayout->addWidget(m_exportWordBtn);
btnLayout->addWidget(m_exportTextBtn);
mainLayout->addLayout(btnLayout);
// 标签页
m_tabWidget = new QTabWidget(this);
// HTML预览页
m_htmlView = new QTextBrowser(this);
m_htmlView->setOpenExternalLinks(true);
m_tabWidget->addTab(m_htmlView, "HTML预览");
// 文本预览页
m_textView = new QTextEdit(this);
m_textView->setReadOnly(true);
m_tabWidget->addTab(m_textView, "文本预览");
// 数据编辑页
m_dataEditWidget = new QWidget(this);
auto* dataLayout = new QVBoxLayout(m_dataEditWidget);
// 测量参数组
auto* measureGroup = new QGroupBox("测量参数", this);
auto* measureLayout = new QFormLayout(measureGroup);
m_spectrumFileEdit = new QLineEdit(this);
m_startTimeEdit = new QLineEdit(this);
m_liveTimeEdit = new QLineEdit(this);
m_realTimeEdit = new QLineEdit(this);
m_deadTimeEdit = new QLineEdit(this);
measureLayout->addRow("能谱文件:", m_spectrumFileEdit);
measureLayout->addRow("开始时间 (yyyy/M/d hh:mm:ss):", m_startTimeEdit);
measureLayout->addRow("活时间 (秒):", m_liveTimeEdit);
measureLayout->addRow("实时间 (秒):", m_realTimeEdit);
measureLayout->addRow("死时间 (%):", m_deadTimeEdit);
dataLayout->addWidget(measureGroup);
// 刻度参数组
auto* calibGroup = new QGroupBox("刻度参数", this);
auto* calibLayout = new QFormLayout(calibGroup);
m_calibFileEdit = new QLineEdit(this);
m_energyInterceptEdit = new QLineEdit(this);
m_energySlopeEdit = new QLineEdit(this);
m_energyQuadraticEdit = new QLineEdit(this);
calibLayout->addRow("刻度文件:", m_calibFileEdit);
calibLayout->addRow("能量截距 (keV):", m_energyInterceptEdit);
calibLayout->addRow("能量斜率 (keV/ch):", m_energySlopeEdit);
calibLayout->addRow("二次项系数 (keV/(ch^2)):", m_energyQuadraticEdit);
dataLayout->addWidget(calibGroup);
// 核素库与分析参数
auto* paramGroup = new QGroupBox("核素库与分析参数", this);
auto* paramLayout = new QFormLayout(paramGroup);
m_nuclideLibEdit = new QLineEdit(this);
m_peakMatchWidthEdit = new QLineEdit(this);
m_startEnergyEdit = new QLineEdit(this);
m_endEnergyEdit = new QLineEdit(this);
m_statErrorEdit = new QLineEdit(this);
paramLayout->addRow("核素库文件:", m_nuclideLibEdit);
paramLayout->addRow("峰匹配宽度 (*FWHM):", m_peakMatchWidthEdit);
paramLayout->addRow("起始能量 (keV):", m_startEnergyEdit);
paramLayout->addRow("终止能量 (keV):", m_endEnergyEdit);
paramLayout->addRow("统计误差限值 (%):", m_statErrorEdit);
dataLayout->addWidget(paramGroup);
// 峰数据表格
auto* peakGroup = new QGroupBox("能谱峰数据", this);
auto* peakLayout = new QVBoxLayout(peakGroup);
m_peakTable = new QTableWidget(this);
m_peakTable->setColumnCount(7);
m_peakTable->setHorizontalHeaderLabels(
QStringList() << "核素" << "平均活度(Bq)" << "能量(keV)"
<< "峰活度(Bq)" << "MDA(Bq)" << "提示码" << "备注");
m_peakTable->horizontalHeader()->setStretchLastSection(true);
m_peakTable->setRowCount(10); // 预留10行
peakLayout->addWidget(m_peakTable);
dataLayout->addWidget(peakGroup);
// 签名信息
auto* signGroup = new QGroupBox("签名信息", this);
auto* signLayout = new QFormLayout(signGroup);
m_analystEdit = new QLineEdit(this);
m_reviewerEdit = new QLineEdit(this);
m_laboratoryEdit = new QLineEdit(this);
m_reportDateEdit = new QLineEdit(this);
signLayout->addRow("分析员:", m_analystEdit);
signLayout->addRow("审查员:", m_reviewerEdit);
signLayout->addRow("实验室:", m_laboratoryEdit);
signLayout->addRow("报告日期 (yyyy-MM-dd):", m_reportDateEdit);
dataLayout->addWidget(signGroup);
dataLayout->addStretch();
m_tabWidget->addTab(m_dataEditWidget, "数据编辑");
mainLayout->addWidget(m_tabWidget);
resize(1000, 700);
setWindowTitle("能谱分析报告生成器");
}
void ReportWidget::setupConnections()
{
connect(m_sampleBtn, &QPushButton::clicked, this, &ReportWidget::loadSampleData);
connect(m_refreshBtn, &QPushButton::clicked, this, &ReportWidget::refreshReport);
connect(m_exportHtmlBtn, &QPushButton::clicked, this, &ReportWidget::exportHtml);
connect(m_exportWordBtn, &QPushButton::clicked, this, &ReportWidget::exportWord);
connect(m_exportTextBtn, &QPushButton::clicked, this, &ReportWidget::exportText);
}
void ReportWidget::setReportData(const SpectrumReportData &data)
{
m_data = data;
m_generator.setData(m_data);
// 更新UI
m_spectrumFileEdit->setText(m_data.measurement.spectrumFile);
m_startTimeEdit->setText(m_data.measurement.startTime.toString("yyyy/M/d hh:mm:ss"));
m_liveTimeEdit->setText(QString::number(m_data.measurement.liveTime));
m_realTimeEdit->setText(QString::number(m_data.measurement.realTime));
m_deadTimeEdit->setText(QString::number(m_data.measurement.deadTime));
m_calibFileEdit->setText(m_data.calibration.fileName);
m_energyInterceptEdit->setText(QString::number(m_data.calibration.energy.intercept));
m_energySlopeEdit->setText(QString::number(m_data.calibration.energy.slope));
m_energyQuadraticEdit->setText(QString::number(m_data.calibration.energy.quadratic, 'E', 3));
m_nuclideLibEdit->setText(m_data.nuclideLib.fileName);
m_peakMatchWidthEdit->setText(QString::number(m_data.nuclideLib.peakMatchWidth));
m_startEnergyEdit->setText(QString::number(m_data.analysis.startEnergy));
m_endEnergyEdit->setText(QString::number(m_data.analysis.endEnergy));
m_statErrorEdit->setText(QString::number(m_data.analysis.statErrorLimit));
m_analystEdit->setText(m_data.signature.analyst);
m_reviewerEdit->setText(m_data.signature.reviewer);
m_laboratoryEdit->setText(m_data.signature.laboratory);
m_reportDateEdit->setText(m_data.signature.reportDate.toString("yyyy-MM-dd"));
updatePeakTable();
refreshReport();
}
SpectrumReportData ReportWidget::reportData() const
{
return m_data;
}
void ReportWidget::refreshReport()
{
updateDataFromUi();
m_generator.setData(m_data);
// 更新HTML预览
m_htmlView->setHtml(m_generator.generateHtmlReport());
// 更新文本预览
m_textView->setPlainText(m_generator.generateTextReport());
}
void ReportWidget::updateDataFromUi()
{
// 测量参数
m_data.measurement.spectrumFile = m_spectrumFileEdit->text();
m_data.measurement.startTime = QDateTime::fromString(m_startTimeEdit->text(), "yyyy/M/d hh:mm:ss");
m_data.measurement.liveTime = m_liveTimeEdit->text().toDouble();
m_data.measurement.realTime = m_realTimeEdit->text().toDouble();
m_data.measurement.deadTime = m_deadTimeEdit->text().toDouble();
// 刻度
m_data.calibration.fileName = m_calibFileEdit->text();
m_data.calibration.energy.intercept = m_energyInterceptEdit->text().toDouble();
m_data.calibration.energy.slope = m_energySlopeEdit->text().toDouble();
m_data.calibration.energy.quadratic = m_energyQuadraticEdit->text().toDouble();
// 核素库
m_data.nuclideLib.fileName = m_nuclideLibEdit->text();
m_data.nuclideLib.peakMatchWidth = m_peakMatchWidthEdit->text().toDouble();
// 分析参数
m_data.analysis.startEnergy = m_startEnergyEdit->text().toDouble();
m_data.analysis.endEnergy = m_endEnergyEdit->text().toDouble();
m_data.analysis.statErrorLimit = m_statErrorEdit->text().toDouble();
// 签名
m_data.signature.analyst = m_analystEdit->text();
m_data.signature.reviewer = m_reviewerEdit->text();
m_data.signature.laboratory = m_laboratoryEdit->text();
m_data.signature.reportDate = QDate::fromString(m_reportDateEdit->text(), "yyyy-MM-dd");
// 峰数据(从表格读取)
m_data.clearPeaks();
for (int row = 0; row < m_peakTable->rowCount(); ++row) {
QTableWidgetItem* nuclideItem = m_peakTable->item(row, 0);
if (!nuclideItem || nuclideItem->text().isEmpty()) {
continue;
}
SpectrumPeak peak;
peak.nuclide = nuclideItem->text();
QTableWidgetItem* avgItem = m_peakTable->item(row, 1);
if (avgItem) peak.avgActivity = avgItem->text().toDouble();
QTableWidgetItem* energyItem = m_peakTable->item(row, 2);
if (energyItem) peak.energy = energyItem->text().toDouble();
QTableWidgetItem* peakActItem = m_peakTable->item(row, 3);
if (peakActItem) peak.peakActivity = peakActItem->text().toDouble();
QTableWidgetItem* mdaItem = m_peakTable->item(row, 4);
if (mdaItem) peak.mda = mdaItem->text().toDouble();
QTableWidgetItem* codeItem = m_peakTable->item(row, 5);
if (codeItem) peak.peakCode = codeItem->text();
QTableWidgetItem* remarkItem = m_peakTable->item(row, 6);
if (remarkItem) peak.remark = remarkItem->text();
m_data.addPeak(peak);
}
}
void ReportWidget::updatePeakTable()
{
m_peakTable->setRowCount(m_data.peakCount() + 5); // 多留几行
for (int i = 0; i < m_data.peakCount(); ++i) {
const auto& peak = m_data.peaks[i];
m_peakTable->setItem(i, 0, new QTableWidgetItem(peak.nuclide));
m_peakTable->setItem(i, 1, new QTableWidgetItem(QString::number(peak.avgActivity, 'E', 4)));
m_peakTable->setItem(i, 2, new QTableWidgetItem(QString::number(peak.energy, 'f', 2)));
m_peakTable->setItem(i, 3, new QTableWidgetItem(QString::number(peak.peakActivity, 'E', 3)));
m_peakTable->setItem(i, 4, new QTableWidgetItem(QString::number(peak.mda, 'E', 3)));
m_peakTable->setItem(i, 5, new QTableWidgetItem(peak.peakCode));
m_peakTable->setItem(i, 6, new QTableWidgetItem(peak.remark));
}
}
void ReportWidget::exportHtml()
{
QString fileName = QFileDialog::getSaveFileName(
this, "导出HTML报告", "能谱分析报告.html",
"HTML文件 (*.html *.htm);;所有文件 (*.*)");
if (fileName.isEmpty()) {
return;
}
updateDataFromUi();
m_generator.setData(m_data);
if (m_generator.saveToFile(fileName)) {
QMessageBox::information(this, "成功", "HTML报告导出成功");
} else {
QMessageBox::warning(this, "失败", "导出失败,请检查文件路径。");
}
}
void ReportWidget::exportWord()
{
QString fileName = QFileDialog::getSaveFileName(
this, "导出Word报告", "能谱分析报告.doc",
"Word文档 (*.doc);;所有文件 (*.*)");
if (fileName.isEmpty()) {
return;
}
updateDataFromUi();
m_generator.setData(m_data);
if (m_generator.saveToWordFile(fileName)) {
QMessageBox::information(this, "成功", "Word报告导出成功\n可用 Microsoft Word 或 WPS 打开查看。");
} else {
QMessageBox::warning(this, "失败", "导出失败,请检查文件路径。");
}
}
void ReportWidget::exportText()
{
QString fileName = QFileDialog::getSaveFileName(
this, "导出文本报告", "能谱分析报告.txt",
"文本文件 (*.txt);;所有文件 (*.*)");
if (fileName.isEmpty()) {
return;
}
updateDataFromUi();
m_generator.setData(m_data);
if (m_generator.saveToFile(fileName)) {
QMessageBox::information(this, "成功", "文本报告导出成功!");
} else {
QMessageBox::warning(this, "失败", "导出失败,请检查文件路径。");
}
}
void ReportWidget::loadSampleData()
{
SpectrumReportData data;
data.fillSampleData();
setReportData(data);
}

View File

@ -0,0 +1,96 @@
#ifndef REPORTWIDGET_H
#define REPORTWIDGET_H
#include <QWidget>
#include "spectrumdata.h"
#include "reportgenerator.h"
class QTextEdit;
class QPushButton;
class QTabWidget;
class QTableWidget;
class QLabel;
class QLineEdit;
class QFormLayout;
class QTextBrowser;
class ReportWidget : public QWidget
{
Q_OBJECT
public:
explicit ReportWidget(QWidget *parent = nullptr);
// 设置报告数据
void setReportData(const SpectrumReportData& data);
// 获取当前数据
SpectrumReportData reportData() const;
public slots:
// 刷新报告显示
void refreshReport();
// 导出HTML报告
void exportHtml();
// 导出Word报告
void exportWord();
// 导出文本报告
void exportText();
// 加载示例数据
void loadSampleData();
private:
void setupUi();
void setupConnections();
void updateDataFromUi();
void updatePeakTable();
// UI组件
QTabWidget* m_tabWidget;
QTextBrowser* m_htmlView;
QTextEdit* m_textView;
QWidget* m_dataEditWidget;
// 数据编辑组件
QLineEdit* m_spectrumFileEdit;
QLineEdit* m_startTimeEdit;
QLineEdit* m_liveTimeEdit;
QLineEdit* m_realTimeEdit;
QLineEdit* m_deadTimeEdit;
QLineEdit* m_calibFileEdit;
QLineEdit* m_energyInterceptEdit;
QLineEdit* m_energySlopeEdit;
QLineEdit* m_energyQuadraticEdit;
QLineEdit* m_nuclideLibEdit;
QLineEdit* m_peakMatchWidthEdit;
QLineEdit* m_startEnergyEdit;
QLineEdit* m_endEnergyEdit;
QLineEdit* m_statErrorEdit;
QTableWidget* m_peakTable;
QLineEdit* m_analystEdit;
QLineEdit* m_reviewerEdit;
QLineEdit* m_laboratoryEdit;
QLineEdit* m_reportDateEdit;
// 按钮
QPushButton* m_refreshBtn;
QPushButton* m_exportHtmlBtn;
QPushButton* m_exportWordBtn;
QPushButton* m_exportTextBtn;
QPushButton* m_sampleBtn;
// 数据
SpectrumReportData m_data;
ReportGenerator m_generator;
};
#endif // REPORTWIDGET_H

View File

@ -0,0 +1,109 @@
#include "spectrumdata.h"
SpectrumReportData::SpectrumReportData()
{
}
void SpectrumReportData::addPeak(const SpectrumPeak &peak)
{
peaks.append(peak);
}
void SpectrumReportData::clearPeaks()
{
peaks.clear();
}
int SpectrumReportData::peakCount() const
{
return peaks.size();
}
void SpectrumReportData::fillSampleData()
{
// 测量参数
measurement.spectrumFile = "3#-20250416";
measurement.startTime = QDateTime(QDate(2025, 4, 16), QTime(20, 1, 22));
measurement.liveTime = 86400.0;
measurement.realTime = 86444.0;
measurement.deadTime = 0.05;
// 刻度信息
calibration.fileName = "刻度文件.Clb";
calibration.standardSource = "Co60";
// 能量刻度
calibration.energy.generateTime = QDateTime(QDate(2025, 3, 21), QTime(11, 3, 28));
calibration.energy.intercept = 0.057;
calibration.energy.slope = 0.090;
calibration.energy.quadratic = -3.127e-9;
// 效率刻度
calibration.efficiency.generateTime = QDateTime(QDate(2025, 3, 21), QTime(11, 2, 50));
calibration.efficiency.inflectionEnergy = 80.00;
calibration.efficiency.aboveInflection = "一次方程";
calibration.efficiency.aboveError = 0.0;
calibration.efficiency.belowInflection = "二次方程";
calibration.efficiency.belowError = 0.0;
calibration.efficiency.logEquation =
"-1.164361E+01 + (4.475904E+00*Log(E)) + (-5.221437E-01*Log(E)^2)";
// 核素库
nuclideLib.fileName = "Xe131m-131-133主要射线.Lib";
nuclideLib.peakMatchWidth = 0.500;
// 分析参数
analysis.startEnergy = 0.96;
analysis.endEnergy = 271.36;
analysis.statErrorLimit = 30.000;
analysis.activityRatio = 1.0;
analysis.mdaMethod = "传统方法";
// 能谱峰数据
peaks.clear();
SpectrumPeak peak1;
peak1.nuclide = "XE-133";
peak1.nuclideCode = "I";
peak1.avgActivity = 2.7056e-01;
peak1.avgUncertainty = 0.02;
peak1.energy = 81.00;
peak1.peakActivity = 2.706e-01;
peak1.peakUncertainty = 0.02;
peak1.mda = 3.393e-02;
peak1.peakCode = "@";
peak1.remark = "G";
peaks.append(peak1);
SpectrumPeak peak2;
peak2.nuclide = "XE-131M";
peak2.nuclideCode = "T";
peak2.avgActivity = 0.0;
peak2.avgUncertainty = 0.01;
peak2.energy = 163.93;
peak2.peakActivity = 2.220e-01;
peak2.peakUncertainty = 0.01;
peak2.mda = 4.506e-01;
peak2.peakCode = "%";
peak2.remark = "G";
peaks.append(peak2);
SpectrumPeak peak3;
peak3.nuclide = "XE-133M";
peak3.nuclideCode = "I";
peak3.avgActivity = 0.0;
peak3.avgUncertainty = 0.02;
peak3.energy = 233.22;
peak3.peakActivity = 0.0;
peak3.peakUncertainty = 0.02;
peak3.mda = 9.827e-02;
peak3.peakCode = "%";
peak3.remark = "G";
peaks.append(peak3);
// 签名信息
signature.analyst = "XESPM-III";
signature.reviewer = "管理员";
signature.laboratory = "GAMMASPEC";
signature.reportDate = QDate(2025, 11, 13);
}

View File

@ -0,0 +1,114 @@
#ifndef SPECTRUMDATA_H
#define SPECTRUMDATA_H
#include <QString>
#include <QDateTime>
#include <QVector>
// 能量刻度参数
struct EnergyCalibration {
QDateTime generateTime; // 生成时间
double intercept = 0.0; // 截距 (keV)
double slope = 0.0; // 斜率 (keV/ch)
double quadratic = 0.0; // 二次项系数 (keV/(ch^2))
};
// 效率刻度参数
struct EfficiencyCalibration {
QDateTime generateTime; // 生成时间
double inflectionEnergy = 0.0; // 拐点能量 (keV)
QString aboveInflection; // 拐点以上方法
double aboveError = 0.0; // 拐点以上误差
QString belowInflection; // 拐点以下方法
double belowError = 0.0; // 拐点以下误差
QString logEquation; // 效率对数方程式
};
// 刻度信息
struct Calibration {
QString fileName; // 刻度文件名
EnergyCalibration energy; // 能量刻度
EfficiencyCalibration efficiency; // 效率刻度
QString standardSource; // 刻度标准源
};
// 核素库信息
struct NuclideLibrary {
QString fileName; // 库文件名
double peakMatchWidth = 0.0; // 峰匹配宽度 (*FWHM, keV)
};
// 分析参数
struct AnalysisParameters {
double startEnergy = 0.0; // 起始能量 (keV)
double endEnergy = 0.0; // 终止能量 (keV)
double statErrorLimit = 0.0; // 统计误差限值 (%)
double activityRatio = 0.0; // 活度比例系数
QString mdaMethod; // 探测限计算方法
};
// 单个峰数据
struct SpectrumPeak {
QString nuclide; // 核素名称
QString nuclideCode; // 核素提示码
double avgActivity = 0.0; // 平均活度 (Bq)
double avgUncertainty = 0.0; // 平均活度不确定度 (%)
double energy = 0.0; // 峰能量 (keV)
double peakActivity = 0.0; // 峰活度 (Bq)
double peakUncertainty = 0.0;// 峰活度不确定度 (%)
double mda = 0.0; // 最小可探测活度 MDA (Bq)
QString peakCode; // 峰提示码
QString remark; // 备注
};
// 测量参数
struct MeasurementParams {
QString spectrumFile; // 能谱文件名
QDateTime startTime; // 开始时间
double liveTime = 0.0; // 活时间 (秒)
double realTime = 0.0; // 实时间 (秒)
double deadTime = 0.0; // 死时间 (%)
};
// 报告签名信息
struct ReportSignature {
QString analyst; // 分析员
QString reviewer; // 审查员
QString laboratory; // 实验室
QDate reportDate; // 报告日期
};
// 完整的能谱报告数据
class SpectrumReportData
{
public:
SpectrumReportData();
// 测量参数
MeasurementParams measurement;
// 刻度信息
Calibration calibration;
// 核素库
NuclideLibrary nuclideLib;
// 分析参数
AnalysisParameters analysis;
// 能谱峰列表
QVector<SpectrumPeak> peaks;
// 签名信息
ReportSignature signature;
// 工具方法
void addPeak(const SpectrumPeak& peak);
void clearPeaks();
int peakCount() const;
// 示例数据填充
void fillSampleData();
};
#endif // SPECTRUMDATA_H

View File

@ -1,6 +1,8 @@
TARGET = EnergySpectrumAnalyer TARGET = EnergySpectrumAnalyer
QT += core gui widgets concurrent datavisualization network sql QT += core gui widgets concurrent datavisualization network sql xml
QT += gui-private
CONFIG += c++17 release CONFIG += c++17 release
msvc { msvc {
@ -41,7 +43,9 @@ INCLUDEPATH += \
$${PWD}/AntiConformEnergySpectrumView\ $${PWD}/AntiConformEnergySpectrumView\
$${PWD}/CoincidenceEventTimeView\ $${PWD}/CoincidenceEventTimeView\
$${PWD}/NuclideAnalysisView \ $${PWD}/NuclideAnalysisView \
$${PWD}/BackgroundTaskListView $${PWD}/BackgroundTaskListView \
$${PWD}/analysisReport
DEPENDPATH += \ DEPENDPATH += \
@ -64,7 +68,9 @@ DEPENDPATH += \
$${PWD}/AntiConformEnergySpectrumView\ $${PWD}/AntiConformEnergySpectrumView\
$${PWD}/CoincidenceEventTimeView\ $${PWD}/CoincidenceEventTimeView\
$${PWD}/NuclideAnalysisView \ $${PWD}/NuclideAnalysisView \
$${PWD}/BackgroundTaskListView $${PWD}/BackgroundTaskListView \
$${PWD}/analysisReport
SOURCES += \ SOURCES += \
@ -78,6 +84,7 @@ SOURCES += \
CustomQwtPlot.cpp \ CustomQwtPlot.cpp \
DataProcessWorkPool.cpp \ DataProcessWorkPool.cpp \
DeviceConnectManagement.cpp \ DeviceConnectManagement.cpp \
EfficiencyScale.cpp \
EnergyCountPeakFitView/EnergyCountPeakFitView.cpp \ EnergyCountPeakFitView/EnergyCountPeakFitView.cpp \
EnergyCountPeakFitView/PeakFitParamsDialog.cpp \ EnergyCountPeakFitView/PeakFitParamsDialog.cpp \
EnergyCountPeakFitView/PlotRectItem.cpp \ EnergyCountPeakFitView/PlotRectItem.cpp \
@ -121,6 +128,9 @@ SOURCES += \
ConformToTheEnergySpectrum/ConformToTheEnergySpectrum.cpp \ ConformToTheEnergySpectrum/ConformToTheEnergySpectrum.cpp \
NuclideLib/NuclideLib.cpp \ NuclideLib/NuclideLib.cpp \
AntiConformEnergySpectrumView/AntiConformEnergySpectrumView.cpp \ AntiConformEnergySpectrumView/AntiConformEnergySpectrumView.cpp \
analysisReport/reportgenerator.cpp \
analysisReport/reportwidget.cpp \
analysisReport/spectrumdata.cpp \
main.cpp main.cpp
HEADERS += \ HEADERS += \
@ -135,6 +145,7 @@ HEADERS += \
CustomQwtPlot.h \ CustomQwtPlot.h \
DataProcessWorkPool.h \ DataProcessWorkPool.h \
DeviceConnectManagement.h \ DeviceConnectManagement.h \
EfficiencyScale.h \
EnergyCountPeakFitView/EnergyCountPeakFitView.h \ EnergyCountPeakFitView/EnergyCountPeakFitView.h \
EnergyCountPeakFitView/PeakFitParamsDialog.h \ EnergyCountPeakFitView/PeakFitParamsDialog.h \
EnergyCountPeakFitView/PlotRectItem.h \ EnergyCountPeakFitView/PlotRectItem.h \
@ -179,13 +190,17 @@ HEADERS += \
EnergyCountPeakFitView/EnergyCountPeakFitView.h \ EnergyCountPeakFitView/EnergyCountPeakFitView.h \
ConformToTheEnergySpectrum/ConformToTheEnergySpectrum.h \ ConformToTheEnergySpectrum/ConformToTheEnergySpectrum.h \
NuclideLib/NuclideLib.h \ NuclideLib/NuclideLib.h \
AntiConformEnergySpectrumView/AntiConformEnergySpectrumView.h AntiConformEnergySpectrumView/AntiConformEnergySpectrumView.h \
analysisReport/reportgenerator.h \
analysisReport/reportwidget.h \
analysisReport/spectrumdata.h
FORMS += \ FORMS += \
2DSpectralCompliance/TwoDSpectralCompliance.ui \ 2DSpectralCompliance/TwoDSpectralCompliance.ui \
AboutDlg.ui \ AboutDlg.ui \
CountRateAnalysisView/CountRateAnalysisView.ui \ CountRateAnalysisView/CountRateAnalysisView.ui \
DeviceConnectManagement.ui \ DeviceConnectManagement.ui \
EfficiencyScale.ui \
EnergyScaleForm.ui \ EnergyScaleForm.ui \
MainWindow.ui \ MainWindow.ui \
MeasureAnalysisHistoryForm/MeasureAnalysisHistoryForm.ui \ MeasureAnalysisHistoryForm/MeasureAnalysisHistoryForm.ui \