import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; import 'dart:async'; import '../../core/auth/auth_state.dart'; import '../../core/theme/app_theme.dart'; class AppShell extends ConsumerStatefulWidget { final Widget child; const AppShell({super.key, required this.child}); @override ConsumerState createState() => _AppShellState(); } class _AppShellState extends ConsumerState { bool _sidebarExpanded = true; final String _loginTime = DateFormat('HH:mm:ss').format(DateTime.now()); final List<_NavItem> _navItems = const [ _NavItem(icon: Icons.input, label: '入库管理', path: '/stock-in'), _NavItem(icon: Icons.output, label: '出库管理', path: '/stock-out'), _NavItem(icon: Icons.inventory_2, label: '库存管理', path: '/inventory'), _NavItem( icon: Icons.account_balance_wallet, label: '财务管理', path: '/finance'), _NavItem(icon: Icons.people, label: '往来单位', path: '/partners'), _NavItem(icon: Icons.category, label: '基础数据', path: '/products'), _NavItem(icon: Icons.settings, label: '系统设置', path: '/settings'), ]; @override Widget build(BuildContext context) { final user = ref.watch(authStateProvider).user; final location = GoRouterState.of(context).matchedLocation; final sidebarWidth = _sidebarExpanded ? 200.0 : 56.0; return Scaffold( body: Column( children: [ // Top Bar Container( height: 56, color: AppTheme.primary, padding: const EdgeInsets.symmetric(horizontal: 8), child: Row( children: [ IconButton( icon: Icon( _sidebarExpanded ? Icons.menu_open : Icons.menu, color: Colors.white), onPressed: () => setState(() => _sidebarExpanded = !_sidebarExpanded), tooltip: _sidebarExpanded ? '收起侧边栏' : '展开侧边栏', ), const SizedBox(width: 4), const Icon(Icons.wine_bar, color: Colors.white, size: 22), const SizedBox(width: 8), const Text( '酒库管理系统', style: TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.w600, letterSpacing: 0.5), ), const Spacer(), if (user != null) ...[ const Icon(Icons.business, color: Colors.white70, size: 14), const SizedBox(width: 4), Text(user.shopNo, style: const TextStyle( color: Colors.white70, fontSize: 13)), const SizedBox(width: 20), const Icon(Icons.person_outline, color: Colors.white70, size: 14), const SizedBox(width: 4), Text(user.username, style: const TextStyle( color: Colors.white70, fontSize: 13)), const SizedBox(width: 8), PopupMenuButton( 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(); context.go('/login'); } }, itemBuilder: (context) => [ const PopupMenuItem( value: 'profile', padding: EdgeInsets.zero, child: _HoverMenuItem( icon: Icons.manage_accounts_outlined, label: '个人设置', ), ), const PopupMenuDivider(height: 1), const PopupMenuItem( value: 'logout', padding: EdgeInsets.zero, child: _HoverMenuItem( icon: Icons.logout_outlined, label: '退出登录', danger: true, ), ), ], ), const SizedBox(width: 8), ], ], ), ), // Main area Expanded( child: Row( children: [ // Sidebar AnimatedContainer( duration: const Duration(milliseconds: 200), curve: Curves.easeInOut, width: sidebarWidth, color: AppTheme.primaryDark, child: Column( children: [ Expanded( child: ListView( padding: const EdgeInsets.symmetric(vertical: 8), children: _navItems.map((item) { final isActive = location.startsWith(item.path); return _SidebarItem( item: item, isActive: isActive, expanded: _sidebarExpanded, onTap: () => context.go(item.path), ); }).toList(), ), ), ], ), ), // Content area Expanded( child: Column( children: [ Expanded(child: widget.child), // Status bar Container( height: 28, color: const Color(0xFF37474F), padding: const EdgeInsets.symmetric(horizontal: 12), child: Row( children: [ if (user != null) ...[ _StatusItem( icon: Icons.store, text: '门店编号:${user.shopNo}'), const _StatusDivider(), _StatusItem( icon: Icons.person, text: '登录用户:${user.username}'), const _StatusDivider(), _StatusItem( icon: Icons.login, text: '登录时间:$_loginTime'), const _StatusDivider(), ], const _ClockWidget(), const Spacer(), const _StatusItem( icon: Icons.info_outline, text: 'v1.0.0'), ], ), ), ], ), ), ], ), ), ], ), ); } } class _NavItem { final IconData icon; final String label; final String path; const _NavItem( {required this.icon, required this.label, required this.path}); } class _SidebarItem extends StatelessWidget { final _NavItem item; final bool isActive; final bool expanded; final VoidCallback onTap; const _SidebarItem({ required this.item, required this.isActive, required this.expanded, required this.onTap, }); @override Widget build(BuildContext context) { return SizedBox( height: 48, child: Material( color: isActive ? AppTheme.primary.withAlpha(76) : Colors.transparent, child: InkWell( onTap: onTap, hoverColor: Colors.white.withAlpha(13), splashColor: Colors.white.withAlpha(26), highlightColor: Colors.white.withAlpha(13), child: Row( children: [ // Active indicator bar Container( width: 3, height: isActive ? 28 : 0, color: Colors.white, margin: EdgeInsets.only(right: expanded ? 13 : 0), ), if (!isActive) SizedBox(width: expanded ? 16 : 3), Expanded( child: Row( mainAxisAlignment: expanded ? MainAxisAlignment.start : MainAxisAlignment.center, children: [ Icon( item.icon, color: isActive ? Colors.white : Colors.white60, size: 20, ), if (expanded) ...[ const SizedBox(width: 12), Flexible( child: Text( item.label, style: TextStyle( color: isActive ? Colors.white : Colors.white70, fontSize: 14, fontWeight: isActive ? FontWeight.w500 : FontWeight.normal, ), overflow: TextOverflow.ellipsis, ), ), ], ], ), ), ], ), ), ), ); } } class _StatusItem extends StatelessWidget { final IconData icon; final String text; const _StatusItem({required this.icon, required this.text}); @override Widget build(BuildContext context) { return Row( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, size: 11, color: Colors.white54), const SizedBox(width: 4), Text(text, style: const TextStyle(color: Colors.white54, fontSize: 11)), ], ); } } class _StatusDivider extends StatelessWidget { const _StatusDivider(); @override Widget build(BuildContext context) { return Container( width: 1, height: 12, color: Colors.white24, margin: const EdgeInsets.symmetric(horizontal: 10), ); } } class _ClockWidget extends StatefulWidget { const _ClockWidget(); @override State<_ClockWidget> createState() => _ClockWidgetState(); } class _ClockWidgetState extends State<_ClockWidget> { late Timer _timer; late String _time; @override void initState() { super.initState(); _time = DateFormat('HH:mm:ss').format(DateTime.now()); _timer = Timer.periodic(const Duration(seconds: 1), (_) { if (mounted) setState(() => _time = DateFormat('HH:mm:ss').format(DateTime.now())); }); } @override void dispose() { _timer.cancel(); super.dispose(); } @override Widget build(BuildContext context) { 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, ), ), ], ), ), ); } }