| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389 |
- import 'package:flutter/material.dart';
- import 'package:intl/intl.dart';
- import '../../services/water_service.dart';
-
- /// 实时监测列表页面
- class MonitorPage extends StatefulWidget {
- const MonitorPage({super.key});
- @override
- State<MonitorPage> createState() => _MonitorPageState();
- }
-
- class _MonitorPageState extends State<MonitorPage> {
- final WaterService _service = WaterService.instance;
- List<MonitorItem> _items = [];
- bool _isLoading = true;
- String? _error;
- MonitorType? _filterType;
-
- @override
- void initState() {
- super.initState();
- _loadData();
- }
-
- Future<void> _loadData() async {
- setState(() {
- _isLoading = true;
- _error = null;
- });
- try {
- final data = await _service.getMonitorList();
- if (mounted) {
- setState(() {
- _items = data;
- _isLoading = false;
- });
- }
- } catch (e) {
- if (mounted) {
- setState(() {
- _error = e.toString();
- _isLoading = false;
- });
- }
- }
- }
-
- List<MonitorItem> get _filteredItems {
- if (_filterType == null) return _items;
- return _items.where((item) => item.type == _filterType).toList();
- }
-
- int _countByStatus(DeviceStatus status) {
- return _items.where((item) => item.status == status).length;
- }
-
- @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: Column(
- children: [
- // 状态概览栏
- if (!_isLoading && _error == null) _buildStatusBar(theme),
- // 类型筛选
- if (!_isLoading && _error == null) _buildFilterBar(theme),
- // 列表内容
- Expanded(
- child: _buildBody(theme),
- ),
- ],
- ),
- );
- }
-
- Widget _buildStatusBar(ThemeData theme) {
- final online = _countByStatus(DeviceStatus.online);
- final offline = _countByStatus(DeviceStatus.offline);
- final warning = _countByStatus(DeviceStatus.warning);
-
- return Container(
- padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
- color: theme.colorScheme.surfaceContainerHighest.withAlpha(100),
- child: Row(
- children: [
- _StatusBadge(label: '在线', count: online, color: Colors.green),
- const SizedBox(width: 16),
- _StatusBadge(label: '离线', count: offline, color: Colors.grey),
- const SizedBox(width: 16),
- _StatusBadge(label: '告警', count: warning, color: Colors.orange),
- const Spacer(),
- Text(
- '共 ${_items.length} 个监测点',
- style: theme.textTheme.bodySmall,
- ),
- ],
- ),
- );
- }
-
- Widget _buildFilterBar(ThemeData theme) {
- return SingleChildScrollView(
- scrollDirection: Axis.horizontal,
- padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
- child: Row(
- children: [
- _FilterChip(
- label: '全部',
- selected: _filterType == null,
- onTap: () => setState(() => _filterType = null),
- ),
- const SizedBox(width: 8),
- ...MonitorType.values.map((type) {
- return Padding(
- padding: const EdgeInsets.only(right: 8),
- child: _FilterChip(
- label: type.label,
- selected: _filterType == type,
- onTap: () => setState(() => _filterType = type),
- ),
- );
- }),
- ],
- ),
- );
- }
-
- 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: 8),
- Text(_error!, style: theme.textTheme.bodySmall),
- const SizedBox(height: 16),
- FilledButton.icon(
- onPressed: _loadData,
- icon: const Icon(Icons.refresh),
- label: const Text('重试'),
- ),
- ],
- ),
- );
- }
-
- final items = _filteredItems;
- if (items.isEmpty) {
- return Center(
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- Icon(Icons.sensors_off, 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: items.length,
- itemBuilder: (context, index) => _MonitorCard(item: items[index]),
- ),
- );
- }
- }
-
- /// 监测点卡片
- class _MonitorCard extends StatelessWidget {
- final MonitorItem item;
- const _MonitorCard({required this.item});
-
- @override
- Widget build(BuildContext context) {
- final theme = Theme.of(context);
- final statusColor = Color(item.status.color);
- final timeFormat = DateFormat('HH:mm:ss');
-
- return Card(
- margin: const EdgeInsets.only(bottom: 10),
- elevation: 0,
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(12),
- side: BorderSide(color: theme.colorScheme.outlineVariant.withAlpha(100)),
- ),
- child: Padding(
- padding: const EdgeInsets.all(16),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- // 标题行
- Row(
- children: [
- Container(
- width: 8,
- height: 8,
- decoration: BoxDecoration(
- shape: BoxShape.circle,
- color: statusColor,
- ),
- ),
- const SizedBox(width: 8),
- Expanded(
- child: Text(
- item.name,
- style: theme.textTheme.titleMedium?.copyWith(
- fontWeight: FontWeight.w600,
- ),
- ),
- ),
- Container(
- padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
- decoration: BoxDecoration(
- color: statusColor.withAlpha(30),
- borderRadius: BorderRadius.circular(4),
- ),
- child: Text(
- item.type.label,
- style: TextStyle(fontSize: 11, color: statusColor),
- ),
- ),
- const SizedBox(width: 8),
- Container(
- padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
- decoration: BoxDecoration(
- color: statusColor.withAlpha(30),
- borderRadius: BorderRadius.circular(4),
- ),
- child: Text(
- item.statusLabel,
- style: TextStyle(fontSize: 11, color: statusColor),
- ),
- ),
- ],
- ),
- const SizedBox(height: 12),
- // 数据行
- if (item.status != DeviceStatus.offline) ...[
- Wrap(
- spacing: 16,
- runSpacing: 8,
- children: [
- if (item.flow != null)
- _DataChip(icon: Icons.water_drop, label: '流量', value: '${item.flow!.toStringAsFixed(1)} m³/h'),
- if (item.pressure != null)
- _DataChip(icon: Icons.speed, label: '压力', value: '${item.pressure!.toStringAsFixed(2)} MPa'),
- if (item.level != null)
- _DataChip(icon: Icons.straighten, label: '液位', value: '${item.level!.toStringAsFixed(1)} m'),
- if (item.ph != null)
- _DataChip(icon: Icons.science, label: 'pH', value: item.ph!.toStringAsFixed(1)),
- if (item.turbidity != null)
- _DataChip(icon: Icons.blur_on, label: '浊度', value: '${item.turbidity!.toStringAsFixed(1)} NTU'),
- if (item.chlorine != null)
- _DataChip(icon: Icons.bubble_chart, label: '余氯', value: '${item.chlorine!.toStringAsFixed(2)} mg/L'),
- ],
- ),
- const SizedBox(height: 8),
- ] else
- Padding(
- padding: const EdgeInsets.symmetric(vertical: 8),
- child: Text(
- '设备离线,暂无数据',
- style: TextStyle(color: Colors.grey.shade500, fontSize: 13),
- ),
- ),
- // 更新时间
- Row(
- mainAxisAlignment: MainAxisAlignment.end,
- children: [
- Icon(Icons.access_time, size: 12, color: Colors.grey.shade500),
- const SizedBox(width: 4),
- Text(
- '更新于 ${timeFormat.format(item.updateTime)}',
- style: TextStyle(fontSize: 11, color: Colors.grey.shade500),
- ),
- ],
- ),
- ],
- ),
- ),
- );
- }
- }
-
- class _DataChip extends StatelessWidget {
- final IconData icon;
- final String label;
- final String value;
-
- const _DataChip({required this.icon, required this.label, required this.value});
-
- @override
- Widget build(BuildContext context) {
- return Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- Icon(icon, size: 14, color: Colors.grey.shade600),
- const SizedBox(width: 4),
- Text(
- '$label: ',
- style: TextStyle(fontSize: 12, color: Colors.grey.shade600),
- ),
- Text(
- value,
- style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600),
- ),
- ],
- );
- }
- }
-
- class _StatusBadge extends StatelessWidget {
- final String label;
- final int count;
- final Color color;
-
- const _StatusBadge({required this.label, required this.count, required this.color});
-
- @override
- Widget build(BuildContext context) {
- return Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- Container(
- width: 8,
- height: 8,
- decoration: BoxDecoration(shape: BoxShape.circle, color: color),
- ),
- const SizedBox(width: 4),
- Text('$label $count', style: const TextStyle(fontSize: 13)),
- ],
- );
- }
- }
-
- class _FilterChip extends StatelessWidget {
- final String label;
- final bool selected;
- final VoidCallback onTap;
-
- const _FilterChip({required this.label, required this.selected, required this.onTap});
-
- @override
- Widget build(BuildContext context) {
- final theme = Theme.of(context);
- return InkWell(
- onTap: onTap,
- borderRadius: BorderRadius.circular(16),
- child: Container(
- padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6),
- decoration: BoxDecoration(
- color: selected ? theme.colorScheme.primaryContainer : Colors.grey.shade100,
- borderRadius: BorderRadius.circular(16),
- border: selected
- ? Border.all(color: theme.colorScheme.primary, width: 1)
- : null,
- ),
- child: Text(
- label,
- style: TextStyle(
- fontSize: 13,
- color: selected ? theme.colorScheme.primary : Colors.grey.shade700,
- fontWeight: selected ? FontWeight.w600 : FontWeight.normal,
- ),
- ),
- ),
- );
- }
- }
|