expense_application_apply_page.dart 7.2 KB

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