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:
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user