diff --git a/lib/base/base_api_provider.dart b/lib/base/base_api_provider.dart new file mode 100644 index 0000000..7c6d7df --- /dev/null +++ b/lib/base/base_api_provider.dart @@ -0,0 +1,20 @@ +import 'package:get/get.dart'; +import 'package:starwork_flutter/flavors.dart'; + +class BaseApiProvider extends GetConnect { + @override + void onInit() { + httpClient.baseUrl = F.apiHost; + + // 请求拦截 + httpClient.addRequestModifier((request) { + request.headers['Authorization'] = '12345678'; + return request; + }); + + // 响应拦截 + httpClient.addResponseModifier((request, response) { + return response; + }); + } +} diff --git a/lib/common/enums/login_type.dart b/lib/common/enums/login_type.dart new file mode 100644 index 0000000..5255818 --- /dev/null +++ b/lib/common/enums/login_type.dart @@ -0,0 +1,5 @@ +enum LoginType { + phoneCode, + phonePassword; + +} \ No newline at end of file diff --git a/lib/flavors.dart b/lib/flavors.dart index b29aa5a..b3abb83 100644 --- a/lib/flavors.dart +++ b/lib/flavors.dart @@ -29,4 +29,21 @@ class F { } } + static String get apiHost { + switch (appFlavor) { + case Flavor.skyDev: + return 'https://loacl.work.star-lock.cn/api'; + case Flavor.skyPre: + return 'https://loacl.work.star-lock.cn/api'; + case Flavor.skyRelease: + return 'https://loacl.work.star-lock.cn/api'; + case Flavor.xhjDev: + return 'https://loacl.work.star-lock.cn/api'; + case Flavor.xhjPre: + return 'https://loacl.work.star-lock.cn/api'; + case Flavor.xhjRelease: + return 'https://loacl.work.star-lock.cn/api'; + } + } + } diff --git a/lib/pages/my_home_page.dart b/lib/pages/my_home_page.dart deleted file mode 100644 index c9f3fc5..0000000 --- a/lib/pages/my_home_page.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:flutter/material.dart'; -import '../flavors.dart'; - -class MyHomePage extends StatelessWidget { - const MyHomePage({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: Text(F.title)), - body: Center(child: Text('Hello ${F.title}')), - ); - } -} diff --git a/lib/routes/app_pages.dart b/lib/routes/app_pages.dart index f0b16c0..180776b 100644 --- a/lib/routes/app_pages.dart +++ b/lib/routes/app_pages.dart @@ -2,6 +2,12 @@ import 'package:get/get.dart'; import 'package:starwork_flutter/routes/app_routes.dart'; import 'package:starwork_flutter/views/home/home_binding.dart'; import 'package:starwork_flutter/views/home/home_view.dart'; +import 'package:starwork_flutter/views/login/forgotPassword/forgot_password_binding.dart'; +import 'package:starwork_flutter/views/login/forgotPassword/forgot_password_view.dart'; +import 'package:starwork_flutter/views/login/forgotPassword/setNewPassword/set_new_password_binding.dart'; +import 'package:starwork_flutter/views/login/forgotPassword/setNewPassword/set_new_password_view.dart'; +import 'package:starwork_flutter/views/login/inputVerificationCode/input_verification_code_binding.dart'; +import 'package:starwork_flutter/views/login/inputVerificationCode/input_verification_code_view.dart'; import 'package:starwork_flutter/views/login/login_binding.dart'; import 'package:starwork_flutter/views/login/login_view.dart'; import 'package:starwork_flutter/views/main/main_binding.dart'; @@ -39,5 +45,20 @@ class AppPages { page: () => const MineView(), binding: MineBinding(), ), + GetPage( + name: AppRoutes.inputVerificationCode, + page: () => const InputVerificationCodeView(), + binding: InputVerificationCodeBinding(), + ), + GetPage( + name: AppRoutes.forgotPassword, + page: () => const ForgotPasswordView(), + binding: ForgotPasswordBinding(), + ), + GetPage( + name: AppRoutes.setNewPassword, + page: () => const SetNewPasswordView(), + binding: SetNewPasswordBinding(), + ), ]; } diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 06efc4e..c50736b 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -5,4 +5,7 @@ class AppRoutes{ static const String main = '/main'; static const String messages = '/messages'; static const String mine = '/mine'; + static const String inputVerificationCode = '/inputVerificationCode'; + static const String forgotPassword = '/forgotPassword'; + static const String setNewPassword = '/setNewPassword'; } \ No newline at end of file diff --git a/lib/views/login/forgotPassword/forgot_password_binding.dart b/lib/views/login/forgotPassword/forgot_password_binding.dart new file mode 100644 index 0000000..81d8467 --- /dev/null +++ b/lib/views/login/forgotPassword/forgot_password_binding.dart @@ -0,0 +1,11 @@ +import 'package:get/get.dart'; +import 'package:starwork_flutter/views/login/forgotPassword/forgot_password_controller.dart'; + +class ForgotPasswordBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut( + () => ForgotPasswordController(), + ); + } +} diff --git a/lib/views/login/forgotPassword/forgot_password_controller.dart b/lib/views/login/forgotPassword/forgot_password_controller.dart new file mode 100644 index 0000000..5b6a552 --- /dev/null +++ b/lib/views/login/forgotPassword/forgot_password_controller.dart @@ -0,0 +1,20 @@ +import 'package:flutter/widgets.dart'; +import 'package:get/get.dart'; +import 'package:starwork_flutter/base/base_controller.dart'; +import 'package:starwork_flutter/routes/app_routes.dart'; +import 'package:starwork_flutter/views/login/login_controller.dart'; + +class ForgotPasswordController extends BaseController { + // 在任意位置获取已注入的实例 + final loginController = Get.find(); + + // 获取手机验证码 + void requestPhoneCode() { + Get.toNamed( + AppRoutes.inputVerificationCode, + parameters: { + 'phone': loginController.phoneController.text.trim(), + }, + ); + } +} diff --git a/lib/views/login/forgotPassword/forgot_password_view.dart b/lib/views/login/forgotPassword/forgot_password_view.dart new file mode 100644 index 0000000..ba2592e --- /dev/null +++ b/lib/views/login/forgotPassword/forgot_password_view.dart @@ -0,0 +1,102 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/src/widgets/framework.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import 'package:starwork_flutter/views/login/forgotPassword/forgot_password_controller.dart'; + +class ForgotPasswordView extends GetView { + const ForgotPasswordView({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(), + body: Padding( + padding: EdgeInsets.symmetric(horizontal: 40.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '请输入手机号'.tr, + style: TextStyle( + fontSize: 22.sp, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + SizedBox( + height: 20.h, + ), + TextField( + controller: controller.loginController.phoneController, + keyboardType: TextInputType.phone, + textInputAction: TextInputAction.done, + maxLength: 11, + decoration: InputDecoration( + counterText: '', + hintText: '请输入手机号码'.tr, + border: const UnderlineInputBorder(), + suffixIcon: controller.loginController.phoneController.text.isNotEmpty + ? IconButton( + icon: const Icon(Icons.clear, color: Colors.grey), + onPressed: () { + controller.loginController.phoneController.clear(); // 清空输入 + }, + ) + : null, + // 如果没有内容,就不显示清除按钮, + // 获取焦点时的边框 + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: controller.loginController.isFormValid.value + ? Colors.blue + : Colors.blue.withOpacity(0.5), + ), // + ), + ), + ), + SizedBox( + height: 48.h, + ), + Obx( + () => ElevatedButton( + onPressed: controller.loginController.isFormValid.value + ? controller.requestPhoneCode + : null, + style: ButtonStyle( + minimumSize: + MaterialStateProperty.all(Size(double.infinity, 44.h)), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.r), + ), + ), + backgroundColor: MaterialStateProperty.all( + controller.loginController.isFormValid.value + ? Colors.blue + : Colors.blue.withOpacity(0.5), + ), + ), + child: Text( + '获取验证码'.tr, + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/views/login/forgotPassword/setNewPassword/set_new_password_binding.dart b/lib/views/login/forgotPassword/setNewPassword/set_new_password_binding.dart new file mode 100644 index 0000000..ad91e22 --- /dev/null +++ b/lib/views/login/forgotPassword/setNewPassword/set_new_password_binding.dart @@ -0,0 +1,11 @@ +import 'package:get/get.dart'; +import 'package:starwork_flutter/views/login/forgotPassword/setNewPassword/set_new_password_controller.dart'; + +class SetNewPasswordBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut( + () => SetNewPasswordController(), + ); + } +} diff --git a/lib/views/login/forgotPassword/setNewPassword/set_new_password_controller.dart b/lib/views/login/forgotPassword/setNewPassword/set_new_password_controller.dart new file mode 100644 index 0000000..4303240 --- /dev/null +++ b/lib/views/login/forgotPassword/setNewPassword/set_new_password_controller.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:starwork_flutter/base/base_controller.dart'; + +class SetNewPasswordController extends BaseController { + TextEditingController passwordController = TextEditingController(); + TextEditingController newPasswordController = TextEditingController(); + final isPasswordVisible = true.obs; + final isNewPasswordVisible = true.obs; + final isFormValid = false.obs; + final isNewFormValid = false.obs; + final isAllFormValid = false.obs; + + void setNewPassword(){ + + } +} \ No newline at end of file diff --git a/lib/views/login/forgotPassword/setNewPassword/set_new_password_view.dart b/lib/views/login/forgotPassword/setNewPassword/set_new_password_view.dart new file mode 100644 index 0000000..e9229d3 --- /dev/null +++ b/lib/views/login/forgotPassword/setNewPassword/set_new_password_view.dart @@ -0,0 +1,239 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/src/widgets/framework.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import 'package:starwork_flutter/views/login/forgotPassword/setNewPassword/set_new_password_controller.dart'; + +class SetNewPasswordView extends GetView { + const SetNewPasswordView({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(), + body: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 40.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '设置新密码'.tr, + style: TextStyle( + fontSize: 22.sp, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + SizedBox( + height: 20.h, + ), + TextField( + controller: controller.passwordController, + keyboardType: TextInputType.visiblePassword, + textInputAction: TextInputAction.done, + obscureText: controller.isPasswordVisible.value, + decoration: InputDecoration( + counterText: '', + hintText: '请输入新密码'.tr, + border: const UnderlineInputBorder(), + suffixIcon: IconButton( + icon: Icon( + controller.isPasswordVisible.value + ? Icons.visibility + : Icons.visibility_off, + ), + onPressed: () => controller.isPasswordVisible.value = + !controller.isPasswordVisible.value, + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: controller.isFormValid.value + ? Colors.blue + : Colors.blue.withOpacity(0.5), + ), // + ), + ), + ), + SizedBox( + height: 20.h, + ), + TextField( + controller: controller.newPasswordController, + keyboardType: TextInputType.visiblePassword, + textInputAction: TextInputAction.done, + obscureText: controller.isNewPasswordVisible.value, + decoration: InputDecoration( + counterText: '', + hintText: '再次输入新密码'.tr, + border: const UnderlineInputBorder(), + suffixIcon: IconButton( + icon: Icon( + controller.isNewPasswordVisible.value + ? Icons.visibility + : Icons.visibility_off, + ), + onPressed: () => controller.isNewPasswordVisible.value = + !controller.isNewPasswordVisible.value, + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: controller.isNewFormValid.value + ? Colors.blue + : Colors.blue.withOpacity(0.5), + ), // + ), + ), + ), + SizedBox( + height: 20.h, + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 8.h), + decoration: BoxDecoration( + color: Colors.grey.shade200, + borderRadius: BorderRadius.all( + Radius.circular( + 14.r, + ), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Checkbox( + value: true, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4.r), + ), + activeColor: Colors.blue, + onChanged: (value) {}, + ), + Expanded( + child: Text( + '8-16位,由大写字母、小写字母、数字、特殊字符任意两种及以上组成', + style: TextStyle( + fontSize: 12.sp, + color: Colors.black45, + fontWeight: FontWeight.w500, + ), + ), + ) + ], + ), + Row( + children: [ + Checkbox( + value: true, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4.r), + ), + activeColor: Colors.blue, + onChanged: (value) {}, + ), + Expanded( + child: Text( + '连续相同的字符不能超过4个', + style: TextStyle( + fontSize: 12.sp, + color: Colors.black45, + fontWeight: FontWeight.w500, + ), + ), + ) + ], + ), + Row( + children: [ + Checkbox( + value: true, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4.r), + ), + activeColor: Colors.blue, + onChanged: (value) {}, + ), + Expanded( + child: Text( + '连续的数字不能超过4个', + style: TextStyle( + fontSize: 12.sp, + color: Colors.black45, + fontWeight: FontWeight.w500, + ), + ), + ) + ], + ), + Row( + children: [ + Checkbox( + value: true, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4.r), + ), + activeColor: Colors.blue, + onChanged: (value) {}, + ), + Expanded( + child: Text( + '不能使用安全性低的密码', + style: TextStyle( + fontSize: 12.sp, + color: Colors.black45, + fontWeight: FontWeight.w500, + ), + ), + ) + ], + ) + ], + ), + ), + SizedBox( + height: 48.h, + ), + ElevatedButton( + onPressed: controller.isAllFormValid.value + ? controller.setNewPassword + : null, + style: ButtonStyle( + minimumSize: + MaterialStateProperty.all(Size(double.infinity, 44.h)), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.r), + ), + ), + backgroundColor: MaterialStateProperty.all( + controller.isFormValid.value + ? Colors.blue + : Colors.blue.withOpacity(0.5), + ), + ), + child: Text( + '提交'.tr, + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/views/login/inputVerificationCode/input_verification_code_binding.dart b/lib/views/login/inputVerificationCode/input_verification_code_binding.dart new file mode 100644 index 0000000..f0db345 --- /dev/null +++ b/lib/views/login/inputVerificationCode/input_verification_code_binding.dart @@ -0,0 +1,11 @@ +import 'package:get/get.dart'; +import 'package:starwork_flutter/views/login/inputVerificationCode/input_verification_code_controller.dart'; + +class InputVerificationCodeBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut( + () => InputVerificationCodeController(), + ); + } +} diff --git a/lib/views/login/inputVerificationCode/input_verification_code_controller.dart b/lib/views/login/inputVerificationCode/input_verification_code_controller.dart new file mode 100644 index 0000000..eafed43 --- /dev/null +++ b/lib/views/login/inputVerificationCode/input_verification_code_controller.dart @@ -0,0 +1,79 @@ +import 'dart:async'; + +import 'package:get/get.dart'; +import 'package:starwork_flutter/base/base_controller.dart'; +import 'package:starwork_flutter/routes/app_routes.dart'; + +class InputVerificationCodeController extends BaseController { + var phone = ''.obs; + var previousRoute = ''.obs; + +// 是否正在倒计时 + var isCountingDown = false.obs; + +// 倒计时剩余秒数 + var countdownValue = 60.obs; + + @override + void onInit() { + super.onInit(); + + final params = Get.parameters; + String? phoneParam = params['phone']; + // 在目标页面获取来源路由 + previousRoute.value = Get.previousRoute; // 如 "/login" + + if (phoneParam != null && phoneParam.isNotEmpty) { + phone.value = maskMiddleFive(phoneParam); + // 开始倒计时(比如60秒) + startCountdown(); + } + } + + String maskMiddleFive(String phone) { + if (phone.length < 8) return phone; // 如果手机号少于8位,不处理(或根据需求返回原值/报错) + + // 假设手机号至少有 8 位,我们保留前 3 位,中间 5 位替换为 ****,显示后 3 位 + // 13812345678 → 138****678 + String firstPart = phone.substring(0, 3); // 前3位:138 + String middleReplaced = '*****'; // 中间5位替换为 **** + String lastPart = phone.substring(8); // 后3位:678 + + return '$firstPart$middleReplaced$lastPart'; // 拼接:138****678 + } + + /// 开始倒计时(比如60秒) + void startCountdown() { + if (isCountingDown.value) return; // 避免重复启动 + + isCountingDown.value = true; + countdownValue.value = 60; + + // 使用 Timer 实现倒计时 + Timer.periodic(const Duration(seconds: 1), (timer) { + if (countdownValue.value > 1) { + countdownValue.value--; + } else { + timer.cancel(); // 停止计时器 + isCountingDown.value = false; + countdownValue.value = 0; + } + }); + } + + void requestPhoneCodeLogin(String code) { + if (previousRoute.value.contains(AppRoutes.login)) { + _handleLogin(); + } else if (previousRoute.value.contains(AppRoutes.forgotPassword)) { + _handleForgotPassword(); + } + } + + void _handleLogin() { + Get.offAndToNamed(AppRoutes.main); + } + + void _handleForgotPassword() { + Get.toNamed(AppRoutes.setNewPassword); + } +} diff --git a/lib/views/login/inputVerificationCode/input_verification_code_view.dart b/lib/views/login/inputVerificationCode/input_verification_code_view.dart new file mode 100644 index 0000000..b5ef988 --- /dev/null +++ b/lib/views/login/inputVerificationCode/input_verification_code_view.dart @@ -0,0 +1,121 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import 'package:pinput/pinput.dart'; +import 'package:starwork_flutter/views/login/inputVerificationCode/input_verification_code_controller.dart'; + +class InputVerificationCodeView + extends GetView { + const InputVerificationCodeView({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(), + body: SafeArea( + child: Container( + width: 1.sw, + padding: EdgeInsets.symmetric( + horizontal: 40.w, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "请输入验证码", + style: TextStyle( + fontSize: 22.sp, + fontWeight: FontWeight.w500, + ), + ), + Text( + '${'已发送验证码至'.tr}${controller.phone.value}', + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w500, + color: Colors.grey, + ), + ), + Obx( + () => _buildRequestCodeButton(), + ), + SizedBox( + height: 32.h, + ), + // Pinput 验证码输入框 + Pinput( + length: 6, + obscureText: false, + defaultPinTheme: PinTheme( + width: 50.w, + height: 50.w, + textStyle: TextStyle(fontSize: 20.sp, color: Colors.black), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: Colors.grey), + borderRadius: BorderRadius.circular(8.r), + ), + ), + validator: (value) { + if (value!.isEmpty) return '请输入验证码'; + if (value.length != 6) return '验证码需6位'; + return null; + }, + onCompleted: (pin) { + controller.requestPhoneCodeLogin(pin); + }, + ), + ], + ), + ), + ), + ); + } + + _buildRequestCodeButton() { + if (controller.isCountingDown.value) { + return RichText( + textAlign: TextAlign.center, + text: TextSpan( + children: [ + TextSpan( + text: '如未收到,请'.tr, + style: TextStyle( + fontSize: 14.sp, + color: Colors.grey, + ), + ), + TextSpan( + text: '${controller.countdownValue.value}s'.tr, + style: TextStyle( + fontSize: 14.sp, + color: Colors.red, + ), + ), + TextSpan( + text: '后重新获取'.tr, + style: TextStyle( + fontSize: 14.sp, + color: Colors.grey, + ), + ), + ], + ), + ); + } else { + return GestureDetector( + onTap: () { + controller.startCountdown(); + }, + child: Text( + '重新获取验证码'.tr, + style: TextStyle( + fontSize: 14.sp, + color: Colors.blue, + ), + ), + ); + } + } +} diff --git a/lib/views/login/login_controller.dart b/lib/views/login/login_controller.dart index bd64029..09bc957 100644 --- a/lib/views/login/login_controller.dart +++ b/lib/views/login/login_controller.dart @@ -1,20 +1,31 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:get/get.dart'; import 'package:starwork_flutter/base/base_controller.dart'; +import 'package:starwork_flutter/common/enums/login_type.dart'; +import 'package:starwork_flutter/routes/app_pages.dart'; +import 'package:starwork_flutter/routes/app_routes.dart'; class LoginController extends BaseController { int phoneNumberSize = 11; - TextEditingController phoneController = TextEditingController(); + TextEditingController passwordController = TextEditingController(); + final isFormValid = false.obs; + final isPasswordVisible = true.obs; + final isPrivacyAgreementValid = 0.obs; + final loginType = LoginType.phoneCode.obs; @override void onInit() { super.onInit(); // 监听输入变化 phoneController.addListener(_validateForm); + + phoneController.text = '18269109817'; } @override @@ -31,7 +42,158 @@ class LoginController extends BaseController { // 获取手机验证码 void requestPhoneCode() { - debugPrint("获取手机验证码"); - showToast("获取手机验证码"); + if (isPrivacyAgreementValid.value != 1) { + _showCustomDialog( + title: '欢迎使用星勤'.tr, + content: '1', + onConfirm: () { + isPrivacyAgreementValid.value = 1; + Get.toNamed( + AppRoutes.inputVerificationCode, + parameters: { + 'phone': phoneController.text.trim(), + + }, + ); + }, + ); + } else { + Get.toNamed( + AppRoutes.inputVerificationCode, + parameters: { + 'phone': phoneController.text.trim(), + + }, + ); + } + } + + void _showCustomDialog({ + required String title, + required String content, + required VoidCallback onConfirm, + }) { + Get.dialog( + Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14.r), // 圆角 + ), + backgroundColor: Colors.white, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: EdgeInsets.only(top: 22.h), + child: Text( + title, + style: TextStyle( + fontSize: 18.sp, + fontWeight: FontWeight.bold, + ), + ), + ), + Padding( + padding: EdgeInsets.all(22.w), + child: RichText( + text: TextSpan( + children: [ + TextSpan( + text: '请你阅读并同意'.tr, + style: TextStyle( + fontSize: 12.sp, + color: Colors.grey, + ), + ), + TextSpan( + text: '《用户协议》'.tr, + style: TextStyle( + fontSize: 12.sp, + color: Colors.blue, + ), + ), + TextSpan( + text: '和'.tr, + style: TextStyle( + fontSize: 12.sp, + color: Colors.grey, + ), + ), + TextSpan( + text: '《隐私政策》'.tr, + style: TextStyle( + fontSize: 12.sp, + color: Colors.blue, + ), + ), + ], + ), + ), + ), + SizedBox(height: 20.h), + Container( + decoration: const BoxDecoration( + border: Border( + top: BorderSide( + color: Colors.grey, // 右侧边框颜色 + width: 0.5, // 右侧边框宽度 + ), + ), + ), + child: Row( + children: [ + Expanded( + // 左侧文本占一半 + child: GestureDetector( + onTap: () { + Get.back(); + }, + child: Container( + alignment: Alignment.center, + height: 42.h, + decoration: const BoxDecoration( + border: Border( + right: BorderSide( + color: Colors.grey, // 右侧边框颜色 + width: 0.5, // 右侧边框宽度 + ), + ), + ), + child: Text( + '取消'.tr, + textAlign: TextAlign.center, + style: TextStyle(fontSize: 14.sp, color: Colors.grey), + ), + ), + ), + ), + Expanded( + // 右侧文本占一半 + child: GestureDetector( + onTap: () { + Get.back(); + onConfirm(); + }, + child: Container( + alignment: Alignment.center, + height: 42.h, + child: Text( + '同意并继续'.tr, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14.sp, + color: Colors.blue, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ), + ], + ), + ) + ], + ), + ), + ); } } diff --git a/lib/views/login/login_view.dart b/lib/views/login/login_view.dart index 7df27a5..0b32301 100644 --- a/lib/views/login/login_view.dart +++ b/lib/views/login/login_view.dart @@ -1,6 +1,10 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; +import 'package:starwork_flutter/common/enums/login_type.dart'; +import 'package:starwork_flutter/routes/app_routes.dart'; import 'login_controller.dart'; @@ -15,6 +19,7 @@ class LoginView extends GetView { FocusScope.of(context).unfocus(); }, child: Scaffold( + resizeToAvoidBottomInset: true, body: SafeArea( child: SingleChildScrollView( child: _buildBody(), @@ -25,21 +30,28 @@ class LoginView extends GetView { } Widget _buildBody() { + return SizedBox( + height: 0.9.sh, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + _buildTop(), + _buildPrivacyPolicy(), + ], + ), + ); + } + + _buildTop() { return Container( - height: 1.sh, - margin: EdgeInsets.symmetric(vertical: 48.h), - padding: EdgeInsets.symmetric(horizontal: 32.w), + margin: EdgeInsets.symmetric(horizontal: 40.w, vertical: 40.h), child: Column( children: [ _buildTitle(), - SizedBox( - height: 32.h, - ), + SizedBox(height: 20.h), _buildPhoneInputAndLoginButton(), - SizedBox( - height: 32.h, - ), - _buildPrivacyPolicy(), + SizedBox(height: 20.h), ], ), ); @@ -58,9 +70,6 @@ class LoginView extends GetView { fontWeight: FontWeight.w500, ), ), - SizedBox( - height: 4.h, - ), Text( '未注册手机号验证后将自动创建账号'.tr, style: TextStyle( @@ -87,6 +96,15 @@ class LoginView extends GetView { counterText: '', hintText: '请输入手机号码'.tr, border: const UnderlineInputBorder(), + suffixIcon: controller.phoneController.text.isNotEmpty + ? IconButton( + icon: const Icon(Icons.clear, color: Colors.grey), + onPressed: () { + controller.phoneController.clear(); // 清空输入 + }, + ) + : null, + // 如果没有内容,就不显示清除按钮, // 获取焦点时的边框 focusedBorder: UnderlineInputBorder( borderSide: BorderSide( @@ -94,12 +112,47 @@ class LoginView extends GetView { ? Colors.blue : Colors.blue.withOpacity(0.5), ), // - // 🔥 你想要的颜色 + ), + ), + ), + Visibility( + visible: controller.loginType.value == LoginType.phonePassword, + child: SizedBox( + height: 18.h, + ), + ), + Visibility( + visible: controller.loginType.value == LoginType.phonePassword, + child: TextField( + controller: controller.passwordController, + keyboardType: TextInputType.visiblePassword, + textInputAction: TextInputAction.done, + obscureText: controller.isPasswordVisible.value, + decoration: InputDecoration( + counterText: '', + hintText: '请输入密码'.tr, + border: const UnderlineInputBorder(), + suffixIcon: IconButton( + icon: Icon( + controller.isPasswordVisible.value + ? Icons.visibility + : Icons.visibility_off, + ), + onPressed: () => controller.isPasswordVisible.value = + !controller.isPasswordVisible.value, + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: controller.isFormValid.value + ? Colors.blue + : Colors.blue.withOpacity(0.5), + ), // + ), ), ), ), SizedBox( - height: 24.h, + height: 48.h, ), ElevatedButton( onPressed: controller.isFormValid.value @@ -120,7 +173,9 @@ class LoginView extends GetView { ), ), child: Text( - '获取验证码'.tr, + controller.loginType.value == LoginType.phoneCode + ? '获取验证码'.tr + : '登录'.tr, style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w500, @@ -128,35 +183,157 @@ class LoginView extends GetView { ), ), ), - SizedBox( - height: 22.h, - ), - TextButton( - onPressed: () {}, - child: Text( - '密码登录'.tr, - style: TextStyle( - fontSize: 14.sp, - fontWeight: FontWeight.w500, - color: Colors.grey, + SizedBox(height: 20.h), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Visibility( + visible: LoginType.phoneCode == controller.loginType.value, + child: TextButton( + onPressed: () { + controller.loginType.value = LoginType.phonePassword; + }, + child: Text( + '密码登录'.tr, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w500, + color: Colors.grey, + ), + ), + ), ), - ), + Visibility( + visible: LoginType.phonePassword == controller.loginType.value, + child: TextButton( + onPressed: () { + controller.loginType.value = LoginType.phoneCode; + }, + child: Text( + '验证码登录'.tr, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w500, + color: Colors.grey, + ), + ), + ), + ), + Visibility( + visible: LoginType.phonePassword == controller.loginType.value, + child: Text( + '|', + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w500, + color: Colors.grey, + ), + ), + ), + Visibility( + visible: LoginType.phonePassword == controller.loginType.value, + child: TextButton( + style: ButtonStyle( + overlayColor: MaterialStateProperty.all(Colors.transparent), // 墨水覆盖 + side: MaterialStateProperty.all(null), // 取消边框 + ), + onPressed: () { + Get.toNamed( + AppRoutes.forgotPassword, + parameters: { + 'phone': controller.phoneController.text, + }, + ); + }, + child: Text( + '忘记密码'.tr, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w500, + color: Colors.grey, + ), + ), + ), + ) + ], ) ], ), ); } + _handleLoginButtonText() { + if (controller.loginType.value == LoginType.phoneCode) { + return; + } else if (controller.loginType.value == LoginType.phonePassword) { + return '验证码登录'.tr + ' | '.tr + '忘记密码'.tr; + } + } + _buildPrivacyPolicy() { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Radio( - value: '1', - groupValue: 1, - onChanged: (value) {}, - ), - ], + return GestureDetector( + onTap: () { + controller.isPrivacyAgreementValid.value = + controller.isPrivacyAgreementValid.value == 1 ? 0 : 1; + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Obx( + () => Checkbox( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4.r), + ), + activeColor: Colors.blue, + value: controller.isPrivacyAgreementValid.value == 1, + // 转换为 bool + onChanged: (bool? value) { + controller.isPrivacyAgreementValid.value = + value == true ? 1 : 0; + }, + ), + ), + RichText( + textAlign: TextAlign.center, + text: TextSpan( + children: [ + TextSpan( + text: '我已阅读并同意'.tr, + style: TextStyle( + fontSize: 13.sp, + color: Colors.grey, + ), + ), + TextSpan( + text: '《用户协议》'.tr, + style: TextStyle( + fontSize: 13.sp, + color: Colors.blue, + ), + ), + TextSpan( + text: '和'.tr, + style: TextStyle( + fontSize: 13.sp, + color: Colors.grey, + ), + ), + TextSpan( + text: '《隐私政策》'.tr, + style: TextStyle( + fontSize: 13.sp, + color: Colors.blue, + ), + ), + ], + ), + ) + ], + ), ); } } diff --git a/pubspec.lock b/pubspec.lock index 87c23d5..37a53dc 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -341,6 +341,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "6.0.2" + pinput: + dependency: "direct main" + description: + name: pinput + sha256: "8a73be426a91fefec90a7f130763ca39772d547e92f19a827cf4aa02e323d35a" + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.0.1" platform: dependency: transitive description: @@ -490,6 +498,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.3.2" + universal_platform: + dependency: transitive + description: + name: universal_platform + sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0" vector_math: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a55ec06..e497416 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -27,6 +27,8 @@ dependencies: flutter_screenutil: ^5.9.3 # 提示 fluttertoast: ^8.2.8 + # 验证码输入框 + pinput: ^5.0.1 dev_dependencies: