import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import '../utils/img_url.dart'; /// ============================================================ /// 【ConfirmBox】全局二次确认弹窗 —— 对标小程序 components/ConfirmBox /// /// 一、与 Vue3 版的架构差异: /// 小程序不支持动态插入 DOM,所以 Vue 版需要每个页面手动放置 /// ``,并在 service.js 里按路由注册 controller。 /// Flutter 的 showDialog 可以从任意 context 直接弹出(等价于 Web /// 动态挂载),因此这里无需注册机制,单文件即可全局使用。 /// /// 二、用法(对标 Vue 的 `await proxy.$confirm({...})`): /// final ok = await showConfirmBox( /// context, /// title: '提示', /// content: '确定要删除吗?', /// ); /// 点"确定"返回 true;点"取消"、点遮罩、按返回键返回 false。 /// ============================================================ Future showConfirmBox( BuildContext context, { String title = '提示', String content = '', String cancelText = '取消', String confirmText = '确定', bool showCancel = true, double? minWidth, // 弹窗最小宽度(逻辑像素,调用方自行 .w 换算) }) { return showDialog( context: context, barrierDismissible: true, // 点遮罩关闭 → false(对标 onPopupClose) barrierColor: Colors.black54, builder: (_) => ConfirmBox( title: title, content: content, cancelText: cancelText, confirmText: confirmText, showCancel: showCancel, minWidth: minWidth, ), ).then((result) => result ?? false); } class ConfirmBox extends StatelessWidget { const ConfirmBox({ super.key, required this.title, required this.content, required this.cancelText, required this.confirmText, required this.showCancel, this.minWidth, }); final String title; final String content; final String cancelText; final String confirmText; final bool showCancel; final double? minWidth; @override Widget build(BuildContext context) { return Dialog( backgroundColor: Colors.transparent, child: Container( padding: EdgeInsets.symmetric(vertical: 30.w, horizontal: 15.w), // 60rpx 30rpx constraints: minWidth != null ? BoxConstraints(minWidth: minWidth!) : null, decoration: BoxDecoration( borderRadius: BorderRadius.circular(20.w), // 40rpx image: DecorationImage( image: NetworkImage( ImgUrl.build('/StaticResource/2026-03-31/header-bac.png')), fit: BoxFit.fill, // background-size: 100% 100% ), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ if (title.isNotEmpty) Text( title, style: TextStyle( fontSize: 16.sp, // 32rpx fontWeight: FontWeight.w600, color: const Color(0xFF1D2B47), ), ), if (content.isNotEmpty) Padding( padding: EdgeInsets.only(top: 18.w), // 36rpx child: ConstrainedBox( constraints: BoxConstraints(maxWidth: 263.w), // max-width 526rpx child: Text( content, style: TextStyle( fontSize: 14.sp, // 28rpx color: const Color(0xFF1D2B47), ), ), ), ), Padding( padding: EdgeInsets.only(top: 23.w), // 46rpx child: Row( mainAxisSize: MainAxisSize.min, children: [ if (showCancel) ...[ _buildButton( text: cancelText, backgroundColor: const Color(0xFFFDFFFF), textColor: const Color(0xFF6195F6), onTap: () => Navigator.of(context).pop(false), ), SizedBox(width: 10.w), // 20rpx ], _buildButton( text: confirmText, gradient: const LinearGradient( // CSS: linear-gradient(360deg, #8AD1FD -11.22%, #3C84FF 60.2%, #3D7DF5 100%) begin: Alignment.bottomCenter, end: Alignment.topCenter, colors: [ Color(0xFF8AD1FD), Color(0xFF3C84FF), Color(0xFF3D7DF5), ], stops: [0, 0.602, 1], ), textColor: Colors.white, onTap: () => Navigator.of(context).pop(true), ), ], ), ), ], ), ), ); } /// 胶囊按钮(对标 default-button5 渐变确定键 / default-button6 白底取消键) Widget _buildButton({ required String text, required Color textColor, required VoidCallback onTap, Color? backgroundColor, Gradient? gradient, }) { return GestureDetector( onTap: onTap, child: Container( height: 46.w, // 92rpx padding: EdgeInsets.symmetric(horizontal: 20.w), constraints: BoxConstraints(minWidth: 135.w), // min-width 270rpx alignment: Alignment.center, decoration: BoxDecoration( color: backgroundColor, gradient: gradient, borderRadius: BorderRadius.circular(100), // border-radius: 100px boxShadow: gradient != null ? [ // CSS: 0px 15px 11.3px rgba(61,125,245,0.08) // (inset 内发光 Flutter 不支持,省略) BoxShadow( color: const Color(0x143D7DF5), offset: Offset(0, 15.w), blurRadius: 11.w, ), ] : null, ), child: Text( text, style: TextStyle(fontSize: 16.sp, color: textColor), // 32rpx ), ), ); } }