jsmind.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. <template>
  2. <!-- 普通菜单 -->
  3. <div class="jsmind_layout">
  4. <div class="noMind" v-if="mindV">
  5. <img src="../../assets/nominddata.png" alt />
  6. </div>
  7. <!-- <div class="jsmind_toolbar" v-if="showBar">
  8. <el-upload
  9. class="pad"
  10. :multiple="false"
  11. ref="upload"
  12. action="action"
  13. :before-upload="beforeUpload"
  14. :http-request="upload"
  15. >
  16. <el-button type="primary" size="medium">导入</el-button>
  17. </el-upload>
  18. <el-button @click="save_nodearray_file" size="medium">保存</el-button>
  19. <el-button @click="screen_shot" size="medium">下载导图</el-button>
  20. <el-button @click="get_nodearray_data" size="medium">获取数据</el-button>
  21. <el-button @click="addNode" size="medium">新增节点</el-button>
  22. <el-button @click="addBrotherNode" size="medium">新增兄弟节点</el-button>
  23. <el-button @click="editNode" size="medium">编辑节点</el-button>
  24. <el-button @click="removeNode" size="medium">删除节点</el-button>
  25. <el-button @click="zoomIn" size="medium" :disabled="isZoomIn">放大</el-button>
  26. <el-button @click="zoomOut" size="medium" :disabled="isZoomOut" class="pad">缩小</el-button>
  27. <span>展开:</span>
  28. <el-select
  29. v-model="level"
  30. placeholder="展开节点"
  31. @change="expand_to_level"
  32. class="pad pad-left"
  33. size="medium"
  34. >
  35. <el-option
  36. v-for="item in nodeOptions"
  37. :key="item.value"
  38. :label="item.label"
  39. :value="item.value"
  40. ></el-option>
  41. </el-select>
  42. <span>主题:</span>
  43. <el-select v-model="localTheme" placeholder="选择主题" @change="set_theme" size="medium">
  44. <el-option
  45. v-for="item in themeOptions"
  46. :key="item.value"
  47. :label="item.label"
  48. :value="item.value"
  49. ></el-option>
  50. </el-select>
  51. </div>-->
  52. <div id="jsmind_container" ref="container"></div>
  53. <!-- <el-drawer title="编辑节点" :visible.sync="dialogVisible" size="500px">
  54. <el-form label-width="80px" class="form-con">
  55. <el-form-item label="字体大小">
  56. <el-input-number
  57. controls-position="right"
  58. v-model.number="nodeOption.fontSize"
  59. class="ele-width"
  60. :min="1"
  61. :max="30"
  62. maxlength="2"
  63. ></el-input-number>
  64. </el-form-item>
  65. <el-form-item label="字体粗细">
  66. <el-select v-model="nodeOption.fontWeight" class="ele-width">
  67. <el-option value="normal" label="常规"></el-option>
  68. <el-option value="bold" label="粗体"></el-option>
  69. <el-option value="bolder" label="更粗"></el-option>
  70. </el-select>
  71. </el-form-item>
  72. <el-form-item label="字体样式">
  73. <el-select v-model="nodeOption.fontStyle" class="ele-width">
  74. <el-option value="normal" label="标准"></el-option>
  75. <el-option value="italic" label="斜体"></el-option>
  76. <el-option value="oblique" label="倾斜"></el-option>
  77. </el-select>
  78. </el-form-item>
  79. <el-row>
  80. <el-col :span="12">
  81. <el-form-item label="背景颜色">
  82. <el-color-picker v-model="nodeOption.bgColor" show-alpha size="mini"></el-color-picker>
  83. </el-form-item>
  84. </el-col>
  85. <el-col :span="12">
  86. <el-form-item label="字体颜色">
  87. <el-color-picker v-model="nodeOption.fontColor" show-alpha size="mini"></el-color-picker>
  88. </el-form-item>
  89. </el-col>
  90. </el-row>
  91. <el-form-item label="节点内容">
  92. <el-input
  93. type="textarea"
  94. :rows="2"
  95. v-model="nodeOption.content"
  96. class="ele-width"
  97. maxlength="64"
  98. ></el-input>
  99. </el-form-item>
  100. </el-form>
  101. <template v-slot:footer>
  102. <div class="right mr-10">
  103. <el-button type="primary" class="common-btn" @click="sureEditNode" size="medium">确 定</el-button>
  104. </div>
  105. </template>
  106. </el-drawer>-->
  107. </div>
  108. </template>
  109. <script>
  110. import "jsmind/style/jsmind.css";
  111. import jsMind from "jsmind/js/jsmind.js";
  112. window.jsMind = jsMind;
  113. require("jsmind/js/jsmind.draggable.js");
  114. require("jsmind/js/jsmind.screenshot.js");
  115. export default {
  116. props: {
  117. showBar: {
  118. // 是否显示工具栏,显示启用编辑
  119. type: Boolean,
  120. default: true,
  121. },
  122. theme: {
  123. // 主题
  124. type: String,
  125. default: "primary",
  126. },
  127. lineColor: {
  128. // 线条颜色
  129. type: String,
  130. default: "skyblue",
  131. },
  132. mindData: {
  133. type: Object,
  134. default: {},
  135. },
  136. },
  137. data() {
  138. return {
  139. mindV: false,
  140. i: 0,
  141. mind: {},
  142. jm: null,
  143. isZoomIn: false,
  144. isZoomOut: false,
  145. level: 0,
  146. nodeOptions: [
  147. { value: 1, label: "展开到一级节点" },
  148. { value: 2, label: "展开到二级节点" },
  149. { value: 3, label: "展开到三级节点" },
  150. { value: 0, label: "展开全部节点" },
  151. { value: -1, label: "隐藏全部节点" },
  152. ],
  153. themeOptions: [
  154. { value: "default", label: "default" },
  155. { value: "primary", label: "primary" },
  156. { value: "warning", label: "warning" },
  157. { value: "danger", label: "danger" },
  158. { value: "success", label: "success" },
  159. { value: "info", label: "info" },
  160. { value: "greensea", label: "greensea" },
  161. { value: "nephrite", label: "nephrite" },
  162. { value: "belizehole", label: "belizehole" },
  163. { value: "wisteria", label: "wisteria" },
  164. { value: "asphalt", label: "asphalt" },
  165. { value: "orange", label: "orange" },
  166. { value: "pumpkin", label: "pumpkin" },
  167. { value: "pomegranate", label: "pomegranate" },
  168. { value: "clouds", label: "clouds" },
  169. { value: "asbestos", label: "asbestos" },
  170. ],
  171. localTheme: this.theme,
  172. dialogVisible: false,
  173. nodeOption: {
  174. content: "",
  175. bgColor: "",
  176. fontColor: "",
  177. fontSize: "",
  178. fontWeight: "",
  179. fontStyle: "",
  180. },
  181. };
  182. },
  183. watch: {
  184. mindData: {
  185. handler: function (cur, old) {
  186. this.mind = cur;
  187. if (cur.data.length) {
  188. if (cur.data[0].topic === "" && cur.data.length === 1) {
  189. this.mindV = true;
  190. } else {
  191. this.mindV = false;
  192. }
  193. if (this.jm) {
  194. this.jm.show(this.mind);
  195. } else {
  196. this.open_empty();
  197. }
  198. }
  199. },
  200. deep: true, //对象内部的属性监听,也叫深度监听
  201. },
  202. },
  203. created() {},
  204. mounted() {
  205. this.getData();
  206. // this.mouseWheel();
  207. },
  208. methods: {
  209. beforeUpload(file) {
  210. // 上传文件之前钩子
  211. if (file) {
  212. jsMind.util.file.read(file, (jsmindData) => {
  213. const mind = jsMind.util.json.string2json(jsmindData);
  214. if (mind) {
  215. this.jm.show(mind);
  216. this.$message({ type: "success", message: "打开成功" });
  217. } else {
  218. this.prompt_info("不能打开mindmap文件");
  219. }
  220. });
  221. } else {
  222. this.prompt_info("请先选择文件");
  223. return false;
  224. }
  225. },
  226. upload() {},
  227. getData() {
  228. // this.$API({
  229. // name: "getMind",
  230. // })
  231. // .then((res) => {
  232. // this.mind = res.data;
  233. // this.open_empty();
  234. // })
  235. // .catch((error) => {
  236. // this.$message.error(error);
  237. // });
  238. if (
  239. !this.mind.data ||
  240. (this.mind.data[0].topic === "" && this.mind.data.length === 1)
  241. ) {
  242. this.mindV = true;
  243. } else {
  244. this.mindV = false;
  245. }
  246. this.mind = this.mindData;
  247. this.open_empty();
  248. },
  249. open_empty() {
  250. const options = {
  251. container: "jsmind_container", // 必选,容器ID
  252. editable: this.showBar, // 可选,是否启用编辑
  253. theme: this.localTheme, // 可选,主题
  254. view: {
  255. line_width: 2, // 思维导图线条的粗细
  256. // line_color: this.lineColor, // 思维导图线条的颜色
  257. },
  258. shortcut: {
  259. enable: true, // 禁用快捷键
  260. },
  261. layout: {
  262. hspace: 20, // 节点之间的水平间距
  263. vspace: 10, // 节点之间的垂直间距
  264. pspace: 13, // 节点与连接线之间的水平间距(用于容纳节点收缩/展开控制器)
  265. },
  266. mode: "side", // 显示模式,子节点只分布在根节点右侧
  267. };
  268. this.jm = jsMind.show(options, this.mind);
  269. // 改变窗口大小重置画布
  270. window.onresize = () => {
  271. this.jm.resize();
  272. };
  273. this.getDepth(this.jm.mind.root, 1);
  274. this.$forceUpdate();
  275. },
  276. // 获取层级数 i
  277. getDepth(obj, k) {
  278. this.i = Math.max(this.i, k);
  279. if (obj.children) {
  280. obj.children.forEach((v) => {
  281. this.getDepth(v, k + 1);
  282. });
  283. }
  284. },
  285. save_nodearray_file() {
  286. const mindData = this.jm.get_data("node_array");
  287. const mindName = mindData.meta.name;
  288. const mindStr = jsMind.util.json.json2string(mindData);
  289. jsMind.util.file.save(mindStr, "text/jsmind", mindName + ".jm");
  290. },
  291. screen_shot() {
  292. this.jm.screenshot.shootDownload();
  293. },
  294. expand_all() {
  295. this.jm.expand_all();
  296. },
  297. collapse_all() {
  298. this.jm.collapse_all();
  299. },
  300. expand_to_level(num) {
  301. switch (num) {
  302. case -1:
  303. this.collapse_all();
  304. break;
  305. case 0:
  306. this.expand_all();
  307. break;
  308. default:
  309. this.jm.expand_to_depth(num);
  310. break;
  311. }
  312. },
  313. zoomIn() {
  314. if (this.jm.view.zoomIn()) {
  315. this.isZoomOut = false;
  316. } else {
  317. this.isZoomIn = true;
  318. }
  319. },
  320. zoomOut() {
  321. // debugger;
  322. if (this.jm.view.zoomOut()) {
  323. this.isZoomIn = false;
  324. } else {
  325. this.isZoomOut = true;
  326. }
  327. },
  328. prompt_info(msg) {
  329. this.$message({ type: "warning", message: msg });
  330. },
  331. get_nodearray_data() {
  332. const mindData = this.jm.get_data("node_array");
  333. const mindString = jsMind.util.json.json2string(mindData);
  334. this.$message({ type: "info", message: mindString });
  335. },
  336. set_theme(themeName) {
  337. this.jm.set_theme(themeName);
  338. },
  339. scrollFunc(e) {
  340. e = e || window.event;
  341. if (e.wheelDelta) {
  342. if (e.wheelDelta > 0) {
  343. this.zoomIn();
  344. } else {
  345. this.zoomOut();
  346. }
  347. } else if (e.detail) {
  348. if (e.detail > 0) {
  349. this.zoomIn();
  350. } else {
  351. this.zoomOut();
  352. }
  353. }
  354. this.jm.resize();
  355. },
  356. // 鼠标滚轮放大缩小
  357. mouseWheel() {
  358. if (document.addEventListener) {
  359. document.addEventListener("domMouseScroll", this.scrollFunc, false);
  360. }
  361. this.$refs.container.onmousewheel = this.scrollFunc;
  362. },
  363. // 新增节点
  364. addNode() {
  365. let selectedNode = this.jm.get_selected_node();
  366. if (!selectedNode) {
  367. this.$message({ type: "warning", message: "请先选择一个节点!" });
  368. return;
  369. }
  370. let nodeid = jsMind.util.uuid.newid();
  371. let topic = "new Node";
  372. let newNode = this.jm.add_node(selectedNode, nodeid, topic);
  373. if (newNode) {
  374. this.jm.select_node(nodeid);
  375. this.jm.begin_edit(nodeid);
  376. this.getDepth(this.jm.mind.root, 1);
  377. }
  378. },
  379. // 新增兄弟节点
  380. addBrotherNode() {
  381. let selectedNode = this.jm.get_selected_node();
  382. if (!selectedNode) {
  383. this.$message({ type: "warning", message: "请先选择一个节点!" });
  384. return;
  385. } else if (selectedNode.isroot) {
  386. this.$message({
  387. type: "warning",
  388. message: "不能在根节点添加,请重新选择节点!",
  389. });
  390. return;
  391. }
  392. let nodeid = jsMind.util.uuid.newid();
  393. let topic = "new Node";
  394. let newNode = this.jm.insert_node_after(selectedNode, nodeid, topic);
  395. if (newNode) {
  396. this.jm.select_node(nodeid);
  397. this.jm.begin_edit(nodeid);
  398. }
  399. },
  400. // 获取选中标签的 ID
  401. get_selected_nodeid() {
  402. let selectedNode = this.jm.get_selected_node();
  403. if (selectedNode) {
  404. return selectedNode.id;
  405. } else {
  406. return null;
  407. }
  408. },
  409. // 删除节点
  410. removeNode() {
  411. let selectedId = this.get_selected_nodeid();
  412. if (!selectedId) {
  413. this.$message({
  414. type: "warning",
  415. message: "请先选择一个节点!",
  416. });
  417. return;
  418. }
  419. this.jm.remove_node(selectedId);
  420. this.i = 0;
  421. this.getDepth(this.jm.mind.root, 1);
  422. },
  423. // 编辑节点
  424. editNode() {
  425. let selectedId = this.get_selected_nodeid();
  426. if (!selectedId) {
  427. this.$message({ type: "warning", message: "请先选择一个节点!" });
  428. return;
  429. }
  430. let nodeObj = this.jm.get_node(selectedId);
  431. this.nodeOption.content = nodeObj.topic;
  432. this.nodeOption.bgColor = nodeObj.data["background-color"];
  433. this.nodeOption.fontColor = nodeObj.data["foreground-color"];
  434. this.nodeOption.fontSize = nodeObj.data["font-size"];
  435. this.nodeOption.fontWeight = nodeObj.data["font-weight"];
  436. this.nodeOption.fontStyle = nodeObj.data["font-style"];
  437. this.dialogVisible = true;
  438. },
  439. sureEditNode() {
  440. let selectedId = this.get_selected_nodeid();
  441. this.jm.update_node(selectedId, this.nodeOption.content);
  442. this.jm.set_node_font_style(
  443. selectedId,
  444. this.nodeOption.fontSize,
  445. this.nodeOption.fontWeight,
  446. this.nodeOption.fontStyle
  447. );
  448. this.jm.set_node_color(
  449. selectedId,
  450. this.nodeOption.bgColor,
  451. this.nodeOption.fontColor
  452. );
  453. this.nodeOption = {
  454. content: "",
  455. bgColor: "",
  456. fontColor: "",
  457. fontSize: "",
  458. fontWeight: "",
  459. fontStyle: "",
  460. };
  461. this.dialogVisible = false;
  462. },
  463. },
  464. beforeDestroy() {
  465. // document.removeEventListener("domMouseScroll", this.scrollFunc, false);
  466. },
  467. };
  468. </script>
  469. <style scoped>
  470. .jsmind_layout {
  471. display: flex;
  472. flex-direction: column;
  473. width: 100%;
  474. height: calc(100%);
  475. /* height: 500px; */
  476. /* margin: 15px 5px 0 0; */
  477. background: #fff;
  478. overflow: hidden;
  479. flex-shrink: 0;
  480. position: relative;
  481. }
  482. .jsmind_title {
  483. position: absolute;
  484. top: 20px;
  485. left: 20px;
  486. font-size: 20px;
  487. color: #8d8d8d;
  488. }
  489. .noMind {
  490. position: absolute;
  491. display: flex;
  492. justify-content: center;
  493. align-items: center;
  494. width: 100%;
  495. height: 100%;
  496. z-index: 999;
  497. background: #fff;
  498. }
  499. .jsmind_layout .jsmind_toolbar {
  500. width: 100%;
  501. padding: 0 10px 10px 10px;
  502. height: auto;
  503. flex-shrink: 0;
  504. display: flex;
  505. align-items: center;
  506. flex-wrap: wrap;
  507. background-color: #f8f9fa;
  508. box-shadow: 0 0 4px #b8b8b8;
  509. }
  510. .jsmind_layout >>> .el-button--medium,
  511. .jsmind_layout >>> .el-input--medium {
  512. margin-top: 10px;
  513. }
  514. .jsmind_layout #jsmind_container {
  515. /* flex: 1 1 auto; */
  516. height: 100%;
  517. }
  518. .jsmind_layout >>> .jsmind-inner {
  519. /* overflow: hidden auto !important; */
  520. /* height: auto; */
  521. }
  522. .jsmind_layout >>> .el-upload-list {
  523. display: none !important;
  524. }
  525. /* 隐藏滚动条 */
  526. .jsmind_layout .jsmind-inner::-webkit-scrollbar {
  527. display: none;
  528. }
  529. .jsmind_layout .pad {
  530. margin-right: 10px;
  531. }
  532. .jsmind_layout .pad-left {
  533. margin-left: 10px;
  534. }
  535. .jsmind_layout >>> jmnode.selected {
  536. background-color: #b9b9b9;
  537. color: #fff;
  538. box-shadow: 2px 2px 8px #777;
  539. }
  540. .jsmind_layout >>> jmnode:hover {
  541. box-shadow: 2px 2px 8px #777;
  542. }
  543. .jsmind_layout .form-con {
  544. padding-top: 20px;
  545. }
  546. .jsmind_layout .ele-width {
  547. width: 96%;
  548. }
  549. </style>