BusinessZone
是业务功能导航菜单组件,负责:
permissionMap
模块的权限计算Redux State (currentKey, dataState)
↓
getBtnAvailability()
↓
btnAvailability 对象
↓
useItems() Hook
↓
items 菜单配置
↓
渲染菜单按钮
BusinessZone
├── 患者管理 (patient_management) - 可展开
│ ├── 注册 (register)
│ ├── 任务列表 (worklist)
│ ├── 历史列表 (historylist)
│ ├── 归档列表 (archivelist)
│ ├── 回收站 (bin)
│ └── 输出列表 (outputlist)
├── 检查 (exam)
├── ———— (分隔线)
├── 图像处理 (process)
└── 打印 (print)
根据当前业务流程位置和数据状态计算每个按钮的可用性:
// 1. 获取当前位置
const currentKey = useSelector(state => state.BusinessFlow.currentKey);
// 2. 根据位置获取数据状态
const dataState: DataState = useSelector((state: RootState) => {
if (currentKey === 'worklist') {
return {
hasSelection: state.workSelection.selectedIds.length > 0,
hasExposedImage: /* ... */
};
}
// ... 其他位置
});
// 3. 计算按钮可用性
const btnAvailability = getBtnAvailability(currentKey, dataState);
权限计算逻辑:
getBtnAvailability
函数封装了复杂的权限逻辑使用自定义 Hook useItems
生成菜单配置:
function useItems(btnAvailability: Record<string, boolean>) {
return [
{
key: 'patient_management',
label: <FormattedMessage id="patient" />,
icon: 'Registration',
disabled: !btnAvailability['patient_management'],
children: [/* 子菜单 */]
},
// ... 其他菜单项
];
}
设计优势:
使用局部状态控制子菜单的显示:
const [floatingMenuVisible, setFloatingMenuVisible] = useState(false);
const handlePatientManagementClick = () => {
setFloatingMenuVisible(!floatingMenuVisible);
};
交互流程:
floatingMenuVisible
状态floatingMenuVisible && <子菜单>
onMenuClick
回调通过动态生成 className 实现当前菜单项的视觉高亮:
const buttonClassNameGenerator = (
itemkey: string,
currentkey: string
): string => {
const originalName = 'w-full max-w-full whitespace-nowrap ...';
if (itemkey === currentkey) {
return originalName + ' border border-red-500 ';
}
return originalName;
};
菜单按钮使用多层溢出控制确保文本不会撑破布局:
// IconButton 样式
style={{
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}}
// 内部 span 样式
<span style={{
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
flex: 1,
minWidth: 0
}}>
技巧:
overflow: hidden
限制边界textOverflow: ellipsis
显示省略号whiteSpace: nowrap
禁止换行flex: 1
+ minWidth: 0
允许弹性收缩onMenuClick?: (key: string) => void
:菜单项点击回调currentKey
:当前业务流程标识(BusinessFlowSlice)workSelection.selectedIds
:任务列表选择状态historySelection.selectedIds
:历史列表选择状态bodyPositionList.exposureStatus
:曝光状态permission.triggerPermissionCalc
:权限重新计算触发器渲染输出:业务功能菜单,包括:
事件派发:
onMenuClick(key)
:通知父组件切换业务流程permissionMap
模块负责)根据用户角色、当前状态、数据条件等因素动态控制功能可用性。
实现方式:
const btnAvailability = getBtnAvailability(
currentKey as LocationKey,
dataState
);
disabled: !btnAvailability['exam'];
关键点:
permissionMap
模块支持多语言界面,根据用户语言设置显示对应文本。
使用方式:
<FormattedMessage
id="patient"
defaultMessage={'语言包中没有定义patient的翻译文本'}
/>
组成部分:
id
:翻译键,在语言包中查找defaultMessage
:找不到翻译时的默认文本将组件逻辑提取为可复用的 Hook 函数。
示例:
function useItems(btnAvailability: Record<string, boolean>) {
return [
/* 菜单配置 */
];
}
优势:
根据状态或条件决定是否渲染元素。
方式:
// 三元运算符
{item.type === 'divider' ? <hr /> : <IconButton />}
// 逻辑与运算符
{item.key === 'patient_management' && floatingMenuVisible && <SubMenu />}
根据条件动态生成 CSS 类名。
实现:
const buttonClassNameGenerator = (itemkey: string, currentkey: string) => {
const base = 'w-full ...';
return itemkey === currentkey ? base + ' border border-red-500' : base;
};
应用:
子菜单的展开状态在本组件维护,而菜单选择状态由父组件通过 Redux 维护。
分工:
使用 Flex 容器实现垂直菜单布局。
<Flex vertical className="min-h-0 overflow-y-auto flex-grow">
关键属性:
vertical
:垂直方向排列flex-grow
:占据剩余空间overflow-y-auto
:内容溢出时显示滚动条min-h-0
:允许收缩到最小高度按钮的可用性完全由数据驱动,而非硬编码。
流程:
数据状态变化 → 权限重新计算 → disabled 属性更新 → UI 自动更新
使用 useSelector
订阅 Redux 状态,只在相关状态变化时重新渲染。
示例:
// 订阅特定状态切片
const currentKey = useSelector(
(state: RootState) => state.BusinessFlow.currentKey
);
// 触发器模式:订阅触发权限重新计算
useSelector((state: RootState) => state.permission.triggerPermissionCalc);
代码中包含多个 console.log
语句:
disabled: console.log(`我要看看exam对应的值:${!btnAvailability['exam']}`),
!btnAvailability['exam'];
问题:
改进建议:移除或使用专门的日志系统
文件中有大量被注释掉的代码:
// const [visibleItems, setVisibleItems] = useState(items);
// const businessZoneRef = useRef<HTMLDivElement>(null);
// useEffect(() => { ... }
建议:使用版本控制系统管理代码历史,移除无用注释
国际化的 defaultMessage
使用了非标准文本:
defaultMessage={'语言包中没有定义patient的翻译文本'}
问题:
改进建议:使用英文或有意义的默认文本
子菜单按钮的禁用状态使用的是父菜单的 item.disabled
:
<IconButton
disabled={item.disabled} // 应该是 child.disabled
>
潜在问题:子菜单项无法独立控制禁用状态
dataState
的计算逻辑有重复代码,可以提取为独立函数。
useSelector((state: RootState) => state.permission.triggerPermissionCalc);
这个订阅没有使用返回值,仅用于触发重新渲染。更好的方式是明确说明这个订阅的用途。
许多菜单项都使用 'Registration' 图标,可能是占位符,需要更新为正确的图标。
src/domain/permissionMap.ts
:权限规则定义和计算逻辑src/states/BusinessFlowSlice.ts
:业务流程状态管理src/states/workSelection.ts
:任务列表选择状态src/states/historySelection.ts
:历史列表选择状态src/states/bodyPositionList.ts
:体位列表和曝光状态src/states/permission.ts
:权限状态管理src/components/IconButton.tsx
:图标按钮组件src/components/Icon/index.tsx
:图标组件src/assets/i18n/
:国际化语言包