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'; import 'outing_log_api.dart'; import 'outing_log_model.dart'; class OutingLogCreatePage extends ConsumerStatefulWidget { const OutingLogCreatePage({super.key}); @override ConsumerState createState() => _OutingLogCreatePageState(); } class _OutingLogCreatePageState extends ConsumerState { final _contentCtrl = TextEditingController(); final _clientCtrl = TextEditingController(); final _addressCtrl = TextEditingController(); final _planCtrl = TextEditingController(); DateTime _date = DateTime.now(); TimeOfDay _startTime = const TimeOfDay(hour: 9, minute: 0); TimeOfDay _endTime = const TimeOfDay(hour: 17, minute: 0); String _visitType = '客户拜访'; final _visitTypes = ['客户拜访', '外出办事', '其他']; @override void dispose() { _contentCtrl.dispose(); _clientCtrl.dispose(); _addressCtrl.dispose(); _planCtrl.dispose(); super.dispose(); } @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: [ // GPS 信息卡片 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.shield_outlined, size: 22, color: AppColors.success, ), const SizedBox(width: 10), const Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '深圳市南山区科技园南路 88 号', style: TextStyle( fontSize: 14, color: AppColors.textPrimary, ), ), SizedBox(height: 4), Text( '22.5431°N, 113.9532°E · 精度 15m', style: TextStyle( fontSize: 12, color: AppColors.textSecondary, ), ), ], ), ], ), ), const SizedBox(height: 8), // 基本信息 FormSection( title: '基本信息', children: [ _buildFormField('创建人', '张三', readOnly: true), const Divider(height: 1, color: AppColors.border), _buildFormField('部门', '市场部', readOnly: true), const Divider(height: 1, color: AppColors.border), _buildFormField( '日期', '${_date.year}-${_date.month.toString().padLeft(2, '0')}-${_date.day.toString().padLeft(2, '0')}', onTap: _pickDate, ), ], ), const SizedBox(height: 8), // 外出详情 FormSection( title: '外出详情', children: [ _buildSelectField('外出类型', _visitType, _pickVisitType), const Divider(height: 1, color: AppColors.border), _buildFormField( '外出地点', _addressCtrl.text.isEmpty ? null : _addressCtrl.text, hint: '请输入外出地点', showArrow: false, onTap: () {}, ), if (_addressCtrl.text.isEmpty) Padding( padding: const EdgeInsets.only(bottom: 4), child: TextField( controller: _addressCtrl, 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 Divider(height: 1, color: AppColors.border), _buildFormField( '开始时间', _startTime.format(context), onTap: () => _pickTime(true), ), const Divider(height: 1, color: AppColors.border), _buildFormField( '结束时间', _endTime.format(context), onTap: () => _pickTime(false), ), const Divider(height: 1, color: AppColors.border), const SizedBox(height: 8), const Text( '外出事由', style: TextStyle( fontSize: 14, color: AppColors.textSecondary, ), ), const SizedBox(height: 8), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppColors.bgPage, borderRadius: BorderRadius.circular(4), ), child: TextField( controller: _contentCtrl, 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: '附件', children: [ Container( height: 80, decoration: BoxDecoration( color: AppColors.bgPage, borderRadius: BorderRadius.circular(4), ), child: const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.add_photo_alternate_outlined, size: 28, color: AppColors.primary, ), SizedBox(height: 4), Text( '添加附件', style: TextStyle( fontSize: 12, color: AppColors.primary, ), ), ], ), ), ), ], ), const SizedBox(height: 80), ], ), ), ), ), ), // ActionBar ActionBar( centerLabel: '存草稿', rightLabel: '提交', showLeft: false, onCenterTap: _saveDraft, onRightTap: _submit, ), ], ); } Widget _buildFormField( String label, String? value, { String? hint, bool readOnly = false, bool showArrow = true, VoidCallback? onTap, }) { final hasValue = value != null && value.isNotEmpty; return GestureDetector( onTap: readOnly ? null : onTap, child: Container( height: 44, alignment: Alignment.centerLeft, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( label, style: const TextStyle( fontSize: 14, color: AppColors.textSecondary, ), ), Row( mainAxisSize: MainAxisSize.min, children: [ Text( hasValue ? value : (hint ?? '请选择或填写'), style: TextStyle( fontSize: 14, color: hasValue ? AppColors.textPrimary : AppColors.textPlaceholder, ), ), if (showArrow && !readOnly) ...[ const SizedBox(width: 4), const Icon( Icons.chevron_right, size: 14, color: AppColors.textPlaceholder, ), ], ], ), ], ), ), ); } Widget _buildSelectField(String label, String value, VoidCallback onTap) { return GestureDetector( onTap: onTap, child: Container( height: 44, alignment: Alignment.centerLeft, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( label, style: const TextStyle( fontSize: 14, color: AppColors.textSecondary, ), ), Row( mainAxisSize: MainAxisSize.min, children: [ Text( value, style: const TextStyle( fontSize: 14, color: AppColors.textPrimary, ), ), const SizedBox(width: 4), const Icon( Icons.chevron_right, size: 14, color: AppColors.textPlaceholder, ), ], ), ], ), ), ); } Future _pickDate() async { final picked = await showDatePicker( context: context, initialDate: _date, firstDate: DateTime(2020), lastDate: DateTime(2030), ); if (picked != null) setState(() => _date = picked); } Future _pickTime(bool isStart) async { final picked = await showTimePicker( context: context, initialTime: isStart ? _startTime : _endTime, ); if (picked != null) { setState(() { if (isStart) { _startTime = picked; } else { _endTime = picked; } }); } } Future _pickVisitType() async { final result = await showDialog( context: context, builder: (ctx) => SimpleDialog( title: const Text('选择外出类型'), children: _visitTypes .map( (t) => SimpleDialogOption( onPressed: () => Navigator.pop(ctx, t), child: Text(t), ), ) .toList(), ), ); if (result != null) setState(() => _visitType = result); } Future _saveDraft() async { // TODO: 存草稿逻辑 if (context.mounted) { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('已保存草稿'))); } } Future _submit() async { try { final visitDate = DateTime(_date.year, _date.month, _date.day); await ref .read(outingLogApiProvider) .create( OutingLogModel( id: '', visitNo: '', salespersonId: '', salespersonName: '张三', deptId: '', deptName: '市场部', customerName: _clientCtrl.text, visitDate: visitDate, visitStartTime: DateTime( visitDate.year, visitDate.month, visitDate.day, _startTime.hour, _startTime.minute, ), visitEndTime: DateTime( visitDate.year, visitDate.month, visitDate.day, _endTime.hour, _endTime.minute, ), visitType: _visitType, visitPurpose: '', visitLocation: _addressCtrl.text, visitSummary: _contentCtrl.text, nextVisitTime: visitDate, createTime: DateTime.now(), updateTime: DateTime.now(), ), ); if (context.mounted) context.pop(); } catch (_) {} } }