expense_detail_page.dart 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter_riverpod/flutter_riverpod.dart';
  3. import '../../core/theme/app_colors.dart';
  4. import '../../core/utils/date_utils.dart' as du;
  5. import '../../core/utils/responsive.dart';
  6. import '../../shared/models/approval_status.dart';
  7. import '../../shared/widgets/form_section.dart';
  8. import '../../shared/widgets/form_field_row.dart';
  9. import '../../shared/widgets/status_tag.dart';
  10. import 'expense_model.dart';
  11. final expenseDetailProvider =
  12. FutureProvider.autoDispose.family<ExpenseModel, String>(
  13. (ref, id) async {
  14. await Future.delayed(const Duration(milliseconds: 300));
  15. return ExpenseModel(
  16. id: id,
  17. reportNo: 'BX202605001',
  18. applicantId: 'u-001',
  19. applicantName: '张三',
  20. deptId: 'dept-001',
  21. deptName: '市场部',
  22. expenseType: '差旅费',
  23. totalAmount: 2580.00,
  24. invoiceCount: 3,
  25. status: 'pending',
  26. createTime: DateTime(2026, 5, 20),
  27. updateTime: DateTime(2026, 5, 20),
  28. details: [
  29. ExpenseDetailModel(
  30. id: 'det-001',
  31. expenseId: id,
  32. expenseDate: DateTime(2026, 5, 19),
  33. expenseType: '交通费',
  34. expenseDesc: '北京-上海高铁',
  35. amount: 553.00,
  36. totalAmount: 553.00,
  37. ),
  38. ExpenseDetailModel(
  39. id: 'det-002',
  40. expenseId: id,
  41. expenseDate: DateTime(2026, 5, 19),
  42. expenseType: '住宿费',
  43. expenseDesc: '上海酒店住宿',
  44. amount: 800.00,
  45. totalAmount: 800.00,
  46. ),
  47. ],
  48. approvalRecords: [
  49. ApprovalRecord(
  50. id: 'ar-001',
  51. bizId: id,
  52. bizType: 'expense',
  53. approverId: 'u-mgr',
  54. approverName: '李四',
  55. approvalLevel: 1,
  56. action: 'pending',
  57. opinion: '',
  58. approvalTime: DateTime(2026, 5, 21),
  59. ),
  60. ],
  61. );
  62. });
  63. class ExpenseDetailPage extends ConsumerWidget {
  64. final String id;
  65. const ExpenseDetailPage({super.key, required this.id});
  66. @override
  67. Widget build(BuildContext context, WidgetRef ref) {
  68. final detailAsync = ref.watch(expenseDetailProvider(id));
  69. final r = ResponsiveHelper.of(context);
  70. return Scaffold(
  71. appBar: AppBar(title: const Text('报销单详情')),
  72. body: detailAsync.when(
  73. loading: () =>
  74. const Center(child: CircularProgressIndicator()),
  75. error: (_, __) => const Center(child: Text('加载失败')),
  76. data: (expense) => Center(
  77. child: ConstrainedBox(
  78. constraints:
  79. BoxConstraints(maxWidth: r.detailTwoColumns ? 700 : double.infinity),
  80. child: SingleChildScrollView(
  81. padding: const EdgeInsets.symmetric(vertical: 8),
  82. child: Column(
  83. children: [
  84. FormSection(
  85. title: '基本信息',
  86. children: [
  87. FormFieldRow(
  88. label: '单号',
  89. value: expense.reportNo,
  90. showArrow: false),
  91. FormFieldRow(
  92. label: '报销类型',
  93. value: expense.expenseType,
  94. showArrow: false),
  95. FormFieldRow(
  96. label: '报销金额',
  97. value: '¥${expense.totalAmount.toStringAsFixed(2)}',
  98. showArrow: false),
  99. FormFieldRow(
  100. label: '部门',
  101. value: expense.deptName,
  102. showArrow: false),
  103. FormFieldRow(
  104. label: '申请人',
  105. value: expense.applicantName,
  106. showArrow: false),
  107. FormFieldRow(
  108. label: '创建时间',
  109. value: du.DateUtils.formatDateTime(
  110. expense.createTime),
  111. showArrow: false),
  112. FormFieldRow(
  113. label: '状态',
  114. value: '',
  115. showArrow: false,
  116. onTap: null,
  117. ), // 状态用 StatusTag
  118. Padding(
  119. padding: const EdgeInsets.fromLTRB(14, 0, 14, 12),
  120. child: Row(
  121. children: [
  122. const SizedBox(
  123. width: 72,
  124. child: Text('状态',
  125. style: TextStyle(
  126. color: AppColors.textSecondary,
  127. fontSize: 13))),
  128. StatusTag(status: expense.status),
  129. ],
  130. ),
  131. ),
  132. FormFieldRow(
  133. label: '备注',
  134. value: expense.remark.isEmpty
  135. ? '-'
  136. : expense.remark,
  137. showArrow: false),
  138. ],
  139. ),
  140. FormSection(
  141. title: '报销明细',
  142. children: expense.details.isEmpty
  143. ? [
  144. const Padding(
  145. padding: EdgeInsets.all(14),
  146. child: Text('无明细',
  147. style: TextStyle(
  148. color: AppColors.textHint,
  149. fontSize: 13)),
  150. )
  151. ]
  152. : expense.details
  153. .map((d) => Padding(
  154. padding: const EdgeInsets.symmetric(
  155. horizontal: 14, vertical: 8),
  156. child: Row(
  157. mainAxisAlignment:
  158. MainAxisAlignment.spaceBetween,
  159. children: [
  160. Column(
  161. crossAxisAlignment:
  162. CrossAxisAlignment.start,
  163. children: [
  164. Text(d.expenseDesc,
  165. style: const TextStyle(
  166. fontSize: 13,
  167. color: AppColors
  168. .textPrimary)),
  169. Text(d.invoiceNo,
  170. style: const TextStyle(
  171. fontSize: 10,
  172. color: AppColors
  173. .textHint)),
  174. ],
  175. ),
  176. Text(
  177. '¥${d.totalAmount.toStringAsFixed(2)}',
  178. style: const TextStyle(
  179. color: AppColors.primary,
  180. fontSize: 14)),
  181. ],
  182. ),
  183. ))
  184. .toList(),
  185. ),
  186. ],
  187. ),
  188. ),
  189. ),
  190. ),
  191. ),
  192. );
  193. }
  194. }