Flutter for OpenHarmony 猫咪管家App实战 - 疫苗记录实现

养猫的朋友都知道,疫苗接种是猫咪健康管理中非常重要的一环。今天我们来实现一个疫苗记录功能,帮助铲屎官们追踪猫咪的疫苗接种情况。


功能规划

在开始写代码之前,先想清楚这个页面需要哪些功能:

  • 展示疫苗相关的科普知识
  • 显示已有的疫苗接种记录列表
  • 支持添加新的疫苗记录
  • 空状态时给出友好提示

这样规划下来,整个页面的结构就比较清晰了。


页面基础结构

首先搭建页面的基本框架:

dart 复制代码
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:intl/intl.dart';
import '../../providers/health_provider.dart';
import '../../models/health_record.dart';
import 'add_health_record_screen.dart';

这里引入了几个关键依赖,provider用于状态管理,screenutil处理屏幕适配,intl负责日期格式化。

把这些工具组合起来,能让开发效率提升不少。

接下来定义疫苗记录页面的主体类:

dart 复制代码
class VaccinationScreen extends StatelessWidget {
  final String catId;

  const VaccinationScreen({super.key, required this.catId});

这是一个无状态组件,通过catId来标识当前是哪只猫咪的疫苗记录。

使用StatelessWidget是因为数据变化由Provider来管理,页面本身不需要维护状态。


构建主界面

页面的build方法是整个UI的入口:

dart 复制代码
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('疫苗记录')),
      body: Consumer<HealthProvider>(
        builder: (context, provider, child) {
          final records = provider.getRecordsByType(catId, HealthRecordType.vaccination);

Scaffold提供了Material Design的基础页面结构,包含AppBar和body区域。

Consumer组件监听HealthProvider的变化,当数据更新时自动重建UI。

获取到记录后,需要判断是否为空:

dart 复制代码
          if (records.isEmpty) {
            return Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Icon(Icons.vaccines, size: 80.sp, color: Colors.grey[300]),
                  SizedBox(height: 16.h),
                  Text('暂无疫苗记录', style: TextStyle(color: Colors.grey[600])),

空状态的处理很重要,用一个大图标配合文字提示,让用户知道当前没有数据。

使用.sp和.h单位是screenutil的适配写法,能在不同屏幕上保持一致的视觉效果。

空状态下还要提供添加按钮:

dart 复制代码
                  SizedBox(height: 16.h),
                  ElevatedButton(
                    onPressed: () => _addRecord(context),
                    child: const Text('添加疫苗记录'),
                  ),
                ],
              ),
            );
          }

在空状态页面放置添加按钮,引导用户进行第一次记录。

这种设计比单纯显示"暂无数据"要友好得多。


有数据时的列表展示

当有疫苗记录时,展示列表内容:

dart 复制代码
          return ListView(
            padding: EdgeInsets.all(16.w),
            children: [
              _buildVaccineInfo(),
              SizedBox(height: 16.h),
              Text('疫苗记录', style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold)),
              SizedBox(height: 8.h),
              ...records.map((r) => _buildRecordCard(r)),
            ],
          );
        },
      ),

ListView作为滚动容器,内部先放疫苗知识卡片,再放记录列表。

使用展开运算符...将记录列表映射为卡片组件。

页面底部添加悬浮按钮:

dart 复制代码
      floatingActionButton: FloatingActionButton(
        onPressed: () => _addRecord(context),
        backgroundColor: Colors.orange,
        child: const Icon(Icons.add),
      ),
    );
  }

FloatingActionButton是Material Design的标准添加按钮样式。

橙色主题与整个App的风格保持一致。


疫苗知识卡片

这个卡片展示一些基础的疫苗科普:

