dev #1

Merged
niuwu666 merged 6 commits from dev into master 2024-08-24 20:56:12 +08:00
22 changed files with 17822 additions and 312 deletions

View File

@ -1,8 +1,9 @@
{
"name": "muyu",
"version": "3.6.3",
"description": "若依管理系统",
"author": "若依",
"description": "张腾管理系统",
"author": "张腾",
"vuedraggable": "2.23.0",
"license": "MIT",
"scripts": {
"dev": "vue-cli-service serve",
@ -11,6 +12,7 @@
"preview": "node build/index.js --preview",
"lint": "eslint --ext .js,.vue src"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
@ -58,7 +60,9 @@
"vue-meta": "2.4.0",
"vue-router": "3.4.9",
"vuedraggable": "2.24.3",
"vuex": "3.6.0"
"vuex": "3.6.0",
"lodash": "4.17.15",
"vue-codemirror": "^4.0.6"
},
"devDependencies": {
"@vue/cli-plugin-babel": "4.4.6",

33
src/api/etl/etl.js 100644
View File

@ -0,0 +1,33 @@
import request from "@/utils/request";
export function showTask() {
return request({
url: '/integration/task/selectAll',
method: 'get',
})
}
export function addTask(params) {
return request({
url: '/integration/task/addTask',
method: 'post',
params
})
}
export function delTask(id) {
return request({
url: '/integration/task/delTask?id='+id,
method: 'delete',
})
}
export function updTask(params) {
return request({
url: '/integration/task/delTask',
method: 'put',
params
})
}

View File

@ -0,0 +1,40 @@
let dataA = {
name: '流程A',
nodeList: [
{
id: 'nodeA',
name: '流程A-节点A',
type: 'task',
left: '26px',
top: '161px',
ico: 'el-icon-user-solid'
},
{
id: 'nodeB',
name: '流程A-节点B',
type: 'task',
left: '340px',
top: '161px',
ico: 'el-icon-goods'
},
{
id: 'nodeC',
name: '流程A-节点C',
type: 'task',
left: '739px',
top: '161px',
ico: 'el-icon-present'
}
],
lineList: [{
from: 'nodeA',
to: 'nodeB'
}, {
from: 'nodeB',
to: 'nodeC'
}]
}
export function getDataA () {
return dataA
}

View File

@ -0,0 +1,61 @@
let dataB = {
name: '流程B',
nodeList: [
{
id: 'nodeA',
name: '节点A-不可拖拽',
type: 'task',
left: '18px',
top: '223px',
ico: 'el-icon-user-solid',
state: 'success',
viewOnly: true
},
{
id: 'nodeB',
type: 'task',
name: '流程B-节点B',
left: '351px',
top: '96px',
ico: 'el-icon-goods',
state: 'error'
},
{
id: 'nodeC',
name: '流程B-节点C',
type: 'task',
left: '354px',
top: '351px',
ico: 'el-icon-present',
state: 'warning'
}, {
id: 'nodeD',
name: '流程B-节点D',
type: 'task',
left: '723px',
top: '215px',
ico: 'el-icon-present',
state: 'running'
}
],
lineList: [{
from: 'nodeA',
to: 'nodeB',
label: '条件A'
}, {
from: 'nodeA',
to: 'nodeC',
label: '条件B'
}, {
from: 'nodeB',
to: 'nodeD'
}, {
from: 'nodeC',
to: 'nodeD'
}
]
}
export function getDataB () {
return dataB
}

View File

@ -0,0 +1,42 @@
let dataC = {
name: '流程C',
nodeList: [
{
id: 'nodeA',
name: '流程C-节点A',
type: 'task',
left: '400px',
top: '15px',
ico: 'el-icon-user-solid'
},
{
id: 'nodeB',
name: '流程C-节点B',
type: 'task',
left: '400px',
top: '200px',
ico: 'el-icon-goods'
},
{
id: 'nodeC',
name: '流程C-节点C',
type: 'task',
left: '400px',
top: '378px',
ico: 'el-icon-present'
}
],
lineList: [
{
from: 'nodeA',
to: 'nodeB'
}, {
from: 'nodeB',
to: 'nodeC'
}
]
}
export function getDataC () {
return dataC
}

View File

@ -0,0 +1,71 @@
var dataD = {
name: '流程D',
nodeList: [
{
id: 'nodeA',
name: '流程D-节点A',
type: 'task',
left: '18px',
top: '223px',
ico: 'el-icon-user-solid',
state: 'success'
},
{
id: 'nodeB',
type: 'task',
name: '流程D-节点B',
left: '351px',
top: '96px',
ico: 'el-icon-goods',
state: 'error'
},
{
id: 'nodeC',
name: '流程D-节点C',
type: 'task',
left: '354px',
top: '351px',
ico: 'el-icon-present',
state: 'warning'
}, {
id: 'nodeD',
name: '流程D-节点D',
type: 'task',
left: '723px',
top: '215px',
ico: 'el-icon-present',
state: 'running'
}
],
lineList: [{
from: 'nodeA',
to: 'nodeB',
label: '直线,自定义线样式,固定锚点',
connector: 'Straight',
anchors: ['Top', 'Bottom'],
paintStyle: {strokeWidth: 2, stroke: '#1879FF'}
}, {
from: 'nodeA',
to: 'nodeC',
label: '贝塞尔曲线,固定锚点',
connector: 'Bezier',
anchors: ['Bottom', 'Left']
}, {
from: 'nodeB',
to: 'nodeD',
label: '默认连线样式,动态锚点'
}, {
from: 'nodeC',
to: 'nodeD',
label: '默认连线样式,动态锚点'
}, {
from: 'nodeC',
to: 'nodeC',
label: '自连接'
}
]
}
export function getDataD() {
return dataD
}

View File

@ -0,0 +1,54 @@
var dataE = {
name: '流程E力导图',
nodeList: [
{
id: 'nodeA',
name: '流程D-节点A',
type: 'task',
ico: 'el-icon-user-solid',
state: 'success'
},
{
id: 'nodeB',
type: 'task',
name: '流程D-节点B',
ico: 'el-icon-goods',
state: 'error'
},
{
id: 'nodeC',
name: '流程D-节点C',
type: 'task',
ico: 'el-icon-present',
state: 'warning'
}, {
id: 'nodeD',
name: '流程D-节点D',
type: 'task',
ico: 'el-icon-present',
state: 'running'
}
],
lineList: [{
from: 'nodeA',
to: 'nodeB'
}, {
from: 'nodeA',
to: 'nodeC',
label: 'hello'
}, {
from: 'nodeB',
to: 'nodeD'
}, {
from: 'nodeC',
to: 'nodeD'
}, {
from: 'nodeC',
to: 'nodeC'
}
]
}
export function getDataE() {
return dataE
}

View File

@ -0,0 +1,182 @@
/**
* 感谢 https://github.com/chaangliu/ForceDirectedLayout/blob/master/javascript/force-directed.js
* A force directed graph layout implementation by liuchang on 2018/05/10.
*/
const CANVAS_WIDTH = 1000
const CANVAS_HEIGHT = 1000
let k
let mNodeList = []
let mEdgeList = []
let mDxMap = {}
let mDyMap = {}
let mNodeMap = {}
export function ForceDirected(data = {}) {
// generate nodes and edges
// for (let i = 0; i < 20; i++) {
// mNodeList.push(new Node(i))
// }
k = 0
mNodeList = []
mEdgeList = []
mDxMap = {}
mDyMap = {}
mNodeMap = {}
let nodeList = data.nodeList
for (let i = 0; i < nodeList.length; i++) {
let node = nodeList[i]
mNodeList.push(node)
}
// for (let i = 0; i < 20; i++) {
// let edgeCount = Math.random() * 8 + 1
// for (let j = 0; j < edgeCount; j++) {
// let targetId = Math.floor(Math.random() * 20)
// let edge = new Edge(i, targetId)
// mEdgeList.push(edge)
// }
// }
// line 转 edge
let lineList = data.lineList
for (let i = 0; i < lineList.length; i++) {
let line = lineList[i]
let edge = new Edge(line.from, line.to)
mEdgeList.push(edge)
}
if (mNodeList && mEdgeList) {
k = Math.sqrt(CANVAS_WIDTH * CANVAS_HEIGHT / mNodeList.length)
}
for (let i = 0; i < mNodeList.length; i++) {
let node = mNodeList[i]
if (node) {
mNodeMap[node.id] = node
}
}
// 随机生成坐标. Generate coordinates randomly.
let initialX, initialY, initialSize = 40.0
for (let i in mNodeList) {
initialX = CANVAS_WIDTH * 0.5
initialY = CANVAS_HEIGHT * 0.5
mNodeList[i].x = initialX + initialSize * (Math.random() - 0.5)
mNodeList[i].y = initialY + initialSize * (Math.random() - 0.5)
}
// 迭代200次. Iterate 200 times.
for (let i = 0; i < 200; i++) {
calculateRepulsive()
calculateTraction()
updateCoordinates()
}
// console.log(JSON.stringify(new Result(mNodeList, mEdgeList)))
// 坐标添加px
for (let i = 0; i < mNodeList.length; i++) {
let node = mNodeList[i]
node.left = node.x + 'px'
node.top = node.y + 'px'
node.x = undefined
node.y = undefined
}
data.nodeList = mNodeList
// console.log(data)
return data
}
function Node(id = null) {
this.id = id
this.x = 22
this.y = null
}
function Edge(source = null, target = null) {
this.source = source
this.target = target
}
/**
* 计算两个Node的斥力产生的单位位移
* Calculate the displacement generated by the repulsive force between two nodes.*
*/
function calculateRepulsive() {
let ejectFactor = 6
let distX, distY, dist
for (let i = 0; i < mNodeList.length; i++) {
mDxMap[mNodeList[i].id] = 0.0
mDyMap[mNodeList[i].id] = 0.0
for (let j = 0; j < mNodeList.length; j++) {
if (i !== j) {
distX = mNodeList[i].x - mNodeList[j].x
distY = mNodeList[i].y - mNodeList[j].y
dist = Math.sqrt(distX * distX + distY * distY)
}
if (dist < 30) {
ejectFactor = 5
}
if (dist > 0 && dist < 250) {
let id = mNodeList[i].id
mDxMap[id] = mDxMap[id] + distX / dist * k * k / dist * ejectFactor
mDyMap[id] = mDyMap[id] + distY / dist * k * k / dist * ejectFactor
}
}
}
}
/**
* 计算Edge的引力对两端Node产生的引力
* Calculate the traction force generated by the edge acted on the two nodes of its two ends.
*/
function calculateTraction() {
let condenseFactor = 3
let startNode, endNode
for (let e = 0; e < mEdgeList.length; e++) {
let eStartID = mEdgeList[e].source
let eEndID = mEdgeList[e].target
startNode = mNodeMap[eStartID]
endNode = mNodeMap[eEndID]
if (!startNode) {
console.log('Cannot find start node id: ' + eStartID + ', please check it out.')
return
}
if (!endNode) {
console.log('Cannot find end node id: ' + eEndID + ', please check it out.')
return
}
let distX, distY, dist
distX = startNode.x - endNode.x
distY = startNode.y - endNode.y
dist = Math.sqrt(distX * distX + distY * distY)
mDxMap[eStartID] = mDxMap[eStartID] - distX * dist / k * condenseFactor
mDyMap[eStartID] = mDyMap[eStartID] - distY * dist / k * condenseFactor
mDxMap[eEndID] = mDxMap[eEndID] + distX * dist / k * condenseFactor
mDyMap[eEndID] = mDyMap[eEndID] + distY * dist / k * condenseFactor
}
}
/**
* 更新坐标
* update the coordinates.
*/
function updateCoordinates() {
let maxt = 4, maxty = 3 // Additional coefficients.
for (let v = 0; v < mNodeList.length; v++) {
let node = mNodeList[v]
let dx = Math.floor(mDxMap[node.id])
let dy = Math.floor(mDyMap[node.id])
if (dx < -maxt) dx = -maxt
if (dx > maxt) dx = maxt
if (dy < -maxty) dy = -maxty
if (dy > maxty) dy = maxty
node.x = node.x + dx >= CANVAS_WIDTH || node.x + dx <= 0 ? node.x - dx : node.x + dx
node.y = node.y + dy >= CANVAS_HEIGHT || node.y + dy <= 0 ? node.y - dy : node.y + dy
}
}
function Result(nodes = null, links = null) {
this.nodes = nodes
this.links = links
}

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,225 @@
/*画布容器*/
#efContainer {
position: relative;
overflow: scroll;
flex: 1;
}
/*顶部工具栏*/
.ef-tooltar {
padding-left: 10px;
box-sizing: border-box;
height: 42px;
line-height: 42px;
z-index: 3;
border-bottom: 1px solid #DADCE0;
}
.jtk-overlay {
cursor: pointer;
color: #4A4A4A;
}
/*节点菜单*/
.ef-node-pmenu {
cursor: pointer;
height: 32px;
line-height: 32px;
width: 225px;
display: block;
font-weight: bold;
color: #4A4A4A;
padding-left: 5px;
}
.ef-node-pmenu:hover {
background-color: #E0E0E0;
}
.ef-node-menu-li {
color: #565758;
width: 150px;
border: 1px dashed #E0E3E7;
margin: 5px 0 5px 0;
padding: 5px;
border-radius: 5px;
padding-left: 8px;
}
.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
}
/*节点的最外层容器*/
.ef-node-container {
position: absolute;
display: flex;
width: 170px;
height: 32px;
border: 1px solid #E0E3E7;
border-radius: 5px;
background-color: #fff;
}
.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;
}
.ef-node-left-ico:hover {
/* 设置拖拽的样式 */
cursor: crosshair;
}
/*节点显示的文字*/
.ef-node-text {
color: #565758;
font-size: 12px;
line-height: 32px;
margin-left: 8px;
width: 100px;
/* 设置超出宽度文本显示方式*/
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;
}
/*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;
}
.ef-node-form-body {
margin-top: 10px;
padding-right: 10px;
padding-bottom: 20px;
}
/* 连线中的label 样式*/
.jtk-overlay.flowLabel:not(.aLabel) {
padding: 4px 10px;
background-color: white;
color: #565758 !important;
border: 1px solid #E0E3E7;
border-radius: 5px;
}
/* label 为空的样式 */
.emptyFlowLabel {
}
.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-img {
}
.ef-img-hover {
}
.ef-drop-hover{
border: 1px dashed #1879FF;
}

