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
@@ -1,3 +1,5 @@
import 'dart:ui' as ui;
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
@@ -25,6 +27,7 @@ class PostCard extends StatelessWidget {
required this.onLike,
required this.onComment,
this.showCommentComposer = true,
this.hasCardBackground = true,
});
final UserPostDto post;
@@ -34,6 +37,9 @@ class PostCard extends StatelessWidget {
/// 是否显示卡片内嵌的评论预览 + 输入框(列表页 true;详情页由全量留言接管)
final bool showCommentComposer;
/// 是否显示白色卡片背景(列表页 true;详情页与留言区共用一张大卡片 → false)
final bool hasCardBackground;
String get _avatar {
final url = ImgUrl.build(post.avatar);
return url.isEmpty
@@ -54,6 +60,54 @@ class PostCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
final card = Stack(
children: [
Positioned(
top: 0,
left: 0,
right: 0,
height: 85.h,
child: IgnorePointer(
// 卡片顶部右上角光晕,对标 Vue SubjectCard::before 的双层 radial-gradient
child: CustomPaint(painter: _CardTopGlowPainter()),
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 21.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CardHeader(
avatar: _avatar,
name: _name,
subject: _subject,
time: _time,
),
if (post.type == 1) ...[
SizedBox(height: 5.h),
ResolveProblemBox(desc: post.content),
],
if (post.type == 2 || post.type == 3) ...[
SizedBox(height: 15.h),
AlbumDesc(text: post.content),
],
if (post.urls.isNotEmpty) ...[
SizedBox(height: 15.h),
AlbumGrid(urls: post.urls.map(ImgUrl.build).toList()),
],
SizedBox(height: 10.h),
LikeCommentBar(
post: post,
onLike: onLike,
onComment: onComment,
showComposer: showCommentComposer,
),
],
),
),
],
);
if (!hasCardBackground) return card;
return Container(
margin: EdgeInsets.only(bottom: 10.h),
clipBehavior: Clip.antiAlias,
@@ -61,67 +115,65 @@ class PostCard extends StatelessWidget {
color: Colors.white,
borderRadius: BorderRadius.circular(15.r),
),
child: Stack(
children: [
Positioned(
top: 0,
left: 0,
right: 0,
height: 85.h,
child: IgnorePointer(
child: DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topRight,
end: Alignment.bottomLeft,
colors: [
const Color(0xFFCEE9FF).withValues(alpha: 0.9),
const Color(0xFFF6FBFF).withValues(alpha: 0.0),
],
),
),
),
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 21.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CardHeader(
avatar: _avatar,
name: _name,
subject: _subject,
time: _time,
),
if (post.type == 1) ...[
SizedBox(height: 5.h),
ResolveProblemBox(desc: post.content),
],
if (post.type == 2 || post.type == 3) ...[
SizedBox(height: 15.h),
AlbumDesc(text: post.content),
],
if (post.urls.isNotEmpty) ...[
SizedBox(height: 15.h),
AlbumGrid(urls: post.urls.map(ImgUrl.build).toList()),
],
SizedBox(height: 10.h),
LikeCommentBar(
post: post,
onLike: onLike,
onComment: onComment,
showComposer: showCommentComposer,
),
],
),
),
],
),
child: card,
);
}
}
/// 卡片顶部光晕:对标 Vue `SubjectCard::before` 的两层 radial-gradient
/// (椭圆均以右上角为圆心,CSS 先写的在上层):
/// radial-gradient(120% 85% at 100% 0%,
/// rgba(206,233,255,1) 0%, rgba(206,233,255,.26) 10%, rgba(255,255,255,0) 58%)
/// radial-gradient(75% 55% at 100% 0%,
/// rgba(196,226,255,.88) 0%, rgba(255,255,255,1) 64%)
class _CardTopGlowPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
// 先画下层(更小、偏白的光晕),再画上层(更大的浅蓝光晕)
_paintGlow(
canvas,
size,
rx: size.width * 0.75,
ry: size.height * 0.55,
colors: [const Color(0xFFC4E2FF).withValues(alpha: 0.88), Colors.white],
stops: const [0, 0.64],
);
_paintGlow(
canvas,
size,
rx: size.width * 1.20,
ry: size.height * 0.85,
colors: [
const Color(0xFFCEE9FF),
const Color(0xFFCEE9FF).withValues(alpha: 0.26),
Colors.white.withValues(alpha: 0.0),
],
stops: const [0, 0.10, 0.58],
);
}
/// 用 canvas.scale 把圆形径向渐变拉成 CSS 的椭圆(圆心 = 右上角)
void _paintGlow(
Canvas canvas,
Size size, {
required double rx,
required double ry,
required List<Color> colors,
required List<double> stops,
}) {
canvas.save();
canvas.translate(size.width, 0);
canvas.scale(rx, ry);
final Paint paint = Paint()
..shader = ui.Gradient.radial(Offset.zero, 1.0, colors, stops);
canvas.drawRect(const Rect.fromLTWH(-1, 0, 1, 1), paint);
canvas.restore();
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
/// 卡片标题:头像 + 姓名 + 学科标签 + 时间
class CardHeader extends StatelessWidget {
const CardHeader({