index.tsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import {
  2. AimOutlined,
  3. CompressOutlined,
  4. ExpandOutlined,
  5. MinusOutlined,
  6. PlusOutlined,
  7. QuestionCircleFilled,
  8. } from "@ant-design/icons";
  9. import { Button, ConfigProvider, Divider, Slider, Tooltip } from "antd";
  10. import React, { useEffect, useMemo, useRef, useState } from "react";
  11. import { useFullscreen } from "ahooks";
  12. import { useModel } from "umi";
  13. import { MiniMap } from "@antv/x6-plugin-minimap";
  14. import insertCss from "insert-css";
  15. import { TopicType } from "@/enum";
  16. insertCss(`
  17. .navigation-view {
  18. position: absolute;
  19. bottom: 32px;
  20. right: 32px;
  21. width: 330px;
  22. height: 210px;
  23. background-color: #fff;
  24. border: 1px solid #e9edf2;
  25. border-radius: 8px 8px 0 0;
  26. overflow: hidden;
  27. z-index: 1;
  28. box-shadow: 0 4px 10px 1px rgba(0, 0, 0, .1);
  29. }`);
  30. export default function Footer() {
  31. const [isFullscreen, { toggleFullscreen }] = useFullscreen(document.body);
  32. const navigationViewRef = useRef(null);
  33. const [showNavigation, setShowNavigation] = useState(false);
  34. const [scale, setScale] = useState(100);
  35. const { selectedCell, graph } = useModel("mindMapModel");
  36. useEffect(() => {
  37. if (!graph || !navigationViewRef.current) return;
  38. graph.use(
  39. new MiniMap({
  40. container: navigationViewRef.current,
  41. width: 330,
  42. height: 210,
  43. padding: 10,
  44. })
  45. );
  46. }, [graph, navigationViewRef.current]);
  47. graph?.on("scale", (args) => {
  48. setScale(args.sx * 100);
  49. });
  50. const countInfo = useMemo(() => {
  51. return {
  52. selectedNodeCount: selectedCell.filter((cell) => cell.isNode()).length,
  53. selectedNodeTextCount: selectedCell.reduce(
  54. (a, b) => a + b.data?.label?.length || 0,
  55. 0
  56. ),
  57. nodeCount: graph?.getNodes().length || 0,
  58. textCount:
  59. graph?.getNodes().reduce((a, b) => a + b.data?.label?.length || 0, 0) ||
  60. 0,
  61. };
  62. }, [selectedCell, graph]);
  63. const handleZoom = (value: number) => {
  64. graph?.zoomTo(value / 100);
  65. };
  66. const handleZoomFit = () => {
  67. graph?.zoomTo(1);
  68. };
  69. const handleOnChange = (value: number) => {
  70. setScale(value);
  71. handleZoom(value);
  72. };
  73. const handleFocusCenter = () => {
  74. const center = graph
  75. ?.getCells()
  76. .find((cell) => cell.data?.type === TopicType.main);
  77. center && graph?.centerCell(center);
  78. };
  79. return (
  80. <ConfigProvider componentSize="small">
  81. <div className="absolute w-full h-24px left-0 bottom-0 bg-white flex justify-between items-center px-16px">
  82. <div className="footer-left"></div>
  83. <div className="footer-right flex items-center">
  84. <div className="mr-8px">
  85. 字数:
  86. {countInfo.selectedNodeCount
  87. ? `${countInfo.selectedNodeTextCount}/`
  88. : ""}
  89. {countInfo.textCount}
  90. </div>
  91. <div>
  92. 主题数:
  93. {countInfo.selectedNodeCount
  94. ? `${countInfo.selectedNodeCount}/`
  95. : ""}
  96. {countInfo.nodeCount}
  97. </div>
  98. <Divider type="vertical" />
  99. <Tooltip title="模板">
  100. <Button type="text" icon={<i className="iconfont icon-buju" />} />
  101. </Tooltip>
  102. <Tooltip title="定位到中心主题">
  103. <Button
  104. type="text"
  105. icon={<AimOutlined />}
  106. onClick={handleFocusCenter}
  107. />
  108. </Tooltip>
  109. <Tooltip title={showNavigation ? "关闭视图导航" : "显示视图导航"}>
  110. <Button
  111. type="text"
  112. icon={<i className="iconfont icon-map" />}
  113. onClick={() => setShowNavigation(!showNavigation)}
  114. />
  115. </Tooltip>
  116. <div
  117. className="navigation-view"
  118. style={{ display: showNavigation ? "block" : "none" }}
  119. ref={navigationViewRef}
  120. ></div>
  121. <Button
  122. type="text"
  123. icon={<MinusOutlined />}
  124. onClick={() => handleZoom(scale - 2)}
  125. />
  126. <Slider
  127. min={20}
  128. max={200}
  129. className="w-120px m-0 mx-8px"
  130. tooltip={{ formatter: (val) => `${val}%` }}
  131. value={scale}
  132. onChange={handleOnChange}
  133. />
  134. <Button
  135. type="text"
  136. icon={<PlusOutlined />}
  137. onClick={() => handleZoom(scale + 2)}
  138. />
  139. <Tooltip title="重置缩放">
  140. <div
  141. className="cursor-pointer mx-8px w-40px"
  142. onClick={handleZoomFit}
  143. >
  144. {scale}%
  145. </div>
  146. </Tooltip>
  147. {isFullscreen ? (
  148. <Button
  149. type="text"
  150. icon={<CompressOutlined />}
  151. onClick={toggleFullscreen}
  152. />
  153. ) : (
  154. <Button
  155. type="text"
  156. icon={<ExpandOutlined />}
  157. onClick={toggleFullscreen}
  158. />
  159. )}
  160. <Divider type="vertical" />
  161. <Button type="text" icon={<QuestionCircleFilled />} />
  162. </div>
  163. </div>
  164. </ConfigProvider>
  165. );
  166. }