Files
崔宝新 d5f73cfbb5 refactor(class): 优化接口请求与页面细节
1. 统一处理后端统一包结构,自动解析data字段
2. 新增点赞评论栏显示控制参数,详情页隐藏该栏
3. 简化详情页数据更新逻辑,对齐Vue版本实现
4. 重构确认弹窗组件,优化样式与弹窗尺寸
5. 为评论项添加水平边距,优化列表排版
2026-09-07 16:02:14 +08:00

199 lines
6.6 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../utils/img_url.dart';
/// ============================================================
/// 【ConfirmBox】全局二次确认弹窗 —— 对标小程序 components/ConfirmBox
///
/// 一、与 Vue3 版的架构差异:
/// 小程序不支持动态插入 DOM,所以 Vue 版需要每个页面手动放置
/// `<ConfirmBox />`,并在 service.js 里按路由注册 controller。
/// Flutter 的 showDialog 可以从任意 context 直接弹出(等价于 Web
/// 动态挂载),因此这里无需注册机制,单文件即可全局使用。
///
/// 二、用法(对标 Vue 的 `await proxy.$confirm({...})`):
/// final ok = await showConfirmBox(
/// context,
/// title: '提示',
/// content: '确定要删除吗?',
/// );
/// 点"确定"返回 true;点"取消"、点遮罩、按返回键返回 false。
/// ============================================================
Future<bool> showConfirmBox(
BuildContext context, {
String title = '提示',
String content = '',
String cancelText = '取消',
String confirmText = '确定',
bool showCancel = true,
double? minWidth, // 弹窗最小宽度(逻辑像素,调用方自行 .w 换算)
}) {
// 默认弹窗固定宽度 310.w(按钮 135.w × 2 + 间距 10.w + 左右 15.w×2 = 310.w)
minWidth ??= 310.w;
return showDialog<bool>(
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,
// Dialog 默认左右 insetPadding: 40 太宽,310.w 在手机上放不下
// 这里减到 20,给 310.w 固定宽度留足空间(375-40=335 > 310)
insetPadding: EdgeInsets.symmetric(horizontal: 20.w),
child: Container(
width: minWidth, // 固定宽度(minWidth 语义变了:这里就是固定宽)
padding: EdgeInsets.symmetric(vertical: 30.w, horizontal: 15.w),
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),
isConfirm: 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),
isConfirm: true,
),
],
),
),
],
),
),
);
}
/// 胶囊按钮:取消键(白底+蓝字+边框+阴影)/ 确定键(渐变+边框+阴影)
Widget _buildButton({
required String text,
required Color textColor,
required VoidCallback onTap,
required bool isConfirm,
Color? backgroundColor,
Gradient? gradient,
}) {
return GestureDetector(
onTap: onTap,
child: Container(
width: 135.w,
height: 49.w,
alignment: Alignment.center,
decoration: BoxDecoration(
color: backgroundColor,
gradient: gradient,
borderRadius: BorderRadius.circular(50.w),
boxShadow: [
if (isConfirm)
BoxShadow(
color: Colors.black.withValues(alpha: 0.25),
offset: Offset(0, 1.w),
blurRadius: 9.2.w,
spreadRadius: 3.w,
)
else
BoxShadow(
color: Colors.black.withValues(alpha: 0.25),
offset: Offset(0, 2.w),
blurRadius: 12.2.w,
spreadRadius: 0.5.w,
),
],
),
child: Text(
text,
style: TextStyle(fontSize: 16.sp, color: textColor),
),
),
);
}
}