Files
jiu/client/lib/core/auth/auth_state.dart
T
wangjia cc687ae0c3 feat(client): 登录模块对接后端 API
- 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>
2026-04-04 23:18:57 +08:00

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),
);