添加编辑器
parent
f762f71144
commit
055f3847c2
@ -0,0 +1,144 @@
|
|||||||
|
<template>
|
||||||
|
<div style="width: 100%; height: 100vh">
|
||||||
|
<div class="top"></div>
|
||||||
|
<div class="left">
|
||||||
|
<el-card
|
||||||
|
class="moduleCard"
|
||||||
|
shadow="never"
|
||||||
|
:draggable="true"
|
||||||
|
@dragstart="onDragStart($event, 'line', {})"
|
||||||
|
:style="{ display: 'inline-block', margin: '0 4px 4px 0' }"
|
||||||
|
:body-style="{ padding: '4px 0' }"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div style="position: absolute; top: 50%; transform: translateY(-50%); width: 64px; height: 1px; background-color: #0002"></div>
|
||||||
|
</template>
|
||||||
|
<div class="moduleText">线</div>
|
||||||
|
</el-card>
|
||||||
|
<el-card
|
||||||
|
class="moduleCard"
|
||||||
|
shadow="never"
|
||||||
|
:draggable="true"
|
||||||
|
@dragstart="onDragStart($event, 'circuitComponent', { type: 1 })"
|
||||||
|
:style="{ display: 'inline-block', margin: '0 4px 4px 0' }"
|
||||||
|
:body-style="{ padding: '4px 0' }"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<el-image style="width: 100%; height: 100%" :src="img1" fit="contain" />
|
||||||
|
</template>
|
||||||
|
<div class="moduleText">元件1</div>
|
||||||
|
</el-card>
|
||||||
|
<el-card
|
||||||
|
class="moduleCard"
|
||||||
|
shadow="never"
|
||||||
|
:draggable="true"
|
||||||
|
@dragstart="onDragStart($event, 'circuitComponent', { type: 2 })"
|
||||||
|
:style="{ display: 'inline-block', margin: '0 4px 4px 0' }"
|
||||||
|
:body-style="{ padding: '4px 0' }"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<el-image style="width: 100%; height: 100%" :src="img2" fit="contain" />
|
||||||
|
</template>
|
||||||
|
<div class="moduleText">元件2</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
<div class="right" @drop="onDrop">
|
||||||
|
<VueFlow
|
||||||
|
class="flow"
|
||||||
|
id="flowA"
|
||||||
|
:min-zoom="0.01"
|
||||||
|
ref="flowRef"
|
||||||
|
:snapToGrid="isSnapToGrid"
|
||||||
|
:panOnDrag="false"
|
||||||
|
:zoomOnScroll="false"
|
||||||
|
:autoPanSpeed="0"
|
||||||
|
:snapGrid="[10, 10]"
|
||||||
|
v-model:nodes="nodes"
|
||||||
|
v-model:edges="edges"
|
||||||
|
fit-view-on-init
|
||||||
|
default-marker-color="#409EFF"
|
||||||
|
@node-click="logEvent('click', $event)"
|
||||||
|
@pane-click="logEvent('paneClick', $event)"
|
||||||
|
@node-drag-start="logEvent('nodeDrag', $event)"
|
||||||
|
@node-contextMenu="logEvent('contextmenu', $event)"
|
||||||
|
@dragover="onDragOver"
|
||||||
|
>
|
||||||
|
<Background :size="1" :gap="20" pattern-color="#BDBDBD" style="background-color: #fff" />
|
||||||
|
|
||||||
|
<template #node-circuitComponent="props">
|
||||||
|
<CircuitComponentNode v-bind="props" />
|
||||||
|
</template>
|
||||||
|
<template #node-line="props">
|
||||||
|
<LineNode v-bind="props" />
|
||||||
|
</template>
|
||||||
|
</VueFlow>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import img1 from '@/assets/electronicComponents/1.png';
|
||||||
|
import img2 from '@/assets/electronicComponents/2.png';
|
||||||
|
import CircuitComponentNode from './nodes/circuitComponent.vue';
|
||||||
|
import LineNode from './nodes/line.vue';
|
||||||
|
import { MarkerType, useVueFlow, VueFlow } from '@vue-flow/core';
|
||||||
|
import { Background } from '@vue-flow/background';
|
||||||
|
import tool from './tool.js';
|
||||||
|
|
||||||
|
const nodes = ref([]);
|
||||||
|
const edges = ref([]);
|
||||||
|
const isSnapToGrid = ref(true);
|
||||||
|
|
||||||
|
const { onDragStart, onDrop, onDragOver } = tool();
|
||||||
|
const { addEdges, updateNode, removeNodes, addNodes } = useVueFlow({ id: 'flowA' });
|
||||||
|
|
||||||
|
const logEvent = async (eventname, event) => {
|
||||||
|
switch (eventname) {
|
||||||
|
case 'paneClick':
|
||||||
|
break;
|
||||||
|
case 'click':
|
||||||
|
break;
|
||||||
|
case 'nodeDrag':
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.top{
|
||||||
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
.left {
|
||||||
|
display: inline-block;
|
||||||
|
width: 200px;
|
||||||
|
height: 100%;
|
||||||
|
vertical-align: top;
|
||||||
|
|
||||||
|
.moduleCard {
|
||||||
|
width: calc(50% - 4px);
|
||||||
|
display: inline-block;
|
||||||
|
height: 150px;
|
||||||
|
|
||||||
|
:deep(.el-card__header) {
|
||||||
|
width: 100%;
|
||||||
|
height: 100px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-card__body) {
|
||||||
|
padding: 0 20px 0 20px !important;
|
||||||
|
|
||||||
|
.moduleText {
|
||||||
|
line-height: 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
display: inline-block;
|
||||||
|
width: 960px;
|
||||||
|
height: 540px;
|
||||||
|
position: absolute;
|
||||||
|
left: 200px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,73 @@
|
|||||||
|
<template>
|
||||||
|
<div :style="{ width: props.dimensions.width * props.ratioWidth + 'px', height: props.dimensions.height * props.ratioHeight + 'px' }">
|
||||||
|
<NodeResizer color="#000" v-if="!props.isView && !props.isHideHandle && props.selected" />
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="custom-node"
|
||||||
|
:style="{
|
||||||
|
width: props.dimensions.width * props.ratioWidth + 'px',
|
||||||
|
height: props.dimensions.height * props.ratioHeight + 'px',
|
||||||
|
pointerEvents: props.isView ? 'auto' : 'none'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<el-image style="width: 100%; height: 100%" :src="props.data.customData.type === 1 ? img1:img2" fit="contain" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import img1 from '@/assets/electronicComponents/1.png';
|
||||||
|
import img2 from '@/assets/electronicComponents/2.png';
|
||||||
|
import { defineEmits, defineProps, ref } from 'vue';
|
||||||
|
import { NodeResizer } from '@vue-flow/node-resizer';
|
||||||
|
import { Handle, Position } from '@vue-flow/core';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
ratioWidth: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
ratioHeight: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
isView: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
inputData: {
|
||||||
|
type: Object,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
isHideHandle: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
selected: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
dimensions: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
onMounted(() => {
|
||||||
|
console.log(props);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.custom-node {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,72 @@
|
|||||||
|
<template>
|
||||||
|
<div :style="{ width: props.dimensions.width * props.ratioWidth + 'px', height: props.dimensions.height * props.ratioHeight + 'px' }">
|
||||||
|
<NodeResizer color="#000" v-if="!props.isView && !props.isHideHandle && props.selected" />
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="custom-node"
|
||||||
|
:style="{
|
||||||
|
width: props.dimensions.width * props.ratioWidth + 'px',
|
||||||
|
height: props.dimensions.height * props.ratioHeight + 'px',
|
||||||
|
pointerEvents: props.isView ? 'auto' : 'none'
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div style="position: absolute;top: 50%;left: 0;transform: translateY(-50%);width: 100%;height: 1px;background-color: #0002"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import img1 from '@/assets/electronicComponents/1.png';
|
||||||
|
import img2 from '@/assets/electronicComponents/2.png';
|
||||||
|
import { defineEmits, defineProps, ref } from 'vue';
|
||||||
|
import { NodeResizer } from '@vue-flow/node-resizer';
|
||||||
|
import { Handle, Position } from '@vue-flow/core';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
ratioWidth: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
ratioHeight: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
isView: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
inputData: {
|
||||||
|
type: Object,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
isHideHandle: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
selected: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
dimensions: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
onMounted(() => {
|
||||||
|
console.log(props);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.custom-node {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,87 @@
|
|||||||
|
import { useVueFlow } from '@vue-flow/core';
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
|
const getId = (type) => {
|
||||||
|
return `${type}_${uuidv4().replaceAll('-', '_')}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getOption = (e) => {
|
||||||
|
if (e === 'circuitComponent') {
|
||||||
|
return {
|
||||||
|
title: ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const getNodeSize = (e) => {
|
||||||
|
if (e === 'circuitComponent') {
|
||||||
|
return { width: 50, height: 50 };
|
||||||
|
} else if (e === 'line') {
|
||||||
|
return { width: 100, height: 20 };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const nameEnum = {};
|
||||||
|
const tool = () => {
|
||||||
|
const nodeType = ref('');
|
||||||
|
const customData = ref({ type: 1 });
|
||||||
|
const { addNodes, screenToFlowCoordinate, onNodesInitialized, updateNode } = useVueFlow({ id: 'flowA' });
|
||||||
|
const onDragStart = (event, type, data) => {
|
||||||
|
if (event.dataTransfer) {
|
||||||
|
event.dataTransfer.setData('application/vueflow', type);
|
||||||
|
event.dataTransfer.effectAllowed = 'move';
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeType.value = type;
|
||||||
|
customData.value = data || { type: 1 };
|
||||||
|
document.addEventListener('drop', onDragEnd);
|
||||||
|
};
|
||||||
|
const onDragEnd = () => {
|
||||||
|
nodeType.value = null;
|
||||||
|
document.removeEventListener('drop', onDragEnd);
|
||||||
|
};
|
||||||
|
const onDrop = (event) => {
|
||||||
|
const dimensions = getNodeSize(nodeType.value);
|
||||||
|
|
||||||
|
const position = screenToFlowCoordinate({
|
||||||
|
x: event.clientX,
|
||||||
|
y: event.clientY
|
||||||
|
});
|
||||||
|
|
||||||
|
const nodeId = getId(nodeType.value);
|
||||||
|
const newNode = {
|
||||||
|
id: nodeId,
|
||||||
|
name: nameEnum[nodeType.value],
|
||||||
|
draggable: true,
|
||||||
|
type: nodeType.value,
|
||||||
|
dimensions,
|
||||||
|
position,
|
||||||
|
data: { options: getOption(nodeType.value), outputData: {}, customData: customData.value }
|
||||||
|
};
|
||||||
|
|
||||||
|
const { off } = onNodesInitialized(() => {
|
||||||
|
updateNode(nodeId, (node) => ({
|
||||||
|
position: { x: node.position.x - node.dimensions.width / 2, y: node.position.y - node.dimensions.height / 2 }
|
||||||
|
}));
|
||||||
|
|
||||||
|
off();
|
||||||
|
});
|
||||||
|
|
||||||
|
addNodes(newNode);
|
||||||
|
};
|
||||||
|
const onDragOver = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (nodeType.value) {
|
||||||
|
if (event.dataTransfer) {
|
||||||
|
event.dataTransfer.dropEffect = 'move';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
onDragStart,
|
||||||
|
onDragEnd,
|
||||||
|
onDrop,
|
||||||
|
onDragOver
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export default tool;
|
||||||
Loading…
Reference in New Issue