| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444 |
- import 'dart:async';
- import 'package:flutter/material.dart';
- import '../../services/revenue_service.dart';
-
- /// 抄表录入页面
- class MeterReadingPage extends StatefulWidget {
- const MeterReadingPage({super.key});
-
- @override
- State<MeterReadingPage> createState() => _MeterReadingPageState();
- }
-
- class _MeterReadingPageState extends State<MeterReadingPage> {
- final RevenueService _service = RevenueService.instance;
- final _searchController = TextEditingController();
- Timer? _debounce;
- List<CustomerInfo> _customers = [];
- bool _searching = false;
- CustomerInfo? _selectedCustomer;
-
- // 录入表单
- final _readingController = TextEditingController();
- final _remarkController = TextEditingController();
- final List<String> _photos = [];
- bool _submitting = false;
-
- @override
- void initState() {
- super.initState();
- _loadInitialCustomers();
- }
-
- @override
- void dispose() {
- _searchController.dispose();
- _readingController.dispose();
- _remarkController.dispose();
- _debounce?.cancel();
- super.dispose();
- }
-
- Future<void> _loadInitialCustomers() async {
- setState(() => _searching = true);
- final customers = await _service.searchCustomers('');
- if (mounted) {
- setState(() {
- _customers = customers;
- _searching = false;
- });
- }
- }
-
- void _onSearchChanged(String query) {
- _debounce?.cancel();
- _debounce = Timer(const Duration(milliseconds: 500), () async {
- setState(() => _searching = true);
- final results = await _service.searchCustomers(query);
- if (mounted) {
- setState(() {
- _customers = results;
- _searching = false;
- });
- }
- });
- }
-
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- appBar: AppBar(
- title: const Text('抄表录入'),
- ),
- body: _selectedCustomer != null
- ? _buildReadingForm()
- : _buildCustomerList(),
- );
- }
-
- Widget _buildCustomerList() {
- return Column(
- children: [
- // 搜索栏
- Padding(
- padding: const EdgeInsets.all(16),
- child: TextField(
- controller: _searchController,
- decoration: InputDecoration(
- hintText: '搜索用户姓名、表号或地址',
- prefixIcon: const Icon(Icons.search),
- suffixIcon: _searchController.text.isNotEmpty
- ? IconButton(
- icon: const Icon(Icons.clear),
- onPressed: () {
- _searchController.clear();
- _loadInitialCustomers();
- },
- )
- : null,
- border: OutlineInputBorder(
- borderRadius: BorderRadius.circular(12),
- ),
- filled: true,
- ),
- onChanged: _onSearchChanged,
- ),
- ),
- // 用户列表
- Expanded(
- child: _searching
- ? const Center(child: CircularProgressIndicator())
- : _customers.isEmpty
- ? Center(
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- Icon(Icons.search_off,
- size: 64, color: Colors.grey.shade300),
- const SizedBox(height: 16),
- const Text('未找到匹配用户',
- style: TextStyle(color: Colors.grey)),
- ],
- ),
- )
- : ListView.builder(
- padding: const EdgeInsets.symmetric(horizontal: 12),
- itemCount: _customers.length,
- itemBuilder: (ctx, i) => _CustomerTile(
- customer: _customers[i],
- onTap: () => setState(
- () => _selectedCustomer = _customers[i]),
- ),
- ),
- ),
- ],
- );
- }
-
- Widget _buildReadingForm() {
- final customer = _selectedCustomer!;
- return SingleChildScrollView(
- padding: const EdgeInsets.all(16),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- // 用户信息卡
- Card(
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(12)),
- color: Colors.blue.shade50,
- child: Padding(
- padding: const EdgeInsets.all(16),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(
- children: [
- const Icon(Icons.person, color: Colors.blue),
- const SizedBox(width: 8),
- Expanded(
- child: Text(
- customer.name,
- style: const TextStyle(
- fontSize: 16, fontWeight: FontWeight.w600),
- ),
- ),
- TextButton.icon(
- onPressed: () => setState(() {
- _selectedCustomer = null;
- _readingController.clear();
- _remarkController.clear();
- _photos.clear();
- }),
- icon: const Icon(Icons.arrow_back, size: 16),
- label: const Text('切换用户'),
- ),
- ],
- ),
- const Divider(),
- _FormInfoRow(label: '地址', value: customer.address),
- _FormInfoRow(label: '水表号', value: customer.meterId),
- _FormInfoRow(label: '水表型号', value: customer.meterModel),
- _FormInfoRow(label: '联系电话', value: customer.phone),
- ],
- ),
- ),
- ),
- const SizedBox(height: 16),
-
- // 读数录入
- Card(
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(12)),
- child: Padding(
- padding: const EdgeInsets.all(16),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(
- children: [
- const Icon(Icons.speed,
- size: 20, color: Colors.orange),
- const SizedBox(width: 8),
- Text('表计读数',
- style: Theme.of(context).textTheme.titleMedium),
- ],
- ),
- const SizedBox(height: 16),
- TextField(
- controller: _readingController,
- keyboardType:
- const TextInputType.numberWithOptions(decimal: true),
- decoration: InputDecoration(
- labelText: '当前读数 (m³)',
- hintText: '请输入水表当前读数',
- border: OutlineInputBorder(
- borderRadius: BorderRadius.circular(8),
- ),
- prefixIcon: const Icon(Icons.numbers),
- ),
- ),
- const SizedBox(height: 16),
- TextField(
- controller: _remarkController,
- maxLines: 2,
- decoration: InputDecoration(
- labelText: '备注(可选)',
- hintText: '如水表异常、更换水表等',
- border: OutlineInputBorder(
- borderRadius: BorderRadius.circular(8),
- ),
- ),
- ),
- ],
- ),
- ),
- ),
- const SizedBox(height: 16),
-
- // 拍照记录
- Card(
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(12)),
- child: Padding(
- padding: const EdgeInsets.all(16),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(
- children: [
- const Icon(Icons.camera_alt,
- size: 20, color: Colors.purple),
- const SizedBox(width: 8),
- Text('拍照记录',
- style: Theme.of(context).textTheme.titleMedium),
- const SizedBox(width: 8),
- Text(
- '(${_photos.length}/3)',
- style: TextStyle(
- fontSize: 12, color: Colors.grey.shade600),
- ),
- ],
- ),
- const SizedBox(height: 12),
- Wrap(
- spacing: 8,
- runSpacing: 8,
- children: [
- ..._photos.asMap().entries.map(
- (entry) => Stack(
- children: [
- Container(
- width: 80,
- height: 80,
- decoration: BoxDecoration(
- color: Colors.grey.shade200,
- borderRadius: BorderRadius.circular(8),
- ),
- child: const Icon(Icons.image,
- color: Colors.grey, size: 36),
- ),
- Positioned(
- top: -4,
- right: -4,
- child: GestureDetector(
- onTap: () => setState(
- () => _photos.removeAt(entry.key)),
- child: Container(
- width: 22,
- height: 22,
- decoration: const BoxDecoration(
- color: Colors.red,
- shape: BoxShape.circle,
- ),
- child: const Icon(Icons.close,
- size: 14, color: Colors.white),
- ),
- ),
- ),
- ],
- ),
- ),
- if (_photos.length < 3)
- GestureDetector(
- onTap: () => setState(() =>
- _photos.add('meter_photo_${_photos.length + 1}')),
- child: Container(
- width: 80,
- height: 80,
- decoration: BoxDecoration(
- border: Border.all(
- color: Colors.purple, width: 1.5),
- borderRadius: BorderRadius.circular(8),
- ),
- child: const Icon(Icons.add_a_photo,
- color: Colors.purple, size: 28),
- ),
- ),
- ],
- ),
- ],
- ),
- ),
- ),
- const SizedBox(height: 24),
-
- // 提交按钮
- SizedBox(
- width: double.infinity,
- child: FilledButton.icon(
- onPressed: _submitting ? null : _submitReading,
- icon: _submitting
- ? const SizedBox(
- width: 16,
- height: 16,
- child: CircularProgressIndicator(
- strokeWidth: 2, color: Colors.white))
- : const Icon(Icons.check, size: 18),
- label: Text(_submitting ? '提交中...' : '提交读数'),
- ),
- ),
- ],
- ),
- );
- }
-
- Future<void> _submitReading() async {
- final reading = double.tryParse(_readingController.text);
- if (reading == null || reading <= 0) {
- ScaffoldMessenger.of(context).showSnackBar(
- const SnackBar(
- content: Text('请输入有效的表计读数'),
- backgroundColor: Colors.red,
- ),
- );
- return;
- }
-
- setState(() => _submitting = true);
- final success = await _service.submitMeterReading(
- meterId: _selectedCustomer!.meterId,
- reading: reading,
- photoPath: _photos.isEmpty ? null : _photos.first,
- remark: _remarkController.text.isEmpty ? null : _remarkController.text,
- );
- if (mounted) {
- setState(() => _submitting = false);
- if (success) {
- ScaffoldMessenger.of(context).showSnackBar(
- SnackBar(
- content: Text('${_selectedCustomer!.name} 的读数已提交: $reading m³'),
- backgroundColor: Colors.green,
- ),
- );
- setState(() {
- _selectedCustomer = null;
- _readingController.clear();
- _remarkController.clear();
- _photos.clear();
- });
- }
- }
- }
- }
-
- class _CustomerTile extends StatelessWidget {
- final CustomerInfo customer;
- final VoidCallback onTap;
-
- const _CustomerTile({required this.customer, required this.onTap});
-
- @override
- Widget build(BuildContext context) {
- return Card(
- margin: const EdgeInsets.only(bottom: 8),
- shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
- child: ListTile(
- onTap: onTap,
- shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
- leading: CircleAvatar(
- backgroundColor: Colors.blue.shade50,
- child: Text(customer.name[0],
- style: TextStyle(color: Colors.blue.shade700)),
- ),
- title: Text(customer.name,
- style: const TextStyle(fontWeight: FontWeight.w500)),
- subtitle: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text('${customer.address} | ${customer.meterId}',
- style: const TextStyle(fontSize: 12)),
- ],
- ),
- trailing: const Icon(Icons.chevron_right, color: Colors.grey),
- ),
- );
- }
- }
-
- class _FormInfoRow extends StatelessWidget {
- final String label;
- final String value;
-
- const _FormInfoRow({required this.label, required this.value});
-
- @override
- Widget build(BuildContext context) {
- return Padding(
- padding: const EdgeInsets.symmetric(vertical: 3),
- child: Row(
- children: [
- SizedBox(
- width: 70,
- child: Text(label,
- style: TextStyle(fontSize: 13, color: Colors.grey.shade600)),
- ),
- Expanded(
- child: Text(value,
- style: const TextStyle(fontSize: 13)),
- ),
- ],
- ),
- );
- }
- }
|