feat(client): 只读角色隐藏所有写按钮(WriteGuard 统一控件 + 顶栏只读标识)
- 新增 isReadonlyProvider(role == 'readonly')+ WriteGuard 控件,统一隐藏写控件 - 入库/出库/库存/商品/基础数据/往来单位/财务/设置 各屏:只读时隐藏 新增/编辑/删除/审核/提交/驳回/结清/盘点/导入/激活/用户管理 等写操作 - 顶栏显示「只读」标识,让只读用户明确当前权限 - 后端 middleware.ReadOnly() 仍兜底 403,UI 隐藏 + 后端拦截双保险 修正:此前误判为后端漏洞/部署 bug,实为前端写按钮未对只读隐藏。 后端经验证正确拦截只读写操作(线上 stock-in/product-options 均 403)。 121 测试通过,analyze 无 error。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../core/auth/auth_state.dart';
|
||||
|
||||
/// 写操作守卫:当前登录用户为只读角色(role == 'readonly')时,
|
||||
/// 隐藏被包裹的写操作控件(新增/编辑/删除/审核/提交/结清/导入…)。
|
||||
///
|
||||
/// 统一入口,避免在各页面散落 `if (!ref.watch(isReadonlyProvider))` 判断。
|
||||
/// 后端 `middleware.ReadOnly()` 仍会对只读用户的写请求兜底返回 403,
|
||||
/// 本控件只负责「不让按钮出现」,二者配合:UI 不误导 + 后端不可绕过。
|
||||
///
|
||||
/// 用法:
|
||||
/// ```dart
|
||||
/// WriteGuard(child: ElevatedButton(onPressed: _add, child: const Text('新建')))
|
||||
/// ```
|
||||
/// 列表 children 里可用 [hidden] 配合 collection-if 直接剔除分隔符:
|
||||
/// ```dart
|
||||
/// if (!WriteGuard.isReadonly(ref)) ...[button, const SizedBox(width: 8)]
|
||||
/// ```
|
||||
class WriteGuard extends ConsumerWidget {
|
||||
final Widget child;
|
||||
|
||||
/// 只读时显示的占位控件,默认完全隐藏(不占位)。
|
||||
final Widget placeholder;
|
||||
|
||||
const WriteGuard({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.placeholder = const SizedBox.shrink(),
|
||||
});
|
||||
|
||||
/// 供需要在 collection-if / 复合条件中判断的场景直接调用。
|
||||
static bool isReadonly(WidgetRef ref) => ref.watch(isReadonlyProvider);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return ref.watch(isReadonlyProvider) ? placeholder : child;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user