From 7191c4f42fb0723f3e9a2518b49a0709561a0c79 Mon Sep 17 00:00:00 2001 From: hs <873121290@qq.com> Date: Wed, 3 Jun 2026 15:00:26 +0800 Subject: [PATCH] =?UTF-8?q?=E7=89=B9=E6=AE=8A=E4=BD=9C=E4=B8=9A=E4=BF=AE?= =?UTF-8?q?=E6=94=B90602?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/customWidget/guarded_tap.dart | 152 ++++++++++++++++++ .../Tap/special_work_apply_base_page.dart | 56 ++++--- .../home/Tap/special_work_task_page_base.dart | 37 ++++- .../home/Tap/special_work_wait_scaffold.dart | 14 +- 4 files changed, 222 insertions(+), 37 deletions(-) create mode 100644 lib/customWidget/guarded_tap.dart diff --git a/lib/customWidget/guarded_tap.dart b/lib/customWidget/guarded_tap.dart new file mode 100644 index 0000000..4729776 --- /dev/null +++ b/lib/customWidget/guarded_tap.dart @@ -0,0 +1,152 @@ +import 'package:flutter/material.dart'; +import 'package:qhd_prevention/tools/click_util.dart'; + +class GuardedGestureDetector extends StatelessWidget { + final Widget child; + final GuardedTapCallback? onTap; + final Object? guardKey; + final int delayMs; + final bool lockDuringExecution; + final HitTestBehavior? behavior; + + const GuardedGestureDetector({ + super.key, + required this.child, + this.onTap, + this.guardKey, + this.delayMs = ClickUtil.defaultDelayMs, + this.lockDuringExecution = true, + this.behavior, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + behavior: behavior, + onTap: ClickUtil.wrap( + onTap, + key: guardKey, + delayMs: delayMs, + lockDuringExecution: lockDuringExecution, + ), + child: child, + ); + } +} + +class GuardedInkWell extends StatelessWidget { + final Widget child; + final GuardedTapCallback? onTap; + final Object? guardKey; + final int delayMs; + final bool lockDuringExecution; + final BorderRadius? borderRadius; + final Color? splashColor; + final Color? highlightColor; + + const GuardedInkWell({ + super.key, + required this.child, + this.onTap, + this.guardKey, + this.delayMs = ClickUtil.defaultDelayMs, + this.lockDuringExecution = true, + this.borderRadius, + this.splashColor, + this.highlightColor, + }); + + @override + Widget build(BuildContext context) { + return InkWell( + borderRadius: borderRadius, + splashColor: splashColor, + highlightColor: highlightColor, + onTap: ClickUtil.wrap( + onTap, + key: guardKey, + delayMs: delayMs, + lockDuringExecution: lockDuringExecution, + ), + child: child, + ); + } +} + +class GuardedTextButton extends StatelessWidget { + final Widget child; + final GuardedTapCallback? onPressed; + final Object? guardKey; + final int delayMs; + final bool lockDuringExecution; + final ButtonStyle? style; + + const GuardedTextButton({ + super.key, + required this.child, + this.onPressed, + this.guardKey, + this.delayMs = ClickUtil.defaultDelayMs, + this.lockDuringExecution = true, + this.style, + }); + + @override + Widget build(BuildContext context) { + return TextButton( + style: style, + onPressed: ClickUtil.wrap( + onPressed, + key: guardKey, + delayMs: delayMs, + lockDuringExecution: lockDuringExecution, + ), + child: child, + ); + } +} + +class GuardedIconButton extends StatelessWidget { + final Widget icon; + final GuardedTapCallback? onPressed; + final Object? guardKey; + final int delayMs; + final bool lockDuringExecution; + final EdgeInsetsGeometry? padding; + final BoxConstraints? constraints; + final double? iconSize; + final String? tooltip; + final Color? color; + + const GuardedIconButton({ + super.key, + required this.icon, + this.onPressed, + this.guardKey, + this.delayMs = ClickUtil.defaultDelayMs, + this.lockDuringExecution = true, + this.padding, + this.constraints, + this.iconSize, + this.tooltip, + this.color, + }); + + @override + Widget build(BuildContext context) { + return IconButton( + icon: icon, + padding: padding, + constraints: constraints, + iconSize: iconSize, + tooltip: tooltip, + color: color, + onPressed: ClickUtil.wrap( + onPressed, + key: guardKey, + delayMs: delayMs, + lockDuringExecution: lockDuringExecution, + ), + ); + } +} diff --git a/lib/pages/home/Tap/special_work_apply_base_page.dart b/lib/pages/home/Tap/special_work_apply_base_page.dart index b1026d1..bd50430 100644 --- a/lib/pages/home/Tap/special_work_apply_base_page.dart +++ b/lib/pages/home/Tap/special_work_apply_base_page.dart @@ -48,6 +48,7 @@ abstract class SpecialWorkApplyBaseState extends State { bool isEditable = true; bool loadingInit = false; + bool _isSubmitting = false; List levelList = []; final List workTypeList = const ["内部作业", "相关方作业"]; @@ -856,44 +857,48 @@ abstract class SpecialWorkApplyBaseState /// 通用提交入口 Future submit(String status) async { - await beforeSubmit(status); - if (!FormUtils.hasValue(pd, 'workLevel')) { - ToastUtil.showNormal(context, '请选择作业等级'); - return; - } - final preparers = - enableSafeProtection - ? safeController.buildPreparers() - : >[]; + if (_isSubmitting) return; - final newGroups = await handleSignLogs(preparers); + _isSubmitting = true; + LoadingDialogHelper.show(); - if (status == '1') { - if (!await checkForm(newGroups)) { + try { + await beforeSubmit(status); + if (!FormUtils.hasValue(pd, 'workLevel')) { + ToastUtil.showNormal(context, '请选择作业等级'); return; } + final preparers = + enableSafeProtection + ? safeController.buildPreparers() + : >[]; - if (enableSafeProtection) { - final err = safeController.validate(isSubmit: true); - if (err != null) { - ToastUtil.showNormal(context, err); + final newGroups = await handleSignLogs(preparers); + + if (status == '1') { + if (!await checkForm(newGroups)) { + return; + } + + if (enableSafeProtection) { + final err = safeController.validate(isSubmit: true); + if (err != null) { + ToastUtil.showNormal(context, err); + return; + } + } + + if (!await validateExtraForm()) { return; } } - if (!await validateExtraForm()) { + if (!await preSubmitUpload()) { return; } - } - if (!await preSubmitUpload()) { - return; - } + final payload = await buildSubmitPayload(status, newGroups); - final payload = await buildSubmitPayload(status, newGroups); - - LoadingDialogHelper.show(); - try { final result = status == '1' ? await SpecialWorkApi.specialWorkSave(payload, workType) @@ -909,6 +914,7 @@ abstract class SpecialWorkApplyBaseState ToastUtil.showNormal(context, '作业提交失败:$e'); } finally { LoadingDialogHelper.dismiss(); + _isSubmitting = false; } } diff --git a/lib/pages/home/Tap/special_work_task_page_base.dart b/lib/pages/home/Tap/special_work_task_page_base.dart index 63cc3af..21a956e 100644 --- a/lib/pages/home/Tap/special_work_task_page_base.dart +++ b/lib/pages/home/Tap/special_work_task_page_base.dart @@ -86,7 +86,7 @@ abstract class SpecialWorkTaskPageBase extends StatefulWidget { /// 子类可重写:提交成功后怎么返回。 /// 默认返回上一页。 void onSubmitSuccess(BuildContext context) { - Navigator.of(context).pop(); + Navigator.of(context).pop(true); } @override @@ -132,6 +132,8 @@ class _SpecialWorkTaskPageBaseState extends State { /// 备注/意见输入 final TextEditingController _contentController = TextEditingController(); + bool _isSubmitting = false; + @override void initState() { super.initState(); @@ -273,11 +275,22 @@ class _SpecialWorkTaskPageBaseState extends State { /// - 是否定位 /// - 是否需要额外附件/人数/日期 Future _submit(String status) async { + if (_isSubmitting) return; + + _isSubmitting = true; + LoadingDialogHelper.show(); + + void finishSubmit() { + LoadingDialogHelper.hide(); + _isSubmitting = false; + } + final Map others = {}; bool isDelayTime = true; if (pd['componentName'] == 'completeDelayTime' && other['isCompleteWork'] == 1) { if (other['isDelayWork'] == 1 && !FormUtils.hasValue(other, 'hotTime')) { + finishSubmit(); ToastUtil.showNormal(context, '请选择动火日期'); return; } @@ -299,6 +312,7 @@ class _SpecialWorkTaskPageBaseState extends State { context, '【$stepName】未完成,当前填写$currentFilllTimes次,应填写$minFillTimes次,无法流转到下一步', ); + finishSubmit(); return; } } @@ -314,6 +328,7 @@ class _SpecialWorkTaskPageBaseState extends State { if (qNumber.isNotEmpty) { for (var i = 0; i < qNumber.length; i++) { if (qNumber[i].isNotEmpty && (i >= aNumber.length || aNumber[i].isEmpty)) { + finishSubmit(); ToastUtil.showNormal(context, '安全防护措施第${index + 1}项未填写内容'); return; } @@ -321,11 +336,13 @@ class _SpecialWorkTaskPageBaseState extends State { } if ('${measure['status']}' == '-1' && widget.mesureText.isNotEmpty) { + finishSubmit(); ToastUtil.showNormal(context, '存在不合格的安全措施,请打回'); return; } if (!FormUtils.hasValue(measure, 'signPath')) { + finishSubmit(); ToastUtil.showNormal(context, '安全防护措施第${index + 1}项请签字'); return; } @@ -336,6 +353,7 @@ class _SpecialWorkTaskPageBaseState extends State { // 3) 定位必填 if (FormUtils.hasValue(pd, 'locateStepFlag') && pd['locateStepFlag'] == 1) { if (pd['longitude'] == null || pd['latitude'] == null) { + finishSubmit(); ToastUtil.showNormal(context, '请定位'); return; } @@ -347,6 +365,7 @@ class _SpecialWorkTaskPageBaseState extends State { final selectLevel = group['selectLevel']; if (selectLevel != 1 && other['isCompleteWork'] != 998) { if (!(FormUtils.hasValue(other, 'isDelayWork') && other['isDelayWork'] == 1)) { + finishSubmit(); ToastUtil.showNormal(context, '请选择${group['actorField'] ?? '处理人'}'); return; } @@ -357,9 +376,11 @@ class _SpecialWorkTaskPageBaseState extends State { // 5) 签字必填 if (signImages.isEmpty) { if (!FormUtils.hasValue(other, 'isCompleteWork')) { + finishSubmit(); ToastUtil.showNormal(context, '请签字'); return; } else if (other['isCompleteWork'] == 1) { + finishSubmit(); ToastUtil.showNormal(context, '请签字'); return; } @@ -404,17 +425,16 @@ class _SpecialWorkTaskPageBaseState extends State { form['signLogs'] = _groups; } - LoadingDialogHelper.show(); final signResult = await _checkSignature(); if (!signResult) { - LoadingDialogHelper.hide(); + finishSubmit(); ToastUtil.showNormal(context, '作业提交失败'); return; } try { final res = await SpecialWorkApi.specialWorkNextStep(form); - LoadingDialogHelper.hide(); + finishSubmit(); if (res['success'] == true) { ToastUtil.showNormal(context, '提交成功'); widget.onSubmitSuccess(context); @@ -422,7 +442,7 @@ class _SpecialWorkTaskPageBaseState extends State { ToastUtil.showNormal(context, res['errMessage'] ?? '作业提交失败'); } } catch (e) { - LoadingDialogHelper.hide(); + finishSubmit(); ToastUtil.showNormal(context, '作业提交失败'); } } @@ -666,11 +686,12 @@ class _SpecialWorkTaskPageBaseState extends State { barrierDismissible:false, ); if (confirmed != null) { - if (confirmed.trim().isNotEmpty) { + if (!mounted) return; + final rejectText = confirmed.trim(); + if (rejectText.isNotEmpty) { setState(() { - rejectReason = '${confirmed}'; + rejectReason = rejectText; }); - Navigator.pop(context); _submit('2'); }else{ ToastUtil.showError(context, '请输入打回原因'); diff --git a/lib/pages/home/Tap/special_work_wait_scaffold.dart b/lib/pages/home/Tap/special_work_wait_scaffold.dart index beba331..9fc9fc7 100644 --- a/lib/pages/home/Tap/special_work_wait_scaffold.dart +++ b/lib/pages/home/Tap/special_work_wait_scaffold.dart @@ -449,6 +449,12 @@ class _SpecialWorkWaitPageBaseState extends State { } } + Future _handleOpenDetail(Map item, bool isEdit) async { + await widget.onOpenDetail(context, item, isEdit); + if (!mounted) return; + _refreshList(); + } + Widget _buildListItem(Map item) { final workInfo = widget.getWorkInfo(item); final info = widget.getInfo(workInfo); @@ -456,12 +462,12 @@ class _SpecialWorkWaitPageBaseState extends State { // 准备按钮回调 final onFlowTap = () => _openFlowDrawer(item); - final onViewTap = () => widget.onOpenDetail(context, item, false); + final onViewTap = () => _handleOpenDetail(item, false); final onWithdrawTap = widget.canWithdraw(item, workInfo) ? () => _handleWithdraw(item) : null; final onDeleteTap = widget.canDeleteAndEdit(item, workInfo) ? () => _handleDelete(item) : null; - final onEditTap = widget.canDeleteAndEdit(item, workInfo) ? () => widget.onOpenDetail(context, item, true) : null; - final onApproveTap = widget.canApprove(item, workInfo) ? () => widget.onOpenDetail(context, item, true) : null; - final onGasAnalysisTap = widget.isGasStep(item) ? () => widget.onOpenDetail(context, item, true) : null; + final onEditTap = widget.canDeleteAndEdit(item, workInfo) ? () => _handleOpenDetail(item, true) : null; + final onApproveTap = widget.canApprove(item, workInfo) ? () => _handleOpenDetail(item, true) : null; + final onGasAnalysisTap = widget.isGasStep(item) ? () => _handleOpenDetail(item, true) : null; return Card( color: Colors.white,