import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'package:kaer_with_panels/app/model/enum/kr_request_type.dart'; import 'package:kaer_with_panels/app/model/kr_area_code.dart'; import 'package:kaer_with_panels/app/modules/kr_login/views/kr_search_area_code_view.dart'; import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart'; import 'package:kaer_with_panels/app/widgets/kr_local_image.dart'; import '../../../routes/app_pages.dart'; import '../controllers/kr_login_controller.dart'; import 'package:kaer_with_panels/app/localization/app_translations.dart'; import 'package:kaer_with_panels/app/services/api_service/api.dart'; import 'package:kaer_with_panels/app/common/app_config.dart'; import 'package:kaer_with_panels/app/services/kr_site_config_service.dart'; class KRLoginView extends GetView { const KRLoginView({super.key}); @override Widget build(BuildContext context) { final theme = Theme.of(context); return Scaffold( backgroundColor: theme.scaffoldBackgroundColor, body: GestureDetector( behavior: HitTestBehavior.translucent, onTap: () { if (!FocusScope.of(context).hasPrimaryFocus) { FocusScope.of(context).unfocus(); } _hideDropdown(); }, child: SingleChildScrollView( padding: EdgeInsets.symmetric(horizontal: 20.w), child: Obx(() { switch (controller.kr_loginStatus.value) { case KRLoginProgressStatus.kr_check: return _buildCheckView(context); case KRLoginProgressStatus.kr_loginByCode: return _buildLoginByCodeView(context); case KRLoginProgressStatus.kr_loginByPsd: return _buildLoginByPsdView(context); case KRLoginProgressStatus.kr_registerSendCode: return _buildRegisterSendCodeView(context); case KRLoginProgressStatus.kr_registerSetPsd: return _buildRegisterSetPsdView(context); case KRLoginProgressStatus.kr_forgetPsdSendCode: return _buildForgetPsdSendCodeView(context); case KRLoginProgressStatus.kr_forgetPsdSetPsd: return _buildForgetPsdSetPsdView(context); default: return Container(); } }), ), ), ); } Widget _buildCheckView(BuildContext context) { // 构建登录视图 - 直接显示邮箱和密码输入框 return Column( mainAxisSize: MainAxisSize.min, children: [ // 总是显示返回按钮,包括 kr_check 状态 _buildBackButton(Theme.of(context)), _buildHeaderSection(Theme.of(context)), // 邮箱输入框 _buildInputSection(context, Theme.of(context)), SizedBox(height: 8.w), // 密码输入框 _buildPasswordInput(Theme.of(context)), _buildDynamicContent(Theme.of(context)), _buildNextButton(controller.kr_getNextBtnText(), Theme.of(context)), SizedBox(height: _getBottomPadding()), ], ); } /// 构建密码输入框(专用于 kr_check 状态) Widget _buildPasswordInput(ThemeData theme) { return Container( width: double.infinity, height: 52.w, decoration: ShapeDecoration( color: theme.cardColor, shape: RoundedRectangleBorder( side: BorderSide(width: 0.5, color: const Color(0xFFD2D2D2)), borderRadius: BorderRadius.circular(10.r), ), ), child: Row( children: [ Padding( padding: EdgeInsets.only(left: 12.w), child: _buildIcon("login_psd"), ), SizedBox(width: 8.w), Expanded( child: Obx(() => TextField( controller: controller.psdController, obscureText: controller.kr_obscureText.value, keyboardType: TextInputType.text, decoration: InputDecoration( hintText: '请输入密码', hintStyle: theme.textTheme.bodySmall?.copyWith( fontSize: 14.sp, fontFamily: 'AlibabaPuHuiTi-Regular', ), border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16.w), suffixIcon: controller.kr_psdHasText.value ? IconButton( icon: Icon( controller.kr_obscureText.value ? Icons.visibility_off : Icons.visibility, color: const Color(0xFF999999), ), onPressed: () { controller.kr_obscureText.value = !controller.kr_obscureText.value; }, ) : null, ), style: theme.textTheme.bodyMedium?.copyWith( fontSize: 14.sp, fontFamily: 'AlibabaPuHuiTi-Regular', ), )), ), ], ), ); } /// 获取底部间距 double _getBottomPadding() { switch (controller.kr_loginStatus.value) { case KRLoginProgressStatus.kr_check: case KRLoginProgressStatus.kr_loginByCode: case KRLoginProgressStatus.kr_loginByPsd: // 这些状态内容较少,减小底部间距 return 48.w; case KRLoginProgressStatus.kr_registerSetPsd: case KRLoginProgressStatus.kr_forgetPsdSetPsd: // 这些状态有额外的输入框,保持原有间距 return 48.w; default: // 其他状态使用中等间距 return 48.w; } } Widget _buildLoginByCodeView(BuildContext context) { // 构建验证码登录视图的代码 return Column( mainAxisSize: MainAxisSize.min, children: [ _buildBackButton(Theme.of(context)), _buildHeaderSection(Theme.of(context)), _buildInputSection(context, Theme.of(context)), _buildDynamicContent(Theme.of(context)), _buildNextButton(controller.kr_getNextBtnText(), Theme.of(context)), SizedBox(height: _getBottomPadding()), ], ); } Widget _buildLoginByPsdView(BuildContext context) { // 构建密码登录视图的代码 return Column( mainAxisSize: MainAxisSize.min, children: [ _buildBackButton(Theme.of(context)), _buildHeaderSection(Theme.of(context)), _buildInputSection(context, Theme.of(context)), _buildDynamicContent(Theme.of(context)), _buildNextButton(controller.kr_getNextBtnText(), Theme.of(context)), SizedBox(height: _getBottomPadding()), ], ); } Widget _buildRegisterSendCodeView(BuildContext context) { // 构建单页注册视图 - 所有字段显示在一个页面 final theme = Theme.of(context); return Column( mainAxisSize: MainAxisSize.min, children: [ _buildBackButton(theme), _buildHeaderSection(theme), // 邮箱输入框 _buildRegistrationEmailInput(theme), SizedBox(height: 8.w), // 验证码输入框(根据站点配置决定是否显示,包括底部间距) _buildRegistrationCodeInputWithSpacing(theme), // 密码输入框 _buildRegistrationPasswordInput(theme), SizedBox(height: 8.w), // 确认密码输入框 _buildRegistrationConfirmPasswordInput(theme), SizedBox(height: 8.w), // 邀请码输入框(可选) _buildInviteCodeInput(theme), SizedBox(height: 17.w), // 注册按钮 _buildNextButton('注册账号', theme), SizedBox(height: _getBottomPadding()), ], ); } Widget _buildRegisterSetPsdView(BuildContext context) { // 构建注册设置密码视图的代码 return Column( mainAxisSize: MainAxisSize.min, children: [ _buildBackButton(Theme.of(context)), _buildHeaderSection(Theme.of(context)), _buildInputSection(context, Theme.of(context)), Column( children: [ SizedBox(height: 8.w), _buildInputContainer(context, true, Theme.of(context)), SizedBox(height: 8.w), _buildInviteCodeInput(Theme.of(context)), ], ), _buildDynamicContent(Theme.of(context)), _buildNextButton(controller.kr_getNextBtnText(), Theme.of(context)), SizedBox(height: _getBottomPadding()), ], ); } Widget _buildForgetPsdSendCodeView(BuildContext context) { // 构建忘记密码发送验证码视图的代码 return Column( mainAxisSize: MainAxisSize.min, children: [ _buildBackButton(Theme.of(context)), _buildHeaderSection(Theme.of(context)), _buildInputSection(context, Theme.of(context)), _buildDynamicContent(Theme.of(context)), _buildNextButton(controller.kr_getNextBtnText(), Theme.of(context)), SizedBox(height: _getBottomPadding()), ], ); } Widget _buildForgetPsdSetPsdView(BuildContext context) { // 构建忘记密码设置密码视图的代码 return Column( mainAxisSize: MainAxisSize.min, children: [ _buildBackButton(Theme.of(context)), _buildHeaderSection(Theme.of(context)), _buildInputSection(context, Theme.of(context)), if (controller.kr_loginStatus.value == KRLoginProgressStatus.kr_forgetPsdSetPsd) Column( children: [ SizedBox(height: 8.w), _buildInputContainer(context, true, Theme.of(context)), ], ), _buildDynamicContent(Theme.of(context)), _buildNextButton(controller.kr_getNextBtnText(), Theme.of(context)), SizedBox(height: _getBottomPadding()), ], ); } /// 构建头部文本部分 Widget _buildHeaderSection(ThemeData theme) { // 根据不同状态决定是否显示额外信息 switch (controller.kr_loginStatus.value) { case KRLoginProgressStatus.kr_loginByCode: case KRLoginProgressStatus.kr_registerSendCode: case KRLoginProgressStatus.kr_forgetPsdSendCode: // 验证码相关状态 if (!controller.kr_canSendCode.value) { // 已发送验证码状态 return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ SizedBox(height: 24.w), _buildHeaderText(AppTranslations.kr_login.welcome, theme), SizedBox(height: 8.w), _buildHeaderText( _isSendPhone() ? AppTranslations.kr_login.verifyPhone : AppTranslations.kr_login.verifyEmail, theme ), SizedBox(height: 8.w), Text( AppTranslations.kr_login.codeSent(controller.accountController.text), style: theme.textTheme.bodySmall?.copyWith( fontSize: 13.sp, fontFamily: 'AlibabaPuHuiTi-Regular', color: const Color(0xFF666666), ), ), SizedBox(height: 24.w), ], ); } // 未发送验证码状态,显示简单标题 return Padding( padding: EdgeInsets.only(top: 24.w, bottom: 24.w), child: _buildHeaderText(AppTranslations.kr_login.welcome, theme), ); case KRLoginProgressStatus.kr_loginByPsd: // 密码登录状态 return Padding( padding: EdgeInsets.only(top: 24.w, bottom: 24.w), child: _buildHeaderText(AppTranslations.kr_login.welcome, theme), ); case KRLoginProgressStatus.kr_registerSetPsd: // 注册设置密码状态 return Padding( padding: EdgeInsets.only(top: 24.w, bottom: 24.w), child: _buildHeaderText(AppTranslations.kr_login.welcome, theme), ); case KRLoginProgressStatus.kr_forgetPsdSetPsd: // 忘记密码设置新密码状态 return Padding( padding: EdgeInsets.only(top: 24.w, bottom: 24.w), child: _buildHeaderText(AppTranslations.kr_login.welcome, theme), ); case KRLoginProgressStatus.kr_check: default: // 初始状态和其他状态 return Padding( padding: EdgeInsets.only(top: 24.w, bottom: 24.w), child: _buildHeaderText(AppTranslations.kr_login.welcome, theme), ); } } /// 判断是否为验证码输入状态 bool _isCodeInput() { switch (controller.kr_loginStatus.value) { case KRLoginProgressStatus.kr_loginByCode: case KRLoginProgressStatus.kr_registerSendCode: case KRLoginProgressStatus.kr_forgetPsdSendCode: return true; default: return false; } } /// 判断是否手机号验证 bool _isSendPhone() { return controller.kr_loginType == KRLoginType.kr_telephone; } /// 修改 _buildInputSection 方法 Widget _buildInputSection(BuildContext context, ThemeData theme) { return Obx(() { final loginStatus = controller.kr_loginStatus.value; if (loginStatus == KRLoginProgressStatus.kr_check) { return CompositedTransformTarget( link: controller.kr_layerLink, child: Container( width: double.infinity, child: Row( children: [ Obx(() => Visibility( visible: controller.kr_loginType.value == KRLoginType.kr_telephone, maintainState: true, maintainSize: false, maintainAnimation: true, child: Row( mainAxisSize: MainAxisSize.min, children: [ _buildAreaSelector(theme), SizedBox(width: 8.w), ], ), )), Expanded( child: Container( height: 52.w, padding: EdgeInsets.symmetric(horizontal: 12.w), decoration: ShapeDecoration( color: theme.cardColor, shape: RoundedRectangleBorder( side: BorderSide(width: 0.5, color: const Color(0xFFD2D2D2)), borderRadius: BorderRadius.circular(10.r), ), ), child: Row( children: [ Obx(() => Visibility( visible: controller.kr_loginType.value != KRLoginType.kr_telephone, maintainState: true, maintainAnimation: true, child: Row( mainAxisSize: MainAxisSize.min, children: [ _buildIcon("login_account"), SizedBox(width: 8.w), ], ), )), Expanded( child: TextField( focusNode: controller.kr_accountFocusNode, controller: controller.accountController, keyboardType: TextInputType.text, decoration: InputDecoration( hintText: 'login.enterEmailOrPhone'.tr, hintStyle: theme.textTheme.bodySmall?.copyWith( fontSize: 14.sp, fontFamily: 'AlibabaPuHuiTi-Regular', ), border: InputBorder.none, ), style: theme.textTheme.bodyMedium?.copyWith( fontSize: 14.sp, fontFamily: 'AlibabaPuHuiTi-Regular', ), onChanged: (value) { if (controller.kr_emailList.isNotEmpty) { _showDropdown(context); } else { _hideDropdown(); } }, ), ), _buildClearButton(), ], ), ), ), ], ), ), ); } return _buildInputContainer(context, false, theme); }); } /// 构建输入容器 Widget _buildInputContainer(BuildContext context, bool isNewPsd, ThemeData theme) { if (controller.kr_loginStatus.value == KRLoginProgressStatus.kr_check && controller.kr_loginType.value == KRLoginType.kr_telephone) { return Row( children: [ _buildAreaSelector(theme), SizedBox(width: 8.w), Expanded( child: _buildPhoneInput(context, theme), ), ], ); } return CompositedTransformTarget( link: controller.kr_layerLink, child: Container( width: double.infinity, height: 52.w, decoration: ShapeDecoration( color: theme.cardColor, shape: RoundedRectangleBorder( side: BorderSide(width: 0.5, color: const Color(0xFFD2D2D2)), borderRadius: BorderRadius.circular(10.r), ), ), child: Row( children: [ Padding( padding: EdgeInsets.only(left: 12.w), child: _buildInputLeftWideget(theme), ), SizedBox(width: 8.w), Expanded( child: TextField( controller: controller.kr_loginStatus.value == KRLoginProgressStatus.kr_check ? controller.accountController : controller.kr_loginStatus.value == KRLoginProgressStatus.kr_loginByCode || controller.kr_loginStatus.value == KRLoginProgressStatus.kr_registerSendCode || controller.kr_loginStatus.value == KRLoginProgressStatus.kr_forgetPsdSendCode ? controller.codeController : isNewPsd ? controller.agPsdController : controller.psdController, keyboardType: (controller.kr_loginStatus.value == KRLoginProgressStatus.kr_loginByCode || controller.kr_loginStatus.value == KRLoginProgressStatus.kr_registerSendCode || controller.kr_loginStatus.value == KRLoginProgressStatus.kr_forgetPsdSendCode) ? TextInputType.number : TextInputType.text, decoration: InputDecoration( hintText: controller.kr_loginStatus.value == KRLoginProgressStatus.kr_check ? 'login.enterEmailOrPhone'.tr : controller.kr_loginStatus.value == KRLoginProgressStatus.kr_loginByCode || controller.kr_loginStatus.value == KRLoginProgressStatus.kr_registerSendCode || controller.kr_loginStatus.value == KRLoginProgressStatus.kr_forgetPsdSendCode ? 'login.enterCode'.tr : isNewPsd ? 'login.reenterPassword'.tr : 'login.enterPassword'.tr, hintStyle: theme.textTheme.bodySmall?.copyWith( fontSize: 14.sp, fontFamily: 'AlibabaPuHuiTi-Regular', ), border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16.w), suffixIcon: (controller.kr_loginStatus.value == KRLoginProgressStatus.kr_loginByPsd || controller.kr_loginStatus.value == KRLoginProgressStatus.kr_registerSetPsd || controller.kr_loginStatus.value == KRLoginProgressStatus.kr_forgetPsdSetPsd) && (isNewPsd ? controller.kr_agPsdHasText.value : controller.kr_psdHasText.value) ? IconButton( icon: Icon( controller.kr_obscureText.value ? Icons.visibility_off : Icons.visibility, color: const Color(0xFF999999), ), onPressed: () { controller.kr_obscureText.value = !controller.kr_obscureText.value; }, ) : null, ), style: theme.textTheme.bodyMedium?.copyWith( fontSize: 14.sp, fontFamily: 'AlibabaPuHuiTi-Regular', ), obscureText: (controller.kr_loginStatus.value == KRLoginProgressStatus.kr_loginByPsd || controller.kr_loginStatus.value == KRLoginProgressStatus.kr_registerSetPsd || controller.kr_loginStatus.value == KRLoginProgressStatus.kr_forgetPsdSetPsd) && controller.kr_obscureText.value, onChanged: (value) { if (controller.kr_emailList.length > 0) { _showDropdown(context); } else { _hideDropdown(); } }, ), ), if (controller.kr_loginStatus.value == KRLoginProgressStatus.kr_loginByCode || controller.kr_loginStatus.value == KRLoginProgressStatus.kr_registerSendCode || controller.kr_loginStatus.value == KRLoginProgressStatus.kr_forgetPsdSendCode) ...[ if (controller.kr_codeHasText.value) GestureDetector( onTap: () { controller.codeController.clear(); }, child: Container( height: double.infinity, padding: EdgeInsets.symmetric(horizontal: 8.w), child: _buildIcon("login_close"), ), ), _buildSendCodeButton(theme), ] else if (controller.kr_loginStatus.value == KRLoginProgressStatus.kr_check ? controller.kr_accountHasText.value : isNewPsd ? controller.kr_agPsdHasText.value : controller.kr_psdHasText.value) ...[ GestureDetector( onTap: () { if (controller.kr_loginStatus.value == KRLoginProgressStatus.kr_check) { controller.kr_emailList.clear(); _hideDropdown(); } if (controller.kr_loginStatus.value == KRLoginProgressStatus.kr_check) { controller.accountController.clear(); } else if (isNewPsd) { controller.agPsdController.clear(); } else { controller.psdController.clear(); } }, child: Container( height: double.infinity, padding: EdgeInsets.symmetric(horizontal: 8.w), child: _buildIcon("login_close"), ), ), ], ], ), ), ); } /// 构建发送验证码按钮 Widget _buildSendCodeButton(ThemeData theme) { return Obx(() => GestureDetector( onTap: controller.kr_canSendCode.value ? () => controller.kr_sendCode() : null, child: Container( alignment: Alignment.center, width: 100.w, padding: EdgeInsets.only(right: 4.w), child: Text( controller.kr_countdownText.value, style: theme.textTheme.bodySmall?.copyWith( fontSize: 14.sp, color: controller.kr_canSendCode.value ? const Color(0xFF1797FF) : const Color(0xFF999999), fontFamily: 'AlibabaPuHuiTi-Regular', ), ), ), )); } /// 显示悬浮框 void _showDropdown(BuildContext context) { final theme = Theme.of(context); if (controller.isDropdownVisible) return; controller.overlayEntry = OverlayEntry( builder: (_) => Positioned( width: MediaQuery.of(context).size.width - 40.w, child: CompositedTransformFollower( link: controller.kr_layerLink, showWhenUnlinked: false, offset: Offset(0, 54.w), child: Material( elevation: 4, borderRadius: BorderRadius.circular(10.r), child: Obx(() { return Container( constraints: BoxConstraints(maxHeight: 216.w), decoration: ShapeDecoration( color: theme.cardColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10.r), ), ), child: ListView.builder( padding: EdgeInsets.zero, shrinkWrap: true, itemCount: controller.kr_emailList.length, itemBuilder: (context, index) { final email = controller.kr_emailList[index]; return InkWell( onTap: () { controller.accountController.text = email; controller.accountController.selection = TextSelection.fromPosition( TextPosition(offset: email.length), ); _hideDropdown(); }, child: Container( padding: EdgeInsets.symmetric( vertical: 12.w, horizontal: 15.w, ), child: Text( email, style: theme.textTheme.bodyMedium?.copyWith( fontSize: 14.sp, fontFamily: 'AlibabaPuHuiTi-Regular', ), ), ), ); }, ), ); }), ), ), ), ); Overlay.of(context).insert(controller.overlayEntry!); controller.isDropdownVisible = true; } /// 隐藏悬浮框 void _hideDropdown() { if (controller.isDropdownVisible) { controller.overlayEntry?.remove(); controller.overlayEntry = null; controller.isDropdownVisible = false; } } /// 构建输入框左侧组件 Widget _buildInputLeftWideget(ThemeData theme) { return Obx(() { switch (controller.kr_loginStatus.value) { case KRLoginProgressStatus.kr_check: // 检查状态下,根据登录类型判断返回内容 return controller.kr_loginType.value == KRLoginType.kr_telephone ? _buildChoiceCode(theme) : _buildIcon("login_account"); case KRLoginProgressStatus.kr_loginByCode: case KRLoginProgressStatus.kr_registerSendCode: case KRLoginProgressStatus.kr_forgetPsdSendCode: // 验证码相关状态,返回通用图标 return _buildIcon("login_code"); case KRLoginProgressStatus.kr_registerSetPsd: case KRLoginProgressStatus.kr_forgetPsdSetPsd: // 设置密码相关状态,返回通用图标 return _buildIcon("login_psd"); default: // 默认返回通用图标 return SizedBox(); } }); } /// 动态显示内容部分 Widget _buildDynamicContent(ThemeData theme) { // 先确定内容 Widget? content; switch (controller.kr_loginStatus.value) { case KRLoginProgressStatus.kr_check: // 在登录状态显示"忘记密码"和切换按钮 content = _buildLoginBtns(theme); case KRLoginProgressStatus.kr_loginByCode: case KRLoginProgressStatus.kr_loginByPsd: content = _buildLoginBtns(theme); default: return SizedBox(height: 17.w); // 移除 const,因为使用了扩展方法 } // 有内容时的布局 return Column( mainAxisSize: MainAxisSize.min, children: [ SizedBox(height: 12.w), // 与输入框的间距 content, SizedBox(height: 17.w), // 与下一步按钮的间距 ], ); } /// 构建协议文本 Widget _buildAgreementText(ThemeData theme) { return Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ KrLocalImage( imageName: "selete_s", width: 20.w, height: 20.w, ), SizedBox(width: 4.w), Expanded( child: Wrap( alignment: WrapAlignment.start, spacing: 4.w, runSpacing: 4.w, crossAxisAlignment: WrapCrossAlignment.center, children: [ Text( 'login.agreeTerms'.tr, style: _commonTextStyle(fontSize: 13), ), GestureDetector( onTap: () => Get.toNamed(Routes.KR_WEBVIEW, arguments: { 'url': Api.kr_getSiteTos, 'title': 'login.termsOfService'.tr, }), child: Text( 'login.termsOfService'.tr, style: _commonTextStyle(fontSize: 13, color: const Color(0xFF1796FF)), ), ), Text( AppTranslations.kr_login.and, style: _commonTextStyle(fontSize: 13), ), GestureDetector( onTap: () => Get.toNamed(Routes.KR_WEBVIEW, arguments: { 'url': Api.kr_getSitePrivacy, 'title': 'login.privacyPolicy'.tr, }), child: Text( 'login.privacyPolicy'.tr, style: _commonTextStyle(fontSize: 13, color: const Color(0xFF1796FF)), ), ), ], ), ), ], ); } /// 构建登录按钮行 Widget _buildLoginBtns(ThemeData theme) { // 在 kr_check 状态下显示不同的按钮 if (controller.kr_loginStatus.value == KRLoginProgressStatus.kr_check) { return SizedBox( height: 24.w, child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ _buildHoverableTextButton( text: 'login.forgotPassword'.tr, onTap: () => controller.kr_loginStatus.value = KRLoginProgressStatus.kr_forgetPsdSendCode, theme: theme, ), SizedBox(width: 16.w), _buildHoverableTextButton( text: '点我注册', onTap: () { // 跳转到注册页面 controller.kr_loginStatus.value = KRLoginProgressStatus.kr_registerSendCode; }, theme: theme, ), ], ), ); } // 其他状态保持原有逻辑 return SizedBox( height: 24.w, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ GestureDetector( onTap: () => controller.kr_loginStatus.value = KRLoginProgressStatus.kr_forgetPsdSendCode, behavior: HitTestBehavior.opaque, child: Text( 'login.forgotPassword'.tr, style: theme.textTheme.bodySmall?.copyWith( fontSize: 13.sp, color: const Color(0xFF666666), fontWeight: FontWeight.w500, fontFamily: 'AlibabaPuHuiTi-Medium', ), ), ), GestureDetector( onTap: () { controller.kr_loginStatus.value = controller.kr_loginStatus.value == KRLoginProgressStatus.kr_loginByPsd ? KRLoginProgressStatus.kr_loginByCode : KRLoginProgressStatus.kr_loginByPsd; }, behavior: HitTestBehavior.opaque, child: Text( controller.kr_loginStatus.value == KRLoginProgressStatus.kr_loginByPsd ? 'login.codeLogin'.tr : "login.passwordLogin".tr, style: theme.textTheme.bodySmall?.copyWith( fontSize: 13.sp, color: const Color(0xFF666666), fontWeight: FontWeight.w500, fontFamily: 'AlibabaPuHuiTi-Medium', ), ), ), ], ), ); } /// 构建下一步按钮 Widget _buildNextButton(String text, ThemeData theme) { return TextButton( onPressed: () { switch (controller.kr_loginStatus.value) { case KRLoginProgressStatus.kr_check: controller.kr_check(); break; case KRLoginProgressStatus.kr_loginByCode: case KRLoginProgressStatus.kr_loginByPsd: controller.kr_login(); break; case KRLoginProgressStatus.kr_registerSendCode: // 直接调用注册,不再需要多步骤 controller.kr_register(); break; case KRLoginProgressStatus.kr_registerSetPsd: controller.kr_register(); break; case KRLoginProgressStatus.kr_forgetPsdSendCode: controller.kr_checkCode(); break; case KRLoginProgressStatus.kr_forgetPsdSetPsd: controller.kr_setNewPsdByForgetPsd(); break; } }, style: TextButton.styleFrom( padding: EdgeInsets.zero, minimumSize: Size.zero, tapTargetSize: MaterialTapTargetSize.shrinkWrap, ), child: Container( width: double.infinity, height: 52.w, decoration: BoxDecoration( color: const Color(0xFF1797FF), borderRadius: BorderRadius.circular(12.r), boxShadow: [ BoxShadow( color: const Color(0xFF1797FF).withOpacity(0.3), blurRadius: 8.r, offset: Offset(0, 4.w), ), ], ), alignment: Alignment.center, child: Text( text, style: TextStyle( fontSize: 17.sp, color: Colors.white, fontWeight: FontWeight.w600, letterSpacing: 0.5, fontFamily: 'AlibabaPuHuiTi-Medium', ), ), ), ); } /// Widget _buildIcon(String name) { return KrLocalImage( imageName: name, width: 20.w, height: 20.w, ); } /// 修改区域选择器样式 Widget _buildChoiceCode(ThemeData theme) { return GestureDetector( onTap: () { KRSearchAreaView.show((KRAreaCodeItem selectedArea, int index) { controller.kr_cutSeleteCodeIndex.value = index; }); }, child: Container( padding: EdgeInsets.symmetric(horizontal: 12.w), child: Row( mainAxisSize: MainAxisSize.min, children: [ Obx(() => Text( controller.kr_areaCodeList[controller.kr_cutSeleteCodeIndex.value].kr_icon, style: theme.textTheme.bodyMedium?.copyWith( fontSize: 28.sp, fontFamily: 'AlibabaPuHuiTi-Regular', fontWeight: FontWeight.w400, height: 1.40, ), )), SizedBox(width: 8.w), Obx(() => Text( '+${controller.kr_areaCodeList[controller.kr_cutSeleteCodeIndex.value].kr_dialCode}', style: theme.textTheme.bodyMedium?.copyWith( fontSize: 15.sp, fontFamily: 'AlibabaPuHuiTi-Regular', fontWeight: FontWeight.w400, height: 1.40, ), )), SizedBox(width: 4.w), Icon( Icons.keyboard_arrow_down, color: theme.textTheme.bodyMedium?.color, size: 20.w, ), ], ), ), ); } /// 通用文本样式 TextStyle _commonTextStyle({ required double fontSize, FontWeight fontWeight = FontWeight.w400, Color? color, }) { return KrAppTextStyle( fontSize: fontSize, fontWeight: fontWeight, color: color, ); } /// 构建标题文本 Widget _buildHeaderText(String text, ThemeData theme) { return Text( text, style: theme.textTheme.headlineMedium?.copyWith( fontSize: 24.sp, fontFamily: 'AlibabaPuHuiTi-Bold', color: theme.textTheme.bodyMedium?.color, height: 1.3, ), ); } Widget _buildInviteCodeInput(ThemeData theme) { return Container( width: double.infinity, padding: EdgeInsets.symmetric(horizontal: 12.w), height: 52.w, decoration: ShapeDecoration( color: theme.cardColor, shape: RoundedRectangleBorder( side: BorderSide(width: 0.5, color: const Color(0xFFD2D2D2)), borderRadius: BorderRadius.circular(10.r), ), ), child: Row( children: [ _buildIcon("login_psd"), SizedBox(width: 8.w), Expanded( child: TextField( controller: controller.inviteCodeController, decoration: InputDecoration( hintText: AppTranslations.kr_login.enterInviteCode, hintStyle: theme.textTheme.bodySmall?.copyWith( fontSize: 14.sp, fontFamily: 'AlibabaPuHuiTi-Regular', ), border: InputBorder.none, ), style: theme.textTheme.bodyMedium?.copyWith( fontSize: 14.sp, fontFamily: 'AlibabaPuHuiTi-Regular', ), ), ), Obx(() => Visibility( visible: controller.kr_inviteCodeHasText.value, child: GestureDetector( onTap: () { controller.inviteCodeController.clear(); }, child: Container( height: double.infinity, padding: EdgeInsets.only(left: 5.w, right: 5.w), child: _buildIcon("login_close"), ), ), )), ], ), ); } Widget _buildBackButton(ThemeData theme) { return GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { print('👆 返回按钮被点击了!'); print('👆 当前登录状态: ${controller.kr_loginStatus.value}'); // 只有在初始登录状态(kr_check)时,返回按钮才返回主页 // 其他所有状态(包括注册页面)都返回到上一步 if (controller.kr_loginStatus.value == KRLoginProgressStatus.kr_check) { print('👆 调用 Get.back() 返回主页'); Get.back(); print('👆 Get.back() 调用完成'); } else { // 其他状态:返回到上一步(例如从注册页返回登录页) print('👆 调用 controller.kr_back() 返回上一步'); controller.kr_back(); } }, child: Padding( padding: EdgeInsets.fromLTRB(0, 20.w, 0, 0), child: Row( children: [ Icon( size: 16.w, Icons.arrow_back_ios_sharp, color: theme.textTheme.bodyMedium?.color, ), SizedBox(width: 4.w), Text( AppTranslations.kr_login.back, textAlign: TextAlign.center, style: theme.textTheme.bodySmall?.copyWith( fontSize: 14.sp, fontFamily: 'AlibabaPuHuiTi-Medium', fontWeight: FontWeight.w500, color: theme.textTheme.bodyMedium?.color, height: 1.60, ), ), ], ), ), ); } Widget _buildPhoneInput(BuildContext context, ThemeData theme) { return Container( height: 52.w, padding: EdgeInsets.symmetric(horizontal: 12.w), decoration: ShapeDecoration( color: theme.cardColor, shape: RoundedRectangleBorder( side: BorderSide(width: 0.5, color: const Color(0xFFD2D2D2)), borderRadius: BorderRadius.circular(10.r), ), ), child: Row( children: [ _buildIcon("login_account"), SizedBox(width: 8.w), Expanded( child: Focus( onFocusChange: (hasFocus) { if (hasFocus) { controller.kr_accountFocusNode.requestFocus(); } }, child: TextField( focusNode: controller.kr_accountFocusNode, controller: controller.accountController, keyboardType: TextInputType.phone, decoration: InputDecoration( hintText: 'login.enterEmailOrPhone'.tr, hintStyle: theme.textTheme.bodySmall?.copyWith( fontSize: 14.sp, fontFamily: 'AlibabaPuHuiTi-Regular', ), border: InputBorder.none, ), style: theme.textTheme.bodyMedium?.copyWith( fontSize: 14.sp, fontFamily: 'AlibabaPuHuiTi-Regular', ), ), ), ), _buildClearButton(), ], ), ); } Widget _buildClearButton() { return Obx(() => Visibility( visible: controller.kr_accountHasText.value, child: GestureDetector( onTap: () { controller.accountController.clear(); }, child: Container( height: double.infinity, padding: EdgeInsets.only(left: 5.w, right: 5.w), child: _buildIcon("login_close"), ), ), )); } Widget _buildAreaSelector(ThemeData theme) { return GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { KRSearchAreaView.show((KRAreaCodeItem selectedArea, int index) { controller.kr_cutSeleteCodeIndex.value = index; }); }, child: Container( height: 52.w, decoration: ShapeDecoration( color: theme.cardColor, shape: RoundedRectangleBorder( side: BorderSide(width: 0.5, color: const Color(0xFFD2D2D2)), borderRadius: BorderRadius.circular(10.r), ), ), child: _buildChoiceCode(theme), ), ); } /// 构建可悬停的文本按钮 Widget _buildHoverableTextButton({ required String text, required VoidCallback onTap, required ThemeData theme, }) { return MouseRegion( cursor: SystemMouseCursors.click, child: StatefulBuilder( builder: (context, setState) { bool isHovering = false; return MouseRegion( onEnter: (_) => setState(() => isHovering = true), onExit: (_) => setState(() => isHovering = false), child: GestureDetector( onTap: onTap, behavior: HitTestBehavior.opaque, child: Text( text, style: theme.textTheme.bodySmall?.copyWith( fontSize: 13.sp, color: isHovering ? const Color(0xFF1796FF) : const Color(0xFF666666), fontWeight: FontWeight.w500, fontFamily: 'AlibabaPuHuiTi-Medium', ), ), ), ); }, ), ); } /// ========== 注册页面相关组件 ========== /// 构建注册页面的邮箱输入框 Widget _buildRegistrationEmailInput(ThemeData theme) { return Container( width: double.infinity, height: 52.w, decoration: ShapeDecoration( color: theme.cardColor, shape: RoundedRectangleBorder( side: BorderSide(width: 0.5, color: const Color(0xFFD2D2D2)), borderRadius: BorderRadius.circular(10.r), ), ), child: Row( children: [ Padding( padding: EdgeInsets.only(left: 12.w), child: _buildIcon("login_account"), ), SizedBox(width: 8.w), Expanded( child: TextField( controller: controller.accountController, keyboardType: TextInputType.emailAddress, decoration: InputDecoration( hintText: 'login.enterEmail'.tr, hintStyle: theme.textTheme.bodySmall?.copyWith( fontSize: 14.sp, fontFamily: 'AlibabaPuHuiTi-Regular', ), border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16.w), ), style: theme.textTheme.bodyMedium?.copyWith( fontSize: 14.sp, fontFamily: 'AlibabaPuHuiTi-Regular', ), ), ), Obx(() => Visibility( visible: controller.kr_accountHasText.value, child: GestureDetector( onTap: () => controller.accountController.clear(), child: Container( height: double.infinity, padding: EdgeInsets.symmetric(horizontal: 8.w), child: _buildIcon("login_close"), ), ), )), ], ), ); } /// 构建注册页面的验证码输入框(包含间距) Widget _buildRegistrationCodeInputWithSpacing(ThemeData theme) { // 从站点配置服务获取验证配置(站点配置不是响应式的,不需要 Obx) final siteConfig = KRSiteConfigService(); final needVerification = siteConfig.isEmailVerificationEnabled() || siteConfig.isRegisterVerificationEnabled(); // 如果不需要验证码,返回空容器 if (!needVerification) { return SizedBox.shrink(); } // 显示验证码输入框和底部间距 return Column( mainAxisSize: MainAxisSize.min, children: [ _buildRegistrationCodeInput(theme), SizedBox(height: 8.w), ], ); } /// 构建注册页面的验证码输入框(根据站点配置显示) Widget _buildRegistrationCodeInput(ThemeData theme) { // 从站点配置服务获取验证配置 final siteConfig = KRSiteConfigService(); final needVerification = siteConfig.isEmailVerificationEnabled() || siteConfig.isRegisterVerificationEnabled(); // 如果不需要验证码,返回空容器 if (!needVerification) { return SizedBox.shrink(); } // 显示验证码输入框 return Container( width: double.infinity, height: 52.w, decoration: ShapeDecoration( color: theme.cardColor, shape: RoundedRectangleBorder( side: BorderSide(width: 0.5, color: const Color(0xFFD2D2D2)), borderRadius: BorderRadius.circular(10.r), ), ), child: Row( children: [ Padding( padding: EdgeInsets.only(left: 12.w), child: _buildIcon("login_code"), ), SizedBox(width: 8.w), Expanded( child: TextField( controller: controller.codeController, keyboardType: TextInputType.number, decoration: InputDecoration( hintText: 'login.enterCode'.tr, hintStyle: theme.textTheme.bodySmall?.copyWith( fontSize: 14.sp, fontFamily: 'AlibabaPuHuiTi-Regular', ), border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16.w), ), style: theme.textTheme.bodyMedium?.copyWith( fontSize: 14.sp, fontFamily: 'AlibabaPuHuiTi-Regular', ), ), ), if (controller.kr_codeHasText.value) GestureDetector( onTap: () => controller.codeController.clear(), child: Container( height: double.infinity, padding: EdgeInsets.symmetric(horizontal: 8.w), child: _buildIcon("login_close"), ), ), _buildSendCodeButton(theme), ], ), ); } /// 构建注册页面的密码输入框 Widget _buildRegistrationPasswordInput(ThemeData theme) { return Container( width: double.infinity, height: 52.w, decoration: ShapeDecoration( color: theme.cardColor, shape: RoundedRectangleBorder( side: BorderSide(width: 0.5, color: const Color(0xFFD2D2D2)), borderRadius: BorderRadius.circular(10.r), ), ), child: Row( children: [ Padding( padding: EdgeInsets.only(left: 12.w), child: _buildIcon("login_psd"), ), SizedBox(width: 8.w), Expanded( child: Obx(() => TextField( controller: controller.psdController, obscureText: controller.kr_obscureText.value, keyboardType: TextInputType.text, decoration: InputDecoration( hintText: 'login.enterPassword'.tr, hintStyle: theme.textTheme.bodySmall?.copyWith( fontSize: 14.sp, fontFamily: 'AlibabaPuHuiTi-Regular', ), border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16.w), suffixIcon: controller.kr_psdHasText.value ? IconButton( icon: Icon( controller.kr_obscureText.value ? Icons.visibility_off : Icons.visibility, color: const Color(0xFF999999), ), onPressed: () { controller.kr_obscureText.value = !controller.kr_obscureText.value; }, ) : null, ), style: theme.textTheme.bodyMedium?.copyWith( fontSize: 14.sp, fontFamily: 'AlibabaPuHuiTi-Regular', ), )), ), ], ), ); } /// 构建注册页面的确认密码输入框 Widget _buildRegistrationConfirmPasswordInput(ThemeData theme) { return Container( width: double.infinity, height: 52.w, decoration: ShapeDecoration( color: theme.cardColor, shape: RoundedRectangleBorder( side: BorderSide(width: 0.5, color: const Color(0xFFD2D2D2)), borderRadius: BorderRadius.circular(10.r), ), ), child: Row( children: [ Padding( padding: EdgeInsets.only(left: 12.w), child: _buildIcon("login_psd"), ), SizedBox(width: 8.w), Expanded( child: Obx(() => TextField( controller: controller.agPsdController, obscureText: controller.kr_obscureText.value, keyboardType: TextInputType.text, decoration: InputDecoration( hintText: 'login.reenterPassword'.tr, hintStyle: theme.textTheme.bodySmall?.copyWith( fontSize: 14.sp, fontFamily: 'AlibabaPuHuiTi-Regular', ), border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16.w), suffixIcon: controller.kr_agPsdHasText.value ? IconButton( icon: Icon( controller.kr_obscureText.value ? Icons.visibility_off : Icons.visibility, color: const Color(0xFF999999), ), onPressed: () { controller.kr_obscureText.value = !controller.kr_obscureText.value; }, ) : null, ), style: theme.textTheme.bodyMedium?.copyWith( fontSize: 14.sp, fontFamily: 'AlibabaPuHuiTi-Regular', ), )), ), ], ), ); } }