141 lines
5.5 KiB
Dart
141 lines
5.5 KiB
Dart
import 'package:flutter/material.dart';
|
||
|
||
/// 通用带输入框的确认弹窗组件
|
||
/// [title]:弹窗标题
|
||
/// [hintText]:输入框提示文字
|
||
/// [labelText]:输入框标签文字
|
||
/// [confirmText]:确认按钮文字(默认"保存")
|
||
/// [cancelText]:取消按钮文字(默认"取消")
|
||
/// [onConfirm]:确认按钮回调,参数为输入的文本,返回bool表示是否关闭弹窗
|
||
/// [inputValidator]:输入内容校验器,返回null表示校验通过,返回字符串表示错误提示
|
||
/// [maxWidth]:弹窗内容宽约束;横屏页面传一个较小的值,避免 AlertDialog 被输入框撑满屏幕
|
||
Future<void> 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),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
},
|
||
);
|
||
}
|