feat(mobile): #24 Flutter三合一APP骨架搭建

- Flutter 3.x 项目初始化(pubspec.yaml + 目录结构)
- 核心依赖:dio/go_router/provider/shared_preferences/hive/geolocator
- 统一登录页 + Token管理(TokenService + AuthInterceptor)
- 底部Tab三合一导航(供水/巡检/营收)
- 供水管理Tab:监测数据列表(MonitorListPage)
- 巡检Tab:任务列表+状态筛选+进度展示(PatrolTaskListPage)
- 营收Tab:抄表页面+账单列表(MeterReadingPage/BillListPage)
- 消息推送服务(PushService)
- GPS定位服务(LocationService)
- 拍照/相册服务(CameraService)
- 离线缓存服务(CacheService)
- Android/iOS打包配置+权限声明
- GoRouter路由+Auth认证守卫
- Provider状态管理
This commit is contained in:
2026-06-15 08:52:55 +08:00
parent 37bcc78eee
commit d9d72405c6
41 changed files with 3014 additions and 0 deletions
@@ -0,0 +1,194 @@
import 'package:flutter/material.dart';
import '../models/monitor_data_model.dart';
/// 供水监测数据列表页面
class MonitorListPage extends StatefulWidget {
const MonitorListPage({super.key});
@override
State<MonitorListPage> createState() => _MonitorListPageState();
}
class _MonitorListPageState extends State<MonitorListPage>
with AutomaticKeepAliveClientMixin {
List<MonitorDataModel> _monitorData = [];
bool _isLoading = true;
@override
bool get wantKeepAlive => true;
@override
void initState() {
super.initState();
_loadData();
}
void _loadData() {
// 模拟数据加载
Future.delayed(const Duration(milliseconds: 500), () {
setState(() {
_monitorData = _generateMockData();
_isLoading = false;
});
});
}
List<MonitorDataModel> _generateMockData() {
final stations = [
('城东加压站', 'ST-001'),
('城西水厂', 'ST-002'),
('南区配水站', 'ST-003'),
('北区调蓄池', 'ST-004'),
('中心泵站', 'ST-005'),
('开发区监测点', 'ST-006'),
('高新区水厂', 'ST-007'),
('工业园加压站', 'ST-008'),
];
return stations.map((station) {
return MonitorDataModel(
id: station.$2,
stationName: station.$1,
stationCode: station.$2,
pressure: 0.2 + (station.$2.hashCode % 30) / 100.0,
flow: 100 + (station.$2.hashCode % 500).toDouble(),
quality: 95 + (station.$2.hashCode % 5).toDouble(),
status: station.$2.hashCode % 7 == 0 ? 'warning' : 'normal',
updateTime: DateTime.now().subtract(
Duration(minutes: station.$2.hashCode % 60),
),
);
}).toList();
}
Color _getStatusColor(String status) {
switch (status) {
case 'normal':
return Colors.green;
case 'warning':
return Colors.orange;
case 'error':
return Colors.red;
default:
return Colors.grey;
}
}
@override
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
appBar: AppBar(
title: const Text('供水监测'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () {
setState(() {
_isLoading = true;
});
_loadData();
},
),
],
),
body: _isLoading
? const Center(child: CircularProgressIndicator())
: RefreshIndicator(
onRefresh: () async {
setState(() {
_isLoading = true;
});
_loadData();
},
child: ListView.builder(
padding: const EdgeInsets.all(12),
itemCount: _monitorData.length,
itemBuilder: (context, index) {
final item = _monitorData[index];
return _buildMonitorCard(item);
},
),
),
);
}
Widget _buildMonitorCard(MonitorDataModel item) {
return Card(
margin: const EdgeInsets.only(bottom: 12),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(Icons.water_drop, color: Color(0xFF1976D2), size: 20),
const SizedBox(width: 8),
Expanded(
child: Text(
item.stationName,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: _getStatusColor(item.status).withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: _getStatusColor(item.status),
),
),
child: Text(
item.status == 'normal' ? '正常' : '告警',
style: TextStyle(
fontSize: 12,
color: _getStatusColor(item.status),
),
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
_buildDataItem('水压', '${item.pressure.toStringAsFixed(2)} MPa'),
_buildDataItem('流量', '${item.flow.toStringAsFixed(1)} m³/h'),
_buildDataItem('水质', '${item.quality.toStringAsFixed(1)}%'),
],
),
const SizedBox(height: 8),
Text(
'更新时间: ${item.updateTime.month}/${item.updateTime.day} '
'${item.updateTime.hour.toString().padLeft(2, '0')}:'
'${item.updateTime.minute.toString().padLeft(2, '0')}',
style: TextStyle(fontSize: 12, color: Colors.grey[500]),
),
],
),
),
);
}
Widget _buildDataItem(String label, String value) {
return Expanded(
child: Column(
children: [
Text(label, style: TextStyle(fontSize: 12, color: Colors.grey[600])),
const SizedBox(height: 4),
Text(
value,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
],
),
);
}
}