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:
崔宝新
2026-09-07 15:01:14 +08:00
parent 586c75f841
commit 7175bf3054
6 changed files with 659 additions and 287 deletions
+111 -30
View File
@@ -330,7 +330,7 @@ class _ClassUpdateViewState extends ConsumerState<ClassUpdateView> {
return Container(
height: 58.h,
margin: EdgeInsets.fromLTRB(14.w, 12.h, 14.w, 15.h),
padding: EdgeInsets.fromLTRB(2.w, 2.h, 5.w, 2.h),
// padding: EdgeInsets.fromLTRB(2.w, 2.h, 5.w, 2.h),
decoration: BoxDecoration(
image: DecorationImage(
image: CachedNetworkImageProvider(
@@ -339,12 +339,30 @@ class _ClassUpdateViewState extends ConsumerState<ClassUpdateView> {
fit: BoxFit.fill,
),
),
child: Row(
child: Stack(
fit: StackFit.expand,
children: [
_buildOnlineLeft(),
const Spacer(),
_buildAvatarStack(),
_buildAllButton(),
LayoutBuilder(
builder: (context, rowConstraints) {
final rowWidth = rowConstraints.maxWidth;
return Row(
children: [
_buildOnlineLeft(),
Expanded(
// 占据剩余空间,把真实可用宽度传给头像组动态计算数量
child: LayoutBuilder(
builder: (context, constraints) =>
_buildAvatarStack(constraints.maxWidth, rowWidth),
),
),
// 预留「全部」按钮宽度(按钮已悬浮于右上角,不在 Row 内占位)
SizedBox(width: 62.w),
],
);
},
),
// 「全部」按钮:悬浮右上角(对标 Vue position: absolute; top/right: 2rpx)
Positioned(top: 1, right: 1, child: _buildAllButton()),
],
),
);
@@ -354,7 +372,38 @@ class _ClassUpdateViewState extends ConsumerState<ClassUpdateView> {
return Row(
children: [
SizedBox(width: 15.w),
const Text('在线同学', style: TextStyle(fontSize: 14, color: _titleColor)),
// 「在线同学」底部渐变高亮条(对标 Vue 的 linear-gradient 下划线)
// clipBehavior: Clip.none 允许渐变条溢出 Stack 边界(bottom 为负值时不被裁剪)
Stack(
clipBehavior: Clip.none,
children: [
Positioned(
left: 0,
bottom: -4,
child: Container(
width: 59.5.w,
height: 9.h,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50.r),
gradient: const LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
stops: [0, 0.3578, 1],
colors: [
Color(0x808AECFD), // rgba(138,236,253,0.5)
Color(0x331D9CFF), // rgba(29,156,255,0.2)
Color(0x003D7DF5), // rgba(61,125,245,0)
],
),
),
),
),
const Text(
'在线同学',
style: TextStyle(fontSize: 14, color: _titleColor),
),
],
),
SizedBox(width: 5.w),
Text(
'$_onlineCount',
@@ -370,30 +419,40 @@ class _ClassUpdateViewState extends ConsumerState<ClassUpdateView> {
);
}
/// 头像组:在线在前、离线在后,最多展示 6 个
Widget _buildAvatarStack() {
final urls = _avatarUrls.take(6).toList();
if (urls.isEmpty) return const SizedBox.shrink();
/// 头像组:在线在前、离线在后,数量按剩余宽度动态计算(对标 Vue updateAvatarMaxCount,最多 20)
/// 可用宽度限制在右侧背景块内(对标 Vue 左区 40% + 44rpx 内边距):
/// 左界 = 行宽 40% + 22px,右界 = 「全部」按钮左缘;同时不超过文字右侧空间,避免压住文字
Widget _buildAvatarStack(double maxWidth, double rowWidth) {
final size = 25.w;
final overlap = size * 0.94;
return SizedBox(
width: size + (urls.length - 1) * overlap,
height: size,
child: Stack(
children: [
for (int i = 0; i < urls.length; i++)
Positioned(
left: i * overlap,
child: GestureDetector(
// 点头像 → 大图预览单张
onTap: () => ImagePreviewViewer.showSingle(
context,
imageProvider: CachedNetworkImageProvider(urls[i]),
// 0.6 行宽 − 22px 左间距 − 62px 按钮宽
final rightBlockAvail = rowWidth * 0.6 - 84.w;
final avail = maxWidth < rightBlockAvail ? maxWidth : rightBlockAvail;
// 首个占满 size,后续每个只前进 overlap
final fitCount = avail >= size ? ((avail - size) / overlap).floor() + 1 : 0;
final urls = _avatarUrls.take(fitCount > 20 ? 20 : fitCount).toList();
if (urls.isEmpty) return const SizedBox.shrink();
return Align(
alignment: Alignment.centerRight,
child: SizedBox(
width: size + (urls.length - 1) * overlap,
height: size,
child: Stack(
children: [
for (int i = 0; i < urls.length; i++)
Positioned(
left: i * overlap,
child: GestureDetector(
// 点头像 → 大图预览单张
onTap: () => ImagePreviewViewer.showSingle(
context,
imageProvider: CachedNetworkImageProvider(urls[i]),
),
child: UserCircleAvatar(url: urls[i], radius: size / 2),
),
child: UserCircleAvatar(url: urls[i], radius: size / 2),
),
),
],
],
),
),
);
}
@@ -404,7 +463,7 @@ class _ClassUpdateViewState extends ConsumerState<ClassUpdateView> {
onTap: () => context.push('/class/all-student'),
child: Container(
width: 62.w,
height: 34.h,
height: 33.5.h,
alignment: Alignment.center,
decoration: BoxDecoration(
gradient: const LinearGradient(
@@ -412,10 +471,32 @@ class _ClassUpdateViewState extends ConsumerState<ClassUpdateView> {
end: Alignment.bottomCenter,
colors: [Color(0xFF3D7DF5), Color(0xFF3C84FF), Color(0xFF8AD1FD)],
),
// 对标 Vue border-radius: 0 30rpx 0 40rpx(右上 15 / 左下 20,其余直角)
borderRadius: BorderRadius.only(
topRight: Radius.circular(15.r),
bottomLeft: Radius.circular(15.r),
bottomRight: Radius.circular(20.r),
bottomLeft: Radius.circular(20.r),
),
// 对标 Vue box-shadow 外层:0 15rpx 11.3rpx rgba(61,125,245,0.08)
boxShadow: const [
BoxShadow(
color: Color(0x143D7DF5),
offset: Offset(0, 7.5),
blurRadius: 5.65,
),
],
),
// 对标 Vue box-shadow 内层 inset 0 2rpx 18.4rpx rgba(183,236,255,0.56)
// Flutter 无内阴影,用顶部渐变近似(浅蓝光晕从顶边向下淡出)
foregroundDecoration: BoxDecoration(
borderRadius: BorderRadius.only(
topRight: Radius.circular(15.r),
bottomLeft: Radius.circular(20.r),
),
gradient: const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
stops: [0, 0.3],
colors: [Color(0x8FB7ECFF), Color(0x00B7ECFF)],
),
),
child: Row(