From d5f73cfbb52e98d5475af6c351bb5af62c5b5ba8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B4=94=E5=AE=9D=E6=96=B0?= Date: Mon, 7 Sep 2026 16:02:14 +0800 Subject: [PATCH] =?UTF-8?q?refactor(class):=20=E4=BC=98=E5=8C=96=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E8=AF=B7=E6=B1=82=E4=B8=8E=E9=A1=B5=E9=9D=A2=E7=BB=86?= =?UTF-8?q?=E8=8A=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 统一处理后端统一包结构,自动解析data字段 2. 新增点赞评论栏显示控制参数,详情页隐藏该栏 3. 简化详情页数据更新逻辑,对齐Vue版本实现 4. 重构确认弹窗组件,优化样式与弹窗尺寸 5. 为评论项添加水平边距,优化列表排版 --- lib/core/api/class/api.dart | 9 ++- lib/core/components/confirm_box.dart | 58 ++++++++++++------- lib/features/class/my_update_detail_page.dart | 52 ++++++----------- .../class/widgets/class_update_widgets.dart | 17 ++++-- 4 files changed, 72 insertions(+), 64 deletions(-) diff --git a/lib/core/api/class/api.dart b/lib/core/api/class/api.dart index 28e2714..67cf887 100644 --- a/lib/core/api/class/api.dart +++ b/lib/core/api/class/api.dart @@ -113,11 +113,16 @@ class ClassApi { } /// 动态详情(含全量留言 / 点赞头像)—— GET /api/v1/UserPost/GetUserPostDetail/{id} + /// + /// 后端统一包 {code, data} 结构,需取 data 内层再解析(对标 Vue request 拦截器自动解包)。 Future getUserPostDetail(int id) async { - final data = await DioClient.instance.get( + final raw = await DioClient.instance.get( '/api/v1/UserPost/GetUserPostDetail/$id', ); - return UserPostDto.fromJson(data as Map); + final data = raw is Map && raw['data'] is Map + ? raw['data'] as Map + : raw as Map; + return UserPostDto.fromJson(data); } /// 删除评论 —— DELETE /api/v1/UserPost/DeleteUserCommon/{id} diff --git a/lib/core/components/confirm_box.dart b/lib/core/components/confirm_box.dart index 139bb35..15b1d33 100644 --- a/lib/core/components/confirm_box.dart +++ b/lib/core/components/confirm_box.dart @@ -29,6 +29,8 @@ Future showConfirmBox( bool showCancel = true, double? minWidth, // 弹窗最小宽度(逻辑像素,调用方自行 .w 换算) }) { + // 默认弹窗固定宽度 310.w(按钮 135.w × 2 + 间距 10.w + 左右 15.w×2 = 310.w) + minWidth ??= 310.w; return showDialog( context: context, barrierDismissible: true, // 点遮罩关闭 → false(对标 onPopupClose) @@ -66,15 +68,18 @@ class ConfirmBox extends StatelessWidget { 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( - padding: EdgeInsets.symmetric(vertical: 30.w, horizontal: 15.w), // 60rpx 30rpx - constraints: - minWidth != null ? BoxConstraints(minWidth: minWidth!) : null, + 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')), + ImgUrl.build('/StaticResource/2026-03-31/header-bac.png'), + ), fit: BoxFit.fill, // background-size: 100% 100% ), ), @@ -94,7 +99,9 @@ class ConfirmBox extends StatelessWidget { Padding( padding: EdgeInsets.only(top: 18.w), // 36rpx child: ConstrainedBox( - constraints: BoxConstraints(maxWidth: 263.w), // max-width 526rpx + constraints: BoxConstraints( + maxWidth: 263.w, + ), // max-width 526rpx child: Text( content, style: TextStyle( @@ -115,6 +122,7 @@ class ConfirmBox extends StatelessWidget { backgroundColor: const Color(0xFFFDFFFF), textColor: const Color(0xFF6195F6), onTap: () => Navigator.of(context).pop(false), + isConfirm: false, ), SizedBox(width: 10.w), // 20rpx ], @@ -133,6 +141,7 @@ class ConfirmBox extends StatelessWidget { ), textColor: Colors.white, onTap: () => Navigator.of(context).pop(true), + isConfirm: true, ), ], ), @@ -143,40 +152,45 @@ class ConfirmBox extends StatelessWidget { ); } - /// 胶囊按钮(对标 default-button5 渐变确定键 / default-button6 白底取消键) + /// 胶囊按钮:取消键(白底+蓝字+边框+阴影)/ 确定键(渐变+边框+阴影) Widget _buildButton({ required String text, required Color textColor, required VoidCallback onTap, + required bool isConfirm, 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 + width: 135.w, + height: 49.w, 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, + 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), // 32rpx + style: TextStyle(fontSize: 16.sp, color: textColor), ), ), ); diff --git a/lib/features/class/my_update_detail_page.dart b/lib/features/class/my_update_detail_page.dart index 5c2dbe6..dfebd6a 100644 --- a/lib/features/class/my_update_detail_page.dart +++ b/lib/features/class/my_update_detail_page.dart @@ -4,6 +4,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:go_router/go_router.dart'; import 'package:new_learn/core/api/class/api.dart'; import 'package:new_learn/core/api/class/model.dart'; +import 'package:new_learn/core/components/confirm_box.dart'; import 'package:new_learn/core/components/user_circle_avatar.dart'; import 'package:new_learn/core/providers/user_provider.dart'; import 'package:new_learn/core/utils/img_url.dart'; @@ -41,30 +42,19 @@ class _MyUpdateDetailPageState extends ConsumerState { /// 用详情接口增量更新评论相关字段(对标 Vue fetchDetail) /// - /// 关键:只有当 detail 确实带增量内容时才更新,且单个字段非空才覆盖。 - /// 否则后端未返回评论/计数(空壳)时,会把列表带入的正确数据"清空" - /// —— 表现为初次闪现对的数据后瞬间空白。 + /// 简化逻辑:接口成功返回就直接用详情数据覆盖评论相关字段, + /// 和 Vue 版一致(Vue 里 res.commons !== undefined 就直接覆盖)。 Future _fetchDetail() async { try { final res = await ClassApi.instance.getUserPostDetail(_post.id); if (!mounted) return; - // 空壳判定:detail 未携带任何有效增量 → 完全信任列表带入的数据,不更新 - final hasDetail = - res.commons.isNotEmpty || - res.userLikeAvatars.isNotEmpty || - res.replyCount > 0 || - res.likeCount > 0 || - res.userLikeStatus; - if (!hasDetail) return; setState(() { _post = _post.copyWith( - commons: res.commons.isNotEmpty ? res.commons : _post.commons, - userLikeAvatars: res.userLikeAvatars.isNotEmpty - ? res.userLikeAvatars - : _post.userLikeAvatars, + commons: res.commons, + userLikeAvatars: res.userLikeAvatars, userLikeStatus: res.userLikeStatus, - replyCount: res.replyCount > 0 ? res.replyCount : _post.replyCount, - likeCount: res.likeCount > 0 ? res.likeCount : _post.likeCount, + replyCount: res.replyCount, + likeCount: res.likeCount, ); }); } catch (_) { @@ -158,6 +148,7 @@ class _MyUpdateDetailPageState extends ConsumerState { onLike: _toggleLike, onComment: (_) {}, hasCardBackground: false, + showLikeCommentBar: false, ), _DetailComments(post: _post, onDelete: _deleteComment), ], @@ -199,22 +190,10 @@ class _DetailCommentsState extends ConsumerState<_DetailComments> { _active = null; _pending = index; }); - final ok = await showDialog( - context: context, - builder: (ctx) => AlertDialog( - title: const Text('是否删除评论'), - content: const Text('删除评论后将不可恢复,请慎重选择!'), - actions: [ - TextButton( - onPressed: () => Navigator.pop(ctx, false), - child: const Text('取消'), - ), - TextButton( - onPressed: () => Navigator.pop(ctx, true), - child: const Text('确定'), - ), - ], - ), + final ok = await showConfirmBox( + context, + title: '是否删除评论', + content: '删除评论后将不可恢复,请慎重选择!', ); if (!mounted) return; setState(() => _pending = null); @@ -287,7 +266,12 @@ class _DetailCommentsState extends ConsumerState<_DetailComments> { ); } else { for (int i = 0; i < _post.commons.length; i++) { - items.add(_commentItem(_post.commons[i], i, myId)); + items.add( + Padding( + padding: EdgeInsets.symmetric(horizontal: 16.w), + child: _commentItem(_post.commons[i], i, myId), + ), + ); } } items.add(SizedBox(height: 16.h)); diff --git a/lib/features/class/widgets/class_update_widgets.dart b/lib/features/class/widgets/class_update_widgets.dart index 84cb257..649c1cf 100644 --- a/lib/features/class/widgets/class_update_widgets.dart +++ b/lib/features/class/widgets/class_update_widgets.dart @@ -28,6 +28,7 @@ class PostCard extends StatelessWidget { required this.onComment, this.showCommentComposer = true, this.hasCardBackground = true, + this.showLikeCommentBar = true, }); final UserPostDto post; @@ -40,6 +41,9 @@ class PostCard extends StatelessWidget { /// 是否显示白色卡片背景(列表页 true;详情页与留言区共用一张大卡片 → false) final bool hasCardBackground; + /// 是否显示点赞/评论标记栏(列表页 true;详情页不需要 → false) + final bool showLikeCommentBar; + String get _avatar { final url = ImgUrl.build(post.avatar); return url.isEmpty @@ -96,12 +100,13 @@ class PostCard extends StatelessWidget { AlbumGrid(urls: post.urls.map(ImgUrl.build).toList()), ], SizedBox(height: 10.h), - LikeCommentBar( - post: post, - onLike: onLike, - onComment: onComment, - showComposer: showCommentComposer, - ), + if (showLikeCommentBar) + LikeCommentBar( + post: post, + onLike: onLike, + onComment: onComment, + showComposer: showCommentComposer, + ), ], ), ),