Flutter宠物驱虫记录器应用开发教程

项目简介

宠物驱虫记录器是一款专为宠物主人设计的健康管理应用,帮助用户系统化管理宠物的驱虫记录、制定驱虫计划、设置提醒通知,并提供健康监测功能。应用集成了宠物信息管理、驱虫记录追踪、智能提醒系统、健康状态监测等功能,为宠物主人提供全方位的驱虫管理解决方案。
运行效果图
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

核心功能特性

  • 宠物档案管理:完整的宠物信息录入,包含基本信息、健康状况、成长记录
  • 驱虫记录追踪:详细记录每次驱虫情况,包含药物信息、剂量、效果评价
  • 智能驱虫计划:制定个性化驱虫计划,支持定期、季节性、按需等多种模式
  • 提醒通知系统:智能提醒驱虫时间,支持多种提醒方式和自定义提醒周期
  • 健康状态监测:记录宠物日常健康状况,及时发现异常情况
  • 数据统计分析:全面的驱虫数据统计,包含费用分析、效果评估、趋势分析

技术架构

开发环境

  • 框架:Flutter 3.x
  • 开发语言:Dart
  • UI组件:Material Design 3
  • 状态管理:StatefulWidget + setState
  • 动画效果:AnimationController + Tween
  • 数据存储:本地存储 + SharedPreferences

项目结构

lib/
├── main.dart                 # 应用入口和主要逻辑
├── models/                   # 数据模型
│   ├── pet.dart              # 宠物模型
│   ├── deworming_record.dart # 驱虫记录模型
│   ├── deworming_plan.dart   # 驱虫计划模型
│   ├── reminder.dart         # 提醒模型
│   ├── health_monitoring.dart # 健康监测模型
│   └── stats.dart            # 统计数据模型
├── pages/                    # 页面组件
│   ├── records_page.dart     # 驱虫记录页面
│   ├── pets_page.dart        # 宠物管理页面
│   ├── plans_page.dart       # 驱虫计划页面
│   ├── reminders_page.dart   # 提醒页面
│   └── stats_page.dart       # 统计分析页面
└── widgets/                  # 自定义组件
    ├── pet_card.dart         # 宠物卡片组件
    ├── record_card.dart      # 记录卡片组件
    └── stats_chart.dart      # 统计图表组件

数据模型设计

宠物信息模型(Pet)

宠物模型是应用的核心数据结构,记录每只宠物的详细信息:

class Pet {
  final String id;              // 宠物唯一标识
  final String name;            // 宠物名称
  final String species;         // 物种:猫、狗、兔子等
  final String breed;           // 品种
  final String gender;          // 性别:公、母
  final DateTime birthDate;     // 出生日期
  final double weight;          // 体重(kg)
  final String color;           // 毛色
  final String avatarUrl;       // 头像图片路径
  final String microchipId;     // 芯片号
  final String notes;           // 备注信息
  final DateTime createdDate;   // 创建日期
  final bool isActive;          // 是否活跃状态
}

驱虫记录模型(DewormingRecord)

驱虫记录模型详细记录每次驱虫的完整信息:

class DewormingRecord {
  final String id;              // 记录唯一标识
  final String petId;           // 关联宠物ID
  final String petName;         // 宠物名称
  final DateTime treatmentDate; // 驱虫日期
  final String medicineType;    // 药物类型:内驱、外驱、内外同驱
  final String medicineName;    // 药物名称
  final String medicineBrand;   // 药物品牌
  final double dosage;          // 用药剂量
  final String dosageUnit;      // 剂量单位:ml、片、滴等
  final String administrationMethod; // 给药方式:口服、滴剂、喷剂等
  final double petWeight;       // 驱虫时宠物体重
  final String veterinarian;    // 兽医师姓名
  final String clinic;          // 诊所名称
  final double cost;            // 费用
  final String notes;           // 备注信息
  final List<String> symptoms;  // 驱虫前症状
  final List<String> sideEffects; // 副作用记录
  final String effectiveness;   // 效果评价:优秀、良好、一般、差
  final List<String> photos;    // 相关照片
  final DateTime nextDueDate;   // 下次驱虫日期
}

驱虫计划模型(DewormingPlan)

驱虫计划模型用于制定和管理长期驱虫策略:

class DewormingPlan {
  final String id;              // 计划唯一标识
  final String petId;           // 关联宠物ID
  final String petName;         // 宠物名称
  final String planType;        // 计划类型:定期、季节性、按需
  final String medicineType;    // 药物类型
  final int intervalDays;       // 间隔天数
  final DateTime startDate;     // 开始日期
  final DateTime? endDate;      // 结束日期(可选)
  final bool isActive;          // 是否激活
  final String reminderTime;    // 提醒时间
  final List<String> reminderDays; // 提醒天数(提前几天)
  final String notes;           // 备注信息
  final DateTime createdDate;   // 创建日期
}

提醒模型(DewormingReminder)

提醒模型管理所有驱虫相关的提醒通知:

class DewormingReminder {
  final String id;              // 提醒唯一标识
  final String petId;           // 关联宠物ID
  final String petName;         // 宠物名称
  final DateTime reminderDate;  // 提醒日期
  final String reminderType;    // 提醒类型:驱虫到期、计划提醒
  final String medicineType;    // 药物类型
  final String title;           // 提醒标题
  final String message;         // 提醒内容
  final bool isCompleted;       // 是否已完成
  final DateTime createdDate;   // 创建日期
}

健康监测模型(HealthMonitoring)

健康监测模型记录宠物的日常健康状况:

class HealthMonitoring {
  final String id;              // 记录唯一标识
  final String petId;           // 关联宠物ID
  final String petName;         // 宠物名称
  final DateTime checkDate;     // 检查日期
  final double weight;          // 体重
  final String appetite;        // 食欲状况:正常、减退、亢进
  final String energy;          // 精神状态:活跃、正常、萎靡
  final String stoolCondition;  // 大便状况:正常、稀软、腹泻、便秘
  final bool hasVomiting;       // 是否呕吐
  final bool hasItching;        // 是否瘙痒
  final String skinCondition;   // 皮肤状况:正常、红疹、脱毛、干燥
  final List<String> symptoms;  // 其他症状
  final String notes;           // 备注信息
  final List<String> photos;    // 相关照片
}

统计数据模型(DewormingStats)

统计模型提供全面的数据分析功能:

class DewormingStats {
  final int totalPets;                    // 宠物总数
  final int totalRecords;                 // 驱虫记录总数
  final int activePlans;                  // 活跃计划数
  final int pendingReminders;             // 待处理提醒数
  final double totalCost;                 // 总费用
  final Map<String, int> medicineTypeDistribution; // 药物类型分布
  final Map<String, int> monthlyTreatments; // 月度治疗次数
  final Map<String, double> costByMonth;  // 月度费用统计
  final String mostUsedMedicine;          // 最常用药物
  final String mostFrequentClinic;        // 最常去诊所
}

应用主界面设计

主页面结构

应用采用底部导航栏设计,包含五个主要功能模块:

class PetDewormingHomePage extends StatefulWidget {
  const PetDewormingHomePage({super.key});

  
  State<PetDewormingHomePage> createState() => _PetDewormingHomePageState();
}

class _PetDewormingHomePageState extends State<PetDewormingHomePage>
    with TickerProviderStateMixin {
  int _selectedIndex = 0;
  
  // 数据存储
  List<Pet> _pets = [];
  List<DewormingRecord> _dewormingRecords = [];
  List<DewormingPlan> _dewormingPlans = [];
  List<DewormingReminder> _reminders = [];
  List<HealthMonitoring> _healthRecords = [];
  DewormingStats? _stats;
  
  // 筛选和搜索
  String _searchQuery = '';
  String? _selectedPet;
  String? _selectedMedicineType;
  DateTime? _selectedDateRange;
  
  // 动画控制器
  late AnimationController _fadeAnimationController;
  late Animation<double> _fadeAnimation;
  late AnimationController _slideAnimationController;
  late Animation<Offset> _slideAnimation;
}

底部导航栏设计

底部导航栏提供五个主要功能入口:

bottomNavigationBar: NavigationBar(
  selectedIndex: _selectedIndex,
  onDestinationSelected: (index) {
    setState(() => _selectedIndex = index);
  },
  destinations: const [
    NavigationDestination(
      icon: Icon(Icons.medical_services),
      label: '驱虫记录',
    ),
    NavigationDestination(
      icon: Icon(Icons.pets),
      label: '我的宠物',
    ),
    NavigationDestination(
      icon: Icon(Icons.schedule),
      label: '驱虫计划',
    ),
    NavigationDestination(
      icon: Icon(Icons.notifications),
      label: '提醒',
    ),
    NavigationDestination(
      icon: Icon(Icons.analytics),
      label: '统计',
    ),
  ],
)

动画效果实现

应用使用多种动画效果提升用户体验:

void _setupAnimations() {
  _fadeAnimationController = AnimationController(
    duration: const Duration(milliseconds: 800),
    vsync: this,
  );

  _fadeAnimation = Tween<double>(
    begin: 0.0,
    end: 1.0,
  ).animate(CurvedAnimation(
    parent: _fadeAnimationController,
    curve: Curves.easeInOut,
  ));

  _slideAnimationController = AnimationController(
    duration: const Duration(milliseconds: 1000),
    vsync: this,
  );

  _slideAnimation = Tween<Offset>(
    begin: const Offset(0, 0.3),
    end: Offset.zero,
  ).animate(CurvedAnimation(
    parent: _slideAnimationController,
    curve: Curves.elasticOut,
  ));

  _fadeAnimationController.forward();
  _slideAnimationController.forward();
}

