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:
wangjia
2026-04-07 22:20:28 +08:00
parent 31ea370cea
commit d717fb3735
70 changed files with 6919 additions and 2580 deletions
+83 -17
View File
@@ -72,7 +72,7 @@ class _AppShellState extends ConsumerState<AppShell> {
const Icon(Icons.business,
color: Colors.white70, size: 14),
const SizedBox(width: 4),
Text(user.hotelNo,
Text(user.shopNo,
style: const TextStyle(
color: Colors.white70, fontSize: 13)),
const SizedBox(width: 20),
@@ -86,6 +86,11 @@ class _AppShellState extends ConsumerState<AppShell> {
PopupMenuButton<String>(
icon: const Icon(Icons.keyboard_arrow_down,
color: Colors.white70),
offset: const Offset(0, 36),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6)),
color: Colors.white,
elevation: 8,
onSelected: (v) {
if (v == 'logout') {
ref.read(authStateProvider.notifier).logout();
@@ -93,21 +98,24 @@ class _AppShellState extends ConsumerState<AppShell> {
}
},
itemBuilder: (context) => [
const PopupMenuItem(
value: 'profile',
child: ListTile(
leading: Icon(Icons.manage_accounts, size: 18),
title: Text('个人设置'),
dense: true,
)),
const PopupMenuDivider(),
const PopupMenuItem(
value: 'logout',
child: ListTile(
leading: Icon(Icons.logout, size: 18),
title: Text('退出登录'),
dense: true,
)),
const PopupMenuItem<String>(
value: 'profile',
padding: EdgeInsets.zero,
child: _HoverMenuItem(
icon: Icons.manage_accounts_outlined,
label: '个人设置',
),
),
const PopupMenuDivider(height: 1),
const PopupMenuItem<String>(
value: 'logout',
padding: EdgeInsets.zero,
child: _HoverMenuItem(
icon: Icons.logout_outlined,
label: '退出登录',
danger: true,
),
),
],
),
const SizedBox(width: 8),
@@ -161,7 +169,7 @@ class _AppShellState extends ConsumerState<AppShell> {
if (user != null) ...[
_StatusItem(
icon: Icons.store,
text: '门店编号:${user.hotelNo}'),
text: '门店编号:${user.shopNo}'),
const _StatusDivider(),
_StatusItem(
icon: Icons.person,
@@ -334,3 +342,61 @@ class _ClockWidgetState extends State<_ClockWidget> {
return _StatusItem(icon: Icons.access_time, text: '当前时间:$_time');
}
}
class _HoverMenuItem extends StatefulWidget {
final IconData icon;
final String label;
final bool danger;
const _HoverMenuItem({
required this.icon,
required this.label,
this.danger = false,
});
@override
State<_HoverMenuItem> createState() => _HoverMenuItemState();
}
class _HoverMenuItemState extends State<_HoverMenuItem> {
bool _hovered = false;
@override
Widget build(BuildContext context) {
final color = widget.danger
? const Color(0xFFE53935)
: const Color(0xFF333333);
final hoverBg = widget.danger
? const Color(0xFFFFF0F0)
: const Color(0xFFF0F4FF);
return MouseRegion(
onEnter: (_) => setState(() => _hovered = true),
onExit: (_) => setState(() => _hovered = false),
cursor: SystemMouseCursors.click,
child: AnimatedContainer(
duration: const Duration(milliseconds: 120),
width: 152,
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
decoration: BoxDecoration(
color: _hovered ? hoverBg : Colors.transparent,
borderRadius: BorderRadius.circular(4),
),
child: Row(
children: [
Icon(widget.icon, size: 16, color: color),
const SizedBox(width: 10),
Text(
widget.label,
style: TextStyle(
fontSize: 14,
color: color,
fontWeight: FontWeight.w400,
),
),
],
),
),
);
}
}