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

336 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';
/// 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<String, String>? initial;
final void Function(Map<String, String> form) onSubmit;
const PunishModal({Key? key, this.initial, required this.onSubmit})
: super(key: key);
@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();
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<void> showPunishDialog(
BuildContext context, {
Map<String, String>? initial,
required void Function(Map<String, String>) onSubmit,
}) {
return showDialog<void>(
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),
),
),
),
);
},
);
}