驱虫记录功能实现

驱虫记录页面

驱虫记录页面是应用的核心功能,展示所有驱虫记录:

Widget _buildRecordsPage() {
  final filteredRecords = _getFilteredRecords();

  return Column(
    children: [
      // 快速统计卡片
      if (_stats != null) _buildQuickStatsCard(),
      
      // 筛选标签显示
      if (_searchQuery.isNotEmpty ||
          _selectedPet != null ||
          _selectedMedicineType != null)
        Container(
          padding: const EdgeInsets.all(16),
          child: Wrap(
            spacing: 8,
            runSpacing: 8,
            children: [
              if (_searchQuery.isNotEmpty)
                Chip(
                  label: Text('搜索: $_searchQuery'),
                  onDeleted: () {
                    setState(() => _searchQuery = '');
                  },
                ),
              if (_selectedPet != null)
                Chip(
                  label: Text('宠物: $_selectedPet'),
                  onDeleted: () {
                    setState(() => _selectedPet = null);
                  },
                ),
              if (_selectedMedicineType != null)
                Chip(
                  label: Text('类型: $_selectedMedicineType'),
                  onDeleted: () {
                    setState(() => _selectedMedicineType = null);
                  },
                ),
            ],
          ),
        ),
      
      // 驱虫记录列表
      Expanded(
        child: filteredRecords.isEmpty
            ? _buildEmptyState()
            : ListView.builder(
                padding: const EdgeInsets.all(16),
                itemCount: filteredRecords.length,
                itemBuilder: (context, index) {
                  final record = filteredRecords[index];
                  return _buildRecordCard(record);
                },
              ),
      ),
    ],
  );
}

快速统计卡片

快速统计卡片显示关键指标概览:

Widget _buildQuickStatsCard() {
  final stats = _stats!;

  return Container(
    margin: const EdgeInsets.all(16),
    child: Card(
      elevation: 4,
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                Icon(
                  Icons.dashboard,
                  color: Colors.teal.shade600,
                  size: 24,
                ),
                const SizedBox(width: 8),
                Text(
                  '驱虫概览',
                  style: TextStyle(
                    fontSize: 18,
                    fontWeight: FontWeight.bold,
                    color: Colors.teal.shade700,
                  ),
                ),
              ],
            ),
            const SizedBox(height: 16),
            Row(
              children: [
                Expanded(
                  child: _buildStatItem(
                    Icons.pets, '宠物数量', '${stats.totalPets}只', Colors.teal,
                  ),
                ),
                Expanded(
                  child: _buildStatItem(
                    Icons.medical_services, '驱虫记录', '${stats.totalRecords}次', Colors.blue,
                  ),
                ),
                Expanded(
                  child: _buildStatItem(
                    Icons.schedule, '活跃计划', '${stats.activePlans}个', Colors.green,
                  ),
                ),
                Expanded(
                  child: _buildStatItem(
                    Icons.notifications, '待处理', '${stats.pendingReminders}个', Colors.orange,
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    ),
  );
}

驱虫记录卡片设计

每条驱虫记录以卡片形式展示详细信息:

Widget _buildRecordCard(DewormingRecord record) {
  final daysSinceLastTreatment = DateTime.now().difference(record.treatmentDate).inDays;
  final daysUntilNext = record.nextDueDate.difference(DateTime.now()).inDays;
  final isOverdue = daysUntilNext < 0;

  return Card(
    elevation: 2,
    margin: const EdgeInsets.only(bottom: 12),
    child: InkWell(
      onTap: () => _showRecordDetail(record),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 标题行
            Row(
              children: [
                Container(
                  padding: const EdgeInsets.all(8),
                  decoration: BoxDecoration(
                    color: _getMedicineTypeColor(record.medicineType).withValues(alpha: 0.1),
                    borderRadius: BorderRadius.circular(8),
                  ),
                  child: Icon(
                    _getMedicineTypeIcon(record.medicineType),
                    color: _getMedicineTypeColor(record.medicineType),
                    size: 24,
                  ),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        record.petName,
                        style: const TextStyle(
                          fontSize: 16,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      Text(
                        '${record.medicineType}${record.medicineName}',
                        style: TextStyle(
                          color: Colors.grey.shade600,
                          fontSize: 14,
                        ),
                      ),
                    ],
                  ),
                ),
                Column(
                  crossAxisAlignment: CrossAxisAlignment.end,
                  children: [
                    Container(
                      padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
                      decoration: BoxDecoration(
                        color: _getMedicineTypeColor(record.medicineType),
                        borderRadius: BorderRadius.circular(12),
                      ),
                      child: Text(
                        record.medicineType,
                        style: const TextStyle(
                          color: Colors.white,
                          fontSize: 12,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                    ),
                    const SizedBox(height: 4),
                    Text(
                      _formatDate(record.treatmentDate),
                      style: TextStyle(
                        color: Colors.grey.shade500,
                        fontSize: 10,
                      ),
                    ),
                  ],
                ),
              ],
            ),

            const SizedBox(height: 12),

            // 药物信息
            Container(
              padding: const EdgeInsets.all(12),
              decoration: BoxDecoration(
                color: Colors.teal.withValues(alpha: 0.1),
                borderRadius: BorderRadius.circular(8),
                border: Border.all(color: Colors.teal.withValues(alpha: 0.3)),
              ),
              child: Row(
                children: [
                  Icon(
                    Icons.medication,
                    color: Colors.teal.shade600,
                    size: 20,
                  ),
                  const SizedBox(width: 8),
                  Expanded(
                    child: Text(
                      '${record.medicineBrand} ${record.dosage}${record.dosageUnit}',
                      style: TextStyle(
                        fontSize: 14,
                        fontWeight: FontWeight.w500,
                        color: Colors.teal.shade700,
                      ),
                    ),
                  ),
                  Text(
                    ${record.cost.toStringAsFixed(0)}',
                    style: TextStyle(
                      fontSize: 14,
                      fontWeight: FontWeight.bold,
                      color: Colors.teal.shade600,
                    ),
                  ),
                ],
              ),
            ),

            const SizedBox(height: 12),

            // 下次驱虫信息
            Row(
              children: [
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        '下次驱虫',
                        style: TextStyle(
                          fontSize: 12,
                          color: Colors.grey.shade600,
                        ),
                      ),
                      const SizedBox(height: 4),
                      Row(
                        children: [
                          Icon(
                            isOverdue ? Icons.warning : Icons.schedule,
                            size: 16,
                            color: isOverdue ? Colors.red : Colors.green,
                          ),
                          const SizedBox(width: 4),
                          Text(
                            isOverdue 
                                ? '已逾期 ${(-daysUntilNext)}天'
                                : '还有 ${daysUntilNext}天',
                            style: TextStyle(
                              fontSize: 12,
                              color: isOverdue ? Colors.red : Colors.green,
                              fontWeight: FontWeight.w500,
                            ),
                          ),
                        ],
                      ),
                    ],
                  ),
                ),
                Container(
                  padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
                  decoration: BoxDecoration(
                    color: _getEffectivenessColor(record.effectiveness).withValues(alpha: 0.1),
                    borderRadius: BorderRadius.circular(12),
                    border: Border.all(
                      color: _getEffectivenessColor(record.effectiveness).withValues(alpha: 0.3),
                    ),
                  ),
                  child: Text(
                    '效果: ${record.effectiveness}',
                    style: TextStyle(
                      fontSize: 10,
                      color: _getEffectivenessColor(record.effectiveness),
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
              ],
            ),

            // 诊所信息
            if (record.clinic.isNotEmpty) ...[
              const SizedBox(height: 8),
              Row(
                children: [
                  Icon(
                    Icons.local_hospital,
                    size: 14,
                    color: Colors.grey.shade600,
                  ),
                  const SizedBox(width: 4),
                  Text(
                    '${record.clinic}${record.veterinarian}',
                    style: TextStyle(
                      color: Colors.grey.shade600,
                      fontSize: 11,
                    ),
                  ),
                ],
              ),
            ],
          ],
        ),
      ),
    ),
  );
}

宠物管理功能

宠物列表页面

宠物页面展示所有宠物的基本信息和驱虫状态:

Widget _buildPetsPage() {
  return ListView.builder(
    padding: const EdgeInsets.all(16),
    itemCount: _pets.length,
    itemBuilder: (context, index) {
      final pet = _pets[index];
      return _buildPetCard(pet);
    },
  );
}

宠物卡片设计

每只宠物以卡片形式展示详细信息:

