feat(client): 全模块 API 对接 + 修复登陆跳转 + 菜单 UI 优化
API 对接: - 入库/出库/库存/财务/往来单位/基础数据全部对接后端 REST API - 新增 repositories、providers、models 层,统一分层架构 - auth 从 flutter_secure_storage 迁移到 shared_preferences 登陆跳转修复: - 将 _RouterNotifier 提取为独立 Riverpod provider,appRouterProvider 使用 ref.read 避免依赖链导致 router 重建后跳回 /login - redirect 函数新增 initialized 守卫,防止 auth 未恢复时误重定向 - 添加调试日志(Router/Auth/ApiClient)定位 401 触发的 logout 链路 退出菜单 UI: - 去掉 ListTile,改用 Row + 自定义 padding,文字左对齐 - MouseRegion + AnimatedContainer 实现 hover 高亮(普通项蓝底/退出红底) - 菜单圆角 6px,elevation 8,分割线高度 1px Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -15,7 +15,7 @@ class LoginScreen extends ConsumerStatefulWidget {
|
||||
|
||||
class _LoginScreenState extends ConsumerState<LoginScreen> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _hotelCodeCtrl = TextEditingController();
|
||||
final _shopCodeCtrl = TextEditingController();
|
||||
final _usernameCtrl = TextEditingController();
|
||||
final _passwordCtrl = TextEditingController();
|
||||
final _hotelCodeFocus = FocusNode();
|
||||
@@ -42,7 +42,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
|
||||
_loadHistory();
|
||||
_hotelCodeFocus.addListener(() {
|
||||
if (_hotelCodeFocus.hasFocus && _hotelCodeHistory.isNotEmpty) {
|
||||
_openDropdown(_hotelLayerLink, _hotelCodeHistory, _hotelCodeCtrl,
|
||||
_openDropdown(_hotelLayerLink, _hotelCodeHistory, _shopCodeCtrl,
|
||||
isHotel: true);
|
||||
} else if (!_hotelCodeFocus.hasFocus) {
|
||||
_closeHotel();
|
||||
@@ -68,7 +68,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
|
||||
});
|
||||
// If a field is already focused, open its dropdown now that history loaded
|
||||
if (_hotelCodeFocus.hasFocus && hotels.isNotEmpty) {
|
||||
_openDropdown(_hotelLayerLink, hotels, _hotelCodeCtrl, isHotel: true);
|
||||
_openDropdown(_hotelLayerLink, hotels, _shopCodeCtrl, isHotel: true);
|
||||
}
|
||||
if (_usernameFocus.hasFocus && users.isNotEmpty) {
|
||||
_openDropdown(_usernameLayerLink, users, _usernameCtrl, isHotel: false);
|
||||
@@ -174,7 +174,7 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
|
||||
void dispose() {
|
||||
_hotelEntry?.remove();
|
||||
_usernameEntry?.remove();
|
||||
_hotelCodeCtrl.dispose();
|
||||
_shopCodeCtrl.dispose();
|
||||
_usernameCtrl.dispose();
|
||||
_passwordCtrl.dispose();
|
||||
_hotelCodeFocus.dispose();
|
||||
@@ -189,17 +189,21 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
|
||||
_errorMessage = null;
|
||||
});
|
||||
try {
|
||||
debugPrint('[Login] calling AuthRepository.login...');
|
||||
final user = await AuthRepository.login(
|
||||
hotelCode: _hotelCodeCtrl.text.trim(),
|
||||
shopCode: _shopCodeCtrl.text.trim(),
|
||||
username: _usernameCtrl.text.trim(),
|
||||
password: _passwordCtrl.text,
|
||||
);
|
||||
debugPrint('[Login] API success, username=${user.username}');
|
||||
await LoginHistoryStorage.record(
|
||||
_hotelCodeCtrl.text.trim(),
|
||||
_shopCodeCtrl.text.trim(),
|
||||
_usernameCtrl.text.trim(),
|
||||
);
|
||||
await ref.read(authStateProvider.notifier).login(user);
|
||||
debugPrint('[Login] notifier.login done, mounted=$mounted, going to /stock-in');
|
||||
if (mounted) context.go('/stock-in');
|
||||
debugPrint('[Login] context.go called');
|
||||
} on AuthException catch (e) {
|
||||
setState(() => _errorMessage = e.message);
|
||||
} catch (e) {
|
||||
@@ -283,11 +287,11 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// Hotel code field
|
||||
// Shop code field
|
||||
CompositedTransformTarget(
|
||||
link: _hotelLayerLink,
|
||||
child: TextFormField(
|
||||
controller: _hotelCodeCtrl,
|
||||
controller: _shopCodeCtrl,
|
||||
focusNode: _hotelCodeFocus,
|
||||
decoration: InputDecoration(
|
||||
labelText: '门店编号',
|
||||
|
||||
Reference in New Issue
Block a user