NavbarFloat.tsx 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. import React, { useState, useRef } from 'react';
  2. import { Menu, Button, Drawer } from 'antd';
  3. import {
  4. MenuUnfoldOutlined,
  5. UserOutlined,
  6. FileSearchOutlined,
  7. AlertOutlined,
  8. ToolOutlined,
  9. PrinterOutlined,
  10. } from '@ant-design/icons';
  11. import type { MenuProps } from 'antd';
  12. interface NavbarFloatProps {
  13. position?: 'left' | 'right';
  14. className?: string;
  15. }
  16. const NavbarFloat: React.FC<NavbarFloatProps> = ({
  17. position = 'left',
  18. className,
  19. }) => {
  20. const [open, setOpen] = useState(false);
  21. const [btnPos, setBtnPos] = useState({ x: 100, y: 100 });
  22. React.useEffect(() => {
  23. // 组件挂载后再安全访问 window
  24. setBtnPos({
  25. x: window.innerWidth - 96,
  26. y: window.innerHeight - 96,
  27. });
  28. }, []);
  29. const [dragged, setDragged] = useState(false);
  30. const dragging = useRef(false);
  31. const offset = useRef({ x: 0, y: 0 });
  32. // 拖拽事件
  33. const onMouseDown = (e: React.MouseEvent) => {
  34. dragging.current = true;
  35. setDragged(false);
  36. offset.current = {
  37. x: e.clientX - btnPos.x,
  38. y: e.clientY - btnPos.y,
  39. };
  40. document.addEventListener('mousemove', onMouseMove);
  41. document.addEventListener('mouseup', onMouseUp);
  42. };
  43. const onMouseMove = (e: MouseEvent) => {
  44. if (!dragging.current) return;
  45. let x = e.clientX - offset.current.x;
  46. let y = e.clientY - offset.current.y;
  47. // 限制在窗口内
  48. x = Math.max(0, Math.min(window.innerWidth - 80, x));
  49. y = Math.max(0, Math.min(window.innerHeight - 80, y));
  50. setBtnPos({ x, y });
  51. setDragged(true); // 标记为拖拽
  52. };
  53. const onMouseUp = () => {
  54. dragging.current = false;
  55. setTimeout(() => setDragged(false), 100); // 短暂延迟后重置
  56. document.removeEventListener('mousemove', onMouseMove);
  57. document.removeEventListener('mouseup', onMouseUp);
  58. };
  59. const menuItems = [
  60. {
  61. key: 'patient',
  62. icon: <Button shape="circle" icon={<UserOutlined />} />,
  63. label: '患者',
  64. },
  65. {
  66. key: 'examination',
  67. icon: <Button shape="circle" icon={<FileSearchOutlined />} />,
  68. label: '检查',
  69. },
  70. {
  71. key: 'emergency',
  72. icon: <Button shape="circle" icon={<AlertOutlined />} />,
  73. label: '急诊',
  74. },
  75. {
  76. key: 'process',
  77. icon: <Button shape="circle" icon={<ToolOutlined />} />,
  78. label: '处理',
  79. },
  80. {
  81. key: 'print',
  82. icon: <Button shape="circle" icon={<PrinterOutlined />} />,
  83. label: '打印',
  84. },
  85. ];
  86. const handleClick: MenuProps['onClick'] = (e) => {
  87. console.log('clicked ', e.key);
  88. setOpen(false); // 点击菜单后自动关闭抽屉
  89. };
  90. const drawerPlacement = position === 'right' ? 'right' : 'left';
  91. return (
  92. <>
  93. {!open && (
  94. <div
  95. style={{
  96. position: 'fixed',
  97. left: undefined,
  98. top: undefined,
  99. right: window.innerWidth - btnPos.x,
  100. bottom: window.innerHeight - btnPos.y,
  101. zIndex: 1100,
  102. cursor: 'grab',
  103. }}
  104. className={className}
  105. onMouseDown={onMouseDown}
  106. >
  107. <Button
  108. type="primary"
  109. shape="circle"
  110. size="large"
  111. icon={<MenuUnfoldOutlined style={{ fontSize: 32 }} />}
  112. onClick={(e) => {
  113. e.stopPropagation();
  114. if (dragged) return; // 拖拽后不弹抽屉
  115. setOpen(true);
  116. }}
  117. style={{
  118. width: 64,
  119. height: 64,
  120. backgroundColor: '#fff',
  121. color: '#333',
  122. boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
  123. display: 'flex',
  124. alignItems: 'center',
  125. justifyContent: 'center',
  126. userSelect: 'none',
  127. }}
  128. />
  129. </div>
  130. )}
  131. <Drawer
  132. placement={drawerPlacement}
  133. open={open}
  134. onClose={() => setOpen(false)}
  135. closable={false}
  136. width={120}
  137. maskClosable={true}
  138. style={{
  139. top: 0,
  140. height: '100vh',
  141. }}
  142. >
  143. <Menu
  144. mode="vertical"
  145. onClick={handleClick}
  146. items={menuItems}
  147. style={{ border: 'none', backgroundColor: 'transparent' }}
  148. />
  149. </Drawer>
  150. </>
  151. );
  152. };
  153. export default NavbarFloat;