dart 复制代码
  Widget _buildVaccineInfo() {
    return Card(
      color: Colors.blue[50],
      child: Padding(
        padding: EdgeInsets.all(16.w),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                Icon(Icons.info, color: Colors.blue, size: 20.sp),
                SizedBox(width: 8.w),
                Text('疫苗知识', style: TextStyle(fontWeight: FontWeight.bold, color: Colors.blue[800])),
              ],
            ),

卡片使用浅蓝色背景,与疫苗主题相呼应。

标题行用Row布局,图标和文字水平排列。

知识内容部分:

dart 复制代码
            SizedBox(height: 8.h),
            Text(
              '• 猫三联:预防猫瘟、猫鼻支、猫杯状病毒\n'
              '• 狂犬疫苗:预防狂犬病\n'
              '• 首次免疫:8周龄开始,间隔3-4周接种\n'
              '• 加强免疫:每年一次',
              style: TextStyle(fontSize: 13.sp, color: Colors.blue[700]),
            ),
          ],
        ),
      ),
    );
  }

用项目符号列出关键信息,简洁明了。

这些都是养猫必备的基础知识,放在这里方便用户随时查看。


记录卡片组件

每条疫苗记录用卡片形式展示:

dart 复制代码
  Widget _buildRecordCard(HealthRecord record) {
    return Card(
      margin: EdgeInsets.only(bottom: 8.h),
      child: ListTile(
        leading: CircleAvatar(
          backgroundColor: Colors.blue[100],
          child: Icon(Icons.vaccines, color: Colors.blue, size: 20.sp),
        ),
        title: Text(record.title),

Card配合ListTile是展示列表项的经典组合。

leading位置放圆形头像,用疫苗图标作为视觉标识。

副标题显示日期和医院信息:

dart 复制代码
        subtitle: Text(
          '${DateFormat('yyyy-MM-dd').format(record.date)}${record.hospital != null ? ' · ${record.hospital}' : ''}',
        ),

日期格式化为年月日形式,如果有医院信息就一并显示。

用三元表达式处理可选字段,代码更简洁。

右侧显示下次接种时间:

dart 复制代码
        trailing: record.nextDate != null
            ? Column(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.end,
                children: [
                  Text('下次', style: TextStyle(fontSize: 10.sp, color: Colors.grey)),
                  Text(DateFormat('MM-dd').format(record.nextDate!), style: TextStyle(color: Colors.orange)),
                ],
              )
            : null,
      ),
    );
  }

下次接种日期用橙色高亮显示,提醒用户注意。

如果没有设置下次日期,trailing就为null,不显示任何内容。


添加记录的导航

点击添加按钮时跳转到添加页面:

dart 复制代码
  void _addRecord(BuildContext context) {
    Navigator.push(context, MaterialPageRoute(
      builder: (_) => AddHealthRecordScreen(catId: catId, initialType: HealthRecordType.vaccination),
    ));
  }
}

使用Navigator.push进行页面跳转,传入catId和初始类型。

initialType设为vaccination,添加页面会默认选中疫苗类型。


数据模型设计

疫苗记录复用了健康记录的数据模型:

dart 复制代码
enum HealthRecordType {
  vaccination,
  deworming,
  checkup,
  treatment,
  other,
}

枚举类型定义了几种健康记录类型,vaccination就是疫苗。

这样设计可以让不同类型的健康记录共用一套逻辑。

记录模型的核心字段:

dart 复制代码
class HealthRecord {
  final String id;
  final String catId;
  final HealthRecordType type;
  final String title;
  final DateTime date;
  final DateTime? nextDate;
  final String? hospital;
  final String? notes;

包含了记录的基本信息,nextDate和hospital是可选字段。

使用final修饰保证数据不可变,符合Flutter的最佳实践。


Provider状态管理

HealthProvider负责管理所有健康记录:

dart 复制代码
class HealthProvider extends ChangeNotifier {
  final List<HealthRecord> _records = [];

