Files
HIS-GUI/gui/dialogs/patient_dialog.cpp
2026-04-07 17:18:33 +08:00

1744 lines
68 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "patient_dialog.h"
#include "core/his_core.h"
#include "core/ward_service.h"
#include "core/patient_service.h"
#include "core/doctor_service.h"
#include "core/medicine_service.h"
#include "core/department_service.h"
#include "core/patient_case_service.h"
#include "models/patient.h"
#include "models/ward.h"
#include "models/medicine.h"
#include "models/doctor.h"
#include "models/patient_case.h"
#include "utils/input_validator.h"
#include <QMessageBox>
#include <QTreeWidgetItem>
#include <QHeaderView>
#include <QGroupBox>
#include <QVBoxLayout>
#include <QRegularExpression>
#include <QHBoxLayout>
#include <QFormLayout>
#include <QLabel>
#include <QLineEdit>
#include <QSpinBox>
#include <QDoubleSpinBox>
#include <QComboBox>
#include <QTextEdit>
#include <QPushButton>
#include <QTableWidget>
#include <QTableWidgetItem>
#include <QTabWidget>
#include <QDialogButtonBox>
#include <QDateEdit>
#include <ctime>
#include <iomanip>
#include <sstream>
// ========== 工具函数 ==========
static QString formatCurrentDate() {
time_t now = time(nullptr);
struct tm timeinfo;
localtime_r(&now, &timeinfo);
char buffer[30];
strftime(buffer, 30, "%Y-%m-%d %H:%M", &timeinfo);
return QString(buffer);
}
static QString formatDate(time_t t) {
if (t == 0) return QObject::tr("-");
struct tm timeinfo;
localtime_r(&t, &timeinfo);
char buffer[30];
strftime(buffer, 30, "%Y-%m-%d %H:%M", &timeinfo);
return QString(buffer);
}
static QString wardTypeToText(WardType t) {
switch (t) {
case WardType::Normal: return QObject::tr("普通病房");
case WardType::Special: return QObject::tr("特需病房");
case WardType::ICU: return QObject::tr("ICU");
}
return QObject::tr("未知");
}
static WardType parseWardType(const QString& s) {
if (s.contains("ICU", Qt::CaseInsensitive)) return WardType::ICU;
if (s.contains("特需", Qt::CaseInsensitive)) return WardType::Special;
return WardType::Normal;
}
static PatientStatus parsePatientStatus(const QString& s) {
if (s.contains("住院", Qt::CaseInsensitive)) return PatientStatus::Inpatient;
if (s.contains("出院", Qt::CaseInsensitive)) return PatientStatus::Discharged;
if (s.contains("已就诊", Qt::CaseInsensitive)) return PatientStatus::Visited;
return PatientStatus::Outpatient;
}
static DoctorTitle parseDoctorTitle(const QString& s) {
if (s.contains("主任", Qt::CaseInsensitive)) return DoctorTitle::Chief;
if (s.contains("副主任", Qt::CaseInsensitive)) return DoctorTitle::AssociateChief;
if (s.contains("主治", Qt::CaseInsensitive)) return DoctorTitle::Attending;
return DoctorTitle::Resident;
}
static QString doctorTitleToText(DoctorTitle t) {
switch (t) {
case DoctorTitle::Chief: return QObject::tr("主任医师");
case DoctorTitle::AssociateChief: return QObject::tr("副主任医师");
case DoctorTitle::Attending: return QObject::tr("主治医师");
case DoctorTitle::Resident: return QObject::tr("住院医师");
}
return QObject::tr("未知");
}
static std::vector<Ward> collectExistingWards(core::HisCore& core) {
std::vector<Ward> wards;
core.ctx_.wards.for_each([&wards](const std::string&, const Ward& w) { wards.push_back(w); });
return wards;
}
static std::vector<std::string> collectExistingPatientIds(core::HisCore& core) {
std::vector<std::string> ids;
core.ctx_.patients.for_each([&ids](const std::string& k, const Patient&) { ids.push_back(k); });
return ids;
}
static std::vector<std::string> collectExistingDoctorIds(core::HisCore& core) {
std::vector<std::string> ids;
core.ctx_.doctors.for_each([&ids](const std::string& k, const Doctor&) { ids.push_back(k); });
return ids;
}
static std::vector<std::string> collectExistingMedicineIds(core::HisCore& core) {
std::vector<std::string> ids;
core.ctx_.medicines.for_each([&ids](const std::string& k, const Medicine&) { ids.push_back(k); });
return ids;
}
static std::vector<std::string> collectExistingDepartments(core::HisCore& core) {
std::set<std::string> depts;
core.ctx_.wards.for_each([&depts](const std::string&, const Ward& w) { depts.insert(w.DepartmentID); });
return std::vector<std::string>(depts.begin(), depts.end());
}
// ========== PatientAddDialog ==========
PatientAddDialog::PatientAddDialog(core::HisCore& core, const QString& patientId, QWidget* parent)
: QDialog(parent), core_(core), patientId_(patientId), isEditMode_(!patientId.isEmpty()) {
setupUI();
if (isEditMode_) {
loadPatientData();
}
}
PatientAddDialog::~PatientAddDialog() {}
void PatientAddDialog::setupUI() {
setWindowTitle(isEditMode_ ? tr("编辑患者信息") : tr("添加患者"));
setModal(true);
resize(400, 300);
setAttribute(Qt::WA_DeleteOnClose);
auto* mainLayout = new QVBoxLayout(this);
formLayout_ = new QFormLayout();
formLayout_->setSpacing(10);
formLayout_->setContentsMargins(20, 20, 20, 20);
if (isEditMode_) {
idLabel_ = new QLabel(patientId_, this);
idLabel_->setEnabled(false);
formLayout_->addRow(tr("患者ID:"), idLabel_);
} else {
QString newPatientId = QString::fromStdString(Patient::generateUniqueId());
idLabel_ = new QLabel(newPatientId, this);
idLabel_->setEnabled(false);
formLayout_->addRow(tr("患者ID (自动生成):"), idLabel_);
}
nameEdit_ = new QLineEdit(this);
nameEdit_->setPlaceholderText(tr("请输入姓名"));
input_validator::setMaxLength(nameEdit_); // 限制最大255字符
formLayout_->addRow(tr("姓名:"), nameEdit_);
ageSpin_ = new QSpinBox(this);
ageSpin_->setRange(0, 200);
ageSpin_->setValue(0);
formLayout_->addRow(tr("年龄:"), ageSpin_);
genderCombo_ = new QComboBox(this);
genderCombo_->addItems({tr(""), tr(""), tr("其他")});
formLayout_->addRow(tr("性别:"), genderCombo_);
contactEdit_ = new QLineEdit(this);
contactEdit_->setPlaceholderText(tr("请输入联系方式"));
input_validator::setMaxLength(contactEdit_); // 限制最大255字符
formLayout_->addRow(tr("联系方式:"), contactEdit_);
if (isEditMode_) {
statusCombo_ = new QComboBox(this);
statusCombo_->addItems({tr("未挂号"), tr("门诊"), tr("急诊"), tr("已就诊"), tr("住院"), tr("出院")});
formLayout_->addRow(tr("状态:"), statusCombo_);
}
buttonBox_ = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
connect(buttonBox_, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttonBox_, &QDialogButtonBox::rejected, this, &QDialog::reject);
formLayout_->addRow(buttonBox_);
mainLayout->addLayout(formLayout_);
}
void PatientAddDialog::loadPatientData() {
auto* p = core_.patientService.findPatient(patientId_.toStdString());
if (!p) return;
nameEdit_->setText(QString::fromStdString(p->Name));
ageSpin_->setValue(p->Age);
int genderIdx = (p->Gender == QObject::tr("").toStdString()) ? 1 : (p->Gender == QObject::tr("其他").toStdString()) ? 2 : 0;
genderCombo_->setCurrentIndex(genderIdx);
contactEdit_->setText(QString::fromStdString(p->Contact));
if (statusCombo_) {
int statusIdx = 0;
if (p->Status == PatientStatus::Visited) statusIdx = 1;
else if (p->Status == PatientStatus::Inpatient) statusIdx = 2;
else if (p->Status == PatientStatus::Discharged) statusIdx = 3;
statusCombo_->setCurrentIndex(statusIdx);
}
}
QString PatientAddDialog::getName() const { return nameEdit_->text(); }
int PatientAddDialog::getAge() const { return ageSpin_->value(); }
QString PatientAddDialog::getGender() const { return genderCombo_->currentText(); }
QString PatientAddDialog::getContact() const { return contactEdit_->text(); }
QString PatientAddDialog::getStatus() const {
if (statusCombo_) return statusCombo_->currentText();
return tr("门诊");
}
// ========== PatientAdmitDialog ==========
PatientAdmitDialog::PatientAdmitDialog(core::HisCore& core, const QString& patientId, QWidget* parent)
: QDialog(parent), core_(core), patientId_(patientId) {
setupUI();
}
PatientAdmitDialog::~PatientAdmitDialog() {}
void PatientAdmitDialog::setupUI() {
setWindowTitle(tr("入院 - %1").arg(patientId_));
setModal(true);
resize(500, 400);
setAttribute(Qt::WA_DeleteOnClose);
auto* mainLayout = new QVBoxLayout(this);
auto* formLayout = new QFormLayout();
formLayout->setSpacing(10);
formLayout->setContentsMargins(20, 20, 20, 20);
// 自动显示入院日期
dateLabel_ = new QLabel(formatCurrentDate(), this);
dateLabel_->setEnabled(false);
formLayout->addRow(tr("入院日期:"), dateLabel_);
// 病房选择
wardCombo_ = new QComboBox(this);
wardCombo_->addItem(tr("-- 请选择病房 --"));
core_.wardService.for_eachWard([this](const std::string&, const Ward& ward) {
QString display = QString("%1 (%2) - 空床: %3/%4")
.arg(QString::fromStdString(ward.WardID))
.arg(QString::fromStdString(ward.DepartmentID))
.arg(ward.freeBedCount())
.arg(ward.MaxBeds);
wardCombo_->addItem(display, QString::fromStdString(ward.WardID));
});
formLayout->addRow(tr("选择病房:"), wardCombo_);
// 床位选择(联动病房)
bedCombo_ = new QComboBox(this);
bedCombo_->addItem(tr("-- 先选择病房 --"));
formLayout->addRow(tr("选择床位:"), bedCombo_);
// 入院原因
reasonEdit_ = new QTextEdit(this);
reasonEdit_->setMaximumHeight(80);
reasonEdit_->setPlaceholderText(tr("请输入入院原因"));
input_validator::installTextEditValidator(reasonEdit_); // 限制最大255字符
formLayout->addRow(tr("入院原因:"), reasonEdit_);
buttonBox_ = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
connect(buttonBox_, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttonBox_, &QDialogButtonBox::rejected, this, &QDialog::reject);
formLayout->addRow(buttonBox_);
mainLayout->addLayout(formLayout);
// 联动床位选择
connect(wardCombo_, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index) {
bedCombo_->clear();
if (index == 0) {
bedCombo_->addItem(tr("-- 先选择病房 --"));
return;
}
QString wardId = wardCombo_->currentData().toString();
auto* ward = core_.wardService.findWard(wardId.toStdString());
if (!ward) return;
bedCombo_->addItem(tr("-- 请选择床位 --"));
for (const auto& bed : ward->Beds) {
if (bed.Status == BedStatus::Free) {
bedCombo_->addItem(QString::fromStdString(bed.BedID), QString::fromStdString(bed.BedID));
}
}
});
}
QString PatientAdmitDialog::getWardId() const {
return wardCombo_->currentData().toString();
}
QString PatientAdmitDialog::getBedId() const {
return bedCombo_->currentData().toString();
}
QString PatientAdmitDialog::getReason() const {
return reasonEdit_->toPlainText();
}
// ========== DiagnosisAddDialog ==========
DiagnosisAddDialog::DiagnosisAddDialog(core::HisCore& core, const QString& patientId, QWidget* parent)
: QDialog(parent), core_(core), patientId_(patientId) {
setupUI();
}
DiagnosisAddDialog::~DiagnosisAddDialog() {}
void DiagnosisAddDialog::setupUI() {
setWindowTitle(tr("添加诊断记录 - %1").arg(patientId_));
setModal(true);
resize(500, 450);
setAttribute(Qt::WA_DeleteOnClose);
auto* mainLayout = new QVBoxLayout(this);
auto* formLayout = new QFormLayout();
formLayout->setSpacing(10);
formLayout->setContentsMargins(20, 20, 20, 20);
// 医生选择(从已有医生中选择)
doctorCombo_ = new QComboBox(this);
doctorCombo_->addItem(tr("-- 请选择医生 --"));
core_.doctorService.for_eachDoctor([this](const std::string&, const Doctor& d) {
QString display = QString("%1 (%2) - %3")
.arg(QString::fromStdString(d.DoctorID))
.arg(QString::fromStdString(d.Name))
.arg(doctorTitleToText(d.Title));
doctorCombo_->addItem(display, QString::fromStdString(d.DoctorID));
});
formLayout->addRow(tr("选择医生:"), doctorCombo_);
// 自动显示诊断日期
dateLabel_ = new QLabel(formatCurrentDate(), this);
dateLabel_->setEnabled(false);
formLayout->addRow(tr("诊断日期:"), dateLabel_);
diagnosisEdit_ = new QTextEdit(this);
diagnosisEdit_->setMinimumHeight(60);
diagnosisEdit_->setPlaceholderText(tr("请输入诊断内容"));
input_validator::installTextEditValidator(diagnosisEdit_); // 限制最大255字符
formLayout->addRow(tr("诊断:"), diagnosisEdit_);
prescriptionEdit_ = new QTextEdit(this);
prescriptionEdit_->setMinimumHeight(60);
prescriptionEdit_->setPlaceholderText(tr("请输入处方"));
input_validator::installTextEditValidator(prescriptionEdit_); // 限制最大255字符
formLayout->addRow(tr("处方:"), prescriptionEdit_);
remarksEdit_ = new QLineEdit(this);
remarksEdit_->setPlaceholderText(tr("备注(可选)"));
input_validator::setMaxLength(remarksEdit_); // 限制最大255字符
formLayout->addRow(tr("备注:"), remarksEdit_);
buttonBox_ = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
connect(buttonBox_, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttonBox_, &QDialogButtonBox::rejected, this, &QDialog::reject);
formLayout->addRow(buttonBox_);
mainLayout->addLayout(formLayout);
}
QString DiagnosisAddDialog::getDoctorId() const {
return doctorCombo_->currentData().toString();
}
QString DiagnosisAddDialog::getDiagnosis() const {
return diagnosisEdit_->toPlainText();
}
QString DiagnosisAddDialog::getPrescription() const {
return prescriptionEdit_->toPlainText();
}
QString DiagnosisAddDialog::getRemarks() const {
return remarksEdit_->text();
}
// ========== MedicineManageDialog ==========
MedicineManageDialog::MedicineManageDialog(core::HisCore& core, const QString& medicineId, QWidget* parent)
: QDialog(parent), core_(core), medicineId_(medicineId), isEditMode_(!medicineId.isEmpty()) {
setupUI();
if (isEditMode_) {
loadMedicineData();
}
}
MedicineManageDialog::~MedicineManageDialog() {}
void MedicineManageDialog::setupUI() {
setWindowTitle(isEditMode_ ? tr("更新药品信息") : tr("添加药品"));
setModal(true);
resize(400, 350);
setAttribute(Qt::WA_DeleteOnClose);
auto* mainLayout = new QVBoxLayout(this);
auto* formLayout = new QFormLayout();
formLayout->setSpacing(10);
formLayout->setContentsMargins(20, 20, 20, 20);
if (isEditMode_) {
idLabel_ = new QLabel(medicineId_, this);
idLabel_->setEnabled(false);
formLayout->addRow(tr("药品ID:"), idLabel_);
} else {
QString newMedId = QString::fromStdString(Medicine::generateUniqueId());
idLabel_ = new QLabel(newMedId, this);
idLabel_->setEnabled(false);
formLayout->addRow(tr("药品ID (自动生成):"), idLabel_);
}
genericEdit_ = new QLineEdit(this);
genericEdit_->setPlaceholderText(tr("请输入通用名"));
input_validator::setMaxLength(genericEdit_); // 限制最大255字符
formLayout->addRow(tr("通用名:"), genericEdit_);
brandEdit_ = new QLineEdit(this);
brandEdit_->setPlaceholderText(tr("请输入商品名"));
input_validator::setMaxLength(brandEdit_); // 限制最大255字符
formLayout->addRow(tr("商品名:"), brandEdit_);
deptEdit_ = new QComboBox(this);
deptEdit_->addItem(tr("-- 请选择科室 --"));
std::set<std::string> departments;
core_.wardService.for_eachWard([&departments](const std::string&, const Ward& w) {
departments.insert(w.DepartmentID);
});
for (const auto& dept : departments) {
deptEdit_->addItem(QString::fromStdString(dept), QString::fromStdString(dept));
}
formLayout->addRow(tr("所属科室:"), deptEdit_);
stockSpin_ = new QSpinBox(this);
stockSpin_->setRange(0, input_validator::MAX_INT_VALUE); // 使用int最大值限制
stockSpin_->setValue(0);
formLayout->addRow(tr("库存:"), stockSpin_);
priceSpin_ = new QDoubleSpinBox(this);
priceSpin_->setRange(0.0, input_validator::MAX_DOUBLE_VALUE); // 使用double最大值
priceSpin_->setDecimals(2);
priceSpin_->setValue(0.0);
formLayout->addRow(tr("单价:"), priceSpin_);
buttonBox_ = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
connect(buttonBox_, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttonBox_, &QDialogButtonBox::rejected, this, &QDialog::reject);
formLayout->addRow(buttonBox_);
mainLayout->addLayout(formLayout);
}
void MedicineManageDialog::loadMedicineData() {
auto* m = core_.medicineService.findMedicine(medicineId_.toStdString());
if (!m) return;
genericEdit_->setText(QString::fromStdString(m->GenericName));
brandEdit_->setText(QString::fromStdString(m->BrandName));
int deptIdx = deptEdit_->findData(QString::fromStdString(m->DepartmentID));
if (deptIdx != -1) {
deptEdit_->setCurrentIndex(deptIdx);
}
stockSpin_->setValue(m->StockQuantity);
priceSpin_->setValue(m->UnitPrice);
}
QString MedicineManageDialog::getGenericName() const { return genericEdit_->text(); }
QString MedicineManageDialog::getBrandName() const { return brandEdit_->text(); }
QString MedicineManageDialog::getDepartment() const { return deptEdit_->currentText(); }
int MedicineManageDialog::getStock() const { return stockSpin_->value(); }
double MedicineManageDialog::getPrice() const { return priceSpin_->value(); }
bool MedicineManageDialog::isEditMode() const { return isEditMode_; }
// ========== WardBedManageDialog ==========
WardBedManageDialog::WardBedManageDialog(core::HisCore& core, QWidget* parent)
: QDialog(parent), core_(core) {
setupUI();
refreshWardTable();
}
WardBedManageDialog::~WardBedManageDialog() {}
void WardBedManageDialog::setupUI() {
setWindowTitle(tr("病房床位管理"));
setModal(true);
resize(1000, 700);
setAttribute(Qt::WA_DeleteOnClose);
auto* mainLayout = new QVBoxLayout(this);
tabWidget_ = new QTabWidget(this);
// ===== Tab 1: 添加病房 =====
auto* addWardTab = new QWidget();
auto* addWardLayout = new QVBoxLayout(addWardTab);
auto* addWardForm = new QFormLayout();
QString newWardId = QString::fromStdString(Ward::generateUniqueId());
wardIdDisplay_ = new QLabel(newWardId, addWardTab);
wardIdDisplay_->setEnabled(false);
addWardForm->addRow(tr("病房ID (自动生成):"), wardIdDisplay_);
wardDeptEdit_ = new QComboBox(addWardTab);
wardDeptEdit_->addItem(tr("-- 请选择科室 --"));
core_.wardService.for_eachWard([this](const std::string&, const Ward& w) {
wardDeptEdit_->addItem(QString::fromStdString(w.DepartmentID), QString::fromStdString(w.DepartmentID));
});
addWardForm->addRow(tr("科室:"), wardDeptEdit_);
wardTypeCombo_ = new QComboBox(addWardTab);
wardTypeCombo_->addItems({tr("普通病房"), tr("特需病房"), tr("ICU")});
addWardForm->addRow(tr("病房类型:"), wardTypeCombo_);
wardMaxBedsSpin_ = new QSpinBox(addWardTab);
wardMaxBedsSpin_->setRange(1, 1000);
wardMaxBedsSpin_->setValue(10);
addWardForm->addRow(tr("最大床位数:"), wardMaxBedsSpin_);
auto* addWardBtn = new QPushButton(tr("添加病房"), addWardTab);
connect(addWardBtn, &QPushButton::clicked, this, &WardBedManageDialog::addWard);
addWardForm->addRow(addWardBtn);
addWardLayout->addLayout(addWardForm);
addWardLayout->addStretch();
tabWidget_->addTab(addWardTab, tr("添加病房"));
// ===== Tab 2: 添加床位 =====
auto* addBedTab = new QWidget();
auto* addBedLayout = new QVBoxLayout(addBedTab);
auto* addBedForm = new QFormLayout();
bedWardCombo_ = new QComboBox(addBedTab);
bedWardCombo_->addItem(tr("-- 请选择病房 --"));
core_.wardService.for_eachWard([this](const std::string&, const Ward& ward) {
QString display = QString("%1 (%2) - 空床: %3/%4")
.arg(QString::fromStdString(ward.WardID))
.arg(QString::fromStdString(ward.DepartmentID))
.arg(ward.freeBedCount())
.arg(ward.MaxBeds);
bedWardCombo_->addItem(display, QString::fromStdString(ward.WardID));
});
addBedForm->addRow(tr("选择病房:"), bedWardCombo_);
bedIdDisplay_ = new QLabel(tr("请先选择病房"), addBedTab);
bedIdDisplay_->setEnabled(false);
addBedForm->addRow(tr("床位ID (自动生成):"), bedIdDisplay_);
auto* addBedBtn = new QPushButton(tr("添加床位"), addBedTab);
connect(addBedBtn, &QPushButton::clicked, this, &WardBedManageDialog::addBed);
addBedForm->addRow(addBedBtn);
connect(bedWardCombo_, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index) {
if (index == 0) {
bedIdDisplay_->setText(tr("请先选择病房"));
return;
}
QString wardId = bedWardCombo_->currentData().toString();
auto* ward = core_.wardService.findWard(wardId.toStdString());
if (!ward) return;
QString newBedId = QString::fromStdString(Bed::generateUniqueId());
bedIdDisplay_->setText(newBedId);
});
addBedLayout->addLayout(addBedForm);
addBedLayout->addStretch();
tabWidget_->addTab(addBedTab, tr("添加床位"));
// ===== Tab 3: 删除床位 =====
auto* deleteBedTab = new QWidget();
auto* deleteBedLayout = new QVBoxLayout(deleteBedTab);
auto* deleteBedForm = new QFormLayout();
deleteBedWardCombo_ = new QComboBox(deleteBedTab);
deleteBedWardCombo_->addItem(tr("-- 请选择病房 --"));
core_.wardService.for_eachWard([this](const std::string&, const Ward& ward) {
QString display = QString("%1 (%2)")
.arg(QString::fromStdString(ward.WardID))
.arg(QString::fromStdString(ward.DepartmentID));
deleteBedWardCombo_->addItem(display, QString::fromStdString(ward.WardID));
});
deleteBedForm->addRow(tr("选择病房:"), deleteBedWardCombo_);
deleteBedCombo_ = new QComboBox(deleteBedTab);
deleteBedCombo_->addItem(tr("-- 选择床位 --"));
deleteBedForm->addRow(tr("选择床位:"), deleteBedCombo_);
applyDeleteBedBtn_ = new QPushButton(tr("删除床位"), deleteBedTab);
connect(applyDeleteBedBtn_, &QPushButton::clicked, this, &WardBedManageDialog::deleteBed);
deleteBedForm->addRow(applyDeleteBedBtn_);
connect(deleteBedWardCombo_, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index) {
deleteBedCombo_->clear();
if (index == 0) {
deleteBedCombo_->addItem(tr("-- 选择床位 --"));
return;
}
QString wardId = deleteBedWardCombo_->currentData().toString();
auto* ward = core_.wardService.findWard(wardId.toStdString());
if (!ward) return;
for (const auto& bed : ward->Beds) {
QString status = (bed.Status == BedStatus::Occupied)
? tr("%1 [已占用 - 患者: %2]").arg(QString::fromStdString(bed.BedID)).arg(QString::fromStdString(bed.PatientID))
: tr("%1 [空闲]").arg(QString::fromStdString(bed.BedID));
deleteBedCombo_->addItem(status, QString::fromStdString(bed.BedID));
}
});
deleteBedLayout->addLayout(deleteBedForm);
deleteBedLayout->addStretch();
tabWidget_->addTab(deleteBedTab, tr("删除床位"));
// ===== Tab 4: 病房列表 =====
auto* listTab = new QWidget();
auto* listLayout = new QVBoxLayout(listTab);
wardTable_ = new QTableWidget(0, 6, listTab);
wardTable_->setHorizontalHeaderLabels({tr("病房ID"), tr("科室"), tr("类型"), tr("最大床位"), tr("空闲床位"), tr("占用率")});
wardTable_->setSelectionBehavior(QAbstractItemView::SelectRows);
wardTable_->setEditTriggers(QAbstractItemView::NoEditTriggers);
wardTable_->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
listLayout->addWidget(wardTable_);
tabWidget_->addTab(listTab, tr("病房列表"));
mainLayout->addWidget(tabWidget_);
auto* buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, this);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
mainLayout->addWidget(buttonBox);
}
void WardBedManageDialog::refreshWardTable() {
wardTable_->setRowCount(0);
int row = 0;
core_.wardService.for_eachWard([this, &row](const std::string&, const Ward& ward) {
wardTable_->insertRow(row);
wardTable_->setItem(row, 0, new QTableWidgetItem(QString::fromStdString(ward.WardID)));
wardTable_->setItem(row, 1, new QTableWidgetItem(QString::fromStdString(ward.DepartmentID)));
wardTable_->setItem(row, 2, new QTableWidgetItem(wardTypeToText(ward.Type)));
wardTable_->setItem(row, 3, new QTableWidgetItem(QString::number(ward.MaxBeds)));
wardTable_->setItem(row, 4, new QTableWidgetItem(QString::number(ward.freeBedCount())));
wardTable_->setItem(row, 5, new QTableWidgetItem(QString("%1%").arg(int(ward.occupancyRate() * 100))));
row++;
});
}
void WardBedManageDialog::addWard() {
QString newWardId = wardIdDisplay_->text();
QString dept = wardDeptEdit_->currentText();
if (dept.isEmpty() || dept == tr("-- 请选择科室 --")) {
QMessageBox::warning(this, tr("错误"), tr("请选择科室"));
return;
}
WardType type = parseWardType(wardTypeCombo_->currentText());
int maxBeds = wardMaxBedsSpin_->value();
Ward ward(newWardId.toStdString(), dept.toStdString(), type, maxBeds);
if (!core_.wardService.addWard(ward)) {
QMessageBox::warning(this, tr("添加失败"), tr("病房ID已存在"));
return;
}
QMessageBox::information(this, tr("成功"), tr("已添加病房: %1").arg(newWardId));
// 重置表单
QString nextWardId = QString::fromStdString(Ward::generateUniqueId());
wardIdDisplay_->setText(nextWardId);
wardDeptEdit_->setCurrentIndex(0);
wardTypeCombo_->setCurrentIndex(0);
wardMaxBedsSpin_->setValue(10);
// 刷新病房下拉列表
bedWardCombo_->clear();
bedWardCombo_->addItem(tr("-- 请选择病房 --"));
deleteBedWardCombo_->clear();
deleteBedWardCombo_->addItem(tr("-- 请选择病房 --"));
core_.wardService.for_eachWard([this](const std::string&, const Ward& ward) {
QString display = QString("%1 (%2) - 空床: %3/%4")
.arg(QString::fromStdString(ward.WardID))
.arg(QString::fromStdString(ward.DepartmentID))
.arg(ward.freeBedCount())
.arg(ward.MaxBeds);
bedWardCombo_->addItem(display, QString::fromStdString(ward.WardID));
deleteBedWardCombo_->addItem(QString("%1 (%2)").arg(QString::fromStdString(ward.WardID)).arg(QString::fromStdString(ward.DepartmentID)), QString::fromStdString(ward.WardID));
});
refreshWardTable();
}
void WardBedManageDialog::addBed() {
if (bedWardCombo_->currentIndex() == 0) {
QMessageBox::warning(this, tr("错误"), tr("请选择病房"));
return;
}
QString wardId = bedWardCombo_->currentData().toString();
QString bedId = bedIdDisplay_->text();
if (bedId.isEmpty() || bedId == tr("请先选择病房")) {
QMessageBox::warning(this, tr("错误"), tr("无法生成床位ID"));
return;
}
if (!core_.wardService.addBed(wardId.toStdString(), bedId.toStdString())) {
QMessageBox::warning(this, tr("添加失败"), tr("床位已存在"));
return;
}
QMessageBox::information(this, tr("成功"), tr("已添加床位: %1 到病房: %2").arg(bedId).arg(wardId));
// 更新床位ID
auto* ward = core_.wardService.findWard(wardId.toStdString());
if (ward) {
QString newBedId = QString::fromStdString(Bed::generateUniqueId());
bedIdDisplay_->setText(newBedId);
}
refreshWardTable();
}
void WardBedManageDialog::deleteBed() {
if (deleteBedWardCombo_->currentIndex() == 0 || deleteBedCombo_->currentIndex() == 0) {
QMessageBox::warning(this, tr("错误"), tr("请选择病房和床位"));
return;
}
QString wardId = deleteBedWardCombo_->currentData().toString();
QString bedId = deleteBedCombo_->currentData().toString();
// 获取当前床位信息
auto* ward = core_.wardService.findWard(wardId.toStdString());
if (!ward) {
QMessageBox::warning(this, tr("错误"), tr("病房不存在"));
return;
}
Bed* bed = nullptr;
for (auto& b : ward->Beds) {
if (b.BedID == bedId.toStdString()) {
bed = &b;
break;
}
}
if (!bed) {
QMessageBox::warning(this, tr("错误"), tr("床位不存在"));
return;
}
// 如果床位有患者,自动释放
if (bed->Status == BedStatus::Occupied && !bed->PatientID.empty()) {
std::string patientId = bed->PatientID;
if (!core_.patientService.releaseBed(wardId.toStdString(), bedId.toStdString())) {
QMessageBox::warning(this, tr("删除失败"), tr("床位释放失败"));
return;
}
// 更新病例记录
auto* pc = core_.patientCaseService.getCase(patientId);
if (pc) {
for (auto& ar : pc->AdmissionRecords) {
if (ar.isCurrentlyAdmitted() && ar.BedID == bedId.toStdString()) {
ar.DischargeTime = time(nullptr);
ar.DischargeSummary = "自动释放床位";
break;
}
}
}
QMessageBox::information(this, tr("成功"), tr("已删除床位: %1患者 %2 已自动释放床位").arg(bedId).arg(QString::fromStdString(patientId)));
} else {
if (!core_.wardService.removeBed(wardId.toStdString(), bedId.toStdString())) {
QMessageBox::warning(this, tr("删除失败"), tr("无法删除床位"));
return;
}
QMessageBox::information(this, tr("成功"), tr("已删除床位: %1").arg(bedId));
}
// 刷新床位列表
deleteBedCombo_->clear();
deleteBedCombo_->addItem(tr("-- 选择床位 --"));
ward = core_.wardService.findWard(wardId.toStdString());
if (ward) {
for (const auto& b : ward->Beds) {
QString status = (b.Status == BedStatus::Occupied)
? tr("%1 [已占用 - 患者: %2]").arg(QString::fromStdString(b.BedID)).arg(QString::fromStdString(b.PatientID))
: tr("%1 [空闲]").arg(QString::fromStdString(b.BedID));
deleteBedCombo_->addItem(status, QString::fromStdString(b.BedID));
}
}
refreshWardTable();
}
// ========== WardOccupancyDialog ==========
WardOccupancyDialog::WardOccupancyDialog(core::HisCore& core, QWidget* parent)
: QDialog(parent), core_(core) {
setupUI();
}
WardOccupancyDialog::~WardOccupancyDialog() {}
void WardOccupancyDialog::setupUI() {
setWindowTitle(tr("病房使用情况"));
setModal(true);
resize(900, 600);
setAttribute(Qt::WA_DeleteOnClose);
auto* mainLayout = new QVBoxLayout(this);
// 筛选条件
auto* filterGroup = new QGroupBox(tr("筛选条件"), this);
auto* filterLayout = new QFormLayout(filterGroup);
wardTypeFilter_ = new QComboBox(filterGroup);
wardTypeFilter_->addItems({tr("全部"), tr("普通病房"), tr("特需病房"), tr("ICU")});
gradeFilter_ = new QComboBox(filterGroup);
gradeFilter_->addItems({tr("全部"), tr("高占用率 (>=80%)"), tr("中占用率 (40-80%)"), tr("低占用率 (<40%)")});
auto* applyBtn = new QPushButton(tr("应用筛选"), filterGroup);
connect(applyBtn, &QPushButton::clicked, this, &WardOccupancyDialog::refreshData);
filterLayout->addRow(tr("病房类型:"), wardTypeFilter_);
filterLayout->addRow(tr("占用率筛选:"), gradeFilter_);
filterLayout->addWidget(applyBtn);
mainLayout->addWidget(filterGroup);
// 汇总表
summaryTable_ = new QTableWidget(this);
summaryTable_->setColumnCount(7);
summaryTable_->setHorizontalHeaderLabels({tr("病房ID"), tr("科室"), tr("类型"), tr("总床位"), tr("已占用"), tr("空闲"), tr("占用率")});
summaryTable_->setSelectionBehavior(QAbstractItemView::SelectRows);
summaryTable_->setEditTriggers(QAbstractItemView::NoEditTriggers);
summaryTable_->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
mainLayout->addWidget(summaryTable_);
// 床位详情
auto* detailGroup = new QGroupBox(tr("床位详情(点击病房查看)"), this);
auto* detailLayout = new QVBoxLayout(detailGroup);
bedDetailTable_ = new QTableWidget(detailGroup);
bedDetailTable_->setColumnCount(4);
bedDetailTable_->setHorizontalHeaderLabels({tr("床位ID"), tr("状态"), tr("患者"), tr("备注")});
bedDetailTable_->setSelectionBehavior(QAbstractItemView::SelectRows);
bedDetailTable_->setEditTriggers(QAbstractItemView::NoEditTriggers);
bedDetailTable_->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
detailLayout->addWidget(bedDetailTable_);
mainLayout->addWidget(detailGroup);
connect(summaryTable_, &QTableWidget::itemSelectionChanged, this, &WardOccupancyDialog::showBedDetail);
refreshData();
auto* buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, this);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
mainLayout->addWidget(buttonBox);
}
void WardOccupancyDialog::refreshData() {
wardSummaries_.clear();
QString typeFilter = wardTypeFilter_->currentText();
QString gradeFilter = gradeFilter_->currentText();
core_.wardService.for_eachWard([this, typeFilter, gradeFilter](const std::string&, const Ward& ward) {
QString wardType = wardTypeToText(ward.Type);
double rate = ward.occupancyRate();
if (typeFilter != tr("全部") && typeFilter != wardType) return;
if (gradeFilter == tr("高占用率 (>=80%)") && rate < 0.8) return;
if (gradeFilter == tr("中占用率 (40-80%)") && (rate < 0.4 || rate > 0.8)) return;
if (gradeFilter == tr("低占用率 (<40%)") && rate >= 0.4) return;
WardSummary ws;
ws.wardId = QString::fromStdString(ward.WardID);
ws.department = QString::fromStdString(ward.DepartmentID);
ws.type = wardType;
ws.totalBeds = ward.MaxBeds;
ws.occupied = ward.occupiedBedCount();
ws.free = ward.freeBedCount();
ws.rate = rate;
wardSummaries_.append(ws);
});
summaryTable_->setRowCount(wardSummaries_.size());
for (int i = 0; i < wardSummaries_.size(); ++i) {
const auto& ws = wardSummaries_[i];
summaryTable_->setItem(i, 0, new QTableWidgetItem(ws.wardId));
summaryTable_->setItem(i, 1, new QTableWidgetItem(ws.department));
summaryTable_->setItem(i, 2, new QTableWidgetItem(ws.type));
summaryTable_->setItem(i, 3, new QTableWidgetItem(QString::number(ws.totalBeds)));
summaryTable_->setItem(i, 4, new QTableWidgetItem(QString::number(ws.occupied)));
summaryTable_->setItem(i, 5, new QTableWidgetItem(QString::number(ws.free)));
summaryTable_->setItem(i, 6, new QTableWidgetItem(QString("%1%").arg(int(ws.rate * 100))));
}
}
void WardOccupancyDialog::showBedDetail() {
int row = summaryTable_->currentRow();
if (row < 0) return;
QString wardId = summaryTable_->item(row, 0)->text();
auto* ward = core_.wardService.findWard(wardId.toStdString());
if (!ward) return;
bedDetailTable_->setRowCount(static_cast<int>(ward->Beds.size()));
for (size_t i = 0; i < ward->Beds.size(); ++i) {
const auto& bed = ward->Beds[i];
bedDetailTable_->setItem(static_cast<int>(i), 0, new QTableWidgetItem(QString::fromStdString(bed.BedID)));
bedDetailTable_->setItem(static_cast<int>(i), 1, new QTableWidgetItem(bed.Status == BedStatus::Occupied ? tr("已占用") : tr("空闲")));
bedDetailTable_->setItem(static_cast<int>(i), 2, new QTableWidgetItem(bed.PatientID.empty() ? "-" : QString::fromStdString(bed.PatientID)));
bedDetailTable_->setItem(static_cast<int>(i), 3, new QTableWidgetItem(""));
}
}
// ========== DoctorAddDialog ==========
DoctorAddDialog::DoctorAddDialog(core::HisCore& core, const QString& doctorId, QWidget* parent)
: QDialog(parent), core_(core), doctorId_(doctorId), isEditMode_(!doctorId.isEmpty()) {
setupUI();
if (isEditMode_) {
loadDoctorData();
}
}
DoctorAddDialog::~DoctorAddDialog() {}
void DoctorAddDialog::setupUI() {
setWindowTitle(isEditMode_ ? tr("编辑医生信息") : tr("添加医生"));
setModal(true);
resize(450, 300);
setAttribute(Qt::WA_DeleteOnClose);
auto* mainLayout = new QVBoxLayout(this);
auto* formLayout = new QFormLayout();
formLayout->setSpacing(10);
formLayout->setContentsMargins(20, 20, 20, 20);
if (isEditMode_) {
idLabel_ = new QLabel(doctorId_, this);
idLabel_->setEnabled(false);
formLayout->addRow(tr("医生ID:"), idLabel_);
} else {
QString newDoctorId = QString::fromStdString(Doctor::generateUniqueId());
idLabel_ = new QLabel(newDoctorId, this);
idLabel_->setEnabled(false);
formLayout->addRow(tr("医生ID (自动生成):"), idLabel_);
}
nameEdit_ = new QLineEdit(this);
nameEdit_->setPlaceholderText(tr("请输入医生姓名"));
input_validator::setMaxLength(nameEdit_); // 限制最大255字符
formLayout->addRow(tr("姓名:"), nameEdit_);
deptEdit_ = new QComboBox(this);
deptEdit_->addItem(tr("-- 请选择科室 --"));
core_.departmentService.for_eachDepartment([this](const std::string&, const Department& d) {
deptEdit_->addItem(QString::fromStdString(d.Name), QString::fromStdString(d.DepartmentID));
});
formLayout->addRow(tr("科室:"), deptEdit_);
titleCombo_ = new QComboBox(this);
titleCombo_->addItems({tr("住院医师"), tr("主治医师"), tr("副主任医师"), tr("主任医师")});
formLayout->addRow(tr("职称:"), titleCombo_);
scheduleEdit_ = new QLineEdit(this);
scheduleEdit_->setPlaceholderText(tr("请输入排班信息"));
input_validator::setMaxLength(scheduleEdit_); // 限制最大255字符
formLayout->addRow(tr("排班:"), scheduleEdit_);
buttonBox_ = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
connect(buttonBox_, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttonBox_, &QDialogButtonBox::rejected, this, &QDialog::reject);
formLayout->addRow(buttonBox_);
mainLayout->addLayout(formLayout);
}
void DoctorAddDialog::loadDoctorData() {
auto* d = core_.doctorService.findDoctor(doctorId_.toStdString());
if (!d) return;
nameEdit_->setText(QString::fromStdString(d->Name));
int deptIdx = deptEdit_->findData(QString::fromStdString(d->DepartmentID));
if (deptIdx != -1) {
deptEdit_->setCurrentIndex(deptIdx);
}
int titleIdx = (d->Title == DoctorTitle::Chief) ? 3 :
(d->Title == DoctorTitle::AssociateChief) ? 2 :
(d->Title == DoctorTitle::Attending) ? 1 : 0;
titleCombo_->setCurrentIndex(titleIdx);
scheduleEdit_->setText(QString::fromStdString(d->Schedule));
}
QString DoctorAddDialog::getName() const { return nameEdit_->text(); }
QString DoctorAddDialog::getDepartment() const { return deptEdit_->currentText(); }
QString DoctorAddDialog::getTitle() const { return titleCombo_->currentText(); }
QString DoctorAddDialog::getSchedule() const { return scheduleEdit_->text(); }
bool DoctorAddDialog::isEditMode() const { return isEditMode_; }
// ========== PatientCaseViewDialog ==========
PatientCaseViewDialog::PatientCaseViewDialog(core::HisCore& core, const QString& patientId, QWidget* parent)
: QDialog(parent), core_(core), patientId_(patientId) {
setupUI();
refreshCaseDisplay();
}
PatientCaseViewDialog::~PatientCaseViewDialog() {}
void PatientCaseViewDialog::setupUI() {
setWindowTitle(tr("病例信息 - %1").arg(patientId_));
setModal(true);
resize(800, 600);
setAttribute(Qt::WA_DeleteOnClose);
auto* mainLayout = new QVBoxLayout(this);
// 顶部工具栏
auto* toolBar = new QHBoxLayout();
auto* sortLabel = new QLabel(tr("排序方式:"), this);
sortComboBox_ = new QComboBox(this);
sortComboBox_->addItems({tr("按时间"), tr("按医生"), tr("按类型")});
toolBar->addWidget(sortLabel);
toolBar->addWidget(sortComboBox_);
toolBar->addStretch();
mainLayout->addLayout(toolBar);
// 选项卡
tabWidget_ = new QTabWidget(this);
// 预约记录选项卡
auto* appointmentTab = new QWidget();
auto* appointmentLayout = new QVBoxLayout(appointmentTab);
appointmentTable_ = new QTableWidget(0, 3, appointmentTab);
appointmentTable_->setHorizontalHeaderLabels({tr("预约时间"), tr("医生"), tr("备注")});
appointmentTable_->setSelectionBehavior(QAbstractItemView::SelectRows);
appointmentTable_->setEditTriggers(QAbstractItemView::NoEditTriggers);
appointmentTable_->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
appointmentLayout->addWidget(appointmentTable_);
tabWidget_->addTab(appointmentTab, tr("预约记录"));
// 检查记录选项卡
auto* checkTab = new QWidget();
auto* checkLayout = new QVBoxLayout(checkTab);
checkTable_ = new QTableWidget(0, 5, checkTab);
checkTable_->setHorizontalHeaderLabels({tr("时间"), tr("检查项目"), tr("科室"), tr("价格"), tr("开单医生")});
checkTable_->setSelectionBehavior(QAbstractItemView::SelectRows);
checkTable_->setEditTriggers(QAbstractItemView::NoEditTriggers);
checkTable_->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
checkLayout->addWidget(checkTable_);
tabWidget_->addTab(checkTab, tr("检查记录"));
// 诊断记录选项卡
auto* diagnosisTab = new QWidget();
auto* diagnosisLayout = new QVBoxLayout(diagnosisTab);
diagnosisTable_ = new QTableWidget(0, 5, diagnosisTab);
diagnosisTable_->setHorizontalHeaderLabels({tr("时间"), tr("医生"), tr("诊断"), tr("处方"), tr("备注")});
diagnosisTable_->setSelectionBehavior(QAbstractItemView::SelectRows);
diagnosisTable_->setEditTriggers(QAbstractItemView::NoEditTriggers);
diagnosisTable_->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
diagnosisLayout->addWidget(diagnosisTable_);
tabWidget_->addTab(diagnosisTab, tr("诊断记录"));
// 用药记录选项卡
auto* medicineTab = new QWidget();
auto* medicineLayout = new QVBoxLayout(medicineTab);
medicineTable_ = new QTableWidget(0, 6, medicineTab);
medicineTable_->setHorizontalHeaderLabels({tr("时间"), tr("药品"), tr("数量"), tr("用法"), tr("单价"), tr("总价")});
medicineTable_->setSelectionBehavior(QAbstractItemView::SelectRows);
medicineTable_->setEditTriggers(QAbstractItemView::NoEditTriggers);
medicineTable_->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
medicineLayout->addWidget(medicineTable_);
tabWidget_->addTab(medicineTab, tr("用药记录"));
// 住院记录选项卡
auto* admissionTab = new QWidget();
auto* admissionLayout = new QVBoxLayout(admissionTab);
admissionTable_ = new QTableWidget(0, 6, admissionTab);
admissionTable_->setHorizontalHeaderLabels({tr("入院时间"), tr("出院时间"), tr("病房"), tr("床位"), tr("原因"), tr("出院小结")});
admissionTable_->setSelectionBehavior(QAbstractItemView::SelectRows);
admissionTable_->setEditTriggers(QAbstractItemView::NoEditTriggers);
admissionTable_->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
admissionLayout->addWidget(admissionTable_);
tabWidget_->addTab(admissionTab, tr("住院记录"));
mainLayout->addWidget(tabWidget_);
// 关闭按钮
auto* buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, this);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
mainLayout->addWidget(buttonBox);
// 排序变化时刷新
connect(sortComboBox_, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &PatientCaseViewDialog::refreshCaseDisplay);
}
void PatientCaseViewDialog::refreshCaseDisplay() {
PatientCase* pc = core_.patientCaseService.getCase(patientId_.toStdString());
if (!pc) {
diagnosisTable_->setRowCount(0);
medicineTable_->setRowCount(0);
admissionTable_->setRowCount(0);
checkTable_->setRowCount(0);
return;
}
// 清空表格
diagnosisTable_->setRowCount(0);
medicineTable_->setRowCount(0);
admissionTable_->setRowCount(0);
checkTable_->setRowCount(0);
appointmentTable_->setRowCount(0);
// 填充诊断记录
int row = 0;
for (const auto& r : pc->DiagnosisRecords) {
diagnosisTable_->insertRow(row);
diagnosisTable_->setItem(row, 0, new QTableWidgetItem(formatDate(r.Timestamp)));
diagnosisTable_->setItem(row, 1, new QTableWidgetItem(QString::fromStdString(r.DoctorID)));
diagnosisTable_->setItem(row, 2, new QTableWidgetItem(QString::fromStdString(r.Diagnosis)));
diagnosisTable_->setItem(row, 3, new QTableWidgetItem(QString::fromStdString(r.Prescription)));
diagnosisTable_->setItem(row, 4, new QTableWidgetItem(QString::fromStdString(r.Remarks)));
row++;
}
// 填充用药记录
row = 0;
for (const auto& r : pc->MedicineRecords) {
medicineTable_->insertRow(row);
medicineTable_->setItem(row, 0, new QTableWidgetItem(formatDate(r.Timestamp)));
medicineTable_->setItem(row, 1, new QTableWidgetItem(QString::fromStdString(r.MedicineName)));
medicineTable_->setItem(row, 2, new QTableWidgetItem(QString::number(r.Quantity)));
medicineTable_->setItem(row, 3, new QTableWidgetItem(QString::fromStdString(r.Usage)));
medicineTable_->setItem(row, 4, new QTableWidgetItem(QString::number(r.UnitPrice, 'f', 2)));
medicineTable_->setItem(row, 5, new QTableWidgetItem(QString::number(r.getTotalPrice(), 'f', 2)));
row++;
}
// 填充住院记录
row = 0;
for (const auto& r : pc->AdmissionRecords) {
admissionTable_->insertRow(row);
admissionTable_->setItem(row, 0, new QTableWidgetItem(formatDate(r.AdmissionTime)));
admissionTable_->setItem(row, 1, new QTableWidgetItem(r.DischargeTime == 0 ? tr("在院") : formatDate(r.DischargeTime)));
admissionTable_->setItem(row, 2, new QTableWidgetItem(QString::fromStdString(r.WardID)));
admissionTable_->setItem(row, 3, new QTableWidgetItem(QString::fromStdString(r.BedID)));
admissionTable_->setItem(row, 4, new QTableWidgetItem(QString::fromStdString(r.Reason)));
admissionTable_->setItem(row, 5, new QTableWidgetItem(QString::fromStdString(r.DischargeSummary)));
row++;
}
// 填充检查记录
row = 0;
for (const auto& r : pc->CheckRecords) {
checkTable_->insertRow(row);
checkTable_->setItem(row, 0, new QTableWidgetItem(formatDate(r.Timestamp)));
checkTable_->setItem(row, 1, new QTableWidgetItem(QString::fromStdString(r.CheckName)));
checkTable_->setItem(row, 2, new QTableWidgetItem(QString::fromStdString(r.DepartmentID)));
checkTable_->setItem(row, 3, new QTableWidgetItem(QString::number(r.Price, 'f', 2)));
checkTable_->setItem(row, 4, new QTableWidgetItem(QString::fromStdString(r.DoctorID)));
row++;
}
// 填充预约记录
row = 0;
for (const auto& r : pc->AppointmentRecords) {
appointmentTable_->insertRow(row);
appointmentTable_->setItem(row, 0, new QTableWidgetItem(QString::fromStdString(r.AppointmentDate)));
appointmentTable_->setItem(row, 1, new QTableWidgetItem(QString::fromStdString(r.DoctorID)));
appointmentTable_->setItem(row, 2, new QTableWidgetItem(QString::fromStdString(r.Notes)));
row++;
}
}
// ========== MedicineRecordAddDialog ==========
MedicineRecordAddDialog::MedicineRecordAddDialog(core::HisCore& core, const QString& patientId, QWidget* parent)
: QDialog(parent), core_(core), patientId_(patientId),
selectedMedicineId_(QString()), selectedDoctorId_(QString()) {
setupUI();
}
MedicineRecordAddDialog::~MedicineRecordAddDialog() {}
void MedicineRecordAddDialog::setupUI() {
setWindowTitle(tr("添加用药记录 - %1").arg(patientId_));
setModal(true);
resize(500, 400);
auto* mainLayout = new QVBoxLayout(this);
auto* formLayout = new QFormLayout();
formLayout->setSpacing(10);
formLayout->setContentsMargins(20, 20, 20, 20);
// 药品信息显示 - 必须在药品选择之前创建,避免信号触发时访问空指针
stockLabel_ = new QLabel(tr("库存: -"), this);
priceLabel_ = new QLabel(tr("单价: -"), this);
formLayout->addRow(tr("库存信息:"), stockLabel_);
formLayout->addRow(tr("价格信息:"), priceLabel_);
// 药品选择
medicineCombo_ = new QComboBox(this);
medicineCombo_->addItem(tr("-- 请选择药品 --"), QVariant());
core_.medicineService.for_eachMedicine([this](const std::string&, const Medicine& med) {
QString display = QString("%1 (%2) - 库存: %3")
.arg(QString::fromStdString(med.GenericName))
.arg(QString::fromStdString(med.BrandName))
.arg(med.StockQuantity);
medicineCombo_->addItem(display, QString::fromStdString(med.MedicineID));
});
formLayout->addRow(tr("选择药品:"), medicineCombo_);
// 缓存当前选中药品 ID避免 exec 后直接读取 ComboBox 出现悬指针
connect(medicineCombo_, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
[this](int index) {
if (index <= 0) {
selectedMedicineId_.clear();
stockLabel_->setText(tr("库存: -"));
priceLabel_->setText(tr("单价: -"));
return;
}
selectedMedicineId_ = medicineCombo_->itemData(index).toString().trimmed();
});
// 数量
quantitySpin_ = new QSpinBox(this);
quantitySpin_->setRange(1, 1000);
quantitySpin_->setValue(1);
formLayout->addRow(tr("数量:"), quantitySpin_);
// 用法
usageCombo_ = new QComboBox(this);
usageCombo_->addItems({tr("一日三次"), tr("一日两次"), tr("一日一次"), tr("按需服用"), tr("外用")});
formLayout->addRow(tr("用法:"), usageCombo_);
// 医生选择
doctorCombo_ = new QComboBox(this);
doctorCombo_->addItem(tr("-- 请选择医生 --"));
core_.doctorService.for_eachDoctor([this](const std::string&, const Doctor& d) {
QString display = QString("%1 (%2)")
.arg(QString::fromStdString(d.Name))
.arg(QString::fromStdString(d.DoctorID));
doctorCombo_->addItem(display, QString::fromStdString(d.DoctorID));
});
formLayout->addRow(tr("开药医生:"), doctorCombo_);
connect(doctorCombo_, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
[this](int index) {
if (index <= 0) {
selectedDoctorId_.clear();
return;
}
selectedDoctorId_ = doctorCombo_->itemData(index).toString().trimmed();
});
buttonBox_ = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
connect(buttonBox_, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttonBox_, &QDialogButtonBox::rejected, this, &QDialog::reject);
formLayout->addRow(buttonBox_);
mainLayout->addLayout(formLayout);
// 药品选择变化时更新信息
// 药品选择变化时更新信息(✅ 用 lambda彻底避免信号槽坑
connect(medicineCombo_, &QComboBox::currentIndexChanged, this,
[this](int index) {
if (index <= 0) {
stockLabel_->setText(tr("库存: -"));
priceLabel_->setText(tr("单价: -"));
return;
}
QString medicineId = medicineCombo_->itemData(index).toString();
if (medicineId.isEmpty()) {
stockLabel_->setText(tr("库存: -"));
priceLabel_->setText(tr("单价: -"));
return;
}
auto* med = core_.medicineService.findMedicine(medicineId.toStdString());
if (med) {
stockLabel_->setText(tr("库存: %1").arg(med->StockQuantity));
priceLabel_->setText(tr("单价: %1").arg(QString::number(med->UnitPrice, 'f', 2)));
quantitySpin_->setRange(1, med->StockQuantity);
}
}
);
}
QString MedicineRecordAddDialog::getMedicineId() const {
// 优先使用缓存值,提高稳定性,避免控件对象在 exec() 后的潜在内存问题
if (!selectedMedicineId_.isEmpty()) {
return selectedMedicineId_;
}
if (!medicineCombo_) {
return QString();
}
int idx = medicineCombo_->currentIndex();
if (idx <= 0 || idx >= medicineCombo_->count()) {
return QString();
}
return medicineCombo_->itemData(idx).toString().trimmed();
}
int MedicineRecordAddDialog::getQuantity() const {
return quantitySpin_->value();
}
QString MedicineRecordAddDialog::getUsage() const {
return usageCombo_->currentText();
}
QString MedicineRecordAddDialog::getDoctorId() const {
if (!selectedDoctorId_.isEmpty()) {
return selectedDoctorId_;
}
if (!doctorCombo_) {
return QString();
}
int idx = doctorCombo_->currentIndex();
if (idx <= 0 || idx >= doctorCombo_->count()) {
return QString();
}
return doctorCombo_->itemData(idx).toString().trimmed();
}
// ========== CheckRecordAddDialog ==========
CheckRecordAddDialog::CheckRecordAddDialog(core::HisCore& core, const QString& patientId, QWidget* parent)
: QDialog(parent), core_(core), patientId_(patientId),
selectedCheckId_(QString()), selectedDoctorId_(QString()) {
setupUI();
}
CheckRecordAddDialog::~CheckRecordAddDialog() {}
void CheckRecordAddDialog::setupUI() {
setWindowTitle(tr("添加检查记录 - %1").arg(patientId_));
setModal(true);
resize(500, 300);
auto* mainLayout = new QVBoxLayout(this);
auto* formLayout = new QFormLayout();
formLayout->setSpacing(10);
formLayout->setContentsMargins(20, 20, 20, 20);
// 价格信息显示
priceLabel_ = new QLabel(tr("价格: -"), this);
formLayout->addRow(tr("价格信息:"), priceLabel_);
// 检查项目选择
checkCombo_ = new QComboBox(this);
checkCombo_->addItem(tr("-- 请选择检查项目 --"), QVariant());
core_.checkService.for_eachCheck([this](const std::string&, const Check& chk) {
QString display = QString("%1 - %2")
.arg(QString::fromStdString(chk.Name))
.arg(QString::number(chk.Price, 'f', 2));
checkCombo_->addItem(display, QString::fromStdString(chk.CheckID));
});
formLayout->addRow(tr("选择检查项目:"), checkCombo_);
// 缓存当前选中检查 ID
connect(checkCombo_, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
[this](int index) {
if (index <= 0) {
selectedCheckId_.clear();
priceLabel_->setText(tr("价格: -"));
return;
}
selectedCheckId_ = checkCombo_->itemData(index).toString().trimmed();
});
// 医生选择
doctorCombo_ = new QComboBox(this);
doctorCombo_->addItem(tr("-- 请选择医生 --"));
core_.doctorService.for_eachDoctor([this](const std::string&, const Doctor& d) {
QString display = QString("%1 (%2)")
.arg(QString::fromStdString(d.Name))
.arg(QString::fromStdString(d.DoctorID));
doctorCombo_->addItem(display, QString::fromStdString(d.DoctorID));
});
formLayout->addRow(tr("开单医生:"), doctorCombo_);
connect(doctorCombo_, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
[this](int index) {
if (index <= 0) {
selectedDoctorId_.clear();
return;
}
selectedDoctorId_ = doctorCombo_->itemData(index).toString().trimmed();
});
buttonBox_ = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
connect(buttonBox_, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttonBox_, &QDialogButtonBox::rejected, this, &QDialog::reject);
formLayout->addRow(buttonBox_);
mainLayout->addLayout(formLayout);
// 检查项目选择变化时更新信息
connect(checkCombo_, &QComboBox::currentIndexChanged, this,
[this](int index) {
if (index <= 0) {
priceLabel_->setText(tr("价格: -"));
return;
}
QString checkId = checkCombo_->itemData(index).toString();
if (checkId.isEmpty()) {
priceLabel_->setText(tr("价格: -"));
return;
}
auto* chk = core_.checkService.findCheck(checkId.toStdString());
if (chk) {
priceLabel_->setText(tr("价格: %1").arg(QString::number(chk->Price, 'f', 2)));
}
}
);
}
QString CheckRecordAddDialog::getCheckId() const {
if (!selectedCheckId_.isEmpty()) {
return selectedCheckId_;
}
if (!checkCombo_) {
return QString();
}
int idx = checkCombo_->currentIndex();
if (idx <= 0 || idx >= checkCombo_->count()) {
return QString();
}
return checkCombo_->itemData(idx).toString().trimmed();
}
QString CheckRecordAddDialog::getDoctorId() const {
if (!selectedDoctorId_.isEmpty()) {
return selectedDoctorId_;
}
if (!doctorCombo_) {
return QString();
}
int idx = doctorCombo_->currentIndex();
if (idx <= 0 || idx >= doctorCombo_->count()) {
return QString();
}
return doctorCombo_->itemData(idx).toString().trimmed();
}
// ========== RegistrationDialog ==========
RegistrationDialog::RegistrationDialog(core::HisCore& core, const QString& patientId, QWidget* parent)
: QDialog(parent), core_(core), patientId_(patientId) {
setupUI();
loadDepartments();
}
RegistrationDialog::~RegistrationDialog() {}
void RegistrationDialog::setupUI() {
setWindowTitle(tr("挂号 - 患者 %1").arg(patientId_));
setModal(true);
resize(700, 500);
// 不使用 Qt::WA_DeleteOnClose改用手动生命周期管理
auto* mainLayout = new QVBoxLayout(this);
mainLayout->setSpacing(10);
mainLayout->setContentsMargins(20, 20, 20, 20);
// 患者信息
auto* patientInfoLayout = new QHBoxLayout();
patientInfoLayout->addWidget(new QLabel(tr("患者ID:"), this));
auto* patientIdLabel = new QLabel(patientId_, this);
patientIdLabel->setEnabled(false);
patientInfoLayout->addWidget(patientIdLabel);
patientInfoLayout->addSpacing(20);
patientInfoLayout->addWidget(new QLabel(tr("挂号时间:"), this));
dateLabel_ = new QLabel(formatCurrentDate(), this);
dateLabel_->setEnabled(false);
patientInfoLayout->addWidget(dateLabel_);
patientInfoLayout->addStretch();
mainLayout->addLayout(patientInfoLayout);
// 分隔线
auto* separator1 = new QFrame(this);
separator1->setFrameShape(QFrame::HLine);
separator1->setFrameShadow(QFrame::Sunken);
mainLayout->addWidget(separator1);
// 挂号类型选择:门诊或急诊
auto* typeLayout = new QHBoxLayout();
typeLayout->addWidget(new QLabel(tr("挂号类型:"), this));
registrationTypeCombo_ = new QComboBox(this);
registrationTypeCombo_->addItem(tr("门诊"), QVariant(static_cast<int>(0))); // 0 = 门诊
registrationTypeCombo_->addItem(tr("急诊"), QVariant(static_cast<int>(1))); // 1 = 急诊
typeLayout->addWidget(registrationTypeCombo_);
// 挂号费提示
feeLabel_ = new QLabel(tr("挂号费: 10元"), this);
feeLabel_->setStyleSheet("color: blue; font-weight: bold;");
typeLayout->addWidget(feeLabel_);
typeLayout->addStretch();
mainLayout->addLayout(typeLayout);
// 连接挂号类型变化信号
connect(registrationTypeCombo_, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &RegistrationDialog::onRegistrationTypeChanged);
// 分隔线
auto* separator2 = new QFrame(this);
separator2->setFrameShape(QFrame::HLine);
separator2->setFrameShadow(QFrame::Sunken);
mainLayout->addWidget(separator2);
// 科室选择
auto* deptLayout = new QHBoxLayout();
deptLayout->addWidget(new QLabel(tr("选择科室:"), this));
departmentCombo_ = new QComboBox(this);
departmentCombo_->addItem(tr("-- 请选择科室 --"));
deptLayout->addWidget(departmentCombo_);
deptLayout->addStretch();
mainLayout->addLayout(deptLayout);
// 医生列表
mainLayout->addWidget(new QLabel(tr("医生信息:"), this));
doctorTable_ = new QTableWidget(0, 5, this);
doctorTable_->setHorizontalHeaderLabels({
tr("医生ID"),
tr("姓名"),
tr("职称"),
tr("所属科室"),
tr("出诊时间")
});
doctorTable_->setSelectionBehavior(QAbstractItemView::SelectRows);
doctorTable_->setSelectionMode(QAbstractItemView::SingleSelection);
doctorTable_->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
doctorTable_->setEditTriggers(QAbstractItemView::NoEditTriggers);
mainLayout->addWidget(doctorTable_);
// 按钮
auto* buttonLayout = new QHBoxLayout();
confirmButton_ = new QPushButton(tr("确认挂号"), this);
cancelButton_ = new QPushButton(tr("取消"), this);
buttonLayout->addStretch();
buttonLayout->addWidget(confirmButton_);
buttonLayout->addWidget(cancelButton_);
mainLayout->addLayout(buttonLayout);
// 信号与槽
connect(departmentCombo_, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &RegistrationDialog::onDepartmentChanged);
connect(doctorTable_, &QTableWidget::cellDoubleClicked,
this, &RegistrationDialog::onDoctorSelected);
connect(confirmButton_, &QPushButton::clicked,
this, &RegistrationDialog::confirmRegistration);
connect(cancelButton_, &QPushButton::clicked,
this, &QDialog::reject);
}
void RegistrationDialog::loadDepartments() {
if (!departmentCombo_) return;
// 临时断开信号以避免重复触发
disconnect(departmentCombo_, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &RegistrationDialog::onDepartmentChanged);
// 收集所有科室
std::set<std::string> deptSet;
core_.ctx_.doctors.for_each([&deptSet](const std::string&, const Doctor& d) {
if (!d.DepartmentID.empty()) {
deptSet.insert(d.DepartmentID);
}
});
for (const auto& deptId : deptSet) {
const auto* dept = core_.departmentService.findDepartment(deptId);
if (dept && !dept->Name.empty()) {
QString display = QString::fromStdString(dept->Name);
departmentCombo_->addItem(display, QString::fromStdString(deptId));
} else if (!deptId.empty()) {
// 如果找不到科室信息就直接显示ID
departmentCombo_->addItem(QString::fromStdString(deptId), QString::fromStdString(deptId));
}
}
// 重新连接信号
connect(departmentCombo_, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &RegistrationDialog::onDepartmentChanged);
}
void RegistrationDialog::loadDoctorsForDepartment(const QString& deptId) {
doctorTable_->setRowCount(0);
selectedDoctorId_.clear();
if (deptId.isEmpty()) {
return;
}
// 获取该科室的所有医生
std::vector<Doctor> doctors = core_.departmentService.getDoctorsByDepartment(deptId.toStdString());
for (size_t i = 0; i < doctors.size(); ++i) {
int row = static_cast<int>(i);
doctorTable_->insertRow(row);
// 医生ID
auto* idItem = new QTableWidgetItem(QString::fromStdString(doctors[i].DoctorID));
idItem->setFlags(idItem->flags() & ~Qt::ItemIsEditable);
doctorTable_->setItem(row, 0, idItem);
// 姓名
auto* nameItem = new QTableWidgetItem(QString::fromStdString(doctors[i].Name));
nameItem->setFlags(nameItem->flags() & ~Qt::ItemIsEditable);
doctorTable_->setItem(row, 1, nameItem);
// 职称
QString titleText;
switch (doctors[i].Title) {
case DoctorTitle::Chief: titleText = tr("主任医师"); break;
case DoctorTitle::AssociateChief: titleText = tr("副主任医师"); break;
case DoctorTitle::Attending: titleText = tr("主治医师"); break;
case DoctorTitle::Resident: titleText = tr("住院医师"); break;
}
auto* titleItem = new QTableWidgetItem(titleText);
titleItem->setFlags(titleItem->flags() & ~Qt::ItemIsEditable);
doctorTable_->setItem(row, 2, titleItem);
// 所属科室
QString deptName;
const auto* dept = core_.departmentService.findDepartment(doctors[i].DepartmentID);
if (dept) {
deptName = QString::fromStdString(dept->Name);
} else {
deptName = QString::fromStdString(doctors[i].DepartmentID);
}
auto* deptItem = new QTableWidgetItem(deptName);
deptItem->setFlags(deptItem->flags() & ~Qt::ItemIsEditable);
doctorTable_->setItem(row, 3, deptItem);
// 出诊时间
auto* scheduleItem = new QTableWidgetItem(QString::fromStdString(doctors[i].Schedule));
scheduleItem->setFlags(scheduleItem->flags() & ~Qt::ItemIsEditable);
doctorTable_->setItem(row, 4, scheduleItem);
}
}
void RegistrationDialog::onDepartmentChanged(int index) {
selectedDoctorId_.clear();
if (index <= 0) {
doctorTable_->setRowCount(0);
selectedDepartmentId_.clear();
return;
}
QString deptId = departmentCombo_->itemData(index).toString().trimmed();
selectedDepartmentId_ = deptId;
loadDoctorsForDepartment(deptId);
}
void RegistrationDialog::onDoctorSelected(int row, int column) {
(void)column; // 不使用 column 参数,避免编译器警告
if (row < 0) return;
auto* idItem = doctorTable_->item(row, 0);
if (idItem) {
selectedDoctorId_ = idItem->text();
// 高亮选中的行
doctorTable_->selectRow(row);
}
}
void RegistrationDialog::confirmRegistration() {
if (selectedDepartmentId_.isEmpty() || selectedDoctorId_.isEmpty()) {
QMessageBox::warning(this, tr("挂号失败"), tr("请选择科室和医生"));
return;
}
// 验证医生和患者存在
const auto* doctor = core_.doctorService.findDoctor(selectedDoctorId_.toStdString());
const auto* patient = core_.patientService.findPatient(patientId_.toStdString());
if (!doctor || !patient) {
QMessageBox::warning(this, tr("挂号失败"), tr("医生或患者不存在"));
return;
}
// 创建和添加挂号记录
time_t now = std::time(nullptr);
struct tm timeinfo;
localtime_r(&now, &timeinfo);
char dateStr[30];
strftime(dateStr, 30, "%Y-%m-%d %H:%M", &timeinfo);
std::string notes = "通过GUI挂号";
AppointmentRecord record(
selectedDoctorId_.toStdString(),
patientId_.toStdString(),
std::string(dateStr),
notes
);
if (core_.patientCaseService.addAppointmentRecord(patientId_.toStdString(), record)) {
QMessageBox::information(this, tr("挂号成功"),
tr("患者 %1 已成功挂号给医生 %2").arg(patientId_).arg(selectedDoctorId_));
accept();
} else {
QMessageBox::warning(this, tr("挂号失败"), tr("添加挂号记录失败"));
}
}
QString RegistrationDialog::getDepartmentId() const {
return selectedDepartmentId_;
}
QString RegistrationDialog::getDoctorId() const {
return selectedDoctorId_;
}
int RegistrationDialog::getRegistrationType() const {
return registrationType_;
}
void RegistrationDialog::onRegistrationTypeChanged(int index) {
registrationType_ = registrationTypeCombo_->itemData(index).toInt();
if (registrationType_ == 0) {
// 门诊
feeLabel_->setText(tr("挂号费: 10元"));
feeLabel_->setStyleSheet("color: blue; font-weight: bold;");
} else {
// 急诊
feeLabel_->setText(tr("挂号费: 50元 (急诊加收)"));
feeLabel_->setStyleSheet("color: red; font-weight: bold;");
}
}