import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import '../../core/theme/app_colors.dart'; import '../../core/utils/date_utils.dart' as du; import '../../core/utils/responsive.dart'; import '../../shared/widgets/app_card.dart'; import '../../shared/widgets/status_tag.dart'; import '../../shared/widgets/empty_state.dart'; import '../../shared/widgets/loading_widget.dart'; import 'expense_list_controller.dart'; import 'expense_model.dart'; class ExpenseListPage extends ConsumerStatefulWidget { const ExpenseListPage({super.key}); @override ConsumerState createState() => _ExpenseListPageState(); } class _ExpenseListPageState extends ConsumerState { int _page = 1; @override Widget build(BuildContext context) { final status = ref.watch(expenseStatusFilterProvider); final itemsAsync = ref.watch(expenseListProvider(_page)); final r = ResponsiveHelper.of(context); return Scaffold( appBar: AppBar( title: const Text('报销单'), actions: [ TextButton( onPressed: () => context.go('/expense/apply'), child: const Text('+ 新建', style: TextStyle(color: Colors.white, fontSize: 13)), ), ], ), body: Column( children: [ _buildStatusFilter(status, r), Expanded( child: Center( child: ConstrainedBox( constraints: BoxConstraints(maxWidth: r.listMaxWidth), child: itemsAsync.when( loading: () => const LoadingWidget(), error: (_, __) => const EmptyState(message: '加载失败'), data: (items) => items.isEmpty ? const EmptyState(message: '暂无报销单') : ListView.builder( padding: const EdgeInsets.symmetric(vertical: 4), itemCount: items.length, itemBuilder: (_, index) => _buildItem(items[index]), ), ), ), ), ), ], ), ); } Widget _buildStatusFilter(String current, ResponsiveHelper r) { final statuses = [ {'key': '', 'label': '全部'}, {'key': 'pending', 'label': '待审批'}, {'key': 'approved', 'label': '已通过'}, {'key': 'rejected', 'label': '已拒绝'}, ]; final filterBar = SingleChildScrollView( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 12), child: Row( children: statuses.map((s) { final isSelected = current == s['key']; return Padding( padding: const EdgeInsets.only(right: 8), child: GestureDetector( onTap: () { ref.read(expenseStatusFilterProvider.notifier).state = s['key']!; _page = 1; }, child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), decoration: BoxDecoration( color: isSelected ? AppColors.primaryLight : Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all( color: isSelected ? AppColors.primary : const Color(0xFFDDDDDD), ), ), child: Text( s['label']!, style: TextStyle( color: isSelected ? AppColors.primary : AppColors.textSecondary, fontSize: 12, ), ), ), ), ); }).toList(), ), ); return Container( color: Colors.white, padding: const EdgeInsets.symmetric(vertical: 8), child: r.isWide ? Center( child: SizedBox(width: r.listMaxWidth, child: filterBar)) : filterBar, ); } Widget _buildItem(ExpenseModel item) { return Slidable( endActionPane: ActionPane( motion: const ScrollMotion(), children: [ SlidableAction( onPressed: (_) => context.go('/expense/apply?id=${item.id}'), backgroundColor: AppColors.primary, foregroundColor: Colors.white, icon: Icons.edit, label: '编辑', ), ], ), child: AppCard( onTap: () => context.go('/expense/detail/${item.id}'), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(item.reportNo, style: const TextStyle( fontWeight: FontWeight.w600, fontSize: 14, color: AppColors.textPrimary)), StatusTag(status: item.status), ], ), const SizedBox(height: 4), Text( '${item.expenseType} · ¥${item.totalAmount.toStringAsFixed(2)}', style: const TextStyle( color: AppColors.textSecondary, fontSize: 12), ), const SizedBox(height: 4), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(item.applicantName, style: const TextStyle( color: AppColors.textHint, fontSize: 11)), Text(du.DateUtils.formatDateTime(item.createTime), style: const TextStyle( color: AppColors.textHint, fontSize: 11)), ], ), ], ), ), ); } }