From c38f6d69c82edac5e70d3c66a696036c427742b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E9=94=8B?= <2535831261@qq.com> Date: Thu, 9 Jul 2026 14:32:39 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20AuthApi=20=E6=9E=9A?= =?UTF-8?q?=E4=B8=BE=E4=BB=A5=E6=B7=BB=E5=8A=A0=E8=8E=B7=E5=8F=96=E6=8E=A8?= =?UTF-8?q?=E6=B5=81=E5=9C=B0=E5=9D=80=E7=9A=84=20API=20=E8=B7=AF=E5=BE=84?= =?UTF-8?q?=EF=BC=9B=E9=87=8D=E6=9E=84=20Auth=20=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E4=BB=A5=E4=BC=98=E5=8C=96=E8=BE=93=E5=85=A5=E6=8E=A7=E4=BB=B6?= =?UTF-8?q?=E5=92=8C=E6=8C=89=E9=92=AE=E5=B8=83=E5=B1=80=EF=BC=9B=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E6=89=AB=E7=A0=81=E9=A1=B5=E9=9D=A2=E4=BB=A5=E5=AF=BC?= =?UTF-8?q?=E8=88=AA=E5=88=B0=E5=BD=95=E5=88=B6=E9=A1=B5=E9=9D=A2=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/app/config/api_common.dart | 5 +- lib/features/auth/pages/page_auth.dart | 73 +++--- .../scan_qrcode/pages/page_scan_qrcode.dart | 4 +- lib/shared/widgets/app_text.dart | 234 ++++++++++++++++++ lib/shared/widgets/widgets.dart | 1 + 5 files changed, 278 insertions(+), 39 deletions(-) create mode 100644 lib/shared/widgets/app_text.dart diff --git a/lib/app/config/api_common.dart b/lib/app/config/api_common.dart index 0de1f25..99505ed 100644 --- a/lib/app/config/api_common.dart +++ b/lib/app/config/api_common.dart @@ -1,6 +1,9 @@ enum AuthApi { /// 获取 token - getToken('/api/events/device/token'); + getToken('/api/events/device/token'), + + /// 获取推流地址 + getSteamKey('api/events/device/stream/key'); final String path; const AuthApi(this.path); diff --git a/lib/features/auth/pages/page_auth.dart b/lib/features/auth/pages/page_auth.dart index b733238..6ce0839 100644 --- a/lib/features/auth/pages/page_auth.dart +++ b/lib/features/auth/pages/page_auth.dart @@ -20,6 +20,7 @@ class _AuthPageWidgetState extends ConsumerState { void initState() { super.initState(); _controller = TextEditingController(); + _controller?.text = '555'; } @override @@ -36,46 +37,44 @@ class _AuthPageWidgetState extends ConsumerState { child: Column( children: [ SizedBox(height: 180.h), - Text('裁判工作台', style: TextStyle(fontSize: 50, color: Colors.black)), + AppText('裁判工作台', fontSize: 30.sp), SizedBox(height: 20.h), - Text('输入执裁口令', style: TextStyle(fontSize: 30, color: Colors.black)), - SizedBox(height: 20.h), - - SizedBox( - width: 280.w, - height: 80.h, - child: AppTextField(initialValue: 'hhh', controller: _controller), + Text( + '输入执裁口令', + style: TextStyle(fontSize: 18.sp, color: Colors.black), ), SizedBox(height: 20.h), - SizedBox( - width: 280.w, - height: 80.h, - child: AppButton( - label: '确定', - onPressed: () async { - final code = _controller?.text; - final success = await ref - .read(authProvider.notifier) - .auth(code ?? ''); - if (!mounted) return; - if (success) { - AppNavigator.push(const ScanQrCodePage()); - return; - } - final message = ref.read(authProvider).errorMessage; - if (message != null && message.isNotEmpty) { - AppToast.show(message); - } - // return; - // AppNavigator.push( - // const WebviewPage( - // url: - // 'http://192.168.1.64:5173/#/pages/h5/score-entry?eventId=10237&scheduleId=113716750536806400&playerId=74300708945531007', - // ), - // ); - }, - variant: AppButtonVariant.secondary, - isLoading: authState.isLoading, + + Container( + padding: EdgeInsets.symmetric(horizontal: 20.w), + child: Column( + children: [ + AppTextField(controller: _controller), + SizedBox(height: 20.h), + SizedBox( + width: double.maxFinite, + child: AppButton( + label: '确定', + onPressed: () async { + final code = _controller?.text; + final success = await ref + .read(authProvider.notifier) + .auth(code ?? ''); + if (!mounted) return; + if (success) { + AppNavigator.push(const ScanQrCodePage()); + return; + } + final message = ref.read(authProvider).errorMessage; + if (message != null && message.isNotEmpty) { + AppToast.show(message); + } + }, + variant: AppButtonVariant.secondary, + isLoading: authState.isLoading, + ), + ), + ], ), ), ], diff --git a/lib/features/scan_qrcode/pages/page_scan_qrcode.dart b/lib/features/scan_qrcode/pages/page_scan_qrcode.dart index 0f11088..35263b7 100644 --- a/lib/features/scan_qrcode/pages/page_scan_qrcode.dart +++ b/lib/features/scan_qrcode/pages/page_scan_qrcode.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:recording_tool/app/router/app_navigator.dart'; +import 'package:recording_tool/features/recording/pages/page_record.dart'; import 'package:recording_tool/features/scan_qrcode/pages/page_push_test.dart'; import 'package:recording_tool/shared/widgets/app_bar.dart'; import 'package:recording_tool/shared/widgets/app_button.dart'; @@ -40,7 +41,8 @@ class _AuthPageWidgetState extends State { if (result == null || result.isEmpty) return; debugPrint('扫码结果: $result'); Future.delayed(const Duration(milliseconds: 1)).then((_) { - AppNavigator.push(PushSteamTestWidget(rtmpUrl: result)); + AppNavigator.push(const RecordingPage()); + // AppNavigator.push(PushSteamTestWidget(rtmpUrl: result)); // AppNavigator.push( // const WebviewPage(url: 'https://www.dronex.cc/'), // ); diff --git a/lib/shared/widgets/app_text.dart b/lib/shared/widgets/app_text.dart new file mode 100644 index 0000000..c30c71c --- /dev/null +++ b/lib/shared/widgets/app_text.dart @@ -0,0 +1,234 @@ +import 'package:flutter/material.dart'; + +/// 全局文本尺寸语义。 +/// +/// 业务代码应优先选择语义化的 variant,而不是直接散落 `fontSize`。 +/// 这样后续调整整套字号体系时,只需要维护这一处映射。 +enum AppTextVariant { + display, + headline, + title, + subtitle, + body, + bodySmall, + label, + caption, +} + +/// 全局文本颜色语义。 +/// +/// tone 只表达“文本在界面中的语义角色”,具体颜色从当前 Theme 解析, +/// 避免页面直接依赖硬编码色值。 +enum AppTextTone { + primary, + secondary, + tertiary, + inverse, + brand, + success, + warning, + danger, + disabled, +} + +/// 应用级文本组件。 +/// +/// `AppText` 是对 Flutter `Text` 的轻量封装,目标是统一页面里的字号、 +/// 字重、颜色和溢出策略,同时保留 `Text` 的常用能力。 +/// +/// 使用建议: +/// - 普通文案使用默认 `AppText('内容')`。 +/// - 标题使用 `variant: AppTextVariant.title`。 +/// - 错误、警告、成功等状态文案使用 `tone`,不要在业务里直接写颜色。 +/// - 只有遇到一次性视觉细节时才传入 `style`、`fontSize` 或 `fontWeight`。 +class AppText extends StatelessWidget { + const AppText( + this.data, { + super.key, + this.variant = AppTextVariant.body, + this.tone = AppTextTone.primary, + this.style, + this.color, + this.fontSize, + this.fontWeight, + this.height, + this.letterSpacing, + this.textAlign, + this.textDirection, + this.locale, + this.softWrap, + this.overflow, + this.maxLines, + this.semanticsLabel, + this.textWidthBasis, + this.textHeightBehavior, + this.textScaler, + this.selectionColor, + }) : textSpan = null; + + /// 富文本构造器。 + /// + /// 用于同一段文案中存在局部强调、不同颜色或不同字重的场景。 + /// 外层的 `variant`、`tone` 和通用排版参数仍会作为默认样式作用到 span。 + const AppText.rich( + this.textSpan, { + super.key, + this.variant = AppTextVariant.body, + this.tone = AppTextTone.primary, + this.style, + this.color, + this.fontSize, + this.fontWeight, + this.height, + this.letterSpacing, + this.textAlign, + this.textDirection, + this.locale, + this.softWrap, + this.overflow, + this.maxLines, + this.semanticsLabel, + this.textWidthBasis, + this.textHeightBehavior, + this.textScaler, + this.selectionColor, + }) : data = null; + + /// 普通文本内容。与 [textSpan] 二选一。 + final String? data; + + /// 富文本内容。与 [data] 二选一。 + final InlineSpan? textSpan; + + /// 文本尺寸和基础字重语义。 + final AppTextVariant variant; + + /// 文本颜色语义。 + final AppTextTone tone; + + /// 额外样式覆盖。优先级高于 variant 和 tone。 + final TextStyle? style; + + /// 显式颜色覆盖。优先级高于 tone 和 `style.color`。 + final Color? color; + + /// 一次性字号覆盖。常规场景优先使用 [variant]。 + final double? fontSize; + + /// 一次性字重覆盖。常规场景优先使用 [variant]。 + final FontWeight? fontWeight; + + /// 行高覆盖。 + final double? height; + + /// 字间距覆盖。 + final double? letterSpacing; + + final TextAlign? textAlign; + final TextDirection? textDirection; + final Locale? locale; + final bool? softWrap; + final TextOverflow? overflow; + final int? maxLines; + final String? semanticsLabel; + final TextWidthBasis? textWidthBasis; + final TextHeightBehavior? textHeightBehavior; + final TextScaler? textScaler; + final Color? selectionColor; + + @override + Widget build(BuildContext context) { + final effectiveStyle = _resolveStyle(context); + + if (textSpan != null) { + return Text.rich( + textSpan!, + style: effectiveStyle, + textAlign: textAlign, + textDirection: textDirection, + locale: locale, + softWrap: softWrap, + overflow: overflow, + maxLines: maxLines, + semanticsLabel: semanticsLabel, + textWidthBasis: textWidthBasis, + textHeightBehavior: textHeightBehavior, + textScaler: textScaler, + selectionColor: selectionColor, + ); + } + + return Text( + data ?? '', + style: effectiveStyle, + textAlign: textAlign, + textDirection: textDirection, + locale: locale, + softWrap: softWrap, + overflow: overflow, + maxLines: maxLines, + semanticsLabel: semanticsLabel, + textWidthBasis: textWidthBasis, + textHeightBehavior: textHeightBehavior, + textScaler: textScaler, + selectionColor: selectionColor, + ); + } + + /// 合成最终样式。 + /// + /// 优先级从低到高: + /// 1. Theme 中的 TextTheme。 + /// 2. `variant` 与 `tone` 对应的默认样式。 + /// 3. 外部传入的 `style`。 + /// 4. `color`、`fontSize`、`fontWeight` 等显式字段。 + TextStyle _resolveStyle(BuildContext context) { + final baseStyle = _variantStyle(Theme.of(context).textTheme); + final toneStyle = baseStyle.copyWith(color: _toneColor(context)); + final mergedStyle = style == null ? toneStyle : toneStyle.merge(style); + + return mergedStyle.copyWith( + color: color ?? mergedStyle.color, + fontSize: fontSize ?? mergedStyle.fontSize, + fontWeight: fontWeight ?? mergedStyle.fontWeight, + height: height ?? mergedStyle.height, + letterSpacing: letterSpacing ?? mergedStyle.letterSpacing, + ); + } + + TextStyle _variantStyle(TextTheme textTheme) { + return switch (variant) { + AppTextVariant.display => + textTheme.displaySmall ?? const TextStyle(fontSize: 36), + AppTextVariant.headline => + textTheme.headlineSmall ?? const TextStyle(fontSize: 24), + AppTextVariant.title => + textTheme.titleMedium ?? const TextStyle(fontSize: 16), + AppTextVariant.subtitle => + textTheme.titleSmall ?? const TextStyle(fontSize: 14), + AppTextVariant.body => + textTheme.bodyMedium ?? const TextStyle(fontSize: 14), + AppTextVariant.bodySmall => + textTheme.bodySmall ?? const TextStyle(fontSize: 12), + AppTextVariant.label => + textTheme.labelLarge ?? const TextStyle(fontSize: 14), + AppTextVariant.caption => + textTheme.labelSmall ?? const TextStyle(fontSize: 11), + }; + } + + Color _toneColor(BuildContext context) { + final colors = Theme.of(context).colorScheme; + return switch (tone) { + AppTextTone.primary => colors.onSurface, + AppTextTone.secondary => colors.onSurfaceVariant, + AppTextTone.tertiary => colors.outline, + AppTextTone.inverse => colors.onInverseSurface, + AppTextTone.brand => colors.primary, + AppTextTone.success => colors.tertiary, + AppTextTone.warning => colors.secondary, + AppTextTone.danger => colors.error, + AppTextTone.disabled => colors.onSurface.withValues(alpha: 0.38), + }; + } +} diff --git a/lib/shared/widgets/widgets.dart b/lib/shared/widgets/widgets.dart index f457f49..c5baed8 100644 --- a/lib/shared/widgets/widgets.dart +++ b/lib/shared/widgets/widgets.dart @@ -11,6 +11,7 @@ export 'app_refresh_list.dart'; export 'app_search_bar.dart'; export 'app_status_view.dart'; export 'app_tag.dart'; +export 'app_text.dart'; export 'app_text_field.dart'; export 'app_toast.dart'; export 'safe_area_wrapper.dart';