feat: 新增全局确认弹窗、多页面UI优化与网络日志开关
1. 新增全局二次确认弹窗组件confirm_box,支持自定义参数并对标小程序实现
2. 优化班级板块多处UI:
- 修复point_rank_view中Web端字体溢出问题,使用FittedBox自适应缩放
- 重构class_update_widgets的PostCard,新增卡片背景控制开关并替换光晕实现
- 优化class_update_view的在线同学栏与头像组布局,支持动态计算展示数量
- 调整my_update_detail_page的评论区结构,修复评论高亮与删除浮层定位问题
3. 为dio_client新增网络日志总开关,可按需开启请求响应打印
4. 修复多处代码格式与注释细节问题
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
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 换算)
|
||||
}) {
|
||||
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,
|
||||
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
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user