<template>
<div v-loading="loading" v-if="easyFlowVisible" class="box">
<div id="flowContainer" class="container">
<template v-for="node in data.nodeList">
<flow-node
v-show="node.show"
:id="node.id"
:node="node"
:key="node.id"
:nodeData="nodeData"
@deleteNode="deleteNode"
@changeNodeSite="changeNodeSite"
@nodeRightMenu="nodeRightMenu"
@editNode="editNode"
></flow-node>
</template>
</div>
<flow-info v-if="flowInfoVisible" ref="flowInfo" :data="data"></flow-info>
<flow-node-form v-if="nodeFormVisible" ref="nodeForm"></flow-node-form>
<div class="mask"></div>
</div>
</template>
<script>
import draggable from 'vuedraggable'
import { jsPlumb } from 'jsplumb'
import flowNode from '@/components/flow/node'
import flowTool from '@/components/flow/tool'
import FlowInfo from '@/components/flow/info'
import FlowNodeForm from './node_form'
import lodash from 'lodash'
import API from '@/api/build-management/index'
import { SvgTemperature } from '@hui/svg-icon'
export default {
name: 'easyFlow',
data () {
return {
loading: false.jsPlumb: null./ / jsPlumb instance
easyFlowVisible: true.flowInfoVisible: false.nodeFormVisible: false.index: 1.// Default Settings
jsplumbSetting: {
// Dynamic anchor point, position adaptive
Anchors: [
'Top'.'TopCenter'.'TopRight'.'TopLeft'.'Right'.'RightMiddle'.'Bottom'.'BottomCenter'.'BottomRight'.'BottomLeft'.'Left'.'LeftMiddle'].Container: 'flowContainer'.Flowchart; StateMachine; Flowchart
Connector: 'Flowchart'.// The mouse cannot drag the stripper
ConnectionsDetachable: false.// The node is not deleted when the line is deleted
DeleteEndpointsOnDetach: false.// The endpoint of the wire
// Endpoint: ["Dot", {radius: 5}],
Endpoint: ['Rectangle', { height: 10.width: 10}].// Line end style
EndpointStyle: { fill: 'rgba(255,255,255,0)'.outlineWidth: 1 },
LogEnabled: true.// Whether to enable jsPlumb internal logging
/ / draw the line
PaintStyle: { stroke: 'black'.strokeWidth: 3 },
// Draw arrows
Overlays: [['Arrow', { width: 12.length: 12.location: 1 }]],
RenderMode: 'svg'
},
// jsplumb connection parameters
jsplumbConnectOptions: {
isSource: true.isTarget: true
// static static static static static static static static static static static static static static static static static
// anchor: 'Continuous'
// anchor: 'AutoDefault'
},
jsplumbSourceOptions: {
filter:
'.flow-node-drag' /* "span" for tag, ".classname "for class, "#id" for element ID */.filterExclude: false.anchor: 'Continuous'.allowLoopback: false
},
jsplumbTargetOptions: {
filter:
'.flow-node-drag' /* "span" for tag, ".classname "for class, "#id" for element ID */.filterExclude: false.anchor: 'Continuous'.allowLoopback: false
},
// Check whether the load is complete
loadEasyFlowFinish: false./ / data
data: {},
nodeData: {},
levelTreeData: []
// exampleGreyEndpointOptions: {
// endpoint: 'Rectangle',
// paintStyle: { width: 25, height: 21, fillStyle: '#666' },
// isSource: true,
// connectorStyle: { strokeStyle: '#666' },
// isTarget: true
// }}},props: {
flowData: Object
},
components: {
draggable,
flowNode,
flowTool,
FlowInfo,
FlowNodeForm
},
watch: {
flowData: {
// Changing buildLevel or idAndNameDtos updates the legend when it is displayed
handler: async function (val, oldVal) {
this.loading = true
const levelTreeRes = await API.levelTree()
this.levelTreeData = levelTreeRes.data
this.dataReloadC(this.transformData(val))
this.nodeData = val
setTimeout(() = > {
this.loading = false
}, 500)},deep: true.immediate: true
}
},
created () {
this.loading = true
},
async mounted () {
const levelTreeRes = await API.levelTree()
this.levelTreeData = levelTreeRes.data
this.jsPlumb = jsPlumb.getInstance()
this.$nextTick(() = > {
// const data = {
// name: 'process ',
// nodeList: [
/ / {
// id: '1',
// name: 'park ',
// left: '100px',
// top: '15px',
// ico: 'h-icon-user',
// show: true
/ /},
/ / {
// id: '2',
// name: 'school ',
// left: '100px',
// top: '140px',
// ico: 'h-icon-user',
// show: true
/ /},
/ / {
// id: '3',
// name: 'building ',
// left: '100px',
// top: '265px',
// ico: 'h-icon-user',
// show: true
/ /},
/ / {
// id: '4',
// name: 'building ',
// left: '100px',
// top: '390px',
// ico: 'h-icon-user',
// show: true
/ /},
/ / {
// id: '5',
// name: 'floor ',
// left: '100px',
// top: '515px',
// ico: 'h-icon-user',
// show: true
/ /},
/ / {
// id: '6',
// name: 'room ',
// left: '100px',
// top: '640px',
// ico: 'h-icon-user',
// show: true
/ /}
/ /,
// lineList: [
/ / {
// from: '1',
// to: '2'
/ /},
/ / {
// from: '2',
// to: '3'
/ /},
/ / {
// from: '3',
// to: '4'
/ /},
/ / {
// from: '4',
// to: '5'
/ /},
/ / {
// from: '5',
// to: '6'
/ /},
/ / {
// from: '3',
// to: '6',
// anchor: ['Right', 'Left']
/ /},
/ / {
// from: '3',
// to: '5',
// anchor: ['Right', 'Left']
/ /}
/ /]
// }
this.nodeData = this.flowData
this.dataReloadC(this.transformData(this.flowData))
this.loading = false})},methods: {
transformData (data) {
// const data = this.flowData
// const buildLevelListTemp = this.levelTreeData
const buildLevelListTemp = this.levelTreeData
const sortFun = (a, b) = > a.buildLevel - b.buildLevel
const buildLevelList = buildLevelListTemp.sort(sortFun)
// Generate a node
const res = {
name: 'process'.nodeList: [].lineList: []
}
res.nodeList = buildLevelList.map((item, index) = > {
return {
id: item.buildTypeId,
name: item.buildName,
left: '100px'.top: `The ${15 + 125 * (item.buildLevel - 1)}px`.ico: 'h-icon-user'.show: true.buildLevel: item.buildLevel
}
})
res.nodeList.forEach((item, index) = > {
if (index + 1 < res.nodeList.length) {
res.lineList.push({
from: item.id,
to: res.nodeList[index + 1].id
})
}
})
if (
Array.isArray(data.hierarchyForm.idAndNameDtos) &&
data.hierarchyForm.idAndNameDtos.length > 0
) {
data.hierarchyForm.idAndNameDtos.forEach((item, index) = > {
if (
this.levelTreeData[data.hierarchyForm.buildLevel - 1]! = =undefined
) {
res.lineList.push({
from: this.levelTreeData[data.hierarchyForm.buildLevel - 1]
.buildTypeId,
to: item,
anchor: ['Right'.'Left'].paintStyle: { stroke: '#E72528'.strokeWidth: 2}})}})}return res
},
jsPlumbInit () {
const _this = this
this.jsPlumb.ready(function () {
// Import default configurations
_this.jsPlumb.importDefaults(_this.jsplumbSetting)
// Causes the entire jsPlumb to be redrawn immediately.
_this.jsPlumb.setSuspendDrawing(false.true)
// Initialize the node
_this.loadEasyFlow()
// Single click the connection line,
_this.jsPlumb.bind('click'.function (conn, originalEvent) {
console.log('click', conn)
_this
.$confirm('Are you sure to delete the clicked line? '.'tip', {
confirmButtonText: 'sure'.cancelButtonText: 'cancel'.type: 'warning'
})
.then(() = > {
_this.jsPlumb.deleteConnection(conn)
})
.catch(() = >{})})/ / the attachment
_this.jsPlumb.bind('connection'.function (evt) {
console.log('connection', evt)
const from = evt.source.id
const to = evt.target.id
if (_this.loadEasyFlowFinish) {
_this.lineList.push({
from: from.to: to
})
}
})
// Delete the connection
_this.jsPlumb.bind('connectionDetached'.function (evt) {
console.log('connectionDetached', evt)
_this.deleteLine(evt.sourceId, evt.targetId)
})
// Change the connection node of the line
_this.jsPlumb.bind('connectionMoved'.function (evt) {
console.log('connectionMoved', evt)
_this.changeLine(evt.originalSourceId, evt.originalTargetId)
})
/ / click the endpoint
// jsPlumb.bind("endpointClick", function (evt) {
// console.log('endpointClick', evt)
// })
//
// // Double-click Endpoint
// jsPlumb.bind("endpointDblClick", function (evt) {
// console.log('endpointDblClick', evt)
// })
// contextmenu
_this.jsPlumb.bind('contextmenu'.function (evt) {
console.log('contextmenu', evt)
})
// beforeDrop
_this.jsPlumb.bind('beforeDrop'.function (evt) {
console.log('beforeDrop', evt)
_this.$message.error('beforeDrop')
_this.$message({
message: 'Congratulations, this is a success.'.type: 'success'
})
const from = evt.sourceId
const to = evt.targetId
if (from === to) {
_this.$message.error('Cannot connect yourself')
return false
}
if (_this.hasLine(from, to)) {
_this.$message.error('Cannot repeat connection')
return false
}
if (_this.hashOppositeLine(from, to)) {
_this.$message.error('No loopback.')
return false
}
return true
})
// beforeDetach
_this.jsPlumb.bind('beforeDetach'.function (evt) {
console.log('beforeDetach', evt)
})
})
},
// Load flowchart
loadEasyFlow () {
// Initialize the node
for (var i = 0; i < this.data.nodeList.length; i++) {
const node = this.data.nodeList[i]
console.log(node)
// Set the source point, which can be dragged out to connect to other nodes
this.jsPlumb.makeSource(node.id, this.jsplumbSourceOptions)
// // sets the target point, which can be connected by lines drawn from other source points
this.jsPlumb.makeTarget(node.id, this.jsplumbTargetOptions)
// this.jsPlumb.addEndpoint(node.id, this.exampleGreyEndpointOptions)
// Set draggable
// jsPlumb.draggable(node.id, {
// containment: 'parent',
// grid: [10, 10]
// })
this.jsPlumb.draggable(node.id, {
containment: 'parent'
})
// jsPlumb.draggable(node.id)
}
// Initialize the connection
for (var i = 0; i < this.data.lineList.length; i++) {
const line = this.data.lineList[i]
this.jsPlumb.connect(
{
source: line.from,
target: line.to
},
Object.assign({ ... this.jsplumbConnectOptions,anchor: line.anchor,
paintStyle:
line.paintStyle === undefined
? { stroke: 'lightgray'.strokeWidth: 1 }
: line.paintStyle
// connector: ['Bezier']}}))this.$nextTick(function () {
this.loadEasyFlowFinish = true
})
},
getNodes () {
console.log(jsPlumb)
console.log(jsPlumb.Defaults)
},
getLines () {
console.log('line', jsPlumb.getConnections())
},
/ / delete line
deleteLine (from, to) {
this.data.lineList = this.data.lineList.filter(function (line) {
returnline.from ! = =from&& line.to ! == to }) },// Change the connection
changeLine (oldFrom, oldTo) {
this.deleteLine(oldFrom, oldTo)
},
// Change the position of the node
changeNodeSite (data) {
for (var i = 0; i < this.data.nodeList.length; i++) {
const node = this.data.nodeList[i]
if (node.id === data.nodeId) {
node.left = data.left
node.top = data.top
}
}
},
// Add a new node
addNode (evt, nodeMenu, mousePosition) {
console.log('Add node', evt, nodeMenu)
const width = this.$refs.flowTool.$el.clientWidth
const index = this.index++
const nodeId = 'node' + index
var left = mousePosition.left
var top = mousePosition.top
if (mousePosition.left < 0) {
left = evt.originalEvent.layerX - width
}
if (mousePosition.top < 0) {
top = evt.originalEvent.clientY - 50
}
var node = {
id: 'node' + index,
name: 'nodes' + index,
left: left + 'px'.top: top + 'px'.ico: nodeMenu.ico,
show: true
}
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'})})},// Whether the line exists
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
},
// Does it contain opposite lines
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'
},
deleteNode (nodeId) {
this.$confirm('Sure to delete node' + nodeId + '? '.'tip', {
confirmButtonText: 'sure'.cancelButtonText: 'cancel'.type: 'warning'.closeOnClickModal: false
})
.then(() = > {
this.data.nodeList = this.data.nodeList.filter(function (node) {
// return node.id ! == nodeId
if (node.id === nodeId) {
node.show = false
}
return true
})
this.$nextTick(function () {
console.log('delete' + nodeId)
this.jsPlumb.removeAllEndpoints(nodeId)
})
})
.catch(() = > {})
return true
},
editNode (nodeId) {
console.log('Edit node', nodeId)
this.nodeFormVisible = true
this.$nextTick(function () {
this.$refs.nodeForm.init(this.data, nodeId)
})
},
// Process data information
dataInfo () {
this.flowInfoVisible = true
this.$nextTick(function () {
this.$refs.flowInfo.init()
})
},
dataReload (data) {
this.easyFlowVisible = false
this.data.nodeList = []
this.data.lineList = []
this.$nextTick(() = > {
// Get the data in the background and load it
data = lodash.cloneDeep(data)
this.easyFlowVisible = true
this.data = data
this.$nextTick(() = > {
this.jsPlumb = jsPlumb.getInstance()
this.$nextTick(() = > {
this.jsPlumbInit()
})
})
})
},
// Data is reloaded
dataReloadC (data) {
this.dataReload(data)
}
}
}
</script>
<style lang="scss" scoped>
.box {
height: calc(100% - 35px);
position: relative;
.mask {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 1000;
}
.container {
// background-image: linear-gradient(
// 90deg, / /rgba(0.0.0.0.15) 10%, / /rgba(0.0.0.0) 10%/ / / /)linear-gradient(rgba(0.0.0.0.15) 10%.rgba(0.0.0.0) 10%);
background-size: 10px 10px;
height: 100%;
background-color: rgb(251.251.251);
/*background-color: #42b983; * /
position: relative;
}
.labelClass {
background-color: white;
padding: 5px;
opacity: 0.7;
border: 1px solid # 346789;
/*border-radius: 10px; * /
cursor: pointer; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; }}</style>
Copy the code
flowNode
<template>
<div
ref="node"
:style="flowNodeContainer"
@mouseenter="showDelete"
@mouseleave="hideDelete"
@mouseup="changeNodeSite"
>
<! -- <div class="flow-node-header"> -->
<! -- The icon style in the upper left corner -->
<! -- <i :class="nodeClass"></i> -->
<! -- The "Edit" and "Delete" buttons in the upper right corner of the node when the mouse cursor is moved into the node -->
<! -- <div style="position: absolute; top: 0px; right: 0px; line-height: 25px" v-show="mouseEnter"> <a href="#" style @click="editNode"> <img src="@/assets/edit.png" /> </a> <a href="#" style @click="deleteNode"> <img src="@/assets/delete.png" /> </a> </div>-->
<! -- </div> -->
<! -- The body of the node -->
<! -- <div class="flow-node-body">{{node.name}}</div> -->
<div class="box">
<div :class="['flow-node',activeStyle]">
<div class="flow-node-left">
<img src="@/assets/img_ Layer ID.png" alt />
<span>{{node.buildLevel}}</span>
</div>
<div class="flow-node-right">
<span>{{node.name}}</span>
</div>
</div>
<div v-if="activeDot" class="dot"></div>
</div>
</div>
</template>
<script>
export default {
props: {
node: Object.nodeData: Object
},
data () {
return {
// Control node operation display
mouseEnter: false}},computed: {
// Node container style
flowNodeContainer: {
get () {
return {
position: 'absolute'.width: '274px'.top: this.node.top,
left: this.node.left,
boxShadow: this.mouseEnter ? '#66a6e0 0px 0px 12px 0px' : ' '.backgroundColor: 'transparent'
}
}
},
nodeClass () {
var nodeclass = {}
nodeclass[this.node.ico] = true
nodeclass['flow-node-drag'] = true
return nodeclass
},
activeStyle () {
return this.node.buildLevel === this.nodeData.hierarchyForm.buildLevel ? 'active' : ' '
},
activeDot () {
return (this.nodeData.hierarchyForm.idAndNameDtos.length > 0) && (
this.node.buildLevel === this.nodeData.hierarchyForm.buildLevel || this.nodeData.hierarchyForm.idAndNameDtos.find(
id= > {
return id === this.node.id
}
)
)
}
},
watch: {
},
mounted () {
console.log(this.node)
console.log(this.nodeData)
},
methods: {
// Delete a node
deleteNode () {
this.$emit('deleteNode'.this.node.id)
},
// Edit the node
editNode () {
this.$emit('editNode'.this.node.id)
},
// Mouse entry
showDelete () {
this.mouseEnter = true
},
// Mouse away
hideDelete () {
this.mouseEnter = false
},
// The mouse moves and lifts
changeNodeSite () {
// Avoid jitter
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>
<style lang="scss" scoped>
.box {
width: 100%;
padding-right: 18px;
position: relative;
.dot {
position: absolute;
right: 0px;
top: 18px;
width: 14px;
height: 14px;
border-radius: 7px;
background: #e72528;
&:after {
content: ' ';
width: 4px;
height: 4px;
border-radius: 2px;
background: #fff;
position: absolute;
right: 5px;
top: 5px; }}.flow-node {
width: 100%;
height: 50px;
background: #ffffff;
border: 1px solid rgba(0.0.0.0.12);
border-radius: 2px;
overflow: hidden;
position: relative;
.flow-node-left {
width: 48px;
height: 48px;
// background: rgba(0.0.0.0.9);
border-radius: 2px NaNpx 2px;
float: left;
position: relative;
img {
position: relative;
// top: 2px;
}
span {
position: absolute;
left: 18px;
top: 11px;
width: 8px;
height: 26px;
font-family: PingFangSC-Medium;
font-size: 18px;
color: rgba(255.255.255.0.9);
line-height: 26px;
font-weight: 500; }}.flow-node-right {
height: 48px;
line-height: 50px;
font-family: PingFangSC-Regular;
font-size: 14px;
color: rgba(0.0.0.0.7);
line-height: 20px;
font-weight: 400;
float: left;
span {
display: inline-block;
height: 48px;
line-height: 48px;
margin-left: 16px;
max-width: 188px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis; }}}.active {
background: rgba(231.37.40.0.2);
border: 2px solid #e72528;
box-shadow: 0 3px 6px -4px rgba(0.0.0.0.12),
0 6px 16px 0 rgba(0.0.0.0.08), 0 9px 28px 8px rgba(0.0.0.0.05);
border-radius: 2px;
position: relative; / /}}.flow-node-drag {
// width: 25px;
// height: 25px; / / / /}.flow-node-header {
// background-color: #66a6e0;
// height: 25px;
// cursor: pointer;
// border-top-left-radius: 6px;
// border-top-right-radius: 6px; / / / /}.flow-node-header a {
// text-decoration: none;
// line-height: 25px;
// vertical-align: middle; / / / /}.flow-node-header a img {
// line-height: 25px;
// vertical-align: middle;
// margin-bottom: 5px; / / / /}.flow-node-body {
// /*background-color: beige; * /
// background-color: white;
// text-align: center;
// cursor: pointer;
// height: 25px;
// line-height: 25px;
// border-bottom-left-radius: 6px;
// border-bottom-right-radius: 6px;
// }
</style>
Copy the code