From 1f9c5af51bac0c320ff27e5e62dcb943d8c378c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E9=94=8B?= <2535831261@qq.com> Date: Fri, 17 Jul 2026 11:55:13 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E4=BA=8B=E4=BB=B6=E6=B3=A8?= =?UTF-8?q?=E5=86=8C=E6=A8=A1=E5=9E=8B=E5=B9=B6=E6=9B=B4=E6=96=B0=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E9=85=8D=E7=BD=AE=E3=80=82=E6=9B=B4=E6=94=B9=E4=B8=8D?= =?UTF-8?q?=E5=90=8C=E7=8E=AF=E5=A2=83=E7=9A=84=E5=9F=BA=E6=9C=ACurl?= =?UTF-8?q?=EF=BC=8C=E5=A2=9E=E5=BC=BA=E4=BA=8B=E4=BB=B6=E6=B3=A8=E5=86=8C?= =?UTF-8?q?=E9=A1=B9=E7=BB=93=E6=9E=84=EF=BC=8C=E5=B9=B6=E6=94=B9=E8=BF=9B?= =?UTF-8?q?ScanQrCodePage=E4=B8=AD=E7=9A=84=E5=AF=BC=E8=88=AA=E3=80=82?= =?UTF-8?q?=E5=9C=A8AuthViewModel=E4=B8=AD=E6=B7=BB=E5=8A=A0clearAuth?= =?UTF-8?q?=E6=96=B9=E6=B3=95=EF=BC=8C=E4=BB=A5=E4=BE=BF=E6=9B=B4=E5=A5=BD?= =?UTF-8?q?=E5=9C=B0=E8=BF=9B=E8=A1=8C=E7=8A=B6=E6=80=81=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/app/config/app_config.dart | 7 +- lib/features/auth/pages/page_auth.dart | 2 +- .../auth/view_model_auth/view_model_auth.dart | 6 + .../events/model/model_event_info.dart | 164 ++++++------------ .../events/pages/page_event_info.dart | 31 ++-- .../events/state/state_event_info.dart | 2 +- .../scan_qrcode/pages/page_scan_qrcode.dart | 133 ++++++++------ lib/shared/widgets/app_webview.dart | 25 ++- 8 files changed, 187 insertions(+), 183 deletions(-) diff --git a/lib/app/config/app_config.dart b/lib/app/config/app_config.dart index 162758c..b528b5c 100644 --- a/lib/app/config/app_config.dart +++ b/lib/app/config/app_config.dart @@ -32,17 +32,18 @@ class AppConfig { current = switch (environment) { AppEnvironment.dev => const EnvironmentValues( environment: AppEnvironment.dev, - baseUrl: 'http://192.168.1.104:8000', + // baseUrl: 'http://192.168.1.104:8000', + baseUrl: 'https://apitest.dronex.cc', enableNetworkLog: true, ), AppEnvironment.staging => const EnvironmentValues( environment: AppEnvironment.staging, - baseUrl: 'https://example.com/api', + baseUrl: 'https://apitest.dronex.cc', enableNetworkLog: true, ), AppEnvironment.prod => const EnvironmentValues( environment: AppEnvironment.prod, - baseUrl: 'https://example.com/api', + baseUrl: 'https://api.dronex.cc', enableNetworkLog: false, ), }; diff --git a/lib/features/auth/pages/page_auth.dart b/lib/features/auth/pages/page_auth.dart index dd04216..fd36026 100644 --- a/lib/features/auth/pages/page_auth.dart +++ b/lib/features/auth/pages/page_auth.dart @@ -27,7 +27,7 @@ class _AuthPageWidgetState extends ConsumerState { WidgetsBinding.instance.addPostFrameCallback((_) async { final token = AppStorage.getString(StorageKeys.authToken); if (token?.isNotEmpty ?? false) { - AppNavigator.pushReplacement(const ScanQrCodePage()); + AppNavigator.push(const ScanQrCodePage()); } }); } diff --git a/lib/features/auth/view_model_auth/view_model_auth.dart b/lib/features/auth/view_model_auth/view_model_auth.dart index 906a7e2..49ddb13 100644 --- a/lib/features/auth/view_model_auth/view_model_auth.dart +++ b/lib/features/auth/view_model_auth/view_model_auth.dart @@ -65,4 +65,10 @@ class AuthViewModel extends StateNotifier { state = state.copyWith(recordList: data.items); return true; } + + /// 清空授权信息(本地 token + 内存状态) + Future clearAuth() async { + await AppStorage.remove(StorageKeys.authToken); + state = const AuthState(); + } } diff --git a/lib/features/events/model/model_event_info.dart b/lib/features/events/model/model_event_info.dart index d725139..f5f59ce 100644 --- a/lib/features/events/model/model_event_info.dart +++ b/lib/features/events/model/model_event_info.dart @@ -91,7 +91,7 @@ class EventRegistrationList { return EventProfile( name: name, phone: '', - eventTitle: items.isEmpty ? '赛事信息' : items.first.eventTitle, + eventTitle: items.isEmpty ? '赛事信息' : items.first.eventName, avatarUrl: avatar, ); } @@ -101,40 +101,45 @@ class EventRegistrationItem { const EventRegistrationItem({ required this.eventId, required this.itemId, - required this.userId, - required this.eventTitle, - required this.name, - required this.group, - required this.venue, - required this.time, - required this.playerName, - required this.playerPhone, - this.avatarLabel = '头像', + required this.scheduleId, + required this.eventName, + required this.itemName, + required this.groupName, + required this.matchPlace, this.matchStartTime = '', this.matchEndTime = '', this.completed = false, - this.laneNo, - this.status, - this.rawData = const {}, + this.opponentId = '', + this.opponentName = '', + this.teamMembers, + this.userId = '', + this.playerName = '', + this.playerPhone = '', }); final String eventId; final String itemId; - final String userId; - final String eventTitle; - final String name; - final String group; - final String venue; - final String time; - final String playerName; - final String playerPhone; - final String avatarLabel; + final String scheduleId; + final String eventName; + final String itemName; + final String groupName; + final String matchPlace; final String matchStartTime; final String matchEndTime; final bool completed; - final String? laneNo; - final String? status; - final Map rawData; + final String opponentId; + final String opponentName; + final List? 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( Map json, { @@ -142,94 +147,48 @@ class EventRegistrationItem { String playerName = '', }) { final map = Map.from(json); - final startTime = _readString(map, const ['matchStartTime', 'startTime']); - final endTime = _readString(map, const ['matchEndTime', 'endTime']); - final completed = _readBool(map, const ['completed']); + final startTime = _readString(map, const ['matchStartTime']); + final endTime = _readString(map, const ['matchEndTime']); + final teamMembersRaw = map['teamMembers']; return EventRegistrationItem( - eventId: _readString(map, const ['eventId', 'eventsId', 'eventID']), - itemId: _readString(map, const ['itemId', 'eventItemId', 'itemID']), - userId: _readString(map, const [ - 'userId', - 'playerId', - 'registrationId', - ], fallback: userId), - 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', - ]), + eventId: _readString(map, const ['eventId']), + itemId: _readString(map, const ['itemId']), + scheduleId: _readString(map, const ['scheduleId']), + eventName: _readString(map, const ['eventName'], fallback: '赛事信息'), + itemName: _readString(map, const ['itemName'], fallback: '未命名项目'), + groupName: _readString(map, const ['groupName']), + matchPlace: _readString(map, const ['matchPlace']), matchStartTime: startTime, matchEndTime: endTime, - completed: completed, - status: completed ? '已完成' : null, - rawData: map, + completed: _readBool(map, const ['completed']), + opponentId: _readString(map, const ['opponentId']), + opponentName: _readString(map, const ['opponentName']), + teamMembers: teamMembersRaw is List + ? List.from(teamMembersRaw) + : null, + userId: userId, + playerName: playerName, ); } EventProfile toProfile() { return EventProfile( - avatarLabel: avatarLabel, name: playerName, phone: playerPhone, - eventTitle: eventTitle, + eventTitle: eventName, ); } RecordingContext toRecordingContext({EventProfile? profile}) { return RecordingContext( - eventTitle: eventTitle, - matchName: name, - group: group, - venue: venue, - time: time, + eventTitle: eventName, + matchName: itemName, + group: groupName, + venue: matchPlace, + time: scheduleTime, playerName: profile?.name ?? playerName, playerPhone: profile?.phone ?? playerPhone, - laneNo: laneNo, - status: status, + status: statusLabel, ); } } @@ -283,22 +242,13 @@ String _readString( return _readNullableString(map, keys) ?? fallback; } -String _readScheduleTime( - Map map, - String startTime, - String endTime, -) { +String _formatScheduleTime(String startTime, String endTime) { if (startTime.isNotEmpty && endTime.isNotEmpty) { return '$startTime-$endTime'; } if (startTime.isNotEmpty) return startTime; if (endTime.isNotEmpty) return endTime; - return _readString(map, const [ - 'time', - 'competitionTime', - 'matchTime', - 'scheduleTime', - ]); + return ''; } int _readInt(Map map, List keys, {int fallback = 0}) { diff --git a/lib/features/events/pages/page_event_info.dart b/lib/features/events/pages/page_event_info.dart index 4d7220f..9e2ee59 100644 --- a/lib/features/events/pages/page_event_info.dart +++ b/lib/features/events/pages/page_event_info.dart @@ -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/state/state_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'; class EventInfoPage extends ConsumerStatefulWidget { @@ -29,14 +29,17 @@ class _EventInfoPageState extends ConsumerState { } Future onItemTap(EventRegistrationItem item) async { - debugPrint('item tapped: ${item.name}'); + debugPrint('item tapped: ${item.itemName}'); await ref.read(eventInfoProvider.notifier).requestStreamKey(item); if (!mounted) return; - final profile = ref.read(eventInfoProvider).profile; + // final profile = ref.read(eventInfoProvider).profile; + + /// 个人赛 AppNavigator.push( - RecordingPage( - recordingContext: item.toRecordingContext(profile: profile), - streamUrl: EventInfoPage.mockRtmpUrl, + WebviewPage( + url: 'https://drone.apptest.sportsx.cc/#/pages/h5/score-entry', + eventRegistrationItem: item, + playerId: widget.playerId, ), ); } @@ -236,9 +239,9 @@ class _ScheduleCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - item.group.isEmpty - ? item.name - : '${item.name} (${item.group})', + item.groupName.isEmpty + ? item.itemName + : '${item.itemName} (${item.groupName})', maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( @@ -250,7 +253,7 @@ class _ScheduleCard extends StatelessWidget { ), SizedBox(height: 26.h), Text( - item.venue, + item.matchPlace, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( @@ -261,7 +264,7 @@ class _ScheduleCard extends StatelessWidget { ), SizedBox(height: 26.h), Text( - item.time, + item.scheduleTime, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( @@ -297,7 +300,7 @@ class _ScheduleBadge extends StatelessWidget { @override Widget build(BuildContext context) { - if (item.status != null) { + if (item.statusLabel != null) { return CustomPaint( painter: _CutCornerBorderPainter(color: const Color(0xFF7A7A7A)), child: SizedBox( @@ -305,7 +308,7 @@ class _ScheduleBadge extends StatelessWidget { height: 90.h, child: Center( child: Text( - item.status!, + item.statusLabel!, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 20.sp, color: const Color(0xFF2F2F2F)), @@ -316,7 +319,7 @@ class _ScheduleBadge extends StatelessWidget { } return Text( - item.laneNo ?? '', + item.matchPlace, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( diff --git a/lib/features/events/state/state_event_info.dart b/lib/features/events/state/state_event_info.dart index ac5c827..4e5a23c 100644 --- a/lib/features/events/state/state_event_info.dart +++ b/lib/features/events/state/state_event_info.dart @@ -18,7 +18,7 @@ class EventInfoState { final List items; String get eventTitle => - items.isEmpty ? profile.eventTitle : items.first.eventTitle; + items.isEmpty ? profile.eventTitle : items.first.eventName; EventInfoState copyWith({ bool? isLoading, diff --git a/lib/features/scan_qrcode/pages/page_scan_qrcode.dart b/lib/features/scan_qrcode/pages/page_scan_qrcode.dart index 4a930e2..13688f4 100644 --- a/lib/features/scan_qrcode/pages/page_scan_qrcode.dart +++ b/lib/features/scan_qrcode/pages/page_scan_qrcode.dart @@ -54,64 +54,89 @@ class _AuthPageWidgetState extends ConsumerState { @override Widget build(BuildContext context) { - return Scaffold( - appBar: myAppBar(context: context, title: '扫码'), - body: Center( - child: Column( - children: [ - SizedBox(height: 100.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), + return PopScope( + canPop: true, + onPopInvokedWithResult: (didPop, result) async { + if (!didPop) return; + await ref.read(authProvider.notifier).clearAuth(); + }, + child: Scaffold( + appBar: myAppBar(context: context, title: '扫码'), + body: Center( + child: Column( + children: [ + SizedBox(height: 100.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( - builder: (context, ref, child) { - return SizedBox( - width: 280.w, - height: 80.h, - child: AppButton( - label: '查看录像', - onPressed: () async { - final data = ref.watch( - authProvider.select((state) => state.jwtDecodedData), - ); - if (data == null) return; - debugPrint('赛事名字: ${data.eventName}'); - final eventName = data.eventName ?? ''; - await ref - .read(authProvider.notifier) - .getRecordList(eventName); - }, - 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'), + Consumer( + builder: (context, ref, child) { + return SizedBox( + width: 280.w, + height: 80.h, + child: AppButton( + label: '查看录像', + onPressed: () async { + final data = ref.watch( + authProvider.select((state) => state.jwtDecodedData), + ); + if (data == null) return; + debugPrint('赛事名字: ${data.eventName}'); + final eventName = data.eventName ?? ''; + await ref + .read(authProvider.notifier) + .getRecordList(eventName); + }, + variant: AppButtonVariant.secondary, + ), ); }, - 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, + ), + ), + ], + ), ), ), ); diff --git a/lib/shared/widgets/app_webview.dart b/lib/shared/widgets/app_webview.dart index be2677b..0090d09 100644 --- a/lib/shared/widgets/app_webview.dart +++ b/lib/shared/widgets/app_webview.dart @@ -4,6 +4,9 @@ import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.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_android/webview_flutter_android.dart'; import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; @@ -13,6 +16,8 @@ class WebviewPage extends StatefulWidget { final String? title; final String? assetPath; final bool? canPop; + final EventRegistrationItem? eventRegistrationItem; + final String? playerId; const WebviewPage({ super.key, @@ -20,6 +25,8 @@ class WebviewPage extends StatefulWidget { this.title, this.assetPath, this.canPop, + this.eventRegistrationItem, + this.playerId, }); @override @@ -183,8 +190,7 @@ class WebviewPageState extends State { /// 脚本注入传输给h5 数据 Future _injectH5NeedData(WebViewController controller) async { - final token = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjgxNDI4NTczNTY2NjY4ODAwLCJkYXRhIjp7fSwic3ViIjoiODE0Mjg1NzM1NjY2Njg4MDAiLCJleHAiOjE3ODM5MzM1ODQsIm5iZiI6MTc4MzMyODc4NCwiaWF0IjoxNzgzMzI4Nzg0fQ.r1wYfvpV-5HIgwUuvq1Or3jPgjc3AhfJmoV1PNP23-Y'; + final token = AppStorage.getString(StorageKeys.authToken); try { await controller.runJavaScript(''' (function () { @@ -194,7 +200,20 @@ class WebviewPageState extends State { window.__appInstallData = ${true}; 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('已经注入完成'); + })(); '''); } catch (e) {