diff --git a/assets/images/image_blue_team.png b/assets/images/image_blue_team.png new file mode 100644 index 0000000..2eadf9d Binary files /dev/null and b/assets/images/image_blue_team.png differ diff --git a/assets/images/image_red_team.png b/assets/images/image_red_team.png new file mode 100644 index 0000000..41eae11 Binary files /dev/null and b/assets/images/image_red_team.png differ diff --git a/assets/images/image_scan.png b/assets/images/image_scan.png index 57e1d27..d1414ec 100644 Binary files a/assets/images/image_scan.png and b/assets/images/image_scan.png differ diff --git a/assets/images/image_start.png b/assets/images/image_start.png new file mode 100644 index 0000000..0a6cb4b Binary files /dev/null and b/assets/images/image_start.png differ diff --git a/lib/app/config/app_config.dart b/lib/app/config/app_config.dart index 4fc121c..8dc32df 100644 --- a/lib/app/config/app_config.dart +++ b/lib/app/config/app_config.dart @@ -24,7 +24,7 @@ class AppConfig { static AppPackageInfo? packageInfo; static const appName = 'SportsX裁判工作台'; - static const designSize = Size(375, 812); + static const designSize = Size(640, 1024); /// 主裁判计分 H5 链接 diff --git a/lib/features/competition_teams/pages/page_event_team_match.dart b/lib/features/competition_teams/pages/page_event_team_match.dart index 69f6f98..0bd6b22 100644 --- a/lib/features/competition_teams/pages/page_event_team_match.dart +++ b/lib/features/competition_teams/pages/page_event_team_match.dart @@ -10,7 +10,8 @@ import 'package:recording_tool/features/competition_teams/widgets/widget_manual_ import 'package:recording_tool/features/events/model/model_event_info.dart'; import 'package:recording_tool/features/events/request_model/request_model_event.dart'; import 'package:recording_tool/features/events/server/server_events.dart'; -import 'package:recording_tool/shared/widgets/app_button.dart'; +import 'package:recording_tool/gen/assets.gen.dart'; +import 'package:recording_tool/shared/widgets/app_bar.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_webview.dart'; @@ -236,52 +237,68 @@ class _EventTeamMatchPageState extends ConsumerState { final redTeam = _redTeam; final blueTeam = _blueTeam; return Scaffold( - backgroundColor: Colors.white, - appBar: AppBar(), + backgroundColor: const Color(0xFFF6F7F9), + appBar: myAppBar( + context: context, + titleWidget: Text( + '选手检录', + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w500, + color: Colors.white, + fontFamily: 'PingFang SC', + ), + ), + actions: [ + TextButton( + key: const ValueKey('event-team-manual-process'), + onPressed: _manualProcess, + style: TextButton.styleFrom( + foregroundColor: Colors.white, + padding: EdgeInsets.symmetric(horizontal: 14.w), + minimumSize: Size(72.w, kToolbarHeight), + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + child: Text( + '人工处理', + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w500, + color: Colors.white.withValues(alpha: 0.8), + fontFamily: 'PingFang SC', + ), + ), + ), + ], + ), body: SafeArea( top: false, child: Column( children: [ + SizedBox(height: 10.h), + _MatchMetadata(detail: _detail), + SizedBox(height: 12.h), Expanded( child: SingleChildScrollView( - padding: EdgeInsets.fromLTRB(20.w, 12.h, 20.w, 20.h), + padding: EdgeInsets.fromLTRB(8.w, 0, 8.w, 20.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _MatchMetadata(detail: _detail), - SizedBox(height: 10.h), - - SizedBox(height: 8.h), - Align( - alignment: Alignment.centerRight, - child: TextButton( - key: const ValueKey('event-team-manual-process'), - onPressed: _manualProcess, - child: Text( - '人工处理', - style: TextStyle( - fontSize: 18.sp, - fontWeight: FontWeight.w700, - color: const Color(0xFF078AF2), - ), - ), - ), - ), _TeamCard( - teamLabel: '红队', + sideLabel: '红方', team: redTeam, members: _redMembers, - backgroundColor: const Color(0xFFFFEEF0), + backgroundImage: 'assets/images/image_red_team.png', accentColor: const Color(0xFFE84B5B), verifiedUserIds: _verifiedUserIds, winner: _winnerSideIndex == 0, ), - SizedBox(height: 16.h), + SizedBox(height: 8.h), _TeamCard( - teamLabel: '蓝队', + sideLabel: '蓝方', team: blueTeam, members: _blueMembers, - backgroundColor: const Color(0xFFEDF5FF), + backgroundImage: 'assets/images/image_blue_team.png', accentColor: const Color(0xFF287FDD), verifiedUserIds: _verifiedUserIds, winner: _winnerSideIndex == 1, @@ -292,21 +309,20 @@ class _EventTeamMatchPageState extends ConsumerState { ), ), Padding( - padding: EdgeInsets.fromLTRB(20.w, 12.h, 20.w, 20.h), + padding: EdgeInsets.fromLTRB(16.w, 16.h, 16.w, 24.h), child: Column( children: [ - SizedBox( - width: double.infinity, - child: AppButton( - label: '继续扫码', - variant: AppButtonVariant.outline, - onPressed: _continueScan, - ), + _TeamActionButton( + label: '继续扫码', + iconPath: Assets.images.imageScan.path, + onPressed: _continueScan, + filled: true, ), SizedBox(height: 12.h), - SizedBox( - width: double.infinity, - child: AppButton(label: '直接开赛', onPressed: _startDirectly), + _TeamActionButton( + label: '直接开赛', + iconPath: 'assets/images/image_start.png', + onPressed: _startDirectly, ), ], ), @@ -341,25 +357,30 @@ class _MatchMetadata extends StatelessWidget { final groupName = detail.groupName?.trim() ?? ''; return Container( width: double.infinity, - padding: EdgeInsets.all(18.r), + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 12.h), decoration: BoxDecoration( - color: const Color(0xFFF7F9FC), - borderRadius: BorderRadius.circular(14.r), + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.04), + blurRadius: 12.r, + offset: Offset(0, 4.h), + ), + ], ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: Row( children: [ - Row( - children: [ - Expanded( - child: _MetadataText(label: '比赛项目', value: itemName), - ), - SizedBox(width: 16.w), - _MetadataText(label: '场地', value: matchPlace), - ], + Expanded( + child: _MetadataText(label: '场地', value: matchPlace), + ), + SizedBox(width: 6.w), + Expanded( + child: _MetadataText(label: '赛项', value: itemName), + ), + SizedBox(width: 6.w), + Expanded( + child: _MetadataText(label: '组别', value: groupName), ), - SizedBox(height: 14.h), - _MetadataText(label: '组别', value: groupName), ], ), ); @@ -376,13 +397,14 @@ class _MetadataText extends StatelessWidget { Widget build(BuildContext context) { return Text( '$label:${value.isEmpty ? '暂无' : value}', - maxLines: 2, + maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( - fontSize: 17.sp, + fontSize: 13.sp, height: 1.35, - color: const Color(0xFF303640), + color: const Color(0xFF858B95), fontWeight: FontWeight.w600, + fontFamily: 'PingFang SC', ), ); } @@ -390,20 +412,20 @@ class _MetadataText extends StatelessWidget { class _TeamCard extends StatelessWidget { const _TeamCard({ - required this.teamLabel, + required this.sideLabel, required this.team, required this.members, - required this.backgroundColor, + required this.backgroundImage, required this.accentColor, required this.verifiedUserIds, required this.winner, this.emptyMessage = '暂无成员数据', }); - final String teamLabel; + final String sideLabel; final CompetitionTeam team; final List members; - final Color backgroundColor; + final String backgroundImage; final Color accentColor; final Set verifiedUserIds; final bool winner; @@ -414,30 +436,38 @@ class _TeamCard extends StatelessWidget { final visibleMembers = members .where((member) => member.userId.isNotEmpty || member.name.isNotEmpty) .toList(growable: false); - return AnimatedContainer( - duration: const Duration(milliseconds: 180), + final leader = _leaderOf(visibleMembers); + final players = visibleMembers + .where((member) => !identical(member, leader)) + .toList(growable: false); + final leaderVerified = + leader != null && verifiedUserIds.contains(leader.userId); + return Container( width: double.infinity, - padding: EdgeInsets.all(18.r), + constraints: BoxConstraints(minHeight: 106.h), + padding: EdgeInsets.fromLTRB(12.w, 16.h, 16.w, 12.h), decoration: BoxDecoration( - color: backgroundColor, - borderRadius: BorderRadius.circular(14.r), - border: Border.all( - color: winner ? accentColor : accentColor.withValues(alpha: 0.24), - width: winner ? 2 : 1, + image: DecorationImage( + image: AssetImage(backgroundImage), + fit: BoxFit.fill, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( + crossAxisAlignment: CrossAxisAlignment.center, children: [ Expanded( child: Text( - '$teamLabel:${team.name}', + '$sideLabel队伍名称:${team.name}', + maxLines: 1, + overflow: TextOverflow.ellipsis, style: TextStyle( - fontSize: 19.sp, + fontSize: 16.sp, fontWeight: FontWeight.w700, - color: const Color(0xFF252B34), + color: const Color(0xFF30343B), + fontFamily: 'PingFang SC', ), ), ), @@ -456,25 +486,110 @@ class _TeamCard extends StatelessWidget { '胜方', style: TextStyle( color: Colors.white, - fontSize: 12.sp, + fontSize: 11.sp, fontWeight: FontWeight.w700, + fontFamily: 'PingFang SC', ), ), ), ], ), - SizedBox(height: 14.h), + SizedBox(height: 6.h), if (visibleMembers.isEmpty) Text( emptyMessage, - style: TextStyle(fontSize: 15.sp, color: const Color(0xFF747D89)), + style: TextStyle( + fontSize: 13.sp, + color: const Color(0xFF747D89), + fontFamily: 'PingFang SC', + ), ) - else - ...visibleMembers.map( - (member) => _MemberRow( - member: member, - verified: verifiedUserIds.contains(member.userId), - accentColor: accentColor, + else ...[ + if (leader != null) + _TeamTextLine( + label: '队长', + text: _formatMember(leader), + showOk: leaderVerified || leader.isLeader, + ), + if (players.isNotEmpty) + _TeamTextLine( + label: '选手', + text: players.map(_formatMember).join('、'), + showOk: players.any( + (member) => verifiedUserIds.contains(member.userId), + ), + ), + ], + ], + ), + ); + } + + EventTeamMember? _leaderOf(List members) { + for (final member in members) { + if (member.isLeader) return member; + } + return members.isEmpty ? null : members.first; + } + + String _formatMember(EventTeamMember member) { + final name = member.name.trim().isEmpty ? '暂无' : member.name.trim(); + // final id = member.userId.trim(); + // return id.isEmpty ? name : '$name($id)'; + return name; + } +} + +class _TeamTextLine extends StatelessWidget { + const _TeamTextLine({ + required this.label, + required this.text, + required this.showOk, + }); + + final String label; + final String text; + final bool showOk; + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.only(top: 4.h), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + '$label:', + style: TextStyle( + fontSize: 13.sp, + height: 1.35, + color: const Color(0xFF747B86), + fontWeight: FontWeight.w500, + fontFamily: 'PingFang SC', + ), + ), + Flexible( + child: Text( + text.isEmpty ? '暂无' : text, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 13.sp, + height: 1.35, + color: const Color(0xFF4D545F), + fontWeight: FontWeight.w500, + fontFamily: 'PingFang SC', + ), + ), + ), + if (showOk) + Padding( + padding: EdgeInsets.only(left: 6.w), + child: Image.asset( + Assets.images.imageOk.path, + width: 18.w, + height: 14.h, + fit: BoxFit.contain, ), ), ], @@ -483,50 +598,78 @@ class _TeamCard extends StatelessWidget { } } -class _MemberRow extends StatelessWidget { - const _MemberRow({ - required this.member, - required this.verified, - required this.accentColor, +class _TeamActionButton extends StatelessWidget { + const _TeamActionButton({ + required this.label, + required this.iconPath, + required this.onPressed, + this.filled = false, }); - final EventTeamMember member; - final bool verified; - final Color accentColor; + final String label; + final String iconPath; + final VoidCallback onPressed; + final bool filled; @override Widget build(BuildContext context) { - return Padding( - padding: EdgeInsets.symmetric(vertical: 5.h), - child: Row( - children: [ - Container( - width: 6.r, - height: 6.r, - decoration: BoxDecoration( - color: accentColor, - shape: BoxShape.circle, - ), + final radius = BorderRadius.circular(28.r); + final content = Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset( + iconPath, + width: filled ? 18.w : 18.w, + height: filled ? 18.h : 18.h, + fit: BoxFit.contain, + ), + SizedBox(width: 4.w), + Text( + label, + style: TextStyle( + color: filled ? Colors.white : const Color(0xFF359FED), + fontSize: 16.sp, + fontWeight: FontWeight.w700, + fontFamily: 'PingFang SC', ), - SizedBox(width: 10.w), - Expanded( - child: Text( - '${member.isLeader ? '队长' : '选手'}:${member.name.isEmpty ? '暂无' : member.name}', - style: TextStyle(fontSize: 16.sp, color: const Color(0xFF303640)), - ), + ), + ], + ); + + return SizedBox( + width: double.infinity, + height: 45.h, + child: DecoratedBox( + decoration: BoxDecoration( + gradient: filled + ? const LinearGradient( + colors: [Color(0xFF2F8DFF), Color(0xFF57D2EE)], + ) + : null, + color: filled ? null : Colors.white, + borderRadius: radius, + border: filled + ? null + : Border.all(color: const Color(0xFF9FD0F5), width: 1.w), + boxShadow: filled + ? [ + BoxShadow( + color: const Color(0xFF2F8DFF).withValues(alpha: 0.24), + blurRadius: 10.r, + offset: Offset(0, 5.h), + ), + ] + : null, + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: onPressed, + borderRadius: radius, + child: Center(child: content), ), - AnimatedSwitcher( - duration: const Duration(milliseconds: 180), - child: verified - ? Icon( - Icons.check_circle, - key: ValueKey('verified-member-${member.userId}'), - color: const Color(0xFF18A957), - size: 24.r, - ) - : SizedBox(width: 24.r, height: 24.r), - ), - ], + ), ), ); } diff --git a/lib/gen/assets.gen.dart b/lib/gen/assets.gen.dart index 2e6d63c..81485cd 100644 --- a/lib/gen/assets.gen.dart +++ b/lib/gen/assets.gen.dart @@ -36,6 +36,10 @@ class $AssetsImagesGen { AssetGenImage get imageAppNameText => const AssetGenImage('assets/images/image_app_name_text.png'); + /// File path: assets/images/image_blue_team.png + AssetGenImage get imageBlueTeam => + const AssetGenImage('assets/images/image_blue_team.png'); + /// File path: assets/images/image_dialog_bg.png AssetGenImage get imageDialogBg => const AssetGenImage('assets/images/image_dialog_bg.png'); @@ -52,6 +56,10 @@ class $AssetsImagesGen { AssetGenImage get imagePageBg => const AssetGenImage('assets/images/image_page_bg.png'); + /// File path: assets/images/image_red_team.png + AssetGenImage get imageRedTeam => + const AssetGenImage('assets/images/image_red_team.png'); + /// File path: assets/images/image_scan.png AssetGenImage get imageScan => const AssetGenImage('assets/images/image_scan.png'); @@ -60,6 +68,10 @@ class $AssetsImagesGen { AssetGenImage get imageScanQrcode => const AssetGenImage('assets/images/image_scan_qrcode.png'); + /// File path: assets/images/image_start.png + AssetGenImage get imageStart => + const AssetGenImage('assets/images/image_start.png'); + /// File path: assets/images/image_vs.png AssetGenImage get imageVs => const AssetGenImage('assets/images/image_vs.png'); @@ -69,12 +81,15 @@ class $AssetsImagesGen { imageAppBarBg, imageAppIcon, imageAppNameText, + imageBlueTeam, imageDialogBg, imageLogo, imageOk, imagePageBg, + imageRedTeam, imageScan, imageScanQrcode, + imageStart, imageVs, ]; }