View File

@ -0,0 +1,55 @@
<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>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,157 @@
export const easyFlowMixin = {
data() {
return {
jsplumbSetting: {
// 动态锚点、位置自适应
Anchors: ['Top', 'TopCenter', 'TopRight', 'TopLeft', 'Right', 'RightMiddle', 'Bottom', 'BottomCenter', 'BottomRight', 'BottomLeft', 'Left', 'LeftMiddle'],
// 容器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: ''}],
// 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: true,
/**
* 连线的样式
*/
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: 'red'}
Scope: 'jsPlumb_DefaultScope' // 范围具有相同scope的点才可连接
},
/**
* 连线参数
*/
jsplumbConnectOptions: {
isSource: true,
isTarget: true,
// 动态锚点、提供了4个方向 Continuous、AutoDefault
anchor: 'Continuous',
// 设置连线上面的label样式
labelStyle: {
cssClass: 'flowLabel'
},
// 修改了jsplumb 源码支持label 为空传入自定义style
emptyLabelStyle: {
cssClass: 'emptyFlowLabel'
}
},
/**
* 源点配置参数
*/
jsplumbSourceOptions: {
// 设置可以拖拽的类名只要鼠标移动到该类名上的DOM就可以拖拽连线
filter: '.flow-node-drag',
filterExclude: false,
anchor: 'Continuous',
// 是否允许自己连接自己
allowLoopback: true,
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: true,
connector: ['Flowchart', {curviness: 50}],
connectorStyle: {
// 线的颜色
stroke: 'red',
// 线的粗细,值越大线越粗
strokeWidth: 1,
// 设置外边线的颜色,默认设置透明,这样别人就看不见了,点击线的时候可以不用精确点击,参考 https://blog.csdn.net/roymno2/article/details/72717101
outlineStroke: 'transparent',
// 线外边的宽,值越大,线的点击范围越大
outlineWidth: 10
},
connectorHoverStyle: {stroke: 'red', strokeWidth: 2}
},
jsplumbTargetOptions: {
// 设置可以拖拽的类名只要鼠标移动到该类名上的DOM就可以拖拽连线
filter: '.flow-node-drag',
filterExclude: false,
// 是否允许自己连接自己
anchor: 'Continuous',
allowLoopback: true,
dropOptions: {hoverClass: 'ef-drop-hover'}
}
}
}
}

