import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/theme/app_colors.dart'; import '../shell/nav_bar_config.dart'; import '../../core/utils/date_utils.dart' as du; import '../../core/i18n/app_localizations.dart'; import 'outing_log_list_controller.dart'; import 'outing_log_comment.dart'; import 'outing_log_model.dart'; class OutingLogDetailPage extends ConsumerStatefulWidget { final String id; const OutingLogDetailPage({super.key, required this.id}); @override ConsumerState createState() => _OutingLogDetailPageState(); } class _OutingLogDetailPageState extends ConsumerState { late OutingLogModel _log; final bool _isManager = true; // 模拟经理角色 int _rating = 0; final _commentCtrl = TextEditingController(); final List _comments = []; @override void initState() { super.initState(); _log = mockOutingLogs.firstWhere( (e) => e.id == widget.id, orElse: () => mockOutingLogs.first, ); _comments.addAll(_log.comments); // 模拟:进入详情时更新 LastViewedTime Future.delayed(const Duration(milliseconds: 500), () { if (mounted) setState(() {}); }); } @override void didChangeDependencies() { super.didChangeDependencies(); _log = mockOutingLogs.firstWhere( (e) => e.id == widget.id, orElse: () => mockOutingLogs.first, ); } @override void dispose() { _commentCtrl.dispose(); super.dispose(); } void _sendComment() { if (_rating == 0) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('请选择评分')), ); return; } final content = _commentCtrl.text.trim(); if (content.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('请输入点评内容')), ); return; } setState(() { _comments.add(OutingLogComment( id: 'cmt-new-${DateTime.now().millisecondsSinceEpoch}', logId: widget.id, commenterId: 'u-mgr', commenterName: '王经理', commenterPosition: '销售总监', ratingStars: _rating, commentText: content, createTime: DateTime.now(), )); _rating = 0; _commentCtrl.clear(); }); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('点评已发送')), ); } @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context); ref .read(navBarConfigProvider.notifier) .update( NavBarConfig( title: l10n.get('outingLogDetail'), showBack: true, onBack: () => context.pop(), ), ); return Column( children: [ Expanded( child: SingleChildScrollView( child: Column( children: [ _buildMapPlaceholder(), _buildInfoSection(), const SizedBox(height: 8), _buildSectionCard(l10n.get('workSummary'), _log.visitSummary.isNotEmpty ? _log.visitSummary : l10n.get('noWorkSummary')), const SizedBox(height: 8), _buildSectionCard(l10n.get('followUp'), _log.nextPlan.isNotEmpty ? _log.nextPlan : l10n.get('noPlan')), const SizedBox(height: 8), _buildPhotoSection(), const SizedBox(height: 8), _buildCommentSection(), if (_isManager) _buildManagerCommentInput(), const SizedBox(height: 24), ], ), ), ), ], ); } Widget _buildMapPlaceholder() { return Container( width: double.infinity, height: 160, padding: const EdgeInsets.fromLTRB(16, 16, 16, 0), child: GestureDetector( onTap: () { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('模拟:打开原生导航')), ); }, child: Container( decoration: BoxDecoration( color: const Color(0xFFE8F4FD), borderRadius: BorderRadius.circular(8), ), child: Stack( children: [ const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.map_outlined, size: 40, color: AppColors.primary), SizedBox(height: 4), Text('点击查看导航', style: TextStyle( fontSize: 12, color: AppColors.primary)), ], ), ), Positioned( bottom: 8, left: 8, child: Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3), decoration: BoxDecoration( color: Colors.black54, borderRadius: BorderRadius.circular(4), ), child: Text( '${_log.checkInLatitude?.toStringAsFixed(4) ?? "0.0000"}, ${_log.checkInLongitude?.toStringAsFixed(4) ?? "0.0000"}', style: const TextStyle(fontSize: 10, color: Colors.white), ), ), ), ], ), ), ), ); } Widget _buildInfoSection() { return Padding( padding: const EdgeInsets.all(16), child: Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: AppColors.bgCard, borderRadius: BorderRadius.circular(8), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( _log.customerName, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w700, color: AppColors.textPrimary, ), ), const SizedBox(height: 12), _buildInfoRow('业务员', _log.salespersonName), const Divider(height: 12, color: AppColors.border), _buildInfoRow('部门', _log.deptName), const Divider(height: 12, color: AppColors.border), _buildInfoRow('客户名', _log.customerName), const Divider(height: 12, color: AppColors.border), _buildInfoRow('签到地址', _log.checkInAddress), const Divider(height: 12, color: AppColors.border), _buildInfoRow('签到时间', du.DateUtils.formatDateTime(_log.createTime)), ], ), ), ); } Widget _buildInfoRow(String label, String value) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: 70, child: Text(label, style: const TextStyle( fontSize: 13, color: AppColors.textSecondary)), ), Expanded( child: Text(value, style: const TextStyle( fontSize: 13, color: AppColors.textPrimary)), ), ], ), ); } Widget _buildSectionCard(String title, String content) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: AppColors.bgCard, borderRadius: BorderRadius.circular(8), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.textPrimary)), const SizedBox(height: 8), Text(content, style: const TextStyle( fontSize: 14, color: AppColors.textSecondary, height: 1.5)), ], ), ), ); } Widget _buildPhotoSection() { final l10n = AppLocalizations.of(context); final photos = _log.visitPhotos; return Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: AppColors.bgCard, borderRadius: BorderRadius.circular(8), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(l10n.get('sitePhotos'), style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.textPrimary)), const SizedBox(height: 12), if (photos.isEmpty) Text(l10n.get('noPhotos'), style: TextStyle( fontSize: 13, color: AppColors.textPlaceholder)) else Wrap( spacing: 8, runSpacing: 8, children: photos.map((photo) { return GestureDetector( onTap: () { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('模拟:全屏预览照片')), ); }, child: Stack( children: [ Container( width: 100, height: 100, decoration: BoxDecoration( color: const Color(0xFFD0E8F8), borderRadius: BorderRadius.circular(4), ), child: const Center( child: Icon(Icons.image_outlined, size: 36, color: AppColors.primary), ), ), Positioned( bottom: 0, left: 0, right: 0, child: Container( padding: const EdgeInsets.symmetric( horizontal: 3, vertical: 2), color: Colors.black54, child: Text( '${du.DateUtils.formatDate(_log.createTime)} ${_log.checkInLatitude?.toStringAsFixed(2) ?? "0.00"},${_log.checkInLongitude?.toStringAsFixed(2) ?? "0.00"}', style: const TextStyle( fontSize: 8, color: Colors.white), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), ), ], ), ); }).toList(), ), ], ), ), ); } Widget _buildCommentSection() { final l10n = AppLocalizations.of(context); return Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: AppColors.bgCard, borderRadius: BorderRadius.circular(8), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(l10n.get('comments'), style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.textPrimary)), const SizedBox(height: 12), if (_comments.isEmpty) Center( child: Padding( padding: const EdgeInsets.symmetric(vertical: 16), child: Text(l10n.get('noComments'), style: TextStyle( fontSize: 13, color: AppColors.textPlaceholder)), ), ) else ..._comments.map((comment) => _buildCommentBubble(comment)), ], ), ), ); } Widget _buildCommentBubble(OutingLogComment comment) { return Container( width: double.infinity, margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppColors.primaryLight, borderRadius: const BorderRadius.only( topLeft: Radius.circular(8), topRight: Radius.circular(8), bottomRight: Radius.circular(8), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ CircleAvatar( radius: 12, backgroundColor: AppColors.primaryLight, child: Text( comment.commenterName.substring(0, 1), style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: AppColors.primary), ), ), const SizedBox(width: 6), Text(comment.commenterName, style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: AppColors.textPrimary)), const SizedBox(width: 4), if (comment.commenterPosition.isNotEmpty) Text('· ${comment.commenterPosition}', style: const TextStyle( fontSize: 11, color: AppColors.textSecondary)), ], ), Row( mainAxisSize: MainAxisSize.min, children: List.generate(5, (i) { return Icon( i < comment.ratingStars ? Icons.star : Icons.star_border, size: 14, color: AppColors.warning, ); }), ), ], ), const SizedBox(height: 8), Text(comment.commentText, style: const TextStyle( fontSize: 14, color: AppColors.textSecondary, height: 1.5)), const SizedBox(height: 6), Text(du.DateUtils.formatDateTime(comment.createTime), style: const TextStyle( fontSize: 11, color: AppColors.textPlaceholder)), ], ), ); } Widget _buildManagerCommentInput() { final l10n = AppLocalizations.of(context); return Padding( padding: const EdgeInsets.fromLTRB(16, 8, 16, 0), child: Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: AppColors.bgCard, borderRadius: BorderRadius.circular(8), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(l10n.get('managerComment'), style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.textPrimary)), const SizedBox(height: 8), Row( children: [ Text('${l10n.get('statAvgRating')}:', style: TextStyle( fontSize: 13, color: AppColors.textSecondary)), ...List.generate(5, (i) { final starIndex = i + 1; return GestureDetector( onTap: () => setState(() { _rating = _rating == starIndex ? 0 : starIndex; }), child: Padding( padding: const EdgeInsets.only(right: 4), child: Icon( starIndex <= _rating ? Icons.star : Icons.star_border, size: 28, color: AppColors.warning, ), ), ); }), ], ), const SizedBox(height: 8), Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: AppColors.bgPage, borderRadius: BorderRadius.circular(8), ), child: Row( children: [ Expanded( child: TextField( controller: _commentCtrl, decoration: InputDecoration( hintText: l10n.get('inputComment'), 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(width: 8), GestureDetector( onTap: _sendComment, child: Container( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 8), decoration: BoxDecoration( color: AppColors.primary, borderRadius: BorderRadius.circular(18), ), child: Text(l10n.get('send'), style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.white)), ), ), ], ), ), ], ), ), ); } }