初始化仓库

This commit is contained in:
wanglin
2021-06-10 23:22:22 +08:00
commit e9b5b752c8
64 changed files with 5487 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
node_modules
oss.js

5
babel.config.js Normal file
View File

@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

50
package.json Normal file
View File

@@ -0,0 +1,50 @@
{
"name": "thoughts",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"@svgdotjs/svg.js": "^3.0.16",
"core-js": "^3.6.5",
"deepmerge": "^1.5.2",
"element-ui": "^2.15.1",
"vue": "^2.6.11",
"vue-router": "^3.5.1",
"vuex": "^3.6.2"
},
"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",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
"less": "^3.12.2",
"less-loader": "^7.1.0",
"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"
]
}

18
public/index.html Normal file
View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>思绪_一个极简的在线思维导图</title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<script src="http://lxqnsys.com/js/polyfillB.js"></script>
<script src="http://lxqnsys.com/js/aliyun-oss-sdk-6.0.1.min.js"></script>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

BIN
src/.DS_Store vendored Normal file

Binary file not shown.

26
src/App.vue Normal file
View File

@@ -0,0 +1,26 @@
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
<script>
export default {
name: "App",
components: {},
};
</script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
}
</style>

BIN
src/assets/.DS_Store vendored Normal file

Binary file not shown.