Widget _buildPetCard(Pet pet) {
  final age = DateTime.now().difference(pet.birthDate).inDays ~/ 365;
  final petRecords = _dewormingRecords.where((r) => r.petId == pet.id).toList();
  final lastRecord = petRecords.isNotEmpty 
      ? petRecords.reduce((a, b) => a.treatmentDate.isAfter(b.treatmentDate) ? a : b)
      : null;

  return Card(
    elevation: 2,
    margin: const EdgeInsets.only(bottom: 12),
    child: InkWell(
      onTap: () => _showPetDetail(pet),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 宠物基本信息
            Row(
              children: [
                Container(
                  width: 60,
                  height: 60,
                  decoration: BoxDecoration(
                    color: _getSpeciesColor(pet.species).withValues(alpha: 0.1),
                    borderRadius: BorderRadius.circular(30),
                  ),
                  child: Icon(
                    _getSpeciesIcon(pet.species),
                    color: _getSpeciesColor(pet.species),
                    size: 30,
                  ),
                ),
                const SizedBox(width: 16),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        pet.name,
                        style: const TextStyle(
                          fontSize: 18,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      Text(
                        '${pet.species}${pet.breed}',
                        style: TextStyle(
                          color: Colors.grey.shade600,
                          fontSize: 14,
                        ),
                      ),
                      Text(
                        '${age}岁 • ${pet.gender}${pet.weight}kg',
                        style: TextStyle(
                          color: Colors.grey.shade500,
                          fontSize: 12,
                        ),
                      ),
                    ],
                  ),
                ),
                Container(
                  padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
                  decoration: BoxDecoration(
                    color: pet.isActive ? Colors.green : Colors.grey,
                    borderRadius: BorderRadius.circular(12),
                  ),
                  child: Text(
                    pet.isActive ? '活跃' : '非活跃',
                    style: const TextStyle(
                      color: Colors.white,
                      fontSize: 10,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
              ],
            ),

            const SizedBox(height: 16),

            // 驱虫状态
            Container(
              padding: const EdgeInsets.all(12),
              decoration: BoxDecoration(
                color: Colors.grey.shade50,
                borderRadius: BorderRadius.circular(8),
              ),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    '驱虫状态',
                    style: TextStyle(
                      fontSize: 12,
                      color: Colors.grey.shade600,
                      fontWeight: FontWeight.w500,
                    ),
                  ),
                  const SizedBox(height: 8),
                  Row(
                    children: [
                      Expanded(
                        child: _buildStatItem(
                          Icons.medical_services,
                          '总次数',
                          '${petRecords.length}次',
                          Colors.teal,
                        ),
                      ),
                      Expanded(
                        child: _buildStatItem(
                          Icons.attach_money,
                          '总费用',
                          ${petRecords.fold(0.0, (sum, r) => sum + r.cost).toStringAsFixed(0)}',
                          Colors.green,
                        ),
                      ),
                      Expanded(
                        child: _buildStatItem(
                          Icons.schedule,
                          '上次驱虫',
                          lastRecord != null 
                              ? '${DateTime.now().difference(lastRecord.treatmentDate).inDays}天前'
                              : '无记录',
                          Colors.blue,
                        ),
                      ),
                    ],
                  ),
                ],
              ),
            ),

            // 备注信息
            if (pet.notes.isNotEmpty) ...[
              const SizedBox(height: 12),
              Container(
                padding: const EdgeInsets.all(8),
                decoration: BoxDecoration(
                  color: Colors.blue.withValues(alpha: 0.1),
                  borderRadius: BorderRadius.circular(6),
                ),
                child: Row(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Icon(
                      Icons.note,
                      size: 16,
                      color: Colors.blue.shade600,
                    ),
                    const SizedBox(width: 8),
                    Expanded(
                      child: Text(
                        pet.notes,
                        style: TextStyle(
                          color: Colors.blue.shade700,
                          fontSize: 12,
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ],
        ),
      ),
    ),
  );
}

添加宠物对话框

添加新宠物的对话框界面:

void _showAddPetDialog() {
  final nameController = TextEditingController();
  final breedController = TextEditingController();
  final weightController = TextEditingController();
  final colorController = TextEditingController();
  final notesController = TextEditingController();
  String selectedSpecies = '猫';
  String selectedGender = '公';
  DateTime selectedBirthDate = DateTime.now().subtract(const Duration(days: 365));

  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('添加新宠物'),
      content: SingleChildScrollView(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            TextField(
              controller: nameController,
              decoration: const InputDecoration(
                labelText: '宠物名称',
                hintText: '请输入宠物名称',
              ),
            ),
            const SizedBox(height: 16),
            Row(
              children: [
                Expanded(
                  child: DropdownButtonFormField<String>(
                    value: selectedSpecies,
                    decoration: const InputDecoration(labelText: '物种'),
                    items: ['猫', '狗', '兔子', '仓鼠', '鸟类'].map((species) {
                      return DropdownMenuItem(
                        value: species,
                        child: Text(species),
                      );
                    }).toList(),
                    onChanged: (value) {
                      selectedSpecies = value!;
                    },
                  ),
                ),
                const SizedBox(width: 16),
                Expanded(
                  child: DropdownButtonFormField<String>(
                    value: selectedGender,
                    decoration: const InputDecoration(labelText: '性别'),
                    items: ['公', '母'].map((gender) {
                      return DropdownMenuItem(
                        value: gender,
                        child: Text(gender),
                      );
                    }).toList(),
                    onChanged: (value) {
                      selectedGender = value!;
                    },
                  ),
                ),
              ],
            ),
            const SizedBox(height: 16),
            TextField(
              controller: breedController,
              decoration: const InputDecoration(
                labelText: '品种',
                hintText: '请输入品种',
              ),
            ),
            const SizedBox(height: 16),
            Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: weightController,
                    keyboardType: TextInputType.number,
                    decoration: const InputDecoration(
                      labelText: '体重',
                      hintText: '0.0',
                      suffixText: 'kg',
                    ),
                  ),
                ),
                const SizedBox(width: 16),
                Expanded(
                  child: TextField(
                    controller: colorController,
                    decoration: const InputDecoration(
                      labelText: '毛色',
                      hintText: '请输入毛色',
                    ),
                  ),
                ),
              ],
            ),
            const SizedBox(height: 16),
            TextField(
              controller: notesController,
              maxLines: 3,
              decoration: const InputDecoration(
                labelText: '备注',
                hintText: '请输入备注信息',
              ),
            ),
          ],
        ),
      ),
      actions: [
        TextButton(
          onPressed: () => Navigator.of(context).pop(),
          child: const Text('取消'),
        ),
        ElevatedButton(
          onPressed: () {
            if (nameController.text.isNotEmpty) {
              final newPet = Pet(
                id: 'pet${_pets.length + 1}',
                name: nameController.text,
                species: selectedSpecies,
                breed: breedController.text.isNotEmpty ? breedController.text : '未知品种',
                gender: selectedGender,
                birthDate: selectedBirthDate,
                weight: double.tryParse(weightController.text) ?? 0.0,
                color: colorController.text.isNotEmpty ? colorController.text : '未知',
                avatarUrl: '',
                microchipId: '',
                notes: notesController.text,
                createdDate: DateTime.now(),
                isActive: true,
              );
              
              setState(() {
                _pets.add(newPet);
                _calculateStats();
              });
              
              Navigator.of(context).pop();
              
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(content: Text('宠物添加成功!')),
              );
            }
          },
          child: const Text('添加'),
        ),
      ],
    ),
  );
}

驱虫计划功能

驱虫计划页面

驱虫计划页面展示所有制定的驱虫计划:

Widget _buildPlansPage() {
  return ListView.builder(
    padding: const EdgeInsets.all(16),
    itemCount: _dewormingPlans.length,
    itemBuilder: (context, index) {
      final plan = _dewormingPlans[index];
      return _buildPlanCard(plan);
    },
  );
}

驱虫计划卡片设计

每个驱虫计划以卡片形式展示:

