flutter_integrated_whb/lib/pages/KeyProjects/Punishment/PunishmentModalAlert.dart

292 lines
12 KiB
Dart
Raw Normal View History

2025-08-14 15:05:48 +08:00
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';
2025-09-06 17:38:07 +08:00
/// punish_modal.dart
/// 动态高度isp == false 时较矮isp == true 时较高;切换时带动画,并兼容键盘弹出。
2025-08-14 15:05:48 +08:00
class PunishModal extends StatefulWidget {
final Map<String, String>? initial;
final void Function(Map<String, String> form) onSubmit;
const PunishModal({Key? key, this.initial, required this.onSubmit})
2025-09-06 17:38:07 +08:00
: super(key: key);
2025-08-14 15:05:48 +08:00
@override
_PunishModalState createState() => _PunishModalState();
}
class _PunishModalState extends State<PunishModal> {
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<FormState>();
@override
void initState() {
super.initState();
2025-09-06 17:38:07 +08:00
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'] ?? '');
2025-08-14 15:05:48 +08:00
dateText = widget.initial?['DATE'];
2025-09-06 17:38:07 +08:00
isp = widget.initial?['ISPUNISH'] == '1';
2025-08-14 15:05:48 +08:00
_amountFocus.addListener(() {
2025-09-06 17:38:07 +08:00
if (!_amountFocus.hasFocus) _checkNumber();
2025-08-14 15:05:48 +08:00
});
}
@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)) {
2025-09-06 17:38:07 +08:00
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('请输入有效的金额(最多两位小数)')),
);
2025-08-14 15:05:48 +08:00
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;
}
2025-09-06 17:38:07 +08:00
if (dateText?.isEmpty ?? true) {
2025-08-14 15:05:48 +08:00
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) {
2025-09-06 17:38:07 +08:00
// 注意showPunishDialog 会限制外层 maxHeight可被键盘缩小
// 在这里我们读取父级给定的最大高度,并基于 isp 决定目标高度百分比
return LayoutBuilder(builder: (context, constraints) {
// constraints.maxHeight 是外部 ConstrainedBoxshowPunishDialog给的 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;
2025-08-14 15:05:48 +08:00
2025-09-06 17:38:07 +08:00
// 比例配置:若想微调大小请改这里
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)),
2025-08-14 15:05:48 +08:00
),
2025-09-06 17:38:07 +08:00
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)),
],
2025-08-14 15:05:48 +08:00
),
2025-09-06 17:38:07 +08:00
),
2025-08-14 15:05:48 +08:00
2025-09-06 17:38:07 +08:00
// 中间内容:使用 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),
),
2025-08-14 15:05:48 +08:00
2025-09-06 17:38:07 +08:00
const SizedBox(height: 8),
2025-08-14 15:05:48 +08:00
2025-09-06 17:38:07 +08:00
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),
],
2025-08-14 15:05:48 +08:00
2025-09-06 17:38:07 +08:00
const SizedBox(height: 12),
],
),
),
2025-08-14 15:05:48 +08:00
),
),
2025-09-06 17:38:07 +08:00
// Footer 固定(不随内容滚动)
Container(
padding: const EdgeInsets.all(8),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(bottom: Radius.circular(8)),
2025-08-14 15:05:48 +08:00
),
2025-09-06 17:38:07 +08:00
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)),
),
2025-08-14 15:05:48 +08:00
),
2025-09-06 17:38:07 +08:00
],
2025-08-14 15:05:48 +08:00
),
2025-09-06 17:38:07 +08:00
),
],
2025-08-14 15:05:48 +08:00
),
2025-09-06 17:38:07 +08:00
),
2025-08-14 15:05:48 +08:00
),
),
2025-09-06 17:38:07 +08:00
);
});
2025-08-14 15:05:48 +08:00
}
}
2025-09-06 17:38:07 +08:00
/// showPunishDialog外层仅限制 maxHeight受键盘影响
2025-08-14 15:05:48 +08:00
Future<void> showPunishDialog(
2025-09-06 17:38:07 +08:00
BuildContext context, {
Map<String, String>? initial,
required void Function(Map<String, String>) 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;
2025-08-14 15:05:48 +08:00
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (context) {
return AnimatedPadding(
padding: viewInsets + const EdgeInsets.all(16),
duration: const Duration(milliseconds: 200),
curve: Curves.decelerate,
2025-09-06 17:38:07 +08:00
child: SafeArea(
child: Center(
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: availableHeight, maxWidth: maxWidth),
child: Material(
color: Colors.transparent,
child: PunishModal(initial: initial, onSubmit: onSubmit),
),
2025-08-14 15:05:48 +08:00
),
),
),
);
},
);
}