1. 项目背景与需求分析
最近在帮某殡葬服务机构开发一套骨灰撒散预约管理系统,这个需求源于传统殡葬服务数字化转型的实际需求。随着环保理念的普及,越来越多家庭选择生态葬方式,其中骨灰撒散因其简洁、环保的特点受到关注。但现有的预约流程存在几个痛点:
- 预约全靠人工登记,效率低下且易出错
- 家属无法实时查询可预约时段
- 撒散地点、时间等信息管理混乱
- 缺乏电子化凭证和记录存档
这套系统正是为了解决这些问题而设计的。采用Qt C++框架开发桌面应用,主要考虑以下因素:
- 殡葬机构工作人员电脑配置普遍不高,需要轻量级应用
- 需要处理大量本地数据(如逝者信息、预约记录)
- 操作界面需要简洁直观,降低使用门槛
- 对跨平台有一定需求(部分分支机构使用不同操作系统)
2. 系统架构设计
2.1 技术选型
核心框架选择Qt 5.15 LTS版本,主要组件包括:
- Qt Widgets用于UI构建
- SQLite作为本地数据库
- QChart用于数据可视化报表
- QPrinter用于凭证打印
不选用Web方案的原因:
- 殡葬机构网络条件不稳定
- 涉及隐私数据,本地存储更安全
- 工作人员更习惯桌面软件操作模式
2.2 数据库设计
设计了一个包含7张主表的数据库结构:
sql复制-- 逝者信息表
CREATE TABLE deceased (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
gender TEXT,
birth_date TEXT,
death_date TEXT,
cremation_cert_no TEXT UNIQUE
);
-- 家属信息表
CREATE TABLE family (
id INTEGER PRIMARY KEY,
deceased_id INTEGER,
name TEXT NOT NULL,
relation TEXT,
contact TEXT,
FOREIGN KEY(deceased_id) REFERENCES deceased(id)
);
-- 撒散地点表
CREATE TABLE location (
id INTEGER PRIMARY KEY,
name TEXT UNIQUE,
description TEXT,
max_daily_capacity INTEGER
);
-- 预约记录表
CREATE TABLE appointment (
id INTEGER PRIMARY KEY,
deceased_id INTEGER,
location_id INTEGER,
appointment_date TEXT,
time_slot TEXT,
status INTEGER DEFAULT 0,
FOREIGN KEY(deceased_id) REFERENCES deceased(id),
FOREIGN KEY(location_id) REFERENCES location(id)
);
2.3 核心模块划分
系统分为四大功能模块:
- 基础信息管理(逝者/家属信息录入与维护)
- 预约管理(时段查询、预约、变更、取消)
- 数据统计(撒散量统计、时段分析等)
- 系统管理(用户权限、数据备份)
3. 关键功能实现细节
3.1 预约时段管理
这是系统的核心功能,实现逻辑如下:
cpp复制// 检查某地点某日期的可预约时段
QVector<QString> AppointmentSystem::getAvailableSlots(int locationId, const QDate &date) {
QVector<QString> allSlots = {"08:00", "09:30", "11:00", "13:30", "15:00"};
QVector<QString> availableSlots;
QSqlQuery query;
query.prepare("SELECT time_slot FROM appointment "
"WHERE location_id = ? AND appointment_date = ? AND status = 1");
query.addBindValue(locationId);
query.addBindValue(date.toString("yyyy-MM-dd"));
if (!query.exec()) {
qWarning() << "Query failed:" << query.lastError();
return allSlots; // 出错时返回全部时段
}
QSet<QString> bookedSlots;
while (query.next()) {
bookedSlots.insert(query.value(0).toString());
}
for (const auto &slot : allSlots) {
if (!bookedSlots.contains(slot)) {
availableSlots.append(slot);
}
}
return availableSlots;
}
3.2 数据验证与错误处理
在处理逝者信息时,需要特别注意数据验证:
cpp复制bool DataValidator::validateDeceasedInfo(const DeceasedInfo &info) {
// 姓名不能为空且不超过20字
if (info.name.isEmpty() || info.name.length() > 20) {
return false;
}
// 火化证明号必须符合格式
QRegularExpression certRegex("^[A-Z]{2}\\d{8}$");
if (!certRegex.match(info.cremationCertNo).hasMatch()) {
return false;
}
// 死亡日期不能早于出生日期
if (info.birthDate.isValid() && info.deathDate.isValid()
&& info.deathDate < info.birthDate) {
return false;
}
return true;
}
3.3 凭证生成与打印
使用QPrinter实现预约凭证打印功能:
cpp复制void PrintService::printAppointmentConfirmation(const Appointment &appt) {
QPrinter printer(QPrinter::HighResolution);
printer.setPageSize(QPageSize::A5);
QPrintDialog printDialog(&printer);
if (printDialog.exec() != QDialog::Accepted) {
return;
}
QPainter painter;
if (!painter.begin(&printer)) {
qWarning() << "Failed to initialize printer";
return;
}
// 绘制凭证内容
painter.setFont(QFont("Microsoft YaHei", 12));
painter.drawText(100, 100, "骨灰撒散预约凭证");
painter.drawText(100, 150, QString("逝者姓名: %1").arg(appt.deceasedName()));
// ... 其他内容绘制
painter.end();
}
4. 界面设计要点
4.1 主界面布局
采用经典的左侧导航+右侧内容区布局:
- 左侧导航栏使用QListWidget
- 右侧使用QStackedWidget管理不同功能页
- 顶部状态栏显示当前用户和机构信息
cpp复制void MainWindow::setupUi() {
// 主分割布局
QSplitter *splitter = new QSplitter(this);
// 左侧导航
QListWidget *navList = new QListWidget;
navList->addItems({"预约管理", "逝者信息", "数据统计", "系统设置"});
// 右侧内容区
QStackedWidget *stack = new QStackedWidget;
stack->addWidget(new AppointmentPage);
stack->addWidget(new DeceasedInfoPage);
// ... 其他页面
// 信号槽连接
connect(navList, &QListWidget::currentRowChanged,
stack, &QStackedWidget::setCurrentIndex);
splitter->addWidget(navList);
splitter->addWidget(stack);
setCentralWidget(splitter);
}
4.2 预约日历组件
自定义了一个带容量提示的日历控件:
cpp复制class CapacityCalendar : public QCalendarWidget {
public:
explicit CapacityCalendar(QWidget *parent = nullptr);
void setLocationData(int locationId);
protected:
void paintCell(QPainter *painter, const QRect &rect, const QDate &date) const override;
private:
QMap<QDate, int> m_capacityData;
};
void CapacityCalendar::paintCell(QPainter *painter, const QRect &rect, const QDate &date) const {
QCalendarWidget::paintCell(painter, rect, date);
if (m_capacityData.contains(date)) {
int remaining = m_capacityData[date];
QString text = QString::number(remaining);
painter->setPen(remaining > 0 ? Qt::darkGreen : Qt::red);
painter->drawText(rect.adjusted(0, 0, -5, -5),
Qt::AlignBottom | Qt::AlignRight, text);
}
}
5. 开发中的挑战与解决方案
5.1 日期时间处理
遇到的主要问题:
- 需要处理各种日期格式的输入输出
- 节假日和特殊日期需要特殊标记
- 时段冲突检测逻辑复杂
解决方案:
- 统一使用ISO格式(yyyy-MM-dd)内部存储
- 创建HolidayManager类管理特殊日期
- 使用时序数据库优化查询性能
cpp复制class HolidayManager {
public:
static HolidayManager& instance();
bool isHoliday(const QDate &date) const;
void addHoliday(const QDate &date, const QString &name);
private:
HolidayManager();
QMap<QDate, QString> m_holidays;
};
bool HolidayManager::isHoliday(const QDate &date) const {
// 检查周末
if (date.dayOfWeek() == 6 || date.dayOfWeek() == 7) {
return true;
}
// 检查自定义节假日
return m_holidays.contains(date);
}
5.2 数据备份与恢复
考虑到数据敏感性,实现了多重备份机制:
- 每日自动本地备份(加密ZIP)
- 手动导出备份文件
- 备份文件完整性校验
cpp复制void BackupService::createBackup(const QString &path) {
// 创建临时目录
QTemporaryDir tempDir;
// 复制数据库文件
QFile dbFile(m_dbPath);
if (!dbFile.copy(tempDir.filePath("data.db"))) {
throw BackupException("Failed to copy database");
}
// 创建加密压缩包
QuaZip zip(path);
if (!zip.open(QuaZip::mdCreate)) {
throw BackupException("Failed to create zip file");
}
QuaZipFile zipFile(&zip);
if (!zipFile.open(QIODevice::WriteOnly, QuaZipNewInfo("data.db"))) {
zip.close();
throw BackupException("Failed to add file to zip");
}
// 写入数据...
}
6. 实际部署经验
6.1 性能优化技巧
-
数据库查询优化:
- 为常用查询字段添加索引
- 使用预编译语句
- 批量操作使用事务
-
界面响应优化:
- 耗时操作放在后台线程
- 使用模型/视图架构处理大数据集
- 实现数据懒加载
cpp复制// 使用QSqlQueryModel实现懒加载
class LazyDeceasedModel : public QSqlQueryModel {
public:
explicit LazyDeceasedModel(QObject *parent = nullptr);
QVariant data(const QModelIndex &index, int role) const override {
if (!index.isValid()) {
return QVariant();
}
// 只在需要时加载详细信息
if (role == Qt::DisplayRole && index.column() == 0) {
loadDetails(index.row());
}
return QSqlQueryModel::data(index, role);
}
private:
void loadDetails(int row) const;
};
6.2 用户权限管理
实现基于角色的访问控制(RBAC):
- 角色:管理员、操作员、查询员
- 权限细粒度控制到每个功能按钮
cpp复制class UserPermission {
public:
enum Permission {
CanEditDeceased = 0x01,
CanCreateAppointment = 0x02,
CanCancelAppointment = 0x04,
CanViewReports = 0x08,
CanManageSystem = 0x10
};
explicit UserPermission(int role) {
switch (role) {
case 1: // 管理员
m_permissions = 0xFF;
break;
case 2: // 操作员
m_permissions = CanEditDeceased | CanCreateAppointment | CanCancelAppointment;
break;
case 3: // 查询员
m_permissions = CanViewReports;
break;
default:
m_permissions = 0;
}
}
bool hasPermission(Permission p) const {
return m_permissions & p;
}
private:
quint8 m_permissions;
};
7. 项目总结与改进方向
经过三个月的开发和测试,系统已在5家殡葬服务机构投入使用,显著提高了工作效率。主要收获:
-
Qt在业务系统开发中依然具有强大优势:
- 成熟的UI框架
- 完善的数据库支持
- 良好的跨平台能力
-
需要特别注意的领域特性:
- 数据敏感性强,需要严格的安全措施
- 用户计算机水平有限,界面必须极其简单
- 业务规则复杂,需要充分理解需求
未来改进方向:
- 增加移动端查询功能(家属自助查询)
- 集成电子签名认证
- 开发数据可视化分析模块
- 实现多机构数据共享机制
开发这类特殊行业软件,最重要的是充分理解行业特性和用户习惯。我们花了大量时间与殡葬服务人员沟通,观察他们的工作流程,这比技术实现本身更重要。