diff --git a/.vscode/settings.json b/.vscode/settings.json index 25bab7a..8482c2c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "cSpell.words": ["Getx", "tdesign"] + "cSpell.words": ["fenix", "Getx", "tdesign"] } diff --git a/lib/app/bindings/initial_binding.dart b/lib/app/bindings/initial_binding.dart index 620ccce..664b607 100644 --- a/lib/app/bindings/initial_binding.dart +++ b/lib/app/bindings/initial_binding.dart @@ -1,63 +1,15 @@ import 'package:get/get.dart'; -import 'package:dio/dio.dart'; -import 'package:flutter/foundation.dart'; // 导入 debugPrint import 'package:get_storage/get_storage.dart'; import 'package:problem_check_system/data/providers/connectivity_provider.dart'; +import 'package:problem_check_system/data/providers/dio_provider.dart'; import 'package:problem_check_system/data/providers/local_database.dart'; class InitialBinding implements Bindings { @override void dependencies() { - // 全局注册 GetStorage 实例 - 也改为使用 put 确保立即创建 Get.put(GetStorage(), permanent: true); - // 全局注册 Dio 实例 - 使用 put 而不是 lazyPut,并设置为永久 - Get.put(createDioInstance()); - // - Get.put(LocalDatabase()); - Get.put(ConnectivityProvider()); - } - - // 提取创建 Dio 实例的逻辑到单独的方法 - Dio createDioInstance() { - final dio = Dio( - BaseOptions( - baseUrl: 'https://xhdev.anxincloud.cn', // 替换为你的服务器地址 - connectTimeout: const Duration(seconds: 15), - receiveTimeout: const Duration(seconds: 15), - ), - ); - - // 添加日志拦截器,仅在调试模式下打印日志 - if (kDebugMode) { - dio.interceptors.add( - LogInterceptor( - requestBody: true, - responseBody: true, - logPrint: (o) => debugPrint(o.toString()), - ), - ); - } - - // 添加认证拦截器 - dio.interceptors.add( - InterceptorsWrapper( - onRequest: (options, handler) { - final token = ""; //todo 获取token - if (token != null && token.isNotEmpty) { - options.headers['Authorization'] = 'Bearer $token'; - } - return handler.next(options); - }, - onError: (DioException e, handler) { - if (e.response?.statusCode == 401) { - // 如果收到 401 Unauthorized 错误,跳转回登录页 - Get.offAllNamed('/login'); - } - return handler.next(e); - }, - ), - ); - - return dio; + Get.put(DioProvider(), permanent: true); + Get.put(LocalDatabase(), permanent: true); + Get.put(ConnectivityProvider(), permanent: true); } } diff --git a/lib/data/models/auth_model.dart b/lib/data/models/auth_model.dart new file mode 100644 index 0000000..fa1b2a6 --- /dev/null +++ b/lib/data/models/auth_model.dart @@ -0,0 +1,53 @@ +/// 登录请求模型 +class LoginRequest { + final String username; + final String password; + final String wechatJsCode; + + LoginRequest({ + required this.username, + required this.password, + this.wechatJsCode = "", + }); + + Map toJson() { + return { + 'username': username, + 'password': password, + 'wechatJsCode': wechatJsCode, + }; + } + + // 从 Map 创建 LoginRequest 对象 + factory LoginRequest.fromJson(Map json) { + return LoginRequest( + username: json['username'] as String, + password: json['password'] as String, + wechatJsCode: json['wechatJsCode'] as String, + ); + } +} + +/// 登录响应模型 +class LoginResponse { + final String token; + final String refreshToken; + final int expires; + final String name; + + LoginResponse({ + required this.token, + required this.refreshToken, + required this.expires, + required this.name, + }); + + factory LoginResponse.fromJson(Map json) { + return LoginResponse( + token: json['token'] ?? '', + refreshToken: json['refresh_token'] ?? '', + expires: json['expires'] ?? '', + name: json['name'] ?? '', + ); + } +} diff --git a/lib/data/models/login_model.dart b/lib/data/models/login_model.dart deleted file mode 100644 index e214c80..0000000 --- a/lib/data/models/login_model.dart +++ /dev/null @@ -1,12 +0,0 @@ -class LoginModel { - final String username; - final String password; - - // 使用 const 构造函数,并要求所有字段在创建时必须被提供 - const LoginModel({required this.username, required this.password}); - - // (可选)提供一个 toMap 方法,方便将对象转换为 JSON 或 Map - Map toMap() { - return {'username': username, 'password': password}; - } -} diff --git a/lib/data/providers/auth_provider.dart b/lib/data/providers/auth_provider.dart deleted file mode 100644 index d614011..0000000 --- a/lib/data/providers/auth_provider.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:dio/dio.dart'; -import 'package:problem_check_system/data/models/login_model.dart'; - -class AuthProvider { - final String _signInUrl = '/api/Accounts/SignIn'; - - final Dio _dio; - - AuthProvider({required Dio dio}) : _dio = dio; - - Future signIn(LoginModel loginModel) async { - try { - final response = await _dio.post(_signInUrl, data: loginModel.toMap()); - return response; - } on DioException { - rethrow; - } - } -} diff --git a/lib/data/providers/dio_provider.dart b/lib/data/providers/dio_provider.dart new file mode 100644 index 0000000..51ea891 --- /dev/null +++ b/lib/data/providers/dio_provider.dart @@ -0,0 +1,265 @@ +import 'package:dio/dio.dart'; +import 'package:flutter/foundation.dart'; +import 'package:get/get.dart' hide Response; +import 'package:pretty_dio_logger/pretty_dio_logger.dart'; +import 'package:problem_check_system/data/repositories/auth_repository.dart'; +import 'package:problem_check_system/modules/auth/controllers/auth_controller.dart'; + +// DioProvider 是一个 GetxService,确保它在应用生命周期内是单例的。 +// 它负责初始化和配置 Dio 实例,并添加所有拦截器。 +class DioProvider extends GetxService { + static const String _baseUrl = 'https://xh.anxincloud.cn'; + + late final Dio _dio; + + @override + Future onInit() async { + super.onInit(); + _initDio(); + } + + // 初始化 Dio 并配置基础选项。 + void _initDio() { + _dio = Dio( + BaseOptions( + baseUrl: _baseUrl, + connectTimeout: const Duration(seconds: 30), + receiveTimeout: const Duration(seconds: 30), + sendTimeout: const Duration(seconds: 30), + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + ), + ); + + // 添加拦截器。拦截器的顺序非常关键: + // 1. 认证拦截器 (AuthInterceptor): 负责添加 token 和处理 401 错误,优先级最高。 + // 2. 错误拦截器 (ErrorInterceptor): 处理通用错误,作为所有其他拦截器之后的最终捕获。 + // 3. 日志拦截器 (LoggerInterceptor): 在调试模式下打印详细日志,方便开发。 + _dio.interceptors.addAll(_getInterceptors()); + } + + List _getInterceptors() { + return [ + _getAuthInterceptor(), + _getErrorInterceptor(), + if (kDebugMode) _getLoggerInterceptor(), + ]; + } + + /// 认证拦截器:处理请求头中的 token 添加和 401 错误重试。 + Interceptor _getAuthInterceptor() { + return InterceptorsWrapper( + // 在请求发送前执行 + onRequest: (options, handler) async { + try { + // 尝试获取 AuthRepository 并添加 token 到请求头。 + final authRepository = Get.find(); + final token = authRepository.getToken(); + if (token != null && token.isNotEmpty) { + options.headers['Authorization'] = 'Bearer $token'; + } + } catch (e) { + // 如果 AuthRepository 尚未初始化(例如在登录或注册时),则跳过添加认证头。 + debugPrint('AuthRepository 未找到。跳过认证头。'); + } + return handler.next(options); + }, + // 在接收到错误时执行 + onError: (error, handler) async { + // 专门处理 401 Unauthorized 错误。 + if (error.response?.statusCode == 401) { + try { + final authRepository = Get.find(); + // 尝试刷新 token。 + await authRepository.refreshToken(); + + // 如果刷新成功,更新请求头并重试原始请求。 + final newOptions = Options( + method: error.requestOptions.method, + headers: error.requestOptions.headers + ..['Authorization'] = 'Bearer ${authRepository.getToken()}', + ); + + final response = await _dio.request( + error.requestOptions.path, + data: error.requestOptions.data, + queryParameters: error.requestOptions.queryParameters, + options: newOptions, + ); + + // 使用 handler.resolve() 返回重试的结果,阻止错误继续传递。 + return handler.resolve(response); + } on Exception catch (e) { + debugPrint('刷新 token 失败: $e'); + // 如果刷新 token 失败,清除认证数据并跳转到登录页。 + try { + final authController = Get.find(); + await authController.logout(); + } catch (e) { + // 如果 AuthController 找不到,作为备用方案手动清除数据并导航。 + final authRepository = Get.find(); + authRepository.clearAuthData(); + if (Get.currentRoute != '/login') { + Get.offAllNamed('/login'); + } + } + // 传播原始错误,让下一个拦截器(通用错误拦截器)处理。 + return handler.next(error); + } + } + // 对于所有其他非 401 的错误,将错误传递给下一个拦截器。 + return handler.next(error); + }, + ); + } + + /// 日志拦截器:在调试模式下打印详细的请求和响应日志。 + Interceptor _getLoggerInterceptor() { + return PrettyDioLogger( + requestHeader: true, + requestBody: true, + responseHeader: true, + responseBody: true, + error: true, + compact: false, + maxWidth: 90, + ); + } + + /// 错误拦截器:处理通用的网络和服务器端错误,并显示 Snackbar 提示。 + Interceptor _getErrorInterceptor() { + return InterceptorsWrapper( + onError: (error, handler) { + // 处理网络连接超时或未知网络错误。 + if (error.type == DioExceptionType.connectionTimeout || + error.type == DioExceptionType.receiveTimeout || + error.type == DioExceptionType.sendTimeout) { + Get.snackbar('网络超时', '请检查网络连接后重试'); + } else if (error.type == DioExceptionType.unknown) { + Get.snackbar('网络异常', '请检查网络连接后重试'); + } + + // 这部分代码只会在 AuthInterceptor 没有处理的错误(即非 401)时执行。 + if (error.response != null) { + final message = _handleStatusCode(error.response!); + Get.snackbar('请求错误', message); + } + + return handler.next(error); + }, + ); + } + + /// 辅助方法:根据 HTTP 状态码返回用户友好的错误信息。 + String _handleStatusCode(Response response) { + switch (response.statusCode) { + case 400: + return response.data?['detail'] ?? '请求参数错误'; + case 401: + return response.data?['detail'] ?? + '未授权,请重新登录'; // 注意:对于 401 错误,这行代码理想情况下不会被执行。 + case 403: + return response.data?['detail'] ?? '访问被拒绝'; + case 404: + return response.data?['detail'] ?? '请求资源不存在'; + case 422: + final errors = response.data?['errors']; + if (errors != null && errors is Map && errors.isNotEmpty) { + return errors.values.first?.first?.toString() ?? '数据验证失败'; + } + return response.data?['detail'] ?? '数据验证失败'; + case 500: + return response.data?['detail'] ?? '服务器内部错误'; + case 502: + return response.data?['detail'] ?? '网关错误'; + case 503: + return response.data?['detail'] ?? '服务不可用'; + default: + return response.data?['detail'] ?? '网络异常(${response.statusCode})'; + } + } + + void clear() { + _dio.interceptors.clear(); + } + + /// 新增的请求方法 + + /// 发送 GET 请求 + Future get( + String path, { + Map? queryParameters, + Options? options, + CancelToken? cancelToken, + ProgressCallback? onReceiveProgress, + }) async { + return await _dio.get( + path, + queryParameters: queryParameters, + options: options, + cancelToken: cancelToken, + onReceiveProgress: onReceiveProgress, + ); + } + + /// 发送 POST 请求 + Future post( + String path, { + dynamic data, + Map? queryParameters, + Options? options, + CancelToken? cancelToken, + ProgressCallback? onSendProgress, + ProgressCallback? onReceiveProgress, + }) async { + return await _dio.post( + path, + data: data, + queryParameters: queryParameters, + options: options, + cancelToken: cancelToken, + onSendProgress: onSendProgress, + onReceiveProgress: onReceiveProgress, + ); + } + + /// 发送 PUT 请求 + Future put( + String path, { + dynamic data, + Map? queryParameters, + Options? options, + CancelToken? cancelToken, + ProgressCallback? onSendProgress, + ProgressCallback? onReceiveProgress, + }) async { + return await _dio.put( + path, + data: data, + queryParameters: queryParameters, + options: options, + cancelToken: cancelToken, + onSendProgress: onSendProgress, + onReceiveProgress: onReceiveProgress, + ); + } + + /// 发送 DELETE 请求 + Future delete( + String path, { + dynamic data, + Map? queryParameters, + Options? options, + CancelToken? cancelToken, + }) async { + return await _dio.delete( + path, + data: data, + queryParameters: queryParameters, + options: options, + cancelToken: cancelToken, + ); + } +} diff --git a/lib/data/providers/user_provider.dart b/lib/data/providers/user_provider.dart deleted file mode 100644 index a25d44c..0000000 --- a/lib/data/providers/user_provider.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:dio/dio.dart'; - -class UserProvider { - final Dio _dio; - - // 通过构造函数注入 Dio 实例 - UserProvider({required Dio dio}) : _dio = dio; - - /// 调用 API 修改用户密码 - /// - /// @param newPassword 新密码 - Future changePassword(String newPassword) async { - try { - final response = await _dio.post( - '/api/change_password', // 替换为你的修改密码接口地址 - data: { - 'new_password': newPassword, - }, - ); - return response; - } on DioException catch (e) { - // 抛出 DioException 以便在控制器中处理 - throw e; - } - } - - /// 调用 API 获取用户信息 - /// - /// 这个方法可以用于获取用户的姓名、手机号等信息 - Future getUserInfo() async { - try { - final response = await _dio.get('/api/user_info'); // 替换为你的用户信息接口地址 - return response; - } on DioException catch (e) { - throw e; - } - } -} \ No newline at end of file diff --git a/lib/data/repositories/auth_repository.dart b/lib/data/repositories/auth_repository.dart index e69de29..8386862 100644 --- a/lib/data/repositories/auth_repository.dart +++ b/lib/data/repositories/auth_repository.dart @@ -0,0 +1,138 @@ +import 'dart:developer'; + +import 'package:dio/dio.dart'; +import 'package:get/get.dart'; +import 'package:get_storage/get_storage.dart'; +import 'package:problem_check_system/data/models/auth_model.dart'; +import 'package:problem_check_system/data/providers/connectivity_provider.dart'; +import 'package:problem_check_system/data/providers/dio_provider.dart'; + +class AuthRepository extends GetxService { + final DioProvider dioProvider; + final GetStorage storage; + final ConnectivityProvider connectivityProvider; + + AuthRepository({ + required this.dioProvider, + required this.storage, + required this.connectivityProvider, + }); + + static const String _tokenKey = 'token'; + static const String _refreshTokenKey = 'refresh_token'; + static const String _loginKey = 'user'; + static const String _rememberPassword = 'remember_password'; + + void saveToken(String token) { + storage.write(_tokenKey, token); + } + + String? getToken() { + return storage.read(_tokenKey); + } + + void saveRefreshToken(String refreshToken) { + storage.write(_refreshTokenKey, refreshToken); + } + + String? getRefreshToken() { + return storage.read(_refreshTokenKey); + } + + void addLoginKey(LoginRequest login) { + storage.write(_loginKey, login.toJson()); + } + + /// 登录请求 + LoginRequest getLoginKey() { + final loginData = storage.read(_loginKey); + + // 检查是否找到数据,并进行反序列化 + if (loginData != null) { + // 确保类型正确,然后进行反序列化 + return LoginRequest.fromJson(Map.from(loginData)); + } + + // 如果没有找到数据,返回一个默认的空对象 + return LoginRequest(username: '', password: ''); + } + + void removeLoginKey() { + storage.remove(_loginKey); + } + + void addRememberPassword(bool remembered) { + storage.write(_rememberPassword, remembered); + } + + bool getRememberPassword() { + return storage.read(_rememberPassword); + } + + void clearAuthData() { + storage.remove(_tokenKey); + storage.remove(_refreshTokenKey); + } + + // 是否在线 + bool get isOnline { + return connectivityProvider.isOnline.value; + } + + /// Check if a user is currently logged in by verifying the existence of a token. + bool isLoggedIn() { + final token = getToken(); + return token != null && token.isNotEmpty; + } + + /// Handles the user login process by calling the API and saving the response. + Future login(LoginRequest request) async { + try { + final response = await dioProvider.post( + '/api/Accounts/SignIn', + data: request.toJson(), + ); + + final loginResponse = LoginResponse.fromJson(response.data); + return loginResponse; + } catch (e) { + throw Exception(e); + } + } + + /// Refreshes the authentication token using the refresh token. + Future refreshToken() async { + final refreshToken = getRefreshToken(); + if (refreshToken == null || refreshToken.isEmpty) { + throw Exception('没有可用的刷新token'); + } + + try { + final response = await dioProvider.post( + '/auth/refresh', + data: {'refresh_token': refreshToken}, + ); + + final authResponse = LoginResponse.fromJson(response.data); + saveToken(authResponse.token); + saveRefreshToken(authResponse.refreshToken); + + return authResponse; + } catch (e) { + throw Exception(e); + } + } + + /// Logs the user out by calling the API and then clearing local data. + Future logout() async { + try { + // Attempt to call the API, but even if it fails, continue to clear local data. + await dioProvider.post('/auth/logout'); + } on DioException catch (e) { + log('退出登录API调用失败: ${e.message}'); + } finally { + // Regardless of API success or failure, always clear local storage. + clearAuthData(); + } + } +} diff --git a/lib/modules/auth/bindings/auth_binding.dart b/lib/modules/auth/bindings/auth_binding.dart index bc7b64e..217da5f 100644 --- a/lib/modules/auth/bindings/auth_binding.dart +++ b/lib/modules/auth/bindings/auth_binding.dart @@ -1,23 +1,33 @@ import 'package:get/get.dart'; -import 'package:dio/dio.dart'; import 'package:get_storage/get_storage.dart'; +import 'package:problem_check_system/data/providers/connectivity_provider.dart'; +import 'package:problem_check_system/data/providers/dio_provider.dart'; +import 'package:problem_check_system/data/repositories/auth_repository.dart'; import 'package:problem_check_system/modules/auth/controllers/auth_controller.dart'; -import 'package:problem_check_system/data/providers/auth_provider.dart'; class AuthBinding implements Bindings { @override void dependencies() { // 1. 注入数据提供者 (AuthProvider),它依赖于 Dio // 依赖注入通过构造函数完成,使代码更易于测试 - Get.lazyPut(() => AuthProvider(dio: Get.find())); + final DioProvider dioProvider = Get.find(); + final GetStorage storage = Get.find(); + final ConnectivityProvider connectivityProvider = + Get.find(); + + Get.lazyPut( + () => AuthRepository( + dioProvider: dioProvider, + storage: storage, + connectivityProvider: connectivityProvider, + ), + ); // 2. 注入控制器 (AuthController),它依赖于 AuthProvider // 控制器通过 Get.find() 获取已注入的依赖 Get.lazyPut( - () => AuthController( - authProvider: Get.find(), - storage: Get.find(), - ), + () => AuthController(authRepository: Get.find()), + fenix: true, ); } } diff --git a/lib/modules/auth/controllers/auth_controller.dart b/lib/modules/auth/controllers/auth_controller.dart index a7b4330..d17b332 100644 --- a/lib/modules/auth/controllers/auth_controller.dart +++ b/lib/modules/auth/controllers/auth_controller.dart @@ -1,62 +1,45 @@ -// lib/modules/auth/controllers/auth_controller.dart -import 'package:flutter/material.dart'; // 导入此库以使用 TextEditingController +import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:dio/dio.dart'; -import 'package:get_storage/get_storage.dart'; -import 'package:connectivity_plus/connectivity_plus.dart'; -import 'package:problem_check_system/data/providers/auth_provider.dart'; -import 'package:problem_check_system/data/models/login_model.dart'; +import 'package:problem_check_system/data/models/auth_model.dart'; import 'package:problem_check_system/app/routes/app_routes.dart'; +import 'package:problem_check_system/data/repositories/auth_repository.dart'; class AuthController extends GetxController { - final AuthProvider _authProvider; - final GetStorage _storage; - - // 将 TextEditingController 移动到控制器中,由控制器管理它们的生命周期 - late final TextEditingController usernameController = TextEditingController(); - late final TextEditingController passwordController = TextEditingController(); + final AuthRepository _authRepository; + final TextEditingController usernameController = TextEditingController(); + final TextEditingController passwordController = TextEditingController(); final isLoading = false.obs; final rememberPassword = false.obs; - static const _usernameKey = 'username'; - static const _passwordKey = 'password'; - - AuthController({ - required AuthProvider authProvider, - required GetStorage storage, - }) : _authProvider = authProvider, - _storage = storage; + AuthController({required AuthRepository authRepository}) + : _authRepository = authRepository; @override void onInit() { super.onInit(); - _loadSavedCredentials(); + _loadRememberedUser(); } - void _loadSavedCredentials() { - final savedUsername = _storage.read(_usernameKey); - final savedPassword = _storage.read(_passwordKey); - - if (savedUsername != null && savedPassword != null) { - // 从本地存储加载值,并设置给控制器 - usernameController.text = savedUsername; - passwordController.text = savedPassword; - rememberPassword.value = true; + void _loadRememberedUser() { + final remember = _authRepository.getRememberPassword(); + rememberPassword.value = remember; + if (remember) { + final loginData = _authRepository.getLoginKey(); + usernameController.text = loginData.username; + passwordController.text = loginData.password; } } - String getToken() { - return _storage.read('token') ?? ''; + /// Check if the user is already logged in by delegating to the repository. + bool isLoggedIn() { + return _authRepository.isLoggedIn(); } - // 移除了 updateUsername 和 updatePassword 方法,因为不再需要它们来同步值。 - // TextEditingController 本身就是值的来源。 - + /// 登录逻辑 Future login() async { - // 直接从控制器获取文本内容 - final username = usernameController.text; - final password = passwordController.text; + final username = usernameController.text.trim(); + final password = passwordController.text.trim(); if (username.isEmpty || password.isEmpty) { Get.snackbar('输入错误', '用户名和密码不能为空'); @@ -64,79 +47,53 @@ class AuthController extends GetxController { } isLoading.value = true; + final loginData = LoginRequest(username: username, password: password); - var connectivityResult = await (Connectivity().checkConnectivity()); - bool isConnected = - connectivityResult.contains(ConnectivityResult.mobile) || - connectivityResult.contains(ConnectivityResult.wifi) || - connectivityResult.contains(ConnectivityResult.ethernet); - - if (isConnected) { - await _onlineLogin(username, password); + if (_authRepository.isOnline) { + await _onlineLogin(loginData); } else { - await _offlineLogin(username, password); + _offlineLogin(loginData); } - isLoading.value = false; } - Future _onlineLogin(String username, String password) async { + /// 在线登录 + Future _onlineLogin(LoginRequest loginRequest) async { try { - final loginData = LoginModel(username: username, password: password); - - final response = await _authProvider.signIn(loginData); - - if (response.statusCode == 200 && response.data['token'] != null) { - final token = response.data['token']; - final refreshToken = response.data['refreshToken']; - - _storage.write('token', token); - _storage.write('refreshToken', refreshToken); - - // 如果记住密码,保存用户名和密码 - if (rememberPassword.value) { - _storage.write(_usernameKey, username); - _storage.write(_passwordKey, password); - } else { - // 否则清除所有本地凭据 - _storage.remove(_usernameKey); - _storage.remove(_passwordKey); - } - - Get.offAllNamed(AppRoutes.home); - } else { - Get.snackbar('登录失败', '服务器返回了异常数据'); - } - } on DioException catch (e) { - if (e.response?.data != null && e.response?.data['detail'] != null) { - final serverMessage = e.response!.data['detail']; - Get.snackbar('登录失败', serverMessage); + var loginResponse = await _authRepository.login(loginRequest); + // 登录成功后 + _authRepository.saveToken(loginResponse.token); + _authRepository.saveRefreshToken(loginResponse.refreshToken); + _authRepository.addRememberPassword(rememberPassword.value); + + if (rememberPassword.value) { + _authRepository.addLoginKey(loginRequest); } else { - Get.snackbar('网络错误', '登录失败,请检查您的网络连接'); + _authRepository.removeLoginKey(); } + Get.offAllNamed(AppRoutes.home); } catch (e) { - Get.snackbar('错误', '发生未知错误: ${e.toString()}'); + throw Exception(e); } } - Future _offlineLogin(String username, String password) async { - final storedUsername = _storage.read(_usernameKey); - final storedPassword = _storage.read(_passwordKey); + void _offlineLogin(LoginRequest loginRequest) { + final loginData = _authRepository.getLoginKey(); - if (storedUsername != null && - storedPassword != null && - storedUsername == username && - storedPassword == password) { + if (loginData.username == loginRequest.username && + loginData.password == loginRequest.password) { Get.offAllNamed(AppRoutes.home); Get.snackbar('离线登录成功', '您已离线登录到系统'); } else { - Get.snackbar('离线登录失败', '无网络连接,且无法验证本地凭证'); + Get.snackbar('登录失败', '无网络连接,且无法验证本地凭证'); } } + /// Handles the logout process by delegating to the repository. Future logout() async { - _storage.remove('token'); - _storage.remove('refreshToken'); + isLoading.value = true; + await _authRepository.logout(); + isLoading.value = false; Get.offAllNamed(AppRoutes.login); } } diff --git a/lib/modules/auth/views/login_page.dart b/lib/modules/auth/views/login_page.dart index e795857..fa72f87 100644 --- a/lib/modules/auth/views/login_page.dart +++ b/lib/modules/auth/views/login_page.dart @@ -62,7 +62,7 @@ class LoginPage extends GetView { width: 334.w, height: 574.5.h, decoration: BoxDecoration( - color: const Color(0xFFFFFFFF).withOpacity(0.6), + color: const Color(0xFFFFFFFF).withValues(alpha: 153), borderRadius: BorderRadius.all(Radius.circular(23.5.r)), ), padding: EdgeInsets.all(24.w), diff --git a/lib/modules/home/bindings/home_binding.dart b/lib/modules/home/bindings/home_binding.dart index 464ace3..8b3e17f 100644 --- a/lib/modules/home/bindings/home_binding.dart +++ b/lib/modules/home/bindings/home_binding.dart @@ -1,9 +1,7 @@ -import 'package:dio/dio.dart'; import 'package:get/get.dart'; -import 'package:get_storage/get_storage.dart'; -import 'package:problem_check_system/data/providers/auth_provider.dart'; import 'package:problem_check_system/data/providers/connectivity_provider.dart'; import 'package:problem_check_system/data/providers/local_database.dart'; +import 'package:problem_check_system/data/repositories/auth_repository.dart'; import 'package:problem_check_system/modules/auth/controllers/auth_controller.dart'; import 'package:problem_check_system/modules/home/controllers/home_controller.dart'; import 'package:problem_check_system/modules/my/controllers/my_controller.dart'; @@ -12,7 +10,6 @@ import 'package:problem_check_system/modules/problem/controllers/problem_control class HomeBinding implements Bindings { @override void dependencies() { - final Dio dio = Get.find(); final LocalDatabase database = Get.find(); final ConnectivityProvider connectivityProvider = Get.find(); @@ -21,18 +18,23 @@ class HomeBinding implements Bindings { Get.lazyPut( () => ProblemController( localDatabase: database, - dio: dio, + connectivityProvider: connectivityProvider, + dioProvider: Get.find(), ), fenix: true, ); Get.lazyPut(() => MyController()); - Get.lazyPut(() => AuthProvider(dio: dio)); - Get.lazyPut( - () => AuthController( - authProvider: Get.find(), - storage: Get.find(), + Get.lazyPut( + () => AuthRepository( + dioProvider: Get.find(), + storage: Get.find(), + connectivityProvider: connectivityProvider, ), ); + Get.lazyPut( + () => AuthController(authRepository: Get.find()), + fenix: true, + ); } } diff --git a/lib/modules/my/bingdings/change_password_binding.dart b/lib/modules/my/bingdings/change_password_binding.dart index ff0f9f2..aa0323c 100644 --- a/lib/modules/my/bingdings/change_password_binding.dart +++ b/lib/modules/my/bingdings/change_password_binding.dart @@ -1,17 +1,20 @@ -import 'package:dio/dio.dart'; import 'package:get/get.dart'; +import 'package:problem_check_system/data/repositories/auth_repository.dart'; import 'package:problem_check_system/modules/my/controllers/change_password_controller.dart'; -import 'package:problem_check_system/data/providers/user_provider.dart'; // 假设你有这个 Provider class ChangePasswordBinding implements Bindings { @override void dependencies() { - // 如果 ChangePasswordController 依赖于 UserProvider,则需要先注入它 - final dio = Get.find(); - Get.lazyPut(() => UserProvider(dio: dio)); + Get.lazyPut( + () => AuthRepository( + dioProvider: Get.find(), + storage: Get.find(), + connectivityProvider: Get.find(), + ), + ); Get.lazyPut( // 如果需要,可以在这里注入依赖 - () => ChangePasswordController(userProvider: Get.find()), + () => ChangePasswordController(authRepository: Get.find()), ); } -} \ No newline at end of file +} diff --git a/lib/modules/my/controllers/change_password_controller.dart b/lib/modules/my/controllers/change_password_controller.dart index 96865f4..2971b79 100644 --- a/lib/modules/my/controllers/change_password_controller.dart +++ b/lib/modules/my/controllers/change_password_controller.dart @@ -1,5 +1,5 @@ import 'package:get/get.dart'; -import 'package:problem_check_system/data/providers/user_provider.dart'; // 假设你有一个用户相关的 Provider +import 'package:problem_check_system/data/repositories/auth_repository.dart'; class ChangePasswordController extends GetxController { // 响应式变量,用于存储新密码和确认密码 @@ -8,9 +8,9 @@ class ChangePasswordController extends GetxController { var isLoading = false.obs; // 假设你需要一个 UserProvider 来处理 API 请求 - final UserProvider _userProvider; - ChangePasswordController({required UserProvider userProvider}) - : _userProvider = userProvider; + final AuthRepository _authRepository; + ChangePasswordController({required AuthRepository authRepository}) + : _authRepository = authRepository; // 更新新密码 void updateNewPassword(String value) { @@ -54,4 +54,4 @@ class ChangePasswordController extends GetxController { isLoading.value = false; } } -} \ No newline at end of file +} diff --git a/lib/modules/my/views/change_password.dart b/lib/modules/my/views/change_password.dart index 36f4922..fcfe81b 100644 --- a/lib/modules/my/views/change_password.dart +++ b/lib/modules/my/views/change_password.dart @@ -9,7 +9,8 @@ class ChangePasswordPage extends StatelessWidget { @override Widget build(BuildContext context) { // 获取控制器实例 - final ChangePasswordController controller = Get.find(); + final ChangePasswordController controller = + Get.find(); return Scaffold( appBar: _buildAppBar(), @@ -48,10 +49,7 @@ class ChangePasswordPage extends StatelessWidget { centerTitle: true, title: const Text( '修改密码', - style: TextStyle( - color: Colors.black, - fontWeight: FontWeight.bold, - ), + style: TextStyle(color: Colors.black, fontWeight: FontWeight.bold), ), leading: IconButton( icon: const Icon(Icons.arrow_back_ios, color: Colors.black), @@ -74,7 +72,7 @@ class ChangePasswordPage extends StatelessWidget { borderRadius: BorderRadius.circular(12.r), boxShadow: [ BoxShadow( - color: Colors.grey.withOpacity(0.1), + color: Colors.grey.withValues(alpha: 25.5), spreadRadius: 2, blurRadius: 5, offset: const Offset(0, 3), @@ -98,10 +96,7 @@ class ChangePasswordPage extends StatelessWidget { obscureText: obscureText, decoration: InputDecoration( hintText: hintText, - hintStyle: TextStyle( - color: Colors.grey, - fontSize: 14.sp, - ), + hintStyle: TextStyle(color: Colors.grey, fontSize: 14.sp), border: InputBorder.none, // 移除下划线 isDense: true, contentPadding: EdgeInsets.zero, @@ -129,10 +124,7 @@ class ChangePasswordPage extends StatelessWidget { ), child: Text( '取消', - style: TextStyle( - fontSize: 16.sp, - color: const Color(0xFF5695FD), - ), + style: TextStyle(fontSize: 16.sp, color: const Color(0xFF5695FD)), ), ), ), @@ -153,14 +145,11 @@ class ChangePasswordPage extends StatelessWidget { ), child: Text( '确定', - style: TextStyle( - fontSize: 16.sp, - color: Colors.white, - ), + style: TextStyle(fontSize: 16.sp, color: Colors.white), ), ), ), ], ); } -} \ No newline at end of file +} diff --git a/lib/modules/problem/bindings/problem_binding.dart b/lib/modules/problem/bindings/problem_binding.dart index 4fc8e2e..4b0ec50 100644 --- a/lib/modules/problem/bindings/problem_binding.dart +++ b/lib/modules/problem/bindings/problem_binding.dart @@ -1,5 +1,4 @@ import 'package:get/get.dart'; -import 'package:dio/dio.dart'; import 'package:problem_check_system/data/providers/connectivity_provider.dart'; import 'package:problem_check_system/modules/problem/controllers/problem_controller.dart'; import 'package:problem_check_system/data/providers/local_database.dart'; @@ -13,7 +12,7 @@ class ProblemBinding implements Bindings { Get.lazyPut( () => ProblemController( localDatabase: Get.find(), - dio: Get.find(), + dioProvider: Get.find(), connectivityProvider: Get.find(), ), ); diff --git a/lib/modules/problem/controllers/problem_controller.dart b/lib/modules/problem/controllers/problem_controller.dart index b9d2499..b15257b 100644 --- a/lib/modules/problem/controllers/problem_controller.dart +++ b/lib/modules/problem/controllers/problem_controller.dart @@ -5,6 +5,7 @@ import 'package:get/get.dart' hide MultipartFile, FormData; import 'package:flutter/material.dart'; import 'dart:io'; import 'package:path/path.dart' as path; +import 'package:problem_check_system/data/providers/dio_provider.dart'; import 'package:problem_check_system/modules/problem/views/widgets/custom_data_range_dropdown.dart'; import 'package:problem_check_system/data/models/problem_model.dart'; import 'package:problem_check_system/data/providers/local_database.dart'; @@ -34,7 +35,7 @@ class ProblemController extends GetxController /// 是否加载中 final RxBool isLoading = false.obs; - final Dio _dio; + final DioProvider _dioProvider; final ConnectivityProvider _connectivityProvider; late TabController tabController; @@ -50,10 +51,10 @@ class ProblemController extends GetxController ProblemController({ required LocalDatabase localDatabase, - required Dio dio, + required DioProvider dioProvider, required ConnectivityProvider connectivityProvider, }) : _localDatabase = localDatabase, - _dio = dio, + _dioProvider = dioProvider, _connectivityProvider = connectivityProvider; @override @@ -334,7 +335,7 @@ class ProblemController extends GetxController } } - final response = await _dio.post( + final response = await _dioProvider.post( 'https://your-server.com/api/problems', data: formData, options: Options( diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 84fbaae..e2ff0cb 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,13 +8,11 @@ import Foundation import connectivity_plus import file_selector_macos import path_provider_foundation -import shared_preferences_foundation import sqflite_darwin func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) - SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) } diff --git a/pubspec.lock b/pubspec.lock index a96742b..9e881ac 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -129,14 +129,6 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.1.4" - file: - dependency: transitive - description: - name: file - sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.flutter-io.cn" - source: hosted - version: "7.0.1" file_selector_linux: dependency: transitive description: @@ -552,62 +544,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.1.8" - shared_preferences: + pretty_dio_logger: dependency: "direct main" description: - name: shared_preferences - sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.5.3" - shared_preferences_android: - dependency: transitive - description: - name: shared_preferences_android - sha256: "5bcf0772a761b04f8c6bf814721713de6f3e5d9d89caf8d3fe031b02a342379e" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.4.11" - shared_preferences_foundation: - dependency: transitive - description: - name: shared_preferences_foundation - sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.5.4" - shared_preferences_linux: - dependency: transitive - description: - name: shared_preferences_linux - sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + name: pretty_dio_logger + sha256: "36f2101299786d567869493e2f5731de61ce130faa14679473b26905a92b6407" url: "https://pub.flutter-io.cn" source: hosted - version: "2.4.1" - shared_preferences_platform_interface: - dependency: transitive - description: - name: shared_preferences_platform_interface - sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.4.1" - shared_preferences_web: - dependency: transitive - description: - name: shared_preferences_web - sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.4.3" - shared_preferences_windows: - dependency: transitive - description: - name: shared_preferences_windows - sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.4.1" + version: "1.4.0" sky_engine: dependency: transitive description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index b290be6..8befe21 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,7 +20,7 @@ dependencies: path: ^1.9.1 path_provider: ^2.1.5 permission_handler: ^12.0.1 - shared_preferences: ^2.5.3 + pretty_dio_logger: ^1.4.0 sqflite: ^2.4.2 tdesign_flutter: ^0.2.4 uuid: ^4.5.1