7 changed files with 425 additions and 18 deletions
Split View
Diff Options
-
2lib/config/env_config.dart
-
8lib/main.dart
-
7lib/network/network_service.dart
-
7lib/network/user_api.dart
-
27lib/network/user_api.g.dart
-
130lib/pages/mine/login_controller.dart
-
262lib/pages/mine/login_page.dart
@ -0,0 +1,130 @@ |
|||
import 'dart:async'; |
|||
import 'package:get/get.dart'; |
|||
import 'package:get_storage/get_storage.dart'; |
|||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; |
|||
import 'package:dating_touchme_app/network/user_api.dart'; |
|||
|
|||
class LoginController extends GetxController { |
|||
// 手机号输入 |
|||
final phoneNumber = ''.obs; |
|||
// 验证码输入 |
|||
final verificationCode = ''.obs; |
|||
// 是否正在发送验证码 |
|||
final isSendingCode = false.obs; |
|||
// 倒计时秒数 |
|||
final countdownSeconds = 0.obs; |
|||
// 是否正在登录中 |
|||
final isLoggingIn = false.obs; |
|||
|
|||
// 从GetX依赖注入中获取UserApi实例 |
|||
late UserApi _userApi; |
|||
// GetStorage实例,用于存储token等信息 |
|||
final storage = GetStorage(); |
|||
|
|||
@override |
|||
void onInit() { |
|||
super.onInit(); |
|||
// 从全局依赖中获取UserApi |
|||
_userApi = Get.find<UserApi>(); |
|||
} |
|||
|
|||
// 获取验证码 |
|||
Future<void> getVerificationCode() async { |
|||
// 验证手机号格式 |
|||
if (phoneNumber.value.isEmpty || phoneNumber.value.length != 11) { |
|||
SmartDialog.showToast('请输入正确的手机号'); |
|||
return; |
|||
} |
|||
|
|||
isSendingCode.value = true; |
|||
|
|||
try { |
|||
// 构建请求参数 |
|||
final params = { |
|||
'purpose': 1, // 认证 |
|||
'verifiableAccount': phoneNumber.value, |
|||
'verifiableAccountType': 1, // 手机 |
|||
}; |
|||
|
|||
// 调用UserApi中的验证码接口 |
|||
final response = await _userApi.getVerificationCode(params); |
|||
|
|||
// 处理响应 |
|||
if (response.data != null && response.data['code'] == 0) { |
|||
// 开始倒计时 |
|||
startCountdown(); |
|||
} else { |
|||
SmartDialog.showToast(response.data?['message'] ?? '获取验证码失败'); |
|||
} |
|||
} catch (e) { |
|||
SmartDialog.showToast('网络请求失败,请重试'); |
|||
} finally { |
|||
isSendingCode.value = false; |
|||
} |
|||
} |
|||
|
|||
// 开始倒计时 |
|||
void startCountdown() { |
|||
countdownSeconds.value = 60; |
|||
Timer.periodic(const Duration(seconds: 1), (timer) { |
|||
countdownSeconds.value--; |
|||
if (countdownSeconds.value <= 0) { |
|||
timer.cancel(); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
// 清除错误信息 - 由于使用SmartDialog,此方法不再需要 |
|||
// void clearErrorMessage() {} |
|||
|
|||
|
|||
// 登录方法 |
|||
Future<void> login() async { |
|||
// 验证输入 |
|||
if (phoneNumber.value.isEmpty || phoneNumber.value.length != 11) { |
|||
SmartDialog.showToast('请输入正确的手机号'); |
|||
return; |
|||
} |
|||
|
|||
if (verificationCode.value.isEmpty) { |
|||
SmartDialog.showToast('请输入验证码'); |
|||
return; |
|||
} |
|||
|
|||
isLoggingIn.value = true; |
|||
|
|||
try { |
|||
// 构建登录请求参数 |
|||
final params = { |
|||
'account': phoneNumber.value, |
|||
'accountType': 2, // 手机号类型 |
|||
'captcha': verificationCode.value, |
|||
}; |
|||
|
|||
// 调用登录接口 |
|||
final response = await _userApi.login(params); |
|||
|
|||
// 处理响应 |
|||
if (response.data != null && response.data['code'] == 0) { |
|||
// 保存token和用户信息 |
|||
if (response.data['data'] != null) { |
|||
final userData = response.data['data']; |
|||
if (userData['token'] != null) { |
|||
await storage.write('token', userData['token']); |
|||
} |
|||
// 保存用户信息 |
|||
await storage.write('userInfo', userData); |
|||
} |
|||
|
|||
// 登录成功,跳转到首页 |
|||
Get.offAllNamed('/main'); |
|||
} else { |
|||
SmartDialog.showToast(response.data?['message'] ?? '登录失败,请重试'); |
|||
} |
|||
} catch (e) { |
|||
SmartDialog.showToast('网络请求失败,请检查网络连接'); |
|||
} finally { |
|||
isLoggingIn.value = false; |
|||
} |
|||
} |
|||
} |
|||
@ -1,15 +1,261 @@ |
|||
import 'package:dating_touchme_app/generated/assets.dart'; |
|||
import 'package:flutter/material.dart'; |
|||
import 'package:flutter_screenutil/flutter_screenutil.dart'; |
|||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; |
|||
import 'package:get/get.dart'; |
|||
import 'package:dating_touchme_app/pages/mine/login_controller.dart'; |
|||
|
|||
class LoginPage extends StatefulWidget { |
|||
const LoginPage({super.key}); |
|||
class LoginPage extends StatelessWidget { |
|||
LoginPage({super.key}); |
|||
|
|||
// 是否同意协议 |
|||
final agreeTerms = Rx<bool>(false); |
|||
|
|||
@override |
|||
State<LoginPage> createState() => _LoginPageState(); |
|||
} |
|||
|
|||
class _LoginPageState extends State<LoginPage> { |
|||
@override |
|||
Widget build(BuildContext context) { |
|||
return const Placeholder(); |
|||
return GetBuilder<LoginController>( |
|||
init: LoginController(), |
|||
builder: (controller) { |
|||
return Scaffold( |
|||
backgroundColor: Colors.white, |
|||
body: SingleChildScrollView( |
|||
padding: const EdgeInsets.all(20), |
|||
child: SizedBox( |
|||
height: MediaQuery.of(context).size.height, |
|||
child: Column( |
|||
children: [ |
|||
const SizedBox(height: 100), |
|||
|
|||
// Logo和标题区域 |
|||
Center( |
|||
child: Column( |
|||
children: [ |
|||
Image.asset( |
|||
Assets.imagesLoginLogo, |
|||
height: 60, |
|||
), |
|||
const SizedBox(height: 10), |
|||
const Text( |
|||
'心动就动我 幸福马上行动', |
|||
style: TextStyle( |
|||
fontSize: 14, |
|||
color: Color.fromRGBO(153, 153, 153, 1), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
|
|||
const SizedBox(height: 60), |
|||
|
|||
// 错误提示已改为SmartDialog.showToast,此处不再需要UI显示 |
|||
const SizedBox(height: 5), |
|||
|
|||
// 手机号输入框 - 带+86区号 |
|||
Container( |
|||
decoration: BoxDecoration( |
|||
border: Border.all(color: Colors.grey.shade300), |
|||
borderRadius: BorderRadius.circular(8), |
|||
), |
|||
child: Row( |
|||
children: [ |
|||
// +86区号部分 |
|||
Container( |
|||
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 14), |
|||
child: Row( |
|||
children: [ |
|||
const Text( |
|||
'+86', |
|||
style: TextStyle(fontSize: 16), |
|||
), |
|||
const SizedBox(width: 4), |
|||
Icon( |
|||
Icons.keyboard_arrow_down, |
|||
size: 20, |
|||
color: Colors.grey.shade500, |
|||
), |
|||
], |
|||
), |
|||
), |
|||
Container( |
|||
width: 1, |
|||
height: 30.w, |
|||
color: Colors.grey.shade300, |
|||
), |
|||
// 手机号输入部分 |
|||
Expanded( |
|||
child: TextField( |
|||
decoration: const InputDecoration( |
|||
hintText: '请输入你的手机号', |
|||
border: InputBorder.none, |
|||
contentPadding: EdgeInsets.symmetric(horizontal: 15, vertical: 14), |
|||
), |
|||
keyboardType: TextInputType.phone, |
|||
onChanged: (value) { |
|||
controller.phoneNumber.value = value; |
|||
}, |
|||
style: const TextStyle(fontSize: 16), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
|
|||
const SizedBox(height: 20), |
|||
|
|||
// 验证码输入框和获取验证码按钮 |
|||
Container( |
|||
decoration: BoxDecoration( |
|||
border: Border.all(color: Colors.grey.shade300), |
|||
borderRadius: BorderRadius.circular(8), |
|||
), |
|||
child: Row( |
|||
children: [ |
|||
// 验证码输入部分 |
|||
Expanded( |
|||
child: TextField( |
|||
decoration: const InputDecoration( |
|||
hintText: '请输入验证码', |
|||
border: InputBorder.none, |
|||
contentPadding: EdgeInsets.symmetric(horizontal: 15, vertical: 14), |
|||
), |
|||
keyboardType: TextInputType.number, |
|||
onChanged: (value) { |
|||
controller.verificationCode.value = value; |
|||
}, |
|||
style: const TextStyle(fontSize: 16), |
|||
), |
|||
), |
|||
// 获取验证码按钮 |
|||
GestureDetector( |
|||
onTap: controller.isSendingCode.value || controller.countdownSeconds.value > 0 |
|||
? null |
|||
: controller.getVerificationCode, |
|||
child: Container( |
|||
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 14), |
|||
child: Text( |
|||
controller.countdownSeconds.value > 0 |
|||
? '${controller.countdownSeconds.value}秒后重试' |
|||
: '获取验证码', |
|||
style: TextStyle( |
|||
fontSize: 14, |
|||
color: (controller.isSendingCode.value || controller.countdownSeconds.value > 0) |
|||
? Colors.grey.shade400 |
|||
: const Color.fromRGBO(74, 99, 235, 1), |
|||
), |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
|
|||
const SizedBox(height: 24), |
|||
|
|||
// 协议同意复选框 |
|||
Row( |
|||
children: [ |
|||
Obx(() => Checkbox( |
|||
value: agreeTerms.value, |
|||
onChanged: (value) { |
|||
agreeTerms.value = value ?? false; |
|||
}, |
|||
activeColor: const Color.fromRGBO(74, 99, 235, 1), |
|||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, |
|||
)), |
|||
const Text( |
|||
'我已阅读并同意', |
|||
style: TextStyle( |
|||
fontSize: 14, |
|||
color: Color.fromRGBO(153, 153, 153, 1), |
|||
), |
|||
), |
|||
const SizedBox(width: 4), |
|||
GestureDetector( |
|||
onTap: () { |
|||
// 跳转到用户协议页面 |
|||
}, |
|||
child: const Text( |
|||
'动我用户协议', |
|||
style: TextStyle( |
|||
fontSize: 14, |
|||
color: Color.fromRGBO(74, 99, 235, 1), |
|||
), |
|||
), |
|||
), |
|||
const SizedBox(width: 4), |
|||
const Text( |
|||
'和', |
|||
style: TextStyle( |
|||
fontSize: 14, |
|||
color: Color.fromRGBO(153, 153, 153, 1), |
|||
), |
|||
), |
|||
const SizedBox(width: 4), |
|||
GestureDetector( |
|||
onTap: () { |
|||
// 跳转到隐私政策页面 |
|||
}, |
|||
child: const Text( |
|||
'隐私政策', |
|||
style: TextStyle( |
|||
fontSize: 14, |
|||
color: Color.fromRGBO(74, 99, 235, 1), |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
SizedBox(height: 50), |
|||
// 注册并登录按钮 |
|||
Container( |
|||
margin: const EdgeInsets.only(bottom: 50), |
|||
child: ElevatedButton( |
|||
onPressed: controller.isLoggingIn.value |
|||
? null |
|||
: () { |
|||
// 登录逻辑 |
|||
if (!agreeTerms.value) { |
|||
SmartDialog.showToast('请阅读并同意用户协议和隐私政策'); |
|||
return; |
|||
} |
|||
|
|||
// 调用控制器的登录方法 |
|||
controller.login(); |
|||
}, |
|||
style: ElevatedButton.styleFrom( |
|||
minimumSize: const Size(double.infinity, 50), |
|||
backgroundColor: agreeTerms.value ? const Color.fromRGBO(74, 99, 235, 1) : Colors.grey.shade300, |
|||
shape: RoundedRectangleBorder( |
|||
borderRadius: BorderRadius.circular(25), |
|||
), |
|||
elevation: 0, |
|||
), |
|||
child: controller.isLoggingIn.value |
|||
? const SizedBox( |
|||
width: 20, |
|||
height: 20, |
|||
child: CircularProgressIndicator( |
|||
color: Colors.white, |
|||
strokeWidth: 2, |
|||
), |
|||
) |
|||
: const Text( |
|||
'注册并登录', |
|||
style: TextStyle( |
|||
fontSize: 18, |
|||
fontWeight: FontWeight.bold, |
|||
color: Colors.white, |
|||
), |
|||
), |
|||
), |
|||
), |
|||
], |
|||
), |
|||
), |
|||
), |
|||
); |
|||
}, |
|||
); |
|||
} |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save