7 Commits
10 changed files with 253 additions and 236 deletions
+6 -1
View File
@@ -1 +1,6 @@
flutter build apk --release --split-per-abi #!/bin/sh
set -e
flutter build apk --release --split-per-abi
echo "构建完成时间: $(date '+%Y-%m-%d %H:%M:%S')"
+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}) {
+19 -26
View File
@@ -5,14 +5,12 @@ 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 {
const EventInfoPage({super.key, required this.playerId}); const EventInfoPage({super.key, required this.playerId});
final String playerId; final String playerId;
static const mockRtmpUrl =
'rtmp://192.168.1.245:19090/蔡依婷vs夏志豪_空中格斗赛_高中组/蔡依婷vs夏志豪_空中格斗赛_高中组';
@override @override
ConsumerState<EventInfoPage> createState() => _EventInfoPageState(); ConsumerState<EventInfoPage> createState() => _EventInfoPageState();
@@ -22,21 +20,20 @@ class _EventInfoPageState extends ConsumerState<EventInfoPage> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return;
ref.read(eventInfoProvider.notifier).loadRegistrationList();
});
} }
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,
), ),
); );
} }
@@ -179,17 +176,13 @@ class _ScheduleList extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (state.isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (state.items.isEmpty) { if (state.items.isEmpty) {
return Center( return Center(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text( Text(
state.errorMessage ?? '暂无赛事报名信息', '暂无赛事报名信息',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle(fontSize: 18.sp, color: const Color(0xFF2F2F2F)), style: TextStyle(fontSize: 18.sp, color: const Color(0xFF2F2F2F)),
), ),
@@ -236,9 +229,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 +243,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 +254,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 +290,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 +298,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 +309,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(
@@ -2,40 +2,29 @@ import 'package:recording_tool/features/events/model/model_event_info.dart';
class EventInfoState { class EventInfoState {
const EventInfoState({ const EventInfoState({
this.isLoading = false,
this.isRequestingStreamKey = false, this.isRequestingStreamKey = false,
this.errorMessage,
this.profile = const EventProfile(name: '', phone: '', eventTitle: '赛事信息'), this.profile = const EventProfile(name: '', phone: '', eventTitle: '赛事信息'),
this.total = 0, this.total = 0,
this.items = const [], this.items = const [],
}); });
final bool isLoading;
final bool isRequestingStreamKey; final bool isRequestingStreamKey;
final String? errorMessage;
final EventProfile profile; final EventProfile profile;
final int total; final int total;
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? isRequestingStreamKey, bool? isRequestingStreamKey,
String? errorMessage,
bool clearErrorMessage = false,
EventProfile? profile, EventProfile? profile,
int? total, int? total,
List<EventRegistrationItem>? items, List<EventRegistrationItem>? items,
}) { }) {
return EventInfoState( return EventInfoState(
isLoading: isLoading ?? this.isLoading,
isRequestingStreamKey: isRequestingStreamKey:
isRequestingStreamKey ?? this.isRequestingStreamKey, isRequestingStreamKey ?? this.isRequestingStreamKey,
errorMessage: clearErrorMessage
? null
: (errorMessage ?? this.errorMessage),
profile: profile ?? this.profile, profile: profile ?? this.profile,
total: total ?? this.total, total: total ?? this.total,
items: items ?? this.items, items: items ?? this.items,
@@ -5,6 +5,7 @@ import 'package:recording_tool/core/network/api_exception.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/server/server_events.dart'; import 'package:recording_tool/features/events/server/server_events.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/shared/widgets/app_toast.dart';
final eventInfoProvider = final eventInfoProvider =
StateNotifierProvider<EventInfoViewModel, EventInfoState>((ref) { StateNotifierProvider<EventInfoViewModel, EventInfoState>((ref) {
@@ -15,44 +16,42 @@ class EventInfoViewModel extends StateNotifier<EventInfoState> {
EventInfoViewModel(this._ref) : super(const EventInfoState()); EventInfoViewModel(this._ref) : super(const EventInfoState());
static const defaultRegistrationRequest = PlayerRegistrationListReq( static const defaultRegistrationRequest = PlayerRegistrationListReq(
userId: '74300708945530955', userId: '',
status: '', status: '',
); );
static const _fallbackEventId = '10224'; static const _fallbackEventId = '';
static const _fallbackItemId = '2318'; static const _fallbackItemId = '';
static const _fallbackStreamUserId = '74300708949725207'; static const _fallbackStreamUserId = '';
final Ref _ref; final Ref _ref;
Future<void> loadRegistrationList({ Future<bool> loadRegistrationList({
PlayerRegistrationListReq request = defaultRegistrationRequest, PlayerRegistrationListReq request = defaultRegistrationRequest,
}) async { }) async {
state = state.copyWith(isLoading: true, clearErrorMessage: true); if (request.userId.isEmpty) {
return false;
}
try { try {
final result = await _ref final result = await _ref
.read(eventsServerProvider) .read(eventsServerProvider)
.fetchPlayerRegistrationList(request); .fetchPlayerRegistrationList(request);
debugPrint('报名列表请求成功: $result');
state = state.copyWith( state = state.copyWith(
isLoading: false,
profile: result.toProfile(), profile: result.toProfile(),
total: result.total, total: result.total,
items: result.items, items: result.items,
clearErrorMessage: true,
); );
} on ApiException catch (error) { return true;
state = state.copyWith(isLoading: false, errorMessage: error.message);
} catch (error) { } catch (error) {
debugPrint('报名列表请求失败: $error'); debugPrint('报名列表请求失败: $error');
state = state.copyWith(isLoading: false, errorMessage: '报名列表加载失败'); AppToast.show('报名列表加载失败');
return false;
} }
} }
Future<void> requestStreamKey(EventRegistrationItem item) async { Future<void> requestStreamKey(EventRegistrationItem item) async {
state = state.copyWith( state = state.copyWith(isRequestingStreamKey: true);
isRequestingStreamKey: true,
clearErrorMessage: true,
);
final req = StreamKeyReq( final req = StreamKeyReq(
eventId: item.eventId.isNotEmpty ? item.eventId : _fallbackEventId, eventId: item.eventId.isNotEmpty ? item.eventId : _fallbackEventId,
itemId: item.itemId.isNotEmpty ? item.itemId : _fallbackItemId, itemId: item.itemId.isNotEmpty ? item.itemId : _fallbackItemId,
@@ -64,22 +63,15 @@ class EventInfoViewModel extends StateNotifier<EventInfoState> {
.read(eventsServerProvider) .read(eventsServerProvider)
.fetchStreamKey(req); .fetchStreamKey(req);
debugPrint('推流 Key 接口响应: $response'); debugPrint('推流 Key 接口响应: $response');
state = state.copyWith( state = state.copyWith(isRequestingStreamKey: false);
isRequestingStreamKey: false,
clearErrorMessage: true,
);
} on ApiException catch (error) { } on ApiException catch (error) {
debugPrint('推流 Key 接口请求失败: ${error.message}'); debugPrint('推流 Key 接口请求失败: ${error.message}');
state = state.copyWith( state = state.copyWith(isRequestingStreamKey: false);
isRequestingStreamKey: false, AppToast.show(error.message);
errorMessage: error.message,
);
} catch (error) { } catch (error) {
debugPrint('推流 Key 接口请求失败: $error'); debugPrint('推流 Key 接口请求失败: $error');
state = state.copyWith( state = state.copyWith(isRequestingStreamKey: false);
isRequestingStreamKey: false, AppToast.show('推流 Key 获取失败');
errorMessage: '推流 Key 获取失败',
);
} }
} }
} }
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:recording_tool/app/router/app_navigator.dart'; import 'package:recording_tool/app/router/app_navigator.dart';
@@ -6,7 +7,9 @@ import 'package:recording_tool/core/cache/app_storage.dart';
import 'package:recording_tool/core/cache/storage_keys.dart'; import 'package:recording_tool/core/cache/storage_keys.dart';
import 'package:recording_tool/features/auth/pages/page_auth.dart'; import 'package:recording_tool/features/auth/pages/page_auth.dart';
import 'package:recording_tool/features/auth/view_model_auth/view_model_auth.dart'; import 'package:recording_tool/features/auth/view_model_auth/view_model_auth.dart';
import 'package:recording_tool/features/events/model/model_event_info.dart';
import 'package:recording_tool/features/events/pages/page_event_info.dart'; import 'package:recording_tool/features/events/pages/page_event_info.dart';
import 'package:recording_tool/features/events/view_model/view_model_event_info.dart';
import 'package:recording_tool/features/recording/model/model_recording_context.dart'; import 'package:recording_tool/features/recording/model/model_recording_context.dart';
import 'package:recording_tool/shared/widgets/app_bar.dart'; import 'package:recording_tool/shared/widgets/app_bar.dart';
import 'package:recording_tool/shared/widgets/app_button.dart'; import 'package:recording_tool/shared/widgets/app_button.dart';
@@ -54,64 +57,108 @@ 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) {
AppToast.show('查询选手信息失败');
return;
}
EasyLoading.show(status: '查询选手信息...');
try {
final success = await ref
.read(eventInfoProvider.notifier)
.loadRegistrationList(
request: PlayerRegistrationListReq(
userId: playerId,
),
);
EasyLoading.dismiss();
if (!success) {
AppToast.show('查询选手信息失败');
return;
}
if (!mounted) return;
AppNavigator.push(EventInfoPage(playerId: playerId));
} catch (error) {
AppToast.show('查询选手信息失败');
EasyLoading.dismiss();
}
},
variant: AppButtonVariant.secondary,
),
),
],
),
), ),
), ),
); );
+39 -5
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
@@ -57,7 +64,7 @@ class WebviewPageState extends State<WebviewPage> {
late final WebViewController controller; late final WebViewController controller;
controller = WebViewController.fromPlatformCreationParams(params) controller = WebViewController.fromPlatformCreationParams(params)
..setJavaScriptMode(JavaScriptMode.unrestricted) ..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(Colors.transparent) ..setBackgroundColor(Colors.white)
..setNavigationDelegate( ..setNavigationDelegate(
NavigationDelegate( NavigationDelegate(
onProgress: (int progress) { onProgress: (int progress) {
@@ -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,7 +200,20 @@ 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('已经注入完成');
})(); })();
'''); ''');
} catch (e) { } catch (e) {
@@ -212,6 +231,21 @@ class WebviewPageState extends State<WebviewPage> {
return const PlatformWebViewControllerCreationParams(); return const PlatformWebViewControllerCreationParams();
} }
Widget _buildWebView(WebViewController controller) {
PlatformWebViewWidgetCreationParams params =
PlatformWebViewWidgetCreationParams(controller: controller.platform);
if (WebViewPlatform.instance is AndroidWebViewPlatform) {
params =
AndroidWebViewWidgetCreationParams.fromPlatformWebViewWidgetCreationParams(
params,
displayWithHybridComposition: true,
);
}
return WebViewWidget.fromPlatformCreationParams(params: params);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@@ -275,7 +309,7 @@ class WebviewPageState extends State<WebviewPage> {
Expanded( Expanded(
child: Stack( child: Stack(
children: [ children: [
Positioned.fill(child: WebViewWidget(controller: controller)), Positioned.fill(child: _buildWebView(controller)),
if (_isLoading) if (_isLoading)
const Center(child: CircularProgressIndicator()), const Center(child: CircularProgressIndicator()),
], ],