Compare commits
140 Commits
0.6.1
...
0.6.11-dem
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
516676b484 | ||
|
|
10ed3d4f7c | ||
|
|
8fc7f7d32c | ||
|
|
27a0efa4e0 | ||
|
|
7d227e901a | ||
|
|
080d7489e7 | ||
|
|
b11bd5a7ef | ||
|
|
80ae38d295 | ||
|
|
a4b7915196 | ||
|
|
4d4f1b993e | ||
|
|
b814bd35ca | ||
|
|
c57361a360 | ||
|
|
f8c2a62bd6 | ||
|
|
59241717f5 | ||
|
|
e90509cac9 | ||
|
|
8a8cc26c1d | ||
|
|
1d443b9f94 | ||
|
|
60a4f443a7 | ||
|
|
72c2540dcc | ||
|
|
18cec3b75a | ||
|
|
d4aae5268e | ||
|
|
99ca0738d4 | ||
|
|
408ca6d711 | ||
|
|
99dcb55397 | ||
|
|
e11b6647b8 | ||
|
|
78f5d4ec88 | ||
|
|
20eba7b29b | ||
|
|
4c9698a147 | ||
|
|
55da8eac83 | ||
|
|
17ea049393 | ||
|
|
d2e468287d | ||
|
|
ad6085890e | ||
|
|
cd4f1b1bd8 | ||
|
|
a2acf810cb | ||
|
|
14bd7c3705 | ||
|
|
5d0c9dcab1 | ||
|
|
def2f02eea | ||
|
|
a74a60c22d | ||
|
|
74d37f2cbc | ||
|
|
6ca9a116c2 | ||
|
|
8e59677623 | ||
|
|
d06f57a5dc | ||
|
|
07be48d342 | ||
|
|
5a5c7702f5 | ||
|
|
7e1a43143d | ||
|
|
5ff9137745 | ||
|
|
8e30d4a94f | ||
|
|
4b2ebb2e1c | ||
|
|
8c79ffd723 | ||
|
|
839c79405f | ||
|
|
d645adfd37 | ||
|
|
1b1551c6e3 | ||
|
|
78638dc291 | ||
|
|
e972924143 | ||
|
|
8871d8727b | ||
|
|
55945a1a2c | ||
|
|
d5e4044fb2 | ||
|
|
4ee458c509 | ||
|
|
a76ec0dad8 | ||
|
|
f2b247e85c | ||
|
|
3aed09a8c3 | ||
|
|
17b7a023ba | ||
|
|
10cb36829f | ||
|
|
90a55bb995 | ||
|
|
08a37d8971 | ||
|
|
89983f9f47 | ||
|
|
b71a80e383 | ||
|
|
2bf146816b | ||
|
|
daf9888da4 | ||
|
|
b42cee7a2f | ||
|
|
9ecb199608 | ||
|
|
adccef5699 | ||
|
|
94230f8ec6 | ||
|
|
a161661c6b | ||
|
|
08b971cd9a | ||
|
|
b64c8f132b | ||
|
|
d6181591c5 | ||
|
|
e093cb1741 | ||
|
|
a63a92c423 | ||
|
|
2c4f065626 | ||
|
|
ae5d4dd2a6 | ||
|
|
e574883e5f | ||
|
|
1bf60c49c7 | ||
|
|
ddc173cf84 | ||
|
|
2c71c7d102 | ||
|
|
f6cb08bdaa | ||
|
|
05728de21b | ||
|
|
b51027f641 | ||
|
|
ea4fdf8290 | ||
|
|
55796b4e39 | ||
|
|
e534c3138d | ||
|
|
e185abd223 | ||
|
|
3b73f72866 | ||
|
|
0db2f47133 | ||
|
|
80c7ec0fac | ||
|
|
780ce363de | ||
|
|
eaa8929457 | ||
|
|
ad0f62a5ac | ||
|
|
31f21ce013 | ||
|
|
7a20ce2f79 | ||
|
|
b98e7a97ec | ||
|
|
0bb50b3371 | ||
|
|
a03091b28c | ||
|
|
6c33984b8c | ||
|
|
28a4be0631 | ||
|
|
b8a3be7a62 | ||
|
|
259d4028f3 | ||
|
|
fb1251afc1 | ||
|
|
06e3fd428a | ||
|
|
a48e52f1f4 | ||
|
|
f8a345d8de | ||
|
|
d900c2f122 | ||
|
|
443a714549 | ||
|
|
74618a8a2b | ||
|
|
a25bd4c556 | ||
|
|
efe205ae70 | ||
|
|
a0d7473b1f | ||
|
|
7821781f20 | ||
|
|
b90d4ddf8a | ||
|
|
314562c167 | ||
|
|
5f0a9a7ce2 | ||
|
|
98e27841ad | ||
|
|
1b6467728c | ||
|
|
4bb349b2df | ||
|
|
b262336f08 | ||
|
|
2b59087461 | ||
|
|
66c9805efc | ||
|
|
710128901a | ||
|
|
61be0f7ac4 | ||
|
|
7289b3a0ad | ||
|
|
25243e2053 | ||
|
|
060a448cd5 | ||
|
|
bdb6078df6 | ||
|
|
749a4d0e81 | ||
|
|
1749705694 | ||
|
|
eec736be4d | ||
|
|
ffdf53941a | ||
|
|
5676e952f3 | ||
|
|
e049ee6260 | ||
|
|
f1355c9d2a |
82
README.md
@@ -11,13 +11,13 @@
|
||||
|
||||
本项目包含两部分:
|
||||
|
||||
1.一个js思维导图库,不依赖任何框架,你可以使用它来快速完成Web思维导图产品的开发。
|
||||
1.一个 js 思维导图库,不依赖任何框架,你可以使用它来快速完成 Web 思维导图产品的开发。
|
||||
|
||||
开发文档:[https://wanglin2.github.io/mind-map/#/doc/zh/](https://wanglin2.github.io/mind-map/#/doc/zh/)
|
||||
开发文档:[https://wanglin2.github.io/mind-map/#/doc/zh/](https://wanglin2.github.io/mind-map/#/doc/zh/)。
|
||||
|
||||
2.一个Web思维导图,基于思维导图库、Vue2.x、ElementUI开发,可以操作电脑本地文件,所以你可以直接把它当做一个在线版思维导图应用使用,如果觉得github的响应速度慢,你也可以部署到你的服务器上。
|
||||
2.一个 Web 思维导图,基于思维导图库、Vue2.x、ElementUI 开发,可以操作电脑本地文件,所以你可以直接把它当做一个在线版思维导图应用使用,如果觉得 github 的响应速度慢,你也可以部署到你的服务器上。
|
||||
|
||||
在线地址:[https://wanglin2.github.io/mind-map/](https://wanglin2.github.io/mind-map/)
|
||||
在线地址:[https://wanglin2.github.io/mind-map/](https://wanglin2.github.io/mind-map/)。
|
||||
|
||||
另外也提供了客户端可供下载使用,支持`Windows`、`Mac`及`Linux`,下载地址:
|
||||
|
||||
@@ -28,18 +28,15 @@ Github:[releases](https://github.com/wanglin2/mind-map/releases)。
|
||||
# 特性
|
||||
|
||||
- [x] 插件化架构,除核心功能外,其他功能作为插件提供,按需使用,减小打包体积
|
||||
- [x] 支持逻辑结构图、思维导图、组织结构图、目录组织图、时间轴、鱼骨图六种结构
|
||||
- [x] 支持逻辑结构图、思维导图、组织结构图、目录组织图、时间轴(横向、竖向)、鱼骨图等结构
|
||||
- [x] 内置多种主题,允许高度自定义样式,支持注册新主题
|
||||
- [x] 支持快捷键
|
||||
- [x] 节点内容支持图片、图标、超链接、备注、标签、概要
|
||||
- [x] 支持前进后退
|
||||
- [x] 支持拖动、缩放
|
||||
- [x] 支持右键和Ctrl+左键两种多选方式
|
||||
- [x] 支持节点自由拖拽、拖拽调整
|
||||
- [x] 支持多种节点形状
|
||||
- [x] 支持导出为`json`、`png`、`svg`、`pdf`、`markdown`,支持从`json`、`xmind`、`markdown`导入
|
||||
- [x] 支持小地图、支持水印
|
||||
- [x] 支持关联线
|
||||
- [x] 节点内容支持文本(普通文本、富文本)、图片、图标、超链接、备注、标签、概要
|
||||
- [x] 节点支持拖拽(拖拽移动、自由调整)、多种节点形状,支持使用 DDM 完全自定义节点内容
|
||||
- [x] 支持画布拖动、缩放
|
||||
- [x] 支持鼠标按键拖动选择和Ctrl+左键两种多选节点方式
|
||||
- [x] 支持导出为`json`、`png`、`svg`、`pdf`、`markdown`、`xmind`,支持从`json`、`xmind`、`markdown`导入
|
||||
- [x] 支持快捷键、前进后退、关联线、搜索替换、小地图、水印
|
||||
- [x] 提供丰富的配置,满足各种场景各种使用习惯
|
||||
|
||||
# 安装
|
||||
|
||||
@@ -86,17 +83,70 @@ const mindMap = new MindMap({
|
||||
|
||||
# License
|
||||
|
||||
MIT
|
||||
[MIT](./LICENSE)
|
||||
|
||||
# 微信交流群
|
||||
|
||||
<img src="./qrcode.jpg" style="width: 300px" />
|
||||
|
||||
如果已过期,可以微信添加`wanglinguanfang`拉你入群。
|
||||
|
||||
# 请作者喝杯咖啡
|
||||
|
||||
开源不易,如果本项目有帮助到你的话,可以考虑请作者喝杯咖啡哟~
|
||||
|
||||
> 厚椰乳一盒 + 纯牛奶半盒 + 冰块 + 咖啡液 = 生椰拿铁 yyds
|
||||
|
||||
> 转账请备注【思维导图】。你的头像和名字将会出现在下面和[文档页面](https://wanglin2.github.io/mind-map/#/doc/zh/introduction/%E8%AF%B7%E4%BD%9C%E8%80%85%E5%96%9D%E6%9D%AF%E5%92%96%E5%95%A1)
|
||||
|
||||
<p>
|
||||
<img src="./web/src/assets/img/alipay.jpg" style="width: 300px" />
|
||||
<img src="./web/src/assets/img/wechat.jpg" style="width: 300px" />
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/Think.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>Think</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/志斌.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>志斌</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/小土渣的宇宙.jpeg" style="width: 50px;height: 50px;" />
|
||||
<span>小土渣的宇宙</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/qp.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>qp</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/ZXR.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>ZXR</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/花儿朵朵.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>花儿朵朵</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/suka.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>suka</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/Chris.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>Chris</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/水车.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>水车</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/仓鼠.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>仓鼠</span>
|
||||
</span>
|
||||
<span>
|
||||
<img src="./web/src/assets/avatar/千帆.jpg" style="width: 50px;height: 50px;" />
|
||||
<span>千帆</span>
|
||||
</span>
|
||||
</p>
|
||||
70
index.html
BIN
qrcode.jpg
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 182 KiB |
@@ -8,13 +8,22 @@ import Drag from './src/plugins/Drag.js'
|
||||
import Select from './src/plugins/Select.js'
|
||||
import AssociativeLine from './src/plugins/AssociativeLine'
|
||||
import RichText from './src/plugins/RichText'
|
||||
import NodeImgAdjust from './src/plugins/NodeImgAdjust.js'
|
||||
import TouchEvent from './src/plugins/TouchEvent.js'
|
||||
import Search from './src/plugins/Search.js'
|
||||
import xmind from './src/parse/xmind.js'
|
||||
import markdown from './src/parse/markdown.js'
|
||||
import icons from './src/svg/icons.js'
|
||||
import * as constants from './src/constants/constant.js'
|
||||
import themes from './src/themes/index.js'
|
||||
import * as defaultTheme from './src/themes/default.js'
|
||||
|
||||
MindMap.xmind = xmind
|
||||
MindMap.markdown = markdown
|
||||
MindMap.iconList = icons.nodeIconList
|
||||
MindMap.constants = constants
|
||||
MindMap.themes = themes
|
||||
MindMap.defaultTheme = defaultTheme
|
||||
|
||||
MindMap
|
||||
.usePlugin(MiniMap)
|
||||
@@ -26,5 +35,8 @@ MindMap
|
||||
.usePlugin(Select)
|
||||
.usePlugin(AssociativeLine)
|
||||
.usePlugin(RichText)
|
||||
.usePlugin(TouchEvent)
|
||||
.usePlugin(NodeImgAdjust)
|
||||
.usePlugin(Search)
|
||||
|
||||
export default MindMap
|
||||
@@ -7,131 +7,11 @@ import Style from './src/core/render/node/Style'
|
||||
import KeyCommand from './src/core/command/KeyCommand'
|
||||
import Command from './src/core/command/Command'
|
||||
import BatchExecution from './src/utils/BatchExecution'
|
||||
import { layoutValueList, CONSTANTS } from './src/constants/constant'
|
||||
import { layoutValueList, CONSTANTS, commonCaches } from './src/constants/constant'
|
||||
import { SVG } from '@svgdotjs/svg.js'
|
||||
import { simpleDeepClone } from './src/utils'
|
||||
import { simpleDeepClone, getType } from './src/utils'
|
||||
import defaultTheme, { checkIsNodeSizeIndependenceConfig } from './src/themes/default'
|
||||
|
||||
// 默认选项配置
|
||||
const defaultOpt = {
|
||||
// 是否只读
|
||||
readonly: false,
|
||||
// 布局
|
||||
layout: CONSTANTS.LAYOUT.LOGICAL_STRUCTURE,
|
||||
// 如果结构为鱼骨图,那么可以通过该选项控制倾斜角度
|
||||
fishboneDeg: 45,
|
||||
// 主题
|
||||
theme: 'default', // 内置主题:default(默认主题)
|
||||
// 主题配置,会和所选择的主题进行合并
|
||||
themeConfig: {},
|
||||
// 放大缩小的增量比例
|
||||
scaleRatio: 0.1,
|
||||
// 最多显示几个标签
|
||||
maxTag: 5,
|
||||
// 导出图片时的内边距
|
||||
exportPadding: 20,
|
||||
// 展开收缩按钮尺寸
|
||||
expandBtnSize: 20,
|
||||
// 节点里图片和文字的间距
|
||||
imgTextMargin: 5,
|
||||
// 节点里各种文字信息的间距,如图标和文字的间距
|
||||
textContentMargin: 2,
|
||||
// 多选节点时鼠标移动到边缘时的画布移动偏移量
|
||||
selectTranslateStep: 3,
|
||||
// 多选节点时鼠标移动距边缘多少距离时开始偏移
|
||||
selectTranslateLimit: 20,
|
||||
// 自定义节点备注内容显示
|
||||
customNoteContentShow: null,
|
||||
/*
|
||||
{
|
||||
show(){},
|
||||
hide(){}
|
||||
}
|
||||
*/
|
||||
// 是否开启节点自由拖拽
|
||||
enableFreeDrag: false,
|
||||
// 水印配置
|
||||
watermarkConfig: {
|
||||
text: '',
|
||||
lineSpacing: 100,
|
||||
textSpacing: 100,
|
||||
angle: 30,
|
||||
textStyle: {
|
||||
color: '#999',
|
||||
opacity: 0.5,
|
||||
fontSize: 14
|
||||
}
|
||||
},
|
||||
// 达到该宽度文本自动换行
|
||||
textAutoWrapWidth: 500,
|
||||
// 自定义鼠标滚轮事件处理
|
||||
// 可以传一个函数,回调参数为事件对象
|
||||
customHandleMousewheel: null,
|
||||
// 鼠标滚动的行为,如果customHandleMousewheel传了自定义函数,这个属性不生效
|
||||
mousewheelAction: CONSTANTS.MOUSE_WHEEL_ACTION.ZOOM,// zoom(放大缩小)、move(上下移动)
|
||||
// 当mousewheelAction设为move时,可以通过该属性控制鼠标滚动一下视图移动的步长,单位px
|
||||
mousewheelMoveStep: 100,
|
||||
// 默认插入的二级节点的文字
|
||||
defaultInsertSecondLevelNodeText: '二级节点',
|
||||
// 默认插入的二级以下节点的文字
|
||||
defaultInsertBelowSecondLevelNodeText: '分支主题',
|
||||
// 展开收起按钮的颜色
|
||||
expandBtnStyle: {
|
||||
color: '#808080',
|
||||
fill: '#fff'
|
||||
},
|
||||
// 自定义展开收起按钮的图标
|
||||
expandBtnIcon: {
|
||||
open: '',// svg字符串
|
||||
close: ''
|
||||
},
|
||||
// 是否只有当鼠标在画布内才响应快捷键事件
|
||||
enableShortcutOnlyWhenMouseInSvg: true,
|
||||
// 是否开启节点动画过渡
|
||||
enableNodeTransitionMove: true,
|
||||
// 如果开启节点动画过渡,可以通过该属性设置过渡的时间,单位ms
|
||||
nodeTransitionMoveDuration: 300,
|
||||
// 初始根节点的位置
|
||||
initRootNodePosition: null,
|
||||
// 导出png、svg、pdf时的图形内边距
|
||||
exportPaddingX: 10,
|
||||
exportPaddingY: 10,
|
||||
// 节点文本编辑框的z-index
|
||||
nodeTextEditZIndex: 3000,
|
||||
// 节点备注浮层的z-index
|
||||
nodeNoteTooltipZIndex: 3000,
|
||||
// 是否在点击了画布外的区域时结束节点文本的编辑状态
|
||||
isEndNodeTextEditOnClickOuter: true,
|
||||
// 最大历史记录数
|
||||
maxHistoryCount: 1000,
|
||||
// 是否一直显示节点的展开收起按钮,默认为鼠标移上去和激活时才显示
|
||||
alwaysShowExpandBtn: false,
|
||||
// 扩展节点可插入的图标
|
||||
iconList: [
|
||||
// {
|
||||
// name: '',// 分组名称
|
||||
// type: '',// 分组的值
|
||||
// list: [// 分组下的图标列表
|
||||
// {
|
||||
// name: '',// 图标名称
|
||||
// icon:''// 图标,可以传svg或图片
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
],
|
||||
// 节点最大缓存数量
|
||||
maxNodeCacheCount: 1000,
|
||||
// 关联线默认文字
|
||||
defaultAssociativeLineText: '关联',
|
||||
// 思维导图适应画布大小时的内边距
|
||||
fitPadding: 50,
|
||||
// 是否开启按住ctrl键多选节点功能
|
||||
enableCtrlKeyNodeSelection: true,
|
||||
// 设置为左键多选节点,右键拖动画布
|
||||
useLeftKeySelectionRightKeyDrag: false,
|
||||
// 节点即将进入编辑前的回调方法,如果该方法返回true以外的值,那么将取消编辑,函数可以返回一个值,或一个Promise,回调参数为节点实例
|
||||
beforeTextEdit: null
|
||||
}
|
||||
import { defaultOpt } from './src/constants/defaultOptions'
|
||||
|
||||
// 思维导图
|
||||
class MindMap {
|
||||
@@ -152,12 +32,12 @@ class MindMap {
|
||||
this.svg = SVG().addTo(this.el).size(this.width, this.height)
|
||||
this.draw = this.svg.group()
|
||||
|
||||
// 节点id
|
||||
this.uid = 1
|
||||
|
||||
// 初始化主题
|
||||
this.initTheme()
|
||||
|
||||
// 初始化缓存数据
|
||||
this.initCache()
|
||||
|
||||
// 事件类
|
||||
this.event = new Event({
|
||||
mindMap: this
|
||||
@@ -201,6 +81,8 @@ class MindMap {
|
||||
|
||||
// 配置参数处理
|
||||
handleOpt(opt) {
|
||||
// 深拷贝一份节点数据
|
||||
opt.data = simpleDeepClone(opt.data || {})
|
||||
// 检查布局配置
|
||||
if (!layoutValueList.includes(opt.layout)) {
|
||||
opt.layout = CONSTANTS.LAYOUT.LOGICAL_STRUCTURE
|
||||
@@ -252,6 +134,23 @@ class MindMap {
|
||||
this.event.off(event, fn)
|
||||
}
|
||||
|
||||
// 初始化缓存数据
|
||||
initCache() {
|
||||
Object.keys(commonCaches).forEach((key) => {
|
||||
let type = getType(commonCaches[key])
|
||||
let value = ''
|
||||
switch(type) {
|
||||
case 'Boolean':
|
||||
value = false
|
||||
break
|
||||
default:
|
||||
value = null
|
||||
break
|
||||
}
|
||||
commonCaches[key] = value
|
||||
})
|
||||
}
|
||||
|
||||
// 设置主题
|
||||
initTheme() {
|
||||
// 合并主题配置
|
||||
@@ -314,7 +213,7 @@ class MindMap {
|
||||
this.opt.layout = layout
|
||||
this.view.reset()
|
||||
this.renderer.setLayout()
|
||||
this.render()
|
||||
this.render(null, CONSTANTS.CHANGE_LAYOUT)
|
||||
}
|
||||
|
||||
// 执行命令
|
||||
@@ -358,7 +257,7 @@ class MindMap {
|
||||
|
||||
// 获取思维导图数据,节点树、主题、布局等
|
||||
getData(withConfig) {
|
||||
let nodeData = this.command.removeDataUid(this.command.getCopyData())
|
||||
let nodeData = this.command.getCopyData()
|
||||
let data = {}
|
||||
if (withConfig) {
|
||||
data = {
|
||||
|
||||
4
simple-mind-map/package-lock.json
generated
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.6.0",
|
||||
"version": "0.6.7",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"version": "0.6.0",
|
||||
"version": "0.6.7",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@svgdotjs/svg.js": "^3.0.16",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.6.1",
|
||||
"version": "0.6.11-fix.1",
|
||||
"description": "一个简单的web在线思维导图",
|
||||
"authors": [
|
||||
{
|
||||
|
||||
@@ -27,136 +27,169 @@ export const themeList = [
|
||||
{
|
||||
name: '默认',
|
||||
value: 'default',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '暗色2',
|
||||
value: 'dark2',
|
||||
dark: true
|
||||
},
|
||||
{
|
||||
name: '天清绿',
|
||||
value: 'skyGreen',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '脑图经典2',
|
||||
value: 'classic2',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '脑图经典3',
|
||||
value: 'classic3',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '经典绿',
|
||||
value: 'classicGreen',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '经典蓝',
|
||||
value: 'classicBlue',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '天空蓝',
|
||||
value: 'blueSky',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '脑残粉',
|
||||
value: 'brainImpairedPink',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '暗色',
|
||||
value: 'dark',
|
||||
dark: true
|
||||
},
|
||||
{
|
||||
name: '泥土黄',
|
||||
value: 'earthYellow',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '清新绿',
|
||||
value: 'freshGreen',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '清新红',
|
||||
value: 'freshRed',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '浪漫紫',
|
||||
value: 'romanticPurple',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '粉红葡萄',
|
||||
value: 'pinkGrape',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '薄荷',
|
||||
value: 'mint',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '金色vip',
|
||||
value: 'gold',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '活力橙',
|
||||
value: 'vitalityOrange',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '绿叶',
|
||||
value: 'greenLeaf',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '脑图经典',
|
||||
value: 'classic',
|
||||
dark: true
|
||||
},
|
||||
{
|
||||
name: '脑图经典4',
|
||||
value: 'classic4',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '小黄人',
|
||||
value: 'minions',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '简约黑',
|
||||
value: 'simpleBlack',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '课程绿',
|
||||
value: 'courseGreen',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '咖啡',
|
||||
value: 'coffee',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '红色精神',
|
||||
value: 'redSpirit',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '黑色幽默',
|
||||
value: 'blackHumour',
|
||||
dark: true
|
||||
},
|
||||
{
|
||||
name: '深夜办公室',
|
||||
value: 'lateNightOffice',
|
||||
dark: true
|
||||
},
|
||||
{
|
||||
name: '黑金',
|
||||
value: 'blackGold',
|
||||
dark: true
|
||||
},
|
||||
{
|
||||
name: '牛油果',
|
||||
value: 'avocado',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '秋天',
|
||||
value: 'autumn',
|
||||
dark: false
|
||||
},
|
||||
{
|
||||
name: '橙汁',
|
||||
value: 'orangeJuice',
|
||||
dark: true
|
||||
}
|
||||
]
|
||||
|
||||
// 常量
|
||||
export const CONSTANTS = {
|
||||
CHANGE_THEME: 'changeTheme',
|
||||
CHANGE_LAYOUT: 'changeLayout',
|
||||
SET_DATA: 'setData',
|
||||
TRANSFORM_TO_NORMAL_NODE: 'transformAllNodesToNormalNode',
|
||||
MODE: {
|
||||
@@ -170,7 +203,8 @@ export const CONSTANTS = {
|
||||
CATALOG_ORGANIZATION: 'catalogOrganization',
|
||||
TIMELINE: 'timeline',
|
||||
TIMELINE2: 'timeline2',
|
||||
FISHBONE: 'fishbone'
|
||||
FISHBONE: 'fishbone',
|
||||
VERTICAL_TIMELINE: 'verticalTimeline'
|
||||
},
|
||||
DIR: {
|
||||
UP: 'up',
|
||||
@@ -206,9 +240,15 @@ export const CONSTANTS = {
|
||||
BOTTOM: 'bottom',
|
||||
CENTER: 'center'
|
||||
},
|
||||
TIMELINE_DIR: {
|
||||
LAYOUT_GROW_DIR: {
|
||||
LEFT: 'left',
|
||||
TOP: 'top',
|
||||
RIGHT: 'right',
|
||||
BOTTOM: 'bottom'
|
||||
},
|
||||
PASTE_TYPE: {
|
||||
CLIP_BOARD: 'clipBoard',
|
||||
CANVAS: 'canvas'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,6 +286,10 @@ export const layoutList = [
|
||||
name: '时间轴2',
|
||||
value: CONSTANTS.LAYOUT.TIMELINE2,
|
||||
},
|
||||
{
|
||||
name: '竖向时间轴',
|
||||
value: CONSTANTS.LAYOUT.VERTICAL_TIMELINE,
|
||||
},
|
||||
{
|
||||
name: '鱼骨图',
|
||||
value: CONSTANTS.LAYOUT.FISHBONE,
|
||||
@@ -258,5 +302,31 @@ export const layoutValueList = [
|
||||
CONSTANTS.LAYOUT.ORGANIZATION_STRUCTURE,
|
||||
CONSTANTS.LAYOUT.TIMELINE,
|
||||
CONSTANTS.LAYOUT.TIMELINE2,
|
||||
CONSTANTS.LAYOUT.VERTICAL_TIMELINE,
|
||||
CONSTANTS.LAYOUT.FISHBONE
|
||||
]
|
||||
]
|
||||
|
||||
// 节点数据中非样式的字段
|
||||
export const nodeDataNoStylePropList = [
|
||||
'text',
|
||||
'image',
|
||||
'imageTitle',
|
||||
'imageSize',
|
||||
'icon',
|
||||
'tag',
|
||||
'hyperlink',
|
||||
'hyperlinkTitle',
|
||||
'note',
|
||||
'expand',
|
||||
'isActive',
|
||||
'generalization',
|
||||
'richText',
|
||||
'resetRichText',
|
||||
'uid',
|
||||
'activeStyle'
|
||||
]
|
||||
|
||||
// 数据缓存
|
||||
export const commonCaches = {
|
||||
measureCustomNodeContentSizeEl: null
|
||||
}
|
||||
126
simple-mind-map/src/constants/defaultOptions.js
Normal file
@@ -0,0 +1,126 @@
|
||||
import { CONSTANTS } from './constant'
|
||||
|
||||
// 默认选项配置
|
||||
export const defaultOpt = {
|
||||
// 是否只读
|
||||
readonly: false,
|
||||
// 布局
|
||||
layout: CONSTANTS.LAYOUT.LOGICAL_STRUCTURE,
|
||||
// 如果结构为鱼骨图,那么可以通过该选项控制倾斜角度
|
||||
fishboneDeg: 45,
|
||||
// 主题
|
||||
theme: 'default', // 内置主题:default(默认主题)
|
||||
// 主题配置,会和所选择的主题进行合并
|
||||
themeConfig: {},
|
||||
// 放大缩小的增量比例
|
||||
scaleRatio: 0.2,
|
||||
// 鼠标缩放是否以鼠标当前位置为中心点,否则以画布中心点
|
||||
mouseScaleCenterUseMousePosition: true,
|
||||
// 最多显示几个标签
|
||||
maxTag: 5,
|
||||
// 导出图片时的内边距
|
||||
exportPadding: 20,
|
||||
// 展开收缩按钮尺寸
|
||||
expandBtnSize: 20,
|
||||
// 节点里图片和文字的间距
|
||||
imgTextMargin: 5,
|
||||
// 节点里各种文字信息的间距,如图标和文字的间距
|
||||
textContentMargin: 2,
|
||||
// 多选节点时鼠标移动到边缘时的画布移动偏移量
|
||||
selectTranslateStep: 3,
|
||||
// 多选节点时鼠标移动距边缘多少距离时开始偏移
|
||||
selectTranslateLimit: 20,
|
||||
// 自定义节点备注内容显示
|
||||
customNoteContentShow: null,
|
||||
/*
|
||||
{
|
||||
show(){},
|
||||
hide(){}
|
||||
}
|
||||
*/
|
||||
// 是否开启节点自由拖拽
|
||||
enableFreeDrag: false,
|
||||
// 水印配置
|
||||
watermarkConfig: {
|
||||
text: '',
|
||||
lineSpacing: 100,
|
||||
textSpacing: 100,
|
||||
angle: 30,
|
||||
textStyle: {
|
||||
color: '#999',
|
||||
opacity: 0.5,
|
||||
fontSize: 14
|
||||
}
|
||||
},
|
||||
// 达到该宽度文本自动换行
|
||||
textAutoWrapWidth: 500,
|
||||
// 自定义鼠标滚轮事件处理
|
||||
// 可以传一个函数,回调参数为事件对象
|
||||
customHandleMousewheel: null,
|
||||
// 鼠标滚动的行为,如果customHandleMousewheel传了自定义函数,这个属性不生效
|
||||
mousewheelAction: CONSTANTS.MOUSE_WHEEL_ACTION.ZOOM, // zoom(放大缩小)、move(上下移动)
|
||||
// 当mousewheelAction设为move时,可以通过该属性控制鼠标滚动一下视图移动的步长,单位px
|
||||
mousewheelMoveStep: 100,
|
||||
// 当mousewheelAction设为zoom时,默认向前滚动是缩小,向后滚动是放大,如果该属性设为true,那么会反过来
|
||||
mousewheelZoomActionReverse: false,
|
||||
// 默认插入的二级节点的文字
|
||||
defaultInsertSecondLevelNodeText: '二级节点',
|
||||
// 默认插入的二级以下节点的文字
|
||||
defaultInsertBelowSecondLevelNodeText: '分支主题',
|
||||
// 展开收起按钮的颜色
|
||||
expandBtnStyle: {
|
||||
color: '#808080',
|
||||
fill: '#fff'
|
||||
},
|
||||
// 自定义展开收起按钮的图标
|
||||
expandBtnIcon: {
|
||||
open: '', // svg字符串
|
||||
close: ''
|
||||
},
|
||||
// 是否只有当鼠标在画布内才响应快捷键事件
|
||||
enableShortcutOnlyWhenMouseInSvg: true,
|
||||
// 初始根节点的位置
|
||||
initRootNodePosition: null,
|
||||
// 导出png、svg、pdf时的图形内边距
|
||||
exportPaddingX: 10,
|
||||
exportPaddingY: 10,
|
||||
// 节点文本编辑框的z-index
|
||||
nodeTextEditZIndex: 3000,
|
||||
// 节点备注浮层的z-index
|
||||
nodeNoteTooltipZIndex: 3000,
|
||||
// 是否在点击了画布外的区域时结束节点文本的编辑状态
|
||||
isEndNodeTextEditOnClickOuter: true,
|
||||
// 最大历史记录数
|
||||
maxHistoryCount: 1000,
|
||||
// 是否一直显示节点的展开收起按钮,默认为鼠标移上去和激活时才显示
|
||||
alwaysShowExpandBtn: false,
|
||||
// 扩展节点可插入的图标
|
||||
iconList: [
|
||||
// {
|
||||
// name: '',// 分组名称
|
||||
// type: '',// 分组的值
|
||||
// list: [// 分组下的图标列表
|
||||
// {
|
||||
// name: '',// 图标名称
|
||||
// icon:''// 图标,可以传svg或图片
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
],
|
||||
// 节点最大缓存数量
|
||||
maxNodeCacheCount: 1000,
|
||||
// 关联线默认文字
|
||||
defaultAssociativeLineText: '关联',
|
||||
// 思维导图适应画布大小时的内边距
|
||||
fitPadding: 50,
|
||||
// 是否开启按住ctrl键多选节点功能
|
||||
enableCtrlKeyNodeSelection: true,
|
||||
// 设置为左键多选节点,右键拖动画布
|
||||
useLeftKeySelectionRightKeyDrag: false,
|
||||
// 节点即将进入编辑前的回调方法,如果该方法返回true以外的值,那么将取消编辑,函数可以返回一个值,或一个Promise,回调参数为节点实例
|
||||
beforeTextEdit: null,
|
||||
// 是否开启自定义节点内容
|
||||
isUseCustomNodeContent: false,
|
||||
// 自定义返回节点内容的方法
|
||||
customCreateNodeContent: null
|
||||
}
|
||||
@@ -89,7 +89,7 @@ class Command {
|
||||
this.history.shift()
|
||||
}
|
||||
this.activeHistoryIndex = this.history.length - 1
|
||||
this.mindMap.emit('data_change', this.removeDataUid(data))
|
||||
this.mindMap.emit('data_change', data)
|
||||
this.mindMap.emit(
|
||||
'back_forward',
|
||||
this.activeHistoryIndex,
|
||||
@@ -110,7 +110,7 @@ class Command {
|
||||
this.history.length
|
||||
)
|
||||
let data = simpleDeepClone(this.history[this.activeHistoryIndex])
|
||||
this.mindMap.emit('data_change', this.removeDataUid(data))
|
||||
this.mindMap.emit('data_change', data)
|
||||
return data
|
||||
}
|
||||
}
|
||||
@@ -125,7 +125,7 @@ class Command {
|
||||
this.activeHistoryIndex += step
|
||||
this.mindMap.emit('back_forward', this.activeHistoryIndex, this.history.length)
|
||||
let data = simpleDeepClone(this.history[this.activeHistoryIndex])
|
||||
this.mindMap.emit('data_change', this.removeDataUid(data))
|
||||
this.mindMap.emit('data_change', data)
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,8 +57,11 @@ export default class KeyCommand {
|
||||
}
|
||||
Object.keys(this.shortcutMap).forEach(key => {
|
||||
if (this.checkKey(e, key)) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
// 粘贴事件不组织,因为要监听paste事件
|
||||
if (!this.checkKey(e, 'Control+v')) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
}
|
||||
this.shortcutMap[key].forEach(fn => {
|
||||
fn()
|
||||
})
|
||||
|
||||
@@ -4,9 +4,16 @@ import MindMap from '../../layouts/MindMap'
|
||||
import CatalogOrganization from '../../layouts/CatalogOrganization'
|
||||
import OrganizationStructure from '../../layouts/OrganizationStructure'
|
||||
import Timeline from '../../layouts/Timeline'
|
||||
import VerticalTimeline from '../../layouts/VerticalTimeline'
|
||||
import Fishbone from '../../layouts/Fishbone'
|
||||
import TextEdit from './TextEdit'
|
||||
import { copyNodeTree, simpleDeepClone, walk } from '../../utils'
|
||||
import {
|
||||
copyNodeTree,
|
||||
simpleDeepClone,
|
||||
walk,
|
||||
bfsWalk,
|
||||
loadImage
|
||||
} from '../../utils'
|
||||
import { shapeList } from './node/Shape'
|
||||
import { lineStyleProps } from '../../themes/default'
|
||||
import { CONSTANTS } from '../../constants/constant'
|
||||
@@ -25,8 +32,10 @@ const layouts = {
|
||||
[CONSTANTS.LAYOUT.TIMELINE]: Timeline,
|
||||
// 时间轴2
|
||||
[CONSTANTS.LAYOUT.TIMELINE2]: Timeline,
|
||||
// 竖向时间轴
|
||||
[CONSTANTS.LAYOUT.VERTICAL_TIMELINE]: VerticalTimeline,
|
||||
// 鱼骨图
|
||||
[CONSTANTS.LAYOUT.FISHBONE]: Fishbone,
|
||||
[CONSTANTS.LAYOUT.FISHBONE]: Fishbone
|
||||
}
|
||||
|
||||
// 渲染
|
||||
@@ -39,7 +48,7 @@ class Render {
|
||||
this.themeConfig = this.mindMap.themeConfig
|
||||
this.draw = this.mindMap.draw
|
||||
// 渲染树,操作过程中修改的都是这里的数据
|
||||
this.renderTree = merge({}, this.mindMap.opt.data || {})
|
||||
this.renderTree = merge({},this.mindMap.opt.data || {})
|
||||
// 是否重新渲染
|
||||
this.reRender = false
|
||||
// 是否正在渲染中
|
||||
@@ -57,6 +66,12 @@ class Render {
|
||||
this.root = null
|
||||
// 文本编辑框,需要再bindEvent之前实例化,否则单击事件只能触发隐藏文本编辑框,而无法保存文本修改
|
||||
this.textEdit = new TextEdit(this)
|
||||
// 当前复制的数据
|
||||
this.lastBeingCopyData = null
|
||||
this.beingCopyData = null
|
||||
this.beingPasteText = ''
|
||||
this.beingPasteImgSize = 0
|
||||
this.currentBeingPasteType = ''
|
||||
// 布局
|
||||
this.setLayout()
|
||||
// 绑定事件
|
||||
@@ -79,18 +94,24 @@ class Render {
|
||||
// 绑定事件
|
||||
bindEvent() {
|
||||
// 点击事件
|
||||
this.mindMap.on('draw_click', (e) => {
|
||||
this.mindMap.on('draw_click', e => {
|
||||
// 清除激活状态
|
||||
let isTrueClick = true
|
||||
let { useLeftKeySelectionRightKeyDrag } = this.mindMap.opt
|
||||
if (useLeftKeySelectionRightKeyDrag) {
|
||||
let mousedownPos = this.mindMap.event.mousedownPos
|
||||
isTrueClick = Math.abs(e.clientX - mousedownPos.x) <= 5 && Math.abs(e.clientY - mousedownPos.y) <= 5
|
||||
isTrueClick =
|
||||
Math.abs(e.clientX - mousedownPos.x) <= 5 &&
|
||||
Math.abs(e.clientY - mousedownPos.y) <= 5
|
||||
}
|
||||
if (isTrueClick && this.activeNodeList.length > 0) {
|
||||
this.mindMap.execCommand('CLEAR_ACTIVE_NODE')
|
||||
}
|
||||
})
|
||||
// 粘贴事件
|
||||
this.mindMap.on('paste', data => {
|
||||
this.onPaste(data)
|
||||
})
|
||||
}
|
||||
|
||||
// 注册命令
|
||||
@@ -192,6 +213,9 @@ class Render {
|
||||
// 设置节点形状
|
||||
this.setNodeShape = this.setNodeShape.bind(this)
|
||||
this.mindMap.command.add('SET_NODE_SHAPE', this.setNodeShape)
|
||||
// 定位节点
|
||||
this.goTargetNode = this.goTargetNode.bind(this)
|
||||
this.mindMap.command.add('GO_TARGET_NODE', this.goTargetNode)
|
||||
}
|
||||
|
||||
// 注册快捷键
|
||||
@@ -209,7 +233,7 @@ class Render {
|
||||
}
|
||||
this.mindMap.keyCommand.addShortcut('Enter', this.insertNodeWrap)
|
||||
// 插入概要
|
||||
this.mindMap.keyCommand.addShortcut('Control+s', this.addGeneralization)
|
||||
this.mindMap.keyCommand.addShortcut('Control+g', this.addGeneralization)
|
||||
// 展开/收起节点
|
||||
this.toggleActiveExpand = this.toggleActiveExpand.bind(this)
|
||||
this.mindMap.keyCommand.addShortcut('/', this.toggleActiveExpand)
|
||||
@@ -236,6 +260,14 @@ class Render {
|
||||
// 下移节点
|
||||
this.mindMap.keyCommand.addShortcut('Control+Down', this.downNode)
|
||||
// 复制节点、剪切节点、粘贴节点的快捷键需开发者自行注册实现,可参考demo
|
||||
this.copy = this.copy.bind(this)
|
||||
this.mindMap.keyCommand.addShortcut('Control+c', this.copy)
|
||||
this.mindMap.keyCommand.addShortcut('Control+v', () => {
|
||||
// 隐藏输入框可能会失去焦点,所以要重新聚焦
|
||||
this.textEdit.focusHiddenInput()
|
||||
})
|
||||
this.cut = this.cut.bind(this)
|
||||
this.mindMap.keyCommand.addShortcut('Control+x', this.cut)
|
||||
}
|
||||
|
||||
// 开启文字编辑,会禁用回车键和删除键相关快捷键防止冲突
|
||||
@@ -256,7 +288,6 @@ class Render {
|
||||
|
||||
// 渲染
|
||||
render(callback = () => {}, source) {
|
||||
let t = Date.now()
|
||||
// 如果当前还没有渲染完毕,不再触发渲染
|
||||
if (this.isRendering) {
|
||||
// 等待当前渲染完毕后再进行一次渲染
|
||||
@@ -276,7 +307,7 @@ class Render {
|
||||
// 计算布局
|
||||
this.layout.doLayout(root => {
|
||||
// 删除本次渲染时不再需要的节点
|
||||
Object.keys(this.lastNodeCache).forEach((uid) => {
|
||||
Object.keys(this.lastNodeCache).forEach(uid => {
|
||||
if (!this.nodeCache[uid]) {
|
||||
this.lastNodeCache[uid].destroy()
|
||||
if (this.lastNodeCache[uid].parent) {
|
||||
@@ -287,7 +318,7 @@ class Render {
|
||||
// 更新根节点
|
||||
this.root = root
|
||||
// 渲染节点
|
||||
const onEnd = () => {
|
||||
this.root.render(() => {
|
||||
this.isRendering = false
|
||||
this.mindMap.emit('node_tree_render_end')
|
||||
callback && callback()
|
||||
@@ -296,22 +327,13 @@ class Render {
|
||||
this.render(callback, source)
|
||||
} else {
|
||||
// 触发一次保存,因为修改了渲染树的数据
|
||||
if (this.mindMap.richText && [CONSTANTS.CHANGE_THEME, CONSTANTS.SET_DATA].includes(source)) {
|
||||
if (
|
||||
this.mindMap.richText &&
|
||||
[CONSTANTS.CHANGE_THEME, CONSTANTS.SET_DATA].includes(source)
|
||||
) {
|
||||
this.mindMap.command.addHistory()
|
||||
}
|
||||
}
|
||||
}
|
||||
let { enableNodeTransitionMove, nodeTransitionMoveDuration } =
|
||||
this.mindMap.opt
|
||||
this.root.render(() => {
|
||||
let dur = Date.now() - t
|
||||
if (enableNodeTransitionMove && dur <= nodeTransitionMoveDuration) {
|
||||
setTimeout(() => {
|
||||
onEnd()
|
||||
}, nodeTransitionMoveDuration - dur);
|
||||
} else {
|
||||
onEnd()
|
||||
}
|
||||
})
|
||||
})
|
||||
this.mindMap.emit('node_active', null, this.activeNodeList)
|
||||
@@ -413,7 +435,7 @@ class Render {
|
||||
// 规范指定节点数据
|
||||
formatAppointNodes(appointNodes) {
|
||||
if (!appointNodes) return []
|
||||
return Array.isArray(appointNodes) ? appointNodes: [appointNodes]
|
||||
return Array.isArray(appointNodes) ? appointNodes : [appointNodes]
|
||||
}
|
||||
|
||||
// 插入同级节点,多个节点只会操作第一个节点
|
||||
@@ -422,22 +444,35 @@ class Render {
|
||||
if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) {
|
||||
return
|
||||
}
|
||||
let { defaultInsertSecondLevelNodeText, defaultInsertBelowSecondLevelNodeText } = this.mindMap.opt
|
||||
this.textEdit.hideEditTextBox()
|
||||
let {
|
||||
defaultInsertSecondLevelNodeText,
|
||||
defaultInsertBelowSecondLevelNodeText
|
||||
} = this.mindMap.opt
|
||||
let list = appointNodes.length > 0 ? appointNodes : this.activeNodeList
|
||||
let first = list[0]
|
||||
if (first.isGeneralization) {
|
||||
return
|
||||
}
|
||||
if (first.isRoot) {
|
||||
this.insertChildNode(openEdit, appointNodes, appointData)
|
||||
} else {
|
||||
let text = first.layerIndex === 1 ? defaultInsertSecondLevelNodeText : defaultInsertBelowSecondLevelNodeText
|
||||
let text =
|
||||
first.layerIndex === 1
|
||||
? defaultInsertSecondLevelNodeText
|
||||
: defaultInsertBelowSecondLevelNodeText
|
||||
if (first.layerIndex === 1) {
|
||||
first.parent.destroy()
|
||||
}
|
||||
let index = this.getNodeIndex(first)
|
||||
let isRichText = !!this.mindMap.richText
|
||||
first.parent.nodeData.children.splice(index + 1, 0, {
|
||||
inserting: openEdit,
|
||||
data: {
|
||||
text: text,
|
||||
expand: true,
|
||||
richText: isRichText,
|
||||
resetRichText: isRichText,
|
||||
...(appointData || {})
|
||||
},
|
||||
children: []
|
||||
@@ -452,18 +487,30 @@ class Render {
|
||||
if (this.activeNodeList.length <= 0 && appointNodes.length <= 0) {
|
||||
return
|
||||
}
|
||||
let { defaultInsertSecondLevelNodeText, defaultInsertBelowSecondLevelNodeText } = this.mindMap.opt
|
||||
this.textEdit.hideEditTextBox()
|
||||
let {
|
||||
defaultInsertSecondLevelNodeText,
|
||||
defaultInsertBelowSecondLevelNodeText
|
||||
} = this.mindMap.opt
|
||||
let list = appointNodes.length > 0 ? appointNodes : this.activeNodeList
|
||||
list.forEach(node => {
|
||||
if (node.isGeneralization) {
|
||||
return
|
||||
}
|
||||
if (!node.nodeData.children) {
|
||||
node.nodeData.children = []
|
||||
}
|
||||
let text = node.isRoot ? defaultInsertSecondLevelNodeText : defaultInsertBelowSecondLevelNodeText
|
||||
let text = node.isRoot
|
||||
? defaultInsertSecondLevelNodeText
|
||||
: defaultInsertBelowSecondLevelNodeText
|
||||
let isRichText = !!this.mindMap.richText
|
||||
node.nodeData.children.push({
|
||||
inserting: openEdit,
|
||||
data: {
|
||||
text: text,
|
||||
expand: true,
|
||||
richText: isRichText,
|
||||
resetRichText: isRichText,
|
||||
...(appointData || {})
|
||||
},
|
||||
children: []
|
||||
@@ -531,13 +578,81 @@ class Render {
|
||||
this.mindMap.render()
|
||||
}
|
||||
|
||||
// 复制节点
|
||||
copy() {
|
||||
this.beingCopyData = this.copyNode()
|
||||
}
|
||||
|
||||
// 剪切节点
|
||||
cut() {
|
||||
this.mindMap.execCommand('CUT_NODE', copyData => {
|
||||
this.beingCopyData = copyData
|
||||
})
|
||||
}
|
||||
|
||||
// 粘贴节点
|
||||
paste() {
|
||||
if (this.beingCopyData) {
|
||||
this.mindMap.execCommand('PASTE_NODE', this.beingCopyData)
|
||||
}
|
||||
}
|
||||
|
||||
// 粘贴事件
|
||||
async onPaste({ text, img }) {
|
||||
// 检查剪切板数据是否有变化
|
||||
// 通过图片大小来判断图片是否发生变化,可能是不准确的,但是目前没有其他好方法
|
||||
const imgSize = img ? img.size : 0
|
||||
if (this.beingPasteText !== text || this.beingPasteImgSize !== imgSize) {
|
||||
this.currentBeingPasteType = CONSTANTS.PASTE_TYPE.CLIP_BOARD
|
||||
this.beingPasteText = text
|
||||
this.beingPasteImgSize = imgSize
|
||||
}
|
||||
// 检查要粘贴的节点数据是否有变化,节点优先级高于剪切板
|
||||
if (this.lastBeingCopyData !== this.beingCopyData) {
|
||||
this.lastBeingCopyData = this.beingCopyData
|
||||
this.currentBeingPasteType = CONSTANTS.PASTE_TYPE.CANVAS
|
||||
}
|
||||
// 粘贴剪切板的数据
|
||||
if (this.currentBeingPasteType === CONSTANTS.PASTE_TYPE.CLIP_BOARD) {
|
||||
// 存在文本,则创建子节点
|
||||
if (text) {
|
||||
this.mindMap.execCommand('INSERT_CHILD_NODE', false, [], {
|
||||
text
|
||||
})
|
||||
}
|
||||
// 存在图片,则添加到当前激活节点
|
||||
if (img) {
|
||||
try {
|
||||
let imgData = await loadImage(img)
|
||||
if (this.activeNodeList.length > 0) {
|
||||
this.activeNodeList.forEach(node => {
|
||||
this.mindMap.execCommand('SET_NODE_IMAGE', node, {
|
||||
url: imgData.url,
|
||||
title: '',
|
||||
width: imgData.size.width,
|
||||
height: imgData.size.height
|
||||
})
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 粘贴节点数据
|
||||
this.paste()
|
||||
}
|
||||
}
|
||||
|
||||
// 将节点移动到另一个节点的前面
|
||||
insertBefore(node, exist) {
|
||||
if (node.isRoot) {
|
||||
return
|
||||
}
|
||||
// 如果是二级节点变成了下级节点,或是下级节点变成了二级节点,节点样式需要更新
|
||||
let nodeLayerChanged = (node.layerIndex === 1 && exist.layerIndex !== 1) || (node.layerIndex !== 1 && exist.layerIndex === 1)
|
||||
let nodeLayerChanged =
|
||||
(node.layerIndex === 1 && exist.layerIndex !== 1) ||
|
||||
(node.layerIndex !== 1 && exist.layerIndex === 1)
|
||||
// 移动节点
|
||||
let nodeParent = node.parent
|
||||
let nodeBorthers = nodeParent.children
|
||||
@@ -574,7 +689,9 @@ class Render {
|
||||
return
|
||||
}
|
||||
// 如果是二级节点变成了下级节点,或是下级节点变成了二级节点,节点样式需要更新
|
||||
let nodeLayerChanged = (node.layerIndex === 1 && exist.layerIndex !== 1) || (node.layerIndex !== 1 && exist.layerIndex === 1)
|
||||
let nodeLayerChanged =
|
||||
(node.layerIndex === 1 && exist.layerIndex !== 1) ||
|
||||
(node.layerIndex !== 1 && exist.layerIndex === 1)
|
||||
// 移动节点
|
||||
let nodeParent = node.parent
|
||||
let nodeBorthers = nodeParent.children
|
||||
@@ -614,7 +731,7 @@ class Render {
|
||||
}
|
||||
let isAppointNodes = appointNodes.length > 0
|
||||
let list = isAppointNodes ? appointNodes : this.activeNodeList
|
||||
let root = list.find((node) => {
|
||||
let root = list.find(node => {
|
||||
return node.isRoot
|
||||
})
|
||||
if (root) {
|
||||
@@ -687,11 +804,11 @@ class Render {
|
||||
if (node.isRoot) {
|
||||
return
|
||||
}
|
||||
let copyData = copyNodeTree({}, node, false, true)
|
||||
// let copyData = copyNodeTree({}, node, false, true)
|
||||
this.removeActiveNode(node)
|
||||
this.removeOneNode(node)
|
||||
this.mindMap.emit('node_active', null, this.activeNodeList)
|
||||
toNode.nodeData.children.push(copyData)
|
||||
toNode.nodeData.children.push(node.nodeData)
|
||||
this.mindMap.render()
|
||||
if (toNode.isRoot) {
|
||||
toNode.destroy()
|
||||
@@ -700,7 +817,7 @@ class Render {
|
||||
|
||||
// 粘贴节点到节点
|
||||
pasteNode(data) {
|
||||
if (this.activeNodeList.length <= 0) {
|
||||
if (this.activeNodeList.length <= 0 || !data) {
|
||||
return
|
||||
}
|
||||
this.activeNodeList.forEach(item => {
|
||||
@@ -855,21 +972,23 @@ class Render {
|
||||
}
|
||||
|
||||
// 设置节点文本
|
||||
setNodeText(node, text, richText) {
|
||||
setNodeText(node, text, richText, resetRichText) {
|
||||
this.setNodeDataRender(node, {
|
||||
text,
|
||||
richText
|
||||
richText,
|
||||
resetRichText
|
||||
})
|
||||
}
|
||||
|
||||
// 设置节点图片
|
||||
setNodeImage(node, { url, title, width, height }) {
|
||||
setNodeImage(node, { url, title, width, height, custom = false }) {
|
||||
this.setNodeDataRender(node, {
|
||||
image: url,
|
||||
imageTitle: title || '',
|
||||
imageSize: {
|
||||
width,
|
||||
height
|
||||
height,
|
||||
custom
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -982,6 +1101,20 @@ class Render {
|
||||
})
|
||||
}
|
||||
|
||||
// 定位到指定节点
|
||||
goTargetNode(node, callback = () => {}) {
|
||||
let uid = typeof node === 'string' ? node : node.nodeData.data.uid
|
||||
if (!uid) return
|
||||
this.expandToNodeUid(uid, () => {
|
||||
let targetNode = this.findNodeByUid(uid)
|
||||
if (targetNode) {
|
||||
targetNode.active()
|
||||
this.moveNodeToCenter(targetNode)
|
||||
callback()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 更新节点数据
|
||||
setNodeData(node, data) {
|
||||
Object.keys(data).forEach(key => {
|
||||
@@ -990,7 +1123,7 @@ class Render {
|
||||
}
|
||||
|
||||
// 设置节点数据,并判断是否渲染
|
||||
setNodeDataRender(node, data) {
|
||||
setNodeDataRender(node, data, notRender = false) {
|
||||
this.setNodeData(node, data)
|
||||
let changed = node.reRender()
|
||||
if (changed) {
|
||||
@@ -998,7 +1131,7 @@ class Render {
|
||||
// 概要节点
|
||||
node.generalizationBelongNode.updateGeneralization()
|
||||
}
|
||||
this.mindMap.render()
|
||||
if (!notRender) this.mindMap.render()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1018,6 +1151,44 @@ class Render {
|
||||
this.mindMap.view.translateY(offsetY)
|
||||
this.mindMap.view.setScale(1)
|
||||
}
|
||||
|
||||
// 展开到指定uid的节点
|
||||
expandToNodeUid(uid, callback = () => {}) {
|
||||
let parentsList = []
|
||||
const cache = {}
|
||||
bfsWalk(this.renderTree, (node, parent) => {
|
||||
if (node.data.uid === uid) {
|
||||
parentsList = parent ? [...cache[parent.data.uid], parent] : []
|
||||
return 'stop'
|
||||
} else {
|
||||
cache[node.data.uid] = parent ? [...cache[parent.data.uid], parent] : []
|
||||
}
|
||||
})
|
||||
let needRender = false
|
||||
parentsList.forEach(node => {
|
||||
if (!node.data.expand) {
|
||||
needRender = true
|
||||
node.data.expand = true
|
||||
}
|
||||
})
|
||||
if (needRender) {
|
||||
this.mindMap.render(callback)
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
// 根据uid找到对应的节点实例
|
||||
findNodeByUid(uid) {
|
||||
let res = null
|
||||
walk(this.root, null, node => {
|
||||
if (node.nodeData.data.uid === uid) {
|
||||
res = node
|
||||
return true
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
export default Render
|
||||
|
||||
@@ -10,11 +10,14 @@ export default class TextEdit {
|
||||
this.currentNode = null
|
||||
// 文本编辑框
|
||||
this.textEditNode = null
|
||||
// 隐藏的文本输入框
|
||||
this.hiddenInputEl = null
|
||||
// 文本编辑框是否显示
|
||||
this.showTextEdit = false
|
||||
// 如果编辑过程中缩放画布了,那么缓存当前编辑的内容
|
||||
this.cacheEditingText = ''
|
||||
this.bindEvent()
|
||||
this.createHiddenInput()
|
||||
}
|
||||
|
||||
// 事件
|
||||
@@ -46,6 +49,10 @@ export default class TextEdit {
|
||||
this.mindMap.on('before_node_active', () => {
|
||||
this.hideEditTextBox()
|
||||
})
|
||||
// 节点激活事件
|
||||
this.mindMap.on('node_active', () => {
|
||||
this.focusHiddenInput()
|
||||
})
|
||||
// 注册编辑快捷键
|
||||
this.mindMap.keyCommand.addShortcut('F2', () => {
|
||||
if (this.renderer.activeNodeList.length <= 0) {
|
||||
@@ -56,20 +63,66 @@ export default class TextEdit {
|
||||
this.mindMap.on('scale', this.onScale)
|
||||
}
|
||||
|
||||
// 创建一个隐藏的文本输入框
|
||||
createHiddenInput() {
|
||||
if (this.hiddenInputEl) return
|
||||
this.hiddenInputEl = document.createElement('input')
|
||||
this.hiddenInputEl.type = 'text'
|
||||
this.hiddenInputEl.style.cssText = `
|
||||
position: fixed;
|
||||
left: -99999px;
|
||||
top: -99999px;
|
||||
`
|
||||
// 监听粘贴事件
|
||||
this.hiddenInputEl.addEventListener('paste', async event => {
|
||||
event.preventDefault()
|
||||
const text = (event.clipboardData || window.clipboardData).getData('text')
|
||||
const files = event.clipboardData.files
|
||||
let img = null
|
||||
if (files.length > 0) {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
if (/^image\//.test(files[i].type)) {
|
||||
img = files[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
this.mindMap.emit('paste', {
|
||||
text,
|
||||
img
|
||||
})
|
||||
})
|
||||
document.body.appendChild(this.hiddenInputEl)
|
||||
}
|
||||
|
||||
// 让隐藏的文本输入框聚焦
|
||||
focusHiddenInput() {
|
||||
if (this.hiddenInputEl) this.hiddenInputEl.focus()
|
||||
}
|
||||
|
||||
// 注册临时快捷键
|
||||
registerTmpShortcut() {
|
||||
// 注册回车快捷键
|
||||
this.mindMap.keyCommand.addShortcut('Enter', () => {
|
||||
this.hideEditTextBox()
|
||||
})
|
||||
this.mindMap.keyCommand.addShortcut('Tab', () => {
|
||||
this.hideEditTextBox()
|
||||
})
|
||||
}
|
||||
|
||||
// 显示文本编辑框
|
||||
async show(node) {
|
||||
if (typeof this.mindMap.opt.beforeTextEdit === 'function') {
|
||||
// isInserting:是否是刚创建的节点
|
||||
async show(node, e, isInserting = false) {
|
||||
// 使用了自定义节点内容那么不响应编辑事件
|
||||
if (node.isUseCustomNodeContent()) {
|
||||
return
|
||||
}
|
||||
let { beforeTextEdit } = this.mindMap.opt
|
||||
if (typeof beforeTextEdit === 'function') {
|
||||
let isShow = false
|
||||
try {
|
||||
isShow = await this.mindMap.opt.beforeTextEdit(node)
|
||||
isShow = await beforeTextEdit(node, isInserting)
|
||||
} catch (error) {
|
||||
isShow = false
|
||||
}
|
||||
@@ -80,7 +133,7 @@ export default class TextEdit {
|
||||
this.mindMap.view.translateXY(offsetLeft, offsetTop)
|
||||
let rect = node._textData.node.node.getBoundingClientRect()
|
||||
if (this.mindMap.richText) {
|
||||
this.mindMap.richText.showEditText(node, rect)
|
||||
this.mindMap.richText.showEditText(node, rect, isInserting)
|
||||
return
|
||||
}
|
||||
this.showEditTextBox(node, rect)
|
||||
@@ -90,7 +143,8 @@ export default class TextEdit {
|
||||
onScale() {
|
||||
if (!this.currentNode) return
|
||||
if (this.mindMap.richText) {
|
||||
this.mindMap.richText.cacheEditingText = this.mindMap.richText.getEditText()
|
||||
this.mindMap.richText.cacheEditingText =
|
||||
this.mindMap.richText.getEditText()
|
||||
this.mindMap.richText.showTextEdit = false
|
||||
} else {
|
||||
this.cacheEditingText = this.getEditText()
|
||||
@@ -118,7 +172,9 @@ export default class TextEdit {
|
||||
let scale = this.mindMap.view.scale
|
||||
let lineHeight = node.style.merge('lineHeight')
|
||||
let fontSize = node.style.merge('fontSize')
|
||||
let textLines = (this.cacheEditingText || node.nodeData.data.text).split(/\n/gim)
|
||||
let textLines = (this.cacheEditingText || node.nodeData.data.text).split(
|
||||
/\n/gim
|
||||
)
|
||||
let isMultiLine = node._textData.node.attr('data-ismultiLine') === 'true'
|
||||
node.style.domText(this.textEditNode, scale, isMultiLine)
|
||||
this.textEditNode.style.zIndex = this.mindMap.opt.nodeTextEditZIndex
|
||||
@@ -128,9 +184,12 @@ export default class TextEdit {
|
||||
this.textEditNode.style.left = rect.left + 'px'
|
||||
this.textEditNode.style.top = rect.top + 'px'
|
||||
this.textEditNode.style.display = 'block'
|
||||
this.textEditNode.style.maxWidth = this.mindMap.opt.textAutoWrapWidth * scale + 'px'
|
||||
this.textEditNode.style.maxWidth =
|
||||
this.mindMap.opt.textAutoWrapWidth * scale + 'px'
|
||||
if (isMultiLine && lineHeight !== 1) {
|
||||
this.textEditNode.style.transform = `translateY(${-((lineHeight * fontSize - fontSize) / 2) * scale}px)`
|
||||
this.textEditNode.style.transform = `translateY(${
|
||||
-((lineHeight * fontSize - fontSize) / 2) * scale
|
||||
}px)`
|
||||
}
|
||||
this.showTextEdit = true
|
||||
// 选中文本
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Style from './Style'
|
||||
import Shape from './Shape'
|
||||
import { asyncRun } from '../../../utils'
|
||||
import { G, Rect } from '@svgdotjs/svg.js'
|
||||
import { asyncRun, nodeToHTML } from '../../../utils'
|
||||
import { G, Rect, ForeignObject, SVG } from '@svgdotjs/svg.js'
|
||||
import nodeGeneralizationMethods from './nodeGeneralization'
|
||||
import nodeExpandBtnMethods from './nodeExpandBtn'
|
||||
import nodeCommandWrapsMethods from './nodeCommandWraps'
|
||||
@@ -59,6 +59,7 @@ class Node {
|
||||
this.group = null
|
||||
this.shapeNode = null // 节点形状节点
|
||||
// 节点内容对象
|
||||
this._customNodeContent = null
|
||||
this._imgData = null
|
||||
this._iconData = null
|
||||
this._textData = null
|
||||
@@ -154,6 +155,13 @@ class Node {
|
||||
|
||||
// 创建节点的各个内容对象数据
|
||||
createNodeData() {
|
||||
// 自定义节点内容
|
||||
let { isUseCustomNodeContent, customCreateNodeContent } = this.mindMap.opt
|
||||
if (isUseCustomNodeContent && customCreateNodeContent) {
|
||||
this._customNodeContent = customCreateNodeContent(this)
|
||||
}
|
||||
// 如果没有返回内容,那么还是使用内置的节点内容
|
||||
if (this._customNodeContent) return
|
||||
this._imgData = this.createImgNode()
|
||||
this._iconData = this.createIconNode()
|
||||
this._textData = this.createTextNode()
|
||||
@@ -176,6 +184,14 @@ class Node {
|
||||
|
||||
// 计算节点尺寸信息
|
||||
getNodeRect() {
|
||||
// 自定义节点内容
|
||||
if (this.isUseCustomNodeContent()) {
|
||||
let rect = this.measureCustomNodeContentSize(this._customNodeContent)
|
||||
return {
|
||||
width: rect.width,
|
||||
height: rect.height
|
||||
}
|
||||
}
|
||||
// 宽高
|
||||
let imgContentWidth = 0
|
||||
let imgContentHeight = 0
|
||||
@@ -250,22 +266,24 @@ class Node {
|
||||
paddingY += this.shapePadding.paddingY
|
||||
// 节点形状
|
||||
this.shapeNode = this.shapeInstance.createShape()
|
||||
this.shapeNode.addClass('smm-node-shape')
|
||||
this.group.add(this.shapeNode)
|
||||
this.updateNodeShape()
|
||||
// 渲染一个隐藏的矩形区域,用来触发展开收起按钮的显示
|
||||
if (!this.mindMap.opt.alwaysShowExpandBtn) {
|
||||
if (!this._unVisibleRectRegionNode) {
|
||||
this._unVisibleRectRegionNode = new Rect()
|
||||
}
|
||||
this._unVisibleRectRegionNode.fill({
|
||||
color: 'transparent'
|
||||
}).size(this.expandBtnSize, height).x(width).y(0)
|
||||
this.group.add(this._unVisibleRectRegionNode)
|
||||
}
|
||||
this.renderExpandBtnPlaceholderRect()
|
||||
// 概要节点添加一个带所属节点id的类名
|
||||
if (this.isGeneralization && this.generalizationBelongNode) {
|
||||
this.group.addClass('generalization_' + this.generalizationBelongNode.uid)
|
||||
}
|
||||
// 如果存在自定义节点内容,那么使用自定义节点内容
|
||||
if (this.isUseCustomNodeContent()) {
|
||||
let foreignObject = new ForeignObject()
|
||||
foreignObject.width(width)
|
||||
foreignObject.height(height)
|
||||
foreignObject.add(SVG(this._customNodeContent))
|
||||
this.group.add(foreignObject)
|
||||
return
|
||||
}
|
||||
// 图片节点
|
||||
let imgHeight = 0
|
||||
if (this._imgData) {
|
||||
@@ -339,6 +357,21 @@ class Node {
|
||||
this.group.add(textContentNested)
|
||||
}
|
||||
|
||||
// 渲染展开收起按钮的隐藏占位元素
|
||||
renderExpandBtnPlaceholderRect() {
|
||||
if (!this.mindMap.opt.alwaysShowExpandBtn) {
|
||||
let { width, height } = this
|
||||
if (!this._unVisibleRectRegionNode) {
|
||||
this._unVisibleRectRegionNode = new Rect()
|
||||
this._unVisibleRectRegionNode.fill({
|
||||
color: 'transparent'
|
||||
})
|
||||
}
|
||||
this.group.add(this._unVisibleRectRegionNode)
|
||||
this.renderer.layout.renderExpandBtnRect(this._unVisibleRectRegionNode, this.expandBtnSize, width, height, this)
|
||||
}
|
||||
}
|
||||
|
||||
// 给节点绑定事件
|
||||
bindGroupEvent() {
|
||||
// 单击事件,选中节点
|
||||
@@ -408,7 +441,8 @@ class Node {
|
||||
// 右键菜单事件
|
||||
this.group.on('contextmenu', e => {
|
||||
// 按住ctrl键点击鼠标左键不知为何触发的是contextmenu事件
|
||||
if (this.mindMap.opt.readonly || e.ctrlKey) {// || this.isGeneralization
|
||||
if (this.mindMap.opt.readonly || e.ctrlKey) {
|
||||
// || this.isGeneralization
|
||||
return
|
||||
}
|
||||
e.stopPropagation()
|
||||
@@ -442,8 +476,9 @@ class Node {
|
||||
if (!this.group) {
|
||||
return
|
||||
}
|
||||
let { enableNodeTransitionMove, nodeTransitionMoveDuration, alwaysShowExpandBtn } =
|
||||
this.mindMap.opt
|
||||
let {
|
||||
alwaysShowExpandBtn
|
||||
} = this.mindMap.opt
|
||||
if (alwaysShowExpandBtn) {
|
||||
// 需要移除展开收缩按钮
|
||||
if (this._expandBtn && this.nodeData.children.length <= 0) {
|
||||
@@ -467,13 +502,7 @@ class Node {
|
||||
let t = this.group.transform()
|
||||
// 如果节点位置没有变化,则返回
|
||||
if (this.left === t.translateX && this.top === t.translateY) return
|
||||
if (!isLayout && enableNodeTransitionMove) {
|
||||
this.group
|
||||
.animate(nodeTransitionMoveDuration)
|
||||
.translate(this.left - t.translateX, this.top - t.translateY)
|
||||
} else {
|
||||
this.group.translate(this.left - t.translateX, this.top - t.translateY)
|
||||
}
|
||||
this.group.translate(this.left - t.translateX, this.top - t.translateY)
|
||||
}
|
||||
|
||||
// 重新渲染节点,即重新创建节点内容、计算节点大小、计算节点内容布局、更新展开收起按钮,概要及位置
|
||||
@@ -495,8 +524,6 @@ class Node {
|
||||
|
||||
// 递归渲染
|
||||
render(callback = () => {}) {
|
||||
let { enableNodeTransitionMove, nodeTransitionMoveDuration } =
|
||||
this.mindMap.opt
|
||||
// 节点
|
||||
// 重新渲染连线
|
||||
this.renderLine()
|
||||
@@ -505,6 +532,7 @@ class Node {
|
||||
isLayout = true
|
||||
// 创建组
|
||||
this.group = new G()
|
||||
this.group.addClass('smm-node')
|
||||
this.group.css({
|
||||
cursor: 'default'
|
||||
})
|
||||
@@ -518,6 +546,10 @@ class Node {
|
||||
this.needLayout = false
|
||||
this.layout()
|
||||
}
|
||||
if (this.needRerenderExpandBtnPlaceholderRect) {
|
||||
this.needRerenderExpandBtnPlaceholderRect = false
|
||||
this.renderExpandBtnPlaceholderRect()
|
||||
}
|
||||
this.update()
|
||||
}
|
||||
// 子节点
|
||||
@@ -540,20 +572,14 @@ class Node {
|
||||
})
|
||||
)
|
||||
} else {
|
||||
if (enableNodeTransitionMove && !isLayout) {
|
||||
setTimeout(() => {
|
||||
callback()
|
||||
}, nodeTransitionMoveDuration)
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
callback()
|
||||
}
|
||||
// 手动插入的节点立即获得焦点并且开启编辑模式
|
||||
if (this.nodeData.inserting) {
|
||||
delete this.nodeData.inserting
|
||||
this.active()
|
||||
setTimeout(() => {
|
||||
this.mindMap.emit('node_dblclick', this)
|
||||
this.mindMap.emit('node_dblclick', this, null, true)
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
@@ -758,7 +784,7 @@ class Node {
|
||||
|
||||
// 获取padding值
|
||||
getPaddingVale() {
|
||||
let { isActive }= this.nodeData.data
|
||||
let { isActive } = this.nodeData.data
|
||||
return {
|
||||
paddingX: this.getStyle('paddingX', true, isActive),
|
||||
paddingY: this.getStyle('paddingY', true, isActive)
|
||||
@@ -798,6 +824,11 @@ class Node {
|
||||
getData(key) {
|
||||
return key ? this.nodeData.data[key] || '' : this.nodeData.data
|
||||
}
|
||||
|
||||
// 是否存在自定义样式
|
||||
hasCustomStyle() {
|
||||
return this.style.hasCustomStyle()
|
||||
}
|
||||
}
|
||||
|
||||
export default Node
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { tagColorList } from '../../../constants/constant'
|
||||
import { tagColorList, nodeDataNoStylePropList } from '../../../constants/constant'
|
||||
const rootProp = ['paddingX', 'paddingY']
|
||||
const backgroundStyleProps = ['backgroundColor', 'backgroundImage', 'backgroundRepeat', 'backgroundPosition', 'backgroundSize']
|
||||
|
||||
@@ -209,6 +209,17 @@ class Style {
|
||||
node2.fill({ color: color })
|
||||
fillNode.fill({ color: fill })
|
||||
}
|
||||
|
||||
// 是否设置了自定义的样式
|
||||
hasCustomStyle() {
|
||||
let res = false
|
||||
Object.keys(this.ctx.nodeData.data).forEach((item) => {
|
||||
if (!nodeDataNoStylePropList.includes(item)) {
|
||||
res = true
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
Style.cacheStyle = null
|
||||
|
||||
@@ -4,8 +4,8 @@ function setData(data = {}) {
|
||||
}
|
||||
|
||||
// 设置文本
|
||||
function setText(text, richText) {
|
||||
this.mindMap.execCommand('SET_NODE_TEXT', this, text, richText)
|
||||
function setText(text, richText, resetRichText) {
|
||||
this.mindMap.execCommand('SET_NODE_TEXT', this, text, richText, resetRichText)
|
||||
}
|
||||
|
||||
// 设置图片
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { measureText, resizeImgSize, getTextFromHtml } from '../../../utils'
|
||||
import { measureText, resizeImgSize, removeHtmlStyle, addHtmlStyle, checkIsRichText } from '../../../utils'
|
||||
import { Image, SVG, A, G, Rect, Text, ForeignObject } from '@svgdotjs/svg.js'
|
||||
import iconsSvg from '../../../svg/icons'
|
||||
import { CONSTANTS } from '../../../constants/constant'
|
||||
import { CONSTANTS, commonCaches } from '../../../constants/constant'
|
||||
|
||||
// 创建图片节点
|
||||
function createImgNode() {
|
||||
@@ -17,6 +17,15 @@ function createImgNode() {
|
||||
node.on('dblclick', e => {
|
||||
this.mindMap.emit('node_img_dblclick', this, e)
|
||||
})
|
||||
node.on('mouseenter', e => {
|
||||
this.mindMap.emit('node_img_mouseenter', this, node, e)
|
||||
})
|
||||
node.on('mouseleave', e => {
|
||||
this.mindMap.emit('node_img_mouseleave', this, node, e)
|
||||
})
|
||||
node.on('mousemove', e => {
|
||||
this.mindMap.emit('node_img_mousemove', this, node, e)
|
||||
})
|
||||
return {
|
||||
node,
|
||||
width: imgSize[0],
|
||||
@@ -26,9 +35,12 @@ function createImgNode() {
|
||||
|
||||
// 获取图片显示宽高
|
||||
function getImgShowSize() {
|
||||
const { custom, width, height } = this.nodeData.data.imageSize
|
||||
// 如果是自定义了图片的宽高,那么不受最大宽高限制
|
||||
if (custom) return [width, height]
|
||||
return resizeImgSize(
|
||||
this.nodeData.data.imageSize.width,
|
||||
this.nodeData.data.imageSize.height,
|
||||
width,
|
||||
height,
|
||||
this.mindMap.themeConfig.imgMaxWidth,
|
||||
this.mindMap.themeConfig.imgMaxHeight
|
||||
)
|
||||
@@ -52,6 +64,9 @@ function createIconNode() {
|
||||
node = new Image().load(src)
|
||||
}
|
||||
node.size(iconSize, iconSize)
|
||||
node.on('click', e => {
|
||||
this.mindMap.emit('node_icon_click', this, item, e)
|
||||
})
|
||||
return {
|
||||
node,
|
||||
width: iconSize,
|
||||
@@ -64,10 +79,33 @@ function createIconNode() {
|
||||
function createRichTextNode() {
|
||||
let g = new G()
|
||||
// 重新设置富文本节点内容
|
||||
if (this.nodeData.data.resetRichText || [CONSTANTS.CHANGE_THEME].includes(this.mindMap.renderer.renderSource)) {
|
||||
let recoverText = false
|
||||
if (this.nodeData.data.resetRichText) {
|
||||
delete this.nodeData.data.resetRichText
|
||||
let text = getTextFromHtml(this.nodeData.data.text)
|
||||
this.nodeData.data.text = `<p><span style="${this.style.createStyleText()}">${text}</span></p>`
|
||||
recoverText = true
|
||||
}
|
||||
if ([CONSTANTS.CHANGE_THEME].includes(this.mindMap.renderer.renderSource)) {
|
||||
// 如果自定义过样式则不允许覆盖
|
||||
if (!this.hasCustomStyle()) {
|
||||
recoverText = true
|
||||
}
|
||||
}
|
||||
if (recoverText) {
|
||||
let text = this.nodeData.data.text
|
||||
// 判断节点内容是否是富文本
|
||||
let isRichText = checkIsRichText(text)
|
||||
// 样式字符串
|
||||
let style = this.style.createStyleText()
|
||||
if (isRichText) {
|
||||
// 如果是富文本那么线移除内联样式
|
||||
text = removeHtmlStyle(text)
|
||||
// 再添加新的内联样式
|
||||
text = addHtmlStyle(text, 'span', style)
|
||||
} else {
|
||||
// 非富文本
|
||||
text = `<p><span style="${style}">${text}</span></p>`
|
||||
}
|
||||
this.nodeData.data.text = text
|
||||
}
|
||||
let html = `<div>${this.nodeData.data.text}</div>`
|
||||
let div = document.createElement('div')
|
||||
@@ -79,7 +117,7 @@ function createRichTextNode() {
|
||||
el.style.maxWidth = this.mindMap.opt.textAutoWrapWidth + 'px'
|
||||
this.mindMap.el.appendChild(div)
|
||||
let { width, height } = el.getBoundingClientRect()
|
||||
width = Math.ceil(width)
|
||||
width = Math.ceil(width) + 1// 修复getBoundingClientRect方法对实际宽度是小数的元素获取到的值是整数,导致宽度不够文本发生换行的问题
|
||||
height = Math.ceil(height)
|
||||
g.attr('data-width', width)
|
||||
g.attr('data-height', height)
|
||||
@@ -270,6 +308,31 @@ function createNoteNode() {
|
||||
}
|
||||
}
|
||||
|
||||
// 测量自定义节点内容元素的宽高
|
||||
function measureCustomNodeContentSize (content) {
|
||||
if (!commonCaches.measureCustomNodeContentSizeEl) {
|
||||
commonCaches.measureCustomNodeContentSizeEl = document.createElement('div')
|
||||
commonCaches.measureCustomNodeContentSizeEl.style.cssText = `
|
||||
position: fixed;
|
||||
left: -99999px;
|
||||
top: -99999px;
|
||||
`
|
||||
this.mindMap.el.appendChild(commonCaches.measureCustomNodeContentSizeEl)
|
||||
}
|
||||
commonCaches.measureCustomNodeContentSizeEl.innerHTML = ''
|
||||
commonCaches.measureCustomNodeContentSizeEl.appendChild(content)
|
||||
let rect = commonCaches.measureCustomNodeContentSizeEl.getBoundingClientRect()
|
||||
return {
|
||||
width: rect.width,
|
||||
height: rect.height
|
||||
}
|
||||
}
|
||||
|
||||
// 是否使用的是自定义节点内容
|
||||
function isUseCustomNodeContent() {
|
||||
return !!this._customNodeContent
|
||||
}
|
||||
|
||||
export default {
|
||||
createImgNode,
|
||||
getImgShowSize,
|
||||
@@ -278,5 +341,7 @@ export default {
|
||||
createTextNode,
|
||||
createHyperlinkNode,
|
||||
createTagNode,
|
||||
createNoteNode
|
||||
createNoteNode,
|
||||
measureCustomNodeContentSize,
|
||||
isUseCustomNodeContent
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import Node from './Node'
|
||||
import { createUid } from '../../../utils/index'
|
||||
|
||||
// 检查是否存在概要
|
||||
function checkHasGeneralization () {
|
||||
@@ -18,7 +19,7 @@ function createGeneralizationNode () {
|
||||
data: {
|
||||
data: this.nodeData.data.generalization
|
||||
},
|
||||
uid: this.mindMap.uid++,
|
||||
uid: createUid(),
|
||||
renderer: this.renderer,
|
||||
mindMap: this.mindMap,
|
||||
draw: this.draw,
|
||||
@@ -35,15 +36,14 @@ function createGeneralizationNode () {
|
||||
|
||||
// 更新概要节点
|
||||
function updateGeneralization () {
|
||||
if (this.isGeneralization) return
|
||||
this.removeGeneralization()
|
||||
this.createGeneralizationNode()
|
||||
}
|
||||
|
||||
// 渲染概要节点
|
||||
function renderGeneralization () {
|
||||
if (this.isGeneralization) {
|
||||
return
|
||||
}
|
||||
if (this.isGeneralization) return
|
||||
if (!this.checkHasGeneralization()) {
|
||||
this.removeGeneralization()
|
||||
this._generalizationNodeWidth = 0
|
||||
@@ -66,6 +66,7 @@ function renderGeneralization () {
|
||||
|
||||
// 删除概要节点
|
||||
function removeGeneralization () {
|
||||
if (this.isGeneralization) return
|
||||
if (this._generalizationLine) {
|
||||
this._generalizationLine.remove()
|
||||
this._generalizationLine = null
|
||||
@@ -86,6 +87,7 @@ function removeGeneralization () {
|
||||
|
||||
// 隐藏概要节点
|
||||
function hideGeneralization () {
|
||||
if (this.isGeneralization) return
|
||||
if (this._generalizationLine) {
|
||||
this._generalizationLine.hide()
|
||||
}
|
||||
@@ -96,6 +98,7 @@ function hideGeneralization () {
|
||||
|
||||
// 显示概要节点
|
||||
function showGeneralization () {
|
||||
if (this.isGeneralization) return
|
||||
if (this._generalizationLine) {
|
||||
this._generalizationLine.show()
|
||||
}
|
||||
|
||||
@@ -60,29 +60,38 @@ class View {
|
||||
})
|
||||
// 放大缩小视图
|
||||
this.mindMap.event.on('mousewheel', (e, dir, event, isTouchPad) => {
|
||||
let {
|
||||
customHandleMousewheel,
|
||||
mousewheelAction,
|
||||
mouseScaleCenterUseMousePosition,
|
||||
mousewheelMoveStep,
|
||||
mousewheelZoomActionReverse
|
||||
} = this.mindMap.opt
|
||||
// 是否自定义鼠标滚轮事件
|
||||
if (
|
||||
this.mindMap.opt.customHandleMousewheel &&
|
||||
typeof this.mindMap.opt.customHandleMousewheel === 'function'
|
||||
customHandleMousewheel &&
|
||||
typeof customHandleMousewheel === 'function'
|
||||
) {
|
||||
return this.mindMap.opt.customHandleMousewheel(e)
|
||||
return customHandleMousewheel(e)
|
||||
}
|
||||
if (
|
||||
this.mindMap.opt.mousewheelAction === CONSTANTS.MOUSE_WHEEL_ACTION.ZOOM
|
||||
) {
|
||||
// 鼠标滚轮事件控制缩放
|
||||
if (mousewheelAction === CONSTANTS.MOUSE_WHEEL_ACTION.ZOOM) {
|
||||
let cx = mouseScaleCenterUseMousePosition ? e.clientX : undefined
|
||||
let cy = mouseScaleCenterUseMousePosition ? e.clientY : undefined
|
||||
switch (dir) {
|
||||
// 鼠标滚轮,向上和向左,都是缩小
|
||||
case CONSTANTS.DIR.UP:
|
||||
case CONSTANTS.DIR.LEFT:
|
||||
this.narrow()
|
||||
mousewheelZoomActionReverse ? this.enlarge(cx, cy, isTouchPad) : this.narrow(cx, cy, isTouchPad)
|
||||
break
|
||||
// 鼠标滚轮,向下和向右,都是放大
|
||||
case CONSTANTS.DIR.DOWN:
|
||||
case CONSTANTS.DIR.RIGHT:
|
||||
this.enlarge()
|
||||
mousewheelZoomActionReverse ? this.narrow(cx, cy, isTouchPad) : this.enlarge(cx, cy, isTouchPad)
|
||||
break
|
||||
}
|
||||
} else {
|
||||
let step = this.mindMap.opt.mousewheelMoveStep
|
||||
} else {// 鼠标滚轮事件控制画布移动
|
||||
let step = mousewheelMoveStep
|
||||
if (isTouchPad) {
|
||||
step = 5
|
||||
}
|
||||
@@ -170,8 +179,8 @@ class View {
|
||||
// 应用变换
|
||||
transform() {
|
||||
this.mindMap.draw.transform({
|
||||
origin: [0, 0],
|
||||
scale: this.scale,
|
||||
// origin: 'center center',
|
||||
translate: [this.x, this.y]
|
||||
})
|
||||
this.mindMap.emit('view_data_change', this.getTransformData())
|
||||
@@ -190,26 +199,45 @@ class View {
|
||||
}
|
||||
|
||||
// 缩小
|
||||
narrow() {
|
||||
if (this.scale - this.mindMap.opt.scaleRatio > 0.1) {
|
||||
this.scale -= this.mindMap.opt.scaleRatio
|
||||
} else {
|
||||
this.scale = 0.1
|
||||
}
|
||||
narrow(cx, cy, isTouchPad) {
|
||||
const scaleRatio = this.mindMap.opt.scaleRatio / (isTouchPad ? 5 : 1)
|
||||
const scale = Math.max(this.scale - scaleRatio, 0.1)
|
||||
this.scaleInCenter(scale, cx, cy)
|
||||
this.transform()
|
||||
this.mindMap.emit('scale', this.scale)
|
||||
}
|
||||
|
||||
// 放大
|
||||
enlarge() {
|
||||
this.scale += this.mindMap.opt.scaleRatio
|
||||
enlarge(cx, cy, isTouchPad) {
|
||||
const scaleRatio = this.mindMap.opt.scaleRatio / (isTouchPad ? 5 : 1)
|
||||
const scale = this.scale + scaleRatio
|
||||
this.scaleInCenter(scale, cx, cy)
|
||||
this.transform()
|
||||
this.mindMap.emit('scale', this.scale)
|
||||
}
|
||||
|
||||
// 设置缩放
|
||||
setScale(scale) {
|
||||
// 基于指定中心进行缩放,cx,cy 可不指定,此时会使用画布中心点
|
||||
scaleInCenter(scale, cx, cy) {
|
||||
if (cx === undefined || cy === undefined) {
|
||||
cx = this.mindMap.width / 2
|
||||
cy = this.mindMap.height / 2
|
||||
}
|
||||
const prevScale = this.scale
|
||||
const ratio = 1 - scale / prevScale
|
||||
const dx = (cx - this.x) * ratio
|
||||
const dy = (cy - this.y) * ratio
|
||||
this.x += dx
|
||||
this.y += dy
|
||||
this.scale = scale
|
||||
}
|
||||
|
||||
// 设置缩放
|
||||
setScale(scale, cx, cy) {
|
||||
if (cx !== undefined && cy !== undefined) {
|
||||
this.scaleInCenter(scale, cx, cy)
|
||||
} else {
|
||||
this.scale = scale
|
||||
}
|
||||
this.transform()
|
||||
this.mindMap.emit('scale', this.scale)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Node from '../core/render/node/Node'
|
||||
import { CONSTANTS, initRootNodePositionMap } from '../constants/constant'
|
||||
import Lru from '../utils/Lru'
|
||||
import { createUid } from '../utils/index'
|
||||
|
||||
// 布局基类
|
||||
class Base {
|
||||
@@ -48,6 +49,20 @@ class Base {
|
||||
return [CONSTANTS.CHANGE_THEME, CONSTANTS.TRANSFORM_TO_NORMAL_NODE].includes(this.renderer.renderSource)
|
||||
}
|
||||
|
||||
// 层级类型改变
|
||||
checkIsLayerTypeChange(oldIndex, newIndex) {
|
||||
if (oldIndex >= 2 && newIndex >= 2) return false
|
||||
if (oldIndex >= 2 && newIndex < 2) return true
|
||||
if (oldIndex < 2 && newIndex >= 2) return true
|
||||
}
|
||||
|
||||
// 检查是否是结构布局改变重新渲染展开收起按钮占位元素
|
||||
checkIsLayoutChangeRerenderExpandBtnPlaceholderRect(node) {
|
||||
if (this.renderer.renderSource === CONSTANTS.CHANGE_LAYOUT) {
|
||||
node.needRerenderExpandBtnPlaceholderRect = true
|
||||
}
|
||||
}
|
||||
|
||||
// 创建节点实例
|
||||
createNode(data, parent, isRoot, layerIndex) {
|
||||
// 创建节点
|
||||
@@ -55,11 +70,13 @@ class Base {
|
||||
// 数据上保存了节点引用,那么直接复用节点
|
||||
if (data && data._node && !this.renderer.reRender) {
|
||||
newNode = data._node
|
||||
let isLayerTypeChange = this.checkIsLayerTypeChange(newNode.layerIndex, layerIndex)
|
||||
newNode.reset()
|
||||
newNode.layerIndex = layerIndex
|
||||
this.cacheNode(data._node.uid, newNode)
|
||||
this.checkIsLayoutChangeRerenderExpandBtnPlaceholderRect(newNode)
|
||||
// 主题或主题配置改变了需要重新计算节点大小和布局
|
||||
if (this.checkIsNeedResizeSources()) {
|
||||
if (this.checkIsNeedResizeSources() || isLayerTypeChange) {
|
||||
newNode.getSize()
|
||||
newNode.needLayout = true
|
||||
}
|
||||
@@ -68,22 +85,24 @@ class Base {
|
||||
newNode = this.lru.get(data.data.uid)
|
||||
// 保存该节点上一次的数据
|
||||
let lastData = JSON.stringify(newNode.nodeData.data)
|
||||
let isLayerTypeChange = this.checkIsLayerTypeChange(newNode.layerIndex, layerIndex)
|
||||
newNode.reset()
|
||||
newNode.nodeData = newNode.handleData(data || {})
|
||||
newNode.layerIndex = layerIndex
|
||||
this.cacheNode(data.data.uid, newNode)
|
||||
this.checkIsLayoutChangeRerenderExpandBtnPlaceholderRect(newNode)
|
||||
data._node = newNode
|
||||
// 主题或主题配置改变了需要重新计算节点大小和布局
|
||||
let isResizeSource = this.checkIsNeedResizeSources()
|
||||
// 节点数据改变了需要重新计算节点大小和布局
|
||||
let isNodeDataChange = lastData !== JSON.stringify(data.data)
|
||||
if (isResizeSource || isNodeDataChange) {
|
||||
if (isResizeSource || isNodeDataChange || isLayerTypeChange) {
|
||||
newNode.getSize()
|
||||
newNode.needLayout = true
|
||||
}
|
||||
} else {
|
||||
// 创建新节点
|
||||
let uid = this.mindMap.uid++
|
||||
let uid = data.data.uid || createUid()
|
||||
newNode = new Node({
|
||||
data,
|
||||
uid,
|
||||
|
||||
@@ -349,6 +349,11 @@ class CatalogOrganization extends Base {
|
||||
gNode.left = right + generalizationNodeMargin
|
||||
gNode.top = top + (bottom - top - gNode.height) / 2
|
||||
}
|
||||
|
||||
// 渲染展开收起按钮的隐藏占位元素
|
||||
renderExpandBtnRect(rect, expandBtnSize, width, height, node) {
|
||||
rect.size(width, expandBtnSize).x(0).y(height)
|
||||
}
|
||||
}
|
||||
|
||||
export default CatalogOrganization
|
||||
|
||||
@@ -51,8 +51,8 @@ class Fishbone extends Base {
|
||||
// 节点生长方向
|
||||
newNode.dir =
|
||||
index % 2 === 0
|
||||
? CONSTANTS.TIMELINE_DIR.TOP
|
||||
: CONSTANTS.TIMELINE_DIR.BOTTOM
|
||||
? CONSTANTS.LAYOUT_GROW_DIR.TOP
|
||||
: CONSTANTS.LAYOUT_GROW_DIR.BOTTOM
|
||||
}
|
||||
// 计算二级节点的top值
|
||||
if (parent._node.isRoot) {
|
||||
@@ -222,7 +222,7 @@ class Fishbone extends Base {
|
||||
|
||||
// 检查节点是否是上方节点
|
||||
checkIsTop(node) {
|
||||
return node.dir === CONSTANTS.TIMELINE_DIR.TOP
|
||||
return node.dir === CONSTANTS.LAYOUT_GROW_DIR.TOP
|
||||
}
|
||||
|
||||
// 绘制连线,连接该节点到其子节点
|
||||
@@ -239,7 +239,7 @@ class Fishbone extends Base {
|
||||
// 当前节点是根节点
|
||||
// 根节点的子节点是和根节点同一水平线排列
|
||||
let maxx = -Infinity
|
||||
node.children.forEach((item) => {
|
||||
node.children.forEach(item => {
|
||||
if (item.left > maxx) {
|
||||
maxx = item.left
|
||||
}
|
||||
@@ -250,15 +250,15 @@ class Fishbone extends Base {
|
||||
let line = this.draw.path()
|
||||
if (this.checkIsTop(item)) {
|
||||
line.plot(
|
||||
`M ${nodeLineX - offsetX},${item.top + item.height + offset} L ${item.left},${
|
||||
item.top + item.height
|
||||
}`
|
||||
`M ${nodeLineX - offsetX},${item.top + item.height + offset} L ${
|
||||
item.left
|
||||
},${item.top + item.height}`
|
||||
)
|
||||
} else {
|
||||
line.plot(
|
||||
`M ${nodeLineX - offsetX},${
|
||||
item.top - offset
|
||||
} L ${nodeLineX},${item.top}`
|
||||
`M ${nodeLineX - offsetX},${item.top - offset} L ${nodeLineX},${
|
||||
item.top
|
||||
}`
|
||||
)
|
||||
}
|
||||
node.style.line(line)
|
||||
@@ -373,6 +373,27 @@ class Fishbone extends Base {
|
||||
gNode.left = right + generalizationNodeMargin
|
||||
gNode.top = top + (bottom - top - gNode.height) / 2
|
||||
}
|
||||
|
||||
// 渲染展开收起按钮的隐藏占位元素
|
||||
renderExpandBtnRect(rect, expandBtnSize, width, height, node) {
|
||||
let dir = ''
|
||||
if (node.dir === CONSTANTS.LAYOUT_GROW_DIR.TOP) {
|
||||
dir =
|
||||
node.layerIndex === 1
|
||||
? CONSTANTS.LAYOUT_GROW_DIR.TOP
|
||||
: CONSTANTS.LAYOUT_GROW_DIR.BOTTOM
|
||||
} else {
|
||||
dir =
|
||||
node.layerIndex === 1
|
||||
? CONSTANTS.LAYOUT_GROW_DIR.BOTTOM
|
||||
: CONSTANTS.LAYOUT_GROW_DIR.TOP
|
||||
}
|
||||
if (dir === CONSTANTS.LAYOUT_GROW_DIR.TOP) {
|
||||
rect.size(width, expandBtnSize).x(0).y(-expandBtnSize)
|
||||
} else {
|
||||
rect.size(width, expandBtnSize).x(0).y(height)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Fishbone
|
||||
|
||||
@@ -52,8 +52,8 @@ class Fishbone extends Base {
|
||||
// 节点生长方向
|
||||
newNode.dir =
|
||||
index % 2 === 0
|
||||
? CONSTANTS.TIMELINE_DIR.TOP
|
||||
: CONSTANTS.TIMELINE_DIR.BOTTOM
|
||||
? CONSTANTS.LAYOUT_GROW_DIR.TOP
|
||||
: CONSTANTS.LAYOUT_GROW_DIR.BOTTOM
|
||||
}
|
||||
// 计算二级节点的top值
|
||||
if (parent._node.isRoot) {
|
||||
|
||||
@@ -52,8 +52,8 @@ class Fishbone extends Base {
|
||||
// 节点生长方向
|
||||
newNode.dir =
|
||||
index % 2 === 0
|
||||
? CONSTANTS.TIMELINE_DIR.TOP
|
||||
: CONSTANTS.TIMELINE_DIR.BOTTOM
|
||||
? CONSTANTS.LAYOUT_GROW_DIR.TOP
|
||||
: CONSTANTS.LAYOUT_GROW_DIR.BOTTOM
|
||||
}
|
||||
// 计算二级节点的top值
|
||||
if (parent._node.isRoot) {
|
||||
@@ -281,7 +281,7 @@ class Fishbone extends Base {
|
||||
if (
|
||||
node.parent &&
|
||||
node.parent.isRoot &&
|
||||
node.dir === CONSTANTS.TIMELINE_DIR.TOP
|
||||
node.dir === CONSTANTS.LAYOUT_GROW_DIR.TOP
|
||||
) {
|
||||
line.plot(
|
||||
`M ${x},${top} L ${x + lineLength},${
|
||||
|
||||
@@ -172,9 +172,7 @@ class LogicalStructure extends Base {
|
||||
let x2 = item.left
|
||||
let y2 = item.top + item.height / 2
|
||||
// 节点使用横线风格,需要额外渲染横线
|
||||
let nodeUseLineStyleOffset = nodeUseLineStyle
|
||||
? item.width
|
||||
: 0
|
||||
let nodeUseLineStyleOffset = nodeUseLineStyle ? item.width : 0
|
||||
y1 = nodeUseLineStyle && !node.isRoot ? y1 + height / 2 : y1
|
||||
y2 = nodeUseLineStyle ? y2 + item.height / 2 : y2
|
||||
let path = `M ${x1},${y1} L ${x1 + s1},${y1} L ${x1 + s1},${y2} L ${
|
||||
@@ -260,10 +258,7 @@ class LogicalStructure extends Base {
|
||||
if (_x === translateX && _y === translateY) {
|
||||
return
|
||||
}
|
||||
btn.translate(
|
||||
_x - translateX,
|
||||
_y - translateY
|
||||
)
|
||||
btn.translate(_x - translateX, _y - translateY)
|
||||
}
|
||||
|
||||
// 创建概要节点
|
||||
@@ -286,6 +281,11 @@ class LogicalStructure extends Base {
|
||||
gNode.left = right + generalizationNodeMargin
|
||||
gNode.top = top + (bottom - top - gNode.height) / 2
|
||||
}
|
||||
|
||||
// 渲染展开收起按钮的隐藏占位元素
|
||||
renderExpandBtnRect(rect, expandBtnSize, width, height, node) {
|
||||
rect.size(expandBtnSize, height).x(width).y(0)
|
||||
}
|
||||
}
|
||||
|
||||
export default LogicalStructure
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Base from './Base'
|
||||
import { walk, asyncRun } from '../utils'
|
||||
import { CONSTANTS } from '../constants/constant'
|
||||
|
||||
// 思维导图
|
||||
class MindMap extends Base {
|
||||
@@ -45,11 +46,14 @@ class MindMap extends Base {
|
||||
newNode.dir = parent._node.dir
|
||||
} else {
|
||||
// 节点生长方向
|
||||
newNode.dir = index % 2 === 0 ? 'right' : 'left'
|
||||
newNode.dir =
|
||||
index % 2 === 0
|
||||
? CONSTANTS.LAYOUT_GROW_DIR.RIGHT
|
||||
: CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
}
|
||||
// 根据生长方向定位到父节点的左侧或右侧
|
||||
newNode.left =
|
||||
newNode.dir === 'right'
|
||||
newNode.dir === CONSTANTS.LAYOUT_GROW_DIR.RIGHT
|
||||
? parent._node.left +
|
||||
parent._node.width +
|
||||
this.getMarginX(layerIndex)
|
||||
@@ -72,7 +76,7 @@ class MindMap extends Base {
|
||||
let leftChildrenAreaHeight = 0
|
||||
let rightChildrenAreaHeight = 0
|
||||
cur._node.children.forEach(item => {
|
||||
if (item.dir === 'left') {
|
||||
if (item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT) {
|
||||
leftLen++
|
||||
leftChildrenAreaHeight += item.height
|
||||
} else {
|
||||
@@ -109,7 +113,7 @@ class MindMap extends Base {
|
||||
let leftTotalTop = baseTop - node.leftChildrenAreaHeight / 2
|
||||
let rightTotalTop = baseTop - node.rightChildrenAreaHeight / 2
|
||||
node.children.forEach(cur => {
|
||||
if (cur.dir === 'left') {
|
||||
if (cur.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT) {
|
||||
cur.top = leftTotalTop
|
||||
leftTotalTop += cur.height + marginY
|
||||
} else {
|
||||
@@ -162,7 +166,10 @@ class MindMap extends Base {
|
||||
return
|
||||
}
|
||||
let _offset = 0
|
||||
let addHeight = item.dir === 'left' ? leftAddHeight : rightAddHeight
|
||||
let addHeight =
|
||||
item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
? leftAddHeight
|
||||
: rightAddHeight
|
||||
// 上面的节点往上移
|
||||
if (_index < index) {
|
||||
_offset = -addHeight
|
||||
@@ -208,10 +215,8 @@ class MindMap extends Base {
|
||||
let x1 = 0
|
||||
let _s = 0
|
||||
// 节点使用横线风格,需要额外渲染横线
|
||||
let nodeUseLineStyleOffset = nodeUseLineStyle
|
||||
? item.width
|
||||
: 0
|
||||
if (item.dir === 'left') {
|
||||
let nodeUseLineStyleOffset = nodeUseLineStyle ? item.width : 0
|
||||
if (item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT) {
|
||||
_s = -s1
|
||||
x1 = node.layerIndex === 0 ? left : left - expandBtnSize
|
||||
nodeUseLineStyleOffset = -nodeUseLineStyleOffset
|
||||
@@ -220,7 +225,10 @@ class MindMap extends Base {
|
||||
x1 = node.layerIndex === 0 ? left + width : left + width + expandBtnSize
|
||||
}
|
||||
let y1 = top + height / 2
|
||||
let x2 = item.dir === 'left' ? item.left + item.width : item.left
|
||||
let x2 =
|
||||
item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
? item.left + item.width
|
||||
: item.left
|
||||
let y2 = item.top + item.height / 2
|
||||
y1 = nodeUseLineStyle && !node.isRoot ? y1 + height / 2 : y1
|
||||
y2 = nodeUseLineStyle ? y2 + item.height / 2 : y2
|
||||
@@ -246,18 +254,21 @@ class MindMap extends Base {
|
||||
let x1 =
|
||||
node.layerIndex === 0
|
||||
? left + width / 2
|
||||
: item.dir === 'left'
|
||||
: item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
? left - expandBtnSize
|
||||
: left + width + expandBtnSize
|
||||
let y1 = top + height / 2
|
||||
let x2 = item.dir === 'left' ? item.left + item.width : item.left
|
||||
let x2 =
|
||||
item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
? item.left + item.width
|
||||
: item.left
|
||||
let y2 = item.top + item.height / 2
|
||||
y1 = nodeUseLineStyle && !node.isRoot ? y1 + height / 2 : y1
|
||||
y2 = nodeUseLineStyle ? y2 + item.height / 2 : y2
|
||||
// 节点使用横线风格,需要额外渲染横线
|
||||
let nodeUseLineStylePath = ''
|
||||
if (nodeUseLineStyle) {
|
||||
if (item.dir === 'left') {
|
||||
if (item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT) {
|
||||
nodeUseLineStylePath = ` L ${item.left},${y2}`
|
||||
} else {
|
||||
nodeUseLineStylePath = ` L ${item.left + item.width},${y2}`
|
||||
@@ -283,11 +294,14 @@ class MindMap extends Base {
|
||||
let x1 =
|
||||
node.layerIndex === 0
|
||||
? left + width / 2
|
||||
: item.dir === 'left'
|
||||
: item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
? left - expandBtnSize
|
||||
: left + width + expandBtnSize
|
||||
let y1 = top + height / 2
|
||||
let x2 = item.dir === 'left' ? item.left + item.width : item.left
|
||||
let x2 =
|
||||
item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
? item.left + item.width
|
||||
: item.left
|
||||
let y2 = item.top + item.height / 2
|
||||
let path = ''
|
||||
y1 = nodeUseLineStyle && !node.isRoot ? y1 + height / 2 : y1
|
||||
@@ -295,7 +309,7 @@ class MindMap extends Base {
|
||||
// 节点使用横线风格,需要额外渲染横线
|
||||
let nodeUseLineStylePath = ''
|
||||
if (this.mindMap.themeConfig.nodeUseLineStyle) {
|
||||
if (item.dir === 'left') {
|
||||
if (item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT) {
|
||||
nodeUseLineStylePath = ` L ${item.left},${y2}`
|
||||
} else {
|
||||
nodeUseLineStylePath = ` L ${item.left + item.width},${y2}`
|
||||
@@ -320,7 +334,8 @@ class MindMap extends Base {
|
||||
? height / 2
|
||||
: 0
|
||||
// 位置没有变化则返回
|
||||
let _x = (node.dir === 'left' ? 0 - expandBtnSize : width)
|
||||
let _x =
|
||||
node.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT ? 0 - expandBtnSize : width
|
||||
let _y = height / 2 + nodeUseLineStyleOffset
|
||||
if (_x === translateX && _y === translateY) {
|
||||
return
|
||||
@@ -332,7 +347,7 @@ class MindMap extends Base {
|
||||
|
||||
// 创建概要节点
|
||||
renderGeneralization(node, gLine, gNode) {
|
||||
let isLeft = node.dir === 'left'
|
||||
let isLeft = node.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
let {
|
||||
top,
|
||||
bottom,
|
||||
@@ -358,6 +373,15 @@ class MindMap extends Base {
|
||||
(isLeft ? gNode.width : 0)
|
||||
gNode.top = top + (bottom - top - gNode.height) / 2
|
||||
}
|
||||
|
||||
// 渲染展开收起按钮的隐藏占位元素
|
||||
renderExpandBtnRect(rect, expandBtnSize, width, height, node) {
|
||||
if (node.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT) {
|
||||
rect.size(expandBtnSize, height).x(-expandBtnSize).y(0)
|
||||
} else {
|
||||
rect.size(expandBtnSize, height).x(width).y(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default MindMap
|
||||
|
||||
@@ -255,6 +255,11 @@ class OrganizationStructure extends Base {
|
||||
gNode.top = bottom + generalizationNodeMargin
|
||||
gNode.left = left + (right - left - gNode.width) / 2
|
||||
}
|
||||
|
||||
// 渲染展开收起按钮的隐藏占位元素
|
||||
renderExpandBtnRect(rect, expandBtnSize, width, height, node) {
|
||||
rect.size(width, expandBtnSize).x(0).y(height)
|
||||
}
|
||||
}
|
||||
|
||||
export default OrganizationStructure
|
||||
|
||||
@@ -50,8 +50,8 @@ class Timeline extends Base {
|
||||
// 节点生长方向
|
||||
newNode.dir =
|
||||
index % 2 === 0
|
||||
? CONSTANTS.TIMELINE_DIR.BOTTOM
|
||||
: CONSTANTS.TIMELINE_DIR.TOP
|
||||
? CONSTANTS.LAYOUT_GROW_DIR.BOTTOM
|
||||
: CONSTANTS.LAYOUT_GROW_DIR.TOP
|
||||
}
|
||||
} else {
|
||||
newNode.dir = ''
|
||||
@@ -151,7 +151,7 @@ class Timeline extends Base {
|
||||
if (
|
||||
parent &&
|
||||
parent.isRoot &&
|
||||
node.dir === CONSTANTS.TIMELINE_DIR.TOP
|
||||
node.dir === CONSTANTS.LAYOUT_GROW_DIR.TOP
|
||||
) {
|
||||
// 遍历二级节点的子节点
|
||||
node.children.forEach(item => {
|
||||
@@ -280,7 +280,7 @@ class Timeline extends Base {
|
||||
if (
|
||||
node.parent &&
|
||||
node.parent.isRoot &&
|
||||
node.dir === CONSTANTS.TIMELINE_DIR.TOP
|
||||
node.dir === CONSTANTS.LAYOUT_GROW_DIR.TOP
|
||||
) {
|
||||
line.plot(`M ${x},${top} L ${x},${miny}`)
|
||||
} else {
|
||||
@@ -301,7 +301,7 @@ class Timeline extends Base {
|
||||
if (
|
||||
node.parent &&
|
||||
node.parent.isRoot &&
|
||||
node.dir === CONSTANTS.TIMELINE_DIR.TOP
|
||||
node.dir === CONSTANTS.LAYOUT_GROW_DIR.TOP
|
||||
) {
|
||||
btn.translate(
|
||||
width * 0.3 - expandBtnSize / 2 - translateX,
|
||||
@@ -336,6 +336,28 @@ class Timeline extends Base {
|
||||
gNode.left = right + generalizationNodeMargin
|
||||
gNode.top = top + (bottom - top - gNode.height) / 2
|
||||
}
|
||||
|
||||
// 渲染展开收起按钮的隐藏占位元素
|
||||
renderExpandBtnRect(rect, expandBtnSize, width, height, node) {
|
||||
if (this.layout === CONSTANTS.LAYOUT.TIMELINE) {
|
||||
rect.size(width, expandBtnSize).x(0).y(height)
|
||||
} else {
|
||||
let dir = ''
|
||||
if (node.dir === CONSTANTS.LAYOUT_GROW_DIR.TOP) {
|
||||
dir =
|
||||
node.layerIndex === 1
|
||||
? CONSTANTS.LAYOUT_GROW_DIR.TOP
|
||||
: CONSTANTS.LAYOUT_GROW_DIR.BOTTOM
|
||||
} else {
|
||||
dir = CONSTANTS.LAYOUT_GROW_DIR.BOTTOM
|
||||
}
|
||||
if (dir === CONSTANTS.LAYOUT_GROW_DIR.TOP) {
|
||||
rect.size(width, expandBtnSize).x(0).y(-expandBtnSize)
|
||||
} else {
|
||||
rect.size(width, expandBtnSize).x(0).y(height)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Timeline
|
||||
|
||||
431
simple-mind-map/src/layouts/VerticalTimeline.js
Normal file
@@ -0,0 +1,431 @@
|
||||
import Base from './Base'
|
||||
import { walk, asyncRun } from '../utils'
|
||||
import { CONSTANTS } from '../constants/constant'
|
||||
|
||||
// 竖向时间轴
|
||||
class VerticalTimeline extends Base {
|
||||
// 构造函数
|
||||
constructor(opt = {}, layout) {
|
||||
super(opt)
|
||||
this.layout = layout
|
||||
}
|
||||
|
||||
// 布局
|
||||
doLayout(callback) {
|
||||
let task = [
|
||||
() => {
|
||||
this.computedBaseValue()
|
||||
},
|
||||
() => {
|
||||
this.computedTopValue()
|
||||
},
|
||||
() => {
|
||||
this.adjustLeftTopValue()
|
||||
},
|
||||
() => {
|
||||
callback(this.root)
|
||||
}
|
||||
]
|
||||
asyncRun(task)
|
||||
}
|
||||
|
||||
// 遍历数据创建节点、计算根节点的位置,计算根节点的子节点的top值
|
||||
computedBaseValue() {
|
||||
walk(
|
||||
this.renderer.renderTree,
|
||||
null,
|
||||
(cur, parent, isRoot, layerIndex, index) => {
|
||||
let newNode = this.createNode(cur, parent, isRoot, layerIndex)
|
||||
// 根节点定位在画布中心位置
|
||||
if (isRoot) {
|
||||
this.setNodeCenter(newNode)
|
||||
} else {
|
||||
// 非根节点
|
||||
// 节点生长方向
|
||||
// 三级及以下节点以上级为准
|
||||
if (parent._node.dir) {
|
||||
newNode.dir = parent._node.dir
|
||||
} else {
|
||||
newNode.dir =
|
||||
index % 2 === 0
|
||||
? CONSTANTS.LAYOUT_GROW_DIR.RIGHT
|
||||
: CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
}
|
||||
// 定位二级节点的left
|
||||
if (parent._node.isRoot) {
|
||||
newNode.left =
|
||||
parent._node.left +
|
||||
(cur._node.width > parent._node.width
|
||||
? -(cur._node.width - parent._node.width) / 2
|
||||
: (parent._node.width - cur._node.width) / 2)
|
||||
} else {
|
||||
newNode.left =
|
||||
newNode.dir === CONSTANTS.LAYOUT_GROW_DIR.RIGHT
|
||||
? parent._node.left +
|
||||
parent._node.width +
|
||||
this.getMarginX(layerIndex)
|
||||
: parent._node.left -
|
||||
this.getMarginX(layerIndex) -
|
||||
newNode.width
|
||||
}
|
||||
}
|
||||
if (!cur.data.expand) {
|
||||
return true
|
||||
}
|
||||
},
|
||||
(cur, parent, isRoot, layerIndex) => {
|
||||
// 返回时计算节点的areaHeight,也就是子节点所占的高度之和,包括外边距
|
||||
if (isRoot) {
|
||||
return
|
||||
}
|
||||
let len = cur.data.expand === false ? 0 : cur._node.children.length
|
||||
cur._node.childrenAreaHeight = len
|
||||
? cur._node.children.reduce((h, item) => {
|
||||
return h + item.height
|
||||
}, 0) +
|
||||
(len + 1) * this.getMarginY(layerIndex + 1)
|
||||
: 0
|
||||
},
|
||||
true,
|
||||
0
|
||||
)
|
||||
}
|
||||
|
||||
// 遍历节点树计算节点的top
|
||||
computedTopValue() {
|
||||
walk(
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex, index) => {
|
||||
if (
|
||||
node.nodeData.data.expand &&
|
||||
node.children &&
|
||||
node.children.length
|
||||
) {
|
||||
let marginY = this.getMarginY(layerIndex + 1)
|
||||
// 定位二级节点的top
|
||||
if (isRoot) {
|
||||
let top = node.top + node.height
|
||||
let totalTop = top + marginY
|
||||
node.children.forEach(cur => {
|
||||
cur.top = totalTop
|
||||
totalTop += cur.height + marginY
|
||||
})
|
||||
} else {
|
||||
// 定位三级及以下节点的top
|
||||
let marginY = this.getMarginY(layerIndex + 1)
|
||||
let baseTop = node.top + node.height / 2 + marginY
|
||||
// 第一个子节点的top值 = 该节点中心的top值 - 子节点的高度之和的一半
|
||||
let totalTop = baseTop - node.childrenAreaHeight / 2
|
||||
node.children.forEach(cur => {
|
||||
cur.top = totalTop
|
||||
totalTop += cur.height + marginY
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
null,
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
// 调整节点left、top
|
||||
adjustLeftTopValue() {
|
||||
walk(
|
||||
this.root,
|
||||
null,
|
||||
(node, parent, isRoot, layerIndex) => {
|
||||
if (!node.nodeData.data.expand) {
|
||||
return
|
||||
}
|
||||
if (isRoot) return
|
||||
// 判断子节点所占的高度之和是否大于该节点自身,大于则需要调整位置
|
||||
let base = this.getMarginY(layerIndex + 1) * 2 + node.height
|
||||
let difference = node.childrenAreaHeight - base
|
||||
if (difference > 0) {
|
||||
this.updateBrothers(node, difference / 2)
|
||||
}
|
||||
},
|
||||
null,
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
// 更新兄弟节点的top
|
||||
updateBrothers(node, addHeight) {
|
||||
if (node.parent) {
|
||||
let childrenList = node.parent.children
|
||||
let index = childrenList.findIndex(item => {
|
||||
return item === node
|
||||
})
|
||||
childrenList.forEach((item, _index) => {
|
||||
// 自定义节点位置
|
||||
if (item.hasCustomPosition()) return
|
||||
// 三级或三级以下节点自身位置不需要动
|
||||
if (!node.parent.isRoot && item === node) return
|
||||
let _offset = 0
|
||||
// 二级节点上面的兄弟节点不需要移动,自身需要往下移动
|
||||
if (node.parent.isRoot) {
|
||||
// 上面的节点不用移
|
||||
if (_index < index) {
|
||||
_offset = 0
|
||||
} else if (_index > index) {
|
||||
// 下面的节点往下移
|
||||
_offset = addHeight * 2
|
||||
} else {
|
||||
// 自身也要移动
|
||||
_offset = addHeight
|
||||
}
|
||||
} else {
|
||||
// 三级或三级以下节点两侧的兄弟节点向两侧移动
|
||||
// 上面的节点往上移
|
||||
if (_index < index) {
|
||||
_offset = -addHeight
|
||||
} else if (_index > index) {
|
||||
// 下面的节点往下移
|
||||
_offset = addHeight
|
||||
}
|
||||
}
|
||||
item.top += _offset
|
||||
// 同步更新子节点的位置
|
||||
if (item.children && item.children.length) {
|
||||
this.updateChildren(item.children, 'top', _offset)
|
||||
}
|
||||
})
|
||||
// 更新父节点的位置
|
||||
this.updateBrothers(node.parent, addHeight)
|
||||
}
|
||||
}
|
||||
|
||||
// 调整兄弟节点的top
|
||||
updateBrothersTop(node, addHeight) {
|
||||
if (node.parent && !node.parent.isRoot) {
|
||||
let childrenList = node.parent.children
|
||||
let index = childrenList.findIndex(item => {
|
||||
return item === node
|
||||
})
|
||||
childrenList.forEach((item, _index) => {
|
||||
if (item.hasCustomPosition()) {
|
||||
// 适配自定义位置
|
||||
return
|
||||
}
|
||||
let _offset = 0
|
||||
// 下面的节点往下移
|
||||
if (_index > index) {
|
||||
_offset = addHeight
|
||||
}
|
||||
item.top += _offset
|
||||
// 同步更新子节点的位置
|
||||
if (item.children && item.children.length) {
|
||||
this.updateChildren(item.children, 'top', _offset)
|
||||
}
|
||||
})
|
||||
// 更新父节点的位置
|
||||
this.updateBrothersTop(node.parent, addHeight)
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制连线,连接该节点到其子节点
|
||||
renderLine(node, lines, style, lineStyle) {
|
||||
if (lineStyle === 'curve') {
|
||||
this.renderLineCurve(node, lines, style)
|
||||
} else if (lineStyle === 'direct') {
|
||||
this.renderLineDirect(node, lines, style)
|
||||
} else {
|
||||
this.renderLineStraight(node, lines, style)
|
||||
}
|
||||
}
|
||||
|
||||
// 直线连接
|
||||
renderLineStraight(node, lines, style) {
|
||||
if (node.children.length <= 0) {
|
||||
return []
|
||||
}
|
||||
let { expandBtnSize } = node
|
||||
if (!this.mindMap.opt.alwaysShowExpandBtn) {
|
||||
expandBtnSize = 0
|
||||
}
|
||||
if (node.isRoot) {
|
||||
// 当前节点是根节点
|
||||
let prevBother = node
|
||||
// 根节点的子节点是和根节点同一水平线排列
|
||||
node.children.forEach((item, index) => {
|
||||
let y1 = prevBother.top + prevBother.height
|
||||
let y2 = item.top
|
||||
let x = node.left + node.width / 2
|
||||
let path = `M ${x},${y1} L ${x},${y2}`
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
prevBother = item
|
||||
})
|
||||
} else {
|
||||
// 当前节点为非根节点
|
||||
if (node.dir === CONSTANTS.LAYOUT_GROW_DIR.RIGHT) {
|
||||
let nodeRight = node.left + node.width
|
||||
let nodeYCenter = node.top + node.height / 2
|
||||
let marginX = this.getMarginX(node.layerIndex + 1)
|
||||
let offset = (marginX - expandBtnSize) * 0.6
|
||||
node.children.forEach((item, index) => {
|
||||
let itemLeft = item.left
|
||||
let itemYCenter = item.top + item.height / 2
|
||||
let path = `
|
||||
M ${nodeRight},${nodeYCenter}
|
||||
L ${nodeRight + offset},${nodeYCenter}
|
||||
L ${nodeRight + offset},${itemYCenter}
|
||||
L ${itemLeft},${itemYCenter}`
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
})
|
||||
} else {
|
||||
let nodeLeft = node.left
|
||||
let nodeYCenter = node.top + node.height / 2
|
||||
let marginX = this.getMarginX(node.layerIndex + 1)
|
||||
let offset = (marginX - expandBtnSize) * 0.6
|
||||
node.children.forEach((item, index) => {
|
||||
let itemRight = item.left + item.width
|
||||
let itemYCenter = item.top + item.height / 2
|
||||
let path = `
|
||||
M ${nodeLeft},${nodeYCenter}
|
||||
L ${nodeLeft - offset},${nodeYCenter}
|
||||
L ${nodeLeft - offset},${itemYCenter}
|
||||
L ${itemRight},${itemYCenter}`
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 直连
|
||||
renderLineDirect(node, lines, style) {
|
||||
if (node.children.length <= 0) {
|
||||
return []
|
||||
}
|
||||
let { left, top, width, height, expandBtnSize } = node
|
||||
if (!this.mindMap.opt.alwaysShowExpandBtn) {
|
||||
expandBtnSize = 0
|
||||
}
|
||||
node.children.forEach((item, index) => {
|
||||
if (node.isRoot) {
|
||||
let prevBother = node
|
||||
// 根节点的子节点是和根节点同一水平线排列
|
||||
node.children.forEach((item, index) => {
|
||||
let y1 = prevBother.top + prevBother.height
|
||||
let y2 = item.top
|
||||
let x = node.left + node.width / 2
|
||||
let path = `M ${x},${y1} L ${x},${y2}`
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
prevBother = item
|
||||
})
|
||||
} else {
|
||||
let x1 =
|
||||
item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
? left - expandBtnSize
|
||||
: left + width + expandBtnSize
|
||||
let y1 = top + height / 2
|
||||
let x2 =
|
||||
item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
? item.left + item.width
|
||||
: item.left
|
||||
let y2 = item.top + item.height / 2
|
||||
let path = `M ${x1},${y1} L ${x2},${y2}`
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 曲线风格连线
|
||||
renderLineCurve(node, lines, style) {
|
||||
if (node.children.length <= 0) {
|
||||
return []
|
||||
}
|
||||
let { left, top, width, height, expandBtnSize } = node
|
||||
if (!this.mindMap.opt.alwaysShowExpandBtn) {
|
||||
expandBtnSize = 0
|
||||
}
|
||||
node.children.forEach((item, index) => {
|
||||
if (node.isRoot) {
|
||||
let prevBother = node
|
||||
// 根节点的子节点是和根节点同一水平线排列
|
||||
node.children.forEach((item, index) => {
|
||||
let y1 = prevBother.top + prevBother.height
|
||||
let y2 = item.top
|
||||
let x = node.left + node.width / 2
|
||||
let path = `M ${x},${y1} L ${x},${y2}`
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
prevBother = item
|
||||
})
|
||||
} else {
|
||||
let x1 =
|
||||
item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
? left - expandBtnSize
|
||||
: left + width + expandBtnSize
|
||||
let y1 = top + height / 2
|
||||
let x2 =
|
||||
item.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
? item.left + item.width
|
||||
: item.left
|
||||
let y2 = item.top + item.height / 2
|
||||
let path = this.cubicBezierPath(x1, y1, x2, y2)
|
||||
lines[index].plot(path)
|
||||
style && style(lines[index], item)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 渲染按钮
|
||||
renderExpandBtn(node, btn) {
|
||||
let { width, height, expandBtnSize, isRoot } = node
|
||||
if (!isRoot) {
|
||||
let { translateX, translateY } = btn.transform()
|
||||
if (node.dir === CONSTANTS.LAYOUT_GROW_DIR.RIGHT) {
|
||||
btn.translate(width - translateX, height / 2 - translateY)
|
||||
} else {
|
||||
btn.translate(-expandBtnSize - translateX, height / 2 - translateY)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建概要节点
|
||||
renderGeneralization(node, gLine, gNode) {
|
||||
let isLeft = node.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT
|
||||
let {
|
||||
top,
|
||||
bottom,
|
||||
left,
|
||||
right,
|
||||
generalizationLineMargin,
|
||||
generalizationNodeMargin
|
||||
} = this.getNodeBoundaries(node, 'h', isLeft)
|
||||
let x = isLeft
|
||||
? left - generalizationLineMargin
|
||||
: right + generalizationLineMargin
|
||||
let x1 = x
|
||||
let y1 = top
|
||||
let x2 = x
|
||||
let y2 = bottom
|
||||
let cx = x1 + (isLeft ? -20 : 20)
|
||||
let cy = y1 + (y2 - y1) / 2
|
||||
let path = `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
|
||||
gLine.plot(path)
|
||||
gNode.left =
|
||||
x +
|
||||
(isLeft ? -generalizationNodeMargin : generalizationNodeMargin) -
|
||||
(isLeft ? gNode.width : 0)
|
||||
gNode.top = top + (bottom - top - gNode.height) / 2
|
||||
}
|
||||
|
||||
// 渲染展开收起按钮的隐藏占位元素
|
||||
renderExpandBtnRect(rect, expandBtnSize, width, height, node) {
|
||||
if (node.dir === CONSTANTS.LAYOUT_GROW_DIR.LEFT) {
|
||||
rect.size(expandBtnSize, height).x(-expandBtnSize).y(0)
|
||||
} else {
|
||||
rect.size(expandBtnSize, height).x(width).y(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default VerticalTimeline
|
||||
@@ -1,5 +1,11 @@
|
||||
import JSZip from 'jszip'
|
||||
import xmlConvert from 'xml-js'
|
||||
import {
|
||||
getTextFromHtml,
|
||||
imgToDataUrl,
|
||||
parseDataUrl,
|
||||
getImageSize
|
||||
} from '../utils/index'
|
||||
|
||||
// 解析.xmind文件
|
||||
const parseXmindFile = file => {
|
||||
@@ -10,7 +16,7 @@ const parseXmindFile = file => {
|
||||
let content = ''
|
||||
if (zip.files['content.json']) {
|
||||
let json = await zip.files['content.json'].async('string')
|
||||
content = transformXmind(json)
|
||||
content = await transformXmind(json, zip.files)
|
||||
} else if (zip.files['content.xml']) {
|
||||
let xml = await zip.files['content.xml'].async('string')
|
||||
let json = xmlConvert.xml2json(xml)
|
||||
@@ -33,18 +39,20 @@ const parseXmindFile = file => {
|
||||
}
|
||||
|
||||
// 转换xmind数据
|
||||
const transformXmind = content => {
|
||||
const transformXmind = async (content, files) => {
|
||||
let data = JSON.parse(content)[0]
|
||||
let nodeTree = data.rootTopic
|
||||
let newTree = {}
|
||||
let walk = (node, newNode) => {
|
||||
let waitLoadImageList = []
|
||||
let walk = async (node, newNode) => {
|
||||
newNode.data = {
|
||||
// 节点内容
|
||||
text: node.title
|
||||
}
|
||||
// 节点备注
|
||||
if (node.notes) {
|
||||
newNode.data.note = (node.notes.realHTML || node.notes.plain).content
|
||||
let notesData = node.notes.realHTML || node.notes.plain
|
||||
newNode.data.note = notesData ? notesData.content || '' : ''
|
||||
}
|
||||
// 超链接
|
||||
if (node.href && /^https?:\/\//.test(node.href)) {
|
||||
@@ -54,6 +62,42 @@ const transformXmind = content => {
|
||||
if (node.labels && node.labels.length > 0) {
|
||||
newNode.data.tag = node.labels
|
||||
}
|
||||
// 图片
|
||||
if (node.image && /\.(jpg|jpeg|png|gif|webp)$/.test(node.image.src)) {
|
||||
try {
|
||||
// 处理异步逻辑
|
||||
let resolve = null
|
||||
let promise = new Promise(_resolve => {
|
||||
resolve = _resolve
|
||||
})
|
||||
waitLoadImageList.push(promise)
|
||||
// 读取图片
|
||||
let imageType = /\.([^.]+)$/.exec(node.image.src)[1]
|
||||
let imageBase64 =
|
||||
`data:image/${imageType};base64,` +
|
||||
(await files['resources/' + node.image.src.split('/')[1]].async(
|
||||
'base64'
|
||||
))
|
||||
newNode.data.image = imageBase64
|
||||
// 如果图片尺寸不存在
|
||||
if (!node.image.width && !node.image.height) {
|
||||
let imageSize = await getImageSize(imageBase64)
|
||||
newNode.data.imageSize = {
|
||||
width: imageSize.width,
|
||||
height: imageSize.height
|
||||
}
|
||||
} else {
|
||||
newNode.data.imageSize = {
|
||||
width: node.image.width,
|
||||
height: node.image.height
|
||||
}
|
||||
}
|
||||
resolve()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
// 子节点
|
||||
newNode.children = []
|
||||
if (
|
||||
@@ -69,6 +113,7 @@ const transformXmind = content => {
|
||||
}
|
||||
}
|
||||
walk(nodeTree, newTree)
|
||||
await Promise.all(waitLoadImageList)
|
||||
return newTree
|
||||
}
|
||||
|
||||
@@ -157,8 +202,127 @@ const transformOldXmind = content => {
|
||||
return newTree
|
||||
}
|
||||
|
||||
// 数据转换为xmind文件
|
||||
const transformToXmind = async (data, name) => {
|
||||
const id = 'simpleMindMap_' + Date.now()
|
||||
const imageList = []
|
||||
// 转换核心数据
|
||||
let newTree = {}
|
||||
let waitLoadImageList = []
|
||||
let walk = async (node, newNode, isRoot) => {
|
||||
let newData = {
|
||||
structureClass: 'org.xmind.ui.logic.right',
|
||||
title: getTextFromHtml(node.data.text), // 节点文本
|
||||
children: {
|
||||
attached: []
|
||||
}
|
||||
}
|
||||
// 备注
|
||||
if (node.data.note !== undefined) {
|
||||
newData.notes = {
|
||||
realHTML: {
|
||||
content: node.data.note
|
||||
},
|
||||
plain: {
|
||||
content: node.data.note
|
||||
}
|
||||
}
|
||||
}
|
||||
// 超链接
|
||||
if (node.data.hyperlink !== undefined) {
|
||||
newData.href = node.data.hyperlink
|
||||
}
|
||||
// 标签
|
||||
if (node.data.tag !== undefined) {
|
||||
newData.labels = node.data.tag || []
|
||||
}
|
||||
// 图片
|
||||
if (node.data.image) {
|
||||
try {
|
||||
// 处理异步逻辑
|
||||
let resolve = null
|
||||
let promise = new Promise(_resolve => {
|
||||
resolve = _resolve
|
||||
})
|
||||
waitLoadImageList.push(promise)
|
||||
let imgName = ''
|
||||
let imgData = node.data.image
|
||||
// 网络图片要先转换成data:url
|
||||
if (/^https?:\/\//.test(node.data.image)) {
|
||||
imgData = await imgToDataUrl(node.data.image)
|
||||
}
|
||||
// 从data:url中解析出图片类型和base64
|
||||
let dataUrlRes = parseDataUrl(imgData)
|
||||
imgName = 'image_' + imageList.length + '.' + dataUrlRes.type
|
||||
imageList.push({
|
||||
name: imgName,
|
||||
data: dataUrlRes.base64
|
||||
})
|
||||
newData.image = {
|
||||
src: 'xap:resources/' + imgName,
|
||||
width: node.data.imageSize.width,
|
||||
height: node.data.imageSize.height
|
||||
}
|
||||
resolve()
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
// 样式
|
||||
// 暂时不考虑样式
|
||||
if (isRoot) {
|
||||
newData.class = 'topic'
|
||||
newNode.id = id
|
||||
newNode.class = 'sheet'
|
||||
newNode.title = name
|
||||
newNode.extensions = []
|
||||
newNode.topicPositioning = 'fixed'
|
||||
newNode.topicOverlapping = 'overlap'
|
||||
newNode.coreVersion = '2.100.0'
|
||||
newNode.rootTopic = newData
|
||||
} else {
|
||||
Object.keys(newData).forEach(key => {
|
||||
newNode[key] = newData[key]
|
||||
})
|
||||
}
|
||||
if (node.children && node.children.length > 0) {
|
||||
node.children.forEach(child => {
|
||||
let newChild = {}
|
||||
walk(child, newChild)
|
||||
newData.children.attached.push(newChild)
|
||||
})
|
||||
}
|
||||
}
|
||||
walk(data, newTree, true)
|
||||
await Promise.all(waitLoadImageList)
|
||||
const contentData = [newTree]
|
||||
// 创建压缩包
|
||||
const zip = new JSZip()
|
||||
zip.file('content.json', JSON.stringify(contentData))
|
||||
zip.file(
|
||||
'metadata.json',
|
||||
`{"modifier":"","dataStructureVersion":"1","layoutEngineVersion":"2","activeSheetId":"${id}"}`
|
||||
)
|
||||
const manifestData = {
|
||||
'file-entries': { 'content.json': {}, 'metadata.json': {} }
|
||||
}
|
||||
// 图片
|
||||
if (imageList.length > 0) {
|
||||
imageList.forEach(item => {
|
||||
manifestData['file-entries']['resources/' + item.name] = {}
|
||||
const img = zip.folder('resources')
|
||||
img.file(item.name, item.data, { base64: true })
|
||||
})
|
||||
}
|
||||
zip.file('manifest.json', JSON.stringify(manifestData))
|
||||
const zipData = await zip.generateAsync({ type: 'blob' })
|
||||
return zipData
|
||||
}
|
||||
|
||||
export default {
|
||||
parseXmindFile,
|
||||
transformXmind,
|
||||
transformOldXmind
|
||||
transformOldXmind,
|
||||
transformToXmind
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
import associativeLineControlsMethods from './associativeLine/associativeLineControls'
|
||||
import associativeLineTextMethods from './associativeLine/associativeLineText'
|
||||
|
||||
// 关联线类
|
||||
// 关联线插件
|
||||
class AssociativeLine {
|
||||
constructor(opt = {}) {
|
||||
this.mindMap = opt.mindMap
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { bfsWalk, throttle } from '../utils'
|
||||
import Base from '../layouts/Base'
|
||||
|
||||
// 节点拖动类
|
||||
|
||||
// 节点拖动插件
|
||||
class Drag extends Base {
|
||||
// 构造函数
|
||||
constructor({ mindMap }) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { imgToDataUrl, downloadFile, readBlob } from '../utils'
|
||||
import { imgToDataUrl, downloadFile, readBlob, removeHTMLEntities } from '../utils'
|
||||
import { SVG } from '@svgdotjs/svg.js'
|
||||
import drawBackgroundImageToCanvas from '../utils/simulateCSSBackgroundInCanvas'
|
||||
import { transformToMarkdown } from '../parse/toMarkdown'
|
||||
|
||||
// 导出类
|
||||
// 导出插件
|
||||
class Export {
|
||||
// 构造函数
|
||||
constructor(opt) {
|
||||
@@ -154,6 +154,7 @@ class Export {
|
||||
*/
|
||||
async png(name, transparent = false) {
|
||||
let { node, str } = await this.getSvgData()
|
||||
str = removeHTMLEntities(str)
|
||||
// 如果开启了富文本,则使用htmltocanvas转换为图片
|
||||
if (this.mindMap.richText) {
|
||||
let res = await this.mindMap.richText.handleExportPng(node.node)
|
||||
@@ -180,6 +181,17 @@ class Export {
|
||||
this.mindMap.doExportPDF.pdf(name, img)
|
||||
}
|
||||
|
||||
// 导出为xmind
|
||||
async xmind(name) {
|
||||
if (!this.mindMap.doExportXMind) {
|
||||
throw new Error('请注册ExportXMind插件')
|
||||
}
|
||||
const data = this.mindMap.getData()
|
||||
const blob = await this.mindMap.doExportXMind.xmind(data, name)
|
||||
const res = await readBlob(blob)
|
||||
return res
|
||||
}
|
||||
|
||||
// 导出为svg
|
||||
// plusCssText:附加的css样式,如果svg中存在dom节点,想要设置一些针对节点的样式可以通过这个参数传入
|
||||
async svg(name, plusCssText) {
|
||||
@@ -196,6 +208,7 @@ class Export {
|
||||
node.first().before(SVG(`<title>${name}</title>`))
|
||||
await this.drawBackgroundToSvg(node)
|
||||
let str = node.svg()
|
||||
str = removeHTMLEntities(str)
|
||||
// 转换成blob数据
|
||||
let blob = new Blob([str], {
|
||||
type: 'image/svg+xml'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import JsPDF from 'jspdf'
|
||||
|
||||
// 导出PDF类,需要通过Export插件使用
|
||||
// 导出PDF插件,需要通过Export插件使用
|
||||
class ExportPDF {
|
||||
// 构造函数
|
||||
constructor(opt) {
|
||||
|
||||
19
simple-mind-map/src/plugins/ExportXMind.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import xmind from '../parse/xmind'
|
||||
|
||||
// 导出XMind插件,需要通过Export插件使用
|
||||
class ExportXMind {
|
||||
// 构造函数
|
||||
constructor(opt) {
|
||||
this.mindMap = opt.mindMap
|
||||
}
|
||||
|
||||
// 导出xmind
|
||||
async xmind(data, name) {
|
||||
const zipData = await xmind.transformToXmind(data, name)
|
||||
return zipData
|
||||
}
|
||||
}
|
||||
|
||||
ExportXMind.instanceName = 'doExportXMind'
|
||||
|
||||
export default ExportXMind
|
||||
@@ -1,7 +1,7 @@
|
||||
import { bfsWalk } from '../utils'
|
||||
import { CONSTANTS } from '../constants/constant'
|
||||
|
||||
// 键盘导航类
|
||||
// 键盘导航插件
|
||||
class KeyboardNavigation {
|
||||
// 构造函数
|
||||
constructor(opt) {
|
||||
@@ -28,8 +28,7 @@ class KeyboardNavigation {
|
||||
this.focus(dir)
|
||||
} else {
|
||||
let root = this.mindMap.renderer.root
|
||||
this.mindMap.renderer.moveNodeToCenter(root)
|
||||
root.active()
|
||||
this.mindMap.execCommand('GO_TARGET_NODE', root)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,8 +80,7 @@ class KeyboardNavigation {
|
||||
|
||||
// 找到了则让目标节点聚焦
|
||||
if (targetNode) {
|
||||
this.mindMap.renderer.moveNodeToCenter(targetNode)
|
||||
targetNode.active()
|
||||
this.mindMap.execCommand('GO_TARGET_NODE', targetNode)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// 小地图类
|
||||
import { isWhite, isTransparent, getVisibleColorFromTheme } from '../utils/index'
|
||||
|
||||
// 小地图插件
|
||||
class MiniMap {
|
||||
// 构造函数
|
||||
constructor(opt) {
|
||||
@@ -20,7 +22,7 @@ class MiniMap {
|
||||
* boxHeight:小地图容器的高度
|
||||
*/
|
||||
calculationMiniMap(boxWidth, boxHeight) {
|
||||
let { svgHTML, rect, origWidth, origHeight, scaleX, scaleY } =
|
||||
let { svg, rect, origWidth, origHeight, scaleX, scaleY } =
|
||||
this.mindMap.getSvgData()
|
||||
// 计算数据
|
||||
let boxRatio = boxWidth / boxHeight
|
||||
@@ -65,8 +67,10 @@ class MiniMap {
|
||||
Math.max(0, ((_rectY2 - origHeight) / _rectHeight) * actHeight) +
|
||||
miniMapBoxTop +
|
||||
'px'
|
||||
|
||||
this.removeNodeContent(svg)
|
||||
return {
|
||||
svgHTML, // 小地图html
|
||||
svgHTML: svg.svg(), // 小地图html
|
||||
viewBoxStyle, // 视图框的位置信息
|
||||
miniMapBoxScale, // 视图框的缩放值
|
||||
miniMapBoxLeft, // 视图框的left值
|
||||
@@ -74,6 +78,26 @@ class MiniMap {
|
||||
}
|
||||
}
|
||||
|
||||
// 移除节点的内容
|
||||
removeNodeContent(svg) {
|
||||
if (svg.hasClass('smm-node')) {
|
||||
let shape = svg.findOne('.smm-node-shape')
|
||||
let fill = shape.attr('fill')
|
||||
if (isWhite(fill) || isTransparent(fill)) {
|
||||
shape.attr('fill', getVisibleColorFromTheme(this.mindMap.themeConfig))
|
||||
}
|
||||
svg.clear()
|
||||
svg.add(shape)
|
||||
return
|
||||
}
|
||||
let children = svg.children()
|
||||
if (children && children.length > 0) {
|
||||
children.forEach((node) => {
|
||||
this.removeNodeContent(node)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 小地图鼠标按下事件
|
||||
onMousedown(e) {
|
||||
this.isMousedown = true
|
||||
|
||||
226
simple-mind-map/src/plugins/NodeImgAdjust.js
Normal file
@@ -0,0 +1,226 @@
|
||||
// 节点图片大小调整插件
|
||||
import { resizeImgSizeByOriginRatio } from '../utils/index'
|
||||
import btnsSvg from '../svg/btns'
|
||||
|
||||
class NodeImgAdjust {
|
||||
// 构造函数
|
||||
constructor({ mindMap }) {
|
||||
this.mindMap = mindMap
|
||||
this.resizeBtnSize = 26 // 调整按钮的大小
|
||||
this.handleEl = null // 自定义元素,用来渲染临时图片、调整按钮
|
||||
this.isShowHandleEl = false // 自定义元素是否在显示中
|
||||
this.node = null // 当前节点实例
|
||||
this.img = null // 当前节点的图片节点
|
||||
this.rect = null // 当前图片节点的尺寸信息
|
||||
this.isMousedown = false // 当前是否是按住调整按钮状态
|
||||
this.currentImgWidth = 0 // 当前拖拽实时图片的大小
|
||||
this.currentImgHeight = 0
|
||||
this.isAdjusted = false // 是否是拖拽结束后的渲染期间
|
||||
this.bindEvent()
|
||||
}
|
||||
|
||||
// 监听事件
|
||||
bindEvent() {
|
||||
this.onNodeImgMouseleave = this.onNodeImgMouseleave.bind(this)
|
||||
this.onNodeImgMousemove = this.onNodeImgMousemove.bind(this)
|
||||
this.onMousemove = this.onMousemove.bind(this)
|
||||
this.onMouseup = this.onMouseup.bind(this)
|
||||
this.onRenderEnd = this.onRenderEnd.bind(this)
|
||||
this.mindMap.on('node_img_mouseleave', this.onNodeImgMouseleave)
|
||||
this.mindMap.on('node_img_mousemove', this.onNodeImgMousemove)
|
||||
this.mindMap.on('mousemove', this.onMousemove)
|
||||
this.mindMap.on('mouseup', this.onMouseup)
|
||||
this.mindMap.on('node_mouseup', this.onMouseup)
|
||||
this.mindMap.on('node_tree_render_end', this.onRenderEnd)
|
||||
}
|
||||
|
||||
// 解绑事件
|
||||
unBindEvent() {
|
||||
this.mindMap.off('node_img_mouseleave', this.onNodeImgMouseleave)
|
||||
this.mindMap.off('node_img_mousemove', this.onNodeImgMousemove)
|
||||
this.mindMap.off('mousemove', this.onMousemove)
|
||||
this.mindMap.off('mouseup', this.onMouseup)
|
||||
this.mindMap.off('node_mouseup', this.onMouseup)
|
||||
this.mindMap.off('node_tree_render_end', this.onRenderEnd)
|
||||
}
|
||||
|
||||
// 节点图片鼠标移动事件
|
||||
onNodeImgMousemove(node, img) {
|
||||
// 如果当前正在拖动调整中那么直接返回
|
||||
if (this.isMousedown || this.isAdjusted || this.mindMap.opt.readonly) return
|
||||
// 如果在当前节点内移动,以及自定义元素已经是显示状态,那么直接返回
|
||||
if (this.node === node && this.isShowHandleEl) return
|
||||
// 更新当前节点信息
|
||||
this.node = node
|
||||
this.img = img
|
||||
this.rect = this.img.rbox()
|
||||
// 显示自定义元素
|
||||
this.showHandleEl()
|
||||
}
|
||||
|
||||
// 节点图片鼠标移出事件
|
||||
onNodeImgMouseleave() {
|
||||
if (this.isMousedown) return
|
||||
this.hideHandleEl()
|
||||
}
|
||||
|
||||
// 隐藏节点实际的图片
|
||||
hideNodeImage() {
|
||||
if (!this.img) return
|
||||
this.img.hide()
|
||||
}
|
||||
|
||||
// 显示节点实际的图片
|
||||
showNodeImage() {
|
||||
if (!this.img) return
|
||||
this.img.show()
|
||||
}
|
||||
|
||||
// 显示自定义元素
|
||||
showHandleEl() {
|
||||
if (!this.handleEl) {
|
||||
this.createResizeBtnEl()
|
||||
}
|
||||
this.setHandleElRect()
|
||||
document.body.appendChild(this.handleEl)
|
||||
this.isShowHandleEl = true
|
||||
}
|
||||
|
||||
// 隐藏自定义元素
|
||||
hideHandleEl() {
|
||||
if (!this.isShowHandleEl) return
|
||||
this.isShowHandleEl = false
|
||||
document.body.removeChild(this.handleEl)
|
||||
this.handleEl.style.backgroundImage = ``
|
||||
this.handleEl.style.width = 0
|
||||
this.handleEl.style.height = 0
|
||||
this.handleEl.style.left = 0
|
||||
this.handleEl.style.top = 0
|
||||
}
|
||||
|
||||
// 设置自定义元素尺寸位置信息
|
||||
setHandleElRect() {
|
||||
let { width, height, x, y } = this.rect
|
||||
this.handleEl.style.left = `${x}px`
|
||||
this.handleEl.style.top = `${y}px`
|
||||
this.currentImgWidth = width
|
||||
this.currentImgHeight = height
|
||||
this.updateHandleElSize()
|
||||
}
|
||||
|
||||
// 更新自定义元素宽高
|
||||
updateHandleElSize() {
|
||||
this.handleEl.style.width = `${this.currentImgWidth}px`
|
||||
this.handleEl.style.height = `${this.currentImgHeight}px`
|
||||
}
|
||||
|
||||
// 创建调整按钮元素
|
||||
createResizeBtnEl() {
|
||||
// 容器元素
|
||||
this.handleEl = document.createElement('div')
|
||||
this.handleEl.style.cssText = `
|
||||
pointer-events: none;
|
||||
position: fixed;
|
||||
background-size: cover;
|
||||
`
|
||||
// 调整按钮元素
|
||||
const btnEl = document.createElement('div')
|
||||
btnEl.innerHTML = btnsSvg.imgAdjust
|
||||
btnEl.style.cssText = `
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
pointer-events: auto;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
width: ${this.resizeBtnSize}px;
|
||||
height: ${this.resizeBtnSize}px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: nwse-resize;
|
||||
`
|
||||
this.handleEl.appendChild(btnEl)
|
||||
// 给按钮元素绑定事件
|
||||
btnEl.addEventListener('mouseenter', () => {
|
||||
// 移入按钮,会触发节点图片的移出事件,所以需要再次显示按钮
|
||||
this.showHandleEl()
|
||||
})
|
||||
btnEl.addEventListener('mouseleave', () => {
|
||||
// 移除按钮,需要隐藏按钮
|
||||
if (this.isMousedown) return
|
||||
this.hideHandleEl()
|
||||
})
|
||||
btnEl.addEventListener('mousedown', e => {
|
||||
this.onMousedown(e)
|
||||
})
|
||||
}
|
||||
|
||||
// 鼠标按钮按下事件
|
||||
onMousedown() {
|
||||
this.isMousedown = true
|
||||
// 隐藏节点实际图片
|
||||
this.hideNodeImage()
|
||||
// 将节点图片渲染到自定义元素上
|
||||
this.handleEl.style.backgroundImage = `url(${this.node.nodeData.data.image})`
|
||||
}
|
||||
|
||||
// 鼠标移动
|
||||
onMousemove(e) {
|
||||
if (!this.isMousedown) return
|
||||
e.preventDefault()
|
||||
// 计算当前拖拽位置对应的图片的实时大小
|
||||
let { width: imageOriginWidth, height: imageOriginHeight } =
|
||||
this.node.nodeData.data.imageSize
|
||||
let newWidth = e.clientX - this.rect.x
|
||||
let newHeight = e.clientY - this.rect.y
|
||||
if (newWidth <= 0 || newHeight <= 0) return
|
||||
let [actWidth, actHeight] = resizeImgSizeByOriginRatio(
|
||||
imageOriginWidth,
|
||||
imageOriginHeight,
|
||||
newWidth,
|
||||
newHeight
|
||||
)
|
||||
this.currentImgWidth = actWidth
|
||||
this.currentImgHeight = actHeight
|
||||
this.updateHandleElSize()
|
||||
}
|
||||
|
||||
// 鼠标松开
|
||||
onMouseup() {
|
||||
if (!this.isMousedown) return
|
||||
// 显示节点实际图片
|
||||
this.showNodeImage()
|
||||
// 隐藏自定义元素
|
||||
this.hideHandleEl()
|
||||
// 更新节点图片为新的大小
|
||||
let { image, imageTitle } = this.node.nodeData.data
|
||||
let { scaleX, scaleY } = this.mindMap.draw.transform()
|
||||
this.mindMap.execCommand('SET_NODE_IMAGE', this.node, {
|
||||
url: image,
|
||||
title: imageTitle,
|
||||
width: this.currentImgWidth / scaleX,
|
||||
height: this.currentImgHeight / scaleY,
|
||||
custom: true // 代表自定义了图片大小
|
||||
})
|
||||
this.isAdjusted = true
|
||||
this.isMousedown = false
|
||||
}
|
||||
|
||||
// 渲染完成事件
|
||||
onRenderEnd() {
|
||||
if (!this.isAdjusted) {
|
||||
this.hideHandleEl()
|
||||
return
|
||||
}
|
||||
this.isAdjusted = false
|
||||
}
|
||||
|
||||
// 插件被移除前做的事情
|
||||
beforePluginRemove() {
|
||||
this.unBindEvent()
|
||||
}
|
||||
}
|
||||
|
||||
NodeImgAdjust.instanceName = 'nodeImgAdjust'
|
||||
|
||||
export default NodeImgAdjust
|
||||
@@ -1,7 +1,7 @@
|
||||
import Quill from 'quill'
|
||||
import 'quill/dist/quill.snow.css'
|
||||
import html2canvas from 'html2canvas'
|
||||
import { walk, getTextFromHtml } from '../utils'
|
||||
import { walk, getTextFromHtml, isWhite, getVisibleColorFromTheme } from '../utils'
|
||||
import { CONSTANTS } from '../constants/constant'
|
||||
|
||||
let extended = false
|
||||
@@ -28,7 +28,7 @@ let fontSizeList = new Array(100).fill(0).map((_, index) => {
|
||||
return index + 'px'
|
||||
})
|
||||
|
||||
// 节点支持富文本编辑功能
|
||||
// 富文本编辑插件
|
||||
class RichText {
|
||||
constructor({ mindMap, pluginOpt }) {
|
||||
this.mindMap = mindMap
|
||||
@@ -39,6 +39,7 @@ class RichText {
|
||||
this.range = null
|
||||
this.lastRange = null
|
||||
this.node = null
|
||||
this.isInserting = false
|
||||
this.styleEl = null
|
||||
this.cacheEditingText = ''
|
||||
this.lostStyle = false
|
||||
@@ -145,11 +146,12 @@ class RichText {
|
||||
}
|
||||
|
||||
// 显示文本编辑控件
|
||||
showEditText(node, rect) {
|
||||
showEditText(node, rect, isInserting) {
|
||||
if (this.showTextEdit) {
|
||||
return
|
||||
}
|
||||
this.node = node
|
||||
this.isInserting = isInserting
|
||||
if (!rect) rect = node._textData.node.node.getBoundingClientRect()
|
||||
this.mindMap.emit('before_show_text_edit')
|
||||
this.mindMap.renderer.textEdit.registerTmpShortcut()
|
||||
@@ -174,11 +176,12 @@ class RichText {
|
||||
}
|
||||
// 使用节点的填充色,否则如果节点颜色是白色的话编辑时看不见
|
||||
let bgColor = node.style.merge('fillColor')
|
||||
let color = node.style.merge('color')
|
||||
this.textEditNode.style.marginLeft = `-${paddingX * scaleX}px`
|
||||
this.textEditNode.style.marginTop = `-${paddingY * scaleY}px`
|
||||
this.textEditNode.style.zIndex = this.mindMap.opt.nodeTextEditZIndex
|
||||
this.textEditNode.style.backgroundColor =
|
||||
bgColor === 'transparent' ? '#fff' : bgColor
|
||||
bgColor === 'transparent' ? isWhite(color) ? getVisibleColorFromTheme(this.mindMap.themeConfig) : '#fff' : bgColor
|
||||
this.textEditNode.style.minWidth = originWidth + paddingX * 2 + 'px'
|
||||
this.textEditNode.style.minHeight = originHeight + 'px'
|
||||
this.textEditNode.style.left = rect.left + 'px'
|
||||
@@ -200,7 +203,8 @@ class RichText {
|
||||
this.initQuillEditor()
|
||||
document.querySelector('.ql-editor').style.minHeight = originHeight + 'px'
|
||||
this.showTextEdit = true
|
||||
this.focus()
|
||||
// 如果是刚创建的节点,那么默认全选,否则普通激活不全选
|
||||
this.focus(isInserting ? 0 : null)
|
||||
if (!node.nodeData.data.richText) {
|
||||
// 如果是非富文本的情况,需要手动应用文本样式
|
||||
this.setTextStyleIfNotRichText(node)
|
||||
@@ -219,7 +223,7 @@ class RichText {
|
||||
underline: node.style.merge('textDecoration') === 'underline',
|
||||
strike: node.style.merge('textDecoration') === 'line-through'
|
||||
}
|
||||
this.formatAllText(style)
|
||||
this.pureFormatAllText(style)
|
||||
}
|
||||
|
||||
// 获取当前正在编辑的内容
|
||||
@@ -250,6 +254,7 @@ class RichText {
|
||||
this.showTextEdit = false
|
||||
this.mindMap.emit('rich_text_selection_change', false)
|
||||
this.node = null
|
||||
this.isInserting = false
|
||||
}
|
||||
|
||||
// 初始化Quill富文本编辑器
|
||||
@@ -264,6 +269,12 @@ class RichText {
|
||||
handler: function () {
|
||||
// 覆盖默认的回车键换行
|
||||
}
|
||||
},
|
||||
tab: {
|
||||
key: 9,
|
||||
handler: function () {
|
||||
// 覆盖默认的tab键
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -271,6 +282,8 @@ class RichText {
|
||||
theme: 'snow'
|
||||
})
|
||||
this.quill.on('selection-change', range => {
|
||||
// 刚创建的节点全选不需要显示操作条
|
||||
if (this.isInserting) return
|
||||
this.lastRange = this.range
|
||||
this.range = null
|
||||
if (range) {
|
||||
@@ -325,7 +338,7 @@ class RichText {
|
||||
|
||||
// 中文输入结束
|
||||
onCompositionEnd() {
|
||||
if (!this.showTextEdit) {
|
||||
if (!this.showTextEdit || !this.lostStyle) {
|
||||
return
|
||||
}
|
||||
this.isCompositing = false
|
||||
@@ -338,9 +351,9 @@ class RichText {
|
||||
}
|
||||
|
||||
// 聚焦
|
||||
focus() {
|
||||
focus(start) {
|
||||
let len = this.quill.getLength()
|
||||
this.quill.setSelection(len, len)
|
||||
this.quill.setSelection(typeof start === 'number' ? start : len, len)
|
||||
}
|
||||
|
||||
// 格式化当前选中的文本
|
||||
@@ -372,6 +385,11 @@ class RichText {
|
||||
// 格式化所有文本
|
||||
formatAllText(config = {}) {
|
||||
this.syncFormatToNodeConfig(config)
|
||||
this.pureFormatAllText(config)
|
||||
}
|
||||
|
||||
// 纯粹的格式化所有文本
|
||||
pureFormatAllText(config = {}) {
|
||||
this.quill.formatText(0, this.quill.getLength(), config)
|
||||
}
|
||||
|
||||
|
||||
160
simple-mind-map/src/plugins/Search.js
Normal file
@@ -0,0 +1,160 @@
|
||||
import { bfsWalk, getTextFromHtml, isUndef } from '../utils/index'
|
||||
|
||||
// 搜索插件
|
||||
class Search {
|
||||
// 构造函数
|
||||
constructor({ mindMap }) {
|
||||
this.mindMap = mindMap
|
||||
// 是否正在搜索
|
||||
this.isSearching = false
|
||||
// 搜索文本
|
||||
this.searchText = ''
|
||||
// 匹配的节点列表
|
||||
this.matchNodeList = []
|
||||
// 当前所在的节点列表索引
|
||||
this.currentIndex = -1
|
||||
// 不要复位搜索文本
|
||||
this.notResetSearchText = false
|
||||
this.onDataChange = this.onDataChange.bind(this)
|
||||
this.mindMap.on('data_change', this.onDataChange)
|
||||
}
|
||||
|
||||
// 节点数据改变了,需要重新搜索
|
||||
onDataChange() {
|
||||
if (this.notResetSearchText) {
|
||||
this.notResetSearchText = false
|
||||
return
|
||||
}
|
||||
this.searchText = ''
|
||||
}
|
||||
|
||||
// 搜索
|
||||
search(text, callback) {
|
||||
if (isUndef(text)) return this.endSearch()
|
||||
text = String(text)
|
||||
this.isSearching = true
|
||||
if (this.searchText === text) {
|
||||
// 和上一次搜索文本一样,那么搜索下一个
|
||||
this.searchNext(callback)
|
||||
} else {
|
||||
// 和上次搜索文本不一样,那么重新开始
|
||||
this.searchText = text
|
||||
this.doSearch()
|
||||
this.searchNext(callback)
|
||||
}
|
||||
this.emitEvent()
|
||||
}
|
||||
|
||||
// 结束搜索
|
||||
endSearch() {
|
||||
if (!this.isSearching) return
|
||||
this.searchText = ''
|
||||
this.matchNodeList = []
|
||||
this.currentIndex = -1
|
||||
this.notResetSearchText = false
|
||||
this.isSearching = false
|
||||
this.emitEvent()
|
||||
}
|
||||
|
||||
// 搜索匹配的节点
|
||||
doSearch() {
|
||||
this.matchNodeList = []
|
||||
this.currentIndex = -1
|
||||
bfsWalk(this.mindMap.renderer.root, node => {
|
||||
let { richText, text } = node.nodeData.data
|
||||
if (richText) {
|
||||
text = getTextFromHtml(text)
|
||||
}
|
||||
if (text.includes(this.searchText)) {
|
||||
this.matchNodeList.push(node)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 搜索下一个,定位到下一个匹配节点
|
||||
searchNext(callback) {
|
||||
if (!this.isSearching || this.matchNodeList.length <= 0) return
|
||||
if (this.currentIndex < this.matchNodeList.length - 1) {
|
||||
this.currentIndex++
|
||||
} else {
|
||||
this.currentIndex = 0
|
||||
}
|
||||
let currentNode = this.matchNodeList[this.currentIndex]
|
||||
this.notResetSearchText = true
|
||||
this.mindMap.execCommand('GO_TARGET_NODE', currentNode, () => {
|
||||
this.notResetSearchText = false
|
||||
callback()
|
||||
})
|
||||
}
|
||||
|
||||
// 替换当前节点
|
||||
replace(replaceText) {
|
||||
if (
|
||||
isUndef(replaceText) ||
|
||||
!this.isSearching ||
|
||||
this.matchNodeList.length <= 0
|
||||
)
|
||||
return
|
||||
replaceText = String(replaceText)
|
||||
let currentNode = this.matchNodeList[this.currentIndex]
|
||||
if (!currentNode) return
|
||||
let text = this.getReplacedText(currentNode, this.searchText, replaceText)
|
||||
this.notResetSearchText = true
|
||||
currentNode.setText(text, currentNode.nodeData.data.richText, true)
|
||||
this.matchNodeList = this.matchNodeList.filter(node => {
|
||||
return currentNode !== node
|
||||
})
|
||||
if (this.currentIndex > this.matchNodeList.length - 1) {
|
||||
this.currentIndex = -1
|
||||
} else {
|
||||
this.currentIndex--
|
||||
}
|
||||
this.emitEvent()
|
||||
}
|
||||
|
||||
// 替换所有
|
||||
replaceAll(replaceText) {
|
||||
if (
|
||||
isUndef(replaceText) ||
|
||||
!this.isSearching ||
|
||||
this.matchNodeList.length <= 0
|
||||
)
|
||||
return
|
||||
replaceText = String(replaceText)
|
||||
this.matchNodeList.forEach(node => {
|
||||
let text = this.getReplacedText(node, this.searchText, replaceText)
|
||||
this.mindMap.renderer.setNodeDataRender(
|
||||
node,
|
||||
{
|
||||
text,
|
||||
resetRichText: !!node.nodeData.data.richText
|
||||
},
|
||||
true
|
||||
)
|
||||
})
|
||||
this.mindMap.render()
|
||||
this.mindMap.command.addHistory()
|
||||
this.endSearch()
|
||||
}
|
||||
|
||||
// 获取某个节点替换后的文本
|
||||
getReplacedText(node, searchText, replaceText) {
|
||||
let { richText, text } = node.nodeData.data
|
||||
if (richText) {
|
||||
text = getTextFromHtml(text)
|
||||
}
|
||||
return text.replaceAll(searchText, replaceText)
|
||||
}
|
||||
|
||||
// 发送事件
|
||||
emitEvent() {
|
||||
this.mindMap.emit('search_info_change', {
|
||||
currentIndex: this.currentIndex,
|
||||
total: this.matchNodeList.length
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Search.instanceName = 'search'
|
||||
|
||||
export default Search
|
||||
@@ -1,7 +1,6 @@
|
||||
import { bfsWalk, throttle } from '../utils'
|
||||
|
||||
// 选择节点类
|
||||
|
||||
// 节点选择插件
|
||||
class Select {
|
||||
// 构造函数
|
||||
constructor({ mindMap }) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// 手势事件支持类
|
||||
|
||||
// 手势事件支持插件
|
||||
class TouchEvent {
|
||||
// 构造函数
|
||||
constructor({ mindMap }) {
|
||||
@@ -50,16 +49,20 @@ class TouchEvent {
|
||||
} else if (len === 2) {
|
||||
let touch1 = e.touches[0]
|
||||
let touch2 = e.touches[1]
|
||||
let distance = Math.sqrt(
|
||||
Math.pow(touch1.clientX - touch2.clientX, 2) +
|
||||
Math.pow(touch1.clientY - touch2.clientY, 2)
|
||||
)
|
||||
let ox = touch1.clientX - touch2.clientX
|
||||
let oy = touch1.clientY - touch2.clientY
|
||||
let distance = Math.sqrt(Math.pow(ox, 2) + Math.pow(oy, 2))
|
||||
// 以两指中心点进行缩放
|
||||
let { x: touch1ClientX, y: touch1ClientY } = this.mindMap.toPos(touch1.clientX, touch1.clientY)
|
||||
let { x: touch2ClientX, y: touch2ClientY } = this.mindMap.toPos(touch2.clientX, touch2.clientY)
|
||||
let cx = (touch1ClientX + touch2ClientX) / 2
|
||||
let cy = (touch1ClientY + touch2ClientY) / 2
|
||||
if (distance > this.doubleTouchmoveDistance) {
|
||||
// 放大
|
||||
this.mindMap.view.enlarge()
|
||||
this.mindMap.view.enlarge(cx, cy)
|
||||
} else {
|
||||
// 缩小
|
||||
this.mindMap.view.narrow()
|
||||
this.mindMap.view.narrow(cx, cy)
|
||||
}
|
||||
this.doubleTouchmoveDistance = distance
|
||||
}
|
||||
@@ -82,7 +85,8 @@ class TouchEvent {
|
||||
this.clickNum = 0
|
||||
this.dispatchMouseEvent('dblclick', ev.target, ev)
|
||||
} else {
|
||||
this.dispatchMouseEvent('click', ev.target, ev)
|
||||
// 点击事件应该不用模拟
|
||||
// this.dispatchMouseEvent('click', ev.target, ev)
|
||||
}
|
||||
}
|
||||
this.touchesNum = 0
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Text, G } from '@svgdotjs/svg.js'
|
||||
import { degToRad, camelCaseToHyphen } from '../utils'
|
||||
import merge from 'deepmerge'
|
||||
|
||||
// 水印类
|
||||
// 水印插件
|
||||
class Watermark {
|
||||
constructor(opt = {}) {
|
||||
this.mindMap = opt.mindMap
|
||||
|
||||
@@ -4,7 +4,11 @@ const open = `<svg t="1618141562310" class="icon" viewBox="0 0 1024 1024" versio
|
||||
// 收缩按钮
|
||||
const close = `<svg t="1618141589243" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13611" width="200" height="200"><path d="M512 105.472c225.28 0 407.04 181.76 407.04 407.04s-181.76 407.04-407.04 407.04-407.04-181.76-407.04-407.04 181.76-407.04 407.04-407.04z m0-74.24c-265.216 0-480.768 215.552-480.768 480.768s215.552 480.768 480.768 480.768 480.768-215.552 480.768-480.768-215.552-480.768-480.768-480.768z" p-id="13612"></path><path d="M252.928 474.624h518.144v74.24h-518.144z" p-id="13613"></path></svg>`
|
||||
|
||||
// 图片调整按钮
|
||||
const imgAdjust = `<svg width="12px" height="12px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#ffffff" d="M1008.128 614.4a25.6 25.6 0 0 0-27.648 5.632l-142.848 142.848L259.072 186.88 401.92 43.52A25.6 25.6 0 0 0 384 0h-358.4a25.6 25.6 0 0 0-25.6 25.6v358.4a25.6 25.6 0 0 0 43.52 17.92l143.36-142.848 578.048 578.048-142.848 142.848a25.6 25.6 0 0 0 17.92 43.52h358.4a25.6 25.6 0 0 0 25.6-25.6v-358.4a25.6 25.6 0 0 0-15.872-25.088z" /></svg>`
|
||||
|
||||
export default {
|
||||
open,
|
||||
close
|
||||
close,
|
||||
imgAdjust
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
// 深度优先遍历树
|
||||
export const walk = (
|
||||
root,
|
||||
@@ -31,9 +33,11 @@ export const walk = (
|
||||
|
||||
// 广度优先遍历树
|
||||
export const bfsWalk = (root, callback) => {
|
||||
callback(root)
|
||||
let stack = [root]
|
||||
let isStop = false
|
||||
if (callback(root, null) === 'stop') {
|
||||
isStop = true
|
||||
}
|
||||
while (stack.length) {
|
||||
if (isStop) {
|
||||
break
|
||||
@@ -41,8 +45,9 @@ export const bfsWalk = (root, callback) => {
|
||||
let cur = stack.shift()
|
||||
if (cur.children && cur.children.length) {
|
||||
cur.children.forEach(item => {
|
||||
if (isStop) return
|
||||
stack.push(item)
|
||||
if (callback(item) === 'stop') {
|
||||
if (callback(item, cur) === 'stop') {
|
||||
isStop = true
|
||||
}
|
||||
})
|
||||
@@ -50,6 +55,26 @@ export const bfsWalk = (root, callback) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 按原比例缩放图片
|
||||
export const resizeImgSizeByOriginRatio = (
|
||||
width,
|
||||
height,
|
||||
newWidth,
|
||||
newHeight
|
||||
) => {
|
||||
let arr = []
|
||||
let nRatio = width / height
|
||||
let mRatio = newWidth / newHeight
|
||||
if (nRatio > mRatio) {
|
||||
// 固定高度
|
||||
arr = [nRatio * newHeight, newHeight]
|
||||
} else {
|
||||
// 固定宽度
|
||||
arr = [newWidth, newWidth / nRatio]
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
// 缩放图片尺寸
|
||||
export const resizeImgSize = (width, height, maxWidth, maxHeight) => {
|
||||
let nRatio = width / height
|
||||
@@ -137,7 +162,12 @@ export const copyRenderTree = (tree, root, removeActiveState = false) => {
|
||||
}
|
||||
|
||||
// 复制节点树数据
|
||||
export const copyNodeTree = (tree, root, removeActiveState = false, keepId = false) => {
|
||||
export const copyNodeTree = (
|
||||
tree,
|
||||
root,
|
||||
removeActiveState = false,
|
||||
keepId = false
|
||||
) => {
|
||||
tree.data = simpleDeepClone(root.nodeData ? root.nodeData.data : root.data)
|
||||
// 去除节点id,因为节点id不能重复
|
||||
if (tree.data.id && !keepId) delete tree.data.id
|
||||
@@ -188,6 +218,18 @@ export const imgToDataUrl = src => {
|
||||
})
|
||||
}
|
||||
|
||||
// 解析dataUrl
|
||||
export const parseDataUrl = data => {
|
||||
if (!/^data:/.test(data)) return data
|
||||
let [typeStr, base64] = data.split(',')
|
||||
let res = /^data:[^/]+\/([^;]+);/.exec(typeStr)
|
||||
let type = res[1]
|
||||
return {
|
||||
type,
|
||||
base64
|
||||
}
|
||||
}
|
||||
|
||||
// 下载文件
|
||||
export const downloadFile = (file, fileName) => {
|
||||
let a = document.createElement('a')
|
||||
@@ -236,8 +278,8 @@ export const degToRad = deg => {
|
||||
return deg * (Math.PI / 180)
|
||||
}
|
||||
|
||||
// 驼峰转连字符
|
||||
export const camelCaseToHyphen = (str) => {
|
||||
// 驼峰转连字符
|
||||
export const camelCaseToHyphen = str => {
|
||||
return str.replace(/([a-z])([A-Z])/g, (...args) => {
|
||||
return args[1] + '-' + args[2].toLowerCase()
|
||||
})
|
||||
@@ -258,11 +300,8 @@ export const measureText = (text, { italic, bold, fontSize, fontFamily }) => {
|
||||
}
|
||||
measureTextContext.save()
|
||||
measureTextContext.font = font
|
||||
const {
|
||||
width,
|
||||
actualBoundingBoxAscent,
|
||||
actualBoundingBoxDescent
|
||||
} = measureTextContext.measureText(text)
|
||||
const { width, actualBoundingBoxAscent, actualBoundingBoxDescent } =
|
||||
measureTextContext.measureText(text)
|
||||
measureTextContext.restore()
|
||||
const height = actualBoundingBoxAscent + actualBoundingBoxDescent
|
||||
return { width, height }
|
||||
@@ -270,7 +309,9 @@ export const measureText = (text, { italic, bold, fontSize, fontFamily }) => {
|
||||
|
||||
// 拼接font字符串
|
||||
export const joinFontStr = ({ italic, bold, fontSize, fontFamily }) => {
|
||||
return `${italic ? 'italic ' : ''} ${bold ? 'bold ' : ''} ${fontSize}px ${fontFamily} `
|
||||
return `${italic ? 'italic ' : ''} ${
|
||||
bold ? 'bold ' : ''
|
||||
} ${fontSize}px ${fontFamily} `
|
||||
}
|
||||
|
||||
// 在下一个事件循环里执行任务
|
||||
@@ -336,7 +377,7 @@ export const checkNodeOuter = (mindMap, node) => {
|
||||
|
||||
// 提取html字符串里的纯文本
|
||||
let getTextFromHtmlEl = null
|
||||
export const getTextFromHtml = (html) => {
|
||||
export const getTextFromHtml = html => {
|
||||
if (!getTextFromHtmlEl) {
|
||||
getTextFromHtmlEl = document.createElement('div')
|
||||
}
|
||||
@@ -345,15 +386,157 @@ export const getTextFromHtml = (html) => {
|
||||
}
|
||||
|
||||
// 将blob转成data:url
|
||||
export const readBlob = (blob) => {
|
||||
export const readBlob = blob => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let reader = new FileReader()
|
||||
reader.onload = (evt) => {
|
||||
reader.onload = evt => {
|
||||
resolve(evt.target.result)
|
||||
}
|
||||
reader.onerror = (err) => {
|
||||
reader.onerror = err => {
|
||||
reject(err)
|
||||
}
|
||||
reader.readAsDataURL(blob)
|
||||
})
|
||||
}
|
||||
|
||||
// 将dom节点转换成html字符串
|
||||
let nodeToHTMLWrapEl = null
|
||||
export const nodeToHTML = node => {
|
||||
if (!nodeToHTMLWrapEl) {
|
||||
nodeToHTMLWrapEl = document.createElement('div')
|
||||
}
|
||||
nodeToHTMLWrapEl.innerHTML = ''
|
||||
nodeToHTMLWrapEl.appendChild(node)
|
||||
return nodeToHTMLWrapEl.innerHTML
|
||||
}
|
||||
|
||||
// 获取图片大小
|
||||
export const getImageSize = src => {
|
||||
return new Promise(resolve => {
|
||||
let img = new Image()
|
||||
img.src = src
|
||||
img.onload = () => {
|
||||
resolve({
|
||||
width: img.width,
|
||||
height: img.height
|
||||
})
|
||||
}
|
||||
img.onerror = () => {
|
||||
resolve({
|
||||
width: 0,
|
||||
height: 0
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 创建节点唯一的id
|
||||
export const createUid = () => {
|
||||
return uuidv4()
|
||||
}
|
||||
|
||||
// 加载图片文件
|
||||
export const loadImage = imgFile => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let fr = new FileReader()
|
||||
fr.readAsDataURL(imgFile)
|
||||
fr.onload = async e => {
|
||||
let url = e.target.result
|
||||
let size = await getImageSize(url)
|
||||
resolve({
|
||||
url,
|
||||
size
|
||||
})
|
||||
}
|
||||
fr.onerror = error => {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 移除字符串中的html实体
|
||||
export const removeHTMLEntities = (str) => {
|
||||
[[' ', ' ']].forEach((item) => {
|
||||
str = str.replaceAll(item[0], item[1])
|
||||
})
|
||||
return str
|
||||
}
|
||||
|
||||
// 获取一个数据的类型
|
||||
export const getType = (data) => {
|
||||
return Object.prototype.toString.call(data).slice(7, -1)
|
||||
}
|
||||
|
||||
// 判断一个数据是否是null和undefined和空字符串
|
||||
export const isUndef = (data) => {
|
||||
return data === null || data === undefined || data === ''
|
||||
}
|
||||
|
||||
// 移除html字符串中节点的内联样式
|
||||
export const removeHtmlStyle = (html) => {
|
||||
return html.replaceAll(/(<[^\s]+)\s+style=["'][^'"]+["']\s*(>)/g, '$1$2')
|
||||
}
|
||||
|
||||
// 给html标签中指定的标签添加内联样式
|
||||
export const addHtmlStyle = (html, tag, style) => {
|
||||
const reg = new RegExp(`(<${tag}[^>]*)(>[^<>]*</${tag}>)`, 'g')
|
||||
return html.replaceAll(reg, `$1 style="${style}"$2`)
|
||||
}
|
||||
|
||||
// 检查一个字符串是否是富文本字符
|
||||
let checkIsRichTextEl = null
|
||||
export const checkIsRichText = (str) => {
|
||||
if (!checkIsRichTextEl) {
|
||||
checkIsRichTextEl = document.createElement('div')
|
||||
}
|
||||
checkIsRichTextEl.innerHTML = str
|
||||
for (let c = checkIsRichTextEl.childNodes, i = c.length; i--;) {
|
||||
if (c[i].nodeType == 1) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 搜索和替换html字符串中指定的文本
|
||||
let replaceHtmlTextEl = null
|
||||
export const replaceHtmlText = (html, searchText, replaceText) => {
|
||||
if (!replaceHtmlTextEl) {
|
||||
replaceHtmlTextEl = document.createElement('div')
|
||||
}
|
||||
replaceHtmlTextEl.innerHTML = html
|
||||
let walk = (root) => {
|
||||
let childNodes = root.childNodes
|
||||
childNodes.forEach((node) => {
|
||||
if (node.nodeType === 1) {// 元素节点
|
||||
walk(node)
|
||||
} else if (node.nodeType === 3) {// 文本节点
|
||||
root.replaceChild(document.createTextNode(node.nodeValue.replaceAll(searchText, replaceText)), node)
|
||||
}
|
||||
})
|
||||
}
|
||||
walk(replaceHtmlTextEl)
|
||||
return replaceHtmlTextEl.innerHTML
|
||||
}
|
||||
|
||||
// 判断一个颜色是否是白色
|
||||
export const isWhite = (color) => {
|
||||
color = String(color).replaceAll(/\s+/g, '')
|
||||
return ['#fff', '#ffffff', '#FFF', '#FFFFFF', 'rgb(255,255,255)'].includes(color) || /rgba\(255,255,255,[^)]+\)/.test(color)
|
||||
}
|
||||
|
||||
// 判断一个颜色是否是透明
|
||||
export const isTransparent = (color) => {
|
||||
color = String(color).replaceAll(/\s+/g, '')
|
||||
return ['', 'transparent'].includes(color) || /rgba\(\d+,\d+,\d+,0\)/.test(color)
|
||||
}
|
||||
|
||||
// 从当前主题里获取一个非透明非白色的颜色
|
||||
export const getVisibleColorFromTheme = (themeConfig) => {
|
||||
let { lineColor, root, second, node } = themeConfig
|
||||
let list = [lineColor, root.fillColor, root.color, second.fillColor, second.color, node.fillColor, node.color, root.borderColor, second.borderColor, node.borderColor]
|
||||
for(let i = 0; i < list.length; i++) {
|
||||
let color = list[i]
|
||||
if (!isTransparent(color) && !isWhite(color)) {
|
||||
return color
|
||||
}
|
||||
}
|
||||
}
|
||||
15
web/package-lock.json
generated
@@ -35,7 +35,8 @@
|
||||
"prettier": "^1.19.1",
|
||||
"vconsole": "^3.15.1",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"webpack": "^4.44.2"
|
||||
"webpack": "^4.44.2",
|
||||
"webpack-dynamic-public-path": "^1.0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@achrinza/node-ipc": {
|
||||
@@ -16224,6 +16225,12 @@
|
||||
"decamelize": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-dynamic-public-path": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/webpack-dynamic-public-path/-/webpack-dynamic-public-path-1.0.8.tgz",
|
||||
"integrity": "sha512-AF6onorpvmiC+I/dQ19SOi+oN66oEy9h4deam7gPs1Qa1mOQ9i7IRsOahaukohKAciys7NfX+YFboRn4rmpuKw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/webpack-log": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz",
|
||||
@@ -29476,6 +29483,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"webpack-dynamic-public-path": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/webpack-dynamic-public-path/-/webpack-dynamic-public-path-1.0.8.tgz",
|
||||
"integrity": "sha512-AF6onorpvmiC+I/dQ19SOi+oN66oEy9h4deam7gPs1Qa1mOQ9i7IRsOahaukohKAciys7NfX+YFboRn4rmpuKw==",
|
||||
"dev": true
|
||||
},
|
||||
"webpack-log": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz",
|
||||
|
||||
@@ -6,10 +6,11 @@
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build && node ../copy.js",
|
||||
"lint": "vue-cli-service lint",
|
||||
"buildLibrary": "vue-cli-service build --target lib --name simpleMindMap ../simple-mind-map/full.js --dest ../simple-mind-map/dist && esbuild ../simple-mind-map/full.js --bundle --external:buffer --format=esm --outfile=../simple-mind-map/dist/simpleMindMap.esm.js",
|
||||
"buildLibrary": "vue-cli-service build --target lib --name simpleMindMap ../simple-mind-map/full.js --dest ../simple-mind-map/dist && esbuild ../simple-mind-map/full.js --bundle --external:buffer --format=esm --outfile=../simple-mind-map/dist/simpleMindMap.esm.js && esbuild ../simple-mind-map/full.js --bundle --minify --external:buffer --format=esm --outfile=../simple-mind-map/dist/simpleMindMap.esm.min.js",
|
||||
"format": "prettier --write src/* src/*/* src/*/*/* src/*/*/*/*",
|
||||
"buildDoc": "node ./scripts/buildDoc.js",
|
||||
"autoBuildDoc": "node ./scripts/autoBuildDoc.js"
|
||||
"autoBuildDoc": "node ./scripts/autoBuildDoc.js",
|
||||
"createNodeImageList": "node ./scripts/createNodeImageList.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@toast-ui/editor": "^3.1.5",
|
||||
@@ -39,7 +40,8 @@
|
||||
"prettier": "^1.19.1",
|
||||
"vconsole": "^3.15.1",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"webpack": "^4.44.2"
|
||||
"webpack": "^4.44.2",
|
||||
"webpack-dynamic-public-path": "^1.0.8"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
|
||||
|
Before Width: | Height: | Size: 695 KiB |
@@ -4,8 +4,14 @@
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0">
|
||||
<link rel="icon" href="./dist/logo.png">
|
||||
<title>一个简单的web思维导图实现</title>
|
||||
<link rel="icon" href="dist/logo.ico">
|
||||
<title>思绪思维导图</title>
|
||||
<script>
|
||||
// 自定义静态资源的路径
|
||||
window.externalPublicPath = './dist/'
|
||||
// 接管应用
|
||||
window.takeOverApp = false
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
@@ -13,5 +19,73 @@
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
<script>
|
||||
const getDataFromBackend = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
resolve({
|
||||
mindMapData: {
|
||||
root:{
|
||||
"data": {
|
||||
"text": "根节点"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
theme:{
|
||||
"template":"avocado",
|
||||
"config":{}
|
||||
},
|
||||
layout:"logicalStructure",
|
||||
config: {},
|
||||
view: null,
|
||||
},
|
||||
lang: 'zh',
|
||||
localConfig: null
|
||||
})
|
||||
}, 200)
|
||||
})
|
||||
}
|
||||
const setTakeOverAppMethods = (data) => {
|
||||
window.takeOverAppMethods = {}
|
||||
// 获取思维导图数据的函数
|
||||
window.takeOverAppMethods.getMindMapData = () => {
|
||||
return data.mindMapData
|
||||
}
|
||||
// 保存思维导图数据的函数
|
||||
window.takeOverAppMethods.saveMindMapData = (data) => {
|
||||
console.log(data)
|
||||
}
|
||||
// 获取语言的函数
|
||||
window.takeOverAppMethods.getLanguage = () => {
|
||||
return data.lang
|
||||
}
|
||||
// 保存语言的函数
|
||||
window.takeOverAppMethods.saveLanguage = (lang) => {
|
||||
console.log(lang)
|
||||
}
|
||||
// 获取本地配置的函数
|
||||
window.takeOverAppMethods.getLocalConfig = () => {
|
||||
return data.localConfig
|
||||
}
|
||||
// 保存本地配置的函数
|
||||
window.takeOverAppMethods.saveLocalConfig = (config) => {
|
||||
console.log(config)
|
||||
}
|
||||
}
|
||||
window.onload = async () => {
|
||||
if (!window.takeOverApp) return
|
||||
// 请求数据
|
||||
const data = await getDataFromBackend()
|
||||
// 设置全局的方法
|
||||
setTakeOverAppMethods(data)
|
||||
// 思维导图实例创建完成事件
|
||||
window.$bus.$on('app_inited', (mindMap) => {
|
||||
console.log(mindMap)
|
||||
})
|
||||
// 可以通过window.$bus.$on()来监听应用的一些事件
|
||||
// 实例化页面
|
||||
window.initApp()
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
BIN
web/public/logo.ico
Normal file
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 8.5 KiB |
50
web/scripts/createNodeImageList.js
Normal file
@@ -0,0 +1,50 @@
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
const fileDest = path.join(__dirname, '../src/assets/svg')
|
||||
const targetDest = path.join(__dirname, '../src/config/image.js')
|
||||
|
||||
const run = (dir) => {
|
||||
let dirs = fs.readdirSync(dir)
|
||||
dirs.forEach(item => {
|
||||
let cur = path.join(dir, item)
|
||||
if (fs.statSync(cur).isDirectory()) {
|
||||
walkDir(cur, item)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const list = []
|
||||
const importList = []
|
||||
const walkDir = (dir, item) => {
|
||||
let files = fs.readdirSync(dir)
|
||||
let name = files.find((file) => {
|
||||
return !/\./.test(file)
|
||||
})
|
||||
let fileList = files.filter((file) => {
|
||||
return /\.svg$/.test(file)
|
||||
})
|
||||
let itemList = []
|
||||
fileList.forEach((file) => {
|
||||
let fileName = item + '_' + file.replace(/\.svg$/, '').replaceAll('-', '')
|
||||
importList.push(`import ${fileName} from '../assets/svg/${item}/${file}'`)
|
||||
itemList.push({
|
||||
url: fileName,
|
||||
width: 100,
|
||||
height: 100
|
||||
})
|
||||
})
|
||||
list.push({
|
||||
name,
|
||||
list: itemList
|
||||
})
|
||||
const content = `
|
||||
// 该文件请运行npm run createNodeImageList命令自动生成
|
||||
${importList.join('\n')}
|
||||
export default ${JSON.stringify(list, null, 2).replace(/(url":\s*)"([^"]+)"(,)/g, '$1$2$3')}
|
||||
`
|
||||
fs.writeFileSync(targetDest, content)
|
||||
}
|
||||
|
||||
run(fileDest)
|
||||
console.log('运行成功')
|
||||
BIN
web/src/.DS_Store
vendored
@@ -6,6 +6,8 @@ const SIMPLE_MIND_MAP_DATA = 'SIMPLE_MIND_MAP_DATA'
|
||||
const SIMPLE_MIND_MAP_LANG = 'SIMPLE_MIND_MAP_LANG'
|
||||
const SIMPLE_MIND_MAP_LOCAL_CONFIG = 'SIMPLE_MIND_MAP_LOCAL_CONFIG'
|
||||
|
||||
let mindMapData = null
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-08-02 22:36:48
|
||||
@@ -29,6 +31,10 @@ const copyMindMapTreeData = (tree, root) => {
|
||||
* @Desc: 获取缓存的思维导图数据
|
||||
*/
|
||||
export const getData = () => {
|
||||
if (window.takeOverApp) {
|
||||
mindMapData = window.takeOverAppMethods.getMindMapData()
|
||||
return mindMapData
|
||||
}
|
||||
let store = localStorage.getItem(SIMPLE_MIND_MAP_DATA)
|
||||
if (store === null) {
|
||||
return simpleDeepClone(exampleData)
|
||||
@@ -48,8 +54,18 @@ export const getData = () => {
|
||||
*/
|
||||
export const storeData = data => {
|
||||
try {
|
||||
let originData = getData()
|
||||
let originData = null
|
||||
if (window.takeOverApp) {
|
||||
originData = mindMapData
|
||||
} else {
|
||||
originData = getData()
|
||||
}
|
||||
originData.root = copyMindMapTreeData({}, data)
|
||||
if (window.takeOverApp) {
|
||||
mindMapData = originData
|
||||
window.takeOverAppMethods.saveMindMapData(originData)
|
||||
return
|
||||
}
|
||||
Vue.prototype.$bus.$emit('write_local_file', originData)
|
||||
let dataStr = JSON.stringify(originData)
|
||||
localStorage.setItem(SIMPLE_MIND_MAP_DATA, dataStr)
|
||||
@@ -65,11 +81,21 @@ export const storeData = data => {
|
||||
*/
|
||||
export const storeConfig = config => {
|
||||
try {
|
||||
let originData = getData()
|
||||
let originData = null
|
||||
if (window.takeOverApp) {
|
||||
originData = mindMapData
|
||||
} else {
|
||||
originData = getData()
|
||||
}
|
||||
originData = {
|
||||
...originData,
|
||||
...config
|
||||
}
|
||||
if (window.takeOverApp) {
|
||||
mindMapData = originData
|
||||
window.takeOverAppMethods.saveMindMapData(originData)
|
||||
return
|
||||
}
|
||||
Vue.prototype.$bus.$emit('write_local_file', originData)
|
||||
let dataStr = JSON.stringify(originData)
|
||||
localStorage.setItem(SIMPLE_MIND_MAP_DATA, dataStr)
|
||||
@@ -85,6 +111,10 @@ export const storeConfig = config => {
|
||||
* @Desc: 存储语言
|
||||
*/
|
||||
export const storeLang = lang => {
|
||||
if (window.takeOverApp) {
|
||||
window.takeOverAppMethods.saveLanguage(lang)
|
||||
return
|
||||
}
|
||||
localStorage.setItem(SIMPLE_MIND_MAP_LANG, lang)
|
||||
}
|
||||
|
||||
@@ -95,6 +125,9 @@ export const storeLang = lang => {
|
||||
* @Desc: 获取存储的语言
|
||||
*/
|
||||
export const getLang = () => {
|
||||
if (window.takeOverApp) {
|
||||
return window.takeOverAppMethods.getLanguage() || 'zh'
|
||||
}
|
||||
let lang = localStorage.getItem(SIMPLE_MIND_MAP_LANG)
|
||||
if (lang) {
|
||||
return lang
|
||||
@@ -110,6 +143,9 @@ export const getLang = () => {
|
||||
* @Desc: 存储本地配置
|
||||
*/
|
||||
export const storeLocalConfig = config => {
|
||||
if (window.takeOverApp) {
|
||||
return window.takeOverAppMethods.saveLocalConfig(config)
|
||||
}
|
||||
localStorage.setItem(SIMPLE_MIND_MAP_LOCAL_CONFIG, JSON.stringify(config))
|
||||
}
|
||||
|
||||
@@ -120,6 +156,9 @@ export const storeLocalConfig = config => {
|
||||
* @Desc: 获取本地配置
|
||||
*/
|
||||
export const getLocalConfig = () => {
|
||||
if (window.takeOverApp) {
|
||||
return window.takeOverAppMethods.getLocalConfig()
|
||||
}
|
||||
let config = localStorage.getItem(SIMPLE_MIND_MAP_LOCAL_CONFIG)
|
||||
if (config) {
|
||||
return JSON.parse(config)
|
||||
|
||||
BIN
web/src/assets/.DS_Store
vendored
BIN
web/src/assets/avatar/Chris.jpg
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
web/src/assets/avatar/ZXR.jpg
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
web/src/assets/avatar/default.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
web/src/assets/avatar/qp.jpg
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
web/src/assets/avatar/suka.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
web/src/assets/avatar/仓鼠.jpg
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
web/src/assets/avatar/千帆.jpg
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
web/src/assets/avatar/小土渣的宇宙.jpeg
Normal file
|
After Width: | Height: | Size: 123 KiB |
BIN
web/src/assets/avatar/水车.jpg
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
web/src/assets/avatar/花儿朵朵.jpg
Normal file
|
After Width: | Height: | Size: 54 KiB |
@@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 2479351 */
|
||||
src: url('iconfont.woff2?t=1686298427624') format('woff2'),
|
||||
url('iconfont.woff?t=1686298427624') format('woff'),
|
||||
url('iconfont.ttf?t=1686298427624') format('truetype');
|
||||
src: url('iconfont.woff2?t=1690537337895') format('woff2'),
|
||||
url('iconfont.woff?t=1690537337895') format('woff'),
|
||||
url('iconfont.ttf?t=1690537337895') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@@ -13,6 +13,70 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.iconlieri:before {
|
||||
content: "\e60b";
|
||||
}
|
||||
|
||||
.iconmoon_line:before {
|
||||
content: "\e745";
|
||||
}
|
||||
|
||||
.iconsousuo:before {
|
||||
content: "\e693";
|
||||
}
|
||||
|
||||
.iconjiantouyou:before {
|
||||
content: "\e62d";
|
||||
}
|
||||
|
||||
.iconbianji1:before {
|
||||
content: "\e60a";
|
||||
}
|
||||
|
||||
.icondaohang1:before {
|
||||
content: "\e632";
|
||||
}
|
||||
|
||||
.iconyanjing:before {
|
||||
content: "\e8bf";
|
||||
}
|
||||
|
||||
.iconwangzhan:before {
|
||||
content: "\e628";
|
||||
}
|
||||
|
||||
.iconcsdn:before {
|
||||
content: "\e608";
|
||||
}
|
||||
|
||||
.iconshejiaotubiao-10:before {
|
||||
content: "\e644";
|
||||
}
|
||||
|
||||
.iconstar:before {
|
||||
content: "\e7df";
|
||||
}
|
||||
|
||||
.iconfork:before {
|
||||
content: "\e641";
|
||||
}
|
||||
|
||||
.iconxiazai:before {
|
||||
content: "\e613";
|
||||
}
|
||||
|
||||
.iconteamwork:before {
|
||||
content: "\e870";
|
||||
}
|
||||
|
||||
.iconshuiyin:before {
|
||||
content: "\e67a";
|
||||
}
|
||||
|
||||
.iconxmind:before {
|
||||
content: "\ea57";
|
||||
}
|
||||
|
||||
.iconmouseR:before {
|
||||
content: "\e6bd";
|
||||
}
|
||||
|
||||
BIN
web/src/assets/img/block1.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
web/src/assets/img/block3.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
web/src/assets/img/block4.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
web/src/assets/img/catalogOrganization.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
web/src/assets/img/darkNightLceBlade.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
web/src/assets/img/fishbone.png
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
web/src/assets/img/lemonBubbles.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
web/src/assets/img/logicalStructure.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
web/src/assets/img/logo2.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
web/src/assets/img/mindMap.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
web/src/assets/img/morandi.jpg
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
web/src/assets/img/neonLamp.jpg
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
web/src/assets/img/oreo.jpg
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
web/src/assets/img/organizationStructure.png
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
web/src/assets/img/rose.jpg
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
web/src/assets/img/seaBlueLine.jpg
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
web/src/assets/img/shallowSea.jpg
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
web/src/assets/img/split.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
web/src/assets/img/timeline.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
web/src/assets/img/timeline2.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
web/src/assets/img/verticalTimeline.jpg
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
web/src/assets/img/verticalTimeline.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
1
web/src/assets/svg/business/-.svg
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
1
web/src/assets/svg/business/-_1.svg
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
1
web/src/assets/svg/business/-_10.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M401.9 584.7h16v16h-16zM385.9 600.7h-16v-16h16v16z m-31.9 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-31.9 0h-16v-16h16v16zM146.1 584.7h16v16h-16zM162.1 569h-16v-15.7h16V569z m0-31.5h-16v-15.7h16v15.7z m0-31.5h-16v-15.7h16V506z m0-31.5h-16v-15.7h16v15.7z m0-31.5h-16v-15.7h16V443z m0-31.5h-16v-15.7h16v15.7z m0-31.5h-16v-15.7h16V380z m0-31.5h-16v-15.7h16v15.7z m0-31.5h-16v-15.7h16V317z m0-31.4h-16v-15.7h16v15.7zM146.1 238.1h16v16h-16zM385.9 254.1h-16v-16h16v16z m-31.9 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-31.9 0h-16v-16h16v16zM401.9 238.1h16v16h-16zM417.9 569h-16v-15.7h16V569z m0-31.5h-16v-15.7h16v15.7z m0-31.5h-16v-15.7h16V506z m0-31.5h-16v-15.7h16v15.7z m0-31.5h-16v-15.7h16V443z m0-31.5h-16v-15.7h16v15.7z m0-31.5h-16v-15.7h16V380z m0-31.5h-16v-15.7h16v15.7z m0-31.5h-16v-15.7h16V317z m0-31.4h-16v-15.7h16v15.7zM860.1 682.9h16v16h-16zM844.2 698.9h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-31.9 0h-16v-16h16v16z m-32 0h-16v-16h16v16zM604.3 682.9h16v16h-16zM620.3 667.1h-16v-15.7h16v15.7z m0-31.4h-16V620h16v15.7z m0-31.5h-16v-15.7h16v15.7z m0-31.5h-16V557h16v15.7z m0-31.5h-16v-15.7h16v15.7z m0-31.5h-16V494h16v15.7z m0-31.5h-16v-15.7h16v15.7z m0-31.5h-16V431h16v15.7z m0-31.5h-16v-15.7h16v15.7z m0-31.5h-16V368h16v15.7zM604.3 336.2h16v16h-16zM844.2 352.2h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-31.9 0h-16v-16h16v16z m-32 0h-16v-16h16v16zM860.1 336.2h16v16h-16zM876.1 667.1h-16v-15.7h16v15.7z m0-31.4h-16V620h16v15.7z m0-31.5h-16v-15.7h16v15.7z m0-31.5h-16V557h16v15.7z m0-31.5h-16v-15.7h16v15.7z m0-31.5h-16V494h16v15.7z m0-31.5h-16v-15.7h16v15.7z m0-31.5h-16V431h16v15.7z m0-31.5h-16v-15.7h16v15.7z m0-31.5h-16V368h16v15.7z" fill="#0A0408" /><path d="M246.9 107.8h531.6v531.6H246.9z" fill="#FFFFFF" /><path d="M786.5 647.4H238.9V99.8h547.6v547.6z m-531.6-16h515.6V115.8H254.9v515.6z" fill="#0A0408" /><path d="M305.1 166h415.1v415.1H305.1z" fill="#55B7A8" /><path d="M389.9 238.2h195.3v16H389.9zM384.6 338.8h256.2v16H384.6zM384.6 441.2h256.2v16H384.6z" fill="#0A0408" /><path d="M868.8 918H156.5l-50-415.4h812.3z" fill="#FFFFFF" /><path d="M875.9 926H149.4L97.5 494.6h830.4l-52 431.4z m-712.3-16h698.1l48.1-399.4H115.5L163.6 910z" fill="#0A0408" /><path d="M809.6 866.5H215.8L174.1 554h677.2z" fill="#F4BE6F" /><path d="M154.1 639.7h154.3v16H154.1zM154.1 717.9h258.3v16H154.1z" fill="#FFFFFF" /><path d="M842.7 318.8h50.9v50.9h-50.9z" fill="#DC444A" /><path d="M901.6 377.7h-66.9v-66.9h66.9v66.9z m-50.9-16h34.9v-34.9h-34.9v34.9z" fill="#0A0408" /><path d="M128.6 220.8h50.9v50.9h-50.9z" fill="#DC444A" /><path d="M187.5 279.7h-66.9v-66.9h66.9v66.9z m-50.9-16h34.9v-34.9h-34.9v34.9z" fill="#0A0408" /></svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
1
web/src/assets/svg/business/-_11.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M153.9 105.9h715.4v812.8H153.9z" fill="#55B7A8" /><path d="M877.3 926.8H145.9V97.9h731.4v828.9z m-715.4-16h699.4V113.9H161.9v796.9z" fill="#0A0408" /><path d="M221.3 182.9h580.5v658.8H221.3z" fill="#FFFFFF" /><path d="M793.8 833.8h16v16h-16zM777.7 849.8h-16.1v-16h16.1v16z m-32.3 0h-16.1v-16h16.1v16z m-32.2 0h-16.1v-16h16.1v16z m-32.3 0h-16.1v-16h16.1v16z m-32.2 0h-16.1v-16h16.1v16z m-32.3 0h-16.1v-16h16.1v16z m-32.2 0H568v-16h16.1v16z m-32.3 0h-16.1v-16h16.1v16z m-32.3 0h-16.1v-16h16.1v16z m-32.2 0h-16.1v-16h16.1v16z m-32.3 0H439v-16h16.1v16z m-32.2 0h-16.1v-16h16.1v16z m-32.3 0h-16.1v-16h16.1v16z m-32.2 0h-16.1v-16h16.1v16z m-32.3 0H310v-16h16.1v16z m-32.3 0h-16.1v-16h16.1v16z m-32.2 0h-16.1v-16h16.1v16zM213.3 833.8h16v16h-16zM229.3 818.1h-16v-15.7h16v15.7z m0-31.4h-16V771h16v15.7z m0-31.3h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16V724z m0-31.3h-16V677h16v15.7z m0-31.4h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16v15.7z m0-31.3h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16v15.7z m0-31.3h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16v15.7z m0-31.3h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16v15.7z m0-31.3h-16v-15.7h16v15.7z m0-31.4h-16V332h16v15.7z m0-31.3h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16V285z m0-31.4h-16V238h16v15.6z m0-31.3h-16v-15.7h16v15.7zM213.3 174.9h16v16h-16zM777.7 190.9h-16.1v-16h16.1v16z m-32.3 0h-16.1v-16h16.1v16z m-32.2 0h-16.1v-16h16.1v16z m-32.3 0h-16.1v-16h16.1v16z m-32.2 0h-16.1v-16h16.1v16z m-32.3 0h-16.1v-16h16.1v16z m-32.2 0H568v-16h16.1v16z m-32.3 0h-16.1v-16h16.1v16z m-32.3 0h-16.1v-16h16.1v16z m-32.2 0h-16.1v-16h16.1v16z m-32.3 0H439v-16h16.1v16z m-32.2 0h-16.1v-16h16.1v16z m-32.3 0h-16.1v-16h16.1v16z m-32.2 0h-16.1v-16h16.1v16z m-32.3 0H310v-16h16.1v16z m-32.3 0h-16.1v-16h16.1v16z m-32.2 0h-16.1v-16h16.1v16zM793.8 174.9h16v16h-16zM809.8 818.1h-16v-15.7h16v15.7z m0-31.4h-16V771h16v15.7z m0-31.3h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16V724z m0-31.3h-16V677h16v15.7z m0-31.4h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16v15.7z m0-31.3h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16v15.7z m0-31.3h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16v15.7z m0-31.3h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16v15.7z m0-31.3h-16v-15.7h16v15.7z m0-31.4h-16V332h16v15.7z m0-31.3h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16V285z m0-31.4h-16V238h16v15.6z m0-31.3h-16v-15.7h16v15.7z" fill="#0A0408" /><path d="M364.5 306.6m-44.8 0a44.8 44.8 0 1 0 89.6 0 44.8 44.8 0 1 0-89.6 0Z" fill="#DC444A" /><path d="M364.5 359.4c-29.1 0-52.8-23.7-52.8-52.8s23.7-52.8 52.8-52.8 52.8 23.7 52.8 52.8-23.7 52.8-52.8 52.8z m0-89.6c-20.3 0-36.8 16.5-36.8 36.8s16.5 36.8 36.8 36.8 36.8-16.5 36.8-36.8-16.5-36.8-36.8-36.8zM459.3 262.6h144.1v16H459.3zM459.3 332.2h244.1v16H459.3z" fill="#0A0408" /><path d="M364.5 516.3m-44.8 0a44.8 44.8 0 1 0 89.6 0 44.8 44.8 0 1 0-89.6 0Z" fill="#DC444A" /><path d="M364.5 569.1c-29.1 0-52.8-23.7-52.8-52.8s23.7-52.8 52.8-52.8 52.8 23.7 52.8 52.8-23.7 52.8-52.8 52.8z m0-89.6c-20.3 0-36.8 16.5-36.8 36.8 0 20.3 16.5 36.8 36.8 36.8s36.8-16.5 36.8-36.8c0-20.3-16.5-36.8-36.8-36.8zM459.3 472.3h144.1v16H459.3zM459.3 541.9h244.1v16H459.3z" fill="#0A0408" /><path d="M364.5 726m-44.8 0a44.8 44.8 0 1 0 89.6 0 44.8 44.8 0 1 0-89.6 0Z" fill="#DC444A" /><path d="M364.5 778.8c-29.1 0-52.8-23.7-52.8-52.8s23.7-52.8 52.8-52.8 52.8 23.7 52.8 52.8-23.7 52.8-52.8 52.8z m0-89.6c-20.3 0-36.8 16.5-36.8 36.8 0 20.3 16.5 36.8 36.8 36.8s36.8-16.5 36.8-36.8c0-20.3-16.5-36.8-36.8-36.8zM459.3 682h144.1v16H459.3zM459.3 751.6h244.1v16H459.3z" fill="#0A0408" /><path d="M359 72.4h305.2v75.9H359z" fill="#EBB866" /><path d="M672.2 156.2H351V64.4h321.2v91.8z m-305.2-16h289.2V80.4H367v59.8z" fill="#0A0408" /><path d="M808.3 807.9m-141.7 0a141.7 141.7 0 1 0 283.4 0 141.7 141.7 0 1 0-283.4 0Z" fill="#EBB866" /><path d="M808.3 957.6c-82.5 0-149.7-67.1-149.7-149.7s67.1-149.7 149.7-149.7S958 725.4 958 807.9s-67.2 149.7-149.7 149.7z m0-283.4c-73.7 0-133.7 60-133.7 133.7s60 133.7 133.7 133.7S942 881.6 942 807.9s-60-133.7-133.7-133.7z" fill="#0A0408" /><path d="M810.3 727.1l26 52.5 58 8.5-42 40.9 9.9 57.8-51.9-27.3-51.9 27.3 9.9-57.8-41.9-40.9 58-8.5z" fill="#FFFFFF" /><path d="M872.8 901.4l-62.5-32.9-62.5 32.9 11.9-69.6-50.6-49.3 69.9-10.2 31.3-63.3 31.3 63.3 69.9 10.2-50.6 49.3 11.9 69.6z m-62.5-51l41.3 21.7-7.9-45.9 33.4-32.5L831 787l-20.6-41.8-20.7 41.8-46.1 6.7 33.4 32.5-7.9 45.9 41.2-21.7z" fill="#0A0408" /></svg>
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
1
web/src/assets/svg/business/-_12.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M74 183.7h216V918H74z" fill="#55B7A8" /><path d="M298 926H66V175.7h232V926zM82 910h200V191.7H82V910z" fill="#0A0408" /><path d="M125.6 246.6h116.8v229.2H125.6z" fill="#FFFFFF" /><path d="M250.4 483.8H117.6V238.6h132.8v245.2z m-116.8-16h100.8V254.6H133.6v213.2z" fill="#0A0408" /><path d="M178.8 783.9m-55.2 0a55.2 55.2 0 1 0 110.4 0 55.2 55.2 0 1 0-110.4 0Z" fill="#FFFFFF" /><path d="M178.8 847.1c-34.8 0-63.2-28.3-63.2-63.2s28.3-63.2 63.2-63.2c34.8 0 63.2 28.3 63.2 63.2s-28.4 63.2-63.2 63.2z m0-110.4c-26 0-47.2 21.2-47.2 47.2s21.2 47.2 47.2 47.2 47.2-21.2 47.2-47.2-21.2-47.2-47.2-47.2z" fill="#0A0408" /><path d="M519.4 224.2L728 168.3l190.1 709.3-208.6 55.9" fill="#FFFFFF" /><path d="M517.346 216.397l7.727-2.07 4.14 15.454-7.727 2.07zM544.1 225.8l-4.1-15.5 14.9-4 4.1 15.5-14.9 4z m29.7-7.9l-4.1-15.5 14.9-4 4.1 15.5-14.9 4z m29.7-8l-4.1-15.5 14.9-4 4.1 15.5-14.9 4z m29.7-8l-4.1-15.5 14.9-4 4.1 15.5-14.9 4zM663 194l-4.1-15.5 14.9-4 4.1 15.5-14.9 4z m29.7-8l-4.1-15.5 14.9-4 4.1 15.5-14.9 4zM722.4 178.1l-4.2-15.5 15.5-4.1 4.1 15.4zM904.2 856.5l-4.1-15.4 15.5-4.1 4.1 15.4-15.5 4.1z m-8.3-30.8l-4.1-15.4 15.5-4.1 4.1 15.4-15.5 4.1z m-8.3-30.9l-4.1-15.4 15.5-4.1 4.1 15.4-15.5 4.1z m-8.2-30.8l-4.1-15.4 15.5-4.1 4.1 15.4-15.5 4.1z m-8.3-30.8l-4.1-15.4 15.5-4.1 4.1 15.4-15.5 4.1z m-8.2-30.9l-4.1-15.4 15.5-4.1 4.1 15.4-15.5 4.1z m-8.3-30.8l-4.1-15.4L866 652l4.1 15.4-15.5 4.1z m-8.3-30.9l-4.1-15.4 15.5-4.1 4.1 15.4-15.5 4.1z m-8.2-30.8l-4.1-15.4 15.5-4.1 4.1 15.4-15.5 4.1z m-8.3-30.8l-4.1-15.4 15.5-4.1 4.1 15.4-15.5 4.1z m-8.3-30.9l-4.1-15.4 15.5-4.1L837 544l-15.5 4.1z m-8.2-30.8l-4.1-15.4 15.5-4.1 4.1 15.4-15.5 4.1z m-8.3-30.9l-4.1-15.4 15.5-4.1 4.1 15.4-15.5 4.1z m-8.2-30.8l-4.1-15.4 15.5-4.1 4.1 15.4-15.5 4.1z m-8.3-30.8l-4.1-15.4 15.5-4.1 4.1 15.4-15.5 4.1z m-8.3-30.9l-4.1-15.4 15.5-4.1 4.1 15.4-15.5 4.1z m-8.2-30.8l-4.1-15.4 15.5-4.1 4.1 15.4-15.5 4.1z m-8.3-30.8l-4.1-15.4 15.5-4.1 4.1 15.4-15.5 4.1z m-8.3-30.9l-4.1-15.4 15.5-4.1 4.1 15.4-15.5 4.1z m-8.2-30.8l-4.1-15.4 15.5-4.1 4.1 15.4-15.5 4.1z m-8.3-30.9l-4.1-15.4 15.5-4.1 4.1 15.4-15.5 4.1z m-8.3-30.8l-4.1-15.4 15.5-4.1 4.1 15.4-15.5 4.1zM908.303 871.94l15.454-4.142 4.143 15.455-15.455 4.142zM734.2 935.1l-4.1-15.5 14.9-4 4.1 15.5-14.9 4z m29.7-7.9l-4.1-15.5 14.9-4 4.1 15.5-14.9 4z m29.7-8l-4.1-15.5 14.9-4 4.1 15.5-14.9 4z m29.7-7.9l-4.1-15.5 14.9-4 4.1 15.5-14.9 4z m29.7-8l-4.1-15.5 14.9-4 4.1 15.5-14.9 4z m29.7-8l-4.1-15.5 14.9-4 4.1 15.5-14.9 4zM707.443 925.66l7.727-2.07 4.141 15.454-7.727 2.07z" fill="#0A0408" /><path d="M583.619 272.12l112.817-30.229 59.317 221.385-112.817 30.228z" fill="#FFFFFF" /><path d="M637.3 503.3l-63.5-236.8L702.1 232l63.5 236.8-128.3 34.5z m-43.9-225.6l55.2 205.9 97.4-26.1-55.2-205.9-97.4 26.1z" fill="#0A0408" /><path d="M722.664239 791.097256a55.2 55.2 0 1 0 106.63871-28.571762 55.2 55.2 0 1 0-106.63871 28.571762Z" fill="#FFFFFF" /><path d="M776 840c-27.9 0-53.5-18.6-61-46.8-9-33.7 11-68.4 44.7-77.4s68.4 11 77.4 44.7c9 33.6-11 68.4-44.7 77.4-5.5 1.4-11 2.1-16.4 2.1z m0-110.4c-4 0-8.1 0.5-12.2 1.6-25.1 6.7-40.1 32.7-33.4 57.8 6.7 25.1 32.7 40.1 57.8 33.4 25.1-6.7 40.1-32.7 33.4-57.8-5.7-21-24.8-35-45.6-35z" fill="#0A0408" /><path d="M888.182 860.34l47.04-12.603 12.603 47.04-47.04 12.603z" fill="#DC444A" /><path d="M895.2 917.2l-16.8-62.5 62.5-16.8 16.8 62.5-62.5 16.8zM898 866l8.5 31.6 31.6-8.5-8.5-31.6L898 866z" fill="#0A0408" /><path d="M698.202 151.04l47.04-12.604 12.603 47.04-47.04 12.603z" fill="#DC444A" /><path d="M705.1 207.9l-16.8-62.5 62.5-16.8 16.8 62.5-62.5 16.8z m2.9-51.2l8.5 31.6 31.6-8.5-8.5-31.6-31.6 8.5z" fill="#0A0408" /><path d="M291.4 183.7h216V918h-216z" fill="#EBB866" /><path d="M515.4 926h-232V175.7h232V926z m-216-16h200V191.7h-200V910z" fill="#0A0408" /><path d="M343 246.6h116.8v229.2H343z" fill="#FFFFFF" /><path d="M467.8 483.8H335V238.6h132.8v245.2z m-116.8-16h100.8V254.6H351v213.2z" fill="#0A0408" /><path d="M396.2 783.9m-55.2 0a55.2 55.2 0 1 0 110.4 0 55.2 55.2 0 1 0-110.4 0Z" fill="#FFFFFF" /><path d="M396.2 847.1c-34.8 0-63.2-28.3-63.2-63.2s28.3-63.2 63.2-63.2c34.8 0 63.2 28.3 63.2 63.2s-28.4 63.2-63.2 63.2z m0-110.4c-26 0-47.2 21.2-47.2 47.2s21.2 47.2 47.2 47.2 47.2-21.2 47.2-47.2-21.2-47.2-47.2-47.2z" fill="#0A0408" /><path d="M712.5 941.5L518.4 217.2" fill="#FFFFFF" /><path d="M510.727 219.228l15.454-4.141 194.074 724.328-15.454 4.141z" fill="#0A0408" /></svg>
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
1
web/src/assets/svg/business/-_13.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M96.4 364.2h830.3v529.5H96.4z" fill="#FFFFFF" /><path d="M926.7 901.7h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-22.3-9.7h-16v-16h16v16z m830.3-13.9h-16v-16h16v16zM104.4 860h-16v-16h16v16z m830.3-13.9h-16v-16h16v16zM104.4 828h-16v-16h16v16z m830.3-13.9h-16v-16h16v16zM104.4 796h-16v-16h16v16z m830.3-13.9h-16v-16h16v16zM104.4 764h-16v-16h16v16z m830.3-13.9h-16v-16h16v16zM104.4 732h-16v-16h16v16z m830.3-13.9h-16v-16h16v16zM104.4 700h-16v-16h16v16z m830.3-13.9h-16v-16h16v16zM104.4 668h-16v-16h16v16z m830.3-13.9h-16v-16h16v16zM104.4 636h-16v-16h16v16z m830.3-13.9h-16v-16h16v16zM104.4 604h-16v-16h16v16z m830.3-13.9h-16v-16h16v16zM104.4 572h-16v-16h16v16z m830.3-13.9h-16v-16h16v16zM104.4 540h-16v-16h16v16z m830.3-13.9h-16v-16h16v16zM104.4 508h-16v-16h16v16z m830.3-13.9h-16v-16h16v16zM104.4 476h-16v-16h16v16z m830.3-13.9h-16v-16h16v16zM104.4 444h-16v-16h16v16z m830.3-13.9h-16v-16h16v16zM104.4 412h-16v-16h16v16z m830.3-13.9h-16v-16h16v16zM104.4 380h-16v-23.8h8.2v8h7.8V380z m822.3-7.8h-14.1v-16h22.1v9.9h-8v6.1z m-30.1 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32 0h-16v-16h16v16z" fill="#0A0408" /><path d="M172.6 438.3h677.8v381.4H172.6z" fill="#F4BE6F" /><path d="M96.4 258.8h830.3v105.9H96.4z" fill="#55B7A8" /><path d="M934.7 372.7H88.4V250.8h846.3v121.9z m-830.3-16h814.3v-89.9H104.4v89.9z" fill="#0A0408" /><path d="M457.3 107.2h108.5v203.7H457.3z" fill="#EBB866" /><path d="M573.8 318.9H449.3V99.2h124.5v219.7z m-108.5-16h92.5V115.2h-92.5v187.7z" fill="#0A0408" /><path d="M308.7 560.2m-65.9 0a65.9 65.9 0 1 0 131.8 0 65.9 65.9 0 1 0-131.8 0Z" fill="#FFFFFF" /><path d="M308.7 634.1c-40.8 0-73.9-33.2-73.9-73.9s33.2-73.9 73.9-73.9 73.9 33.2 73.9 73.9-33.1 73.9-73.9 73.9z m0-131.9c-32 0-57.9 26-57.9 57.9s26 57.9 57.9 57.9 57.9-26 57.9-57.9-25.9-57.9-57.9-57.9z" fill="#0A0408" /><path d="M418.7 767.9c0-60.7-49.2-109.9-109.9-109.9s-109.9 49.2-109.9 109.9h219.8z" fill="#FFFFFF" /><path d="M426.7 775.9H190.8v-8c0-65 52.9-117.9 117.9-117.9s117.9 52.9 117.9 117.9v8z m-219.6-16h203.3c-4.1-52.5-48.1-93.9-101.6-93.9s-97.6 41.4-101.7 93.9zM457.3 662.8h313.9v16H457.3zM457.3 751.2h261.8v16H457.3z" fill="#0A0408" /><path d="M457.3 512.7h313.9v65.9H457.3z" fill="#FFFFFF" /><path d="M779.2 586.7H449.3v-81.9h329.9v81.9z m-313.9-16h297.9v-49.9H465.3v49.9z" fill="#0A0408" /><path d="M512 258.6m-20.3 0a20.3 20.3 0 1 0 40.6 0 20.3 20.3 0 1 0-40.6 0Z" fill="#FFFFFF" /><path d="M512 287c-15.6 0-28.3-12.7-28.3-28.3s12.7-28.3 28.3-28.3 28.3 12.7 28.3 28.3S527.6 287 512 287z m0-40.7c-6.8 0-12.3 5.5-12.3 12.3S505.2 271 512 271s12.3-5.5 12.3-12.3-5.5-12.4-12.3-12.4z" fill="#0A0408" /><path d="M71.5 868.8h49.8v49.8H71.5z" fill="#DC504F" /><path d="M129.2 926.6H63.5v-65.8h65.8v65.8z m-49.7-16h33.8v-33.8H79.5v33.8z" fill="#0A0408" /><path d="M899.4 868.8h49.8v49.8h-49.8z" fill="#DC504F" /><path d="M957.1 926.6h-65.8v-65.8h65.8v65.8z m-49.7-16h33.8v-33.8h-33.8v33.8z" fill="#0A0408" /></svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
1
web/src/assets/svg/business/-_14.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M886.6 657.2h-16.8v-7.6l-0.7-7.6 0.7-0.1v-0.7h7.7l7.6-0.7zM853.8 657.2h-16v-16h16v16z m-32.1 0h-16v-16h16v16z m-32.1 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32.1 0h-16v-16h16v16z m-32.1 0h-16v-16h16v16z m-32.1 0h-16v-16h16v16z m-32.1 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32.1 0h-16v-16h16v16z m-32.1 0h-16v-16h16v16z m-32.1 0h-16v-16h16v16z m-32 0h-16v-16h16v16zM436.8 657.2h-18.2l4.3-17.7 6.9 1.7h7v1.8l1.7 0.4-1.7 6.8zM442.3 627.7l-15.5-3.8 3.9-15.7 15.5 3.8-3.9 15.7z m7.8-31.4l-15.5-3.8 3.9-15.7 15.5 3.8-3.9 15.7z m7.7-31.4l-15.5-3.8 3.9-15.7 15.5 3.8-3.9 15.7z m7.7-31.4l-15.5-3.8 3.9-15.7 15.5 3.8-3.9 15.7z m7.8-31.4l-15.5-3.8 3.9-15.7 15.5 3.8-3.9 15.7z m7.7-31.4l-15.5-3.8 3.9-15.7 15.5 3.8-3.9 15.7z m7.7-31.4l-15.5-3.8 3.9-15.7 15.5 3.8-3.9 15.7z m7.7-31.5l-15.5-3.8 3.9-15.7 15.5 3.8-3.9 15.7zM504.2 376.4l-15.6-3.8 3.4-13.8h14.3v16h-1.7zM923.3 374.8h-16v-16h16v16z m-32.1 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32.1 0h-16v-16h16v16z m-32.1 0h-16v-16h16v16z m-32.1 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32.1 0h-16v-16h16v16z m-32.1 0h-16v-16h16v16z m-32.1 0h-16v-16h16v16z m-32.1 0h-16v-16h16v16z m-32 0h-16v-16h16v16z m-32.1 0h-16v-16h16v16zM950.2 377.7l-5-2.9h-5.8v-3.4l-3-1.8 3-5v-5.8h21.9zM867.8 626.8l-1.4-15.2 15.9-1.4 1.4 15.2-15.9 1.4z m-2.8-30.3l-1.4-15.2 15.9-1.4 1.4 15.2-15.9 1.4z m-2.8-30.3l-1.4-15.2 15.9-1.4 1.4 15.2-15.9 1.4z m-2.7-30.4l-1.4-15.2 15.9-1.4 1.4 15.2-15.9 1.4z m14-26.6l-13.8-8.1 7.7-13.2 13.8 8.1-7.7 13.2z m15.4-26.3l-13.8-8.1 7.7-13.2 13.8 8.1-7.7 13.2z m15.3-26.3l-13.8-8.1 7.7-13.2 13.8 8.1-7.7 13.2z m15.3-26.3l-13.8-8.1 7.7-13.2 13.8 8.1-7.7 13.2z m15.4-26.3l-13.8-8.1 7.7-13.2 13.8 8.1-7.7 13.2z" fill="#0A0408" /><path d="M795.3 649.2h-413l69.5-282.4h413.1L778.5 508z" fill="#EBB866" /><path d="M804.3 657.2H372.1l73.5-298.5h433.6l-92.4 151 17.5 147.5z m-411.8-16h393.8l-16.1-135 80.4-131.4H458.1l-65.6 266.4z" fill="#0A0408" /><path d="M238.4 855.7h-66.1l202.5-750.5H441z" fill="#55B7A8" /><path d="M244.5 863.7h-82.7L368.7 97.2h82.7L244.5 863.7z m-61.8-16h49.6l198.2-734.5H381L182.7 847.7z" fill="#0A0408" /><path d="M693.7 508h-453l69.5-282.5h453z" fill="#EBB866" /><path d="M700 516H230.5L304 217.5h469.5L700 516z m-449.1-16h436.6L753 233.5H316.5L250.9 500z" fill="#0A0408" /><path d="M115.6 850.1h189.8v68.8H115.6z" fill="#68A4D9" /><path d="M313.4 926.9H107.6v-84.8h205.8v84.8z m-189.8-16h173.8v-52.8H123.6v52.8z" fill="#0A0408" /><path d="M65.6 910.9h289.8v16H65.6zM278.6 358.8h232.7v16H278.6z" fill="#0A0408" /></svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
1
web/src/assets/svg/business/-_15.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M548.1 455.7h-72l-36 39.8 36 39.9h72l36-39.9z" fill="#DC444A" /><path d="M551.7 543.4h-79.1l-43.2-47.9 43.2-47.9h79.1l43.2 47.9-43.2 47.9z m-72-16h64.9l28.8-31.9-28.8-31.9h-64.9l-28.8 31.9 28.8 31.9z" fill="#0A0408" /><path d="M512.1 899.4l102.5-110.5-64.4-253.6h-76.1l-64.5 253.6z" fill="#FFFFFF" /><path d="M512.1 911.2l-111.3-120 67.1-263.8h88.5l67.1 263.8-111.4 120z m-93.7-124.6l93.7 101 93.7-101-61.9-243.4h-63.6l-61.9 243.4z" fill="#0A0408" /><path d="M512.1 843.5l72.4-78L539 586.6h-53.7l-45.5 178.9z" fill="#DC444A" /><path d="M512.1 240.3m-167.3 0a167.3 167.3 0 1 0 334.6 0 167.3 167.3 0 1 0-334.6 0Z" fill="#EBB866" /><path d="M512.1 415.5c-96.6 0-175.3-78.6-175.3-175.3S415.5 65 512.1 65c96.6 0 175.3 78.6 175.3 175.3s-78.6 175.2-175.3 175.2z m0-334.5c-87.8 0-159.3 71.4-159.3 159.3s71.4 159.3 159.3 159.3c87.8 0 159.3-71.4 159.3-159.3S599.9 81 512.1 81z" fill="#0A0408" /><path d="M880.3 950H143.9V645.6c0-61.2 116.2-144.2 177.4-144.2L512 774.7l190.7-273.3c61.2 0 177.4 82.9 177.4 144.2V950z" fill="#FFFFFF" /><path d="M888.3 958H135.9V645.6c0-43.4 48.2-85.4 68.9-101.4 39.9-30.9 85.7-50.8 116.6-50.8h4.2l186.6 267.3 186.6-267.3h4.2c30.9 0 76.7 19.9 116.6 50.8 20.7 16 68.9 57.9 68.9 101.4V958z m-736.4-16h720.4V645.6c0-25.2-23.4-58.4-62.6-88.7-35.5-27.4-75.2-45.6-102.7-47.3L512.1 788.8 317.3 509.6c-27.5 1.7-67.2 19.9-102.7 47.3-39.2 30.3-62.6 63.5-62.6 88.7V942z" fill="#0A0408" /><path d="M441.2 896.3H209.6V704.8c0-38.5 31.2-90.7 69.8-90.7h45.3l116.6 172v110.2zM574.6 896.3h231.7V704.8c0-38.5-31.2-90.7-69.8-90.7h-45.3L574.6 786v110.3z" fill="#55B7A8" /><path d="M520.1 930.4h-16v-15.2h16v15.2z m0-31.2h-16v-16h16v16z m0-32h-16v-16h16v16z m0-32h-16v-16h16v16z m0-32h-16v-16h16v16z m0-32h-16v-16h16v16z" fill="#0A0408" /></svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
1
web/src/assets/svg/business/-_16.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M841.7 915.2L518.5 797.3 183.1 915.2V107.1h658.6z" fill="#FFFFFF" /><path d="M849.7 926.7L518.4 805.8 175.1 926.5V99.1h674.5v827.6zM191.1 115.1v788.8l327.4-115.1 315.1 115V115.1H191.1z" fill="#0A0408" /><path d="M801.6 868.3l-18.2-6.6 2.2-6.2v-6.6h2.5l0.8-2.3 6.2 2.3h6.5zM256.2 856.6l-5.3-15.1 14.8-5.2 5.3 15.1-14.8 5.2z m512.4-0.3l-14.8-5.4 5.5-15 14.8 5.4-5.5 15z m-482.8-10.2l-5.3-15.1 14.8-5.2 5.3 15.1-14.8 5.2z m453.3-0.6l-14.8-5.4 5.5-15 14.8 5.4-5.5 15z m-423.6-9.8l-5.3-15.1 14.8-5.2 5.3 15.1-14.8 5.2z m394.1-1l-14.8-5.4 5.5-15 14.8 5.4-5.5 15z m-364.5-9.4l-5.3-15.1 14.8-5.2 5.3 15.1-14.8 5.2z m334.9-1.4l-14.8-5.4 5.5-15 14.8 5.4-5.5 15z m-305.2-9.1l-5.3-15.1 14.8-5.2 5.3 15.1-14.8 5.2z m275.7-1.6l-14.8-5.4 5.5-15 14.8 5.4-5.5 15z m-246.1-8.8l-5.3-15.1 14.8-5.2 5.3 15.1-14.8 5.2z m216.6-2l-14.8-5.4 5.5-15 14.8 5.4-5.5 15zM434.1 794l-5.3-15.1 14.8-5.2 5.3 15.1-14.8 5.2z m157.4-2.4l-14.8-5.4 5.5-15 14.8 5.4-5.5 15z m-127.8-8l-5.3-15.1 14.8-5.2 5.3 15.1-14.8 5.2z m98.2-2.8l-14.8-5.4 5.5-15 14.8 5.4-5.5 15z m-68.5-7.7l-5.3-15.1 14.8-5.2 5.3 15.1-14.8 5.2z m39-3l-14.8-5.4 5.5-15 14.8 5.4-5.5 15zM223.2 868.2v-19.3h6.6l6.2-2.2 0.8 2.2h2.4v6.6l2.2 6.3zM239.2 833.2h-16v-15.7h16v15.7z m0-31.4h-16V786h16v15.8z m0-31.5h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16v15.7z m0-31.4h-16V629h16v15.7z m0-31.4h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16v15.7z m0-31.5h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16V519z m0-31.4h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16V362z m0-31.5h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16v15.7z m0-31.4h-16V252h16v15.7z m0-31.4h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16v15.7zM223.2 157.5h16v16h-16zM770 173.5h-15.6v-16H770v16z m-31.2 0h-15.6v-16h15.6v16z m-31.2 0H692v-16h15.6v16z m-31.3 0h-15.6v-16h15.6v16z m-31.2 0h-15.6v-16h15.6v16z m-31.2 0h-15.6v-16h15.6v16z m-31.2 0H567v-16h15.6v16z m-31.3 0h-15.6v-16h15.6v16z m-31.2 0h-15.6v-16h15.6v16z m-31.2 0h-15.6v-16H489v16z m-31.3 0h-15.6v-16h15.6v16z m-31.2 0h-15.6v-16h15.6v16z m-31.2 0h-15.6v-16h15.6v16z m-31.2 0h-15.6v-16h15.6v16z m-31.3 0h-15.6v-16h15.6v16z m-31.2 0H286v-16h15.6v16z m-31.2 0h-15.6v-16h15.6v16zM785.6 157.5h16v16h-16zM801.6 833.2h-16v-15.7h16v15.7z m0-31.4h-16V786h16v15.8z m0-31.5h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16v15.7z m0-31.4h-16V629h16v15.7z m0-31.4h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16v15.7z m0-31.5h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16V519z m0-31.4h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16V362z m0-31.5h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16v15.7z m0-31.4h-16V252h16v15.7z m0-31.4h-16v-15.7h16v15.7z m0-31.4h-16v-15.7h16v15.7z" fill="#0A0408" /><path d="M752.2 791.2l-235.4-85.9-244.2 85.9V196.6h479.6z" fill="#55B7A8" /><path d="M512.4 305.7l42.9 86.8 95.8 13.9-69.4 67.6 16.4 95.5-85.7-45.1-85.7 45.1 16.4-95.5-69.4-67.6 95.8-13.9z" fill="#EBB866" /><path d="M608.7 584.1l-96.3-50.6-96.3 50.6 18.4-107.3-77.9-76 107.7-15.7 48.2-97.6 48.2 97.6 107.7 15.7-77.9 76 18.2 107.3z m-96.3-68.7l75.1 39.5-14.3-83.6 60.7-59.2-83.9-12.2-37.5-76.1-37.5 76.1-84.1 12.1 60.7 59.2-14.3 83.6 75.1-39.4z" fill="#0A0408" /><path d="M205.7 140h50.9v50.9h-50.9z" fill="#DC444A" /><path d="M264.6 198.9h-66.9V132h66.9v66.9z m-50.9-16h34.9V148h-34.9v34.9z" fill="#0A0408" /><path d="M768.2 140h50.9v50.9h-50.9z" fill="#DC444A" /><path d="M827.1 198.9h-66.9V132h66.9v66.9z m-50.9-16h34.9V148h-34.9v34.9z" fill="#0A0408" /></svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |