336 lines
12 KiB
Dart
336 lines
12 KiB
Dart
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),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
},
|
||
);
|
||
}
|