mirror of
https://github.com/wanglin2/mind-map.git
synced 2026-02-19 14:58:28 +08:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a438f2906 | ||
|
|
260de4987d | ||
|
|
31cc658c06 | ||
|
|
be6b41d74d | ||
|
|
4beeead53d | ||
|
|
af2df6acd3 | ||
|
|
c08d66acf2 | ||
|
|
5a9cb9ac07 | ||
|
|
1662cd1be7 | ||
|
|
9dd5b3d47e |
69
README.md
69
README.md
@@ -20,11 +20,13 @@
|
||||
|
||||
- [x] 支持多种节点形状
|
||||
|
||||
- [x] 支持导出为`json`、`png`、`svg`、`pdf`,支持从`json`、`xmind`导入
|
||||
|
||||
## 目录介绍
|
||||
|
||||
1.`simple-mind-map`
|
||||
|
||||
思维导图工具库。
|
||||
思维导图工具库,框架无关,`Vue`、`React`等框架或无框架都可以使用。
|
||||
|
||||
2.`web`
|
||||
|
||||
@@ -32,7 +34,7 @@
|
||||
|
||||
3.`dist`
|
||||
|
||||
打包后的资源文件夹。
|
||||
打包`web`后的资源文件夹。
|
||||
|
||||
4.`docs`
|
||||
|
||||
@@ -88,7 +90,7 @@ npm run build
|
||||
|
||||
# 安装
|
||||
|
||||
> 当然仓库版本:0.2.4,当前npm版本:0.2.4
|
||||
> 当然仓库版本:0.2.8,当前npm版本:0.2.7
|
||||
|
||||
```bash
|
||||
npm i simple-mind-map
|
||||
@@ -100,7 +102,7 @@ npm i simple-mind-map
|
||||
>
|
||||
>```js
|
||||
>module.exports = {
|
||||
> transpileDependencies: ['simple-mind-map']
|
||||
> transpileDependencies: ['simple-mind-map']
|
||||
>}
|
||||
>```
|
||||
>
|
||||
@@ -123,7 +125,39 @@ const mindMap = new MindMap({
|
||||
});
|
||||
```
|
||||
|
||||
### Xmind解析方法
|
||||
|
||||
v0.2.7+
|
||||
|
||||
可以通过如下方法获取解析`Xmind`文件的方法:
|
||||
|
||||
```js
|
||||
import MindMap from "simple-mind-map";
|
||||
|
||||
console.log(MindMap.xmind)
|
||||
```
|
||||
|
||||
`MindMap.xmind`对象上挂载了两个方法:
|
||||
|
||||
#### parseXmindFile(file)
|
||||
|
||||
解析`.xmind`文件,返回解析后的数据,注意是完整的数据,包含节点树、主题、结构等,可以使用`mindMap.setFullData(data)`来将返回的数据渲染到画布上
|
||||
|
||||
`file`:`File`对象
|
||||
|
||||
#### transformXmind(content)
|
||||
|
||||
转换`xmind`数据,`.xmind`文件本质上是一个压缩包,改成`zip`后缀可以解压缩,里面存在一个`content.json`文件,如果你自己解析出了这个文件,那么可以把这个文件内容传递给这个方法进行转换,转换后的数据,注意是完整的数据,包含节点树、主题、结构等,可以使用`mindMap.setFullData(data)`来将返回的数据渲染到画布上
|
||||
|
||||
`content`:`.xmind`压缩包内的`content.json`文件内容
|
||||
|
||||
#### transformOldXmind(content)
|
||||
|
||||
v0.2.8+
|
||||
|
||||
针对`xmind8`版本的数据解析,因为该版本的`.xmind`文件内没有`content.json`,对应的是`content.xml`。
|
||||
|
||||
`content`:`.xmind`压缩包内的`content.xml`文件内容
|
||||
|
||||
### 实例化选项:
|
||||
|
||||
@@ -291,10 +325,18 @@ v0.1.7+。切换模式为只读或编辑。
|
||||
|
||||
#### setData(data)
|
||||
|
||||
动态设置思维导图数据
|
||||
动态设置思维导图数据,纯节点数据
|
||||
|
||||
`data`:思维导图结构数据
|
||||
|
||||
#### setFullData(*data*)
|
||||
|
||||
v0.2.7+
|
||||
|
||||
动态设置思维导图数据,包括节点数据、布局、主题、视图
|
||||
|
||||
`data`:完整数据,结构可参考[exportFullData](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exportFullData.json)
|
||||
|
||||
|
||||
#### export(type, isDownload, fileName)
|
||||
|
||||
@@ -920,6 +962,23 @@ v0.2.4+
|
||||
|
||||
设置节点形状,`SET_NODE_SHAPE`命令的快捷方法
|
||||
|
||||
#### getSelfStyle(prop)
|
||||
|
||||
v0.2.5+
|
||||
|
||||
获取节点自身的自定义样式
|
||||
|
||||
#### getParentSelfStyle(prop)
|
||||
|
||||
v0.2.5+
|
||||
|
||||
获取最近一个存在自身自定义样式的祖先节点的自定义样式
|
||||
|
||||
#### getSelfInhertStyle(prop)
|
||||
|
||||
v0.2.5+
|
||||
|
||||
获取自身可继承的自定义样式
|
||||
|
||||
|
||||
## 内置工具方法
|
||||
|
||||
@@ -1 +1 @@
|
||||
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>一个简单的web思维导图实现</title><link href="dist/js/chunk-2d20ec02.81d632f4.js" rel="prefetch"><link href="dist/js/chunk-2d216b67.228f2009.js" rel="prefetch"><link href="dist/js/chunk-e86f1494.f8dd20e2.js" rel="prefetch"><link href="dist/css/app.b771b210.css" rel="preload" as="style"><link href="dist/css/chunk-vendors.6fd71983.css" rel="preload" as="style"><link href="dist/js/app.4bae41a9.js" rel="preload" as="script"><link href="dist/js/chunk-vendors.54c023de.js" rel="preload" as="script"><link href="dist/css/chunk-vendors.6fd71983.css" rel="stylesheet"><link href="dist/css/app.b771b210.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but thoughts doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="dist/js/chunk-vendors.54c023de.js"></script><script src="dist/js/app.4bae41a9.js"></script></body></html>
|
||||
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>一个简单的web思维导图实现</title><link href="dist/js/chunk-2d20ec02.81d632f4.js" rel="prefetch"><link href="dist/js/chunk-2d216b67.228f2009.js" rel="prefetch"><link href="dist/js/chunk-35b0a040.cb76da7d.js" rel="prefetch"><link href="dist/css/app.9be46b7b.css" rel="preload" as="style"><link href="dist/css/chunk-vendors.6fd71983.css" rel="preload" as="style"><link href="dist/js/app.91233d74.js" rel="preload" as="script"><link href="dist/js/chunk-vendors.e4b722f1.js" rel="preload" as="script"><link href="dist/css/chunk-vendors.6fd71983.css" rel="stylesheet"><link href="dist/css/app.9be46b7b.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but thoughts doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="dist/js/chunk-vendors.e4b722f1.js"></script><script src="dist/js/app.91233d74.js"></script></body></html>
|
||||
66
simple-mind-map/example/exportFullData.json
Normal file
66
simple-mind-map/example/exportFullData.json
Normal file
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"layout": "logicalStructure",
|
||||
"root": {
|
||||
"data": {
|
||||
"text": "根节点",
|
||||
"expand": true,
|
||||
"isActive": false
|
||||
},
|
||||
"children": [{
|
||||
"data": {
|
||||
"text": "二级节点",
|
||||
"generalization": {
|
||||
"text": "概要",
|
||||
"expand": true,
|
||||
"isActive": false
|
||||
},
|
||||
"expand": true,
|
||||
"isActive": false
|
||||
},
|
||||
"children": [{
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
"expand": true,
|
||||
"isActive": false
|
||||
},
|
||||
"children": []
|
||||
}, {
|
||||
"data": {
|
||||
"text": "分支主题",
|
||||
"expand": true,
|
||||
"isActive": false
|
||||
},
|
||||
"children": []
|
||||
}]
|
||||
}]
|
||||
},
|
||||
"theme": {
|
||||
"template": "classic4",
|
||||
"config": {}
|
||||
},
|
||||
"view": {
|
||||
"transform": {
|
||||
"scaleX": 1,
|
||||
"scaleY": 1,
|
||||
"shear": 0,
|
||||
"rotate": 0,
|
||||
"translateX": 0,
|
||||
"translateY": 0,
|
||||
"originX": 0,
|
||||
"originY": 0,
|
||||
"a": 1,
|
||||
"b": 0,
|
||||
"c": 0,
|
||||
"d": 1,
|
||||
"e": 0,
|
||||
"f": 0
|
||||
},
|
||||
"state": {
|
||||
"scale": 1,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"sx": 0,
|
||||
"sy": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
import {
|
||||
SVG
|
||||
} from '@svgdotjs/svg.js'
|
||||
import xmind from './src/parse/xmind'
|
||||
|
||||
// 默认选项配置
|
||||
const defaultOpt = {
|
||||
@@ -319,7 +320,7 @@ class MindMap {
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-08-03 22:58:12
|
||||
* @Desc: 动态设置思维导图数据
|
||||
* @Desc: 动态设置思维导图数据,纯节点数据
|
||||
*/
|
||||
setData(data) {
|
||||
this.execCommand('CLEAR_ACTIVE_NODE')
|
||||
@@ -328,6 +329,32 @@ class MindMap {
|
||||
this.reRender()
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-09-21 16:39:13
|
||||
* @Desc: 动态设置思维导图数据,包括节点数据、布局、主题、视图
|
||||
*/
|
||||
setFullData(data) {
|
||||
if (data.root) {
|
||||
this.setData(data.root)
|
||||
}
|
||||
if (data.layout) {
|
||||
this.setLayout(data.layout)
|
||||
}
|
||||
if (data.theme) {
|
||||
if (data.theme.template) {
|
||||
this.setTheme(data.theme.template)
|
||||
}
|
||||
if (data.theme.config) {
|
||||
this.setThemeConfig(data.theme.config)
|
||||
}
|
||||
}
|
||||
if (data.view) {
|
||||
this.view.setTransformData(data.view)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-07-01 22:06:38
|
||||
@@ -369,4 +396,6 @@ class MindMap {
|
||||
}
|
||||
}
|
||||
|
||||
MindMap.xmind = xmind
|
||||
|
||||
export default MindMap
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.2.4",
|
||||
"version": "0.2.8",
|
||||
"description": "一个简单的web在线思维导图",
|
||||
"authors": [
|
||||
{
|
||||
@@ -25,7 +25,9 @@
|
||||
"canvg": "^3.0.7",
|
||||
"deepmerge": "^1.5.2",
|
||||
"eventemitter3": "^4.0.7",
|
||||
"jspdf": "^2.5.1"
|
||||
"jspdf": "^2.5.1",
|
||||
"jszip": "^3.10.1",
|
||||
"xml-js": "^1.6.11"
|
||||
},
|
||||
"keywords": [
|
||||
"javascript",
|
||||
|
||||
@@ -189,7 +189,7 @@ class Drag extends Base {
|
||||
// 连接线
|
||||
this.line = this.draw.path()
|
||||
this.line.opacity(0.5)
|
||||
this.node.style.line(this.line)
|
||||
this.node.styleLine(this.line, this.node)
|
||||
// 同级位置占位符
|
||||
this.placeholder = this.draw.rect().fill({
|
||||
color: this.node.style.merge('lineColor', true)
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { imgToDataUrl, downloadFile } from './utils'
|
||||
import JsPDF from 'jspdf'
|
||||
import {
|
||||
SVG,
|
||||
} from '@svgdotjs/svg.js'
|
||||
const URL = window.URL || window.webkitURL || window
|
||||
|
||||
/**
|
||||
@@ -23,9 +26,9 @@ class Export {
|
||||
* @Date: 2021-07-02 07:44:06
|
||||
* @Desc: 导出
|
||||
*/
|
||||
async export(type, isDownload = true, name = '思维导图') {
|
||||
async export(type, isDownload = true, name = '思维导图', ...args) {
|
||||
if (this[type]) {
|
||||
let result = await this[type](name)
|
||||
let result = await this[type](name, ...args)
|
||||
if (isDownload && type !== 'pdf') {
|
||||
downloadFile(result, name + '.' + type)
|
||||
}
|
||||
@@ -228,8 +231,9 @@ class Export {
|
||||
* @Date: 2021-07-04 14:54:07
|
||||
* @Desc: 导出为svg
|
||||
*/
|
||||
async svg() {
|
||||
async svg(name) {
|
||||
let { node } = await this.getSvgData()
|
||||
node.first().before(SVG(`<title>${name}</title>`))
|
||||
await this.drawBackgroundToSvg(node)
|
||||
let str = node.svg()
|
||||
// 转换成blob数据
|
||||
@@ -244,8 +248,22 @@ class Export {
|
||||
* @Date: 2021-08-03 22:19:17
|
||||
* @Desc: 导出为json
|
||||
*/
|
||||
json () {
|
||||
let data = this.mindMap.command.getCopyData()
|
||||
json (name, withConfig = true) {
|
||||
let nodeData = this.mindMap.command.getCopyData()
|
||||
let data = {}
|
||||
if (withConfig) {
|
||||
data = {
|
||||
layout: this.mindMap.getLayout(),
|
||||
root: nodeData,
|
||||
theme: {
|
||||
template: this.mindMap.getTheme(),
|
||||
config: this.mindMap.getCustomThemeConfig()
|
||||
},
|
||||
view: this.mindMap.view.getTransformData()
|
||||
}
|
||||
} else {
|
||||
data = nodeData
|
||||
}
|
||||
let str = JSON.stringify(data)
|
||||
let blob = new Blob([str])
|
||||
return URL.createObjectURL(blob)
|
||||
@@ -256,8 +274,8 @@ class Export {
|
||||
* @Date: 2021-08-03 22:24:24
|
||||
* @Desc: 专有文件,其实就是json文件
|
||||
*/
|
||||
smm () {
|
||||
return this.json();
|
||||
smm (name, withConfig) {
|
||||
return this.json(name, withConfig);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -711,6 +711,8 @@ class Node {
|
||||
* @Desc: 渲染节点到画布,会移除旧的,创建新的
|
||||
*/
|
||||
renderNode() {
|
||||
// 连线
|
||||
this.renderLine()
|
||||
this.removeAllEvent()
|
||||
this.removeAllNode()
|
||||
this.createNodeData()
|
||||
@@ -750,13 +752,13 @@ class Node {
|
||||
* @Desc: 递归渲染
|
||||
*/
|
||||
render() {
|
||||
// 连线
|
||||
this.renderLine()
|
||||
// 节点
|
||||
if (this.initRender) {
|
||||
this.initRender = false
|
||||
this.renderNode()
|
||||
} else {
|
||||
// 连线
|
||||
this.renderLine()
|
||||
this.update()
|
||||
}
|
||||
// 子节点
|
||||
@@ -861,10 +863,30 @@ class Node {
|
||||
this._lines = this._lines.slice(0, childrenLen)
|
||||
}
|
||||
// 画线
|
||||
this.renderer.layout.renderLine(this, this._lines)
|
||||
// 添加样式
|
||||
this._lines.forEach((line) => {
|
||||
this.style.line(line)
|
||||
this.renderer.layout.renderLine(this, this._lines, (line, node) => {
|
||||
// 添加样式
|
||||
this.styleLine(line, node)
|
||||
})
|
||||
// 和父级的连线也需要更新
|
||||
if (this.parent) {
|
||||
this.parent.renderLine()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: flydreame
|
||||
* @Date: 2022-09-17 12:41:29
|
||||
* @Desc: 设置连线样式
|
||||
*/
|
||||
styleLine(line, node) {
|
||||
let width = node.getSelfInhertStyle('lineWidth') || node.getStyle('lineWidth', true)
|
||||
let color = node.getSelfInhertStyle('lineColor') || node.getStyle('lineColor', true)
|
||||
let dasharray = node.getSelfInhertStyle('lineDasharray') || node.getStyle('lineDasharray', true)
|
||||
this.style.line(line, {
|
||||
width,
|
||||
color,
|
||||
dasharray,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1151,6 +1173,40 @@ class Node {
|
||||
return v === undefined ? '' : v
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: flydreame
|
||||
* @Date: 2022-09-17 11:21:15
|
||||
* @Desc: 获取自定义样式
|
||||
*/
|
||||
getSelfStyle(prop) {
|
||||
return this.style.getSelfStyle(prop)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: flydreame
|
||||
* @Date: 2022-09-17 11:21:26
|
||||
* @Desc: 获取最近一个存在自身自定义样式的祖先节点的自定义样式
|
||||
*/
|
||||
getParentSelfStyle(prop) {
|
||||
if (this.parent) {
|
||||
return this.parent.getSelfStyle(prop) || this.parent.getParentSelfStyle(prop)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: flydreame
|
||||
* @Date: 2022-09-17 12:15:30
|
||||
* @Desc: 获取自身可继承的自定义样式
|
||||
*/
|
||||
getSelfInhertStyle(prop) {
|
||||
return this.getSelfStyle(prop) // 自身
|
||||
|| this.getParentSelfStyle(prop) // 父级
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-04 22:18:07
|
||||
|
||||
@@ -66,7 +66,7 @@ class Style {
|
||||
}
|
||||
}
|
||||
// 优先使用节点本身的样式
|
||||
return this.ctx.nodeData.data[prop] !== undefined ? this.ctx.nodeData.data[prop] : defaultConfig[prop]
|
||||
return this.getSelfStyle(prop) !== undefined ? this.getSelfStyle(prop) : defaultConfig[prop]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,6 +79,16 @@ class Style {
|
||||
return this.merge(prop, root, isActive)
|
||||
}
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: flydreame
|
||||
* @Date: 2022-09-17 12:09:39
|
||||
* @Desc: 获取自身自定义样式
|
||||
*/
|
||||
getSelfStyle(prop) {
|
||||
return this.ctx.nodeData.data[prop]
|
||||
}
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-11 10:12:56
|
||||
@@ -173,8 +183,8 @@ class Style {
|
||||
* @Date: 2021-04-11 14:50:49
|
||||
* @Desc: 连线
|
||||
*/
|
||||
line(node) {
|
||||
node.stroke({ width: this.merge('lineWidth', true), color: this.merge('lineColor', true) }).fill({ color: 'none' })
|
||||
line(node, { width, color, dasharray } = {}) {
|
||||
node.stroke({ width, color, dasharray }).fill({ color: 'none' })
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -231,7 +231,7 @@ class CatalogOrganization extends Base {
|
||||
* @Date: 2021-04-11 14:42:48
|
||||
* @Desc: 绘制连线,连接该节点到其子节点
|
||||
*/
|
||||
renderLine(node, lines) {
|
||||
renderLine(node, lines, style) {
|
||||
if (node.children.length <= 0) {
|
||||
return [];
|
||||
}
|
||||
@@ -263,6 +263,7 @@ class CatalogOrganization extends Base {
|
||||
let path = `M ${x2},${y1 + s1} L ${x2},${y1 + s1 > y2 ? y2 + item.height : y2}`
|
||||
// 竖线
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
})
|
||||
minx = Math.min(minx, x1)
|
||||
maxx = Math.max(maxx, x1)
|
||||
@@ -271,12 +272,14 @@ class CatalogOrganization extends Base {
|
||||
node.style.line(line1)
|
||||
line1.plot(`M ${x1},${y1} L ${x1},${y1 + s1}`)
|
||||
node._lines.push(line1)
|
||||
style && style(line1, node)
|
||||
// 水平线
|
||||
if (len > 0) {
|
||||
let lin2 = this.draw.path()
|
||||
node.style.line(lin2)
|
||||
lin2.plot(`M ${minx},${y1 + s1} L ${maxx},${y1 + s1}`)
|
||||
node._lines.push(lin2)
|
||||
style && style(lin2, node)
|
||||
}
|
||||
} else {
|
||||
// 非根节点
|
||||
@@ -320,6 +323,7 @@ class CatalogOrganization extends Base {
|
||||
path = `M ${x2},${y2} L ${_left},${y2}`
|
||||
}
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
})
|
||||
// 竖线
|
||||
if (len > 0) {
|
||||
@@ -333,6 +337,7 @@ class CatalogOrganization extends Base {
|
||||
lin2.show()
|
||||
}
|
||||
node._lines.push(lin2)
|
||||
style && style(lin2, node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ class LogicalStructure extends Base {
|
||||
* @Date: 2021-04-11 14:42:48
|
||||
* @Desc: 绘制连线,连接该节点到其子节点
|
||||
*/
|
||||
renderLine(node, lines) {
|
||||
renderLine(node, lines, style) {
|
||||
if (node.children.length <= 0) {
|
||||
return [];
|
||||
}
|
||||
@@ -169,6 +169,7 @@ class LogicalStructure extends Base {
|
||||
path = this.cubicBezierPath(x1, y1, x2, y2)
|
||||
}
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -182,7 +182,7 @@ class MindMap extends Base {
|
||||
* @Date: 2021-04-11 14:42:48
|
||||
* @Desc: 绘制连线,连接该节点到其子节点
|
||||
*/
|
||||
renderLine(node, lines) {
|
||||
renderLine(node, lines, style) {
|
||||
if (node.children.length <= 0) {
|
||||
return [];
|
||||
}
|
||||
@@ -205,6 +205,7 @@ class MindMap extends Base {
|
||||
path = this.cubicBezierPath(x1, y1, x2, y2)
|
||||
}
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -147,7 +147,7 @@ class OrganizationStructure extends Base {
|
||||
* @Date: 2021-04-11 14:42:48
|
||||
* @Desc: 绘制连线,连接该节点到其子节点
|
||||
*/
|
||||
renderLine(node, lines) {
|
||||
renderLine(node, lines, style) {
|
||||
if (node.children.length <= 0) {
|
||||
return [];
|
||||
}
|
||||
@@ -177,6 +177,7 @@ class OrganizationStructure extends Base {
|
||||
}
|
||||
let path = `M ${x2},${y1 + s1} L ${x2},${y2}`
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
})
|
||||
minx = Math.min(x1, minx)
|
||||
maxx = Math.max(x1, maxx)
|
||||
@@ -186,12 +187,14 @@ class OrganizationStructure extends Base {
|
||||
expandBtnSize = len > 0 && !isRoot ? expandBtnSize : 0
|
||||
line1.plot(`M ${x1},${y1 + expandBtnSize} L ${x1},${y1 + s1}`)
|
||||
node._lines.push(line1)
|
||||
style && style(line1, node)
|
||||
// 水平线
|
||||
if (len > 0) {
|
||||
let lin2 = this.draw.path()
|
||||
node.style.line(lin2)
|
||||
lin2.plot(`M ${minx},${y1 + s1} L ${maxx},${y1 + s1}`)
|
||||
node._lines.push(lin2)
|
||||
style && style(lin2, node)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
173
simple-mind-map/src/parse/xmind.js
Normal file
173
simple-mind-map/src/parse/xmind.js
Normal file
@@ -0,0 +1,173 @@
|
||||
import JSZip from "jszip";
|
||||
import xmlConvert from "xml-js";
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-09-21 14:07:47
|
||||
* @Desc: 解析.xmind文件
|
||||
*/
|
||||
const parseXmindFile = (file) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
JSZip.loadAsync(file).then(
|
||||
async (zip) => {
|
||||
try {
|
||||
let content = "";
|
||||
if (zip.files["content.json"]) {
|
||||
let json = await zip.files["content.json"].async("string");
|
||||
content = transformXmind(json);
|
||||
} else if (zip.files["content.xml"]) {
|
||||
let xml = await zip.files["content.xml"].async("string");
|
||||
let json = xmlConvert.xml2json(xml);
|
||||
content = transformOldXmind(json);
|
||||
}
|
||||
if (content) {
|
||||
resolve(content);
|
||||
} else {
|
||||
reject(new Error("解析失败"));
|
||||
}
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
},
|
||||
(e) => {
|
||||
reject(e);
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-09-21 18:57:25
|
||||
* @Desc: 转换xmind数据
|
||||
*/
|
||||
const transformXmind = (content) => {
|
||||
let data = JSON.parse(content)[0];
|
||||
let nodeTree = data.rootTopic;
|
||||
let newTree = {};
|
||||
let walk = (node, newNode) => {
|
||||
newNode.data = {
|
||||
// 节点内容
|
||||
text: node.title,
|
||||
};
|
||||
// 节点备注
|
||||
if (node.notes) {
|
||||
newNode.data.note = (node.notes.realHTML || node.notes.plain).content;
|
||||
}
|
||||
// 超链接
|
||||
if (node.href && /^https?:\/\//.test(node.href)) {
|
||||
newNode.data.hyperlink = node.href;
|
||||
}
|
||||
// 标签
|
||||
if (node.labels && node.labels.length > 0) {
|
||||
newNode.data.tag = node.labels;
|
||||
}
|
||||
// 子节点
|
||||
newNode.children = [];
|
||||
if (
|
||||
node.children &&
|
||||
node.children.attached &&
|
||||
node.children.attached.length > 0
|
||||
) {
|
||||
node.children.attached.forEach((item) => {
|
||||
let newChild = {};
|
||||
newNode.children.push(newChild);
|
||||
walk(item, newChild);
|
||||
});
|
||||
}
|
||||
};
|
||||
walk(nodeTree, newTree);
|
||||
return newTree;
|
||||
};
|
||||
|
||||
/**
|
||||
* javascript comment
|
||||
* @Author: 王林25
|
||||
* @Date: 2022-09-23 15:51:51
|
||||
* @Desc: 转换旧版xmind数据,xmind8
|
||||
*/
|
||||
const transformOldXmind = (content) => {
|
||||
let data = JSON.parse(content);
|
||||
let elements = data.elements;
|
||||
let root = null;
|
||||
let getRoot = (arr) => {
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (!root && arr[i].name === "topic") {
|
||||
root = arr[i];
|
||||
return;
|
||||
}
|
||||
}
|
||||
arr.forEach((item) => {
|
||||
getRoot(item.elements);
|
||||
});
|
||||
};
|
||||
getRoot(elements);
|
||||
let newTree = {};
|
||||
let getItemByName = (arr, name) => {
|
||||
return arr.find((item) => {
|
||||
return item.name === name;
|
||||
});
|
||||
};
|
||||
let walk = (node, newNode) => {
|
||||
let nodeElements = node.elements;
|
||||
newNode.data = {
|
||||
// 节点内容
|
||||
text: getItemByName(nodeElements, "title").elements[0].text,
|
||||
};
|
||||
try {
|
||||
// 节点备注
|
||||
let notesElement = getItemByName(nodeElements, "notes");
|
||||
if (notesElement) {
|
||||
newNode.data.note =
|
||||
notesElement.elements[0].elements[0].elements[0].text;
|
||||
}
|
||||
} catch (error) {}
|
||||
try {
|
||||
// 超链接
|
||||
if (
|
||||
node.attributes &&
|
||||
node.attributes["xlink:href"] &&
|
||||
/^https?:\/\//.test(node.attributes["xlink:href"])
|
||||
) {
|
||||
newNode.data.hyperlink = node.attributes["xlink:href"];
|
||||
}
|
||||
} catch (error) {}
|
||||
try {
|
||||
// 标签
|
||||
let labelsElement = getItemByName(nodeElements, "labels");
|
||||
if (labelsElement) {
|
||||
newNode.data.tag = labelsElement.elements.map((item) => {
|
||||
return item.elements[0].text;
|
||||
});
|
||||
}
|
||||
} catch (error) {}
|
||||
// 子节点
|
||||
newNode.children = [];
|
||||
let _children = getItemByName(nodeElements, "children");
|
||||
if (_children && _children.elements && _children.elements.length > 0) {
|
||||
_children.elements.forEach((item) => {
|
||||
if (item.name === "topics") {
|
||||
item.elements.forEach((item2) => {
|
||||
let newChild = {};
|
||||
newNode.children.push(newChild);
|
||||
walk(item2, newChild);
|
||||
});
|
||||
} else {
|
||||
let newChild = {};
|
||||
newNode.children.push(newChild);
|
||||
walk(item, newChild);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
walk(root, newTree);
|
||||
return newTree;
|
||||
};
|
||||
|
||||
export default {
|
||||
parseXmindFile,
|
||||
transformXmind,
|
||||
transformOldXmind,
|
||||
};
|
||||
@@ -17,6 +17,8 @@ export default {
|
||||
lineWidth: 1,
|
||||
// 连线的颜色
|
||||
lineColor: '#549688',
|
||||
// 连线样式
|
||||
lineDasharray: 'none',
|
||||
// 概要连线的粗细
|
||||
generalizationLineWidth: 1,
|
||||
// 概要连线的颜色
|
||||
|
||||
@@ -101,10 +101,10 @@
|
||||
<span class="name">颜色</span>
|
||||
<span
|
||||
class="block"
|
||||
v-popover:popover
|
||||
v-popover:popover2
|
||||
:style="{ backgroundColor: style.generalizationLineColor }"
|
||||
></span>
|
||||
<el-popover ref="popover" placement="bottom" trigger="click">
|
||||
<el-popover ref="popover2" placement="bottom" trigger="click">
|
||||
<Color
|
||||
:color="style.generalizationLineColor"
|
||||
@change="
|
||||
|
||||
@@ -215,7 +215,11 @@ export default {
|
||||
* @Desc: 动态设置思维导图数据
|
||||
*/
|
||||
setData(data) {
|
||||
this.mindMap.setData(data)
|
||||
if (data.root) {
|
||||
this.mindMap.setFullData(data)
|
||||
} else {
|
||||
this.mindMap.setData(data)
|
||||
}
|
||||
this.manualSave()
|
||||
},
|
||||
|
||||
|
||||
@@ -3,21 +3,22 @@
|
||||
class="nodeDialog"
|
||||
title="导出"
|
||||
:visible.sync="dialogVisible"
|
||||
width="500"
|
||||
width="700px"
|
||||
>
|
||||
<div>
|
||||
<div class="nameInputBox">
|
||||
<span class="name">导出文件名称</span>
|
||||
<el-input style="width: 300px" v-model="fileName" size="mini"></el-input>
|
||||
<el-checkbox v-show="['smm', 'json'].includes(exportType)" v-model="widthConfig" style="margin-left: 12px">是否包含主题、结构等配置数据</el-checkbox>
|
||||
</div>
|
||||
<el-radio-group v-model="exportType">
|
||||
<el-radio label="smm">专有文件(.smm)</el-radio>
|
||||
<el-radio label="json">json文件(.json)</el-radio>
|
||||
<el-radio label="png">图片文件(.png)</el-radio>
|
||||
<el-radio label="svg">svg文件(.svg)</el-radio>
|
||||
<el-radio label="pdf">pdf文件(.pdf)</el-radio>
|
||||
<el-radio-group v-model="exportType" size="mini">
|
||||
<el-radio-button label="smm">专有文件(.smm)</el-radio-button>
|
||||
<el-radio-button label="json">json文件(.json)</el-radio-button>
|
||||
<el-radio-button label="png">图片文件(.png)</el-radio-button>
|
||||
<el-radio-button label="svg">svg文件(.svg)</el-radio-button>
|
||||
<el-radio-button label="pdf">pdf文件(.pdf)</el-radio-button>
|
||||
</el-radio-group>
|
||||
<div class="tip">tips:.smm文件可用于导入</div>
|
||||
<div class="tip">tips:.smm和.json文件可用于导入</div>
|
||||
</div>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
@@ -38,7 +39,8 @@ export default {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
exportType: "smm",
|
||||
fileName: '思维导图'
|
||||
fileName: '思维导图',
|
||||
widthConfig: true
|
||||
};
|
||||
},
|
||||
created() {
|
||||
@@ -62,7 +64,7 @@ export default {
|
||||
* @Desc: 确定
|
||||
*/
|
||||
confirm() {
|
||||
this.$bus.$emit("export", this.exportType, true, this.fileName);
|
||||
this.$bus.$emit("export", this.exportType, true, this.fileName, this.widthConfig);
|
||||
this.$notify.info({
|
||||
title: '消息',
|
||||
message: '如果没有触发下载,请检查是否被浏览器拦截了'
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<el-dialog class="nodeDialog" title="导入" :visible.sync="dialogVisible" width="500">
|
||||
<el-dialog class="nodeDialog" title="导入" :visible.sync="dialogVisible" width="300px">
|
||||
<el-upload ref="upload" action="x" :file-list="fileList" :auto-upload="false" :multiple="false" :on-change="onChange" :limit="1" :on-exceed="onExceed">
|
||||
<el-button slot="trigger" size="small" type="primary">选取文件</el-button>
|
||||
<div slot="tip" class="el-upload__tip">只能上传.smm文件</div>
|
||||
<div slot="tip" class="el-upload__tip">支持.smm、.json、.xmind文件</div>
|
||||
</el-upload>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
@@ -12,6 +12,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MindMap from 'simple-mind-map'
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-06-24 22:53:54
|
||||
@@ -44,9 +46,9 @@ export default {
|
||||
* @Desc: 文件选择
|
||||
*/
|
||||
onChange(file) {
|
||||
let reg = /\.smm$/;
|
||||
let reg = /\.(smm|xmind|json)$/;
|
||||
if (!reg.test(file.name)) {
|
||||
this.$message.error("请选择.smm文件");
|
||||
this.$message.error("请选择.smm、.json、.xmind文件");
|
||||
this.fileList = [];
|
||||
} else {
|
||||
this.fileList.push(file)
|
||||
@@ -81,6 +83,15 @@ export default {
|
||||
return this.$message.error("请选择要导入的文件");
|
||||
}
|
||||
let file = this.fileList[0];
|
||||
if (/\.(smm|json)$/.test(file.name)) {
|
||||
this.handleSmm(file)
|
||||
} else if (/\.xmind$/.test(file.name)) {
|
||||
this.handleXmind(file)
|
||||
}
|
||||
this.cancel();
|
||||
},
|
||||
|
||||
handleSmm(file) {
|
||||
let fileReader = new FileReader()
|
||||
fileReader.readAsText(file.raw)
|
||||
fileReader.onload = (evt) => {
|
||||
@@ -96,8 +107,18 @@ export default {
|
||||
this.$message.error("文件解析失败");
|
||||
}
|
||||
}
|
||||
this.cancel();
|
||||
},
|
||||
|
||||
async handleXmind(file) {
|
||||
try {
|
||||
let data = await MindMap.xmind.parseXmindFile(file.raw)
|
||||
this.$bus.$emit('setData', data)
|
||||
this.$message.success("导入成功");
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
this.$message.error("文件解析失败");
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -143,7 +143,7 @@
|
||||
</el-popover>
|
||||
</div>
|
||||
<div class="rowItem">
|
||||
<span class="name" v-popover:popover5>样式</span>
|
||||
<span class="name">样式</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
style="width: 80px"
|
||||
@@ -233,7 +233,7 @@
|
||||
>
|
||||
<el-option
|
||||
v-for="item in shapeList"
|
||||
:key="item"
|
||||
:key="item.value"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
>
|
||||
@@ -241,6 +241,65 @@
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 线条 -->
|
||||
<div class="title">线条</div>
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">颜色</span>
|
||||
<span
|
||||
class="block"
|
||||
v-popover:popover5
|
||||
:style="{ width: '80px', backgroundColor: style.lineColor }"
|
||||
:class="{ disabled: checkDisabled('lineColor') }"
|
||||
></span>
|
||||
<el-popover ref="popover5" placement="bottom" trigger="click" :disabled="checkDisabled('lineColor')">
|
||||
<Color
|
||||
:color="style.lineColor"
|
||||
@change="changeLineColor"
|
||||
></Color>
|
||||
</el-popover>
|
||||
</div>
|
||||
<div class="rowItem">
|
||||
<span class="name">样式</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
style="width: 80px"
|
||||
v-model="style.lineDasharray"
|
||||
placeholder=""
|
||||
:disabled="checkDisabled('lineDasharray')"
|
||||
@change="update('lineDasharray')"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in borderDasharrayList"
|
||||
:key="item.value"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">宽度</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
style="width: 80px"
|
||||
v-model="style.lineWidth"
|
||||
placeholder=""
|
||||
:disabled="checkDisabled('lineWidth')"
|
||||
@change="update('lineWidth')"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in borderWidthList"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 节点内边距 -->
|
||||
<div class="title noTop">节点内边距</div>
|
||||
<div class="row">
|
||||
@@ -323,6 +382,9 @@ export default {
|
||||
fillColor: "",
|
||||
borderDasharray: "",
|
||||
borderRadius: "",
|
||||
lineColor: "",
|
||||
lineDasharray: "",
|
||||
lineWidth: "",
|
||||
},
|
||||
};
|
||||
},
|
||||
@@ -382,6 +444,9 @@ export default {
|
||||
"fillColor",
|
||||
"borderDasharray",
|
||||
"borderRadius",
|
||||
"lineColor",
|
||||
"lineDasharray",
|
||||
"lineWidth",
|
||||
].forEach((item) => {
|
||||
this.style[item] = this.activeNodes[0].getStyle(
|
||||
item,
|
||||
@@ -450,6 +515,16 @@ export default {
|
||||
this.update("borderColor");
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: flydreame
|
||||
* @Date: 2022-09-17 10:18:15
|
||||
* @Desc: 修改线条颜色
|
||||
*/
|
||||
changeLineColor(color) {
|
||||
this.style.lineColor = color;
|
||||
this.update("lineColor");
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-05-05 10:18:59
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
<div
|
||||
class="toolbarBtn"
|
||||
:class="{
|
||||
disabled: activeNodes.length <= 0 || hasRoot,
|
||||
disabled: activeNodes.length <= 0 || hasRoot || hasGeneralization,
|
||||
}"
|
||||
@click="$bus.$emit('execCommand', 'ADD_GENERALIZATION')"
|
||||
>
|
||||
@@ -194,14 +194,14 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
hasRoot() {
|
||||
return this.activeNodes.find((node) => {
|
||||
return this.activeNodes.findIndex((node) => {
|
||||
return node.isRoot;
|
||||
});
|
||||
}) !== -1;
|
||||
},
|
||||
hasGeneralization() {
|
||||
return this.activeNodes.find((node) => {
|
||||
return this.activeNodes.findIndex((node) => {
|
||||
return node.isGeneralization;
|
||||
});
|
||||
}) !== -1;;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
||||
Reference in New Issue
Block a user