  List<HealthRecord> getRecordsByType(String catId, HealthRecordType type) {
    return _records
        .where((r) => r.catId == catId && r.type == type)
        .toList()
      ..sort((a, b) => b.date.compareTo(a.date));
  }

getRecordsByType方法按猫咪ID和类型筛选记录。

返回前按日期降序排列,最新的记录显示在最前面。

添加记录的方法:

dart 复制代码
  void addRecord(HealthRecord record) {
    _records.add(record);
    notifyListeners();
  }

添加记录后调用notifyListeners通知所有监听者。

Consumer组件会自动重建UI,展示新添加的记录。


日期处理技巧

日期格式化是这个页面的常见操作:

dart 复制代码
DateFormat('yyyy-MM-dd').format(record.date)

intl包提供的DateFormat类支持各种日期格式。

yyyy-MM-dd是最常用的年月日格式。

下次接种日期用简短格式:

dart 复制代码
DateFormat('MM-dd').format(record.nextDate!)

只显示月日,因为年份通常是当年或明年,不需要特别标注。

感叹号表示这里已经确认nextDate不为null。


屏幕适配方案

screenutil的使用贯穿整个页面:

dart 复制代码
Icon(Icons.vaccines, size: 80.sp, color: Colors.grey[300])
SizedBox(height: 16.h)
EdgeInsets.all(16.w)

.sp用于字体和图标大小,会根据屏幕宽度等比缩放。

.h和.w分别用于高度和宽度,确保在不同设备上布局一致。


颜色主题设计

页面使用了蓝色系作为疫苗相关的主题色:

dart 复制代码
Colors.blue[50]   // 卡片背景
Colors.blue[100]  // 头像背景
Colors.blue[700]  // 文字颜色
Colors.blue[800]  // 标题颜色

不同深浅的蓝色形成层次感。

与橙色的强调色形成对比,突出重要信息。


空状态设计思路

空状态不只是显示"暂无数据":

dart 复制代码
Center(
  child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: [
      Icon(Icons.vaccines, size: 80.sp, color: Colors.grey[300]),
      SizedBox(height: 16.h),
      Text('暂无疫苗记录', style: TextStyle(color: Colors.grey[600])),
      SizedBox(height: 16.h),
      ElevatedButton(
        onPressed: () => _addRecord(context),
        child: const Text('添加疫苗记录'),
      ),
    ],
  ),
)

大图标让页面不会显得太空。

添加按钮引导用户进行下一步操作。


ListTile的灵活运用

ListTile是Flutter中非常实用的组件:

dart 复制代码
ListTile(
  leading: CircleAvatar(...),
  title: Text(record.title),
  subtitle: Text(...),
  trailing: Column(...),
)

leading、title、subtitle、trailing四个位置可以放置不同内容。

自动处理间距和对齐,省去很多布局代码。


条件渲染技巧

根据数据是否存在决定显示内容:

dart 复制代码
record.hospital != null ? ' · ${record.hospital}' : ''

三元表达式处理可选字段的显示。

如果hospital为null,就显示空字符串。

trailing的条件渲染:

dart 复制代码
trailing: record.nextDate != null
    ? Column(...)
    : null,

当nextDate为null时,trailing也为null,不占用空间。

这种写法比用Visibility组件更简洁。


小结

疫苗记录页面虽然功能不复杂,但涉及到的知识点不少:

  • Consumer监听Provider数据变化
  • 空状态和有数据状态的切换
  • Card和ListTile的组合使用
  • 日期格式化和条件渲染

把这些基础打牢,后面做更复杂的功能就会轻松很多。


欢迎加入OpenHarmony跨平台开发社区,一起交流Flutter开发经验:

https://openharmonycrossplatform.csdn.net

相关推荐
哈哈不让取名字2 小时前
C++代码冗余消除
开发语言·c++·算法
一起养小猫2 小时前
Flutter for OpenHarmony 实战:CustomPainter游戏画面渲染详解
flutter·游戏
ghie90902 小时前
基于C#实现俄罗斯方块游戏
开发语言·游戏·c#
燕山石头2 小时前
java模拟Modbus-tcp从站
java·开发语言·tcp/ip
lixzest2 小时前
C++工程师的成长
开发语言·c++·程序人生·职场和发展
总有刁民想爱朕ha2 小时前
Python YOLOv8 进阶教程
开发语言·python·yolo
2301_765703142 小时前
C++中的策略模式应用
开发语言·c++·算法
Xxtaoaooo2 小时前
React Native跨平台鸿蒙开发实战:JS 与 ArkTS Native的通信机制详解
javascript·react native·harmonyos
普通网友2 小时前
云计算数据加密选型:合规要求(GDPR / 等保)下的方法选择
开发语言·云计算·perl