feat: 增加loading库
This commit is contained in:
parent
1adeb429cc
commit
981eef1145
@ -1,3 +1,5 @@
|
||||
class ApiPath {
|
||||
static const String sendValidationCode = "/v1/common/sendValidationCode";
|
||||
static const String validationCodeLogin = "/v1/user/codeLogin";
|
||||
static const String passwordLogin = "/v1/user/pwdLogin";
|
||||
}
|
||||
|
||||
@ -13,20 +13,7 @@ class BaseApiService {
|
||||
dio.options.connectTimeout = const Duration(seconds: 30);
|
||||
dio.options.receiveTimeout = const Duration(seconds: 30);
|
||||
|
||||
// 🔥 添加日志拦截器
|
||||
if (kDebugMode) {
|
||||
dio.interceptors.add(dioAlias.LogInterceptor(
|
||||
request: true,
|
||||
requestHeader: true,
|
||||
requestBody: true,
|
||||
responseHeader: true,
|
||||
responseBody: true,
|
||||
error: true,
|
||||
logPrint: (obj) {
|
||||
debugPrint('[DIO] $obj');
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// 统一请求方法
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
// 验证码接口请求参数
|
||||
// 登陆后可使用 sendValidationCodeAuth 接口,免图片滑动验证,只需要 channel,codeType 两个参数即可
|
||||
import 'package:starwork_flutter/common/constant/validation_code_type.dart';
|
||||
import 'package:starwork_flutter/common/constant/verification_code_channel.dart';
|
||||
import 'package:starwork_flutter/common/constant/verification_code_type.dart';
|
||||
|
||||
class SendValidationCodeRequest {
|
||||
final String countryCode;
|
||||
final String account;
|
||||
final String channel; //1 短信,2 邮箱
|
||||
final ValidationCodeType codeType;
|
||||
final VerificationCodeChannel channel; //1 短信,2 邮箱
|
||||
final VerificationCodeType codeType;
|
||||
|
||||
SendValidationCodeRequest({
|
||||
required this.countryCode,
|
||||
@ -19,7 +20,7 @@ class SendValidationCodeRequest {
|
||||
return {
|
||||
'countryCode': countryCode,
|
||||
'account': account,
|
||||
'channel': channel,
|
||||
'channel': channel.value,
|
||||
'codeType': codeType.value,
|
||||
};
|
||||
}
|
||||
|
||||
9
lib/api/model/user/response/token_response.dart
Normal file
9
lib/api/model/user/response/token_response.dart
Normal file
@ -0,0 +1,9 @@
|
||||
class LoginResponse {
|
||||
final String token;
|
||||
|
||||
LoginResponse({required this.token});
|
||||
|
||||
factory LoginResponse.fromJson(Map<String, dynamic> json) {
|
||||
return LoginResponse(token: json['token']);
|
||||
}
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
|
||||
class UserModel {
|
||||
final int id;
|
||||
final String name;
|
||||
final String email;
|
||||
|
||||
UserModel({required this.id, required this.name, required this.email});
|
||||
|
||||
factory UserModel.fromJson(Map<String, dynamic> json) {
|
||||
return UserModel(
|
||||
id: json['id'],
|
||||
name: json['name'],
|
||||
email: json['email'],
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'email': email,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -2,8 +2,6 @@ import 'package:starwork_flutter/api/api_path.dart';
|
||||
import 'package:starwork_flutter/api/api_response.dart';
|
||||
import 'package:starwork_flutter/api/base_api_service.dart';
|
||||
import 'package:starwork_flutter/api/model/common/request/send_validation_code_request.dart';
|
||||
import 'package:starwork_flutter/api/model/string_response.dart';
|
||||
import 'package:starwork_flutter/api/model/user/user_model.dart';
|
||||
import 'package:starwork_flutter/common/constant/http_constant.dart';
|
||||
|
||||
class CommonApiService {
|
||||
|
||||
@ -1,20 +1,26 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:starwork_flutter/api/api_path.dart';
|
||||
import 'package:starwork_flutter/api/api_response.dart';
|
||||
import 'package:starwork_flutter/api/base_api_service.dart';
|
||||
import 'package:starwork_flutter/api/model/user/user_model.dart';
|
||||
import 'package:starwork_flutter/api/model/user/request/validation_code_login.dart';
|
||||
import 'package:starwork_flutter/api/model/user/response/token_response.dart';
|
||||
import 'package:starwork_flutter/common/constant/http_constant.dart';
|
||||
|
||||
class UserApiService {
|
||||
final BaseApiService _api;
|
||||
|
||||
UserApiService(this._api); // 通过构造函数注入
|
||||
|
||||
Future<ApiResponse<UserModel>> login(Map<String, dynamic> userData) {
|
||||
// 验证码登录
|
||||
Future<ApiResponse<LoginResponse>> validationCodeLogin({
|
||||
required ValidationCodeLoginRequest request,
|
||||
}) {
|
||||
return _api.makeRequest(
|
||||
// 通过实例调用
|
||||
path: '/login',
|
||||
method: 'POST',
|
||||
data: userData,
|
||||
fromJson: (data) => UserModel.fromJson(data),
|
||||
path: ApiPath.validationCodeLogin,
|
||||
method: HttpConstant.post,
|
||||
data: request.toJson(),
|
||||
fromJson: (data) => LoginResponse.fromJson(data),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
29
lib/app.dart
29
lib/app.dart
@ -1,7 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:starwork_flutter/common/constant/cache_keys.dart';
|
||||
import 'package:starwork_flutter/common/utils/shared_preferences_utils.dart';
|
||||
import 'package:starwork_flutter/flavors.dart';
|
||||
import 'package:starwork_flutter/i18n/app_i18n.dart';
|
||||
import 'package:starwork_flutter/routes/app_pages.dart';
|
||||
@ -19,11 +22,15 @@ class App extends StatelessWidget {
|
||||
builder: (_, child) {
|
||||
return GetMaterialApp(
|
||||
theme: ThemeData(
|
||||
textSelectionTheme: TextSelectionThemeData(
|
||||
cursorColor: Colors.blue.shade200,
|
||||
selectionColor: Colors.blue.shade100,
|
||||
selectionHandleColor: Colors.blue.shade300,
|
||||
)),
|
||||
scaffoldBackgroundColor: Colors.white,
|
||||
brightness: Brightness.light,
|
||||
textSelectionTheme: TextSelectionThemeData(
|
||||
cursorColor: Colors.blue.shade200,
|
||||
selectionColor: Colors.blue.shade100,
|
||||
selectionHandleColor: Colors.blue.shade300,
|
||||
),
|
||||
dialogBackgroundColor: Colors.white,
|
||||
),
|
||||
// 必须配置以下三个本地化代理
|
||||
localizationsDelegates: const [
|
||||
GlobalMaterialLocalizations.delegate, // 提供Material组件本地化字符串
|
||||
@ -41,10 +48,20 @@ class App extends StatelessWidget {
|
||||
fallbackLocale: const Locale('zh', 'CN'),
|
||||
// 必须设置 fallback 没有该语言时使用
|
||||
getPages: AppPages.pages,
|
||||
initialRoute: AppRoutes.login,
|
||||
initialRoute: _checkIsLogin(),
|
||||
debugShowCheckedModeBanner: false,
|
||||
builder: EasyLoading.init(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
String _checkIsLogin() {
|
||||
var token = SharedPreferencesUtils.getString(CacheKeys.token);
|
||||
if (token != null && token.isNotEmpty) {
|
||||
return AppRoutes.main;
|
||||
} else {
|
||||
return AppRoutes.login;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:starwork_flutter/api/base_api_service.dart';
|
||||
@ -9,6 +10,8 @@ import 'package:starwork_flutter/api/service/common_api_service.dart';
|
||||
import 'package:starwork_flutter/api/service/user_api_service.dart';
|
||||
import 'package:starwork_flutter/common/utils/shared_preferences_utils.dart';
|
||||
import 'package:starwork_flutter/i18n/app_i18n.dart';
|
||||
import 'package:starwork_flutter/views/login/login_controller.dart';
|
||||
import 'package:starwork_flutter/views/main/main_controller.dart';
|
||||
|
||||
class AppInitialization {
|
||||
static Future<void> initializeApp() async {
|
||||
@ -16,18 +19,18 @@ class AppInitialization {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
setSystemStatusBar();
|
||||
await SharedPreferencesUtils.init();
|
||||
initEasyLoading();
|
||||
|
||||
// 日志:方便调试
|
||||
print('✅ SharedPreferences initialized');
|
||||
|
||||
Get.lazyPut(() => BaseApiService());
|
||||
Get.lazyPut(() => CommonApiService(Get.find<BaseApiService>()));
|
||||
Get.put(BaseApiService());
|
||||
Get.put(CommonApiService(Get.find<BaseApiService>()));
|
||||
Get.put(UserApiService(Get.find<BaseApiService>()));
|
||||
Get.put(LoginController());
|
||||
Get.put(MainController());
|
||||
|
||||
print('✅ API services registered');
|
||||
} catch (e, stack) {
|
||||
print('❌ Initialization failed: $e');
|
||||
print(stack);
|
||||
// 可以上报错误(Sentry 等)
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
@ -43,4 +46,20 @@ class AppInitialization {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static void initEasyLoading() {
|
||||
EasyLoading.instance
|
||||
..displayDuration = const Duration(milliseconds: 1000)
|
||||
..indicatorType = EasyLoadingIndicatorType.wanderingCubes
|
||||
..loadingStyle = EasyLoadingStyle.dark
|
||||
..indicatorSize = 45.0
|
||||
..radius = 10.0
|
||||
..progressColor = Colors.yellow
|
||||
..backgroundColor = Colors.green
|
||||
..indicatorColor = Colors.yellow
|
||||
..textColor = Colors.yellow
|
||||
..maskColor = Colors.blue.withOpacity(0.5)
|
||||
..userInteractions = false
|
||||
..dismissOnTap = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,17 +1,34 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class BaseController extends GetxController {
|
||||
void showToast(String message) {
|
||||
Fluttertoast.showToast(
|
||||
msg: message,
|
||||
toastLength: Toast.LENGTH_SHORT,
|
||||
gravity: ToastGravity.CENTER,
|
||||
backgroundColor: Colors.black54,
|
||||
textColor: Colors.white,
|
||||
fontSize: 14.0.sp,
|
||||
);
|
||||
EasyLoading.showToast(message);
|
||||
}
|
||||
|
||||
void showLoading() {
|
||||
EasyLoading.show(status: 'loading...');
|
||||
}
|
||||
|
||||
void hideLoading() {
|
||||
EasyLoading.dismiss();
|
||||
}
|
||||
|
||||
void showSuccess({String message = '操作成功'}) {
|
||||
EasyLoading.showSuccess(message.tr);
|
||||
}
|
||||
|
||||
void showError({String message = '操作失败'}) {
|
||||
EasyLoading.showError(message.tr);
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
if (EasyLoading.isShow) {
|
||||
EasyLoading.dismiss();
|
||||
}
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
class CacheKeys {
|
||||
static const String isSendValidationCode = 'isSendValidationCode';
|
||||
static const String token = 'token';
|
||||
}
|
||||
|
||||
36
lib/common/constant/platform_type.dart
Normal file
36
lib/common/constant/platform_type.dart
Normal file
@ -0,0 +1,36 @@
|
||||
class PlatformType {
|
||||
static const web = PlatformType('1', 'web');
|
||||
static const app = PlatformType('2', 'app');
|
||||
static const smallProgram = PlatformType('3', '小程序');
|
||||
static const pc = PlatformType('4', 'pc');
|
||||
|
||||
final String value;
|
||||
final String label;
|
||||
|
||||
const PlatformType(this.value, this.label);
|
||||
|
||||
// 支持通过字符串值查找枚举实例
|
||||
static PlatformType? fromValue(String? value) {
|
||||
return {
|
||||
'1': web,
|
||||
'2': app,
|
||||
'3': smallProgram,
|
||||
'4': pc,
|
||||
}[value];
|
||||
}
|
||||
|
||||
// 支持 toString() 直接输出 value
|
||||
@override
|
||||
String toString() => value;
|
||||
|
||||
// 可选:支持 == 比较
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is PlatformType &&
|
||||
runtimeType == other.runtimeType &&
|
||||
value == other.value;
|
||||
|
||||
@override
|
||||
int get hashCode => value.hashCode;
|
||||
}
|
||||
@ -1,46 +0,0 @@
|
||||
class ValidationCodeType {
|
||||
static const login = ValidationCodeType('1', '登录');
|
||||
static const reset = ValidationCodeType('2', '重置密码');
|
||||
static const bindPhone = ValidationCodeType('3', '绑定手机号');
|
||||
static const unbindPhone = ValidationCodeType('4', '解绑手机号');
|
||||
static const deleteAccount = ValidationCodeType('5', '删除账号');
|
||||
static const bindEmail = ValidationCodeType('6', '绑定邮箱');
|
||||
static const unbindEmail = ValidationCodeType('7', '解绑邮箱');
|
||||
static const deleteLock = ValidationCodeType('8', '删除门锁');
|
||||
static const updatePassword = ValidationCodeType('9', '修改密码');
|
||||
|
||||
final String value;
|
||||
final String label;
|
||||
|
||||
const ValidationCodeType(this.value, this.label);
|
||||
|
||||
// 支持通过字符串值查找枚举实例
|
||||
static ValidationCodeType? fromValue(String? value) {
|
||||
return {
|
||||
'1': login,
|
||||
'2': reset,
|
||||
'3': bindPhone,
|
||||
'4': unbindPhone,
|
||||
'5': deleteAccount,
|
||||
'6': bindEmail,
|
||||
'7': unbindEmail,
|
||||
'8': deleteLock,
|
||||
'9': updatePassword,
|
||||
}[value];
|
||||
}
|
||||
|
||||
// 支持 toString() 直接输出 value
|
||||
@override
|
||||
String toString() => value;
|
||||
|
||||
// 可选:支持 == 比较
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is ValidationCodeType &&
|
||||
runtimeType == other.runtimeType &&
|
||||
value == other.value;
|
||||
|
||||
@override
|
||||
int get hashCode => value.hashCode;
|
||||
}
|
||||
@ -1,15 +1,15 @@
|
||||
// Description: 验证码渠道枚举类
|
||||
class ValidationCodeChannel {
|
||||
static const sms = ValidationCodeChannel('1', '短信');
|
||||
static const email = ValidationCodeChannel('2', '邮箱');
|
||||
class VerificationCodeChannel {
|
||||
static const sms = VerificationCodeChannel('1', '短信');
|
||||
static const email = VerificationCodeChannel('2', '邮箱');
|
||||
|
||||
final String value;
|
||||
final String label;
|
||||
|
||||
const ValidationCodeChannel(this.value, this.label);
|
||||
const VerificationCodeChannel(this.value, this.label);
|
||||
|
||||
// 支持通过字符串值查找枚举实例
|
||||
static ValidationCodeChannel? fromValue(String? value) {
|
||||
static VerificationCodeChannel? fromValue(String? value) {
|
||||
return {
|
||||
'1': sms,
|
||||
'2': email,
|
||||
@ -24,7 +24,7 @@ class ValidationCodeChannel {
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is ValidationCodeChannel &&
|
||||
other is VerificationCodeChannel &&
|
||||
runtimeType == other.runtimeType &&
|
||||
value == other.value;
|
||||
|
||||
46
lib/common/constant/verification_code_type.dart
Normal file
46
lib/common/constant/verification_code_type.dart
Normal file
@ -0,0 +1,46 @@
|
||||
class VerificationCodeType {
|
||||
static const login = VerificationCodeType('1', '登录');
|
||||
static const reset = VerificationCodeType('2', '重置密码');
|
||||
static const bindPhone = VerificationCodeType('3', '绑定手机号');
|
||||
static const unbindPhone = VerificationCodeType('4', '解绑手机号');
|
||||
static const deleteAccount = VerificationCodeType('5', '删除账号');
|
||||
static const bindEmail = VerificationCodeType('6', '绑定邮箱');
|
||||
static const unbindEmail = VerificationCodeType('7', '解绑邮箱');
|
||||
static const deleteLock = VerificationCodeType('8', '删除门锁');
|
||||
static const updatePassword = VerificationCodeType('9', '修改密码');
|
||||
|
||||
final String value;
|
||||
final String label;
|
||||
|
||||
const VerificationCodeType(this.value, this.label);
|
||||
|
||||
// 支持通过字符串值查找枚举实例
|
||||
static VerificationCodeType? fromValue(String? value) {
|
||||
return {
|
||||
'1': login,
|
||||
'2': reset,
|
||||
'3': bindPhone,
|
||||
'4': unbindPhone,
|
||||
'5': deleteAccount,
|
||||
'6': bindEmail,
|
||||
'7': unbindEmail,
|
||||
'8': deleteLock,
|
||||
'9': updatePassword,
|
||||
}[value];
|
||||
}
|
||||
|
||||
// 支持 toString() 直接输出 value
|
||||
@override
|
||||
String toString() => value;
|
||||
|
||||
// 可选:支持 == 比较
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is VerificationCodeType &&
|
||||
runtimeType == other.runtimeType &&
|
||||
value == other.value;
|
||||
|
||||
@override
|
||||
int get hashCode => value.hashCode;
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'dart:async';
|
||||
|
||||
class SharedPreferencesUtils {
|
||||
static SharedPreferences? _prefs;
|
||||
@ -8,7 +9,63 @@ class SharedPreferencesUtils {
|
||||
_prefs = await SharedPreferences.getInstance();
|
||||
}
|
||||
|
||||
// 使用
|
||||
|
||||
/// 存储带过期时间的字符串
|
||||
/// [key] 键
|
||||
/// [value] 值
|
||||
/// [expiry] 过期时间(如 Duration(hours: 1))
|
||||
static Future<bool> setStringWithExpiry(
|
||||
String key,
|
||||
String value,
|
||||
Duration expiry,
|
||||
) async {
|
||||
final prefs = _prefs;
|
||||
if (prefs == null) return false;
|
||||
|
||||
final expiryKey = '_expiry_$key'; // 用 _expiry_ 前缀标记过期时间
|
||||
final expireAt = DateTime.now().add(expiry).millisecondsSinceEpoch;
|
||||
|
||||
// 使用 Transaction 确保原子性(可选)
|
||||
return await prefs.setString(key, value) &&
|
||||
await prefs.setInt(expiryKey, expireAt);
|
||||
}
|
||||
|
||||
/// 获取带过期时间的字符串
|
||||
/// 如果已过期,则自动删除并返回 null
|
||||
static String? getStringWithExpiry(String key) {
|
||||
final prefs = _prefs;
|
||||
if (prefs == null) return null;
|
||||
|
||||
final expiryKey = '_expiry_$key';
|
||||
final expireAt = prefs.getInt(expiryKey);
|
||||
|
||||
// 如果没有过期时间,视为永不过期(或已过期?根据需求)
|
||||
if (expireAt == null) {
|
||||
return prefs.getString(key);
|
||||
}
|
||||
|
||||
// 比较当前时间
|
||||
final now = DateTime.now().millisecondsSinceEpoch;
|
||||
if (now > expireAt) {
|
||||
// 已过期,清理
|
||||
removeKeyWithExpiry(key);
|
||||
return null;
|
||||
}
|
||||
|
||||
return prefs.getString(key);
|
||||
}
|
||||
|
||||
/// 删除带过期时间的键(清理 value 和 expiry)
|
||||
static Future<void> removeKeyWithExpiry(String key) async {
|
||||
final prefs = _prefs;
|
||||
if (prefs == null) return;
|
||||
|
||||
final expiryKey = '_expiry_$key';
|
||||
await prefs.remove(key);
|
||||
await prefs.remove(expiryKey);
|
||||
}
|
||||
|
||||
|
||||
static Future<bool> setString(String key, String value) async {
|
||||
return _prefs?.setString(key, value) ?? Future.value(false);
|
||||
}
|
||||
@ -16,4 +73,4 @@ class SharedPreferencesUtils {
|
||||
static String? getString(String key) {
|
||||
return _prefs?.getString(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,24 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:get/get.dart';
|
||||
import 'package:starwork_flutter/api/api_response.dart';
|
||||
import 'package:starwork_flutter/api/model/common/request/send_validation_code_request.dart';
|
||||
import 'package:starwork_flutter/api/model/user/request/validation_code_login.dart';
|
||||
import 'package:starwork_flutter/api/service/common_api_service.dart';
|
||||
import 'package:starwork_flutter/api/service/user_api_service.dart';
|
||||
import 'package:starwork_flutter/base/base_controller.dart';
|
||||
import 'package:starwork_flutter/common/constant/cache_keys.dart';
|
||||
import 'package:starwork_flutter/common/constant/platform_type.dart';
|
||||
import 'package:starwork_flutter/common/constant/verification_code_channel.dart';
|
||||
import 'package:starwork_flutter/common/constant/verification_code_type.dart';
|
||||
import 'package:starwork_flutter/common/utils/shared_preferences_utils.dart';
|
||||
import 'package:starwork_flutter/routes/app_routes.dart';
|
||||
|
||||
class InputVerificationCodeController extends BaseController {
|
||||
final userApi = Get.find<UserApiService>();
|
||||
final commonApi = Get.find<CommonApiService>();
|
||||
|
||||
var rawPhone = ''.obs;
|
||||
var phone = ''.obs;
|
||||
var previousRoute = ''.obs;
|
||||
|
||||
@ -15,7 +29,7 @@ class InputVerificationCodeController extends BaseController {
|
||||
var countdownValue = 60.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
void onInit() async {
|
||||
super.onInit();
|
||||
|
||||
final params = Get.parameters;
|
||||
@ -24,56 +38,92 @@ class InputVerificationCodeController extends BaseController {
|
||||
previousRoute.value = Get.previousRoute; // 如 "/login"
|
||||
|
||||
if (phoneParam != null && phoneParam.isNotEmpty) {
|
||||
rawPhone.value = phoneParam;
|
||||
phone.value = maskMiddleFive(phoneParam);
|
||||
// 开始倒计时(比如60秒)
|
||||
startCountdown();
|
||||
if (previousRoute.value.contains(AppRoutes.login)) {
|
||||
seedVerificationCode(codeType: VerificationCodeType.login);
|
||||
} else if (previousRoute.value.contains(AppRoutes.forgotPassword)) {
|
||||
seedVerificationCode(codeType: VerificationCodeType.reset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
super.onClose();
|
||||
String key = '${CacheKeys.isSendValidationCode}_${rawPhone.value}';
|
||||
String value = '${rawPhone.value}_${countdownValue.value}';
|
||||
// 存储发送验证码的时间戳,避免重复发送
|
||||
SharedPreferencesUtils.setStringWithExpiry(
|
||||
key,
|
||||
value,
|
||||
Duration(seconds: countdownValue.value),
|
||||
);
|
||||
}
|
||||
|
||||
String maskMiddleFive(String phone) {
|
||||
if (phone.length < 8) return phone; // 如果手机号少于8位,不处理(或根据需求返回原值/报错)
|
||||
|
||||
// 假设手机号至少有 8 位,我们保留前 3 位,中间 5 位替换为 ****,显示后 3 位
|
||||
// 13812345678 → 138****678
|
||||
String firstPart = phone.substring(0, 3); // 前3位:138
|
||||
String middleReplaced = '*****'; // 中间5位替换为 ****
|
||||
String lastPart = phone.substring(8); // 后3位:678
|
||||
|
||||
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) {
|
||||
// 校验验证码
|
||||
void checkVerificationCode(String pin) {
|
||||
if (previousRoute.value.contains(AppRoutes.login)) {
|
||||
_handleLogin();
|
||||
Get.offAllNamed(AppRoutes.main);
|
||||
} else if (previousRoute.value.contains(AppRoutes.forgotPassword)) {
|
||||
_handleForgotPassword();
|
||||
Get.toNamed(AppRoutes.setNewPassword);
|
||||
}
|
||||
}
|
||||
|
||||
void _handleLogin() {
|
||||
Get.offAndToNamed(AppRoutes.main);
|
||||
void _handleSeedVerificationCode({
|
||||
required VerificationCodeType codeType,
|
||||
}) async {}
|
||||
|
||||
// 发送验证码
|
||||
void seedVerificationCode({
|
||||
required VerificationCodeType codeType,
|
||||
}) async {
|
||||
showLoading();
|
||||
var sendValidationCodeResult = await commonApi.sendValidationCode(
|
||||
request: SendValidationCodeRequest(
|
||||
countryCode: '+86',
|
||||
account: rawPhone.value.trim(),
|
||||
channel: VerificationCodeChannel.sms,
|
||||
codeType: codeType,
|
||||
),
|
||||
);
|
||||
if (sendValidationCodeResult.isSuccess) {
|
||||
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;
|
||||
}
|
||||
});
|
||||
showSuccess(message: '验证码已发送,请注意查收'.tr);
|
||||
} else {
|
||||
showError(message: sendValidationCodeResult.errorMsg!);
|
||||
}
|
||||
hideLoading();
|
||||
}
|
||||
|
||||
void _handleForgotPassword() {
|
||||
Get.toNamed(AppRoutes.setNewPassword);
|
||||
// 跳转页面
|
||||
void _toPage() async {
|
||||
if (previousRoute.value.contains(AppRoutes.login)) {
|
||||
Get.offAllNamed(AppRoutes.main);
|
||||
} else if (previousRoute.value.contains(AppRoutes.forgotPassword)) {
|
||||
Get.toNamed(AppRoutes.setNewPassword);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,8 @@ 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/common/constant/verification_code_type.dart';
|
||||
import 'package:starwork_flutter/routes/app_routes.dart';
|
||||
import 'package:starwork_flutter/views/login/inputVerificationCode/input_verification_code_controller.dart';
|
||||
|
||||
class InputVerificationCodeView
|
||||
@ -47,6 +49,7 @@ class InputVerificationCodeView
|
||||
Pinput(
|
||||
length: 6,
|
||||
obscureText: false,
|
||||
autofocus: true,
|
||||
defaultPinTheme: PinTheme(
|
||||
width: 50.w,
|
||||
height: 50.w,
|
||||
@ -63,7 +66,7 @@ class InputVerificationCodeView
|
||||
return null;
|
||||
},
|
||||
onCompleted: (pin) {
|
||||
controller.requestPhoneCodeLogin(pin);
|
||||
controller.checkVerificationCode(pin);
|
||||
},
|
||||
),
|
||||
],
|
||||
@ -106,7 +109,15 @@ class InputVerificationCodeView
|
||||
} else {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
controller.startCountdown();
|
||||
if (controller.previousRoute.value.contains(AppRoutes.login)) {
|
||||
controller.seedVerificationCode(
|
||||
codeType: VerificationCodeType.login,
|
||||
);
|
||||
} else if (controller.previousRoute.value.contains(AppRoutes.forgotPassword)) {
|
||||
controller.seedVerificationCode(
|
||||
codeType: VerificationCodeType.reset,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
'重新获取验证码'.tr,
|
||||
|
||||
@ -2,21 +2,16 @@ 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/api/model/common/request/send_validation_code_request.dart';
|
||||
import 'package:starwork_flutter/api/service/common_api_service.dart';
|
||||
import 'package:starwork_flutter/base/base_controller.dart';
|
||||
import 'package:starwork_flutter/common/constant/login_type.dart';
|
||||
import 'package:starwork_flutter/common/constant/validation_code_channel.dart';
|
||||
import 'package:starwork_flutter/common/constant/validation_code_type.dart';
|
||||
|
||||
import 'package:starwork_flutter/routes/app_pages.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:starwork_flutter/base/base_controller.dart';
|
||||
import 'package:starwork_flutter/common/constant/cache_keys.dart';
|
||||
import 'package:starwork_flutter/common/constant/login_type.dart';
|
||||
import 'package:starwork_flutter/common/utils/shared_preferences_utils.dart';
|
||||
|
||||
import 'package:starwork_flutter/routes/app_routes.dart';
|
||||
|
||||
class LoginController extends BaseController {
|
||||
final commonApi = Get.find<CommonApiService>();
|
||||
|
||||
int phoneNumberSize = 11;
|
||||
TextEditingController phoneController = TextEditingController();
|
||||
TextEditingController passwordController = TextEditingController();
|
||||
@ -44,7 +39,6 @@ class LoginController extends BaseController {
|
||||
|
||||
void _validateForm() {
|
||||
isFormValid.value = phoneController.text.length == phoneNumberSize;
|
||||
debugPrint('isFormValid: ${isFormValid.value}');
|
||||
}
|
||||
|
||||
// 获取手机验证码
|
||||
@ -55,23 +49,15 @@ class LoginController extends BaseController {
|
||||
content: '1',
|
||||
onConfirm: () async {
|
||||
isPrivacyAgreementValid.value = 1;
|
||||
Get.toNamed(
|
||||
AppRoutes.inputVerificationCode,
|
||||
parameters: {
|
||||
'phone': phoneController.text.trim(),
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
_handleRequestPhoneCode();
|
||||
}
|
||||
|
||||
void _handleRequestPhoneCode() async {
|
||||
var sendValidationCodeResult = await commonApi.sendValidationCode(
|
||||
request: SendValidationCodeRequest(
|
||||
countryCode: '+86',
|
||||
account: phoneController.text.trim(),
|
||||
channel: ValidationCodeChannel.sms.value,
|
||||
codeType: ValidationCodeType.login,
|
||||
),
|
||||
);
|
||||
|
||||
if (sendValidationCodeResult.isSuccess) {
|
||||
} else {
|
||||
Get.toNamed(
|
||||
AppRoutes.inputVerificationCode,
|
||||
parameters: {
|
||||
@ -88,10 +74,10 @@ class LoginController extends BaseController {
|
||||
}) {
|
||||
Get.dialog(
|
||||
Dialog(
|
||||
backgroundColor: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(14.r), // 圆角
|
||||
),
|
||||
backgroundColor: Colors.white,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
|
||||
32
pubspec.lock
32
pubspec.lock
@ -126,6 +126,14 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_easyloading:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_easyloading
|
||||
sha256: ba21a3c883544e582f9cc455a4a0907556714e1e9cf0eababfcb600da191d17c
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.0.5"
|
||||
flutter_flavorizr:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@ -155,6 +163,14 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "5.9.3"
|
||||
flutter_spinkit:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_spinkit
|
||||
sha256: d2696eed13732831414595b98863260e33e8882fc069ee80ec35d4ac9ddb0472
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "5.2.1"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@ -165,14 +181,6 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
fluttertoast:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: fluttertoast
|
||||
sha256: "95f349437aeebe524ef7d6c9bde3e6b4772717cf46a0eb6a3ceaddc740b297cc"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "8.2.8"
|
||||
get:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -461,6 +469,14 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
skeletonizer:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: skeletonizer
|
||||
sha256: "0dcacc51c144af4edaf37672072156f49e47036becbc394d7c51850c5c1e884b"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.4.3"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
|
||||
@ -25,12 +25,15 @@ dependencies:
|
||||
shared_preferences: ^2.2.3
|
||||
# 屏幕适配
|
||||
flutter_screenutil: ^5.9.3
|
||||
# 提示
|
||||
fluttertoast: ^8.2.8
|
||||
# 验证码输入框
|
||||
pinput: ^5.0.1
|
||||
# 网络请求库
|
||||
dio: ^5.9.0
|
||||
# loading 动画库 需要指定flutter_spinkit为固定版本
|
||||
flutter_easyloading: ^3.0.0
|
||||
flutter_spinkit: 5.2.1
|
||||
# 骨架屏
|
||||
skeletonizer: ^1.4.3
|
||||
|
||||
|
||||
dev_dependencies:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user