import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import '../../services/water_service.dart'; /// 今日值班页面 class DispatchPage extends StatefulWidget { const DispatchPage({super.key}); @override State createState() => _DispatchPageState(); } class _DispatchPageState extends State { final WaterService _service = WaterService.instance; DutyInfo? _dutyInfo; bool _isLoading = true; String? _error; @override void initState() { super.initState(); _loadData(); } Future _loadData() async { setState(() { _isLoading = true; _error = null; }); try { final data = await _service.getTodayDuty(); if (mounted) { setState(() { _dutyInfo = data; _isLoading = false; }); } } catch (e) { if (mounted) { setState(() { _error = e.toString(); _isLoading = false; }); } } } @override Widget build(BuildContext context) { final theme = Theme.of(context); return Scaffold( appBar: AppBar( title: const Text('今日值班'), centerTitle: true, actions: [ IconButton( icon: const Icon(Icons.refresh), onPressed: _isLoading ? null : _loadData, ), ], ), body: _buildBody(theme), ); } Widget _buildBody(ThemeData theme) { if (_isLoading) { return const Center(child: CircularProgressIndicator()); } if (_error != null) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.error_outline, size: 48, color: theme.colorScheme.error), const SizedBox(height: 16), Text('加载失败', style: theme.textTheme.titleMedium), const SizedBox(height: 16), FilledButton.icon( onPressed: _loadData, icon: const Icon(Icons.refresh), label: const Text('重试'), ), ], ), ); } final info = _dutyInfo!; final dateFormat = DateFormat('yyyy年MM月dd日'); return RefreshIndicator( onRefresh: _loadData, child: ListView( padding: const EdgeInsets.all(16), children: [ // 班次概览卡片 _ShiftOverviewCard( date: dateFormat.format(info.date), shiftName: info.shiftName, shiftTime: info.shiftTime, memberCount: info.members.length + 1, instructionCount: info.instructions.length, ), const SizedBox(height: 20), // 值班长 _SectionHeader(title: '值班长', icon: Icons.star), const SizedBox(height: 8), _PersonCard( person: info.leader, isLeader: true, ), const SizedBox(height: 20), // 值班人员 _SectionHeader(title: '值班人员', icon: Icons.people), const SizedBox(height: 8), ...info.members.map((member) => Padding( padding: const EdgeInsets.only(bottom: 8), child: _PersonCard(person: member), )), const SizedBox(height: 20), // 调度指令台账 _SectionHeader( title: '调度指令台账', icon: Icons.assignment, trailing: Text( '${info.instructions.length} 条', style: TextStyle(fontSize: 13, color: Colors.grey.shade600), ), ), const SizedBox(height: 8), ...info.instructions.map((instruction) => Padding( padding: const EdgeInsets.only(bottom: 8), child: _InstructionCard(instruction: instruction), )), const SizedBox(height: 16), ], ), ); } } /// 班次概览卡片 class _ShiftOverviewCard extends StatelessWidget { final String date; final String shiftName; final String shiftTime; final int memberCount; final int instructionCount; const _ShiftOverviewCard({ required this.date, required this.shiftName, required this.shiftTime, required this.memberCount, required this.instructionCount, }); @override Widget build(BuildContext context) { final theme = Theme.of(context); final primary = theme.colorScheme.primary; return Card( elevation: 0, color: theme.colorScheme.primaryContainer.withAlpha(80), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), child: Padding( padding: const EdgeInsets.all(20), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.calendar_today, size: 18, color: primary), const SizedBox(width: 8), Text( date, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: primary, ), ), const SizedBox(width: 12), Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 3), decoration: BoxDecoration( color: primary.withAlpha(30), borderRadius: BorderRadius.circular(12), ), child: Text( shiftName, style: TextStyle(fontSize: 12, color: primary, fontWeight: FontWeight.w600), ), ), ], ), const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ _InfoColumn(icon: Icons.schedule, label: '值班时间', value: shiftTime), Container(width: 1, height: 40, color: Colors.grey.shade300), _InfoColumn(icon: Icons.people, label: '值班人数', value: '$memberCount 人'), Container(width: 1, height: 40, color: Colors.grey.shade300), _InfoColumn(icon: Icons.assignment, label: '调度指令', value: '$instructionCount 条'), ], ), ], ), ), ); } } class _InfoColumn extends StatelessWidget { final IconData icon; final String label; final String value; const _InfoColumn({required this.icon, required this.label, required this.value}); @override Widget build(BuildContext context) { return Column( children: [ Icon(icon, size: 20, color: Colors.grey.shade600), const SizedBox(height: 4), Text(label, style: TextStyle(fontSize: 11, color: Colors.grey.shade600)), const SizedBox(height: 2), Text(value, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600)), ], ); } } /// 段落标题 class _SectionHeader extends StatelessWidget { final String title; final IconData icon; final Widget? trailing; const _SectionHeader({required this.title, required this.icon, this.trailing}); @override Widget build(BuildContext context) { final theme = Theme.of(context); return Row( children: [ Icon(icon, size: 20, color: theme.colorScheme.primary), const SizedBox(width: 8), Text(title, style: theme.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600)), const Spacer(), if (trailing != null) trailing!, ], ); } } /// 值班人员卡片 class _PersonCard extends StatelessWidget { final DutyPerson person; final bool isLeader; const _PersonCard({required this.person, this.isLeader = false}); @override Widget build(BuildContext context) { final theme = Theme.of(context); return Card( elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), side: BorderSide( color: isLeader ? theme.colorScheme.primary.withAlpha(80) : Colors.grey.shade200, ), ), child: Padding( padding: const EdgeInsets.all(14), child: Row( children: [ CircleAvatar( radius: 22, backgroundColor: isLeader ? theme.colorScheme.primary.withAlpha(30) : Colors.grey.shade100, child: Text( person.name.substring(0, 1), style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: isLeader ? theme.colorScheme.primary : Colors.grey.shade700, ), ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( person.name, style: theme.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600), ), if (isLeader) ...[ const SizedBox(width: 6), Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 1), decoration: BoxDecoration( color: Colors.amber.shade100, borderRadius: BorderRadius.circular(4), ), child: Text( '值班长', style: TextStyle(fontSize: 10, color: Colors.amber.shade800), ), ), ], ], ), const SizedBox(height: 4), Text( person.role, style: TextStyle(fontSize: 12, color: Colors.grey.shade600), ), ], ), ), // 拨打电话按钮 Container( decoration: BoxDecoration( color: Colors.green.shade50, borderRadius: BorderRadius.circular(8), ), child: IconButton( icon: Icon(Icons.phone, size: 20, color: Colors.green.shade700), onPressed: () { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('拨打电话: ${person.phone}'), duration: const Duration(seconds: 2), ), ); }, ), ), ], ), ), ); } } /// 调度指令卡片 class _InstructionCard extends StatelessWidget { final DutyInstruction instruction; const _InstructionCard({required this.instruction}); @override Widget build(BuildContext context) { final theme = Theme.of(context); final statusColor = Color(instruction.status.color); return Card( elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), side: BorderSide(color: Colors.grey.shade200), ), child: Padding( padding: const EdgeInsets.all(14), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( instruction.title, style: theme.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600), ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration( color: statusColor.withAlpha(30), borderRadius: BorderRadius.circular(6), ), child: Text( instruction.status.label, style: TextStyle(fontSize: 11, color: statusColor, fontWeight: FontWeight.w500), ), ), ], ), const SizedBox(height: 8), Text( instruction.content, style: TextStyle(fontSize: 13, color: Colors.grey.shade700, height: 1.5), ), const SizedBox(height: 10), Row( children: [ Icon(Icons.access_time, size: 12, color: Colors.grey.shade500), const SizedBox(width: 4), Text( instruction.time, style: TextStyle(fontSize: 11, color: Colors.grey.shade500), ), const SizedBox(width: 16), Icon(Icons.person_outline, size: 12, color: Colors.grey.shade500), const SizedBox(width: 4), Text( instruction.issuer, style: TextStyle(fontSize: 11, color: Colors.grey.shade500), ), ], ), ], ), ), ); } }