Widget _buildPlanCard(DewormingPlan plan) {
  final nextDueDate = _calculateNextDueDate(plan);
  final daysUntilNext = nextDueDate?.difference(DateTime.now()).inDays ?? 0;

  return Card(
    elevation: 2,
    margin: const EdgeInsets.only(bottom: 12),
    child: InkWell(
      onTap: () => _showPlanDetail(plan),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 标题行
            Row(
              children: [
                Container(
                  padding: const EdgeInsets.all(8),
                  decoration: BoxDecoration(
                    color: plan.isActive 
                        ? Colors.green.withValues(alpha: 0.1)
                        : Colors.grey.withValues(alpha: 0.1),
                    borderRadius: BorderRadius.circular(8),
                  ),
                  child: Icon(
                    plan.isActive ? Icons.schedule : Icons.pause_circle,
                    color: plan.isActive ? Colors.green : Colors.grey,
                    size: 24,
                  ),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        plan.petName,
                        style: const TextStyle(
                          fontSize: 16,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      Text(
                        '${plan.planType}${plan.medicineType}',
                        style: TextStyle(
                          color: Colors.grey.shade600,
                          fontSize: 14,
                        ),
                      ),
                    ],
                  ),
                ),
                Column(
                  crossAxisAlignment: CrossAxisAlignment.end,
                  children: [
                    Container(
                      padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
                      decoration: BoxDecoration(
                        color: plan.isActive ? Colors.green : Colors.grey,
                        borderRadius: BorderRadius.circular(12),
                      ),
                      child: Text(
                        plan.isActive ? '活跃' : '暂停',
                        style: const TextStyle(
                          color: Colors.white,
                          fontSize: 12,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                    ),
                    const SizedBox(height: 4),
                    Text(
                      '每${plan.intervalDays}天',
                      style: TextStyle(
                        color: Colors.grey.shade500,
                        fontSize: 10,
                      ),
                    ),
                  ],
                ),
              ],
            ),

            const SizedBox(height: 16),

            // 计划详情
            Container(
              padding: const EdgeInsets.all(12),
              decoration: BoxDecoration(
                color: Colors.grey.shade50,
                borderRadius: BorderRadius.circular(8),
              ),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Row(
                    children: [
                      Expanded(
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Text(
                              '开始日期',
                              style: TextStyle(
                                fontSize: 10,
                                color: Colors.grey.shade600,
                              ),
                            ),
                            Text(
                              _formatDate(plan.startDate),
                              style: const TextStyle(
                                fontSize: 12,
                                fontWeight: FontWeight.w500,
                              ),
                            ),
                          ],
                        ),
                      ),
                      if (plan.endDate != null)
                        Expanded(
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              Text(
                                '结束日期',
                                style: TextStyle(
                                  fontSize: 10,
                                  color: Colors.grey.shade600,
                                ),
                              ),
                              Text(
                                _formatDate(plan.endDate!),
                                style: const TextStyle(
                                  fontSize: 12,
                                  fontWeight: FontWeight.w500,
                                ),
                              ),
                            ],
                          ),
                        ),
                      Expanded(
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Text(
                              '提醒时间',
                              style: TextStyle(
                                fontSize: 10,
                                color: Colors.grey.shade600,
                              ),
                            ),
                            Text(
                              plan.reminderTime,
                              style: const TextStyle(
                                fontSize: 12,
                                fontWeight: FontWeight.w500,
                              ),
                            ),
                          ],
                        ),
                      ),
                    ],
                  ),
                  if (nextDueDate != null) ...[
                    const SizedBox(height: 12),
                    Container(
                      padding: const EdgeInsets.all(8),
                      decoration: BoxDecoration(
                        color: daysUntilNext <= 7 
                            ? Colors.orange.withValues(alpha: 0.1)
                            : Colors.blue.withValues(alpha: 0.1),
                        borderRadius: BorderRadius.circular(6),
                        border: Border.all(
                          color: daysUntilNext <= 7 
                              ? Colors.orange.withValues(alpha: 0.3)
                              : Colors.blue.withValues(alpha: 0.3),
                        ),
                      ),
                      child: Row(
                        children: [
                          Icon(
                            daysUntilNext <= 7 ? Icons.warning : Icons.schedule,
                            size: 16,
                            color: daysUntilNext <= 7 ? Colors.orange : Colors.blue,
                          ),
                          const SizedBox(width: 8),
                          Text(
                            '下次驱虫: ${_formatDate(nextDueDate)} (${daysUntilNext}天后)',
                            style: TextStyle(
                              fontSize: 11,
                              color: daysUntilNext <= 7 ? Colors.orange.shade700 : Colors.blue.shade700,
                              fontWeight: FontWeight.w500,
                            ),
                          ),
                        ],
                      ),
                    ),
                  ],
                ],
              ),
            ),

            // 备注
            if (plan.notes.isNotEmpty) ...[
              const SizedBox(height: 8),
              Text(
                plan.notes,
                style: TextStyle(
                  color: Colors.grey.shade600,
                  fontSize: 12,
                ),
              ),
            ],
          ],
        ),
      ),
    ),
  );
}

提醒通知功能

提醒页面

提醒页面展示所有待处理的驱虫提醒:

Widget _buildRemindersPage() {
  final sortedReminders = _reminders
      .where((reminder) => !reminder.isCompleted)
      .toList()
    ..sort((a, b) => a.reminderDate.compareTo(b.reminderDate));

  return ListView.builder(
    padding: const EdgeInsets.all(16),
    itemCount: sortedReminders.length,
    itemBuilder: (context, index) {
      final reminder = sortedReminders[index];
      return _buildReminderCard(reminder);
    },
  );
}

提醒卡片设计

每个提醒以卡片形式展示,根据紧急程度使用不同颜色:

Widget _buildReminderCard(DewormingReminder reminder) {
  final daysUntilReminder = reminder.reminderDate.difference(DateTime.now()).inDays;
  final isOverdue = daysUntilReminder < 0;
  final isToday = daysUntilReminder == 0;

  return Card(
    elevation: 2,
    margin: const EdgeInsets.only(bottom: 12),
    child: InkWell(
      onTap: () => _showReminderDetail(reminder),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 标题行
            Row(
              children: [
                Container(
                  padding: const EdgeInsets.all(8),
                  decoration: BoxDecoration(
                    color: isOverdue 
                        ? Colors.red.withValues(alpha: 0.1)
                        : isToday 
                            ? Colors.orange.withValues(alpha: 0.1)
                            : Colors.blue.withValues(alpha: 0.1),
                    borderRadius: BorderRadius.circular(8),
                  ),
                  child: Icon(
                    isOverdue 
                        ? Icons.warning
                        : isToday 
                            ? Icons.today
                            : Icons.schedule,
                    color: isOverdue 
                        ? Colors.red
                        : isToday 
                            ? Colors.orange
                            : Colors.blue,
                    size: 24,
                  ),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        reminder.title,
                        style: const TextStyle(
                          fontSize: 16,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      Text(
                        '${reminder.petName}${reminder.medicineType}',
                        style: TextStyle(
                          color: Colors.grey.shade600,
                          fontSize: 14,
                        ),
                      ),
                    ],
                  ),
                ),
                Column(
                  crossAxisAlignment: CrossAxisAlignment.end,
                  children: [
                    Container(
                      padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
                      decoration: BoxDecoration(
                        color: isOverdue 
                            ? Colors.red
                            : isToday 
                                ? Colors.orange
                                : Colors.blue,
                        borderRadius: BorderRadius.circular(12),
                      ),
                      child: Text(
                        isOverdue 
                            ? '已逾期'
                            : isToday 
                                ? '今天'
                                : '${daysUntilReminder}天后',
                        style: const TextStyle(
                          color: Colors.white,
                          fontSize: 10,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                    ),
                    const SizedBox(height: 4),
                    Text(
                      _formatDate(reminder.reminderDate),
                      style: TextStyle(
                        color: Colors.grey.shade500,
                        fontSize: 10,
                      ),
                    ),
                  ],
                ),
              ],
            ),

            const SizedBox(height: 12),

            // 提醒内容
            Container(
              padding: const EdgeInsets.all(12),
              decoration: BoxDecoration(
                color: Colors.grey.shade50,
                borderRadius: BorderRadius.circular(8),
              ),
              child: Text(
                reminder.message,
                style: TextStyle(
                  color: Colors.grey.shade700,
                  fontSize: 13,
                ),
              ),
            ),

            const SizedBox(height: 12),

            // 操作按钮
            Row(
              mainAxisAlignment: MainAxisAlignment.end,
              children: [
                TextButton(
                  onPressed: () => _postponeReminder(reminder),
                  child: const Text('推迟'),
                ),
                const SizedBox(width: 8),
                ElevatedButton(
                  onPressed: () => _completeReminder(reminder),
                  style: ElevatedButton.styleFrom(
                    backgroundColor: Colors.teal,
                    foregroundColor: Colors.white,
                  ),
                  child: const Text('完成'),
                ),
              ],
            ),
          ],
        ),
      ),
    ),
  );
}

提醒操作功能

提醒的推迟和完成操作:

void _postponeReminder(DewormingReminder reminder) {
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('推迟提醒'),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          ListTile(
            title: const Text('推迟1天'),
            onTap: () {
              _updateReminderDate(reminder, 1);
              Navigator.of(context).pop();
            },
          ),
          ListTile(
            title: const Text('推迟3天'),
            onTap: () {
              _updateReminderDate(reminder, 3);
              Navigator.of(context).pop();
            },
          ),
          ListTile(
            title: const Text('推迟1周'),
            onTap: () {
              _updateReminderDate(reminder, 7);
              Navigator.of(context).pop();
            },
          ),
        ],
      ),
      actions: [
        TextButton(
          onPressed: () => Navigator.of(context).pop(),
          child: const Text('取消'),
        ),
      ],
    ),
  );
}

void _updateReminderDate(DewormingReminder reminder, int days) {
  setState(() {
    final index = _reminders.indexWhere((r) => r.id == reminder.id);
    if (index != -1) {
      _reminders[index] = DewormingReminder(
        id: reminder.id,
        petId: reminder.petId,
        petName: reminder.petName,
        reminderDate: reminder.reminderDate.add(Duration(days: days)),
        reminderType: reminder.reminderType,
        medicineType: reminder.medicineType,
        title: reminder.title,
        message: reminder.message,
        isCompleted: reminder.isCompleted,
        createdDate: reminder.createdDate,
      );
    }
  });
  
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(content: Text('提醒已推迟${days}天')),
  );
}

void _completeReminder(DewormingReminder reminder) {
  setState(() {
    final index = _reminders.indexWhere((r) => r.id == reminder.id);
    if (index != -1) {
      _reminders[index] = DewormingReminder(
        id: reminder.id,
        petId: reminder.petId,
        petName: reminder.petName,
        reminderDate: reminder.reminderDate,
        reminderType: reminder.reminderType,
        medicineType: reminder.medicineType,
        title: reminder.title,
        message: reminder.message,
        isCompleted: true,
        createdDate: reminder.createdDate,
      );
    }
    _calculateStats();
  });
  
  ScaffoldMessenger.of(context).showSnackBar(
    const SnackBar(content: Text('提醒已完成')),
  );
}

统计分析功能

统计页面结构

统计页面提供全面的数据分析功能:

