diff --git a/client/lib/screens/finance/finance_screen.dart b/client/lib/screens/finance/finance_screen.dart index 8a8290a..0f356b1 100644 --- a/client/lib/screens/finance/finance_screen.dart +++ b/client/lib/screens/finance/finance_screen.dart @@ -11,7 +11,7 @@ import '../../widgets/page_scaffold.dart'; import '../../providers/connectivity_provider.dart'; import '../../core/utils/export_util.dart'; import '../../repositories/finance_repository.dart'; -import '../../core/auth/auth_state.dart'; +import '../../widgets/write_guard.dart'; class FinanceScreen extends ConsumerWidget { const FinanceScreen({super.key}); @@ -161,7 +161,7 @@ class _FinanceTabState extends ConsumerState<_FinanceTab> { MobileCardField('状态', null, valueWidget: _StatusBadge(r.status)), if (r.remark?.isNotEmpty == true) MobileCardField('备注', r.remark), ], - actions: (canClose && !ref.watch(isReadonlyProvider)) + actions: (canClose && !WriteGuard.isReadonly(ref)) ? [ TextButton( onPressed: () => _closeRecord(r), @@ -307,7 +307,7 @@ class _FinanceTabState extends ConsumerState<_FinanceTab> { case 'actions': if ((r.type == 'payable' || r.type == 'receivable') && r.status == 'open' && - !ref.watch(isReadonlyProvider)) { + !WriteGuard.isReadonly(ref)) { return DataCell(TextButton( onPressed: () => _closeRecord(r), child: const Text('结清', @@ -408,13 +408,13 @@ class _FinanceTabState extends ConsumerState<_FinanceTab> { }, toolbar: Row( children: [ - if (addLabel != null && !ref.watch(isReadonlyProvider)) + if (addLabel != null && !WriteGuard.isReadonly(ref)) ElevatedButton.icon( onPressed: _showAddDialog, icon: const Icon(Icons.add, size: 16), label: Text(addLabel), ), - if (addLabel != null && !ref.watch(isReadonlyProvider)) + if (addLabel != null && !WriteGuard.isReadonly(ref)) const SizedBox(width: 8), OutlinedButton.icon( onPressed: () { diff --git a/client/lib/screens/inventory/inventory_list_screen.dart b/client/lib/screens/inventory/inventory_list_screen.dart index a98c877..b79ed85 100644 --- a/client/lib/screens/inventory/inventory_list_screen.dart +++ b/client/lib/screens/inventory/inventory_list_screen.dart @@ -9,6 +9,7 @@ import '../../core/responsive/responsive.dart'; import '../../core/theme/app_theme.dart'; import '../../models/inventory.dart'; import '../../core/auth/auth_state.dart'; +import '../../widgets/write_guard.dart'; import '../../providers/inventory_provider.dart'; import '../../widgets/data_table_card.dart'; import '../../widgets/mobile_list_card.dart'; @@ -292,7 +293,7 @@ class _InventoryListScreenState extends ConsumerState { message: item.remark.isEmpty ? '' : item.remark, waitDuration: const Duration(milliseconds: 300), child: GestureDetector( - onTap: ref.watch(isReadonlyProvider) + onTap: WriteGuard.isReadonly(ref) ? null : () => _editRemark(context, item), child: Row( @@ -360,7 +361,7 @@ class _InventoryListScreenState extends ConsumerState { MobileCardField('供应商', item.supplierName), ], actions: [ - if (!ref.watch(isReadonlyProvider)) + if (!WriteGuard.isReadonly(ref)) TextButton( onPressed: () => _editRemark(context, item), child: const Text('备注', style: TextStyle(fontSize: 13)), @@ -554,7 +555,7 @@ class _InventoryListScreenState extends ConsumerState { ref.read(inventoryListProvider.notifier).setPageSize(s), toolbar: Row( children: [ - if (!ref.watch(isReadonlyProvider)) ...[ + if (!WriteGuard.isReadonly(ref)) ...[ OutlinedButton.icon( onPressed: () => context.go('/inventory/check'), icon: const Icon(Icons.fact_check, size: 16), diff --git a/client/lib/screens/partners/partners_screen.dart b/client/lib/screens/partners/partners_screen.dart index 7ae165e..ce13862 100644 --- a/client/lib/screens/partners/partners_screen.dart +++ b/client/lib/screens/partners/partners_screen.dart @@ -11,7 +11,7 @@ import '../../widgets/data_table_card.dart'; import '../../widgets/mobile_list_card.dart'; import '../../widgets/page_scaffold.dart'; import '../../core/utils/export_util.dart'; -import '../../core/auth/auth_state.dart'; +import '../../widgets/write_guard.dart'; class PartnersScreen extends ConsumerStatefulWidget { const PartnersScreen({super.key}); @@ -167,7 +167,7 @@ class _PartnersScreenState extends ConsumerState { if (p.phone?.isNotEmpty == true) MobileCardField('电话', p.phone), if (p.address?.isNotEmpty == true) MobileCardField('地址', p.address), ], - actions: ref.watch(isReadonlyProvider) + actions: WriteGuard.isReadonly(ref) ? const [] : [ TextButton( @@ -194,7 +194,7 @@ class _PartnersScreenState extends ConsumerState { mobileCards: partners.map(partnerCard).toList(), toolbar: Row( children: [ - if (!ref.watch(isReadonlyProvider)) ...[ + if (!WriteGuard.isReadonly(ref)) ...[ ElevatedButton.icon( onPressed: onAdd, icon: const Icon(Icons.add, size: 16), @@ -285,7 +285,7 @@ class _PartnersScreenState extends ConsumerState { )), DataCell(Row( mainAxisSize: MainAxisSize.min, - children: ref.watch(isReadonlyProvider) + children: WriteGuard.isReadonly(ref) ? const [] : [ TextButton( diff --git a/client/lib/screens/products/products_screen.dart b/client/lib/screens/products/products_screen.dart index 7a5dc3e..223cba1 100644 --- a/client/lib/screens/products/products_screen.dart +++ b/client/lib/screens/products/products_screen.dart @@ -10,7 +10,7 @@ import '../../widgets/data_table_card.dart'; import '../../widgets/mobile_list_card.dart'; import '../../widgets/page_scaffold.dart'; import '../../core/utils/export_util.dart'; -import '../../core/auth/auth_state.dart'; +import '../../widgets/write_guard.dart'; class ProductsScreen extends ConsumerStatefulWidget { const ProductsScreen({super.key}); @@ -355,10 +355,9 @@ class _ProductsScreenState extends ConsumerState { required VoidCallback onAdd, required VoidCallback onExport, }) { - final readonly = ref.watch(isReadonlyProvider); return Row( children: [ - if (!readonly) ...[ + if (!WriteGuard.isReadonly(ref)) ...[ ElevatedButton.icon( onPressed: onAdd, icon: const Icon(Icons.add, size: 16), @@ -420,7 +419,7 @@ class _ProductsScreenState extends ConsumerState { MobileCardField('单品数量', '$quantity'), if (remark?.isNotEmpty == true) MobileCardField('备注', remark), ], - actions: ref.watch(isReadonlyProvider) + actions: WriteGuard.isReadonly(ref) ? const [] : [ TextButton( @@ -437,19 +436,20 @@ class _ProductsScreenState extends ConsumerState { } Widget _actionButtons({required VoidCallback onEdit, required VoidCallback onDelete}) { - if (ref.watch(isReadonlyProvider)) return const SizedBox.shrink(); - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - TextButton( - onPressed: onEdit, - child: const Text('编辑', style: TextStyle(fontSize: 12)), - ), - TextButton( - onPressed: onDelete, - child: const Text('删除', style: TextStyle(fontSize: 12, color: AppTheme.danger)), - ), - ], + return WriteGuard( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + TextButton( + onPressed: onEdit, + child: const Text('编辑', style: TextStyle(fontSize: 12)), + ), + TextButton( + onPressed: onDelete, + child: const Text('删除', style: TextStyle(fontSize: 12, color: AppTheme.danger)), + ), + ], + ), ); } diff --git a/client/lib/screens/settings/settings_screen.dart b/client/lib/screens/settings/settings_screen.dart index bb95dd9..7968796 100644 --- a/client/lib/screens/settings/settings_screen.dart +++ b/client/lib/screens/settings/settings_screen.dart @@ -9,6 +9,7 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/auth/auth_state.dart'; +import '../../widgets/write_guard.dart'; import '../../core/config/app_config.dart'; import '../../core/config/app_info.dart'; import '../../core/responsive/responsive.dart'; @@ -218,12 +219,13 @@ class _SettingsScreenState extends ConsumerState { padding: const EdgeInsets.symmetric(horizontal: 12), child: Row( children: [ - if (!ref.watch(isReadonlyProvider)) - ElevatedButton.icon( + WriteGuard( + child: ElevatedButton.icon( onPressed: () => _showAddUserDialog(context), icon: const Icon(Icons.person_add, size: 16), label: const Text('新增用户'), ), + ), ], ), ), @@ -293,7 +295,7 @@ class _SettingsScreenState extends ConsumerState { )), DataCell(Row( mainAxisSize: MainAxisSize.min, - children: ref.watch(isReadonlyProvider) + children: WriteGuard.isReadonly(ref) ? const [] : [ TextButton( @@ -457,7 +459,7 @@ class _SettingsScreenState extends ConsumerState { style: const TextStyle(fontSize: 13), ), ), - if (!ref.watch(isReadonlyProvider)) ...[ + if (!WriteGuard.isReadonly(ref)) ...[ const SizedBox(width: 8), ElevatedButton( onPressed: _isActivating ? null : _activateLicense, @@ -619,13 +621,12 @@ class _SettingsScreenState extends ConsumerState { style: const TextStyle( fontFamily: 'monospace', fontSize: 12))), DataCell(Text('${r.currentNo}')), - DataCell(ref.watch(isReadonlyProvider) - ? const SizedBox() - : TextButton( + DataCell(WriteGuard( + child: TextButton( onPressed: () => _showNumberRuleDialog(context, r), child: const Text('编辑', - style: TextStyle(fontSize: 12)))), + style: TextStyle(fontSize: 12))))), ])) .toList(), ), @@ -804,7 +805,7 @@ class _SettingsScreenState extends ConsumerState { // ── 数据导入 Tab ────────────────────────────────────────── Widget _buildImportTab() { - if (ref.watch(isReadonlyProvider)) { + if (WriteGuard.isReadonly(ref)) { return const Center( child: Text('只读账号无导入权限', style: TextStyle(color: AppTheme.textSecondary)), diff --git a/client/lib/screens/shell/app_shell.dart b/client/lib/screens/shell/app_shell.dart index db56d4e..2cd57e5 100644 --- a/client/lib/screens/shell/app_shell.dart +++ b/client/lib/screens/shell/app_shell.dart @@ -6,6 +6,7 @@ import 'package:intl/intl.dart'; import 'dart:async'; import 'package:flutter/foundation.dart' show kIsWeb; import '../../core/auth/auth_state.dart'; +import '../../widgets/write_guard.dart'; import '../../core/config/app_config.dart'; import '../../core/responsive/responsive.dart'; import '../../core/theme/app_theme.dart'; @@ -210,7 +211,7 @@ class _AppShellState extends ConsumerState { ), const SizedBox(width: 4), _ShopButton(user: user, version: appVersion), - if (ref.watch(isReadonlyProvider)) ...[ + if (WriteGuard.isReadonly(ref)) ...[ const SizedBox(width: 8), Container( padding: diff --git a/client/lib/screens/stock_in/stock_in_list_screen.dart b/client/lib/screens/stock_in/stock_in_list_screen.dart index a008c6c..6c2830d 100644 --- a/client/lib/screens/stock_in/stock_in_list_screen.dart +++ b/client/lib/screens/stock_in/stock_in_list_screen.dart @@ -22,7 +22,7 @@ import '../../providers/product_provider.dart' show productRepositoryProvider; import '../../repositories/product_repository.dart'; import '../../providers/finance_provider.dart' show financeRepositoryProvider; import '../../providers/shop_provider.dart' show shopInfoProvider; -import '../../core/auth/auth_state.dart'; +import '../../widgets/write_guard.dart'; class StockInListScreen extends ConsumerStatefulWidget { const StockInListScreen({super.key}); @@ -281,7 +281,7 @@ class _StockInListScreenState extends ConsumerState { ref.read(stockInListProvider.notifier).setPageSize(s), toolbar: Row( children: [ - if (showNewButton && !ref.watch(isReadonlyProvider)) + if (showNewButton && !WriteGuard.isReadonly(ref)) ElevatedButton.icon( onPressed: () => context.go('/stock-in/new'), icon: const Icon(Icons.add, size: 16), @@ -345,7 +345,7 @@ class _StockInListScreenState extends ConsumerState { /// 操作按钮列表,表格与移动端卡片共用。 List _orderActions(BuildContext context, StockInOrder o) { - final readonly = ref.watch(isReadonlyProvider); + final readonly = WriteGuard.isReadonly(ref); return [ TextButton( onPressed: () => _showDetail(context, o.id), diff --git a/client/lib/screens/stock_out/stock_out_list_screen.dart b/client/lib/screens/stock_out/stock_out_list_screen.dart index b2d7089..8fbf3eb 100644 --- a/client/lib/screens/stock_out/stock_out_list_screen.dart +++ b/client/lib/screens/stock_out/stock_out_list_screen.dart @@ -20,7 +20,7 @@ import '../../providers/inventory_provider.dart'; import '../../providers/tab_state_provider.dart'; import '../../providers/product_provider.dart'; import '../../providers/finance_provider.dart' show financeRepositoryProvider; -import '../../core/auth/auth_state.dart'; +import '../../widgets/write_guard.dart'; class StockOutListScreen extends ConsumerStatefulWidget { const StockOutListScreen({super.key}); @@ -287,7 +287,7 @@ class _StockOutListScreenState extends ConsumerState { ref.read(stockOutListProvider.notifier).setPageSize(s), toolbar: Row( children: [ - if (showNewButton && !ref.watch(isReadonlyProvider)) + if (showNewButton && !WriteGuard.isReadonly(ref)) ElevatedButton.icon( onPressed: () => context.go('/stock-out/new'), icon: const Icon(Icons.add, size: 16), @@ -351,7 +351,7 @@ class _StockOutListScreenState extends ConsumerState { /// 操作按钮列表,表格与移动端卡片共用。 List _orderActions(BuildContext context, StockOutOrder o) { - final readonly = ref.watch(isReadonlyProvider); + final readonly = WriteGuard.isReadonly(ref); return [ TextButton( onPressed: () => _showDetail(context, o.id),