From 0816864f4b87d9f51068a8dd6341e3f32a517ad9 Mon Sep 17 00:00:00 2001 From: Jolie <412895109@qq.com> Date: Tue, 4 Nov 2025 00:58:06 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=99=BB=E5=BD=95=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/config/env_config.dart | 2 - lib/main.dart | 8 +- lib/network/network_service.dart | 7 +- lib/network/user_api.dart | 7 +- lib/network/user_api.g.dart | 27 ++- lib/pages/mine/login_controller.dart | 130 +++++++++++++ lib/pages/mine/login_page.dart | 262 ++++++++++++++++++++++++++- 7 files changed, 425 insertions(+), 18 deletions(-) create mode 100644 lib/pages/mine/login_controller.dart diff --git a/lib/config/env_config.dart b/lib/config/env_config.dart index 332cbe1..4fceff6 100644 --- a/lib/config/env_config.dart +++ b/lib/config/env_config.dart @@ -1,5 +1,3 @@ -import 'package:flutter/foundation.dart'; - /// 环境类型枚举 enum Environment { dev, release } diff --git a/lib/main.dart b/lib/main.dart index cfecb75..9897114 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:dating_touchme_app/config/env_config.dart'; +import 'package:dating_touchme_app/network/network_service.dart'; import 'package:dating_touchme_app/pages/main_page.dart'; import 'package:dating_touchme_app/pages/mine/login_page.dart'; import 'package:flutter/material.dart'; @@ -18,6 +19,11 @@ void main() async { // 设置环境配置 - 根据是否为release模式 EnvConfig.setEnvironment(Environment.dev); + + // 初始化全局依赖 + final networkService = NetworkService(); + Get.put(networkService); + Get.put(networkService.userApi); SystemChrome.setSystemUIOverlayStyle( const SystemUiOverlayStyle( @@ -66,6 +72,6 @@ class MyApp extends StatelessWidget { final token = storage.read('token'); // 如果token为空,显示登录页面,否则显示主页 - return token == null || token.isEmpty ? const LoginPage() : MainPage(); + return token == null || token.isEmpty ? LoginPage() : MainPage(); } } diff --git a/lib/network/network_service.dart b/lib/network/network_service.dart index 59264b2..0eb55e1 100644 --- a/lib/network/network_service.dart +++ b/lib/network/network_service.dart @@ -1,6 +1,5 @@ -import 'package:dio/dio.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:retrofit/retrofit.dart'; import 'api_service.dart'; import 'network_config.dart'; @@ -29,8 +28,6 @@ class NetworkService { // 初始化文件上传API服务 final fileDio = NetworkConfig.createDio(type: ApiType.file); _fileApiService = ApiService(fileDio); - - } /// 通用GET请求 @@ -183,7 +180,7 @@ class NetworkService { void _handleError(dynamic error) { // 统一错误处理逻辑 - print('网络错误: $error'); + debugPrint('网络错误: $error'); // 这里可以根据不同的错误类型进行处理 } } \ No newline at end of file diff --git a/lib/network/user_api.dart b/lib/network/user_api.dart index 10be5c8..975f931 100644 --- a/lib/network/user_api.dart +++ b/lib/network/user_api.dart @@ -7,11 +7,16 @@ part 'user_api.g.dart'; abstract class UserApi { factory UserApi(Dio dio) = _UserApi; - @POST("auth/login") + @POST("dating-agency-uec/authorize/by-captcha") Future> login( @Body() Map data, ); @GET("user/info") Future> getUserInfo(); + + @POST("dating-agency-uec/authorize/get/auth-captcha") + Future> getVerificationCode( + @Body() Map data, + ); } \ No newline at end of file diff --git a/lib/network/user_api.g.dart b/lib/network/user_api.g.dart index 0889e19..bc2e076 100644 --- a/lib/network/user_api.g.dart +++ b/lib/network/user_api.g.dart @@ -32,7 +32,7 @@ class _UserApi implements UserApi { Options(method: 'POST', headers: _headers, extra: _extra) .compose( _dio.options, - 'auth/login', + 'dating-agency-uec/authorize/by-captcha', queryParameters: queryParameters, data: _data, ) @@ -66,6 +66,31 @@ class _UserApi implements UserApi { return httpResponse; } + @override + Future> getVerificationCode( + Map data, + ) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + final _data = {}; + _data.addAll(data); + final _options = _setStreamType>( + Options(method: 'POST', headers: _headers, extra: _extra) + .compose( + _dio.options, + 'dating-agency-uec/authorize/get/auth-captcha', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); + final _result = await _dio.fetch(_options); + final _value = _result.data; + final httpResponse = HttpResponse(_value, _result); + return httpResponse; + } + RequestOptions _setStreamType(RequestOptions requestOptions) { if (T != dynamic && !(requestOptions.responseType == ResponseType.bytes || diff --git a/lib/pages/mine/login_controller.dart b/lib/pages/mine/login_controller.dart new file mode 100644 index 0000000..44a6c9a --- /dev/null +++ b/lib/pages/mine/login_controller.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(); + } + + // 获取验证码 + Future 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 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; + } + } +} \ No newline at end of file diff --git a/lib/pages/mine/login_page.dart b/lib/pages/mine/login_page.dart index 6087cae..6ca5f17 100644 --- a/lib/pages/mine/login_page.dart +++ b/lib/pages/mine/login_page.dart @@ -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(false); - @override - State createState() => _LoginPageState(); -} - -class _LoginPageState extends State { @override Widget build(BuildContext context) { - return const Placeholder(); + return GetBuilder( + 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, + ), + ), + ), + ), + ], + ), + ), + ), + ); + }, + ); } }