feat: 完成班级模块全量功能迭代

本次提交完成了班级模块的多项核心功能开发与优化:
1. 新增DELETE请求封装,支持业务删除接口且不自动重试
2. 完善UserPostCommonDto模型,新增头像字段适配详情接口
3. 重构路由架构,使用StatefulShellRoute实现Tab页面保活
4. 新增我的互动页面,包含我的动态/点赞/留言三个子页
5. 新增全部同学页面,展示班级全员信息与在线状态
6. 新增动态详情页面,支持查看完整评论与点赞头像
7. 抽离通用PostListBody组件,复用列表分页与交互逻辑
8. 优化班级列表页加载逻辑与图片预览交互
9. 新增评论删除、多图预览等细节交互功能
This commit is contained in:
崔宝新
2026-09-07 09:20:06 +08:00
parent 0c5aee59c0
commit 586c75f841
11 changed files with 1331 additions and 87 deletions
+73 -36
View File
@@ -2,6 +2,8 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:go_router/go_router.dart';
import 'package:new_learn/core/components/image_preview_viewer.dart';
import 'package:new_learn/core/components/user_circle_avatar.dart';
import 'package:new_learn/core/models/user_model.dart';
import 'package:new_learn/core/providers/user_provider.dart';
@@ -121,7 +123,10 @@ class _ClassUpdateViewState extends ConsumerState<ClassUpdateView> {
..addAll(res.data);
_pageIndex = 1;
_prevPageId = res.data.isNotEmpty ? res.data.last.id : 0;
_hasMore = res.data.length >= _pageSize;
// 优先用总条数判断是否还有更多;total 不可用时退回"本页满页才继续"
_hasMore = res.total > 0
? res.data.length < res.total
: res.data.length >= _pageSize;
_loading = false;
});
} catch (_) {
@@ -147,7 +152,10 @@ class _ClassUpdateViewState extends ConsumerState<ClassUpdateView> {
_posts.addAll(res.data);
_pageIndex += 1;
if (res.data.isNotEmpty) _prevPageId = res.data.last.id;
_hasMore = res.data.length >= _pageSize;
// 累计长度 vs 总条数,即使每页都返回满页也能在到底后正确收尾
_hasMore = res.total > 0
? _posts.length < res.total
: res.data.length >= _pageSize;
_loadingMore = false;
});
} catch (_) {
@@ -258,24 +266,43 @@ class _ClassUpdateViewState extends ConsumerState<ClassUpdateView> {
controller: _scrollController,
physics: const AlwaysScrollableScrollPhysics(),
padding: EdgeInsets.symmetric(horizontal: 14.w),
itemCount: _posts.length + (_hasMore ? 1 : 0),
itemCount: _posts.length + 1,
itemBuilder: (context, index) {
if (index >= _posts.length) {
// 底部占位:加载中显示菊花,没有更多则显示提示文案
if (_hasMore) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 16.h),
child: const Center(
child: SizedBox(
width: 22,
height: 22,
child: CircularProgressIndicator(strokeWidth: 2.5),
),
),
);
}
return Padding(
padding: EdgeInsets.symmetric(vertical: 16.h),
child: const Center(
child: SizedBox(
width: 22,
height: 22,
child: CircularProgressIndicator(strokeWidth: 2.5),
padding: EdgeInsets.symmetric(vertical: 20.h),
child: Center(
child: Text(
'没有更多了',
style: TextStyle(
fontSize: 12.sp,
color: const Color(0xFFB4C2E0),
),
),
),
);
}
return PostCard(
post: _posts[index],
onLike: () => _toggleLike(index),
onComment: (content) => _submitComment(index, content),
final post = _posts[index];
return GestureDetector(
onTap: () => context.push('/class/my-update-detail', extra: post),
child: PostCard(
post: post,
onLike: () => _toggleLike(index),
onComment: (content) => _submitComment(index, content),
),
);
},
),
@@ -357,39 +384,49 @@ class _ClassUpdateViewState extends ConsumerState<ClassUpdateView> {
for (int i = 0; i < urls.length; i++)
Positioned(
left: i * overlap,
child: UserCircleAvatar(url: urls[i], radius: size / 2),
child: GestureDetector(
// 点头像 → 大图预览单张
onTap: () => ImagePreviewViewer.showSingle(
context,
imageProvider: CachedNetworkImageProvider(urls[i]),
),
child: UserCircleAvatar(url: urls[i], radius: size / 2),
),
),
],
),
);
}
/// 右侧「全部」按钮
/// 右侧「全部」按钮 → 全部同学页
Widget _buildAllButton() {
return Container(
width: 62.w,
height: 34.h,
alignment: Alignment.center,
decoration: BoxDecoration(
gradient: const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xFF3D7DF5), Color(0xFF3C84FF), Color(0xFF8AD1FD)],
return GestureDetector(
onTap: () => context.push('/class/all-student'),
child: Container(
width: 62.w,
height: 34.h,
alignment: Alignment.center,
decoration: BoxDecoration(
gradient: const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xFF3D7DF5), Color(0xFF3C84FF), Color(0xFF8AD1FD)],
),
borderRadius: BorderRadius.only(
topRight: Radius.circular(15.r),
bottomLeft: Radius.circular(15.r),
bottomRight: Radius.circular(20.r),
),
),
borderRadius: BorderRadius.only(
topRight: Radius.circular(15.r),
bottomLeft: Radius.circular(15.r),
bottomRight: Radius.circular(20.r),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text('全部', style: TextStyle(fontSize: 14, color: Colors.white)),
SizedBox(width: 2.w),
Icon(Icons.chevron_right, size: 14, color: Colors.white),
],
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text('全部', style: TextStyle(fontSize: 14, color: Colors.white)),
SizedBox(width: 2.w),
Icon(Icons.chevron_right, size: 14, color: Colors.white),
],
),
);
}