expense_detail_page.dart 7.6 KB

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