Widget _buildStatsPage() {
  if (_stats == null) {
    return const Center(child: CircularProgressIndicator());
  }

  final stats = _stats!;

  return SingleChildScrollView(
    padding: const EdgeInsets.all(16),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        // 总体统计卡片
        _buildOverallStatsCard(),
        
        const SizedBox(height: 20),
        
        // 药物类型分布
        Text(
          '药物类型分布',
          style: Theme.of(context).textTheme.titleLarge?.copyWith(
                fontWeight: FontWeight.bold,
              ),
        ),
        const SizedBox(height: 16),
        _buildMedicineTypeDistributionCard(),
        
        const SizedBox(height: 24),
        
        // 月度趋势
        Text(
          '月度驱虫趋势',
          style: Theme.of(context).textTheme.titleLarge?.copyWith(
                fontWeight: FontWeight.bold,
              ),
        ),
        const SizedBox(height: 16),
        _buildMonthlyTrendCard(),
        
        const SizedBox(height: 24),
        
        // 费用分析
        Text(
          '费用分析',
          style: Theme.of(context).textTheme.titleLarge?.copyWith(
                fontWeight: FontWeight.bold,
              ),
        ),
        const SizedBox(height: 16),
        _buildCostAnalysisCard(),
      ],
    ),
  );
}

总体统计卡片

总体统计展示关键指标:

Widget _buildOverallStatsCard() {
  final stats = _stats!;

  return Card(
    elevation: 4,
    child: Padding(
      padding: const EdgeInsets.all(20),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            children: [
              Icon(
                Icons.analytics,
                color: Colors.teal.shade600,
                size: 28,
              ),
              const SizedBox(width: 12),
              Text(
                '驱虫统计总览',
                style: TextStyle(
                  fontSize: 20,
                  fontWeight: FontWeight.bold,
                  color: Colors.teal.shade700,
                ),
              ),
            ],
          ),
          const SizedBox(height: 20),
          Row(
            children: [
              Expanded(
                child: _buildStatItem(
                  Icons.pets,
                  '宠物总数',
                  '${stats.totalPets}只',
                  Colors.teal,
                ),
              ),
              Expanded(
                child: _buildStatItem(
                  Icons.medical_services,
                  '驱虫记录',
                  '${stats.totalRecords}次',
                  Colors.blue,
                ),
              ),
            ],
          ),
          const SizedBox(height: 16),
          Row(
            children: [
              Expanded(
                child: _buildStatItem(
                  Icons.schedule,
                  '活跃计划',
                  '${stats.activePlans}个',
                  Colors.green,
                ),
              ),
              Expanded(
                child: _buildStatItem(
                  Icons.notifications,
                  '待处理提醒',
                  '${stats.pendingReminders}个',
                  Colors.orange,
                ),
              ),
            ],
          ),
          const SizedBox(height: 16),
          Container(
            width: double.infinity,
            padding: const EdgeInsets.all(16),
            decoration: BoxDecoration(
              color: Colors.green.withValues(alpha: 0.1),
              borderRadius: BorderRadius.circular(12),
              border: Border.all(color: Colors.green.withValues(alpha: 0.3)),
            ),
            child: Column(
              children: [
                Icon(
                  Icons.attach_money,
                  color: Colors.green.shade600,
                  size: 32,
                ),
                const SizedBox(height: 8),
                Text(
                  '总费用',
                  style: TextStyle(
                    fontSize: 14,
                    color: Colors.green.shade600,
                  ),
                ),
                Text(
                  ${stats.totalCost.toStringAsFixed(2)}',
                  style: TextStyle(
                    fontSize: 24,
                    fontWeight: FontWeight.bold,
                    color: Colors.green.shade700,
                  ),
                ),
              ],
            ),
          ),
          const SizedBox(height: 20),
          Container(
            padding: const EdgeInsets.all(16),
            decoration: BoxDecoration(
              color: Colors.grey.shade50,
              borderRadius: BorderRadius.circular(12),
            ),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  '偏好分析',
                  style: TextStyle(
                    fontSize: 16,
                    fontWeight: FontWeight.bold,
                    color: Colors.grey.shade700,
                  ),
                ),
                const SizedBox(height: 12),
                Row(
                  children: [
                    Expanded(
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Text(
                            '最常用药物',
                            style: TextStyle(
                              fontSize: 12,
                              color: Colors.grey.shade600,
                            ),
                          ),
                          const SizedBox(height: 4),
                          Text(
                            stats.mostUsedMedicine,
                            style: const TextStyle(
                              fontSize: 14,
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                        ],
                      ),
                    ),
                    Expanded(
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Text(
                            '最常去诊所',
                            style: TextStyle(
                              fontSize: 12,
                              color: Colors.grey.shade600,
                            ),
                          ),
                          const SizedBox(height: 4),
                          Text(
                            stats.mostFrequentClinic,
                            style: const TextStyle(
                              fontSize: 14,
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                        ],
                      ),
                    ),
                  ],
                ),
              ],
            ),
          ),
        ],
      ),
    ),
  );
}

药物类型分布统计

药物类型分布以进度条形式展示:

Widget _buildMedicineTypeDistributionCard() {
  final stats = _stats!;

  return Card(
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        children: stats.medicineTypeDistribution.entries.map((entry) {
          final total = stats.medicineTypeDistribution.values.fold(0, (sum, value) => sum + value);
          final percentage = total > 0 ? entry.value / total : 0.0;
          return Padding(
            padding: const EdgeInsets.only(bottom: 12),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Row(
                      children: [
                        Icon(
                          _getMedicineTypeIcon(entry.key),
                          color: _getMedicineTypeColor(entry.key),
                          size: 16,
                        ),
                        const SizedBox(width: 8),
                        Text(entry.key),
                      ],
                    ),
                    Text(
                      '${entry.value}次 (${(percentage * 100).toStringAsFixed(1)}%)',
                      style: TextStyle(
                        color: _getMedicineTypeColor(entry.key),
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                  ],
                ),
                const SizedBox(height: 4),
                LinearProgressIndicator(
                  value: percentage,
                  backgroundColor: Colors.grey.shade300,
                  valueColor: AlwaysStoppedAnimation<Color>(
                    _getMedicineTypeColor(entry.key),
                  ),
                ),
              ],
            ),
          );
        }).toList(),
      ),
    ),
  );
}

月度趋势分析

月度驱虫趋势展示:

Widget _buildMonthlyTrendCard() {
  final stats = _stats!;

  return Card(
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            '最近6个月驱虫次数',
            style: TextStyle(
              fontSize: 14,
              fontWeight: FontWeight.bold,
              color: Colors.grey.shade700,
            ),
          ),
          const SizedBox(height: 16),
          SizedBox(
            height: 200,
            child: ListView.builder(
              scrollDirection: Axis.horizontal,
              itemCount: stats.monthlyTreatments.length,
              itemBuilder: (context, index) {
                final entry = stats.monthlyTreatments.entries.elementAt(index);
                final maxValue = stats.monthlyTreatments.values.isNotEmpty 
                    ? stats.monthlyTreatments.values.reduce((a, b) => a > b ? a : b)
                    : 1;
                final height = (entry.value / maxValue) * 150;
                
                return Container(
                  width: 60,
                  margin: const EdgeInsets.only(right: 8),
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.end,
                    children: [
                      Text(
                        '${entry.value}',
                        style: const TextStyle(
                          fontSize: 12,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      const SizedBox(height: 4),
                      Container(
                        width: 40,
                        height: height,
                        decoration: BoxDecoration(
                          color: Colors.teal,
                          borderRadius: BorderRadius.circular(4),
                        ),
                      ),
                      const SizedBox(height: 8),
                      Text(
                        entry.key.substring(5),
                        style: TextStyle(
                          fontSize: 10,
                          color: Colors.grey.shade600,
                        ),
                      ),
                    ],
                  ),
                );
              },
            ),
          ),
        ],
      ),
    ),
  );
}

工具函数和辅助方法

颜色和图标映射

应用使用多种颜色和图标来区分不同的药物类型和宠物种类:

Color _getMedicineTypeColor(String medicineType) {
  switch (medicineType) {
    case '内驱':
      return Colors.blue;
    case '外驱':
      return Colors.green;
    case '内外同驱':
      return Colors.purple;
    default:
      return Colors.grey;
  }
}

IconData _getMedicineTypeIcon(String medicineType) {
  switch (medicineType) {
    case '内驱':
      return Icons.medication;
    case '外驱':
      return Icons.pest_control;
    case '内外同驱':
      return Icons.medical_services;
    default:
      return Icons.healing;
  }
}

Color _getSpeciesColor(String species) {
  switch (species) {
    case '猫':
      return Colors.orange;
    case '狗':
      return Colors.brown;
    case '兔子':
      return Colors.pink;
    case '仓鼠':
      return Colors.amber;
    case '鸟类':
      return Colors.cyan;
    default:
      return Colors.grey;
  }
}

IconData _getSpeciesIcon(String species) {
  switch (species) {
    case '猫':
      return Icons.pets;
    case '狗':
      return Icons.pets;
    case '兔子':
      return Icons.cruelty_free;
    case '仓鼠':
      return Icons.pets;
    case '鸟类':
      return Icons.flutter_dash;
    default:
      return Icons.pets;
  }
}

Color _getEffectivenessColor(String effectiveness) {
  switch (effectiveness) {
    case '优秀':
      return Colors.green;
    case '良好':
      return Colors.blue;
    case '一般':
      return Colors.orange;
    case '差':
      return Colors.red;
    default:
      return Colors.grey;
  }
}

时间格式化

时间显示格式化函数:

String _formatDate(DateTime dateTime) {
  return '${dateTime.year}-${dateTime.month.toString().padLeft(2, '0')}-${dateTime.day.toString().padLeft(2, '0')}';
}

String _formatDateTime(DateTime dateTime) {
  return '${_formatDate(dateTime)} ${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}';
}

统计项组件

统计项组件用于显示各种数值指标:

