| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238 |
- import 'package:easy_refresh/easy_refresh.dart';
- import 'package:flutter/material.dart';
- import 'package:flutter_riverpod/flutter_riverpod.dart';
- import 'package:go_router/go_router.dart';
- import 'package:tdesign_flutter/tdesign_flutter.dart';
- import '../../core/theme/app_colors.dart';
- import '../../shared/widgets/empty_state.dart';
- import '../shell/nav_bar_config.dart';
- import '../../core/i18n/app_localizations.dart';
- import '../../shared/widgets/loading_widget.dart';
- import '../../shared/widgets/message_item.dart';
- import 'message_controller.dart';
- import 'message_model.dart';
- /// 消息通知聚合页
- ///
- /// 展示 5 种消息类型:
- /// - 审批待办(📋)→ 详情页 + 审批操作栏
- /// - 审批结果(✅/❌)→ 详情查看结果
- /// - 撤回通知(↩)→ 消息列表
- /// - 系统公告(📢)→ 公告详情
- /// - 过期提醒(⏰)→ 对应详情页
- ///
- /// 左滑操作:标记已读(蓝)+ 删除(红)。已读消息不可左滑。
- class MessageListPage extends ConsumerWidget {
- const MessageListPage({super.key});
- @override
- Widget build(BuildContext context, WidgetRef ref) {
- final messagesAsync = ref.watch(messageListProvider);
- final unreadCount = ref.watch(unreadCountProvider);
- final location = GoRouterState.of(context).uri.toString();
- final l10n = AppLocalizations.of(context);
- if (location.startsWith('/messages')) {
- ref
- .read(navBarConfigProvider.notifier)
- .update(
- NavBarConfig(
- title: l10n.get('messageNotifications'),
- showBack: true,
- leadingIcon: Icons.close,
- // 仅在有未读时显示"全部已读"按钮
- showRight: unreadCount > 0,
- rightWidget: unreadCount > 0
- ? GestureDetector(
- onTap: () {
- TDMessage.showMessage(
- context: context,
- content: l10n.get('markAllRead'),
- theme: MessageTheme.success,
- icon: true,
- duration: 2000,
- );
- },
- child: Padding(
- padding: const EdgeInsets.symmetric(
- horizontal: 12,
- vertical: 8,
- ),
- child: Text(
- l10n.get('markAllRead'),
- style: const TextStyle(
- fontSize: AppFontSizes.body,
- color: AppColors.primary,
- ),
- ),
- ),
- )
- : null,
- ),
- );
- }
- return EasyRefresh(
- header: TDRefreshHeader(),
- onRefresh: () async {
- ref.invalidate(messageListProvider);
- await ref.read(messageListProvider.future);
- },
- child: messagesAsync.when(
- loading: () => const SingleChildScrollView(
- physics: AlwaysScrollableScrollPhysics(),
- child: LoadingWidget(),
- ),
- error: (_, _) => SingleChildScrollView(
- physics: const AlwaysScrollableScrollPhysics(),
- child: EmptyState(message: l10n.get('loadFailed')),
- ),
- data: (messages) => messages.isEmpty
- ? SingleChildScrollView(
- physics: const AlwaysScrollableScrollPhysics(),
- child: EmptyState(message: l10n.get('noMessages')),
- )
- : ListView.separated(
- padding: const EdgeInsets.fromLTRB(
- AppSpacing.md,
- AppSpacing.md,
- AppSpacing.md,
- AppSpacing.lg,
- ),
- itemCount: messages.length,
- separatorBuilder: (_, _) => const SizedBox(height: 12),
- itemBuilder: (_, i) => _buildItem(context, ref, messages[i], l10n),
- ),
- ),
- );
- }
- Widget _buildItem(
- BuildContext context,
- WidgetRef ref,
- MessageModel msg,
- AppLocalizations l10n,
- ) {
- final (icon, iconColor, iconBg) = _messageIconProps(msg.msgType);
- return MessageItem(
- icon: icon,
- iconColor: iconColor,
- iconBackground: iconBg,
- title: msg.title,
- time: _formatTime(msg.createTime),
- sender: _messageSender(msg.msgType, l10n),
- summary: msg.content,
- unread: !msg.isRead,
- onTap: () => _navigateToBiz(context, msg),
- // 仅未读消息展示左滑操作
- onToggleRead: msg.isRead
- ? null
- : () {
- TDMessage.showMessage(
- context: context,
- content: '${l10n.get("markRead")}:${msg.title}',
- theme: MessageTheme.success,
- icon: true,
- duration: 2000,
- );
- },
- onDelete: () {
- showDialog(
- context: context,
- builder: (ctx) => AlertDialog(
- title: Text(l10n.get('confirm')),
- content: Text(
- l10n.getString('confirmAction', args: {'action': l10n.get('delete')}),
- ),
- actions: [
- TextButton(
- onPressed: () => Navigator.of(ctx).pop(),
- child: Text(l10n.get('cancel')),
- ),
- TextButton(
- onPressed: () {
- Navigator.of(ctx).pop();
- TDMessage.showMessage(
- context: context,
- content: '${l10n.get("deletedToast")}${msg.title}',
- theme: MessageTheme.warning,
- icon: true,
- duration: 2000,
- );
- },
- child: Text(l10n.get('confirm')),
- ),
- ],
- ),
- );
- },
- );
- }
- /// 按消息类型返回(图标, 图标色, 图标背景色)
- (IconData, Color, Color) _messageIconProps(String msgType) {
- switch (msgType) {
- case 'announcement':
- return (Icons.campaign_outlined, AppColors.warning, AppColors.warningBg);
- case 'approval_result':
- return (Icons.check_circle_outlined, AppColors.success, AppColors.successBg);
- case 'withdraw_notice':
- return (Icons.undo_outlined, AppColors.statusGray, const Color(0xFFF0F0F0));
- case 'expiry_reminder':
- return (Icons.timer_off_outlined, AppColors.danger, AppColors.dangerBg);
- case 'approval_notice':
- default:
- return (Icons.assignment_outlined, AppColors.primary, AppColors.primaryLight);
- }
- }
- /// 按消息类型返回发送者标签
- String _messageSender(String msgType, AppLocalizations l10n) {
- switch (msgType) {
- case 'announcement':
- return l10n.get('systemNotice');
- case 'approval_notice':
- return l10n.get('approvalNotice');
- case 'approval_result':
- return l10n.get('systemMessage');
- case 'withdraw_notice':
- return l10n.get('systemMessage');
- case 'expiry_reminder':
- return l10n.get('systemMessage');
- default:
- return l10n.get('systemMessage');
- }
- }
- /// 格式化时间为 MM-DD HH:mm
- String _formatTime(DateTime time) {
- final month = time.month.toString().padLeft(2, '0');
- final day = time.day.toString().padLeft(2, '0');
- final hour = time.hour.toString().padLeft(2, '0');
- final minute = time.minute.toString().padLeft(2, '0');
- return '$month-$day $hour:$minute';
- }
- /// 按 BizType 路由跳转
- void _navigateToBiz(BuildContext context, MessageModel msg) {
- if (msg.bizType == null || msg.bizId == null) return;
- switch (msg.bizType) {
- case 'expense':
- context.push('/expense/detail/${msg.bizId}');
- break;
- case 'overtime':
- context.push('/overtime/detail/${msg.bizId}');
- break;
- case 'vehicle':
- context.push('/vehicle/detail/${msg.bizId}');
- break;
- case 'announcement':
- context.push('/announcement/detail/${msg.bizId}');
- break;
- case 'expense_application':
- context.push('/expense-apply/detail/${msg.bizId}');
- break;
- }
- }
- }
|