求助文心一言帮我用antv x6开发一个直线审批流程设计页面Vue2.0
之前使用DeepSeek协助开发过类似的页面,这是当时的页面效果:
这次的客户是某乡的养殖工厂,销售只争取到了供应链模块的定制开发,因为涉及到新的领域,需要做的是新瓶装旧酒,给功能界面提升档次,以便给用户更好的体验,便于推广其他模块。我们决定采用antv框架重新设计模块中的图表与表格,流程审批设计页面当然还是由我来开发的。
开始尝试问询:
帮我把下面的代码用antv x6重写:
<template>
<div class="approval-flow">
<a-button type="primary" @click="addNode" style="margin-bottom: 20px">
添加节点
</a-button>
<a-steps :current="null" direction="horizontal" class="custom-steps">
<a-step v-for="(node, index) in nodes" :key="node.id">
<template #title>
<div class="node-card">
<div class="card-header">
<span>{。。。。。。
因为之前并没有使用过antv框架的经验,简直无从问起,索性直接要求将之前的步骤条+表单的页面重写成为x6流程图,结果当然是惨不忍睹,先后尝试了文心一言、豆包、DeepSeek以及360纳米的多种模式,这种提问当时下文心一言、豆包和DeepSeek的答案差不多,都是一个只有一张画布的界面,添加按钮不好使,DeepSeek还是一样的服务器繁忙好多次。360纳米的多种模式只有一个模式给出了包含代码的答案,但是也等的很漫长。
总结这些答案发现了一点,就是x6的自定义节点和antd表单弹窗之间无法连贯,x6的节点种类繁多,新旧版本之间的注册节点方式有很大不同,导致代码千奇百怪。我需要的提问方式是把涉及到x6的节点的部分尽量的简化,得到一个x6简单节点+antd表单弹窗的代码,然后注册一个自定义节点加入到代码中。
提问文心一言3.5:
帮我用antv x6开发一个直线审批流程设计页面Vue2.0+antd+antv x6实现,节点的添加与编辑通过antd表单弹窗的形式实现,双击节点弹出编辑窗口,右键点击节点删除。
因为提问的内容比较抽象,失败的可能性很大,我这次使用的是回答速度快的一言。第一次还是给出了vue3.0的代码,再次提交后给出了比较靠谱的答案,竟然直接运行成功,大大超出了我的预期:
<template>
<div>
<!-- 添加按钮 -->
<a-button @click="openAddModal">添加节点</a-button>
<!-- 保存按钮 -->
<a-button @click="saveProcess">保存流程</a-button>
<!-- X6画布容器 -->
<div ref="graphContainer" style="width: 100%; height: 800px; border: 1px solid #ccc;"></div>
<!-- 添加节点模态框 -->
<a-modal
:visible="addModalVisible"
title="添加节点"
@ok="handleAddOk"
@cancel="handleAddCancel"
>
<a-form :form="addForm">
<a-form-item label="节点名称">
<a-input v-decorator="[
'name',
{
rules: [
{ required: true, message: '请输入节点名称' }
]
}
]" />
</a-form-item>
<a-form-item label="关联角色">
<a-input v-decorator="[
'role',
{
rules: [
{ required: true, message: '请输入关联角色' }
]
}
]" />
</a-form-item>
</a-form>
</a-modal>
<!-- 修改节点模态框 -->
<a-modal
:visible="editModalVisible"
title="修改节点"
@ok="handleEditOk"
@cancel="handleEditCancel"
>
<a-form :form="editForm">
<a-form-item label="节点名称">
<a-input v-decorator="[
'name',
{
rules: [
{ required: true, message: '请输入节点名称' }
]
}
]" />
</a-form-item>
<a-form-item label="关联角色">
<a-input v-decorator="[
'role',
{
rules: [
{ required: true, message: '请输入关联角色' }
]
}
]" />
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script>
import { Graph, Shape } from '@antv/x6';
export default {
data() {
return {
graph: null,
addModalVisible: false,
addForm: this.$form.createForm(this),
editModalVisible: false,
editForm: this.$form.createForm(this),
currentNode: null,
nodes: [
{ id: 'start', name: '发起人', role: '发起人', x: 50, y: 200 }
]
};
},
mounted() {
// 初始化X6图形
this.initGraph();
// 渲染节点
this.renderNodes();
},
methods: {
initGraph() {
// 创建X6图形实例
this.graph = new Graph({
container: this.$refs.graphContainer,
grid: true,
connecting: {
snap: true
},
interacting: {
nodeMovable: false
}
});
// 监听节点点击事件
this.graph.on('node:click', ({ node }) => {
this.openEditModal(node);
});
// 监听节点右键事件
this.graph.on('node:contextmenu', ({ node, e }) => {
if (node.id!== 'start') {
// 删除节点
if (confirm('确定要删除该节点吗?')) {
this.nodes = this.nodes.filter(n => n.id!== node.id);
this.renderNodes();
}
}
});
},
renderNodes() {
// 清空现有图形
this.graph.clearCells();
// 依次添加节点并连线
for (let i = 0; i < this.nodes.length; i++) {
const node = this.nodes[i];
const x6Node = new Shape.Rect({
id: node.id,
x: node.x,
y: node.y,
width: 100,
height: 50,
label: `${node.name}\n${node.role}`,
attrs: {
body: {
fill: '#f5f5f5',
stroke: '#d9d9d9',
strokeWidth: 1
}
}
});
this.graph.addNode(x6Node);
if (i > 0) {
const prevNode = this.nodes[i - 1];
const edge = new Shape.Edge({
source: prevNode.id,
target: node.id
});
this.graph.addEdge(edge);
}
}
},
openAddModal() {
// 打开添加节点模态框
this.addModalVisible = true;
},
handleAddOk() {
// 处理添加节点确认
this.addForm.validateFields((err, values) => {
if (!err) {
const lastNode = this.nodes[this.nodes.length - 1];
const newNode = {
id: `node-${Date.now()}`,
name: values.name,
role: values.role,
x: lastNode.x + 200,
y: lastNode.y
};
this.nodes.push(newNode);
this.renderNodes();
this.addModalVisible = false;
this.addForm.resetFields();
}
});
},
handleAddCancel() {
// 处理添加节点取消
this.addModalVisible = false;
this.addForm.resetFields();
},
openEditModal(node) {
// 打开修改节点模态框
this.currentNode = this.nodes.find(n => n.id === node.id);
this.editForm.setFieldsValue({
name: this.currentNode.name,
role: this.currentNode.role
});
this.editModalVisible = true;
},
handleEditOk() {
// 处理修改节点确认
this.editForm.validateFields((err, values) => {
if (!err) {
this.currentNode.name = values.name;
this.currentNode.role = values.role;
this.renderNodes();
this.editModalVisible = false;
this.editForm.resetFields();
}
});
},
handleEditCancel() {
// 处理修改节点取消
this.editModalVisible = false;
this.editForm.resetFields();
},
saveProcess() {
// 保存流程,这里可以添加保存到后端的逻辑
console.log(' 保存流程:', this.nodes);
}
}
};
</script>
<style scoped>
/* 可根据需要添加自定义样式 */
</style>
运行效果:
1)初始界面
2)添加节点
3)删除节点,右键点击节点
有几个小问题
①我的提问中“添加与编辑通过antd表单弹窗的形式”这句导致一言给出代码中使用了两个表单来处理编辑与删除,这很好解决,参照之前版本的代码,去掉一个表单,然后通过node.id判断当前操作是编辑还是删除就可以了。
②表单中的角色使用的input而不是select,也是提问中没有声明的原因,这不影响,参照之前版本的代码,改一下就可以了。
③相比于上个版本的步骤条+卡片来说节点展示方式过于简单,需要自定义节点。
打开antv x6的图表示例,找到自定义节点,发现第一个例子就是我所需要的:
自定义节点 | X6
将代码中的节点替换为我的自定义节点就可以了,这是完善之后的代码:
<template>
<div>
<!-- 添加按钮 -->
<a-button @click="openEditModal">添加节点</a-button>
<!-- 保存按钮 -->
<a-button @click="saveProcess">保存流程</a-button>
<!-- X6画布容器 -->
<div ref="graphContainer" style="width: 100%; height: 800px; border: 1px solid #ccc;"></div>
<!-- 修改节点模态框 -->
<a-modal
:visible="editModalVisible"
:title="title"
@ok="handleEditOk"
@cancel="handleEditCancel"
>
<a-form :form="editForm">
<a-form-item label="节点名称">
<a-input v-decorator="[
'name',
{
rules: [
{ required: true, message: '请输入节点名称' }
]
}
]" />
</a-form-item>
<a-form-item label="关联角色">
<a-select @change="handleRoleChange" v-decorator="[
'role',
{
rules: [
{ required: true, message: '请输入关联角色' }
]
}
]" placeholder="请选择角色">
<a-select-option
v-for="role in roles"
:key="role.value"
:value="role.value"
>
{{ role.label }}
</a-select-option>
</a-select>
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script>
import { Graph, Shape } from '@antv/x6';
Graph.registerNode(
'custom-node',
{
width: 200,
height: 60,
attrs: {
body: {
stroke: '#5F95FF',
strokeWidth: 1,
fill: 'rgba(95,149,255,0.05)',
refWidth: 1,
refHeight: 1,
},
image: {
'xlink:href': require('../assets/shenqingren.png'),
width: 16,
height: 16,
x: 12,
y: 12,
},
title: {
text: '123',
refX: 40,
refY: 14,
fill: 'rgba(0,0,0,0.85)',
fontSize: 12,
'text-anchor': 'start',
},
text: {
text: '456',
refX: 40,
refY: 38,
fontSize: 12,
fill: 'rgba(0,0,0,0.6)',
'text-anchor': 'start',
},
},
markup: [
{
tagName: 'rect',
selector: 'body',
},
{
tagName: 'image',
selector: 'image',
},
{
tagName: 'text',
selector: 'title',
},
{
tagName: 'text',
selector: 'text',
},
],
},
true,
)
export default {
data() {
return {
graph: null,
editModalVisible: false,
editForm: this.$form.createForm(this),
currentNode: {},
title: '修改节点',
roles: [
{ label: "发起人角色", value: "applicant", icon: require('../assets/shenqingren.png') },
{ label: "部门主管", value: "manager", icon: require('../assets/bumenshenhe.png') },
{ label: "财务", value: "finance", icon: require('../assets/caiwushenhe.png') },
{ label: "总经理", value: "ceo", icon: require('../assets/zongjinglishenhe.png') },
],
selectedIcon: '', // 用于存储选中的角色的图标
nodes: [
{ id: 'start', name: '发起人', role: 'applicant', icon: require('../assets/shenqingren.png'), x: 100, y: 50 }
]
};
},
mounted() {
// 初始化X6图形
this.initGraph();
// 渲染节点
this.renderNodes();
this.editForm = this.$form.createForm(this);
},
methods: {
initGraph() {
// 创建X6图形实例
this.graph = new Graph({
container: this.$refs.graphContainer,
grid: true,
connecting: {
snap: true
},
interacting: {
nodeMovable: false
}
});
// 监听节点点击事件
this.graph.on('node:dblclick', ({ node }) => {
this.openEditModal(node);
});
// 监听节点右键事件
this.graph.on('node:contextmenu', ({ node, e }) => {
console.log(e)
if (node.id === 'start') {
return
}
// 删除节点
if (confirm('确定要删除该节点吗?')) {
this.nodes = this.nodes.filter(n => n.id!== node.id);
this.renderNodes();
}
});
},
renderNodes() {
// 清空现有图形
this.graph.clearCells();
// 依次添加节点并连线
for (let i = 0; i < this.nodes.length; i++) {
const node = this.nodes[i]
this.graph.addNode({
id: node.id,
x: node.x,
y: node.y,
shape: 'custom-node',
attrs: {
title: {
text: `${node.name}`, // 直接设置 text 属性
},
text: {
text: `${node.role}`, // 直接设置 text 属性
},
image: {
'xlink:href': node.icon, // 使用节点的 icon 属性
width: 16,
height: 16,
x: 12,
y: 12,
},
},
})
if (i > 0) {
const prevNode = this.nodes[i - 1];
const edge = new Shape.Edge({
source: prevNode.id,
target: node.id
});
this.graph.addEdge(edge);
}
}
},
handleRoleChange(value) {
// 当用户选择角色时,根据值查找角色对象
this.selectedIcon = this.roles.find(role => role.value === value).icon;
},
openEditModal(node) {
// 打开修改节点模态框
this.editModalVisible = true;
this.title = '修改节点';
if(!node.id){
this.title = '添加节点';
this.currentNode = {};
return
}
this.currentNode = this.nodes.find(n => n.id === node.id);
this.selectedIcon = this.currentNode.icon
this.$nextTick(() => {
this.editForm.setFieldsValue({
name: this.currentNode.name,
role: this.currentNode.role
});
});
},
handleEditOk() {
// 处理修改节点确认
this.editForm.validateFields((err, values) => {
if (err) {
return
}
if(this.currentNode.id){
this.currentNode.name = values.name;
this.currentNode.role = values.role;
this.currentNode.icon = this.selectedIcon;
this.renderNodes();
this.editModalVisible = false;
this.editForm.resetFields();
}
else{
const lastNode = this.nodes[this.nodes.length - 1];
const newNode = {
id: `node-${Date.now()}`,
name: values.name,
role: values.role,
icon: this.selectedIcon,
x: lastNode.x + 300,
y: lastNode.y
};
this.nodes.push(newNode);
}
this.renderNodes();
this.editModalVisible = false;
this.editForm.resetFields();
});
},
handleEditCancel() {
// 处理修改节点取消
this.editModalVisible = false;
this.editForm.resetFields();
},
saveProcess() {
// 保存流程,这里可以添加保存到后端的逻辑
console.log(' 保存流程:', this.nodes);
}
}
};
</script>
<style scoped>
/* 可根据需要添加自定义样式 */
</style>
运行效果:
1)点击按钮添加节点
2)双击节点弹出编辑
3)右击节点删除