View File

@ -0,0 +1,79 @@
<template>
<div
ref="node"
:style="nodeContainerStyle"
@click="clickNode"
@mouseup="changeNodeSite"
: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">
{{node.name}}
</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.id : 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 ? false : true
return nodeIcoClass
}
},
methods: {
//
clickNode() {
this.$emit('clickNode', this.node.id)
},
//
changeNodeSite() {
//
if (this.node.left == this.$refs.node.style.left && this.node.top == this.$refs.node.style.top) {
return;
}
this.$emit('changeNodeSite', {
nodeId: this.node.id,
left: this.$refs.node.style.left,
top: this.$refs.node.style.top,
})
}
}
}
</script>

View File

@ -0,0 +1,134 @@
<template>
<div>
<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.name"></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>
<el-button icon="el-icon-close">重置</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">重置</el-button>
<el-button type="primary" icon="el-icon-check" @click="saveLine"></el-button>
</el-form-item>
</el-form>
</div>
<!-- <div class="el-node-form-tag"></div>-->
</div>
</div>
</template>
<script>
import { cloneDeep } from 'lodash'
export default {
data() {
return {
visible: true,
// node line
type: 'node',
node: {},
line: {},
data: {},
stateList: [{
state: 'success',
label: '成功'
}, {
state: 'warning',
label: '警告'
}, {
state: 'error',
label: '错误'
}, {
state: 'running',
label: '运行中'
}]
}
},
methods: {
/**
* 表单修改这里可以根据传入的ID进行业务信息获取
* @param data
* @param id
*/
nodeInit(data, id) {
this.type = 'node'
this.data = data
data.nodeList.filter((node) => {
if (node.id === id) {
this.node = cloneDeep(node)
}
})
},
lineInit(line) {
this.type = 'line'
this.line = line
},
// 线
saveLine() {
this.$emit('setLineLabel', this.line.from, this.line.to, this.line.label)
},
save() {
this.data.nodeList.filter((node) => {
if (node.id === this.node.id) {
node.name = this.node.name
node.left = this.node.left
node.top = this.node.top
node.ico = this.node.ico
node.state = this.node.state
this.$emit('repaintEverything')
}
})
}
}
}
</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;
}
</style>

