flutter_for_openharmony口腔护理app实战+预约管理实现

引言

定期的口腔检查和治疗是维护口腔健康的重要环节。一个好的口腔护理应用不仅要帮助用户记录日常护理情况,还应该提供便捷的预约管理功能,让用户能够轻松安排和追踪自己的就诊计划。

本文将详细讲解如何在 Flutter 中实现一个完整的预约管理页面,包括预约列表展示、状态分类、新建预约对话框等功能。

功能概述

预约管理页面需要实现以下核心功能:

  • 分类展示:将预约按"待就诊"和"已完成"两种状态分类显示
  • 预约卡片:展示预约的详细信息,包括就诊目的、时间、医院、医生等
  • 新建预约:通过浮动按钮触发新建预约对话框
  • 日期时间选择:支持选择预约的具体日期和时间

页面基础结构

预约管理页面使用 StatelessWidget 实现,因为页面本身不需要维护内部状态,所有数据都来自 AppProvider

dart 复制代码
class AppointmentPage extends StatelessWidget {
  const AppointmentPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('预约管理')),
      floatingActionButton: FloatingActionButton(
        onPressed: () => _showAddDialog(context),
        backgroundColor: const Color(0xFF26A69A),
        child: const Icon(Icons.add),
      ),

页面使用 Scaffold 作为基础框架,配置了标题栏和浮动操作按钮。浮动按钮使用应用的主题色,点击时触发新建预约对话框。

数据获取与分类

使用 Consumer 监听 AppProvider 的数据变化,并将预约按状态分类:

dart 复制代码
      body: Consumer<AppProvider>(
        builder: (context, provider, _) {
          final pending = provider.appointments
              .where((a) => a.status == 'pending').toList();
          final completed = provider.appointments
              .where((a) => a.status == 'completed').toList();

通过 where 方法过滤出待就诊和已完成的预约列表。这种函数式的写法简洁明了,易于理解和维护。

页面主体布局采用 SingleChildScrollView 包裹 Column,确保内容可以滚动:

dart 复制代码
          return SingleChildScrollView(
            padding: const EdgeInsets.all(16),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const Text('待就诊', 
                    style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                const SizedBox(height: 12),
                if (pending.isEmpty)
                  _buildEmptyCard('暂无待就诊预约')
                else
                  ...pending.map((a) => _buildAppointmentCard(context, a, true)),
                const SizedBox(height: 24),
                const Text('已完成', 
                    style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                const SizedBox(height: 12),
                if (completed.isEmpty)
                  _buildEmptyCard('暂无已完成预约')
                else
                  ...completed.map((a) => _buildAppointmentCard(context, a, false)),
              ],
            ),
          );
        },
      ),
    );
  }

使用展开运算符 ... 将预约列表映射为卡片组件列表,这是 Dart 中处理列表嵌入的优雅方式。

空状态卡片

当某个分类下没有预约时,显示一个友好的空状态提示:

dart 复制代码
Widget _buildEmptyCard(String text) {
  return Container(
    padding: const EdgeInsets.all(20),
    decoration: BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.circular(12),
    ),
    child: Center(
      child: Text(text, style: const TextStyle(color: Colors.grey)),
    ),
  );
}

空状态卡片使用灰色文字,视觉上比较柔和,不会给用户造成压迫感。圆角白色背景与其他卡片保持一致的风格。

预约卡片组件

预约卡片是页面的核心组件,需要展示丰富的信息:

dart 复制代码
Widget _buildAppointmentCard(BuildContext context, 
    Appointment appointment, bool isPending) {
  return Container(
    margin: const EdgeInsets.only(bottom: 12),
    padding: const EdgeInsets.all(16),
    decoration: BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.circular(12),
      border: isPending ? Border.all(color: const Color(0xFF26A69A)) : null,
    ),

待就诊的预约卡片添加主题色边框,与已完成的预约形成视觉区分。这种设计让用户一眼就能识别出需要关注的预约。

卡片头部展示预约的主要信息:

dart 复制代码
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Row(
          children: [
            Container(
              padding: const EdgeInsets.all(10),
              decoration: BoxDecoration(
                color: isPending 
                    ? const Color(0xFF26A69A).withOpacity(0.1)
                    : Colors.grey.shade100,
                shape: BoxShape.circle,
              ),
              child: Icon(
                Icons.calendar_month,
                color: isPending ? const Color(0xFF26A69A) : Colors.grey,
              ),
            ),
            const SizedBox(width: 12),

图标容器根据预约状态使用不同的背景色和图标色,待就诊使用主题色,已完成使用灰色。

预约标题和时间的展示:

dart 复制代码
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(appointment.purpose, 
                       style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
                  Text(
                    DateFormat('yyyy-MM-dd HH:mm').format(appointment.dateTime),
                    style: TextStyle(color: Colors.grey.shade600),
                  ),
                ],
              ),
            ),

使用 intl 包的 DateFormat 来格式化日期时间,这是 Flutter 中处理日期格式化的标准方式。

待就诊状态标签:

dart 复制代码
            if (isPending)
              Container(
                padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
                decoration: BoxDecoration(
                  color: Colors.orange.shade100,
                  borderRadius: BorderRadius.circular(4),
                ),
                child: Text('待就诊', 
                    style: TextStyle(color: Colors.orange.shade700, fontSize: 12)),
              ),
          ],
        ),

