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

336 lines
12 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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),
),
),
),
);
},
);
}