View File

@ -0,0 +1,142 @@
<template>
<div class="flow-menu" ref="tool">
<div v-for="menu in menuList" :key="menu.id">
<span class="ef-node-pmenu" @click="menu.open = !menu.open"><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" :key="subMenu.id" :type="subMenu.type">
<i :class="subMenu.ico"></i> {{subMenu.name}}
</li>
</draggable>
</ul>
</div>
</div>
</template>
<script>
import draggable from 'vuedraggable'
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: false,
disabled: false,
ghostClass: 'tt',
// 使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: {}
}
]
},
{
id: '2',
type: 'group',
name: '结束节点',
ico: 'el-icon-video-pause',
open: true,
children: [
{
id: '21',
type: 'end',
name: '流程结束',
ico: 'el-icon-caret-right',
//
style: {}
}, {
id: '22',
type: 'over',
name: '数据清理',
ico: 'el-icon-shopping-cart-full',
//
style: {}
}
]
}
],
nodeMenu: {}
}
},
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();
}
}
},
methods: {
//
getMenuByType(type) {
for (let i = 0; i < this.menuList.length; i++) {
let children = this.menuList[i].children;
for (let j = 0; j < children.length; j++) {
if (children[j].type === type) {
return children[j]
}
}
}
},
//
move(evt, a, b, c) {
var type = evt.item.attributes.type.nodeValue
this.nodeMenu = this.getMenuByType(type)
},
//
end(evt, e) {
this.$emit('addNode', evt, this.nodeMenu, mousePosition)
},
//
isFirefox() {
var userAgent = navigator.userAgent
if (userAgent.indexOf("Firefox") > -1) {
return true
}
return false
}
}
}
</script>

