chore: release v1.0.18
Deploy / build-linux-web (push) Successful in 53s
Deploy / build-windows (push) Successful in 1m48s
Deploy / build-macos (push) Successful in 1m17s
Deploy / build-android (push) Successful in 4m13s
Deploy / build-ios (push) Successful in 9s
Deploy / release-deploy (push) Successful in 1m37s
Deploy / build-linux-web (push) Successful in 53s
Deploy / build-windows (push) Successful in 1m48s
Deploy / build-macos (push) Successful in 1m17s
Deploy / build-android (push) Successful in 4m13s
Deploy / build-ios (push) Successful in 9s
Deploy / release-deploy (push) Successful in 1m37s
移动端响应式适配(抽屉导航/列表卡片/弹窗自适应)、Android 正式签名与 APK 发布、 iOS(TestFlight) 工程与 CI、多平台构建流水线、相关文档同步。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import 'package:intl/intl.dart';
|
||||
import 'dart:async';
|
||||
import '../../core/auth/auth_state.dart';
|
||||
import '../../core/config/app_config.dart';
|
||||
import '../../core/responsive/responsive.dart';
|
||||
import '../../core/theme/app_theme.dart';
|
||||
import '../../providers/connectivity_provider.dart';
|
||||
import '../../providers/shop_provider.dart';
|
||||
@@ -24,6 +25,7 @@ class _AppShellState extends ConsumerState<AppShell> {
|
||||
bool _sidebarExpanded = true;
|
||||
final String _loginTime = DateFormat('HH:mm:ss').format(DateTime.now());
|
||||
bool _forceDialogShown = false;
|
||||
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
|
||||
void _showForceUpdateDialog(
|
||||
BuildContext context, AppUpdateInfo info) {
|
||||
@@ -69,11 +71,86 @@ class _AppShellState extends ConsumerState<AppShell> {
|
||||
_NavItem(icon: Icons.info_outline, label: '关于我们', path: '/about'),
|
||||
];
|
||||
|
||||
/// 窄屏(手机)侧滑抽屉导航,容纳全部菜单项。
|
||||
Widget _buildDrawer(
|
||||
BuildContext context, AuthUser? user, String location) {
|
||||
final shopAsync = ref.watch(shopInfoProvider);
|
||||
final shopName = shopAsync.valueOrNull?.name ?? user?.shopNo ?? '';
|
||||
final logoUrl = shopAsync.valueOrNull?.logoUrl ?? '';
|
||||
return Drawer(
|
||||
child: Container(
|
||||
color: AppTheme.primaryDark,
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
// 抽屉头部:门店 Logo + 名称 + 编号
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.fromLTRB(16, 20, 16, 16),
|
||||
color: AppTheme.primary,
|
||||
child: Row(
|
||||
children: [
|
||||
_ShopLogo(
|
||||
logoUrl: logoUrl, shopName: shopName, size: 40),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
shopName.isEmpty ? '—' : shopName,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
if (user != null) ...[
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'${user.shopNo} · ${user.username}',
|
||||
style: const TextStyle(
|
||||
color: Colors.white70, fontSize: 12),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
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: true,
|
||||
onTap: () {
|
||||
Navigator.pop(context); // 关闭抽屉
|
||||
context.go(item.path);
|
||||
},
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final user = ref.watch(authStateProvider).user;
|
||||
final isOnline = ref.watch(connectivityProvider);
|
||||
final location = GoRouterState.of(context).matchedLocation;
|
||||
final isMobile = context.isMobile;
|
||||
final sidebarWidth = _sidebarExpanded ? 200.0 : 56.0;
|
||||
final updateNotifier = ref.watch(updateProvider.notifier);
|
||||
final updateInfo = ref.watch(updateProvider).valueOrNull;
|
||||
@@ -82,6 +159,8 @@ class _AppShellState extends ConsumerState<AppShell> {
|
||||
|
||||
return SelectionArea(
|
||||
child: Scaffold(
|
||||
key: _scaffoldKey,
|
||||
drawer: isMobile ? _buildDrawer(context, user, location) : null,
|
||||
body: Column(
|
||||
children: [
|
||||
// Top Bar
|
||||
@@ -93,41 +172,54 @@ class _AppShellState extends ConsumerState<AppShell> {
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
_sidebarExpanded ? Icons.menu_open : Icons.menu,
|
||||
isMobile
|
||||
? Icons.menu
|
||||
: (_sidebarExpanded ? Icons.menu_open : Icons.menu),
|
||||
color: Colors.white),
|
||||
onPressed: () =>
|
||||
setState(() => _sidebarExpanded = !_sidebarExpanded),
|
||||
tooltip: _sidebarExpanded ? '收起侧边栏' : '展开侧边栏',
|
||||
onPressed: () {
|
||||
if (isMobile) {
|
||||
_scaffoldKey.currentState?.openDrawer();
|
||||
} else {
|
||||
setState(() => _sidebarExpanded = !_sidebarExpanded);
|
||||
}
|
||||
},
|
||||
tooltip: isMobile
|
||||
? '菜单'
|
||||
: (_sidebarExpanded ? '收起侧边栏' : '展开侧边栏'),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
_ShopButton(user: user, version: appVersion),
|
||||
const Spacer(),
|
||||
if (user != null) ...[
|
||||
MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: () => _showShopPanel(context, user, version: appVersion),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.business,
|
||||
color: Colors.white70, size: 14),
|
||||
const SizedBox(width: 4),
|
||||
Text(user.shopNo,
|
||||
style: const TextStyle(
|
||||
color: Colors.white70, fontSize: 13)),
|
||||
],
|
||||
// 窄屏隐藏门店号/用户名文字块,仅保留用户菜单,避免顶栏拥挤
|
||||
if (!isMobile) ...[
|
||||
MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: () =>
|
||||
_showShopPanel(context, user, version: appVersion),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
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),
|
||||
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<String>(
|
||||
icon: const Icon(Icons.keyboard_arrow_down,
|
||||
color: Colors.white70),
|
||||
@@ -172,32 +264,33 @@ class _AppShellState extends ConsumerState<AppShell> {
|
||||
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(),
|
||||
// Sidebar(仅宽屏;窄屏改用 Drawer)
|
||||
if (!isMobile)
|
||||
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(
|
||||
@@ -278,7 +371,8 @@ class _AppShellState extends ConsumerState<AppShell> {
|
||||
),
|
||||
),
|
||||
Expanded(child: widget.child),
|
||||
// Status bar
|
||||
// Status bar(仅宽屏;窄屏隐藏,离线已有顶部横幅提示)
|
||||
if (!isMobile)
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final w = constraints.maxWidth;
|
||||
@@ -515,7 +609,7 @@ void _showShopPanel(BuildContext context, AuthUser u, {String version = 'v1.0.0'
|
||||
builder: (ctx) => Dialog(
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
child: SizedBox(
|
||||
width: 360,
|
||||
width: ctx.dialogWidth(360),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
|
||||
Reference in New Issue
Block a user