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

292 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';
/// punish_modal.dart
/// 动态高度isp == false 时较矮isp == true 时较高;切换时带动画,并兼容键盘弹出。
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})
: 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';
_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 是外部 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;
// 比例配置:若想微调大小请改这里
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<void> showPunishDialog(
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;
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,
child: SafeArea(
child: Center(
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: availableHeight, maxWidth: maxWidth),
child: Material(
color: Colors.transparent,
child: PunishModal(initial: initial, onSubmit: onSubmit),
),
),
),
),
);
},
);
}