View File

@ -0,0 +1,546 @@
<template>
<div v-if="easyFlowVisible" style="height: calc(100vh);">
<el-row>
<!--顶部工具菜单-->
<el-col :span="24">
<div class="ef-tooltar">
<el-link type="primary" :underline="false">{{data.name}}</el-link>
<el-divider direction="vertical"></el-divider>
<el-button type="text" icon="el-icon-delete" size="large" @click="deleteElement" :disabled="!this.activeElement.type"></el-button>
<el-divider direction="vertical"></el-divider>
<el-button type="text" icon="el-icon-download" size="large" @click="downloadData"></el-button>
<el-divider direction="vertical"></el-divider>
<el-button type="text" icon="el-icon-plus" size="large" @click="zoomAdd"></el-button>
<el-divider direction="vertical"></el-divider>
<el-button type="text" icon="el-icon-minus" size="large" @click="zoomSub"></el-button>
<div style="float: right;margin-right: 5px">
<el-button type="info" plain round icon="el-icon-document" @click="dataInfo" size="mini">流程信息</el-button>
<el-button type="primary" plain round @click="dataReloadA" icon="el-icon-refresh" size="mini">切换流程A</el-button>
<el-button type="primary" plain round @click="dataReloadB" icon="el-icon-refresh" size="mini">切换流程B</el-button>
<el-button type="primary" plain round @click="dataReloadC" icon="el-icon-refresh" size="mini">切换流程C</el-button>
<el-button type="primary" plain round @click="dataReloadD" icon="el-icon-refresh" size="mini">自定义样式</el-button>
<el-button type="primary" plain round @click="dataReloadE" icon="el-icon-refresh" 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
:id="node.id"
:key="node.id"
:node="node"
:activeElement="activeElement"
@changeNodeSite="changeNodeSite"
@nodeRightMenu="nodeRightMenu"
@clickNode="clickNode"
>
</flow-node>
</template>
<!-- 给画布一个默认的宽度和高度 -->
<div style="position:absolute;top: 2000px;left: 2000px;">&nbsp;</div>
</div>
<!-- 右侧表单 -->
<div style="width: 300px;border-left: 1px solid #dce3e8;background-color: #FBFBFB">
<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 draggable from 'vuedraggable'
// import { jsPlumb } from 'jsplumb'
// 使jsplumb
import './jsplumb'
import { easyFlowMixin } from '@/components/ef/mixins'
import flowNode from '@/components/ef/node'
import nodeMenu from '@/components/ef/node_menu'
import FlowInfo from '@/components/ef/info'
import FlowHelp from '@/components/ef/help'
import FlowNodeForm from './node_form.vue'
import lodash from 'lodash'
import { getDataA } from './data_A'
import { getDataB } from './data_B'
import { getDataC } from './data_C'
import { getDataD } from './data_D'
import { getDataE } from './data_E'
import { ForceDirected } from './force-directed'
export default {
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
},
zoom: 0.5
}
},
//
mixins: [easyFlowMixin],
components: {
draggable, 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.jsPlumb = jsPlumb.getInstance()
this.$nextTick(() => {
// A
this.dataReload(getDataB())
})
},
methods: {
//
getUUID() {
return Math.random().toString(36).substr(3, 10)
},
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, originalEvent) => {
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) => {
console.log('contextmenu', evt)
})
// 线
this.jsPlumb.bind("beforeDrop", (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
}
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]
// 线
this.jsPlumb.makeSource(node.id, lodash.merge(this.jsplumbSourceOptions, {}))
// // 线
this.jsPlumb.makeTarget(node.id, this.jsplumbTargetOptions)
if (!node.viewOnly) {
this.jsPlumb.draggable(node.id, {
containment: 'parent',
stop: function (el) {
//
console.log('拖拽结束: ', el)
}
})
}
}
// 线
for (var i = 0; i < this.data.lineList.length; i++) {
let line = this.data.lineList[i]
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
}
})
},
//
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)
},
//
changeNodeSite(data) {
for (var i = 0; i < this.data.nodeList.length; i++) {
let node = this.data.nodeList[i]
if (node.id === data.nodeId) {
node.left = data.left
node.top = data.top
}
}
},
/**
* 拖拽结束后添加新的节点
* @param evt
* @param nodeMenu 被添加的节点对象
* @param mousePosition 鼠标拖拽结束的坐标
*/
addNode(evt, nodeMenu, mousePosition) {
var screenX = evt.originalEvent.clientX, screenY = evt.originalEvent.clientY
let efContainer = this.$refs.efContainer
var containerRect = efContainer.getBoundingClientRect()
var left = screenX, top = screenY
//
if (left < containerRect.x || left > containerRect.width + containerRect.x || top < containerRect.y || containerRect.y > containerRect.y + containerRect.height) {
this.$message.error("请把节点拖入到画布中")
return
}
left = left - containerRect.x + efContainer.scrollLeft
top = top - containerRect.y + efContainer.scrollTop
//
left -= 85
top -= 16
var nodeId = this.getUUID()
//
var origName = nodeMenu.name
var nodeName = origName
var index = 1
while (index < 10000) {
var repeat = false
for (var i = 0; i < this.data.nodeList.length; i++) {
let node = this.data.nodeList[i]
if (node.name === nodeName) {
nodeName = origName + index
repeat = true
}
}
if (repeat) {
index++
continue
}
break
}
var node = {
id: nodeId,
name: nodeName,
type: nodeMenu.type,
left: left + 'px',
top: top + 'px',
ico: nodeMenu.ico,
state: 'success'
}
/**
* 这里可以进行业务判断是否能够添加该节点
*/
this.data.nodeList.push(node)
this.$nextTick(function () {
this.jsPlumb.makeSource(nodeId, this.jsplumbSourceOptions)
this.jsPlumb.makeTarget(nodeId, this.jsplumbTargetOptions)
this.jsPlumb.draggable(nodeId, {
containment: 'parent',
stop: function (el) {
//
console.log('拖拽结束: ', el)
}
})
})
},
/**
* 删除节点
* @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.id === nodeId) {
//
// node.show = false
return false
}
return true
})
this.$nextTick(function () {
this.jsPlumb.removeAllEndpoints(nodeId);
})
}).catch(() => {
})
return true
},
clickNode(nodeId) {
this.activeElement.type = 'node'
this.activeElement.nodeId = nodeId
this.$refs.nodeForm.nodeInit(this.data, nodeId)
},
// 线
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)
},
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()
})
})
})
},
// dataA
dataReloadA() {
this.dataReload(getDataA())
},
// dataB
dataReloadB() {
this.dataReload(getDataB())
},
// dataC
dataReloadC() {
this.dataReload(getDataC())
},
// dataD
dataReloadD() {
this.dataReload(getDataD())
},
// dataE
dataReloadE() {
let dataE = getDataE()
let tempData = lodash.cloneDeep(dataE)
let data = ForceDirected(tempData)
this.dataReload(data)
this.$message({
message: '力导图每次产生的布局是不一样的',
type: 'warning'
});
},
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>

View File

@ -0,0 +1,29 @@
// 是否具有该线
export function hasLine(data, from, to) {
for (let i = 0; i < data.lineList.length; i++) {
let line = data.lineList[i]
if (line.from === from && line.to === to) {
return true
}
}
return false
}
// 是否含有相反的线
export function hashOppositeLine(data, from, to) {
return hasLine(data, to, from)
}
// 获取连线
export function getConnector(jsp, from, to) {
let connection = jsp.getConnections({
source: from,
target: to
})[0]
return connection
}
// 获取唯一标识
export function uuid() {
return Math.random().toString(36).substr(3, 10)
}

View File

@ -1,5 +1,10 @@
import Vue from 'vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import '@/components/ef/index.css'
Vue.use(ElementUI, {size: 'small'})
import Cookies from 'js-cookie'
import Element from 'element-ui'

View File

@ -45,6 +45,11 @@ export const constantRoutes = [
component: () => import('@/views/login'),
hidden: true
},
{
path: '/easyFlow',
component: () => import('@/components/ef/panel'),
hidden: true
},
{
path: '/register',
component: () => import('@/views/register'),

View File

@ -1,309 +0,0 @@
<template>
<div class="app-container">
<el-form v-show="showSearch" ref="queryForm" :inline="true" :model="queryParams" label-width="68px" size="small">
<el-form-item label="数据来源名称" prop="name">
<el-input
v-model="queryParams.name"
clearable
placeholder="请输入数据来源名称"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="存放数据库名称" prop="databaseName">
<el-input
v-model="queryParams.databaseName"
clearable
placeholder="请输入存放数据库名称"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button icon="el-icon-search" size="mini" type="primary" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
v-hasPermi="['system:post:add']"
icon="el-icon-plus"
plain
size="mini"
type="primary"
@click="handleAdd"
>新增
</el-button>
</el-col>
<el-col :span="1.5">
<el-button
v-hasPermi="['system:post:edit']"
:disabled="single"
icon="el-icon-edit"
plain
size="mini"
type="success"
@click="handleUpdate"
>修改
</el-button>
</el-col>
<el-col :span="1.5">
<el-button
v-hasPermi="['system:post:remove']"
:disabled="multiple"
icon="el-icon-delete"
plain
size="mini"
type="danger"
@click="handleDelete"
>删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button
v-hasPermi="['system:post:export']"
icon="el-icon-download"
plain
size="mini"
type="warning"
@click="handleExport"
>导出
</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="dataList" @selection-change="handleSelectionChange">
<el-table-column align="center" type="selection" width="55"/>
<el-table-column align="name" label="数据来源名称" prop="postId"/>
<el-table-column align="url" label="数据来源地址" prop="postCode"/>
<el-table-column align="port" label="来源地址端口号" prop="postName"/>
<el-table-column align="dataTypeId" label="数据来源类型" prop="postName"/>
<el-table-column align="databaseName" label="存放数据库名称" prop="postSort"/>
<el-table-column align="createTime" label="存放数据库名称" prop="postSort"/>
<el-table-column align="center" label="状态" prop="status">
<template slot-scope="scope">
<dict-tag :options="dict.type.sys_normal_disable" :value="scope.row.status"/>
</template>
</el-table-column>
<el-table-column align="center" label="创建时间" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column align="center" class-name="small-padding fixed-width" label="操作">
<template slot-scope="scope">
<el-button
v-hasPermi="['system:post:edit']"
icon="el-icon-edit"
size="mini"
type="text"
@click="handleUpdate(scope.row)"
>修改
</el-button>
<el-button
v-hasPermi="['system:post:remove']"
icon="el-icon-delete"
size="mini"
type="text"
@click="handleDelete(scope.row)"
>删除
</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:limit.sync="queryParams.pageSize"
:page.sync="queryParams.pageNum"
:total="total"
@pagination="getList"
/>
<!-- 添加或修改岗位对话框 -->
<el-dialog :title="title" :visible.sync="open" append-to-body width="500px">
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-form-item label="岗位名称" prop="postName">
<el-input v-model="form.postName" placeholder="请输入岗位名称"/>
</el-form-item>
<el-form-item label="岗位编码" prop="postCode">
<el-input v-model="form.postCode" placeholder="请输入编码名称"/>
</el-form-item>
<el-form-item label="岗位顺序" prop="postSort">
<el-input-number v-model="form.postSort" :min="0" controls-position="right"/>
</el-form-item>
<el-form-item label="岗位状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio
v-for="dict in dict.type.sys_normal_disable"
:key="dict.value"
:label="dict.value"
>{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入内容" type="textarea"/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {addPost, delPost, getPost, listPost, updatePost} from "@/api/system/post";
import {getList} from "@/api/dataSources/dataSources";
export default {
name: "DataSources",
dicts: ['sys_normal_disable'],
data() {
return {
//
loading: true,
//
ids: [],
//
single: true,
//
multiple: true,
//
showSearch: true,
//
total: 0,
//
dataList: [],
//
title: "",
//
open: false,
//
queryParams: {
pageNum: 1,
pageSize: 10,
name: undefined,
databaseName: undefined
},
//
form: {},
//
rules: {
pageNum: [
{required: true, message: "岗位名称不能为空", trigger: "blur"}
],
pageSize: [
{required: true, message: "岗位编码不能为空", trigger: "blur"}
],
postSort: [
{required: true, message: "岗位顺序不能为空", trigger: "blur"}
]
}
};
},
created() {
this.getList();
},
methods: {
/** 查询岗位列表 */
getList() {
this.loading = true;
getList(this.queryParams).then(response => {
this.dataList = response.data.records;
this.total = response.data.total;
this.loading = false;
});
},
//
cancel() {
this.open = false;
this.reset();
},
//
reset() {
this.form = {
postId: undefined,
postCode: undefined,
postName: undefined,
postSort: 0,
status: "0",
remark: undefined
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.postId)
this.single = selection.length != 1
this.multiple = !selection.length
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.open = true;
this.title = "添加岗位";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const postId = row.postId || this.ids
getPost(postId).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改岗位";
});
},
/** 提交按钮 */
submitForm: function () {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.postId != undefined) {
updatePost(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
addPost(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
}
});
},
/** 删除按钮操作 */
handleDelete(row) {
const postIds = row.postId || this.ids;
this.$modal.confirm('是否确认删除岗位编号为"' + postIds + '"的数据项?').then(function () {
return delPost(postIds);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {
});
},
/** 导出按钮操作 */
handleExport() {
this.download('system/post/export', {
...this.queryParams
}, `post_${new Date().getTime()}.xlsx`)
}
}
};
</script>

