feat: 验证码登录、密码登录页UI开发

This commit is contained in:
liyi 2025-08-29 16:19:50 +08:00
parent d858805563
commit 422e6a0291
19 changed files with 1074 additions and 54 deletions

View File

@ -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<void>((request) {
request.headers['Authorization'] = '12345678';
return request;
});
//
httpClient.addResponseModifier((request, response) {
return response;
});
}
}

View File

@ -0,0 +1,5 @@
enum LoginType {
phoneCode,
phonePassword;
}

View File

@ -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';
}
}
}

View File

@ -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}')),
);
}
}

View File

@ -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(),
),
];
}

View File

@ -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';
}

View File

@ -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>(
() => ForgotPasswordController(),
);
}
}

View File

@ -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<LoginController>();
//
void requestPhoneCode() {
Get.toNamed(
AppRoutes.inputVerificationCode,
parameters: {
'phone': loginController.phoneController.text.trim(),
},
);
}
}

View File

@ -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<ForgotPasswordController> {
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,
),
),
),
),
],
),
),
);
}
}

View File

@ -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>(
() => SetNewPasswordController(),
);
}
}

View File

@ -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(){
}
}

View File

@ -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<SetNewPasswordController> {
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,
),
),
),
],
),
),
),
);
}
}

View File

@ -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>(
() => InputVerificationCodeController(),
);
}
}

View File

@ -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); // 3138
String middleReplaced = '*****'; // 5 ****
String lastPart = phone.substring(8); // 3678
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);
}
}

View File

@ -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<InputVerificationCodeController> {
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,
),
),
);
}
}
}

View File

@ -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,
),
),
),
),
),
],
),
)
],
),
),
);
}
}

View File

@ -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<LoginController> {
FocusScope.of(context).unfocus();
},
child: Scaffold(
resizeToAvoidBottomInset: true,
body: SafeArea(
child: SingleChildScrollView(
child: _buildBody(),
@ -25,21 +30,28 @@ class LoginView extends GetView<LoginController> {
}
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<LoginController> {
fontWeight: FontWeight.w500,
),
),
SizedBox(
height: 4.h,
),
Text(
'未注册手机号验证后将自动创建账号'.tr,
style: TextStyle(
@ -87,6 +96,15 @@ class LoginView extends GetView<LoginController> {
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<LoginController> {
? 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<LoginController> {
),
),
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<LoginController> {
),
),
),
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,
),
),
],
),
)
],
),
);
}
}

View File

@ -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:

View File

@ -27,6 +27,8 @@ dependencies:
flutter_screenutil: ^5.9.3
# 提示
fluttertoast: ^8.2.8
# 验证码输入框
pinput: ^5.0.1
dev_dependencies: