7 changed files with 425 additions and 18 deletions
Unified 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/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 |
@override |
||||
Widget build(BuildContext context) { |
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