MindmapModal.vue 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. <template>
  2. <div v-if="visible" class="mindmap-modal" style="user-select: none">
  3. <div class="mindmap-modal_tool">
  4. <el-form ref="formRef" :model="formData" class="flex items-center" inline>
  5. <el-form-item label="收缩至" prop="level" class="mr-12px">
  6. <el-select
  7. v-model="formData.level"
  8. placeholder="请选择"
  9. style="width: 120px"
  10. @change="changeLevel"
  11. >
  12. <el-option
  13. v-for="item in 10"
  14. :key="item"
  15. :label="`${item}级`"
  16. :value="item"
  17. />
  18. </el-select>
  19. </el-form-item>
  20. <el-form-item label="布局" prop="layout" class="mr-12px">
  21. <el-select
  22. v-model="formData.layout"
  23. placeholder="请选择"
  24. style="width: 120px"
  25. @change="changeLayout"
  26. >
  27. <el-option label="逻辑右" value="logicalStructure" />
  28. <el-option label="逻辑左" value="logicalStructureLeft" />
  29. <el-option label="垂直" value="organizationStructure" />
  30. </el-select>
  31. </el-form-item>
  32. <el-form-item label="搜索" props="search">
  33. <div class="flex">
  34. <el-input v-model="formData.search" style="width: 120px" />
  35. <el-button
  36. class="ml-12px"
  37. :icon="Search"
  38. @click="handleSearch"
  39. ></el-button>
  40. </div>
  41. </el-form-item>
  42. </el-form>
  43. <div class="flex gap-4px">
  44. <el-button type="primary" link @click="handleSave" class="mr-8px"
  45. ><img class="w-1em mr-4px" :src="saveImg" />保存</el-button
  46. >
  47. <!-- <el-tooltip content="添加">
  48. <el-button
  49. type="default"
  50. circle
  51. :icon="CirclePlusFilled"
  52. @click="addNode"
  53. />
  54. </el-tooltip> -->
  55. <el-tooltip content="详情">
  56. <el-button
  57. type="default"
  58. circle
  59. :icon="InfoFilled"
  60. @click="openNodeDetail"
  61. />
  62. </el-tooltip>
  63. <el-tooltip content="自适应">
  64. <el-button type="default" circle @click="autoFit">
  65. <img :src="AutoIcon" style="width: 1em" />
  66. </el-button>
  67. </el-tooltip>
  68. <el-tooltip content="定位到根节点">
  69. <el-button type="default" circle :icon="Aim" @click="zoomToRoot" />
  70. </el-tooltip>
  71. <el-tooltip content="放大">
  72. <el-button
  73. type="default"
  74. circle
  75. :icon="ZoomIn"
  76. @click="handleZoom(1)"
  77. />
  78. </el-tooltip>
  79. <el-tooltip content="缩小">
  80. <el-button
  81. type="default"
  82. circle
  83. :icon="ZoomOut"
  84. @click="handleZoom(0)"
  85. />
  86. </el-tooltip>
  87. </div>
  88. </div>
  89. <div class="mindmap-modal_body">
  90. <Mindmap
  91. v-model:data="data"
  92. ref="mindmapRef"
  93. @node-dbl-click="handleNodeDbClick"
  94. />
  95. </div>
  96. <!-- <div class="mindmap-modal_footer">
  97. </div> -->
  98. </div>
  99. <ConfigDrawer ref="configDrawerRef" @ok="handleConfigOk"/>
  100. </template>
  101. <script setup lang="ts">
  102. import { ref, defineExpose, reactive, defineProps, watch } from "vue";
  103. import {
  104. Aim,
  105. // CirclePlusFilled,
  106. // Close,
  107. InfoFilled,
  108. Search,
  109. ZoomIn,
  110. ZoomOut,
  111. } from "@element-plus/icons-vue";
  112. import type { MindMapInstance } from "@/components/mindmap/Mindmap.vue";
  113. import type { FormInstance } from "element-plus";
  114. import { ElMessage } from "element-plus";
  115. import { bfsWalk } from "simple-mind-map/src/utils";
  116. // import { useEditBomStore } from "@/store/editbom";
  117. import Mindmap from "@/components/mindmap/Mindmap.vue";
  118. import ConfigDrawer from "./ConfigDrawer.vue";
  119. import AutoIcon from "@/assets/auto.svg";
  120. import saveImg from "@/assets/save.svg";
  121. const props = defineProps<{
  122. defaultOpen?: boolean;
  123. hideClose?: boolean;
  124. defaultData?: any;
  125. }>();
  126. const visible = ref(!!props.defaultOpen);
  127. const mindmapRef = ref<MindMapInstance>();
  128. const configDrawerRef = ref();
  129. const formRef = ref<FormInstance>();
  130. // const editBomStore = useEditBomStore();
  131. const data = ref(
  132. props?.defaultData || {
  133. id: "root",
  134. data: {
  135. name: "根节点",
  136. },
  137. children: [],
  138. }
  139. );
  140. // 1、初次读取本地缓存配置
  141. const defaultConfig = JSON.parse(localStorage.getItem("mindmap-config") || "{}");
  142. const formData = reactive({
  143. level: defaultConfig?.level,
  144. layout: defaultConfig?.layout ?? "logicalStructure",
  145. search: defaultConfig?.search || "",
  146. });
  147. // 2、监听配置项变化,保存到本地缓存
  148. watch(
  149. () => formData,
  150. (val) => {
  151. localStorage.setItem("mindmap-config", JSON.stringify(val));
  152. },
  153. {
  154. deep: true
  155. }
  156. );
  157. // 定位到根节点
  158. const zoomToRoot = () => {
  159. const mindmap = mindmapRef.value?.getInstance();
  160. mindmap?.renderer?.setRootNodeCenter();
  161. };
  162. // 切换布局
  163. const changeLayout = (value: string) => {
  164. const mindmap = mindmapRef.value?.getInstance();
  165. mindmap?.setLayout(value);
  166. let fited = false;
  167. mindmap?.on("node_tree_render_end", () => {
  168. // @ts-ignore
  169. !fited && mindmap?.view?.fit();
  170. fited = true;
  171. });
  172. };
  173. // 切换层级
  174. const changeLevel = (level: number) => {
  175. const mindmap = mindmapRef.value?.getInstance();
  176. mindmap?.execCommand("UNEXPAND_TO_LEVEL", level);
  177. };
  178. const autoFit = () => {
  179. const mindmap = mindmapRef.value?.getInstance();
  180. // @ts-ignore
  181. mindmap?.view?.fit();
  182. };
  183. // 节点详情
  184. const openNodeDetail = () => {
  185. const activeList = mindmapRef.value?.getActiveNodeList();
  186. if (!activeList?.length) {
  187. ElMessage.warning("请选择查看节点");
  188. return;
  189. }
  190. configDrawerRef.value.open(activeList[0].nodeData);
  191. };
  192. // 缩放
  193. const handleZoom = (num: number) => {
  194. const mindmap = mindmapRef.value?.getInstance();
  195. if (num) {
  196. // @ts-ignore
  197. mindmap?.view?.enlarge();
  198. } else {
  199. // @ts-ignore
  200. mindmap?.view?.narrow();
  201. }
  202. };
  203. // 添加节点
  204. // const addNode = () => {
  205. // const mindmap = mindmapRef.value?.getInstance();
  206. // const activeList = mindmapRef.value?.getActiveNodeList();
  207. // if (!activeList?.length) {
  208. // ElMessage.warning("请选择父节点");
  209. // return;
  210. // }
  211. // mindmap?.execCommand("INSERT_CHILD_NODE");
  212. // };
  213. // 搜索
  214. const handleSearch = () => {
  215. const result: any[] = [];
  216. const mindmap = mindmapRef.value?.getInstance();
  217. const data = mindmap?.getData(false);
  218. mindmap?.execCommand("CLEAR_ACTIVE_NODE");
  219. bfsWalk(data, (node: any) => {
  220. if (node.data?.name?.includes(formData.search)) {
  221. result.push(node);
  222. }
  223. });
  224. if (result.length) {
  225. result.forEach((node) => {
  226. const n = mindmap?.renderer.findNodeByUid(node.data?.uid);
  227. n?.active();
  228. });
  229. } else {
  230. ElMessage.warning("未找到节点");
  231. }
  232. };
  233. const handleNodeDbClick = (node: any) => {
  234. console.log("handleNodeDbClick", node);
  235. if (node.isRoot) return;
  236. configDrawerRef.value.open(node.nodeData);
  237. };
  238. // 节点配置结束
  239. const handleConfigOk = (updateNodeData: any) => {
  240. const mindmap = mindmapRef.value?.getInstance();
  241. const allData = mindmap?.getData(false);
  242. bfsWalk(allData, (node: any) => {
  243. if (node.data.uid === updateNodeData.uid) {
  244. node.data = updateNodeData;
  245. }
  246. });
  247. mindmap?.updateData(allData);
  248. }
  249. // 保存
  250. const handleSave = () => {
  251. const mindmap = mindmapRef.value?.getInstance();
  252. const data = mindmap?.getData(false);
  253. bfsWalk(data, (node: any) => {
  254. delete node.smmVersion;
  255. delete node.data.uid;
  256. delete node.data.expand;
  257. delete node.data.isActive;
  258. });
  259. console.log("保存数据:", data);
  260. try {
  261. window.parent?.BpmTools?.program(
  262. {
  263. interfaceCode: "Common.doSaveBOMAiImageData",
  264. model: data,
  265. },
  266. (res: any) => {
  267. console.log("保存结果:", res);
  268. ElMessage.success("保存成功!");
  269. }
  270. );
  271. } catch (error) {
  272. console.log(error);
  273. ElMessage.error("保存失败!");
  274. }
  275. };
  276. watch(
  277. () => props?.defaultData,
  278. (val) => {
  279. data.value = val;
  280. }
  281. );
  282. // 打开弹窗
  283. const open = () => {
  284. visible.value = true;
  285. };
  286. // 关闭弹窗
  287. const close = () => {
  288. formRef.value?.resetFields();
  289. visible.value = false;
  290. };
  291. defineExpose({
  292. open,
  293. close,
  294. });
  295. </script>
  296. <style lang="less" scoped>
  297. .mindmap-modal {
  298. position: absolute;
  299. top: 0;
  300. left: 0;
  301. width: 100%;
  302. height: 100%;
  303. z-index: 99;
  304. display: flex;
  305. flex-direction: column;
  306. align-items: center;
  307. &_footer {
  308. height: 40px;
  309. width: 100%;
  310. box-sizing: border-box;
  311. background: #fff;
  312. display: flex;
  313. // justify-content: end;
  314. align-items: center;
  315. padding: 0 20px;
  316. line-height: 40px;
  317. }
  318. &_tool {
  319. width: 100%;
  320. box-sizing: border-box;
  321. background: #fff;
  322. display: flex;
  323. justify-content: space-between;
  324. align-items: center;
  325. padding: 0 20px;
  326. }
  327. &_body {
  328. flex: 1;
  329. width: 100%;
  330. background: #fafafa;
  331. overflow: hidden;
  332. }
  333. }
  334. :deep(*) {
  335. .el-form-item {
  336. margin-top: 12px;
  337. margin-bottom: 12px;
  338. }
  339. }
  340. </style>