1.还原授权页 UI

2.还原扫码页 UI
This commit is contained in:
2026-07-27 16:52:58 +08:00
parent 20d501f673
commit 686db244fd
11 changed files with 445 additions and 195 deletions

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 795 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1011 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

+6 -6
View File
@@ -15,9 +15,9 @@ class JwtDecodedData {
int? deviceId; int? deviceId;
String? deviceRole; String? deviceRole;
String? eventName; String? eventName;
double? oId; String? oId;
List<double>? oIds; List<String>? oIds;
double? organizerId; String? organizerId;
JwtDecodedData({ JwtDecodedData({
this.authType, this.authType,
@@ -36,11 +36,11 @@ class JwtDecodedData {
deviceId: json["deviceId"], deviceId: json["deviceId"],
deviceRole: json["deviceRole"], deviceRole: json["deviceRole"],
eventName: json["eventName"], eventName: json["eventName"],
oId: json["oId"]?.toDouble(), oId: json["oId"]?.toString(),
oIds: json["oIds"] == null oIds: json["oIds"] == null
? [] ? []
: List<double>.from(json["oIds"]!.map((x) => x?.toDouble())), : List<String>.from(json["oIds"]!.map((x) => x?.toString())),
organizerId: json["organizerId"]?.toDouble(), organizerId: json["organizerId"]?.toString(),
); );
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
+199 -81
View File
@@ -8,7 +8,10 @@ import 'package:recording_tool/core/cache/storage_keys.dart';
import 'package:recording_tool/core/utils/device_utils.dart'; import 'package:recording_tool/core/utils/device_utils.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/scan_qrcode/pages/page_scan_qrcode.dart'; import 'package:recording_tool/features/scan_qrcode/pages/page_scan_qrcode.dart';
import 'package:recording_tool/shared/widgets/widgets.dart'; import 'package:recording_tool/gen/assets.gen.dart';
import 'package:recording_tool/shared/widgets/app_button.dart';
import 'package:recording_tool/shared/widgets/app_dialog.dart';
import 'package:recording_tool/shared/widgets/app_toast.dart';
class AuthPageWidget extends ConsumerStatefulWidget { class AuthPageWidget extends ConsumerStatefulWidget {
const AuthPageWidget({super.key}); const AuthPageWidget({super.key});
@@ -18,13 +21,12 @@ class AuthPageWidget extends ConsumerStatefulWidget {
} }
class _AuthPageWidgetState extends ConsumerState<AuthPageWidget> { class _AuthPageWidgetState extends ConsumerState<AuthPageWidget> {
late TextEditingController? _controller; late final TextEditingController _controller;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_controller = TextEditingController(); _controller = TextEditingController(text: '999779');
_controller?.text = '986570';
WidgetsBinding.instance.addPostFrameCallback((_) async { WidgetsBinding.instance.addPostFrameCallback((_) async {
final token = AppStorage.getString(StorageKeys.authToken); final token = AppStorage.getString(StorageKeys.authToken);
@@ -36,7 +38,7 @@ class _AuthPageWidgetState extends ConsumerState<AuthPageWidget> {
@override @override
void dispose() { void dispose() {
_controller?.dispose(); _controller.dispose();
super.dispose(); super.dispose();
} }
@@ -44,88 +46,204 @@ class _AuthPageWidgetState extends ConsumerState<AuthPageWidget> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final authState = ref.watch(authProvider); final authState = ref.watch(authProvider);
return Center( return AnnotatedRegion<SystemUiOverlayStyle>(
child: Column( value: SystemUiOverlayStyle.dark.copyWith(
children: [ statusBarColor: Colors.transparent,
SizedBox(height: 180.h), systemNavigationBarColor: Colors.white,
AppText('裁判工作台', fontSize: 30.sp), ),
SizedBox(height: 20.h), child: Scaffold(
Text( backgroundColor: Colors.white,
'输入执裁口令', body: Stack(
style: TextStyle(fontSize: 18.sp, color: Colors.black), children: [
), Positioned(
SizedBox(height: 20.h), top: 0,
left: 0,
Container( right: 0,
padding: EdgeInsets.symmetric(horizontal: 20.w), child: Image.asset(
child: Column( _AuthAssets.pageBg,
children: [ width: double.infinity,
AppTextField( fit: BoxFit.fitWidth,
controller: _controller, ),
keyboardType: TextInputType.number, ),
maxLength: 6, SafeArea(
inputFormatters: [ child: Padding(
FilteringTextInputFormatter.digitsOnly, padding: EdgeInsets.symmetric(horizontal: 32.w),
LengthLimitingTextInputFormatter(6), child: SingleChildScrollView(
], child: Column(
), children: [
SizedBox(height: 20.h), SizedBox(height: 190.h),
SizedBox( ClipRRect(
width: double.maxFinite, borderRadius: BorderRadius.circular(24.r),
child: AppButton( child: Image.asset(
label: '确定', _AuthAssets.appIcon,
onPressed: () async { width: 82.w,
final code = _controller?.text; height: 82.w,
final success = await ref fit: BoxFit.cover,
.read(authProvider.notifier) ),
.auth(code ?? ''); ),
if (!mounted) return; SizedBox(height: 18.h),
if (success) { Image.asset(
AppNavigator.push(const ScanQrCodePage()); _AuthAssets.appNameText,
return; width: 132.w,
} height: 28.w,
final message = ref.read(authProvider).errorMessage; fit: BoxFit.cover,
if (message != null && message.isNotEmpty) { ),
AppToast.show(message); SizedBox(height: 78.h),
} _PassCodeInput(controller: _controller),
}, SizedBox(height: 20.h),
variant: AppButtonVariant.secondary, _GradientConfirmButton(
isLoading: authState.isLoading, isLoading: authState.isLoading,
onPressed: _handleSubmit,
),
SizedBox(height: 20.h),
AppButton(
onPressed: () async {
final deviceCode = await DeviceUtils.deviceCode();
if (!mounted) return;
AppDialog.confirm(context, title: '设备码:$deviceCode');
},
label: '获取设备码',
),
],
), ),
), ),
],
),
),
..._buildTestArea(authState.isLoading),
],
),
);
}
/// 测试区域
List<Widget> _buildTestArea(bool isLoading) {
return [
SizedBox(height: 20.h),
Container(
padding: EdgeInsets.symmetric(horizontal: 20.w),
child: Column(
children: [
SizedBox(
width: double.maxFinite,
child: AppButton(
label: '获取设备码',
onPressed: () async {
DeviceUtils.deviceCode().then((code) {
AppDialog.confirm(context, title: '设备码', message: code);
});
},
variant: AppButtonVariant.secondary,
isLoading: isLoading,
), ),
), ),
], ],
), ),
), ),
]; );
}
Future<void> _handleSubmit() async {
final success = await ref
.read(authProvider.notifier)
.auth(_controller.text);
if (!mounted) return;
if (success) {
AppNavigator.push(const ScanQrCodePage());
return;
}
final message = ref.read(authProvider).errorMessage;
if (message != null && message.isNotEmpty) {
AppToast.show(message);
}
} }
} }
class _PassCodeInput extends StatelessWidget {
const _PassCodeInput({required this.controller});
final TextEditingController controller;
@override
Widget build(BuildContext context) {
return Container(
height: 50.h,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.08),
blurRadius: 10.r,
offset: Offset(0, 2.h),
),
],
),
child: TextField(
controller: controller,
keyboardType: TextInputType.number,
textAlign: TextAlign.center,
maxLength: 6,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(6),
],
style: TextStyle(
color: const Color(0xFF2F3338),
fontSize: 16.sp,
fontWeight: FontWeight.w500,
),
decoration: InputDecoration(
hintText: '请输入执裁口令',
hintStyle: TextStyle(
color: const Color(0xFFB6B9BF),
fontSize: 16.sp,
fontWeight: FontWeight.w400,
),
counterText: '',
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 16.w,
vertical: 14.h,
),
),
),
);
}
}
class _GradientConfirmButton extends StatelessWidget {
const _GradientConfirmButton({
required this.isLoading,
required this.onPressed,
});
final bool isLoading;
final VoidCallback onPressed;
@override
Widget build(BuildContext context) {
return Opacity(
opacity: isLoading ? 0.72 : 1,
child: GestureDetector(
onTap: isLoading ? null : onPressed,
child: Container(
height: 50.h,
width: double.infinity,
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25.r),
gradient: const LinearGradient(
colors: [Color(0xFF268DFF), Color(0xFF5DD4F6)],
),
boxShadow: [
BoxShadow(
color: const Color(0xFF2196F3).withValues(alpha: 0.28),
blurRadius: 14.r,
offset: Offset(0, 6.h),
),
],
),
child: isLoading
? SizedBox.square(
dimension: 18.r,
child: CircularProgressIndicator(
strokeWidth: 2.r,
valueColor: const AlwaysStoppedAnimation<Color>(
Colors.white,
),
),
)
: Text(
'确定',
style: TextStyle(
color: Colors.white,
fontSize: 16.sp,
fontWeight: FontWeight.w500,
),
),
),
),
);
}
}
class _AuthAssets {
const _AuthAssets._();
static String pageBg = Assets.images.imagePageBg.path;
static String appIcon = Assets.images.imageAppIcon.path;
static String appNameText = Assets.images.imageAppNameText.path;
}
@@ -50,8 +50,13 @@ class AuthViewModel extends StateNotifier<AuthState> {
Future<bool> parseTokenSetState(String token) async { Future<bool> parseTokenSetState(String token) async {
final decoded = JwtDecoder.decode(token); final decoded = JwtDecoder.decode(token);
if (decoded['data'] != null && decoded['data'] is Map<String, dynamic>) { if (decoded['data'] != null && decoded['data'] is Map<String, dynamic>) {
final data = JwtDecodedData.fromJson(decoded['data']); try {
state = state.copyWith(jwtDecodedData: data); final data = JwtDecodedData.fromJson(decoded['data']);
state = state.copyWith(jwtDecodedData: data);
} catch (error) {
state = const AuthState(errorMessage: '认证失败,请重试');
return false;
}
return true; return true;
} }
return false; return false;
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_easyloading/flutter_easyloading.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';
@@ -12,8 +13,6 @@ 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/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_button.dart';
import 'package:recording_tool/shared/widgets/app_qr_scanner_dialog.dart'; import 'package:recording_tool/shared/widgets/app_qr_scanner_dialog.dart';
import 'package:recording_tool/shared/widgets/app_toast.dart'; import 'package:recording_tool/shared/widgets/app_toast.dart';
@@ -34,10 +33,10 @@ class ScanQrCodePage extends ConsumerStatefulWidget {
); );
@override @override
ConsumerState<ScanQrCodePage> createState() => _AuthPageWidgetState(); ConsumerState<ScanQrCodePage> createState() => _ScanQrCodePageState();
} }
class _AuthPageWidgetState extends ConsumerState<ScanQrCodePage> { class _ScanQrCodePageState extends ConsumerState<ScanQrCodePage> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@@ -58,109 +57,106 @@ class _AuthPageWidgetState extends ConsumerState<ScanQrCodePage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final eventName = ref.watch(
authProvider.select(
(state) => state.jwtDecodedData?.eventName?.trim() ?? '',
),
);
return PopScope( return PopScope(
canPop: true, canPop: true,
onPopInvokedWithResult: (didPop, result) async { onPopInvokedWithResult: (didPop, result) async {
if (!didPop) return; if (!didPop) return;
await ref.read(authProvider.notifier).clearAuth(); await ref.read(authProvider.notifier).clearAuth();
}, },
child: Scaffold( child: AnnotatedRegion<SystemUiOverlayStyle>(
appBar: myAppBar(context: context, title: '扫码'), value: SystemUiOverlayStyle.dark.copyWith(
body: Center( statusBarColor: Colors.transparent,
child: Column( systemNavigationBarColor: Colors.white,
),
child: Scaffold(
backgroundColor: Colors.white,
body: Stack(
children: [ children: [
SizedBox(height: 100.h), Positioned(
Text( top: 0,
'裁判工作台', left: 0,
style: TextStyle(fontSize: 30, color: Colors.black), right: 0,
child: Image.asset(
_ScanAssets.pageBg,
width: double.infinity,
fit: BoxFit.fitWidth,
),
), ),
SizedBox(height: 100.h), SafeArea(
Text( child: Padding(
'扫描选手参赛凭证进行执裁', padding: EdgeInsets.fromLTRB(16.w, 12.h, 16.w, 32.h),
style: TextStyle(fontSize: 30, color: Colors.black), child: Column(
), crossAxisAlignment: CrossAxisAlignment.start,
SizedBox(height: 20.h), children: [
const _BrandHeader(),
// Consumer( SizedBox(height: 26.h),
// builder: (context, ref, child) { Text(
// return SizedBox( eventName.isEmpty ? '赛事信息' : eventName,
// width: 280.w, maxLines: 2,
// height: 80.h, overflow: TextOverflow.ellipsis,
// child: AppButton( style: TextStyle(
// label: '查看录像', color: const Color(0xFF3C3F44),
// onPressed: () async { fontSize: 24.sp,
// final data = ref.watch( fontWeight: FontWeight.w700,
// authProvider.select((state) => state.jwtDecodedData), height: 1.25,
// ); ),
// if (data == null) return; ),
// debugPrint('赛事名字: ${data.eventName}'); const Spacer(flex: 134),
// final eventName = data.eventName ?? ''; Center(
// await ref child: GestureDetector(
// .read(authProvider.notifier) behavior: HitTestBehavior.opaque,
// .getRecordList(eventName); onTap: _handleScan,
// }, child: Image.asset(
// variant: AppButtonVariant.secondary, _ScanAssets.scanQrcode,
// ), width: 166.w,
// ); height: 165.w,
// }, fit: BoxFit.contain,
// ), ),
SizedBox(height: 16.h), ),
),
Consumer( SizedBox(height: 28.h),
builder: (context, ref, child) { Center(
return SizedBox( child: Text(
width: 280.w, '扫描选手参赛凭证进行执裁',
height: 80.h, style: TextStyle(
child: AppButton( color: const Color(0xFFABAFB6),
label: '参赛队伍', fontSize: 16.sp,
onPressed: () async { fontWeight: FontWeight.w400,
await AppNavigator.push( height: 1.2,
const CompetitionTeamListPage(), ),
context: context, ),
); ),
}, const Spacer(flex: 211),
variant: AppButtonVariant.secondary, Row(
), children: [
); Expanded(
}, child: _BottomOutlineButton(
), label: '查看录像',
SizedBox(height: 16.h), onPressed: _handleViewRecords,
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(); SizedBox(width: 12.w),
if (!success) { Expanded(
AppToast.show('查询选手信息失败'); child: _BottomOutlineButton(
return; label: '参赛队伍',
} onPressed: () async {
if (!mounted) return; await AppNavigator.push(
const CompetitionTeamListPage(),
AppNavigator.push(EventInfoPage(playerId: playerId)); context: context,
} catch (error) { );
AppToast.show('查询选手信息失败'); },
EasyLoading.dismiss(); ),
} ),
}, ],
variant: AppButtonVariant.secondary, ),
],
),
), ),
), ),
], ],
@@ -169,4 +165,125 @@ class _AuthPageWidgetState extends ConsumerState<ScanQrCodePage> {
), ),
); );
} }
Future<void> _handleScan() 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) {
EasyLoading.dismiss();
AppToast.show('查询选手信息失败');
}
}
Future<void> _handleViewRecords() async {
final eventName = ref.read(authProvider).jwtDecodedData?.eventName ?? '';
if (eventName.trim().isEmpty) {
AppToast.show('暂无赛事信息');
return;
}
EasyLoading.show(status: '查询录像...');
try {
final success = await ref
.read(authProvider.notifier)
.getRecordList(eventName);
EasyLoading.dismiss();
if (!success) {
AppToast.show('暂无录像');
return;
}
AppToast.show('录像列表已更新');
} catch (error) {
EasyLoading.dismiss();
AppToast.show('查询录像失败');
}
}
}
class _BrandHeader extends StatelessWidget {
const _BrandHeader();
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset(
_ScanAssets.appIcon,
width: 34.w,
height: 34.w,
fit: BoxFit.cover,
),
SizedBox(width: 8.w),
Image.asset(
_ScanAssets.appNameText,
width: 117.w,
height: 29.w,
fit: BoxFit.contain,
),
],
);
}
}
class _BottomOutlineButton extends StatelessWidget {
const _BottomOutlineButton({required this.label, required this.onPressed});
final String label;
final VoidCallback onPressed;
@override
Widget build(BuildContext context) {
return SizedBox(
height: 38.h,
child: Material(
color: Colors.white.withValues(alpha: 0.08),
shape: StadiumBorder(
side: BorderSide(color: const Color(0xFF9CCEFF), width: 1.r),
),
child: InkWell(
onTap: onPressed,
customBorder: const StadiumBorder(),
child: Center(
child: Text(
label,
style: TextStyle(
color: const Color(0xFF53A7F3),
fontSize: 14.sp,
fontWeight: FontWeight.w500,
height: 1.2,
),
),
),
),
),
);
}
}
class _ScanAssets {
const _ScanAssets._();
static const pageBg = 'assets/images/image_page_bg.png';
static const appIcon = 'assets/images/image_app_icon.png';
static const appNameText = 'assets/images/image_app_name_text.png';
static const scanQrcode = 'assets/images/image_scan_qrcode.png';
} }
+18 -8
View File
@@ -24,13 +24,13 @@ class $AssetsHtmlGen {
class $AssetsImagesGen { class $AssetsImagesGen {
const $AssetsImagesGen(); const $AssetsImagesGen();
/// File path: assets/images/image_copy.png /// File path: assets/images/image_app_icon.png
AssetGenImage get imageCopy => AssetGenImage get imageAppIcon =>
const AssetGenImage('assets/images/image_copy.png'); const AssetGenImage('assets/images/image_app_icon.png');
/// File path: assets/images/image_delete.png /// File path: assets/images/image_app_name_text.png
AssetGenImage get imageDelete => AssetGenImage get imageAppNameText =>
const AssetGenImage('assets/images/image_delete.png'); const AssetGenImage('assets/images/image_app_name_text.png');
/// File path: assets/images/image_dialog_bg.png /// File path: assets/images/image_dialog_bg.png
AssetGenImage get imageDialogBg => AssetGenImage get imageDialogBg =>
@@ -40,16 +40,26 @@ class $AssetsImagesGen {
AssetGenImage get imageLogo => AssetGenImage get imageLogo =>
const AssetGenImage('assets/images/image_logo.png'); const AssetGenImage('assets/images/image_logo.png');
/// File path: assets/images/image_page_bg.png
AssetGenImage get imagePageBg =>
const AssetGenImage('assets/images/image_page_bg.png');
/// File path: assets/images/image_scan_qrcode.png
AssetGenImage get imageScanQrcode =>
const AssetGenImage('assets/images/image_scan_qrcode.png');
/// File path: assets/images/image_vs.png /// File path: assets/images/image_vs.png
AssetGenImage get imageVs => AssetGenImage get imageVs =>
const AssetGenImage('assets/images/image_vs.png'); const AssetGenImage('assets/images/image_vs.png');
/// List of all assets /// List of all assets
List<AssetGenImage> get values => [ List<AssetGenImage> get values => [
imageCopy, imageAppIcon,
imageDelete, imageAppNameText,
imageDialogBg, imageDialogBg,
imageLogo, imageLogo,
imagePageBg,
imageScanQrcode,
imageVs, imageVs,
]; ];
} }