重构参赛队伍相关模型,更新API接口以支持队伍详情获取;

调整UI逻辑以使用新的数据结构,优化比赛信息展示;
更新构建脚本以支持新功能。
This commit is contained in:
2026-07-22 13:35:53 +08:00
parent d6e80df5bf
commit 1264ebdd7c
8 changed files with 140 additions and 153 deletions
+1 -1
View File
@@ -23,7 +23,7 @@ class _AuthPageWidgetState extends ConsumerState<AuthPageWidget> {
void initState() {
super.initState();
_controller = TextEditingController();
_controller?.text = '555';
// _controller?.text = '299689';
WidgetsBinding.instance.addPostFrameCallback((_) async {
final token = AppStorage.getString(StorageKeys.authToken);
@@ -42,44 +42,59 @@ class CompetitionMatchup {
}
}
/// 参赛项目/组别列表项(来自 /api/events/device/item/group
class CompetitionTeamListItem {
const CompetitionTeamListItem({
required this.id,
required this.eventName,
required this.itemName,
required this.eventId,
required this.itemId,
required this.name,
required this.groupName,
required this.matchPlace,
required this.matchStartTime,
required this.matchEndTime,
required this.matchups,
this.completed = false,
this.matchStartTime = '',
this.matchEndTime = '',
this.matchups = const [],
});
final String id;
final String eventName;
final String itemName;
final String eventId;
final String itemId;
final String name;
final String groupName;
final String matchPlace;
final String matchStartTime;
final String matchEndTime;
/// 对阵明细暂未由列表接口返回,默认空
final List<CompetitionMatchup> matchups;
final bool completed;
String get title => groupName.isEmpty ? itemName : '$itemName $groupName';
String get title => groupName.isEmpty ? name : '$name $groupName';
String get scheduleTime => '$matchStartTime-$matchEndTime';
String get scheduleTime {
if (matchStartTime.isEmpty && matchEndTime.isEmpty) return '';
if (matchStartTime.isNotEmpty && matchEndTime.isNotEmpty) {
return '$matchStartTime-$matchEndTime';
}
if (matchStartTime.isNotEmpty) return matchStartTime;
return matchEndTime;
}
factory CompetitionTeamListItem.fromJson(Map<String, dynamic> json) {
return CompetitionTeamListItem(
eventId: (json['eventId'] ?? '').toString(),
itemId: (json['itemId'] ?? '').toString(),
name: (json['name'] ?? '').toString(),
groupName: (json['groupName'] ?? '').toString(),
matchStartTime: (json['matchStartTime'] ?? '').toString(),
matchEndTime: (json['matchEndTime'] ?? '').toString(),
);
}
CompetitionTeamListItem copyWith({List<CompetitionMatchup>? matchups}) {
return CompetitionTeamListItem(
id: id,
eventName: eventName,
itemName: itemName,
eventId: eventId,
itemId: itemId,
name: name,
groupName: groupName,
matchPlace: matchPlace,
matchStartTime: matchStartTime,
matchEndTime: matchEndTime,
matchups: matchups ?? this.matchups,
completed: completed,
);
}
}
@@ -98,4 +113,59 @@ class CompetitionTeamPageResult {
final int pageSize;
bool get hasMore => page * pageSize < total;
factory CompetitionTeamPageResult.fromJson(
dynamic json, {
required int page,
required int pageSize,
}) {
if (json is List) {
// 整表无分页:本页即全部,hasMore = false
final items = json
.whereType<Map>()
.map(
(item) => CompetitionTeamListItem.fromJson(
Map<String, dynamic>.from(item),
),
)
.toList(growable: false);
final effectivePageSize = items.isEmpty ? pageSize : items.length;
return CompetitionTeamPageResult(
items: items,
total: items.length,
page: page,
pageSize: effectivePageSize,
);
}
if (json is Map) {
final map = Map<String, dynamic>.from(json);
final rawItems =
map['items'] ?? map['rows'] ?? map['list'] ?? map['records'];
final items = rawItems is List
? rawItems
.whereType<Map>()
.map(
(item) => CompetitionTeamListItem.fromJson(
Map<String, dynamic>.from(item),
),
)
.toList(growable: false)
: const <CompetitionTeamListItem>[];
final total = (map['total'] as num?)?.toInt() ?? items.length;
return CompetitionTeamPageResult(
items: items,
total: total,
page: page,
pageSize: pageSize,
);
}
return CompetitionTeamPageResult(
items: const [],
total: 0,
page: page,
pageSize: pageSize,
);
}
}
@@ -8,9 +8,14 @@ import 'package:recording_tool/shared/widgets/app_empty_view.dart';
import 'package:recording_tool/shared/widgets/app_toast.dart';
class CompetitionTeamDetailPage extends ConsumerWidget {
const CompetitionTeamDetailPage({super.key, required this.itemId});
const CompetitionTeamDetailPage({
super.key,
required this.itemId,
required this.eventId,
});
final String itemId;
final String eventId;
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -19,7 +24,7 @@ class CompetitionTeamDetailPage extends ConsumerWidget {
);
CompetitionTeamListItem? item;
for (final candidate in items) {
if (candidate.id == itemId) {
if (candidate.itemId == itemId) {
item = candidate;
break;
}
@@ -30,6 +35,8 @@ class CompetitionTeamDetailPage extends ConsumerWidget {
appBar: AppBar(title: Text(item?.title ?? '参赛队伍')),
body: item == null
? const AppEmptyView(message: '未找到对阵信息')
: item.matchups.isEmpty
? const AppEmptyView(message: '暂无对阵信息')
: ListView.separated(
padding: EdgeInsets.fromLTRB(20.w, 24.h, 20.w, 36.h),
itemCount: item.matchups.length,
@@ -42,7 +49,7 @@ class CompetitionTeamDetailPage extends ConsumerWidget {
onManualProcess: () => _handleManualProcess(
context,
ref,
itemId: item!.id,
itemId: item!.itemId,
matchup: matchup,
),
);
@@ -61,7 +61,10 @@ class _CompetitionTeamListPageState
return _CompetitionScheduleCard(
item: item,
onTap: () => AppNavigator.push(
CompetitionTeamDetailPage(itemId: item.id),
CompetitionTeamDetailPage(
itemId: item.itemId,
eventId: item.eventId,
),
context: context,
),
);
@@ -82,8 +85,10 @@ class _CompetitionScheduleCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
final scheduleText = item.scheduleTime.isEmpty ? '时间待定' : item.scheduleTime;
return Material(
key: ValueKey('competition-team-item-${item.id}'),
key: ValueKey('competition-team-item-${item.itemId}'),
color: Colors.white,
borderRadius: BorderRadius.circular(14.r),
child: InkWell(
@@ -120,40 +125,18 @@ class _CompetitionScheduleCard extends StatelessWidget {
),
),
SizedBox(height: 18.h),
_InfoLine(
icon: Icons.location_on_outlined,
text: item.matchPlace,
),
SizedBox(height: 12.h),
_InfoLine(
icon: Icons.schedule_outlined,
text: item.scheduleTime,
text: scheduleText,
),
],
),
),
SizedBox(width: 14.w),
Container(
width: 72.w,
height: 72.h,
alignment: Alignment.center,
decoration: BoxDecoration(
color: item.completed
? const Color(0xFFF1F3F6)
: const Color(0xFFEAF3FF),
borderRadius: BorderRadius.circular(16.r),
),
child: Text(
item.completed ? '已完成' : item.matchPlace,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: item.completed ? 14.sp : 20.sp,
fontWeight: FontWeight.w700,
color: item.completed
? const Color(0xFF69717D)
: const Color(0xFF147FEA),
),
),
Icon(
Icons.chevron_right,
size: 28.r,
color: const Color(0xFF9AA3AF),
),
],
),
@@ -1,118 +1,40 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:recording_tool/app/config/api_common.dart';
import 'package:recording_tool/core/network/api_client.dart';
import 'package:recording_tool/core/network/providers/dio_providers.dart';
import 'package:recording_tool/features/competition_teams/model/model_competition_team.dart';
final competitionTeamsServerProvider = Provider<CompetitionTeamsServer>((ref) {
return const CompetitionTeamsServer();
return CompetitionTeamsServer(ref.watch(apiClientProvider));
});
class CompetitionTeamsServer {
const CompetitionTeamsServer();
CompetitionTeamsServer(this._apiClient);
static const totalCount = 10;
final ApiClient _apiClient;
Future<CompetitionTeamPageResult> fetchPage({
required int page,
required int pageSize,
}) async {
final start = (page - 1) * pageSize;
if (start >= totalCount) {
return CompetitionTeamPageResult(
items: const [],
total: totalCount,
}) {
return _apiClient.get(
AuthApi.getTeamList.path,
queryParameters: {'page': page, 'pageSize': pageSize},
parser: (json) => CompetitionTeamPageResult.fromJson(
json,
page: page,
pageSize: pageSize,
);
}
final end = (start + pageSize).clamp(0, totalCount);
final items = List.generate(
end - start,
(index) => _buildItem(start + index),
);
return CompetitionTeamPageResult(
items: items,
total: totalCount,
page: page,
pageSize: pageSize,
);
}
CompetitionTeamListItem _buildItem(int index) {
final number = index + 1;
final group = index.isEven ? '小学组' : '初中组';
final hour = 9 + index ~/ 2;
return CompetitionTeamListItem(
id: 'competition-$number',
eventName: '全国青少年无人机大赛',
itemName: index % 3 == 0 ? '空中足球赛' : '空中格斗赛',
groupName: group,
matchPlace: '场地 ${index % 4 + 1}',
matchStartTime: '${hour.toString().padLeft(2, '0')}:00',
matchEndTime: '${hour.toString().padLeft(2, '0')}:30',
completed: index == totalCount - 1,
matchups: [
_buildMatchup(itemNumber: number, matchNumber: 1),
_buildMatchup(itemNumber: number, matchNumber: 2),
],
);
}
CompetitionMatchup _buildMatchup({
required int itemNumber,
required int matchNumber,
}) {
final matchupId = 'match-$itemNumber-$matchNumber';
final teamAIndex = (itemNumber + matchNumber - 2) % _teamNames.length;
final teamBIndex = (teamAIndex + 1) % _teamNames.length;
return CompetitionMatchup(
id: matchupId,
teamA: _buildTeam(
id: '$matchupId-team-a',
name: _teamNames[teamAIndex],
playerOffset: teamAIndex * 3,
),
teamB: _buildTeam(
id: '$matchupId-team-b',
name: _teamNames[teamBIndex],
playerOffset: teamBIndex * 3,
),
);
}
CompetitionTeam _buildTeam({
required String id,
required String name,
required int playerOffset,
Future<dynamic> fetchTeamDetail({
required String itemId,
required String eventId,
}) {
return CompetitionTeam(
id: id,
name: name,
players: List.generate(3, (index) {
final playerIndex = (playerOffset + index) % _playerNames.length;
return CompetitionPlayer(
id: '$id-player-$index',
name: _playerNames[playerIndex],
);
}),
return _apiClient.get(
AuthApi.getTeamDetail.path,
queryParameters: {'itemId': itemId, 'eventId': eventId},
);
}
static const _teamNames = ['王多鱼队', '钱多多队', '逐风少年队', '蓝翼飞行队', '星火队'];
static const _playerNames = [
'王伟',
'李庆超',
'王大亮',
'韩宁政',
'阮晴桦',
'刘美玲',
'陈子航',
'周白芷',
'林培伦',
'蔡依婷',
'夏志豪',
'赵云飞',
'孙雨泽',
'吴佳琪',
'郑凯文',
];
}
@@ -47,7 +47,7 @@ class CompetitionTeamsViewModel extends StateNotifier<CompetitionTeamsState> {
}) {
final updatedItems = state.items
.map((item) {
if (item.id != itemId) return item;
if (item.itemId != itemId) return item;
final updatedMatchups = item.matchups
.map((matchup) {
if (matchup.id != matchupId) return matchup;
@@ -67,7 +67,7 @@ class CompetitionTeamsViewModel extends StateNotifier<CompetitionTeamsState> {
CompetitionTeamListItem? findItem(String itemId) {
for (final item in state.items) {
if (item.id == itemId) return item;
if (item.itemId == itemId) return item;
}
return null;
}