jsmind.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  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 from "jsmind";
  111. import "jsmind/style/jsmind.css";
  112. export default {
  113. props: {
  114. showBar: {
  115. // 是否显示工具栏,显示启用编辑
  116. type: Boolean,
  117. default: true,
  118. },
  119. theme: {
  120. // 主题
  121. type: String,
  122. default: "primary",
  123. },
  124. lineColor: {
  125. // 线条颜色
  126. type: String,
  127. default: "skyblue",
  128. },
  129. mindData: {
  130. type: Object,
  131. default: {},
  132. },
  133. },
  134. data() {
  135. return {
  136. mindV: false,
  137. i: 0,
  138. mind: {},
  139. jm: null,
  140. isZoomIn: false,
  141. isZoomOut: false,
  142. level: 0,
  143. nodeOptions: [
  144. { value: 1, label: "展开到一级节点" },
  145. { value: 2, label: "展开到二级节点" },
  146. { value: 3, label: "展开到三级节点" },
  147. { value: 0, label: "展开全部节点" },
  148. { value: -1, label: "隐藏全部节点" },
  149. ],
  150. themeOptions: [
  151. { value: "default", label: "default" },
  152. { value: "primary", label: "primary" },
  153. { value: "warning", label: "warning" },
  154. { value: "danger", label: "danger" },
  155. { value: "success", label: "success" },
  156. { value: "info", label: "info" },
  157. { value: "greensea", label: "greensea" },
  158. { value: "nephrite", label: "nephrite" },
  159. { value: "belizehole", label: "belizehole" },
  160. { value: "wisteria", label: "wisteria" },
  161. { value: "asphalt", label: "asphalt" },
  162. { value: "orange", label: "orange" },
  163. { value: "pumpkin", label: "pumpkin" },
  164. { value: "pomegranate", label: "pomegranate" },
  165. { value: "clouds", label: "clouds" },
  166. { value: "asbestos", label: "asbestos" },
  167. ],
  168. localTheme: this.theme,
  169. dialogVisible: false,
  170. nodeOption: {
  171. content: "",
  172. bgColor: "",
  173. fontColor: "",
  174. fontSize: "",
  175. fontWeight: "",
  176. fontStyle: "",
  177. },
  178. };
  179. },
  180. watch: {
  181. mindData: {
  182. handler: function (cur, old) {
  183. this.mind = cur;
  184. if (cur.data.length) {
  185. if (cur.data[0].topic === "" && cur.data.length === 1) {
  186. this.mindV = true;
  187. } else {
  188. this.mindV = false;
  189. }
  190. if (this.jm) {
  191. this.jm.show(this.mind);
  192. } else {
  193. this.open_empty();
  194. }
  195. }
  196. },
  197. deep: true, //对象内部的属性监听,也叫深度监听
  198. },
  199. },
  200. created() { },
  201. mounted() {
  202. this.getData();
  203. // this.mouseWheel();
  204. },
  205. methods: {
  206. beforeUpload(file) {
  207. // 上传文件之前钩子
  208. if (file) {
  209. jsMind.util.file.read(file, (jsmindData) => {
  210. const mind = jsMind.util.json.string2json(jsmindData);
  211. if (mind) {
  212. this.jm.show(mind);
  213. this.$message({ type: "success", message: "打开成功" });
  214. } else {
  215. this.prompt_info("不能打开mindmap文件");
  216. }
  217. });
  218. } else {
  219. this.prompt_info("请先选择文件");
  220. return false;
  221. }
  222. },
  223. upload() { },
  224. getData() {
  225. // this.$API({
  226. // name: "getMind",
  227. // })
  228. // .then((res) => {
  229. // this.mind = res.data;
  230. // this.open_empty();
  231. // })
  232. // .catch((error) => {
  233. // this.$message.error(error);
  234. // });
  235. if (
  236. !this.mind.data ||
  237. (this.mind.data[0].topic === "" && this.mind.data.length === 1)
  238. ) {
  239. this.mindV = true;
  240. } else {
  241. this.mindV = false;
  242. }
  243. this.mind = this.mindData;
  244. this.open_empty();
  245. },
  246. open_empty() {
  247. const options = {
  248. container: "jsmind_container", // 必选,容器ID
  249. editable: this.showBar, // 可选,是否启用编辑
  250. theme: this.localTheme, // 可选,主题
  251. view: {
  252. line_width: 2, // 思维导图线条的粗细
  253. // line_color: this.lineColor, // 思维导图线条的颜色
  254. },
  255. shortcut: {
  256. enable: true, // 禁用快捷键
  257. },
  258. layout: {
  259. hspace: 20, // 节点之间的水平间距
  260. vspace: 10, // 节点之间的垂直间距
  261. pspace: 13, // 节点与连接线之间的水平间距(用于容纳节点收缩/展开控制器)
  262. },
  263. mode: "side", // 显示模式,子节点只分布在根节点右侧
  264. };
  265. this.jm = jsMind.show(options, this.mind);
  266. // 改变窗口大小重置画布
  267. window.onresize = () => {
  268. this.jm.resize();
  269. };
  270. this.getDepth(this.jm.mind.root, 1);
  271. this.$forceUpdate();
  272. },
  273. // 获取层级数 i
  274. getDepth(obj, k) {
  275. this.i = Math.max(this.i, k);
  276. if (obj.children) {
  277. obj.children.forEach((v) => {
  278. this.getDepth(v, k + 1);
  279. });
  280. }
  281. },
  282. save_nodearray_file() {
  283. const mindData = this.jm.get_data("node_array");
  284. const mindName = mindData.meta.name;
  285. const mindStr = jsMind.util.json.json2string(mindData);
  286. jsMind.util.file.save(mindStr, "text/jsmind", mindName + ".jm");
  287. },
  288. screen_shot() {
  289. this.jm.screenshot.shootDownload();
  290. },
  291. expand_all() {
  292. this.jm.expand_all();
  293. },
  294. collapse_all() {
  295. this.jm.collapse_all();
  296. },
  297. expand_to_level(num) {
  298. switch (num) {
  299. case -1:
  300. this.collapse_all();
  301. break;
  302. case 0:
  303. this.expand_all();
  304. break;
  305. default:
  306. this.jm.expand_to_depth(num);
  307. break;
  308. }
  309. },
  310. zoomIn() {
  311. if (this.jm.view.zoomIn()) {
  312. this.isZoomOut = false;
  313. } else {
  314. this.isZoomIn = true;
  315. }
  316. },
  317. zoomOut() {
  318. // debugger;
  319. if (this.jm.view.zoomOut()) {
  320. this.isZoomIn = false;
  321. } else {
  322. this.isZoomOut = true;
  323. }
  324. },
  325. prompt_info(msg) {
  326. this.$message({ type: "warning", message: msg });
  327. },
  328. get_nodearray_data() {
  329. const mindData = this.jm.get_data("node_array");
  330. const mindString = jsMind.util.json.json2string(mindData);
  331. this.$message({ type: "info", message: mindString });
  332. },
  333. set_theme(themeName) {
  334. this.jm.set_theme(themeName);
  335. },
  336. scrollFunc(e) {
  337. e = e || window.event;
  338. if (e.wheelDelta) {
  339. if (e.wheelDelta > 0) {
  340. this.zoomIn();
  341. } else {
  342. this.zoomOut();
  343. }
  344. } else if (e.detail) {
  345. if (e.detail > 0) {
  346. this.zoomIn();
  347. } else {
  348. this.zoomOut();
  349. }
  350. }
  351. this.jm.resize();
  352. },
  353. // 鼠标滚轮放大缩小
  354. mouseWheel() {
  355. if (document.addEventListener) {
  356. document.addEventListener("domMouseScroll", this.scrollFunc, false);
  357. }
  358. this.$refs.container.onmousewheel = this.scrollFunc;
  359. },
  360. // 新增节点
  361. addNode() {
  362. let selectedNode = this.jm.get_selected_node();
  363. if (!selectedNode) {
  364. this.$message({ type: "warning", message: "请先选择一个节点!" });
  365. return;
  366. }
  367. let nodeid = jsMind.util.uuid.newid();
  368. let topic = "new Node";
  369. let newNode = this.jm.add_node(selectedNode, nodeid, topic);
  370. if (newNode) {
  371. this.jm.select_node(nodeid);
  372. this.jm.begin_edit(nodeid);
  373. this.getDepth(this.jm.mind.root, 1);
  374. }
  375. },
  376. // 新增兄弟节点
  377. addBrotherNode() {
  378. let selectedNode = this.jm.get_selected_node();
  379. if (!selectedNode) {
  380. this.$message({ type: "warning", message: "请先选择一个节点!" });
  381. return;
  382. } else if (selectedNode.isroot) {
  383. this.$message({
  384. type: "warning",
  385. message: "不能在根节点添加,请重新选择节点!",
  386. });
  387. return;
  388. }
  389. let nodeid = jsMind.util.uuid.newid();
  390. let topic = "new Node";
  391. let newNode = this.jm.insert_node_after(selectedNode, nodeid, topic);
  392. if (newNode) {
  393. this.jm.select_node(nodeid);
  394. this.jm.begin_edit(nodeid);
  395. }
  396. },
  397. // 获取选中标签的 ID
  398. get_selected_nodeid() {
  399. let selectedNode = this.jm.get_selected_node();
  400. if (selectedNode) {
  401. return selectedNode.id;
  402. } else {
  403. return null;
  404. }
  405. },
  406. // 删除节点
  407. removeNode() {
  408. let selectedId = this.get_selected_nodeid();
  409. if (!selectedId) {
  410. this.$message({
  411. type: "warning",
  412. message: "请先选择一个节点!",
  413. });
  414. return;
  415. }
  416. this.jm.remove_node(selectedId);
  417. this.i = 0;
  418. this.getDepth(this.jm.mind.root, 1);
  419. },
  420. // 编辑节点
  421. editNode() {
  422. let selectedId = this.get_selected_nodeid();
  423. if (!selectedId) {
  424. this.$message({ type: "warning", message: "请先选择一个节点!" });
  425. return;
  426. }
  427. let nodeObj = this.jm.get_node(selectedId);
  428. this.nodeOption.content = nodeObj.topic;
  429. this.nodeOption.bgColor = nodeObj.data["background-color"];
  430. this.nodeOption.fontColor = nodeObj.data["foreground-color"];
  431. this.nodeOption.fontSize = nodeObj.data["font-size"];
  432. this.nodeOption.fontWeight = nodeObj.data["font-weight"];
  433. this.nodeOption.fontStyle = nodeObj.data["font-style"];
  434. this.dialogVisible = true;
  435. },
  436. sureEditNode() {
  437. let selectedId = this.get_selected_nodeid();
  438. this.jm.update_node(selectedId, this.nodeOption.content);
  439. this.jm.set_node_font_style(
  440. selectedId,
  441. this.nodeOption.fontSize,
  442. this.nodeOption.fontWeight,
  443. this.nodeOption.fontStyle
  444. );
  445. this.jm.set_node_color(
  446. selectedId,
  447. this.nodeOption.bgColor,
  448. this.nodeOption.fontColor
  449. );
  450. this.nodeOption = {
  451. content: "",
  452. bgColor: "",
  453. fontColor: "",
  454. fontSize: "",
  455. fontWeight: "",
  456. fontStyle: "",
  457. };
  458. this.dialogVisible = false;
  459. },
  460. },
  461. beforeDestroy() {
  462. // document.removeEventListener("domMouseScroll", this.scrollFunc, false);
  463. },
  464. };
  465. </script>
  466. <style scoped>
  467. .jsmind_layout {
  468. display: flex;
  469. flex-direction: column;
  470. width: 100%;
  471. height: calc(100%);
  472. /* height: 500px; */
  473. /* margin: 15px 5px 0 0; */
  474. background: #fff;
  475. overflow: hidden;
  476. flex-shrink: 0;
  477. position: relative;
  478. }
  479. .jsmind_title {
  480. position: absolute;
  481. top: 20px;
  482. left: 20px;
  483. font-size: 20px;
  484. color: #8d8d8d;
  485. }
  486. .noMind {
  487. position: absolute;
  488. display: flex;
  489. justify-content: center;
  490. align-items: center;
  491. width: 100%;
  492. height: 100%;
  493. z-index: 999;
  494. background: #fff;
  495. }
  496. .jsmind_layout .jsmind_toolbar {
  497. width: 100%;
  498. padding: 0 10px 10px 10px;
  499. height: auto;
  500. flex-shrink: 0;
  501. display: flex;
  502. align-items: center;
  503. flex-wrap: wrap;
  504. background-color: #f8f9fa;
  505. box-shadow: 0 0 4px #b8b8b8;
  506. }
  507. .jsmind_layout>>>.el-button--medium,
  508. .jsmind_layout>>>.el-input--medium {
  509. margin-top: 10px;
  510. }
  511. .jsmind_layout #jsmind_container {
  512. /* flex: 1 1 auto; */
  513. height: 100%;
  514. }
  515. .jsmind_layout>>>.jsmind-inner {
  516. /* overflow: hidden auto !important; */
  517. /* height: auto; */
  518. }
  519. .jsmind_layout>>>.el-upload-list {
  520. display: none !important;
  521. }
  522. /* 隐藏滚动条 */
  523. .jsmind_layout .jsmind-inner::-webkit-scrollbar {
  524. display: none;
  525. }
  526. .jsmind_layout .pad {
  527. margin-right: 10px;
  528. }
  529. .jsmind_layout .pad-left {
  530. margin-left: 10px;
  531. }
  532. .jsmind_layout>>>jmnode {
  533. white-space: inherit;
  534. word-wrap: break-word;
  535. max-width: 500px;
  536. max-height: 75px;
  537. overflow: auto;
  538. }
  539. .jsmind_layout>>>jmnode::-webkit-scrollbar {
  540. /*滚动条整体样式*/
  541. width: 6px;
  542. /*高宽分别对应横竖滚动条的尺寸*/
  543. height: 6px;
  544. }
  545. /*定义滚动条轨道 内阴影+圆角*/
  546. .jsmind_layout>>>jmnode::-webkit-scrollbar-track {
  547. border-radius: 10px;
  548. background-color: rgba(0, 0, 0, 0.1);
  549. }
  550. /*定义滑块 内阴影+圆角*/
  551. .jsmind_layout>>>jmnode::-webkit-scrollbar-thumb {
  552. border-radius: 10px;
  553. -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .3);
  554. background-color: rgba(0, 0, 0, 0.1);
  555. }
  556. .jsmind_layout>>>jmnode.selected {
  557. background-color: #b9b9b9;
  558. color: #fff;
  559. box-shadow: 2px 2px 8px #777;
  560. }
  561. .jsmind_layout>>>jmnode:hover {
  562. box-shadow: 2px 2px 8px #777;
  563. }
  564. .jsmind_layout .form-con {
  565. padding-top: 20px;
  566. }
  567. .jsmind_layout .ele-width {
  568. width: 96%;
  569. }</style>