feat: 增加loading库
This commit is contained in:
parent
1adeb429cc
commit
981eef1145
@ -1,3 +1,5 @@
|
|||||||
class ApiPath {
|
class ApiPath {
|
||||||
static const String sendValidationCode = "/v1/common/sendValidationCode";
|
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.connectTimeout = const Duration(seconds: 30);
|
||||||
dio.options.receiveTimeout = 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 两个参数即可
|
// 登陆后可使用 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 {
|
class SendValidationCodeRequest {
|
||||||
final String countryCode;
|
final String countryCode;
|
||||||
final String account;
|
final String account;
|
||||||
final String channel; //1 短信,2 邮箱
|
final VerificationCodeChannel channel; //1 短信,2 邮箱
|
||||||
final ValidationCodeType codeType;
|
final VerificationCodeType codeType;
|
||||||
|
|
||||||
SendValidationCodeRequest({
|
SendValidationCodeRequest({
|
||||||
required this.countryCode,
|
required this.countryCode,
|
||||||
@ -19,7 +20,7 @@ class SendValidationCodeRequest {
|
|||||||
return {
|
return {
|
||||||
'countryCode': countryCode,
|
'countryCode': countryCode,
|
||||||
'account': account,
|
'account': account,
|
||||||
'channel': channel,
|
'channel': channel.value,
|
||||||
'codeType': codeType.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/api_response.dart';
|
||||||
import 'package:starwork_flutter/api/base_api_service.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/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';
|
import 'package:starwork_flutter/common/constant/http_constant.dart';
|
||||||
|
|
||||||
class CommonApiService {
|
class CommonApiService {
|
||||||
|
|||||||
@ -1,20 +1,26 @@
|
|||||||
import 'package:get/get.dart';
|
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/api_response.dart';
|
||||||
import 'package:starwork_flutter/api/base_api_service.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 {
|
class UserApiService {
|
||||||
final BaseApiService _api;
|
final BaseApiService _api;
|
||||||
|
|
||||||
UserApiService(this._api); // 通过构造函数注入
|
UserApiService(this._api); // 通过构造函数注入
|
||||||
|
|
||||||
Future<ApiResponse<UserModel>> login(Map<String, dynamic> userData) {
|
// 验证码登录
|
||||||
|
Future<ApiResponse<LoginResponse>> validationCodeLogin({
|
||||||
|
required ValidationCodeLoginRequest request,
|
||||||
|
}) {
|
||||||
return _api.makeRequest(
|
return _api.makeRequest(
|
||||||
// 通过实例调用
|
// 通过实例调用
|
||||||
path: '/login',
|
path: ApiPath.validationCodeLogin,
|
||||||
method: 'POST',
|
method: HttpConstant.post,
|
||||||
data: userData,
|
data: request.toJson(),
|
||||||
fromJson: (data) => UserModel.fromJson(data),
|
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/material.dart';
|
||||||
|
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:get/get.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/flavors.dart';
|
||||||
import 'package:starwork_flutter/i18n/app_i18n.dart';
|
import 'package:starwork_flutter/i18n/app_i18n.dart';
|
||||||
import 'package:starwork_flutter/routes/app_pages.dart';
|
import 'package:starwork_flutter/routes/app_pages.dart';
|
||||||
@ -19,11 +22,15 @@ class App extends StatelessWidget {
|
|||||||
builder: (_, child) {
|
builder: (_, child) {
|
||||||
return GetMaterialApp(
|
return GetMaterialApp(
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
textSelectionTheme: TextSelectionThemeData(
|
scaffoldBackgroundColor: Colors.white,
|
||||||
cursorColor: Colors.blue.shade200,
|
brightness: Brightness.light,
|
||||||
selectionColor: Colors.blue.shade100,
|
textSelectionTheme: TextSelectionThemeData(
|
||||||
selectionHandleColor: Colors.blue.shade300,
|
cursorColor: Colors.blue.shade200,
|
||||||
)),
|
selectionColor: Colors.blue.shade100,
|
||||||
|
selectionHandleColor: Colors.blue.shade300,
|
||||||
|
),
|
||||||
|
dialogBackgroundColor: Colors.white,
|
||||||
|
),
|
||||||
// 必须配置以下三个本地化代理
|
// 必须配置以下三个本地化代理
|
||||||
localizationsDelegates: const [
|
localizationsDelegates: const [
|
||||||
GlobalMaterialLocalizations.delegate, // 提供Material组件本地化字符串
|
GlobalMaterialLocalizations.delegate, // 提供Material组件本地化字符串
|
||||||
@ -41,10 +48,20 @@ class App extends StatelessWidget {
|
|||||||
fallbackLocale: const Locale('zh', 'CN'),
|
fallbackLocale: const Locale('zh', 'CN'),
|
||||||
// 必须设置 fallback 没有该语言时使用
|
// 必须设置 fallback 没有该语言时使用
|
||||||
getPages: AppPages.pages,
|
getPages: AppPages.pages,
|
||||||
initialRoute: AppRoutes.login,
|
initialRoute: _checkIsLogin(),
|
||||||
debugShowCheckedModeBanner: false,
|
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/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:starwork_flutter/api/base_api_service.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/api/service/user_api_service.dart';
|
||||||
import 'package:starwork_flutter/common/utils/shared_preferences_utils.dart';
|
import 'package:starwork_flutter/common/utils/shared_preferences_utils.dart';
|
||||||
import 'package:starwork_flutter/i18n/app_i18n.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 {
|
class AppInitialization {
|
||||||
static Future<void> initializeApp() async {
|
static Future<void> initializeApp() async {
|
||||||
@ -16,18 +19,18 @@ class AppInitialization {
|
|||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
setSystemStatusBar();
|
setSystemStatusBar();
|
||||||
await SharedPreferencesUtils.init();
|
await SharedPreferencesUtils.init();
|
||||||
|
initEasyLoading();
|
||||||
|
|
||||||
// 日志:方便调试
|
Get.put(BaseApiService());
|
||||||
print('✅ SharedPreferences initialized');
|
Get.put(CommonApiService(Get.find<BaseApiService>()));
|
||||||
|
Get.put(UserApiService(Get.find<BaseApiService>()));
|
||||||
Get.lazyPut(() => BaseApiService());
|
Get.put(LoginController());
|
||||||
Get.lazyPut(() => CommonApiService(Get.find<BaseApiService>()));
|
Get.put(MainController());
|
||||||
|
|
||||||
print('✅ API services registered');
|
print('✅ API services registered');
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
print('❌ Initialization failed: $e');
|
print('❌ Initialization failed: $e');
|
||||||
print(stack);
|
print(stack);
|
||||||
// 可以上报错误(Sentry 等)
|
|
||||||
rethrow;
|
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/material.dart';
|
||||||
|
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
class BaseController extends GetxController {
|
class BaseController extends GetxController {
|
||||||
void showToast(String message) {
|
void showToast(String message) {
|
||||||
Fluttertoast.showToast(
|
EasyLoading.showToast(message);
|
||||||
msg: message,
|
}
|
||||||
toastLength: Toast.LENGTH_SHORT,
|
|
||||||
gravity: ToastGravity.CENTER,
|
void showLoading() {
|
||||||
backgroundColor: Colors.black54,
|
EasyLoading.show(status: 'loading...');
|
||||||
textColor: Colors.white,
|
}
|
||||||
fontSize: 14.0.sp,
|
|
||||||
);
|
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 {
|
class CacheKeys {
|
||||||
|
static const String isSendValidationCode = 'isSendValidationCode';
|
||||||
static const String token = 'token';
|
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: 验证码渠道枚举类
|
// Description: 验证码渠道枚举类
|
||||||
class ValidationCodeChannel {
|
class VerificationCodeChannel {
|
||||||
static const sms = ValidationCodeChannel('1', '短信');
|
static const sms = VerificationCodeChannel('1', '短信');
|
||||||
static const email = ValidationCodeChannel('2', '邮箱');
|
static const email = VerificationCodeChannel('2', '邮箱');
|
||||||
|
|
||||||
final String value;
|
final String value;
|
||||||
final String label;
|
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 {
|
return {
|
||||||
'1': sms,
|
'1': sms,
|
||||||
'2': email,
|
'2': email,
|
||||||
@ -24,7 +24,7 @@ class ValidationCodeChannel {
|
|||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
other is ValidationCodeChannel &&
|
other is VerificationCodeChannel &&
|
||||||
runtimeType == other.runtimeType &&
|
runtimeType == other.runtimeType &&
|
||||||
value == other.value;
|
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 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
class SharedPreferencesUtils {
|
class SharedPreferencesUtils {
|
||||||
static SharedPreferences? _prefs;
|
static SharedPreferences? _prefs;
|
||||||
@ -8,7 +9,63 @@ class SharedPreferencesUtils {
|
|||||||
_prefs = await SharedPreferences.getInstance();
|
_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 {
|
static Future<bool> setString(String key, String value) async {
|
||||||
return _prefs?.setString(key, value) ?? Future.value(false);
|
return _prefs?.setString(key, value) ?? Future.value(false);
|
||||||
}
|
}
|
||||||
@ -16,4 +73,4 @@ class SharedPreferencesUtils {
|
|||||||
static String? getString(String key) {
|
static String? getString(String key) {
|
||||||
return _prefs?.getString(key);
|
return _prefs?.getString(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,10 +1,24 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:get/get.dart';
|
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/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';
|
import 'package:starwork_flutter/routes/app_routes.dart';
|
||||||
|
|
||||||
class InputVerificationCodeController extends BaseController {
|
class InputVerificationCodeController extends BaseController {
|
||||||
|
final userApi = Get.find<UserApiService>();
|
||||||
|
final commonApi = Get.find<CommonApiService>();
|
||||||
|
|
||||||
|
var rawPhone = ''.obs;
|
||||||
var phone = ''.obs;
|
var phone = ''.obs;
|
||||||
var previousRoute = ''.obs;
|
var previousRoute = ''.obs;
|
||||||
|
|
||||||
@ -15,7 +29,7 @@ class InputVerificationCodeController extends BaseController {
|
|||||||
var countdownValue = 60.obs;
|
var countdownValue = 60.obs;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() async {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
|
|
||||||
final params = Get.parameters;
|
final params = Get.parameters;
|
||||||
@ -24,56 +38,92 @@ class InputVerificationCodeController extends BaseController {
|
|||||||
previousRoute.value = Get.previousRoute; // 如 "/login"
|
previousRoute.value = Get.previousRoute; // 如 "/login"
|
||||||
|
|
||||||
if (phoneParam != null && phoneParam.isNotEmpty) {
|
if (phoneParam != null && phoneParam.isNotEmpty) {
|
||||||
|
rawPhone.value = phoneParam;
|
||||||
phone.value = maskMiddleFive(phoneParam);
|
phone.value = maskMiddleFive(phoneParam);
|
||||||
// 开始倒计时(比如60秒)
|
if (previousRoute.value.contains(AppRoutes.login)) {
|
||||||
startCountdown();
|
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) {
|
String maskMiddleFive(String phone) {
|
||||||
if (phone.length < 8) return phone; // 如果手机号少于8位,不处理(或根据需求返回原值/报错)
|
if (phone.length < 8) return phone; // 如果手机号少于8位,不处理(或根据需求返回原值/报错)
|
||||||
|
|
||||||
// 假设手机号至少有 8 位,我们保留前 3 位,中间 5 位替换为 ****,显示后 3 位
|
|
||||||
// 13812345678 → 138****678
|
|
||||||
String firstPart = phone.substring(0, 3); // 前3位:138
|
String firstPart = phone.substring(0, 3); // 前3位:138
|
||||||
String middleReplaced = '*****'; // 中间5位替换为 ****
|
String middleReplaced = '*****'; // 中间5位替换为 ****
|
||||||
String lastPart = phone.substring(8); // 后3位:678
|
String lastPart = phone.substring(8); // 后3位:678
|
||||||
|
|
||||||
return '$firstPart$middleReplaced$lastPart'; // 拼接:138****678
|
return '$firstPart$middleReplaced$lastPart'; // 拼接:138****678
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 开始倒计时(比如60秒)
|
// 校验验证码
|
||||||
void startCountdown() {
|
void checkVerificationCode(String pin) {
|
||||||
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) {
|
|
||||||
if (previousRoute.value.contains(AppRoutes.login)) {
|
if (previousRoute.value.contains(AppRoutes.login)) {
|
||||||
_handleLogin();
|
Get.offAllNamed(AppRoutes.main);
|
||||||
} else if (previousRoute.value.contains(AppRoutes.forgotPassword)) {
|
} else if (previousRoute.value.contains(AppRoutes.forgotPassword)) {
|
||||||
_handleForgotPassword();
|
Get.toNamed(AppRoutes.setNewPassword);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleLogin() {
|
void _handleSeedVerificationCode({
|
||||||
Get.offAndToNamed(AppRoutes.main);
|
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:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pinput/pinput.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';
|
import 'package:starwork_flutter/views/login/inputVerificationCode/input_verification_code_controller.dart';
|
||||||
|
|
||||||
class InputVerificationCodeView
|
class InputVerificationCodeView
|
||||||
@ -47,6 +49,7 @@ class InputVerificationCodeView
|
|||||||
Pinput(
|
Pinput(
|
||||||
length: 6,
|
length: 6,
|
||||||
obscureText: false,
|
obscureText: false,
|
||||||
|
autofocus: true,
|
||||||
defaultPinTheme: PinTheme(
|
defaultPinTheme: PinTheme(
|
||||||
width: 50.w,
|
width: 50.w,
|
||||||
height: 50.w,
|
height: 50.w,
|
||||||
@ -63,7 +66,7 @@ class InputVerificationCodeView
|
|||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
onCompleted: (pin) {
|
onCompleted: (pin) {
|
||||||
controller.requestPhoneCodeLogin(pin);
|
controller.checkVerificationCode(pin);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -106,7 +109,15 @@ class InputVerificationCodeView
|
|||||||
} else {
|
} else {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
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(
|
child: Text(
|
||||||
'重新获取验证码'.tr,
|
'重新获取验证码'.tr,
|
||||||
|
|||||||
@ -2,21 +2,16 @@ import 'package:flutter/cupertino.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.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';
|
import 'package:starwork_flutter/routes/app_routes.dart';
|
||||||
|
|
||||||
class LoginController extends BaseController {
|
class LoginController extends BaseController {
|
||||||
final commonApi = Get.find<CommonApiService>();
|
|
||||||
|
|
||||||
int phoneNumberSize = 11;
|
int phoneNumberSize = 11;
|
||||||
TextEditingController phoneController = TextEditingController();
|
TextEditingController phoneController = TextEditingController();
|
||||||
TextEditingController passwordController = TextEditingController();
|
TextEditingController passwordController = TextEditingController();
|
||||||
@ -44,7 +39,6 @@ class LoginController extends BaseController {
|
|||||||
|
|
||||||
void _validateForm() {
|
void _validateForm() {
|
||||||
isFormValid.value = phoneController.text.length == phoneNumberSize;
|
isFormValid.value = phoneController.text.length == phoneNumberSize;
|
||||||
debugPrint('isFormValid: ${isFormValid.value}');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取手机验证码
|
// 获取手机验证码
|
||||||
@ -55,23 +49,15 @@ class LoginController extends BaseController {
|
|||||||
content: '1',
|
content: '1',
|
||||||
onConfirm: () async {
|
onConfirm: () async {
|
||||||
isPrivacyAgreementValid.value = 1;
|
isPrivacyAgreementValid.value = 1;
|
||||||
|
Get.toNamed(
|
||||||
|
AppRoutes.inputVerificationCode,
|
||||||
|
parameters: {
|
||||||
|
'phone': phoneController.text.trim(),
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
} else {
|
||||||
_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) {
|
|
||||||
Get.toNamed(
|
Get.toNamed(
|
||||||
AppRoutes.inputVerificationCode,
|
AppRoutes.inputVerificationCode,
|
||||||
parameters: {
|
parameters: {
|
||||||
@ -88,10 +74,10 @@ class LoginController extends BaseController {
|
|||||||
}) {
|
}) {
|
||||||
Get.dialog(
|
Get.dialog(
|
||||||
Dialog(
|
Dialog(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(14.r), // 圆角
|
borderRadius: BorderRadius.circular(14.r), // 圆角
|
||||||
),
|
),
|
||||||
backgroundColor: Colors.white,
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
32
pubspec.lock
32
pubspec.lock
@ -126,6 +126,14 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
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:
|
flutter_flavorizr:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@ -155,6 +163,14 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.9.3"
|
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:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -165,14 +181,6 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
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:
|
get:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -461,6 +469,14 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.1"
|
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:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
|
|||||||
@ -25,12 +25,15 @@ dependencies:
|
|||||||
shared_preferences: ^2.2.3
|
shared_preferences: ^2.2.3
|
||||||
# 屏幕适配
|
# 屏幕适配
|
||||||
flutter_screenutil: ^5.9.3
|
flutter_screenutil: ^5.9.3
|
||||||
# 提示
|
|
||||||
fluttertoast: ^8.2.8
|
|
||||||
# 验证码输入框
|
# 验证码输入框
|
||||||
pinput: ^5.0.1
|
pinput: ^5.0.1
|
||||||
# 网络请求库
|
# 网络请求库
|
||||||
dio: ^5.9.0
|
dio: ^5.9.0
|
||||||
|
# loading 动画库 需要指定flutter_spinkit为固定版本
|
||||||
|
flutter_easyloading: ^3.0.0
|
||||||
|
flutter_spinkit: 5.2.1
|
||||||
|
# 骨架屏
|
||||||
|
skeletonizer: ^1.4.3
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user