状态标签使用橙色系配色,与主题色形成对比,突出提醒用户这是待处理的预约。

卡片底部展示医院和医生信息:

dart 复制代码
        const SizedBox(height: 12),
        const Divider(height: 1),
        const SizedBox(height: 12),
        Row(
          children: [
            const Icon(Icons.local_hospital, size: 16, color: Colors.grey),
            const SizedBox(width: 4),
            Text(appointment.hospital, 
                 style: TextStyle(color: Colors.grey.shade600)),
            const SizedBox(width: 16),
            const Icon(Icons.person, size: 16, color: Colors.grey),
            const SizedBox(width: 4),
            Text(appointment.doctorName, 
                 style: TextStyle(color: Colors.grey.shade600)),
          ],
        ),
      ],
    ),
  );
}

使用分割线将主要信息和次要信息分开,图标配合文字让信息更加直观易读。

新建预约对话框

点击浮动按钮时弹出新建预约对话框,这是一个相对复杂的交互组件:

dart 复制代码
void _showAddDialog(BuildContext context) {
  final purposeController = TextEditingController();
  final doctorController = TextEditingController();
  final hospitalController = TextEditingController(text: '市口腔医院');
  DateTime selectedDate = DateTime.now().add(const Duration(days: 1));

初始化文本控制器和默认日期。医院字段预填了默认值,日期默认为明天,这些都是为了减少用户的输入负担。

对话框使用 StatefulBuilder 来管理内部状态:

dart 复制代码
  showDialog(
    context: context,
    builder: (ctx) => StatefulBuilder(
      builder: (context, setState) => AlertDialog(
        title: const Text('新建预约'),
        content: SingleChildScrollView(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              TextField(
                controller: purposeController,
                decoration: const InputDecoration(labelText: '就诊目的'),
              ),
              const SizedBox(height: 12),
              TextField(
                controller: hospitalController,
                decoration: const InputDecoration(labelText: '医院'),
              ),
              const SizedBox(height: 12),
              TextField(
                controller: doctorController,
                decoration: const InputDecoration(labelText: '医生'),
              ),

StatefulBuilder 允许我们在 StatelessWidget 中创建有状态的对话框,这是一个非常实用的技巧。

日期时间选择器的实现:

dart 复制代码
              const SizedBox(height: 12),
              ListTile(
                contentPadding: EdgeInsets.zero,
                title: const Text('预约时间'),
                subtitle: Text(DateFormat('yyyy-MM-dd HH:mm').format(selectedDate)),
                trailing: const Icon(Icons.calendar_today),
                onTap: () async {
                  final date = await showDatePicker(
                    context: context,
                    initialDate: selectedDate,
                    firstDate: DateTime.now(),
                    lastDate: DateTime.now().add(const Duration(days: 365)),
                  );

使用 ListTile 作为日期选择的触发器,点击后先弹出日期选择器。firstDate 设为当前日期,防止用户选择过去的日期。

时间选择的处理:

dart 复制代码
                  if (date != null) {
                    final time = await showTimePicker(
                      context: context,
                      initialTime: TimeOfDay.fromDateTime(selectedDate),
                    );
                    if (time != null) {
                      setState(() {
                        selectedDate = DateTime(
                          date.year, date.month, date.day, 
                          time.hour, time.minute
                        );
                      });
                    }
                  }
                },
              ),
            ],
          ),
        ),

日期选择完成后继续弹出时间选择器,两者结合生成完整的预约时间。使用 setState 更新界面显示。

对话框的操作按钮:

dart 复制代码
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(ctx), 
            child: const Text('取消')
          ),
          ElevatedButton(
            onPressed: () {
              if (purposeController.text.isEmpty || 
                  doctorController.text.isEmpty) {
                return;
              }
              final appointment = Appointment(
                dateTime: selectedDate,
                doctorName: doctorController.text,
                hospital: hospitalController.text,
                purpose: purposeController.text,
              );
              context.read<AppProvider>().addAppointment(appointment);
              Navigator.pop(ctx);
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(content: Text('预约已创建'))
              );
            },
            child: const Text('保存'),
          ),
        ],
      ),
    ),
  );
}

保存前进行简单的表单验证,确保必填字段不为空。创建成功后关闭对话框并显示提示消息。

数据模型定义

预约功能依赖的数据模型在 oral_models.dart 中定义:

dart 复制代码
class Appointment {
  final String id;
  final DateTime dateTime;
  final String doctorName;
  final String hospital;
  final String purpose;
  final String status;

  Appointment({
    String? id,
    required this.dateTime,
    required this.doctorName,
    required this.hospital,
    required this.purpose,
    this.status = 'pending',
  }) : id = id ?? DateTime.now().millisecondsSinceEpoch.toString();
}

