重构事件注册模型并更新应用配置。更改不同环境的基本url,增强事件注册项结构,并改进ScanQrCodePage中的导航。在AuthViewModel中添加clearAuth方法,以便更好地进行状态管理。

This commit is contained in:
2026-07-17 11:55:13 +08:00
parent 831ca1059b
commit 1f9c5af51b
8 changed files with 187 additions and 183 deletions
+4 -3
View File
@@ -32,17 +32,18 @@ class AppConfig {
current = switch (environment) { current = switch (environment) {
AppEnvironment.dev => const EnvironmentValues( AppEnvironment.dev => const EnvironmentValues(
environment: AppEnvironment.dev, environment: AppEnvironment.dev,
baseUrl: 'http://192.168.1.104:8000', // baseUrl: 'http://192.168.1.104:8000',
baseUrl: 'https://apitest.dronex.cc',
enableNetworkLog: true, enableNetworkLog: true,
), ),
AppEnvironment.staging => const EnvironmentValues( AppEnvironment.staging => const EnvironmentValues(
environment: AppEnvironment.staging, environment: AppEnvironment.staging,
baseUrl: 'https://example.com/api', baseUrl: 'https://apitest.dronex.cc',
enableNetworkLog: true, enableNetworkLog: true,
), ),
AppEnvironment.prod => const EnvironmentValues( AppEnvironment.prod => const EnvironmentValues(
environment: AppEnvironment.prod, environment: AppEnvironment.prod,
baseUrl: 'https://example.com/api', baseUrl: 'https://api.dronex.cc',
enableNetworkLog: false, enableNetworkLog: false,
), ),
}; };
+1 -1
View File
@@ -27,7 +27,7 @@ class _AuthPageWidgetState extends ConsumerState<AuthPageWidget> {
WidgetsBinding.instance.addPostFrameCallback((_) async { WidgetsBinding.instance.addPostFrameCallback((_) async {
final token = AppStorage.getString(StorageKeys.authToken); final token = AppStorage.getString(StorageKeys.authToken);
if (token?.isNotEmpty ?? false) { if (token?.isNotEmpty ?? false) {
AppNavigator.pushReplacement(const ScanQrCodePage()); AppNavigator.push(const ScanQrCodePage());
} }
}); });
} }
@@ -65,4 +65,10 @@ class AuthViewModel extends StateNotifier<AuthState> {
state = state.copyWith(recordList: data.items); state = state.copyWith(recordList: data.items);
return true; return true;
} }
/// 清空授权信息(本地 token + 内存状态)
Future<void> clearAuth() async {
await AppStorage.remove(StorageKeys.authToken);
state = const AuthState();
}
} }
+57 -107
View File
@@ -91,7 +91,7 @@ class EventRegistrationList {
return EventProfile( return EventProfile(
name: name, name: name,
phone: '', phone: '',
eventTitle: items.isEmpty ? '赛事信息' : items.first.eventTitle, eventTitle: items.isEmpty ? '赛事信息' : items.first.eventName,
avatarUrl: avatar, avatarUrl: avatar,
); );
} }
@@ -101,40 +101,45 @@ class EventRegistrationItem {
const EventRegistrationItem({ const EventRegistrationItem({
required this.eventId, required this.eventId,
required this.itemId, required this.itemId,
required this.userId, required this.scheduleId,
required this.eventTitle, required this.eventName,
required this.name, required this.itemName,
required this.group, required this.groupName,
required this.venue, required this.matchPlace,
required this.time,
required this.playerName,
required this.playerPhone,
this.avatarLabel = '头像',
this.matchStartTime = '', this.matchStartTime = '',
this.matchEndTime = '', this.matchEndTime = '',
this.completed = false, this.completed = false,
this.laneNo, this.opponentId = '',
this.status, this.opponentName = '',
this.rawData = const {}, this.teamMembers,
this.userId = '',
this.playerName = '',
this.playerPhone = '',
}); });
final String eventId; final String eventId;
final String itemId; final String itemId;
final String userId; final String scheduleId;
final String eventTitle; final String eventName;
final String name; final String itemName;
final String group; final String groupName;
final String venue; final String matchPlace;
final String time;
final String playerName;
final String playerPhone;
final String avatarLabel;
final String matchStartTime; final String matchStartTime;
final String matchEndTime; final String matchEndTime;
final bool completed; final bool completed;
final String? laneNo; final String opponentId;
final String? status; final String opponentName;
final Map<String, dynamic> rawData; final List<dynamic>? teamMembers;
/// 来自报名列表父级,不在 item JSON 内
final String userId;
final String playerName;
final String playerPhone;
String get scheduleTime =>
_formatScheduleTime(matchStartTime, matchEndTime);
String? get statusLabel => completed ? '已完成' : null;
factory EventRegistrationItem.fromJson( factory EventRegistrationItem.fromJson(
Map<dynamic, dynamic> json, { Map<dynamic, dynamic> json, {
@@ -142,94 +147,48 @@ class EventRegistrationItem {
String playerName = '', String playerName = '',
}) { }) {
final map = Map<String, dynamic>.from(json); final map = Map<String, dynamic>.from(json);
final startTime = _readString(map, const ['matchStartTime', 'startTime']); final startTime = _readString(map, const ['matchStartTime']);
final endTime = _readString(map, const ['matchEndTime', 'endTime']); final endTime = _readString(map, const ['matchEndTime']);
final completed = _readBool(map, const ['completed']); final teamMembersRaw = map['teamMembers'];
return EventRegistrationItem( return EventRegistrationItem(
eventId: _readString(map, const ['eventId', 'eventsId', 'eventID']), eventId: _readString(map, const ['eventId']),
itemId: _readString(map, const ['itemId', 'eventItemId', 'itemID']), itemId: _readString(map, const ['itemId']),
userId: _readString(map, const [ scheduleId: _readString(map, const ['scheduleId']),
'userId', eventName: _readString(map, const ['eventName'], fallback: '赛事信息'),
'playerId', itemName: _readString(map, const ['itemName'], fallback: '未命名项目'),
'registrationId', groupName: _readString(map, const ['groupName']),
], fallback: userId), matchPlace: _readString(map, const ['matchPlace']),
eventTitle: _readString(map, const [
'eventTitle',
'eventName',
'competitionName',
'matchTitle',
], fallback: '赛事信息'),
name: _readString(map, const [
'name',
'itemName',
'eventItemName',
'matchName',
'projectName',
], fallback: '未命名项目'),
group: _readString(map, const [
'group',
'groupName',
'categoryName',
'levelName',
]),
venue: _readString(map, const [
'matchPlace',
'venue',
'venueName',
'siteName',
'fieldName',
'placeName',
]),
time: _readScheduleTime(map, startTime, endTime),
playerName: _readString(map, const [
'playerName',
'userName',
'nameCn',
'realName',
'athleteName',
], fallback: playerName),
playerPhone: _readString(map, const [
'playerPhone',
'phone',
'mobile',
'telephone',
'userPhone',
]),
laneNo: _readNullableString(map, const [
'laneNo',
'laneNumber',
'trackNo',
'number',
'serialNo',
]),
matchStartTime: startTime, matchStartTime: startTime,
matchEndTime: endTime, matchEndTime: endTime,
completed: completed, completed: _readBool(map, const ['completed']),
status: completed ? '已完成' : null, opponentId: _readString(map, const ['opponentId']),
rawData: map, opponentName: _readString(map, const ['opponentName']),
teamMembers: teamMembersRaw is List
? List<dynamic>.from(teamMembersRaw)
: null,
userId: userId,
playerName: playerName,
); );
} }
EventProfile toProfile() { EventProfile toProfile() {
return EventProfile( return EventProfile(
avatarLabel: avatarLabel,
name: playerName, name: playerName,
phone: playerPhone, phone: playerPhone,
eventTitle: eventTitle, eventTitle: eventName,
); );
} }
RecordingContext toRecordingContext({EventProfile? profile}) { RecordingContext toRecordingContext({EventProfile? profile}) {
return RecordingContext( return RecordingContext(
eventTitle: eventTitle, eventTitle: eventName,
matchName: name, matchName: itemName,
group: group, group: groupName,
venue: venue, venue: matchPlace,
time: time, time: scheduleTime,
playerName: profile?.name ?? playerName, playerName: profile?.name ?? playerName,
playerPhone: profile?.phone ?? playerPhone, playerPhone: profile?.phone ?? playerPhone,
laneNo: laneNo, status: statusLabel,
status: status,
); );
} }
} }
@@ -283,22 +242,13 @@ String _readString(
return _readNullableString(map, keys) ?? fallback; return _readNullableString(map, keys) ?? fallback;
} }
String _readScheduleTime( String _formatScheduleTime(String startTime, String endTime) {
Map<String, dynamic> map,
String startTime,
String endTime,
) {
if (startTime.isNotEmpty && endTime.isNotEmpty) { if (startTime.isNotEmpty && endTime.isNotEmpty) {
return '$startTime-$endTime'; return '$startTime-$endTime';
} }
if (startTime.isNotEmpty) return startTime; if (startTime.isNotEmpty) return startTime;
if (endTime.isNotEmpty) return endTime; if (endTime.isNotEmpty) return endTime;
return _readString(map, const [ return '';
'time',
'competitionTime',
'matchTime',
'scheduleTime',
]);
} }
int _readInt(Map<String, dynamic> map, List<String> keys, {int fallback = 0}) { int _readInt(Map<String, dynamic> map, List<String> keys, {int fallback = 0}) {
+17 -14
View File
@@ -5,7 +5,7 @@ import 'package:recording_tool/app/router/app_navigator.dart';
import 'package:recording_tool/features/events/model/model_event_info.dart'; import 'package:recording_tool/features/events/model/model_event_info.dart';
import 'package:recording_tool/features/events/state/state_event_info.dart'; import 'package:recording_tool/features/events/state/state_event_info.dart';
import 'package:recording_tool/features/events/view_model/view_model_event_info.dart'; import 'package:recording_tool/features/events/view_model/view_model_event_info.dart';
import 'package:recording_tool/features/recording/pages/page_record.dart'; import 'package:recording_tool/shared/widgets/app_webview.dart';
import 'package:recording_tool/shared/widgets/widgets.dart'; import 'package:recording_tool/shared/widgets/widgets.dart';
class EventInfoPage extends ConsumerStatefulWidget { class EventInfoPage extends ConsumerStatefulWidget {
@@ -29,14 +29,17 @@ class _EventInfoPageState extends ConsumerState<EventInfoPage> {
} }
Future<void> onItemTap(EventRegistrationItem item) async { Future<void> onItemTap(EventRegistrationItem item) async {
debugPrint('item tapped: ${item.name}'); debugPrint('item tapped: ${item.itemName}');
await ref.read(eventInfoProvider.notifier).requestStreamKey(item); await ref.read(eventInfoProvider.notifier).requestStreamKey(item);
if (!mounted) return; if (!mounted) return;
final profile = ref.read(eventInfoProvider).profile; // final profile = ref.read(eventInfoProvider).profile;
/// 个人赛
AppNavigator.push( AppNavigator.push(
RecordingPage( WebviewPage(
recordingContext: item.toRecordingContext(profile: profile), url: 'https://drone.apptest.sportsx.cc/#/pages/h5/score-entry',
streamUrl: EventInfoPage.mockRtmpUrl, eventRegistrationItem: item,
playerId: widget.playerId,
), ),
); );
} }
@@ -236,9 +239,9 @@ class _ScheduleCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
item.group.isEmpty item.groupName.isEmpty
? item.name ? item.itemName
: '${item.name} ${item.group}', : '${item.itemName} ${item.groupName}',
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(
@@ -250,7 +253,7 @@ class _ScheduleCard extends StatelessWidget {
), ),
SizedBox(height: 26.h), SizedBox(height: 26.h),
Text( Text(
item.venue, item.matchPlace,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(
@@ -261,7 +264,7 @@ class _ScheduleCard extends StatelessWidget {
), ),
SizedBox(height: 26.h), SizedBox(height: 26.h),
Text( Text(
item.time, item.scheduleTime,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(
@@ -297,7 +300,7 @@ class _ScheduleBadge extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (item.status != null) { if (item.statusLabel != null) {
return CustomPaint( return CustomPaint(
painter: _CutCornerBorderPainter(color: const Color(0xFF7A7A7A)), painter: _CutCornerBorderPainter(color: const Color(0xFF7A7A7A)),
child: SizedBox( child: SizedBox(
@@ -305,7 +308,7 @@ class _ScheduleBadge extends StatelessWidget {
height: 90.h, height: 90.h,
child: Center( child: Center(
child: Text( child: Text(
item.status!, item.statusLabel!,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: 20.sp, color: const Color(0xFF2F2F2F)), style: TextStyle(fontSize: 20.sp, color: const Color(0xFF2F2F2F)),
@@ -316,7 +319,7 @@ class _ScheduleBadge extends StatelessWidget {
} }
return Text( return Text(
item.laneNo ?? '', item.matchPlace,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(
@@ -18,7 +18,7 @@ class EventInfoState {
final List<EventRegistrationItem> items; final List<EventRegistrationItem> items;
String get eventTitle => String get eventTitle =>
items.isEmpty ? profile.eventTitle : items.first.eventTitle; items.isEmpty ? profile.eventTitle : items.first.eventName;
EventInfoState copyWith({ EventInfoState copyWith({
bool? isLoading, bool? isLoading,
@@ -54,64 +54,89 @@ class _AuthPageWidgetState extends ConsumerState<ScanQrCodePage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return PopScope(
appBar: myAppBar(context: context, title: '扫码'), canPop: true,
body: Center( onPopInvokedWithResult: (didPop, result) async {
child: Column( if (!didPop) return;
children: [ await ref.read(authProvider.notifier).clearAuth();
SizedBox(height: 100.h), },
Text('裁判工作台', style: TextStyle(fontSize: 30, color: Colors.black)), child: Scaffold(
SizedBox(height: 100.h), appBar: myAppBar(context: context, title: '扫码'),
Text( body: Center(
'扫描选手参赛凭证进行执裁', child: Column(
style: TextStyle(fontSize: 30, color: Colors.black), children: [
), SizedBox(height: 100.h),
SizedBox(height: 20.h), Text(
'裁判工作台',
style: TextStyle(fontSize: 30, color: Colors.black),
),
SizedBox(height: 100.h),
Text(
'扫描选手参赛凭证进行执裁',
style: TextStyle(fontSize: 30, color: Colors.black),
),
SizedBox(height: 20.h),
Consumer( Consumer(
builder: (context, ref, child) { builder: (context, ref, child) {
return SizedBox( return SizedBox(
width: 280.w, width: 280.w,
height: 80.h, height: 80.h,
child: AppButton( child: AppButton(
label: '查看录像', label: '查看录像',
onPressed: () async { onPressed: () async {
final data = ref.watch( final data = ref.watch(
authProvider.select((state) => state.jwtDecodedData), authProvider.select((state) => state.jwtDecodedData),
); );
if (data == null) return; if (data == null) return;
debugPrint('赛事名字: ${data.eventName}'); debugPrint('赛事名字: ${data.eventName}');
final eventName = data.eventName ?? ''; final eventName = data.eventName ?? '';
await ref await ref
.read(authProvider.notifier) .read(authProvider.notifier)
.getRecordList(eventName); .getRecordList(eventName);
}, },
variant: AppButtonVariant.secondary, variant: AppButtonVariant.secondary,
), ),
);
},
),
SizedBox(height: 16.h),
SizedBox(
width: 280.w,
height: 80.h,
child: AppButton(
label: '扫码',
onPressed: () async {
final String? playerId = await AppQrScannerDialog.show(
context,
);
if (playerId == null || playerId.isEmpty) return;
/// TODO
AppNavigator.push(
EventInfoPage(playerId: '74300708945530955'),
); );
}, },
variant: AppButtonVariant.secondary,
), ),
), SizedBox(height: 16.h),
],
Consumer(
builder: (context, ref, child) {
return SizedBox(
width: 280.w,
height: 80.h,
child: AppButton(
label: '参赛队伍',
onPressed: () async {},
variant: AppButtonVariant.secondary,
),
);
},
),
SizedBox(height: 16.h),
SizedBox(
width: 280.w,
height: 80.h,
child: AppButton(
label: '扫码',
onPressed: () async {
final String? playerId = await AppQrScannerDialog.show(
context,
);
if (playerId == null || playerId.isEmpty) return;
/// TODO
AppNavigator.push(
EventInfoPage(playerId: '74300708945530955'),
);
},
variant: AppButtonVariant.secondary,
),
),
],
),
), ),
), ),
); );
+21 -2
View File
@@ -4,6 +4,9 @@ import 'dart:convert';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:recording_tool/app/router/app_navigator.dart'; import 'package:recording_tool/app/router/app_navigator.dart';
import 'package:recording_tool/core/cache/app_storage.dart';
import 'package:recording_tool/core/cache/storage_keys.dart';
import 'package:recording_tool/features/events/model/model_event_info.dart';
import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter/webview_flutter.dart';
import 'package:webview_flutter_android/webview_flutter_android.dart'; import 'package:webview_flutter_android/webview_flutter_android.dart';
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';
@@ -13,6 +16,8 @@ class WebviewPage extends StatefulWidget {
final String? title; final String? title;
final String? assetPath; final String? assetPath;
final bool? canPop; final bool? canPop;
final EventRegistrationItem? eventRegistrationItem;
final String? playerId;
const WebviewPage({ const WebviewPage({
super.key, super.key,
@@ -20,6 +25,8 @@ class WebviewPage extends StatefulWidget {
this.title, this.title,
this.assetPath, this.assetPath,
this.canPop, this.canPop,
this.eventRegistrationItem,
this.playerId,
}); });
@override @override
@@ -183,8 +190,7 @@ class WebviewPageState extends State<WebviewPage> {
/// 脚本注入传输给h5 数据 /// 脚本注入传输给h5 数据
Future<void> _injectH5NeedData(WebViewController controller) async { Future<void> _injectH5NeedData(WebViewController controller) async {
final token = final token = AppStorage.getString(StorageKeys.authToken);
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjgxNDI4NTczNTY2NjY4ODAwLCJkYXRhIjp7fSwic3ViIjoiODE0Mjg1NzM1NjY2Njg4MDAiLCJleHAiOjE3ODM5MzM1ODQsIm5iZiI6MTc4MzMyODc4NCwiaWF0IjoxNzgzMzI4Nzg0fQ.r1wYfvpV-5HIgwUuvq1Or3jPgjc3AhfJmoV1PNP23-Y';
try { try {
await controller.runJavaScript(''' await controller.runJavaScript('''
(function () { (function () {
@@ -194,6 +200,19 @@ class WebviewPageState extends State<WebviewPage> {
window.__appInstallData = ${true}; window.__appInstallData = ${true};
window.AppBridge.token = '$token'; window.AppBridge.token = '$token';
window.AppBridge.scoreEntryContext = {
eventId: '${widget.eventRegistrationItem?.eventId}',
itemId: '${widget.eventRegistrationItem?.itemId}',
scheduleId: '${widget.eventRegistrationItem?.scheduleId}',
userId: '${widget.playerId}',
opponentId: '${widget.eventRegistrationItem?.opponentId}',
};
// console.log('window.AppBridge?.scoreEntryContext.eventId', window.AppBridge?.scoreEntryContext.eventId);
// console.log('window.AppBridge?.scoreEntryContext.itemId', window.AppBridge?.scoreEntryContext.itemId);
// console.log('window.AppBridge?.scoreEntryContext.scheduleId', window.AppBridge?.scoreEntryContext.scheduleId);
// console.log('window.AppBridge?.scoreEntryContext.userId', window.AppBridge?.scoreEntryContext.userId);
// console.log('window.AppBridge?.scoreEntryContext.opponentId', window.AppBridge?.scoreEntryContext.opponentId);
// confirm('已经注入完成');
})(); })();
'''); ''');