View File

@ -0,0 +1,183 @@
<template>
<div>
<h1 style="color: #00afff" align="center">发布任务</h1>
<el-table :data="tableData" style="width: 100%">
<el-table-column label="编号" width="180">
<template slot-scope="scope">
<span style="margin-left: 10px">{{ scope.row.id }}</span>
</template>
</el-table-column>
<el-table-column label="任务名称" width="180">
<template slot-scope="scope">
<span style="margin-left: 10px">{{ scope.row.name }}</span>
</template>
</el-table-column>
<el-table-column label="任务权重级别" width="180">
<template slot-scope="scope">
<span style="margin-left: 10px">{{ scope.row.weight }}</span>
</template>
</el-table-column>
<el-table-column label="任务执行状态" width="180">
<template slot-scope="scope">
<span style="margin-left: 10px">{{ scope.row.processTotal }}</span>
</template>
</el-table-column>
<el-table-column label="最终执行结果" width="180">
<template slot-scope="scope">
<span style="margin-left: 10px">{{ scope.row.status }}</span>
</template>
</el-table-column>
<el-table-column label="总处理条数" width="180">
<template slot-scope="scope">
<span style="margin-left: 10px">{{ scope.row.total }}</span>
</template>
</el-table-column>
<el-table-column label="创建人" width="180">
<template slot-scope="scope">
<span style="margin-left: 10px">{{ scope.row.createBy }}</span>
</template>
</el-table-column>
<el-table-column label="创建时间" width="180">
<template slot-scope="scope">
<span style="margin-left: 10px">{{ scope.row.createTime }}</span>
</template>
</el-table-column>
<el-table-column label="修改人" width="180">
<template slot-scope="scope">
<span style="margin-left: 10px">{{ scope.row.updateBy }}</span>
</template>
</el-table-column>
<el-table-column label="修改时间" width="180">
<template slot-scope="scope">
<span style="margin-left: 10px">{{ scope.row.updateTime }}</span>
</template>
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button
size="mini"
@click="handleEdit(scope.$index, scope.row)">编辑</el-button>
<el-button
size="mini"
type="danger"
@click="handleDelete(scope.$index, scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-button @click="toAdd"></el-button>
<el-dialog
title="提示"
:visible.sync="dialogVisible"
width="30%"
>
<el-radio-group v-model="labelPosition" size="small">
<el-radio-button label="left">左对齐</el-radio-button>
<el-radio-button label="right">右对齐</el-radio-button>
<el-radio-button label="top">顶部对齐</el-radio-button>
</el-radio-group>
<div style="margin: 20px;"></div>
<el-form :label-position="labelPosition" label-width="80px" :model="formLabelAlign">
<el-form-item label="任务名称">
<el-input v-model="formLabelAlign.name"></el-input>
</el-form-item>
<el-form-item label="任务权重">
<el-input v-model="formLabelAlign.weight"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="doAdd()"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
//jsjsjson,
//import from ',
import {addTask, delTask, showTask} from "../../../api/etl/etl";
export default {
name: "Task",
//import使"
components: {},
props: {},
data() {
//"
return {
formLabelAlign:{},
labelPosition: 'right',
tableData:[],
task:{},
dialogVisible: false
};
},
// data",
computed: {},
//data",
watch: {},
//",
methods: {
toAdd(){
this.dialogVisible = true
},
doAdd(){
this.dialogVisible = false
addTask(this.task).then(res =>{
if (res.code == 200){
this.$message.success("添加任务成功")
location.reload()
}else{
this.$message.error("异常")
}
})
},
getData(){
showTask().then(res => {
this.tableData = res.data
})
},
handleEdit(index, row) {
console.log(index, row);
},
handleDelete(index, row) {
console.log(index, row);
delTask(row.id).then(res =>{
if (res.code == 200){
this.$message.success(res.msg)
location.reload()
}else{
this.$message.error(res.msg)
}
})
}
},
// - 访this",
created() {
this.getData()
},
// - 访DOM",
mounted() {
},
beforeCreate() {
}, // - ",
beforeMount() {
}, // - ",
beforeUpdate() {
}, // - ",
updated() {
}, // - ",
beforeDestroy() {
}, // - ",
destroyed() {
}, // - ",
activated() {
} //keep-alive",
};
</script>
<style scoped>
</style>