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>
This commit is contained in:
@@ -1,34 +1,108 @@
|
||||
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 token;
|
||||
final String accessToken;
|
||||
final String refreshToken;
|
||||
final String username;
|
||||
final String hotelName;
|
||||
final String realName;
|
||||
final String hotelNo;
|
||||
final int hotelId;
|
||||
|
||||
const AuthUser({
|
||||
required this.token,
|
||||
required this.accessToken,
|
||||
required this.refreshToken,
|
||||
required this.username,
|
||||
required this.hotelName,
|
||||
required this.realName,
|
||||
required this.hotelNo,
|
||||
required this.hotelId,
|
||||
});
|
||||
|
||||
// Kept for ApiClient compatibility
|
||||
String get token => accessToken;
|
||||
}
|
||||
|
||||
class AuthState {
|
||||
final AuthUser? user;
|
||||
const AuthState({this.user});
|
||||
final bool initialized;
|
||||
const AuthState({this.user, this.initialized = false});
|
||||
bool get isLoggedIn => user != null;
|
||||
}
|
||||
|
||||
class AuthNotifier extends StateNotifier<AuthState> {
|
||||
AuthNotifier() : super(const AuthState());
|
||||
final FlutterSecureStorage _storage;
|
||||
|
||||
void login(AuthUser user) => state = AuthState(user: user);
|
||||
void logout() => state = const AuthState();
|
||||
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 authStateProvider = StateNotifierProvider<AuthNotifier, AuthState>(
|
||||
(ref) => AuthNotifier(),
|
||||
final _secureStorage = FlutterSecureStorage(
|
||||
mOptions: MacOsOptions(groupId: 'com.jiu.client'),
|
||||
);
|
||||
|
||||
final authStateProvider = StateNotifierProvider<AuthNotifier, AuthState>(
|
||||
(ref) => AuthNotifier(_secureStorage),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user