Widget _buildStatItem(IconData icon, String label, String value, Color color) {
  return Column(
    children: [
      Icon(icon, color: color, size: 20),
      const SizedBox(height: 4),
      Text(
        label,
        style: TextStyle(
          fontSize: 10,
          color: Colors.grey.shade600,
        ),
        textAlign: TextAlign.center,
      ),
      const SizedBox(height: 2),
      Text(
        value,
        style: TextStyle(
          fontSize: 12,
          fontWeight: FontWeight.bold,
          color: color,
        ),
        textAlign: TextAlign.center,
      ),
    ],
  );
}

筛选和搜索功能

筛选记录的核心逻辑:

List<DewormingRecord> _getFilteredRecords() {
  return _dewormingRecords.where((record) {
    // 搜索过滤
    if (_searchQuery.isNotEmpty) {
      final query = _searchQuery.toLowerCase();
      if (!record.petName.toLowerCase().contains(query) &&
          !record.medicineName.toLowerCase().contains(query) &&
          !record.medicineBrand.toLowerCase().contains(query) &&
          !record.clinic.toLowerCase().contains(query) &&
          !record.veterinarian.toLowerCase().contains(query)) {
        return false;
      }
    }

    // 宠物过滤
    if (_selectedPet != null && record.petName != _selectedPet) {
      return false;
    }

    // 药物类型过滤
    if (_selectedMedicineType != null && record.medicineType != _selectedMedicineType) {
      return false;
    }

    return true;
  }).toList()
    ..sort((a, b) => b.treatmentDate.compareTo(a.treatmentDate));
}

搜索对话框

搜索功能通过对话框实现:

void _showSearchDialog() {
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('搜索驱虫记录'),
      content: TextField(
        autofocus: true,
        decoration: const InputDecoration(
          hintText: '输入宠物名称、药物名称、品牌或诊所',
          prefixIcon: Icon(Icons.search),
        ),
        onChanged: (value) {
          _searchQuery = value;
        },
        onSubmitted: (value) {
          Navigator.of(context).pop();
          setState(() {});
        },
      ),
      actions: [
        TextButton(
          onPressed: () => Navigator.of(context).pop(),
          child: const Text('取消'),
        ),
        ElevatedButton(
          onPressed: () {
            Navigator.of(context).pop();
            setState(() {});
          },
          child: const Text('搜索'),
        ),
      ],
    ),
  );
}

筛选对话框

筛选对话框提供多维度筛选:

void _showFilterDialog() {
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('筛选驱虫记录'),
      content: SingleChildScrollView(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text('宠物:'),
            const SizedBox(height: 8),
            Wrap(
              spacing: 8,
              children: [
                FilterChip(
                  label: const Text('全部'),
                  selected: _selectedPet == null,
                  onSelected: (selected) {
                    setState(() {
                      _selectedPet = selected ? null : _selectedPet;
                    });
                  },
                ),
                ..._pets.map((pet) => FilterChip(
                      label: Text(pet.name),
                      selected: _selectedPet == pet.name,
                      onSelected: (selected) {
                        setState(() {
                          _selectedPet = selected ? pet.name : null;
                        });
                      },
                    )),
              ],
            ),
            const SizedBox(height: 16),
            const Text('药物类型:'),
            const SizedBox(height: 8),
            Wrap(
              spacing: 8,
              children: [
                FilterChip(
                  label: const Text('全部'),
                  selected: _selectedMedicineType == null,
                  onSelected: (selected) {
                    setState(() {
                      _selectedMedicineType = selected ? null : _selectedMedicineType;
                    });
                  },
                ),
                ...['内驱', '外驱', '内外同驱'].map((type) => FilterChip(
                      label: Text(type),
                      selected: _selectedMedicineType == type,
                      onSelected: (selected) {
                        setState(() {
                          _selectedMedicineType = selected ? type : null;
                        });
                      },
                    )),
              ],
            ),
          ],
        ),
      ),
      actions: [
        TextButton(
          onPressed: () => Navigator.of(context).pop(),
          child: const Text('取消'),
        ),
        ElevatedButton(
          onPressed: () {
            Navigator.of(context).pop();
            setState(() {});
          },
          child: const Text('应用'),
        ),
      ],
    ),
  );
}

数据初始化和统计计算

数据初始化

应用启动时初始化示例数据:

void _initializeData() {
  // 初始化宠物数据
  _pets = [
    Pet(
      id: 'pet001',
      name: '小白',
      species: '猫',
      breed: '英国短毛猫',
      gender: '母',
      birthDate: DateTime(2022, 3, 15),
      weight: 4.2,
      color: '银渐层',
      avatarUrl: 'assets/pets/cat_white.jpg',
      microchipId: 'MC001234567',
      notes: '性格温顺,喜欢晒太阳',
      createdDate: DateTime.now().subtract(const Duration(days: 365)),
      isActive: true,
    ),
    Pet(
      id: 'pet002',
      name: '旺财',
      species: '狗',
      breed: '金毛寻回犬',
      gender: '公',
      birthDate: DateTime(2021, 8, 20),
      weight: 28.5,
      color: '金黄色',
      avatarUrl: 'assets/pets/dog_golden.jpg',
      microchipId: 'MC001234568',
      notes: '活泼好动,喜欢游泳',
      createdDate: DateTime.now().subtract(const Duration(days: 400)),
      isActive: true,
    ),
    Pet(
      id: 'pet003',
      name: '小灰',
      species: '兔子',
      breed: '荷兰兔',
      gender: '公',
      birthDate: DateTime(2023, 1, 10),
      weight: 1.8,
      color: '灰白色',
      avatarUrl: 'assets/pets/rabbit_gray.jpg',
      microchipId: 'MC001234569',
      notes: '胆小安静,喜欢吃胡萝卜',
      createdDate: DateTime.now().subtract(const Duration(days: 200)),
      isActive: true,
    ),
  ];

  // 初始化驱虫记录
  _dewormingRecords = [
    DewormingRecord(
      id: 'dr001',
      petId: 'pet001',
      petName: '小白',
      treatmentDate: DateTime.now().subtract(const Duration(days: 30)),
      medicineType: '内驱',
      medicineName: '拜宠清',
      medicineBrand: '拜耳',
      dosage: 1.0,
      dosageUnit: '片',
      administrationMethod: '口服',
      petWeight: 4.1,
      veterinarian: '张医生',
      clinic: '爱宠动物医院',
      cost: 45.0,
      notes: '驱虫前发现有轻微腹泻症状',
      symptoms: ['腹泻', '食欲不振'],
      sideEffects: [],
      effectiveness: '良好',
      photos: ['deworming_001.jpg'],
      nextDueDate: DateTime.now().add(const Duration(days: 60)),
    ),
    DewormingRecord(
      id: 'dr002',
      petId: 'pet002',
      petName: '旺财',
      treatmentDate: DateTime.now().subtract(const Duration(days: 45)),
      medicineType: '外驱',
      medicineName: '福来恩',
      medicineBrand: '勃林格',
      dosage: 2.68,
      dosageUnit: 'ml',
      administrationMethod: '滴剂',
      petWeight: 28.3,
      veterinarian: '李医生',
      clinic: '宠物之家诊所',
      cost: 120.0,
      notes: '夏季预防跳蚤和蜱虫',
      symptoms: ['瘙痒', '皮肤红疹'],
      sideEffects: ['轻微皮肤刺激'],
      effectiveness: '优秀',
      photos: ['deworming_002.jpg', 'deworming_003.jpg'],
      nextDueDate: DateTime.now().add(const Duration(days: 45)),
    ),
    DewormingRecord(
      id: 'dr003',
      petId: 'pet003',
      petName: '小灰',
      treatmentDate: DateTime.now().subtract(const Duration(days: 60)),
      medicineType: '内驱',
      medicineName: '兔用驱虫药',
      medicineBrand: '小宠',
      dosage: 0.5,
      dosageUnit: 'ml',
      administrationMethod: '口服',
      petWeight: 1.7,
      veterinarian: '王医生',
      clinic: '小动物专科医院',
      cost: 25.0,
      notes: '定期预防性驱虫',
      symptoms: [],
      sideEffects: [],
      effectiveness: '良好',
      photos: [],
      nextDueDate: DateTime.now().add(const Duration(days: 30)),
    ),
  ];

  // 初始化驱虫计划
  _dewormingPlans = [
    DewormingPlan(
      id: 'dp001',
      petId: 'pet001',
      petName: '小白',
      planType: '定期',
      medicineType: '内驱',
      intervalDays: 90,
      startDate: DateTime.now().subtract(const Duration(days: 30)),
      isActive: true,
      reminderTime: '09:00',
      reminderDays: ['7', '3', '1'],
      notes: '每3个月进行一次内驱',
      createdDate: DateTime.now().subtract(const Duration(days: 100)),
    ),
    DewormingPlan(
      id: 'dp002',
      petId: 'pet002',
      petName: '旺财',
      planType: '季节性',
      medicineType: '外驱',
      intervalDays: 30,
      startDate: DateTime(2024, 4, 1),
      endDate: DateTime(2024, 10, 31),
      isActive: true,
      reminderTime: '18:00',
      reminderDays: ['5', '2'],
      notes: '夏季防虫计划',
      createdDate: DateTime.now().subtract(const Duration(days: 80)),
    ),
  ];

  // 初始化提醒
  _reminders = [
    DewormingReminder(
      id: 'rm001',
      petId: 'pet001',
      petName: '小白',
      reminderDate: DateTime.now().add(const Duration(days: 3)),
      reminderType: '驱虫到期',
      medicineType: '内驱',
      title: '小白驱虫提醒',
      message: '小白的内驱即将到期,请及时安排驱虫',
      isCompleted: false,
      createdDate: DateTime.now().subtract(const Duration(days: 4)),
    ),
    DewormingReminder(
      id: 'rm002',
      petId: 'pet002',
      petName: '旺财',
      reminderDate: DateTime.now().add(const Duration(days: 7)),
      reminderType: '计划提醒',
      medicineType: '外驱',
      title: '旺财外驱计划',
      message: '根据夏季防虫计划,旺财需要进行外驱',
      isCompleted: false,
      createdDate: DateTime.now().subtract(const Duration(days: 2)),
    ),
  ];

  // 初始化健康监测记录
  _healthRecords = [
    HealthMonitoring(
      id: 'hm001',
      petId: 'pet001',
      petName: '小白',
      checkDate: DateTime.now().subtract(const Duration(days: 1)),
      weight: 4.2,
      appetite: '正常',
      energy: '活跃',
      stoolCondition: '正常',
      hasVomiting: false,
      hasItching: false,
      skinCondition: '正常',
      symptoms: [],
      notes: '整体状况良好',
      photos: [],
    ),
    HealthMonitoring(
      id: 'hm002',
      petId: 'pet002',
      petName: '旺财',
      checkDate: DateTime.now().subtract(const Duration(days: 2)),
      weight: 28.5,
      appetite: '正常',
      energy: '活跃',
      stoolCondition: '正常',
      hasVomiting: false,
      hasItching: true,
      skinCondition: '轻微红疹',
      symptoms: ['轻微瘙痒'],
      notes: '可能需要检查是否有寄生虫',
      photos: ['health_001.jpg'],
    ),
  ];
}

