import 'package:flutter/material.dart'; /// 通用带输入框的确认弹窗组件 /// [title]:弹窗标题 /// [hintText]:输入框提示文字 /// [labelText]:输入框标签文字 /// [confirmText]:确认按钮文字(默认"保存") /// [cancelText]:取消按钮文字(默认"取消") /// [onConfirm]:确认按钮回调,参数为输入的文本,返回bool表示是否关闭弹窗 /// [inputValidator]:输入内容校验器,返回null表示校验通过,返回字符串表示错误提示 /// [maxWidth]:弹窗内容宽约束;横屏页面传一个较小的值,避免 AlertDialog 被输入框撑满屏幕 Future showInputConfirmDialog({ required BuildContext context, required String title, required String hintText, String labelText = '', String confirmText = '保存', String cancelText = '取消', required Function(String inputText) onConfirm, String? Function(String inputText)? inputValidator, double? maxWidth, String? initialText, }) async { // 输入框控制器(initialText 非空时预填,用于改名等回显场景) final TextEditingController controller = TextEditingController( text: initialText ?? '', ); await showDialog( context: context, builder: (BuildContext context) { // 横屏页面(如区域编辑器)键盘常占半屏:AlertDialog 的 title/actions 不随 // scrollable 滚动,短屏下会被撑出 RenderFlex 溢出、输入框/按钮被裁而“不好改”。 // 改为自定义 Dialog:键盘弹出时贴顶对齐 + 整体限高 + 字段区可滚,按钮永远可见; // 没键盘时(如竖屏)仍居中,不改变原有观感。 final MediaQueryData mq = MediaQuery.of(context); final double keyboardInset = mq.viewInsets.bottom; final bool lifted = keyboardInset > 0; // 弹窗可用最大高度:屏高 - 键盘 - 顶部安全区,再夹到一个合理区间 final double maxDialogHeight = (mq.size.height - keyboardInset - 24).clamp( 140.0, 520.0, ); final double contentWidth = (maxWidth ?? 280) + 40; // 加左右 20 padding return Dialog( alignment: lifted ? Alignment.topCenter : Alignment.center, backgroundColor: Colors.white, surfaceTintColor: Colors.transparent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), insetPadding: EdgeInsets.fromLTRB( 24, mq.padding.top + (lifted ? 12 : 24), 24, keyboardInset + 12, ), child: ConstrainedBox( constraints: BoxConstraints( maxHeight: maxDialogHeight, maxWidth: contentWidth, ), child: Padding( padding: const EdgeInsets.fromLTRB(20, 16, 20, 8), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( title, style: const TextStyle( fontSize: 17, fontWeight: FontWeight.w700, ), ), const SizedBox(height: 12), // 字段区单独可滚:极矮屏下也能滚到输入框,不会把标题/按钮顶出去 Flexible( child: SingleChildScrollView( child: TextField( controller: controller, decoration: InputDecoration( hintText: hintText, border: const OutlineInputBorder(), labelText: labelText, ), autofocus: true, // 自动聚焦输入框 ), ), ), const SizedBox(height: 6), // 按钮行固定在底部,始终可见可点 Row( mainAxisAlignment: MainAxisAlignment.end, children: [ // 取消按钮 TextButton( onPressed: () { Navigator.of(context).pop(); }, child: Text(cancelText), ), // 确认按钮 TextButton( onPressed: () { // 获取输入文本并去空格 final String inputText = controller.text.trim(); // 1. 执行自定义校验 if (inputValidator != null) { final String? errorMsg = inputValidator(inputText); if (errorMsg != null) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(errorMsg), backgroundColor: Colors.red, ), ); return; } } // 2. 执行确认回调 onConfirm(inputText); // 3. 关闭弹窗 Navigator.of(context).pop(); }, child: Text(confirmText), ), ], ), ], ), ), ), ); }, ); }