import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:qhd_prevention/customWidget/ItemWidgetFactory.dart'; import 'package:qhd_prevention/customWidget/picker/CupertinoDatePicker.dart'; import 'package:qhd_prevention/customWidget/toast_util.dart'; import 'package:qhd_prevention/pages/home/tap/item_list_widget.dart'; /// punlish_modal.dart /// 把你提供的 uniapp 弹窗翻译为 Flutter 版本。 /// 提供: /// - 可复用的 PunishModal Widget /// - showPunishDialog 辅助函数 /// - 一个最小的 main() 示例展示如何调用 class PunishModal extends StatefulWidget { /// 初始数据(可选),key 与原 uniapp 对应: /// "ISPUNISH" : '1' 或 '2' /// "REASON" /// "AMOUT" /// "RECTIFICATIONDEPT_NAME" /// "RECTIFICATIONOR_NAME" /// "DATE" (字符串) final Map? initial; final void Function(Map form) onSubmit; const PunishModal({Key? key, this.initial, required this.onSubmit}) : super(key: key); @override _PunishModalState createState() => _PunishModalState(); } class _PunishModalState extends State { late bool isp = false; // 默认否 late TextEditingController reasonController; late TextEditingController amountController; late TextEditingController deptController; late TextEditingController personController; String? dateText; final FocusNode _amountFocus = FocusNode(); final _formKey = GlobalKey(); @override void initState() { super.initState(); reasonController = TextEditingController( text: widget.initial?['REASON'] ?? '', ); amountController = TextEditingController( text: widget.initial?['AMOUT'] ?? '', ); deptController = TextEditingController( text: widget.initial?['RECTIFICATIONDEPT_NAME'] ?? '', ); personController = TextEditingController( text: widget.initial?['RECTIFICATIONOR_NAME'] ?? '', ); dateText = widget.initial?['DATE']; isp = widget.initial?['ISPUNISH'] == '1' ? true : false; // 相当于 @blur=\"checkNumber\",监听焦点失去时做检查 _amountFocus.addListener(() { if (!_amountFocus.hasFocus) { _checkNumber(); } }); } @override void dispose() { reasonController.dispose(); amountController.dispose(); deptController.dispose(); personController.dispose(); _amountFocus.dispose(); super.dispose(); } void _checkNumber() { final text = amountController.text.trim(); if (text.isEmpty) return; // 允许整数或两位小数 final reg = RegExp(r"^\d+(?:\.\d{1,2})?$"); if (!reg.hasMatch(text)) { // 显示错误提示,并做简单修正(可按需改变) ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('请输入有效的金额(最多两位小数)'))); // 尝试提取数字部分 final match = RegExp(r"\d+(?:\.\d{1,2})?").firstMatch(text); if (match != null) { amountController.text = match.group(0)!; } else { amountController.clear(); } } } void _onConfirm() { if (isp) { if (reasonController.text.trim().isEmpty) { ToastUtil.showNormal(context, '请填写处罚原因'); return; } if (amountController.text.trim().isEmpty) { ToastUtil.showNormal(context, '请填写处罚金额'); return; } if (dateText?.length == 0) { ToastUtil.showNormal(context, '请选择下发处罚时间'); return; } } final result = { 'ISPUNISH': isp ? "1" : "2", 'REASON': reasonController.text.trim(), 'AMOUT': amountController.text.trim(), 'RECTIFICATIONDEPT_NAME': deptController.text.trim(), 'RECTIFICATIONOR_NAME': personController.text.trim(), 'DATE': dateText ?? '', }; widget.onSubmit(result); Navigator.of(context).pop(); } @override Widget build(BuildContext context) { // 我们改为根据内容自动伸缩,但当内容过高时仍然限制最大高度并可滚动。 final maxBodyHeight = MediaQuery.of(context).size.height * 0.6; // 可按需调整 return Material( color: Colors.transparent, child: Center( child: Container( width: MediaQuery.of(context).size.width * 0.9, // 不再强制固定高度,使用 Column 的 min size,让弹窗高度随内容变化 decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ // Header Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 10, ), decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(8)), ), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ const Expanded( child: Text( '处罚', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, ), ), ), IconButton( onPressed: () => Navigator.of(context).pop(), icon: const Icon(Icons.close, color: Colors.red), ), ], ), ), // Body (表单) // 使用 ConstrainedBox 限制最大高度,内部使用 SingleChildScrollView,这样当字段少时弹窗高度自动变小,字段多时会出现滚动。 ConstrainedBox( constraints: BoxConstraints(maxHeight: maxBodyHeight), child: SingleChildScrollView( padding: const EdgeInsets.all(12), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 是否进行罚款 ListItemFactory.createYesNoSection( title: '是否进行罚款', horizontalPadding: 0, groupValue: isp, onChanged: (v) => setState(() => isp = v), ), const SizedBox(height: 8), // 以下内容仅在 isp == '1' 时显示 if (isp) ...[ ItemListWidget.singleLineTitleText( label: '处罚原因', isEditable: true, controller: reasonController, hintText: '请输入处罚原因', ), const Divider(), ItemListWidget.singleLineTitleText( label: '处罚金额(元)', isEditable: true, keyboardType: const TextInputType.numberWithOptions( decimal: true, ), controller: amountController, hintText: '请输入处罚金额', ), const Divider(), ItemListWidget.singleLineTitleText( label: '被处罚单位', isEditable: false, text: widget.initial?['RECTIFICATIONDEPT_NAME'] ?? '', ), const Divider(), ItemListWidget.singleLineTitleText( label: '被处罚人', isEditable: false, text: widget.initial?['RECTIFICATIONOR_NAME'] ?? '', ), const Divider(), ItemListWidget.selectableLineTitleTextRightButton( label: '下发处罚时间', isEditable: true, text: '', onTap: () async { DateTime? picked = await BottomDateTimePicker.showDate( context, ); if (picked != null) { setState(() { dateText = DateFormat( 'yyyy-MM-dd HH:mm', ).format(picked); }); } }, ), ], const SizedBox(height: 12), ], ), ), ), ), // Footer 按钮 Container( padding: const EdgeInsets.all(8), decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical( bottom: Radius.circular(8), ), ), child: Row( children: [ Expanded( child: OutlinedButton( onPressed: () => Navigator.of(context).pop(), child: const Text('关闭'), ), ), const SizedBox(width: 8), Expanded( child: ElevatedButton( style: ElevatedButton.styleFrom( foregroundColor: Colors.green, backgroundColor: Colors.white, side: const BorderSide(color: Colors.green), ), onPressed: _onConfirm, child: const Text( '确认', style: TextStyle(color: Colors.green), ), ), ), ], ), ), ], ), ), ), ); } Widget _buildLabel(String text) => Padding( padding: const EdgeInsets.only(bottom: 6), child: Text(text, style: const TextStyle(fontWeight: FontWeight.w600)), ); } /// 显示showPunishDialog Future showPunishDialog( BuildContext context, { Map? initial, required void Function(Map) onSubmit, }) { return showDialog( context: context, barrierDismissible: false, builder: (context) { final viewInsets = MediaQuery.of(context).viewInsets; return AnimatedPadding( padding: viewInsets + const EdgeInsets.all(16), duration: const Duration(milliseconds: 200), curve: Curves.decelerate, child: Align( alignment: Alignment.center, child: Material( color: Colors.transparent, child: Container( // 保持原来 Dialog 的圆角和背景 decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)), child: PunishModal(initial: initial, onSubmit: onSubmit), ), ), ), ); }, ); }