init
22
.editorconfig
Normal file
@ -0,0 +1,22 @@
|
||||
# 告诉EditorConfig插件,这是根文件,不用继续往上查找
|
||||
root = true
|
||||
|
||||
# 匹配全部文件
|
||||
[*]
|
||||
# 设置字符集
|
||||
charset = utf-8
|
||||
# 缩进风格,可选space、tab
|
||||
indent_style = space
|
||||
# 缩进的空格数
|
||||
indent_size = 2
|
||||
# 结尾换行符,可选lf、cr、crlf
|
||||
end_of_line = lf
|
||||
# 在文件结尾插入新行
|
||||
insert_final_newline = true
|
||||
# 删除一行中的前后空格
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# 匹配md结尾的文件
|
||||
[*.md]
|
||||
insert_final_newline = false
|
||||
trim_trailing_whitespace = false
|
||||
8
.env.development
Normal file
@ -0,0 +1,8 @@
|
||||
# 开发环境配置
|
||||
ENV = 'development'
|
||||
|
||||
# 开发环境
|
||||
VUE_APP_BASE_API = '/kg-api'
|
||||
|
||||
# 路由懒加载
|
||||
VUE_CLI_BABEL_TRANSPILE_MODULES = true
|
||||
6
.env.production
Normal file
@ -0,0 +1,6 @@
|
||||
# 生产环境配置
|
||||
ENV = 'production'
|
||||
|
||||
# 生产环境
|
||||
VUE_APP_BASE_API = '/kg-api'
|
||||
|
||||
7
.env.staging
Normal file
@ -0,0 +1,7 @@
|
||||
NODE_ENV = production
|
||||
|
||||
# 测试环境配置
|
||||
ENV = 'staging'
|
||||
|
||||
# 测试环境
|
||||
VUE_APP_BASE_API = '/stage-api'
|
||||
10
.eslintignore
Normal file
@ -0,0 +1,10 @@
|
||||
# 忽略build目录下类型为js的文件的语法检查
|
||||
build/*.js
|
||||
# 忽略src/assets目录下文件的语法检查
|
||||
src/assets
|
||||
# 忽略public目录下文件的语法检查
|
||||
public
|
||||
# 忽略当前目录下为js的文件的语法检查
|
||||
*.js
|
||||
# 忽略当前目录下为vue的文件的语法检查
|
||||
*.vue
|
||||
23
.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
24
README.md
Normal file
@ -0,0 +1,24 @@
|
||||
# kg-builder
|
||||
|
||||
## Project setup
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||
3
babel.config.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
presets: ["@vue/cli-plugin-babel/preset"]
|
||||
};
|
||||
15178
package-lock.json
generated
Normal file
72
package.json
Normal file
@ -0,0 +1,72 @@
|
||||
{
|
||||
"name": "kg-builder",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "cross-env NODE_OPTIONS=--openssl-legacy-provider vue-cli-service serve",
|
||||
"build": "cross-env NODE_OPTIONS=--openssl-legacy-provider vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@antv/x6": "^1.28.1",
|
||||
"axios": "^0.24.0",
|
||||
"core-js": "^3.6.5",
|
||||
"element-ui": "^2.13.2",
|
||||
"html2canvas": "^1.4.0",
|
||||
"jquery": "^3.5.1",
|
||||
"js-cookie": "^3.0.1",
|
||||
"jsplumb": "^2.15.6",
|
||||
"less": "^3.9.0",
|
||||
"less-loader": "^5.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"qs": "^6.10.2",
|
||||
"script-ext-html-webpack-plugin": "^2.1.5",
|
||||
"vue": "^2.6.11",
|
||||
"vue-axios": "^2.1.5",
|
||||
"vue-codemirror": "^4.0.6",
|
||||
"vue-router": "^3.2.0",
|
||||
"vuedraggable": "^2.24.3",
|
||||
"vuex": "^3.4.0",
|
||||
"wangeditor": "^4.7.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-plugin-router": "~4.5.0",
|
||||
"@vue/cli-plugin-vuex": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"@vue/eslint-config-prettier": "^6.0.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"cross-env": "^10.1.0",
|
||||
"d3": "5.0.0",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-prettier": "^3.1.3",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"prettier": "^1.19.1",
|
||||
"sass": "^1.26.5",
|
||||
"sass-loader": "^8.0.2",
|
||||
"vue-template-compiler": "^2.6.11"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"eslint:recommended",
|
||||
"@vue/prettier"
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint"
|
||||
},
|
||||
"rules": {
|
||||
"no-unused-vars": "off"
|
||||
}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
]
|
||||
}
|
||||
BIN
public/favicon.ico
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
18
public/index.html
Normal file
@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<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">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.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>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
<script src="<%= BASE_URL %>static/icon/iconfont.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
2
public/robots.txt
Normal file
@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Disallow: /
|
||||
539
public/static/icon/demo.css
Normal 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: 40px;
|
||||
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;
|
||||
}
|
||||
11228
public/static/icon/demo_index.html
Normal file
1935
public/static/icon/iconfont.css
Normal file
1
public/static/icon/iconfont.js
Normal file
3369
public/static/icon/iconfont.json
Normal file
BIN
public/static/icon/iconfont.ttf
Normal file
BIN
public/static/icon/iconfont.woff
Normal file
BIN
public/static/icon/iconfont.woff2
Normal file
168
public/static/kgData.json
Normal file
@ -0,0 +1,168 @@
|
||||
{
|
||||
"node": [
|
||||
{
|
||||
"flag": "1",
|
||||
"code": "2730103",
|
||||
"parentCode": "27301",
|
||||
"grade": "2",
|
||||
"name": "儒家",
|
||||
"uuid": "26365002",
|
||||
"imgsrc": ""
|
||||
},
|
||||
{
|
||||
"code": "273010308",
|
||||
"flag": "0",
|
||||
"parentCode": "2730103",
|
||||
"grade": "4",
|
||||
"name": "王守仁",
|
||||
"uuid": "46178689",
|
||||
"imgsrc": "http://h.bytravel.cn/ren/0/head/2057.gif"
|
||||
},
|
||||
{
|
||||
"code": "273010307",
|
||||
"flag": "0",
|
||||
"parentCode": "2730103",
|
||||
"grade": "4",
|
||||
"name": "陆九渊",
|
||||
"uuid": "46178686",
|
||||
"imgsrc": "https://bkimg.cdn.bcebos.com/pic/dcc451da81cb39dbde12202dd0160924aa1830fe?x-bce-process=image/resize,m_lfit,w_268,limit_1/format,f_jpg"
|
||||
},
|
||||
{
|
||||
"code": "273010306",
|
||||
"flag": "0",
|
||||
"parentCode": "2730103",
|
||||
"grade": "4",
|
||||
"name": "朱熹",
|
||||
"uuid": "46178681",
|
||||
"imgsrc": "https://bkimg.cdn.bcebos.com/pic/f31fbe096b63f624da9384678944ebf81b4ca38c?x-bce-process=image/resize,m_lfit,w_268,limit_1/format,f_jpg"
|
||||
},
|
||||
{
|
||||
"code": "273010305",
|
||||
"flag": "0",
|
||||
"parentCode": "2730103",
|
||||
"grade": "4",
|
||||
"name": "程颐",
|
||||
"uuid": "46178676",
|
||||
"imgsrc": "https://bkimg.cdn.bcebos.com/pic/060828381f30e92489594e3b4f086e061d95f73f?x-bce-process=image/resize,m_lfit,w_268,limit_1/format,f_jpg"
|
||||
},
|
||||
{
|
||||
"code": "273010304",
|
||||
"flag": "0",
|
||||
"parentCode": "2730103",
|
||||
"grade": "4",
|
||||
"name": "董仲舒",
|
||||
"uuid": "46178671",
|
||||
"imgsrc": "https://bkimg.cdn.bcebos.com/pic/b17eca8065380cd7bbccaaf5a344ad34588281d3?x-bce-process=image/resize,m_lfit,w_268,limit_1/format,f_jpg"
|
||||
},
|
||||
{
|
||||
"code": "273010309",
|
||||
"flag": "0",
|
||||
"parentCode": "2730103",
|
||||
"grade": "4",
|
||||
"name": "曾子",
|
||||
"uuid": "46178665",
|
||||
"imgsrc": "https://bkimg.cdn.bcebos.com/pic/838ba61ea8d3fd1fa226566e3f4e251f95ca5fb3?x-bce-process=image/resize,m_lfit,w_268,limit_1/format,f_jpg"
|
||||
},
|
||||
{
|
||||
"code": "273010303",
|
||||
"flag": "0",
|
||||
"parentCode": "2730103",
|
||||
"grade": "4",
|
||||
"name": "荀子",
|
||||
"uuid": "46178660",
|
||||
"imgsrc": "https://bkimg.cdn.bcebos.com/pic/37d3d539b6003af36cf611c53b2ac65c1138b6c3?x-bce-process=image/resize,m_lfit,w_268,limit_1/format,f_jpg"
|
||||
},
|
||||
{
|
||||
"code": "273010302",
|
||||
"flag": "0",
|
||||
"parentCode": "2730103",
|
||||
"grade": "4",
|
||||
"name": "孟子",
|
||||
"uuid": "46178654",
|
||||
"imgsrc": "http://img.duoziwang.com/2018/19/07051620110108.jpg"
|
||||
},
|
||||
{
|
||||
"code": "273010301",
|
||||
"flag": "0",
|
||||
"parentCode": "2730103",
|
||||
"grade": "4",
|
||||
"name": "孔子",
|
||||
"uuid": "46178648",
|
||||
"imgsrc": "http://img.duoziwang.com/2018/17/05142055603532.jpg"
|
||||
}
|
||||
],
|
||||
"relationship": [
|
||||
{
|
||||
"sourceId": "26365002",
|
||||
"targetId": "46178689",
|
||||
"name": "代表人物",
|
||||
"targetcode": "273010308",
|
||||
"uuid": "91804311",
|
||||
"sourcecode": "2730103"
|
||||
},
|
||||
{
|
||||
"sourceId": "26365002",
|
||||
"targetId": "46178686",
|
||||
"name": "代表人物",
|
||||
"targetcode": "273010307",
|
||||
"uuid": "91804310",
|
||||
"sourcecode": "2730103"
|
||||
},
|
||||
{
|
||||
"sourceId": "26365002",
|
||||
"targetId": "46178681",
|
||||
"name": "代表人物",
|
||||
"targetcode": "273010306",
|
||||
"uuid": "91804309",
|
||||
"sourcecode": "2730103"
|
||||
},
|
||||
{
|
||||
"sourceId": "26365002",
|
||||
"targetId": "46178676",
|
||||
"name": "代表人物",
|
||||
"targetcode": "273010305",
|
||||
"uuid": "91804308",
|
||||
"sourcecode": "2730103"
|
||||
},
|
||||
{
|
||||
"sourceId": "26365002",
|
||||
"targetId": "46178671",
|
||||
"name": "代表人物",
|
||||
"targetcode": "273010304",
|
||||
"uuid": "91804307",
|
||||
"sourcecode": "2730103"
|
||||
},
|
||||
{
|
||||
"sourceId": "26365002",
|
||||
"targetId": "46178665",
|
||||
"name": "代表人物",
|
||||
"targetcode": "273010309",
|
||||
"uuid": "91804306",
|
||||
"sourcecode": "2730103"
|
||||
},
|
||||
{
|
||||
"sourceId": "26365002",
|
||||
"targetId": "46178660",
|
||||
"name": "代表人物",
|
||||
"targetcode": "273010303",
|
||||
"uuid": "91804305",
|
||||
"sourcecode": "2730103"
|
||||
},
|
||||
{
|
||||
"sourceId": "26365002",
|
||||
"targetId": "46178654",
|
||||
"name": "代表人物",
|
||||
"targetcode": "273010302",
|
||||
"uuid": "91804295",
|
||||
"sourcecode": "2730103"
|
||||
},
|
||||
{
|
||||
"sourceId": "26365002",
|
||||
"targetId": "46178648",
|
||||
"name": "代表人物",
|
||||
"targetcode": "273010301",
|
||||
"uuid": "91804294",
|
||||
"sourcecode": "2730103"
|
||||
}
|
||||
]
|
||||
}
|
||||
37
src/App.vue
Normal file
@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<kg-header ref="header"></kg-header>
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import KgHeader from "@/components/KGHeader";
|
||||
export default {
|
||||
components: {
|
||||
KgHeader,
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
// text-align: center;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
#nav {
|
||||
//padding: 30px;
|
||||
|
||||
a {
|
||||
font-weight: bold;
|
||||
color: #2c3e50;
|
||||
|
||||
&.router-link-exact-active {
|
||||
color: #42b983;
|
||||
}
|
||||
}
|
||||
}
|
||||
body{ margin: 0px; }
|
||||
</style>
|
||||
2
src/api/index.js
Normal file
@ -0,0 +1,2 @@
|
||||
export { default as kgBuilderApi } from "./modules/kgBuilderApi";
|
||||
export { default as datasourceApi } from "./modules/datasourceApi";
|
||||
82
src/api/modules/datasourceApi.js
Normal file
@ -0,0 +1,82 @@
|
||||
import request from "@/utils/request";
|
||||
import BaseAPI from '@/utils/BaseAPI'
|
||||
|
||||
class datasourceApi extends BaseAPI{
|
||||
// 获取数据源
|
||||
getDatasource() {
|
||||
return request({
|
||||
url: "/datasource/getDataSource",
|
||||
method: "get"
|
||||
});
|
||||
}
|
||||
// 获取数据表
|
||||
getTableInfo(datasourceId) {
|
||||
return request({
|
||||
url: "/datasource/getDataTable?datasourceId=" + datasourceId,
|
||||
method: "get"
|
||||
});
|
||||
}
|
||||
// 获取数据列
|
||||
getTableColumn(tableId) {
|
||||
return request({
|
||||
url: "/datasource/getDataColumn?dataTableId=" + tableId,
|
||||
method: "get"
|
||||
});
|
||||
}
|
||||
//获取表及列
|
||||
getDataTableInfo(tableId) {
|
||||
return request({
|
||||
url: "/datasource/getDataTableInfo?dataTableId=" + tableId,
|
||||
method: "get"
|
||||
});
|
||||
}
|
||||
//获取预览数据
|
||||
getPreviewData(data) {
|
||||
return this.post("/datasource/getTableRecords",data,{
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}});
|
||||
// return request({
|
||||
// url: "/datasource/getTableRecords",
|
||||
// method: "post",
|
||||
// data: data
|
||||
// });
|
||||
}
|
||||
//保存数据源
|
||||
saveDatasource(data) {
|
||||
return this.post("/datasource/saveDataSource",data,{
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}});
|
||||
// return request({
|
||||
// url: "/datasource/saveDataSource",
|
||||
// method: "post",
|
||||
// data: data
|
||||
// });
|
||||
}
|
||||
//保存数据表
|
||||
saveDataTable(data) {
|
||||
return this.post("/datasource/saveDataTable",data,{
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}});
|
||||
// return request({
|
||||
// url: "/datasource/saveDataTable",
|
||||
// method: "post",
|
||||
// data: data
|
||||
// });
|
||||
}
|
||||
//获取数据表记录
|
||||
getDataRecord(data) {
|
||||
return this.post("/datasource/getTableRecords",data,{
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}});
|
||||
// return request({
|
||||
// url: "/datasource/getTableRecords",
|
||||
// method: "post",
|
||||
// data: data
|
||||
// });
|
||||
}
|
||||
}
|
||||
export default new datasourceApi();
|
||||
186
src/api/modules/kgBuilderApi.js
Normal file
@ -0,0 +1,186 @@
|
||||
import BaseAPI from '@/utils/BaseAPI'
|
||||
|
||||
class kgBuilderApi extends BaseAPI{
|
||||
// 获取图谱数据
|
||||
getKgData() {
|
||||
return this.get("/static/kgData.json");
|
||||
}
|
||||
feedBack(data) {
|
||||
return this.post("/feedBack",data);
|
||||
}
|
||||
saveData(data) {
|
||||
return this.post("/er/saveData",data,{
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
getDomainNode(domainId) {
|
||||
return this.get('/er/getDomainNode', {
|
||||
domainId
|
||||
})
|
||||
|
||||
}
|
||||
execute(domainId) {
|
||||
return this.get('/er/execute', {
|
||||
domainId
|
||||
})
|
||||
|
||||
}
|
||||
getDomains(data) {
|
||||
return this.post("/getGraph",data,{
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
createDomain(data) {
|
||||
return this.get("/createDomain",data);
|
||||
// return request({
|
||||
// url: "/createDomain?domain=" + data.domain + "&type=" + data.type,
|
||||
// method: "get"
|
||||
// });
|
||||
}
|
||||
getCypherResult(data) {
|
||||
return this.get("/getCypherResult",data);
|
||||
}
|
||||
getNodeContent(data) {
|
||||
return this.post("/getNodeContent",data);
|
||||
}
|
||||
getNodeImage(data) {
|
||||
return this.post("/getNodeImage",data);
|
||||
}
|
||||
getNodeDetail(data) {
|
||||
return this.post("/getNodeDetail",data);
|
||||
}
|
||||
saveNodeImage(data) {
|
||||
return this.post("/saveNodeImage",data,{
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
}
|
||||
saveNodeContent(data) {
|
||||
return this.post("/saveNodeContent",data,{
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
}
|
||||
getDomainGraph(data) {
|
||||
return this.post("/queryGraphResult",data,{
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
}
|
||||
getRelationNodeCount(data) {
|
||||
return this.post("/getRelationNodeCount",data);
|
||||
}
|
||||
getMoreRelationNode(data) {
|
||||
return this.get("/getMoreRelationNode",data);
|
||||
}
|
||||
deleteDomain(data) {
|
||||
return this.post("/deleteDomain",data);
|
||||
}
|
||||
renameDomain(data) {
|
||||
return this.put("/kg/domain/rename", data);
|
||||
}
|
||||
renameDomain(data) {
|
||||
return this.put("/kg/domain/rename", data, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
}
|
||||
getRecommendGraph(data) {
|
||||
return this.post("/getRecommendGraph",data,{
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
}
|
||||
createNode(data) {
|
||||
return this.post("/createNode",data,{
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
}
|
||||
deleteNode(data) {
|
||||
return this.post("/deleteNode",data);
|
||||
}
|
||||
deleteLink(data) {
|
||||
return this.post("/deleteLink",data);
|
||||
}
|
||||
createLink(data) {
|
||||
return this.post("/createLink",data,{
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
}
|
||||
updateLink(data) {
|
||||
return this.post("/updateLink",data);
|
||||
}
|
||||
updateNodeName(data) {
|
||||
return this.post("/updateNodeName",data,{
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
}
|
||||
batchCreateNode(data) {
|
||||
return this.post("/batchCreateNode",data,{
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
}
|
||||
batchCreateChildNode(data) {
|
||||
return this.post("/batchCreateChildNode",data,{
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
}
|
||||
batchCreateSameNode(data) {
|
||||
return this.post("/batchCreateSameNode",data,{
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
}
|
||||
exportGraph(data) {
|
||||
return this.post("/exportGraph",data);
|
||||
}
|
||||
download(data) {
|
||||
return this.get("/download/"+data,);
|
||||
}
|
||||
intelligentSearch(data) {
|
||||
return this.post("/intelligent/search", data, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getSearchSuggestions(query, domain) {
|
||||
return this.get("/intelligent/suggestions", {
|
||||
query,
|
||||
domain
|
||||
});
|
||||
}
|
||||
|
||||
updateCoordinateOfNode(data) {
|
||||
return this.post("/updateCoordinateOfNode",data,{
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
export default new kgBuilderApi();
|
||||
31
src/api/token.js
Normal file
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* @description: token操作
|
||||
*/
|
||||
|
||||
export const TOKENKEY = "authtoken";
|
||||
|
||||
// 存储token
|
||||
export const setToken = (token, key) => {
|
||||
localStorage.setItem(key || TOKENKEY, token);
|
||||
};
|
||||
|
||||
// 获取token
|
||||
export const getToken = key => localStorage.getItem(key || TOKENKEY) || null;
|
||||
|
||||
// 删除token
|
||||
export const removeToken = key => {
|
||||
localStorage.removeItem(key || TOKENKEY);
|
||||
};
|
||||
|
||||
// 获取cookie
|
||||
export const getCookie = name => {
|
||||
var arr;
|
||||
var reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)");
|
||||
if ((arr = document.cookie.match(reg))) return +unescape(arr[2]);
|
||||
else return null;
|
||||
};
|
||||
|
||||
// 移除所有
|
||||
export const removeAll = () => {
|
||||
localStorage.clear();
|
||||
};
|
||||
BIN
src/assets/3yuanzuimport.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
src/assets/logo.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
src/assets/sanyuanzuimport1.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
src/assets/sanyuanzuimport2.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
src/assets/treeimport1.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
src/assets/treeimport2.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
src/assets/中国一重.png
Normal file
|
After Width: | Height: | Size: 513 KiB |
837
src/components/KGBuilder.vue
Normal file
@ -0,0 +1,837 @@
|
||||
<template>
|
||||
<div>
|
||||
<div id="gid_tc" style="float:left;">
|
||||
<div id="gid"></div>
|
||||
<div class="mengceng"></div>
|
||||
</div>
|
||||
<div class="svg-set-box clearfix">
|
||||
<div class="ctwh-dibmr">
|
||||
<span>显示范围:</span>
|
||||
<a
|
||||
:key="index"
|
||||
v-for="(m, index) in pageSizeList"
|
||||
@click="setMatchSize(m, this)"
|
||||
href="javascript:void(0)"
|
||||
:class="[m.isActive ? 'sd-active' : '', 'ss-d sd' + (index + 1)]"
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
<div class="ctwh-dibmr" style="float: right">
|
||||
<ul class="toolbar">
|
||||
<li>
|
||||
<a href="javascript:;" @click="zoomIn"
|
||||
><span><i class="el-icon-zoom-in"></i>放大</span></a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="javascript:;" @click="zoomOut"
|
||||
><span><i class="el-icon-zoom-out"></i>缩小</span></a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="javascript:;" @click="refresh"
|
||||
><span><i class="el-icon-refresh-right"></i>还原</span></a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
v-if="!isFullscreen"
|
||||
id="fullscreenbtn"
|
||||
href="javascript:;"
|
||||
@click="showFull"
|
||||
>
|
||||
<span><i class="el-icon-full-screen"></i>全屏</span>
|
||||
</a>
|
||||
<a
|
||||
v-else
|
||||
id="fullscreenbtn"
|
||||
href="javascript:;"
|
||||
@click="exitFullScreen"
|
||||
>
|
||||
<span><i class="el-icon-full-screen"></i>退出全屏</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
import * as d3 from 'd3'
|
||||
import $ from 'jquery'
|
||||
|
||||
export default {
|
||||
props: ['pid'],
|
||||
data() {
|
||||
return {
|
||||
theme: 0,
|
||||
loading: false,
|
||||
width: 1000,
|
||||
height: 800,
|
||||
gcontainer: {},
|
||||
svg: {},
|
||||
zoom: null,
|
||||
arrowMarker: {},
|
||||
simulation: {},
|
||||
isFullscreen: false,
|
||||
qaGraphNode: {},
|
||||
qaButtonGroup: {},
|
||||
qaGraphNodeText: {},
|
||||
qaGraphLink: {},
|
||||
qaGraphLinkText: {},
|
||||
graph: {
|
||||
nodes: [],
|
||||
links: [],
|
||||
},
|
||||
defaultR: 30,
|
||||
colorList: [
|
||||
'#ff8373',
|
||||
'#f9c62c',
|
||||
'#a5ca34',
|
||||
'#6fce7a',
|
||||
'#70d3bd',
|
||||
'#ea91b0',
|
||||
],
|
||||
pageSizeList: [
|
||||
{ size: 100, isActive: false },
|
||||
{ size: 300, isActive: false },
|
||||
{ size: 500, isActive: true },
|
||||
{ size: 1000, isActive: false },
|
||||
],
|
||||
toolbarData: [
|
||||
{ name: '编辑', value: 1, code: 'edit' },
|
||||
{ name: '展开', value: 1, code: 'more' },
|
||||
{ name: '追加', value: 1, code: 'append' },
|
||||
{ name: '连线', value: 1, code: 'link' },
|
||||
{ name: '删除', value: 1, code: 'delete' },
|
||||
],
|
||||
selectUuid: 0,
|
||||
nodeRecordList: [],
|
||||
}
|
||||
},
|
||||
components: {},
|
||||
mounted() {
|
||||
this.initGraphContainer()
|
||||
this.addMaker()
|
||||
this.initGraph()
|
||||
},
|
||||
created() {},
|
||||
watch: {},
|
||||
methods: {
|
||||
initGraphContainer() {
|
||||
this.gcontainer = d3.select('#gid')
|
||||
if (this.isFullscreen) {
|
||||
this.width = window.screen.width
|
||||
this.height = window.screen.height
|
||||
} else {
|
||||
this.width = $('#' + this.pid).width()
|
||||
this.height = $('#' + this.pid).height()
|
||||
}
|
||||
this.svg = this.gcontainer.append('svg')
|
||||
var sWidth = this.width
|
||||
var sHeight = this.height
|
||||
this.svg.attr('width', sWidth)
|
||||
this.svg.attr('height', sHeight)
|
||||
// this.svg.attr("viewBox", "0 0 " + sWidth / 2 + " " + sHeight / 2);
|
||||
this.svg.attr('id', 'svg_idx')
|
||||
this.svg.attr('preserveAspectRatio', 'xMidYMidmeet')
|
||||
this.simulation = d3
|
||||
.forceSimulation()
|
||||
.force('charge', d3.forceManyBody().strength(-1500))
|
||||
.force(
|
||||
'link',
|
||||
d3
|
||||
.forceLink()
|
||||
.distance(60)
|
||||
.id(function (d) {
|
||||
return d.uuid
|
||||
})
|
||||
)
|
||||
.force('collide', d3.forceCollide().strength(-30))
|
||||
.force('center', d3.forceCenter(this.width / 2, this.height / 2))
|
||||
this.qaGraphLink = this.svg.append('g').attr('class', 'line')
|
||||
this.qaGraphLinkText = this.svg.append('g').attr('class', 'lineText')
|
||||
this.qaGraphNode = this.svg.append('g').attr('class', 'node')
|
||||
this.qaGraphNodeText = this.svg.append('g').attr('class', 'nodeText')
|
||||
this.nodeButtonGroup = this.svg.append('g').attr('class', 'nodeButton')
|
||||
this.svg.on(
|
||||
'click',
|
||||
function () {
|
||||
d3.selectAll('.buttongroup').classed('notshow', true)
|
||||
},
|
||||
false
|
||||
)
|
||||
},
|
||||
initGraph() {
|
||||
var _this = this
|
||||
axios.get('/static/kgData.json', {}).then(function (response) {
|
||||
var data = response.data
|
||||
_this.graph.nodes = data.node
|
||||
_this.graph.links = data.relationship
|
||||
_this.updateGraph()
|
||||
})
|
||||
},
|
||||
addMaker() {
|
||||
var arrowMarker = this.svg
|
||||
.append('marker')
|
||||
.attr('id', 'arrow')
|
||||
.attr('markerUnits', 'strokeWidth')
|
||||
.attr('markerWidth', '12') //
|
||||
.attr('markerHeight', '12')
|
||||
.attr('viewBox', '0 0 12 12')
|
||||
.attr('refX', '30')
|
||||
.attr('refY', '6')
|
||||
.attr('orient', 'auto')
|
||||
var arrowPath = 'M2,2 L10,6 L2,10 L6,6 L2,2' // 定义箭头形状
|
||||
arrowMarker.append('path').attr('d', arrowPath).attr('fill', '#ccc')
|
||||
},
|
||||
openNode() {
|
||||
var _this = this
|
||||
var noddd = [
|
||||
{
|
||||
flag: '1',
|
||||
code: '27301111',
|
||||
parentCode: '273',
|
||||
grade: '2',
|
||||
name: '儒家2',
|
||||
uuid: '4617858011',
|
||||
},
|
||||
{
|
||||
code: '273012222',
|
||||
flag: '1',
|
||||
parentCode: '273',
|
||||
grade: '3',
|
||||
name: '故事轶闻2',
|
||||
uuid: '2636501111',
|
||||
},
|
||||
]
|
||||
var newships = [
|
||||
{
|
||||
sourceId: '273',
|
||||
targetId: '2636501111',
|
||||
name: '',
|
||||
targetcode: '2730107',
|
||||
uuid: '91804213',
|
||||
sourcecode: '27301',
|
||||
},
|
||||
{
|
||||
sourceId: '273',
|
||||
targetId: '4617858011',
|
||||
name: '',
|
||||
targetcode: '273010723',
|
||||
uuid: '91804389',
|
||||
sourcecode: '2730107',
|
||||
},
|
||||
]
|
||||
_this.graph.nodes = _this.graph.nodes.concat(noddd)
|
||||
_this.graph.links = _this.graph.links.concat(newships)
|
||||
_this.updateGraph()
|
||||
},
|
||||
drawNode(nodes) {
|
||||
var _this = this
|
||||
var node = this.qaGraphNode.selectAll('circle').data(nodes, function (d) {
|
||||
return d.uuid
|
||||
})
|
||||
node.exit().remove()
|
||||
var nodeEnter = node.enter().append('circle')
|
||||
nodeEnter.on('click', function (d) {
|
||||
console.log('触发单击')
|
||||
_this.selectUuid = d.uuid
|
||||
var out_buttongroup_id = '.out_buttongroup_' + d.uuid
|
||||
var selectItem = d3.select(out_buttongroup_id)._groups[0][0]
|
||||
if (selectItem.classList.contains('notshow')) {
|
||||
_this.svg.selectAll('.buttongroup').classed('notshow', true)
|
||||
d3.select(out_buttongroup_id).classed('notshow', false)
|
||||
} else {
|
||||
d3.select(out_buttongroup_id).classed('notshow', true)
|
||||
}
|
||||
event.stopPropagation()
|
||||
})
|
||||
nodeEnter.on('dblclick', function (d) {
|
||||
console.log('触发双击:' + d)
|
||||
event.preventDefault()
|
||||
})
|
||||
nodeEnter.on('mouseenter', function () {
|
||||
console.log('鼠标移入')
|
||||
d3.select(this).style('stroke-width', '6')
|
||||
})
|
||||
nodeEnter.on('mouseleave', function () {
|
||||
console.log('鼠标移出')
|
||||
d3.select(this).style('stroke-width', 2)
|
||||
//todo其他节点和连线一并显示
|
||||
d3.select('.node').style('fill-opacity', 1)
|
||||
d3.select('.nodeText').style('fill-opacity', 1)
|
||||
d3.selectAll('.line').style('stroke-opacity', 1)
|
||||
d3.selectAll('.lineText').style('fill-opacity', 1)
|
||||
})
|
||||
nodeEnter.on('mouseover', function (d) {
|
||||
//todo鼠标放上去只显示相关节点,其他节点和连线隐藏
|
||||
d3.selectAll('.node').style('fill-opacity', 0.1)
|
||||
var relvantNodeIds = []
|
||||
var relvantNodes = _this.graph.links.filter(function (n) {
|
||||
return n.sourceId == d.uuid || n.targetId == d.uuid
|
||||
})
|
||||
relvantNodes.forEach(function (item) {
|
||||
relvantNodeIds.push(item.sourceId)
|
||||
relvantNodeIds.push(item.targetId)
|
||||
})
|
||||
//显示相关的节点
|
||||
_this.qaGraphNode
|
||||
.selectAll('circle')
|
||||
.style('fill-opacity', function (c) {
|
||||
if (relvantNodeIds.indexOf(c.uuid) > -1) {
|
||||
return 1.0
|
||||
}
|
||||
})
|
||||
//透明所有节点文字
|
||||
d3.selectAll('.nodeText').style('fill-opacity', 0.1)
|
||||
//显示相关的节点文字
|
||||
_this.qaGraphNodeText
|
||||
.selectAll('text')
|
||||
.style('fill-opacity', function (c) {
|
||||
if (relvantNodeIds.indexOf(c.uuid) > -1) {
|
||||
return 1.0
|
||||
}
|
||||
})
|
||||
//透明所有连线
|
||||
d3.selectAll('.line').style('stroke-opacity', 0.1)
|
||||
//显示相关的连线
|
||||
_this.qaGraphLink
|
||||
.selectAll('line')
|
||||
.style('stroke-opacity', function (c) {
|
||||
if (c.lk.targetId === d.uuid) {
|
||||
console.log(c)
|
||||
return 1.0
|
||||
}
|
||||
})
|
||||
//透明所有连线文字
|
||||
d3.selectAll('.lineText').style('fill-opacity', 0.1)
|
||||
//显示相关的连线文字
|
||||
_this.qaGraphLinkText
|
||||
.selectAll('.lineText')
|
||||
.style('fill-opacity', function (c) {
|
||||
if (c.lk.targetId === d.uuid) {
|
||||
return 1.0
|
||||
}
|
||||
})
|
||||
})
|
||||
nodeEnter.call(
|
||||
d3
|
||||
.drag()
|
||||
.on('start', _this.dragStarted)
|
||||
.on('drag', _this.dragged)
|
||||
.on('end', _this.dragEnded)
|
||||
)
|
||||
node = nodeEnter.merge(node).text(function (d) {
|
||||
return d.name
|
||||
})
|
||||
node.style('stroke', function (d) {
|
||||
if (d.color) {
|
||||
return d.color
|
||||
}
|
||||
return '#A4ED6C'
|
||||
})
|
||||
node.style('stroke-opacity', 0.6)
|
||||
node.attr('r', function (d) {
|
||||
if (d.r) {
|
||||
return d.r
|
||||
}
|
||||
return d.index === 0 ? 28 : 20
|
||||
})
|
||||
node.attr('fill', function (d, i) {
|
||||
//创建圆形图像
|
||||
if (d.imgsrc) {
|
||||
var img_w = 77,
|
||||
img_h = 80
|
||||
var defs = d3.selectAll('svg >defs')
|
||||
var catpattern = defs
|
||||
.append('pattern')
|
||||
.attr('id', 'catpattern' + i)
|
||||
.attr('height', 1)
|
||||
.attr('width', 1)
|
||||
catpattern
|
||||
.append('image')
|
||||
.attr('x', -(img_w / 2 - d.r))
|
||||
.attr('y', -(img_h / 2 - d.r))
|
||||
.attr('width', img_w)
|
||||
.attr('height', img_h)
|
||||
.attr('xlink:href', d.imgsrc)
|
||||
return 'url(#catpattern' + i + ')'
|
||||
} else {
|
||||
if (d.cur === '1') {
|
||||
return _this.colorList[0]
|
||||
} else {
|
||||
return _this.colorList[2]
|
||||
}
|
||||
}
|
||||
})
|
||||
node
|
||||
.append('title') // 为每个节点设置title
|
||||
.text(function (d) {
|
||||
if (d.name) {
|
||||
return d.name
|
||||
}
|
||||
return ''
|
||||
})
|
||||
return node
|
||||
},
|
||||
drawNodeText(nodes) {
|
||||
var _this = this
|
||||
var nodeText = this.qaGraphNodeText
|
||||
.selectAll('text')
|
||||
.data(nodes, function (d) {
|
||||
return d.uuid
|
||||
})
|
||||
nodeText.exit().remove()
|
||||
var nodeTextEnter = nodeText.enter().append('text')
|
||||
nodeTextEnter.call(
|
||||
d3
|
||||
.drag()
|
||||
.on('start', _this.dragStarted)
|
||||
.on('drag', _this.dragged)
|
||||
.on('end', _this.dragEnded)
|
||||
)
|
||||
nodeText = nodeTextEnter.merge(nodeText).text(function (d) {
|
||||
return d.name
|
||||
})
|
||||
nodeText
|
||||
.style('fill', function () {
|
||||
return '#333'
|
||||
})
|
||||
.attr('class', 'nodeText')
|
||||
.attr('dy', '3.6em')
|
||||
.attr('font-family', '宋体')
|
||||
.attr('font-size', 16)
|
||||
.attr('text-anchor', 'middle')
|
||||
.text(function (d) {
|
||||
return d.name
|
||||
})
|
||||
nodeText
|
||||
.append('title') // 为每个节点设置title
|
||||
.text(function (d) {
|
||||
if (d.name) {
|
||||
return d.name
|
||||
}
|
||||
return ''
|
||||
})
|
||||
return nodeText
|
||||
},
|
||||
drawLink(links) {
|
||||
var _this = this
|
||||
var link = this.qaGraphLink.selectAll('line').data(links, function (d) {
|
||||
return d.uuid
|
||||
})
|
||||
link.exit().remove()
|
||||
var linkEnter = link
|
||||
.enter()
|
||||
.append('line')
|
||||
.attr('class', 'link')
|
||||
.attr('stroke-width', 1)
|
||||
.attr('stroke', function () {
|
||||
return _this.colorList[2]
|
||||
})
|
||||
.attr('fill', function () {
|
||||
return _this.colorList[2]
|
||||
})
|
||||
.attr('marker-end', 'url(#arrow)') // 箭头
|
||||
link = linkEnter.merge(link)
|
||||
return link
|
||||
},
|
||||
drawLinkText(links) {
|
||||
var _this = this
|
||||
var linktext = _this.qaGraphLinkText
|
||||
.selectAll('text')
|
||||
.data(links, function (d) {
|
||||
return d.uuid
|
||||
})
|
||||
linktext.exit().remove()
|
||||
var linkTextEnter = linktext
|
||||
.enter()
|
||||
.append('text')
|
||||
.attr('class', 'lineText')
|
||||
.style('fill', '#875034')
|
||||
.style('font-size', '16px')
|
||||
.text(function (d) {
|
||||
return d.lk.name
|
||||
})
|
||||
linktext = linkTextEnter.merge(linktext).text(function (d) {
|
||||
return d.lk.name
|
||||
})
|
||||
return linktext
|
||||
},
|
||||
drawButtonGroup(nodes) {
|
||||
var _this = this
|
||||
d3.selectAll('.nodeButton >g').remove()
|
||||
var nodeButton = _this.nodeButtonGroup
|
||||
.selectAll('.nodeButton')
|
||||
.data(nodes, function (d) {
|
||||
return d.uuid
|
||||
})
|
||||
nodeButton.exit().remove()
|
||||
var nodeButtonEnter = nodeButton
|
||||
.enter()
|
||||
.append('use') // 为每个节点组添加一个 use 子元素
|
||||
.attr('r', function (d) {
|
||||
if (!d.r) {
|
||||
return _this.defaultR
|
||||
}
|
||||
return d.r
|
||||
})
|
||||
.attr('uuid', function (d) {
|
||||
return d.uuid
|
||||
})
|
||||
.attr('xlink:href', function (d) {
|
||||
if (!d.r) {
|
||||
return '#out_circle_' + _this.defaultR
|
||||
}
|
||||
return '#out_circle_' + d.r
|
||||
}) // 指定 use 引用的内容
|
||||
.attr('class', function (d) {
|
||||
return 'buttongroup out_buttongroup_' + d.uuid
|
||||
})
|
||||
.classed('notshow', true)
|
||||
nodeButton = nodeButtonEnter.merge(nodeButton)
|
||||
return nodeButton
|
||||
},
|
||||
drawToolButton() {
|
||||
var _this = this
|
||||
//先删除所有为节点自定义的按钮组
|
||||
d3.selectAll('svg >defs').remove()
|
||||
var nodes = _this.graph.nodes
|
||||
var pie = d3.pie().value(function (d) {
|
||||
return d.value //处理数据,指定value作为计算比例的字段
|
||||
})
|
||||
var piedata = pie(_this.toolbarData)
|
||||
var nodeButtonGroup = this.svg.append('defs')
|
||||
var nodeRArr = []
|
||||
nodes.forEach(function (m) {
|
||||
if (!m.r) {
|
||||
m.r = _this.defaultR
|
||||
}
|
||||
//按半径分别定义每种按钮组的图标
|
||||
if (nodeRArr.indexOf(m.r) == -1) {
|
||||
nodeRArr.push(m.r)
|
||||
var nbtng = nodeButtonGroup
|
||||
.append('g')
|
||||
.attr('id', 'out_circle_' + m.r) //为每一个节点定制一个按钮组,在画按钮组的时候为其指定该id
|
||||
var buttonGroupEnter = nbtng
|
||||
.selectAll('.buttongroup')
|
||||
.data(piedata)
|
||||
.enter()
|
||||
.append('g')
|
||||
.attr('class', function (d) {
|
||||
return 'action_' + d.data.code
|
||||
})
|
||||
var arc = d3
|
||||
.arc()
|
||||
.innerRadius(m.r)
|
||||
.outerRadius(m.r + 30)
|
||||
buttonGroupEnter
|
||||
.append('path')
|
||||
.attr('d', function (d) {
|
||||
return arc(d)
|
||||
})
|
||||
.attr('fill', '#E6A23C')
|
||||
.style('opacity', 0.6)
|
||||
.attr('stroke', '#6CB7ED')
|
||||
.attr('stroke-width', 1)
|
||||
buttonGroupEnter
|
||||
.append('text')
|
||||
.attr('transform', function (d) {
|
||||
return 'translate(' + arc.centroid(d) + ')'
|
||||
})
|
||||
.attr('text-anchor', 'middle')
|
||||
.text(function (d) {
|
||||
return d.data.name
|
||||
})
|
||||
.style('fill', function () {
|
||||
return '#fff'
|
||||
})
|
||||
.attr('font-size', 10)
|
||||
}
|
||||
})
|
||||
},
|
||||
bindEventButtonGroup() {
|
||||
var _this = this
|
||||
//按钮组事件绑定
|
||||
_this.toolbarData.forEach(function (m) {
|
||||
var btnClass = '.action_' + m.code
|
||||
_this.svg.selectAll(btnClass).on('click', function (d) {
|
||||
console.log(
|
||||
d.data.name + ':' + d.data.code + ':uuid:' + _this.selectUuid
|
||||
)
|
||||
})
|
||||
})
|
||||
},
|
||||
formatData() {
|
||||
var _this = this
|
||||
var lks = _this.graph.links
|
||||
var nodes = _this.graph.nodes
|
||||
nodes.forEach(function (n) {
|
||||
if (n.center === 1 || n.center === '1') {
|
||||
n.fx = _this.width / 2
|
||||
n.fy = _this.height / 2
|
||||
}
|
||||
if (typeof n.fx === 'undefined' || n.fx === '') {
|
||||
n.fx = null
|
||||
}
|
||||
if (typeof n.fy === 'undefined' || n.fy === '') {
|
||||
n.fy = null
|
||||
}
|
||||
})
|
||||
var links = []
|
||||
lks.forEach(function (m) {
|
||||
var sourceNode = nodes.filter(function (n) {
|
||||
return n.uuid === m.sourceId
|
||||
})[0]
|
||||
if (!sourceNode) return
|
||||
var targetNode = nodes.filter(function (n) {
|
||||
return n.uuid === m.targetId
|
||||
})[0]
|
||||
if (!targetNode) return
|
||||
links.push({ source: sourceNode.uuid, target: targetNode.uuid, lk: m })
|
||||
})
|
||||
var data = {}
|
||||
data.nodes = nodes
|
||||
data.links = links
|
||||
return data
|
||||
},
|
||||
updateGraph() {
|
||||
var _this = this
|
||||
var data = _this.formatData()
|
||||
var nodes = data.nodes
|
||||
var links = data.links
|
||||
//定义按钮组引用的use元素
|
||||
_this.drawToolButton(nodes)
|
||||
// 更新节点
|
||||
var graphNode = _this.drawNode(nodes)
|
||||
// 更新节点文字
|
||||
var graphNodeText = _this.drawNodeText(nodes)
|
||||
// 更新按钮组
|
||||
var graphNodeButtonGroup = _this.drawButtonGroup(nodes)
|
||||
// 更新连线 links
|
||||
var graphLink = _this.drawLink(links)
|
||||
// 更新连线文字
|
||||
var graphLinkText = _this.drawLinkText(links)
|
||||
_this.simulation
|
||||
.nodes(nodes)
|
||||
.alphaTarget(0)
|
||||
.alphaDecay(0.05)
|
||||
.on('tick', ticked)
|
||||
function ticked() {
|
||||
// 更新连线坐标
|
||||
graphLink
|
||||
.attr('x1', function (d) {addNodeButton
|
||||
return d.source.x
|
||||
})
|
||||
.attr('y1', function (d) {
|
||||
return d.source.y
|
||||
})
|
||||
.attr('x2', function (d) {
|
||||
return d.target.x
|
||||
})
|
||||
.attr('y2', function (d) {
|
||||
return d.target.y
|
||||
})
|
||||
// 刷新连接线上的文字位置
|
||||
graphLinkText
|
||||
.attr('x', function (d) {
|
||||
if (!d.source.x || !d.target.x) return 0
|
||||
var x = (parseFloat(d.source.x) + parseFloat(d.target.x)) / 2
|
||||
return x
|
||||
})
|
||||
.attr('y', function (d) {
|
||||
if (!d.source.y || !d.target.y) return 0
|
||||
var y = (parseFloat(d.source.y) + parseFloat(d.target.y)) / 2
|
||||
return y
|
||||
})
|
||||
// 更新节点坐标
|
||||
graphNode
|
||||
.attr('cx', function (d) {
|
||||
return d.x
|
||||
})
|
||||
.attr('cy', function (d) {
|
||||
return d.y
|
||||
})
|
||||
// 更新节点操作按钮组坐标
|
||||
graphNodeButtonGroup
|
||||
.attr('cx', function (d) {
|
||||
return d.x
|
||||
})
|
||||
.attr('cy', function (d) {
|
||||
return d.y
|
||||
})
|
||||
.attr('transform', function (d) {
|
||||
return 'translate(' + d.x + ',' + d.y + ') scale(1)'
|
||||
})
|
||||
// 更新文字坐标
|
||||
graphNodeText
|
||||
.attr('x', function (d) {
|
||||
return d.x
|
||||
})
|
||||
.attr('y', function (d) {
|
||||
return d.y
|
||||
})
|
||||
}
|
||||
_this.simulation.force('link').links(links)
|
||||
_this.simulation.force(
|
||||
'center',
|
||||
d3.forceCenter(_this.width / 2, _this.height / 2)
|
||||
)
|
||||
_this.simulation.alpha(1).restart()
|
||||
// 鼠标滚轮缩放
|
||||
_this.zoom = d3.zoom().scaleExtent([0.1, 4]).on('zoom', _this.zoomed)
|
||||
_this.svg.call(_this.zoom)
|
||||
// 静止双击缩放
|
||||
_this.svg.on('dblclick.zoom', null)
|
||||
//为按钮组绑定事件
|
||||
_this.bindEventButtonGroup()
|
||||
},
|
||||
dragStarted(d) {
|
||||
if (!d3.event.active) this.simulation.alphaTarget(0.8).restart()
|
||||
d.x = d3.event.x
|
||||
d.y = d3.event.y
|
||||
d.fx = d.x
|
||||
d.fy = d.y
|
||||
},
|
||||
dragged(d) {
|
||||
d.x = d3.event.x
|
||||
d.y = d3.event.y
|
||||
d.fx = d3.event.x
|
||||
d.fy = d3.event.y
|
||||
},
|
||||
dragEnded(d) {
|
||||
if (!d3.event.active) this.simulation.alphaTarget(0)
|
||||
d.x = d3.event.x
|
||||
d.y = d3.event.y
|
||||
d.fx = d3.event.x
|
||||
d.fy = d3.event.y
|
||||
},
|
||||
zoomed() {
|
||||
d3.selectAll('.node').attr('transform', d3.event.transform)
|
||||
d3.selectAll('.nodeText text').attr('transform', d3.event.transform)
|
||||
d3.selectAll('.line').attr('transform', d3.event.transform)
|
||||
d3.selectAll('.lineText text').attr('transform', d3.event.transform)
|
||||
d3.selectAll('.nodeButton').attr('transform', d3.event.transform)
|
||||
//_this.svg.selectAll("g").attr("transform", d3.event.transform);
|
||||
},
|
||||
zoomClick(direction) {
|
||||
var self = this
|
||||
var factor = 0.2
|
||||
var targetZoom = 1
|
||||
var extent = self.zoom.scaleExtent()
|
||||
targetZoom = 1 + factor * direction
|
||||
if (targetZoom < extent[0] || targetZoom > extent[1]) {
|
||||
return false
|
||||
}
|
||||
self.zoom.scaleBy(self.svg, targetZoom) // 执行该方法后 会触发zoom事件
|
||||
},
|
||||
zoomIn() {
|
||||
this.zoomClick(1)
|
||||
},
|
||||
zoomOut() {
|
||||
this.zoomClick(-1)
|
||||
},
|
||||
refresh() {
|
||||
this.svg.call(this.zoom.transform, d3.zoomIdentity)
|
||||
},
|
||||
showFull() {
|
||||
this.isFullscreen = !this.isFullscreen
|
||||
var full = document.getElementById('kg_container')
|
||||
this.fullScreen(full)
|
||||
},
|
||||
fullScreen(element) {
|
||||
if (element.requestFullscreen) {
|
||||
element.requestFullscreen()
|
||||
} else if (element.mozRequestFullScreen) {
|
||||
element.mozRequestFullScreen()
|
||||
} else if (element.webkitRequestFullscreen) {
|
||||
element.webkitRequestFullscreen()
|
||||
} else if (element.msRequestFullscreen) {
|
||||
element.msRequestFullscreen()
|
||||
}
|
||||
},
|
||||
exitFullScreen() {
|
||||
this.isFullscreen = !this.isFullscreen
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen()
|
||||
} else if (document.mozCancelFullScreen) {
|
||||
document.mozCancelFullScreen()
|
||||
} else if (document.webkitExitFullscreen) {
|
||||
document.webkitExitFullscreen()
|
||||
}
|
||||
},
|
||||
btnCollapseNode() {},
|
||||
btnOpenNode() {},
|
||||
close() {},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.svg-set-box {
|
||||
height: 46px;
|
||||
line-height: 46px;
|
||||
padding-left: 15px;
|
||||
color: #7f7f7f;
|
||||
/* background: #f7f7f7; */
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
}
|
||||
.ctwh-dibmr {
|
||||
display: inline-block;
|
||||
}
|
||||
.ss-d {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: 10px;
|
||||
border-radius: 50%;
|
||||
background: #dedede;
|
||||
}
|
||||
.sd1 {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
.sd2 {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
}
|
||||
.sd3 {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
.sd4 {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.sd-active {
|
||||
background: #08aefc !important;
|
||||
}
|
||||
.toolbar {
|
||||
margin-left: 150px;
|
||||
margin-right: 15px;
|
||||
line-height: 18px;
|
||||
}
|
||||
ul,
|
||||
li {
|
||||
list-style: none;
|
||||
}
|
||||
.toolbar li {
|
||||
float: left;
|
||||
width: 60px;
|
||||
}
|
||||
.notshow {
|
||||
display: none;
|
||||
}
|
||||
.nodeText {
|
||||
pointer-events: all;
|
||||
cursor: pointer;
|
||||
stroke-dasharray: 0 0 0 0;
|
||||
stroke-dashoffset: 10;
|
||||
transition: all ease 0.1s;
|
||||
}
|
||||
.nodeText:hover {
|
||||
stroke-dashoffset: 0;
|
||||
stroke-dasharray: 100;
|
||||
}
|
||||
</style>
|
||||
70
src/components/KGBuilderMenuBlank.vue
Normal file
@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<!-- 空白处右键 -->
|
||||
<ul
|
||||
class="el-dropdown-menu el-popper blankmenubar"
|
||||
@click="menuBarClick"
|
||||
:style="blankMenuStyle"
|
||||
@mouseleave="menuBarLeave"
|
||||
id="blank_menubar"
|
||||
v-show="menuBarShow"
|
||||
>
|
||||
<li class="el-dropdown-menu__item" @click="btnAddSingle">
|
||||
<svg class="icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-jiedian"></use>
|
||||
</svg>
|
||||
<span class="pl-15">添加节点</span>
|
||||
</li>
|
||||
<li class="el-dropdown-menu__item" @click="btnQuickAddNode">
|
||||
<svg class="icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-add-rd"></use>
|
||||
</svg>
|
||||
<span class="pl-15">快速添加</span>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
data: Object
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
top: "0px",
|
||||
left: "0px",
|
||||
menuBarShow: false
|
||||
};
|
||||
},
|
||||
inject: ['quickAddNodes'],
|
||||
components: {},
|
||||
computed: {
|
||||
blankMenuStyle() {
|
||||
return {
|
||||
position: "absolute",
|
||||
top: this.top+'px',
|
||||
left: this.left+'px'
|
||||
};
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init(data) {
|
||||
this.top = data.top;
|
||||
this.left = data.left;
|
||||
this.menuBarShow = data.show;
|
||||
},
|
||||
btnAddSingle() {
|
||||
this.$emit('changeCursor')
|
||||
},
|
||||
btnQuickAddNode() {
|
||||
this.quickAddNodes(this)
|
||||
},
|
||||
menuBarClick() {
|
||||
this.menuBarShow=false;
|
||||
},
|
||||
menuBarLeave() {
|
||||
this.menuBarShow=false;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style></style>
|
||||
63
src/components/KGBuilderMenuLink.vue
Normal file
@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<!-- 连线按钮组 -->
|
||||
<ul
|
||||
v-show="linkMenuShow"
|
||||
id="link_menubar2"
|
||||
class="el-dropdown-menu el-popper linkmenubar"
|
||||
:style="linuMenuStyle"
|
||||
@mouseleave="linkMenuBarLeave"
|
||||
>
|
||||
<li class="el-dropdown-menu__item" @click="updateLink">
|
||||
<span class="pl-15">编辑</span>
|
||||
</li>
|
||||
<li class="el-dropdown-menu__item" @click="deleteLink">
|
||||
<span class="pl-15">删除</span>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// import * as d3 from "d3";
|
||||
export default {
|
||||
inject: ['deleteLinkName', 'updateLinkName'],
|
||||
components: {},
|
||||
props: {},
|
||||
data() {
|
||||
return {
|
||||
top: '0px',
|
||||
left: '0px',
|
||||
linkMenuShow: false,
|
||||
clike: false,
|
||||
data:null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
linuMenuStyle() {
|
||||
return {
|
||||
position: 'absolute',
|
||||
top: this.top + 'px',
|
||||
left: this.left + 'px'
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init(data) {
|
||||
this.top = data.top
|
||||
this.left = data.left
|
||||
this.linkMenuShow = data.show
|
||||
this.data = data.sdata
|
||||
},
|
||||
updateLink() {
|
||||
this.updateLinkName(this.data)
|
||||
},
|
||||
deleteLink() {
|
||||
this.deleteLinkName(this.data)
|
||||
},
|
||||
linkMenuBarLeave() {
|
||||
// d3.select(this).style("display", "none");
|
||||
this.linkMenuShow = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style></style>
|
||||
628
src/components/KGBuilderSimple.vue
Normal file
@ -0,0 +1,628 @@
|
||||
<template>
|
||||
<div>
|
||||
<div id="gid_tc" style="float:left;">
|
||||
<div id="gid"></div>
|
||||
<ul
|
||||
class="el-dropdown-menu el-popper"
|
||||
id="my_custom_menu"
|
||||
style="display: none;"
|
||||
>
|
||||
<li class="el-dropdown-menu__item" @click="btnOpenNode">
|
||||
<svg class="ctwh_icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-zk-all"></use>
|
||||
</svg>
|
||||
<span class="pl-15">展开</span>
|
||||
</li>
|
||||
<li class="el-dropdown-menu__item" @click="btnCollapseNode">
|
||||
<svg class="ctwh_icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-shouqi"></use>
|
||||
</svg>
|
||||
<span class="pl-15">收起</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div
|
||||
class="el-dropdown-menu el-popper"
|
||||
id="richContainer"
|
||||
style="display: none;width: 420px;height: 300px;position: absolute; left: 222px; top: 75px;"
|
||||
>
|
||||
<span
|
||||
@click="close"
|
||||
class="close-x"
|
||||
style="
|
||||
float: right;
|
||||
padding: 4px 8px;
|
||||
cursor: pointer;
|
||||
color: rgb(236, 105, 65);font-size: 16px;margin: -20px -33px 0 0;background: #fff;border-radius: 50%;border: 1px solid #ddd;z-index: 999;font-family: '微软雅黑';"
|
||||
>X</span
|
||||
>
|
||||
<div class="search_result_list_min">
|
||||
<div
|
||||
:key="'m_' + index"
|
||||
v-for="(item, index) in nodeRecordList"
|
||||
class="search_result_item_min"
|
||||
>
|
||||
<div>
|
||||
<div class="datatitle">
|
||||
<a href="javascript:;" @click="linkto(item)"
|
||||
>{{ index + 1 }}.{{ item.条目名称 }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="datasource">
|
||||
<span>{{ item.工具书名称 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="datacontent" @click="linkto(item)" v-html="item.快照"></span>
|
||||
{{ item.快照 }}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mengceng"></div>
|
||||
</div>
|
||||
<div class="svg-set-box clearfix">
|
||||
<div class="ctwh-dibmr">
|
||||
<span>显示范围:</span>
|
||||
<a
|
||||
:key="index"
|
||||
v-for="(m, index) in pageSizeList"
|
||||
@click="setMatchSize(m, this)"
|
||||
href="javascript:void(0)"
|
||||
:class="[m.isActive ? 'sd-active' : '', 'ss-d sd' + (index + 1)]"
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
<div class="ctwh-dibmr" style="float: right">
|
||||
<ul class="toolbar">
|
||||
<li>
|
||||
<a href="javascript:;" @click="zoomin"
|
||||
><span><i class="el-icon-zoom-in"></i>放大</span></a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="javascript:;" @click="zoomout"
|
||||
><span><i class="el-icon-zoom-out"></i>缩小</span></a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href="javascript:;" @click="refresh"
|
||||
><span><i class="el-icon-refresh-right"></i>还原</span></a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
v-if="!isFullscreen"
|
||||
id="fullscreenbtn"
|
||||
href="javascript:;"
|
||||
@click="showFull"
|
||||
>
|
||||
<span><i class="el-icon-full-screen"></i>全屏</span>
|
||||
</a>
|
||||
<a
|
||||
v-else
|
||||
id="fullscreenbtn"
|
||||
href="javascript:;"
|
||||
@click="exitfullscreen"
|
||||
>
|
||||
<span><i class="el-icon-full-screen"></i>退出全屏</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import * as d3 from "d3";
|
||||
import $ from "jquery";
|
||||
|
||||
export default {
|
||||
props: ["pid"],
|
||||
data() {
|
||||
return {
|
||||
qaGraphNode: {},
|
||||
qaGraphNodeText: {},
|
||||
qaGraphLink: {},
|
||||
qaGraphLinkText: {},
|
||||
theme: 0,
|
||||
loading: false,
|
||||
width: 1000,
|
||||
height: 800,
|
||||
gcontainer: {},
|
||||
svg: {},
|
||||
zoom: null,
|
||||
arrowMarker: {},
|
||||
simulation: {},
|
||||
isFullscreen: false,
|
||||
graph: {
|
||||
nodes: [],
|
||||
links: []
|
||||
},
|
||||
colorList: [
|
||||
"#ff8373",
|
||||
"#f9c62c",
|
||||
"#a5ca34",
|
||||
"#6fce7a",
|
||||
"#70d3bd",
|
||||
"#ea91b0"
|
||||
],
|
||||
pageSizeList: [
|
||||
{ size: 100, isActive: false },
|
||||
{ size: 300, isActive: false },
|
||||
{ size: 500, isActive: true },
|
||||
{ size: 1000, isActive: false }
|
||||
],
|
||||
nodeRecordList: []
|
||||
};
|
||||
},
|
||||
components: {},
|
||||
mounted() {
|
||||
this.initGraphContainer();
|
||||
this.addMaker();
|
||||
this.initGraph();
|
||||
},
|
||||
created() {},
|
||||
watch: {},
|
||||
methods: {
|
||||
initGraphContainer() {
|
||||
this.gcontainer = d3.select("#gid");
|
||||
if (this.isFullscreen) {
|
||||
this.width = window.screen.width;
|
||||
this.height = window.screen.height;
|
||||
} else {
|
||||
this.width = $("#" + this.pid).width();
|
||||
this.height = $("#" + this.pid).height();
|
||||
}
|
||||
this.svg = this.gcontainer.append("svg");
|
||||
var sWidth = this.width;
|
||||
var sHeight = this.height;
|
||||
this.svg.attr("width", sWidth);
|
||||
this.svg.attr("height", sHeight);
|
||||
// this.svg.attr("viewBox", "0 0 " + sWidth / 2 + " " + sHeight / 2);
|
||||
this.svg.attr("id", "svg_idx");
|
||||
this.svg.attr("preserveAspectRatio", "xMidYMidmeet");
|
||||
this.simulation = d3
|
||||
.forceSimulation()
|
||||
.force("charge", d3.forceManyBody().strength(-1500))
|
||||
.force(
|
||||
"link",
|
||||
d3
|
||||
.forceLink()
|
||||
.distance(60)
|
||||
.id(function(d) {
|
||||
return d.uuid;
|
||||
})
|
||||
)
|
||||
.force("collide", d3.forceCollide().strength(-30))
|
||||
.force("center", d3.forceCenter(this.width / 2, this.height / 2));
|
||||
this.qaGraphLink = this.svg.append("g").attr("class", "line");
|
||||
this.qaGraphLinkText = this.svg.append("g").attr("class", "lineText");
|
||||
this.qaGraphNode = this.svg.append("g").attr("class", "node");
|
||||
this.qaGraphNodeText = this.svg.append("g").attr("class", "nodeText");
|
||||
},
|
||||
initGraph() {
|
||||
var _this = this;
|
||||
axios.get("/static/kgData.json", {}).then(function(response) {
|
||||
var data = response.data;
|
||||
_this.graph.nodes = data.node;
|
||||
_this.graph.links = data.relationship;
|
||||
_this.updateGraph();
|
||||
});
|
||||
},
|
||||
addMaker() {
|
||||
var arrowMarker = this.svg
|
||||
.append("marker")
|
||||
.attr("id", "arrow")
|
||||
.attr("markerUnits", "strokeWidth")
|
||||
.attr("markerWidth", "12") //
|
||||
.attr("markerHeight", "12")
|
||||
.attr("viewBox", "0 0 12 12")
|
||||
.attr("refX", "30")
|
||||
.attr("refY", "6")
|
||||
.attr("orient", "auto");
|
||||
var arrowPath = "M2,2 L10,6 L2,10 L6,6 L2,2"; // 定义箭头形状
|
||||
arrowMarker
|
||||
.append("path")
|
||||
.attr("d", arrowPath)
|
||||
.attr("fill", "#ccc");
|
||||
},
|
||||
drawnode(node) {
|
||||
var _this = this;
|
||||
var nodeEnter = node.enter().append("circle");
|
||||
nodeEnter.on("click", function(d) {
|
||||
console.log("触发单击:" + d);
|
||||
|
||||
// eslint-disable-next-line no-debugger
|
||||
debugger;
|
||||
_this.opennode();
|
||||
console.log("ddd");
|
||||
//
|
||||
});
|
||||
nodeEnter.on("dblclick", function(d) {
|
||||
event.preventDefault();
|
||||
console.log("触发双击:" + d);
|
||||
});
|
||||
nodeEnter.call(
|
||||
d3
|
||||
.drag()
|
||||
.on("start", _this.dragstarted)
|
||||
.on("drag", _this.dragged)
|
||||
.on("end", _this.dragended)
|
||||
);
|
||||
return nodeEnter;
|
||||
},
|
||||
opennode() {
|
||||
var _this = this;
|
||||
var noddd = [
|
||||
{
|
||||
flag: "1",
|
||||
code: "27301",
|
||||
parentCode: "273",
|
||||
grade: "2",
|
||||
name: "儒家2",
|
||||
uuid: "4617858011"
|
||||
},
|
||||
{
|
||||
code: "2730107",
|
||||
flag: "1",
|
||||
parentCode: "27301",
|
||||
grade: "3",
|
||||
name: "故事轶闻2",
|
||||
uuid: "2636501111"
|
||||
}
|
||||
];
|
||||
var newships = [
|
||||
{
|
||||
sourceid: "46178580",
|
||||
targetid: "2636501111",
|
||||
name: "",
|
||||
targetcode: "2730107",
|
||||
uuid: "91804213",
|
||||
sourcecode: "27301"
|
||||
},
|
||||
{
|
||||
sourceid: "46178580",
|
||||
targetid: "4617858011",
|
||||
name: "",
|
||||
targetcode: "273010723",
|
||||
uuid: "91804389",
|
||||
sourcecode: "2730107"
|
||||
}
|
||||
];
|
||||
_this.graph.nodes = _this.graph.nodes.concat(noddd);
|
||||
_this.graph.links = _this.graph.links.concat(newships);
|
||||
_this.updateGraph();
|
||||
},
|
||||
drawNodeText(nodeText) {
|
||||
var _this = this;
|
||||
var nodeTextEnter = nodeText.enter().append("text");
|
||||
nodeTextEnter.call(
|
||||
d3
|
||||
.drag()
|
||||
.on("start", _this.dragstarted)
|
||||
.on("drag", _this.dragged)
|
||||
.on("end", _this.dragended)
|
||||
);
|
||||
return nodeTextEnter;
|
||||
},
|
||||
drawLink(link) {
|
||||
var _this = this;
|
||||
var linkEnter = link
|
||||
.enter()
|
||||
.append("line")
|
||||
.attr("stroke-width", 1)
|
||||
.attr("stroke", function() {
|
||||
return _this.colorList[2];
|
||||
})
|
||||
.attr("marker-end", "url(#arrow)"); // 箭头
|
||||
return linkEnter;
|
||||
},
|
||||
drawLinkText(linktext) {
|
||||
var linkTextEnter = linktext
|
||||
.enter()
|
||||
.append("text")
|
||||
.attr("class", "lineText")
|
||||
.style("fill", "#875034")
|
||||
.style("font-size", "16px")
|
||||
.text(function(d) {
|
||||
return d.lk.name;
|
||||
});
|
||||
return linkTextEnter;
|
||||
},
|
||||
updateGraph() {
|
||||
var _this = this;
|
||||
var lks = _this.graph.links;
|
||||
var nodes = _this.graph.nodes;
|
||||
nodes.forEach(function(n) {
|
||||
if (n.center === 1 || n.center === "1") {
|
||||
n.fx = _this.width / 2;
|
||||
n.fy = _this.height / 2;
|
||||
}
|
||||
if (typeof n.fx === "undefined" || n.fx === "") {
|
||||
n.fx = null;
|
||||
}
|
||||
if (typeof n.fy === "undefined" || n.fy === "") {
|
||||
n.fy = null;
|
||||
}
|
||||
});
|
||||
var links = [];
|
||||
// eslint-disable-next-line no-debugger
|
||||
debugger;
|
||||
lks.forEach(function(m) {
|
||||
var sourceNode = nodes.filter(function(n) {
|
||||
return n.uuid === m.sourceid;
|
||||
})[0];
|
||||
if (typeof sourceNode === "undefined") return;
|
||||
var targetNode = nodes.filter(function(n) {
|
||||
return n.uuid === m.targetid;
|
||||
})[0];
|
||||
if (typeof targetNode === "undefined") return;
|
||||
links.push({ source: sourceNode.uuid, target: targetNode.uuid, lk: m });
|
||||
});
|
||||
// 更新节点
|
||||
//_this.qaGraphNode = _this.drawnode(nodes);
|
||||
var node = _this.qaGraphNode.selectAll("circle").data(nodes, function(d) {
|
||||
return d.uuid;
|
||||
});
|
||||
node.exit().remove();
|
||||
var nodeEnter = _this.drawnode(node);
|
||||
node = nodeEnter.merge(node).text(function(d) {
|
||||
return d.name;
|
||||
});
|
||||
node.attr("r", 25);
|
||||
node.attr("fill", "red");
|
||||
node
|
||||
.append("title") // 为每个节点设置title
|
||||
.text(function(d) {
|
||||
return d.name;
|
||||
});
|
||||
// 更新节点文字
|
||||
//_this.qaGraphNodeText = _this.drawNodeText(nodes);
|
||||
var nodeText = _this.qaGraphNodeText
|
||||
.selectAll("text")
|
||||
.data(nodes, function(d) {
|
||||
return d.uuid;
|
||||
});
|
||||
nodeText.exit().remove();
|
||||
var nodeTextEnter = _this.drawNodeText(nodeText);
|
||||
nodeText = nodeTextEnter.merge(nodeText).text(function(d) {
|
||||
return d.name;
|
||||
});
|
||||
nodeText
|
||||
.style("fill", function() {
|
||||
if (_this.theme === 0) {
|
||||
return "#333";
|
||||
} else {
|
||||
return "#fff";
|
||||
}
|
||||
})
|
||||
.attr("class", "nodeText")
|
||||
.attr("dy", "3.6em")
|
||||
.attr("font-family", "宋体")
|
||||
.attr("font-size", 16)
|
||||
.attr("text-anchor", "middle")
|
||||
.text(function(d) {
|
||||
return d.name;
|
||||
});
|
||||
nodeText
|
||||
.append("title") // 为每个节点设置title
|
||||
.text(function(d) {
|
||||
if (typeof d.name !== "undefined") {
|
||||
return d.name;
|
||||
}
|
||||
return "";
|
||||
});
|
||||
// 更新连线 links
|
||||
// _this.qaGraphLink = _this.drawLink(links);
|
||||
var link = _this.qaGraphLink.selectAll("line").data(links, function(d) {
|
||||
return d.uuid;
|
||||
});
|
||||
link.exit().remove();
|
||||
var linkEnter = _this.drawLink(link);
|
||||
link = linkEnter.merge(link);
|
||||
// 更新连线文字
|
||||
//_this.qaGraphLinkText = _this.drawLinkText(links);
|
||||
var linktext = _this.qaGraphLinkText
|
||||
.selectAll("text")
|
||||
.data(links, function(d) {
|
||||
return d.uuid;
|
||||
});
|
||||
linktext.exit().remove();
|
||||
var linkTextEnter = _this.drawLinkText(linktext);
|
||||
linktext = linkTextEnter.merge(linktext).text(function(d) {
|
||||
return d.lk.name;
|
||||
});
|
||||
_this.simulation
|
||||
.nodes(nodes)
|
||||
.alphaTarget(0)
|
||||
.alphaDecay(0.05)
|
||||
.on("tick", ticked);
|
||||
function ticked() {
|
||||
// 更新连线坐标
|
||||
link
|
||||
.attr("x1", function(d) {
|
||||
return d.source.x;
|
||||
})
|
||||
.attr("y1", function(d) {
|
||||
return d.source.y;
|
||||
})
|
||||
.attr("x2", function(d) {
|
||||
return d.target.x;
|
||||
})
|
||||
.attr("y2", function(d) {
|
||||
return d.target.y;
|
||||
});
|
||||
// 刷新连接线上的文字位置
|
||||
linktext
|
||||
.attr("x", function(d) {
|
||||
if (
|
||||
typeof d.source.x === "undefined" ||
|
||||
typeof d.target.x === "undefined"
|
||||
)
|
||||
return 0;
|
||||
var x = (parseFloat(d.source.x) + parseFloat(d.target.x)) / 2;
|
||||
return x;
|
||||
})
|
||||
.attr("y", function(d) {
|
||||
if (
|
||||
typeof d.source.y === "undefined" ||
|
||||
typeof d.target.y === "undefined"
|
||||
)
|
||||
return 0;
|
||||
var y = (parseFloat(d.source.y) + parseFloat(d.target.y)) / 2;
|
||||
return y;
|
||||
});
|
||||
// 更新节点坐标
|
||||
node
|
||||
.attr("cx", function(d) {
|
||||
return d.x;
|
||||
})
|
||||
.attr("cy", function(d) {
|
||||
return d.y;
|
||||
});
|
||||
// 更新文字坐标
|
||||
nodeText
|
||||
.attr("x", function(d) {
|
||||
return d.x;
|
||||
})
|
||||
.attr("y", function(d) {
|
||||
return d.y;
|
||||
});
|
||||
}
|
||||
|
||||
_this.simulation.force("link").links(links);
|
||||
_this.simulation.force(
|
||||
"center",
|
||||
d3.forceCenter(_this.width / 2, _this.height / 2)
|
||||
);
|
||||
_this.simulation.alpha(1).restart();
|
||||
// 鼠标滚轮缩放
|
||||
_this.zoom = d3
|
||||
.zoom()
|
||||
.scaleExtent([0.1, 4])
|
||||
.on("zoom", _this.zoomed);
|
||||
_this.svg.call(_this.zoom);
|
||||
_this.svg.on("dblclick.zoom", null); // 静止双击缩放
|
||||
},
|
||||
dragstarted(d) {
|
||||
if (!d3.event.active) this.simulation.alphaTarget(0.3).restart();
|
||||
d.fx = d.x;
|
||||
d.fy = d.y;
|
||||
},
|
||||
dragged(d) {
|
||||
d.fx = d3.event.x;
|
||||
d.fy = d3.event.y;
|
||||
},
|
||||
dragended(d) {
|
||||
if (!d3.event.active) this.simulation.alphaTarget(0);
|
||||
d.fx = d3.event.x;
|
||||
d.fy = d3.event.y;
|
||||
},
|
||||
zoomed() {
|
||||
this.svg.selectAll("g").attr("transform", d3.event.transform);
|
||||
},
|
||||
zoomClick(direction) {
|
||||
var self = this;
|
||||
var factor = 0.2;
|
||||
var targetZoom = 1;
|
||||
var extent = self.zoom.scaleExtent();
|
||||
targetZoom = 1 + factor * direction;
|
||||
if (targetZoom < extent[0] || targetZoom > extent[1]) {
|
||||
return false;
|
||||
}
|
||||
self.zoom.scaleBy(self.svg, targetZoom); // 执行该方法后 会触发zoom事件
|
||||
},
|
||||
zoomin() {
|
||||
this.zoomClick(1);
|
||||
},
|
||||
zoomout() {
|
||||
this.zoomClick(-1);
|
||||
},
|
||||
refresh() {
|
||||
this.svg.call(this.zoom.transform, d3.zoomIdentity);
|
||||
},
|
||||
showFull() {
|
||||
this.isFullscreen = !this.isFullscreen;
|
||||
var full = document.getElementById("kg_container");
|
||||
this.fullscreen(full);
|
||||
},
|
||||
fullscreen(element) {
|
||||
if (element.requestFullscreen) {
|
||||
element.requestFullscreen();
|
||||
} else if (element.mozRequestFullScreen) {
|
||||
element.mozRequestFullScreen();
|
||||
} else if (element.webkitRequestFullscreen) {
|
||||
element.webkitRequestFullscreen();
|
||||
} else if (element.msRequestFullscreen) {
|
||||
element.msRequestFullscreen();
|
||||
}
|
||||
},
|
||||
exitfullscreen() {
|
||||
this.isFullscreen = !this.isFullscreen;
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen();
|
||||
} else if (document.mozCancelFullScreen) {
|
||||
document.mozCancelFullScreen();
|
||||
} else if (document.webkitExitFullscreen) {
|
||||
document.webkitExitFullscreen();
|
||||
}
|
||||
},
|
||||
btnCollapseNode() {},
|
||||
btnOpenNode() {},
|
||||
close() {}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.svg-set-box {
|
||||
height: 46px;
|
||||
line-height: 46px;
|
||||
padding-left: 15px;
|
||||
color: #7f7f7f;
|
||||
/* background: #f7f7f7; */
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
}
|
||||
.ctwh-dibmr {
|
||||
display: inline-block;
|
||||
}
|
||||
.ss-d {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: 10px;
|
||||
border-radius: 50%;
|
||||
background: #dedede;
|
||||
}
|
||||
.sd1 {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
.sd2 {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
}
|
||||
.sd3 {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
.sd4 {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.sd-active {
|
||||
background: #08aefc !important;
|
||||
}
|
||||
.toolbar {
|
||||
margin-left: 150px;
|
||||
margin-right: 15px;
|
||||
line-height: 18px;
|
||||
}
|
||||
ul,
|
||||
li {
|
||||
list-style: none;
|
||||
}
|
||||
.toolbar li {
|
||||
float: left;
|
||||
width: 60px;
|
||||
}
|
||||
</style>
|
||||
1481
src/components/KGBuilder_v1.vue
Normal file
56
src/components/KGFocus.vue
Normal file
@ -0,0 +1,56 @@
|
||||
<!--
|
||||
* @Description: file content
|
||||
* @Author: tc
|
||||
* @Date: 2022-01-01 12:53:00
|
||||
* @LastEditors: your name
|
||||
* @LastEditTime: 2022-01-01 14:06:07
|
||||
-->
|
||||
<template>
|
||||
<div id="follow-us" class="guanzhu" style="padding: 20px;">
|
||||
<h2 class="hometitle"></h2>
|
||||
<ul>
|
||||
<li class="wx">
|
||||
<img
|
||||
src="@/assets/中国一重.png" style="width: 100%;" />
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
data: Object
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
};
|
||||
},
|
||||
mounted() {},
|
||||
components: {},
|
||||
methods: {
|
||||
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.guanzhu ul li {
|
||||
font-size: 12px;
|
||||
margin-bottom: 10px;
|
||||
background: #fff;
|
||||
color: #525252;
|
||||
line-height: 40px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 2px;
|
||||
position: relative;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
.guanzhu .wx img {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
107
src/components/KGHeader.vue
Normal file
@ -0,0 +1,107 @@
|
||||
<!--
|
||||
* @Description: file content
|
||||
* @Author: your name
|
||||
* @Date: 2022-01-01 12:53:00
|
||||
* @LastEditors: your name
|
||||
* @LastEditTime: 2022-01-01 13:43:38
|
||||
-->
|
||||
<template>
|
||||
<div class="menu">
|
||||
<nav class="nav" id="topnav">
|
||||
<h1 class="logo"><a href="/">中国一重</a></h1>
|
||||
<ul style="float: left;margin-left: 60px;padding: 10px;">
|
||||
<template v-for="nav in navList">
|
||||
<li @mouseover="selectStyle(nav)" >
|
||||
<a
|
||||
:href="nav.linkUrl"
|
||||
>{{ nav.title }}</a
|
||||
>
|
||||
<ul class="sub-nav" v-if="nav.childrens" v-show="nav.active" @mouseout="outStyle(nav)">
|
||||
<li v-for="children in nav.childrens">
|
||||
<a :href="children.linkUrl">{{ children.title }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
data: Object
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
navList: []
|
||||
};
|
||||
},
|
||||
mounted() {},
|
||||
components: {},
|
||||
methods: {
|
||||
selectStyle(nav) {
|
||||
var _this = this;
|
||||
this.$nextTick(function() {
|
||||
_this.navList.forEach(function(item) {
|
||||
item.active = false;
|
||||
});
|
||||
nav.active = true;
|
||||
});
|
||||
},
|
||||
outStyle(nav) {
|
||||
nav.active = false;
|
||||
},
|
||||
changeIcon() {
|
||||
this.isOpen = !this.isOpen;
|
||||
},
|
||||
searchActive() {
|
||||
this.search_active = !this.search_active;
|
||||
},
|
||||
clickNav(nav) {
|
||||
nav.active = !nav.active;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
#topnav_current { color: #00A7EB; }
|
||||
.menu { height: 76px; width: 100%; background-color: #000; }
|
||||
.nav { height: 80px; width: 100%; margin: 0 auto; }
|
||||
.nav li { float: left; position: relative; }
|
||||
.nav li a { color: #bdbdbd; padding: 0 10px; display: inline-block; text-decoration:none}
|
||||
.nav li a:hover { color: #fff; }
|
||||
.nav li .sub-nav { position: absolute; top: 30px; width: 120px; background: #FFF; left: -20px; /* display: none; */z-index: 9999}
|
||||
.nav li .sub-nav li { clear: left; height: 20px; line-height: 35px; position: relative; width: 200px; padding: 5px 20px }
|
||||
.nav li .sub-nav li a { font-size: 15px; font-weight: 400; color: #404040; line-height: 28px; }
|
||||
.nav li .sub-nav li a:hover { color: #000; border-left: 2px solid #000; }
|
||||
.a_active { color: #00A7EB !important; }
|
||||
.logo { float: left;margin-left: 70px;width: 260px;font-size: 26px;}
|
||||
.logo a { color: #00A7EB;text-decoration: none; }
|
||||
.hometitle {
|
||||
font-size: 18px;
|
||||
color: #282828;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
text-transform: uppercase;
|
||||
padding-bottom: 15px;
|
||||
margin-bottom: 25px;
|
||||
position: relative;
|
||||
}
|
||||
.hometitle:after {
|
||||
content: "";
|
||||
background-color: #282828;
|
||||
left: 0;
|
||||
width: 50px;
|
||||
height: 2px;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
-webkit-transition: 0.5s;
|
||||
-moz-transition: 0.5s;
|
||||
-ms-transition: 0.5s;
|
||||
-o-transition: 0.5s;
|
||||
transition: 0.5s;
|
||||
}
|
||||
</style>
|
||||
283
src/components/KGTable.vue
Normal file
@ -0,0 +1,283 @@
|
||||
<template>
|
||||
<div class="kg-table" ref="kgTable">
|
||||
<div
|
||||
ref="tableOperate"
|
||||
v-show="buttonGroup"
|
||||
class="table-operate button-group"
|
||||
>
|
||||
<slot name="button-group"></slot>
|
||||
</div>
|
||||
<div class="pagi-table" ref="pagiTable">
|
||||
<p
|
||||
class="notice mt10"
|
||||
v-show="
|
||||
multipleSelection.length > 0 && config.selection && !config.notShowNum
|
||||
"
|
||||
>
|
||||
|
||||
已选择<span>{{ multipleSelection.length }}</span
|
||||
>项
|
||||
</p>
|
||||
<el-table
|
||||
ref="table"
|
||||
stripe
|
||||
size="medium"
|
||||
v-loading="loading"
|
||||
class="tableStyle"
|
||||
:data="pageObj.list"
|
||||
style="width: 100%"
|
||||
v-bind="tableBind"
|
||||
:row-key="config.rowKey || 'id'"
|
||||
@selection-change="onSelectionChange"
|
||||
@select="onSelectChange"
|
||||
@select-all="onSelectAllChange"
|
||||
>
|
||||
<el-table-column v-if="config.selection" type="selection" width="55">
|
||||
</el-table-column>
|
||||
|
||||
<template v-for="item in columns">
|
||||
<!-- 自定义 header -->
|
||||
<el-table-column
|
||||
:key="item.prop"
|
||||
v-if="item.type === 'slotHeader'"
|
||||
v-bind="item"
|
||||
:show-overflow-tooltip="item.tooltip"
|
||||
>
|
||||
<template slot="header">
|
||||
<slot :name="'header' + item.prop">
|
||||
{{ item.label }}
|
||||
</slot>
|
||||
</template>
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row[item.prop] }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- slot渲染 -->
|
||||
<el-table-column
|
||||
:key="'slot-' + item.prop"
|
||||
v-else-if="item.type === 'slot'"
|
||||
:label="item.label"
|
||||
:prop="item.prop"
|
||||
:width="item.width"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<slot :name="item.slotName" :row="scope.row"></slot>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 正常渲染 -->
|
||||
<el-table-column
|
||||
v-else
|
||||
:key="'normal-' + item.prop"
|
||||
:label="item.label"
|
||||
:prop="item.prop"
|
||||
:align="item.align ? item.align : 'left'"
|
||||
:width="item.width"
|
||||
:show-overflow-tooltip="true"
|
||||
>
|
||||
</el-table-column>
|
||||
</template>
|
||||
|
||||
<el-table-column
|
||||
v-if="operation && operation.label"
|
||||
:label="operation.label"
|
||||
:width="operation.width"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<span v-if="operation.type !== 'link'">
|
||||
<template v-for="(item, index) in operation.options">
|
||||
<el-button
|
||||
:key="'operation' + index"
|
||||
:icon="item.icon"
|
||||
:size="item.size"
|
||||
:type="item.type"
|
||||
:disabled="
|
||||
item.disabled &&
|
||||
scope.row[item.disabled.field] === item.disabled.value
|
||||
"
|
||||
@click="handleClick(item.method, scope.row)"
|
||||
v-if="
|
||||
!item.hide ||
|
||||
!item.hide.value.includes(scope.row[item.hide.field])
|
||||
"
|
||||
><svg-icon :icon="item.icon"></svg-icon
|
||||
>{{ item.label }}</el-button
|
||||
>
|
||||
</template>
|
||||
</span>
|
||||
<span
|
||||
v-else-if="operation.doubleTitle && operation.type === 'link'"
|
||||
>
|
||||
<el-link
|
||||
class="btn-a"
|
||||
v-for="(item, index) in operation.options"
|
||||
:key="'operation' + index"
|
||||
v-bind="item"
|
||||
@click="handleClick(item.method, scope.row)"
|
||||
>
|
||||
<svg-icon :icon="item.icon"></svg-icon>
|
||||
{{
|
||||
scope.row[item.flag] === item.realValue
|
||||
? item.values.real
|
||||
: item.values.unde
|
||||
}}</el-link
|
||||
>
|
||||
</span>
|
||||
<span v-else>
|
||||
<el-link
|
||||
class="btn-a"
|
||||
v-for="(item, index) in operation.options"
|
||||
:key="'operation' + index"
|
||||
v-bind="item"
|
||||
@click="handleClick(item.method, scope.row)"
|
||||
>
|
||||
<svg-icon :icon="item.icon"></svg-icon>
|
||||
{{ item.label }}</el-link
|
||||
>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<Pagination
|
||||
v-if="config.pagination"
|
||||
:total="pageObj.totalCount"
|
||||
layout="sizes, prev, pager, next, jumper"
|
||||
:page.sync="pageObj.currentPage"
|
||||
:totalPage="pageObj.totalPage"
|
||||
:limit.sync="pageObj.pageSize"
|
||||
@pagination="getPage"
|
||||
></Pagination>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'kgTable',
|
||||
props: {
|
||||
buttonGroup: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
columns: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
pageObj: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({
|
||||
currentPage: 1,
|
||||
pageSize: 15,
|
||||
list: [],
|
||||
totalCount: 0
|
||||
})
|
||||
},
|
||||
operation: {
|
||||
type: Object
|
||||
},
|
||||
config: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
pagination: true, // 是否显示分页
|
||||
selection: true, // 多选
|
||||
rowKey: 'id', // table row-key配置参数
|
||||
notUseMaxHeight: false
|
||||
})
|
||||
}
|
||||
},
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
multipleSelection: [],
|
||||
maxHeight: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
tableBind() {
|
||||
const obj = {}
|
||||
if (!this.config.notUseMaxHeight) {
|
||||
obj['max-height'] = this.maxHeight
|
||||
}
|
||||
return obj
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'pageObj.list': {
|
||||
deep: true,
|
||||
handler() {
|
||||
this.multipleSelection.length = 0
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {},
|
||||
mounted() {
|
||||
this.$nextTick(function () {
|
||||
const operateHeight = this.$refs.tableOperate.clientHeight
|
||||
this.maxHeight =
|
||||
this.$refs.kgTable.clientHeight -
|
||||
108 -
|
||||
operateHeight -
|
||||
10 +
|
||||
(this.config.pagination ? 0 : 64) +
|
||||
'px'
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
onSelectionChange(selection) {
|
||||
this.multipleSelection = selection
|
||||
this.$emit('selection-change', this.multipleSelection)
|
||||
},
|
||||
onSelectChange(selection, row) {
|
||||
this.$emit('select', selection, row)
|
||||
},
|
||||
onSelectAllChange(selection) {
|
||||
this.$emit('select-all', selection)
|
||||
},
|
||||
getPage({ page, limit }) {
|
||||
this.$emit('pagination', { page, limit })
|
||||
},
|
||||
handleClick(method, row) {
|
||||
this.$emit('handleClick', { method, row })
|
||||
},
|
||||
toggleRowSelection(row, selected) {
|
||||
this.$refs.table.toggleRowSelection(row, selected)
|
||||
}
|
||||
},
|
||||
beforeDestroy() {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.kg-table {
|
||||
height: 100%;
|
||||
.btn-a {
|
||||
font-size: 14px;
|
||||
padding: 3px 10px;
|
||||
text-decoration: none;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
.icon {
|
||||
margin-right: 3px;
|
||||
vertical-align: -3px;
|
||||
}
|
||||
&:hover {
|
||||
color: #105aac;
|
||||
background-color: #f3f5f7;
|
||||
border-color: #d3deeb;
|
||||
}
|
||||
&:focus {
|
||||
color: #fff;
|
||||
background-color: #1264bf;
|
||||
border-color: #1264bf;
|
||||
}
|
||||
}
|
||||
/deep/ .el-link.is-underline:hover:after {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
80
src/components/KGWanted.vue
Normal file
@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="反馈"
|
||||
:visible.sync="dialogVisible"
|
||||
width="70%"
|
||||
customClass="flowHelp"
|
||||
>
|
||||
<el-alert
|
||||
style="margin:10px"
|
||||
title="有点迫不及待了吧"
|
||||
type="success"
|
||||
description="不听不听,王八念经,黑灰化肥会挥发发灰黑化肥挥发;灰黑化肥会挥发发黑灰化肥发挥。 黑灰化肥会挥发发灰黑化肥黑灰挥发化为灰……"
|
||||
>
|
||||
</el-alert>
|
||||
<el-form ref="form" :model="form" label-width="80px">
|
||||
<el-form-item label="反馈主题">
|
||||
<el-input v-model="form.name"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="反馈类型">
|
||||
<el-select v-model="form.type" placeholder="请选择反馈类型">
|
||||
<el-option label="需求" value="0"></el-option>
|
||||
<el-option label="bug" value="1"></el-option>
|
||||
<el-option label="表扬" value="2"></el-option>
|
||||
<el-option label="加入" value="3"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="内容">
|
||||
<el-input type="textarea" v-model="form.desc" rows="6" :autosize="{ minRows: 6, maxRows: 10}" placeholder="简短描述一下吧,我应该会选择性看到,看到了我也不见得做,O(∩_∩)O哈哈~"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="联系方式">
|
||||
<el-input v-model="form.email"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="onSubmit">立即创建</el-button>
|
||||
<el-button @click="dialogVisible=!dialogVisible">取消</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { kgBuilderApi } from "@/api";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
form:{
|
||||
name:"",
|
||||
type:"0",
|
||||
desc:"",
|
||||
email:""
|
||||
}
|
||||
};
|
||||
},
|
||||
components: {},
|
||||
methods: {
|
||||
init() {
|
||||
this.dialogVisible = true;
|
||||
},
|
||||
onSubmit(){
|
||||
let data = this.form;
|
||||
kgBuilderApi.feedBack(data).then(result => {
|
||||
if (result.code == 200) {
|
||||
this.dialogVisible=false;
|
||||
this.$message({
|
||||
message: "操作成功",
|
||||
type: "success"
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.flowHelp {
|
||||
height: 80%;
|
||||
}
|
||||
</style>
|
||||
36
src/components/SvgIcon.vue
Normal file
@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<svg class="icon svg-icon" aria-hidden="true" :style="ClassStyle">
|
||||
<use :xlink:href="IconName"></use>
|
||||
</svg>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'svg-icon',
|
||||
props: {
|
||||
size: {
|
||||
type: [Number, String],
|
||||
default: 16
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
fill: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
ClassStyle () {
|
||||
return `width:${parseInt(this.size)};height:${parseInt(this.size)};fill:${this.fill};`
|
||||
},
|
||||
IconName () {
|
||||
return `#${this.icon}`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
10
src/components/index.js
Normal file
@ -0,0 +1,10 @@
|
||||
/*
|
||||
* 动态注册全局组件
|
||||
*/
|
||||
const allComponents = require.context('./', true, /\.vue$/)
|
||||
|
||||
export default Vue => {
|
||||
allComponents.keys().forEach(item => {
|
||||
Vue.component(item.replace(/\.\//, '').replace(/\.vue$/, ''), allComponents(item).default)
|
||||
})
|
||||
}
|
||||
4
src/config/index.js
Normal file
@ -0,0 +1,4 @@
|
||||
// base url
|
||||
const BASE_URL = process.env.VUE_APP_BASE_URL;
|
||||
|
||||
export { BASE_URL };
|
||||
18
src/main.js
Normal file
@ -0,0 +1,18 @@
|
||||
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 axios from "axios";
|
||||
import components from './components/index'
|
||||
|
||||
Vue.prototype.$http = axios; //正确的使用
|
||||
Vue.config.productionTip = false;
|
||||
Vue.use(ElementUI);
|
||||
Vue.use(components)
|
||||
new Vue({
|
||||
router,
|
||||
store,
|
||||
render: h => h(App)
|
||||
}).$mount("#app");
|
||||
55
src/router/index.js
Normal file
@ -0,0 +1,55 @@
|
||||
import Vue from "vue";
|
||||
import VueRouter from "vue-router";
|
||||
import Home from "../views/Home.vue";
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: "/",
|
||||
name: "Home",
|
||||
component: () => import("../views/kgbuilder/index_v1.vue")
|
||||
},
|
||||
{
|
||||
path: "/builder",
|
||||
name: "builder",
|
||||
component: () => import("../views/kgbuilder/index.vue")
|
||||
},
|
||||
{
|
||||
path: "/kg",
|
||||
name: "kg",
|
||||
component: () => import("../views/kgbuilder/index_v1.vue")
|
||||
},
|
||||
{
|
||||
path: "/er",
|
||||
name: "er",
|
||||
component: () => import("../views/erbuilder/index.vue")
|
||||
},
|
||||
{
|
||||
path: "/ds",
|
||||
name: "ds",
|
||||
component: () => import("../views/datasource/index.vue")
|
||||
},
|
||||
{
|
||||
path: "/icon",
|
||||
name: "icon",
|
||||
component: () => import("../views/icon/index.vue")
|
||||
},
|
||||
{
|
||||
path: "/about",
|
||||
name: "About",
|
||||
// route level code-splitting
|
||||
// this generates a separate chunk (about.[hash].js) for this route
|
||||
// which is lazy-loaded when the route is visited.
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "about" */ "../views/About.vue")
|
||||
}
|
||||
];
|
||||
|
||||
const router = new VueRouter({
|
||||
mode: "history",
|
||||
base: process.env.BASE_URL,
|
||||
routes
|
||||
});
|
||||
|
||||
export default router;
|
||||
31
src/settings.js
Normal file
@ -0,0 +1,31 @@
|
||||
module.exports = {
|
||||
title: "Dream it possible",
|
||||
|
||||
/**
|
||||
* 是否系统布局配置
|
||||
*/
|
||||
showSettings: false,
|
||||
|
||||
/**
|
||||
* 是否显示 tagsView
|
||||
*/
|
||||
tagsView: true,
|
||||
|
||||
/**
|
||||
* 是否固定头部
|
||||
*/
|
||||
fixedHeader: false,
|
||||
|
||||
/**
|
||||
* 是否显示logo
|
||||
*/
|
||||
sidebarLogo: true,
|
||||
|
||||
/**
|
||||
* @type {string | array} 'production' | ['production', 'development']
|
||||
* @description Need show err logs component.
|
||||
* The default is only used in the production env
|
||||
* If you want to also use it in dev, you can pass ['production', 'development']
|
||||
*/
|
||||
errorLog: "production",
|
||||
};
|
||||
11
src/store/index.js
Normal file
@ -0,0 +1,11 @@
|
||||
import Vue from "vue";
|
||||
import Vuex from "vuex";
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
export default new Vuex.Store({
|
||||
state: {},
|
||||
mutations: {},
|
||||
actions: {},
|
||||
modules: {}
|
||||
});
|
||||
39
src/utils/BaseAPI.js
Normal file
@ -0,0 +1,39 @@
|
||||
import request from '@/utils/request'
|
||||
import qs from 'qs'
|
||||
|
||||
export default class BaseAPI {
|
||||
get (url, params) {
|
||||
return request({ url, method: 'GET', params })
|
||||
}
|
||||
|
||||
post (url, data, config) {
|
||||
let temp
|
||||
|
||||
if (
|
||||
config &&
|
||||
config?.headers &&
|
||||
(config?.headers['Content-Type'].indexOf('application/json') !== -1 ||
|
||||
config?.headers['Content-Type'].indexOf('multipart/form-data') !== -1)
|
||||
) {
|
||||
temp = data
|
||||
} else {
|
||||
temp = qs.stringify(data)
|
||||
}
|
||||
return request(Object.assign({ url, method: 'POST', data: temp }, config))
|
||||
}
|
||||
|
||||
put(url, data, config) {
|
||||
let temp
|
||||
if (
|
||||
config &&
|
||||
config?.headers &&
|
||||
(config?.headers['Content-Type'].indexOf('application/json') !== -1 ||
|
||||
config?.headers['Content-Type'].indexOf('multipart/form-data') !== -1)
|
||||
) {
|
||||
temp = data
|
||||
} else {
|
||||
temp = qs.stringify(data)
|
||||
}
|
||||
return request(Object.assign({ url, method: 'PUT', data: temp }, config))
|
||||
}
|
||||
}
|
||||
15
src/utils/auth.js
Normal file
@ -0,0 +1,15 @@
|
||||
import Cookies from "js-cookie";
|
||||
|
||||
const TokenKey = "Admin-Token";
|
||||
|
||||
export function getToken() {
|
||||
return Cookies.get(TokenKey);
|
||||
}
|
||||
|
||||
export function setToken(token) {
|
||||
return Cookies.set(TokenKey, token);
|
||||
}
|
||||
|
||||
export function removeToken() {
|
||||
return Cookies.remove(TokenKey);
|
||||
}
|
||||
6
src/utils/errorCode.js
Normal file
@ -0,0 +1,6 @@
|
||||
export default {
|
||||
"401": "认证失败,无法访问系统资源",
|
||||
"403": "当前操作没有权限",
|
||||
"404": "访问资源不存在",
|
||||
default: "系统未知错误,请反馈给管理员"
|
||||
};
|
||||
39
src/utils/event-bus.js
Normal file
@ -0,0 +1,39 @@
|
||||
|
||||
class EventPublic {
|
||||
constructor() {
|
||||
this.event = {}
|
||||
}
|
||||
$on(type, callback) {
|
||||
if (!this.event[type]) {
|
||||
this.event[type] = [callback]
|
||||
} else {
|
||||
this.event[type].push(callback)
|
||||
}
|
||||
}
|
||||
$emit(type, ...args) {
|
||||
if (!this.event[type]) {
|
||||
return
|
||||
}
|
||||
this.event[type].forEach(res => {
|
||||
res.apply(this, args)
|
||||
})
|
||||
}
|
||||
$off(type, callback) {
|
||||
if (!this.event[type]) {
|
||||
return
|
||||
}
|
||||
this.event[type] = this.event[type].filter(res => {
|
||||
return res != callback
|
||||
})
|
||||
}
|
||||
// 执行一次
|
||||
$once(type, callback) {
|
||||
function f() {
|
||||
callback()
|
||||
this.$off(type, f)
|
||||
}
|
||||
this.$on(type, f)
|
||||
}
|
||||
}
|
||||
export const EventBus = new EventPublic()
|
||||
|
||||
107
src/utils/request.js
Normal file
@ -0,0 +1,107 @@
|
||||
import axios from "axios";
|
||||
import { Notification, MessageBox, Message } from "element-ui";
|
||||
import store from "@/store";
|
||||
import { getToken } from "@/utils/auth";
|
||||
import errorCode from "@/utils/errorCode";
|
||||
|
||||
const queue = [] // 请求队列
|
||||
// 创建axios实例
|
||||
const service = axios.create({
|
||||
// axios中请求配置有baseURL选项,表示请求URL公共部分
|
||||
baseURL: process.env.VUE_APP_BASE_API,
|
||||
// 超时
|
||||
timeout: 10 * 60 * 1000,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'
|
||||
}
|
||||
});
|
||||
// 取消重复请求
|
||||
const removeRepeatRequest = config => {
|
||||
for (const key in queue) {
|
||||
const index = +key
|
||||
const item = queue[key]
|
||||
|
||||
if (
|
||||
item.url === config.url &&
|
||||
item.method === config.method &&
|
||||
JSON.stringify(item.params) === JSON.stringify(config.params) &&
|
||||
JSON.stringify(item.data) === JSON.stringify(config.data)
|
||||
) {
|
||||
// 执行取消操作
|
||||
item.cancel('操作太频繁,请稍后再试')
|
||||
queue.splice(index, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
// request拦截器
|
||||
service.interceptors.request.use(
|
||||
config => {
|
||||
// 是否需要设置 token
|
||||
const isToken = (config.headers || {}).isToken === false;
|
||||
if (getToken() && !isToken) {
|
||||
config.headers["Authorization"] = "Bearer " + getToken(); // 让每个请求携带自定义token 请根据实际情况自行修改
|
||||
}
|
||||
return config;
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// 响应拦截器
|
||||
service.interceptors.response.use(
|
||||
res => {
|
||||
// 未设置状态码则默认成功状态
|
||||
const code = res.data.code || 200;
|
||||
// 获取错误信息
|
||||
const msg = errorCode[code] || res.data.msg || errorCode["default"];
|
||||
if (code === 401) {
|
||||
MessageBox.confirm(
|
||||
"登录状态已过期,您可以继续留在该页面,或者重新登录",
|
||||
"系统提示",
|
||||
{
|
||||
confirmButtonText: "重新登录",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}
|
||||
).then(() => {
|
||||
store.dispatch("LogOut").then(() => {
|
||||
location.href = "/index";
|
||||
});
|
||||
});
|
||||
} else if (code === 500) {
|
||||
Message({
|
||||
message: msg,
|
||||
type: "error"
|
||||
});
|
||||
return Promise.reject(new Error(msg));
|
||||
} else if (code !== 200) {
|
||||
Notification.error({
|
||||
title: msg
|
||||
});
|
||||
return Promise.reject("error");
|
||||
} else {
|
||||
return res.data;
|
||||
}
|
||||
},
|
||||
error => {
|
||||
console.log("err" + error);
|
||||
let { message } = error;
|
||||
if (message == "Network Error") {
|
||||
message = "后端接口连接异常";
|
||||
} else if (message.includes("timeout")) {
|
||||
message = "系统接口请求超时";
|
||||
} else if (message.includes("Request failed with status code")) {
|
||||
message = "系统接口" + message.substr(message.length - 3) + "异常";
|
||||
}
|
||||
Message({
|
||||
message: message,
|
||||
type: "error",
|
||||
duration: 5 * 1000
|
||||
});
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
export default service;
|
||||
13
src/views/About.vue
Normal file
@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<div class="about">
|
||||
about
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
22
src/views/Home.vue
Normal file
@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div id="kg_container" class="home">
|
||||
<KGBuilder pid="kg_container" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// @ is an alias to /src
|
||||
import KGBuilder from "@/components/KGBuilder.vue";
|
||||
export default {
|
||||
name: "Home",
|
||||
components: {
|
||||
KGBuilder
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
#kg_container {
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
568
src/views/datasource/index.vue
Normal file
@ -0,0 +1,568 @@
|
||||
<template>
|
||||
<div class="mind-box">
|
||||
<!-- 左侧 -->
|
||||
<el-scrollbar class="mind-l">
|
||||
<div class="ml-m">
|
||||
<h2 class="ml-ht">数据源列表</h2>
|
||||
<div class="ml-a-box">
|
||||
<div class="block">
|
||||
<el-button
|
||||
@click="addWStatus = true"
|
||||
size="mini"
|
||||
type="primary"
|
||||
plain
|
||||
>新增</el-button
|
||||
>
|
||||
<el-aside
|
||||
v-if="dataSourceList.length > 0"
|
||||
|
||||
>
|
||||
<el-menu :router="false">
|
||||
<el-submenu
|
||||
:index="dIndex + ''"
|
||||
:key="dIndex + ''"
|
||||
v-for="(d, dIndex) in dataSourceList"
|
||||
@click.native="clickDataSource(d, dIndex)"
|
||||
>
|
||||
<template slot="title"
|
||||
><i class="el-icon-message"></i>{{ d.datasourceType
|
||||
}}{{ d.datasourceAlia }}</template
|
||||
>
|
||||
<el-menu-item-group
|
||||
v-if="d.tableList.length > 0"
|
||||
v-for="(t, tIndex) in d.tableList"
|
||||
:key="tIndex + ''"
|
||||
@click.native="getFields(t.dataTableId)"
|
||||
>
|
||||
<!-- <template slot="title">分组一</template> -->
|
||||
<el-menu-item :index="dIndex + '_' + tIndex"
|
||||
>[{{ t.dataTableName }}]
|
||||
<el-dropdown @command="getTableOperate" class="ds-down">
|
||||
<span class="el-dropdown-link">...</span>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item
|
||||
:command="{ action: 'viewData', db: d, tb: t }"
|
||||
>预览数据</el-dropdown-item
|
||||
>
|
||||
<el-dropdown-item
|
||||
:command="{
|
||||
action: 'rename',
|
||||
t: t,
|
||||
tIndex: tIndex,
|
||||
dIndex: dIndex
|
||||
}"
|
||||
>重命名</el-dropdown-item
|
||||
>
|
||||
<el-dropdown-item
|
||||
:command="{ action: 'delete', db: d, tb: t }"
|
||||
>删除</el-dropdown-item
|
||||
>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</el-menu-item>
|
||||
</el-menu-item-group>
|
||||
</el-submenu>
|
||||
</el-menu>
|
||||
</el-aside>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
<div class="mind-con">
|
||||
<!-- 头部 -->
|
||||
<div class="mind-top clearfix"></div>
|
||||
<el-scrollbar class="mind-cen">
|
||||
<el-dialog title="数据源编辑" :visible.sync="addWStatus">
|
||||
<el-form :model="form">
|
||||
<el-form-item label="数据库类型">
|
||||
<el-select
|
||||
v-model="form.dbType"
|
||||
placeholder="请选择数据库"
|
||||
@change="dbTypeChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="t in dataTypeList"
|
||||
:key="t.type"
|
||||
:label="t.type"
|
||||
:value="t"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="ip及端口">
|
||||
<el-input v-model="form.ipAndPort"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="数据库别名">
|
||||
<el-input v-model="form.dbName"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="数据库名称">
|
||||
<el-input v-model="form.dbCode"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户名">
|
||||
<el-input v-model="form.dbUserName"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="密码">
|
||||
<el-input v-model="form.dbPassWord"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<!--<el-button type="primary" @click="testDataSource">测试连接</el-button>-->
|
||||
<el-button type="primary" @click="addDataSource">确 定</el-button>
|
||||
<el-button @click="addWStatus = false">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
<el-dialog title="选择数据表" :visible.sync="selectTableStatus">
|
||||
<el-form :model="tableForm">
|
||||
<el-checkbox
|
||||
:indeterminate="isIndeterminate"
|
||||
v-model="checkAll"
|
||||
@change="handleCheckAllChange"
|
||||
>全选</el-checkbox
|
||||
>
|
||||
<div style="margin: 15px 0;"></div>
|
||||
<el-checkbox-group
|
||||
v-model="tableForm.dataTables"
|
||||
@change="handleCheckedChange"
|
||||
>
|
||||
<el-checkbox
|
||||
v-for="table in dataTableList"
|
||||
:label="table"
|
||||
:key="table"
|
||||
>{{ table }}</el-checkbox
|
||||
>
|
||||
</el-checkbox-group>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="saveDataTable">确 定</el-button>
|
||||
<el-button @click="selectTableStatus = false">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
<el-table
|
||||
v-show="dataFieldList.length > 0"
|
||||
:data="dataFieldList"
|
||||
stripe
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column prop="dataColumnName" label="列名" width="180">
|
||||
</el-table-column>
|
||||
<el-table-column prop="dataColumnAlia" label="列别名" width="180">
|
||||
</el-table-column>
|
||||
<el-table-column prop="dataColumnType" label="类型">
|
||||
</el-table-column>
|
||||
<el-table-column prop="isPrimary" label="是否主键">
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.isPrimary == 1" type="success">是</el-tag>
|
||||
<el-tag v-else type="info">否</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div v-show="pageRecord.beanList.length > 0" class="ds-table-wrap">
|
||||
<div class="line-choose">
|
||||
<span>共 {{ pageRecord.totalCount }}条结果</span>
|
||||
</div>
|
||||
<el-table
|
||||
style="width: 100%"
|
||||
v-loading="listLoading"
|
||||
element-loading-text="给我一点时间"
|
||||
fit
|
||||
highlight-current-row
|
||||
:data="pageRecord.beanList"
|
||||
class="x-table"
|
||||
>
|
||||
<el-table-column
|
||||
width="200"
|
||||
:label="value"
|
||||
v-for="(value, key) in pageRecord.headerFields"
|
||||
:key="key"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<el-popover
|
||||
placement="top"
|
||||
:title="value"
|
||||
popper-class="table-item-popover"
|
||||
trigger="hover"
|
||||
>
|
||||
<div>{{ scope.row[value] }}</div>
|
||||
<span slot="reference" class="table-item-span"
|
||||
>{{ scope.row[value] }}
|
||||
</span>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
@size-change="handleSizeChange"
|
||||
class="txtCenter"
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="pageRecord.currentPage"
|
||||
:page-sizes="[10, 20, 30]"
|
||||
:page-size="pageRecord.pageSize"
|
||||
layout=" sizes, prev, pager, next"
|
||||
:total="pageRecord.totalCount"
|
||||
></el-pagination>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { datasourceApi } from "@/api";
|
||||
export default {
|
||||
name: "ds",
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
listLoading: false,
|
||||
isIndeterminate: true,
|
||||
checkAll: false,
|
||||
tableQueryForm: {
|
||||
dataSourceId: 0,
|
||||
dataTableName: "",
|
||||
currentPage: 1,
|
||||
pageSize: 10
|
||||
},
|
||||
form: {
|
||||
dbType: "mysql",
|
||||
driverName: "com.mysql.cj.jdbc.Driver",
|
||||
ipAndPort: "rm-uf649ipw0c2fdok97xo.mysql.rds.aliyuncs.com:3306",
|
||||
dbName: "zst",
|
||||
dbCode: "kg",
|
||||
dbUserName: "tan",
|
||||
dbPassWord: "Miracletan2021"
|
||||
},
|
||||
tableForm: {
|
||||
dataSourceId: 0,
|
||||
dataTables: []
|
||||
},
|
||||
addWStatus: false,
|
||||
selectTableStatus: false,
|
||||
checkAll: false,
|
||||
dataTypeList: [
|
||||
{ type: "mysql", driver: "com.mysql.cj.jdbc.Driver" },
|
||||
{ type: "postgresql", driver: "org.postgresql.Driver" },
|
||||
{ type: "mariadb", driver: "org.mariadb.jdbc.Driver" },
|
||||
{
|
||||
type: "sqlserver",
|
||||
driver: "com.microsoft.sqlserver.jdbc.SQLServerDriver"
|
||||
},
|
||||
{ type: "oracle", driver: "oracle.jdbc.driver.OracleDriver" },
|
||||
{ type: "hive", driver: "org.apache.hive.jdbc.HiveDriver" }
|
||||
],
|
||||
dataSourceList: [],
|
||||
dataTableList: [],
|
||||
dataFieldList: [],
|
||||
pageRecord: {
|
||||
headerFields: [],
|
||||
beanList: [],
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
totalPage: 0
|
||||
}
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.initDataSource();
|
||||
},
|
||||
methods: {
|
||||
handleCheckedChange(value) {
|
||||
let checkedCount = value.length;
|
||||
this.checkAll = checkedCount === this.dataTableList.length;
|
||||
this.isIndeterminate =
|
||||
checkedCount > 0 && checkedCount < this.tableForm.dataTables.length;
|
||||
},
|
||||
handleCheckAllChange(val) {
|
||||
this.tableForm.dataTables = val ? this.dataTableList : [];
|
||||
this.isIndeterminate = false;
|
||||
},
|
||||
clickDataSource(item, index) {
|
||||
this.getTableList(item.datasourceId, index);
|
||||
},
|
||||
initDataSource() {
|
||||
let _this = this;
|
||||
datasourceApi.getDatasource().then(result => {
|
||||
if (result.code == 200) {
|
||||
let list = result.data;
|
||||
_this.dataSourceList = [];
|
||||
if (list) {
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
let m = list[i];
|
||||
m.openState = 0;
|
||||
m.selected = 0;
|
||||
_this.dataSourceList.push(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
testDataSource() {},
|
||||
saveDataTable() {
|
||||
let data = this.tableForm;
|
||||
let _this = this;
|
||||
datasourceApi.saveDataTable(JSON.stringify(data)).then(result => {
|
||||
if (result.code == 200) {
|
||||
_this.selectTableStatus = false;
|
||||
_this.initDataSource();
|
||||
_this.$message.success(result.msg);
|
||||
}
|
||||
});
|
||||
},
|
||||
getTableList(datasourceId, index) {
|
||||
let _this = this;
|
||||
_this.pageRecord.beanList = [];
|
||||
datasourceApi.getTableInfo(datasourceId).then(result => {
|
||||
if (result.code == 200) {
|
||||
let list = result.data;
|
||||
if (list) {
|
||||
for (let i = 0; i < _this.dataSourceList.length; i++) {
|
||||
if (i == index) {
|
||||
_this.dataSourceList[index].openState = !_this.dataSourceList[
|
||||
index
|
||||
].openState;
|
||||
_this.dataSourceList[index].selected = !_this.dataSourceList[
|
||||
index
|
||||
].selected;
|
||||
_this.dataSourceList[index].tableList = list;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
getFields(dataTableId) {
|
||||
let _this = this;
|
||||
_this.pageRecord.beanList = [];
|
||||
datasourceApi.getTableColumn(dataTableId).then(result => {
|
||||
if (result.code == 200) {
|
||||
_this.dataFieldList = result.data;
|
||||
}
|
||||
});
|
||||
},
|
||||
dbTypeChange(value) {
|
||||
this.form.driverName = value.driver;
|
||||
this.form.dbType = value.type;
|
||||
},
|
||||
addDataSource() {
|
||||
let data = this.form;
|
||||
let _this = this;
|
||||
datasourceApi.saveDatasource(data).then(result => {
|
||||
if (result.code == 200) {
|
||||
_this.addWStatus = false;
|
||||
_this.selectTableStatus = true;
|
||||
_this.dataTableList = result.data.tables;
|
||||
_this.tableForm.dataSourceId = result.data.sourceId;
|
||||
_this.initDataSource();
|
||||
_this.$message.success(result.msg);
|
||||
}
|
||||
});
|
||||
},
|
||||
deleteTable() {},
|
||||
getTableRecord() {
|
||||
let _this = this;
|
||||
let query = JSON.stringify(_this.tableQueryForm);
|
||||
_this.pageRecord.beanList = [];
|
||||
_this.pageRecord.headerFields = [];
|
||||
_this.listLoading = true;
|
||||
datasourceApi.getDataRecord(query).then(result => {
|
||||
if (result.code == 200) {
|
||||
_this.pageRecord.headerFields = result.data.heads;
|
||||
_this.pageRecord.beanList = result.data.data;
|
||||
_this.pageRecord.currentPage = result.data.pageIndex;
|
||||
_this.pageRecord.pageSize = result.data.pageSize;
|
||||
_this.pageRecord.totalCount = result.data.totalCount;
|
||||
_this.pageRecord.totalPage = result.data.totalPage;
|
||||
_this.listLoading = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
getTableOperate(command) {
|
||||
var _this = this;
|
||||
_this.dataFieldList = [];
|
||||
if (command.action === "viewData") {
|
||||
_this.tableQueryForm.dataSourceId = command.db.datasourceId;
|
||||
_this.tableQueryForm.dataTableName = command.tb.dataTableName;
|
||||
_this.getTableRecord();
|
||||
}
|
||||
if (command.action === "rename") {
|
||||
_this.$message.error("暂不支持");
|
||||
}
|
||||
if (command.action === "delete") {
|
||||
_this.$message.error("暂不支持");
|
||||
}
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.pageRecord.currentPage = val;
|
||||
this.getTableRecord();
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
this.pageRecord.currentPage = 1;
|
||||
this.pageRecord.pageSize = val;
|
||||
this.getTableRecord();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.ds-tree {
|
||||
border-bottom: solid 1px gray;
|
||||
padding: 10px;
|
||||
}
|
||||
.el-dropdown {
|
||||
float: right;
|
||||
}
|
||||
.el-pagination {
|
||||
text-align: center;
|
||||
}
|
||||
.el-table {
|
||||
padding: 35px;
|
||||
}
|
||||
.mind-box {
|
||||
height: calc(100vh - 85px);
|
||||
overflow: hidden;
|
||||
}
|
||||
.mind-l {
|
||||
width: 300px;
|
||||
float: left;
|
||||
background: #f7f9fc;
|
||||
height: 100%;
|
||||
border-right: 1px solid #d3e2ec;
|
||||
text-align: left;
|
||||
}
|
||||
.ml-ht {
|
||||
padding-top: 20px;
|
||||
line-height: 50px;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
text-align: center;
|
||||
color: #333;
|
||||
border-bottom: 1px solid #d3e2ec;
|
||||
}
|
||||
.ml-a-box {
|
||||
margin: 10px;
|
||||
}
|
||||
.ml-a {
|
||||
display: inline-block;
|
||||
min-width: 46px;
|
||||
line-height: 1;
|
||||
padding: 6px 8px 6px 8px;
|
||||
margin: 0 4px 5px 0;
|
||||
background: #fff;
|
||||
border: 1px solid #e3e3e3;
|
||||
box-sizing: border-box;
|
||||
transition: 0.3s;
|
||||
}
|
||||
.ml-a span {
|
||||
max-width: 190px;
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.ml-a-all {
|
||||
display: block;
|
||||
margin: 10px 10px 0;
|
||||
text-align: center;
|
||||
}
|
||||
.ml-a span:empty:before {
|
||||
content: "閺堫亜鎳¢崥锟<E5B4A5>";
|
||||
color: #adadad;
|
||||
}
|
||||
.ml-a small {
|
||||
color: #999;
|
||||
}
|
||||
.ml-a:hover {
|
||||
background: #f4f4f4;
|
||||
}
|
||||
.ml-a.cur,
|
||||
.ml-a.cur small {
|
||||
background: #156498;
|
||||
color: #fff;
|
||||
}
|
||||
.ml-btn-box {
|
||||
text-align: right;
|
||||
padding: 0 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.ml-btn {
|
||||
padding: 0 5px;
|
||||
color: #156498;
|
||||
}
|
||||
.mind-con {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.mind-top {
|
||||
line-height: 70px;
|
||||
height: 70px;
|
||||
padding: 0 22px;
|
||||
border-bottom: 1px solid #ededed;
|
||||
}
|
||||
.mt-m {
|
||||
color: #666;
|
||||
margin-right: 30px;
|
||||
}
|
||||
.mt-m i {
|
||||
font-size: 18px;
|
||||
color: #333;
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
}
|
||||
.mb-con .search,
|
||||
.mind-top .search {
|
||||
border: 1px solid #e2e2e2;
|
||||
}
|
||||
.svg-a-sm {
|
||||
font-size: 14px;
|
||||
color: #156498;
|
||||
margin-right: 30px;
|
||||
}
|
||||
.mind-cen {
|
||||
height: calc(100% - 70px);
|
||||
}
|
||||
.half-auto {
|
||||
height: 40%;
|
||||
}
|
||||
.mind-bottom {
|
||||
height: 490px;
|
||||
box-sizing: border-box;
|
||||
border-top: 1px solid #ededed;
|
||||
}
|
||||
.ss-d {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: 10px;
|
||||
border-radius: 50%;
|
||||
background: #dedede;
|
||||
}
|
||||
.sd {
|
||||
margin: 2px;
|
||||
}
|
||||
.sd-active {
|
||||
color: red !important;
|
||||
}
|
||||
.btn-line + .btn-line {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.co {
|
||||
color: #ee8407 !important;
|
||||
}
|
||||
.a {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.fl {
|
||||
float: left;
|
||||
}
|
||||
.fr {
|
||||
float: right;
|
||||
}
|
||||
.tl {
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
||||
61
src/views/erbuilder/components/help.vue
Normal file
@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="帮助"
|
||||
:visible.sync="dialogVisible"
|
||||
width="70%"
|
||||
customClass="flowHelp"
|
||||
>
|
||||
<el-tabs tab-position="left">
|
||||
<el-tab-pane label="如何新增">
|
||||
<el-divider content-position="left">如何新增</el-divider>
|
||||
<div>按住鼠标拖拽左侧组件到中间画布中松开鼠标即可</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="如何删除">
|
||||
<el-divider content-position="left">页面删除</el-divider>
|
||||
<div>
|
||||
鼠标点中需要删除的节点,点击左上角的删除图标
|
||||
</div>
|
||||
<el-divider content-position="left">通过代码删除</el-divider>
|
||||
<pre>this.deleteNode(nodeId)</pre>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="如何移动">
|
||||
<el-divider content-position="left">如何移动</el-divider>
|
||||
<div>鼠标移动到节点中,当鼠标变为可拖拽的图标时按下鼠标移动到新的位置松开鼠标</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="如何连线">
|
||||
<el-divider content-position="left">如何连线</el-divider>
|
||||
<div>鼠标移动到节点中左侧的图标上,当鼠标变为+时按下鼠标移动到另一个节点中松开鼠标</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="如何添加条件">
|
||||
<el-divider content-position="left">如何添加条件</el-divider>
|
||||
<div>点击画布中的连线,在页面右侧会出现一个表单,输入新的条件,点击【保存】</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="如何进行后端交互存储">
|
||||
<el-divider content-position="left">如何进行后端交互存储</el-divider>
|
||||
<div>参考: https://gitee.com/xiaoka2017/easy-flow-sdk</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false
|
||||
}
|
||||
},
|
||||
components: {},
|
||||
methods: {
|
||||
init() {
|
||||
this.dialogVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.flowHelp {
|
||||
height: 80%;
|
||||
}
|
||||
</style>
|
||||
164
src/views/erbuilder/components/initData.js
Normal file
@ -0,0 +1,164 @@
|
||||
let dataB = {
|
||||
"domainName": "测试知识图谱领域",
|
||||
"domainId":"xxxxx",
|
||||
"nodeList": [
|
||||
{
|
||||
"nodeKey": "table-11",
|
||||
"nodeName": "kg_domain",
|
||||
"type": "task",
|
||||
"left": "256px",
|
||||
"top": "74px",
|
||||
"ico": "el-icon-menu",
|
||||
"state": "success",
|
||||
"viewOnly": 1,
|
||||
"alia": "kg_domain",
|
||||
"sourceId": 4,
|
||||
"startNode":1,
|
||||
"items": [
|
||||
{
|
||||
"columnId": 120,
|
||||
"ico": "el-icon-film",
|
||||
"isPrimary": 0,
|
||||
"itemId": "table-11-120",
|
||||
"itemCode": "commend",
|
||||
"itemName": "commend",
|
||||
"itemType": "int(11)"
|
||||
},
|
||||
{
|
||||
"columnId": 121,
|
||||
"ico": "el-icon-film",
|
||||
"isPrimary": 0,
|
||||
"itemId": "table-11-121",
|
||||
"itemCode": "createuser",
|
||||
"itemName": "createuser",
|
||||
"itemType": "varchar(255)"
|
||||
},
|
||||
{
|
||||
"columnId": 122,
|
||||
"ico": "el-icon-film",
|
||||
"isPrimary": 0,
|
||||
"itemId": "table-11-122",
|
||||
"itemCode": "nodeKey",
|
||||
"itemName": "nodeKey",
|
||||
"itemType": "int(11)"
|
||||
},
|
||||
{
|
||||
"columnId": 123,
|
||||
"ico": "el-icon-film",
|
||||
"isPrimary": 0,
|
||||
"itemId": "table-11-123",
|
||||
"itemCode": "nodeName",
|
||||
"itemName": "nodeName",
|
||||
"itemType": "varchar(255)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nodeKey": "table-13",
|
||||
"nodeName": "kg_category",
|
||||
"type": "task",
|
||||
"left": "675px",
|
||||
"top": "197px",
|
||||
"ico": "el-icon-menu",
|
||||
"state": "success",
|
||||
"viewOnly": 1,
|
||||
"alia": "kg_category",
|
||||
"sourceId": 4,
|
||||
"startNode":0,
|
||||
"items": [
|
||||
{
|
||||
"columnId": 137,
|
||||
"ico": "el-icon-film",
|
||||
"isPrimary": 0,
|
||||
"itemId": "table-13-137",
|
||||
"itemCode": "CategoryId",
|
||||
"itemName": "CategoryId",
|
||||
"itemType": "bigint(11)"
|
||||
},
|
||||
{
|
||||
"columnId": 138,
|
||||
"ico": "el-icon-film",
|
||||
"isPrimary": 0,
|
||||
"itemId": "table-13-138",
|
||||
"itemCode": "CategoryNodeCode",
|
||||
"itemName": "CategoryNodeCode",
|
||||
"itemType": "varchar(255)"
|
||||
},
|
||||
{
|
||||
"columnId": 139,
|
||||
"ico": "el-icon-film",
|
||||
"isPrimary": 0,
|
||||
"itemId": "table-13-139",
|
||||
"itemCode": "CategoryNodeId",
|
||||
"itemName": "CategoryNodeId",
|
||||
"itemType": "int(11)"
|
||||
},
|
||||
{
|
||||
"columnId": 140,
|
||||
"ico": "el-icon-film",
|
||||
"isPrimary": 0,
|
||||
"itemId": "table-13-140",
|
||||
"itemCode": "CategoryNodeName",
|
||||
"itemName": "CategoryNodeName",
|
||||
"itemType": "varchar(255)"
|
||||
},
|
||||
{
|
||||
"columnId": 141,
|
||||
"ico": "el-icon-film",
|
||||
"isPrimary": 0,
|
||||
"itemId": "table-13-141",
|
||||
"itemCode": "Color",
|
||||
"itemName": "Color",
|
||||
"itemType": "varchar(255)"
|
||||
},
|
||||
{
|
||||
"columnId": 142,
|
||||
"ico": "el-icon-film",
|
||||
"isPrimary": 0,
|
||||
"itemId": "table-13-142",
|
||||
"itemCode": "CreateTime",
|
||||
"itemName": "CreateTime",
|
||||
"itemType": "datetime"
|
||||
},
|
||||
{
|
||||
"columnId": 151,
|
||||
"ico": "el-icon-film",
|
||||
"isPrimary": 0,
|
||||
"itemId": "table-13-151",
|
||||
"itemCode": "TreeLevel",
|
||||
"itemName": "TreeLevel",
|
||||
"itemType": "int(11)"
|
||||
},
|
||||
{
|
||||
"columnId": 152,
|
||||
"ico": "el-icon-film",
|
||||
"isPrimary": 0,
|
||||
"itemId": "table-13-152",
|
||||
"itemCode": "UpdateTime",
|
||||
"itemName": "UpdateTime",
|
||||
"itemType": "datetime"
|
||||
},
|
||||
{
|
||||
"columnId": 153,
|
||||
"ico": "el-icon-film",
|
||||
"isPrimary": 0,
|
||||
"itemId": "table-13-153",
|
||||
"itemCode": "UpdateUser",
|
||||
"itemName": "UpdateUser",
|
||||
"itemType": "varchar(64)"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"lineList": [
|
||||
{
|
||||
"from": "table-11-122",
|
||||
"to": "table-13-137",
|
||||
"label":"AAA"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export function getDataB() {
|
||||
return dataB;
|
||||
}
|
||||
160
src/views/erbuilder/components/mixins.js
Normal file
@ -0,0 +1,160 @@
|
||||
export const easyFlowMixin = {
|
||||
data () {
|
||||
return {
|
||||
jsplumbSetting: {
|
||||
// 动态锚点、位置自适应
|
||||
//Anchor: ['Top', 'TopCenter', 'TopRight', 'TopLeft', 'Right', 'RightMiddle', 'Bottom', 'BottomCenter', 'BottomRight', 'BottomLeft', 'Left', 'LeftMiddle'],
|
||||
Anchor: ['RightMiddle', 'LeftMiddle'],
|
||||
Anchors: ["Right", "Left"],
|
||||
// 容器ID
|
||||
Container: 'efContainer',
|
||||
// 连线的样式,直线或者曲线等,可选值: StateMachine、Flowchart,Bezier、Straight
|
||||
//Connector: ['Bezier', {curviness: 100}],
|
||||
Connector: ['Straight', {stub: 20, gap: 1}],
|
||||
//Connector: ['Flowchart', {stub: 30, gap: 1, alwaysRespectStubs: false, midpoint: 0.5, cornerRadius: 10}],
|
||||
// Connector: ['StateMachine', {margin: 5, curviness: 10, proximityLimit: 80}],
|
||||
// 鼠标不能拖动删除线
|
||||
ConnectionsDetachable: false,
|
||||
// 删除线的时候节点不删除
|
||||
DeleteEndpointsOnDetach: false,
|
||||
/**
|
||||
* 连线的两端端点类型:圆形
|
||||
* radius: 圆的半径,越大圆越大
|
||||
*/
|
||||
Endpoint: ['Dot', {radius: 5, cssClass: 'ef-dot', hoverClass: 'ef-dot-hover'}],
|
||||
/**
|
||||
* 连线的两端端点类型:矩形
|
||||
* height: 矩形的高
|
||||
* width: 矩形的宽
|
||||
*/
|
||||
// Endpoint: ['Rectangle', {height: 20, width: 20, cssClass: 'ef-rectangle', hoverClass: 'ef-rectangle-hover'}],
|
||||
/**
|
||||
* 图像端点
|
||||
*/
|
||||
// Endpoint: ['Image', {src: 'https://www.easyicon.net/api/resizeApi.php?id=1181776&size=32', cssClass: 'ef-img', hoverClass: 'ef-img-hover'}],
|
||||
/**
|
||||
* 空白端点
|
||||
*/
|
||||
//Endpoint: ['Blank', {Overlays: ''}],
|
||||
//Endpoint: "Dot", // 端点类型
|
||||
//Endpoints: [['Dot', {radius: 5, cssClass: 'ef-dot', hoverClass: 'ef-dot-hover'}], ['Rectangle', {height: 20, width: 20, cssClass: 'ef-rectangle', hoverClass: 'ef-rectangle-hover'}]],
|
||||
/**
|
||||
* 连线的两端端点样式
|
||||
* fill: 颜色值,如:#12aabb,为空不显示
|
||||
* outlineWidth: 外边线宽度
|
||||
*/
|
||||
EndpointStyle: {fill: '#1879ffa1', outlineWidth: 1},
|
||||
// 是否打开jsPlumb的内部日志记录
|
||||
LogEnabled: false,
|
||||
/**
|
||||
* 连线的样式
|
||||
*/
|
||||
PaintStyle: {
|
||||
// 线的颜色
|
||||
stroke: '#E0E3E7',
|
||||
// 线的粗细,值越大线越粗
|
||||
strokeWidth: 1,
|
||||
// 设置外边线的颜色,默认设置透明,这样别人就看不见了,点击线的时候可以不用精确点击,参考 https://blog.csdn.net/roymno2/article/details/72717101
|
||||
outlineStroke: 'transparent',
|
||||
// 线外边的宽,值越大,线的点击范围越大
|
||||
outlineWidth: 10
|
||||
},
|
||||
DragOptions: {cursor: 'pointer', zIndex: 2000},
|
||||
/**
|
||||
* 叠加 参考: https://www.jianshu.com/p/d9e9918fd928
|
||||
*/
|
||||
Overlays: [
|
||||
// 箭头叠加
|
||||
['Arrow', {
|
||||
width: 10, // 箭头尾部的宽度
|
||||
length: 8, // 从箭头的尾部到头部的距离
|
||||
location: 1, // 位置,建议使用0~1之间
|
||||
direction: 1, // 方向,默认值为1(表示向前),可选-1(表示向后)
|
||||
foldback: 0.623 // 折回,也就是尾翼的角度,默认0.623,当为1时,为正三角
|
||||
}],
|
||||
// ['Diamond', {
|
||||
// events: {
|
||||
// dblclick: function (diamondOverlay, originalEvent) {
|
||||
// console.log('double click on diamond overlay for : ' + diamondOverlay.component)
|
||||
// }
|
||||
// }
|
||||
// }],
|
||||
['Label', {
|
||||
label: '',
|
||||
location: 0.1,
|
||||
cssClass: 'aLabel'
|
||||
}]
|
||||
],
|
||||
// 绘制图的模式 svg、canvas
|
||||
RenderMode: 'svg',
|
||||
// 鼠标滑过线的样式
|
||||
HoverPaintStyle: {stroke: '#b0b2b5', strokeWidth: 1},
|
||||
// 滑过锚点效果
|
||||
EndpointHoverStyle: {fill: 'orange'},
|
||||
Scope: 'jsPlumb_DefaultScope' // 范围,具有相同scope的点才可连接
|
||||
},
|
||||
/**
|
||||
* 连线参数
|
||||
*/
|
||||
jsplumbConnectOptions: {
|
||||
isSource: false,
|
||||
isTarget: false,
|
||||
// 动态锚点、提供了4个方向 Continuous、AutoDefault
|
||||
anchor: 'Continuous',
|
||||
// 设置连线上面的label样式
|
||||
labelStyle: {
|
||||
cssClass: 'flowLabel'
|
||||
},
|
||||
// 修改了jsplumb 源码,支持label 为空传入自定义style
|
||||
emptyLabelStyle: {
|
||||
cssClass: 'emptyFlowLabel'
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 源点配置参数
|
||||
*/
|
||||
jsplumbSourceOptions: {
|
||||
// 设置可以拖拽的类名,只要鼠标移动到该类名上的DOM,就可以拖拽连线
|
||||
filter: '.flow-node-drag',
|
||||
filterExclude: false,
|
||||
anchor: ['RightMiddle', 'LeftMiddle'],
|
||||
// 是否允许自己连接自己
|
||||
allowLoopback: false,
|
||||
maxConnections: 1,
|
||||
onMaxConnections: function (info, e) {
|
||||
console.log(`超过了最大值连线: ${info.maxConnections}`)
|
||||
}
|
||||
},
|
||||
// 参考 https://www.cnblogs.com/mq0036/p/7942139.html
|
||||
jsplumbSourceOptions2: {
|
||||
// 设置可以拖拽的类名,只要鼠标移动到该类名上的DOM,就可以拖拽连线
|
||||
filter: '.flow-node-drag',
|
||||
filterExclude: false,
|
||||
anchor: 'Continuous',
|
||||
// 是否允许自己连接自己
|
||||
allowLoopback: false,
|
||||
connector: ['Flowchart', {curviness: 50}],
|
||||
connectorStyle: {
|
||||
// 线的颜色
|
||||
stroke: 'red',
|
||||
// 线的粗细,值越大线越粗
|
||||
strokeWidth: 1,
|
||||
// 设置外边线的颜色,默认设置透明,这样别人就看不见了,点击线的时候可以不用精确点击,参考 https://blog.csdn.net/roymno2/article/details/72717101
|
||||
outlineStroke: 'transparent',
|
||||
// 线外边的宽,值越大,线的点击范围越大
|
||||
outlineWidth: 10
|
||||
},
|
||||
connectorHoverStyle: {stroke: 'orange', strokeWidth: 2}
|
||||
},
|
||||
jsplumbTargetOptions: {
|
||||
// 设置可以拖拽的类名,只要鼠标移动到该类名上的DOM,就可以拖拽连线
|
||||
filter: '.flow-node-drag',
|
||||
filterExclude: false,
|
||||
// 是否允许自己连接自己
|
||||
anchor: 'Continuous',
|
||||
allowLoopback: false,
|
||||
dropOptions: {hoverClass: 'ef-drop-hover'}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
241
src/views/erbuilder/components/node.vue
Normal file
@ -0,0 +1,241 @@
|
||||
<template>
|
||||
<div
|
||||
ref="node"
|
||||
:style="nodeContainerStyle"
|
||||
:class="nodeContainerClass"
|
||||
>
|
||||
<!-- 最左侧的那条竖线 -->
|
||||
<div class="ef-node-left"></div>
|
||||
<!-- 节点类型的图标 -->
|
||||
<div class="ef-node-left-ico flow-node-drag">
|
||||
<i :class="nodeIcoClass"></i>
|
||||
</div>
|
||||
<!-- 节点名称 -->
|
||||
<div class="ef-node-text" :show-overflow-tooltip="true">
|
||||
<div class="table node">
|
||||
<div class="name">
|
||||
<span>{{ node.nodeName }}</span>
|
||||
<i class="node-ico-edit el-icon-edit-outline" @click="clickNode"></i>
|
||||
<i class="node-ico-delete el-icon-delete" @click="deleteNode"></i>
|
||||
</div>
|
||||
<div class="table-columns">
|
||||
<ul>
|
||||
<li
|
||||
class="flow-node-drag node"
|
||||
:show-overflow-tooltip="true"
|
||||
v-for="it in node.items"
|
||||
:id="it.itemId"
|
||||
>
|
||||
<span class="column-span">{{ it.itemName }}</span>
|
||||
<el-divider direction="vertical"></el-divider>
|
||||
<span class="column-span">{{ it.itemType }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 节点状态图标 -->
|
||||
<div class="ef-node-right-ico">
|
||||
<i
|
||||
class="el-icon-circle-check el-node-state-success"
|
||||
v-show="node.state === 'success'"
|
||||
></i>
|
||||
<i
|
||||
class="el-icon-circle-close el-node-state-error"
|
||||
v-show="node.state === 'error'"
|
||||
></i>
|
||||
<i
|
||||
class="el-icon-warning-outline el-node-state-warning"
|
||||
v-show="node.state === 'warning'"
|
||||
></i>
|
||||
<i
|
||||
class="el-icon-loading el-node-state-running"
|
||||
v-show="node.state === 'running'"
|
||||
></i>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
node: Object,
|
||||
activeElement: Object
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
computed: {
|
||||
nodeContainerClass() {
|
||||
return {
|
||||
"ef-node-container": true,
|
||||
"ef-node-active":
|
||||
this.activeElement.type === "node"
|
||||
? this.activeElement.nodeId === this.node.nodeKey
|
||||
: false
|
||||
};
|
||||
},
|
||||
// 节点容器样式
|
||||
nodeContainerStyle() {
|
||||
return {
|
||||
top: this.node.top,
|
||||
left: this.node.left
|
||||
};
|
||||
},
|
||||
nodeIcoClass() {
|
||||
var nodeIcoClass = {};
|
||||
nodeIcoClass[this.node.ico] = true;
|
||||
// 添加该class可以推拽连线出来,viewOnly 可以控制节点是否运行编辑
|
||||
nodeIcoClass["flow-node-drag"] = this.node.viewOnly>0;
|
||||
return nodeIcoClass;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 点击节点
|
||||
clickNode() {
|
||||
this.$emit("clickNode", this.node);
|
||||
},
|
||||
deleteNode(e) {
|
||||
this.$emit("deleteNode", this.node.nodeKey);
|
||||
e.stopPropagation(); //阻止冒泡
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.table-columns ul {
|
||||
padding: 0px;
|
||||
}
|
||||
.table-columns li {
|
||||
list-style: none;
|
||||
border: 1px dashed lightblue;
|
||||
margin: 1px;
|
||||
z-index: 10;
|
||||
text-align: left;
|
||||
padding: 0 5px;
|
||||
}
|
||||
.column-span {
|
||||
margin: 0 5px;
|
||||
}
|
||||
.node-ico-edit {
|
||||
margin-right: 30px;
|
||||
line-height: 32px;
|
||||
position: absolute;
|
||||
right: 25px;
|
||||
color: #153df0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.node-ico-delete {
|
||||
margin-right: 30px;
|
||||
line-height: 32px;
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
color: #f56c6c;
|
||||
cursor: pointer;
|
||||
}
|
||||
/*节点的最外层容器*/
|
||||
.ef-node-container {
|
||||
position: absolute;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
width: 230px;
|
||||
height: auto;
|
||||
border: 1px solid #e0e3e7;
|
||||
border-radius: 5px;
|
||||
background-color: #fff;
|
||||
z-index: -1;
|
||||
}
|
||||
.ef-node-container:hover {
|
||||
/* 设置移动样式*/
|
||||
cursor: move;
|
||||
background-color: #f0f7ff;
|
||||
/*box-shadow: #1879FF 0px 0px 12px 0px;*/
|
||||
background-color: #f0f7ff;
|
||||
border: 1px dashed #1879ff;
|
||||
}
|
||||
|
||||
/*节点激活样式*/
|
||||
.ef-node-active {
|
||||
background-color: #f0f7ff;
|
||||
/*box-shadow: #1879FF 0px 0px 12px 0px;*/
|
||||
background-color: #f0f7ff;
|
||||
border: 1px solid #1879ff;
|
||||
}
|
||||
|
||||
/*节点左侧的竖线*/
|
||||
.ef-node-left {
|
||||
width: 4px;
|
||||
background-color: #1879ff;
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
|
||||
/*节点左侧的图标*/
|
||||
.ef-node-left-ico {
|
||||
line-height: 32px;
|
||||
margin-left: 8px;
|
||||
display: block;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.ef-node-left-ico:hover {
|
||||
/* 设置拖拽的样式 */
|
||||
/* cursor: crosshair; */
|
||||
}
|
||||
|
||||
/*节点显示的文字*/
|
||||
.ef-node-text {
|
||||
color: #565758;
|
||||
font-size: 12px;
|
||||
line-height: 32px;
|
||||
margin-left: 8px;
|
||||
width: 210px;
|
||||
/* 设置超出宽度文本显示方式*/
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/*节点右侧的图标*/
|
||||
.ef-node-right-ico {
|
||||
line-height: 32px;
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
color: #84cf65;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/*节点的几种状态样式*/
|
||||
.el-node-state-success {
|
||||
line-height: 32px;
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
color: #84cf65;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.el-node-state-error {
|
||||
line-height: 32px;
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
color: #f56c6c;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.el-node-state-warning {
|
||||
line-height: 32px;
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
color: #e6a23c;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.el-node-state-running {
|
||||
line-height: 32px;
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
color: #84cf65;
|
||||
cursor: default;
|
||||
}
|
||||
</style>
|
||||
309
src/views/erbuilder/components/node_form.vue
Normal file
@ -0,0 +1,309 @@
|
||||
<template>
|
||||
<el-drawer
|
||||
ref="drawer"
|
||||
title="编辑"
|
||||
:with-header="false"
|
||||
:visible.sync="drawer"
|
||||
:direction="direction"
|
||||
:append-to-body="true"
|
||||
>
|
||||
<div class="ef-node-form">
|
||||
<div class="ef-node-form-header">
|
||||
编辑
|
||||
</div>
|
||||
<div class="ef-node-form-body">
|
||||
<el-form
|
||||
:model="node"
|
||||
ref="dataForm"
|
||||
label-width="80px"
|
||||
v-show="type === 'node'"
|
||||
>
|
||||
<el-form-item label="类型">
|
||||
<el-input v-model="node.type" :disabled="true"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="名称">
|
||||
<el-input v-model="node.nodeName"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="left坐标">
|
||||
<el-input v-model="node.left" :disabled="true"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="top坐标">
|
||||
<el-input v-model="node.top" :disabled="true"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="ico图标">
|
||||
<el-input v-model="node.ico"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="node.state" placeholder="请选择">
|
||||
<el-option
|
||||
v-for="item in stateList"
|
||||
:key="item.state"
|
||||
:label="item.label"
|
||||
:value="item.state"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="字段">
|
||||
<el-checkbox
|
||||
:indeterminate="isIndeterminate"
|
||||
v-model="checkAll"
|
||||
@change="handleCheckAllChange"
|
||||
>全选
|
||||
</el-checkbox>
|
||||
<el-checkbox-group
|
||||
v-model="checkedColumns"
|
||||
@change="handlecheckedColumnsChange"
|
||||
>
|
||||
<kg-table
|
||||
:columns="insidetableColumns"
|
||||
:pageObj="pageObj"
|
||||
:config="tableConfig"
|
||||
>
|
||||
<template v-for="item in insidetableColumns">
|
||||
<el-tooltip
|
||||
:slot="'header' + item.prop"
|
||||
class="item"
|
||||
effect="dark"
|
||||
:content="item.label"
|
||||
placement="top"
|
||||
:key="item.prop"
|
||||
>
|
||||
<el-checkbox :label="item.label">
|
||||
{{ item.label }}
|
||||
</el-checkbox>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</kg-table>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button icon="el-icon-close" @click="drawer = false"
|
||||
>重置</el-button
|
||||
>
|
||||
<el-button type="primary" icon="el-icon-check" @click="save"
|
||||
>保存</el-button
|
||||
>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-form
|
||||
:model="line"
|
||||
ref="dataForm"
|
||||
label-width="80px"
|
||||
v-show="type === 'line'"
|
||||
>
|
||||
<el-form-item label="条件">
|
||||
<el-input v-model="line.label"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button icon="el-icon-close" @click="drawer = false"
|
||||
>重置</el-button
|
||||
>
|
||||
<el-button type="primary" icon="el-icon-check" @click="saveLine"
|
||||
>保存</el-button
|
||||
>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { cloneDeep } from "lodash";
|
||||
import { datasourceApi } from "@/api";
|
||||
import kgTable from "@/components/KGTable.vue";
|
||||
export default {
|
||||
components: {
|
||||
kgTable
|
||||
},
|
||||
computed: {
|
||||
// 内部表格
|
||||
insidetableColumns() {
|
||||
const columns = [];
|
||||
this.columns &&
|
||||
this.columns.forEach(item => {
|
||||
columns.push({
|
||||
prop: item.dataColumnName,
|
||||
type: "slotHeader",
|
||||
tooltip: true,
|
||||
label: item.dataColumnName,
|
||||
minWidth: 120
|
||||
});
|
||||
});
|
||||
return columns;
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: true,
|
||||
// node 或 line
|
||||
type: "node",
|
||||
checkModel: false,
|
||||
node: {},
|
||||
line: {},
|
||||
data: {},
|
||||
drawer: false,
|
||||
direction: "rtl",
|
||||
checkAll: false,
|
||||
isIndeterminate: true,
|
||||
columns: [],
|
||||
checkedColumns: [],
|
||||
stateList: [
|
||||
{
|
||||
state: "success",
|
||||
label: "成功"
|
||||
},
|
||||
{
|
||||
state: "warning",
|
||||
label: "警告"
|
||||
},
|
||||
{
|
||||
state: "error",
|
||||
label: "错误"
|
||||
},
|
||||
{
|
||||
state: "running",
|
||||
label: "运行中"
|
||||
}
|
||||
],
|
||||
// 内部表格
|
||||
tableConfig: {
|
||||
pagination: false, // 是否显示分页
|
||||
selection: false, // 多选
|
||||
rowKey: "id", // table row-key配置参数
|
||||
notUseMaxHeight: true
|
||||
},
|
||||
pageObj: {
|
||||
currentPage: 1,
|
||||
pageSize: 15,
|
||||
list: [],
|
||||
totalCount: 0
|
||||
}
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleCheckAllChange(val) {
|
||||
let arr = this.columns.map(function(col) {
|
||||
return col.dataColumnName;
|
||||
});
|
||||
this.checkedColumns = val ? arr : [];
|
||||
this.isIndeterminate = false;
|
||||
},
|
||||
handlecheckedColumnsChange(value) {
|
||||
let checkedCount = value.length;
|
||||
this.checkAll = checkedCount === this.columns.length;
|
||||
this.isIndeterminate =
|
||||
checkedCount > 0 && checkedCount < this.columns.length;
|
||||
this.checkedColumns = value;
|
||||
},
|
||||
changeColumns() {
|
||||
this.data.nodeList.filter(node => {
|
||||
if (node.nodeKey === this.node.nodeKey) {
|
||||
node.nodeName = this.node.nodeName;
|
||||
node.left = this.node.left;
|
||||
node.top = this.node.top;
|
||||
node.ico = this.node.ico;
|
||||
node.state = this.node.state;
|
||||
let nodeItems = this.columns.filter(c => {
|
||||
if (this.checkedColumns.indexOf(c.dataColumnName) > -1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
node.items = [];
|
||||
for (var j = 0; j < nodeItems.length; j++) {
|
||||
let tr = {
|
||||
columnId: nodeItems[j].dataColumnId,
|
||||
ico: "el-icon-film",
|
||||
isPrimary: 0,
|
||||
itemId: node.id + "-" + nodeItems[j].dataColumnId,
|
||||
itemCode: nodeItems[j].dataColumnName,
|
||||
itemName: nodeItems[j].dataColumnName,
|
||||
itemType: nodeItems[j].dataColumnType
|
||||
};
|
||||
node.items.push(tr);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 表单修改
|
||||
* @param data
|
||||
* @param node
|
||||
*/
|
||||
nodeInit(data, node) {
|
||||
this.drawer = true;
|
||||
this.type = "node";
|
||||
this.data = data;
|
||||
this.node = cloneDeep(node);
|
||||
let arr=node.items.map(function(col) {
|
||||
return col.itemCode;
|
||||
});
|
||||
this.checkedColumns = arr ? arr : [];
|
||||
let param = {
|
||||
dataSourceId: node.sourceId,
|
||||
dataTableName: node.nodeName,
|
||||
currentPage: 1,
|
||||
pageSize: 5
|
||||
};
|
||||
datasourceApi.getPreviewData(param).then(response => {
|
||||
if (response.code == 200) {
|
||||
this.pageObj.list = response.data.data;
|
||||
this.columns = response.data.heads;
|
||||
}
|
||||
});
|
||||
},
|
||||
lineInit(line) {
|
||||
this.drawer = true;
|
||||
this.type = "line";
|
||||
this.line = line;
|
||||
},
|
||||
// 修改连线
|
||||
saveLine() {
|
||||
this.drawer = false;
|
||||
this.$emit("setLineLabel", this.line.from, this.line.to, this.line.label);
|
||||
},
|
||||
save() {
|
||||
this.drawer = false;
|
||||
this.changeColumns();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.el-node-form-tag {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
margin-left: -15px;
|
||||
height: 40px;
|
||||
width: 15px;
|
||||
background-color: #fbfbfb;
|
||||
border: 1px solid rgb(220, 227, 232);
|
||||
border-right: none;
|
||||
z-index: 0;
|
||||
}
|
||||
/*node-form*/
|
||||
.ef-node-form-header {
|
||||
height: 32px;
|
||||
border-top: 1px solid #dce3e8;
|
||||
border-bottom: 1px solid #dce3e8;
|
||||
background: #f1f3f4;
|
||||
color: #000;
|
||||
line-height: 32px;
|
||||
padding-left: 12px;
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.ef-node-form-body {
|
||||
margin-top: 10px;
|
||||
padding-right: 10px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
.ef-node-form-body {
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
||||
60
src/views/erbuilder/components/node_info.vue
Normal file
@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<el-dialog title="数据信息" :visible.sync="dialogVisible" width="70%">
|
||||
<el-alert
|
||||
title="使用说明"
|
||||
type="warning"
|
||||
description="以下流程信息可以被存储起来,方便下一次流程加载"
|
||||
show-icon
|
||||
close-text="知道了"
|
||||
>
|
||||
</el-alert>
|
||||
<br />
|
||||
<!--一个高亮显示的插件-->
|
||||
<codemirror
|
||||
:value="flowJsonData"
|
||||
:options="options"
|
||||
class="code"
|
||||
></codemirror>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import "codemirror/lib/codemirror.css";
|
||||
import { codemirror } from "vue-codemirror";
|
||||
|
||||
require("codemirror/mode/javascript/javascript.js");
|
||||
|
||||
export default {
|
||||
props: {
|
||||
data: Object
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
flowJsonData: {},
|
||||
options: {
|
||||
mode: { name: "javascript", json: true },
|
||||
lineNumbers: true
|
||||
}
|
||||
};
|
||||
},
|
||||
components: {
|
||||
codemirror
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.dialogVisible = true;
|
||||
this.flowJsonData = JSON.stringify(this.data, null, 4).toString();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.el-dialog__body {
|
||||
padding: 30px 20px;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
word-break: break-all;
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
||||
263
src/views/erbuilder/components/node_tree.vue
Normal file
@ -0,0 +1,263 @@
|
||||
<template>
|
||||
<div class="flow-menu" ref="tool">
|
||||
<div v-for="menu in menuList" :key="menu.id">
|
||||
<span class="ef-node-pmenu" @click="menuClick(menu)"
|
||||
><i
|
||||
:class="{
|
||||
'el-icon-caret-bottom': menu.open,
|
||||
'el-icon-caret-right': !menu.open
|
||||
}"
|
||||
></i
|
||||
> {{ menu.name }}</span
|
||||
>
|
||||
<ul v-show="menu.open" class="ef-node-menu-ul">
|
||||
<draggable
|
||||
@end="end"
|
||||
@start="move"
|
||||
v-model="menu.children"
|
||||
:options="draggableOptions"
|
||||
>
|
||||
<li
|
||||
v-for="subMenu in menu.children"
|
||||
class="ef-node-menu-li"
|
||||
:id="subMenu.id"
|
||||
:key="subMenu.id"
|
||||
:type="subMenu.type"
|
||||
>
|
||||
<i :class="subMenu.ico"></i> {{ subMenu.name }}
|
||||
</li>
|
||||
</draggable>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import draggable from "vuedraggable";
|
||||
import { datasourceApi } from "@/api";
|
||||
var mousePosition = {
|
||||
left: -1,
|
||||
top: -1
|
||||
};
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
activeNames: "1",
|
||||
// draggable配置参数参考 https://www.cnblogs.com/weixin186/p/10108679.html
|
||||
draggableOptions: {
|
||||
preventOnFilter: false,
|
||||
sort: true,
|
||||
disabled: false,
|
||||
ghostClass: "tt",
|
||||
//
|
||||
|
||||
scroll: true,
|
||||
animation: "300",
|
||||
// 不使用H5原生的配置
|
||||
forceFallback: true
|
||||
// 拖拽的时候样式
|
||||
// fallbackClass: 'flow-node-draggable'
|
||||
},
|
||||
// 默认打开的左侧菜单的id
|
||||
defaultOpeneds: ["1", "2"],
|
||||
menuList: [
|
||||
{
|
||||
id: "1",
|
||||
type: "group",
|
||||
name: "开始节点",
|
||||
ico: "el-icon-video-play",
|
||||
open: true,
|
||||
children: [
|
||||
{
|
||||
id: "11",
|
||||
type: "timer",
|
||||
name: "数据接入",
|
||||
ico: "el-icon-time",
|
||||
// 自定义覆盖样式
|
||||
style: {}
|
||||
},
|
||||
{
|
||||
id: "12",
|
||||
type: "task",
|
||||
name: "接口调用",
|
||||
ico: "el-icon-odometer",
|
||||
// 自定义覆盖样式
|
||||
style: {}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
datasourceList: [],
|
||||
nodeMenu: {
|
||||
table: {},
|
||||
columns: []
|
||||
}
|
||||
};
|
||||
},
|
||||
components: {
|
||||
draggable
|
||||
},
|
||||
created() {
|
||||
/**
|
||||
* 以下是为了解决在火狐浏览器上推拽时弹出tab页到搜索问题
|
||||
* @param event
|
||||
*/
|
||||
if (this.isFirefox()) {
|
||||
document.body.ondrop = function(event) {
|
||||
// 解决火狐浏览器无法获取鼠标拖拽结束的坐标问题
|
||||
mousePosition.left = event.layerX;
|
||||
mousePosition.top = event.clientY - 50;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
};
|
||||
}
|
||||
//加载数据源列表
|
||||
this.getDatasourceList();
|
||||
},
|
||||
methods: {
|
||||
menuClick(menu) {
|
||||
menu.open = !menu.open;
|
||||
if (menu.open && menu.children.length == 0) {
|
||||
this.getDataTableList(menu.id);
|
||||
}
|
||||
},
|
||||
//加载数据源
|
||||
getDatasourceList() {
|
||||
datasourceApi.getDatasource().then(response => {
|
||||
this.datasourceList = response.data;
|
||||
this.menuList = [];
|
||||
for (let i = 0; i < this.datasourceList.length; i++) {
|
||||
let data = this.datasourceList[i];
|
||||
let item = {
|
||||
id: data.datasourceId,
|
||||
type: "group",
|
||||
name: data.datasourceName,
|
||||
ico: "el-icon-video-play",
|
||||
open: false,
|
||||
children: []
|
||||
};
|
||||
this.menuList.push(item);
|
||||
}
|
||||
});
|
||||
},
|
||||
//加载数据表
|
||||
getDataTableList(sourceId) {
|
||||
datasourceApi.getTableInfo(sourceId).then(response => {
|
||||
for (let i = 0; i < this.menuList.length; i++) {
|
||||
if (this.menuList[i].id == sourceId) {
|
||||
this.menuList[i].children = [];
|
||||
for (let j = 0; j < response.data.length; j++) {
|
||||
let tableItem = response.data[j];
|
||||
let submitItem = {
|
||||
id: tableItem.dataTableId,
|
||||
type: "timer",
|
||||
name: tableItem.dataTableName,
|
||||
ico: "el-icon-menu",
|
||||
// 自定义覆盖样式
|
||||
style: {}
|
||||
};
|
||||
this.menuList[i].children.push(submitItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
//获取数据表字段
|
||||
getDataTableInfo(tableId, evt) {
|
||||
datasourceApi.getDataTableInfo(tableId).then(response => {
|
||||
let columns = [];
|
||||
let tableItem = response.data;
|
||||
let item = {
|
||||
nodeKey: "table-" + tableItem.table.dataTableId,
|
||||
tableId: tableItem.table.dataTableId,
|
||||
type: "timer",
|
||||
nodeName: tableItem.table.dataTableName,
|
||||
alia: tableItem.table.dataTableAlia,
|
||||
sourceId: tableItem.table.datasourceId,
|
||||
startNode:0,
|
||||
ico: "el-icon-menu",
|
||||
// 自定义覆盖样式
|
||||
style: {},
|
||||
state: "success",
|
||||
viewOnly: 1 //0 不可拖拽
|
||||
};
|
||||
for (let i = 0; i < tableItem.column.length; i++) {
|
||||
let columnItem = tableItem.column[i];
|
||||
let data = {
|
||||
itemId: item.nodeKey + "-" + columnItem.dataColumnId,
|
||||
columnId: columnItem.dataColumnId,
|
||||
itemCode: columnItem.dataColumnName,
|
||||
itemName:
|
||||
columnItem.dataColumnName + "[" + columnItem.dataColumnAlia + "]",
|
||||
itemType: columnItem.dataColumnType,
|
||||
isPrimary: columnItem.isPrimary,
|
||||
ico: "el-icon-film"
|
||||
};
|
||||
columns.push(data);
|
||||
}
|
||||
this.nodeMenu.table = item;
|
||||
this.nodeMenu.columns = columns;
|
||||
this.$emit("addNode", evt, this.nodeMenu, mousePosition);
|
||||
});
|
||||
},
|
||||
// 拖拽开始时触发
|
||||
move(evt) {},
|
||||
// 拖拽结束时触发
|
||||
end(evt, e) {
|
||||
let tableId = evt.item.attributes.id.nodeValue;
|
||||
this.getDataTableInfo(tableId, evt);
|
||||
},
|
||||
// 是否是火狐浏览器
|
||||
isFirefox() {
|
||||
var userAgent = navigator.userAgent;
|
||||
if (userAgent.indexOf("Firefox") > -1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
/*节点菜单*/
|
||||
.ef-node-pmenu {
|
||||
cursor: pointer;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
width: 255px;
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
color: #4a4a4a;
|
||||
text-align: left;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.ef-node-pmenu:hover {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.ef-node-menu-li {
|
||||
color: #565758;
|
||||
width: 180px;
|
||||
border: 1px dashed #e0e3e7;
|
||||
margin: 5px 0 5px 0;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
padding-left: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.ef-node-menu-li:hover {
|
||||
/* 设置移动样式*/
|
||||
cursor: move;
|
||||
background-color: #f0f7ff;
|
||||
border: 1px dashed #1879ff;
|
||||
border-left: 4px solid #1879ff;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.ef-node-menu-ul {
|
||||
list-style: none;
|
||||
padding-left: 20px;
|
||||
}
|
||||
</style>
|
||||
888
src/views/erbuilder/index.vue
Normal file
@ -0,0 +1,888 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="easyFlowVisible"
|
||||
style="height: calc(100vh)"
|
||||
@contextmenu="hiddenLinkMenu"
|
||||
@click="hiddenLinkMenu"
|
||||
>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<div class="ef-tooltar">
|
||||
<el-tag
|
||||
:key="tag.id"
|
||||
v-for="tag in domainList"
|
||||
@click="initERData(tag.id)"
|
||||
closable
|
||||
:disable-transitions="false"
|
||||
@close="deleteEr(tag.id)"
|
||||
>
|
||||
{{ tag.name }}
|
||||
</el-tag>
|
||||
<el-input
|
||||
class="input-new-tag"
|
||||
v-if="inputVisible"
|
||||
v-model="inputValue"
|
||||
ref="saveTagInput"
|
||||
size="small"
|
||||
@keyup.enter.native="createDomain"
|
||||
@blur="createDomain"
|
||||
>
|
||||
</el-input>
|
||||
<el-button
|
||||
v-else
|
||||
class="button-new-tag"
|
||||
size="small"
|
||||
@click="showAddDomain"
|
||||
>+ 添加领域</el-button
|
||||
>
|
||||
</div>
|
||||
</el-col>
|
||||
<!--顶部工具菜单-->
|
||||
<el-col :span="24">
|
||||
<div class="ef-tooltar">
|
||||
<el-link type="primary" :underline="false">
|
||||
当前领域 【{{ data.domainName }}】</el-link
|
||||
>
|
||||
<el-divider direction="vertical"></el-divider>
|
||||
<el-button
|
||||
type="text"
|
||||
@click="refreshData"
|
||||
icon="el-icon-refresh"
|
||||
size="large"
|
||||
>刷新</el-button
|
||||
>
|
||||
<el-divider direction="vertical"></el-divider>
|
||||
<el-button
|
||||
type="text"
|
||||
icon="el-icon-download"
|
||||
size="large"
|
||||
@click="downloadData"
|
||||
></el-button>
|
||||
|
||||
<div style="float: right; margin-right: 5px">
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-document"
|
||||
@click="saveERdata"
|
||||
size="mini"
|
||||
>保存</el-button
|
||||
>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-document"
|
||||
@click="executeERdata"
|
||||
size="mini"
|
||||
>生成图谱</el-button
|
||||
>
|
||||
<el-button
|
||||
type="info"
|
||||
plain
|
||||
round
|
||||
icon="el-icon-document"
|
||||
@click="dataInfo"
|
||||
size="mini"
|
||||
>预览数据</el-button
|
||||
>
|
||||
|
||||
<el-button
|
||||
type="info"
|
||||
plain
|
||||
round
|
||||
icon="el-icon-document"
|
||||
@click="openHelp"
|
||||
size="mini"
|
||||
>帮助</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div style="display: flex; height: calc(100% - 47px)">
|
||||
<div style="width: 230px; border-right: 1px solid #dce3e8">
|
||||
<node-menu @addNode="addNode" ref="nodeMenu"></node-menu>
|
||||
</div>
|
||||
<div id="efContainer" ref="efContainer" class="container" v-flowDrag>
|
||||
<template v-for="node in data.nodeList">
|
||||
<flow-node
|
||||
:key="node.nodeKey"
|
||||
:id="node.nodeKey"
|
||||
:node="node"
|
||||
:activeElement="activeElement"
|
||||
@clickNode="clickNode"
|
||||
@nodeRightMenu="nodeRightMenu"
|
||||
@deleteNode="deleteNode"
|
||||
>
|
||||
</flow-node>
|
||||
</template>
|
||||
<!-- 给画布一个默认的宽度和高度 -->
|
||||
<div style="position: absolute; top: 2000px; left: 2000px"> </div>
|
||||
</div>
|
||||
<ul
|
||||
v-show="showLineMenu"
|
||||
:style="linkmenubarStyle"
|
||||
class="el-dropdown-menu el-popper linkmenubar"
|
||||
>
|
||||
<li class="el-dropdown-menu__item">
|
||||
<span class="pl-15 el-icon-reading" @click="editActiveElement"
|
||||
>编辑</span
|
||||
>
|
||||
</li>
|
||||
<li class="el-dropdown-menu__item">
|
||||
<span class="pl-15 el-icon-delete" @click="deleteElement">删除</span>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- 右侧表单 -->
|
||||
<div>
|
||||
<flow-node-form
|
||||
ref="nodeForm"
|
||||
@setLineLabel="setLineLabel"
|
||||
@repaintEverything="repaintEverything"
|
||||
></flow-node-form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 流程数据详情 -->
|
||||
<flow-info v-if="flowInfoVisible" ref="flowInfo" :data="data"></flow-info>
|
||||
<flow-help v-if="flowHelpVisible" ref="flowHelp"></flow-help>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { jsPlumb } from "jsplumb";
|
||||
import lodash from "lodash";
|
||||
import { easyFlowMixin } from "@/views/erbuilder/components/mixins";
|
||||
import flowNode from "@/views/erbuilder/components/node";
|
||||
import nodeMenu from "@/views/erbuilder/components/node_tree";
|
||||
import FlowInfo from "@/views/erbuilder/components/node_info";
|
||||
import FlowHelp from "@/views/erbuilder/components/help";
|
||||
import FlowNodeForm from "@/views/erbuilder/components/node_form";
|
||||
import { kgBuilderApi } from "@/api";
|
||||
export default {
|
||||
name: "er",
|
||||
data() {
|
||||
return {
|
||||
// jsPlumb 实例
|
||||
jsPlumb: null,
|
||||
// 控制画布销毁
|
||||
easyFlowVisible: true,
|
||||
// 控制流程数据显示与隐藏
|
||||
flowInfoVisible: false,
|
||||
// 是否加载完毕标志位
|
||||
loadEasyFlowFinish: false,
|
||||
flowHelpVisible: false,
|
||||
// 数据
|
||||
data: {},
|
||||
// 激活的元素、可能是节点、可能是连线
|
||||
activeElement: {
|
||||
// 可选值 node 、line
|
||||
type: undefined,
|
||||
// 节点ID
|
||||
nodeId: undefined,
|
||||
// 连线ID
|
||||
sourceId: undefined,
|
||||
targetId: undefined,
|
||||
label: undefined
|
||||
},
|
||||
zoom: 0.5,
|
||||
showLineMenu: false,
|
||||
linkmenubar: {
|
||||
top: "-1000px",
|
||||
left: "-1000px"
|
||||
},
|
||||
domainQuery: {
|
||||
domainId: 0,
|
||||
type: 3,
|
||||
pageSize: 10,
|
||||
pageIndex: 1
|
||||
},
|
||||
inputVisible: false,
|
||||
inputValue: "",
|
||||
domainList: []
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
linkmenubarStyle: function() {
|
||||
return {
|
||||
top: this.linkmenubar.top + "px",
|
||||
left: this.linkmenubar.left + "px"
|
||||
};
|
||||
}
|
||||
},
|
||||
// 一些基础配置移动该文件中
|
||||
mixins: [easyFlowMixin],
|
||||
components: {
|
||||
flowNode,
|
||||
nodeMenu,
|
||||
FlowInfo,
|
||||
FlowNodeForm,
|
||||
FlowHelp
|
||||
},
|
||||
directives: {
|
||||
flowDrag: {
|
||||
bind(el, binding, vnode, oldNode) {
|
||||
if (!binding) {
|
||||
return;
|
||||
}
|
||||
el.onmousedown = e => {
|
||||
if (e.button === 2) {
|
||||
// 右键不管
|
||||
return;
|
||||
}
|
||||
// 鼠标按下,计算当前原始距离可视区的高度
|
||||
let disX = e.clientX;
|
||||
let disY = e.clientY;
|
||||
el.style.cursor = "move";
|
||||
|
||||
document.onmousemove = function(e) {
|
||||
// 移动时禁止默认事件
|
||||
e.preventDefault();
|
||||
const left = e.clientX - disX;
|
||||
disX = e.clientX;
|
||||
el.scrollLeft += -left;
|
||||
|
||||
const top = e.clientY - disY;
|
||||
disY = e.clientY;
|
||||
el.scrollTop += -top;
|
||||
};
|
||||
|
||||
document.onmouseup = function(e) {
|
||||
el.style.cursor = "auto";
|
||||
document.onmousemove = null;
|
||||
document.onmouseup = null;
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initDomain();
|
||||
this.jsPlumb = jsPlumb.getInstance();
|
||||
},
|
||||
methods: {
|
||||
showAddDomain() {
|
||||
this.inputVisible = true;
|
||||
this.$nextTick(_ => {
|
||||
this.$refs.saveTagInput.$refs.input.focus();
|
||||
});
|
||||
},
|
||||
deleteEr(domainId) {
|
||||
this.$message.success("计划中");
|
||||
},
|
||||
initDomain() {
|
||||
let data = JSON.stringify(this.domainQuery);
|
||||
kgBuilderApi.getDomains(data).then(response => {
|
||||
if (response.code == 200) {
|
||||
this.domainList = response.data.nodeList;
|
||||
}
|
||||
});
|
||||
},
|
||||
createDomain() {
|
||||
let inputValue = this.inputValue;
|
||||
if (inputValue) {
|
||||
let data = {
|
||||
domain: inputValue,
|
||||
type: 3
|
||||
};
|
||||
kgBuilderApi.createDomain(data).then(response => {
|
||||
if (response.code == 200) {
|
||||
this.data.domainName = inputValue;
|
||||
this.data.domainId = response.data;
|
||||
this.domainList.push({ id: response.data, name: inputValue });
|
||||
}
|
||||
});
|
||||
}
|
||||
this.inputVisible = false;
|
||||
this.inputValue = "";
|
||||
},
|
||||
saveERdata() {
|
||||
let data = JSON.stringify(this.data);
|
||||
kgBuilderApi.saveData(data).then(response => {
|
||||
if (response.code == 200) {
|
||||
if (response.data) {
|
||||
this.$message.success("保存成功");
|
||||
} else {
|
||||
this.$message.error("暂时没有更多数据");
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
executeERdata() {
|
||||
kgBuilderApi.execute(this.data.domainId).then(response => {
|
||||
if (response.code == 200) {
|
||||
if (response.data) {
|
||||
this.$message.success("保存成功");
|
||||
} else {
|
||||
this.$message.error("暂时没有更多数据");
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
// 初始化数据
|
||||
initERData(domainId) {
|
||||
kgBuilderApi.getDomainNode(domainId).then(response => {
|
||||
if (response.code == 200) {
|
||||
if (response.data) {
|
||||
this.$nextTick(() => {
|
||||
// 默认加载流程A的数据、在这里可以根据具体的业务返回符合流程数据格式的数据即可
|
||||
this.dataReload(response.data);
|
||||
});
|
||||
} else {
|
||||
this.$message.error("暂时没有更多数据");
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
hiddenLinkMenu(e) {
|
||||
//e.preventDefault();
|
||||
this.showLineMenu = false;
|
||||
},
|
||||
jsPlumbInit() {
|
||||
this.jsPlumb.ready(() => {
|
||||
// 导入默认配置
|
||||
this.jsPlumb.importDefaults(this.jsplumbSetting);
|
||||
// 会使整个jsPlumb立即重绘。
|
||||
this.jsPlumb.setSuspendDrawing(false, true);
|
||||
// 初始化节点
|
||||
this.loadEasyFlow();
|
||||
// 单点击了连接线, https://www.cnblogs.com/ysx215/p/7615677.html
|
||||
this.jsPlumb.bind("click", conn => {
|
||||
this.activeElement.type = "line";
|
||||
this.activeElement.sourceId = conn.sourceId;
|
||||
this.activeElement.targetId = conn.targetId;
|
||||
this.$refs.nodeForm.lineInit({
|
||||
from: conn.sourceId,
|
||||
to: conn.targetId,
|
||||
label: conn.getLabel()
|
||||
});
|
||||
});
|
||||
// 连线
|
||||
this.jsPlumb.bind("connection", evt => {
|
||||
let from = evt.source.id;
|
||||
let to = evt.target.id;
|
||||
if (this.loadEasyFlowFinish) {
|
||||
this.data.lineList.push({ from: from, to: to });
|
||||
}
|
||||
});
|
||||
|
||||
// 删除连线回调
|
||||
this.jsPlumb.bind("connectionDetached", evt => {
|
||||
this.deleteLine(evt.sourceId, evt.targetId);
|
||||
});
|
||||
|
||||
// 改变线的连接节点
|
||||
this.jsPlumb.bind("connectionMoved", evt => {
|
||||
this.changeLine(evt.originalSourceId, evt.originalTargetId);
|
||||
});
|
||||
|
||||
// 连线右击
|
||||
this.jsPlumb.bind("contextmenu", (evt, e) => {
|
||||
let left = e.clientX;
|
||||
let top = e.clientY;
|
||||
this.showLineMenu = true;
|
||||
//console.log("contextmenu", evt);
|
||||
this.linkmenubar.top = top;
|
||||
this.linkmenubar.left = left;
|
||||
this.activeElement.type = "line";
|
||||
this.activeElement.sourceId = evt.sourceId;
|
||||
this.activeElement.targetId = evt.targetId;
|
||||
this.activeElement.label = evt.getLabel();
|
||||
e.preventDefault(); //禁止浏览器右键
|
||||
e.stopPropagation(); //阻止冒泡
|
||||
});
|
||||
// 连线
|
||||
this.jsPlumb.bind("beforeDrop", evt => {
|
||||
console.log(evt);
|
||||
let from = evt.sourceId;
|
||||
let to = evt.targetId;
|
||||
if (from === to) {
|
||||
this.$message.error("节点不支持连接自己");
|
||||
return false;
|
||||
}
|
||||
if (this.hasLine(from, to)) {
|
||||
this.$message.error("该关系已存在,不允许重复创建");
|
||||
return false;
|
||||
}
|
||||
if (this.hashOppositeLine(from, to)) {
|
||||
this.$message.error("不支持两个节点之间连线回环");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.isSameTableLine(from, to)) {
|
||||
//this.$message.error("同一个表之间无需连线");
|
||||
return false;
|
||||
}
|
||||
if (this.isMutilLine(from, to)) {
|
||||
this.$message.error("两个表之间只能存在一根连线");
|
||||
return false;
|
||||
}
|
||||
this.$message.success("连接成功");
|
||||
return true;
|
||||
});
|
||||
// beforeDetach
|
||||
this.jsPlumb.bind("beforeDetach", evt => {
|
||||
console.log("beforeDetach", evt);
|
||||
});
|
||||
|
||||
this.jsPlumb.setContainer(this.$refs.efContainer);
|
||||
});
|
||||
},
|
||||
// 加载流程图
|
||||
loadEasyFlow() {
|
||||
// 初始化节点
|
||||
for (var i = 0; i < this.data.nodeList.length; i++) {
|
||||
let node = this.data.nodeList[i];
|
||||
if (node.viewOnly > 0) {
|
||||
this.jsPlumb.draggable(node.nodeKey, {
|
||||
containment: "parent",
|
||||
stop: function(el, e) {
|
||||
//debugger;
|
||||
let posArr = el.pos;
|
||||
node.left = posArr[0] + "px";
|
||||
node.top = posArr[1] + "px";
|
||||
}
|
||||
});
|
||||
}
|
||||
let nodeItems = node.items;
|
||||
for (var j = 0; j < nodeItems.length; j++) {
|
||||
// 设置源点,可以拖出线连接其他节点
|
||||
//let obj = lodash.merge(this.jsplumbSourceOptions, {});
|
||||
this.jsPlumb.makeSource(nodeItems[j].itemId);
|
||||
// // 设置目标点,其他源点拖出的线可以连接该节点
|
||||
this.jsPlumb.makeTarget(
|
||||
nodeItems[j].itemId,
|
||||
this.jsplumbSourceOptions
|
||||
);
|
||||
}
|
||||
}
|
||||
// 初始化连线
|
||||
for (var m = 0; m < this.data.lineList.length; m++) {
|
||||
let line = this.data.lineList[m];
|
||||
var connParam = {
|
||||
source: line.from,
|
||||
target: line.to,
|
||||
label: line.label ? line.label : "",
|
||||
connector: line.connector ? line.connector : "",
|
||||
anchors: line.anchors ? line.anchors : undefined,
|
||||
paintStyle: line.paintStyle ? line.paintStyle : undefined
|
||||
};
|
||||
this.jsPlumb.connect(connParam, this.jsplumbConnectOptions);
|
||||
}
|
||||
this.$nextTick(function() {
|
||||
this.loadEasyFlowFinish = true;
|
||||
});
|
||||
},
|
||||
// 设置连线条件
|
||||
setLineLabel(from, to, label) {
|
||||
var conn = this.jsPlumb.getConnections({
|
||||
source: from,
|
||||
target: to
|
||||
})[0];
|
||||
if (!label || label === "") {
|
||||
conn.removeClass("flowLabel");
|
||||
conn.addClass("emptyFlowLabel");
|
||||
} else {
|
||||
conn.addClass("flowLabel");
|
||||
}
|
||||
conn.setLabel({
|
||||
label: label
|
||||
});
|
||||
this.data.lineList.forEach(function(line) {
|
||||
if (line.from === from && line.to === to) {
|
||||
line.label = label;
|
||||
}
|
||||
});
|
||||
},
|
||||
//编辑激活的连线
|
||||
editActiveElement() {
|
||||
this.activeElement.type = "line";
|
||||
let sourceId = this.activeElement.sourceId;
|
||||
let targetId = this.activeElement.targetId;
|
||||
let label = this.activeElement.label;
|
||||
this.$refs.nodeForm.lineInit({
|
||||
from: sourceId,
|
||||
to: targetId,
|
||||
label: label
|
||||
});
|
||||
},
|
||||
// 删除激活的元素
|
||||
deleteElement() {
|
||||
if (this.activeElement.type === "node") {
|
||||
this.deleteNode(this.activeElement.nodeId);
|
||||
} else if (this.activeElement.type === "line") {
|
||||
this.$confirm("确定删除所点击的线吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
})
|
||||
.then(() => {
|
||||
var conn = this.jsPlumb.getConnections({
|
||||
source: this.activeElement.sourceId,
|
||||
target: this.activeElement.targetId
|
||||
})[0];
|
||||
this.jsPlumb.deleteConnection(conn);
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
},
|
||||
// 删除线
|
||||
deleteLine(from, to) {
|
||||
this.data.lineList = this.data.lineList.filter(function(line) {
|
||||
if (line.from === from && line.to === to) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
},
|
||||
// 改变连线
|
||||
changeLine(oldFrom, oldTo) {
|
||||
this.deleteLine(oldFrom, oldTo);
|
||||
},
|
||||
/**
|
||||
* 拖拽结束后添加新的节点
|
||||
* @param evt
|
||||
* @param nodeMenu 被添加的节点对象
|
||||
* @param mousePosition 鼠标拖拽结束的坐标
|
||||
*/
|
||||
addNode(evt, nodeMenu, mousePosition) {
|
||||
if (!this.data.domainId) {
|
||||
this.$message.warning("请先选择或者创建领域");
|
||||
return;
|
||||
}
|
||||
let screenX = evt.originalEvent.clientX;
|
||||
let screenY = evt.originalEvent.clientY;
|
||||
let efContainer = this.$refs.efContainer;
|
||||
let containerRect = efContainer.getBoundingClientRect();
|
||||
let left = screenX;
|
||||
let top = screenY;
|
||||
// 计算是否拖入到容器中
|
||||
if (
|
||||
left < containerRect.x ||
|
||||
left > containerRect.width + containerRect.x ||
|
||||
top < containerRect.y ||
|
||||
containerRect.y > containerRect.y + containerRect.height
|
||||
) {
|
||||
this.$message.warning("请把节点拖入到中间画布里");
|
||||
return;
|
||||
}
|
||||
left = left - containerRect.x + efContainer.scrollLeft;
|
||||
top = top - containerRect.y + efContainer.scrollTop;
|
||||
// 居中
|
||||
left -= 85;
|
||||
top -= 16;
|
||||
var nodeId = nodeMenu.table.nodeKey;
|
||||
var nodeName = nodeMenu.table.nodeName;
|
||||
let nodeExists =
|
||||
_.findIndex(this.data.nodeList, function(o) {
|
||||
return o.id == nodeId;
|
||||
}) > -1;
|
||||
if (nodeExists) {
|
||||
this.$message.warning("当前表节点[" + nodeName + "]已经存在于画布中");
|
||||
return;
|
||||
}
|
||||
|
||||
var node = {
|
||||
nodeKey: nodeId,
|
||||
nodeName: nodeName,
|
||||
type: "task",
|
||||
left: left + "px",
|
||||
top: top + "px",
|
||||
ico: nodeMenu.table.ico,
|
||||
state: "success",
|
||||
viewOnly: 1,
|
||||
startNode: 0,
|
||||
alia: nodeMenu.table.alia,
|
||||
sourceId: nodeMenu.table.sourceId,
|
||||
tableId: nodeMenu.table.tableId,
|
||||
items: nodeMenu.columns
|
||||
};
|
||||
/**
|
||||
* 这里可以进行业务判断、是否能够添加该节点
|
||||
*/
|
||||
//console.log(this.data)
|
||||
this.data.nodeList.push(node);
|
||||
this.$nextTick(function() {
|
||||
let nodeItems = node.items;
|
||||
if (node.viewOnly > 0) {
|
||||
this.jsPlumb.draggable(node.nodeKey, {
|
||||
containment: "parent",
|
||||
stop: function(el) {
|
||||
// 拖拽节点结束后的对调
|
||||
//console.log("拖拽结束: ", el);
|
||||
}
|
||||
});
|
||||
}
|
||||
for (var j = 0; j < nodeItems.length; j++) {
|
||||
// 设置源点,可以拖出线连接其他节点
|
||||
//let obj = lodash.merge(this.jsplumbSourceOptions, {});
|
||||
this.jsPlumb.makeSource(nodeItems[j].itemId);
|
||||
// 设置目标点,其他源点拖出的线可以连接该节点
|
||||
this.jsPlumb.makeTarget(
|
||||
nodeItems[j].itemId,
|
||||
this.jsplumbTargetOptions
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 删除节点
|
||||
* @param nodeId 被删除节点的ID
|
||||
*/
|
||||
deleteNode(nodeId) {
|
||||
this.$confirm("确定要删除节点及有关的所有连线" + nodeId + "?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning",
|
||||
closeOnClickModal: false
|
||||
})
|
||||
.then(() => {
|
||||
/**
|
||||
* 这里需要进行业务判断,是否可以删除
|
||||
*/
|
||||
this.data.nodeList = this.data.nodeList.filter(function(node) {
|
||||
if (node.nodeKey === nodeId) {
|
||||
// 伪删除,将节点隐藏,否则会导致位置错位
|
||||
// node.show = false
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
//删除和该节点有关的所有连线
|
||||
let deleteArr = [];
|
||||
this.data.lineList = this.data.lineList.filter(function(line) {
|
||||
let fromArr = line.from.split("-");
|
||||
let fromTablePrex = fromArr[0] + "-" + fromArr[1];
|
||||
let toArr = line.to.split("-");
|
||||
let toTablePrex = toArr[0] + "-" + toArr[1];
|
||||
if (fromTablePrex === nodeId) {
|
||||
deleteArr.push({ sourceId: line.from, targetId: line.to });
|
||||
return false;
|
||||
}
|
||||
if (toTablePrex === nodeId) {
|
||||
deleteArr.push({ sourceId: line.to, targetId: line.from });
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
this.$nextTick(function() {
|
||||
for (let i = 0; i < deleteArr.length; i++) {
|
||||
var conn = this.jsPlumb.getConnections({
|
||||
source: deleteArr[i].sourceId,
|
||||
target: deleteArr[i].targetId
|
||||
})[0];
|
||||
this.jsPlumb.deleteConnection(conn);
|
||||
}
|
||||
this.jsPlumb.removeAllEndpoints(nodeId);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
return true;
|
||||
},
|
||||
clickNode(node) {
|
||||
let nodeId = node.nodeKey;
|
||||
this.activeElement.type = "node";
|
||||
this.activeElement.nodeId = nodeId;
|
||||
this.$refs.nodeForm.nodeInit(this.data, node);
|
||||
},
|
||||
// 是否具有该线
|
||||
hasLine(from, to) {
|
||||
for (var i = 0; i < this.data.lineList.length; i++) {
|
||||
var line = this.data.lineList[i];
|
||||
if (line.from === from && line.to === to) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
// 是否含有相反的线
|
||||
hashOppositeLine(from, to) {
|
||||
return this.hasLine(to, from);
|
||||
},
|
||||
// 是否是同表之间列连线
|
||||
isSameTableLine(from, to) {
|
||||
let fromArr = from.split("-");
|
||||
let fromTablePrex = fromArr[0] + "-" + fromArr[1];
|
||||
let toArr = to.split("-");
|
||||
let toTablePrex = toArr[0] + "-" + toArr[1];
|
||||
if (fromTablePrex === toTablePrex) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
// 两个表间只能连一根线
|
||||
isMutilLine(from, to) {
|
||||
let fromArr = from.split("-");
|
||||
let fromTablePrex = fromArr[0] + "-" + fromArr[1];
|
||||
let toArr = to.split("-");
|
||||
let toTablePrex = toArr[0] + "-" + toArr[1];
|
||||
let newLine = fromTablePrex + "_" + toTablePrex;
|
||||
let newLine2 = toTablePrex + "_" + fromTablePrex;
|
||||
for (var i = 0; i < this.data.lineList.length; i++) {
|
||||
var line = this.data.lineList[i];
|
||||
let existFromArr = line.from.split("-");
|
||||
let existFromTablePrex = existFromArr[0] + "-" + existFromArr[1];
|
||||
let existToArr = line.to.split("-");
|
||||
let existToTablePrex = existToArr[0] + "-" + existToArr[1];
|
||||
let oldLine = existFromTablePrex + "_" + existToTablePrex;
|
||||
let oldLine2 = existToTablePrex + "_" + existFromTablePrex;
|
||||
if (
|
||||
oldLine === newLine ||
|
||||
oldLine === newLine2 ||
|
||||
oldLine2 === newLine ||
|
||||
oldLine2 === newLine2
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
nodeRightMenu(nodeId, evt) {
|
||||
this.menu.show = true;
|
||||
this.menu.curNodeId = nodeId;
|
||||
this.menu.left = evt.x + "px";
|
||||
this.menu.top = evt.y + "px";
|
||||
},
|
||||
repaintEverything() {
|
||||
this.jsPlumb.repaint();
|
||||
},
|
||||
// 流程数据信息
|
||||
dataInfo() {
|
||||
this.flowInfoVisible = true;
|
||||
this.$nextTick(function() {
|
||||
this.$refs.flowInfo.init();
|
||||
});
|
||||
},
|
||||
// 加载流程图
|
||||
dataReload(data) {
|
||||
this.easyFlowVisible = false;
|
||||
this.data.nodeList = [];
|
||||
this.data.lineList = [];
|
||||
this.$nextTick(() => {
|
||||
data = lodash.cloneDeep(data);
|
||||
this.easyFlowVisible = true;
|
||||
this.data = data;
|
||||
this.$nextTick(() => {
|
||||
this.jsPlumb = jsPlumb.getInstance();
|
||||
this.$nextTick(() => {
|
||||
this.jsPlumbInit();
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
// 刷新数据
|
||||
refreshData() {
|
||||
//this.dataReload(getDataB());
|
||||
},
|
||||
zoomAdd() {
|
||||
if (this.zoom >= 1) {
|
||||
return;
|
||||
}
|
||||
this.zoom = this.zoom + 0.1;
|
||||
this.$refs.efContainer.style.transform = `scale(${this.zoom})`;
|
||||
this.jsPlumb.setZoom(this.zoom);
|
||||
},
|
||||
zoomSub() {
|
||||
if (this.zoom <= 0) {
|
||||
return;
|
||||
}
|
||||
this.zoom = this.zoom - 0.1;
|
||||
this.$refs.efContainer.style.transform = `scale(${this.zoom})`;
|
||||
this.jsPlumb.setZoom(this.zoom);
|
||||
},
|
||||
// 下载数据
|
||||
downloadData() {
|
||||
this.$confirm("确定要下载该流程数据吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning",
|
||||
closeOnClickModal: false
|
||||
})
|
||||
.then(() => {
|
||||
var datastr =
|
||||
"data:text/json;charset=utf-8," +
|
||||
encodeURIComponent(JSON.stringify(this.data, null, "\t"));
|
||||
var downloadAnchorNode = document.createElement("a");
|
||||
downloadAnchorNode.setAttribute("href", datastr);
|
||||
downloadAnchorNode.setAttribute("download", "data.json");
|
||||
downloadAnchorNode.click();
|
||||
downloadAnchorNode.remove();
|
||||
this.$message.success("正在下载中,请稍后...");
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
openHelp() {
|
||||
this.flowHelpVisible = true;
|
||||
this.$nextTick(function() {
|
||||
this.$refs.flowHelp.init();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
/*画布容器*/
|
||||
#efContainer {
|
||||
position: relative;
|
||||
overflow: scroll;
|
||||
flex: 1;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/*顶部工具栏*/
|
||||
.ef-tooltar {
|
||||
padding-left: 10px;
|
||||
box-sizing: border-box;
|
||||
height: 42px;
|
||||
line-height: 42px;
|
||||
z-index: 3;
|
||||
border-bottom: 1px solid #dadce0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.jtk-overlay {
|
||||
cursor: pointer;
|
||||
color: #4a4a4a;
|
||||
}
|
||||
|
||||
/* 连线中的label 样式*/
|
||||
.jtk-overlay.flowLabel:not(.aLabel) {
|
||||
padding: 4px 10px;
|
||||
background-color: white;
|
||||
color: #565758 !important;
|
||||
border: 1px solid #e0e3e7;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.ef-dot {
|
||||
background-color: #1879ff;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.ef-dot-hover {
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
.ef-rectangle {
|
||||
background-color: #1879ff;
|
||||
}
|
||||
|
||||
.ef-rectangle-hover {
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
.ef-drop-hover {
|
||||
border: 1px dashed #1879ff;
|
||||
}
|
||||
.el-tag + .el-tag {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.button-new-tag {
|
||||
margin-left: 10px;
|
||||
height: 32px;
|
||||
line-height: 30px;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.input-new-tag {
|
||||
width: 90px;
|
||||
margin-left: 10px;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
</style>
|
||||
11252
src/views/icon/index.vue
Normal file
525
src/views/kgbuilder/components/kg_form.vue
Normal file
@ -0,0 +1,525 @@
|
||||
<template>
|
||||
<el-drawer
|
||||
ref="drawer"
|
||||
title="编辑"
|
||||
:with-header="false"
|
||||
:visible.sync="drawerShow"
|
||||
:direction="direction"
|
||||
:append-to-body="true"
|
||||
>
|
||||
<!--导入-->
|
||||
<div v-show="operate == 'import'" class="pd-20">
|
||||
<el-form>
|
||||
<el-form-item label="类型" label-width="120px">
|
||||
<el-radio-group v-model="uploadParam.type">
|
||||
<el-radio key="index-1" label="1">三元组</el-radio>
|
||||
<el-radio key="index-2" label="2">单元格树</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<div v-if="uploadParam.type==1">
|
||||
<div>导入csv或者excel,三元组结构:节点-节点-关系如果是csv,注意字符集为utf-8无bom格式<br/>【不会的用记事本打开,然后另存为,选择utf-8 无bom】)</div>
|
||||
<el-carousel >
|
||||
<el-carousel-item v-for="item in [require('@/assets/sanyuanzuimport1.png'),require('@/assets/sanyuanzuimport2.png')]" :key="item">
|
||||
<img :src="item" />
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
</div>
|
||||
<div v-else style="max-height:calc(100vh - 80px);over-flow-y:scroll">
|
||||
<div>支持合并单元格,设置颜色,设置关系需在节点后以###拼接,只识别一组关系</div>
|
||||
<el-carousel height="450px">
|
||||
<el-carousel-item v-for="item in [require('@/assets/treeimport1.png'),require('@/assets/treeimport2.png')]" :key="item">
|
||||
<img :src="item" style="height: 500px;"/>
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="图谱领域" label-width="120px">
|
||||
<el-input
|
||||
style="width:100%"
|
||||
v-model="uploadParam.domain"
|
||||
placeholder="请输入内容"
|
||||
>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="选择文件" label-width="120px">
|
||||
<el-upload
|
||||
class=""
|
||||
ref="uploadExcel"
|
||||
:action="uploadGraphUrl"
|
||||
accept=".csv,.xls,.xlsx"
|
||||
:show-file-list="true"
|
||||
:data="uploadParam"
|
||||
:on-success="uploadExcelSuccess"
|
||||
:auto-upload="false"
|
||||
>
|
||||
<el-button
|
||||
slot="trigger"
|
||||
class="btn-bo"
|
||||
>
|
||||
<i class="el-icon-upload"></i>
|
||||
选择文件
|
||||
</el-button>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
<el-form-item label-width="120px">
|
||||
<el-button @click="resetSubmit">取 消</el-button>
|
||||
<el-button type="primary" @click="submitUpload">确 定</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<!--导出-->
|
||||
<div v-show="operate == 'export'" class="pd-20">
|
||||
<el-form>
|
||||
<el-form-item label="图谱领域" label-width="120px">
|
||||
<el-autocomplete
|
||||
style="width:100%"
|
||||
v-model="uploadParam.domain"
|
||||
placeholder="请输入内容"
|
||||
><!--:fetch-suggestions="querySearch"-->
|
||||
</el-autocomplete>
|
||||
</el-form-item>
|
||||
<el-button type="primary" @click="exportCsv">确 定</el-button>
|
||||
</el-form>
|
||||
</div>
|
||||
<!--节点编辑-->
|
||||
<div v-show="operate == 'nodeEdit'" class="pd-20">
|
||||
<el-tabs
|
||||
type="card"
|
||||
tab-position="top"
|
||||
v-model="propActiveName"
|
||||
@tab-click="propHandleClick"
|
||||
style="margin: 10px"
|
||||
>
|
||||
<el-tab-pane label="属性编辑" name="propEdit">
|
||||
<el-form :model="graphData">
|
||||
<el-form-item label="节点名称" label-width="120px">
|
||||
<el-input v-model="graphData.name" style="width:324px"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="选择颜色" label-width="120px">
|
||||
<el-color-picker
|
||||
id="colorpicker"
|
||||
v-model="graphData.color"
|
||||
:predefine="predefineColors"
|
||||
>
|
||||
</el-color-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="节点半径" label-width="120px">
|
||||
<el-slider :min="25" v-model="graphData.r" style="width:324px"></el-slider>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="添加图片" name="propImage">
|
||||
<el-form>
|
||||
<el-form-item label="本地上传" label-width="120px">
|
||||
<el-upload
|
||||
class=""
|
||||
name="file"
|
||||
ref="upload"
|
||||
:headers="uploadHeader"
|
||||
:action="uploadFileUrl"
|
||||
accept=".jpg,.png"
|
||||
:multiple="false"
|
||||
:show-file-list="false"
|
||||
:data="uploadImageParam"
|
||||
:before-upload="beforeUpload"
|
||||
:on-success="uploadSuccess"
|
||||
:auto-upload="true"
|
||||
:limit="1"
|
||||
>
|
||||
<el-button slot="trigger" size="small" type="primary"
|
||||
>选择</el-button
|
||||
>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
<el-form-item label="网络地址" label-width="120px">
|
||||
<el-input v-model="netImageUrl" style="width: 60%"></el-input>
|
||||
<a href="javascript:void(0)" @click="addNetImage" class="cg">
|
||||
<i class="el-icon-plus"></i>
|
||||
</a>
|
||||
</el-form-item>
|
||||
<el-form-item label="已选图片" label-width="120px">
|
||||
<ul class="el-upload-list el-upload-list--picture-card">
|
||||
<li
|
||||
v-for="item in nodeImageList"
|
||||
class="el-upload-list__item is-success"
|
||||
>
|
||||
<img
|
||||
:src="imageUrlFormat(item)"
|
||||
alt=""
|
||||
class="el-upload-list__item-thumbnail"
|
||||
/>
|
||||
<label class="el-upload-list__item-status-label">
|
||||
<i class="el-icon-upload-success el-icon-check"></i>
|
||||
</label>
|
||||
<i class="el-icon-close" @click="imageHandleRemove(item)"></i>
|
||||
<span class="el-upload-list__item-actions">
|
||||
<span class="el-upload-list__item-preview">
|
||||
<i
|
||||
class="el-icon-zoom-in"
|
||||
@click="handlePictureCardPreview(item)"
|
||||
></i>
|
||||
</span>
|
||||
<span class="el-upload-list__item-delete">
|
||||
<i
|
||||
class="el-icon-delete"
|
||||
@click="imageHandleRemove(item)"
|
||||
></i>
|
||||
</span>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="添加描述" name="richTextEdit">
|
||||
<div
|
||||
ref="editorToolbar"
|
||||
class="wange-toolbar"
|
||||
></div>
|
||||
<div
|
||||
ref="editorContent"
|
||||
class="wangeditor-form"
|
||||
></div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button
|
||||
v-show="propActiveName == 'propImage'"
|
||||
type="primary"
|
||||
@click="saveNodeImage"
|
||||
class="btn-line cur"
|
||||
>保存</el-button
|
||||
>
|
||||
<el-button
|
||||
v-show="propActiveName == 'richTextEdit'"
|
||||
@click="saveNodeContent"
|
||||
type="primary"
|
||||
class="btn-line cur"
|
||||
>保存</el-button
|
||||
>
|
||||
<el-button
|
||||
v-show="propActiveName == 'propEdit' && graphData.uuid != 0"
|
||||
type="primary"
|
||||
@click="createNode"
|
||||
>更新</el-button
|
||||
>
|
||||
<el-button
|
||||
v-show="propActiveName == 'propEdit' && graphData.uuid == 0"
|
||||
type="primary"
|
||||
@click="createNode"
|
||||
>创建</el-button
|
||||
>
|
||||
<el-button @click="resetSubmit">取消</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<!--段落识别-->
|
||||
<div v-show="operate == 'recognition'" class="pd-20">
|
||||
<div class="mb-l">段落识别</div>
|
||||
开发中。。。
|
||||
</div>
|
||||
<!--添加下级-->
|
||||
<div v-show="operate == 'batchAddChild'" class="pd-20">
|
||||
<div class="mb-l">添加下级</div>
|
||||
<el-form ref="form" label-width="120px">
|
||||
<el-form-item label="关系">
|
||||
<el-input v-model="batchCreateData.relation"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="子节点名称">
|
||||
<el-input v-model="batchCreateData.targetNodeNames"></el-input>
|
||||
<span class="mb-label">(多个以英文逗号隔开)</span>
|
||||
</el-form-item>
|
||||
<el-form-item label-width="120px">
|
||||
<el-button type="primary" @click="batchCreateChildNode">确定</el-button>
|
||||
<el-button @click="resetSubmit">取消</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
</div>
|
||||
<!--批量添加-->
|
||||
<div v-show="operate == 'batchAdd'">
|
||||
<div class="mb-l">批量添加</div>
|
||||
<el-form ref="form" label-width="120px">
|
||||
<el-form-item label="源节点名称">
|
||||
<el-input v-model="batchCreateData.sourceNodeName"></el-input>
|
||||
<span class="mb-label">(只能添加一个)</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="关系">
|
||||
<el-input v-model="batchCreateData.relation"></el-input>
|
||||
<span class="mb-label">(只能添加一个)</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="子节点名称">
|
||||
<el-input v-model="batchCreateData.targetNodeNames"></el-input>
|
||||
<span class="mb-label">(多个以英文逗号隔开,可不填)</span>
|
||||
</el-form-item>
|
||||
<el-form-item label-width="120px">
|
||||
<el-button type="primary" @click="batchCreateNode">确定</el-button>
|
||||
<el-button @click="resetSubmit">取消</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
|
||||
<!--添加同级-->
|
||||
<div v-show="operate == 'batchAddSame'" class="pd-20">
|
||||
<div class="mb-l">添加同级</div>
|
||||
<el-form ref="form" label-width="120px">
|
||||
<el-form-item label="源节点名称">
|
||||
<el-input v-model="batchCreateData.sourceNodeName"></el-input>
|
||||
<span class="mb-label">(多个以英文逗号隔开)</span>
|
||||
</el-form-item>
|
||||
<el-form-item label-width="120px">
|
||||
<el-button type="primary" @click="batchCreateSameNode">确定</el-button>
|
||||
<el-button @click="resetSubmit">取消</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import wangEditor from 'wangeditor'
|
||||
import { kgBuilderApi } from "@/api";
|
||||
export default {
|
||||
props: {
|
||||
data: Object
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
domainId:0,
|
||||
uploadHeader:{
|
||||
// 'Content-Type': 'multipart/form-data'
|
||||
},
|
||||
uploadGraphUrl: process.env.VUE_APP_BASE_API+"/importGraph",
|
||||
direction: "rtl",
|
||||
drawerShow: false,
|
||||
operate: "",
|
||||
batchCreateData:{
|
||||
sourceUuid:'',
|
||||
sourceName: '',
|
||||
targetNames: '',
|
||||
relation: ''
|
||||
},
|
||||
propActiveName: "propEdit",
|
||||
contentActiveName: "propImage",
|
||||
uploadFileUrl: process.env.VUE_APP_BASE_API+"/file/upload",
|
||||
graphData:{
|
||||
uuid: '0',
|
||||
color: "ff4500",
|
||||
name: "",
|
||||
r: 30,
|
||||
x: "",
|
||||
y: ""
|
||||
},
|
||||
predefineColors: [
|
||||
"#ff4500",
|
||||
"#ff8c00",
|
||||
"#90ee90",
|
||||
"#00ced1",
|
||||
"#1e90ff",
|
||||
"#c71585"
|
||||
],
|
||||
editorContent:"",
|
||||
uploadImageParam: {},
|
||||
nodeImageList: [],
|
||||
netImageUrl: "",
|
||||
uploadParam: { domain: "", type: '1' },
|
||||
};
|
||||
},
|
||||
components: {},
|
||||
methods: {
|
||||
init(drawerShow,operate,domain) {
|
||||
this.operate = operate;
|
||||
this.drawerShow = drawerShow;
|
||||
this.uploadParam.domain=domain;
|
||||
this.propActiveName="propEdit";
|
||||
},
|
||||
initNode(drawerShow,operate,node,domainId) {
|
||||
this.operate = operate;
|
||||
this.drawerShow = drawerShow;
|
||||
this.domainId=domainId;
|
||||
this.graphData=node;
|
||||
this.propActiveName="propEdit";
|
||||
},
|
||||
initBatchAddChild(drawerShow,operate,node,domain) {
|
||||
this.operate = operate;
|
||||
this.drawerShow = drawerShow;
|
||||
this.domain=domain;
|
||||
this.batchCreateData.sourceUuid=node.uuid;
|
||||
this.propActiveName="propEdit";
|
||||
},
|
||||
batchCreateNode(){
|
||||
this.init(false,"");
|
||||
this.$emit("batchCreateNode",this.batchCreateData);
|
||||
},
|
||||
batchCreateChildNode(){
|
||||
this.init(false,"");
|
||||
this.$emit("batchCreateChildNode",this.batchCreateData);
|
||||
},
|
||||
batchCreateSameNode(){
|
||||
this.init(false,"");
|
||||
this.$emit("batchCreateSameNode",this.batchCreateData);
|
||||
},
|
||||
createNode(){
|
||||
this.init(false,"");
|
||||
this.$emit("createNode",this.graphData);
|
||||
},
|
||||
initImage(imageList){
|
||||
this.nodeImageList=imageList;
|
||||
},
|
||||
initContent(content){
|
||||
this.editorContent=content;
|
||||
},
|
||||
bthRecognition(){
|
||||
|
||||
},
|
||||
resetSubmit() {
|
||||
this.drawerShow=false;
|
||||
this.propActiveName="propEdit"
|
||||
},
|
||||
//节点上传图片
|
||||
saveNodeImage() {
|
||||
let data = {
|
||||
domainId: this.domainId,
|
||||
nodeId: this.graphData.uuid,
|
||||
//imageList: JSON.stringify(this.nodeImageList)
|
||||
imagePath: this.nodeImageList[0].file
|
||||
};
|
||||
this.init(false,"");
|
||||
this.$emit("saveNodeImage",data);
|
||||
},
|
||||
//上传富文本
|
||||
saveNodeContent() {
|
||||
let data = {
|
||||
domainId: this.domainId,
|
||||
nodeId: this.graphData.uuid,
|
||||
content: this.editorContent
|
||||
};
|
||||
this.init(false,"");
|
||||
this.$emit("saveNodeContent",data);
|
||||
},
|
||||
//预览图片
|
||||
handlePictureCardPreview(item) {
|
||||
this.dialogImageUrl = this.imageUrlFormat(item);
|
||||
this.dialogImageVisible = true;
|
||||
},
|
||||
//添加网络图片
|
||||
addNetImage() {
|
||||
if (this.netImageUrl != "") {
|
||||
if(this.nodeImageList.length==0){
|
||||
this.nodeImageList.push({ file: this.netImageUrl, imageType: 1 });
|
||||
this.netImageUrl = "";
|
||||
}else{
|
||||
this.$message({
|
||||
message: '一个节点只能使用一张图片,如果有多张图片,可以添加到富文本中',
|
||||
type: 'warning'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
//移除图片
|
||||
imageHandleRemove(url) {
|
||||
this.nodeImageList.splice(this.nodeImageList.indexOf(url), 1);
|
||||
},
|
||||
//图片格式化
|
||||
imageUrlFormat(item) {
|
||||
if(item.file.indexOf("http")===0){
|
||||
return item.file;
|
||||
}else{
|
||||
return process.env.VUE_APP_BASE_API+item.file;
|
||||
}
|
||||
},
|
||||
beforeUpload(){
|
||||
if(this.nodeImageList.length>0){
|
||||
this.$message({
|
||||
message: '一个节点只能使用一张图片,如果有多张图片,可以添加到富文本中',
|
||||
type: 'warning'
|
||||
});
|
||||
}
|
||||
},
|
||||
uploadSuccess(res, file) {
|
||||
if (res.success == 1) {
|
||||
for (let i = 0; i < res.results.length; i++) {
|
||||
let fileItem = res.results[i];
|
||||
if(this.nodeImageList.length==0){
|
||||
this.nodeImageList.push({ file: fileItem.url, imageType: 0 });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.$message.error(res.msg);
|
||||
}
|
||||
},
|
||||
initEditor() {
|
||||
if (this.editor != null) return;
|
||||
let _this=this;
|
||||
this.editor = new wangEditor(this.$refs.editorToolbar, this.$refs.editorContent)
|
||||
this.editor.config.onchange = function(html) {
|
||||
_this.editorContent = html;
|
||||
};
|
||||
this.editor.config.uploadFileName = "file";
|
||||
//this.editor.config.uploadImgHeaders = headers;
|
||||
this.editor.config.uploadImgServer = process.env.VUE_APP_BASE_API+"/file/upload"; // 上传图片到服务器
|
||||
this.editor.config.uploadImgHooks = {
|
||||
// 如果服务器端返回的不是 {errno:0, data: [...]} 这种格式,可使用该配置
|
||||
// (但是,服务器端返回的必须是一个 JSON 格式字符串!!!否则会报错)
|
||||
customInsert: function(insertImg, res, editor) {
|
||||
// 图片上传并返回结果,自定义插入图片的事件(而不是编辑器自动插入图片!!!)
|
||||
// insertImg 是插入图片的函数,editor 是编辑器对象,result 是服务器端返回的结果
|
||||
for (let i = 0; i < res.results.length; i++) {
|
||||
let fileItem = res.results[i];
|
||||
insertImg(process.env.VUE_APP_BASE_API+fileItem.url);
|
||||
}
|
||||
}
|
||||
};
|
||||
this.editor.create();
|
||||
},
|
||||
propHandleClick(tab) {
|
||||
if (tab.name == "richTextEdit") {
|
||||
this.initEditor();
|
||||
this.editorContent = "";
|
||||
this.$emit("initNodeContent",{domainId:this.domainId,nodeId:this.graphData.uuid});
|
||||
|
||||
}
|
||||
if (tab.name == "propImage") {
|
||||
this.nodeImageList = [];
|
||||
this.$emit("initNodeImage",{domainId:this.domainId,nodeId:this.graphData.uuid});
|
||||
}
|
||||
},
|
||||
exportCsv() {
|
||||
let data = { domain: this.uploadParam.domain };
|
||||
kgBuilderApi.exportGraph(data).then(result => {
|
||||
if (result.code == 200) {
|
||||
this.exportFormVisible = false;
|
||||
window.open(process.env.VUE_APP_BASE_API+result.csvUrl);
|
||||
}
|
||||
});
|
||||
},
|
||||
submitUpload() {
|
||||
this.$refs.uploadExcel.submit();
|
||||
//关闭窗口
|
||||
this.init(false,"");
|
||||
//刷新领域标签
|
||||
this.$emit("getDomain",1);
|
||||
},
|
||||
uploadExcelSuccess() {
|
||||
this.$refs.uploadExcel.clearFiles();
|
||||
this.uploadParam.domain = "";
|
||||
this.$message({
|
||||
message: "操作成功",
|
||||
type: "success"
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.pd-20{
|
||||
padding: 20px;
|
||||
}
|
||||
.el-drawer__body {
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
||||
131
src/views/kgbuilder/components/kg_help.vue
Normal file
@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="帮助"
|
||||
:visible.sync="dialogVisible"
|
||||
width="70%"
|
||||
customClass="flowHelp"
|
||||
>
|
||||
<el-tabs tab-position="left">
|
||||
<el-tab-pane label="基本功能">
|
||||
<el-divider content-position="left">基本功能</el-divider>
|
||||
<div>1. 新增节点,添加连线,快速添加节点和关系</div>
|
||||
<div>2. 节点的颜色和大小可修改</div>
|
||||
<div>3. 节点和关系的编辑,删除</div>
|
||||
<div>4. 导出成图片</div>
|
||||
<div>5. csv导入三元组、excel导入单元格树</div>
|
||||
<div>6. 导出csv</div>
|
||||
<div>7. 添加图片和富文本</div>
|
||||
<div>8. 节点之间多个关系</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="运行与启动">
|
||||
<el-divider content-position="left">安装jdk</el-divider>
|
||||
|
||||
<div>
|
||||
可参考:https://blog.csdn.net/qq_42003566/article/details/82629570
|
||||
</div>
|
||||
<el-divider content-position="left">安装Neo4j</el-divider>
|
||||
<div>
|
||||
可参考:[https://www.cnblogs.com/ljhdo/p/5521577.html](https://www.cnblogs.com/ljhdo/p/5521577.html),注意开放外网访问
|
||||
0.0.0.0
|
||||
</div>
|
||||
<el-divider content-position="left">IDEA 导入项目</el-divider>
|
||||
<div>
|
||||
导入成功后对着项目根目录,右键->maven->reimport,等待其执行完成
|
||||
</div>
|
||||
<div>
|
||||
倘若下载jar包太慢,自己配置外部maven仓库https://blog.csdn.net/liu_shi_jun/article/details/78733633
|
||||
以上配置在linux下配置自行百度
|
||||
</div>
|
||||
<el-divider content-position="left">配置参数</el-divider>
|
||||
<div>找到目录 src/main/resources</div>
|
||||
<div>
|
||||
修改application.yml,neo4j配置url,password,改成自己的,同理修改mysql(mysql脚本在根目录下,kg_builder.sql)
|
||||
</div>
|
||||
<el-divider content-position="left">后台打包发布</el-divider>
|
||||
<div>
|
||||
在idea 右侧 有 maven project
|
||||
工具栏,点击展开lifecycle-clean,然后install,等待完成后在控制台可以看见打包的目录,
|
||||
</div>
|
||||
<div>
|
||||
例如:[INFO] Installing
|
||||
F:\git\Neo4j\kgmaker\target\kgmaker-0.0.1-SNAPSHOT.jar
|
||||
复制jar包,去windows 或者linux下 切换到jar包目录执行 jar包 java -jar
|
||||
xxx.jar 即可启动,想部署到tomcat自行百度,springboot配置外部tomcat
|
||||
</div>
|
||||
<el-divider content-position="left">启动前端</el-divider>
|
||||
<div>没有前端基础的小伙伴,先自行百度安装nodejs,npm等环境</div>
|
||||
<div>1.npm install // 安装依赖</div>
|
||||
<div>2.npm run serve //启动</div>
|
||||
<div>3.npm run build //发布</div>
|
||||
<div>启动后访问http://localhost</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="注意事项">
|
||||
<el-divider content-position="left">图谱三元组导入</el-divider>
|
||||
<div>
|
||||
支持,.xlsx,.xls,.csv,编码格式一定要是utf-8
|
||||
无bom格式的,格式:节点-节点-关系,在本地测试时上传下载的文件要和neo4j在同一台电脑,当然如果能传到七牛或者hdfs上也是一样的,必须确认neo4j能访问到,否则load不成功
|
||||
</div>
|
||||
<el-divider content-position="left">图数据库版本与驱动</el-divider>
|
||||
<div>本项目适用Neo4j版本3.x版本,对应驱动是1.7.5</div>
|
||||
<div>4.x版本,需要升级驱动,对应的utils也需要升级,自行扩展</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="推荐">
|
||||
<el-divider content-position="left">图数据库</el-divider>
|
||||
<div>
|
||||
由于neo4j集群等功能需要付费使用,囊中羞涩的可以换成Nebula,国产的,性能贼棒,集群开源版也开放
|
||||
<a href="https://docs.nebula-graph.com.cn"
|
||||
>Nebula https://docs.nebula-graph.com.cn</a
|
||||
>
|
||||
</div>
|
||||
<el-divider content-position="left">前端可视化</el-divider>
|
||||
<div>
|
||||
由于d3.js的文档和api开放度足够高,推荐组件化做的比较好的前端库G6
|
||||
<a href="https://docs.nebula-graph.com.cn"
|
||||
>G6 https://g6.antv.vision/zh/examples/gallery</a
|
||||
>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="源码下载">
|
||||
<el-divider content-position="left">github</el-divider>
|
||||
<div>
|
||||
<a href="https://github.com/MiracleTanC/Neo4j-KGBuilder"
|
||||
>https://github.com/MiracleTanC/Neo4j-KGBuilder</a
|
||||
>
|
||||
</div>
|
||||
<el-divider content-position="left">gitee</el-divider>
|
||||
<div>
|
||||
<a href="https://gitee.com/MiraculousWarmHeart/Neo4j"
|
||||
>https://gitee.com/MiraculousWarmHeart/Neo4j</a
|
||||
>
|
||||
</div>
|
||||
<el-divider content-position="left">分支差异</el-divider>
|
||||
<div>
|
||||
master分支不是前后端分离版本,springboot+thymleaf+vue,嵌入式太深
|
||||
</div>
|
||||
<div>dev分支是前后端分离版本springboot+vue,前端组件化</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false
|
||||
};
|
||||
},
|
||||
components: {},
|
||||
methods: {
|
||||
init() {
|
||||
this.dialogVisible = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.flowHelp {
|
||||
height: 80%;
|
||||
}
|
||||
</style>
|
||||
60
src/views/kgbuilder/components/kg_json.vue
Normal file
@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<el-dialog title="数据信息" :visible.sync="dialogVisible" width="70%">
|
||||
<el-alert
|
||||
title="使用说明"
|
||||
type="warning"
|
||||
description="以下图谱信息可以被存储起来,方便下一次数据加载"
|
||||
show-icon
|
||||
close-text="知道了"
|
||||
>
|
||||
</el-alert>
|
||||
<br />
|
||||
<!--一个高亮显示的插件-->
|
||||
<codemirror
|
||||
:value="flowJsonData"
|
||||
:options="options"
|
||||
class="code"
|
||||
></codemirror>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import "codemirror/lib/codemirror.css";
|
||||
import { codemirror } from "vue-codemirror";
|
||||
|
||||
require("codemirror/mode/javascript/javascript.js");
|
||||
|
||||
export default {
|
||||
props: {
|
||||
data: Object
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
flowJsonData: {},
|
||||
options: {
|
||||
mode: { name: "javascript", json: true },
|
||||
lineNumbers: true
|
||||
}
|
||||
};
|
||||
},
|
||||
components: {
|
||||
codemirror
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.dialogVisible = true;
|
||||
this.flowJsonData = JSON.stringify(this.data, null, 4).toString();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.el-dialog__body {
|
||||
padding: 30px 20px;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
word-break: break-all;
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
||||
69
src/views/kgbuilder/components/menu_blank.vue
Normal file
@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<!-- 空白处右键 -->
|
||||
<ul
|
||||
class="el-dropdown-menu el-popper blankmenubar"
|
||||
@click="menuBarClick"
|
||||
:style="blankMenuStyle"
|
||||
@mouseleave="menuBarLeave"
|
||||
id="blank_menubar"
|
||||
v-show="menuBarShow"
|
||||
>
|
||||
<li class="el-dropdown-menu__item" @click="btnAddSingle">
|
||||
<svg class="icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-jiedian"></use>
|
||||
</svg>
|
||||
<span class="pl-15">添加节点</span>
|
||||
</li>
|
||||
<li class="el-dropdown-menu__item" @click="btnQuickAddNode">
|
||||
<svg class="icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-add-rd"></use>
|
||||
</svg>
|
||||
<span class="pl-15">快速添加</span>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
data: Object
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
top: "0px",
|
||||
left: "0px",
|
||||
menuBarShow: false
|
||||
};
|
||||
},
|
||||
components: {},
|
||||
computed: {
|
||||
blankMenuStyle() {
|
||||
return {
|
||||
position: "absolute",
|
||||
top: this.top+'px',
|
||||
left: this.left+'px'
|
||||
};
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init(data) {
|
||||
this.top = data.top;
|
||||
this.left = data.left;
|
||||
this.menuBarShow = data.show;
|
||||
},
|
||||
btnAddSingle() {
|
||||
this.$emit("btnAddSingle");
|
||||
},
|
||||
btnQuickAddNode() {
|
||||
this.$emit("btnQuickAddNode");
|
||||
},
|
||||
menuBarClick() {
|
||||
this.menuBarShow=false;
|
||||
},
|
||||
menuBarLeave() {
|
||||
this.menuBarShow=false;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style></style>
|
||||
74
src/views/kgbuilder/components/menu_link.vue
Normal file
@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<!-- 连线按钮组 -->
|
||||
<ul
|
||||
class="el-dropdown-menu el-popper linkmenubar"
|
||||
id="link_menubar2"
|
||||
:style="linuMenuStyle"
|
||||
@mouseleave="linkMenuBarLeave"
|
||||
v-show="linkMenuShow"
|
||||
>
|
||||
<li class="el-dropdown-menu__item" @click="updateLinkName">
|
||||
<svg class="icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-editor"></use>
|
||||
</svg>
|
||||
<span class="pl-15">编辑</span>
|
||||
</li>
|
||||
<li class="el-dropdown-menu__item" @click="deleteLink">
|
||||
<svg class="icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-shanchu"></use>
|
||||
</svg>
|
||||
<span class="pl-15">删除</span>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// import * as d3 from "d3";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
top: '0px',
|
||||
left: '0px',
|
||||
linkMenuShow: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
linuMenuStyle() {
|
||||
return {
|
||||
position:'absolute',
|
||||
top: this.top+ "px",
|
||||
left: this.left+ "px"
|
||||
};
|
||||
},
|
||||
},
|
||||
components: {
|
||||
|
||||
},
|
||||
methods: {
|
||||
init(data) {
|
||||
//debugger
|
||||
this.top=data.top;
|
||||
this.left=data.left;
|
||||
this.linkMenuShow=data.show;
|
||||
|
||||
},
|
||||
updateLinkName(){
|
||||
this.$emit("updateLinkName");
|
||||
},
|
||||
deleteLink(){
|
||||
this.$emit("deleteLink");
|
||||
},
|
||||
linkMenuBarLeave() {
|
||||
//d3.select(this).style("display", "none");
|
||||
this.linkMenuShow=false;
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
|
||||
</style>
|
||||
60
src/views/kgbuilder/components/node_info.vue
Normal file
@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<el-dialog title="数据信息" :visible.sync="dialogVisible" width="70%">
|
||||
<el-alert
|
||||
title="使用说明"
|
||||
type="warning"
|
||||
description="以下流程信息可以被存储起来,方便下一次流程加载"
|
||||
show-icon
|
||||
close-text="知道了"
|
||||
>
|
||||
</el-alert>
|
||||
<br />
|
||||
<!--一个高亮显示的插件-->
|
||||
<codemirror
|
||||
:value="flowJsonData"
|
||||
:options="options"
|
||||
class="code"
|
||||
></codemirror>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import "codemirror/lib/codemirror.css";
|
||||
import { codemirror } from "vue-codemirror";
|
||||
|
||||
require("codemirror/mode/javascript/javascript.js");
|
||||
|
||||
export default {
|
||||
props: {
|
||||
data: Object
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
flowJsonData: {},
|
||||
options: {
|
||||
mode: { name: "javascript", json: true },
|
||||
lineNumbers: true
|
||||
}
|
||||
};
|
||||
},
|
||||
components: {
|
||||
codemirror
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.dialogVisible = true;
|
||||
this.flowJsonData = JSON.stringify(this.data, null, 4).toString();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.el-dialog__body {
|
||||
padding: 30px 20px;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
word-break: break-all;
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
||||
73
src/views/kgbuilder/components/node_richer.vue
Normal file
@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<div id="richContainer" :style="richerStyle" v-show="richerShow">
|
||||
<!-- <div
|
||||
class="mind-fj-box"
|
||||
v-show="showImageList.length > 0 || editorContent != ''"
|
||||
>
|
||||
<div class="mind-carousel" v-show="showImageList.length > 0">
|
||||
<el-carousel height="197px" :interval="2000" arrow="always">
|
||||
<el-carousel-item v-for="item in showImageList" :key="item.ID">
|
||||
<div class="carous-img">
|
||||
<img :src="item.fileName" alt="" />
|
||||
</div>
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
</div>
|
||||
|
||||
</div> -->
|
||||
<el-scrollbar v-show="editorContent != ''" class="mind-fj-p">
|
||||
<p v-html="editorContent"></p>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import E from "wangeditor"
|
||||
export default {
|
||||
props: {
|
||||
data: Object
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
richerShow:false,
|
||||
left:'-1000px',
|
||||
top:'-1000px',
|
||||
editor: null,
|
||||
editorContent:'',
|
||||
showImageList:[],
|
||||
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
richerStyle() {
|
||||
return {
|
||||
width: '400px',
|
||||
position: "absolute",
|
||||
top: this.top+'px',
|
||||
left: this.left+'px'
|
||||
};
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
|
||||
} ,
|
||||
components: {
|
||||
|
||||
},
|
||||
methods: {
|
||||
init(content,imageList,left,top){
|
||||
this.richerShow=true;
|
||||
this.left=left;
|
||||
this.top=top;
|
||||
this.editorContent=content;
|
||||
this.showImageList=imageList;
|
||||
},
|
||||
close(){
|
||||
this.richerShow=false;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
|
||||
</style>
|
||||
2046
src/views/kgbuilder/index.vue
Normal file
1677
src/views/kgbuilder/index_v1.vue
Normal file
125
vue.config.js
Normal file
@ -0,0 +1,125 @@
|
||||
"use strict";
|
||||
const path = require("path");
|
||||
const webpack = require("webpack");
|
||||
const defaultSettings = require("./src/settings.js");
|
||||
|
||||
function resolve(dir) {
|
||||
return path.join(__dirname, dir);
|
||||
}
|
||||
|
||||
const name = defaultSettings.title || "暖暖动听"; // 标题
|
||||
|
||||
const port = process.env.port || process.env.npm_config_port || 80; // 端口
|
||||
|
||||
// vue.config.js 配置说明
|
||||
//官方vue.config.js 参考文档 https://cli.vuejs.org/zh/config/#css-loaderoptions
|
||||
// 这里只列一部分,具体配置参考文档
|
||||
module.exports = {
|
||||
// 部署生产环境和开发环境下的URL。
|
||||
// 默认情况下,Vue CLI 会假设你的应用是被部署在一个域名的根路径上
|
||||
// 例如 https://www.dream.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.dream.vip/admin/,则设置 baseUrl 为 /admin/。
|
||||
publicPath: process.env.NODE_ENV === "production" ? "/" : "/",
|
||||
// 在npm run build 或 yarn build 时 ,生成文件的目录名称(要和baseUrl的生产环境路径一致)(默认dist)
|
||||
outputDir: "dist",
|
||||
// 用于放置生成的静态资源 (js、css、img、fonts) 的;(项目打包之后,静态资源会放在这个文件夹下)
|
||||
assetsDir: "static",
|
||||
// 是否开启eslint保存检测,有效值:ture | false | 'error'
|
||||
lintOnSave: process.env.NODE_ENV === "development",
|
||||
// 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。
|
||||
productionSourceMap: false,
|
||||
// webpack-dev-server 相关配置
|
||||
devServer: {
|
||||
host: "0.0.0.0",
|
||||
port: port,
|
||||
open: true,
|
||||
proxy: {
|
||||
// detail: https://cli.vuejs.org/config/#devserver-proxy
|
||||
[process.env.VUE_APP_BASE_API]: {
|
||||
target: `http://localhost:8080`,
|
||||
changeOrigin: true,
|
||||
pathRewrite: {
|
||||
["^" + process.env.VUE_APP_BASE_API]: ""
|
||||
}
|
||||
}
|
||||
},
|
||||
disableHostCheck: true
|
||||
},
|
||||
configureWebpack: {
|
||||
name: name,
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": resolve("src")
|
||||
}
|
||||
}
|
||||
},
|
||||
chainWebpack(config) {
|
||||
config.plugin("provide").use(webpack.ProvidePlugin, [
|
||||
{
|
||||
$: "jquery",
|
||||
jQuery: "jquery",
|
||||
jquery: "jquery",
|
||||
"window.jQuery": "jquery"
|
||||
}
|
||||
]);
|
||||
config.plugins.delete("preload"); // TODO: need test
|
||||
config.plugins.delete("prefetch"); // TODO: need test
|
||||
|
||||
// set svg-sprite-loader
|
||||
config.module
|
||||
.rule("svg")
|
||||
.exclude.add(resolve("src/assets/icons"))
|
||||
.end();
|
||||
config.module
|
||||
.rule("icons")
|
||||
.test(/\.svg$/)
|
||||
.include.add(resolve("src/assets/icons"))
|
||||
.end()
|
||||
.use("svg-sprite-loader")
|
||||
.loader("svg-sprite-loader")
|
||||
.options({
|
||||
symbolId: "icon-[name]"
|
||||
})
|
||||
.end();
|
||||
|
||||
config.when(process.env.NODE_ENV !== "development", config => {
|
||||
config
|
||||
.plugin("ScriptExtHtmlWebpackPlugin")
|
||||
.after("html")
|
||||
.use("script-ext-html-webpack-plugin", [
|
||||
{
|
||||
// `runtime` must same as runtimeChunk name. default is `runtime`
|
||||
inline: /runtime\..*\.js$/
|
||||
}
|
||||
])
|
||||
.end();
|
||||
config.optimization.splitChunks({
|
||||
chunks: "all",
|
||||
cacheGroups: {
|
||||
libs: {
|
||||
name: "chunk-libs",
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
priority: 10,
|
||||
chunks: "initial" // only package third parties that are initially dependent
|
||||
},
|
||||
elementUI: {
|
||||
name: "chunk-elementUI", // split elementUI into a single package
|
||||
priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
|
||||
test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
|
||||
},
|
||||
commons: {
|
||||
name: "chunk-commons",
|
||||
test: resolve("src/components"), // can customize your rules
|
||||
minChunks: 3, // minimum common number
|
||||
priority: 5,
|
||||
reuseExistingChunk: true
|
||||
}
|
||||
}
|
||||
});
|
||||
config.optimization.runtimeChunk("single"),
|
||||
{
|
||||
from: path.resolve(__dirname, "./public/robots.txt"), //防爬虫文件
|
||||
to: "./" //到根目录下
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||