BasicLayout.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. // BasicLayout.tsx
  2. import React from 'react';
  3. import { useSelector, useDispatch } from 'react-redux';
  4. import { Layout, Row, Col, ConfigProvider } from 'antd';
  5. import { TabBar } from 'antd-mobile';
  6. import logo from '@/assets/imgs/logo-v3.jpg';
  7. import NavbarFloat from './NavbarFloat';
  8. import NavMenu from './NavMenu';
  9. import BottomBar from './BottomBar';
  10. import StatusBar, { StatusBarProps } from './StateBar';
  11. import RegisterPage from '@/pages/patient/register';
  12. import WorklistPage from '@/pages/patient/worklist';
  13. import HistorylistPage from '@/pages/patient/HistoryList';
  14. import ArchivelistPage from '@/pages/patient/ArchiveList';
  15. import BinPage from '@/pages/patient/Bin';
  16. import OutputlistPage from '@/pages/patient/OutputList';
  17. import PatientManagement from '@/pages/patient/PatientManagement';
  18. import MeButton from '@/pages/security/components/MeButton';
  19. import Profile from '@/pages/security/Profile';
  20. import ExamPage from '@/pages/exam/ExamPage';
  21. import { RootState } from '@/states/store';
  22. import store from '@/states/store';
  23. import { setBusinessFlow } from '@/states/BusinessFlowSlice';
  24. import ImageProcessingPage from '@/pages/view/ImageProcessingPage';
  25. import PrintPage from '@/pages/output/print/PrintPage';
  26. import { pageLayoutConfig } from '@/config/pageLayout';
  27. import selectedWorksToPrint from '@/domain/patient/selectedWorksToPrint';
  28. import { message } from 'antd';
  29. // import { Link } from 'react-router-dom';
  30. // import { MenuOutlined } from '@ant-design/icons';
  31. // const { Content, Footer } = Layout;
  32. interface BasicLayoutProps {
  33. children: React.ReactNode;
  34. }
  35. // //自定义breakpoint
  36. // const breakpoint = {
  37. // xs: 480, // 小屏幕设备(手机)
  38. // sm: 576, // 中小屏幕设备(平板)
  39. // md: 768, // 中等屏幕设备(小桌面显示器)
  40. // lg: 992, // 大屏幕设备(桌面显示器)
  41. // xl: 1200, // 超大屏幕设备(大桌面显示器)
  42. // xxl: 1600 // 超超大屏幕设备(超大桌面显示器)
  43. // };
  44. const BasicLayout: React.FC<BasicLayoutProps> = () => {
  45. const status: StatusBarProps = {
  46. diskStatus: 'available', // 可用状态
  47. batteryLevel: 0,
  48. wifiStrength: 0,
  49. heatCapacity: 0,
  50. };
  51. const dispatch = useDispatch();
  52. const currentKey = useSelector(
  53. (state: RootState) => state.BusinessFlow.currentKey
  54. );
  55. // key和内容组件的映射
  56. const contentMap = {
  57. exam: <ExamPage />,
  58. process: <ImageProcessingPage />,
  59. view: <ImageProcessingPage />,
  60. register: <RegisterPage />,
  61. worklist: <WorklistPage />,
  62. historylist: <HistorylistPage />,
  63. archivelist: <ArchivelistPage />,
  64. bin: <BinPage />,
  65. outputlist: <OutputlistPage />,
  66. patient_management: <PatientManagement />,
  67. profile: <Profile />, // 需确保已引入 Profile 组件
  68. print: <PrintPage />,
  69. };
  70. //感知菜单项点击
  71. const handleMenuClick = async (key: string) => {
  72. // 特殊处理打印按钮
  73. // todo 这一段逻辑后续可以考虑放到 middleware 里处理,即 businessFlowMiddlewareLogic
  74. if (key === 'print') {
  75. if (currentKey === 'worklist' || currentKey === 'historylist') {
  76. // 获取当前选中的记录
  77. const state = store.getState();
  78. const selectedIds =
  79. currentKey === 'historylist'
  80. ? state.historySelection.selectedIds
  81. : currentKey === 'worklist'
  82. ? state.workSelection.selectedIds
  83. : [];
  84. if (selectedIds.length === 0) {
  85. message.warning('请先选择要打印的项目');
  86. return;
  87. }
  88. try {
  89. // 调用 selectedWorksToPrint 加载数据并跳转
  90. await selectedWorksToPrint(selectedIds, currentKey as 'worklist' | 'historylist');
  91. } catch (error) {
  92. console.error('[BusinessZone] Print error:', error);
  93. message.error('加载打印数据失败,请重试');
  94. }
  95. } else if(currentKey === 'exam' || currentKey === 'process') {
  96. // 其他按钮直接切换页面
  97. dispatch(setBusinessFlow(key));
  98. }
  99. }
  100. else {
  101. // 其他按钮直接切换页面
  102. dispatch(setBusinessFlow(key));
  103. }
  104. };
  105. return (
  106. // ConfigProvider 用于自定义 Ant Design 的主题和 breakpoints
  107. <ConfigProvider
  108. theme={
  109. {
  110. // token: {
  111. // // 自定义屏幕尺寸断点,与要求匹配
  112. // screenXS: 576, // xs: <576px
  113. // screenSM: 768, // sm: ≥768px
  114. // screenMD: 992, // md: ≥992px
  115. // screenLG: 1200, // lg: ≥1200px
  116. // screenXL: 1400, // xl: ≥1400px
  117. // screenXXL: 1600, // xxl: ≥1600px
  118. // },
  119. }
  120. }
  121. >
  122. <Layout
  123. style={{
  124. minHeight: '100vh',
  125. // border: '1px solid #ccc'
  126. }}
  127. className="h-[100vh] xl:text-[48px] lg:text-[40px] md:text-[30px] sm:text-[20px] xs:text-[15px]"
  128. >
  129. {/* 第一行:品牌和状态区域 */}
  130. {pageLayoutConfig.brandAndStatusPosition === 'top' && (
  131. <Row
  132. className="xl:h-[9vh] text-[100%]"
  133. style={{ borderBottom: '1px solid #e5e7eb' }}
  134. >
  135. {/* 品牌区域 */}
  136. <Col xl={4} lg={4} md={5} sm={0} xs={0} className="h-full">
  137. <img
  138. src={logo}
  139. alt="Logo"
  140. className="h-full w-auto object-contain"
  141. />
  142. </Col>
  143. {/* 状态区域 */}
  144. <Col
  145. xl={20}
  146. lg={20}
  147. md={19}
  148. sm={0}
  149. xs={0}
  150. className="items-center
  151. justify-end xl:flex
  152. lg:flex
  153. md:flex
  154. sm:hidden
  155. xs:hidden
  156. h-full
  157. text-[100%]"
  158. >
  159. {/* text-[100%] 用于传导父元素的字体大小,最终影响图标大小 */}
  160. <StatusBar
  161. diskStatus={status.diskStatus}
  162. batteryLevel={status.batteryLevel}
  163. wifiStrength={status.wifiStrength}
  164. heatCapacity={status.heatCapacity}
  165. />
  166. </Col>
  167. </Row>
  168. )}
  169. {/* 第二行:导航区域(第1列)和内容区域(第2列) */}
  170. <Row
  171. style={{
  172. flex: 1,
  173. display: 'flex',
  174. // paddingTop: '24px', // 顶部留白
  175. // border: '1px solid red',
  176. }}
  177. className="xxl:h-[90vh] xl:h-[90vh] lg:h-[90vh] md:h-[90vh] sm:h-[90vh] xs:h-[90vh] pt-2"
  178. >
  179. {/* 导航区域 */}
  180. <Col xl={2} lg={2} md={0} sm={0} xs={0} className="h-full">
  181. <NavMenu onMenuClick={handleMenuClick} />
  182. </Col>
  183. {/* 内容区域 */}
  184. <Col
  185. xl={22}
  186. lg={22}
  187. xs={24}
  188. md={24}
  189. sm={24}
  190. style={{
  191. flex: 1,
  192. // border: '1px solid blue'
  193. }}
  194. className="h-full"
  195. >
  196. {/* {children} */}
  197. {contentMap[currentKey]}
  198. </Col>
  199. </Row>
  200. {/* 第一行:品牌和状态区域 */}
  201. {pageLayoutConfig.brandAndStatusPosition === 'bottom' && (
  202. <Row
  203. className="xl:h-[9vh] text-[100%]"
  204. style={{ borderBottom: '1px solid #e5e7eb' }}
  205. >
  206. {/* 品牌区域 */}
  207. <Col xl={8} lg={8} md={5} sm={0} xs={0} className="h-full">
  208. {/* 底部通栏:仅在桌面端显示 */}
  209. <div className="xl:block lg:block md:hidden sm:hidden xs:hidden h-full">
  210. <BottomBar />
  211. </div>
  212. </Col>
  213. {/* 状态区域 */}
  214. <Col
  215. xl={16}
  216. lg={16}
  217. md={19}
  218. sm={0}
  219. xs={0}
  220. className="items-center
  221. justify-end xl:flex
  222. lg:flex
  223. md:flex
  224. sm:hidden
  225. xs:hidden
  226. h-full
  227. text-[100%]"
  228. >
  229. {/* text-[100%] 用于传导父元素的字体大小,最终影响图标大小 */}
  230. <StatusBar
  231. diskStatus={status.diskStatus}
  232. batteryLevel={status.batteryLevel}
  233. wifiStrength={status.wifiStrength}
  234. heatCapacity={status.heatCapacity}
  235. />
  236. </Col>
  237. </Row>
  238. )}
  239. {/* Tabbar:固定在底部,仅在手机屏幕显示 */}
  240. <div className="sticky bottom-0 w-full bg-red xl:hidden">
  241. <TabBar
  242. className="xl:hidden lg:hidden md:hidden sm:block xs:block"
  243. onChange={(key) => handleMenuClick(key)}
  244. >
  245. <TabBar.Item
  246. className="text-red-500"
  247. title="患者管理"
  248. key="patient_management"
  249. icon={<div>🏠</div>}
  250. />
  251. <TabBar.Item
  252. className="text-red-500"
  253. title="检查"
  254. key="examination"
  255. icon={<div>👤</div>}
  256. />
  257. <TabBar.Item
  258. className="text-red-500"
  259. title="急诊"
  260. key="emergency"
  261. icon={<div>⚙️</div>}
  262. />
  263. <TabBar.Item
  264. className="text-red-500"
  265. title="图像处理"
  266. key="process"
  267. icon={<div>⚙️</div>}
  268. />
  269. <TabBar.Item
  270. className="text-red-500"
  271. title="打印"
  272. key="print"
  273. icon={<div>⚙️</div>}
  274. />
  275. <TabBar.Item
  276. title="我的"
  277. key="profile"
  278. icon={
  279. <MeButton
  280. isLogin={true} // TODO: 替换为实际登录状态
  281. avatarUrl={undefined} // TODO: 替换为实际头像
  282. onClick={() => handleMenuClick('profile')}
  283. // 不传递username
  284. />
  285. }
  286. />
  287. </TabBar>
  288. </div>
  289. <NavbarFloat
  290. className="fixed hidden sm:hidden xs:hidden md:block lg:hidden xl:hidden"
  291. position="right"
  292. onMenuClick={handleMenuClick}
  293. />
  294. </Layout>
  295. </ConfigProvider>
  296. );
  297. };
  298. export default BasicLayout;