expense_application_apply_page.dart 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter_riverpod/flutter_riverpod.dart';
  3. import 'package:go_router/go_router.dart';
  4. import 'package:tdesign_flutter/tdesign_flutter.dart';
  5. import '../../core/theme/app_colors.dart';
  6. import '../../core/utils/responsive.dart';
  7. import '../../shared/widgets/form_section.dart';
  8. import '../../shared/widgets/form_field_row.dart';
  9. class ExpenseApplicationApplyPage extends ConsumerStatefulWidget {
  10. final String? id;
  11. const ExpenseApplicationApplyPage({super.key, this.id});
  12. @override
  13. ConsumerState<ExpenseApplicationApplyPage> createState() =>
  14. _ExpenseApplicationApplyPageState();
  15. }
  16. class _ExpenseApplicationApplyPageState
  17. extends ConsumerState<ExpenseApplicationApplyPage> {
  18. final _formKey = GlobalKey<FormState>();
  19. String _expenseType = '差旅费';
  20. final _amountController = TextEditingController();
  21. final _purposeController = TextEditingController();
  22. final _remarkController = TextEditingController();
  23. static const _types = ['差旅费', '办公用品', '招待费', '交通费', '通讯费', '其他'];
  24. @override
  25. void dispose() {
  26. _amountController.dispose();
  27. _purposeController.dispose();
  28. _remarkController.dispose();
  29. super.dispose();
  30. }
  31. @override
  32. Widget build(BuildContext context) {
  33. final r = ResponsiveHelper.of(context);
  34. return Scaffold(
  35. appBar: TDNavBar(
  36. title: '报销申请',
  37. titleColor: Colors.white,
  38. backgroundColor: const Color(0xFF00ABF3),
  39. centerTitle: false,
  40. ),
  41. body: Column(
  42. children: [
  43. Expanded(
  44. child: Align(alignment: Alignment.topCenter,
  45. child: ConstrainedBox(
  46. constraints: BoxConstraints(maxWidth: r.formMaxWidth),
  47. child: SingleChildScrollView(
  48. padding: const EdgeInsets.symmetric(vertical: 8),
  49. child: Form(
  50. key: _formKey,
  51. child: _buildForm(),
  52. ),
  53. ),
  54. ),
  55. ),
  56. ),
  57. _buildBottomButtons(),
  58. ],
  59. ),
  60. );
  61. }
  62. Widget _buildForm() {
  63. return FormSection(
  64. title: '基本信息',
  65. children: [
  66. FormFieldRow(
  67. label: '费用类型',
  68. value: _expenseType,
  69. onTap: _showTypePicker,
  70. ),
  71. Padding(
  72. padding: const EdgeInsets.fromLTRB(14, 0, 14, 12),
  73. child: Row(
  74. children: [
  75. const SizedBox(
  76. width: 72,
  77. child: Text('预计金额',
  78. style: TextStyle(
  79. color: AppColors.textSecondary, fontSize: 13)),
  80. ),
  81. const SizedBox(width: 8),
  82. Expanded(
  83. child: TextFormField(
  84. controller: _amountController,
  85. keyboardType:
  86. const TextInputType.numberWithOptions(decimal: true),
  87. decoration: const InputDecoration(
  88. hintText: '请输入金额',
  89. border: OutlineInputBorder(),
  90. contentPadding:
  91. EdgeInsets.symmetric(horizontal: 10, vertical: 8),
  92. isDense: true,
  93. ),
  94. validator: (value) {
  95. if (value == null || value.isEmpty) {
  96. return '请输入预计金额';
  97. }
  98. if (double.tryParse(value) == null) {
  99. return '请输入有效数字';
  100. }
  101. return null;
  102. },
  103. ),
  104. ),
  105. ],
  106. ),
  107. ),
  108. Padding(
  109. padding: const EdgeInsets.fromLTRB(14, 0, 14, 12),
  110. child: Row(
  111. children: [
  112. const SizedBox(
  113. width: 72,
  114. child: Text('用途说明',
  115. style: TextStyle(
  116. color: AppColors.textSecondary, fontSize: 13)),
  117. ),
  118. const SizedBox(width: 8),
  119. Expanded(
  120. child: TextFormField(
  121. controller: _purposeController,
  122. maxLines: 2,
  123. decoration: const InputDecoration(
  124. hintText: '请输入用途说明',
  125. border: OutlineInputBorder(),
  126. contentPadding:
  127. EdgeInsets.symmetric(horizontal: 10, vertical: 8),
  128. isDense: true,
  129. ),
  130. validator: (value) {
  131. if (value == null || value.isEmpty) {
  132. return '请输入用途说明';
  133. }
  134. return null;
  135. },
  136. ),
  137. ),
  138. ],
  139. ),
  140. ),
  141. FormFieldRow(
  142. label: '备注',
  143. value: _remarkController.text.isEmpty ? null : _remarkController.text,
  144. hint: '选填',
  145. onTap: _showRemarkEditor,
  146. ),
  147. ],
  148. );
  149. }
  150. Widget _buildBottomButtons() {
  151. return Container(
  152. padding: const EdgeInsets.all(12),
  153. decoration: BoxDecoration(
  154. color: Colors.white,
  155. boxShadow: [
  156. BoxShadow(
  157. color: Colors.black.withValues(alpha: 0.05),
  158. blurRadius: 4,
  159. offset: const Offset(0, -1),
  160. ),
  161. ],
  162. ),
  163. child: Row(
  164. children: [
  165. Expanded(
  166. child: TDButton(
  167. text: '存草稿',
  168. type: TDButtonType.outline,
  169. theme: TDButtonTheme.primary,
  170. size: TDButtonSize.medium,
  171. onTap: _handleSaveDraft,
  172. ),
  173. ),
  174. const SizedBox(width: 12),
  175. Expanded(
  176. flex: 2,
  177. child: TDButton(
  178. text: '提交申请',
  179. type: TDButtonType.fill,
  180. theme: TDButtonTheme.primary,
  181. size: TDButtonSize.medium,
  182. onTap: _handleSubmit,
  183. ),
  184. ),
  185. ],
  186. ),
  187. );
  188. }
  189. void _showTypePicker() {
  190. showModalBottomSheet(
  191. context: context,
  192. builder: (_) => Column(
  193. mainAxisSize: MainAxisSize.min,
  194. children: _types
  195. .map((t) => ListTile(
  196. title: Text(t),
  197. onTap: () {
  198. setState(() => _expenseType = t);
  199. Navigator.pop(context);
  200. },
  201. ))
  202. .toList(),
  203. ),
  204. );
  205. }
  206. void _showRemarkEditor() {
  207. showGeneralDialog(
  208. context: context,
  209. pageBuilder: (_, __, ___) => TDAlertDialog(
  210. title: '备注',
  211. contentWidget: TextField(
  212. controller: _remarkController,
  213. maxLines: 3,
  214. decoration: const InputDecoration(
  215. hintText: '请输入备注信息…', border: OutlineInputBorder()),
  216. ),
  217. leftBtn: TDDialogButtonOptions(
  218. title: '取消',
  219. action: () => Navigator.pop(context),
  220. ),
  221. rightBtn: TDDialogButtonOptions(
  222. title: '确定',
  223. theme: TDButtonTheme.primary,
  224. action: () {
  225. setState(() {});
  226. Navigator.pop(context);
  227. },
  228. ),
  229. ),
  230. );
  231. }
  232. void _handleSaveDraft() {
  233. if (!_formKey.currentState!.validate()) return;
  234. ScaffoldMessenger.of(context).showSnackBar(
  235. const SnackBar(content: Text('草稿已保存')),
  236. );
  237. context.pop();
  238. }
  239. void _handleSubmit() {
  240. if (!_formKey.currentState!.validate()) return;
  241. ScaffoldMessenger.of(context).showSnackBar(
  242. const SnackBar(content: Text('提交成功')),
  243. );
  244. context.pop();
  245. }
  246. }