统计数据计算

实时计算各种统计指标:

void _calculateStats() {
  final totalPets = _pets.where((pet) => pet.isActive).length;
  final totalRecords = _dewormingRecords.length;
  final activePlans = _dewormingPlans.where((plan) => plan.isActive).length;
  final pendingReminders = _reminders.where((reminder) => !reminder.isCompleted).length;
  final totalCost = _dewormingRecords.fold(0.0, (sum, record) => sum + record.cost);

  // 计算药物类型分布
  final medicineTypeDistribution = <String, int>{};
  for (final record in _dewormingRecords) {
    medicineTypeDistribution[record.medicineType] = 
        (medicineTypeDistribution[record.medicineType] ?? 0) + 1;
  }

  // 计算月度治疗次数
  final monthlyTreatments = <String, int>{};
  for (final record in _dewormingRecords) {
    final monthKey = '${record.treatmentDate.year}-${record.treatmentDate.month.toString().padLeft(2, '0')}';
    monthlyTreatments[monthKey] = (monthlyTreatments[monthKey] ?? 0) + 1;
  }

  // 计算月度费用
  final costByMonth = <String, double>{};
  for (final record in _dewormingRecords) {
    final monthKey = '${record.treatmentDate.year}-${record.treatmentDate.month.toString().padLeft(2, '0')}';
    costByMonth[monthKey] = (costByMonth[monthKey] ?? 0.0) + record.cost;
  }

  // 计算最常用药物
  final medicineCount = <String, int>{};
  for (final record in _dewormingRecords) {
    medicineCount[record.medicineName] = (medicineCount[record.medicineName] ?? 0) + 1;
  }
  final mostUsedMedicine = medicineCount.entries.isNotEmpty
      ? medicineCount.entries.reduce((a, b) => a.value > b.value ? a : b).key
      : '暂无';

  // 计算最常去的诊所
  final clinicCount = <String, int>{};
  for (final record in _dewormingRecords) {
    clinicCount[record.clinic] = (clinicCount[record.clinic] ?? 0) + 1;
  }
  final mostFrequentClinic = clinicCount.entries.isNotEmpty
      ? clinicCount.entries.reduce((a, b) => a.value > b.value ? a : b).key
      : '暂无';

  _stats = DewormingStats(
    totalPets: totalPets,
    totalRecords: totalRecords,
    activePlans: activePlans,
    pendingReminders: pendingReminders,
    totalCost: totalCost,
    medicineTypeDistribution: medicineTypeDistribution,
    monthlyTreatments: monthlyTreatments,
    costByMonth: costByMonth,
    mostUsedMedicine: mostUsedMedicine,
    mostFrequentClinic: mostFrequentClinic,
  );
}

快速记录功能

快速记录对话框

快速记录功能让用户能够快速添加驱虫记录:

void _showQuickRecordDialog() {
  if (_pets.isEmpty) {
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('请先添加宠物信息')),
    );
    return;
  }

  String selectedPetId = _pets.first.id;
  String selectedMedicineType = '内驱';
  final medicineNameController = TextEditingController();
  final dosageController = TextEditingController();
  final costController = TextEditingController();
  DateTime selectedDate = DateTime.now();

  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('快速记录驱虫'),
      content: SingleChildScrollView(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            DropdownButtonFormField<String>(
              value: selectedPetId,
              decoration: const InputDecoration(labelText: '选择宠物'),
              items: _pets.map((pet) {
                return DropdownMenuItem(
                  value: pet.id,
                  child: Text(pet.name),
                );
              }).toList(),
              onChanged: (value) {
                selectedPetId = value!;
              },
            ),
            const SizedBox(height: 16),
            DropdownButtonFormField<String>(
              value: selectedMedicineType,
              decoration: const InputDecoration(labelText: '药物类型'),
              items: ['内驱', '外驱', '内外同驱'].map((type) {
                return DropdownMenuItem(
                  value: type,
                  child: Text(type),
                );
              }).toList(),
              onChanged: (value) {
                selectedMedicineType = value!;
              },
            ),
            const SizedBox(height: 16),
            TextField(
              controller: medicineNameController,
              decoration: const InputDecoration(
                labelText: '药物名称',
                hintText: '请输入药物名称',
              ),
            ),
            const SizedBox(height: 16),
            Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: dosageController,
                    keyboardType: TextInputType.number,
                    decoration: const InputDecoration(
                      labelText: '剂量',
                      hintText: '1.0',
                    ),
                  ),
                ),
                const SizedBox(width: 16),
                Expanded(
                  child: TextField(
                    controller: costController,
                    keyboardType: TextInputType.number,
                    decoration: const InputDecoration(
                      labelText: '费用',
                      hintText: '0.00',
                      prefixText: '¥',
                    ),
                  ),
                ),
              ],
            ),
            const SizedBox(height: 16),
            ListTile(
              title: const Text('驱虫日期'),
              subtitle: Text(_formatDate(selectedDate)),
              trailing: const Icon(Icons.calendar_today),
              onTap: () async {
                final date = await showDatePicker(
                  context: context,
                  initialDate: selectedDate,
                  firstDate: DateTime.now().subtract(const Duration(days: 365)),
                  lastDate: DateTime.now(),
                );
                if (date != null) {
                  selectedDate = date;
                }
              },
            ),
          ],
        ),
      ),
      actions: [
        TextButton(
          onPressed: () => Navigator.of(context).pop(),
          child: const Text('取消'),
        ),
        ElevatedButton(
          onPressed: () {
            if (medicineNameController.text.isNotEmpty) {
              final selectedPet = _pets.firstWhere((pet) => pet.id == selectedPetId);
              final newRecord = DewormingRecord(
                id: 'dr${_dewormingRecords.length + 1}',
                petId: selectedPetId,
                petName: selectedPet.name,
                treatmentDate: selectedDate,
                medicineType: selectedMedicineType,
                medicineName: medicineNameController.text,
                medicineBrand: '未知品牌',
                dosage: double.tryParse(dosageController.text) ?? 1.0,
                dosageUnit: selectedMedicineType == '外驱' ? 'ml' : '片',
                administrationMethod: selectedMedicineType == '外驱' ? '滴剂' : '口服',
                petWeight: selectedPet.weight,
                veterinarian: '自行记录',
                clinic: '家庭护理',
                cost: double.tryParse(costController.text) ?? 0.0,
                notes: '快速记录',
                symptoms: [],
                sideEffects: [],
                effectiveness: '良好',
                photos: [],
                nextDueDate: selectedDate.add(Duration(
                  days: selectedMedicineType == '外驱' ? 30 : 90,
                )),
              );
              
              setState(() {
                _dewormingRecords.add(newRecord);
                _calculateStats();
              });
              
              Navigator.of(context).pop();
              
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(content: Text('驱虫记录添加成功!')),
              );
            }
          },
          child: const Text('保存'),
        ),
      ],
    ),
  );
}

详细记录对话框

完整的驱虫记录添加功能:

