Browse Source

增加登录页面

ios
Jolie 4 months ago
parent
commit
0816864f4b
7 changed files with 425 additions and 18 deletions
  1. 2
      lib/config/env_config.dart
  2. 8
      lib/main.dart
  3. 7
      lib/network/network_service.dart
  4. 7
      lib/network/user_api.dart
  5. 27
      lib/network/user_api.g.dart
  6. 130
      lib/pages/mine/login_controller.dart
  7. 262
      lib/pages/mine/login_page.dart

2
lib/config/env_config.dart

@ -1,5 +1,3 @@
import 'package:flutter/foundation.dart';
///
enum Environment { dev, release }

8
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<String>('token');
// token为空
return token == null || token.isEmpty ? const LoginPage() : MainPage();
return token == null || token.isEmpty ? LoginPage() : MainPage();
}
}

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

7
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<HttpResponse<dynamic>> login(
@Body() Map<String, dynamic> data,
);
@GET("user/info")
Future<HttpResponse<dynamic>> getUserInfo();
@POST("dating-agency-uec/authorize/get/auth-captcha")
Future<HttpResponse<dynamic>> getVerificationCode(
@Body() Map<String, dynamic> data,
);
}

27
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<HttpResponse<dynamic>> getVerificationCode(
Map<String, dynamic> data,
) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
final _data = <String, dynamic>{};
_data.addAll(data);
final _options = _setStreamType<HttpResponse<dynamic>>(
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<T>(RequestOptions requestOptions) {
if (T != dynamic &&
!(requestOptions.responseType == ResponseType.bytes ||

130
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<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;
}
}
}

262
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<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.showToastUI显示
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,
),
),
),
),
],
),
),
),
);
},
);
}
}
Loading…
Cancel
Save