Files
water-management-system/mobile-app/lib/features/revenue/pages/meter_reading_page.dart
T
bot_dev2 d9d72405c6 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状态管理
2026-06-15 08:52:55 +08:00

239 lines
8.4 KiB
Dart

import 'package:flutter/material.dart';
import '../models/meter_reading_model.dart';
import 'bill_list_page.dart';
/// 抄表页面(营收 Tab 入口,包含抄表列表和账单入口)
class MeterReadingPage extends StatefulWidget {
const MeterReadingPage({super.key});
@override
State<MeterReadingPage> createState() => _MeterReadingPageState();
}
class _MeterReadingPageState extends State<MeterReadingPage>
with AutomaticKeepAliveClientMixin {
List<MeterReadingModel> _readings = [];
bool _isLoading = true;
@override
bool get wantKeepAlive => true;
@override
void initState() {
super.initState();
_loadData();
}
void _loadData() {
Future.delayed(const Duration(milliseconds: 500), () {
setState(() {
_readings = _generateMockData();
_isLoading = false;
});
});
}
List<MeterReadingModel> _generateMockData() {
return [
MeterReadingModel(
id: '1', meterNo: 'WM-2024-001', userName: '张三',
address: '城东街道12号', previousReading: 1250.5,
status: 'pending', readingDate: DateTime.now(),
),
MeterReadingModel(
id: '2', meterNo: 'WM-2024-002', userName: '李四',
address: '城西大道88号', previousReading: 3420.0,
currentReading: 3456.0, status: 'completed',
readingDate: DateTime.now().subtract(const Duration(days: 1)),
reader: '王工',
),
MeterReadingModel(
id: '3', meterNo: 'WM-2024-003', userName: '王五',
address: '南区花园5栋', previousReading: 890.0,
status: 'pending', readingDate: DateTime.now(),
),
MeterReadingModel(
id: '4', meterNo: 'WM-2024-004', userName: '赵六',
address: '北区商铺16号', previousReading: 5600.0,
currentReading: 5780.0, status: 'completed',
readingDate: DateTime.now().subtract(const Duration(days: 2)),
reader: '李工',
),
MeterReadingModel(
id: '5', meterNo: 'WM-2024-005', userName: '孙七',
address: '中心路28号', previousReading: 2100.0,
status: 'abnormal', readingDate: DateTime.now(),
remark: '水表损坏',
),
];
}
Color _getStatusColor(String status) {
switch (status) {
case 'pending': return Colors.orange;
case 'completed': return Colors.green;
case 'abnormal': return Colors.red;
default: return Colors.grey;
}
}
String _getStatusText(String status) {
switch (status) {
case 'pending': return '待抄表';
case 'completed': return '已完成';
case 'abnormal': return '异常';
default: return status;
}
}
void _showReadingDialog(MeterReadingModel reading) {
final controller = TextEditingController();
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Text('抄表 - ${reading.userName}'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('表号: ${reading.meterNo}'),
Text('上次读数: ${reading.previousReading}'),
const SizedBox(height: 12),
TextField(
controller: controller,
keyboardType: const TextInputType.numberWithOptions(decimal: true),
decoration: const InputDecoration(
labelText: '当前读数',
hintText: '请输入当前水表读数',
),
),
],
),
actions: [
TextButton(onPressed: () => Navigator.pop(ctx), child: const Text('取消')),
ElevatedButton(
onPressed: () {
// TODO: 提交抄表数据
Navigator.pop(ctx);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('抄表数据已提交')),
);
},
child: const Text('提交'),
),
],
),
);
}
@override
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
appBar: AppBar(
title: const Text('营收管理'),
actions: [
IconButton(
icon: const Icon(Icons.receipt_long),
tooltip: '账单列表',
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (_) => const BillListPage()),
);
},
),
],
),
body: _isLoading
? const Center(child: CircularProgressIndicator())
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 统计卡片
Padding(
padding: const EdgeInsets.all(12),
child: Row(
children: [
_buildStatCard('待抄表', '${_readings.where((r) => r.status == 'pending').length}', Colors.orange),
const SizedBox(width: 8),
_buildStatCard('已完成', '${_readings.where((r) => r.status == 'completed').length}', Colors.green),
const SizedBox(width: 8),
_buildStatCard('异常', '${_readings.where((r) => r.status == 'abnormal').length}', Colors.red),
],
),
),
const Padding(
padding: EdgeInsets.symmetric(horizontal: 12),
child: Text('抄表任务', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
),
// 抄表列表
Expanded(
child: ListView.builder(
padding: const EdgeInsets.all(12),
itemCount: _readings.length,
itemBuilder: (context, index) {
final reading = _readings[index];
return Card(
margin: const EdgeInsets.only(bottom: 8),
child: ListTile(
leading: CircleAvatar(
backgroundColor: _getStatusColor(reading.status).withOpacity(0.1),
child: Icon(
Icons.speed,
color: _getStatusColor(reading.status),
),
),
title: Text(reading.userName),
subtitle: Text('${reading.meterNo} · ${reading.address}'),
trailing: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
_getStatusText(reading.status),
style: TextStyle(
fontSize: 12,
color: _getStatusColor(reading.status),
fontWeight: FontWeight.bold,
),
),
if (reading.status == 'pending')
TextButton(
onPressed: () => _showReadingDialog(reading),
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
minimumSize: const Size(0, 0),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
child: const Text('抄表', style: TextStyle(fontSize: 12)),
),
],
),
),
);
},
),
),
],
),
);
}
Widget _buildStatCard(String label, String count, Color color) {
return Expanded(
child: Card(
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
children: [
Text(count, style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: color)),
const SizedBox(height: 4),
Text(label, style: TextStyle(fontSize: 12, color: Colors.grey[600])),
],
),
),
),
);
}
}