This commit is contained in:
不考上研不改网名 2025-11-07 09:29:29 +08:00
commit 7e635bd243
77 changed files with 55768 additions and 0 deletions

22
.editorconfig Normal file
View 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
View File

@ -0,0 +1,8 @@
# 开发环境配置
ENV = 'development'
# 开发环境
VUE_APP_BASE_API = '/kg-api'
# 路由懒加载
VUE_CLI_BABEL_TRANSPILE_MODULES = true

6
.env.production Normal file
View File

@ -0,0 +1,6 @@
# 生产环境配置
ENV = 'production'
# 生产环境
VUE_APP_BASE_API = '/kg-api'

7
.env.staging Normal file
View File

@ -0,0 +1,7 @@
NODE_ENV = production
# 测试环境配置
ENV = 'staging'
# 测试环境
VUE_APP_BASE_API = '/stage-api'

10
.eslintignore Normal file
View File

@ -0,0 +1,10 @@
# 忽略build目录下类型为js的文件的语法检查
build/*.js
# 忽略src/assets目录下文件的语法检查
src/assets
# 忽略public目录下文件的语法检查
public
# 忽略当前目录下为js的文件的语法检查
*.js
# 忽略当前目录下为vue的文件的语法检查
*.vue

23
.gitignore vendored Normal file
View 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
View 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
View File

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

15178
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

72
package.json Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

18
public/index.html Normal file
View 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
View File

@ -0,0 +1,2 @@
User-agent: *
Disallow: /

539
public/static/icon/demo.css Normal file
View File

@ -0,0 +1,539 @@
/* Logo 字体 */
@font-face {
font-family: "iconfont logo";
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
}
.logo {
font-family: "iconfont logo";
font-size: 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;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

168
public/static/kgData.json Normal file
View 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
View 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
View File

@ -0,0 +1,2 @@
export { default as kgBuilderApi } from "./modules/kgBuilderApi";
export { default as datasourceApi } from "./modules/datasourceApi";

View 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();

View 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
View 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();
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

BIN
src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
src/assets/treeimport1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
src/assets/treeimport2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

BIN
src/assets/中国一重.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 KiB

View 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>

View 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>

View 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>

View 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>

File diff suppressed because it is too large Load Diff

View 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
View 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
View 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>

View 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>

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,6 @@
export default {
"401": "认证失败,无法访问系统资源",
"403": "当前操作没有权限",
"404": "访问资源不存在",
default: "系统未知错误,请反馈给管理员"
};

39
src/utils/event-bus.js Normal file
View 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
View 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
View 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
View 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>

View 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>

View 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>

View 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;
}

View 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、FlowchartBezier、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, // 位置建议使用01之间
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'}
}
}
}
}

View 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>

View 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>

View 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>

View 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
>&nbsp;{{ 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>

View 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">&nbsp;</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

File diff suppressed because it is too large Load Diff

View 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>

View 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配置urlpassword,改成自己的同理修改mysqlmysql脚本在根目录下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>没有前端基础的小伙伴先自行百度安装nodejsnpm等环境</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>

View 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>

View 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>

View 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>

View 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>

View 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>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

125
vue.config.js Normal file
View 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: "./" //到根目录下
};
});
}
};