import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../core/theme/app_colors.dart'; import '../shell/nav_bar_config.dart'; import '../../core/utils/responsive.dart'; import '../../shared/widgets/form_section.dart'; import '../../shared/widgets/action_bar.dart'; import '../../core/i18n/app_localizations.dart'; class OutingLogCreatePage extends ConsumerStatefulWidget { const OutingLogCreatePage({super.key}); @override ConsumerState createState() => _OutingLogCreatePageState(); } class _OutingLogCreatePageState extends ConsumerState { final _customerCtrl = TextEditingController(); final _summaryCtrl = TextEditingController(); final _planCtrl = TextEditingController(); // GPS 模拟 String _gpsAddress = '深圳市南山区科技园南路88号'; final double _gpsLat = 22.5431; final double _gpsLng = 113.9532; double _gpsAccuracy = 15.0; bool _gpsFailed = false; // 客户联想 final List _mockCustomers = [ '华软科技', '云创数据', '数据引力', '天诚科技', '博思软件', '智云科技', '恒通信息', '创新无限', ]; List _customerSuggestions = []; bool _showCustomerSuggestions = false; String? _selectedCustomer; // 客户联系人 final Map>> _mockContacts = { '华软科技': [ {'name': '赵经理', 'phone': '13800138001', 'position': 'IT经理'}, {'name': '李主管', 'phone': '13800138002', 'position': '采购主管'}, ], '云创数据': [ {'name': '陈经理', 'phone': '13800138003', 'position': '技术总监'}, ], '数据引力': [ {'name': '孙总', 'phone': '13800138004', 'position': '总经理'}, ], '天诚科技': [ {'name': '周主任', 'phone': '13800138005', 'position': '办公室主任'}, ], }; Map? _selectedContact; // 照片 final List _photos = []; static const int _maxPhotos = 9; @override void dispose() { _customerCtrl.dispose(); _summaryCtrl.dispose(); _planCtrl.dispose(); super.dispose(); } void _onCustomerChanged(String value) { setState(() { _selectedCustomer = null; _selectedContact = null; if (value.isEmpty) { _customerSuggestions = []; _showCustomerSuggestions = false; } else { _customerSuggestions = _mockCustomers .where((c) => c.contains(value)) .toList(); _showCustomerSuggestions = _customerSuggestions.isNotEmpty; } }); } void _selectCustomer(String customer) { setState(() { _selectedCustomer = customer; _customerCtrl.text = customer; _showCustomerSuggestions = false; _selectedContact = null; }); } Future _pickContact() async { if (_selectedCustomer == null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('请先选择客户名称')), ); return; } final contacts = _mockContacts[_selectedCustomer]; if (contacts == null || contacts.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('该客户暂无联系人')), ); return; } final result = await showDialog>( context: context, builder: (ctx) => SimpleDialog( title: const Text('选择联系人'), children: contacts .map( (c) => SimpleDialogOption( onPressed: () => Navigator.pop(ctx, c), child: Text('${c['name']} ${c['position']} ${c['phone']}'), ), ) .toList(), ), ); if (result != null) { setState(() => _selectedContact = result); } } Future _takePhoto() async { if (_photos.length >= _maxPhotos) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('最多拍摄9张照片')), ); return; } final idx = _photos.length + 1; setState(() { _photos.add('photo_placeholder_$idx'); }); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( '模拟拍照:已拍摄第 $idx 张照片(含水印:${DateTime.now().toString().substring(0, 19)} | $_gpsLat°N, $_gpsLng°E)', ), ), ); } } void _removePhoto(int index) { setState(() => _photos.removeAt(index)); } Future _simulateGps() async { setState(() { _gpsFailed = false; _gpsAddress = '深圳市南山区科技园南路88号'; _gpsAccuracy = 15.0; }); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('GPS定位成功')), ); } Future _saveDraft() async { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('已保存为草稿')), ); } } Future _submit() async { if (_gpsFailed) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('无法获取GPS定位,请检查位置权限')), ); return; } if (_gpsAddress.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('GPS定位中,请稍后')), ); return; } if (_photos.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('请至少拍摄一张现场照片')), ); return; } if (_summaryCtrl.text.trim().isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('请填写工作总结')), ); return; } if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('外勤日志提交成功')), ); context.pop(); } } @override Widget build(BuildContext context) { final r = ResponsiveHelper.of(context); final l10n = AppLocalizations.of(context); ref .read(navBarConfigProvider.notifier) .update( NavBarConfig( title: l10n.get('outingLogCreate'), showBack: true, onBack: () => context.pop(), ), ); return Column( children: [ Expanded( child: Align( alignment: Alignment.topCenter, child: ConstrainedBox( constraints: BoxConstraints(maxWidth: r.formMaxWidth), child: SingleChildScrollView( padding: const EdgeInsets.symmetric(vertical: 8), child: Column( children: [ _buildGpsSection(), const SizedBox(height: 8), FormSection( title: l10n.get('customerInfo'), children: [ Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 4), decoration: BoxDecoration( color: AppColors.bgPage, borderRadius: BorderRadius.circular(4), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ TextField( controller: _customerCtrl, decoration: InputDecoration( hintText: l10n.get('searchCustomer'), hintStyle: TextStyle( fontSize: 14, color: AppColors.textPlaceholder, ), border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 10), isDense: true, ), style: const TextStyle( fontSize: 14, color: AppColors.textPrimary, ), onChanged: _onCustomerChanged, ), if (_showCustomerSuggestions) ..._customerSuggestions.map( (s) => GestureDetector( onTap: () => _selectCustomer(s), child: Container( padding: const EdgeInsets.symmetric( vertical: 8, horizontal: 4), decoration: const BoxDecoration( border: Border( top: BorderSide( color: AppColors.border), ), ), child: Text( s, style: const TextStyle( fontSize: 14, color: AppColors.textPrimary, ), ), ), ), ), ], ), ), const SizedBox(height: 12), GestureDetector( onTap: _pickContact, child: Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 12), decoration: BoxDecoration( color: AppColors.bgPage, borderRadius: BorderRadius.circular(4), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( _selectedContact != null ? '${_selectedContact!['name']} ${_selectedContact!['phone']}' : l10n.get('selectContactHint'), style: TextStyle( fontSize: 14, color: _selectedContact != null ? AppColors.textPrimary : AppColors.textPlaceholder, ), ), const Icon(Icons.chevron_right, size: 14, color: AppColors.textPlaceholder), ], ), ), ), ], ), const SizedBox(height: 8), FormSection( title: l10n.get('workSummary'), children: [ Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppColors.bgPage, borderRadius: BorderRadius.circular(4), ), child: TextField( controller: _summaryCtrl, maxLines: 5, decoration: const InputDecoration( hintText: '请填写本次外勤工作总结(必填)', hintStyle: TextStyle( fontSize: 14, color: AppColors.textPlaceholder, ), border: InputBorder.none, contentPadding: EdgeInsets.zero, isDense: true, ), style: const TextStyle( fontSize: 14, color: AppColors.textPrimary, ), ), ), ], ), const SizedBox(height: 8), FormSection( title: l10n.get('followUp'), children: [ Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 4), decoration: BoxDecoration( color: AppColors.bgPage, borderRadius: BorderRadius.circular(4), ), child: TextField( controller: _planCtrl, decoration: const InputDecoration( hintText: '后续推进计划(选填)', hintStyle: TextStyle( fontSize: 14, color: AppColors.textPlaceholder, ), border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 10), isDense: true, ), style: const TextStyle( fontSize: 14, color: AppColors.textPrimary, ), ), ), ], ), const SizedBox(height: 8), FormSection( title: l10n.get('sitePhotos'), actionText: _photos.length >= _maxPhotos ? l10n.get('limitReached') : l10n.get('takePhoto'), showAction: _photos.length < _maxPhotos, actionIcon: Icons.camera_alt_outlined, onActionTap: _photos.length >= _maxPhotos ? null : _takePhoto, children: [ if (_photos.isEmpty) GestureDetector( onTap: _takePhoto, child: Container( height: 100, decoration: BoxDecoration( color: AppColors.bgPage, borderRadius: BorderRadius.circular(4), border: Border.all( color: AppColors.border, width: 1), ), child: const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.camera_alt_outlined, size: 32, color: AppColors.primary), SizedBox(height: 4), Text( '点击拍照(至少1张)', style: TextStyle( fontSize: 12, color: AppColors.textPlaceholder, ), ), ], ), ), ), ) else Wrap( spacing: 8, runSpacing: 8, children: [ ..._photos.asMap().entries.map( (entry) => _buildPhotoThumbnail( entry.key, entry.value), ), if (_photos.length < _maxPhotos) GestureDetector( onTap: _takePhoto, child: Container( width: 80, height: 80, decoration: BoxDecoration( color: AppColors.bgPage, borderRadius: BorderRadius.circular(4), border: Border.all( color: AppColors.border), ), child: const Icon(Icons.add, size: 28, color: AppColors.primary), ), ), ], ), const SizedBox(height: 8), Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: AppColors.primaryLight, borderRadius: BorderRadius.circular(4), ), child: Row( children: [ const Icon(Icons.info_outline, size: 14, color: AppColors.primary), const SizedBox(width: 6), Expanded( child: Text( '照片将自动添加水印:服务器授时 + GPS经纬度(${_gpsLat.toStringAsFixed(4)}°N, ${_gpsLng.toStringAsFixed(4)}°E)', style: const TextStyle( fontSize: 11, color: AppColors.textSecondary, ), ), ), ], ), ), ], ), const SizedBox(height: 80), ], ), ), ), ), ), ActionBar( centerLabel: l10n.get('saveDraft'), rightLabel: l10n.get('submit'), showLeft: false, onCenterTap: _saveDraft, onRightTap: _submit, ), ], ); } Widget _buildGpsSection() { if (_gpsFailed) { return Container( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppColors.bgCard, borderRadius: BorderRadius.circular(8), ), child: Row( children: [ const Icon(Icons.location_off, size: 22, color: AppColors.statusGray), const SizedBox(width: 10), const Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('无法获取当前位置', style: TextStyle( fontSize: 14, color: AppColors.textPrimary)), SizedBox(height: 4), Text('请检查位置权限设置', style: TextStyle( fontSize: 12, color: AppColors.textSecondary)), ], ), ), GestureDetector( onTap: _simulateGps, child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: AppColors.primaryLight, borderRadius: BorderRadius.circular(4), ), child: const Text('重试', style: TextStyle(fontSize: 12, color: AppColors.primary)), ), ), ], ), ); } final isWarning = _gpsAccuracy > 100; return Container( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppColors.bgCard, borderRadius: BorderRadius.circular(8), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon( Icons.shield_outlined, size: 22, color: isWarning ? AppColors.warning : AppColors.success, ), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(_gpsAddress, style: const TextStyle( fontSize: 14, color: AppColors.textPrimary)), const SizedBox(height: 4), Row( children: [ Text( '${_gpsLat.toStringAsFixed(4)}°N, ${_gpsLng.toStringAsFixed(4)}°E · 精度 ${_gpsAccuracy.toStringAsFixed(0)}m', style: TextStyle( fontSize: 12, color: isWarning ? AppColors.warning : AppColors.textSecondary, ), ), if (isWarning) ...[ const SizedBox(width: 6), const Icon(Icons.warning_amber_rounded, size: 14, color: AppColors.warning), ], ], ), ], ), ), ], ), ); } Widget _buildPhotoThumbnail(int index, String photo) { return Stack( children: [ Container( width: 80, height: 80, decoration: BoxDecoration( color: const Color(0xFFD0E8F8), borderRadius: BorderRadius.circular(4), ), child: const Center( child: Icon(Icons.image_outlined, size: 32, color: AppColors.primary), ), ), Positioned( top: -4, right: -4, child: GestureDetector( onTap: () => _removePhoto(index), child: Container( padding: const EdgeInsets.all(2), decoration: const BoxDecoration( color: AppColors.danger, shape: BoxShape.circle, ), child: const Icon(Icons.close, size: 14, color: Colors.white), ), ), ), Positioned( bottom: 2, left: 2, right: 2, child: Container( padding: const EdgeInsets.symmetric(horizontal: 2), color: Colors.black54, child: Text( '${DateTime.now().hour.toString().padLeft(2, '0')}:${DateTime.now().minute.toString().padLeft(2, '0')} ${_gpsLat.toStringAsFixed(2)},${_gpsLng.toStringAsFixed(2)}', style: const TextStyle(fontSize: 8, color: Colors.white), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), ), ], ); } }