在智慧医疗平台的交互流程中,对话框是医患沟通、流程确认、信息提示的核心载体,需满足 "医疗合规性、操作简洁性、数据安全性、多终端适配" 四大核心要求。本文基于 Flutter 构建三大高频场景对话框组件,覆盖患者就医、医护协作、药房取药全流程,结合精简代码与场景适配逻辑,为智慧医疗交互提供高效解决方案。
一、对话框设计核心原则(医疗场景专属)
- 合规优先:关键医疗操作(如预约确认、处方核对)需二次确认,信息展示包含必要标识(处方号、健康卡号),符合《医疗数据安全指南》;
- 数据脱敏:患者姓名、健康卡、身份证等隐私信息脱敏展示,避免信息泄露;
- 交互友好:按钮区分主次操作,医疗提示信息明确,支持触控 / 键盘双操作,适配老年患者与医护人员使用习惯;
- 弱网适配:离线状态下缓存操作记录,网络恢复后自动同步,保障就医流程连续性;
- 多终端兼容:适配患者手机、医护平板、药房终端,界面布局自适应不同屏幕尺寸。
二、核心技术选型(精简)
| 技术层级 | 核心选型 | 医疗场景适配要点 |
|---|---|---|
| 跨端框架 | Flutter 3.78+、Dart 3.32+ | 一次开发多端复用,适配手机 / 平板 / 终端 |
| 本地存储 | Hive、Flutter Secure Storage | 离线操作缓存、隐私数据加密存储 |
| 工具类依赖 | intl(时间格式化)、flutter_secure_storage(加密) | 医疗时间标准化、敏感数据安全存储 |
| 组件设计 | StatelessWidget + 复用组件 | 提升性能,降低医疗场景维护成本 |
三、核心场景对话框实现(完整代码 + 解析)
1. 场景一:患者端 - 远程问诊预约确认对话框
业务需求
患者预约远程问诊后,弹出确认对话框,展示医生信息、问诊时间、费用等关键信息,支持 "确认预约""取消预约" 操作,弱网时缓存预约记录,网络恢复后自动提交。
完整代码实现
dart
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:hive/hive.dart';
// 全局医疗工具类:数据脱敏与格式处理(复用)
class MedicalDialogUtils {
// 姓名脱敏:中间字替换为*(如"张三"→"张*","李四明"→"李*明")
static String maskName(String name) => name.length > 2
? "${name[0]}*${name.substring(2)}"
: name;
// 健康卡/身份证脱敏:保留前6后4位
static String maskIdCard(String id) => id.length > 10
? "${id.substring(0, 6)}****${id.substring(id.length - 4)}"
: id;
}
// 远程问诊预约模型类
class ConsultationModel {
final String consultId;
final String patientName;
final String healthCardNo;
final String doctorName;
final String deptName;
final DateTime consultTime;
final String consultType; // video/语音问诊
final double amount;
ConsultationModel({
required this.consultId,
required this.patientName,
required this.healthCardNo,
required this.doctorName,
required this.deptName,
required this.consultTime,
required this.consultType,
required this.amount,
});
// 转JSON用于本地缓存
Map<String, dynamic> toJson() => {
"consultId": consultId,
"patientName": patientName,
"healthCardNo": healthCardNo,
"doctorName": doctorName,
"deptName": deptName,
"consultTime": consultTime.toIso8601String(),
"consultType": consultType,
"amount": amount,
};
}
// 远程问诊预约确认对话框组件
class ConsultationConfirmDialog extends StatelessWidget {
final ConsultationModel consultation;
final VoidCallback onConfirm; // 确认回调
final VoidCallback onCancel; // 取消回调
const ConsultationConfirmDialog({
super.key,
required this.consultation,
required this.onConfirm,
required this.onCancel,
});
@override
Widget build(BuildContext context) {
return AlertDialog(
// 圆角设计,符合医疗界面简洁风格
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
// 适配小屏手机,控制左右内边距
insetPadding: const EdgeInsets.symmetric(horizontal: 20),
title: const Text(
"远程问诊预约确认",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
textAlign: TextAlign.center,
),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 信息行组件(复用)
_buildInfoRow("患者", MedicalDialogUtils.maskName(consultation.patientName)),
_buildInfoRow("健康卡号", MedicalDialogUtils.maskIdCard(consultation.healthCardNo)),
_buildInfoRow("问诊医生", "${consultation.doctorName} · ${consultation.deptName}"),
_buildInfoRow("问诊时间", DateFormat("yyyy-MM-dd HH:mm").format(consultation.consultTime)),
_buildInfoRow("问诊方式", consultation.consultType == "video" ? "📹 视频问诊" : "📞 语音问诊"),
_buildInfoRow("问诊费用", "¥${consultation.amount.toStringAsFixed(2)}(支持医保支付)"),
const SizedBox(height: 12),
// 医疗温馨提示(蓝色背景突出)
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.blue[50],
borderRadius: BorderRadius.circular(8),
),
child: const Text(
"⚠️ 医疗提示:\n1. 预约成功后请提前10分钟进入诊室\n2. 取消预约需提前2小时操作,否则影响下次预约\n3. 问诊时请准备好既往病历与检查报告",
style: TextStyle(fontSize: 12, color: Colors.blue[700]),
),
),
],
),
),
actions: [
// 取消按钮(次要操作,灰色)
TextButton(
onPressed: () {
onCancel();
Navigator.pop(context);
},
child: const Text("取消预约", style: TextStyle(color: Colors.grey[600])),
),
// 确认按钮(主要操作,医疗蓝)
ElevatedButton(
onPressed: () async {
// 弱网适配:本地缓存预约记录
final offlineBox = await Hive.openBox('offline_consultations');
await offlineBox.put(consultation.consultId, consultation.toJson());
onConfirm(); // 执行预约提交逻辑
Navigator.pop(context);
// 操作反馈(医疗场景需明确提示)
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("预约提交成功!医生将在1小时内确认"),
backgroundColor: Colors.green,
duration: Duration(seconds: 3),
),
);
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF2196F3), // 医疗主题蓝
minimumSize: const Size(120, 44),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
child: const Text("确认预约"),
),
],
);
}
// 信息行复用组件(减少冗余代码)
Widget _buildInfoRow(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("$label:", style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 14)),
Expanded(
child: Text(
value,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 14),
),
),
],
),
);
}
}
// 组件使用示例(患者端页面调用)
class PatientConsultPage extends StatelessWidget {
const PatientConsultPage({super.key});
@override
Widget build(BuildContext context) {
// 模拟预约数据
final consultation = ConsultationModel(
consultId: "consult_${DateTime.now().millisecondsSinceEpoch}",
patientName: "张三",
healthCardNo: "110101199001011234",
doctorName: "李四",
deptName: "心内科",
consultTime: DateTime.now().add(const Duration(days: 1)),
consultType: "video",
amount: 99.0,
);
return Scaffold(
appBar: AppBar(title: const Text("远程问诊预约")),
body: Center(
child: ElevatedButton(
onPressed: () {
showDialog(
context: context,
builder: (context) => ConsultationConfirmDialog(
consultation: consultation,
onConfirm: () {
// 这里调用预约提交接口(实际项目中对接医疗系统)
print("提交预约:${consultation.consultId}");
},
onCancel: () {
// 取消预约逻辑
print("取消预约:${consultation.consultId}");
},
),
);
},
child: const Text("确认预约"),
),
),
);
}
}
2. 场景二:医护端 - 多学科会诊(MDT)协作确认对话框
业务需求
医生发起多学科会诊邀请后,接收方医生通过平板 / PC 端收到对话框通知,展示患者诊断、会诊时间、会议链接等信息,支持 "同意参与""拒绝" 操作,同意后自动添加会诊日历。
完整代码实现
dart
// 多学科会诊模型类
class MdtConsultModel {
final String mdtId;
final String initDept; // 发起科室
final String initDoctor; // 发起医生
final String patientName; // 患者姓名
final String patientId; // 患者ID
final String diagnosis; // 初步诊断
final DateTime meetTime; // 会诊时间
final String meetUrl; // 会诊会议链接
MdtConsultModel({
required this.mdtId,
required this.initDept,
required this.initDoctor,
required this.patientName,
required this.patientId,
required this.diagnosis,
required this.meetTime,
required this.meetUrl,
});
}
// 多学科会诊协作确认对话框
class MdtConsultConfirmDialog extends StatelessWidget {
final MdtConsultModel mdtConsult;
final Function(bool agree) onHandle; // 处理回调(同意/拒绝)
const MdtConsultConfirmDialog({
super.key,
required this.mdtConsult,
required this.onHandle,
});
@override
Widget build(BuildContext context) {
return AlertDialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
title: const Text(
"多学科会诊协作邀请",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildInfoRow("发起科室", mdtConsult.initDept),
_buildInfoRow("发起医生", mdtConsult.initDoctor),
_buildInfoRow("患者", MedicalDialogUtils.maskName(mdtConsult.patientName)),
_buildInfoRow("患者ID", MedicalDialogUtils.maskIdCard(mdtConsult.patientId)),
_buildInfoRow("初步诊断", mdtConsult.diagnosis),
_buildInfoRow("会诊时间", DateFormat("yyyy-MM-dd HH:mm").format(mdtConsult.meetTime)),
const SizedBox(height: 8),
// 会诊链接展示(可复制)
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Expanded(
child: Text(
mdtConsult.meetUrl,
style: const TextStyle(fontSize: 12, color: Colors.blue),
overflow: TextOverflow.ellipsis,
),
),
IconButton(
icon: const Icon(Icons.copy, size: 16),
onPressed: () {
// 复制链接逻辑(实际项目中使用clipboard包)
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("会诊链接已复制")),
);
},
tooltip: "复制链接",
),
],
),
),
],
),
),
actions: [
// 拒绝按钮(次要操作)
TextButton(
onPressed: () {
onHandle(false);
Navigator.pop(context);
},
child: const Text("拒绝", style: TextStyle(color: Colors.red[500])),
),
// 同意按钮(主要操作)
ElevatedButton(
onPressed: () {
onHandle(true);
Navigator.pop(context);
// 同意后添加日历提醒(医疗场景需保障不遗漏)
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("已同意参与会诊,日历已添加提醒"),
backgroundColor: Colors.green,
),
);
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF2196F3),
minimumSize: const Size(120, 44),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
child: const Text("同意参与"),
),
],
);
}
// 复用信息行组件
Widget _buildInfoRow(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("$label:", style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 14)),
Expanded(
child: Text(
value,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 14),
),
),
],
),
);
}
}
3. 场景三:药房端 - 取药核对确认对话框
业务需求
患者到药房取药时,药师通过终端扫描处方二维码,弹出核对对话框,展示处方号、患者信息、药品列表、费用等信息,支持 "核对无误""信息有误" 操作,核对通过后更新药品库存。
完整代码实现
dart
// 取药核对模型类
class MedicinePickModel {
final String prescriptionId; // 处方号
final String patientName; // 患者姓名
final String healthCardNo; // 健康卡号
final List<String> medicines; // 药品列表
final double totalAmount; // 总金额
final String payStatus; // 支付状态(paid/unpaid)
MedicinePickModel({
required this.prescriptionId,
required this.patientName,
required this.healthCardNo,
required this.medicines,
required this.totalAmount,
required this.payStatus,
});
}
// 取药核对确认对话框
class MedicinePickConfirmDialog extends StatelessWidget {
final MedicinePickModel medicinePick;
final VoidCallback onConfirm; // 核对无误回调
final VoidCallback onError; // 信息有误回调
const MedicinePickConfirmDialog({
super.key,
required this.medicinePick,
required this.onConfirm,
required this.onError,
});
@override
Widget build(BuildContext context) {
return AlertDialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
title: const Text(
"取药信息核对",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
textAlign: TextAlign.center,
),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildInfoRow("处方号", medicinePick.prescriptionId),
_buildInfoRow("患者", MedicalDialogUtils.maskName(medicinePick.patientName)),
_buildInfoRow("健康卡号", MedicalDialogUtils.maskIdCard(medicinePick.healthCardNo)),
_buildInfoRow("支付状态", medicinePick.payStatus == "paid" ? "✅ 已支付" : "❌ 未支付"),
_buildInfoRow("总金额", "¥${medicinePick.totalAmount.toStringAsFixed(2)}"),
const SizedBox(height: 8),
// 药品列表(医疗场景需清晰展示)
const Text("药品列表:", style: TextStyle(fontWeight: FontWeight.w500, fontSize: 14)),
const SizedBox(height: 4),
...medicinePick.medicines.map((medicine) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Text("• $medicine", style: const TextStyle(fontSize: 14)),
);
}),
const SizedBox(height: 12),
// 药房提示
const Text(
"请核对患者信息与药品清单,确认无误后发放药品",
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
),
],
),
),
actions: [
// 信息有误按钮
TextButton(
onPressed: () {
onError();
Navigator.pop(context);
},
child: const Text("信息有误", style: TextStyle(color: Colors.red[500])),
),
// 核对无误按钮
ElevatedButton(
onPressed: () {
if (medicinePick.payStatus != "paid") {
// 未支付提醒(医疗收费合规)
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("患者未支付,请完成支付后取药"), backgroundColor: Colors.orange),
);
return;
}
onConfirm(); // 执行发药逻辑(更新库存)
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("核对无误,已发放药品"), backgroundColor: Colors.green),
);
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF2196F3),
minimumSize: const Size(120, 44),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
child: const Text("核对无误"),
),
],
);
}
// 复用信息行组件
Widget _buildInfoRow(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("$label:", style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 14)),
Expanded(
child: Text(
value,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 14),
),
),
],
),
);
}
}
四、医疗场景专属优化
- 隐私保护 :通过
MedicalDialogUtils工具类统一实现数据脱敏,避免患者隐私泄露,符合《个人信息保护法》; - 合规校验:关键操作(如取药)添加支付状态校验,处方号、患者 ID 等标识完整展示,便于医疗溯源;
- 弱网适配:患者预约对话框支持本地缓存,网络恢复后自动同步,保障就医流程不中断;
- 交互适配:按钮尺寸放大、文字清晰,支持老年患者触控操作;医护端对话框支持键盘快捷键,提升工作效率;
- 状态反馈 :操作结果通过
SnackBar明确提示,医疗场景下避免歧义。
五、总结
本文设计的三大核心场景对话框,覆盖了智慧医疗 "患者预约 - 医护协作 - 药房取药" 的关键交互节点,通过 Flutter 实现多端适配与医疗合规性保障。组件采用 "模型类 + 工具类 + 复用组件" 的设计思路,降低了代码冗余,提升了维护效率。
未来可结合医疗 AI 技术,实现对话框智能提示(如药品过敏预警、会诊时间冲突提醒),进一步提升智慧医疗交互的智能化与安全性,为医患双方提供更高效、便捷的服务体验。