Menu.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. import React, { useEffect, useState } from "react";
  2. import { Button, Dropdown, Input, Modal, Switch, InputNumber } from "antd";
  3. import type { DropDownProps, MenuProps } from "antd";
  4. import { useModel } from "umi";
  5. import { useFullscreen } from "ahooks";
  6. export default function Menu() {
  7. const {
  8. project,
  9. canRedo,
  10. canUndo,
  11. onRedo,
  12. onUndo,
  13. graph,
  14. setProject,
  15. onClean,
  16. onCut,
  17. onCopy,
  18. onPaste,
  19. onDelete,
  20. enterPlayMode,
  21. saveTime,
  22. } = useModel("erModel");
  23. const [modal, contextHolder] = Modal.useModal();
  24. const [isFullscreen, { toggleFullscreen }] = useFullscreen(document.body);
  25. const [openKey, setOpenKey] = useState("");
  26. const [open, setOpen] = useState(false);
  27. const [name, setName] = useState(project?.name);
  28. useEffect(() => {
  29. setName(project.name);
  30. }, [project.name]);
  31. const handleClean = () => {
  32. modal.confirm({
  33. title: "确认清除画布全部内容?",
  34. content: "清空后将无法恢复",
  35. cancelText: "取消",
  36. okText: "确定",
  37. onOk() {
  38. onClean();
  39. },
  40. });
  41. };
  42. const handleChangeSetting = (key: string, value: boolean | number | null) => {
  43. setProject({ ...project, setting: { ...project.setting, [key]: value } });
  44. };
  45. const handleChangeName = () => {
  46. name.trim() && setProject({ ...project, name });
  47. }
  48. const handleZoom = (value: number) => {
  49. if (value < 0.2) {
  50. graph?.zoomTo(0.2);
  51. return;
  52. } else if (value > 2) {
  53. graph?.zoomTo(2);
  54. return;
  55. }
  56. graph?.zoomTo(value);
  57. };
  58. const menuData: {
  59. key: string;
  60. label: string;
  61. type: "group";
  62. children: MenuProps["items"];
  63. }[] = [
  64. {
  65. key: "1",
  66. label: "文件",
  67. type: "group",
  68. children: [
  69. {
  70. key: "1-1",
  71. label: (
  72. <span className="flex items-center justify-between">
  73. <span>新建</span>
  74. <span className="color-#666">ctrl+n</span>
  75. </span>
  76. ),
  77. },
  78. { key: "1-2", label: "新标签页打开" },
  79. {
  80. key: "1-3",
  81. label: (
  82. <span className="flex items-center justify-between">
  83. <span>保存</span>
  84. <span className="color-#666">ctrl+s</span>
  85. </span>
  86. ),
  87. },
  88. { key: "1-4", label: "保存为模版" },
  89. { key: "1-5", label: "发布模版" },
  90. { key: "1-6", label: "同步到数据表" },
  91. {
  92. key: "1-7",
  93. label: "导出为图片",
  94. onClick: () => graph && graph?.exportPNG(),
  95. },
  96. ],
  97. },
  98. {
  99. key: "2",
  100. label: "编辑",
  101. type: "group",
  102. children: [
  103. {
  104. key: "1-1",
  105. label: (
  106. <span className="flex items-center justify-between">
  107. <span>撤销</span>
  108. <span className="color-#666">ctrl+z</span>
  109. </span>
  110. ),
  111. onClick: () => canUndo && onUndo(),
  112. },
  113. {
  114. key: "1-2",
  115. label: (
  116. <span className="flex items-center justify-between">
  117. <span>恢复</span>
  118. <span className="color-#666">ctrl+y</span>
  119. </span>
  120. ),
  121. onClick: () => canRedo && onRedo(),
  122. },
  123. { key: "1-3", label: "清除", onClick: handleClean },
  124. {
  125. key: "1-4",
  126. label: (
  127. <span className="flex items-center justify-between">
  128. <span>剪切</span>
  129. <span className="color-#666">ctrl+x</span>
  130. </span>
  131. ),
  132. onClick: onCut,
  133. },
  134. {
  135. key: "1-5",
  136. label: (
  137. <span className="flex items-center justify-between">
  138. <span>复制</span>
  139. <span className="color-#666">ctrl+c</span>
  140. </span>
  141. ),
  142. onClick: onCopy,
  143. },
  144. {
  145. key: "1-6",
  146. label: (
  147. <span className="flex items-center justify-between">
  148. <span>粘贴</span>
  149. <span className="color-#666">ctrl+v</span>
  150. </span>
  151. ),
  152. onClick: onPaste,
  153. },
  154. {
  155. key: "1-7",
  156. label: (
  157. <span className="flex items-center justify-between">
  158. <span>删除</span>
  159. <span className="color-#666">delete</span>
  160. </span>
  161. ),
  162. onClick: onDelete,
  163. },
  164. ],
  165. },
  166. {
  167. key: "3",
  168. label: "视图",
  169. type: "group",
  170. children: [
  171. {
  172. key: "1-1",
  173. label: (
  174. <span className="flex items-center justify-between">
  175. <span>菜单栏隐藏</span>
  176. <Switch
  177. size="small"
  178. checked={project.setting.showMenu}
  179. onChange={(checked) => handleChangeSetting("showMenu", checked)}
  180. />
  181. </span>
  182. ),
  183. },
  184. {
  185. key: "1-2",
  186. label: (
  187. <span className="flex items-center justify-between">
  188. <span>侧边栏隐藏</span>
  189. <Switch
  190. size="small"
  191. checked={project.setting.showSidebar}
  192. onChange={(checked) =>
  193. handleChangeSetting("showSidebar", checked)
  194. }
  195. />
  196. </span>
  197. ),
  198. },
  199. { key: "1-3", label: "演示模式", onClick: () => enterPlayMode() },
  200. {
  201. key: "1-4",
  202. label: (
  203. <span className="flex items-center justify-between">
  204. <span>字段详情</span>
  205. <Switch
  206. size="small"
  207. checked={project.setting.showColumnDetail}
  208. onChange={(checked) =>
  209. handleChangeSetting("showColumnDetail", checked)
  210. }
  211. />
  212. </span>
  213. ),
  214. },
  215. {
  216. key: "1-5",
  217. label: (
  218. <span className="flex items-center justify-between">
  219. <span>显示网格</span>
  220. <Switch
  221. size="small"
  222. checked={project.setting.showGrid}
  223. onChange={(checked) => handleChangeSetting("showGrid", checked)}
  224. />
  225. </span>
  226. ),
  227. },
  228. {
  229. key: "1-6",
  230. label: (
  231. <span className="flex items-center justify-between">
  232. <span>显示关系</span>
  233. <Switch
  234. size="small"
  235. checked={project.setting.showRelation}
  236. onChange={(checked) =>
  237. handleChangeSetting("showRelation", checked)
  238. }
  239. />
  240. </span>
  241. ),
  242. },
  243. { key: "1-7", label: "重置视图", onClick: () => graph?.zoomTo(1) },
  244. {
  245. key: "1-8",
  246. label: (
  247. <span className="flex items-center justify-between">
  248. <span>放大</span>
  249. <span className="color-#666">ctrl+up</span>
  250. </span>
  251. ),
  252. onClick: () => handleZoom((graph?.zoom() || 1) + 0.2),
  253. },
  254. {
  255. key: "1-9",
  256. label: (
  257. <span className="flex items-center justify-between">
  258. <span>缩小</span>
  259. <span className="color-#666">ctrl+down</span>
  260. </span>
  261. ),
  262. onClick: () => handleZoom((graph?.zoom() || 1) - 0.2),
  263. },
  264. {
  265. key: "1-10",
  266. label: isFullscreen ? "退出全屏" : "全屏",
  267. onClick: () => toggleFullscreen(),
  268. },
  269. ],
  270. },
  271. {
  272. key: "4",
  273. label: "设置",
  274. type: "group",
  275. children: [
  276. { key: "1-1", label: "修改记录" },
  277. {
  278. key: "1-2",
  279. label: (
  280. <span className="flex items-center justify-between">
  281. <span>自动保存</span>
  282. <Switch
  283. size="small"
  284. checked={project.setting.autoUpdate}
  285. onChange={(checked) =>
  286. handleChangeSetting("autoUpdate", checked)
  287. }
  288. />
  289. </span>
  290. ),
  291. },
  292. {
  293. key: "1-3",
  294. label: (
  295. <span className="flex items-center justify-between">
  296. <span>自动同步</span>
  297. <Switch size="small" />
  298. </span>
  299. ),
  300. },
  301. {
  302. key: "1-4",
  303. label: "表格宽度",
  304. onClick: () => setOpen(true),
  305. },
  306. ],
  307. },
  308. {
  309. key: "5",
  310. label: "帮助",
  311. type: "group",
  312. children: [
  313. { key: "1-1", label: "快捷键" },
  314. { key: "1-2", label: "操作说明" },
  315. ],
  316. },
  317. ];
  318. const handleOpenChange = (
  319. nextOpen: boolean,
  320. info: { source: "trigger" | "menu" },
  321. key: string
  322. ) => {
  323. if (info.source === "trigger" || nextOpen) {
  324. setOpenKey(nextOpen ? key : "");
  325. }
  326. };
  327. return (
  328. <>
  329. <div className="flex-1 flex items-center">
  330. {contextHolder}
  331. <div className="logo h-48px m-l-12px">
  332. <svg className="icon h-48px w-48px" aria-hidden="true">
  333. <use xlinkHref="#icon-shujujianmo"></use>
  334. </svg>
  335. </div>
  336. <div className="h-full">
  337. <div className="leading-32px flex items-center">
  338. <Input
  339. className="text-24px max-w-200px"
  340. variant="borderless"
  341. value={name}
  342. onChange={(e) => setName(e.target.value)}
  343. onBlur={handleChangeName}
  344. onPressEnter={handleChangeName}
  345. />
  346. {
  347. saveTime && <div className="bg-#eee text-12px leading-20px color-#666 rounded-4px p-x-4px p-y-2px">
  348. 上次保存时间:{saveTime}
  349. </div>
  350. }
  351. </div>
  352. <div className="flex">
  353. {menuData.map((item) => {
  354. return (
  355. <Dropdown
  356. key={item.key}
  357. menu={{ items: item.children, style: { width: 200 } }}
  358. placement="bottomLeft"
  359. open={openKey === item.key}
  360. onOpenChange={(nextOpen, info) =>
  361. handleOpenChange(nextOpen, info, item.key)
  362. }
  363. >
  364. <Button type="text" size="small">
  365. {item.label}
  366. </Button>
  367. </Dropdown>
  368. );
  369. })}
  370. </div>
  371. </div>
  372. </div>
  373. <Modal
  374. title="设置表格宽度"
  375. centered
  376. width={440}
  377. open={open}
  378. okText="确定"
  379. onOk={() => setOpen(false)}
  380. onCancel={() => setOpen(false)}
  381. footer={(_, { OkBtn }) => {
  382. return <OkBtn />;
  383. }}
  384. >
  385. <InputNumber
  386. className="w-full"
  387. min={150}
  388. max={1000}
  389. value={project.setting.tableWidth}
  390. onChange={(value) => handleChangeSetting("tableWidth", value)}
  391. />
  392. </Modal>
  393. </>
  394. );
  395. }