完善游客模式登录 并且新增设备管理

This commit is contained in:
2025-10-17 21:11:40 +08:00
parent 2333ad52a9
commit de9cf751f3
46 changed files with 4590 additions and 543 deletions
+435 -46
View File
@@ -14,6 +14,7 @@ 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<KRLoginController> {
const KRLoginView({super.key});
@@ -21,15 +22,16 @@ class KRLoginView extends GetView<KRLoginController> {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return GestureDetector(
onTap: () {
if (!FocusScope.of(context).hasPrimaryFocus) {
FocusScope.of(context).unfocus();
}
_hideDropdown();
},
child: Container(
color: theme.scaffoldBackgroundColor,
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(() {
@@ -58,16 +60,18 @@ class KRLoginView extends GetView<KRLoginController> {
}
Widget _buildCheckView(BuildContext context) {
// 构建检查视图的代码
// 构建登录视图 - 直接显示邮箱和密码输入框
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Obx(() => Visibility(
visible: controller.kr_loginStatus.value != KRLoginProgressStatus.kr_check,
child: _buildBackButton(Theme.of(context)),
)),
// 总是显示返回按钮,包括 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()),
@@ -75,6 +79,63 @@ class KRLoginView extends GetView<KRLoginController> {
);
}
/// 构建密码输入框(专用于 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) {
@@ -98,10 +159,7 @@ class KRLoginView extends GetView<KRLoginController> {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Obx(() => Visibility(
visible: controller.kr_loginStatus.value != KRLoginProgressStatus.kr_check,
child: _buildBackButton(Theme.of(context)),
)),
_buildBackButton(Theme.of(context)),
_buildHeaderSection(Theme.of(context)),
_buildInputSection(context, Theme.of(context)),
_buildDynamicContent(Theme.of(context)),
@@ -116,10 +174,7 @@ class KRLoginView extends GetView<KRLoginController> {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Obx(() => Visibility(
visible: controller.kr_loginStatus.value != KRLoginProgressStatus.kr_check,
child: _buildBackButton(Theme.of(context)),
)),
_buildBackButton(Theme.of(context)),
_buildHeaderSection(Theme.of(context)),
_buildInputSection(context, Theme.of(context)),
_buildDynamicContent(Theme.of(context)),
@@ -130,18 +185,29 @@ class KRLoginView extends GetView<KRLoginController> {
}
Widget _buildRegisterSendCodeView(BuildContext context) {
// 构建注册发送验证码视图的代码
// 构建单页注册视图 - 所有字段显示在一个页面
final theme = Theme.of(context);
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Obx(() => Visibility(
visible: controller.kr_loginStatus.value != KRLoginProgressStatus.kr_check,
child: _buildBackButton(Theme.of(context)),
)),
_buildHeaderSection(Theme.of(context)),
_buildInputSection(context, Theme.of(context)),
_buildDynamicContent(Theme.of(context)),
_buildNextButton(controller.kr_getNextBtnText(), Theme.of(context)),
_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()),
],
);
@@ -152,10 +218,7 @@ class KRLoginView extends GetView<KRLoginController> {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Obx(() => Visibility(
visible: controller.kr_loginStatus.value != KRLoginProgressStatus.kr_check,
child: _buildBackButton(Theme.of(context)),
)),
_buildBackButton(Theme.of(context)),
_buildHeaderSection(Theme.of(context)),
_buildInputSection(context, Theme.of(context)),
Column(
@@ -178,10 +241,7 @@ class KRLoginView extends GetView<KRLoginController> {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Obx(() => Visibility(
visible: controller.kr_loginStatus.value != KRLoginProgressStatus.kr_check,
child: _buildBackButton(Theme.of(context)),
)),
_buildBackButton(Theme.of(context)),
_buildHeaderSection(Theme.of(context)),
_buildInputSection(context, Theme.of(context)),
_buildDynamicContent(Theme.of(context)),
@@ -196,10 +256,7 @@ class KRLoginView extends GetView<KRLoginController> {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Obx(() => Visibility(
visible: controller.kr_loginStatus.value != KRLoginProgressStatus.kr_check,
child: _buildBackButton(Theme.of(context)),
)),
_buildBackButton(Theme.of(context)),
_buildHeaderSection(Theme.of(context)),
_buildInputSection(context, Theme.of(context)),
if (controller.kr_loginStatus.value == KRLoginProgressStatus.kr_forgetPsdSetPsd)
@@ -670,7 +727,8 @@ class KRLoginView extends GetView<KRLoginController> {
Widget? content;
switch (controller.kr_loginStatus.value) {
case KRLoginProgressStatus.kr_check:
content = _buildAgreementText(theme);
// 在登录状态显示"忘记密码"和切换按钮
content = _buildLoginBtns(theme);
case KRLoginProgressStatus.kr_loginByCode:
case KRLoginProgressStatus.kr_loginByPsd:
content = _buildLoginBtns(theme);
@@ -744,6 +802,34 @@ class KRLoginView extends GetView<KRLoginController> {
/// 构建登录按钮行
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(
@@ -802,7 +888,8 @@ class KRLoginView extends GetView<KRLoginController> {
controller.kr_login();
break;
case KRLoginProgressStatus.kr_registerSendCode:
controller.kr_checkCode();
// 直接调用注册,不再需要多步骤
controller.kr_register();
break;
case KRLoginProgressStatus.kr_registerSetPsd:
controller.kr_register();
@@ -981,8 +1068,21 @@ class KRLoginView extends GetView<KRLoginController> {
Widget _buildBackButton(ThemeData theme) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
controller.kr_back();
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),
@@ -1095,4 +1195,293 @@ class KRLoginView extends GetView<KRLoginController> {
),
);
}
/// 构建可悬停的文本按钮
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',
),
)),
),
],
),
);
}
}