模型使用工厂构造函数自动生成唯一 ID,状态默认为待就诊。这种设计简化了创建预约时的代码。

Provider 中的数据操作

AppProvider 中实现预约的增删改查:

dart 复制代码
List<Appointment> _appointments = [];
List<Appointment> get appointments => _appointments;

void addAppointment(Appointment appointment) {
  _appointments.add(appointment);
  notifyListeners();
}

void updateAppointmentStatus(String id, String status) {
  final index = _appointments.indexWhere((a) => a.id == id);
  if (index != -1) {
    final old = _appointments[index];
    _appointments[index] = Appointment(
      id: old.id,
      dateTime: old.dateTime,
      doctorName: old.doctorName,
      hospital: old.hospital,
      purpose: old.purpose,
      status: status,
    );
    notifyListeners();
  }
}

每次数据变化后调用 notifyListeners() 通知界面更新。由于 Dart 中的类是不可变的,更新状态时需要创建新的实例。

日期格式化处理

项目中使用 intl 包进行日期格式化,需要在 pubspec.yaml 中添加依赖:

yaml 复制代码
dependencies:
  intl: ^0.18.0

然后在代码中导入使用:

dart 复制代码
import 'package:intl/intl.dart';

// 格式化日期时间
DateFormat('yyyy-MM-dd HH:mm').format(dateTime)

// 只格式化日期
DateFormat('yyyy-MM-dd').format(dateTime)

// 中文格式
DateFormat('MM月dd日 HH:mm').format(dateTime)

DateFormat 支持多种格式化模式,可以根据需求灵活选择。

用户体验优化

为了提升用户体验,我们在设计中考虑了以下几点:

视觉层次:通过颜色、边框、阴影等元素区分不同状态的预约,让用户快速识别重要信息。

默认值设置:医院字段预填默认值,日期默认为明天,减少用户输入。

即时反馈:创建预约后显示 SnackBar 提示,让用户知道操作已成功。

表单验证:必填字段为空时不允许提交,避免创建无效数据。

扩展功能建议

基于当前实现,可以考虑添加以下功能:

  • 预约提醒:在预约时间前发送通知提醒用户
  • 预约修改:支持修改已创建的预约信息
  • 预约取消:支持取消待就诊的预约
  • 历史记录:查看更早之前的就诊记录
  • 医生评价:就诊完成后对医生进行评价

状态管理思考

在这个页面中,我们使用了 Provider 进行状态管理。对于预约这种需要持久化的数据,实际项目中还需要考虑:

dart 复制代码
// 数据持久化示例
void saveAppointments() async {
  final prefs = await SharedPreferences.getInstance();
  final jsonList = _appointments.map((a) => a.toJson()).toList();
  await prefs.setString('appointments', jsonEncode(jsonList));
}

void loadAppointments() async {
  final prefs = await SharedPreferences.getInstance();
  final jsonStr = prefs.getString('appointments');
  if (jsonStr != null) {
    final jsonList = jsonDecode(jsonStr) as List;
    _appointments = jsonList.map((j) => Appointment.fromJson(j)).toList();
    notifyListeners();
  }
}

使用 SharedPreferences 或数据库来持久化预约数据,确保应用重启后数据不丢失。

总结

本文详细介绍了口腔护理 App 中预约管理功能的实现。通过合理的 UI 设计和状态管理,我们实现了一个功能完善、体验友好的预约管理页面。核心技术点包括:

  • 使用 Consumer 监听数据变化
  • 通过 StatefulBuilder 在对话框中管理状态
  • 结合日期和时间选择器实现完整的时间选择
  • 使用 intl 包格式化日期显示

这些技术和设计思路可以应用到其他类似的列表管理功能中。

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

相关推荐
军军君012 小时前
Three.js基础功能学习十三:太阳系实例上
前端·javascript·vue.js·学习·3d·前端框架·three
xiaoqi9223 小时前
React Native鸿蒙跨平台如何实现分类页面组件通过searchQuery状态变量管理搜索输入,实现了分类的实时过滤功能
javascript·react native·react.js·ecmascript·harmonyos
牛马1113 小时前
Flutter OverlayEntry
flutter
qq_177767374 小时前
React Native鸿蒙跨平台实现应用介绍页,实现了应用信息卡片展示、特色功能网格布局、权限/联系信息陈列、评分展示、模态框详情交互等通用场景
javascript·react native·react.js·ecmascript·交互·harmonyos
2603_949462104 小时前
Flutter for OpenHarmony社团管理App实战:预算管理实现
android·javascript·flutter
wuhen_n4 小时前
JavaScript内存管理与执行上下文
前端·javascript
Hi_kenyon4 小时前
理解vue中的ref
前端·javascript·vue.js
jin1233225 小时前
基于React Native鸿蒙跨平台地址管理是许多电商、外卖、物流等应用的重要功能模块,实现了地址的添加、编辑、删除和设置默认等功能
javascript·react native·react.js·ecmascript·harmonyos
2601_949975795 小时前
Flutter for OpenHarmony艺考真题题库+帮助中心实现
flutter