cc687ae0c3
- auth_state: 增加 refreshToken 字段 + flutter_secure_storage 持久化 - api_client: 增加 401 自动 refresh + retry 拦截器 - auth_repository: 封装 POST /api/v1/auth/login 调用,错误信息本地化 - login_screen: 增加门店编号字段,调真实 API,行内错误展示 - main: 启动时 restore 持久化登录态,避免每次重启都要重新登录 - 修复 app_shell: hotelName → hotelNo - 修复 widget_test: 移除失效的占位测试 测试账号:门店编号 H001 / 用户名 admin / 密码 password123 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
109 lines
3.3 KiB
Dart
109 lines
3.3 KiB
Dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|
|
|
const _kAccessToken = 'access_token';
|
|
const _kRefreshToken = 'refresh_token';
|
|
const _kUsername = 'username';
|
|
const _kRealName = 'real_name';
|
|
const _kHotelNo = 'hotel_no';
|
|
const _kHotelId = 'hotel_id';
|
|
|
|
class AuthUser {
|
|
final String accessToken;
|
|
final String refreshToken;
|
|
final String username;
|
|
final String realName;
|
|
final String hotelNo;
|
|
final int hotelId;
|
|
|
|
const AuthUser({
|
|
required this.accessToken,
|
|
required this.refreshToken,
|
|
required this.username,
|
|
required this.realName,
|
|
required this.hotelNo,
|
|
required this.hotelId,
|
|
});
|
|
|
|
// Kept for ApiClient compatibility
|
|
String get token => accessToken;
|
|
}
|
|
|
|
class AuthState {
|
|
final AuthUser? user;
|
|
final bool initialized;
|
|
const AuthState({this.user, this.initialized = false});
|
|
bool get isLoggedIn => user != null;
|
|
}
|
|
|
|
class AuthNotifier extends StateNotifier<AuthState> {
|
|
final FlutterSecureStorage _storage;
|
|
|
|
AuthNotifier(this._storage) : super(const AuthState());
|
|
|
|
/// Called at app startup to restore persisted session
|
|
Future<void> restore() async {
|
|
final accessToken = await _storage.read(key: _kAccessToken);
|
|
final refreshToken = await _storage.read(key: _kRefreshToken);
|
|
final username = await _storage.read(key: _kUsername);
|
|
final realName = await _storage.read(key: _kRealName);
|
|
final hotelNo = await _storage.read(key: _kHotelNo);
|
|
final hotelIdStr = await _storage.read(key: _kHotelId);
|
|
|
|
if (accessToken != null && refreshToken != null && username != null) {
|
|
state = AuthState(
|
|
initialized: true,
|
|
user: AuthUser(
|
|
accessToken: accessToken,
|
|
refreshToken: refreshToken,
|
|
username: username,
|
|
realName: realName ?? username,
|
|
hotelNo: hotelNo ?? '',
|
|
hotelId: int.tryParse(hotelIdStr ?? '') ?? 0,
|
|
),
|
|
);
|
|
} else {
|
|
state = const AuthState(initialized: true);
|
|
}
|
|
}
|
|
|
|
Future<void> login(AuthUser user) async {
|
|
await _storage.write(key: _kAccessToken, value: user.accessToken);
|
|
await _storage.write(key: _kRefreshToken, value: user.refreshToken);
|
|
await _storage.write(key: _kUsername, value: user.username);
|
|
await _storage.write(key: _kRealName, value: user.realName);
|
|
await _storage.write(key: _kHotelNo, value: user.hotelNo);
|
|
await _storage.write(key: _kHotelId, value: user.hotelId.toString());
|
|
state = AuthState(initialized: true, user: user);
|
|
}
|
|
|
|
void updateAccessToken(String newToken) {
|
|
if (state.user == null) return;
|
|
_storage.write(key: _kAccessToken, value: newToken);
|
|
state = AuthState(
|
|
initialized: true,
|
|
user: AuthUser(
|
|
accessToken: newToken,
|
|
refreshToken: state.user!.refreshToken,
|
|
username: state.user!.username,
|
|
realName: state.user!.realName,
|
|
hotelNo: state.user!.hotelNo,
|
|
hotelId: state.user!.hotelId,
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> logout() async {
|
|
await _storage.deleteAll();
|
|
state = const AuthState(initialized: true);
|
|
}
|
|
}
|
|
|
|
final _secureStorage = FlutterSecureStorage(
|
|
mOptions: MacOsOptions(groupId: 'com.jiu.client'),
|
|
);
|
|
|
|
final authStateProvider = StateNotifierProvider<AuthNotifier, AuthState>(
|
|
(ref) => AuthNotifier(_secureStorage),
|
|
);
|