BIN
src/assets/icon-font/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,539 @@
/* Logo 字体 */
@font-face {
font-family: "iconfont logo";
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
}
.logo {
font-family: "iconfont logo";
font-size: 160px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* tabs */
.nav-tabs {
position: relative;
}
.nav-tabs .nav-more {
position: absolute;
right: 0;
bottom: 0;
height: 42px;
line-height: 42px;
color: #666;
}
#tabs {
border-bottom: 1px solid #eee;
}
#tabs li {
cursor: pointer;
width: 100px;
height: 40px;
line-height: 40px;
text-align: center;
font-size: 16px;
border-bottom: 2px solid transparent;
position: relative;
z-index: 1;
margin-bottom: -1px;
color: #666;
}
#tabs .active {
border-bottom-color: #f00;
color: #222;
}
.tab-container .content {
display: none;
}
/* 页面布局 */
.main {
padding: 30px 100px;
width: 960px;
margin: 0 auto;
}
.main .logo {
color: #333;
text-align: left;
margin-bottom: 30px;
line-height: 1;
height: 110px;
margin-top: -50px;
overflow: hidden;
*zoom: 1;
}
.main .logo a {
font-size: 160px;
color: #333;
}
.helps {
margin-top: 40px;
}
.helps pre {
padding: 20px;
margin: 10px 0;
border: solid 1px #e7e1cd;
background-color: #fffdef;
overflow: auto;
}
.icon_lists {
width: 100% !important;
overflow: hidden;
*zoom: 1;
}
.icon_lists li {
width: 100px;
margin-bottom: 10px;
margin-right: 20px;
text-align: center;
list-style: none !important;
cursor: default;
}
.icon_lists li .code-name {
line-height: 1.2;
}
.icon_lists .icon {
display: block;
height: 100px;
line-height: 100px;
font-size: 42px;
margin: 10px auto;
color: #333;
-webkit-transition: font-size 0.25s linear, width 0.25s linear;
-moz-transition: font-size 0.25s linear, width 0.25s linear;
transition: font-size 0.25s linear, width 0.25s linear;
}
.icon_lists .icon:hover {
font-size: 100px;
}
.icon_lists .svg-icon {
/* 通过设置 font-size 来改变图标大小 */
width: 1em;
/* 图标和文字相邻时,垂直对齐 */
vertical-align: -0.15em;
/* 通过设置 color 来改变 SVG 的颜色/fill */
fill: currentColor;
/* path 和 stroke 溢出 viewBox 部分在 IE 下会显示
normalize.css 中也包含这行 */
overflow: hidden;
}
.icon_lists li .name,
.icon_lists li .code-name {
color: #666;
}
/* markdown 样式 */
.markdown {
color: #666;
font-size: 14px;
line-height: 1.8;
}
.highlight {
line-height: 1.5;
}
.markdown img {
vertical-align: middle;
max-width: 100%;
}
.markdown h1 {
color: #404040;
font-weight: 500;
line-height: 40px;
margin-bottom: 24px;
}
.markdown h2,
.markdown h3,
.markdown h4,
.markdown h5,
.markdown h6 {
color: #404040;
margin: 1.6em 0 0.6em 0;
font-weight: 500;
clear: both;
}
.markdown h1 {
font-size: 28px;
}
.markdown h2 {
font-size: 22px;
}
.markdown h3 {
font-size: 16px;
}
.markdown h4 {
font-size: 14px;
}
.markdown h5 {
font-size: 12px;
}
.markdown h6 {
font-size: 12px;
}
.markdown hr {
height: 1px;
border: 0;
background: #e9e9e9;
margin: 16px 0;
clear: both;
}
.markdown p {
margin: 1em 0;
}
.markdown>p,
.markdown>blockquote,
.markdown>.highlight,
.markdown>ol,
.markdown>ul {
width: 80%;
}
.markdown ul>li {
list-style: circle;
}
.markdown>ul li,
.markdown blockquote ul>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown>ul li p,
.markdown>ol li p {
margin: 0.6em 0;
}
.markdown ol>li {
list-style: decimal;
}
.markdown>ol li,
.markdown blockquote ol>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown code {
margin: 0 3px;
padding: 0 5px;
background: #eee;
border-radius: 3px;
}
.markdown strong,
.markdown b {
font-weight: 600;
}
.markdown>table {
border-collapse: collapse;
border-spacing: 0px;
empty-cells: show;
border: 1px solid #e9e9e9;
width: 95%;
margin-bottom: 24px;
}
.markdown>table th {
white-space: nowrap;
color: #333;
font-weight: 600;
}
.markdown>table th,
.markdown>table td {
border: 1px solid #e9e9e9;
padding: 8px 16px;
text-align: left;
}
.markdown>table th {
background: #F7F7F7;
}
.markdown blockquote {
font-size: 90%;
color: #999;
border-left: 4px solid #e9e9e9;
padding-left: 0.8em;
margin: 1em 0;
}
.markdown blockquote p {
margin: 0;
}
.markdown .anchor {
opacity: 0;
transition: opacity 0.3s ease;
margin-left: 8px;
}
.markdown .waiting {
color: #ccc;
}
.markdown h1:hover .anchor,
.markdown h2:hover .anchor,
.markdown h3:hover .anchor,
.markdown h4:hover .anchor,
.markdown h5:hover .anchor,
.markdown h6:hover .anchor {
opacity: 1;
display: inline-block;
}
.markdown>br,
.markdown>p>br {
clear: both;
}
.hljs {
display: block;
background: white;
padding: 0.5em;
color: #333333;
overflow-x: auto;
}
.hljs-comment,
.hljs-meta {
color: #969896;
}
.hljs-string,
.hljs-variable,
.hljs-template-variable,
.hljs-strong,
.hljs-emphasis,
.hljs-quote {
color: #df5000;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-type {
color: #a71d5d;
}
.hljs-literal,
.hljs-symbol,
.hljs-bullet,
.hljs-attribute {
color: #0086b3;
}
.hljs-section,
.hljs-name {
color: #63a35c;
}
.hljs-tag {
color: #333333;
}
.hljs-title,
.hljs-attr,
.hljs-selector-id,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #795da3;
}
.hljs-addition {
color: #55a532;
background-color: #eaffea;
}
.hljs-deletion {
color: #bd2c00;
background-color: #ffecec;
}
.hljs-link {
text-decoration: underline;
}
/* 代码高亮 */
/* PrismJS 1.15.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection,
pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection,
code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection,
pre[class*="language-"] ::selection,
code[class*="language-"]::selection,
code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre)>code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
/* Inline code */
:not(pre)>code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function,
.token.class-name {
color: #DD4A68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}

View File

@@ -0,0 +1,464 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>iconfont Demo</title>
<link rel="shortcut icon" href="//img.alicdn.com/imgextra/i2/O1CN01ZyAlrn1MwaMhqz36G_!!6000000001499-73-tps-64-64.ico" type="image/x-icon"/>
<link rel="icon" type="image/svg+xml" href="//img.alicdn.com/imgextra/i4/O1CN01EYTRnJ297D6vehehJ_!!6000000008020-55-tps-64-64.svg"/>
<link rel="stylesheet" href="https://g.alicdn.com/thx/cube/1.3.2/cube.min.css">
<link rel="stylesheet" href="demo.css">
<link rel="stylesheet" href="iconfont.css">
<script src="iconfont.js"></script>
<!-- jQuery -->
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/7bfddb60-08e8-11e9-9b04-53e73bb6408b.js"></script>
<!-- 代码高亮 -->
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/a3f714d0-08e6-11e9-8a15-ebf944d7534c.js"></script>
<style>
.main .logo {
margin-top: 0;
height: auto;
}
.main .logo a {
display: flex;
align-items: center;
}
.main .logo .sub-title {
margin-left: 0.5em;
font-size: 22px;
color: #fff;
background: linear-gradient(-45deg, #3967FF, #B500FE);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
</style>
</head>
<body>
<div class="main">
<h1 class="logo"><a href="https://www.iconfont.cn/" title="iconfont 首页" target="_blank">
<img width="200" src="https://img.alicdn.com/imgextra/i3/O1CN01Mn65HV1FfSEzR6DKv_!!6000000000514-55-tps-228-59.svg">
</a></h1>
<div class="nav-tabs">
<ul id="tabs" class="dib-box">
<li class="dib active"><span>Unicode</span></li>
<li class="dib"><span>Font class</span></li>
<li class="dib"><span>Symbol</span></li>
</ul>
<a href="https://www.iconfont.cn/manage/index?manage_type=myprojects&projectId=2479351" target="_blank" class="nav-more">查看项目</a>
</div>
<div class="tab-container">
<div class="content unicode" style="display: block;">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont">&#xe6f4;</span>
<div class="name">超链接</div>
<div class="code-name">&amp;#xe6f4;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe610;</span>
<div class="name">主题</div>
<div class="code-name">&amp;#xe610;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe60f;</span>
<div class="name">笑脸</div>
<div class="code-name">&amp;#xe60f;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe629;</span>
<div class="name">图 片</div>
<div class="code-name">&amp;#xe629;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe61d;</span>
<div class="name">结构</div>
<div class="code-name">&amp;#xe61d;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe631;</span>
<div class="name">样式</div>
<div class="code-name">&amp;#xe631;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe71f;</span>
<div class="name">符号-大纲树</div>
<div class="code-name">&amp;#xe71f;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe622;</span>
<div class="name">添加子节点</div>
<div class="code-name">&amp;#xe622;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe655;</span>
<div class="name">节点</div>
<div class="code-name">&amp;#xe655;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe696;</span>
<div class="name">删 除</div>
<div class="code-name">&amp;#xe696;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe64c;</span>
<div class="name">HTSCIT_展开</div>
<div class="code-name">&amp;#xe64c;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe673;</span>
<div class="name">HTSCIT_展开2</div>
<div class="code-name">&amp;#xe673;</div>
</li>
</ul>
<div class="article markdown">
<h2 id="unicode-">Unicode 引用</h2>
<hr>
<p>Unicode 是字体在网页端最原始的应用方式,特点是:</p>
<ul>
<li>支持按字体的方式去动态调整图标大小,颜色等等。</li>
<li>默认情况下不支持多色,直接添加多色图标会自动去色。</li>
</ul>
<blockquote>
<p>注意:新版 iconfont 支持两种方式引用多色图标SVG symbol 引用方式和彩色字体图标模式。(使用彩色字体图标需要在「编辑项目」中开启「彩色」选项后并重新生成。)</p>
</blockquote>
<p>Unicode 使用步骤如下:</p>
<h3 id="-font-face">第一步:拷贝项目下面生成的 <code>@font-face</code></h3>
<pre><code class="language-css"
>@font-face {
font-family: 'iconfont';
src: url('iconfont.woff2?t=1622956585729') format('woff2'),
url('iconfont.woff?t=1622956585729') format('woff'),
url('iconfont.ttf?t=1622956585729') format('truetype');
}
</code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
<pre><code class="language-css"
>.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
</code></pre>
<h3 id="-">第三步:挑选相应图标并获取字体编码,应用于页面</h3>
<pre>
<code class="language-html"
>&lt;span class="iconfont"&gt;&amp;#x33;&lt;/span&gt;
</code></pre>
<blockquote>
<p>"iconfont" 是你项目下的 font-family。可以通过编辑项目查看默认是 "iconfont"。</p>
</blockquote>
</div>
</div>
<div class="content font-class">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont iconchaolianjie"></span>
<div class="name">
超链接
</div>
<div class="code-name">.iconchaolianjie
</div>
</li>
<li class="dib">
<span class="icon iconfont iconjingzi"></span>
<div class="name">
主题
</div>
<div class="code-name">.iconjingzi
</div>
</li>
<li class="dib">
<span class="icon iconfont iconxiaolian"></span>
<div class="name">
笑脸
</div>
<div class="code-name">.iconxiaolian
</div>
</li>
<li class="dib">
<span class="icon iconfont iconimage"></span>
<div class="name">
图 片
</div>
<div class="code-name">.iconimage
</div>
</li>
<li class="dib">
<span class="icon iconfont iconjiegou"></span>
<div class="name">
结构
</div>
<div class="code-name">.iconjiegou
</div>
</li>
<li class="dib">
<span class="icon iconfont iconyangshi"></span>
<div class="name">
样式
</div>
<div class="code-name">.iconyangshi
</div>
</li>
<li class="dib">
<span class="icon iconfont iconfuhao-dagangshu"></span>
<div class="name">
符号-大纲树
</div>
<div class="code-name">.iconfuhao-dagangshu
</div>
</li>
<li class="dib">
<span class="icon iconfont icontianjiazijiedian"></span>
<div class="name">
添加子节点
</div>
<div class="code-name">.icontianjiazijiedian
</div>
</li>
<li class="dib">
<span class="icon iconfont iconjiedian"></span>
<div class="name">
节点
</div>
<div class="code-name">.iconjiedian
</div>
</li>
<li class="dib">
<span class="icon iconfont iconshanchu"></span>
<div class="name">
删 除
</div>
<div class="code-name">.iconshanchu
</div>
</li>
<li class="dib">
<span class="icon iconfont iconzhankai"></span>
<div class="name">
HTSCIT_展开
</div>
<div class="code-name">.iconzhankai
</div>
</li>
<li class="dib">
<span class="icon iconfont iconzhankai1"></span>
<div class="name">
HTSCIT_展开2
</div>
<div class="code-name">.iconzhankai1
</div>
</li>
</ul>
<div class="article markdown">
<h2 id="font-class-">font-class 引用</h2>
<hr>
<p>font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。</p>
<p>与 Unicode 使用方式相比,具有如下特点:</p>
<ul>
<li>相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。</li>
<li>因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。</li>
</ul>
<p>使用步骤如下:</p>
<h3 id="-fontclass-">第一步:引入项目下面生成的 fontclass 代码:</h3>
<pre><code class="language-html">&lt;link rel="stylesheet" href="./iconfont.css"&gt;
</code></pre>
<h3 id="-">第二步:挑选相应图标并获取类名,应用于页面:</h3>
<pre><code class="language-html">&lt;span class="iconfont iconxxx"&gt;&lt;/span&gt;
</code></pre>
<blockquote>
<p>"
iconfont" 是你项目下的 font-family。可以通过编辑项目查看默认是 "iconfont"。</p>
</blockquote>
</div>
</div>
<div class="content symbol">
<ul class="icon_lists dib-box">
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconchaolianjie"></use>
</svg>
<div class="name">超链接</div>
<div class="code-name">#iconchaolianjie</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconjingzi"></use>
</svg>
<div class="name">主题</div>
<div class="code-name">#iconjingzi</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconxiaolian"></use>
</svg>
<div class="name">笑脸</div>
<div class="code-name">#iconxiaolian</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconimage"></use>
</svg>
<div class="name">图 片</div>
<div class="code-name">#iconimage</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconjiegou"></use>
</svg>
<div class="name">结构</div>
<div class="code-name">#iconjiegou</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconyangshi"></use>
</svg>
<div class="name">样式</div>
<div class="code-name">#iconyangshi</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconfuhao-dagangshu"></use>
</svg>
<div class="name">符号-大纲树</div>
<div class="code-name">#iconfuhao-dagangshu</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icontianjiazijiedian"></use>
</svg>
<div class="name">添加子节点</div>
<div class="code-name">#icontianjiazijiedian</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconjiedian"></use>
</svg>
<div class="name">节点</div>
<div class="code-name">#iconjiedian</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconshanchu"></use>
</svg>
<div class="name">删 除</div>
<div class="code-name">#iconshanchu</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconzhankai"></use>
</svg>
<div class="name">HTSCIT_展开</div>
<div class="code-name">#iconzhankai</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#iconzhankai1"></use>
</svg>
<div class="name">HTSCIT_展开2</div>
<div class="code-name">#iconzhankai1</div>
</li>
</ul>
<div class="article markdown">
<h2 id="symbol-">Symbol 引用</h2>
<hr>
<p>这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇<a href="">文章</a>
这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:</p>
<ul>
<li>支持多色图标了,不再受单色限制。</li>
<li>通过一些技巧,支持像字体那样,通过 <code>font-size</code>, <code>color</code> 来调整样式。</li>
<li>兼容性较差,支持 IE9+,及现代浏览器。</li>
<li>浏览器渲染 SVG 的性能一般,还不如 png。</li>
</ul>
<p>使用步骤如下:</p>
<h3 id="-symbol-">第一步:引入项目下面生成的 symbol 代码:</h3>
<pre><code class="language-html">&lt;script src="./iconfont.js"&gt;&lt;/script&gt;
</code></pre>
<h3 id="-css-">第二步:加入通用 CSS 代码(引入一次就行):</h3>
<pre><code class="language-html">&lt;style&gt;
.icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
&lt;/style&gt;
</code></pre>
<h3 id="-">第三步:挑选相应图标并获取类名,应用于页面:</h3>
<pre><code class="language-html">&lt;svg class="icon" aria-hidden="true"&gt;
&lt;use xlink:href="#icon-xxx"&gt;&lt;/use&gt;
&lt;/svg&gt;
</code></pre>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function () {
$('.tab-container .content:first').show()
$('#tabs li').click(function (e) {
var tabContent = $('.tab-container .content')
var index = $(this).index()
if ($(this).hasClass('active')) {
return
} else {
$('#tabs li').removeClass('active')
$(this).addClass('active')
tabContent.hide().eq(index).fadeIn()
}
})
})
</script>
</body>
</html>

View File

@@ -0,0 +1,63 @@
@font-face {
font-family: "iconfont"; /* Project id 2479351 */
src: url('iconfont.woff2?t=1622956585729') format('woff2'),
url('iconfont.woff?t=1622956585729') format('woff'),
url('iconfont.ttf?t=1622956585729') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.iconchaolianjie:before {
content: "\e6f4";
}
.iconjingzi:before {
content: "\e610";
}
.iconxiaolian:before {
content: "\e60f";
}
.iconimage:before {
content: "\e629";
}
.iconjiegou:before {
content: "\e61d";
}
.iconyangshi:before {
content: "\e631";
}
.iconfuhao-dagangshu:before {
content: "\e71f";
}
.icontianjiazijiedian:before {
content: "\e622";
}
.iconjiedian:before {
content: "\e655";
}
.iconshanchu:before {
content: "\e696";
}
.iconzhankai:before {
content: "\e64c";
}
.iconzhankai1:before {
content: "\e673";
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,93 @@
{
"id": "2479351",
"name": "思绪",
"font_family": "iconfont",
"css_prefix_text": "icon",
"description": "思维导图",
"glyphs": [
{
"icon_id": "1790486",
"name": "超链接",
"font_class": "chaolianjie",
"unicode": "e6f4",
"unicode_decimal": 59124
},
{
"icon_id": "4608986",
"name": "主题",
"font_class": "jingzi",
"unicode": "e610",
"unicode_decimal": 58896
},
{
"icon_id": "11903017",
"name": "笑脸",
"font_class": "xiaolian",
"unicode": "e60f",
"unicode_decimal": 58895
},
{
"icon_id": "19657962",
"name": "图 片",
"font_class": "image",
"unicode": "e629",
"unicode_decimal": 58921
},
{
"icon_id": "20784489",
"name": "结构",
"font_class": "jiegou",
"unicode": "e61d",
"unicode_decimal": 58909
},
{
"icon_id": "15969341",
"name": "样式",
"font_class": "yangshi",
"unicode": "e631",
"unicode_decimal": 58929
},
{
"icon_id": "2967176",
"name": "符号-大纲树",
"font_class": "fuhao-dagangshu",
"unicode": "e71f",
"unicode_decimal": 59167
},
{
"icon_id": "12316668",
"name": "添加子节点",
"font_class": "tianjiazijiedian",
"unicode": "e622",
"unicode_decimal": 58914
},
{
"icon_id": "14435368",
"name": "节点",
"font_class": "jiedian",
"unicode": "e655",
"unicode_decimal": 58965
},
{
"icon_id": "15765352",
"name": "删 除",
"font_class": "shanchu",
"unicode": "e696",
"unicode_decimal": 59030
},
{
"icon_id": "9592600",
"name": "HTSCIT_展开",
"font_class": "zhankai",
"unicode": "e64c",
"unicode_decimal": 58956
},
{
"icon_id": "9900009",
"name": "HTSCIT_展开2",
"font_class": "zhankai1",
"unicode": "e673",
"unicode_decimal": 58995
}
]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,131 @@
<template>
<div class="imgUploadContainer">
<div class="imgUploadPanel">
<div class="upBtn" v-if="!previewSrc">
<label
for="imgUploadInput"
class="imgUploadInputArea"
@dragenter.stop.prevent
@dragover.stop.prevent
@drop.stop.prevent="onDrop"
v-loading="loading"
element-loading-text="上传中..."
>点击此处添加图片或拖动图片到此</label
>
<input
type="file"
accept="image/*"
id="imgUploadInput"
@change="onImgUploadInputChange"
/>
</div>
<div v-if="previewSrc" class="uploadInfoBox">
<div
class="previewBox"
:style="{ backgroundImage: `url('${previewSrc}')` }"
></div>
<span class="delBtn el-icon-close" @click="deleteImg"></span>
</div>
</div>
</div>
</template>
<script>
import ossUpLoader from "@/utils/oss";
export default {
name: "ImgUpload",
data() {
return {
file: null,
previewSrc: "",
loading: false,
};
},
methods: {
/**
* @Author: 王林
* @Date: 2019-12-22 19:47:19
* @Desc: 图片选择事件
*/
onImgUploadInputChange(e) {
let file = e.target.files[0];
this.uploadImg(file);
},
/**
* @Author: 王林
* @Date: 2019-12-22 20:32:31
* @Desc: 拖动上传图片
*/
onDrop(e) {
let dt = e.dataTransfer;
let file = dt.files && dt.files[0];
this.uploadImg(file);
},
/**
* @Author: 王林
* @Date: 2021-06-06 16:56:14
* @Desc: 显示图片
*/
showImg(file) {
this.file = file;
let fr = new FileReader();
fr.readAsDataURL(file);
fr.onload = (e) => {
this.previewSrc = e.target.result;
};
},
/**
* @Author: 王林
* @Date: 2019-12-22 19:51:13
* @Desc: 上传图片
*/
async uploadImg(file) {
this.loading = true;
ossUpLoader.uploadFile(
file,
() => {},
(e) => {
this.previewSrc = e[0].res.requestUrls[0]
this.loading = false;
},
(e) => {
if (e.length > 0) {
this.loading = false;
this.$message({
message: "上传失败",
type: "warning",
});
}
}
);
},
/**
* @Author: 王林
* @Date: 2021-06-06 22:33:28
* @Desc: 获取图片url
*/
getUrl() {
return this.this.previewSrc;
},
/**
* @Author: 王林
* @Date: 2021-06-06 21:59:57
* @Desc: 删除图片
*/
deleteImg() {
this.previewSrc = "";
this.file = null;
},
},
};
</script>
<style lang="less" scoped>
@import "./style.less";
</style>

View File

@@ -0,0 +1,80 @@
.imgUploadContainer {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background-color: rgba(255,255,255,.9);
z-index: 1000;
.imgUploadPanel {
position: relative;
width: 100%;
font-size: 22px;
white-space: nowrap;
color: #909090;
cursor: default;
user-select: none;
.title {
margin-bottom: 15px;
font-size: 22px;
font-weight: 700;
color: hsla(218,9%,51%,.8);
}
.closeBtn {
position: absolute;
right: 25px;
top: 32px;
cursor: pointer;
}
.imgUploadInputArea {
display: block;
width: 100%;
height: 200px;
font-size: 20px;
color: rgba(51,51,51,.4);
background-color: hsla(0,0%,87%,.6);
border: none;
outline: none;
cursor: pointer;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
white-space: normal;
padding: 10px;
}
#imgUploadInput {
display: none;
}
.uploadInfoBox {
position: relative;
width: 100%;
height: 200px;
background-color: hsla(0,0%,87%,.6);
.previewBox {
width: 100%;
height: 100%;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
}
.delBtn {
position: absolute;
right: 0px;
top: 0px;
cursor: pointer;
width: 20px;
height: 20px;
background-color: #fff;
}
}
}
}

128
src/config/index.js Normal file
View File

@@ -0,0 +1,128 @@
// 字体列表
export const fontFamilyList = [
{
name: '宋体',
value: '宋体, SimSun, Songti SC'
}, {
name: '微软雅黑',
value: '微软雅黑, Microsoft YaHei'
}, {
name: '楷体',
value: '楷体, 楷体_GB2312, SimKai, STKaiti'
}, {
name: '黑体',
value: '黑体, SimHei, Heiti SC'
}, {
name: '隶书',
value: '隶书, SimLi'
}, {
name: 'Andale Mono',
value: 'andale mono'
}, {
name: 'Arial',
value: 'arial, helvetica, sans-serif'
}, {
name: 'arialBlack',
value: 'arial black, avant garde'
}, {
name: 'Comic Sans Ms',
value: 'comic sans ms'
}, {
name: 'Impact',
value: 'impact, chicago'
}, {
name: 'Times New Roman',
value: 'times new roman'
}, {
name: 'Sans-Serif',
value: 'sans-serif'
},
{
name: 'serif',
value: 'serif'
}
]
// 字号
export const fontSizeList = [10, 12, 16, 18, 24, 32, 48]
// 颜色
export const colorList = [
'#4D4D4D',
'#999999',
'#FFFFFF',
'#F44E3B',
'#FE9200',
'#FCDC00',
'#DBDF00',
'#A4DD00',
'#68CCCA',
'#73D8FF',
'#AEA1FF',
'#FDA1FF',
'#333333',
'#808080',
'#cccccc',
'#D33115',
'#E27300',
'#FCC400',
'#B0BC00',
'#68BC00',
'#16A5A5',
'#009CE0',
'#7B64FF',
'#FA28FF',
'#000000',
'#666666',
'#B3B3B3',
'#9F0500',
'#C45100',
'#FB9E00',
'#808900',
'#194D33',
'#0C797D',
'#0062B1',
'#653294',
'#AB149E'
]
// 边框宽度
export const borderWidthList = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// 边框样式
export const borderDasharrayList = [
{
name: '实线',
value: 'none'
},
{
name: '虚线1',
value: '5,5'
},
{
name: '虚线2',
value: '10,10'
},
{
name: '虚线3',
value: '20,10,5,5,5,10'
},
{
name: '虚线4',
value: '5, 5, 1, 5'
},
{
name: '虚线5',
value: '15, 10, 5, 10, 15'
},
{
name: '虚线6',
value: '1, 5'
}
]
// 圆角
export const borderRadiusList = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// 线宽
export const lineWidthList = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

17
src/main.js Normal file
View File

@@ -0,0 +1,17 @@
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import '@/assets/icon-font/iconfont.css';
Vue.config.productionTip = false
Vue.prototype.$bus = new Vue()
Vue.use(ElementUI)
new Vue({
render: h => h(App),
router,
store
}).$mount('#app')

BIN
src/package/.DS_Store vendored Normal file

Binary file not shown.

BIN
src/package/mind-map/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,44 @@
/**
* @Author: 王林
* @Date: 2021-04-15 22:23:24
* @Desc: 完整示例数据
*/
export default {
"root": {
"data": {
"text": "鱼骨头图",
},
"children": [{
"data": {
"text": "分支主题",
"expand": true
},
"children": [{
"data": {
"text": "分支主题",
"hyperlink": "https://naotu.baidu.com/",
"hyperlinkTitle": "百度脑图",
"image": "https://kityminder-img.gz.bcebos.com/865551aedebd1e02ac6e76d24c093231df9aafda",
"imageTitle": "图片名称",
"imageSize": {
"width": 200,
"height": 112
},
"note": "我是备注",
"resource": ["标签1", "标签2"],
"priority": 5,
"progress": 7,
// ... 其他类型的图标
},
"children": []
}]
}]
},
"theme": {
"template": "default",
"config": {
// 自定义配置...
}
},
"layout": "logicalStructure"
}

View File

@@ -0,0 +1,174 @@
import View from './src/View'
import Event from './src/Event'
import Render from './src/Render'
import merge from 'deepmerge'
import theme from './src/themes'
import Style from './src/Style'
import KeyCommand from './src/KeyCommand'
import Command from './src/Command';
import { SVG } from '@svgdotjs/svg.js'
const defaultOpt = {
// 布局
layout: 'logicalStructure',
// 放大缩小的增量比例即step = scaleRatio * width|height
scaleRatio: 0.1,
// 主题
theme: 'default',// 内置主题default默认主题
// 主题配置,会和所选择的主题进行合并
themeConfig: {}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-06 11:18:47
* @Desc: 思维导图
*/
class MindMap {
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-06 11:19:01
* @Desc: 构造函数
*/
constructor(opt = {}) {
this.opt = merge(defaultOpt, opt)
// 容器元素
this.el = this.opt.el
let {
width,
height
} = this.el.getBoundingClientRect()
// 画布宽高
this.width = width
this.height = height
// 画笔
this.draw = SVG().addTo(this.el).size(width, height)
// 节点id
this.uid = 0
// 主题
this.initTheme()
// 事件类
this.event = new Event({
mindMap: this
})
// 按键类
this.keyCommand = new KeyCommand({
mindMap: this
})
// 命令类
this.command = new Command({
mindMap: this
})
// 渲染类
this.renderer = new Render({
mindMap: this
})
// 视图操作类
this.view = new View({
mindMap: this,
draw: this.draw
})
this.render()
setTimeout(() => {
this.command.addHistory()
}, 0);
}
/**
* @Author: 王林
* @Date: 2021-04-24 13:25:50
* @Desc: 监听事件
*/
on(event, fn) {
this.event.on(event, fn)
}
/**
* @Author: 王林
* @Date: 2021-04-24 13:51:35
* @Desc: 触发事件
*/
emit(event, ...args) {
this.event.emit(event, ...args)
}
/**
* @Author: 王林
* @Date: 2021-04-24 13:53:54
* @Desc: 解绑事件
*/
off(event, fn) {
this.event.off(event, fn)
}
/**
* @Author: 王林
* @Date: 2021-05-05 13:32:43
* @Desc: 设置主题
*/
initTheme() {
this.themeConfig = merge(this.opt.theme && theme[this.opt.theme] ? theme[this.opt.theme] : theme.default, this.opt.themeConfig)
Style.setBackgroundStyle(this.el, this.themeConfig)
}
/**
* @Author: 王林
* @Date: 2021-05-05 13:52:08
* @Desc: 设置主题
*/
setTheme(theme) {
this.opt.theme = theme
this.render()
}
/**
* @Author: 王林
* @Date: 2021-05-05 13:50:17
* @Desc: 设置主题配置
*/
setThemeConfig(config) {
this.opt.themeConfig = config
this.render()
}
/**
* @Author: 王林
* @Date: 2021-05-05 14:01:29
* @Desc: 获取某个主题配置值
*/
getThemeConfig(prop) {
return prop === undefined ? this.themeConfig : this.themeConfig[prop]
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-06 18:47:29
* @Desc: 渲染节点
*/
render() {
this.draw.clear()
this.initTheme()
this.renderer.render()
}
/**
* @Author: 王林
* @Date: 2021-05-04 13:01:00
* @Desc: 执行命令
*/
execCommand(...args) {
this.command.exec(...args)
}
}
export default MindMap

View File

@@ -0,0 +1,71 @@
import { copyRenderTree, simpleDeepClone } from './Utils';
/**
* @Author: 王林
* @Date: 2021-05-04 13:10:06
* @Desc: 命令类
*/
class Command {
/**
* @Author: 王林
* @Date: 2021-05-04 13:10:24
* @Desc: 构造函数
*/
constructor(opt = {}) {
this.opt = opt
this.mindMap = opt.mindMap
this.commands = {}
this.history = []
this.activeHistoryIndex = 0
}
/**
* @Author: 王林
* @Date: 2021-05-04 13:12:30
* @Desc: 执行命令
*/
exec(name, ...args) {
if (this.commands[name]) {
this.commands[name].forEach((fn) => {
fn(...args)
})
this.addHistory()
}
}
/**
* @Author: 王林
* @Date: 2021-05-04 13:13:01
* @Desc: 添加命令
*/
add(name, fn) {
if (this.commands[name]) {
this.commands[name].push(fn)
} else[
this.commands[name] = [fn]
]
}
/**
* @Author: 王林
* @Date: 2021-05-04 14:35:43
* @Desc: 添加回退数据
*/
addHistory() {
let data = this.getCopyData()
this.history.push(simpleDeepClone(data))
this.activeHistoryIndex++
this.mindMap.emit('data_change', data)
}
/**
* @Author: 王林
* @Date: 2021-05-04 15:02:58
* @Desc: 获取渲染树数据副本
*/
getCopyData() {
return copyRenderTree({}, this.mindMap.renderer.renderTree)
}
}
export default Command

View File

@@ -0,0 +1,149 @@
import EventEmitter from 'eventemitter3'
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 14:53:09
* @Desc: 事件类
*/
class Event extends EventEmitter {
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 14:53:25
* @Desc: 构造函数
*/
constructor(opt = {}) {
super()
this.opt = opt
this.mindMap = opt.mindMap
this.isMousedown = false
this.mousedownPos = {
x: 0,
y: 0
}
this.mousemovePos = {
x: 0,
y: 0
}
this.mousemoveOffset = {
x: 0,
y: 0
}
this.bindFn()
this.bind()
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 15:52:24
* @Desc: 绑定函数上下文
*/
bindFn() {
this.onDrawClick = this.onDrawClick.bind(this)
this.onMousedown = this.onMousedown.bind(this)
this.onMousemove = this.onMousemove.bind(this)
this.onMouseup = this.onMouseup.bind(this)
this.onMousewheel = this.onMousewheel.bind(this)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 14:53:43
* @Desc: 绑定事件
*/
bind() {
this.mindMap.draw.on('click', this.onDrawClick)
this.mindMap.el.addEventListener('mousedown', this.onMousedown)
window.addEventListener('mousemove', this.onMousemove)
window.addEventListener('mouseup', this.onMouseup)
this.mindMap.el.addEventListener('mousewheel', this.onMousewheel)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 15:40:51
* @Desc: 解绑事件
*/
unbind() {
this.mindMap.el.removeEventListener('mousedown', this.onMousedown)
window.removeEventListener('mousemove', this.onMousemove)
window.removeEventListener('mouseup', this.onMouseup)
this.mindMap.el.removeEventListener('mousewheel', this.onMousewheel)
}
/**
* @Author: 王林
* @Date: 2021-04-24 13:19:39
* @Desc: 画布的单击事件
*/
onDrawClick(e) {
this.emit('draw_click', e)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 15:17:35
* @Desc: 鼠标按下事件
*/
onMousedown(e) {
e.preventDefault()
this.isMousedown = true
this.mousedownPos.x = e.clientX
this.mousedownPos.y = e.clientY
this.emit('mousedown', e, this)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 15:18:32
* @Desc: 鼠标移动事件
*/
onMousemove(e) {
e.preventDefault()
this.mousemovePos.x = e.clientX
this.mousemovePos.y = e.clientY
this.mousemoveOffset.x = e.clientX - this.mousedownPos.x
this.mousemoveOffset.y = e.clientY - this.mousedownPos.y
this.emit('mousemove', e, this)
if (this.isMousedown) {
this.emit('drag', e, this)
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 15:18:57
* @Desc: 鼠标松开事件
*/
onMouseup(e) {
this.isMousedown = false
this.emit('mouseup', e, this)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 15:46:27
* @Desc: 鼠标滚动
*/
onMousewheel(e) {
e.stopPropagation()
e.preventDefault()
let dir
if (e.wheelDeltaY > 0) {
dir = 'up'
} else {
dir = 'down'
}
this.emit('mousewheel', e, dir, this)
}
}
export default Event

View File

@@ -0,0 +1,116 @@
import { keyMap } from './utils/keyMap';
/**
* @Author: 王林
* @Date: 2021-04-24 15:20:46
* @Desc: 快捷按键、命令处理类
*/
export default class KeyCommand {
/**
* @Author: 王林
* @Date: 2021-04-24 15:21:32
* @Desc: 构造函数
*/
constructor(opt) {
this.opt = opt
this.mindMap = opt.mindMap
this.shortcutMap = {
//Enter: [fn]
}
this.bindEvent()
}
/**
* @Author: 王林
* @Date: 2021-04-24 15:23:22
* @Desc: 绑定事件
*/
bindEvent() {
window.addEventListener('keydown', (e) => {
Object.keys(this.shortcutMap).forEach((key) => {
if (this.checkKey(e, key)) {
e.stopPropagation()
e.preventDefault()
this.shortcutMap[key].forEach((fn) => {
fn()
})
}
})
})
}
/**
* @Author: 王林
* @Date: 2021-04-24 19:24:53
* @Desc: 检查键值是否符合
*/
checkKey(e, key) {
let o = this.getOriginEventCodeArr(e)
let k = this.getKeyCodeArr(key)
if (o.length !== k.length) {
return false
}
for (let i = 0; i < o.length; i++) {
let index = k.findIndex((item) => {
return item === o[i];
})
if (index === -1) {
return false
} else {
k.splice(index, 1)
}
}
return true
}
/**
* @Author: 王林
* @Date: 2021-04-24 19:15:19
* @Desc: 获取事件对象里的键值数组
*/
getOriginEventCodeArr(e) {
let arr = []
if (e.ctrlKey || e.metaKey) {
arr.push(keyMap['Control'])
}
if (e.altKey) {
arr.push(keyMap['Alt'])
}
if (e.shiftKey) {
arr.push(keyMap['Shift'])
}
arr.push(e.keyCode)
return arr
}
/**
* @Author: 王林
* @Date: 2021-04-24 19:40:11
* @Desc: 获取快捷键对应的键值数组
*/
getKeyCodeArr(key) {
let keyArr = key.split(/\s*\+\s*/)
let arr = []
keyArr.forEach((item) => {
arr.push(keyMap[item])
})
return arr
}
/**
* @Author: 王林
* @Date: 2021-04-24 15:23:00
* @Desc: 添加快捷键命令
* Enter
* Tab | Insert
* Shift + a
*/
addShortcut(key, fn) {
key.split(/\s*\|\s*/).forEach((item) => {
if (this.shortcutMap[item]) {
this.shortcutMap[item].push(fn)
} else {
this.shortcutMap[item] = [fn]
}
})
}
}

View File

@@ -0,0 +1,397 @@
import Style from './Style';
import {
resizeImgSize
} from './Utils'
import {
Image,
Text,
SVG,
Circle,
Element
} from '@svgdotjs/svg.js'
import btnsSvg from './svg/btns';
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-06 11:26:00
* @Desc: 节点类
*/
class Node {
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-06 11:26:17
* @Desc: 构造函数
*/
constructor(opt = {}) {
// 原始数据
this.originData = opt.originData
// 原始数据里的数据部分
this.data = opt.data
// id
this.uid = opt.uid
// 控制实例
this.mindMap = opt.mindMap
// 渲染实例
this.renderer = opt.renderer
// 主题配置
this.themeConfig = this.mindMap.themeConfig
// 样式实例
this.style = new Style(this, this.themeConfig)
// 渲染器
this.draw = opt.draw || null
// 是否是根节点
this.isRoot = opt.isRoot === undefined ? false : opt.isRoot
// 是否激活
this.isActive = opt.isActive === undefined ? false : opt.isActive
// 是否展开
this.expand = opt.expand === undefined ? true : opt.expand
// 节点层级
this.layerIndex = opt.layerIndex === undefined ? 0 : opt.layerIndex
// 节点宽
this.width = opt.width || 0
// 节点高
this.height = opt.height || 0
// left
this.left = opt.left || 0
// top
this.top = opt.top || 0
// 父节点
this.parent = opt.parent || null
// 子节点
this.children = opt.children || []
// 全部子节点所占的高度之和
this.childrenAreaHeight = opt.childrenAreaHeight || 0
// 文本节点
this.textNode = null
// 其他数据
Object.keys(opt.data).forEach((key) => {
this[key] = opt.data[key]
})
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-06 15:55:04
* @Desc: 添加子节点
*/
addChildren(node) {
this.children.push(node)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-09 09:46:23
* @Desc: 刷新节点的宽高
*/
refreshSize() {
let {
width,
height
} = this.getNodeRect()
this.width = width
this.height = height
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-06 14:52:17
* @Desc: 计算节点尺寸信息
*/
getNodeRect() {
let width = this.themeConfig.paddingX * 2
let height = this.themeConfig.paddingY * 2
let maxWidth = 0
if (this.img) {
let img = this.createImgNode()
if (img.width > maxWidth) {
maxWidth = img.width
}
height += img.height
}
if (this.icon && this.text) {
let icon = this.createIconNode()
let text = this.createTextNode()
if (icon.width + text.width > maxWidth) {
maxWidth = icon.width + text.width
}
height += Math.max(text.height, icon.height)
} else if (this.text) {
let text = this.createTextNode()
if (text.width > maxWidth) {
maxWidth = text.width
}
height += text.height
} else if (this.icon) {
let icon = this.createIconNode()
if (icon.width > maxWidth) {
maxWidth = icon.width
}
height += icon.height
}
return {
width: width + maxWidth,
height
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-09 14:06:17
* @Desc: 创建图片节点
*/
createImgNode() {
if (!this.img) {
return
}
let imgSize = this.getImgShowSize()
return {
node: new Image().load(this.img).size(...imgSize),
width: imgSize[0],
height: imgSize[1]
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-09 14:08:56
* @Desc: 创建文本节点
*/
createTextNode() {
if (!this.text) {
return
}
let node = this.draw.text(this.text)
this.style.text(node)
let {
width,
height
} = node.bbox()
let cloneNode = node.clone()
node.remove()
return {
node: cloneNode,
width,
height
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-09 14:10:48
* @Desc: 创建icon节点
*/
createIconNode() {
if (!this.icon) {
return
}
let node = SVG('<svg t="1617947697619" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="999" width="200" height="200"><path d="M512 899.5c-213.668 0-387.5-173.832-387.5-387.5S298.332 124.5 512 124.5 899.5 298.332 899.5 512 725.668 899.5 512 899.5z" fill="#4472C4" p-id="1000"></path><path d="M512 137c-206.776 0-375 168.224-375 375s168.224 375 375 375 375-168.224 375-375-168.224-375-375-375m0-25c220.914 0 400 179.086 400 400S732.914 912 512 912 112 732.914 112 512s179.086-400 400-400z" fill="#4472C4" p-id="1001"></path><path d="M597.681 335.009c0-7.67-2.36-13.569-7.08-17.109a35.115 35.115 0 0 0-20.061-5.9c-3.54 0-6.49 1.77-10.029 4.13-3.54 2.95-6.49 5.31-8.26 7.08a75.758 75.758 0 0 0-11.211 13.569c-3.54 4.72-7.67 9.44-11.209 13.569-11.209 12.979-23.009 27.139-35.988 41.3-13.569 14.749-26.549 27.729-38.938 39.528-1.18 1.18-2.95 2.36-4.13 3.54l-4.72 2.36c-1.77 1.18-3.54 1.77-4.72 2.95l-5.31 3.54c-2.95 2.36-5.31 4.13-7.08 5.9-2.36 2.36-2.95 4.13-2.95 5.9 0 7.08 2.95 12.389 10.03 16.519 5.9 4.72 12.979 6.49 20.059 6.49a31.985 31.985 0 0 0 14.756-3.543c4.13-2.36 8.26-5.9 12.979-10.619 2.95-3.54 6.49-7.67 11.209-12.979l11.8-12.979c2.95-2.95 7.67-7.67 13.569-14.159s12.389-14.159 20.649-23.009c-1.77 9.44-3.54 20.649-4.72 33.628-2.36 12.979-4.13 25.959-5.9 40.118l-4.72 41.888c-1.18 14.159-2.36 27.729-2.95 39.528-1.18 22.419-2.36 44.838-2.95 67.257q-1.77 33.628-1.77 58.407c0 9.44 2.36 16.519 7.67 21.829 5.31 5.9 12.389 8.26 21.829 8.26a43.479 43.479 0 0 0 15.929-3.54c4.72-2.36 7.67-5.31 7.67-8.85 0-1.77-0.59-5.31-0.59-11.209a149.392 149.392 0 0 1-2.36-18.879 116.91 116.91 0 0 1-2.36-21.239 132.008 132.008 0 0 1-1.18-20.649c0-41.3 1.18-82.6 4.72-124.484 3.54-41.3 10.03-82.6 20.649-123.3a106.366 106.366 0 0 1 2.95-11.209l2.36-11.209 1.77-11.209c-0.002-3.547 0.588-7.086 0.588-11.216z" fill="#FFFFFF" p-id="1002"></path></svg>').size(this.themeConfig.iconSize, this.themeConfig.iconSize)
return {
node,
width: this.themeConfig.iconSize,
height: this.themeConfig.iconSize
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-09 11:10:11
* @Desc: 创建内容节点
*/
createNode() {
let {
left,
top,
width,
height
} = this
let paddingY = this.themeConfig.paddingY
// 创建组
let group = this.draw.group()
// 节点矩形
let _rectNode = group.rect(width, height).x(left).y(top)
this.style.rect(_rectNode)
// 内容节点
let imgNode = this.createImgNode()
let iconNode = this.createIconNode()
let textNode = this.createTextNode()
let imgHeight = imgNode ? imgNode.height : 0
// 图片
if (imgNode) {
group.add(imgNode.node)
imgNode.node.cx(left + width / 2).y(top + paddingY)
}
// icon
if (iconNode) {
group.add(iconNode.node)
iconNode.node.x(left + width / 2).y(top + paddingY + imgHeight + (textNode && textNode.height > iconNode.height ? (textNode.height - iconNode.height) / 2 : 0)).dx(textNode ? -textNode.width / 2 - iconNode.width / 2 : 0)
}
// 文字
if (textNode) {
this.textNode = textNode
group.add(textNode.node)
textNode.node.cx(left + width / 2).y(top + paddingY + imgHeight).dx(iconNode ? iconNode.width / 2 : 0)
}
// 单击事件
group.click((e) => {
e.stopPropagation()
if (this.isActive) {
return;
}
this.mindMap.emit('before_node_active', this, this.renderer.activeNodeList)
this.renderer.clearActive()
this.isActive = true
this.mindMap.execCommand('UPDATE_NODE_DATA', this, {
isActive: this.isActive
})
this.renderer.activeNodeList.push(this)
this.mindMap.render()
this.mindMap.emit('node_active', this, this.renderer.activeNodeList)
})
// 双击事件
group.dblclick(() => {
this.showTextEditBox()
})
return group
}
/**
* @Author: 王林
* @Date: 2021-04-13 22:15:56
* @Desc: 显示文本编辑框
*/
showTextEditBox() {
if (!this.text) {
return;
}
this.renderer.showEditTextBox(this, this.textNode.node.node.getBoundingClientRect())
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 13:55:58
* @Desc: 渲染
*/
render() {
// 连线
this.drawLine()
// 按钮
this.drawBtn()
// 节点
this.draw.add(this.createNode())
// 子节点
if (this.children && this.children.length && this.expand) {
this.children.forEach((child) => {
child.render()
})
}
}
/**
* @Author: 王林
* @Date: 2021-04-10 22:01:53
* @Desc: 连线
*/
drawLine() {
if (!this.expand) {
return;
}
let lines = this.renderer.layout.drawLine(this)
lines.forEach((line) => {
this.style.line(line)
})
}
/**
* @Author: 王林
* @Date: 2021-04-11 19:47:01
* @Desc: 展开收缩按钮
*/
drawBtn() {
if (this.children.length <= 0 || this.isRoot) {
return;
}
let g = this.draw.group()
let iconSvg
if (this.expand) {
iconSvg = btnsSvg.close
} else {
iconSvg = btnsSvg.open
}
let node = SVG(iconSvg).size(20, 20)
let fillNode = new Circle().size(20)
this.renderer.layout.drawIcon(this, [node, fillNode])
node.dx(0).dy(-10)
fillNode.dx(0).dy(-10)
this.style.iconBtn(node, fillNode)
g.mouseover(() => {
g.css({ cursor: 'pointer' })
})
g.mouseout(() => {
g.css({ cursor: 'auto' })
})
g.click(() => {
this.expand = !this.expand
// 需要反映到实际数据上
this.mindMap.execCommand('UPDATE_NODE_DATA', this, {
expand: this.expand
})
this.mindMap.render()
this.mindMap.emit('expand_btn_click', this)
})
g.add(fillNode)
g.add(node)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-09 10:12:51
* @Desc: 获取图片显示宽高
*/
getImgShowSize() {
return resizeImgSize(this.imgWidth, this.imgHeight, this.themeConfig.imgMaxWidth, this.themeConfig.imgMaxHeight)
}
/**
* @Author: 王林
* @Date: 2021-05-04 21:48:49
* @Desc: 获取某个样式
*/
getStyle(prop, root, isActive) {
let v = this.style.merge(prop, root, isActive)
return v === undefined ? '' : v
}
/**
* @Author: 王林
* @Date: 2021-05-04 22:18:07
* @Desc: 修改某个样式
*/
setStyle(prop, value, isActive) {
if (isActive) {
this.mindMap.execCommand('UPDATE_NODE_DATA', this, {
activeStyle: {
...(this.data.activeStyle || {}),
[prop]: value
}
})
} else {
this.mindMap.execCommand('UPDATE_NODE_DATA', this, {
[prop]: value
})
}
this.mindMap.render()
}
}
export default Node

View File

@@ -0,0 +1,256 @@
import merge from 'deepmerge'
import LogicalStructure from './layouts/LogicalStructure'
import { getStrWithBrFromHtml } from './Utils'
// 布局列表
const layouts = {
logicalStructure: LogicalStructure
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 16:25:07
* @Desc: 渲染
*/
class Render {
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 16:25:32
* @Desc: 构造函数
*/
constructor(opt = {}) {
this.opt = opt
this.mindMap = opt.mindMap
this.themeConfig = this.mindMap.themeConfig
this.draw = this.mindMap.draw
// 渲染树,操作过程中修改的都是这里的数据
this.renderTree = merge({}, this.mindMap.opt.data || {})
// 当前激活的节点列表
this.activeNodeList = []
// 根节点
this.root = null
// 文本编辑框
this.textEditNode = null
// 文本编辑框是否显示
this.showTextEdit = false
// 布局
this.layout = new (layouts[this.mindMap.opt.layout] ? layouts[this.mindMap.opt.layout] : layouts.logicalStructure)({
mindMap: this.mindMap,
renderer: this,
renderTree: this.renderTree,
themeConfig: this.mindMap.themeConfig,
draw: this.mindMap.draw
})
// 绑定事件
this.bindEvent()
// 注册命令
this.registerCommands()
}
/**
* @Author: 王林
* @Date: 2021-04-24 13:27:04
* @Desc: 事件
*/
bindEvent() {
this.mindMap.on('draw_click', () => {
// 隐藏文本编辑框
this.hideEditTextBox()
// 清除激活状态
if (this.activeNodeList.length > 0) {
this.clearActive()
this.mindMap.render()
this.mindMap.emit('node_active', null, [])
}
})
this.mindMap.on('expand_btn_click', () => {
this.hideEditTextBox()
})
this.mindMap.on('before_node_active', () => {
this.hideEditTextBox()
})
this.mindMap.keyCommand.addShortcut('Enter', () => {
this.hideEditTextBox()
})
}
/**
* @Author: 王林
* @Date: 2021-05-04 13:19:06
* @Desc: 注册命令
*/
registerCommands() {
this.insertNode = this.insertNode.bind(this)
this.mindMap.command.add('INSERT_NODE', this.insertNode)
this.insertChildNode = this.insertChildNode.bind(this)
this.mindMap.command.add('INSERT_CHILD_NODE', this.insertChildNode)
this.removeNode = this.removeNode.bind(this)
this.mindMap.command.add('REMOVE_NODE', this.removeNode)
this.updateNodeData = this.updateNodeData.bind(this)
this.mindMap.command.add('UPDATE_NODE_DATA', this.updateNodeData)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 16:27:55
* @Desc: 渲染
*/
render() {
this.root = this.layout.doLayout()
this.root.render()
}
/**
* @Author: 王林
* @Date: 2021-04-12 22:45:01
* @Desc: 清楚当前激活的节点
*/
clearActive() {
this.activeNodeList.forEach((item) => {
this.mindMap.execCommand('UPDATE_NODE_DATA', item, {
isActive: false
})
})
this.activeNodeList = []
}
/**
* @Author: 王林
* @Date: 2021-05-04 13:46:08
* @Desc: 获取节点在同级里的索引位置
*/
getNodeIndex(node) {
return node.parent ? node.parent.children.findIndex((item) => {
return item === node
}) : 0
}
/**
* @Author: 王林
* @Date: 2021-05-04 13:19:54
* @Desc: 插入同级节点
*/
insertNode() {
if (this.activeNodeList.length <= 0) {
return;
}
let first = this.activeNodeList[0]
if (first.isRoot) {
this.insertChildNode()
} else {
let index = this.getNodeIndex(first)
first.parent.originData.children.splice(index + 1, 0, {
"data": {
"text": "分支主题",
"expand": true
},
"children": []
})
this.mindMap.render()
}
}
/**
* @Author: 王林
* @Date: 2021-05-04 13:31:02
* @Desc: 插入子节点
*/
insertChildNode() {
if (this.activeNodeList.length <= 0) {
return;
}
let first = this.activeNodeList[0]
first.originData.children.push({
"data": {
"text": "分支主题",
"expand": true
},
"children": []
})
this.mindMap.render()
}
/**
* @Author: 王林
* @Date: 2021-05-04 13:40:39
* @Desc: 移除节点
*/
removeNode() {
if (this.activeNodeList.length <= 0) {
return;
}
this.activeNodeList.forEach((item) => {
if (item.isRoot) {
item.children = []
item.originData.children = []
} else {
let index = this.getNodeIndex(item)
item.parent.children.splice(index, 1)
item.parent.originData.children.splice(index, 1)
}
})
this.clearActive()
this.mindMap.render()
}
/**
* @Author: 王林
* @Date: 2021-05-04 14:19:48
* @Desc: 更新节点数据
*/
updateNodeData(node, data) {
Object.keys(data).forEach((key) => {
node.data[key] = data[key]
})
}
/**
* @Author: 王林
* @Date: 2021-04-13 22:13:02
* @Desc: 显示文本编辑框
*/
showEditTextBox(node, rect) {
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;`
this.textEditNode.setAttribute('contenteditable', true)
document.body.appendChild(this.textEditNode)
}
node.style.domText(this.textEditNode)
this.textEditNode.innerHTML = node.data.text.split(/\n/img).join('<br>')
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'
this.textEditNode.style.display = 'block'
this.showTextEdit = true
}
/**
* @Author: 王林
* @Date: 2021-04-24 13:48:16
* @Desc: 隐藏文本编辑框
*/
hideEditTextBox() {
if (!this.showTextEdit) {
return
}
this.activeNodeList.forEach((node) => {
let str = getStrWithBrFromHtml(this.textEditNode.innerHTML)
node.data.text = str
this.mindMap.render()
})
this.mindMap.emit('hide_text_edit', this.textEditNode, this.activeNodeList)
this.textEditNode.style.display = 'none'
this.textEditNode.innerHTML = ''
this.textEditNode.style.fontFamily = 'inherit'
this.textEditNode.style.fontSize = 'inherit'
this.textEditNode.style.fontWeight = 'normal'
this.showTextEdit = false
}
}
export default Render

View File

@@ -0,0 +1,122 @@
/**
* @Author: 王林
* @Date: 2021-04-11 10:09:08
* @Desc: 样式类
*/
class Style {
/**
* @Author: 王林
* @Date: 2021-04-11 16:01:53
* @Desc: 设置背景样式
*/
static setBackgroundStyle(el, themeConfig) {
let { backgroundColor, backgroundImage, backgroundRepeat } = themeConfig
el.style.backgroundColor = backgroundColor
if (backgroundImage) {
el.style.backgroundImage = `url(${backgroundImage})`
el.style.backgroundRepeat = backgroundRepeat
}
}
/**
* @Author: 王林
* @Date: 2021-04-11 10:10:11
* @Desc: 构造函数
*/
constructor(ctx, themeConfig) {
this.ctx = ctx
this.themeConfig = themeConfig
}
/**
* @Author: 王林
* @Date: 2021-04-11 12:02:55
* @Desc: 合并样式
*/
merge(prop, root, isActive) {
// 三级及以下节点
let defaultConfig = this.themeConfig.node
if (root) {// 直接使用最外层样式
defaultConfig = this.themeConfig
} else if (this.ctx.layerIndex === 0) {// 根节点
defaultConfig = this.themeConfig.root
} else if (this.ctx.layerIndex === 1) {// 二级节点
defaultConfig = this.themeConfig.secondLevel
}
// 激活状态
if (isActive !== undefined ? isActive : this.ctx.isActive) {
if (this.ctx.activeStyle && this.ctx.activeStyle[prop] !== undefined) {
return this.ctx.activeStyle[prop];
} else if (defaultConfig.active && defaultConfig.active[prop]) {
return defaultConfig.active[prop]
}
}
// 优先使用节点本身的样式
return this.ctx[prop] !== undefined ? this.ctx[prop] : defaultConfig[prop]
}
/**
* @Author: 王林
* @Date: 2021-04-11 10:12:56
* @Desc: 矩形
*/
rect(node) {
node.fill({
color: this.merge('fillColor')
}).stroke({
color: this.merge('borderColor'),
width: this.merge('borderWidth'),
dasharray: this.merge('borderDasharray')
}).radius(this.merge('borderRadius'))
}
/**
* @Author: 王林
* @Date: 2021-04-11 12:07:59
* @Desc: 文字
*/
text(node) {
node.fill({
color: this.merge('color')
}).css({
'font-family': this.merge('fontFamily'),
'font-size': this.merge('fontSize'),
'font-weight': this.merge('fontWeight'),
'font-style': this.merge('fontStyle'),
'text-decoration': this.merge('textDecoration')
})
}
/**
* @Author: 王林
* @Date: 2021-04-13 08:14:34
* @Desc: html文字节点
*/
domText(node) {
node.style.fontFamily = this.merge('fontFamily')
node.style.fontSize = this.merge('fontSize') + 'px'
node.style.fontWeight = this.merge('fontWeight') || 'normal'
}
/**
* @Author: 王林
* @Date: 2021-04-11 14:50:49
* @Desc: 连线
*/
line(node) {
node.stroke({ width: this.merge('lineWidth', true), color: this.merge('lineColor', true) }).fill({ color: 'none' })
}
/**
* @Author: 王林
* @Date: 2021-04-11 20:03:59
* @Desc: 按钮
*/
iconBtn(node, fillNode) {
node.fill({ color: '#808080' })
fillNode.fill({ color: '#fff' })
}
}
export default Style

View File

@@ -0,0 +1,134 @@
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-06 14:13:17
* @Desc: 深度优先遍历树
*/
export const walk = (root, parent, beforeCallback, afterCallback, isRoot, layerIndex = 0) => {
beforeCallback && beforeCallback(root, parent, isRoot, layerIndex)
if (root.children && root.children.length > 0) {
let _layerIndex = layerIndex + 1
root.children.forEach((node) => {
walk(node, root, beforeCallback, afterCallback, false, _layerIndex)
})
}
afterCallback && afterCallback(root, parent, isRoot, layerIndex)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 18:47:20
* @Desc: 广度优先遍历树
*/
export const bfsWalk = (root, callback) => {
callback(root)
let stack = [root]
while (stack.length) {
let cur = stack.shift()
if (cur.children && cur.children.length) {
cur.children.forEach((item) => {
stack.push(item)
callback(item)
})
}
}
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-09 10:44:54
* @Desc: 缩放图片尺寸
*/
export const resizeImgSize = (width, height, maxWidth, maxHeight) => {
let nRatio = width / height
let arr = []
if (maxWidth && maxHeight) {
if (width <= maxWidth && height <= maxHeight) {
arr = [width, height]
} else {
let mRatio = maxWidth / maxHeight
if (nRatio > mRatio) { // 固定高度
arr = [nRatio * maxHeight, maxHeight]
} else { // 固定宽度
arr = [maxWidth, maxWidth / nRatio]
}
}
} else if (maxWidth) {
if (width <= maxWidth) {
arr = [width, height]
} else {
arr = [maxWidth, maxWidth / nRatio]
}
} else if (maxHeight) {
if (height <= maxHeight) {
arr = [width, height]
} else {
arr = [nRatio * maxHeight, maxHeight]
}
}
return arr
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-09 10:18:42
* @Desc: 缩放图片
*/
export const resizeImg = (imgUrl, maxWidth, maxHeight) => {
return new Promise((resolve, reject) => {
let img = new Image()
img.src = imgUrl
img.onload = () => {
let arr = resizeImgSize(img.naturalWidth, img.naturalHeight, maxWidth, maxHeight)
resolve(arr)
}
img.onerror = (e) => {
reject(e)
}
})
}
/**
* @Author: 王林
* @Date: 2021-05-04 12:26:56
* @Desc: 从头html结构字符串里获取带换行符的字符串
*/
export const getStrWithBrFromHtml = (str) => {
str = str.replace(/<br>/img, '\n')
let el = document.createElement('div')
el.innerHTML = str
str = el.textContent
return str;
}
/**
* @Author: 王林
* @Date: 2021-05-04 14:45:39
* @Desc: 极简的深拷贝
*/
export const simpleDeepClone = (data) => {
try {
return JSON.parse(JSON.stringify(data))
} catch (error) {
return null
}
}
/**
* @Author: 王林
* @Date: 2021-05-04 14:40:11
* @Desc: 复制渲染树数据
*/
export const copyRenderTree = (tree, root) => {
tree.data = simpleDeepClone(root.data)
tree.children = []
if (root.children.length > 0) {
root.children.forEach((item, index) => {
tree.children[index] = copyRenderTree({}, item)
})
}
return tree;
}

View File

@@ -0,0 +1,87 @@
import merge from 'deepmerge'
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 14:45:24
* @Desc: 视图操作类
*/
class View {
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 14:45:40
* @Desc: 构造函数
*/
constructor(opt = {}) {
this.opt = opt
this.mindMap = this.opt.mindMap
this.viewBox = {
x: 0,
y: 0,
width: this.mindMap.width,
height: this.mindMap.height
}
this.cacheViewBox = {
x: 0,
y: 0,
width: this.mindMap.width,
height: this.mindMap.height
}
this.scale = 1
this.bind()
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 15:38:51
* @Desc: 绑定
*/
bind() {
// 拖动视图
this.mindMap.event.on('mousedown', () => {
this.cacheViewBox = merge({}, this.viewBox)
})
this.mindMap.event.on('drag', (e, event) => {
// 视图放大缩小后拖动的距离也要相应变化
this.viewBox.x = this.cacheViewBox.x - event.mousemoveOffset.x * this.scale
this.viewBox.y = this.cacheViewBox.y - event.mousemoveOffset.y * this.scale
this.setViewBox()
})
// 放大缩小视图
this.mindMap.event.on('mousewheel', (e, dir) => {
let stepWidth = this.viewBox.width * this.mindMap.opt.scaleRatio
let stepHeight = this.viewBox.height * this.mindMap.opt.scaleRatio
// 放大
if (dir === 'down') {
this.scale += this.mindMap.opt.scaleRatio
this.viewBox.width += stepWidth
this.viewBox.height += stepHeight
} else { // 缩小
this.scale -= this.mindMap.opt.scaleRatio
this.viewBox.width -= stepWidth
this.viewBox.height -= stepHeight
}
this.setViewBox()
})
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 15:43:26
* @Desc: 设置视图
*/
setViewBox() {
let {
x,
y,
width,
height
} = this.viewBox
this.opt.draw.viewbox(x, y, width, height)
}
}
export default View

View File

@@ -0,0 +1,70 @@
/**
* @Author: 王林
* @Date: 2021-04-12 22:24:30
* @Desc: 布局基类
*/
class Base {
/**
* @Author: 王林
* @Date: 2021-04-12 22:25:16
* @Desc: 构造函数
*/
constructor(opt) {
// 控制实例
this.mindMap = opt.mindMap
// 渲染实例
this.renderer = opt.renderer
// 渲染树
this.renderTree = opt.renderTree
// 主题配置
this.themeConfig = opt.themeConfig
// 绘图对象
this.draw = opt.draw
// 根节点
this.root = null
}
/**
* @Author: 王林
* @Date: 2021-04-12 22:39:50
* @Desc: 计算节点位置
*/
doLayout() {
throw new Error('【computed】方法为必要方法需要子类进行重写')
}
/**
* @Author: 王林
* @Date: 2021-04-12 22:41:04
* @Desc: 连线
*/
drawLine() {
throw new Error('【drawLine】方法为必要方法需要子类进行重写')
}
/**
* @Author: 王林
* @Date: 2021-04-12 22:42:08
* @Desc: 定位显示展开收缩按钮
*/
drawIcon() {
throw new Error('【drawIcon】方法为必要方法需要子类进行重写')
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 11:25:52
* @Desc: 更新子节点属性
*/
updateChildren(children, prop, offset) {
children.forEach((item) => {
item[prop] += offset
if (item.children && item.children.length) {
this.updateChildren(item.children, prop, offset)
}
})
}
}
export default Base

View File

@@ -0,0 +1,240 @@
import Base from './Base';
import {
walk
} from '../Utils'
import Node from '../Node'
/**
* @Author: 王林
* @Date: 2021-04-12 22:25:58
* @Desc: 逻辑结构图
*/
class LogicalStructure extends Base {
/**
* @Author: 王林
* @Date: 2021-04-12 22:26:31
* @Desc: 构造函数
*/
constructor(opt = {}) {
super(opt)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-06 14:04:20
* @Desc: 布局
*/
doLayout() {
// 计算节点的left、width、height
this.computedBaseValue()
// 计算节点的top
this.computedTopValue()
// 调整节点top
this.adjustTopValue()
return this.root;
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 09:49:32
* @Desc: 计算节点的left、width、height
*/
computedBaseValue() {
walk(this.renderTree, null, (node, parent, isRoot, layerIndex) => {
// 遍历子节点前设置left、width、height
if (!node.data) {
node.data = {}
}
// 创建节点
let newNode = new Node({
uid: this.mindMap.uid++,
originData: node,
data: node.data,
renderer: this.renderer,
mindMap: this.mindMap,
draw: this.draw,
layerIndex
})
// 计算节点的宽高
newNode.refreshSize()
if (isRoot) {
newNode.isRoot = true
newNode.left = (this.mindMap.width - newNode.width) / 2
newNode.top = (this.mindMap.height - newNode.height) / 2
this.root = newNode
} else {
let marginX = layerIndex === 1 ? this.themeConfig.secondLevel.marginX : this.themeConfig.node.marginX
newNode.left = parent._node.left + parent._node.width + marginX,
newNode.parent = parent._node
parent._node.addChildren(newNode)
}
node._node = newNode
}, (node, parent, isRoot, layerIndex) => {
// 返回时计算节点的areaHeight也就是子节点所占的高度之和包括外边距
let len = node.expand === false ? 0 : node._node.children.length
node._node.childrenAreaHeight = len ? node._node.children.reduce((h, cur) => {
return h + cur.height
}, 0) + (len + 1) * this.getMarginY(layerIndex) : 0
}, true, 0)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 09:59:25
* @Desc: 计算节点的top
*/
computedTopValue() {
walk(this.root, null, (node, parent, isRoot, layerIndex) => {
if (node.children && node.children.length) {
let marginY = this.getMarginY(layerIndex)
// 第一个子节点的top值 = 该节点中心的top值 - 子节点的高度之和的一半
let top = node.top + node.height / 2 - node.childrenAreaHeight / 2
let totalTop = top + marginY
node.children.forEach((cur) => {
cur.top = totalTop
totalTop += cur.height + marginY
})
}
}, null, true)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-08 10:04:05
* @Desc: 调整节点top
*/
adjustTopValue() {
walk(this.root, null, (node, parent, isRoot, layerIndex) => {
// 判断子节点所占的高度之和是否大于该节点自身,大于则需要调整位置
let difference = node.childrenAreaHeight - this.getMarginY(layerIndex) - node.height
if (difference > 0) {
this.updateBrothers(node, difference / 2)
}
}, null, true)
}
/**
* javascript comment
* @Author: 王林25
* @Date: 2021-04-07 14:26:03
* @Desc: 更新兄弟节点的top
*/
updateBrothers(node, addHeight) {
if (node.parent) {
let childrenList = node.parent.children
let index = childrenList.findIndex((item) => {
return item === node
})
childrenList.forEach((item, _index) => {
let _offset = 0
// 上面的节点往上移
if (_index < index) {
_offset = -addHeight
} else if (_index > index) {// 下面的节点往下移
_offset = addHeight
}
item.top += _offset
// 同步更新子节点的位置
if (item.children && item.children.length) {
this.updateChildren(item.children, 'top', _offset)
}
})
// 更新父节点的位置
this.updateBrothers(node.parent, addHeight)
}
}
/**
* @Author: 王林
* @Date: 2021-04-11 15:34:20
* @Desc: 获取节点的marginY
*/
getMarginY(layerIndex) {
return layerIndex === 1 ? this.themeConfig.secondLevel.marginY : this.themeConfig.node.marginY;
}
/**
* @Author: 王林
* @Date: 2021-04-11 15:05:01
* @Desc: 二次贝塞尔曲线
*/
quadraticCurvePath(x1, y1, x2, y2) {
let cx = x1 + (x2 - x1) * 0.2
let cy = y1 + (y2 - y1) * 0.8
return `M ${x1},${y1} Q ${cx},${cy} ${x2},${y2}`
}
/**
* @Author: 王林
* @Date: 2021-04-11 15:05:18
* @Desc: 三次贝塞尔曲线
*/
cubicBezierPath(x1, y1, x2, y2) {
let cx1 = x1 + (x2 - x1) / 2
let cy1 = y1
let cx2 = x2 - (x2 - x1) / 2
let cy2 = y2
return `M ${x1},${y1} C ${cx1},${cy1} ${cx2},${cy2} ${x2},${y2}`
}
/**
* @Author: 王林
* @Date: 2021-04-11 14:42:48
* @Desc: 绘制连线,连接该节点到其子节点
*/
drawLine(node) {
if (node.children.length <= 0) {
return [];
}
let {
left,
top,
width,
height
} = node
let lines = []
if (!node.isRoot) {
let line = this.draw.line(left + width, top + height / 2, left + width + 20, top + height / 2)
lines.push(line)
}
node.children.forEach((item) => {
let x1 = node.layerIndex === 0 ? left + width / 2 : left + width + 20
let y1 = node.layerIndex === 0 ? top + height / 2 : top + height / 2
let x2 = item.left
let y2 = item.top + item.height / 2
let path = ''
if (node.isRoot) {
path = this.quadraticCurvePath(x1, y1, x2, y2)
} else {
path = this.cubicBezierPath(x1, y1, x2, y2)
}
let line = this.draw.path(path)
lines.push(line)
})
return lines;
}
/**
* @Author: 王林
* @Date: 2021-04-11 19:54:26
* @Desc: 渲染按钮
*/
drawIcon(node, icons) {
let {
left,
top,
width,
height
} = node
icons.forEach((icon) => {
icon.x(left + width).y(top + height / 2)
})
}
}
export default LogicalStructure

View File

@@ -0,0 +1,18 @@
/**
* @Author: 王林
* @Date: 2021-04-11 19:46:10
* @Desc: 展开按钮
*/
const open = `<svg t="1618141562310" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13476" width="200" height="200"><path d="M475.136 327.168v147.968h-147.968v74.24h147.968v147.968h74.24v-147.968h147.968v-74.24h-147.968v-147.968h-74.24z m36.864-222.208c225.28 0 407.04 181.76 407.04 407.04s-181.76 407.04-407.04 407.04-407.04-181.76-407.04-407.04 181.76-407.04 407.04-407.04z m0-74.24c-265.216 0-480.768 215.552-480.768 480.768s215.552 480.768 480.768 480.768 480.768-215.552 480.768-480.768-215.552-480.768-480.768-480.768z" p-id="13477"></path></svg>`
/**
* @Author: 王林
* @Date: 2021-04-11 19:46:23
* @Desc: 收缩按钮
*/
const close = `<svg t="1618141589243" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13611" width="200" height="200"><path d="M512 105.472c225.28 0 407.04 181.76 407.04 407.04s-181.76 407.04-407.04 407.04-407.04-181.76-407.04-407.04 181.76-407.04 407.04-407.04z m0-74.24c-265.216 0-480.768 215.552-480.768 480.768s215.552 480.768 480.768 480.768 480.768-215.552 480.768-480.768-215.552-480.768-480.768-480.768z" p-id="13612"></path><path d="M252.928 474.624h518.144v74.24h-518.144z" p-id="13613"></path></svg>`
export default {
open,
close
}

View File

@@ -0,0 +1,40 @@
import defaultTheme from './default';
import merge from 'deepmerge';
/**
* @Author: 王林
* @Date: 2021-04-11 15:22:18
* @Desc: 天空蓝
*/
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(115, 161, 191)',
// 背景颜色
backgroundColor: 'rgb(251, 251, 251)',
// 根节点样式
root: {
fillColor: 'rgb(115, 161, 191)',
active: {
borderColor: 'rgb(57, 80, 96)'
}
},
// 二级节点样式
secondLevel: {
fillColor: 'rgb(238, 243, 246)',
color: '#333',
borderColor: 'rgb(115, 161, 191)',
borderWidth: 1,
fontSize: 14,
active: {
borderColor: 'rgb(57, 80, 96)'
}
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#333',
active: {
borderColor: 'rgb(57, 80, 96)'
}
},
})

View File

@@ -0,0 +1,40 @@
import defaultTheme from './default';
import merge from 'deepmerge';
/**
* @Author: 王林
* @Date: 2021-04-11 15:22:18
* @Desc: 脑残粉
*/
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(191, 115, 148)',
// 背景颜色
backgroundColor: 'rgb(251, 251, 251)',
// 根节点样式
root: {
fillColor: 'rgb(191, 115, 148)',
active: {
borderColor: 'rgb(96, 57, 74)'
}
},
// 二级节点样式
secondLevel: {
fillColor: 'rgb(246, 238, 242)',
color: '#333',
borderColor: 'rgb(191, 115, 148)',
borderWidth: 1,
fontSize: 14,
active: {
borderColor: 'rgb(96, 57, 74)'
}
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#333',
active: {
borderColor: 'rgb(96, 57, 74)'
}
}
})

View File

@@ -0,0 +1,53 @@
import defaultTheme from './default';
import merge from 'deepmerge';
/**
* @Author: 王林
* @Date: 2021-04-11 15:22:18
* @Desc: 脑图经典
*/
export default merge(defaultTheme, {
// 连线的颜色
lineColor: '#fff',
// 连线的粗细
lineWidth: 3,
// 背景颜色
backgroundColor: 'rgb(58, 65, 68)',
// 背景图片
backgroundImage: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDowQzg5QTQ0NDhENzgxMUUzOENGREE4QTg0RDgzRTZDNyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDowQzg5QTQ0NThENzgxMUUzOENGREE4QTg0RDgzRTZDNyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkMwOEQ1NDRGOEQ3NzExRTM4Q0ZEQThBODREODNFNkM3IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkMwOEQ1NDUwOEQ3NzExRTM4Q0ZEQThBODREODNFNkM3Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+e9P33AAAACVJREFUeNpisXJ0YUACTAyoAMr/+eM7EGGRZ4FQ7BycEAZAgAEAHbEGtkoQm/wAAAAASUVORK5CYII=',
// 背景重复
backgroundRepeat: 'repeat',
// 根节点样式
root: {
fillColor: 'rgb(233, 223, 152)',
color: '#333',
fontSize: 24,
borderRadius: 21,
active: {
fillColor: 'rgb(254, 219, 0)',
borderColor: 'transparent'
}
},
// 二级节点样式
secondLevel: {
fillColor: 'rgb(164, 197, 192)',
borderColor: 'transparent',
color: '#333',
fontSize: 16,
borderRadius: 10,
active: {
fillColor: 'rgb(254, 219, 0)',
borderColor: 'transparent'
}
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#fff',
fontWeight: 'bold',
active: {
fillColor: 'rgb(254, 219, 0)',
borderColor: 'transparent'
}
}
})

View File

@@ -0,0 +1,45 @@
import defaultTheme from './default';
import merge from 'deepmerge';
/**
* @Author: 王林
* @Date: 2021-04-11 15:22:18
* @Desc: 经典2
*/
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(51, 51, 51)',
// 连线的粗细
lineWidth: 2,
// 背景颜色
backgroundColor: '#fff',
// 根节点样式
root: {
fillColor: 'rgb(18, 187, 55)',
color: '#fff',
fontSize: 24,
borderRadius: 10,
active: {
borderColor: 'rgb(51, 51, 51)'
}
},
// 二级节点样式
secondLevel: {
fillColor: 'rgb(241, 242, 241)',
borderColor: 'transparent',
color: '#1a1a1a',
fontSize: 18,
borderRadius: 10,
active: {
borderColor: 'rgb(51, 51, 51)'
}
},
// 三级及以下节点样式
node: {
fontSize: 14,
color: '#1a1a1a',
active: {
borderColor: 'rgb(51, 51, 51)'
}
}
})

View File

@@ -0,0 +1,48 @@
import defaultTheme from './default';
import merge from 'deepmerge';
/**
* @Author: 王林
* @Date: 2021-04-11 15:22:18
* @Desc: 经典3
*/
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(94, 202, 110)',
// 连线的粗细
lineWidth: 2,
// 背景颜色
backgroundColor: 'rgb(241, 241, 241)',
// 根节点样式
root: {
fillColor: 'rgb(255, 245, 214)',
color: '#1a1a1a',
fontSize: 24,
borderRadius: 10,
borderColor: 'rgb(249, 199, 84)',
borderWidth: 1,
active: {
borderColor: 'rgb(94, 202, 110)'
}
},
// 二级节点样式
secondLevel: {
fillColor: 'rgb(255, 245, 214)',
borderColor: 'rgb(249, 199, 84)',
borderWidth: 1,
color: '#1a1a1a',
fontSize: 18,
borderRadius: 10,
active: {
borderColor: 'rgb(94, 202, 110)'
}
},
// 三级及以下节点样式
node: {
fontSize: 14,
color: '#1a1a1a',
active: {
borderColor: 'rgb(94, 202, 110)'
}
}
})

View File

@@ -0,0 +1,45 @@
import defaultTheme from './default';
import merge from 'deepmerge';
/**
* @Author: 王林
* @Date: 2021-04-11 15:22:18
* @Desc: 暗色
*/
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(17, 68, 23)',
// 连线的粗细
lineWidth: 2,
// 背景颜色
backgroundColor: 'rgb(15, 16, 17)',
// 根节点样式
root: {
fillColor: 'rgb(28, 178, 43)',
color: '#fff',
fontSize: 24,
borderRadius: 10,
active: {
borderColor: 'rgb(17, 68, 23)'
}
},
// 二级节点样式
secondLevel: {
fillColor: 'rgb(55, 56, 58)',
color: 'rgb(147,148,149)',
fontSize: 18,
borderRadius: 10,
borderWidth: 0,
active: {
borderColor: 'rgb(17, 68, 23)'
}
},
// 三级及以下节点样式
node: {
fontSize: 14,
color: 'rgb(147, 148, 149)',
active: {
borderColor: 'rgb(17, 68, 23)'
}
}
})

View File

@@ -0,0 +1,87 @@
/**
* @Author: 王林
* @Date: 2021-04-11 10:19:55
* @Desc: 默认主题
*/
export default {
// 节点内边距
paddingX: 20,
paddingY: 10,
// 图片显示的最大宽度
imgMaxWidth: 200,
// 图片显示的最大高度
imgMaxHeight: 200,
// icon的大小
iconSize: 20,
// 连线的粗细
lineWidth: 1,
// 连线的颜色
lineColor: '#549688',
// 背景颜色
backgroundColor: '#fafafa',
// 背景图片
backgroundImage: '',
// 背景重复
backgroundRepeat: 'none',
// 根节点样式
root: {
fillColor: '#549688',
fontFamily: '微软雅黑, Microsoft YaHei',
color: '#fff',
fontSize: 16,
fontWeight: 'bold',
fontStyle: 'normal',
borderColor: 'transparent',
borderWidth: 0,
borderDasharray: 'none',
borderRadius: 5,
textDecoration: 'none',
active: {
borderColor: 'rgb(57, 80, 96)',
borderWidth: 3,
borderDasharray: 'none',
}
},
// 二级节点样式
secondLevel: {
marginX: 100,
marginY: 40,
fillColor: '#fff',
fontFamily: '微软雅黑, Microsoft YaHei',
color: '#565656',
fontSize: 16,
fontWeight: 'noraml',
fontStyle: 'normal',
borderColor: '#549688',
borderWidth: 1,
borderDasharray: 'none',
borderRadius: 5,
textDecoration: 'none',
active: {
borderColor: 'rgb(57, 80, 96)',
borderWidth: 3,
borderDasharray: 'none',
}
},
// 三级及以下节点样式
node: {
marginX: 60,
marginY: 40,
fillColor: 'transparent',
fontFamily: '微软雅黑, Microsoft YaHei',
color: '#6a6d6c',
fontSize: 14,
fontWeight: 'noraml',
fontStyle: 'normal',
borderColor: 'transparent',
borderWidth: 0,
borderRadius: 5,
borderDasharray: 'none',
textDecoration: 'none',
active: {
borderColor: 'rgb(57, 80, 96)',
borderWidth: 3,
borderDasharray: 'none',
}
}
}

View File

@@ -0,0 +1,40 @@
import defaultTheme from './default';
import merge from 'deepmerge';
/**
* @Author: 王林
* @Date: 2021-04-11 15:22:18
* @Desc: 泥土黄
*/
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(191, 147, 115)',
// 背景颜色
backgroundColor: 'rgb(251, 251, 251)',
// 根节点样式
root: {
fillColor: 'rgb(191, 147, 115)',
active: {
borderColor: 'rgb(96, 73, 57)'
}
},
// 二级节点样式
secondLevel: {
fillColor: 'rgb(246, 242, 238)',
color: '#333',
borderColor: 'rgb(191, 147, 115)',
borderWidth: 1,
fontSize: 14,
active: {
borderColor: 'rgb(96, 73, 57)'
}
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#333',
active: {
borderColor: 'rgb(96, 73, 57)'
}
}
})

View File

@@ -0,0 +1,25 @@
import defaultTheme from './default';
import merge from 'deepmerge';
/**
* @Author: 王林
* @Date: 2021-04-11 15:22:18
* @Desc: 清新绿
*/
export default merge(defaultTheme, {
// 连线的颜色
lineColor: '#333',
// 背景颜色
backgroundColor: '#d1f6ec',
// 根节点样式
root: {
fillColor: '#1fb27d'
},
// 二级节点样式
secondLevel: {
fillColor: '#fff',
color: '#565656',
borderColor: 'transparent',
borderWidth: 0
},
})

View File

@@ -0,0 +1,40 @@
import defaultTheme from './default';
import merge from 'deepmerge';
/**
* @Author: 王林
* @Date: 2021-04-11 15:22:18
* @Desc: 清新红
*/
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(191, 115, 115)',
// 背景颜色
backgroundColor: 'rgb(251, 251, 251)',
// 根节点样式
root: {
fillColor: 'rgb(191, 115, 115)',
active: {
borderColor: 'rgb(96, 57, 57)'
}
},
// 二级节点样式
secondLevel: {
fillColor: 'rgb(246, 238, 238)',
color: '#333',
borderColor: 'rgb(191, 115, 115)',
borderWidth: 1,
fontSize: 14,
active: {
borderColor: 'rgb(96, 57, 57)'
}
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#333',
active: {
borderColor: 'rgb(96, 57, 57)'
}
}
})

View File

@@ -0,0 +1,25 @@
import defaultTheme from './default';
import freshGreen from './freshGreen';
import blueSky from './blueSky';
import brainImpairedPink from './brainImpairedPink';
import romanticPurple from './romanticPurple';
import freshRed from './freshRed';
import earthYellow from './earthYellow';
import classic from './classic';
import classic2 from './classic2';
import classic3 from './classic3';
import dark from './dark';
export default {
default: defaultTheme,
freshGreen,
blueSky,
brainImpairedPink,
romanticPurple,
freshRed,
earthYellow,
classic,
classic2,
classic3,
dark
}

View File

@@ -0,0 +1,40 @@
import defaultTheme from './default';
import merge from 'deepmerge';
/**
* @Author: 王林
* @Date: 2021-04-11 15:22:18
* @Desc: 浪漫紫
*/
export default merge(defaultTheme, {
// 连线的颜色
lineColor: 'rgb(123, 115, 191)',
// 背景颜色
backgroundColor: 'rgb(251, 251, 251)',
// 根节点样式
root: {
fillColor: 'rgb(123, 115, 191)',
active: {
borderColor: 'rgb(61, 57, 96)'
}
},
// 二级节点样式
secondLevel: {
fillColor: 'rgb(239, 238, 246)',
color: '#333',
borderColor: 'rgb(123, 115, 191)',
borderWidth: 1,
fontSize: 14,
active: {
borderColor: 'rgb(61, 57, 96)'
}
},
// 三级及以下节点样式
node: {
fontSize: 12,
color: '#333',
active: {
borderColor: 'rgb(61, 57, 96)'
}
}
})

View File

@@ -0,0 +1,120 @@
const map = {
'Backspace': 8,
'Tab': 9,
'Enter': 13,
'Shift': 16,
'Control': 17,
'Alt': 18,
'CapsLock': 20,
'Esc': 27,
'Spacebar': 32,
'PageUp': 33,
'PageDown': 34,
'End': 35,
'Home': 36,
'Insert': 45,
'Left': 37,
'Up': 38,
'Right': 39,
'Down': 40,
'Del': 46,
'NumLock': 144,
'Cmd': 91,
'CmdFF': 224,
'F1': 112,
'F2': 113,
'F3': 114,
'F4': 115,
'F5': 116,
'F6': 117,
'F7': 118,
'F8': 119,
'F9': 120,
'F10': 121,
'F11': 122,
'F12': 123,
'`': 192,
'=': 187,
'-': 189,
'/': 191,
'.': 190,
'direction': {
37: 1,
38: 1,
39: 1,
40: 1
},
controlKeys: {
16: 1,
17: 1,
18: 1,
20: 1,
91: 1,
224: 1
},
'notContentChange': {
13: 1,
9: 1,
33: 1,
34: 1,
35: 1,
36: 1,
16: 1,
17: 1,
18: 1,
20: 1,
91: 1,
//上下左右
37: 1,
38: 1,
39: 1,
40: 1,
113: 1,
114: 1,
115: 1,
144: 1,
27: 1
},
'isSelectedNodeKey': {
//上下左右
37: 1,
38: 1,
39: 1,
40: 1,
13: 1,
9: 1
}
}
// 数字
for (let i = 0; i <= 9; i++) {
map[i] = i + 48
}
// 字母
'abcdefghijklmnopqrstuvwxyz'.split('').forEach((n, index) => {
map[n] = index + 65
})
export const keyMap = map
export const isKey = (e, key) => {
let code = typeof e === 'object' ? e.keyCode : e
return map[key] === code
}

44
src/pages/Edit/Index.vue Normal file
View File

@@ -0,0 +1,44 @@
<template>
<div class="container">
<template v-if="show">
<Toolbar></Toolbar>
<Edit></Edit>
</template>
</div>
</template>
<script>
import Toolbar from "./components/Toolbar";
import Edit from "./components/Edit";
import { mapState, mapActions } from "vuex";
export default {
name: "Index",
components: {
Toolbar,
Edit,
},
data() {
return {
show: false,
};
},
async created() {
const loading = this.$loading({
lock: true,
text: "正在加载,请稍后...",
});
await this.getUserMindMapData();
this.show = true;
loading.close();
},
methods: {
...mapActions(["getUserMindMapData"]),
},
};
</script>
<style lang="less" scoped>
.container {
}
</style>

View File

@@ -0,0 +1,236 @@
<template>
<Sidebar ref="sidebar" title="基础样式">
<div class="sidebarContent" v-if="data">
<div class="title noTop">背景</div>
<div class="row">
<Color
:color="style.backgroundColor"
@change="
(color) => {
update('backgroundColor', color);
}
"
></Color>
</div>
<div class="title noTop">连线</div>
<div class="row">
<div class="rowItem">
<span class="name">颜色</span>
<span
class="block"
v-popover:popover
:style="{ backgroundColor: style.lineColor }"
></span>
<el-popover ref="popover" placement="bottom" trigger="click">
<Color
:color="style.lineColor"
@change="
(color) => {
update('lineColor', color);
}
"
></Color>
</el-popover>
</div>
<div class="rowItem">
<span class="name">粗细</span>
<el-select
size="mini"
style="width: 80px"
v-model="style.lineWidth"
placeholder=""
@change="
(value) => {
update('lineWidth', value);
}
"
>
<el-option
v-for="item in lineWidthList"
:key="item"
:label="item"
:value="item"
>
</el-option>
</el-select>
</div>
</div>
<div class="title noTop">节点内边距</div>
<div class="row">
<div class="rowItem">
<span class="name">水平</span>
<el-slider
style="width: 230px"
v-model="style.paddingX"
@change="
(value) => {
update('paddingX', value);
}
"
></el-slider>
</div>
</div>
<div class="row">
<div class="rowItem">
<span class="name">垂直</span>
<el-slider
style="width: 230px"
v-model="style.paddingY"
@change="
(value) => {
update('paddingY', value);
}
"
></el-slider>
</div>
</div>
</div>
</Sidebar>
</template>
<script>
import Sidebar from "./Sidebar";
import Color from "./Color";
import { lineWidthList } from "@/config";
export default {
name: "BaseStyle",
components: {
Sidebar,
Color,
},
props: {
data: {
type: [Object, null],
default: null,
},
mindMap: {
type: Object,
},
},
data() {
return {
lineWidthList,
style: {
backgroundColor: "",
lineColor: "",
lineWidth: "",
paddingX: 0,
paddingY: 0,
},
};
},
created() {
this.$bus.$on("showTheme", () => {
this.$refs.sidebar.show = true;
this.initStyle();
});
},
methods: {
/**
* @Author: 王林
* @Date: 2021-05-05 14:02:12
* @Desc: 初始样式
*/
initStyle() {
[
"backgroundColor",
"lineWidth",
"lineColor",
"paddingX",
"paddingY",
].forEach((key) => {
this.style[key] = this.mindMap.getThemeConfig(key);
});
},
/**
* @Author: 王林
* @Date: 2021-05-05 14:05:40
* @Desc: 更新配置
*/
update(key, value) {
this.style[key] = value;
this.data.theme.config[key] = value;
this.$emit("change");
},
},
};
</script>
<style lang="less" scoped>
.sidebarContent {
padding: 20px;
padding-top: 10px;
.title {
font-size: 16px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: rgba(26, 26, 26, 0.9);
margin-bottom: 10px;
margin-top: 20px;
&.noTop {
margin-top: 0;
}
}
.row {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
.btnGroup {
width: 100%;
display: flex;
justify-content: space-between;
}
.rowItem {
display: flex;
align-items: center;
.name {
font-size: 12px;
margin-right: 5px;
}
.block {
display: inline-block;
width: 30px;
height: 30px;
border: 1px solid #dcdfe6;
border-radius: 4px;
cursor: pointer;
}
}
.styleBtn {
position: relative;
width: 50px;
height: 30px;
background: #fff;
border: 1px solid #eee;
display: flex;
justify-content: center;
align-items: center;
font-weight: bold;
cursor: pointer;
border-radius: 4px;
&.actived {
background-color: #eee;
}
.colorShow {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 2px;
}
}
}
}
</style>

View File

@@ -0,0 +1,92 @@
<template>
<div>
<div class="colorList">
<span
class="colorItem"
v-for="item in colorList"
:style="{ backgroundColor: item }"
:key="item"
@click="clickColorItem(item)"
></span>
</div>
<div class="moreColor">
<span>更多颜色</span>
<el-color-picker
size="mini"
v-model="selectColor"
@change="changeColor"
></el-color-picker>
</div>
</div>
</template>
<script>
import { colorList } from "@/config";
export default {
name: "Color",
props: {
color: {
type: String,
default: "",
},
},
data() {
return {
colorList,
selectColor: "",
};
},
watch: {
color() {
this.selectColor = this.color;
},
},
created() {
this.selectColor = this.color;
},
methods: {
/**
* @Author: 王林
* @Date: 2021-05-05 09:38:06
* @Desc: 点击预设颜色
*/
clickColorItem(color) {
this.$emit("change", color);
},
/**
* @Author: 王林
* @Date: 2021-05-05 10:17:26
* @Desc: 修改颜色
*/
changeColor() {
this.$emit("change", this.selectColor);
},
},
};
</script>
<style lang="less" scoped>
.colorList {
width: 240px;
.colorItem {
display: inline-block;
width: 15px;
height: 15px;
margin-right: 5px;
margin-bottom: 5px;
cursor: pointer;
}
}
.moreColor {
display: flex;
align-items: center;
span {
margin-right: 5px;
}
}
</style>

View File

@@ -0,0 +1,104 @@
<template>
<div class="editContainer">
<div class="mindMapContainer" ref="mindMapContainer"></div>
<Outline></Outline>
<Style></Style>
<BaseStyle :data="mindMapData" :mindMap="mindMap" @change="changeThemeConfig"></BaseStyle>
</div>
</template>
<script>
import MindMap from "@/package/mind-map";
import Outline from "./Outline";
import Style from "./Style";
import BaseStyle from "./BaseStyle";
import exampleData from '@/package/mind-map/example/exampleData';
export default {
name: "Edit",
components: {
Outline,
Style,
BaseStyle,
},
data() {
return {
mindMap: null,
mindMapData: exampleData
};
},
created() {},
mounted() {
this.init();
this.$bus.$on("execCommand", this.execCommand);
},
methods: {
/**
* @Author: 王林
* @Date: 2021-04-10 15:01:01
* @Desc: 初始化
*/
init() {
let { root, layout, theme } = this.mindMapData;
this.mindMap = new MindMap({
el: this.$refs.mindMapContainer,
data: root,
layout: layout,
theme: theme.template,
themeConfig: theme.config,
});
this.mindMap.on("node_active", (...args) => {
this.$bus.$emit("node_active", ...args);
});
this.mindMap.on("data_change", (...args) => {
this.$bus.$emit("data_change", ...args);
});
},
/**
* @Author: 王林
* @Date: 2021-05-05 13:49:25
* @Desc: 修改主题配置
*/
changeThemeConfig() {
this.mindMap.setThemeConfig(this.mindMapData.theme.config)
},
/**
* @Author: 王林
* @Date: 2021-05-05 13:32:11
* @Desc: 重新渲染
*/
reRender() {
this.mindMap.render()
},
/**
* @Author: 王林
* @Date: 2021-05-04 13:08:28
* @Desc: 执行命令
*/
execCommand(...args) {
this.mindMap.execCommand(...args);
},
},
};
</script>
<style lang="less" scoped>
.editContainer {
position: fixed;
left: 0;
right: 0;
top: 62px;
bottom: 0;
.mindMapContainer {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
}
</style>

View File

View File

@@ -0,0 +1,51 @@
<template>
<el-dialog class="nodeImageDialog" title="图片" :visible.sync="dialogVisible" width="500">
<ImgUpload ref="imgUpload"></ImgUpload>
<span slot="footer" class="dialog-footer">
<el-button @click="cancel"> </el-button>
<el-button type="primary" @click="confirm"> </el-button>
</span>
</el-dialog>
</template>
<script>
import ImgUpload from '@/components/ImgUpload';
export default {
name: 'NodeImage',
components: {
ImgUpload
},
data() {
return {
dialogVisible: false,
};
},
created() {
this.$bus.$on("node_active", (...args) => {
let activeNodes = args[1];
this.activeNode = activeNodes[0];
console.log(args);
});
this.$bus.$on("showNodeImage", () => {
this.dialogVisible = true;
});
},
methods: {
cancel() {},
/**
* @Author: 王林
* @Date: 2021-06-06 22:28:20
* @Desc: 确定
*/
confirm() {},
},
};
</script>
<style lang="less" scoped>
.nodeImageDialog {
}
</style>

View File

@@ -0,0 +1,37 @@
<template>
<Sidebar ref="sidebar" title="大纲">
<el-tree :data="data" :props="defaultProps" default-expand-all></el-tree>
</Sidebar>
</template>
<script>
import Sidebar from "./Sidebar";
export default {
name: "Outline",
components: {
Sidebar,
},
data() {
return {
data: [],
defaultProps: {
label(data) {
return data.data.text;
},
},
};
},
created() {
this.$bus.$on("data_change", (data) => {
this.data = [data];
});
this.$bus.$on('showOutline', () => {
this.$refs.sidebar.show = true
})
},
};
</script>
<style lang="less" scoped>
</style>

View File

@@ -0,0 +1,72 @@
<template>
<div class="sidebarContainer" :class="{show: show}">
<span class="closeBtn el-icon-close" @click="show = false"></span>
<div class="sidebarHeader" v-if="title">
{{ title }}
</div>
<div class="sidebarContent">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: "Sidebar",
props: {
title: {
type: String,
default: "",
},
},
data() {
return {
show: false
}
}
};
</script>
<style lang="less" scoped>
.sidebarContainer {
position: fixed;
right: -300px;
top: 62px;
bottom: 0;
width: 300px;
background-color: #fff;
border-left: 1px solid #e8e8e8;
display: flex;
flex-direction: column;
transition: all 0.3s;
&.show {
right: 0;
}
.closeBtn {
position: absolute;
right: 20px;
top: 12px;
font-size: 20px;
cursor: pointer;
}
.sidebarHeader {
width: 100%;
height: 44px;
border-bottom: 1px solid #e8e8e8;
display: flex;
justify-content: center;
align-items: center;
flex-grow: 0;
flex-shrink: 0;
}
.sidebarContent {
width: 100%;
height: 100%;
overflow: auto;
}
}
</style>

View File

@@ -0,0 +1,441 @@
<template>
<Sidebar ref="sidebar" title="节点样式">
<div class="styleBox">
<el-tabs class="tab" v-model="activeTab" @tab-click="handleTabClick">
<el-tab-pane label="常态" name="normal"></el-tab-pane>
<el-tab-pane label="选中状态" name="active"></el-tab-pane>
</el-tabs>
<div class="sidebarContent" v-if="activeNode">
<div class="title noTop">文字</div>
<div class="row">
<el-select
size="mini"
style="width: 160px"
v-model="style.fontFamily"
placeholder=""
@change="update('fontFamily')"
>
<el-option
v-for="item in fontFamilyList"
:key="item.value"
:label="item.name"
:value="item.value"
:style="{ fontFamily: item.value }"
>
</el-option>
</el-select>
<el-select
size="mini"
style="width: 80px"
v-model="style.fontSize"
placeholder=""
@change="update('fontSize')"
>
<el-option
v-for="item in fontSizeList"
:key="item"
:label="item"
:value="item"
>
</el-option>
</el-select>
</div>
<div class="row">
<div class="btnGroup">
<el-tooltip content="颜色" placement="bottom">
<div class="styleBtn" v-popover:popover>
A
<span
class="colorShow"
:style="{ backgroundColor: style.color || '#eee' }"
></span>
</div>
</el-tooltip>
<el-tooltip content="加粗" placement="bottom">
<div
class="styleBtn"
:class="{ actived: style.fontWeight === 'bold' }"
@click="toggleFontWeight"
>
B
</div>
</el-tooltip>
<el-tooltip content="斜体" placement="bottom">
<div
class="styleBtn i"
:class="{ actived: style.fontStyle === 'italic' }"
@click="toggleFontStyle"
>
I
</div>
</el-tooltip>
<el-tooltip content="划线" placement="bottom">
<div
class="styleBtn u"
:style="{ textDecoration: style.textDecoration || 'none' }"
v-popover:popover2
>
U
</div>
</el-tooltip>
</div>
<el-popover ref="popover" placement="bottom" trigger="click">
<Color :color="style.color" @change="changeFontColor"></Color>
</el-popover>
<el-popover ref="popover2" placement="bottom" trigger="click">
<el-radio-group
size="mini"
v-model="style.textDecoration"
@change="update('textDecoration')"
>
<el-radio-button label="underline">下划线</el-radio-button>
<el-radio-button label="line-through">中划线</el-radio-button>
<el-radio-button label="overline">上划线</el-radio-button>
</el-radio-group>
</el-popover>
</div>
<div class="title">边框</div>
<div class="row">
<div class="rowItem">
<span class="name">颜色</span>
<span
class="block"
v-popover:popover3
:style="{ width: '80px', backgroundColor: style.borderColor }"
></span>
<el-popover ref="popover3" placement="bottom" trigger="click">
<Color
:color="style.borderColor"
@change="changeBorderColor"
></Color>
</el-popover>
</div>
<div class="rowItem">
<span class="name" v-popover:popover5>样式</span>
<el-select
size="mini"
style="width: 80px"
v-model="style.borderDasharray"
placeholder=""
@change="update('borderDasharray')"
>
<el-option
v-for="item in borderDasharrayList"
:key="item.value"
:label="item.name"
:value="item.value"
>
</el-option>
</el-select>
</div>
</div>
<div class="row">
<div class="rowItem">
<span class="name">宽度</span>
<el-select
size="mini"
style="width: 80px"
v-model="style.borderWidth"
placeholder=""
@change="update('borderWidth')"
>
<el-option
v-for="item in borderWidthList"
:key="item"
:label="item"
:value="item"
>
</el-option>
</el-select>
</div>
<div class="rowItem">
<span class="name">圆角</span>
<el-select
size="mini"
style="width: 80px"
v-model="style.borderRadius"
placeholder=""
@change="update('borderRadius')"
>
<el-option
v-for="item in borderRadiusList"
:key="item"
:label="item"
:value="item"
>
</el-option>
</el-select>
</div>
</div>
<div class="title">背景</div>
<div class="row">
<div class="rowItem">
<span class="name">颜色</span>
<span
class="block"
v-popover:popover4
:style="{ backgroundColor: style.fillColor }"
></span>
<el-popover ref="popover4" placement="bottom" trigger="click">
<Color :color="style.fillColor" @change="changeFillColor"></Color>
</el-popover>
</div>
</div>
</div>
</div>
</Sidebar>
</template>
<script>
import Sidebar from "./Sidebar";
import Color from "./Color";
import {
fontFamilyList,
fontSizeList,
borderWidthList,
borderDasharrayList,
borderRadiusList,
} from "@/config";
export default {
name: "Style",
components: {
Sidebar,
Color,
},
data() {
return {
fontFamilyList,
fontSizeList,
borderWidthList,
borderDasharrayList,
borderRadiusList,
activeNode: null,
activeTab: "normal",
style: {
color: "",
fontFamily: "",
fontSize: "",
textDecoration: "",
fontWeight: "",
fontStyle: "",
borderWidth: "",
borderColor: "",
fillColor: "",
borderDasharray: "",
borderRadius: "",
},
};
},
created() {
this.$bus.$on("node_active", (...args) => {
this.activeTab = 'normal'
let activeNodes = args[1];
this.activeNode = activeNodes[0];
this.$refs.sidebar.show = activeNodes.length > 0;
this.initNodeStyle();
});
},
methods: {
/**
* @Author: 王林
* @Date: 2021-05-05 11:42:32
* @Desc: tab切换
*/
handleTabClick() {
this.initNodeStyle()
},
/**
* @Author: 王林
* @Date: 2021-05-05 09:48:52
* @Desc: 初始节点样式
*/
initNodeStyle() {
if (!this.activeNode) {
this.activeTab = 'normal'
return;
}
[
"color",
"fontFamily",
"fontSize",
"textDecoration",
"fontWeight",
"fontStyle",
"borderWidth",
"borderColor",
"fillColor",
"borderDasharray",
"borderRadius",
].forEach((item) => {
this.style[item] = this.activeNode.getStyle(item, false, this.activeTab === 'active');
});
},
/**
* @Author: 王林
* @Date: 2021-05-04 22:08:16
* @Desc: 修改样式
*/
update(prop) {
this.activeNode.setStyle(prop, this.style[prop], this.activeTab === 'active');
},
/**
* @Author: 王林
* @Date: 2021-05-05 09:41:34
* @Desc: 切换加粗样式
*/
toggleFontWeight() {
if (this.style.fontWeight === "bold") {
this.style.fontWeight = "normal";
} else {
this.style.fontWeight = "bold";
}
this.update("fontWeight");
},
/**
* @Author: 王林
* @Date: 2021-05-05 09:46:39
* @Desc: 切换字体样式
*/
toggleFontStyle() {
if (this.style.fontStyle === "italic") {
this.style.fontStyle = "normal";
} else {
this.style.fontStyle = "italic";
}
this.update("fontStyle");
},
/**
* @Author: 王林
* @Date: 2021-05-05 10:18:59
* @Desc: 修改字体颜色
*/
changeFontColor(color) {
this.style.color = color;
this.update("color");
},
/**
* @Author: 王林
* @Date: 2021-05-05 10:18:59
* @Desc: 修改边框颜色
*/
changeBorderColor(color) {
this.style.borderColor = color;
this.update("borderColor");
},
/**
* @Author: 王林
* @Date: 2021-05-05 10:18:59
* @Desc: 修改背景颜色
*/
changeFillColor(color) {
this.style.fillColor = color;
this.update("fillColor");
},
},
};
</script>
<style lang="less" scoped>
.styleBox {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
.tab {
flex-grow: 0;
flex-shrink: 0;
padding: 0 20px;
}
}
.sidebarContent {
padding: 20px;
padding-top: 10px;
.title {
font-size: 16px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: rgba(26, 26, 26, 0.9);
margin-bottom: 10px;
margin-top: 20px;
&.noTop {
margin-top: 0;
}
}
.row {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
.btnGroup {
width: 100%;
display: flex;
justify-content: space-between;
}
.rowItem {
display: flex;
align-items: center;
.name {
font-size: 12px;
margin-right: 5px;
}
.block {
display: inline-block;
width: 30px;
height: 30px;
border: 1px solid #dcdfe6;
border-radius: 4px;
cursor: pointer;
}
}
.styleBtn {
position: relative;
width: 50px;
height: 30px;
background: #fff;
border: 1px solid #eee;
display: flex;
justify-content: center;
align-items: center;
font-weight: bold;
cursor: pointer;
border-radius: 4px;
&.actived {
background-color: #eee;
}
&.i {
font-style: italic;
}
&.u {
}
.colorShow {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 2px;
}
}
}
}
</style>

View File

@@ -0,0 +1,155 @@
<template>
<div class="toolbarContainer">
<div class="toolbar">
<div
class="toolbarBtn"
:class="{
disabled: activeNodes.length <= 0,
}"
@click="$bus.$emit('execCommand', 'INSERT_NODE')"
>
<span class="icon iconfont iconjiedian"></span>
<span class="text">插入同级节点</span>
</div>
<div
class="toolbarBtn"
:class="{
disabled: activeNodes.length <= 0,
}"
@click="$bus.$emit('execCommand', 'INSERT_CHILD_NODE')"
>
<span class="icon iconfont icontianjiazijiedian"></span>
<span class="text">插入子节点</span>
</div>
<div
class="toolbarBtn"
:class="{
disabled: activeNodes.length <= 0,
}"
@click="$bus.$emit('execCommand', 'REMOVE_NODE')"
>
<span class="icon iconfont iconshanchu"></span>
<span class="text">删除节点</span>
</div>
<div
class="toolbarBtn"
:class="{
disabled: activeNodes.length <= 0,
}"
@click="$bus.$emit('showNodeImage')"
>
<span class="icon iconfont iconimage"></span>
<span class="text">图片</span>
</div>
<div
class="toolbarBtn"
:class="{
disabled: activeNodes.length <= 0,
}"
@click="$bus.$emit('showNodeIcon')"
>
<span class="icon iconfont iconxiaolian"></span>
<span class="text">图标</span>
</div>
<div
class="toolbarBtn"
:class="{
disabled: activeNodes.length <= 0,
}"
@click="$bus.$emit('showNodeLink')"
>
<span class="icon iconfont iconchaolianjie"></span>
<span class="text">超链接</span>
</div>
<div class="toolbarBtn" @click="$bus.$emit('showOutline')">
<span class="icon iconfont iconfuhao-dagangshu"></span>
<span class="text">显示大纲</span>
</div>
<div class="toolbarBtn" @click="$bus.$emit('showTheme')">
<span class="icon iconfont iconyangshi"></span>
<span class="text">基础样式</span>
</div>
</div>
<NodeImage></NodeImage>
</div>
</template>
<script>
import NodeImage from "./NodeImage";
export default {
name: "Toolbar",
components: {
NodeImage,
},
data() {
return {
activeNodes: [],
};
},
created() {
this.$bus.$on("node_active", (...args) => {
this.activeNodes = args[1];
});
},
};
</script>
<style lang="less" scoped>
.toolbarContainer {
height: 62px;
background: #fafafa;
padding-left: 40px;
width: 100%;
display: flex;
align-items: center;
border-top: 1px solid #e8e8e8;
border-bottom: 1px solid #e8e8e8;
font-size: 12px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: rgba(26, 26, 26, 0.8);
.toolbar {
display: flex;
align-items: center;
.toolbarBtn {
display: flex;
justify-content: center;
flex-direction: column;
cursor: pointer;
margin-right: 20px;
&:hover {
&:not(.disabled) {
.icon {
background: #f5f5f5;
}
}
}
&.disabled {
color: #bcbcbc;
cursor: not-allowed;
}
.icon {
display: flex;
height: 26px;
background: #fff;
border-radius: 4px;
border: 1px solid #e9e9e9;
justify-content: center;
flex-direction: column;
text-align: center;
padding: 0 5px;
}
.text {
margin-top: 3px;
}
}
}
}
</style>

13
src/pages/Index/Index.vue Normal file
View File

@@ -0,0 +1,13 @@
<template>
<div class="container"></div>
</template>
<script>
export default {
name: "Index"
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style lang="less" scoped>
</style>

18
src/router.js Normal file
View File

@@ -0,0 +1,18 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
import IndexPage from '@/pages/Index/Index'
import EditPage from '@/pages/Edit/Index'
Vue.use(VueRouter)
const routes = [
{ path: '/', name: 'Index', component: IndexPage },
{ path: '/edit/:id', name: 'Edit', component: EditPage }
]
const router = new VueRouter({
routes
})
export default router

68
src/store.js Normal file
View File

@@ -0,0 +1,68 @@
import Vue from 'vue'
import Vuex from 'vuex'
import exampleData from './package/mind-map/example/exampleData';
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
userInfo: null,// 用户信息
mindMapData: null// 思维导图数据
},
mutations: {
/**
* @Author: 王林
* @Date: 2020-11-28 15:32:32
* @Desc: 设置用户信息
*/
setUserInfo(state, userInfo) {
state.userInfo = userInfo
},
/**
* @Author: 王林
* @Date: 2021-04-10 14:50:01
* @Desc: 设置思维导图数据
*/
setMindMapData(state, data) {
state.mindMapData = data
}
},
actions: {
/**
* @Author: 王林
* @Date: 2020-11-28 15:28:03
* @Desc: 获取用户信息
*/
async getUserInfo(ctx) {
try {
let { data } = await api.getUserInfo()
ctx.commit('setUserInfo', data.data)
} catch (error) {
console.log(error)
}
},
/**
* @Author: 王林
* @Date: 2021-04-10 14:50:40
* @Desc: 获取思维导图数据
*/
async getUserMindMapData(ctx) {
try {
let { data } = {
data: {
data: {
mindMapData: exampleData
}
}
}
ctx.commit('setMindMapData', data.data)
} catch (error) {
console.log(error)
}
}
}
})
export default store

0
src/utils/index.js Normal file
View File

13
vue.config.js Normal file
View File

@@ -0,0 +1,13 @@
const path = require('path');
module.exports = {
lintOnSave: false,
productionSourceMap: false,
configureWebpack: {
resolve: {
alias: {
'@': path.resolve(__dirname, './src/')
}
}
}
}