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'; /// punish_modal.dart /// 动态高度:isp == false 时较矮,isp == true 时较高;切换时带动画,并兼容键盘弹出。 class PunishModal extends StatefulWidget { 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'; _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?.isEmpty ?? true) { 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) { // 注意:showPunishDialog 会限制外层 maxHeight(可被键盘缩小) // 在这里我们读取父级给定的最大高度,并基于 isp 决定目标高度百分比 return LayoutBuilder(builder: (context, constraints) { // constraints.maxHeight 是外部 ConstrainedBox(showPunishDialog)给的 availableHeight final double availableMaxHeight = constraints.maxHeight.isFinite ? constraints.maxHeight : MediaQuery.of(context).size.height * 0.8; final double maxWidth = constraints.maxWidth.isFinite ? constraints.maxWidth : MediaQuery.of(context).size.width * 0.9; // 比例配置:若想微调大小请改这里 const double expandedRatio = 0.75; // isp == true 时占 availableMaxHeight 的比例 const double collapsedRatio = 0.3; // isp == false 时占 availableMaxHeight 的比例 final double targetHeight = isp ? (availableMaxHeight * expandedRatio).clamp(260.0, availableMaxHeight) : (availableMaxHeight * collapsedRatio).clamp(180.0, availableMaxHeight); // AnimatedContainer 用于在 isp 切换时平滑过渡高度 return Center( child: AnimatedContainer( duration: const Duration(milliseconds: 250), curve: Curves.easeInOut, width: maxWidth, height: targetHeight, child: Material( color: Colors.transparent, child: Container( decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8)), child: Column( mainAxisSize: MainAxisSize.max, 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( 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)), ], ), ), // 中间内容:使用 Expanded + SingleChildScrollView 保证在高度受限时可以滚动 Expanded( child: SingleChildScrollView( padding: const EdgeInsets.all(12), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 是否进行罚款(切换 isp 时会触发 setState,进而调整 AnimatedContainer 高度) ListItemFactory.createYesNoSection( title: '是否进行罚款', horizontalPadding: 0, groupValue: isp, onChanged: (v) => setState(() => isp = v), ), const SizedBox(height: 8), 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: dateText ?? '', onTap: () async { DateTime? picked = await BottomDateTimePicker.showDate(context); if (picked != null) { setState(() { dateText = DateFormat('yyyy-MM-dd HH:mm').format(picked); }); } }, ), ] else ...[ // isp == false 时,你或许希望显示少量提示或空白占位,这里留空 const SizedBox(height: 6), ], 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)), ), ), ], ), ), ], ), ), ), ), ); }); } } /// showPunishDialog:外层仅限制 maxHeight(受键盘影响) Future showPunishDialog( BuildContext context, { Map? initial, required void Function(Map) onSubmit, }) { final mq = MediaQuery.of(context); final viewInsets = mq.viewInsets; // 键盘高度 // availableHeight = 屏高 - 键盘高度 - 外围 padding final double availableHeight = mq.size.height - viewInsets.vertical - 32.0; final double maxWidth = mq.size.width * 0.9; return showDialog( context: context, barrierDismissible: false, builder: (context) { return AnimatedPadding( padding: viewInsets + const EdgeInsets.all(16), duration: const Duration(milliseconds: 200), curve: Curves.decelerate, child: SafeArea( child: Center( child: ConstrainedBox( constraints: BoxConstraints(maxHeight: availableHeight, maxWidth: maxWidth), child: Material( color: Colors.transparent, child: PunishModal(initial: initial, onSubmit: onSubmit), ), ), ), ), ); }, ); }