本demo为了实现自定义流程保存回显新增效果, 复制即用 ,模拟数据太长没在代码中体现, 需要可以找我拿
vxe-table官方连接:Vxe Table v4
antVX6连接:https://x6.antv.antgroup.com/
element连接:Element - The world's most popular Vue UI framework
DataV连接:DataV
效果图
HTML
<template>
<div class="box">
<dv-border-box-13>
<div style="width: 100%; height: 100%; padding: 20px">
<div id="container" class="container">
<div class="flowList">
<vxe-table
border
ref="xTable"
resizable
show-overflow
:loading="loading"
:row-config="{ isCurrent: true, isHover: true }"
:data="tableData"
keep-source
@current-change="currentChangeEvent"
@mounted="highlightFirstRow"
:edit-config="{ trigger: 'manual', mode: 'row', autoClear: false }"
>
<vxe-column field="name" title="流程名称" :edit-render="{}">
<template #edit="{ row }">
<vxe-input
v-model="row.editData.name"
type="text"
placeholder="请输入流程名称"
></vxe-input>
</template>
</vxe-column>
<vxe-column width="90" title="操作">
<template #default="{ row, rowIndex }">
<template v-if="!$refs.xTable.isEditByRow(row)">
<div class="listIcon">
<span class="el-icon-edit-outline icon" @click="edit(row)"></span>
<!-- base64_image -->
<span @click="dele(row)" class="el-icon-delete icon"></span>
</div>
</template>
<template v-else>
<div class="listIcon">
<span @click="save(row)" class="iconfont icon-baocun icon"></span>
<span @click="huixian(row)" class="iconfont icon-weibiaoti545 icon"></span>
</div>
</template>
</template>
</vxe-column>
</vxe-table>
<div class="flowBox">
<el-button class="flowBtn" icon="el-icon-plus" @click="add(-1)">新增</el-button>
</div>
</div>
<div
id="graph-container"
class="graph-container"
v-loading="boxLoading"
element-loading-background="rgba(0, 0, 0, 0.5)"
></div>
<div id="stencil" class="stencil"></div>
</div>
</div>
</dv-border-box-13>
<div style="position: absolute; left: 80px; top: 8px">
<el-breadcrumb separator="/">
<el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>流程配置</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div
style="position: absolute; right: 80px; top: 8px; display: flex; color: aqua; font-size: 12px"
>
<div>Ctrl+A <span style="color: aqua">全选 </span></div>
<div style="margin-left: 10px">Ctrl+C <span style="color: aqua">复制</span></div>
<div style="margin-left: 10px">Ctrl+X <span style="color: aqua">剪切</span></div>
<div style="margin-left: 10px">Ctrl+V <span style="color: aqua">粘贴</span></div>
<div style="margin-left: 10px">Backspace <span style="color: aqua">删除</span></div>
<div style="margin-left: 10px">双击图形进行内容编辑</div>
</div>
<el-dialog
title="流程内容设置"
width="30%"
:visible.sync="dialogFormVisible"
:close-on-click-modal="false"
:close-on-press-escape="false"
:show-close="false"
>
<el-form :model="form">
<el-row>
<el-form-item label="内容">
<el-col :span="16">
<el-input
:autosize="{ minRows: 3, maxRows: 10 }"
type="textarea"
v-model="form.name"
autocomplete="off"
></el-input>
</el-col>
</el-form-item>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="saveDialogData">确 定</el-button>
</div>
</el-dialog>
</div>
</template>
CSS
<style lang="less" scoped>
.flowList {
width: 280px;
height: 100%;
//background-color: #f8f8f9;
}
.flowBtn{
width: 150px;
margin-top: 20px;
// height: ;
}
.flowBox {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.listIcon{
display: flex;
justify-content: space-around;
font-size: 20px;
}
.icon:hover {
color: rgb(12, 161, 219);
cursor: pointer;
}
.box {
width: 100%;
height: 100%;
padding-top: 5px;
// background-color: #fff;
// display: flex;
}
/deep/.el-breadcrumb__inner {
color: #fff;
}
#container {
// padding-top: 10px;
width: 100%;
height: 100%
// background-color: #fff;
}
#container {
display: flex;
border: 1px solid #dfe3e8;
}
#stencil {
width: 180px;
height: 100%;
position: relative;
border-right: 1px solid #dfe3e8;
}
#graph-container {
width: calc(100% - 180px);
height: 100%;
}
/deep/.x6-widget-stencil {
// background: linear-gradient(to right , blue,rgb(32, 32, 146), #00cee9) !important;
background-color: #fff;
}
/deep/.x6-widget-stencil-title {
background-color: #fff;
}
/deep/.x6-widget-stencil-group-title {
background-color: #fff !important;
}
/deep/.x6-widget-transform {
margin: -1px 0 0 -1px;
padding: 0px;
border: 1px solid #239edd;
}
/deep/.x6-widget-transform > div {
border: 1px solid #239edd;
}
/deep/.x6-widget-transform > div:hover {
background-color: #3dafe4;
}
/deep/.x6-widget-transform-active-handle {
background-color: #3dafe4;
}
/deep/.x6-widget-transform-resize {
border-radius: 0;
}
/deep/.x6-widget-selection-inner {
border: 1px solid #239edd;
}
/deep/.x6-widget-selection-box {
opacity: 0;
}
</style>
JS部分
<script>
import html2canvas from 'html2canvas' // 引入插件
import VXETable from 'vxe-table'
import { Graph, Shape } from '@antv/x6'
import { Stencil } from '@antv/x6-plugin-stencil'
import { Transform } from '@antv/x6-plugin-transform'
import { Selection } from '@antv/x6-plugin-selection'
import { Snapline } from '@antv/x6-plugin-snapline'
import { Keyboard } from '@antv/x6-plugin-keyboard'
import { Clipboard } from '@antv/x6-plugin-clipboard'
import { History } from '@antv/x6-plugin-history'
export default {
data() {
return {
loading: false,
boxLoading: false,
// isEdit: false,
graph: null,
stencil: null,
simulatedData: {
// ...模拟的流程数据
},
tableData: [
{
id: 10001,
name: 'XXX识别流程',
editData: {
name: 'XXX识别流程'
}
}
],
dialogFormVisible: false,
formLabelWidth: '120px',
form: {
name: ''
},
nodeItem: null,
editNodeList: []
}
},
mounted() {
this.$nextTick(function () {
this.$refs.xTable.setCurrentRow(this.tableData[0])
this.boxLoading = true
setTimeout(() => {
this.boxLoading = false
let positionData = this.simulatedData
this.editNodeList = positionData.cells.filter((element) => element.shape !== 'edge')
// console.log(this.editNodeList, 'graph')
this.graph.fromJSON(positionData)
}, 500)
})
const container = document.getElementById('container')
const stencilContainer = document.getElementById('stencil')
const graphContainer = document.getElementById('graph-container')
this.graph = new Graph({
container: graphContainer,
grid: true,
mousewheel: {
enabled: true,
zoomAtMousePosition: true,
modifiers: 'ctrl',
minScale: 0.5,
maxScale: 3
},
connecting: {
router: 'manhattan',
connector: {
name: 'rounded',
args: {
radius: 8
}
},
anchor: 'center',
connectionPoint: 'anchor',
allowBlank: false,
snap: {
radius: 20
},
createEdge() {
return new Shape.Edge({
attrs: {
line: {
stroke: '#A2B1C3',
strokeWidth: 2,
targetMarker: {
name: 'block',
width: 12,
height: 8
}
}
},
zIndex: 0
})
},
validateConnection({ targetMagnet }) {
return !!targetMagnet
}
},
highlighting: {
magnetAdsorbed: {
name: 'stroke',
args: {
attrs: {
fill: '#5F95FF',
stroke: '#5F95FF'
}
}
}
}
})
// 初始化插件
this.graph
.use(new Transform({ resizing: true, rotating: true }))
.use(new Selection({ rubberband: true, showNodeSelectionBox: true }))
.use(new Snapline())
.use(new Keyboard())
.use(new Clipboard())
.use(new History())
// 初始化 stencil
const stencil = new Stencil({
title: '诊断流程',
target: this.graph,
stencilGraphWidth: 200,
stencilGraphHeight: 180,
collapsable: true,
groups: [
{
title: '配置项',
name: 'group1'
}
// {
// title: '系统设计图',
// name: 'group2',
// graphHeight: 250,
// layoutOptions: {
// rowHeight: 70
// }
// }
],
layoutOptions: {
columns: 2,
columnWidth: 80,
rowHeight: 55
}
})
stencilContainer.appendChild(stencil.container)
// 其他事件绑定...
this.graph.bindKey(['meta+c', 'ctrl+c'], () => {
const cells = this.graph.getSelectedCells()
if (cells.length) {
this.graph.copy(cells)
}
return false
})
this.graph.bindKey(['meta+x', 'ctrl+x'], () => {
const cells = this.graph.getSelectedCells()
if (cells.length) {
this.graph.cut(cells)
}
return false
})
this.graph.bindKey(['meta+v', 'ctrl+v'], () => {
if (!this.graph.isClipboardEmpty()) {
const cells = this.graph.paste({ offset: 32 })
this.graph.cleanSelection()
this.graph.select(cells)
}
return false
})
// select all
this.graph.bindKey(['meta+a', 'ctrl+a'], () => {
const nodes = this.graph.getNodes()
if (nodes) {
this.graph.select(nodes)
}
})
//delete
this.graph.bindKey('backspace', () => {
const cells = this.graph.getSelectedCells()
if (cells.length) {
this.graph.removeCells(cells)
}
})
// 其他初始化图形...
// 控制连接桩显示/隐藏
const showPorts = (ports, show) => {
for (let i = 0, len = ports.length; i < len; i += 1) {
ports[i].style.visibility = show ? 'visible' : 'hidden'
}
}
this.graph.on('node:mouseenter', () => {
const container = graphContainer
const ports = container.querySelectorAll('.x6-port-body')
showPorts(ports, true)
})
this.graph.on('node:mouseleave', () => {
const container = graphContainer
const ports = container.querySelectorAll('.x6-port-body')
showPorts(ports, false)
})
this.graph.on('node:dblclick', (e) => {
this.$nextTick(() => {
this.$set(this.form, 'name', e.node.label)
this.nodeItem = e.cell // 获取被点击的节点元素对象
this.dialogFormVisible = true
})
})
// #region 初始化图形
const ports = {
groups: {
top: {
position: 'top',
attrs: {
circle: {
r: 4,
magnet: true,
stroke: '#5F95FF',
strokeWidth: 1,
fill: '#fff',
style: {
visibility: 'hidden'
}
}
}
},
right: {
position: 'right',
attrs: {
circle: {
r: 4,
magnet: true,
stroke: '#5F95FF',
strokeWidth: 1,
fill: '#fff',
style: {
visibility: 'hidden'
}
}
}
},
bottom: {
position: 'bottom',
attrs: {
circle: {
r: 4,
magnet: true,
stroke: '#5F95FF',
strokeWidth: 1,
fill: '#fff',
style: {
visibility: 'hidden'
}
}
}
},
left: {
position: 'left',
attrs: {
circle: {
r: 4,
magnet: true,
stroke: '#5F95FF',
strokeWidth: 1,
fill: '#fff',
style: {
visibility: 'hidden'
}
}
}
}
},
items: [
{
group: 'top'
},
{
group: 'right'
},
{
group: 'bottom'
},
{
group: 'left'
}
]
}
Graph.registerNode(
'custom-rect',
{
inherit: 'rect',
width: 66,
height: 36,
attrs: {
body: {
strokeWidth: 1,
stroke: '#5F95FF',
fill: '#EFF4FF'
},
text: {
fontSize: 12,
fill: '#262626'
}
},
ports: { ...ports }
},
true
)
Graph.registerNode(
'custom-polygon',
{
inherit: 'polygon',
width: 66,
height: 36,
attrs: {
body: {
strokeWidth: 1,
stroke: '#5F95FF',
fill: '#EFF4FF'
},
text: {
fontSize: 12,
fill: '#262626'
}
},
ports: {
...ports,
items: [
{
group: 'top'
},
{
group: 'bottom'
}
]
}
},
true
)
Graph.registerNode(
'custom-circle',
{
inherit: 'circle',
width: 45,
height: 45,
attrs: {
body: {
strokeWidth: 1,
stroke: '#5F95FF',
fill: '#EFF4FF'
},
text: {
fontSize: 12,
fill: '#262626'
}
},
ports: { ...ports }
},
true
)
Graph.registerNode(
'custom-image',
{
inherit: 'rect',
width: 52,
height: 52,
markup: [
{
tagName: 'rect',
selector: 'body'
},
{
tagName: 'image'
},
{
tagName: 'text',
selector: 'label'
}
],
attrs: {
body: {
stroke: '#5F95FF',
fill: '#5F95FF'
},
image: {
width: 26,
height: 26,
refX: 13,
refY: 16
},
label: {
refX: 3,
refY: 2,
textAnchor: 'left',
textVerticalAnchor: 'top',
fontSize: 12,
fill: '#fff'
}
},
ports: { ...ports }
},
true
)
const r1 = this.graph.createNode({
shape: 'custom-rect',
label: '开始',
attrs: {
body: {
rx: 20,
ry: 26
}
}
})
const r2 = this.graph.createNode({
shape: 'custom-rect',
label: '过程'
// 内容可编辑
})
const r3 = this.graph.createNode({
shape: 'custom-polygon',
attrs: {
body: {
refPoints: '0,10 10,0 20,10 10,20'
}
},
label: '决策'
})
const r4 = this.graph.createNode({
shape: 'custom-polygon',
attrs: {
body: {
refPoints: '10,0 40,0 30,20 0,20'
}
},
label: '结论'
})
stencil.load([r1, r2, r3, r4], 'group1')
},
methods: {
// 新增
add(row) {
const allNamesHaveValue = this.tableData.every(
(obj) => obj.name !== undefined && obj.name !== null && obj.name !== ''
)
console.log(this.tableData)
console.log(allNamesHaveValue)
if (!allNamesHaveValue && this.tableData.length) {
this.$message({
showClose: true,
message: '请先保存流程',
type: 'warning'
})
return
}
const $table = this.$refs.xTable
const record = {
name: '',
editData: {
name: ''
}
}
$table.insertAt(record, row).then((res) => {
// console.log(res.rows)
this.tableData.push(...res.rows)
$table.setEditCell(res.row, 'name')
})
console.log(this.tableData)
// console.log(this.$refs.xTable)
},
// 修改
edit(row) {
// this.isEdit = true
const $table = this.$refs.xTable
$table.setEditRow(row)
},
// 删除
async dele(row) {
const type = await VXETable.modal.confirm('您确定要删除该条流程数据吗?')
const $table = this.$refs.xTable
if (type === 'confirm') {
$table.remove(row).then((res) => {
const filteredArray = this.tableData.filter((obj) => obj.id !== res.row.id)
this.tableData = filteredArray
console.log(filteredArray)
})
}
},
base64_image() {
html2canvas(document.querySelector('#graph-container')).then((canvas) => {
let image = canvas.toDataURL()
console.log(image, 'image')
//将canvas内容保存为文件并下载
// canvas.toBlob(function (blob) {
// saveAs(blob, '图片名称.png')
// })
})
},
saveDialogData() {
this.$set(this.nodeItem, 'label', this.form.name)
this.$set(this.nodeItem, 'form', this.form)
let isUpdated = false // 用于标记是否已经替换过对象
let updatedList = this.editNodeList.map((obj) => {
if (obj.id === this.nodeItem.id) {
isUpdated = true
return this.nodeItem // 替换为新对象
} else {
return obj // 保持原对象不变
}
})
if (!isUpdated) {
updatedList.push(this.nodeItem) // 如果没有相等的对象,则添加新对象
}
// 更新 this.editNodeList 为替换或添加后的数组
this.editNodeList = updatedList
console.log(this.editNodeList)
this.dialogFormVisible = false
},
// 保存
save(row) {
let flowData = this.graph.toJSON()
// 添加弹框值
if (this.editNodeList.length) {
flowData.cells.forEach((flowObj) => {
this.editNodeList.forEach((item) => {
if (flowObj.id === item.id) {
flowObj.form = item.form
}
})
})
}
// 过滤块数组
let filteredCells = flowData.cells.filter((element) => element.shape !== 'edge')
// 过滤线数组
let linCells = flowData.cells.filter((element) => element.shape === 'edge')
let operator = []
// 遍历重铸数据
filteredCells.forEach((element) => {
operator.push({
operator_name: element.attrs.text.text,
operator_id: element.id,
operator_rect: {
left_X: element.position.x,
left_Y: element.position.y,
right_X: element.position.x + element.size.width,
right_Y: element.position.y + element.size.height
},
operator_next_id: linCells
.map((item) => {
if (item.source.cell === element.id) {
return item.target.cell
}
})
.toString(),
operator_data: element.form
})
})
localStorage.setItem('graph', JSON.stringify(flowData))
const $table = this.$refs.xTable
if (!row.editData.name) {
this.$message({
showClose: true,
message: '请填写流程名称',
type: 'warning'
})
return
} else {
row.name = row.editData.name
}
console.log(row)
$table.clearEdit().then((res) => {
this.loading = true
setTimeout(() => {
this.loading = false
VXETable.modal.message({ content: '保存成功!', status: 'success' })
}, 300)
})
},
// 撤销
huixian(row) {
const $table = this.$refs.xTable
$table.clearEdit().then(() => {
console.log(row)
if (row.name) {
row.editData.name = row.name
} else {
$table.remove(row).then((res) => {
const filteredArray = this.tableData.filter((obj) => obj.id !== res.row.id)
this.tableData = filteredArray
console.log(filteredArray)
})
}
// 还原行数据
})
},
highlightFirstRow({ $table, highlightCells }) {
const firstRow = $table.afterFullData[0]
if (firstRow) {
highlightCells(firstRow) // 假设highlightCells是高亮单元格的函数
}
},
currentChangeEvent({ row }) {
if (row.id) {
this.boxLoading = true
setTimeout(() => {
this.boxLoading = false
let positionData = this.simulatedData
this.editNodeList = positionData.cells.filter((element) => element.shape !== 'edge')
this.graph.fromJSON(positionData)
}, 500)
} else {
}
},
getCurrentEvent() {
console.log('wDsd a das dad ')
VXETable.modal.alert(JSON.stringify(this.$refs.xTable.getCurrentRecord()))
}
}
}
</script>
标签:const,name,VXE,graph,antvX6,element,true,row
From: https://blog.csdn.net/m0_72840382/article/details/140638527