void _showAddRecordDialog() {
  if (_pets.isEmpty) {
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('请先添加宠物信息')),
    );
    return;
  }

  String selectedPetId = _pets.first.id;
  String selectedMedicineType = '内驱';
  final medicineNameController = TextEditingController();
  final medicineBrandController = TextEditingController();
  final dosageController = TextEditingController();
  final veterinarianController = TextEditingController();
  final clinicController = TextEditingController();
  final costController = TextEditingController();
  final notesController = TextEditingController();
  DateTime selectedDate = DateTime.now();
  String selectedEffectiveness = '良好';
  String selectedDosageUnit = '片';
  String selectedAdministrationMethod = '口服';

  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('添加驱虫记录'),
      content: SingleChildScrollView(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            DropdownButtonFormField<String>(
              value: selectedPetId,
              decoration: const InputDecoration(labelText: '选择宠物'),
              items: _pets.map((pet) {
                return DropdownMenuItem(
                  value: pet.id,
                  child: Text('${pet.name} (${pet.species})'),
                );
              }).toList(),
              onChanged: (value) {
                selectedPetId = value!;
              },
            ),
            const SizedBox(height: 16),
            DropdownButtonFormField<String>(
              value: selectedMedicineType,
              decoration: const InputDecoration(labelText: '药物类型'),
              items: ['内驱', '外驱', '内外同驱'].map((type) {
                return DropdownMenuItem(
                  value: type,
                  child: Text(type),
                );
              }).toList(),
              onChanged: (value) {
                selectedMedicineType = value!;
                // 根据药物类型自动调整默认值
                if (value == '外驱') {
                  selectedDosageUnit = 'ml';
                  selectedAdministrationMethod = '滴剂';
                } else {
                  selectedDosageUnit = '片';
                  selectedAdministrationMethod = '口服';
                }
              },
            ),
            const SizedBox(height: 16),
            TextField(
              controller: medicineNameController,
              decoration: const InputDecoration(
                labelText: '药物名称',
                hintText: '请输入药物名称',
              ),
            ),
            const SizedBox(height: 16),
            TextField(
              controller: medicineBrandController,
              decoration: const InputDecoration(
                labelText: '药物品牌',
                hintText: '请输入品牌名称',
              ),
            ),
            const SizedBox(height: 16),
            Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: dosageController,
                    keyboardType: TextInputType.number,
                    decoration: const InputDecoration(
                      labelText: '剂量',
                      hintText: '1.0',
                    ),
                  ),
                ),
                const SizedBox(width: 8),
                Expanded(
                  child: DropdownButtonFormField<String>(
                    value: selectedDosageUnit,
                    decoration: const InputDecoration(labelText: '单位'),
                    items: ['片', 'ml', '滴', 'g'].map((unit) {
                      return DropdownMenuItem(
                        value: unit,
                        child: Text(unit),
                      );
                    }).toList(),
                    onChanged: (value) {
                      selectedDosageUnit = value!;
                    },
                  ),
                ),
              ],
            ),
            const SizedBox(height: 16),
            DropdownButtonFormField<String>(
              value: selectedAdministrationMethod,
              decoration: const InputDecoration(labelText: '给药方式'),
              items: ['口服', '滴剂', '喷剂', '注射'].map((method) {
                return DropdownMenuItem(
                  value: method,
                  child: Text(method),
                );
              }).toList(),
              onChanged: (value) {
                selectedAdministrationMethod = value!;
              },
            ),
            const SizedBox(height: 16),
            Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: veterinarianController,
                    decoration: const InputDecoration(
                      labelText: '兽医师',
                      hintText: '请输入兽医师姓名',
                    ),
                  ),
                ),
                const SizedBox(width: 16),
                Expanded(
                  child: TextField(
                    controller: clinicController,
                    decoration: const InputDecoration(
                      labelText: '诊所',
                      hintText: '请输入诊所名称',
                    ),
                  ),
                ),
              ],
            ),
            const SizedBox(height: 16),
            TextField(
              controller: costController,
              keyboardType: TextInputType.number,
              decoration: const InputDecoration(
                labelText: '费用',
                hintText: '0.00',
                prefixText: '¥',
              ),
            ),
            const SizedBox(height: 16),
            DropdownButtonFormField<String>(
              value: selectedEffectiveness,
              decoration: const InputDecoration(labelText: '效果评价'),
              items: ['优秀', '良好', '一般', '差'].map((effectiveness) {
                return DropdownMenuItem(
                  value: effectiveness,
                  child: Text(effectiveness),
                );
              }).toList(),
              onChanged: (value) {
                selectedEffectiveness = value!;
              },
            ),
            const SizedBox(height: 16),
            TextField(
              controller: notesController,
              maxLines: 3,
              decoration: const InputDecoration(
                labelText: '备注',
                hintText: '请输入备注信息',
              ),
            ),
          ],
        ),
      ),
      actions: [
        TextButton(
          onPressed: () => Navigator.of(context).pop(),
          child: const Text('取消'),
        ),
        ElevatedButton(
          onPressed: () {
            if (medicineNameController.text.isNotEmpty) {
              final selectedPet = _pets.firstWhere((pet) => pet.id == selectedPetId);
              final newRecord = DewormingRecord(
                id: 'dr${_dewormingRecords.length + 1}',
                petId: selectedPetId,
                petName: selectedPet.name,
                treatmentDate: selectedDate,
                medicineType: selectedMedicineType,
                medicineName: medicineNameController.text,
                medicineBrand: medicineBrandController.text.isNotEmpty 
                    ? medicineBrandController.text 
                    : '未知品牌',
                dosage: double.tryParse(dosageController.text) ?? 1.0,
                dosageUnit: selectedDosageUnit,
                administrationMethod: selectedAdministrationMethod,
                petWeight: selectedPet.weight,
                veterinarian: veterinarianController.text.isNotEmpty 
                    ? veterinarianController.text 
                    : '未知',
                clinic: clinicController.text.isNotEmpty 
                    ? clinicController.text 
                    : '未知',
                cost: double.tryParse(costController.text) ?? 0.0,
                notes: notesController.text,
                symptoms: [],
                sideEffects: [],
                effectiveness: selectedEffectiveness,
                photos: [],
                nextDueDate: selectedDate.add(Duration(
                  days: selectedMedicineType == '外驱' ? 30 : 90,
                )),
              );
              
              setState(() {
                _dewormingRecords.add(newRecord);
                _calculateStats();
              });
              
              Navigator.of(context).pop();
              
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(content: Text('驱虫记录添加成功!')),
              );
            }
          },
          child: const Text('保存'),
        ),
      ],
    ),
  );
}

应用特色功能

智能提醒系统

应用具备智能提醒功能:

  1. 自动计算下次驱虫时间:根据药物类型和宠物特征自动计算
  2. 多级提醒机制:提前7天、3天、1天多次提醒
  3. 逾期警告:超过预定时间后显示逾期警告
  4. 个性化提醒:根据宠物品种和年龄调整提醒频率

健康状态监测

应用提供全面的健康监测功能:

  1. 症状记录:详细记录驱虫前后的症状变化
  2. 副作用追踪:记录药物副作用,为后续选药提供参考
  3. 体重监测:跟踪宠物体重变化,调整用药剂量
  4. 效果评估:评价驱虫效果,优化治疗方案

数据分析洞察

应用提供深度数据分析:

  1. 费用统计:分析驱虫费用趋势,合理规划预算
  2. 药物效果对比:比较不同药物的效果和副作用
  3. 季节性分析:分析不同季节的驱虫需求
  4. 宠物健康趋势:长期跟踪宠物健康状况变化

用户体验优化

应用注重用户体验:

  1. 快速记录:一键快速记录常用驱虫信息
  2. 智能搜索:支持多关键词模糊搜索
  3. 数据导出:支持记录数据导出和备份
  4. 离线使用:支持离线记录,网络恢复后同步

技术优化建议

性能优化

  1. 数据缓存机制:实现本地数据缓存,减少重复计算
  2. 懒加载实现:大数据列表采用懒加载方式提升性能
  3. 图片优化:宠物照片采用缩略图和原图分离策略
  4. 内存管理:及时释放不必要的资源和监听器

用户体验优化

  1. 离线功能:支持离线记录和查看,网络恢复后同步
  2. 快捷操作:提供更多快捷记录和编辑方式
  3. 数据备份:支持云端备份和多设备同步
  4. 多语言支持:国际化支持,适配不同语言环境

功能扩展建议

  1. 兽医预约:集成兽医预约功能
  2. 药物提醒:药物库存不足时自动提醒
  3. 社区功能:宠物主人经验分享社区
  4. AI建议:基于宠物特征和历史数据提供AI驱虫建议

项目部署和发布

pubspec.yaml 配置

name: pet_deworming_tracker
description: Flutter宠物驱虫记录器应用

publish_to: 'none'

version: 1.0.0+1

environment:
  sdk: '>=3.0.0 <4.0.0'

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.2
  shared_preferences: ^2.2.2
  path_provider: ^2.1.1
  image_picker: ^1.0.4
  share_plus: ^7.2.1

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^2.0.0

flutter:
  uses-material-design: true
  assets:
    - assets/images/
    - assets/pets/

构建和发布

Android 发布
  1. 生成签名密钥:
keytool -genkey -v -keystore ~/pet-deworming-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias pet-deworming
  1. 构建 APK:
flutter build apk --release
iOS 发布
  1. 在 Xcode 中配置签名和证书
  2. 构建 iOS 应用:
flutter build ios --release

总结

Flutter宠物驱虫记录器应用是一个功能完整、设计精美的宠物健康管理工具。应用通过Material Design 3设计语言,提供了直观友好的用户界面;通过完善的数据模型设计,实现了全面的驱虫记录管理;通过智能提醒和统计分析,帮助宠物主人科学管理宠物健康。

应用的核心价值在于:

  • 系统化管理:科学记录和管理宠物驱虫信息,提高健康管理效率
  • 智能提醒:自动计算和提醒驱虫时间,避免遗漏
  • 数据分析:全面的统计分析,帮助优化驱虫策略
  • 健康监测:持续跟踪宠物健康状况,及时发现问题

通过本教程的学习,开发者可以掌握Flutter应用开发的核心技术,包括状态管理、UI设计、数据处理、动画效果等。同时,也能了解如何设计和实现一个完整的健康管理应用,为后续的项目开发奠定坚实基础。

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

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