mirror of
https://github.com/wanglin2/mind-map.git
synced 2026-02-24 18:38:26 +08:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c789a95e4e | ||
|
|
b0532072c2 | ||
|
|
7d21ae4618 | ||
|
|
0e608de9da | ||
|
|
fbff68c635 | ||
|
|
ec9517c491 | ||
|
|
26e3158bd8 | ||
|
|
c2c9de1c03 | ||
|
|
27d2238d20 | ||
|
|
01dc98f1f8 | ||
|
|
cbd07246bd | ||
|
|
5bb23ca738 | ||
|
|
0886ba7698 | ||
|
|
a65cffa58b | ||
|
|
02e2d432dd | ||
|
|
5c9c3d7934 | ||
|
|
4ca6713675 | ||
|
|
3d18404fd6 | ||
|
|
98dda26bf8 | ||
|
|
cd4c5ecd83 | ||
|
|
fdc0017ccb | ||
|
|
20a5c12bbb | ||
|
|
4eacec125e | ||
|
|
34322ba6d1 | ||
|
|
8210151a7b | ||
|
|
1e00088608 | ||
|
|
ee59b8002a | ||
|
|
d6e625efa6 | ||
|
|
fba0ece8fd | ||
|
|
78ed038012 | ||
|
|
58f7be77ed | ||
|
|
362160c67b | ||
|
|
c8a3c569e1 | ||
|
|
6bc309e3fa |
File diff suppressed because one or more lines are too long
@@ -10,7 +10,7 @@ import BatchExecution from './src/BatchExecution'
|
||||
import { layoutValueList, CONSTANTS } from './src/utils/constant'
|
||||
import { SVG } from '@svgdotjs/svg.js'
|
||||
import { simpleDeepClone } from './src/utils'
|
||||
import defaultTheme from './src/themes/default'
|
||||
import defaultTheme, { checkIsNodeSizeIndependenceConfig } from './src/themes/default'
|
||||
|
||||
// 默认选项配置
|
||||
const defaultOpt = {
|
||||
@@ -118,7 +118,11 @@ const defaultOpt = {
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
]
|
||||
],
|
||||
// 节点最大缓存数量
|
||||
maxNodeCacheCount: 1000,
|
||||
// 关联线默认文字
|
||||
defaultAssociativeLineText: '关联'
|
||||
}
|
||||
|
||||
// 思维导图
|
||||
@@ -208,12 +212,12 @@ class MindMap {
|
||||
}
|
||||
|
||||
// 重新渲染
|
||||
reRender(callback) {
|
||||
reRender(callback, source = '') {
|
||||
this.batchExecution.push('render', () => {
|
||||
this.draw.clear()
|
||||
this.initTheme()
|
||||
this.renderer.reRender = true
|
||||
this.renderer.render(callback)
|
||||
this.renderer.render(callback, source)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -263,7 +267,9 @@ class MindMap {
|
||||
// 设置主题配置
|
||||
setThemeConfig(config) {
|
||||
this.opt.themeConfig = config
|
||||
this.render(null, CONSTANTS.CHANGE_THEME)
|
||||
// 检查改变的是否是节点大小无关的主题属性
|
||||
let res = checkIsNodeSizeIndependenceConfig(config)
|
||||
this.render(null, res ? '' : CONSTANTS.CHANGE_THEME)
|
||||
}
|
||||
|
||||
// 获取自定义主题配置
|
||||
@@ -318,7 +324,7 @@ class MindMap {
|
||||
} else {
|
||||
this.renderer.renderTree = data
|
||||
}
|
||||
this.reRender()
|
||||
this.reRender(() => {}, CONSTANTS.SET_DATA)
|
||||
}
|
||||
|
||||
// 动态设置思维导图数据,包括节点数据、布局、主题、视图
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "simple-mind-map",
|
||||
"version": "0.5.8",
|
||||
"version": "0.5.11",
|
||||
"description": "一个简单的web在线思维导图",
|
||||
"authors": [
|
||||
{
|
||||
|
||||
@@ -3,13 +3,13 @@ import { v4 as uuid } from 'uuid'
|
||||
import {
|
||||
getAssociativeLineTargetIndex,
|
||||
computeCubicBezierPathPoints,
|
||||
joinCubicBezierPath,
|
||||
cubicBezierPath,
|
||||
getNodePoint,
|
||||
computeNodePoints,
|
||||
getNodeLinePath,
|
||||
getDefaultControlPointOffsets
|
||||
getNodeLinePath
|
||||
} from './utils/associativeLineUtils'
|
||||
import associativeLineControlsMethods from './utils/associativeLineControls'
|
||||
import associativeLineTextMethods from './utils/associativeLineText'
|
||||
|
||||
// 关联线类
|
||||
class AssociativeLine {
|
||||
@@ -46,6 +46,14 @@ class AssociativeLine {
|
||||
}
|
||||
// 节流一下,不然很卡
|
||||
this.checkOverlapNode = throttle(this.checkOverlapNode, 100, this)
|
||||
// 控制点相关方法
|
||||
Object.keys(associativeLineControlsMethods).forEach(item => {
|
||||
this[item] = associativeLineControlsMethods[item].bind(this)
|
||||
})
|
||||
// 关联线文字相关方法
|
||||
Object.keys(associativeLineTextMethods).forEach(item => {
|
||||
this[item] = associativeLineTextMethods[item].bind(this)
|
||||
})
|
||||
this.bindEvent()
|
||||
}
|
||||
|
||||
@@ -89,6 +97,8 @@ class AssociativeLine {
|
||||
window.addEventListener('mouseup', e => {
|
||||
this.onControlPointMouseup(e)
|
||||
})
|
||||
// 缩放事件
|
||||
this.mindMap.on('scale', this.onScale)
|
||||
}
|
||||
|
||||
// 创建箭头
|
||||
@@ -177,36 +187,46 @@ class AssociativeLine {
|
||||
.stroke({ width: associativeLineActiveWidth, color: 'transparent' })
|
||||
.fill({ color: 'none' })
|
||||
clickPath.plot(pathStr)
|
||||
// 文字
|
||||
let text = this.createText({ path, clickPath, node, toNode, startPoint, endPoint, controlPoints })
|
||||
// 点击事件
|
||||
clickPath.click(e => {
|
||||
e.stopPropagation()
|
||||
// 如果当前存在激活节点,那么取消激活节点
|
||||
if (this.mindMap.renderer.activeNodeList.length > 0) {
|
||||
this.clearActiveNodes()
|
||||
} else {
|
||||
// 否则清除当前的关联线的激活状态,如果有的话
|
||||
this.clearActiveLine()
|
||||
// 保存当前激活的关联线信息
|
||||
this.activeLine = [path, clickPath, node, toNode]
|
||||
// 让不可见的点击线显示
|
||||
clickPath.stroke({ color: associativeLineActiveColor })
|
||||
// 渲染控制点和连线
|
||||
this.renderControls(
|
||||
startPoint,
|
||||
endPoint,
|
||||
controlPoints[0],
|
||||
controlPoints[1]
|
||||
)
|
||||
this.mindMap.emit(
|
||||
'associative_line_click',
|
||||
path,
|
||||
clickPath,
|
||||
node,
|
||||
toNode
|
||||
)
|
||||
}
|
||||
this.setActiveLine({ path, clickPath, text, node, toNode, startPoint, endPoint, controlPoints })
|
||||
})
|
||||
this.lineList.push([path, clickPath, node, toNode])
|
||||
// 渲染关联线文字
|
||||
this.renderText(this.getText(node, toNode), path, text)
|
||||
this.lineList.push([path, clickPath, text, node, toNode])
|
||||
}
|
||||
|
||||
// 激活某根关联线
|
||||
setActiveLine({ path, clickPath, text, node, toNode, startPoint, endPoint, controlPoints }) {
|
||||
let {
|
||||
associativeLineActiveColor
|
||||
} = this.mindMap.themeConfig
|
||||
// 如果当前存在激活节点,那么取消激活节点
|
||||
if (this.mindMap.renderer.activeNodeList.length > 0) {
|
||||
this.clearActiveNodes()
|
||||
} else {
|
||||
// 否则清除当前的关联线的激活状态,如果有的话
|
||||
this.clearActiveLine()
|
||||
// 保存当前激活的关联线信息
|
||||
this.activeLine = [path, clickPath, text, node, toNode]
|
||||
// 让不可见的点击线显示
|
||||
clickPath.stroke({ color: associativeLineActiveColor })
|
||||
// 如果没有输入过关联线文字,那么显示默认文字
|
||||
if (!this.getText(node, toNode)) {
|
||||
this.renderText(this.mindMap.opt.defaultAssociativeLineText, path, text)
|
||||
}
|
||||
// 渲染控制点和连线
|
||||
this.renderControls(
|
||||
startPoint,
|
||||
endPoint,
|
||||
controlPoints[0],
|
||||
controlPoints[1]
|
||||
)
|
||||
this.mindMap.emit('associative_line_click', path, clickPath, node, toNode)
|
||||
}
|
||||
}
|
||||
|
||||
// 移除所有连接线
|
||||
@@ -214,6 +234,7 @@ class AssociativeLine {
|
||||
this.lineList.forEach(line => {
|
||||
line[0].remove()
|
||||
line[1].remove()
|
||||
line[2].remove()
|
||||
})
|
||||
this.lineList = []
|
||||
}
|
||||
@@ -253,7 +274,8 @@ class AssociativeLine {
|
||||
updateCreatingLine(e) {
|
||||
let { x, y } = this.getTransformedEventPos(e)
|
||||
let startPoint = getNodePoint(this.creatingStartNode)
|
||||
let pathStr = cubicBezierPath(startPoint.x, startPoint.y, x, y)
|
||||
let offsetX = x > startPoint.x ? -10 : 10
|
||||
let pathStr = cubicBezierPath(startPoint.x, startPoint.y, x + offsetX, y)
|
||||
this.creatingLine.plot(pathStr)
|
||||
this.checkOverlapNode(x, y)
|
||||
}
|
||||
@@ -349,20 +371,33 @@ class AssociativeLine {
|
||||
// 删除连接线
|
||||
removeLine() {
|
||||
if (!this.activeLine) return
|
||||
let [, , node, toNode] = this.activeLine
|
||||
let [, , , node, toNode] = this.activeLine
|
||||
this.removeControls()
|
||||
let { associativeLineTargets, associativeLineTargetControlOffsets } =
|
||||
let { associativeLineTargets, associativeLineTargetControlOffsets, associativeLineText } =
|
||||
node.nodeData.data
|
||||
let targetIndex = getAssociativeLineTargetIndex(node, toNode)
|
||||
// 更新关联线文本数据
|
||||
let newAssociativeLineText = {}
|
||||
if (associativeLineText) {
|
||||
Object.keys(associativeLineText).forEach((item) => {
|
||||
if (item !== toNode.nodeData.data.id) {
|
||||
newAssociativeLineText[item] = associativeLineText[item]
|
||||
}
|
||||
})
|
||||
}
|
||||
this.mindMap.execCommand('SET_NODE_DATA', node, {
|
||||
// 目标
|
||||
associativeLineTargets: associativeLineTargets.filter((_, index) => {
|
||||
return index !== targetIndex
|
||||
}),
|
||||
// 偏移量
|
||||
associativeLineTargetControlOffsets: associativeLineTargetControlOffsets
|
||||
? associativeLineTargetControlOffsets.filter((_, index) => {
|
||||
return index !== targetIndex
|
||||
})
|
||||
: []
|
||||
: [],
|
||||
// 文本
|
||||
associativeLineText: newAssociativeLineText
|
||||
})
|
||||
}
|
||||
|
||||
@@ -376,9 +411,16 @@ class AssociativeLine {
|
||||
// 清除激活的线
|
||||
clearActiveLine() {
|
||||
if (this.activeLine) {
|
||||
this.activeLine[1].stroke({
|
||||
let [, clickPath, text, node, toNode] = this.activeLine
|
||||
clickPath.stroke({
|
||||
color: 'transparent'
|
||||
})
|
||||
// 隐藏关联线文本编辑框
|
||||
this.hideEditTextBox()
|
||||
// 如果当前关联线没有文字,则清空文字节点
|
||||
if (!this.getText(node, toNode)) {
|
||||
text.clear()
|
||||
}
|
||||
this.activeLine = null
|
||||
this.removeControls()
|
||||
}
|
||||
@@ -391,6 +433,7 @@ class AssociativeLine {
|
||||
this.lineList.forEach(line => {
|
||||
line[0].hide()
|
||||
line[1].hide()
|
||||
line[2].hide()
|
||||
})
|
||||
this.hideControls()
|
||||
}
|
||||
@@ -401,225 +444,11 @@ class AssociativeLine {
|
||||
this.lineList.forEach(line => {
|
||||
line[0].show()
|
||||
line[1].show()
|
||||
line[2].show()
|
||||
})
|
||||
this.showControls()
|
||||
this.isNodeDragging = false
|
||||
}
|
||||
|
||||
// 创建控制点、连线节点
|
||||
createControlNodes() {
|
||||
let { associativeLineActiveColor } = this.mindMap.themeConfig
|
||||
// 连线
|
||||
this.controlLine1 = this.draw
|
||||
.line()
|
||||
.stroke({ color: associativeLineActiveColor, width: 2 })
|
||||
this.controlLine2 = this.draw
|
||||
.line()
|
||||
.stroke({ color: associativeLineActiveColor, width: 2 })
|
||||
// 控制点
|
||||
this.controlPoint1 = this.createOneControlNode('controlPoint1')
|
||||
this.controlPoint2 = this.createOneControlNode('controlPoint2')
|
||||
}
|
||||
|
||||
// 创建控制点
|
||||
createOneControlNode(pointKey) {
|
||||
let { associativeLineActiveColor } = this.mindMap.themeConfig
|
||||
return this.draw
|
||||
.circle(this.controlPointDiameter)
|
||||
.stroke({ color: associativeLineActiveColor })
|
||||
.fill({ color: '#fff' })
|
||||
.click(e => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
.mousedown(e => {
|
||||
this.onControlPointMousedown(e, pointKey)
|
||||
})
|
||||
}
|
||||
|
||||
// 控制点的鼠标按下事件
|
||||
onControlPointMousedown(e, pointKey) {
|
||||
e.stopPropagation()
|
||||
this.isControlPointMousedown = true
|
||||
this.mousedownControlPointKey = pointKey
|
||||
}
|
||||
|
||||
// 控制点的鼠标移动事件
|
||||
onControlPointMousemove(e) {
|
||||
if (
|
||||
!this.isControlPointMousedown ||
|
||||
!this.mousedownControlPointKey ||
|
||||
!this[this.mousedownControlPointKey]
|
||||
)
|
||||
return
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
let radius = this.controlPointDiameter / 2
|
||||
// 转换鼠标当前的位置
|
||||
let { x, y } = this.getTransformedEventPos(e)
|
||||
this.controlPointMousemoveState.pos = {
|
||||
x,
|
||||
y
|
||||
}
|
||||
// 更新当前拖拽的控制点的位置
|
||||
this[this.mousedownControlPointKey].x(x - radius).y(y - radius)
|
||||
let [path, clickPath, node, toNode] = this.activeLine
|
||||
let [startPoint, endPoint] = computeNodePoints(node, toNode)
|
||||
this.controlPointMousemoveState.startPoint = startPoint
|
||||
this.controlPointMousemoveState.endPoint = endPoint
|
||||
let targetIndex = getAssociativeLineTargetIndex(node, toNode)
|
||||
this.controlPointMousemoveState.targetIndex = targetIndex
|
||||
let offsets = []
|
||||
let associativeLineTargetControlOffsets = node.nodeData.data.associativeLineTargetControlOffsets
|
||||
if (!associativeLineTargetControlOffsets) {
|
||||
// 兼容0.4.5版本,没有associativeLineTargetControlOffsets的情况
|
||||
offsets = getDefaultControlPointOffsets(startPoint, endPoint)
|
||||
} else {
|
||||
offsets = associativeLineTargetControlOffsets[targetIndex]
|
||||
}
|
||||
let point1 = null
|
||||
let point2 = null
|
||||
// 拖拽的是控制点1
|
||||
if (this.mousedownControlPointKey === 'controlPoint1') {
|
||||
point1 = {
|
||||
x,
|
||||
y
|
||||
}
|
||||
point2 = {
|
||||
x: endPoint.x + offsets[1].x,
|
||||
y: endPoint.y + offsets[1].y
|
||||
}
|
||||
// 更新控制点1的连线
|
||||
this.controlLine1.plot(startPoint.x, startPoint.y, point1.x, point1.y)
|
||||
} else {
|
||||
// 拖拽的是控制点2
|
||||
point1 = {
|
||||
x: startPoint.x + offsets[0].x,
|
||||
y: startPoint.y + offsets[0].y
|
||||
}
|
||||
point2 = {
|
||||
x,
|
||||
y
|
||||
}
|
||||
// 更新控制点2的连线
|
||||
this.controlLine2.plot(endPoint.x, endPoint.y, point2.x, point2.y)
|
||||
}
|
||||
// 更新关联线
|
||||
let pathStr = joinCubicBezierPath(startPoint, endPoint, point1, point2)
|
||||
path.plot(pathStr)
|
||||
clickPath.plot(pathStr)
|
||||
}
|
||||
|
||||
// 控制点的鼠标移动事件
|
||||
onControlPointMouseup(e) {
|
||||
if (!this.isControlPointMousedown) return
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
let { pos, startPoint, endPoint, targetIndex } =
|
||||
this.controlPointMousemoveState
|
||||
let [, , node] = this.activeLine
|
||||
let offsetList = []
|
||||
let associativeLineTargetControlOffsets = node.nodeData.data.associativeLineTargetControlOffsets
|
||||
if (!associativeLineTargetControlOffsets) {
|
||||
// 兼容0.4.5版本,没有associativeLineTargetControlOffsets的情况
|
||||
offsetList[targetIndex] = getDefaultControlPointOffsets(startPoint, endPoint)
|
||||
} else {
|
||||
offsetList = associativeLineTargetControlOffsets
|
||||
}
|
||||
let offset1 = null
|
||||
let offset2 = null
|
||||
if (this.mousedownControlPointKey === 'controlPoint1') {
|
||||
// 更新控制点1数据
|
||||
offset1 = {
|
||||
x: pos.x - startPoint.x,
|
||||
y: pos.y - startPoint.y
|
||||
}
|
||||
offset2 = offsetList[targetIndex][1]
|
||||
} else {
|
||||
// 更新控制点2数据
|
||||
offset1 = offsetList[targetIndex][0]
|
||||
offset2 = {
|
||||
x: pos.x - endPoint.x,
|
||||
y: pos.y - endPoint.y
|
||||
}
|
||||
}
|
||||
offsetList[targetIndex] = [offset1, offset2]
|
||||
this.mindMap.execCommand('SET_NODE_DATA', node, {
|
||||
associativeLineTargetControlOffsets: offsetList
|
||||
})
|
||||
// 这里要加个setTimeout0是因为draw_click事件比mouseup事件触发的晚,所以重置isControlPointMousedown需要等draw_click事件触发完以后
|
||||
setTimeout(() => {
|
||||
this.resetControlPoint()
|
||||
}, 0)
|
||||
}
|
||||
|
||||
// 复位控制点移动
|
||||
resetControlPoint() {
|
||||
this.isControlPointMousedown = false
|
||||
this.mousedownControlPointKey = ''
|
||||
this.controlPointMousemoveState = {
|
||||
pos: null,
|
||||
startPoint: null,
|
||||
endPoint: null,
|
||||
targetIndex: ''
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染控制点
|
||||
renderControls(startPoint, endPoint, point1, point2) {
|
||||
if (!this.controlLine1) {
|
||||
this.createControlNodes()
|
||||
}
|
||||
let radius = this.controlPointDiameter / 2
|
||||
// 控制点和起终点的连线
|
||||
this.controlLine1.plot(startPoint.x, startPoint.y, point1.x, point1.y)
|
||||
this.controlLine2.plot(endPoint.x, endPoint.y, point2.x, point2.y)
|
||||
// 控制点
|
||||
this.controlPoint1.x(point1.x - radius).y(point1.y - radius)
|
||||
this.controlPoint2.x(point2.x - radius).y(point2.y - radius)
|
||||
}
|
||||
|
||||
// 删除控制点
|
||||
removeControls() {
|
||||
if (!this.controlLine1) return
|
||||
;[
|
||||
this.controlLine1,
|
||||
this.controlLine2,
|
||||
this.controlPoint1,
|
||||
this.controlPoint2
|
||||
].forEach(item => {
|
||||
item.remove()
|
||||
})
|
||||
this.controlLine1 = null
|
||||
this.controlLine2 = null
|
||||
this.controlPoint1 = null
|
||||
this.controlPoint2 = null
|
||||
}
|
||||
|
||||
// 隐藏控制点
|
||||
hideControls() {
|
||||
if (!this.controlLine1) return
|
||||
;[
|
||||
this.controlLine1,
|
||||
this.controlLine2,
|
||||
this.controlPoint1,
|
||||
this.controlPoint2
|
||||
].forEach(item => {
|
||||
item.hide()
|
||||
})
|
||||
}
|
||||
|
||||
// 显示控制点
|
||||
showControls() {
|
||||
if (!this.controlLine1) return
|
||||
;[
|
||||
this.controlLine1,
|
||||
this.controlLine2,
|
||||
this.controlPoint1,
|
||||
this.controlPoint2
|
||||
].forEach(item => {
|
||||
item.show()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
AssociativeLine.instanceName = 'associativeLine'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { imgToDataUrl, downloadFile } from './utils'
|
||||
import { imgToDataUrl, downloadFile, readBlob } from './utils'
|
||||
import JsPDF from 'jspdf'
|
||||
import { SVG } from '@svgdotjs/svg.js'
|
||||
import drawBackgroundImageToCanvas from './utils/simulateCSSBackgroundInCanvas'
|
||||
@@ -127,6 +127,28 @@ class Export {
|
||||
})
|
||||
}
|
||||
|
||||
// 在svg上绘制思维导图背景
|
||||
drawBackgroundToSvg(svg) {
|
||||
return new Promise(async resolve => {
|
||||
let {
|
||||
backgroundColor = '#fff',
|
||||
backgroundImage,
|
||||
backgroundRepeat = 'repeat'
|
||||
} = this.mindMap.themeConfig
|
||||
// 背景颜色
|
||||
svg.css('background-color', backgroundColor)
|
||||
// 背景图片
|
||||
if (backgroundImage && backgroundImage !== 'none') {
|
||||
let imgDataUrl = await imgToDataUrl(backgroundImage)
|
||||
svg.css('background-image', `url(${imgDataUrl})`)
|
||||
svg.css('background-repeat', backgroundRepeat)
|
||||
resolve()
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 导出为png
|
||||
/**
|
||||
* 方法1.把svg的图片都转化成data:url格式,再转换
|
||||
@@ -145,11 +167,10 @@ class Export {
|
||||
type: 'image/svg+xml'
|
||||
})
|
||||
// 转换成data:url数据
|
||||
let svgUrl = URL.createObjectURL(blob)
|
||||
let svgUrl = await readBlob(blob)
|
||||
// 绘制到canvas上
|
||||
let imgDataUrl = await this.svgToPng(svgUrl, transparent)
|
||||
URL.revokeObjectURL(svgUrl)
|
||||
return imgDataUrl
|
||||
let res = await this.svgToPng(svgUrl, transparent)
|
||||
return res
|
||||
}
|
||||
|
||||
// 导出为pdf
|
||||
@@ -184,28 +205,6 @@ class Export {
|
||||
image.src = img
|
||||
}
|
||||
|
||||
// 在svg上绘制思维导图背景
|
||||
drawBackgroundToSvg(svg) {
|
||||
return new Promise(async resolve => {
|
||||
let {
|
||||
backgroundColor = '#fff',
|
||||
backgroundImage,
|
||||
backgroundRepeat = 'repeat'
|
||||
} = this.mindMap.themeConfig
|
||||
// 背景颜色
|
||||
svg.css('background-color', backgroundColor)
|
||||
// 背景图片
|
||||
if (backgroundImage && backgroundImage !== 'none') {
|
||||
let imgDataUrl = await imgToDataUrl(backgroundImage)
|
||||
svg.css('background-image', `url(${imgDataUrl})`)
|
||||
svg.css('background-repeat', backgroundRepeat)
|
||||
resolve()
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 导出为svg
|
||||
// plusCssText:附加的css样式,如果svg中存在dom节点,想要设置一些针对节点的样式可以通过这个参数传入
|
||||
async svg(name, plusCssText) {
|
||||
@@ -226,28 +225,32 @@ class Export {
|
||||
let blob = new Blob([str], {
|
||||
type: 'image/svg+xml'
|
||||
})
|
||||
return URL.createObjectURL(blob)
|
||||
let res = await readBlob(blob)
|
||||
return res
|
||||
}
|
||||
|
||||
// 导出为json
|
||||
json(name, withConfig = true) {
|
||||
async json(name, withConfig = true) {
|
||||
let data = this.mindMap.getData(withConfig)
|
||||
let str = JSON.stringify(data)
|
||||
let blob = new Blob([str])
|
||||
return URL.createObjectURL(blob)
|
||||
let res = await readBlob(blob)
|
||||
return res
|
||||
}
|
||||
|
||||
// 专有文件,其实就是json文件
|
||||
smm(name, withConfig) {
|
||||
return this.json(name, withConfig)
|
||||
async smm(name, withConfig) {
|
||||
let res = await this.json(name, withConfig)
|
||||
return res
|
||||
}
|
||||
|
||||
// markdown文件
|
||||
md() {
|
||||
async md() {
|
||||
let data = this.mindMap.getData()
|
||||
let content = transformToMarkdown(data)
|
||||
let blob = new Blob([content])
|
||||
return URL.createObjectURL(blob)
|
||||
let res = await readBlob(blob)
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ export default class KeyCommand {
|
||||
if (this.mindMap.richText && this.mindMap.richText.showTextEdit) {
|
||||
return
|
||||
}
|
||||
if (this.mindMap.renderer.textEdit.showTextEdit) {
|
||||
if (this.mindMap.renderer.textEdit.showTextEdit || (this.mindMap.associativeLine && this.mindMap.associativeLine.showTextEdit)) {
|
||||
return
|
||||
}
|
||||
this.isInSvg = false
|
||||
|
||||
@@ -266,7 +266,6 @@ class Render {
|
||||
// 重新渲染需要清除激活状态
|
||||
if (this.reRender) {
|
||||
this.clearActive()
|
||||
this.layout.clearNodePool()
|
||||
}
|
||||
// 计算布局
|
||||
this.layout.doLayout(root => {
|
||||
@@ -289,6 +288,11 @@ class Render {
|
||||
if (this.hasWaitRendering) {
|
||||
this.hasWaitRendering = false
|
||||
this.render(callback, source)
|
||||
} else {
|
||||
// 触发一次保存,因为修改了渲染树的数据
|
||||
if (this.mindMap.richText && [CONSTANTS.CHANGE_THEME, CONSTANTS.SET_DATA].includes(source)) {
|
||||
this.mindMap.command.addHistory()
|
||||
}
|
||||
}
|
||||
}
|
||||
let { enableNodeTransitionMove, nodeTransitionMoveDuration } =
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Node from '../Node'
|
||||
import { CONSTANTS, initRootNodePositionMap } from '../utils/constant'
|
||||
import Lru from '../utils/Lru'
|
||||
|
||||
// 布局基类
|
||||
class Base {
|
||||
@@ -13,8 +14,7 @@ class Base {
|
||||
this.draw = this.mindMap.draw
|
||||
// 根节点
|
||||
this.root = null
|
||||
// 保存所有uid和节点,用于复用
|
||||
this.nodePool = {}
|
||||
this.lru = new Lru(this.mindMap.opt.maxNodeCacheCount)
|
||||
}
|
||||
|
||||
// 计算节点位置
|
||||
@@ -40,16 +40,7 @@ class Base {
|
||||
// 记录本次渲染时的节点
|
||||
this.renderer.nodeCache[uid] = node
|
||||
// 记录所有渲染时的节点
|
||||
this.nodePool[uid] = node
|
||||
// 如果总缓存数量达到1000,直接清空
|
||||
if (Object.keys(this.nodePool).length > 1000) {
|
||||
this.clearNodePool()
|
||||
}
|
||||
}
|
||||
|
||||
// 清空节点存储池
|
||||
clearNodePool() {
|
||||
this.nodePool = {}
|
||||
this.lru.add(uid, node)
|
||||
}
|
||||
|
||||
// 检查当前来源是否需要重新计算节点大小
|
||||
@@ -72,9 +63,9 @@ class Base {
|
||||
newNode.getSize()
|
||||
newNode.needLayout = true
|
||||
}
|
||||
} else if (this.nodePool[data.data.uid] && !this.renderer.reRender) {
|
||||
} else if (this.lru.has(data.data.uid) && !this.renderer.reRender) {
|
||||
// 数据上没有保存节点引用,但是通过uid找到了缓存的节点,也可以复用
|
||||
newNode = this.nodePool[data.data.uid]
|
||||
newNode = this.lru.get(data.data.uid)
|
||||
// 保存该节点上一次的数据
|
||||
let lastData = JSON.stringify(newNode.nodeData.data)
|
||||
newNode.reset()
|
||||
|
||||
57
simple-mind-map/src/themes/autumn.js
Normal file
57
simple-mind-map/src/themes/autumn.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import defaultTheme from './default'
|
||||
import merge from 'deepmerge'
|
||||
|
||||
// 秋天
|
||||
export default merge(defaultTheme, {
|
||||
// 背景颜色
|
||||
backgroundColor: '#fff2df',
|
||||
// 连线的颜色
|
||||
lineColor: '#b0bc47',
|
||||
lineWidth: 3,
|
||||
// 概要连线的粗细
|
||||
generalizationLineWidth: 3,
|
||||
// 概要连线的颜色
|
||||
generalizationLineColor: '#b0bc47',
|
||||
// 根节点样式
|
||||
root: {
|
||||
fillColor: '#e68112',
|
||||
color: '#fff',
|
||||
borderColor: '#e68112',
|
||||
borderWidth: 0,
|
||||
fontSize: 24,
|
||||
active: {
|
||||
borderColor: '#b0bc47',
|
||||
borderWidth: 3
|
||||
}
|
||||
},
|
||||
// 二级节点样式
|
||||
second: {
|
||||
fillColor: '#ffd683',
|
||||
color: '#8c5416',
|
||||
borderColor: '#b0bc47',
|
||||
borderWidth: 2,
|
||||
fontSize: 18,
|
||||
active: {
|
||||
borderColor: '#e68112'
|
||||
}
|
||||
},
|
||||
// 三级及以下节点样式
|
||||
node: {
|
||||
fontSize: 14,
|
||||
color: '#8c5416',
|
||||
active: {
|
||||
borderColor: '#b0bc47'
|
||||
}
|
||||
},
|
||||
// 概要节点样式
|
||||
generalization: {
|
||||
fontSize: 14,
|
||||
fillColor: '#ffd683',
|
||||
borderColor: '#b0bc47',
|
||||
borderWidth: 2,
|
||||
color: '#8c5416',
|
||||
active: {
|
||||
borderColor: '#e68112'
|
||||
}
|
||||
}
|
||||
})
|
||||
57
simple-mind-map/src/themes/avocado.js
Normal file
57
simple-mind-map/src/themes/avocado.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import defaultTheme from './default'
|
||||
import merge from 'deepmerge'
|
||||
|
||||
// 牛油果
|
||||
export default merge(defaultTheme, {
|
||||
// 背景颜色
|
||||
backgroundColor: '#e6f1de',
|
||||
// 连线的颜色
|
||||
lineColor: '#f5ffad',
|
||||
lineWidth: 4,
|
||||
// 概要连线的粗细
|
||||
generalizationLineWidth: 3,
|
||||
// 概要连线的颜色
|
||||
generalizationLineColor: '#749336',
|
||||
// 根节点样式
|
||||
root: {
|
||||
fillColor: '#94c143',
|
||||
color: '#fff',
|
||||
borderColor: '#94c143',
|
||||
borderWidth: 0,
|
||||
fontSize: 24,
|
||||
active: {
|
||||
borderColor: '#749336',
|
||||
borderWidth: 3
|
||||
}
|
||||
},
|
||||
// 二级节点样式
|
||||
second: {
|
||||
fillColor: '#cee498',
|
||||
color: '#749336',
|
||||
borderColor: '#aec668',
|
||||
borderWidth: 2,
|
||||
fontSize: 18,
|
||||
active: {
|
||||
borderColor: '#749336'
|
||||
}
|
||||
},
|
||||
// 三级及以下节点样式
|
||||
node: {
|
||||
fontSize: 14,
|
||||
color: '#749336',
|
||||
active: {
|
||||
borderColor: '#749336'
|
||||
}
|
||||
},
|
||||
// 概要节点样式
|
||||
generalization: {
|
||||
fontSize: 14,
|
||||
fillColor: '#cee498',
|
||||
borderColor: '#aec668',
|
||||
borderWidth: 2,
|
||||
color: '#749336',
|
||||
active: {
|
||||
borderColor: '#749336'
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -34,6 +34,14 @@ export default {
|
||||
associativeLineActiveWidth: 8,
|
||||
// 关联线激活状态的颜色
|
||||
associativeLineActiveColor: 'rgba(2, 167, 240, 1)',
|
||||
// 关联线文字颜色
|
||||
associativeLineTextColor: 'rgb(51, 51, 51)',
|
||||
// 关联线文字大小
|
||||
associativeLineTextFontSize: 14,
|
||||
// 关联线文字行高
|
||||
associativeLineTextLineHeight: 1.2,
|
||||
// 关联线文字字体
|
||||
associativeLineTextFontFamily: '微软雅黑, Microsoft YaHei',
|
||||
// 背景颜色
|
||||
backgroundColor: '#fafafa',
|
||||
// 背景图片
|
||||
@@ -148,4 +156,38 @@ export const supportActiveStyle = [
|
||||
'borderRadius'
|
||||
]
|
||||
|
||||
// 检测主题配置是否是节点大小无关的
|
||||
const nodeSizeIndependenceList = [
|
||||
'lineWidth',
|
||||
'lineColor',
|
||||
'lineDasharray',
|
||||
'lineStyle',
|
||||
'generalizationLineWidth',
|
||||
'generalizationLineColor',
|
||||
'associativeLineWidth',
|
||||
'associativeLineColor',
|
||||
'associativeLineActiveWidth',
|
||||
'associativeLineActiveColor',
|
||||
'associativeLineTextColor',
|
||||
'associativeLineTextFontSize',
|
||||
'associativeLineTextLineHeight',
|
||||
'associativeLineTextFontFamily',
|
||||
'backgroundColor',
|
||||
'backgroundImage',
|
||||
'backgroundRepeat',
|
||||
'backgroundPosition',
|
||||
'backgroundSize'
|
||||
]
|
||||
export const checkIsNodeSizeIndependenceConfig = (config) => {
|
||||
let keys = Object.keys(config)
|
||||
for(let i = 0; i < keys.length; i++) {
|
||||
if (!nodeSizeIndependenceList.find((item) => {
|
||||
return item === keys[i]
|
||||
})) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export const lineStyleProps = ['lineColor', 'lineDasharray', 'lineWidth']
|
||||
|
||||
@@ -27,6 +27,9 @@ import redSpirit from './redSpirit'
|
||||
import blackHumour from './blackHumour'
|
||||
import lateNightOffice from './lateNightOffice'
|
||||
import blackGold from './blackGold'
|
||||
import avocado from './avocado'
|
||||
import autumn from './autumn'
|
||||
import orangeJuice from './orangeJuice'
|
||||
|
||||
export default {
|
||||
default: defaultTheme,
|
||||
@@ -57,5 +60,8 @@ export default {
|
||||
redSpirit,
|
||||
blackHumour,
|
||||
lateNightOffice,
|
||||
blackGold
|
||||
blackGold,
|
||||
avocado,
|
||||
autumn,
|
||||
orangeJuice
|
||||
}
|
||||
|
||||
57
simple-mind-map/src/themes/orangeJuice.js
Normal file
57
simple-mind-map/src/themes/orangeJuice.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import defaultTheme from './default'
|
||||
import merge from 'deepmerge'
|
||||
|
||||
// 橙汁
|
||||
export default merge(defaultTheme, {
|
||||
// 背景颜色
|
||||
backgroundColor: '#070616',
|
||||
// 连线的颜色
|
||||
lineColor: '#fff',
|
||||
lineWidth: 3,
|
||||
// 概要连线的粗细
|
||||
generalizationLineWidth: 3,
|
||||
// 概要连线的颜色
|
||||
generalizationLineColor: '#fff',
|
||||
// 根节点样式
|
||||
root: {
|
||||
fillColor: '#ff6811',
|
||||
color: '#110501',
|
||||
borderColor: '#ff6811',
|
||||
borderWidth: 0,
|
||||
fontSize: 24,
|
||||
active: {
|
||||
borderColor: '#a9a4a9',
|
||||
borderWidth: 3
|
||||
}
|
||||
},
|
||||
// 二级节点样式
|
||||
second: {
|
||||
fillColor: '#070616',
|
||||
color: '#a9a4a9',
|
||||
borderColor: '#ff6811',
|
||||
borderWidth: 2,
|
||||
fontSize: 18,
|
||||
active: {
|
||||
borderColor: '#110501'
|
||||
}
|
||||
},
|
||||
// 三级及以下节点样式
|
||||
node: {
|
||||
fontSize: 14,
|
||||
color: '#a9a4a9',
|
||||
active: {
|
||||
borderColor: '#ff6811'
|
||||
}
|
||||
},
|
||||
// 概要节点样式
|
||||
generalization: {
|
||||
fontSize: 14,
|
||||
fillColor: '',
|
||||
borderColor: '#ff6811',
|
||||
borderWidth: 2,
|
||||
color: '#a9a4a9',
|
||||
active: {
|
||||
borderColor: '#110501'
|
||||
}
|
||||
}
|
||||
})
|
||||
39
simple-mind-map/src/utils/Lru.js
Normal file
39
simple-mind-map/src/utils/Lru.js
Normal file
@@ -0,0 +1,39 @@
|
||||
// LRU缓存类
|
||||
export default class CRU {
|
||||
constructor(max) {
|
||||
this.max = max || 1000
|
||||
this.size = 0
|
||||
this.pool = new Map()
|
||||
}
|
||||
|
||||
add(key, value) {
|
||||
// 如果该key是否已经存在,则先删除
|
||||
this.delete(key)
|
||||
this.pool.set(key, value)
|
||||
this.size++
|
||||
// 如果数量超出最大值,则删除最早的
|
||||
if (this.size > this.max) {
|
||||
let keys = this.pool.keys()
|
||||
let last = keys.next()
|
||||
this.delete(last.value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
delete(key) {
|
||||
if (this.pool.has(key)) {
|
||||
this.pool.delete(key)
|
||||
this.size--
|
||||
}
|
||||
}
|
||||
|
||||
has(key) {
|
||||
return this.pool.has(key)
|
||||
}
|
||||
|
||||
get(key) {
|
||||
if (this.pool.has(key)) {
|
||||
return this.pool.get(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
241
simple-mind-map/src/utils/associativeLineControls.js
Normal file
241
simple-mind-map/src/utils/associativeLineControls.js
Normal file
@@ -0,0 +1,241 @@
|
||||
import {
|
||||
getAssociativeLineTargetIndex,
|
||||
joinCubicBezierPath,
|
||||
computeNodePoints,
|
||||
getDefaultControlPointOffsets
|
||||
} from './associativeLineUtils'
|
||||
|
||||
// 创建控制点、连线节点
|
||||
function createControlNodes() {
|
||||
let { associativeLineActiveColor } = this.mindMap.themeConfig
|
||||
// 连线
|
||||
this.controlLine1 = this.draw
|
||||
.line()
|
||||
.stroke({ color: associativeLineActiveColor, width: 2 })
|
||||
this.controlLine2 = this.draw
|
||||
.line()
|
||||
.stroke({ color: associativeLineActiveColor, width: 2 })
|
||||
// 控制点
|
||||
this.controlPoint1 = this.createOneControlNode('controlPoint1')
|
||||
this.controlPoint2 = this.createOneControlNode('controlPoint2')
|
||||
}
|
||||
|
||||
// 创建控制点
|
||||
function createOneControlNode(pointKey) {
|
||||
let { associativeLineActiveColor } = this.mindMap.themeConfig
|
||||
return this.draw
|
||||
.circle(this.controlPointDiameter)
|
||||
.stroke({ color: associativeLineActiveColor })
|
||||
.fill({ color: '#fff' })
|
||||
.click(e => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
.mousedown(e => {
|
||||
this.onControlPointMousedown(e, pointKey)
|
||||
})
|
||||
}
|
||||
|
||||
// 控制点的鼠标按下事件
|
||||
function onControlPointMousedown(e, pointKey) {
|
||||
e.stopPropagation()
|
||||
this.isControlPointMousedown = true
|
||||
this.mousedownControlPointKey = pointKey
|
||||
}
|
||||
|
||||
// 控制点的鼠标移动事件
|
||||
function onControlPointMousemove(e) {
|
||||
if (
|
||||
!this.isControlPointMousedown ||
|
||||
!this.mousedownControlPointKey ||
|
||||
!this[this.mousedownControlPointKey]
|
||||
)
|
||||
return
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
let radius = this.controlPointDiameter / 2
|
||||
// 转换鼠标当前的位置
|
||||
let { x, y } = this.getTransformedEventPos(e)
|
||||
this.controlPointMousemoveState.pos = {
|
||||
x,
|
||||
y
|
||||
}
|
||||
// 更新当前拖拽的控制点的位置
|
||||
this[this.mousedownControlPointKey].x(x - radius).y(y - radius)
|
||||
let [path, clickPath, text, node, toNode] = this.activeLine
|
||||
let [startPoint, endPoint] = computeNodePoints(node, toNode)
|
||||
this.controlPointMousemoveState.startPoint = startPoint
|
||||
this.controlPointMousemoveState.endPoint = endPoint
|
||||
let targetIndex = getAssociativeLineTargetIndex(node, toNode)
|
||||
this.controlPointMousemoveState.targetIndex = targetIndex
|
||||
let offsets = []
|
||||
let associativeLineTargetControlOffsets =
|
||||
node.nodeData.data.associativeLineTargetControlOffsets
|
||||
if (!associativeLineTargetControlOffsets) {
|
||||
// 兼容0.4.5版本,没有associativeLineTargetControlOffsets的情况
|
||||
offsets = getDefaultControlPointOffsets(startPoint, endPoint)
|
||||
} else {
|
||||
offsets = associativeLineTargetControlOffsets[targetIndex]
|
||||
}
|
||||
let point1 = null
|
||||
let point2 = null
|
||||
// 拖拽的是控制点1
|
||||
if (this.mousedownControlPointKey === 'controlPoint1') {
|
||||
point1 = {
|
||||
x,
|
||||
y
|
||||
}
|
||||
point2 = {
|
||||
x: endPoint.x + offsets[1].x,
|
||||
y: endPoint.y + offsets[1].y
|
||||
}
|
||||
// 更新控制点1的连线
|
||||
this.controlLine1.plot(startPoint.x, startPoint.y, point1.x, point1.y)
|
||||
} else {
|
||||
// 拖拽的是控制点2
|
||||
point1 = {
|
||||
x: startPoint.x + offsets[0].x,
|
||||
y: startPoint.y + offsets[0].y
|
||||
}
|
||||
point2 = {
|
||||
x,
|
||||
y
|
||||
}
|
||||
// 更新控制点2的连线
|
||||
this.controlLine2.plot(endPoint.x, endPoint.y, point2.x, point2.y)
|
||||
}
|
||||
// 更新关联线
|
||||
let pathStr = joinCubicBezierPath(startPoint, endPoint, point1, point2)
|
||||
path.plot(pathStr)
|
||||
clickPath.plot(pathStr)
|
||||
this.updateTextPos(path, text)
|
||||
this.updateTextEditBoxPos(text)
|
||||
}
|
||||
|
||||
// 控制点的鼠标移动事件
|
||||
function onControlPointMouseup(e) {
|
||||
if (!this.isControlPointMousedown) return
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
let { pos, startPoint, endPoint, targetIndex } =
|
||||
this.controlPointMousemoveState
|
||||
let [, , , node] = this.activeLine
|
||||
let offsetList = []
|
||||
let associativeLineTargetControlOffsets =
|
||||
node.nodeData.data.associativeLineTargetControlOffsets
|
||||
if (!associativeLineTargetControlOffsets) {
|
||||
// 兼容0.4.5版本,没有associativeLineTargetControlOffsets的情况
|
||||
offsetList[targetIndex] = getDefaultControlPointOffsets(
|
||||
startPoint,
|
||||
endPoint
|
||||
)
|
||||
} else {
|
||||
offsetList = associativeLineTargetControlOffsets
|
||||
}
|
||||
let offset1 = null
|
||||
let offset2 = null
|
||||
if (this.mousedownControlPointKey === 'controlPoint1') {
|
||||
// 更新控制点1数据
|
||||
offset1 = {
|
||||
x: pos.x - startPoint.x,
|
||||
y: pos.y - startPoint.y
|
||||
}
|
||||
offset2 = offsetList[targetIndex][1]
|
||||
} else {
|
||||
// 更新控制点2数据
|
||||
offset1 = offsetList[targetIndex][0]
|
||||
offset2 = {
|
||||
x: pos.x - endPoint.x,
|
||||
y: pos.y - endPoint.y
|
||||
}
|
||||
}
|
||||
offsetList[targetIndex] = [offset1, offset2]
|
||||
this.mindMap.execCommand('SET_NODE_DATA', node, {
|
||||
associativeLineTargetControlOffsets: offsetList
|
||||
})
|
||||
// 这里要加个setTimeout0是因为draw_click事件比mouseup事件触发的晚,所以重置isControlPointMousedown需要等draw_click事件触发完以后
|
||||
setTimeout(() => {
|
||||
this.resetControlPoint()
|
||||
}, 0)
|
||||
}
|
||||
|
||||
// 复位控制点移动
|
||||
function resetControlPoint() {
|
||||
this.isControlPointMousedown = false
|
||||
this.mousedownControlPointKey = ''
|
||||
this.controlPointMousemoveState = {
|
||||
pos: null,
|
||||
startPoint: null,
|
||||
endPoint: null,
|
||||
targetIndex: ''
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染控制点
|
||||
function renderControls(startPoint, endPoint, point1, point2) {
|
||||
if (!this.controlLine1) {
|
||||
this.createControlNodes()
|
||||
}
|
||||
let radius = this.controlPointDiameter / 2
|
||||
// 控制点和起终点的连线
|
||||
this.controlLine1.plot(startPoint.x, startPoint.y, point1.x, point1.y)
|
||||
this.controlLine2.plot(endPoint.x, endPoint.y, point2.x, point2.y)
|
||||
// 控制点
|
||||
this.controlPoint1.x(point1.x - radius).y(point1.y - radius)
|
||||
this.controlPoint2.x(point2.x - radius).y(point2.y - radius)
|
||||
}
|
||||
|
||||
// 删除控制点
|
||||
function removeControls() {
|
||||
if (!this.controlLine1) return
|
||||
;[
|
||||
this.controlLine1,
|
||||
this.controlLine2,
|
||||
this.controlPoint1,
|
||||
this.controlPoint2
|
||||
].forEach(item => {
|
||||
item.remove()
|
||||
})
|
||||
this.controlLine1 = null
|
||||
this.controlLine2 = null
|
||||
this.controlPoint1 = null
|
||||
this.controlPoint2 = null
|
||||
}
|
||||
|
||||
// 隐藏控制点
|
||||
function hideControls() {
|
||||
if (!this.controlLine1) return
|
||||
;[
|
||||
this.controlLine1,
|
||||
this.controlLine2,
|
||||
this.controlPoint1,
|
||||
this.controlPoint2
|
||||
].forEach(item => {
|
||||
item.hide()
|
||||
})
|
||||
}
|
||||
|
||||
// 显示控制点
|
||||
function showControls() {
|
||||
if (!this.controlLine1) return
|
||||
;[
|
||||
this.controlLine1,
|
||||
this.controlLine2,
|
||||
this.controlPoint1,
|
||||
this.controlPoint2
|
||||
].forEach(item => {
|
||||
item.show()
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
createControlNodes,
|
||||
createOneControlNode,
|
||||
onControlPointMousedown,
|
||||
onControlPointMousemove,
|
||||
onControlPointMouseup,
|
||||
resetControlPoint,
|
||||
renderControls,
|
||||
removeControls,
|
||||
hideControls,
|
||||
showControls
|
||||
}
|
||||
167
simple-mind-map/src/utils/associativeLineText.js
Normal file
167
simple-mind-map/src/utils/associativeLineText.js
Normal file
@@ -0,0 +1,167 @@
|
||||
import { Text } from '@svgdotjs/svg.js'
|
||||
import { getStrWithBrFromHtml } from './index'
|
||||
|
||||
// 创建文字节点
|
||||
function createText(data) {
|
||||
let g = this.draw.group()
|
||||
const setActive = () => {
|
||||
if (
|
||||
!this.activeLine ||
|
||||
this.activeLine[3] !== data.node ||
|
||||
this.activeLine[4] !== data.toNode
|
||||
) {
|
||||
this.setActiveLine({
|
||||
...data,
|
||||
text: g
|
||||
})
|
||||
}
|
||||
}
|
||||
g.click(e => {
|
||||
e.stopPropagation()
|
||||
setActive()
|
||||
})
|
||||
g.on('dblclick', e => {
|
||||
e.stopPropagation()
|
||||
setActive()
|
||||
if (!this.activeLine) return
|
||||
this.showEditTextBox(g)
|
||||
})
|
||||
return g
|
||||
}
|
||||
|
||||
// 显示文本编辑框
|
||||
function showEditTextBox(g) {
|
||||
this.mindMap.emit('before_show_text_edit')
|
||||
// 注册回车快捷键
|
||||
this.mindMap.keyCommand.addShortcut('Enter', () => {
|
||||
this.hideEditTextBox()
|
||||
})
|
||||
|
||||
if (!this.textEditNode) {
|
||||
this.textEditNode = document.createElement('div')
|
||||
this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;background-color:#fff;box-shadow: 0 0 20px rgba(0,0,0,.5);padding: 3px 5px;margin-left: -5px;margin-top: -3px;outline: none; word-break: break-all;`
|
||||
this.textEditNode.setAttribute('contenteditable', true)
|
||||
this.textEditNode.addEventListener('keyup', e => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
this.textEditNode.addEventListener('click', e => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
document.body.appendChild(this.textEditNode)
|
||||
}
|
||||
let {
|
||||
associativeLineTextFontSize,
|
||||
associativeLineTextFontFamily,
|
||||
associativeLineTextLineHeight
|
||||
} = this.mindMap.themeConfig
|
||||
let scale = this.mindMap.view.scale
|
||||
let [, , , node, toNode] = this.activeLine
|
||||
let textLines = (
|
||||
this.getText(node, toNode) || this.mindMap.opt.defaultAssociativeLineText
|
||||
).split(/\n/gim)
|
||||
this.textEditNode.style.fontFamily = associativeLineTextFontFamily
|
||||
this.textEditNode.style.fontSize = associativeLineTextFontSize * scale + 'px'
|
||||
this.textEditNode.style.lineHeight = textLines.length > 1 ? associativeLineTextLineHeight : 'normal'
|
||||
this.textEditNode.style.zIndex = this.mindMap.opt.nodeTextEditZIndex
|
||||
this.textEditNode.innerHTML = textLines.join('<br>')
|
||||
this.textEditNode.style.display = 'block'
|
||||
this.updateTextEditBoxPos(g)
|
||||
this.showTextEdit = true
|
||||
}
|
||||
|
||||
// 处理画布缩放
|
||||
function onScale() {
|
||||
this.hideEditTextBox()
|
||||
}
|
||||
|
||||
// 更新文本编辑框位置
|
||||
function updateTextEditBoxPos(g) {
|
||||
let rect = g.node.getBoundingClientRect()
|
||||
this.textEditNode.style.minWidth = rect.width + 10 + 'px'
|
||||
this.textEditNode.style.minHeight = rect.height + 6 + 'px'
|
||||
this.textEditNode.style.left = rect.left + 'px'
|
||||
this.textEditNode.style.top = rect.top + 'px'
|
||||
}
|
||||
|
||||
// 隐藏文本编辑框
|
||||
function hideEditTextBox() {
|
||||
if (!this.showTextEdit) {
|
||||
return
|
||||
}
|
||||
let [path, , text, node, toNode] = this.activeLine
|
||||
let str = getStrWithBrFromHtml(this.textEditNode.innerHTML)
|
||||
this.mindMap.execCommand('SET_NODE_DATA', node, {
|
||||
associativeLineText: {
|
||||
...(node.nodeData.data.associativeLineText || {}),
|
||||
[toNode.nodeData.data.id]: str
|
||||
}
|
||||
})
|
||||
this.textEditNode.style.display = 'none'
|
||||
this.textEditNode.innerHTML = ''
|
||||
this.showTextEdit = false
|
||||
this.renderText(str, path, text)
|
||||
this.mindMap.emit('hide_text_edit')
|
||||
}
|
||||
|
||||
// 获取某根关联线的文字
|
||||
function getText(node, toNode) {
|
||||
let obj = node.nodeData.data.associativeLineText
|
||||
if (!obj) {
|
||||
return ''
|
||||
}
|
||||
return obj[toNode.nodeData.data.id] || ''
|
||||
}
|
||||
|
||||
// 渲染关联线文字
|
||||
function renderText(str, path, text) {
|
||||
if (!str) return
|
||||
let { associativeLineTextFontSize, associativeLineTextLineHeight } =
|
||||
this.mindMap.themeConfig
|
||||
text.clear()
|
||||
let textArr = str.split(/\n/gim)
|
||||
textArr.forEach((item, index) => {
|
||||
let node = new Text().text(item)
|
||||
node.y(associativeLineTextFontSize * associativeLineTextLineHeight * index)
|
||||
this.styleText(node)
|
||||
text.add(node)
|
||||
})
|
||||
updateTextPos(path, text)
|
||||
}
|
||||
|
||||
// 给文本设置样式
|
||||
function styleText(node) {
|
||||
let {
|
||||
associativeLineTextColor,
|
||||
associativeLineTextFontSize,
|
||||
associativeLineTextFontFamily
|
||||
} = this.mindMap.themeConfig
|
||||
node
|
||||
.fill({
|
||||
color: associativeLineTextColor
|
||||
})
|
||||
.css({
|
||||
'font-family': associativeLineTextFontFamily,
|
||||
'font-size': associativeLineTextFontSize
|
||||
})
|
||||
}
|
||||
|
||||
// 更新关联线文字位置
|
||||
function updateTextPos(path, text) {
|
||||
let pathLength = path.length()
|
||||
let centerPoint = path.pointAt(pathLength / 2)
|
||||
let { width: textWidth, height: textHeight } = text.bbox()
|
||||
text.x(centerPoint.x - textWidth / 2)
|
||||
text.y(centerPoint.y - textHeight / 2)
|
||||
}
|
||||
|
||||
export default {
|
||||
getText,
|
||||
createText,
|
||||
styleText,
|
||||
onScale,
|
||||
showEditTextBox,
|
||||
hideEditTextBox,
|
||||
updateTextEditBoxPos,
|
||||
renderText,
|
||||
updateTextPos
|
||||
}
|
||||
@@ -28,34 +28,6 @@ export const themeList = [
|
||||
name: '默认',
|
||||
value: 'default',
|
||||
},
|
||||
{
|
||||
name: '脑图经典',
|
||||
value: 'classic',
|
||||
},
|
||||
{
|
||||
name: '小黄人',
|
||||
value: 'minions',
|
||||
},
|
||||
{
|
||||
name: '粉红葡萄',
|
||||
value: 'pinkGrape',
|
||||
},
|
||||
{
|
||||
name: '薄荷',
|
||||
value: 'mint',
|
||||
},
|
||||
{
|
||||
name: '金色vip',
|
||||
value: 'gold',
|
||||
},
|
||||
{
|
||||
name: '活力橙',
|
||||
value: 'vitalityOrange',
|
||||
},
|
||||
{
|
||||
name: '绿叶',
|
||||
value: 'greenLeaf',
|
||||
},
|
||||
{
|
||||
name: '暗色2',
|
||||
value: 'dark2',
|
||||
@@ -72,10 +44,6 @@ export const themeList = [
|
||||
name: '脑图经典3',
|
||||
value: 'classic3',
|
||||
},
|
||||
{
|
||||
name: '脑图经典4',
|
||||
value: 'classic4',
|
||||
},
|
||||
{
|
||||
name: '经典绿',
|
||||
value: 'classicGreen',
|
||||
@@ -112,6 +80,38 @@ export const themeList = [
|
||||
name: '浪漫紫',
|
||||
value: 'romanticPurple',
|
||||
},
|
||||
{
|
||||
name: '粉红葡萄',
|
||||
value: 'pinkGrape',
|
||||
},
|
||||
{
|
||||
name: '薄荷',
|
||||
value: 'mint',
|
||||
},
|
||||
{
|
||||
name: '金色vip',
|
||||
value: 'gold',
|
||||
},
|
||||
{
|
||||
name: '活力橙',
|
||||
value: 'vitalityOrange',
|
||||
},
|
||||
{
|
||||
name: '绿叶',
|
||||
value: 'greenLeaf',
|
||||
},
|
||||
{
|
||||
name: '脑图经典',
|
||||
value: 'classic',
|
||||
},
|
||||
{
|
||||
name: '脑图经典4',
|
||||
value: 'classic4',
|
||||
},
|
||||
{
|
||||
name: '小黄人',
|
||||
value: 'minions',
|
||||
},
|
||||
{
|
||||
name: '简约黑',
|
||||
value: 'simpleBlack',
|
||||
@@ -139,12 +139,25 @@ export const themeList = [
|
||||
{
|
||||
name: '黑金',
|
||||
value: 'blackGold',
|
||||
},
|
||||
{
|
||||
name: '牛油果',
|
||||
value: 'avocado',
|
||||
},
|
||||
{
|
||||
name: '秋天',
|
||||
value: 'autumn',
|
||||
},
|
||||
{
|
||||
name: '橙汁',
|
||||
value: 'orangeJuice',
|
||||
}
|
||||
]
|
||||
|
||||
// 常量
|
||||
export const CONSTANTS = {
|
||||
CHANGE_THEME: 'changeTheme',
|
||||
SET_DATA: 'setData',
|
||||
TRANSFORM_TO_NORMAL_NODE: 'transformAllNodesToNormalNode',
|
||||
MODE: {
|
||||
READONLY: 'readonly',
|
||||
|
||||
@@ -342,4 +342,18 @@ export const getTextFromHtml = (html) => {
|
||||
}
|
||||
getTextFromHtmlEl.innerHTML = html
|
||||
return getTextFromHtmlEl.textContent
|
||||
}
|
||||
|
||||
// 将blob转成data:url
|
||||
export const readBlob = (blob) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let reader = new FileReader()
|
||||
reader.onload = (evt) => {
|
||||
resolve(evt.target.result)
|
||||
}
|
||||
reader.onerror = (err) => {
|
||||
reject(err)
|
||||
}
|
||||
reader.readAsDataURL(blob)
|
||||
})
|
||||
}
|
||||
BIN
web/build/icons/icon.icns
Normal file
BIN
web/build/icons/icon.icns
Normal file
Binary file not shown.
BIN
web/build/icons/icon.ico
Normal file
BIN
web/build/icons/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
web/build/icons/icon.png
Normal file
BIN
web/build/icons/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
2023
web/dist_electron/index.js
Normal file
2023
web/dist_electron/index.js
Normal file
File diff suppressed because one or more lines are too long
84
web/dist_electron/package.json
Normal file
84
web/dist_electron/package.json
Normal file
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"name": "thoughts",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"description": "一个简洁的思维导图",
|
||||
"author": "街角小林<1013335014@qq.com>",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/wanglin2/mind-map"
|
||||
},
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build && node ../copy.js",
|
||||
"lint": "vue-cli-service lint",
|
||||
"autoBuildDoc": "node ./scripts/autoBuildDoc.js",
|
||||
"buildDoc": "node ./scripts/buildDoc.js",
|
||||
"electron:build": "vue-cli-service electron:build",
|
||||
"electron:build-all": "vue-cli-service electron:build -p never -mwl",
|
||||
"electron:build-mac": "vue-cli-service electron:build -p never -m",
|
||||
"electron:build-win": "vue-cli-service electron:build -p never -w",
|
||||
"electron:build-linux": "vue-cli-service electron:build -p never -l",
|
||||
"electron:serve": "vue-cli-service electron:serve",
|
||||
"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",
|
||||
"format": "prettier --write src/* src/*/* src/*/*/* src/*/*/*/*",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"postuninstall": "electron-builder install-app-deps"
|
||||
},
|
||||
"main": "background.js",
|
||||
"dependencies": {
|
||||
"@toast-ui/editor": "^3.1.5",
|
||||
"core-js": "^3.6.5",
|
||||
"electron-json-storage": "^4.6.0",
|
||||
"element-ui": "^2.15.1",
|
||||
"fs-extra": "^7.0.1",
|
||||
"highlight.js": "^10.7.3",
|
||||
"open": "^6.4.0",
|
||||
"uuid": "^3.4.0",
|
||||
"v-viewer": "^1.6.4",
|
||||
"vue": "^2.6.11",
|
||||
"vue-i18n": "^8.27.2",
|
||||
"vue-router": "^3.5.1",
|
||||
"vuex": "^3.6.2",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "^4.5.0",
|
||||
"@vue/cli-plugin-eslint": "^4.5.0",
|
||||
"@vue/cli-service": "^4.5.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"chokidar": "^3.5.3",
|
||||
"electron": "^23.1.1",
|
||||
"electron-devtools-installer": "^3.1.0",
|
||||
"esbuild": "^0.17.15",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"less": "^3.12.2",
|
||||
"less-loader": "^7.1.0",
|
||||
"markdown-it": "^13.0.1",
|
||||
"markdown-it-checkbox": "^1.1.0",
|
||||
"prettier": "^1.19.1",
|
||||
"vue-cli-plugin-electron-builder": "~2.1.1",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"webpack": "^4.44.2"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint"
|
||||
},
|
||||
"rules": {}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
]
|
||||
}
|
||||
122
web/dist_electron/preload.js
Normal file
122
web/dist_electron/preload.js
Normal file
@@ -0,0 +1,122 @@
|
||||
/******/ (function(modules) { // webpackBootstrap
|
||||
/******/ // The module cache
|
||||
/******/ var installedModules = {};
|
||||
/******/
|
||||
/******/ // The require function
|
||||
/******/ function __webpack_require__(moduleId) {
|
||||
/******/
|
||||
/******/ // Check if module is in cache
|
||||
/******/ if(installedModules[moduleId]) {
|
||||
/******/ return installedModules[moduleId].exports;
|
||||
/******/ }
|
||||
/******/ // Create a new module (and put it into the cache)
|
||||
/******/ var module = installedModules[moduleId] = {
|
||||
/******/ i: moduleId,
|
||||
/******/ l: false,
|
||||
/******/ exports: {}
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // Execute the module function
|
||||
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
||||
/******/
|
||||
/******/ // Flag the module as loaded
|
||||
/******/ module.l = true;
|
||||
/******/
|
||||
/******/ // Return the exports of the module
|
||||
/******/ return module.exports;
|
||||
/******/ }
|
||||
/******/
|
||||
/******/
|
||||
/******/ // expose the modules object (__webpack_modules__)
|
||||
/******/ __webpack_require__.m = modules;
|
||||
/******/
|
||||
/******/ // expose the module cache
|
||||
/******/ __webpack_require__.c = installedModules;
|
||||
/******/
|
||||
/******/ // define getter function for harmony exports
|
||||
/******/ __webpack_require__.d = function(exports, name, getter) {
|
||||
/******/ if(!__webpack_require__.o(exports, name)) {
|
||||
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
|
||||
/******/ }
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // define __esModule on exports
|
||||
/******/ __webpack_require__.r = function(exports) {
|
||||
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
|
||||
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
||||
/******/ }
|
||||
/******/ Object.defineProperty(exports, '__esModule', { value: true });
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // create a fake namespace object
|
||||
/******/ // mode & 1: value is a module id, require it
|
||||
/******/ // mode & 2: merge all properties of value into the ns
|
||||
/******/ // mode & 4: return value when already ns object
|
||||
/******/ // mode & 8|1: behave like require
|
||||
/******/ __webpack_require__.t = function(value, mode) {
|
||||
/******/ if(mode & 1) value = __webpack_require__(value);
|
||||
/******/ if(mode & 8) return value;
|
||||
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
|
||||
/******/ var ns = Object.create(null);
|
||||
/******/ __webpack_require__.r(ns);
|
||||
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
|
||||
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
|
||||
/******/ return ns;
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // getDefaultExport function for compatibility with non-harmony modules
|
||||
/******/ __webpack_require__.n = function(module) {
|
||||
/******/ var getter = module && module.__esModule ?
|
||||
/******/ function getDefault() { return module['default']; } :
|
||||
/******/ function getModuleExports() { return module; };
|
||||
/******/ __webpack_require__.d(getter, 'a', getter);
|
||||
/******/ return getter;
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // Object.prototype.hasOwnProperty.call
|
||||
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
|
||||
/******/
|
||||
/******/ // __webpack_public_path__
|
||||
/******/ __webpack_require__.p = "";
|
||||
/******/
|
||||
/******/
|
||||
/******/ // Load entry module and return exports
|
||||
/******/ return __webpack_require__(__webpack_require__.s = 0);
|
||||
/******/ })
|
||||
/************************************************************************/
|
||||
/******/ ({
|
||||
|
||||
/***/ "./src/electron/preload.js":
|
||||
/*!*********************************!*\
|
||||
!*** ./src/electron/preload.js ***!
|
||||
\*********************************/
|
||||
/*! no static exports found */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
eval("const { contextBridge, ipcRenderer } = __webpack_require__(/*! electron */ \"electron\")\r\n\r\ncontextBridge.exposeInMainWorld('platform', process.platform)\r\ncontextBridge.exposeInMainWorld('IS_ELECTRON', true)\r\n\r\ncontextBridge.exposeInMainWorld('electronAPI', {\r\n minimize: () => ipcRenderer.send('minimize'),\r\n maximize: () => ipcRenderer.send('maximize'),\r\n unmaximize: () => ipcRenderer.send('unmaximize'),\r\n close: () => ipcRenderer.send('close'),\r\n destroy: () => ipcRenderer.send('destroy'),\r\n create: id => ipcRenderer.send('create', id),\r\n getFileContent: id => ipcRenderer.invoke('getFileContent', id),\r\n save: (id, data, fileName) => ipcRenderer.invoke('save', id, data, fileName),\r\n rename: (id, name) => ipcRenderer.invoke('rename', id, name),\r\n openUrl: url => ipcRenderer.send('openUrl', url),\r\n addRecentFileList: (fileList) => ipcRenderer.invoke('addRecentFileList', fileList),\r\n getRecentFileList: () => ipcRenderer.invoke('getRecentFileList'),\r\n clearRecentFileList: () => ipcRenderer.invoke('clearRecentFileList'),\r\n openFileInDir: file => ipcRenderer.send('openFileInDir', file),\r\n deleteFile: file => ipcRenderer.invoke('deleteFile', file),\r\n onRefreshRecentFileList: callback =>\r\n ipcRenderer.on('refreshRecentFileList', callback),\r\n openFile: file => ipcRenderer.send('openFile', file),\r\n selectOpenFile: () => ipcRenderer.send('selectOpenFile'),\r\n copyFile: file => ipcRenderer.invoke('copyFile', file)\r\n})\r\n\n\n//# sourceURL=webpack:///./src/electron/preload.js?");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 0:
|
||||
/*!***************************************!*\
|
||||
!*** multi ./src/electron/preload.js ***!
|
||||
\***************************************/
|
||||
/*! no static exports found */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
eval("module.exports = __webpack_require__(/*! E:\\wanglin\\mind-map\\web\\src\\electron\\preload.js */\"./src/electron/preload.js\");\n\n\n//# sourceURL=webpack:///multi_./src/electron/preload.js?");
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "electron":
|
||||
/*!***************************!*\
|
||||
!*** external "electron" ***!
|
||||
\***************************/
|
||||
/*! no static exports found */
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
eval("module.exports = require(\"electron\");\n\n//# sourceURL=webpack:///external_%22electron%22?");
|
||||
|
||||
/***/ })
|
||||
|
||||
/******/ });
|
||||
18423
web/package-lock.json
generated
18423
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -2,20 +2,39 @@
|
||||
"name": "thoughts",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"description": "一个简洁的思维导图",
|
||||
"author": "街角小林<1013335014@qq.com>",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/wanglin2/mind-map"
|
||||
},
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build && node ../copy.js",
|
||||
"lint": "vue-cli-service lint",
|
||||
"autoBuildDoc": "node ./scripts/autoBuildDoc.js",
|
||||
"buildDoc": "node ./scripts/buildDoc.js",
|
||||
"electron:build": "vue-cli-service electron:build",
|
||||
"electron:build-all": "vue-cli-service electron:build -p never -mwl",
|
||||
"electron:build-mac": "vue-cli-service electron:build -p never -m",
|
||||
"electron:build-win": "vue-cli-service electron:build -p never -w",
|
||||
"electron:build-linux": "vue-cli-service electron:build -p never -l",
|
||||
"electron:serve": "vue-cli-service electron:serve",
|
||||
"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",
|
||||
"format": "prettier --write src/* src/*/* src/*/*/* src/*/*/*/*",
|
||||
"buildDoc": "node ./scripts/buildDoc.js",
|
||||
"autoBuildDoc": "node ./scripts/autoBuildDoc.js"
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"postuninstall": "electron-builder install-app-deps"
|
||||
},
|
||||
"main": "background.js",
|
||||
"dependencies": {
|
||||
"@toast-ui/editor": "^3.1.5",
|
||||
"core-js": "^3.6.5",
|
||||
"electron-json-storage": "^4.6.0",
|
||||
"element-ui": "^2.15.1",
|
||||
"fs-extra": "^7.0.1",
|
||||
"highlight.js": "^10.7.3",
|
||||
"open": "^6.4.0",
|
||||
"uuid": "^3.4.0",
|
||||
"v-viewer": "^1.6.4",
|
||||
"vue": "^2.6.11",
|
||||
"vue-i18n": "^8.27.2",
|
||||
@@ -29,6 +48,8 @@
|
||||
"@vue/cli-service": "^4.5.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"chokidar": "^3.5.3",
|
||||
"electron": "^23.1.1",
|
||||
"electron-devtools-installer": "^3.1.0",
|
||||
"esbuild": "^0.17.15",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
@@ -37,6 +58,7 @@
|
||||
"markdown-it": "^13.0.1",
|
||||
"markdown-it-checkbox": "^1.1.0",
|
||||
"prettier": "^1.19.1",
|
||||
"vue-cli-plugin-electron-builder": "~2.1.1",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"webpack": "^4.44.2"
|
||||
},
|
||||
|
||||
BIN
web/src/.DS_Store
vendored
BIN
web/src/.DS_Store
vendored
Binary file not shown.
@@ -34,7 +34,17 @@ export const getData = () => {
|
||||
return simpleDeepClone(exampleData)
|
||||
} else {
|
||||
try {
|
||||
return JSON.parse(store)
|
||||
let parsedData = JSON.parse(store)
|
||||
if (window.IS_ELECTRON) {
|
||||
return simpleDeepClone(exampleData)
|
||||
let { root, ...rest } = parsedData
|
||||
return {
|
||||
...rest,
|
||||
root: simpleDeepClone(exampleData).root
|
||||
}
|
||||
} else {
|
||||
return parsedData
|
||||
}
|
||||
} catch (error) {
|
||||
return simpleDeepClone(exampleData)
|
||||
}
|
||||
|
||||
BIN
web/src/assets/.DS_Store
vendored
BIN
web/src/assets/.DS_Store
vendored
Binary file not shown.
BIN
web/src/assets/img/autumn.jpg
Normal file
BIN
web/src/assets/img/autumn.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.8 KiB |
BIN
web/src/assets/img/avocado.jpg
Normal file
BIN
web/src/assets/img/avocado.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.5 KiB |
BIN
web/src/assets/img/icon.png
Normal file
BIN
web/src/assets/img/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
BIN
web/src/assets/img/orangeJuice.jpg
Normal file
BIN
web/src/assets/img/orangeJuice.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.6 KiB |
114
web/src/background.js
Normal file
114
web/src/background.js
Normal file
@@ -0,0 +1,114 @@
|
||||
'use strict'
|
||||
|
||||
import { app, protocol, BrowserWindow } from 'electron'
|
||||
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
|
||||
import path from 'path'
|
||||
import { bindFileHandleEvent } from './electron/fileHandle'
|
||||
import { bindOtherHandleEvent } from './electron/otherHandle'
|
||||
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production'
|
||||
|
||||
// 在应用程序准备就绪之前,必须注册方案
|
||||
protocol.registerSchemesAsPrivileged([
|
||||
{ scheme: 'app', privileges: { secure: true, standard: true } }
|
||||
])
|
||||
|
||||
// 创建主页面
|
||||
let mainWindow = null
|
||||
async function createMainWindow() {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
frame: false,
|
||||
titleBarStyle: 'hiddenInset',
|
||||
webPreferences: {
|
||||
webSecurity: false,
|
||||
nodeIntegration: true,
|
||||
enableRemoteModule: true,
|
||||
contextIsolation: true,
|
||||
preload: path.join(__dirname, 'preload.js')
|
||||
}
|
||||
})
|
||||
|
||||
if (process.env.WEBPACK_DEV_SERVER_URL) {
|
||||
// 如果处于开发模式,则加载开发服务器的url
|
||||
await mainWindow.loadURL(
|
||||
process.env.WEBPACK_DEV_SERVER_URL + '/#/workbenche'
|
||||
)
|
||||
// if (!process.env.IS_TEST) mainWindow.webContents.openDevTools()
|
||||
} else {
|
||||
createProtocol('app')
|
||||
// 非开发环境时加载index.html
|
||||
mainWindow.loadURL('app://./index.html/#/workbenche')
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定事件
|
||||
const bindEvent = () => {
|
||||
bindFileHandleEvent({ mainWindow, initOpenFileQueue })
|
||||
bindOtherHandleEvent()
|
||||
}
|
||||
|
||||
// 关闭所有窗口后退出
|
||||
app.on('window-all-closed', () => {
|
||||
// 在macOS上,应用程序及其菜单栏通常保持活动状态,直到用户使用Cmd+Q明确退出
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
// 在macOS上,当点击dock图标且没有其他窗口打开时,通常会在应用程序中重新创建一个窗口。
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createMainWindow()
|
||||
// bindEvent()
|
||||
}
|
||||
})
|
||||
|
||||
// Attempt to bind file opening #2
|
||||
// https://stackoverflow.com/questions/62420427/how-do-i-make-my-electron-app-the-default-for-opening-files
|
||||
// https://github.com/rchrd2/example-electron-file-association
|
||||
// https://www.jianshu.com/p/a32542277b83
|
||||
const initOpenFileQueue = []
|
||||
app.on('will-finish-launching', () => {
|
||||
// Event fired When someone drags files onto the icon while your app is running
|
||||
if (process.platform == 'win32') {
|
||||
const argv = process.argv
|
||||
if (argv) {
|
||||
argv.forEach(filePath => {
|
||||
if (filePath.indexOf('.smm') >= 0) {
|
||||
initOpenFileQueue.push(filePath)
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
app.on('open-file', (event, file) => {
|
||||
if (app.isReady() === false) {
|
||||
initOpenFileQueue.push(file)
|
||||
} else {
|
||||
console.log(file)
|
||||
}
|
||||
event.preventDefault()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
app.on('ready', async () => {
|
||||
createMainWindow()
|
||||
bindEvent()
|
||||
})
|
||||
|
||||
// 在开发模式下,应父进程的请求干净地退出。
|
||||
if (isDevelopment) {
|
||||
if (process.platform === 'win32') {
|
||||
process.on('message', data => {
|
||||
if (data === 'graceful-exit') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
process.on('SIGTERM', () => {
|
||||
app.quit()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -40,5 +40,8 @@ export const themeMap = {
|
||||
blackHumour: require('../assets/img/blackHumour.jpg'),
|
||||
lateNightOffice: require('../assets/img/lateNightOffice.jpg'),
|
||||
blackGold: require('../assets/img/blackGold.jpg'),
|
||||
autumn: require('../assets/img/autumn.jpg'),
|
||||
avocado: require('../assets/img/avocado.jpg'),
|
||||
orangeJuice: require('../assets/img/orangeJuice.jpg'),
|
||||
}
|
||||
|
||||
3
web/src/css/global.css
Normal file
3
web/src/css/global.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.noDrag {
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
251
web/src/electron/fileHandle.js
Normal file
251
web/src/electron/fileHandle.js
Normal file
@@ -0,0 +1,251 @@
|
||||
import { BrowserWindow, ipcMain, dialog, shell } from 'electron'
|
||||
import fs from 'fs-extra'
|
||||
import path from 'path'
|
||||
import {
|
||||
saveToRecent,
|
||||
clearRecent,
|
||||
removeFileInRecent,
|
||||
replaceFileInRecent,
|
||||
getRecent,
|
||||
saveFileListToRecent
|
||||
} from './storage'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
export const bindFileHandleEvent = ({ mainWindow, initOpenFileQueue }) => {
|
||||
// 通知主页面刷新最近文件列表
|
||||
const notifyMainWindowRefreshRecentFileList = () => {
|
||||
mainWindow.webContents.send('refreshRecentFileList')
|
||||
}
|
||||
|
||||
// 新建编辑页面
|
||||
const openIds = []
|
||||
const createEditWindow = async (event, id) => {
|
||||
openIds.push(id)
|
||||
const win = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
frame: false,
|
||||
titleBarStyle: 'hiddenInset',
|
||||
webPreferences: {
|
||||
webSecurity: false,
|
||||
nodeIntegration: true,
|
||||
enableRemoteModule: true,
|
||||
contextIsolation: true,
|
||||
preload: path.join(__dirname, 'preload.js')
|
||||
}
|
||||
})
|
||||
win.on('closed', () => {
|
||||
// 从openIds数组中删除
|
||||
let index = openIds.find(item => {
|
||||
return item === id
|
||||
})
|
||||
if (index !== -1) {
|
||||
openIds.splice(index, 1)
|
||||
}
|
||||
// 从idToFilePath中删除
|
||||
delete idToFilePath[id]
|
||||
})
|
||||
if (process.env.WEBPACK_DEV_SERVER_URL) {
|
||||
// Load the url of the dev server if in development mode
|
||||
win.loadURL(
|
||||
process.env.WEBPACK_DEV_SERVER_URL + '/#/workbenche/edit/' + id
|
||||
)
|
||||
// if (!process.env.IS_TEST) win.webContents.openDevTools()
|
||||
} else {
|
||||
// Load the index.html when not in development
|
||||
win.loadURL('app://./index.html/#/workbenche/edit/' + id)
|
||||
}
|
||||
}
|
||||
ipcMain.on('create', createEditWindow)
|
||||
|
||||
// 保存文件
|
||||
const idToFilePath = {}
|
||||
ipcMain.handle('save', async (event, id, data, fileName = '未命名') => {
|
||||
if (!idToFilePath[id]) {
|
||||
const webContents = event.sender
|
||||
const win = BrowserWindow.fromWebContents(webContents)
|
||||
const res = dialog.showSaveDialogSync(win, {
|
||||
title: '保存',
|
||||
defaultPath: fileName + '.smm',
|
||||
filters: [{ name: '思维导图', extensions: ['smm'] }]
|
||||
})
|
||||
if (res) {
|
||||
idToFilePath[id] = res
|
||||
fs.writeFile(res, data)
|
||||
saveToRecent(res).then(() => {
|
||||
notifyMainWindowRefreshRecentFileList()
|
||||
})
|
||||
return path.parse(idToFilePath[id]).name
|
||||
}
|
||||
} else {
|
||||
fs.writeFile(idToFilePath[id], data)
|
||||
}
|
||||
})
|
||||
|
||||
// 打开文件
|
||||
const openFile = (event, file) => {
|
||||
let id = uuid()
|
||||
idToFilePath[id] = file
|
||||
saveToRecent(file).then(() => {
|
||||
notifyMainWindowRefreshRecentFileList()
|
||||
})
|
||||
createEditWindow(null, id)
|
||||
}
|
||||
ipcMain.on('openFile', openFile)
|
||||
|
||||
// 选择打开本地文件
|
||||
ipcMain.on('selectOpenFile', event => {
|
||||
const res = dialog.showOpenDialogSync({
|
||||
title: '选择',
|
||||
filters: [{ name: '思维导图', extensions: ['smm'] }]
|
||||
})
|
||||
if (res && res[0]) {
|
||||
openFile(null, res[0])
|
||||
}
|
||||
})
|
||||
|
||||
// 获取文件内容
|
||||
ipcMain.handle('getFileContent', (event, id) => {
|
||||
return new Promise(resolve => {
|
||||
let file = idToFilePath[id]
|
||||
if (!file) {
|
||||
resolve(null)
|
||||
return
|
||||
}
|
||||
fs.readFile(file, { encoding: 'utf-8' }, (err, data) => {
|
||||
resolve({
|
||||
name: path.parse(file).name,
|
||||
content: JSON.parse(data)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// 重命名文件
|
||||
ipcMain.handle('rename', (event, id, name) => {
|
||||
return new Promise(resolve => {
|
||||
if (!idToFilePath[id]) {
|
||||
resolve('文件不存在')
|
||||
return
|
||||
}
|
||||
let oldPath = idToFilePath[id]
|
||||
let { base, ...oldPathData } = path.parse(oldPath)
|
||||
oldPathData.name = name
|
||||
let newPath = path.format(oldPathData)
|
||||
idToFilePath[id] = newPath
|
||||
fs.rename(oldPath, newPath, err => {
|
||||
if (err) {
|
||||
resolve('重命名失败')
|
||||
} else {
|
||||
replaceFileInRecent(oldPath, newPath).then(() => {
|
||||
notifyMainWindowRefreshRecentFileList()
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// 获取最近文件列表
|
||||
ipcMain.handle('getRecentFileList', () => {
|
||||
return getRecent().map(item => {
|
||||
let data = path.parse(item)
|
||||
return {
|
||||
url: item,
|
||||
dir: data.dir,
|
||||
name: data.name
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 清空最近文件列表
|
||||
ipcMain.handle('clearRecentFileList', async () => {
|
||||
try {
|
||||
clearRecent()
|
||||
return ''
|
||||
} catch (error) {
|
||||
return '清空失败'
|
||||
}
|
||||
})
|
||||
|
||||
// 添加到最近文件列表
|
||||
ipcMain.handle('addRecentFileList', async (event, fileList) => {
|
||||
try {
|
||||
console.log(fileList);
|
||||
await saveFileListToRecent(fileList)
|
||||
notifyMainWindowRefreshRecentFileList()
|
||||
} catch (error) {
|
||||
return error
|
||||
}
|
||||
})
|
||||
|
||||
// 打开指定目录
|
||||
ipcMain.on('openFileInDir', (event, file) => {
|
||||
shell.showItemInFolder(file)
|
||||
})
|
||||
|
||||
// 删除指定文件
|
||||
ipcMain.handle('deleteFile', (event, file) => {
|
||||
let res = ''
|
||||
let id = Object.keys(idToFilePath).find(item => {
|
||||
return idToFilePath[item] === file
|
||||
})
|
||||
let index = -1
|
||||
if (id) {
|
||||
index = openIds.findIndex(item => {
|
||||
return item === id
|
||||
})
|
||||
}
|
||||
if (index === -1) {
|
||||
try {
|
||||
fs.rmSync(file)
|
||||
} catch (error) {}
|
||||
removeFileInRecent(file)
|
||||
} else {
|
||||
res = '该文件正在编辑,请关闭后再试'
|
||||
}
|
||||
return res
|
||||
})
|
||||
|
||||
// 复制文件
|
||||
ipcMain.handle('copyFile', async (event, file) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.pathExists(file, (err, exists) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
if (exists) {
|
||||
let { base, ...oldPathData } = path.parse(file)
|
||||
let newName = oldPathData.name + '-复制'
|
||||
let index = 1
|
||||
oldPathData.name = newName
|
||||
let newPath = path.format(oldPathData)
|
||||
// 检查新路径是否已存在
|
||||
while (fs.pathExistsSync(newPath)) {
|
||||
oldPathData.name = newName + index
|
||||
newPath = path.format(oldPathData)
|
||||
index++
|
||||
}
|
||||
fs.copy(file, newPath, err => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
saveToRecent(newPath).then(() => {
|
||||
notifyMainWindowRefreshRecentFileList()
|
||||
})
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
reject('文件不存在')
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// 直接双击文件打开应用时,需要直接打开该文件编辑
|
||||
initOpenFileQueue.forEach((file) => {
|
||||
openFile(null, file)
|
||||
})
|
||||
}
|
||||
18
web/src/electron/otherHandle.js
Normal file
18
web/src/electron/otherHandle.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import { BrowserWindow, ipcMain } from 'electron'
|
||||
import open from 'open'
|
||||
|
||||
export const bindOtherHandleEvent = () => {
|
||||
// 处理缩放事件
|
||||
;['minimize', 'maximize', 'unmaximize', 'close', 'destroy'].forEach(item => {
|
||||
ipcMain.on(item, event => {
|
||||
const webContents = event.sender
|
||||
const win = BrowserWindow.fromWebContents(webContents)
|
||||
win[item]()
|
||||
})
|
||||
})
|
||||
|
||||
// 使用默认浏览器打开指定url
|
||||
ipcMain.on('openUrl', (event, url) => {
|
||||
open(url)
|
||||
})
|
||||
}
|
||||
27
web/src/electron/preload.js
Normal file
27
web/src/electron/preload.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const { contextBridge, ipcRenderer } = require('electron')
|
||||
|
||||
contextBridge.exposeInMainWorld('platform', process.platform)
|
||||
contextBridge.exposeInMainWorld('IS_ELECTRON', true)
|
||||
|
||||
contextBridge.exposeInMainWorld('electronAPI', {
|
||||
minimize: () => ipcRenderer.send('minimize'),
|
||||
maximize: () => ipcRenderer.send('maximize'),
|
||||
unmaximize: () => ipcRenderer.send('unmaximize'),
|
||||
close: () => ipcRenderer.send('close'),
|
||||
destroy: () => ipcRenderer.send('destroy'),
|
||||
create: id => ipcRenderer.send('create', id),
|
||||
getFileContent: id => ipcRenderer.invoke('getFileContent', id),
|
||||
save: (id, data, fileName) => ipcRenderer.invoke('save', id, data, fileName),
|
||||
rename: (id, name) => ipcRenderer.invoke('rename', id, name),
|
||||
openUrl: url => ipcRenderer.send('openUrl', url),
|
||||
addRecentFileList: (fileList) => ipcRenderer.invoke('addRecentFileList', fileList),
|
||||
getRecentFileList: () => ipcRenderer.invoke('getRecentFileList'),
|
||||
clearRecentFileList: () => ipcRenderer.invoke('clearRecentFileList'),
|
||||
openFileInDir: file => ipcRenderer.send('openFileInDir', file),
|
||||
deleteFile: file => ipcRenderer.invoke('deleteFile', file),
|
||||
onRefreshRecentFileList: callback =>
|
||||
ipcRenderer.on('refreshRecentFileList', callback),
|
||||
openFile: file => ipcRenderer.send('openFile', file),
|
||||
selectOpenFile: () => ipcRenderer.send('selectOpenFile'),
|
||||
copyFile: file => ipcRenderer.invoke('copyFile', file)
|
||||
})
|
||||
109
web/src/electron/storage.js
Normal file
109
web/src/electron/storage.js
Normal file
@@ -0,0 +1,109 @@
|
||||
import storage from 'electron-json-storage'
|
||||
|
||||
export const RECENT_FILE_LIST = 'recentFileList'
|
||||
|
||||
// 保存到最近文件
|
||||
export const saveToRecent = file => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let list = getRecent()
|
||||
let index = list.findIndex(item => {
|
||||
return item === file
|
||||
})
|
||||
if (index !== -1) {
|
||||
list.splice(index, 1)
|
||||
}
|
||||
list.push(file)
|
||||
storage.set(RECENT_FILE_LIST, list, err => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 保存到最近文件
|
||||
export const saveFileListToRecent = fileList => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let list = getRecent()
|
||||
fileList.forEach(file => {
|
||||
let index = list.findIndex(item => {
|
||||
return item === file
|
||||
})
|
||||
if (index !== -1) {
|
||||
list.splice(index, 1)
|
||||
}
|
||||
list.push(file)
|
||||
})
|
||||
storage.set(RECENT_FILE_LIST, list, err => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 获取最近文件列表
|
||||
export const getRecent = () => {
|
||||
let res = storage.getSync(RECENT_FILE_LIST)
|
||||
return (Array.isArray(res) ? res : []).filter(item => {
|
||||
return !!item
|
||||
})
|
||||
}
|
||||
|
||||
// 清除最近文件列表
|
||||
export const clearRecent = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
storage.remove(RECENT_FILE_LIST, err => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 从最近文件列表中移除指定文件
|
||||
export const removeFileInRecent = file => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let list = getRecent()
|
||||
let index = list.findIndex(item => {
|
||||
return item === file
|
||||
})
|
||||
if (index !== -1) {
|
||||
list.splice(index, 1)
|
||||
}
|
||||
storage.set(RECENT_FILE_LIST, list, err => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 替换指定文件
|
||||
export const replaceFileInRecent = (oldFile, newFile) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let list = getRecent()
|
||||
let index = list.findIndex(item => {
|
||||
return item === oldFile
|
||||
})
|
||||
if (index !== -1) {
|
||||
list.splice(index, 1)
|
||||
}
|
||||
list.push(newFile)
|
||||
storage.set(RECENT_FILE_LIST, list, err => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -8,11 +8,21 @@ import '@/assets/icon-font/iconfont.css'
|
||||
import 'viewerjs/dist/viewer.css'
|
||||
import VueViewer from 'v-viewer'
|
||||
import i18n from './i18n'
|
||||
import './css/global.css'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
Vue.prototype.$bus = new Vue()
|
||||
Vue.use(ElementUI)
|
||||
Vue.use(VueViewer)
|
||||
Vue.mixin({
|
||||
data () {
|
||||
return {
|
||||
IS_ELECTRON: window.IS_ELECTRON,
|
||||
IS_MAC: window.platform === 'darwin',
|
||||
IS_WIN: window.platform === 'win32'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
new Vue({
|
||||
render: h => h(App),
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
|
||||
> The function of adjusting associated line control points is supported from v0.4.6+
|
||||
|
||||
This plugin is used to support the addition of associative lines.
|
||||
> Relevance support for text editing starting from v0.5.11+
|
||||
|
||||
The plugin is currently not fully functional, and does not support adding text to association lines.
|
||||
This plugin is used to support the addition of associative lines.
|
||||
|
||||
## Register
|
||||
|
||||
|
||||
@@ -7,8 +7,10 @@
|
||||
<blockquote>
|
||||
<p>The function of adjusting associated line control points is supported from v0.4.6+</p>
|
||||
</blockquote>
|
||||
<blockquote>
|
||||
<p>Relevance support for text editing starting from v0.5.11+</p>
|
||||
</blockquote>
|
||||
<p>This plugin is used to support the addition of associative lines.</p>
|
||||
<p>The plugin is currently not fully functional, and does not support adding text to association lines.</p>
|
||||
<h2>Register</h2>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> MindMap <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map'</span>
|
||||
<span class="hljs-keyword">import</span> AssociativeLine <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/AssociativeLine.js'</span>
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# Changelog
|
||||
|
||||
## 0.5.11
|
||||
|
||||
New: Supports associative text editing.
|
||||
|
||||
optimization: Optimizing theme configuration updates, changing configurations that do not involve node size does not trigger node recalculation.
|
||||
|
||||
## 0.5.10
|
||||
|
||||
New: Optimize node reuse logic using LRU caching algorithm.
|
||||
|
||||
## 0.5.10-fix.1
|
||||
|
||||
Fix: Fix the issue of import errors.
|
||||
|
||||
## 0.5.10-fix.2
|
||||
|
||||
Fix: Fixed the issue of switching themes and importing data without triggering data changes in rich text mode.
|
||||
|
||||
New: Add three new themes.
|
||||
|
||||
## 0.5.9
|
||||
|
||||
Change: Unified export method format, using `FileReader` instead of `URL.createObjectURL` to convert `blob` data.
|
||||
|
||||
## 0.5.8
|
||||
|
||||
optimization: 1.The position setting is not triggered when the node position does not change. 2.The unfolding and folding status does not change and does not trigger button updates.
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Changelog</h1>
|
||||
<h2>0.5.11</h2>
|
||||
<p>New: Supports associative text editing.</p>
|
||||
<p>optimization: Optimizing theme configuration updates, changing configurations that do not involve node size does not trigger node recalculation.</p>
|
||||
<h2>0.5.10</h2>
|
||||
<p>New: Optimize node reuse logic using LRU caching algorithm.</p>
|
||||
<h2>0.5.10-fix.1</h2>
|
||||
<p>Fix: Fix the issue of import errors.</p>
|
||||
<h2>0.5.10-fix.2</h2>
|
||||
<p>Fix: Fixed the issue of switching themes and importing data without triggering data changes in rich text mode.</p>
|
||||
<p>New: Add three new themes.</p>
|
||||
<h2>0.5.9</h2>
|
||||
<p>Change: Unified export method format, using <code>FileReader</code> instead of <code>URL.createObjectURL</code> to convert <code>blob</code> data.</p>
|
||||
<h2>0.5.8</h2>
|
||||
<p>optimization: 1.The position setting is not triggered when the node position does not change. 2.The unfolding and folding status does not change and does not trigger button updates.</p>
|
||||
<p>New: 1.The default setting is to move the mouse over the node to display the expand and collapse buttons. 2.Support the list of icons that can be inserted into extended nodes.</p>
|
||||
|
||||
@@ -28,7 +28,7 @@ const mindMap = new MindMap({
|
||||
| data | Object | {} | Mind map data, refer to: https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js | |
|
||||
| layout | String | logicalStructure | Layout type, options: logicalStructure (logical structure diagram), mindMap (mind map), catalogOrganization (catalog organization diagram), organizationStructure (organization structure diagram)、timeline(v0.5.4+, timeline)、timeline2(v0.5.4+, up down alternating timeline)、fishbone(v0.5.4+, fishbone diagram) | |
|
||||
| fishboneDeg(v0.5.4+) | Number | 45 | Set the diagonal angle of the fishbone structure diagram | |
|
||||
| theme | String | default | Theme, options: default, classic, minions, pinkGrape, mint, gold, vitalityOrange, greenLeaf, dark2, skyGreen, classic2, classic3, classic4(v0.2.0+), classicGreen, classicBlue, blueSky, brainImpairedPink, dark, earthYellow, freshGreen, freshRed, romanticPurple, simpleBlack(v0.5.4+), courseGreen(v0.5.4+), coffee(v0.5.4+), redSpirit(v0.5.4+), blackHumour(v0.5.4+), lateNightOffice(v0.5.4+), blackGold(v0.5.4+) | |
|
||||
| theme | String | default | Theme, options: default, classic, minions, pinkGrape, mint, gold, vitalityOrange, greenLeaf, dark2, skyGreen, classic2, classic3, classic4(v0.2.0+), classicGreen, classicBlue, blueSky, brainImpairedPink, dark, earthYellow, freshGreen, freshRed, romanticPurple, simpleBlack(v0.5.4+), courseGreen(v0.5.4+), coffee(v0.5.4+), redSpirit(v0.5.4+), blackHumour(v0.5.4+), lateNightOffice(v0.5.4+), blackGold(v0.5.4+)、、avocado(v.5.10-fix.2+)、autumn(v.5.10-fix.2+)、orangeJuice(v.5.10-fix.2+) | |
|
||||
| themeConfig | Object | {} | Theme configuration, will be merged with the selected theme, available fields refer to: https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/themes/default.js | |
|
||||
| scaleRatio | Number | 0.1 | The incremental scaling ratio | |
|
||||
| maxTag | Number | 5 | The maximum number of tags displayed in the node, any additional tags will be discarded | |
|
||||
@@ -61,6 +61,7 @@ const mindMap = new MindMap({
|
||||
| maxHistoryCount(v0.5.6+) | Number | 1000 | | Maximum number of history records |
|
||||
| alwaysShowExpandBtn(v0.5.8+) | Boolean | false | Whether to always display the expand and collapse buttons of nodes, which are only displayed when the mouse is moved up and activated by default | |
|
||||
| iconList(v0.5.8+) | Array | [] | The icons that can be inserted into the extension node, and each item in the array is an object. Please refer to the "Icon Configuration" table below for the detailed structure of the object | |
|
||||
| maxNodeCacheCount(v0.5.10+) | Number | 1000 | The maximum number of cached nodes. To optimize performance, an internal node cache pool is maintained to reuse nodes. This attribute allows you to specify the maximum number of caches in the pool | |
|
||||
|
||||
### Watermark config
|
||||
|
||||
@@ -139,10 +140,14 @@ List of all currently registered plugins.
|
||||
|
||||
## Instance methods
|
||||
|
||||
### getSvgData()
|
||||
### getSvgData({ paddingX = 0, paddingY = 0 })
|
||||
|
||||
> v0.3.0+
|
||||
|
||||
`paddingX`: Padding x
|
||||
|
||||
`paddingY`: Padding y
|
||||
|
||||
Get the `svg` data and return an object. The detailed structure is as follows:
|
||||
|
||||
```js
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
<td>theme</td>
|
||||
<td>String</td>
|
||||
<td>default</td>
|
||||
<td>Theme, options: default, classic, minions, pinkGrape, mint, gold, vitalityOrange, greenLeaf, dark2, skyGreen, classic2, classic3, classic4(v0.2.0+), classicGreen, classicBlue, blueSky, brainImpairedPink, dark, earthYellow, freshGreen, freshRed, romanticPurple, simpleBlack(v0.5.4+), courseGreen(v0.5.4+), coffee(v0.5.4+), redSpirit(v0.5.4+), blackHumour(v0.5.4+), lateNightOffice(v0.5.4+), blackGold(v0.5.4+)</td>
|
||||
<td>Theme, options: default, classic, minions, pinkGrape, mint, gold, vitalityOrange, greenLeaf, dark2, skyGreen, classic2, classic3, classic4(v0.2.0+), classicGreen, classicBlue, blueSky, brainImpairedPink, dark, earthYellow, freshGreen, freshRed, romanticPurple, simpleBlack(v0.5.4+), courseGreen(v0.5.4+), coffee(v0.5.4+), redSpirit(v0.5.4+), blackHumour(v0.5.4+), lateNightOffice(v0.5.4+), blackGold(v0.5.4+)、、avocado(v.5.10-fix.2+)、autumn(v.5.10-fix.2+)、orangeJuice(v.5.10-fix.2+)</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -287,6 +287,13 @@
|
||||
<td>The icons that can be inserted into the extension node, and each item in the array is an object. Please refer to the "Icon Configuration" table below for the detailed structure of the object</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>maxNodeCacheCount(v0.5.10+)</td>
|
||||
<td>Number</td>
|
||||
<td>1000</td>
|
||||
<td>The maximum number of cached nodes. To optimize performance, an internal node cache pool is maintained to reuse nodes. This attribute allows you to specify the maximum number of caches in the pool</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Watermark config</h3>
|
||||
@@ -406,10 +413,12 @@ mindMap.setTheme(<span class="hljs-string">'Theme name'</span>)
|
||||
</blockquote>
|
||||
<p>List of all currently registered plugins.</p>
|
||||
<h2>Instance methods</h2>
|
||||
<h3>getSvgData()</h3>
|
||||
<h3>getSvgData({ paddingX = 0, paddingY = 0 })</h3>
|
||||
<blockquote>
|
||||
<p>v0.3.0+</p>
|
||||
</blockquote>
|
||||
<p><code>paddingX</code>: Padding x</p>
|
||||
<p><code>paddingY</code>: Padding y</p>
|
||||
<p>Get the <code>svg</code> data and return an object. The detailed structure is as follows:</p>
|
||||
<pre class="hljs"><code>{
|
||||
svg, <span class="hljs-comment">// Element, the overall svg element of the mind map graphics, including: svg (canvas container), g (actual mind map group)</span>
|
||||
|
||||
@@ -15,14 +15,35 @@ After registration and instantiation of `MindMap`, the instance can be obtained
|
||||
|
||||
## Methods
|
||||
|
||||
All exported methods are asynchronous and return an instance of `Promise`. You can use the `then` method to obtain data, or use the `async await` function to obtain:
|
||||
|
||||
```js
|
||||
mindMap.doExport.png().then((data) => {
|
||||
// ...
|
||||
})
|
||||
|
||||
const export = async () => {
|
||||
let data = await mindMap.doExport.png()
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
The returned data is in the format of `data:URL`. You can create an `a` tag to trigger the download:
|
||||
|
||||
```js
|
||||
let a = document.createElement('a')
|
||||
a.href = 'xxx.png'// .png、.svg、.pdf、.md、.json、.smm
|
||||
a.download = 'xxx'
|
||||
a.click()
|
||||
```
|
||||
|
||||
### png(name, transparent = false)
|
||||
|
||||
- `name`: Name, optional
|
||||
|
||||
- `transparent`: v0.5.7+, Specify whether the background of the exported image is transparent
|
||||
|
||||
Exports as `png`, an async method that returns image data, `data:url` data which
|
||||
can be downloaded or displayed.
|
||||
Exports as `png`.
|
||||
|
||||
### svg(name, plusCssText)
|
||||
|
||||
@@ -42,20 +63,7 @@ svg(
|
||||
)
|
||||
```
|
||||
|
||||
Exports as `svg`, an async method that returns `svg` data, `data:url` data which
|
||||
can be downloaded or displayed.
|
||||
|
||||
### getSvgData()
|
||||
|
||||
Gets `svg` data, an async method that returns an object:
|
||||
|
||||
```js
|
||||
{
|
||||
node; // svg object
|
||||
str; // svg string, if rich text editing is enabled and domToImage is set to true, the dom node in the svg character returned by this value will be converted into the form of an image
|
||||
nodeWithDomToImg// v0.4.0+,The svg object after the DOM node is converted to an image has a value only when rich text editing is enabled and domToImage is set to true, otherwise null
|
||||
}
|
||||
```
|
||||
Exports as `svg`.
|
||||
|
||||
### pdf(name)
|
||||
|
||||
@@ -63,7 +71,7 @@ Gets `svg` data, an async method that returns an object:
|
||||
|
||||
`name`:File name
|
||||
|
||||
Export as `pdf`
|
||||
Export as `pdf`. Unlike other export methods, this method does not return data and directly triggers the download.
|
||||
|
||||
### json(name, withConfig)
|
||||
|
||||
@@ -71,10 +79,21 @@ Export as `pdf`
|
||||
|
||||
`withConfig``:Boolean`, default `true`, Whether the data contains configuration, otherwise it is pure mind map node data
|
||||
|
||||
Return `json` data, `data:url` type, you can download it yourself
|
||||
Return `json` data.
|
||||
|
||||
### md()
|
||||
|
||||
> v0.4.7+
|
||||
|
||||
Export as `markdown` file, Return `json` data, `data:url` type, you can download it yourself
|
||||
Export as `markdown` file.
|
||||
|
||||
### getSvgData()
|
||||
|
||||
Gets `svg` data, an async method that returns an object:
|
||||
|
||||
```js
|
||||
{
|
||||
node // svg node
|
||||
str // svg string
|
||||
}
|
||||
```
|
||||
@@ -10,6 +10,22 @@ MindMap.usePlugin(Export)
|
||||
</code></pre>
|
||||
<p>After registration and instantiation of <code>MindMap</code>, the instance can be obtained through <code>mindMap.doExport</code>.</p>
|
||||
<h2>Methods</h2>
|
||||
<p>All exported methods are asynchronous and return an instance of <code>Promise</code>. You can use the <code>then</code> method to obtain data, or use the <code>async await</code> function to obtain:</p>
|
||||
<pre class="hljs"><code>mindMap.doExport.png().then(<span class="hljs-function">(<span class="hljs-params">data</span>) =></span> {
|
||||
<span class="hljs-comment">// ...</span>
|
||||
})
|
||||
|
||||
<span class="hljs-keyword">const</span> <span class="hljs-keyword">export</span> = <span class="hljs-keyword">async</span> () => {
|
||||
<span class="hljs-keyword">let</span> data = <span class="hljs-keyword">await</span> mindMap.doExport.png()
|
||||
<span class="hljs-comment">// ...</span>
|
||||
}
|
||||
</code></pre>
|
||||
<p>The returned data is in the format of <code>data:URL</code>. You can create an <code>a</code> tag to trigger the download:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">let</span> a = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'a'</span>)
|
||||
a.href = <span class="hljs-string">'xxx.png'</span><span class="hljs-comment">// .png、.svg、.pdf、.md、.json、.smm</span>
|
||||
a.download = <span class="hljs-string">'xxx'</span>
|
||||
a.click()
|
||||
</code></pre>
|
||||
<h3>png(name, transparent = false)</h3>
|
||||
<ul>
|
||||
<li>
|
||||
@@ -19,8 +35,7 @@ MindMap.usePlugin(Export)
|
||||
<p><code>transparent</code>: v0.5.7+, Specify whether the background of the exported image is transparent</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>Exports as <code>png</code>, an async method that returns image data, <code>data:url</code> data which
|
||||
can be downloaded or displayed.</p>
|
||||
<p>Exports as <code>png</code>.</p>
|
||||
<h3>svg(name, plusCssText)</h3>
|
||||
<ul>
|
||||
<li>
|
||||
@@ -40,31 +55,29 @@ can be downloaded or displayed.</p>
|
||||
}`</span>
|
||||
)
|
||||
</code></pre>
|
||||
<p>Exports as <code>svg</code>, an async method that returns <code>svg</code> data, <code>data:url</code> data which
|
||||
can be downloaded or displayed.</p>
|
||||
<h3>getSvgData()</h3>
|
||||
<p>Gets <code>svg</code> data, an async method that returns an object:</p>
|
||||
<pre class="hljs"><code>{
|
||||
node; <span class="hljs-comment">// svg object</span>
|
||||
str; <span class="hljs-comment">// svg string, if rich text editing is enabled and domToImage is set to true, the dom node in the svg character returned by this value will be converted into the form of an image</span>
|
||||
nodeWithDomToImg<span class="hljs-comment">// v0.4.0+,The svg object after the DOM node is converted to an image has a value only when rich text editing is enabled and domToImage is set to true, otherwise null</span>
|
||||
}
|
||||
</code></pre>
|
||||
<p>Exports as <code>svg</code>.</p>
|
||||
<h3>pdf(name)</h3>
|
||||
<blockquote>
|
||||
<p>v0.2.1+</p>
|
||||
</blockquote>
|
||||
<p><code>name</code>:File name</p>
|
||||
<p>Export as <code>pdf</code></p>
|
||||
<p>Export as <code>pdf</code>. Unlike other export methods, this method does not return data and directly triggers the download.</p>
|
||||
<h3>json(name, withConfig)</h3>
|
||||
<p><code>name</code>:It is temporarily useless, just pass an empty string</p>
|
||||
<p><code>withConfig``:Boolean</code>, default <code>true</code>, Whether the data contains configuration, otherwise it is pure mind map node data</p>
|
||||
<p>Return <code>json</code> data, <code>data:url</code> type, you can download it yourself</p>
|
||||
<p>Return <code>json</code> data.</p>
|
||||
<h3>md()</h3>
|
||||
<blockquote>
|
||||
<p>v0.4.7+</p>
|
||||
</blockquote>
|
||||
<p>Export as <code>markdown</code> file, Return <code>json</code> data, <code>data:url</code> type, you can download it yourself</p>
|
||||
<p>Export as <code>markdown</code> file.</p>
|
||||
<h3>getSvgData()</h3>
|
||||
<p>Gets <code>svg</code> data, an async method that returns an object:</p>
|
||||
<pre class="hljs"><code>{
|
||||
node <span class="hljs-comment">// svg node</span>
|
||||
str <span class="hljs-comment">// svg string</span>
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -97,6 +97,18 @@ These open-source mind maps are also good, each with its own characteristics, bu
|
||||
|
||||
In summary, in open-source mind maps, it is difficult to find a better choice than `simple-mind-map`. Of course, `simple-mind-map` is far from being the best, and it also has many shortcomings, as you saw in the previous [special note]. However, `simple-mind-map` has always been in a fast iteration process, and we welcome you to join and improve it together.
|
||||
|
||||
## Browser Compatibility
|
||||
|
||||
We recommend using the latest version of the `Chrome` browser.
|
||||
|
||||
Limited testing situation:
|
||||
|
||||
Normal operation: `360` extreme speed browser(v13.5.2036.0)、`opera` browser(v71.0.3770.284)。
|
||||
|
||||
Abnormal operation: `Firefox`(v98.0.2), Node content cannot be displayed in rich text mode. If you want to support the `Firefox` browser, please use non rich text mode.
|
||||
|
||||
Unsupported: `IE` browser.
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://opensource.org/licenses/MIT)
|
||||
|
||||
@@ -7,21 +7,21 @@
|
||||
</blockquote>
|
||||
<h2>Features</h2>
|
||||
<ul>
|
||||
<li><input type="checkbox" id="checkbox18" checked="true" /><label for="checkbox18">Plugin architecture. In addition to core functions, other functions are provided as plugins, which can be used as needed to reduce the overall volume</label></li>
|
||||
<li><input type="checkbox" id="checkbox19" checked="true" /><label for="checkbox19">Supports six types of structures: logical structure diagrams, mind maps,</label>
|
||||
<li><input type="checkbox" id="checkbox216" checked="true" /><label for="checkbox216">Plugin architecture. In addition to core functions, other functions are provided as plugins, which can be used as needed to reduce the overall volume</label></li>
|
||||
<li><input type="checkbox" id="checkbox217" checked="true" /><label for="checkbox217">Supports six types of structures: logical structure diagrams, mind maps,</label>
|
||||
organizational structure diagrams, directory organization diagrams, timeline, and fishbone diagrams</li>
|
||||
<li><input type="checkbox" id="checkbox20" checked="true" /><label for="checkbox20">Built-in multiple themes and allows for highly customized styles, and support register new themes</label></li>
|
||||
<li><input type="checkbox" id="checkbox21" checked="true" /><label for="checkbox21">Supports shortcuts</label></li>
|
||||
<li><input type="checkbox" id="checkbox22" checked="true" /><label for="checkbox22">Node content supports images, icons, hyperlinks, notes, tags, and</label>
|
||||
<li><input type="checkbox" id="checkbox218" checked="true" /><label for="checkbox218">Built-in multiple themes and allows for highly customized styles, and support register new themes</label></li>
|
||||
<li><input type="checkbox" id="checkbox219" checked="true" /><label for="checkbox219">Supports shortcuts</label></li>
|
||||
<li><input type="checkbox" id="checkbox220" checked="true" /><label for="checkbox220">Node content supports images, icons, hyperlinks, notes, tags, and</label>
|
||||
summaries</li>
|
||||
<li><input type="checkbox" id="checkbox23" checked="true" /><label for="checkbox23">Supports forward and backward navigation</label></li>
|
||||
<li><input type="checkbox" id="checkbox24" checked="true" /><label for="checkbox24">Supports dragging and scaling</label></li>
|
||||
<li><input type="checkbox" id="checkbox25" checked="true" /><label for="checkbox25">Supports right-click and Ctrl + left-click to select multiple items</label></li>
|
||||
<li><input type="checkbox" id="checkbox26" checked="true" /><label for="checkbox26">Supports free dragging and dragging to adjust nodes</label></li>
|
||||
<li><input type="checkbox" id="checkbox27" checked="true" /><label for="checkbox27">Supports various node shapes</label></li>
|
||||
<li><input type="checkbox" id="checkbox28" checked="true" /><label for="checkbox28">Supports export to json, png, svg, pdf markdown, and import from json, xmind, markdown</label></li>
|
||||
<li><input type="checkbox" id="checkbox29" checked="true" /><label for="checkbox29">Supports mini map、support watermark</label></li>
|
||||
<li><input type="checkbox" id="checkbox30" checked="true" /><label for="checkbox30">Supports associative lines</label></li>
|
||||
<li><input type="checkbox" id="checkbox221" checked="true" /><label for="checkbox221">Supports forward and backward navigation</label></li>
|
||||
<li><input type="checkbox" id="checkbox222" checked="true" /><label for="checkbox222">Supports dragging and scaling</label></li>
|
||||
<li><input type="checkbox" id="checkbox223" checked="true" /><label for="checkbox223">Supports right-click and Ctrl + left-click to select multiple items</label></li>
|
||||
<li><input type="checkbox" id="checkbox224" checked="true" /><label for="checkbox224">Supports free dragging and dragging to adjust nodes</label></li>
|
||||
<li><input type="checkbox" id="checkbox225" checked="true" /><label for="checkbox225">Supports various node shapes</label></li>
|
||||
<li><input type="checkbox" id="checkbox226" checked="true" /><label for="checkbox226">Supports export to json, png, svg, pdf markdown, and import from json, xmind, markdown</label></li>
|
||||
<li><input type="checkbox" id="checkbox227" checked="true" /><label for="checkbox227">Supports mini map、support watermark</label></li>
|
||||
<li><input type="checkbox" id="checkbox228" checked="true" /><label for="checkbox228">Supports associative lines</label></li>
|
||||
</ul>
|
||||
<h2>Repository Catalog Introduction</h2>
|
||||
<p>1.<code>simple-mind-map</code></p>
|
||||
@@ -31,16 +31,16 @@ frameworks such as Vue and React, or without a framework.</p>
|
||||
<p>This is an online mind map built using the <code>simple-mind-map</code> library and based
|
||||
on <code>Vue2.x</code> and <code>ElementUI</code>. Features include:</p>
|
||||
<ul>
|
||||
<li><input type="checkbox" id="checkbox31" checked="true" /><label for="checkbox31">Toolbar, which supports inserting and deleting nodes, and editing node</label>
|
||||
<li><input type="checkbox" id="checkbox229" checked="true" /><label for="checkbox229">Toolbar, which supports inserting and deleting nodes, and editing node</label>
|
||||
images, icons, hyperlinks, notes, tags, and summaries</li>
|
||||
<li><input type="checkbox" id="checkbox32" checked="true" /><label for="checkbox32">Sidebar, with panels for basic style settings, node style settings,</label>
|
||||
<li><input type="checkbox" id="checkbox230" checked="true" /><label for="checkbox230">Sidebar, with panels for basic style settings, node style settings,</label>
|
||||
outline, theme selection, and structure selection</li>
|
||||
<li><input type="checkbox" id="checkbox33" checked="true" /><label for="checkbox33">Import and export functionality; data is saved in the browser's local</label>
|
||||
<li><input type="checkbox" id="checkbox231" checked="true" /><label for="checkbox231">Import and export functionality; data is saved in the browser's local</label>
|
||||
storage by default, but it also supports creating, opening, and editing
|
||||
local files on the computer directly</li>
|
||||
<li><input type="checkbox" id="checkbox34" checked="true" /><label for="checkbox34">Right-click menu, which supports operations such as expanding, collapsing,</label>
|
||||
<li><input type="checkbox" id="checkbox232" checked="true" /><label for="checkbox232">Right-click menu, which supports operations such as expanding, collapsing,</label>
|
||||
and organizing layout</li>
|
||||
<li><input type="checkbox" id="checkbox35" checked="true" /><label for="checkbox35">Bottom bar, which supports node and word count statistics, switching</label>
|
||||
<li><input type="checkbox" id="checkbox233" checked="true" /><label for="checkbox233">Bottom bar, which supports node and word count statistics, switching</label>
|
||||
between edit and read-only modes, zooming in and out, and switching to
|
||||
full screen, support mini map</li>
|
||||
</ul>
|
||||
@@ -69,6 +69,12 @@ full screen, support mini map</li>
|
||||
<p>3.<a href="https://github.com/hizzgdev/jsmind">jsmind</a>、<a href="https://github.com/ssshooter/mind-elixir-core">Mind-elixir</a>、<a href="https://github.com/ondras/my-mind">my-mind</a>、<a href="https://github.com/awehook/blink-mind">blink-mind</a>、<a href="https://github.com/luvsic3/remind">remind</a>、<a href="https://github.com/hellowuxin/vue3-mindmap">vue3-mindmap</a>、<a href="https://github.com/zyascend/ZMindMap">ZMindMap</a>...</p>
|
||||
<p>These open-source mind maps are also good, each with its own characteristics, but they also have certain drawbacks, such as stopping updates, average interface aesthetics, less functionality, relying on a certain framework, and so on.</p>
|
||||
<p>In summary, in open-source mind maps, it is difficult to find a better choice than <code>simple-mind-map</code>. Of course, <code>simple-mind-map</code> is far from being the best, and it also has many shortcomings, as you saw in the previous [special note]. However, <code>simple-mind-map</code> has always been in a fast iteration process, and we welcome you to join and improve it together.</p>
|
||||
<h2>Browser Compatibility</h2>
|
||||
<p>We recommend using the latest version of the <code>Chrome</code> browser.</p>
|
||||
<p>Limited testing situation:</p>
|
||||
<p>Normal operation: <code>360</code> extreme speed browser(v13.5.2036.0)、<code>opera</code> browser(v71.0.3770.284)。</p>
|
||||
<p>Abnormal operation: <code>Firefox</code>(v98.0.2), Node content cannot be displayed in rich text mode. If you want to support the <code>Firefox</code> browser, please use non rich text mode.</p>
|
||||
<p>Unsupported: <code>IE</code> browser.</p>
|
||||
<h2>License</h2>
|
||||
<p><a href="https://opensource.org/licenses/MIT">MIT</a></p>
|
||||
<h2>Invite the author to a cup of coffee</h2>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
# Participate in translation
|
||||
|
||||
Thanks for the first version English translation provided by [Emircan ERKUL](https://github.com/emircanerkul).
|
||||
> Thanks for the first version English translation provided by [Emircan ERKUL](https://github.com/emircanerkul).
|
||||
>
|
||||
> Due to limited energy, most translations currently use machine translation, so accuracy is inevitably problematic.
|
||||
>
|
||||
> At present, the 【Course】 section is not translated. If you are interested, please join us.
|
||||
|
||||
If you want to participate in the translation of this document, you can clone this repository first.
|
||||
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Participate in translation</h1>
|
||||
<blockquote>
|
||||
<p>Thanks for the first version English translation provided by <a href="https://github.com/emircanerkul">Emircan ERKUL</a>.</p>
|
||||
<p>Due to limited energy, most translations currently use machine translation, so accuracy is inevitably problematic.</p>
|
||||
<p>At present, the 【Course】 section is not translated. If you are interested, please join us.</p>
|
||||
</blockquote>
|
||||
<p>If you want to participate in the translation of this document, you can clone this repository first.</p>
|
||||
<p>The translated documents are in the <code>/web/src/pages/Doc/</code> directory, and currently support English(<code>en</code>) and Simplified Chinese(<code>zh</code>).</p>
|
||||
<p>If you are adding a new language type, you can create a new directory under the <code>/web/src/pages/Doc/</code> directory, Then create a folder for each chapter, You can also directly copy all chapter directories under the existing language directory for translation, Note that you only need to write the <code>index.md</code> file, The <code>index.vue</code> file under the chapter directory is automatically generated by the script according to <code>index.md</code>.</p>
|
||||
|
||||
@@ -129,6 +129,16 @@ Measure the width and height of the text, return value:
|
||||
{ width, height }
|
||||
```
|
||||
|
||||
#### getTextFromHtml(html)
|
||||
|
||||
Extract plain text content from an HTML string.
|
||||
|
||||
#### readBlob(blob)
|
||||
|
||||
> v0.5.9+
|
||||
|
||||
Convert `blob` data to `data:url` data.
|
||||
|
||||
## Simulate CSS background in Canvas
|
||||
|
||||
Import:
|
||||
@@ -157,4 +167,50 @@ drawBackgroundImageToCanvas(ctx, width, height, img, {
|
||||
// success
|
||||
}
|
||||
})
|
||||
```
|
||||
```
|
||||
|
||||
## LRU cache class
|
||||
|
||||
> v0.5.10+
|
||||
|
||||
Import:
|
||||
|
||||
```js
|
||||
import Lru from 'simple-mind-map/src/utils/Lru.js'
|
||||
```
|
||||
|
||||
### Constructor
|
||||
|
||||
```js
|
||||
let lru = new Lru(max)
|
||||
```
|
||||
|
||||
`max`: Specify the maximum number of caches.
|
||||
|
||||
### Instance properties
|
||||
|
||||
#### size
|
||||
|
||||
The current number of caches.
|
||||
|
||||
#### pool
|
||||
|
||||
Get cache pool.
|
||||
|
||||
### Instance methods
|
||||
|
||||
#### add(key, value)
|
||||
|
||||
Add cache.
|
||||
|
||||
#### delete(key)
|
||||
|
||||
Delete cache.
|
||||
|
||||
#### has(key)
|
||||
|
||||
Check if a cache exists.
|
||||
|
||||
#### get(key)
|
||||
|
||||
Gets the value of a cache.
|
||||
@@ -82,6 +82,13 @@ and copying the <code>data</code> of the data object, example:</p>
|
||||
<p>Measure the width and height of the text, return value:</p>
|
||||
<pre class="hljs"><code>{ width, height }
|
||||
</code></pre>
|
||||
<h4>getTextFromHtml(html)</h4>
|
||||
<p>Extract plain text content from an HTML string.</p>
|
||||
<h4>readBlob(blob)</h4>
|
||||
<blockquote>
|
||||
<p>v0.5.9+</p>
|
||||
</blockquote>
|
||||
<p>Convert <code>blob</code> data to <code>data:url</code> data.</p>
|
||||
<h2>Simulate CSS background in Canvas</h2>
|
||||
<p>Import:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> drawBackgroundImageToCanvas <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/utils/simulateCSSBackgroundInCanvas'</span>
|
||||
@@ -105,6 +112,31 @@ drawBackgroundImageToCanvas(ctx, width, height, img, {
|
||||
}
|
||||
})
|
||||
</code></pre>
|
||||
<h2>LRU cache class</h2>
|
||||
<blockquote>
|
||||
<p>v0.5.10+</p>
|
||||
</blockquote>
|
||||
<p>Import:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> Lru <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/utils/Lru.js'</span>
|
||||
</code></pre>
|
||||
<h3>Constructor</h3>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">let</span> lru = <span class="hljs-keyword">new</span> Lru(max)
|
||||
</code></pre>
|
||||
<p><code>max</code>: Specify the maximum number of caches.</p>
|
||||
<h3>Instance properties</h3>
|
||||
<h4>size</h4>
|
||||
<p>The current number of caches.</p>
|
||||
<h4>pool</h4>
|
||||
<p>Get cache pool.</p>
|
||||
<h3>Instance methods</h3>
|
||||
<h4>add(key, value)</h4>
|
||||
<p>Add cache.</p>
|
||||
<h4>delete(key)</h4>
|
||||
<p>Delete cache.</p>
|
||||
<h4>has(key)</h4>
|
||||
<p>Check if a cache exists.</p>
|
||||
<h4>get(key)</h4>
|
||||
<p>Gets the value of a cache.</p>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
|
||||
> 调整关联线控制点的功能从v0.4.6+开始支持
|
||||
|
||||
该插件用于支持添加关联线。
|
||||
> 关联性支持文本编辑从v0.5.11+开始支持
|
||||
|
||||
该插件目前功能还不完善,不支持在关联线上添加文字。
|
||||
该插件用于支持添加关联线。
|
||||
|
||||
## 注册
|
||||
|
||||
|
||||
@@ -7,8 +7,10 @@
|
||||
<blockquote>
|
||||
<p>调整关联线控制点的功能从v0.4.6+开始支持</p>
|
||||
</blockquote>
|
||||
<blockquote>
|
||||
<p>关联性支持文本编辑从v0.5.11+开始支持</p>
|
||||
</blockquote>
|
||||
<p>该插件用于支持添加关联线。</p>
|
||||
<p>该插件目前功能还不完善,不支持在关联线上添加文字。</p>
|
||||
<h2>注册</h2>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> MindMap <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map'</span>
|
||||
<span class="hljs-keyword">import</span> AssociativeLine <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/AssociativeLine.js'</span>
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# Changelog
|
||||
|
||||
## 0.5.11
|
||||
|
||||
新增:支持关联性文本编辑。
|
||||
|
||||
优化:优化主题配置更新,改变不涉及节点大小的配置不触发节点重新计算。
|
||||
|
||||
## 0.5.10
|
||||
|
||||
新增:使用LRU缓存算法优化节点复用逻辑。
|
||||
|
||||
## 0.5.10-fix.1
|
||||
|
||||
修复:修复导入出错的问题。
|
||||
|
||||
## 0.5.10-fix.2
|
||||
|
||||
修复:修复富文本模式下,切换主题、导入数据后没有触发数据改变的问题。
|
||||
|
||||
新增:新增三种主题。
|
||||
|
||||
## 0.5.9
|
||||
|
||||
修改:统一导出方法的格式,使用`FileReader`代替`URL.createObjectURL`转换`blob`数据。
|
||||
|
||||
## 0.5.8
|
||||
|
||||
优化:1.节点位置没有变化不触发位置设置。 2.展开收起状态没有变化不触发按钮更新。
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Changelog</h1>
|
||||
<h2>0.5.11</h2>
|
||||
<p>新增:支持关联性文本编辑。</p>
|
||||
<p>优化:优化主题配置更新,改变不涉及节点大小的配置不触发节点重新计算。</p>
|
||||
<h2>0.5.10</h2>
|
||||
<p>新增:使用LRU缓存算法优化节点复用逻辑。</p>
|
||||
<h2>0.5.10-fix.1</h2>
|
||||
<p>修复:修复导入出错的问题。</p>
|
||||
<h2>0.5.10-fix.2</h2>
|
||||
<p>修复:修复富文本模式下,切换主题、导入数据后没有触发数据改变的问题。</p>
|
||||
<p>新增:新增三种主题。</p>
|
||||
<h2>0.5.9</h2>
|
||||
<p>修改:统一导出方法的格式,使用<code>FileReader</code>代替<code>URL.createObjectURL</code>转换<code>blob</code>数据。</p>
|
||||
<h2>0.5.8</h2>
|
||||
<p>优化:1.节点位置没有变化不触发位置设置。 2.展开收起状态没有变化不触发按钮更新。</p>
|
||||
<p>新增:1.默认改为鼠标移上节点才显示展开收起按钮。 2.支持扩展节点可插入的图标列表。</p>
|
||||
|
||||
@@ -28,7 +28,7 @@ const mindMap = new MindMap({
|
||||
| data | Object | {} | 思维导图数据,可参考:[https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/example/exampleData.js) | |
|
||||
| layout | String | logicalStructure | 布局类型,可选列表:logicalStructure(逻辑结构图)、mindMap(思维导图)、catalogOrganization(目录组织图)、organizationStructure(组织结构图)、timeline(v0.5.4+,时间轴)、timeline2(v0.5.4+,上下交替型时间轴)、fishbone(v0.5.4+,鱼骨图) | |
|
||||
| fishboneDeg(v0.5.4+) | Number | 45 | 设置鱼骨结构图的斜线角度 | |
|
||||
| theme | String | default | 主题,可选列表:default(默认)、classic(脑图经典)、minions(小黄人)、pinkGrape(粉红葡萄)、mint(薄荷)、gold(金色vip)、vitalityOrange(活力橙)、greenLeaf(绿叶)、dark2(暗色2)、skyGreen(天清绿)、classic2(脑图经典2)、classic3(脑图经典3)、classic4(脑图经典4,v0.2.0+)、classicGreen(经典绿)、classicBlue(经典蓝)、blueSky(天空蓝)、brainImpairedPink(脑残粉)、dark(暗色)、earthYellow(泥土黄)、freshGreen(清新绿)、freshRed(清新红)、romanticPurple(浪漫紫)、simpleBlack(v0.5.4+简约黑)、courseGreen(v0.5.4+课程绿)、coffee(v0.5.4+咖啡)、redSpirit(v0.5.4+红色精神)、blackHumour(v0.5.4+黑色幽默)、lateNightOffice(v0.5.4+深夜办公室)、blackGold(v0.5.4+黑金) | |
|
||||
| theme | String | default | 主题,可选列表:default(默认)、classic(脑图经典)、minions(小黄人)、pinkGrape(粉红葡萄)、mint(薄荷)、gold(金色vip)、vitalityOrange(活力橙)、greenLeaf(绿叶)、dark2(暗色2)、skyGreen(天清绿)、classic2(脑图经典2)、classic3(脑图经典3)、classic4(脑图经典4,v0.2.0+)、classicGreen(经典绿)、classicBlue(经典蓝)、blueSky(天空蓝)、brainImpairedPink(脑残粉)、dark(暗色)、earthYellow(泥土黄)、freshGreen(清新绿)、freshRed(清新红)、romanticPurple(浪漫紫)、simpleBlack(v0.5.4+简约黑)、courseGreen(v0.5.4+课程绿)、coffee(v0.5.4+咖啡)、redSpirit(v0.5.4+红色精神)、blackHumour(v0.5.4+黑色幽默)、lateNightOffice(v0.5.4+深夜办公室)、blackGold(v0.5.4+黑金)、avocado(v.5.10-fix.2+牛油果)、autumn(v.5.10-fix.2+秋天)、orangeJuice(v.5.10-fix.2+橙汁) | |
|
||||
| themeConfig | Object | {} | 主题配置,会和所选择的主题进行合并,可用字段可参考:[https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/themes/default.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/themes/default.js) | |
|
||||
| scaleRatio | Number | 0.1 | 放大缩小的增量比例 | |
|
||||
| maxTag | Number | 5 | 节点里最多显示的标签数量,多余的会被丢弃 | |
|
||||
@@ -61,6 +61,7 @@ const mindMap = new MindMap({
|
||||
| maxHistoryCount(v0.5.6+) | Number | 1000 | 最大历史记录数 | |
|
||||
| alwaysShowExpandBtn(v0.5.8+) | Boolean | false | 是否一直显示节点的展开收起按钮,默认为鼠标移上去和激活时才显示 | |
|
||||
| iconList(v0.5.8+) | Array | [] | 扩展节点可插入的图标,数组的每一项为一个对象,对象详细结构请参考下方【图标配置】表格 | |
|
||||
| maxNodeCacheCount(v0.5.10+) | Number | 1000 | 节点最大缓存数量。为了优化性能,内部会维护一个节点缓存池,用来复用节点,通过该属性可以指定池的最大缓存数量 | |
|
||||
|
||||
### 水印配置
|
||||
|
||||
@@ -137,10 +138,14 @@ mindMap.setTheme('主题名称')
|
||||
|
||||
## 实例方法
|
||||
|
||||
### getSvgData()
|
||||
### getSvgData({ paddingX = 0, paddingY = 0 })
|
||||
|
||||
> v0.3.0+
|
||||
|
||||
`paddingX`:水平内边距
|
||||
|
||||
`paddingY`:垂直内边距
|
||||
|
||||
获取`svg`数据,返回一个对象,详细结构如下:
|
||||
|
||||
```js
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
<td>theme</td>
|
||||
<td>String</td>
|
||||
<td>default</td>
|
||||
<td>主题,可选列表:default(默认)、classic(脑图经典)、minions(小黄人)、pinkGrape(粉红葡萄)、mint(薄荷)、gold(金色vip)、vitalityOrange(活力橙)、greenLeaf(绿叶)、dark2(暗色2)、skyGreen(天清绿)、classic2(脑图经典2)、classic3(脑图经典3)、classic4(脑图经典4,v0.2.0+)、classicGreen(经典绿)、classicBlue(经典蓝)、blueSky(天空蓝)、brainImpairedPink(脑残粉)、dark(暗色)、earthYellow(泥土黄)、freshGreen(清新绿)、freshRed(清新红)、romanticPurple(浪漫紫)、simpleBlack(v0.5.4+简约黑)、courseGreen(v0.5.4+课程绿)、coffee(v0.5.4+咖啡)、redSpirit(v0.5.4+红色精神)、blackHumour(v0.5.4+黑色幽默)、lateNightOffice(v0.5.4+深夜办公室)、blackGold(v0.5.4+黑金)</td>
|
||||
<td>主题,可选列表:default(默认)、classic(脑图经典)、minions(小黄人)、pinkGrape(粉红葡萄)、mint(薄荷)、gold(金色vip)、vitalityOrange(活力橙)、greenLeaf(绿叶)、dark2(暗色2)、skyGreen(天清绿)、classic2(脑图经典2)、classic3(脑图经典3)、classic4(脑图经典4,v0.2.0+)、classicGreen(经典绿)、classicBlue(经典蓝)、blueSky(天空蓝)、brainImpairedPink(脑残粉)、dark(暗色)、earthYellow(泥土黄)、freshGreen(清新绿)、freshRed(清新红)、romanticPurple(浪漫紫)、simpleBlack(v0.5.4+简约黑)、courseGreen(v0.5.4+课程绿)、coffee(v0.5.4+咖啡)、redSpirit(v0.5.4+红色精神)、blackHumour(v0.5.4+黑色幽默)、lateNightOffice(v0.5.4+深夜办公室)、blackGold(v0.5.4+黑金)、avocado(v.5.10-fix.2+牛油果)、autumn(v.5.10-fix.2+秋天)、orangeJuice(v.5.10-fix.2+橙汁)</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -287,6 +287,13 @@
|
||||
<td>扩展节点可插入的图标,数组的每一项为一个对象,对象详细结构请参考下方【图标配置】表格</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>maxNodeCacheCount(v0.5.10+)</td>
|
||||
<td>Number</td>
|
||||
<td>1000</td>
|
||||
<td>节点最大缓存数量。为了优化性能,内部会维护一个节点缓存池,用来复用节点,通过该属性可以指定池的最大缓存数量</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>水印配置</h3>
|
||||
@@ -406,10 +413,12 @@ mindMap.setTheme(<span class="hljs-string">'主题名称'</span>)
|
||||
</blockquote>
|
||||
<p>当前注册的所有插件列表。</p>
|
||||
<h2>实例方法</h2>
|
||||
<h3>getSvgData()</h3>
|
||||
<h3>getSvgData({ paddingX = 0, paddingY = 0 })</h3>
|
||||
<blockquote>
|
||||
<p>v0.3.0+</p>
|
||||
</blockquote>
|
||||
<p><code>paddingX</code>:水平内边距</p>
|
||||
<p><code>paddingY</code>:垂直内边距</p>
|
||||
<p>获取<code>svg</code>数据,返回一个对象,详细结构如下:</p>
|
||||
<pre class="hljs"><code>{
|
||||
svg, <span class="hljs-comment">// Element,思维导图图形的整体svg元素,包括:svg(画布容器)、g(实际的思维导图组)</span>
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
}
|
||||
```
|
||||
|
||||
`icon`目前只能使用内置的图标,完整图标可以在[icons.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/svg/icons.js)文件中查看。
|
||||
`icon`可以使用内置的图标,完整图标可以在[icons.js](https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/svg/icons.js)文件中查看。也可以扩展图标,参考[扩展图标](https://wanglin2.github.io/mind-map/#/doc/zh/course19/%E6%89%A9%E5%B1%95%E5%9B%BE%E6%A0%87)。
|
||||
|
||||
创建实例时还支持传递其他很多选项参数,完整选项列表可以在[实例化选项](https://wanglin2.github.io/mind-map/#/doc/zh/constructor/%E5%AE%9E%E4%BE%8B%E5%8C%96%E9%80%89%E9%A1%B9)查看。
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
<span class="hljs-attr">children</span>: []<span class="hljs-comment">// 子节点</span>
|
||||
}
|
||||
</code></pre>
|
||||
<p><code>icon</code>目前只能使用内置的图标,完整图标可以在<a href="https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/svg/icons.js">icons.js</a>文件中查看。</p>
|
||||
<p><code>icon</code>可以使用内置的图标,完整图标可以在<a href="https://github.com/wanglin2/mind-map/blob/main/simple-mind-map/src/svg/icons.js">icons.js</a>文件中查看。也可以扩展图标,参考<a href="https://wanglin2.github.io/mind-map/#/doc/zh/course19/%E6%89%A9%E5%B1%95%E5%9B%BE%E6%A0%87">扩展图标</a>。</p>
|
||||
<p>创建实例时还支持传递其他很多选项参数,完整选项列表可以在<a href="https://wanglin2.github.io/mind-map/#/doc/zh/constructor/%E5%AE%9E%E4%BE%8B%E5%8C%96%E9%80%89%E9%A1%B9">实例化选项</a>查看。</p>
|
||||
<p>这样得到的思维导图可以通过鼠标和快捷键进行操作,比如单击某个节点可以激活它,双击某个节点可以编辑节点文本,按下<code>Tab</code>键会给当前激活的节点添加一个子节点,按下<code>Enter</code>键会给当前激活的节点添加一个兄弟节点等等,完整的快捷键列表可以参考<a href="https://github.com/wanglin2/mind-map/blob/main/web/src/config/zh.js#L246">快捷键列表</a>。</p>
|
||||
<p>当然有些功能还是需要UI界面的,比如图标列表、编辑超链接等等,需要注意的是<code>simple-mind-map</code>库并不包含UI界面,所以需要你自己开发,然后通过<code>simple-mind-map</code>提供的相关API来操作,本教程的其他章节会向你介绍如何使用。</p>
|
||||
|
||||
@@ -8,7 +8,17 @@
|
||||
|
||||
`.smm`是`simple-mind-map`自己定义的一种文件,其实就是`json`文件,换了一个扩展名而已。
|
||||
|
||||
导出直接调用`export`方法即可。
|
||||
导出直接调用`export`方法即可:
|
||||
|
||||
```js
|
||||
mindMap.export(type, isDownload, fileName, ...)
|
||||
```
|
||||
|
||||
`type`:文件类型
|
||||
|
||||
`isDownload`:传`true`会触发下载,`false`则不会,函数会返回导出文件的数据,`data:url`格式,你可以自行下载,`pdf`不支持该参数,默认会直接下载。
|
||||
|
||||
`fileName`:下载的文件名称
|
||||
|
||||
### 导出为smm、json
|
||||
|
||||
|
||||
@@ -7,7 +7,12 @@
|
||||
</blockquote>
|
||||
<p>目前支持导出为<code>.smm</code>、<code>.json</code>、<code>.svg</code>、<code>.png</code>、<code>.pdf</code>、<code>.md</code>文件。</p>
|
||||
<p><code>.smm</code>是<code>simple-mind-map</code>自己定义的一种文件,其实就是<code>json</code>文件,换了一个扩展名而已。</p>
|
||||
<p>导出直接调用<code>export</code>方法即可。</p>
|
||||
<p>导出直接调用<code>export</code>方法即可:</p>
|
||||
<pre class="hljs"><code>mindMap.export(type, isDownload, fileName, ...)
|
||||
</code></pre>
|
||||
<p><code>type</code>:文件类型</p>
|
||||
<p><code>isDownload</code>:传<code>true</code>会触发下载,<code>false</code>则不会,函数会返回导出文件的数据,<code>data:url</code>格式,你可以自行下载,<code>pdf</code>不支持该参数,默认会直接下载。</p>
|
||||
<p><code>fileName</code>:下载的文件名称</p>
|
||||
<h3>导出为smm、json</h3>
|
||||
<p>这两种文件的导出是一样的:</p>
|
||||
<pre class="hljs"><code>mindMap.export(type, isDownload, fileName, withConfig)
|
||||
|
||||
@@ -15,13 +15,35 @@ MindMap.usePlugin(Export)
|
||||
|
||||
## 方法
|
||||
|
||||
所有导出的方法都是异步方法,返回一个`Promise`实例,你可以使用`then`方法获取数据,或者使用`async await`函数获取:
|
||||
|
||||
```js
|
||||
mindMap.doExport.png().then((data) => {
|
||||
// ...
|
||||
})
|
||||
|
||||
const export = async () => {
|
||||
let data = await mindMap.doExport.png()
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
返回的数据为`data:url`格式的,你可以创建一个`a`标签来触发下载:
|
||||
|
||||
```js
|
||||
let a = document.createElement('a')
|
||||
a.href = 'xxx.png'// .png、.svg、.pdf、.md、.json、.smm
|
||||
a.download = 'xxx'
|
||||
a.click()
|
||||
```
|
||||
|
||||
### png(name, transparent = false)
|
||||
|
||||
- `name`:名称,可不传
|
||||
|
||||
- `transparent`:v0.5.7+,指定导出图片的背景是否是透明的
|
||||
|
||||
导出为`png`,异步方法,返回图片数据,`data:url`数据,可以自行下载或显示
|
||||
导出为`png`。
|
||||
|
||||
### svg(name, plusCssText)
|
||||
|
||||
@@ -41,19 +63,7 @@ svg(
|
||||
)
|
||||
```
|
||||
|
||||
导出为`svg`,异步方法,返回`svg`数据,`data:url`数据,可以自行下载或显示
|
||||
|
||||
### getSvgData()
|
||||
|
||||
获取`svg`数据,异步方法,返回一个对象:
|
||||
|
||||
```js
|
||||
{
|
||||
node// svg对象
|
||||
str// svg字符串,如果开启了富文本编辑且domToImage设为true,那么该值返回的svg字符内的dom节点会被转换成图片的形式
|
||||
nodeWithDomToImg// v0.4.0+,DOM节点转换为图片后的svg对象,只有当开启了富文本编辑且domToImage设为true才有值,否则为null
|
||||
}
|
||||
```
|
||||
导出为`svg`。
|
||||
|
||||
### pdf(name)
|
||||
|
||||
@@ -61,7 +71,7 @@ svg(
|
||||
|
||||
`name`:文件名称
|
||||
|
||||
导出为`pdf`
|
||||
导出为`pdf`,和其他导出方法不一样,这个方法不会返回数据,会直接触发下载。
|
||||
|
||||
### json(name, withConfig)
|
||||
|
||||
@@ -69,10 +79,25 @@ svg(
|
||||
|
||||
`withConfig``:Boolean`, 默认为`true`,数据中是否包含配置,否则为纯思维导图节点数据
|
||||
|
||||
返回`json`数据,`data:url`数据,可以自行下载
|
||||
返回`json`数据。
|
||||
|
||||
### smm(name, withConfig)
|
||||
|
||||
`simple-mind-map`自定义的文件格式,其实就是`json`,和`json`方法返回的数据一模一样。
|
||||
|
||||
### md()
|
||||
|
||||
> v0.4.7+
|
||||
|
||||
导出`markdown`文件,返回`data:url`数据,可以自行下载
|
||||
导出`markdown`文件。
|
||||
|
||||
### getSvgData()
|
||||
|
||||
获取`svg`数据,异步方法,返回一个对象:
|
||||
|
||||
```js
|
||||
{
|
||||
node// svg节点
|
||||
str// svg字符串
|
||||
}
|
||||
```
|
||||
@@ -10,6 +10,22 @@ MindMap.usePlugin(Export)
|
||||
</code></pre>
|
||||
<p>注册完且实例化<code>MindMap</code>后可通过<code>mindMap.doExport</code>获取到该实例。</p>
|
||||
<h2>方法</h2>
|
||||
<p>所有导出的方法都是异步方法,返回一个<code>Promise</code>实例,你可以使用<code>then</code>方法获取数据,或者使用<code>async await</code>函数获取:</p>
|
||||
<pre class="hljs"><code>mindMap.doExport.png().then(<span class="hljs-function">(<span class="hljs-params">data</span>) =></span> {
|
||||
<span class="hljs-comment">// ...</span>
|
||||
})
|
||||
|
||||
<span class="hljs-keyword">const</span> <span class="hljs-keyword">export</span> = <span class="hljs-keyword">async</span> () => {
|
||||
<span class="hljs-keyword">let</span> data = <span class="hljs-keyword">await</span> mindMap.doExport.png()
|
||||
<span class="hljs-comment">// ...</span>
|
||||
}
|
||||
</code></pre>
|
||||
<p>返回的数据为<code>data:url</code>格式的,你可以创建一个<code>a</code>标签来触发下载:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">let</span> a = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'a'</span>)
|
||||
a.href = <span class="hljs-string">'xxx.png'</span><span class="hljs-comment">// .png、.svg、.pdf、.md、.json、.smm</span>
|
||||
a.download = <span class="hljs-string">'xxx'</span>
|
||||
a.click()
|
||||
</code></pre>
|
||||
<h3>png(name, transparent = false)</h3>
|
||||
<ul>
|
||||
<li>
|
||||
@@ -19,7 +35,7 @@ MindMap.usePlugin(Export)
|
||||
<p><code>transparent</code>:v0.5.7+,指定导出图片的背景是否是透明的</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>导出为<code>png</code>,异步方法,返回图片数据,<code>data:url</code>数据,可以自行下载或显示</p>
|
||||
<p>导出为<code>png</code>。</p>
|
||||
<h3>svg(name, plusCssText)</h3>
|
||||
<ul>
|
||||
<li>
|
||||
@@ -39,30 +55,31 @@ MindMap.usePlugin(Export)
|
||||
}`</span>
|
||||
)
|
||||
</code></pre>
|
||||
<p>导出为<code>svg</code>,异步方法,返回<code>svg</code>数据,<code>data:url</code>数据,可以自行下载或显示</p>
|
||||
<h3>getSvgData()</h3>
|
||||
<p>获取<code>svg</code>数据,异步方法,返回一个对象:</p>
|
||||
<pre class="hljs"><code>{
|
||||
node<span class="hljs-comment">// svg对象</span>
|
||||
str<span class="hljs-comment">// svg字符串,如果开启了富文本编辑且domToImage设为true,那么该值返回的svg字符内的dom节点会被转换成图片的形式</span>
|
||||
nodeWithDomToImg<span class="hljs-comment">// v0.4.0+,DOM节点转换为图片后的svg对象,只有当开启了富文本编辑且domToImage设为true才有值,否则为null</span>
|
||||
}
|
||||
</code></pre>
|
||||
<p>导出为<code>svg</code>。</p>
|
||||
<h3>pdf(name)</h3>
|
||||
<blockquote>
|
||||
<p>v0.2.1+</p>
|
||||
</blockquote>
|
||||
<p><code>name</code>:文件名称</p>
|
||||
<p>导出为<code>pdf</code></p>
|
||||
<p>导出为<code>pdf</code>,和其他导出方法不一样,这个方法不会返回数据,会直接触发下载。</p>
|
||||
<h3>json(name, withConfig)</h3>
|
||||
<p><code>name</code>:暂时没有用处,传空字符串即可</p>
|
||||
<p><code>withConfig``:Boolean</code>, 默认为<code>true</code>,数据中是否包含配置,否则为纯思维导图节点数据</p>
|
||||
<p>返回<code>json</code>数据,<code>data:url</code>数据,可以自行下载</p>
|
||||
<p>返回<code>json</code>数据。</p>
|
||||
<h3>smm(name, withConfig)</h3>
|
||||
<p><code>simple-mind-map</code>自定义的文件格式,其实就是<code>json</code>,和<code>json</code>方法返回的数据一模一样。</p>
|
||||
<h3>md()</h3>
|
||||
<blockquote>
|
||||
<p>v0.4.7+</p>
|
||||
</blockquote>
|
||||
<p>导出<code>markdown</code>文件,返回<code>data:url</code>数据,可以自行下载</p>
|
||||
<p>导出<code>markdown</code>文件。</p>
|
||||
<h3>getSvgData()</h3>
|
||||
<p>获取<code>svg</code>数据,异步方法,返回一个对象:</p>
|
||||
<pre class="hljs"><code>{
|
||||
node<span class="hljs-comment">// svg节点</span>
|
||||
str<span class="hljs-comment">// svg字符串</span>
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -86,6 +86,17 @@
|
||||
|
||||
综上,在开源的思维导图中,你很难找到一个比`simple-mind-map`更好的选择。当然,`simple-mind-map`也远远谈不上最好,它也有很多不足,如你在前面的【特别说明】所看到的那样,不过`simple-mind-map`一直处于快速迭代中,欢迎你加入进来一起完善它。
|
||||
|
||||
## 浏览器兼容性
|
||||
|
||||
推荐使用最新版`chrome`浏览器。
|
||||
|
||||
有限测试情况:
|
||||
|
||||
正常运行:`360`极速浏览器(v13.5.2036.0)、`opera`浏览器(v71.0.3770.284)。
|
||||
|
||||
非正常运行:`Firefox`(v98.0.2),富文本模式下节点内容无法显示,如果要支持`Firefox`浏览器,请使用非富文本模式。
|
||||
|
||||
不支持:`IE`浏览器。
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -7,19 +7,19 @@
|
||||
</blockquote>
|
||||
<h2>特性</h2>
|
||||
<ul>
|
||||
<li><input type="checkbox" id="checkbox216" checked="true" /><label for="checkbox216">插件化架构,除核心功能外,其他功能作为插件提供,按需使用,减小整体体积</label></li>
|
||||
<li><input type="checkbox" id="checkbox217" checked="true" /><label for="checkbox217">支持逻辑结构图、思维导图、组织结构图、目录组织图、时间轴、鱼骨图六种结构</label></li>
|
||||
<li><input type="checkbox" id="checkbox218" checked="true" /><label for="checkbox218">内置多种主题,允许高度自定义样式,支持注册新主题</label></li>
|
||||
<li><input type="checkbox" id="checkbox219" checked="true" /><label for="checkbox219">支持快捷键</label></li>
|
||||
<li><input type="checkbox" id="checkbox220" checked="true" /><label for="checkbox220">节点内容支持图片、图标、超链接、备注、标签、概要</label></li>
|
||||
<li><input type="checkbox" id="checkbox221" checked="true" /><label for="checkbox221">支持前进后退</label></li>
|
||||
<li><input type="checkbox" id="checkbox222" checked="true" /><label for="checkbox222">支持拖动、缩放</label></li>
|
||||
<li><input type="checkbox" id="checkbox223" checked="true" /><label for="checkbox223">支持右键和Ctrl+左键两种多选方式</label></li>
|
||||
<li><input type="checkbox" id="checkbox224" checked="true" /><label for="checkbox224">支持节点自由拖拽、拖拽调整</label></li>
|
||||
<li><input type="checkbox" id="checkbox225" checked="true" /><label for="checkbox225">支持多种节点形状</label></li>
|
||||
<li><input type="checkbox" id="checkbox226" checked="true" /><label for="checkbox226">支持导出为</label><code>json</code>、<code>png</code>、<code>svg</code>、<code>pdf</code>、<code>markdown</code>,支持从<code>json</code>、<code>xmind</code>、<code>markdown</code>导入</li>
|
||||
<li><input type="checkbox" id="checkbox227" checked="true" /><label for="checkbox227">支持小地图、支持水印</label></li>
|
||||
<li><input type="checkbox" id="checkbox228" checked="true" /><label for="checkbox228">支持关联线</label></li>
|
||||
<li><input type="checkbox" id="checkbox180" checked="true" /><label for="checkbox180">插件化架构,除核心功能外,其他功能作为插件提供,按需使用,减小整体体积</label></li>
|
||||
<li><input type="checkbox" id="checkbox181" checked="true" /><label for="checkbox181">支持逻辑结构图、思维导图、组织结构图、目录组织图、时间轴、鱼骨图六种结构</label></li>
|
||||
<li><input type="checkbox" id="checkbox182" checked="true" /><label for="checkbox182">内置多种主题,允许高度自定义样式,支持注册新主题</label></li>
|
||||
<li><input type="checkbox" id="checkbox183" checked="true" /><label for="checkbox183">支持快捷键</label></li>
|
||||
<li><input type="checkbox" id="checkbox184" checked="true" /><label for="checkbox184">节点内容支持图片、图标、超链接、备注、标签、概要</label></li>
|
||||
<li><input type="checkbox" id="checkbox185" checked="true" /><label for="checkbox185">支持前进后退</label></li>
|
||||
<li><input type="checkbox" id="checkbox186" checked="true" /><label for="checkbox186">支持拖动、缩放</label></li>
|
||||
<li><input type="checkbox" id="checkbox187" checked="true" /><label for="checkbox187">支持右键和Ctrl+左键两种多选方式</label></li>
|
||||
<li><input type="checkbox" id="checkbox188" checked="true" /><label for="checkbox188">支持节点自由拖拽、拖拽调整</label></li>
|
||||
<li><input type="checkbox" id="checkbox189" checked="true" /><label for="checkbox189">支持多种节点形状</label></li>
|
||||
<li><input type="checkbox" id="checkbox190" checked="true" /><label for="checkbox190">支持导出为</label><code>json</code>、<code>png</code>、<code>svg</code>、<code>pdf</code>、<code>markdown</code>,支持从<code>json</code>、<code>xmind</code>、<code>markdown</code>导入</li>
|
||||
<li><input type="checkbox" id="checkbox191" checked="true" /><label for="checkbox191">支持小地图、支持水印</label></li>
|
||||
<li><input type="checkbox" id="checkbox192" checked="true" /><label for="checkbox192">支持关联线</label></li>
|
||||
</ul>
|
||||
<h2>仓库目录介绍</h2>
|
||||
<p>1.<code>simple-mind-map</code></p>
|
||||
@@ -27,11 +27,11 @@
|
||||
<p>2.<code>web</code></p>
|
||||
<p>使用<code>simple-mind-map</code>库,基于<code>vue2.x</code>、<code>ElementUI</code>搭建的在线思维导图。特性:</p>
|
||||
<ul>
|
||||
<li><input type="checkbox" id="checkbox229" checked="true" /><label for="checkbox229">工具栏,支持插入节点、删除节点;编辑节点图片、图标、超链接、备注、标签、概要</label></li>
|
||||
<li><input type="checkbox" id="checkbox230" checked="true" /><label for="checkbox230">侧边栏,基础样式设置面板、节点样式设置面板、大纲面板、主题选择面板、结构选择面板</label></li>
|
||||
<li><input type="checkbox" id="checkbox231" checked="true" /><label for="checkbox231">导入导出功能;数据默认保存在浏览器本地存储,也支持直接创建、打开、编辑电脑本地文件</label></li>
|
||||
<li><input type="checkbox" id="checkbox232" checked="true" /><label for="checkbox232">右键菜单,支持展开、收起、整理布局等操作</label></li>
|
||||
<li><input type="checkbox" id="checkbox233" checked="true" /><label for="checkbox233">底部栏,支持节点数量、字数统计;支持切换编辑和只读模式;支持放大缩小;支持全屏切换;支持小地图</label></li>
|
||||
<li><input type="checkbox" id="checkbox193" checked="true" /><label for="checkbox193">工具栏,支持插入节点、删除节点;编辑节点图片、图标、超链接、备注、标签、概要</label></li>
|
||||
<li><input type="checkbox" id="checkbox194" checked="true" /><label for="checkbox194">侧边栏,基础样式设置面板、节点样式设置面板、大纲面板、主题选择面板、结构选择面板</label></li>
|
||||
<li><input type="checkbox" id="checkbox195" checked="true" /><label for="checkbox195">导入导出功能;数据默认保存在浏览器本地存储,也支持直接创建、打开、编辑电脑本地文件</label></li>
|
||||
<li><input type="checkbox" id="checkbox196" checked="true" /><label for="checkbox196">右键菜单,支持展开、收起、整理布局等操作</label></li>
|
||||
<li><input type="checkbox" id="checkbox197" checked="true" /><label for="checkbox197">底部栏,支持节点数量、字数统计;支持切换编辑和只读模式;支持放大缩小;支持全屏切换;支持小地图</label></li>
|
||||
</ul>
|
||||
<p>提供文档页面服务。</p>
|
||||
<p>3.<code>dist</code></p>
|
||||
@@ -58,6 +58,12 @@
|
||||
<p>3.<a href="https://github.com/hizzgdev/jsmind">jsmind</a>、<a href="https://github.com/ssshooter/mind-elixir-core">Mind-elixir</a>、<a href="https://github.com/ondras/my-mind">my-mind</a>、<a href="https://github.com/awehook/blink-mind">blink-mind</a>、<a href="https://github.com/luvsic3/remind">remind</a>、<a href="https://github.com/hellowuxin/vue3-mindmap">vue3-mindmap</a>、<a href="https://github.com/zyascend/ZMindMap">ZMindMap</a>...</p>
|
||||
<p>这些开源的思维导图也都不错,各有各的特点,但是它们也都有一定缺点,比如停止更新、界面美观度一般、功能比较少、依赖某个框架等等。</p>
|
||||
<p>综上,在开源的思维导图中,你很难找到一个比<code>simple-mind-map</code>更好的选择。当然,<code>simple-mind-map</code>也远远谈不上最好,它也有很多不足,如你在前面的【特别说明】所看到的那样,不过<code>simple-mind-map</code>一直处于快速迭代中,欢迎你加入进来一起完善它。</p>
|
||||
<h2>浏览器兼容性</h2>
|
||||
<p>推荐使用最新版<code>chrome</code>浏览器。</p>
|
||||
<p>有限测试情况:</p>
|
||||
<p>正常运行:<code>360</code>极速浏览器(v13.5.2036.0)、<code>opera</code>浏览器(v71.0.3770.284)。</p>
|
||||
<p>非正常运行:<code>Firefox</code>(v98.0.2),富文本模式下节点内容无法显示,如果要支持<code>Firefox</code>浏览器,请使用非富文本模式。</p>
|
||||
<p>不支持:<code>IE</code>浏览器。</p>
|
||||
<h2>License</h2>
|
||||
<p><a href="https://opensource.org/licenses/MIT">MIT</a></p>
|
||||
<h2>请作者喝杯咖啡</h2>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
# 参与翻译
|
||||
|
||||
感谢[Emircan ERKUL](https://github.com/emircanerkul)提供的第一版英文翻译。
|
||||
> 感谢[Emircan ERKUL](https://github.com/emircanerkul)提供的第一版英文翻译。
|
||||
>
|
||||
>因为精力有限,目前大部分翻译都是使用机翻的,所以准确度难免有问题。
|
||||
>
|
||||
>目前【教程】部分是没有进行翻译的,如果你有兴趣,欢迎加入我们。
|
||||
|
||||
如果你也想参与翻译本文档的话,可以先克隆本仓库。
|
||||
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>参与翻译</h1>
|
||||
<blockquote>
|
||||
<p>感谢<a href="https://github.com/emircanerkul">Emircan ERKUL</a>提供的第一版英文翻译。</p>
|
||||
<p>因为精力有限,目前大部分翻译都是使用机翻的,所以准确度难免有问题。</p>
|
||||
<p>目前【教程】部分是没有进行翻译的,如果你有兴趣,欢迎加入我们。</p>
|
||||
</blockquote>
|
||||
<p>如果你也想参与翻译本文档的话,可以先克隆本仓库。</p>
|
||||
<p>翻译的文档在<code>/web/src/pages/Doc/</code>目录下,目前支持英文(<code>en</code>)、简体中文(<code>zh</code>)两种语言。</p>
|
||||
<p>如果是新增一种语言类型,那么可以在<code>/web/src/pages/Doc/</code>目录下创建一个新目录,然后给每个章节创建一个文件夹,你也可以直接复制已存在的语言目录下的所有章节目录进行翻译,注意,你只需要编写<code>index.md</code>文件,章节目录下的<code>index.vue</code>文件是脚本根据<code>index.md</code>自动生成的。</p>
|
||||
|
||||
@@ -124,6 +124,16 @@ copyNodeTree({}, node)
|
||||
{ width, height }
|
||||
```
|
||||
|
||||
#### getTextFromHtml(html)
|
||||
|
||||
提取html字符串里的纯文本内容。
|
||||
|
||||
#### readBlob(blob)
|
||||
|
||||
> v0.5.9+
|
||||
|
||||
将`blob`数据转成`data:url`数据。
|
||||
|
||||
## 在canvas中模拟css的背景属性
|
||||
|
||||
引入:
|
||||
@@ -153,3 +163,49 @@ drawBackgroundImageToCanvas(ctx, width, height, img, {
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## LRU缓存类
|
||||
|
||||
> v0.5.10+
|
||||
|
||||
引入:
|
||||
|
||||
```js
|
||||
import Lru from 'simple-mind-map/src/utils/Lru.js'
|
||||
```
|
||||
|
||||
### 构造函数
|
||||
|
||||
```js
|
||||
let lru = new Lru(max)
|
||||
```
|
||||
|
||||
`max`:指定最大缓存数量。
|
||||
|
||||
### 实例属性
|
||||
|
||||
#### size
|
||||
|
||||
当前缓存的数量。
|
||||
|
||||
#### pool
|
||||
|
||||
获取缓存池。
|
||||
|
||||
### 实例方法
|
||||
|
||||
#### add(key, value)
|
||||
|
||||
添加缓存。
|
||||
|
||||
#### delete(key)
|
||||
|
||||
删除指定缓存。
|
||||
|
||||
#### has(key)
|
||||
|
||||
检查某个缓存是否存在。
|
||||
|
||||
#### get(key)
|
||||
|
||||
获取某个缓存的值。
|
||||
@@ -77,6 +77,13 @@
|
||||
<p>测量文本的宽高,返回值:</p>
|
||||
<pre class="hljs"><code>{ width, height }
|
||||
</code></pre>
|
||||
<h4>getTextFromHtml(html)</h4>
|
||||
<p>提取html字符串里的纯文本内容。</p>
|
||||
<h4>readBlob(blob)</h4>
|
||||
<blockquote>
|
||||
<p>v0.5.9+</p>
|
||||
</blockquote>
|
||||
<p>将<code>blob</code>数据转成<code>data:url</code>数据。</p>
|
||||
<h2>在canvas中模拟css的背景属性</h2>
|
||||
<p>引入:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> drawBackgroundImageToCanvas <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/utils/simulateCSSBackgroundInCanvas'</span>
|
||||
@@ -100,6 +107,31 @@ drawBackgroundImageToCanvas(ctx, width, height, img, {
|
||||
}
|
||||
})
|
||||
</code></pre>
|
||||
<h2>LRU缓存类</h2>
|
||||
<blockquote>
|
||||
<p>v0.5.10+</p>
|
||||
</blockquote>
|
||||
<p>引入:</p>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">import</span> Lru <span class="hljs-keyword">from</span> <span class="hljs-string">'simple-mind-map/src/utils/Lru.js'</span>
|
||||
</code></pre>
|
||||
<h3>构造函数</h3>
|
||||
<pre class="hljs"><code><span class="hljs-keyword">let</span> lru = <span class="hljs-keyword">new</span> Lru(max)
|
||||
</code></pre>
|
||||
<p><code>max</code>:指定最大缓存数量。</p>
|
||||
<h3>实例属性</h3>
|
||||
<h4>size</h4>
|
||||
<p>当前缓存的数量。</p>
|
||||
<h4>pool</h4>
|
||||
<p>获取缓存池。</p>
|
||||
<h3>实例方法</h3>
|
||||
<h4>add(key, value)</h4>
|
||||
<p>添加缓存。</p>
|
||||
<h4>delete(key)</h4>
|
||||
<p>删除指定缓存。</p>
|
||||
<h4>has(key)</h4>
|
||||
<p>检查某个缓存是否存在。</p>
|
||||
<h4>get(key)</h4>
|
||||
<p>获取某个缓存的值。</p>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -295,6 +295,67 @@
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 关联线文字 -->
|
||||
<div class="title noTop">关联线文字</div>
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">字体</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
v-model="style.associativeLineTextFontFamily"
|
||||
placeholder=""
|
||||
@change="update('associativeLineTextFontFamily', $event)"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in fontFamilyList"
|
||||
:key="item.value"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
:style="{ fontFamily: item.value }"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="rowItem">
|
||||
<span class="name">颜色</span>
|
||||
<span
|
||||
class="block"
|
||||
v-popover:popover6
|
||||
:style="{ backgroundColor: style.associativeLineTextColor }"
|
||||
></span>
|
||||
<el-popover ref="popover6" placement="bottom" trigger="click">
|
||||
<Color
|
||||
:color="style.associativeLineTextColor"
|
||||
@change="
|
||||
color => {
|
||||
update('associativeLineTextColor', color)
|
||||
}
|
||||
"
|
||||
></Color>
|
||||
</el-popover>
|
||||
</div>
|
||||
<div class="rowItem">
|
||||
<span class="name">字号</span>
|
||||
<el-select
|
||||
size="mini"
|
||||
style="width: 80px"
|
||||
v-model="style.associativeLineTextFontSize"
|
||||
placeholder=""
|
||||
@change="update('associativeLineTextFontSize', $event)"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in fontSizeList"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item"
|
||||
:style="{ fontSize: item + 'px' }"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 节点边框风格 -->
|
||||
<div class="title noTop">{{ $t('baseStyle.nodeBorderType') }}</div>
|
||||
<div class="row">
|
||||
@@ -546,7 +607,7 @@
|
||||
<script>
|
||||
import Sidebar from './Sidebar'
|
||||
import Color from './Color'
|
||||
import { lineWidthList, lineStyleList, backgroundRepeatList, backgroundPositionList, backgroundSizeList } from '@/config'
|
||||
import { lineWidthList, lineStyleList, backgroundRepeatList, backgroundPositionList, backgroundSizeList, fontFamilyList, fontSizeList } from '@/config'
|
||||
import ImgUpload from '@/components/ImgUpload'
|
||||
import { storeConfig } from '@/api'
|
||||
import { mapState, mapMutations } from 'vuex'
|
||||
@@ -575,6 +636,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
lineWidthList,
|
||||
fontSizeList,
|
||||
activeTab: 'color',
|
||||
marginActiveTab: 'second',
|
||||
style: {
|
||||
@@ -588,6 +650,9 @@ export default {
|
||||
associativeLineWidth: 0,
|
||||
associativeLineActiveWidth: 0,
|
||||
associativeLineActiveColor: '',
|
||||
associativeLineTextFontSize: 0,
|
||||
associativeLineTextColor: '',
|
||||
associativeLineTextFontFamily: '',
|
||||
paddingX: 0,
|
||||
paddingY: 0,
|
||||
imgMaxWidth: 0,
|
||||
@@ -636,6 +701,9 @@ export default {
|
||||
backgroundSizeList() {
|
||||
return backgroundSizeList[this.$i18n.locale] || backgroundSizeList.zh
|
||||
},
|
||||
fontFamilyList() {
|
||||
return fontFamilyList[this.$i18n.locale] || fontFamilyList.zh
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
activeSidebar(val) {
|
||||
@@ -662,7 +730,7 @@ export default {
|
||||
* @Desc: 初始样式
|
||||
*/
|
||||
initStyle() {
|
||||
;[
|
||||
[
|
||||
'backgroundColor',
|
||||
'lineWidth',
|
||||
'lineStyle',
|
||||
@@ -673,6 +741,9 @@ export default {
|
||||
'associativeLineWidth',
|
||||
'associativeLineActiveWidth',
|
||||
'associativeLineActiveColor',
|
||||
'associativeLineTextFontSize',
|
||||
'associativeLineTextColor',
|
||||
'associativeLineTextFontFamily',
|
||||
'paddingX',
|
||||
'paddingY',
|
||||
'imgMaxWidth',
|
||||
@@ -715,7 +786,7 @@ export default {
|
||||
* @Desc: margin初始值
|
||||
*/
|
||||
initMarginStyle() {
|
||||
;['marginX', 'marginY'].forEach(key => {
|
||||
['marginX', 'marginY'].forEach(key => {
|
||||
this.style[key] = this.mindMap.getThemeConfig()[this.marginActiveTab][
|
||||
key
|
||||
]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="editContainer">
|
||||
<div class="editContainer" :style="{top: IS_ELECTRON ? '40px' : 0}">
|
||||
<div class="mindMapContainer" ref="mindMapContainer"></div>
|
||||
<Count v-if="!isZenMode"></Count>
|
||||
<Navigator :mindMap="mindMap"></Navigator>
|
||||
@@ -46,7 +46,7 @@ import { getData, storeData, storeConfig } from '@/api'
|
||||
import Navigator from './Navigator.vue'
|
||||
import NodeImgPreview from './NodeImgPreview.vue'
|
||||
import SidebarTrigger from './SidebarTrigger.vue'
|
||||
import { mapState } from 'vuex'
|
||||
import { mapState, mapMutations } from 'vuex'
|
||||
import customThemeList from '@/customThemes'
|
||||
import icon from '@/config/icon'
|
||||
|
||||
@@ -93,11 +93,13 @@ export default {
|
||||
mindMap: null,
|
||||
mindMapData: null,
|
||||
prevImg: '',
|
||||
openTest: false
|
||||
openTest: false,
|
||||
isFirst: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
fileName: state => state.fileName,
|
||||
isZenMode: state => state.localConfig.isZenMode,
|
||||
openNodeRichText: state => state.localConfig.openNodeRichText,
|
||||
})
|
||||
@@ -111,9 +113,9 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
async mounted() {
|
||||
// this.showNewFeatureInfo()
|
||||
this.getData()
|
||||
await this.getData()
|
||||
this.init()
|
||||
this.$bus.$on('execCommand', this.execCommand)
|
||||
this.$bus.$on('paddingChange', this.onPaddingChange)
|
||||
@@ -136,8 +138,13 @@ export default {
|
||||
this.test()
|
||||
}, 5000)
|
||||
}
|
||||
if (window.IS_ELECTRON) {
|
||||
this.mindMap.keyCommand.addShortcut('Control+s', this.saveToLocal)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['setFileName', 'setIsUnSave']),
|
||||
|
||||
/**
|
||||
* @Author: 王林25
|
||||
* @Date: 2021-11-22 19:39:28
|
||||
@@ -227,8 +234,16 @@ export default {
|
||||
* @Date: 2021-07-03 22:11:37
|
||||
* @Desc: 获取思维导图数据,实际应该调接口获取
|
||||
*/
|
||||
getData() {
|
||||
let storeData = getData()
|
||||
async getData() {
|
||||
let data = await window.electronAPI.getFileContent(this.$route.params.id)
|
||||
let storeData = null
|
||||
if (data) {
|
||||
this.setFileName(data.name)
|
||||
storeData = data.content
|
||||
} else {
|
||||
this.setFileName('未命名')
|
||||
storeData = getData()
|
||||
}
|
||||
this.mindMapData = storeData
|
||||
},
|
||||
|
||||
@@ -242,9 +257,15 @@ export default {
|
||||
return
|
||||
}
|
||||
this.$bus.$on('data_change', data => {
|
||||
if (!this.isFirst) {
|
||||
this.setIsUnSave(true)
|
||||
} else {
|
||||
this.isFirst = false
|
||||
}
|
||||
storeData(data)
|
||||
})
|
||||
this.$bus.$on('view_data_change', data => {
|
||||
this.setIsUnSave(true)
|
||||
storeConfig({
|
||||
view: data
|
||||
})
|
||||
@@ -292,9 +313,9 @@ export default {
|
||||
iconList: icon
|
||||
})
|
||||
if (this.openNodeRichText) this.addRichTextPlugin()
|
||||
this.mindMap.keyCommand.addShortcut('Control+s', () => {
|
||||
this.manualSave()
|
||||
})
|
||||
// this.mindMap.keyCommand.addShortcut('Control+s', () => {
|
||||
// this.manualSave()
|
||||
// })
|
||||
// 转发事件
|
||||
;[
|
||||
'node_active',
|
||||
@@ -409,6 +430,18 @@ export default {
|
||||
// 移除节点富文本编辑插件
|
||||
removeRichTextPlugin() {
|
||||
this.mindMap.removePlugin(RichText)
|
||||
},
|
||||
|
||||
// 保存到本地文件
|
||||
async saveToLocal() {
|
||||
let id = this.$route.params.id
|
||||
let data = this.mindMap.getData(true)
|
||||
console.log('保存', id, data)
|
||||
let res = await window.electronAPI.save(id, JSON.stringify(data), this.fileName)
|
||||
if (res) {
|
||||
this.setFileName(res)
|
||||
}
|
||||
this.setIsUnSave(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,6 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="tip">{{ $t('export.tips') }}</div>
|
||||
<div class="tip warning" v-if="openNodeRichText && exportType === 'svg' && domToImage">{{ $t('export.svgTips') }}</div>
|
||||
</div>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="cancel">{{ $t('dialog.cancel') }}</el-button>
|
||||
|
||||
@@ -36,9 +36,7 @@
|
||||
<Fullscreen :mindMap="mindMap"></Fullscreen>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a href="https://github.com/wanglin2/mind-map" target="_blank">
|
||||
<span class="iconfont icongithub"></span>
|
||||
</a>
|
||||
<span class="iconfont icongithub" @click="openGithub"></span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -89,6 +87,10 @@ export default {
|
||||
onLangChange(lang) {
|
||||
i18n.locale = lang
|
||||
storeLang(lang)
|
||||
},
|
||||
|
||||
openGithub() {
|
||||
window.electronAPI.openUrl('https://github.com/wanglin2/mind-map')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -506,7 +506,7 @@ export default {
|
||||
this.activeTab = 'normal'
|
||||
return
|
||||
}
|
||||
;[
|
||||
[
|
||||
'shape',
|
||||
'paddingX',
|
||||
'paddingY',
|
||||
|
||||
@@ -42,7 +42,7 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
themeList: [...themeList],// ...customThemeList
|
||||
themeList: [...themeList].reverse(),// ...customThemeList
|
||||
themeMap,
|
||||
theme: ''
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="toolbarContainer">
|
||||
<div class="toolbar">
|
||||
<div class="toolbar" :style="{top: IS_ELECTRON ? '40px' : 0}">
|
||||
<!-- 节点操作 -->
|
||||
<div class="toolbarBlock">
|
||||
<div
|
||||
@@ -126,15 +126,15 @@
|
||||
</div>
|
||||
<!-- 导出 -->
|
||||
<div class="toolbarBlock">
|
||||
<div class="toolbarBtn" @click="createNewLocalFile">
|
||||
<div class="toolbarBtn" @click="createNewLocalFile" v-if="!IS_ELECTRON">
|
||||
<span class="icon iconfont iconxinjian"></span>
|
||||
<span class="text">{{ $t('toolbar.newFile') }}</span>
|
||||
</div>
|
||||
<div class="toolbarBtn" @click="openLocalFile">
|
||||
<div class="toolbarBtn" @click="openLocalFile" v-if="!IS_ELECTRON">
|
||||
<span class="icon iconfont icondakai"></span>
|
||||
<span class="text">{{ $t('toolbar.openFile') }}</span>
|
||||
</div>
|
||||
<div class="toolbarBtn" @click="saveLocalFile">
|
||||
<div class="toolbarBtn" @click="saveLocalFile" v-if="!IS_ELECTRON">
|
||||
<span class="icon iconfont iconlingcunwei"></span>
|
||||
<span class="text">{{ $t('toolbar.saveAs') }}</span>
|
||||
</div>
|
||||
|
||||
32
web/src/pages/Workbenche/Index.vue
Normal file
32
web/src/pages/Workbenche/Index.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<div class="workbencheContainer">
|
||||
<div class="workbencheContent">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Workbenche',
|
||||
created () {
|
||||
document.title = '思绪思维导图'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.workbencheContainer {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.workbencheContent {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
92
web/src/pages/Workbenche/components/AboutDialog.vue
Normal file
92
web/src/pages/Workbenche/components/AboutDialog.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="aboutDialog"
|
||||
title="关于"
|
||||
:visible.sync="dialogVisible"
|
||||
width="480px"
|
||||
@close="onClose"
|
||||
>
|
||||
<div class="aboutBox">
|
||||
<img src="../../../assets/img/icon.png" alt="" />
|
||||
<h2>思绪思维导图</h2>
|
||||
<p>版本:{{ version }}</p>
|
||||
<p>
|
||||
获取源码:<a href="https://github.com/wanglin2/mind-map/tree/electron"
|
||||
>mind-map</a
|
||||
>
|
||||
</p>
|
||||
<p>
|
||||
下载最新版本:<a href="https://github.com/wanglin2/mind-map/releases"
|
||||
>releases</a
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import pkg from '../../../../package.json'
|
||||
|
||||
export default {
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
version: pkg.version
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(val, oldVal) {
|
||||
this.dialogVisible = val
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClose() {
|
||||
this.$emit('change', false)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.aboutDialog {
|
||||
/deep/ .el-dialog__body {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.aboutBox {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-bottom: 30px;
|
||||
|
||||
img {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 10px;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
62
web/src/pages/Workbenche/components/Empty.vue
Normal file
62
web/src/pages/Workbenche/components/Empty.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<div class="workbencheEmptyContainer">
|
||||
<div class="icon iconfont iconwushuju"></div>
|
||||
<div class="tip">暂时没有内容</div>
|
||||
<div class="desc">没有最近使用的文件记录</div>
|
||||
<div class="createBtn" @click="create">立即创建</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { create } from '../utils'
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
create
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.workbencheEmptyContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.icon {
|
||||
font-size: 100px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.tip {
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: 12px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.createBtn {
|
||||
width: 100px;
|
||||
height: 30px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #409eff;
|
||||
color: #409eff;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(64, 158, 255, 0.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
165
web/src/pages/Workbenche/components/FileList.vue
Normal file
165
web/src/pages/Workbenche/components/FileList.vue
Normal file
@@ -0,0 +1,165 @@
|
||||
<template>
|
||||
<div class="workbencheFileListContainer">
|
||||
<div class="title">
|
||||
<span>最近</span>
|
||||
<span class="clearBtn" @click="clear">清空</span>
|
||||
</div>
|
||||
<div class="fileListBox">
|
||||
<Empty v-if="list.length <= 0"></Empty>
|
||||
<el-table v-else :data="list" style="width: 100%">
|
||||
<el-table-column prop="name" label="名称"> </el-table-column>
|
||||
<el-table-column prop="url" label="文件路径"> </el-table-column>
|
||||
<el-table-column label="操作">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
icon="el-icon-edit"
|
||||
circle
|
||||
size="mini"
|
||||
@click="openFile(scope.row.url)"
|
||||
></el-button>
|
||||
<el-button
|
||||
icon="el-icon-document-copy"
|
||||
circle
|
||||
size="mini"
|
||||
@click="copyFile(scope.row.url)"
|
||||
></el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
circle
|
||||
size="mini"
|
||||
@click="deleteFile(scope.row.url, scope.$index)"
|
||||
></el-button>
|
||||
<el-button
|
||||
icon="el-icon-folder-opened"
|
||||
circle
|
||||
size="mini"
|
||||
@click="openFileInDir(scope.row.url)"
|
||||
></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Empty from '../components/Empty.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Empty
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
list: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getRecentFileList()
|
||||
window.electronAPI.onRefreshRecentFileList(() => {
|
||||
this.getRecentFileList()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
// 获取最近文件列表
|
||||
async getRecentFileList() {
|
||||
let list = await window.electronAPI.getRecentFileList()
|
||||
this.list = list.reverse()
|
||||
},
|
||||
|
||||
// 在文件夹里打开文件
|
||||
openFileInDir(file) {
|
||||
window.electronAPI.openFileInDir(file)
|
||||
},
|
||||
|
||||
// 删除文件
|
||||
deleteFile(file, index) {
|
||||
this.$confirm('确定删除该文件?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async () => {
|
||||
let res = await window.electronAPI.deleteFile(file)
|
||||
if (res) {
|
||||
this.$message.error('删除失败')
|
||||
} else {
|
||||
this.list.splice(index, 1)
|
||||
this.$message.success('删除成功')
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
|
||||
// 编辑文件
|
||||
openFile(file) {
|
||||
window.electronAPI.openFile(file)
|
||||
},
|
||||
|
||||
// 清空最近文件列表
|
||||
clear() {
|
||||
this.$confirm('确定清空最近文件?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async () => {
|
||||
let res = await window.electronAPI.clearRecentFileList()
|
||||
if (res) {
|
||||
this.$message.error('清空失败')
|
||||
} else {
|
||||
this.list = []
|
||||
this.$message.success('清空成功')
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
|
||||
// 复制文件
|
||||
async copyFile(file) {
|
||||
try {
|
||||
window.electronAPI.copyFile(file)
|
||||
this.$message.success('复制成功')
|
||||
} catch (error) {
|
||||
this.$message.error('复制失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.workbencheFileListContainer {
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
padding-top: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.title {
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
height: 65px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.clearBtn {
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.fileListBox {
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
15
web/src/pages/Workbenche/components/MacControl.vue
Normal file
15
web/src/pages/Workbenche/components/MacControl.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<div class="macControl" v-if="IS_MAC"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.macControl {
|
||||
width: 100px;
|
||||
height: 100%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
90
web/src/pages/Workbenche/components/Sidebar.vue
Normal file
90
web/src/pages/Workbenche/components/Sidebar.vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<div class="workbencheSidebarContainer">
|
||||
<div class="createBtn" @click="create">开始新建</div>
|
||||
<div class="line"></div>
|
||||
<div class="btn" @click="openLocalFile">
|
||||
<span class="icon iconfont icondakai"></span>
|
||||
<span class="text">打开本地文件</span>
|
||||
</div>
|
||||
<div class="btn active">
|
||||
<span class="icon iconfont iconzuijinliulan"></span>
|
||||
<span class="text">最近文件</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { create } from '../utils'
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
create,
|
||||
|
||||
openLocalFile() {
|
||||
window.electronAPI.selectOpenFile()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.workbencheSidebarContainer {
|
||||
flex-shrink: 0;
|
||||
width: 200px;
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
border-radius: 10px;
|
||||
margin-right: 20px;
|
||||
padding: 15px 20px;
|
||||
|
||||
.createBtn {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
background-color: #409eff;
|
||||
border-radius: 5px;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
|
||||
.line {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: #e4e7ed;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
background-color: #fff;
|
||||
border-radius: 5px;
|
||||
color: #000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
user-select: none;
|
||||
padding: 0 10px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
&.active,
|
||||
&:hover {
|
||||
background-color: rgba(64, 158, 255, 0.3);
|
||||
color: rgba(64, 158, 255, 1);
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
68
web/src/pages/Workbenche/components/WinControl.vue
Normal file
68
web/src/pages/Workbenche/components/WinControl.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<div class="winControl noDrag" v-if="IS_WIN">
|
||||
<div class="winControlBtn iconfont iconzuixiaohua" @click="minimize"></div>
|
||||
<div
|
||||
class="winControlBtn iconfont"
|
||||
:class="[isMaximize ? 'icon3zuidahua-3' : 'iconzuidahua']"
|
||||
@click="toggleMaximize"
|
||||
></div>
|
||||
<div class="winControlBtn iconfont iconguanbi" @click="close"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
isMaximize: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
minimize() {
|
||||
window.electronAPI.minimize()
|
||||
},
|
||||
|
||||
toggleMaximize() {
|
||||
if (this.isMaximize) {
|
||||
this.isMaximize = false
|
||||
window.electronAPI.unmaximize()
|
||||
} else {
|
||||
this.isMaximize = true
|
||||
window.electronAPI.maximize()
|
||||
}
|
||||
},
|
||||
|
||||
close() {
|
||||
window.electronAPI.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.winControl {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
height: 100%;
|
||||
.winControlBtn {
|
||||
width: 40px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: #ccced1;
|
||||
}
|
||||
|
||||
&.iconguanbi {
|
||||
&:hover {
|
||||
background-color: #e81123;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
6
web/src/pages/Workbenche/utils.js
Normal file
6
web/src/pages/Workbenche/utils.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
// 打开新的编辑窗口
|
||||
export const create = () => {
|
||||
window.electronAPI.create(uuid())
|
||||
}
|
||||
134
web/src/pages/Workbenche/views/Edit.vue
Normal file
134
web/src/pages/Workbenche/views/Edit.vue
Normal file
@@ -0,0 +1,134 @@
|
||||
<template>
|
||||
<div class="workbencheEditContainer">
|
||||
<div class="workbencheEditHeader">
|
||||
<MacControl></MacControl>
|
||||
<WinControl></WinControl>
|
||||
<div class="inputBox">
|
||||
<el-input
|
||||
v-model="name"
|
||||
size="mini"
|
||||
placeholder=""
|
||||
@blur="rename"
|
||||
@keyup.enter="rename"
|
||||
></el-input>
|
||||
<div class="modifyDotBox">
|
||||
<div class="modifyDot" v-show="isUnSave"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Edit></Edit>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Edit from '../../Edit/Index.vue'
|
||||
import WinControl from '../components/WinControl.vue'
|
||||
import MacControl from '../components/MacControl.vue'
|
||||
import { mapState, mapMutations } from 'vuex'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Edit,
|
||||
MacControl,
|
||||
WinControl
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
name: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['fileName', 'isUnSave'])
|
||||
},
|
||||
watch: {
|
||||
fileName(val) {
|
||||
this.name = val
|
||||
document.title = val
|
||||
},
|
||||
name(val) {
|
||||
if (!val.trim()) return
|
||||
this.setFileName(val.trim())
|
||||
}
|
||||
},
|
||||
created() {
|
||||
window.onbeforeunload = async e => {
|
||||
e.returnValue = false
|
||||
// 没有未保存内容直接关闭
|
||||
if (!this.isUnSave) {
|
||||
window.electronAPI.destroy()
|
||||
} else {
|
||||
try {
|
||||
// 否则询问用户是否关闭
|
||||
await this.checkIsClose()
|
||||
window.electronAPI.destroy()
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['setFileName']),
|
||||
|
||||
// 重命名文件
|
||||
rename() {
|
||||
let id = this.$route.params.id
|
||||
window.electronAPI.rename(id, this.name.trim())
|
||||
},
|
||||
|
||||
// 询问是否关闭页面
|
||||
checkIsClose() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.$confirm('有操作尚未保存,是否确认关闭?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async () => {
|
||||
resolve()
|
||||
})
|
||||
.catch(() => {
|
||||
reject()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.workbencheEditContainer {
|
||||
.workbencheEditHeader {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
background-color: #ebeef1;
|
||||
-webkit-app-region: drag;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
|
||||
.inputBox {
|
||||
-webkit-app-region: no-drag;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
top: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.modifyDotBox {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
margin-left: 10px;
|
||||
|
||||
.modifyDot {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
background-color: #409eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
150
web/src/pages/Workbenche/views/Home.vue
Normal file
150
web/src/pages/Workbenche/views/Home.vue
Normal file
@@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<div
|
||||
class="workbencheHomeContainer"
|
||||
@drop="onDrop"
|
||||
@dragenter="onDragenter"
|
||||
@dragover="onDragover"
|
||||
@dragleave="onDragleave"
|
||||
>
|
||||
<div class="workbencheHomeHeader">
|
||||
<MacControl></MacControl>
|
||||
<WinControl></WinControl>
|
||||
<div class="rightBar">
|
||||
<el-dropdown @command="handleCommand">
|
||||
<span class="settingBtn el-icon-setting"></span>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item command="about">关于软件</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workbencheHomeContent">
|
||||
<Sidebar></Sidebar>
|
||||
<FileList></FileList>
|
||||
</div>
|
||||
<AboutDialog v-model="showAboutDialog"></AboutDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WinControl from '../components/WinControl.vue'
|
||||
import MacControl from '../components/MacControl.vue'
|
||||
import Sidebar from '../components/Sidebar.vue'
|
||||
import FileList from '../components/FileList.vue'
|
||||
import AboutDialog from '../components/AboutDialog.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
WinControl,
|
||||
MacControl,
|
||||
Sidebar,
|
||||
FileList,
|
||||
AboutDialog
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showAboutDialog: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleCommand(command) {
|
||||
switch (command) {
|
||||
case 'about':
|
||||
this.showAboutDialog = true
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
},
|
||||
|
||||
onDrop(e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
let df = e.dataTransfer
|
||||
let dropFiles = []
|
||||
|
||||
if (df.items !== undefined) {
|
||||
for (let i = 0; i < df.items.length; i++) {
|
||||
let item = df.items[i]
|
||||
if (item.kind === 'file' && item.webkitGetAsEntry().isFile) {
|
||||
let file = item.getAsFile()
|
||||
if (/\.smm$/.test(file.name)) {
|
||||
dropFiles.push(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dropFiles.length === 1) {
|
||||
// 如果只有一个文件,直接打开编辑
|
||||
window.electronAPI.openFile(dropFiles[0].path)
|
||||
} else if (dropFiles.length > 1) {
|
||||
// 否则添加到最近文件列表
|
||||
window.electronAPI.addRecentFileList(
|
||||
dropFiles.map(file => {
|
||||
return file.path
|
||||
})
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
onDragenter(e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
},
|
||||
|
||||
onDragover(e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
},
|
||||
|
||||
onDragleave(e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.workbencheHomeContainer {
|
||||
background-color: #f6f8f9;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.workbencheHomeHeader {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
background-color: #ebeef1;
|
||||
-webkit-app-region: drag;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
|
||||
.rightBar {
|
||||
-webkit-app-region: no-drag;
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.settingBtn {
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.workbencheHomeContent {
|
||||
flex-grow: 1;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -3,29 +3,69 @@ import VueRouter from 'vue-router'
|
||||
import EditPage from '@/pages/Edit/Index'
|
||||
import DocPage from '@/pages/Doc/Index'
|
||||
import routerList from '@/pages/Doc/routerList'
|
||||
import WorkbenchePage from '@/pages/Workbenche/Index'
|
||||
import WorkbencheHomePage from '@/pages/Workbenche/views/Home'
|
||||
import WorkbencheEditPage from '@/pages/Workbenche/views/Edit'
|
||||
|
||||
// 处理没有翻译的章节路由
|
||||
const handleRouterList = () => {
|
||||
let zhList = routerList[0].children
|
||||
for(let i = 1; i < routerList.length; i++) {
|
||||
let list = routerList[i].children
|
||||
zhList.forEach(item => {
|
||||
if (!list.find((item2) => {
|
||||
return item2.path === item.path
|
||||
})) {
|
||||
list.push({
|
||||
...item,
|
||||
lang: 'zh'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
handleRouterList()
|
||||
|
||||
Vue.use(VueRouter)
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Edit',
|
||||
component: EditPage
|
||||
{
|
||||
path: '/',
|
||||
name: 'Edit',
|
||||
component: EditPage
|
||||
},
|
||||
{
|
||||
path: '/workbenche',
|
||||
name: 'Workbenche',
|
||||
component: WorkbenchePage,
|
||||
redirect: '/workbenche/home',
|
||||
children: [
|
||||
{
|
||||
path: 'home',
|
||||
name: 'WorkbencheHome',
|
||||
component: WorkbencheHomePage,
|
||||
},
|
||||
{
|
||||
path: 'edit/:id',
|
||||
name: 'WorkbencheEdit',
|
||||
component: WorkbencheEditPage,
|
||||
}
|
||||
]
|
||||
},
|
||||
...routerList.map((item) => {
|
||||
return {
|
||||
path: `/doc/${item.lang}/`,
|
||||
return {
|
||||
path: `/doc/${item.lang}/`,
|
||||
redirect: `/doc/${item.lang}/introduction/`
|
||||
}
|
||||
}),
|
||||
...routerList.map((item) => {
|
||||
return {
|
||||
path: `/doc/${item.lang}/`,
|
||||
component: DocPage,
|
||||
return {
|
||||
path: `/doc/${item.lang}/`,
|
||||
component: DocPage,
|
||||
children: item.children.map((child) => {
|
||||
return {
|
||||
path: `${child.path}/:h?`,
|
||||
component: () => import(`./pages/Doc/${item.lang}/${child.path}/index.vue`)
|
||||
component: () => import(`./pages/Doc/${child.lang || item.lang}/${child.path}/index.vue`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ Vue.use(Vuex)
|
||||
|
||||
const store = new Vuex.Store({
|
||||
state: {
|
||||
fileName: '',// 本地的文件名
|
||||
isUnSave: false,// 当前操作是否未保存
|
||||
mindMapData: null, // 思维导图数据
|
||||
isHandleLocalFile: false, // 是否操作的是本地文件
|
||||
localConfig: {
|
||||
@@ -15,9 +17,20 @@ const store = new Vuex.Store({
|
||||
// 是否开启节点富文本
|
||||
openNodeRichText: true
|
||||
},
|
||||
activeSidebar: '' // 当前显示的侧边栏
|
||||
activeSidebar: '', // 当前显示的侧边栏
|
||||
localEditList: []// 客户端中正在编辑的思维导图列表
|
||||
},
|
||||
mutations: {
|
||||
// 设置本地文件名
|
||||
setFileName(state, data) {
|
||||
state.fileName = data
|
||||
},
|
||||
|
||||
// 设置当前操作是否未保存
|
||||
setIsUnSave(state, data) {
|
||||
state.isUnSave = data
|
||||
},
|
||||
|
||||
/**
|
||||
* @Author: 王林
|
||||
* @Date: 2021-04-10 14:50:01
|
||||
@@ -59,6 +72,11 @@ const store = new Vuex.Store({
|
||||
*/
|
||||
setActiveSidebar(state, data) {
|
||||
state.activeSidebar = data
|
||||
},
|
||||
|
||||
// 设置客户端中当前正在编辑的思维导图列表
|
||||
setLocalEditList(state, list) {
|
||||
state.localEditList = list
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
|
||||
@@ -1,16 +1,119 @@
|
||||
const path = require('path');
|
||||
const path = require('path')
|
||||
const isDev = process.env.NODE_ENV === 'development'
|
||||
|
||||
module.exports = {
|
||||
publicPath: isDev ? '' : './dist',
|
||||
outputDir: '../dist',
|
||||
lintOnSave: false,
|
||||
productionSourceMap: false,
|
||||
configureWebpack: {
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src/')
|
||||
}
|
||||
}
|
||||
publicPath: isDev ? '' : './dist',
|
||||
outputDir: '../dist',
|
||||
lintOnSave: false,
|
||||
productionSourceMap: false,
|
||||
configureWebpack: {
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src/')
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
pluginOptions: {
|
||||
electronBuilder: {
|
||||
preload: 'src/electron/preload.js',
|
||||
builderOptions: {
|
||||
productName: '思绪思维导图',
|
||||
copyright: 'Copyright © 思绪思维导图',
|
||||
// compression: "maximum", // 机器好的可以打开,配置压缩,开启后会让 .AppImage 格式的客户端启动缓慢
|
||||
asar: true,
|
||||
fileAssociations: [
|
||||
{
|
||||
ext: 'smm',
|
||||
name: 'mind map file',
|
||||
role: 'Editor',
|
||||
icon: './build/icons/icon.ico'
|
||||
}
|
||||
],
|
||||
publish: [
|
||||
{
|
||||
provider: 'github',
|
||||
owner: 'wanglin2',
|
||||
repo: 'mind-map',
|
||||
vPrefixedTagName: true,
|
||||
releaseType: 'draft'
|
||||
}
|
||||
],
|
||||
directories: {
|
||||
output: 'dist_electron'
|
||||
},
|
||||
mac: {
|
||||
target: [
|
||||
{
|
||||
target: 'dmg',
|
||||
arch: ['x64', 'arm64', 'universal']
|
||||
}
|
||||
],
|
||||
artifactName: '${productName}-${os}-${version}-${arch}.${ext}',
|
||||
category: 'public.app-category.utilities',
|
||||
darkModeSupport: true
|
||||
},
|
||||
win: {
|
||||
target: [
|
||||
{
|
||||
target: 'portable',
|
||||
arch: ['x64']
|
||||
},
|
||||
{
|
||||
target: 'nsis',
|
||||
arch: ['x64']
|
||||
}
|
||||
],
|
||||
publisherName: '思绪思维导图',
|
||||
icon: 'build/icons/icon.ico',
|
||||
publish: ['github']
|
||||
},
|
||||
linux: {
|
||||
target: [
|
||||
{
|
||||
target: 'AppImage',
|
||||
arch: ['x64']
|
||||
},
|
||||
{
|
||||
target: 'tar.gz',
|
||||
arch: ['x64', 'arm64']
|
||||
},
|
||||
{
|
||||
target: 'deb',
|
||||
arch: ['x64', 'armv7l', 'arm64']
|
||||
},
|
||||
{
|
||||
target: 'rpm',
|
||||
arch: ['x64']
|
||||
},
|
||||
{
|
||||
target: 'snap',
|
||||
arch: ['x64']
|
||||
},
|
||||
{
|
||||
target: 'pacman',
|
||||
arch: ['x64']
|
||||
}
|
||||
],
|
||||
category: 'Utilities',
|
||||
icon: './build/icon.icns'
|
||||
},
|
||||
dmg: {
|
||||
icon: 'build/icons/icon.icns'
|
||||
},
|
||||
nsis: {
|
||||
oneClick: false,
|
||||
allowToChangeInstallationDirectory: true,
|
||||
perMachine: true,
|
||||
deleteAppDataOnUninstall: true
|
||||
}
|
||||
},
|
||||
// 渲染线程的配置文件
|
||||
chainWebpackRendererProcess: config => {
|
||||
config.plugin('define').tap(args => {
|
||||
args[0]['IS_ELECTRON'] = true
|
||||
return args
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user