feat(mobile): #24 Flutter三合一APP骨架搭建

- Flutter 3.x 项目初始化(pubspec.yaml + 目录结构)
- 核心依赖:dio/go_router/provider/shared_preferences/hive/geolocator
- 统一登录页 + Token管理(TokenService + AuthInterceptor)
- 底部Tab三合一导航(供水/巡检/营收)
- 供水管理Tab:监测数据列表(MonitorListPage)
- 巡检Tab:任务列表+状态筛选+进度展示(PatrolTaskListPage)
- 营收Tab:抄表页面+账单列表(MeterReadingPage/BillListPage)
- 消息推送服务(PushService)
- GPS定位服务(LocationService)
- 拍照/相册服务(CameraService)
- 离线缓存服务(CacheService)
- Android/iOS打包配置+权限声明
- GoRouter路由+Auth认证守卫
- Provider状态管理
This commit is contained in:
2026-06-15 08:52:55 +08:00
parent 37bcc78eee
commit d9d72405c6
41 changed files with 3014 additions and 0 deletions
@@ -0,0 +1,82 @@
import 'package:flutter/material.dart';
import 'token_service.dart';
import '../models/user_model.dart';
/// 认证状态管理
class AuthProvider extends ChangeNotifier {
final TokenService _tokenService = TokenService();
bool _isLoading = false;
bool _isAuthenticated = false;
UserModel? _currentUser;
String? _errorMessage;
bool get isLoading => _isLoading;
bool get isAuthenticated => _isAuthenticated;
UserModel? get currentUser => _currentUser;
String? get errorMessage => _errorMessage;
/// 初始化认证状态
Future<void> init() async {
_isAuthenticated = await _tokenService.isLoggedIn();
if (_isAuthenticated) {
// TODO: 从接口获取用户信息
_currentUser = UserModel(
id: '1',
username: 'admin',
name: '管理员',
role: 'admin',
);
}
notifyListeners();
}
/// 登录
Future<bool> login({
required String username,
required String password,
}) async {
_isLoading = true;
_errorMessage = null;
notifyListeners();
try {
// TODO: 调用实际登录接口
// final response = await DioClient().post('/auth/login', data: {
// 'username': username,
// 'password': password,
// });
// 模拟登录成功
await _tokenService.saveTokens(
token: 'mock_access_token_${DateTime.now().millisecondsSinceEpoch}',
refreshToken: 'mock_refresh_token',
expiresIn: 7200,
);
_currentUser = UserModel(
id: '1',
username: username,
name: username == 'admin' ? '管理员' : username,
role: 'admin',
);
_isAuthenticated = true;
_isLoading = false;
notifyListeners();
return true;
} catch (e) {
_errorMessage = '登录失败,请检查用户名和密码';
_isLoading = false;
notifyListeners();
return false;
}
}
/// 登出
Future<void> logout() async {
await _tokenService.clearTokens();
_isAuthenticated = false;
_currentUser = null;
notifyListeners();
}
}
@@ -0,0 +1,90 @@
import 'package:shared_preferences/shared_preferences.dart';
import '../../../core/constants/app_constants.dart';
/// Token 管理服务
/// 负责 Token 的存储、读取、刷新和清除
class TokenService {
static TokenService? _instance;
SharedPreferences? _prefs;
TokenService();
factory TokenService.getInstance() {
_instance ??= TokenService();
return _instance!;
}
Future<SharedPreferences> get _preferences async {
_prefs ??= await SharedPreferences.getInstance();
return _prefs!;
}
/// 获取 Access Token
Future<String?> getToken() async {
final prefs = await _preferences;
return prefs.getString(AppConstants.tokenKey);
}
/// 获取 Refresh Token
Future<String?> getRefreshToken() async {
final prefs = await _preferences;
return prefs.getString(AppConstants.refreshTokenKey);
}
/// 保存 Token
Future<void> saveTokens({
required String token,
String? refreshToken,
int? expiresIn,
}) async {
final prefs = await _preferences;
await prefs.setString(AppConstants.tokenKey, token);
if (refreshToken != null) {
await prefs.setString(AppConstants.refreshTokenKey, refreshToken);
}
if (expiresIn != null) {
final expiry = DateTime.now().millisecondsSinceEpoch + expiresIn * 1000;
await prefs.setInt(AppConstants.tokenExpiryKey, expiry);
}
}
/// 刷新 Token
Future<bool> refreshToken() async {
// TODO: 实现实际的 Token 刷新逻辑
// 使用 refreshToken 调用后端接口获取新的 accessToken
final refreshTok = await getRefreshToken();
if (refreshTok == null) return false;
try {
// 模拟刷新
// final response = await DioClient().post('/auth/refresh', data: {'refresh_token': refreshTok});
// await saveTokens(token: response.data['data']['token'], refreshToken: response.data['data']['refresh_token']);
return true;
} catch (e) {
return false;
}
}
/// 检查 Token 是否过期
Future<bool> isTokenExpired() async {
final prefs = await _preferences;
final expiry = prefs.getInt(AppConstants.tokenExpiryKey);
if (expiry == null) return true;
return DateTime.now().millisecondsSinceEpoch > expiry;
}
/// 清除所有 Token
Future<void> clearTokens() async {
final prefs = await _preferences;
await prefs.remove(AppConstants.tokenKey);
await prefs.remove(AppConstants.refreshTokenKey);
await prefs.remove(AppConstants.tokenExpiryKey);
}
/// 是否已登录
Future<bool> isLoggedIn() async {
final token = await getToken();
if (token == null) return false;
return !(await isTokenExpired());
}
}