本次提交完成了班级模块的多项核心功能开发与优化: 1. 新增DELETE请求封装,支持业务删除接口且不自动重试 2. 完善UserPostCommonDto模型,新增头像字段适配详情接口 3. 重构路由架构,使用StatefulShellRoute实现Tab页面保活 4. 新增我的互动页面,包含我的动态/点赞/留言三个子页 5. 新增全部同学页面,展示班级全员信息与在线状态 6. 新增动态详情页面,支持查看完整评论与点赞头像 7. 抽离通用PostListBody组件,复用列表分页与交互逻辑 8. 优化班级列表页加载逻辑与图片预览交互 9. 新增评论删除、多图预览等细节交互功能
190 lines
5.8 KiB
Dart
190 lines
5.8 KiB
Dart
import 'package:flutter/material.dart';
|
|
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/user_circle_avatar.dart';
|
|
import 'package:new_learn/core/utils/img_url.dart';
|
|
|
|
/// ============================================================
|
|
/// 【AllStudentPage】「全部同学」页 —— 对标 Vue pages/classPackage/allStudent/index.vue
|
|
///
|
|
/// 一、入口:班级动态页右上角「全部」按钮。
|
|
/// 二、数据:getClassUsers 一次性拉全员(含在线状态),非分页。
|
|
/// 三、布局:按屏宽自适应列数铺满(对标 Vue updateStudentLayout),
|
|
/// 每格正方形,内部 头像 + 名字,头像右上角显示在线绿点。
|
|
/// 四、主题:整页渐变背景 + 透明导航栏(NavBar 叠在渐变上)。
|
|
/// ============================================================
|
|
class AllStudentPage extends StatefulWidget {
|
|
const AllStudentPage({super.key});
|
|
|
|
@override
|
|
State<AllStudentPage> createState() => _AllStudentPageState();
|
|
}
|
|
|
|
class _AllStudentPageState extends State<AllStudentPage> {
|
|
List<ClassUser> _students = [];
|
|
bool _loading = true;
|
|
bool _failed = false;
|
|
|
|
/// 手机端一行固定 4 个(对标 Vue 小屏下的 4 列网格布局)
|
|
static const int _columnCount = 4;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_fetchStudents();
|
|
}
|
|
|
|
Future<void> _fetchStudents() async {
|
|
setState(() {
|
|
_loading = true;
|
|
_failed = false;
|
|
});
|
|
try {
|
|
final users = await ClassApi.instance.getClassUsers();
|
|
if (!mounted) return;
|
|
setState(() {
|
|
_students = users;
|
|
_loading = false;
|
|
});
|
|
} catch (_) {
|
|
if (!mounted) return;
|
|
setState(() {
|
|
_loading = false;
|
|
_failed = true;
|
|
});
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
backgroundColor: const Color(0xFFEDF4FF),
|
|
// 背景渐变与 AppBar 同色(EDF4FF),网格体置于 AppBar 之下,
|
|
// 不再 extendBodyBehindAppBar,避免内容被标题/返回键遮挡。
|
|
appBar: AppBar(
|
|
backgroundColor: const Color(0xFFEDF4FF),
|
|
elevation: 0,
|
|
title: Text(
|
|
'全部同学 (${_students.length})',
|
|
style: const TextStyle(color: Color(0xFF1D2B47)),
|
|
),
|
|
leading: IconButton(
|
|
icon: const Icon(Icons.arrow_back, color: Color(0xFF1D2B47)),
|
|
onPressed: () =>
|
|
context.canPop() ? context.pop() : context.go('/class'),
|
|
),
|
|
),
|
|
body: Container(
|
|
width: double.infinity,
|
|
decoration: const BoxDecoration(
|
|
gradient: LinearGradient(
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
colors: [Color(0xFFEDF4FF), Color(0xFFD1E3FE)],
|
|
),
|
|
),
|
|
child: _buildBody(),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildBody() {
|
|
if (_loading) {
|
|
return const Center(child: CircularProgressIndicator());
|
|
}
|
|
if (_failed) {
|
|
return Center(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(
|
|
'加载失败,请检查网络',
|
|
style: TextStyle(fontSize: 14.sp, color: const Color(0xFF888888)),
|
|
),
|
|
SizedBox(height: 12.h),
|
|
FilledButton(onPressed: _fetchStudents, child: const Text('重新加载')),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
if (_students.isEmpty) {
|
|
return const Center(
|
|
child: Text('暂无同学', style: TextStyle(color: Color(0xFF888888))),
|
|
);
|
|
}
|
|
return GridView.builder(
|
|
padding: EdgeInsets.fromLTRB(14.w, 20.h, 14.w, 20.h),
|
|
physics: const AlwaysScrollableScrollPhysics(),
|
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
|
crossAxisCount: _columnCount,
|
|
crossAxisSpacing: 9.w,
|
|
mainAxisSpacing: 10.h,
|
|
childAspectRatio: 0.78,
|
|
),
|
|
itemCount: _students.length,
|
|
itemBuilder: (context, index) => _StudentItem(user: _students[index]),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 单个同学格:在线点 + 头像 + 名字
|
|
class _StudentItem extends StatelessWidget {
|
|
const _StudentItem({required this.user});
|
|
|
|
final ClassUser user;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Container(
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFF6FAFF),
|
|
borderRadius: BorderRadius.circular(8.r),
|
|
),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
// 头像 + 右上角在线点
|
|
Stack(
|
|
clipBehavior: Clip.none,
|
|
children: [
|
|
UserCircleAvatar(url: _avatarUrl(), radius: 17.5.w),
|
|
if (user.isOnline)
|
|
Positioned(
|
|
right: -2.w,
|
|
top: -2.w,
|
|
child: Container(
|
|
width: 7.w,
|
|
height: 7.w,
|
|
decoration: const BoxDecoration(
|
|
color: Color(0x8000CC96),
|
|
shape: BoxShape.circle,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 7.h),
|
|
Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: 2.w),
|
|
child: Text(
|
|
user.userName,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: const TextStyle(fontSize: 13, color: Color(0xFF1D2B47)),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
String _avatarUrl() {
|
|
final url = ImgUrl.build(user.avatar);
|
|
return url.isEmpty
|
|
? ImgUrl.build('/StaticResource/2026-04-03/fake-avatar.png')
|
|
: url;
|
|
}
|
|
}
|