import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/navigation_provider.dart'; import '../../providers/data_providers.dart'; class _MedicationItem { String name = ''; String dosage = ''; String frequency = '每日1次'; List times = [const TimeOfDay(hour: 8, minute: 0)]; DateTime startDate = DateTime.now(); DateTime? endDate; int weekday = 1; } const _frequencies = ['每日1次', '每日2次', '每日3次', '每周1次', '按需服用']; const _weekdays = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']; class MedicationEditPage extends ConsumerStatefulWidget { final String? medicationId; const MedicationEditPage({super.key, this.medicationId}); @override ConsumerState createState() => _MedicationEditPageState(); } class _MedicationEditPageState extends ConsumerState { final _items = <_MedicationItem>[]; final _nameCtrls = []; final _doseCtrls = []; @override void initState() { super.initState(); _addItem(); } @override void dispose() { for (final c in _nameCtrls) { c.dispose(); } for (final c in _doseCtrls) { c.dispose(); } super.dispose(); } void _addItem() { setState(() { _items.add(_MedicationItem()); _nameCtrls.add(TextEditingController()); _doseCtrls.add(TextEditingController()); }); } void _removeItem(int index) { setState(() { _nameCtrls[index].dispose(); _doseCtrls[index].dispose(); _nameCtrls.removeAt(index); _doseCtrls.removeAt(index); _items.removeAt(index); }); } void _onSave() async { for (int i = 0; i < _items.length; i++) { _items[i].name = _nameCtrls[i].text.trim(); _items[i].dosage = _doseCtrls[i].text.trim(); } final allValid = _items.every( (item) => item.name.isNotEmpty && item.dosage.isNotEmpty, ); if (!allValid) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('请填写所有药品的名称和剂量')), ); return; } final service = ref.read(medicationServiceProvider); try { for (final item in _items) { final timesStr = item.frequency == '按需服用' ? [] : item.times.map((t) => t.format(context)).toList(); await service.create({ 'name': item.name, 'dosage': item.dosage, 'frequency': 'Daily', 'timeOfDay': timesStr, 'startDate': item.startDate.toIso8601String().split('T')[0], if (item.endDate != null) 'endDate': item.endDate!.toIso8601String().split('T')[0], 'source': 'Manual', }); } if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('已添加 ${_items.length} 种药品'), backgroundColor: const Color(0xFF14B8A6), ), ); ref.invalidate(medicationListProvider); ref.invalidate(medicationReminderProvider); popRoute(ref); } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('保存失败:$e'), backgroundColor: Colors.red, ), ); // 仍然返回上一页,避免卡在黑屏 popRoute(ref); } } int _timeCount(String frequency) { switch (frequency) { case '每日1次': return 1; case '每日2次': return 2; case '每日3次': return 3; case '每周1次': return 1; default: return 0; } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFFF6F9FB), appBar: AppBar( backgroundColor: Colors.white, elevation: 0, leading: IconButton( icon: const Icon(Icons.chevron_left), onPressed: () => popRoute(ref), ), title: const Text( '添加用药', style: TextStyle( color: Color(0xFF1A1A1A), fontWeight: FontWeight.w600, ), ), centerTitle: true, actions: [ TextButton( onPressed: _onSave, child: const Text( '保存', style: TextStyle( color: Color(0xFF14B8A6), fontWeight: FontWeight.w600, ), ), ), ], ), body: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ...List.generate(_items.length, (i) => _buildCard(i)), const SizedBox(height: 12), _buildAddButton(), const SizedBox(height: 40), ], ), ), ); } Widget _buildCard(int index) { final item = _items[index]; final count = _timeCount(item.frequency); return Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: const Color(0xFFEEEEEE)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( '药品 ${index + 1}', style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: Color(0xFF14B8A6), ), ), if (_items.length > 1) GestureDetector( onTap: () => _removeItem(index), child: const Icon(Icons.close, size: 18, color: Color(0xFFBDBDBD)), ), ], ), const SizedBox(height: 8), Divider(height: 1, color: const Color(0xFFF0F0F0)), const SizedBox(height: 8), // Name _buildLabel('药品名称'), const SizedBox(height: 4), TextField( controller: _nameCtrls[index], style: const TextStyle(fontSize: 14), decoration: _inputDecoration('请输入药品名称'), ), const SizedBox(height: 8), // Dosage _buildLabel('剂量'), const SizedBox(height: 4), TextField( controller: _doseCtrls[index], style: const TextStyle(fontSize: 14), decoration: _inputDecoration('如:100mg'), ), const SizedBox(height: 8), // Frequency _buildLabel('服用频率'), const SizedBox(height: 4), GestureDetector( onTap: () => _pickFrequency(index), child: Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), border: Border.all(color: const Color(0xFFE0E0E0)), color: const Color(0xFFFAFAFA), ), child: Row( children: [ Text(item.frequency, style: const TextStyle(fontSize: 14, color: Color(0xFF1A1A1A))), const Spacer(), const Icon(Icons.keyboard_arrow_down, size: 20, color: Color(0xFF9E9E9E)), ], ), ), ), const SizedBox(height: 8), // Times (dynamic) if (count > 0) ...[ _buildLabel('服药时间'), const SizedBox(height: 4), Wrap( spacing: 8, runSpacing: 6, children: List.generate(count, (t) => _buildTimePicker(index, t)), ), if (item.frequency == '每周1次') ...[ const SizedBox(height: 8), _buildLabel('选择星期'), const SizedBox(height: 4), GestureDetector( onTap: () => _pickWeekday(index), child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), border: Border.all(color: const Color(0xFFE0E0E0)), color: const Color(0xFFFAFAFA), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Text(_weekdays[item.weekday - 1], style: const TextStyle(fontSize: 14, color: Color(0xFF1A1A1A))), const SizedBox(width: 4), const Icon(Icons.keyboard_arrow_down, size: 18, color: Color(0xFF9E9E9E)), ], ), ), ), ], const SizedBox(height: 8), ], // Start date _buildLabel('开始日期'), const SizedBox(height: 4), GestureDetector( onTap: () => _pickDate(index, isStart: true), child: Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), border: Border.all(color: const Color(0xFFE0E0E0)), color: const Color(0xFFFAFAFA), ), child: Row( children: [ Text( '${item.startDate.year}-${item.startDate.month.toString().padLeft(2, '0')}-${item.startDate.day.toString().padLeft(2, '0')}', style: const TextStyle(fontSize: 14, color: Color(0xFF1A1A1A)), ), const Spacer(), const Icon(Icons.calendar_today, size: 18, color: Color(0xFF9E9E9E)), ], ), ), ), const SizedBox(height: 8), // End date (optional) _buildLabel('结束日期(可选)'), const SizedBox(height: 4), GestureDetector( onTap: () => _pickDate(index, isStart: false), child: Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), border: Border.all(color: const Color(0xFFE0E0E0)), color: const Color(0xFFFAFAFA), ), child: Row( children: [ Text( item.endDate != null ? '${item.endDate!.year}-${item.endDate!.month.toString().padLeft(2, '0')}-${item.endDate!.day.toString().padLeft(2, '0')}' : '不设置', style: TextStyle( fontSize: 14, color: item.endDate != null ? const Color(0xFF1A1A1A) : const Color(0xFFBDBDBD), ), ), const Spacer(), GestureDetector( onTap: item.endDate != null ? () => setState(() => item.endDate = null) : null, child: Icon( item.endDate != null ? Icons.close : Icons.calendar_today, size: 18, color: const Color(0xFF9E9E9E), ), ), ], ), ), ), ], ), ); } Widget _buildLabel(String text) { return Text( text, style: const TextStyle(fontSize: 12, color: Color(0xFF757575)), ); } Widget _buildTimePicker(int itemIndex, int timeIndex) { final item = _items[itemIndex]; final time = item.times[timeIndex]; return GestureDetector( onTap: () => _pickTime(itemIndex, timeIndex), child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), border: Border.all(color: const Color(0xFFE0E0E0)), color: const Color(0xFFFAFAFA), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.access_time, size: 16, color: Color(0xFF14B8A6)), const SizedBox(width: 6), Text( time.format(context), style: const TextStyle(fontSize: 14, color: Color(0xFF1A1A1A)), ), ], ), ), ); } Widget _buildAddButton() { return SizedBox( width: double.infinity, child: OutlinedButton.icon( onPressed: _addItem, icon: const Icon(Icons.add, size: 18), label: const Text('添加', style: TextStyle(fontSize: 14)), style: OutlinedButton.styleFrom( foregroundColor: const Color(0xFF14B8A6), side: const BorderSide(color: Color(0xFFC0E8E2)), padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), backgroundColor: const Color(0xFFF2FAF9), ), ), ); } InputDecoration _inputDecoration(String hint) { return InputDecoration( hintText: hint, hintStyle: const TextStyle(color: Color(0xFFBDBDBD), fontSize: 14), filled: true, fillColor: const Color(0xFFFAFAFA), contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), isDense: true, border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: const BorderSide(color: Color(0xFFE0E0E0)), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: const BorderSide(color: Color(0xFFE0E0E0)), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: const BorderSide(color: Color(0xFF14B8A6)), ), ); } void _pickFrequency(int index) async { final selected = await showModalBottomSheet( context: context, builder: (ctx) => SafeArea( child: Column( mainAxisSize: MainAxisSize.min, children: _frequencies .map((f) => ListTile( title: Text(f), onTap: () => Navigator.pop(ctx, f), )) .toList(), ), ), ); if (selected != null && mounted) { setState(() { final item = _items[index]; item.frequency = selected; final newCount = _timeCount(selected); if (newCount > 0 && item.times.length != newCount) { item.times = List.generate( newCount, (i) => TimeOfDay(hour: 8 + i * 4, minute: 0), ); } }); } } void _pickWeekday(int index) async { final item = _items[index]; final selected = await showModalBottomSheet( context: context, builder: (ctx) => SafeArea( child: Column( mainAxisSize: MainAxisSize.min, children: List.generate(7, (i) { return ListTile( title: Text(_weekdays[i]), selected: item.weekday == i + 1, onTap: () => Navigator.pop(ctx, i + 1), ); }), ), ), ); if (selected != null && mounted) { setState(() => _items[index].weekday = selected); } } void _pickTime(int itemIndex, int timeIndex) async { final item = _items[itemIndex]; final time = await showTimePicker( context: context, initialTime: item.times[timeIndex], ); if (time != null && mounted) { setState(() => item.times[timeIndex] = time); } } void _pickDate(int index, {required bool isStart}) async { final item = _items[index]; final initial = isStart ? item.startDate : (item.endDate ?? DateTime.now()); final date = await showDatePicker( context: context, firstDate: DateTime(2020), lastDate: DateTime(2030), initialDate: initial, ); if (date != null && mounted) { setState(() { if (isStart) { item.startDate = date; } else { item.endDate = date; } }); } } }