import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import '../../services/water_service.dart'; /// 水质查看页面 class QualityPage extends StatefulWidget { const QualityPage({super.key}); @override State createState() => _QualityPageState(); } class _QualityPageState extends State with SingleTickerProviderStateMixin { final WaterService _service = WaterService.instance; List _samples = []; bool _isLoading = true; String? _error; late TabController _tabController; static const _categories = [ QualityCategory.rawWater, QualityCategory.factoryWater, QualityCategory.endWater, ]; @override void initState() { super.initState(); _tabController = TabController(length: _categories.length, vsync: this); _tabController.addListener(() { if (!_tabController.indexIsChanging) { setState(() {}); // trigger rebuild for filtered data } }); _loadData(); } @override void dispose() { _tabController.dispose(); super.dispose(); } Future _loadData() async { setState(() { _isLoading = true; _error = null; }); try { final data = await _service.getQualityData(); if (mounted) { setState(() { _samples = data; _isLoading = false; }); } } catch (e) { if (mounted) { setState(() { _error = e.toString(); _isLoading = false; }); } } } List get _currentSamples { final category = _categories[_tabController.index]; return _samples.where((s) => s.category == category).toList(); } @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, ), ], bottom: TabBar( controller: _tabController, tabs: _categories.map((c) => Tab(text: c.label)).toList(), ), ), 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 samples = _currentSamples; if (samples.isEmpty) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.science_outlined, size: 48, color: Colors.grey.shade400), const SizedBox(height: 16), Text('暂无水质数据', style: theme.textTheme.titleMedium), ], ), ); } return RefreshIndicator( onRefresh: _loadData, child: ListView.builder( padding: const EdgeInsets.all(12), itemCount: samples.length, itemBuilder: (context, index) => _QualityCard(sample: samples[index]), ), ); } } /// 水质数据卡片 class _QualityCard extends StatelessWidget { final QualitySample sample; const _QualityCard({required this.sample}); @override Widget build(BuildContext context) { final theme = Theme.of(context); final dateFormat = DateFormat('MM-dd HH:mm'); final indicators = sample.getIndicators(); final allCompliant = sample.isCompliant; final statusColor = allCompliant ? Colors.green : Colors.red; return Card( margin: const EdgeInsets.only(bottom: 12), elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), side: BorderSide( color: allCompliant ? Colors.grey.shade200 : Colors.red.withAlpha(100), ), ), child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 标题行 Row( children: [ Icon( allCompliant ? Icons.check_circle : Icons.error, size: 20, color: statusColor, ), const SizedBox(width: 8), Expanded( child: Text( sample.source, style: theme.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600), ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration( color: statusColor.withAlpha(25), borderRadius: BorderRadius.circular(6), ), child: Text( allCompliant ? '达标' : '不达标', style: TextStyle( fontSize: 12, color: statusColor, fontWeight: FontWeight.w600, ), ), ), ], ), const SizedBox(height: 6), // 采样时间 Row( children: [ Icon(Icons.access_time, size: 12, color: Colors.grey.shade500), const SizedBox(width: 4), Text( '采样时间: ${dateFormat.format(sample.sampleTime)}', style: TextStyle(fontSize: 11, color: Colors.grey.shade500), ), ], ), const SizedBox(height: 14), // 指标表格 Container( decoration: BoxDecoration( color: Colors.grey.shade50, borderRadius: BorderRadius.circular(8), ), child: Column( children: [ // 表头 Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: Colors.grey.shade100, borderRadius: const BorderRadius.vertical(top: Radius.circular(8)), ), child: Row( children: [ _TableHeaderCell(text: '检测指标', flex: 2), _TableHeaderCell(text: '检测值', flex: 2), _TableHeaderCell(text: '标准值', flex: 2), _TableHeaderCell(text: '结果', flex: 1), ], ), ), // 数据行 ...indicators.map((indicator) => Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), decoration: BoxDecoration( border: Border( bottom: BorderSide(color: Colors.grey.shade200, width: 0.5), ), ), child: Row( children: [ Expanded( flex: 2, child: Text( indicator.name, style: const TextStyle(fontSize: 12), ), ), Expanded( flex: 2, child: Text( indicator.value, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500), ), ), Expanded( flex: 2, child: Text( indicator.standard, style: TextStyle(fontSize: 11, color: Colors.grey.shade600), ), ), Expanded( flex: 1, child: Icon( indicator.isCompliant ? Icons.check : Icons.close, size: 16, color: indicator.isCompliant ? Colors.green : Colors.red, ), ), ], ), )), ], ), ), // 不达标提示 if (!allCompliant) ...[ const SizedBox(height: 12), Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: Colors.red.shade50, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.red.shade200), ), child: Row( children: [ Icon(Icons.warning, size: 16, color: Colors.red.shade700), const SizedBox(width: 8), Expanded( child: Text( '部分指标超标,请关注并及时处理', style: TextStyle(fontSize: 12, color: Colors.red.shade700), ), ), ], ), ), ], ], ), ), ); } } class _TableHeaderCell extends StatelessWidget { final String text; final int flex; const _TableHeaderCell({required this.text, required this.flex}); @override Widget build(BuildContext context) { return Expanded( flex: flex, child: Text( text, style: TextStyle(fontSize: 11, color: Colors.grey.shade700, fontWeight: FontWeight.w600), ), ); } }