mirror of
https://github.com/Powerful-517/yys-editor.git
synced 2025-08-23 08:04:50 +00:00
重新实现拖动功能,其他组件适配
This commit is contained in:
@@ -1,8 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import useDragAndDrop from '@/ts/useDnD';
|
import { getLogicFlowInstance } from '@/ts/useLogicFlow';
|
||||||
|
|
||||||
const { onDragStart } = useDragAndDrop();
|
|
||||||
|
|
||||||
// 使用嵌套结构定义组件分组
|
// 使用嵌套结构定义组件分组
|
||||||
const componentGroups = [
|
const componentGroups = [
|
||||||
@@ -107,25 +105,28 @@ const componentGroups = [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
// 可以轻松添加新的游戏组件组
|
// 可以轻松添加新的游戏组件组
|
||||||
// {
|
{
|
||||||
// id: 'other-game',
|
id: 'other-game',
|
||||||
// title: '其他游戏',
|
title: '其他游戏',
|
||||||
// components: []
|
components: []
|
||||||
// }
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
// 处理组件点击 - 直接使用 onDragStart 的数据格式
|
// 处理组件点击 - 可选:可直接创建节点
|
||||||
const handleComponentClick = (component) => {
|
const handleComponentClick = (component) => {
|
||||||
const nodeData = {
|
// 可选:实现点击直接添加节点到画布
|
||||||
type: component.type,
|
};
|
||||||
label: component.name,
|
|
||||||
position: { x: 100, y: 100 },
|
|
||||||
data: component.data
|
|
||||||
};
|
|
||||||
|
|
||||||
onDragStart({ dataTransfer: { setData: () => {} } } as DragEvent, nodeData);
|
const handleMouseDown = (e, component) => {
|
||||||
|
e.preventDefault(); // 阻止文字选中
|
||||||
|
const lf = getLogicFlowInstance();
|
||||||
|
if (!lf) return;
|
||||||
|
lf.dnd.startDrag({
|
||||||
|
type: component.type,
|
||||||
|
properties: component.data
|
||||||
|
});
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -145,12 +146,7 @@ const handleComponentClick = (component) => {
|
|||||||
:key="component.id"
|
:key="component.id"
|
||||||
class="component-item"
|
class="component-item"
|
||||||
@click="handleComponentClick(component)"
|
@click="handleComponentClick(component)"
|
||||||
draggable="true"
|
@mousedown="(e) => handleMouseDown(e, component)"
|
||||||
@dragstart="(e) => onDragStart(e, {
|
|
||||||
type: component.type,
|
|
||||||
label: component.name,
|
|
||||||
data: component.data
|
|
||||||
})"
|
|
||||||
>
|
>
|
||||||
<div class="component-icon">
|
<div class="component-icon">
|
||||||
<i class="el-icon-plus"></i>
|
<i class="el-icon-plus"></i>
|
||||||
|
@@ -32,7 +32,8 @@ import PropertySelectNode from './nodes/yys/PropertySelectNode.vue';
|
|||||||
// import ImageNode from './nodes/common/ImageNode.vue';
|
// import ImageNode from './nodes/common/ImageNode.vue';
|
||||||
// import TextNode from './nodes/common/TextNode.vue';
|
// import TextNode from './nodes/common/TextNode.vue';
|
||||||
import PropertyPanel from './PropertyPanel.vue';
|
import PropertyPanel from './PropertyPanel.vue';
|
||||||
import {useFilesStore} from "@/ts/useStore";
|
import { useFilesStore } from "@/ts/useStore";
|
||||||
|
import { setLogicFlowInstance, destroyLogicFlowInstance } from '@/ts/useLogicFlow';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
nodes: any[];
|
nodes: any[];
|
||||||
@@ -74,7 +75,7 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
registerNodes(lf.value);
|
registerNodes(lf.value);
|
||||||
renderFlow();
|
renderFlow();
|
||||||
filesStore.setLogicFlowInstance(lf.value);
|
setLogicFlowInstance(lf.value);
|
||||||
|
|
||||||
// 监听节点点击事件,更新 selectedNode
|
// 监听节点点击事件,更新 selectedNode
|
||||||
lf.value.on(EventType.NODE_CLICK, ({ data }) => {
|
lf.value.on(EventType.NODE_CLICK, ({ data }) => {
|
||||||
@@ -88,18 +89,16 @@ onMounted(() => {
|
|||||||
|
|
||||||
// 节点属性改变,如果当前节点是选中节点,则同步更新 selectedNode
|
// 节点属性改变,如果当前节点是选中节点,则同步更新 selectedNode
|
||||||
lf.value.on(EventType.NODE_PROPERTIES_CHANGE, (data) => {
|
lf.value.on(EventType.NODE_PROPERTIES_CHANGE, (data) => {
|
||||||
const nodeId = data.id || (data.value && data.value.id);
|
const nodeId = data.id;
|
||||||
if (selectedNode.value && nodeId === selectedNode.value.id) {
|
if (selectedNode.value && nodeId === selectedNode.value.id) {
|
||||||
if (data.value) {
|
if (data.properties) {
|
||||||
selectedNode.value = data.value;
|
selectedNode.value = {
|
||||||
} else if (data.properties) {
|
...selectedNode.value,
|
||||||
selectedNode.value = {
|
properties: data.properties
|
||||||
...selectedNode.value,
|
};
|
||||||
properties: data.properties
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// 右键事件
|
// 右键事件
|
||||||
lf.value.on('node:contextmenu', handleNodeContextMenu);
|
lf.value.on('node:contextmenu', handleNodeContextMenu);
|
||||||
@@ -110,6 +109,7 @@ onMounted(() => {
|
|||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
lf.value?.destroy();
|
lf.value?.destroy();
|
||||||
lf.value = null;
|
lf.value = null;
|
||||||
|
destroyLogicFlowInstance();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 响应式更新 nodes/edges
|
// 响应式更新 nodes/edges
|
||||||
|
@@ -1,38 +1,31 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useVueFlow } from '@vue-flow/core';
|
import type LogicFlow from '@logicflow/core';
|
||||||
|
// import { useVueFlow } from '@vue-flow/core';
|
||||||
import { QuillEditor } from '@vueup/vue-quill';
|
import { QuillEditor } from '@vueup/vue-quill';
|
||||||
import '@vueup/vue-quill/dist/vue-quill.snow.css';
|
import '@vueup/vue-quill/dist/vue-quill.snow.css';
|
||||||
import { useDialogs } from '../../ts/useDialogs';
|
import { useDialogs } from '../../ts/useDialogs';
|
||||||
|
import { useFilesStore } from '@/ts/useStore';
|
||||||
|
import { getLogicFlowInstance } from '@/ts/useLogicFlow';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
height: {
|
height: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '100%'
|
default: '100%'
|
||||||
|
},
|
||||||
|
node: {
|
||||||
|
type: Object,
|
||||||
|
default: null
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 使用VueFlow的store获取当前选中的节点
|
const filesStore = useFilesStore();
|
||||||
const { findNode, getNodes, updateNode } = useVueFlow();
|
|
||||||
const { openDialog } = useDialogs();
|
const { openDialog } = useDialogs();
|
||||||
|
|
||||||
// getNodes是一个ref对象,而不是函数
|
const selectedNode = computed(() => props.node);
|
||||||
const nodes = getNodes;
|
|
||||||
|
|
||||||
// 当前选中的节点
|
|
||||||
const selectedNode = ref(null);
|
|
||||||
|
|
||||||
// 监听节点变化
|
|
||||||
watch(nodes, (newNodes) => {
|
|
||||||
// 查找选中的节点
|
|
||||||
const selected = newNodes.find(node => node.selected);
|
|
||||||
selectedNode.value = selected || null;
|
|
||||||
}, { deep: true });
|
|
||||||
|
|
||||||
// 计算属性:节点是否选中
|
|
||||||
const hasNodeSelected = computed(() => !!selectedNode.value);
|
const hasNodeSelected = computed(() => !!selectedNode.value);
|
||||||
|
|
||||||
// 计算属性:节点类型
|
|
||||||
const nodeType = computed(() => {
|
const nodeType = computed(() => {
|
||||||
if (!selectedNode.value) return '';
|
if (!selectedNode.value) return '';
|
||||||
return selectedNode.value.type || 'default';
|
return selectedNode.value.type || 'default';
|
||||||
@@ -40,16 +33,21 @@ const nodeType = computed(() => {
|
|||||||
|
|
||||||
// 通用的弹窗处理方法
|
// 通用的弹窗处理方法
|
||||||
const handleOpenDialog = (type: 'shikigami' | 'yuhun' | 'property') => {
|
const handleOpenDialog = (type: 'shikigami' | 'yuhun' | 'property') => {
|
||||||
if (selectedNode.value) {
|
const lf = getLogicFlowInstance();
|
||||||
|
if (selectedNode.value && lf) {
|
||||||
const node = selectedNode.value;
|
const node = selectedNode.value;
|
||||||
const currentData = node.data && node.data[type] ? node.data[type] : undefined;
|
// 取 properties 下的 type 字段
|
||||||
|
const currentData = node.properties && node.properties[type] ? node.properties[type] : undefined;
|
||||||
|
|
||||||
openDialog(
|
openDialog(
|
||||||
type,
|
type,
|
||||||
currentData,
|
currentData,
|
||||||
node,
|
node,
|
||||||
(updatedData, nodeToUpdate) => {
|
(updatedData) => {
|
||||||
updateNode(nodeToUpdate.id, { data: { ...nodeToUpdate.data, [type]: updatedData } });
|
lf.setProperties(node.id, {
|
||||||
|
...node.properties,
|
||||||
|
[type]: updatedData
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -60,18 +58,11 @@ const handleImageUpload = (e) => {
|
|||||||
if (!file) return;
|
if (!file) return;
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = (evt) => {
|
reader.onload = (evt) => {
|
||||||
updateNodeData('url', evt.target.result);
|
// updateNodeData('url', evt.target.result);
|
||||||
};
|
};
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateNodeData = (key, value) => {
|
|
||||||
if (!selectedNode.value) return;
|
|
||||||
const node = findNode(selectedNode.value.id);
|
|
||||||
if (node) {
|
|
||||||
node.data = { ...node.data, [key]: value };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const quillToolbar = [
|
const quillToolbar = [
|
||||||
[{ header: 1 }, { header: 2 }],
|
[{ header: 1 }, { header: 2 }],
|
||||||
@@ -110,6 +101,7 @@ const quillToolbar = [
|
|||||||
<div v-if="nodeType === 'shikigamiSelect'" class="property-section">
|
<div v-if="nodeType === 'shikigamiSelect'" class="property-section">
|
||||||
<div class="section-header">式神属性</div>
|
<div class="section-header">式神属性</div>
|
||||||
<div class="property-item">
|
<div class="property-item">
|
||||||
|
<span>当前选择式神:{{ selectedNode.properties?.shikigami?.name || '未选择' }}</span>
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="handleOpenDialog('shikigami')"
|
@click="handleOpenDialog('shikigami')"
|
||||||
@@ -153,8 +145,8 @@ const quillToolbar = [
|
|||||||
<div class="section-header">图片设置</div>
|
<div class="section-header">图片设置</div>
|
||||||
<div class="property-item">
|
<div class="property-item">
|
||||||
<input type="file" accept="image/*" @change="handleImageUpload" />
|
<input type="file" accept="image/*" @change="handleImageUpload" />
|
||||||
<div v-if="selectedNode.data && selectedNode.data.url" style="margin-top:8px;">
|
<div v-if="selectedNode.value.properties && selectedNode.value.properties.url" style="margin-top:8px;">
|
||||||
<img :src="selectedNode.data.url" alt="预览" style="max-width:100%;max-height:100px;" />
|
<img :src="selectedNode.value.properties.url" alt="预览" style="max-width:100%;max-height:100px;" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -163,14 +155,14 @@ const quillToolbar = [
|
|||||||
<div v-if="nodeType === 'textNode'" class="property-section">
|
<div v-if="nodeType === 'textNode'" class="property-section">
|
||||||
<div class="section-header">文本编辑</div>
|
<div class="section-header">文本编辑</div>
|
||||||
<div class="property-item">
|
<div class="property-item">
|
||||||
<QuillEditor
|
<!-- <QuillEditor-->
|
||||||
v-model:content="selectedNode.data.html"
|
<!-- v-model:content="selectedNode.value.properties.html"-->
|
||||||
contentType="html"
|
<!-- contentType="html"-->
|
||||||
:toolbar="quillToolbar"
|
<!-- :toolbar="quillToolbar"-->
|
||||||
theme="snow"
|
<!-- theme="snow"-->
|
||||||
style="height:120px;"
|
<!-- style="height:120px;"-->
|
||||||
@update:content="val => updateNodeData('html', val)"
|
<!-- @update:content="val => updateNodeData('html', val)"-->
|
||||||
/>
|
<!-- />-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -41,6 +41,7 @@ onMounted(() => {
|
|||||||
:src="currentShikigami.avatar"
|
:src="currentShikigami.avatar"
|
||||||
:alt="currentShikigami.name"
|
:alt="currentShikigami.name"
|
||||||
class="shikigami-image"
|
class="shikigami-image"
|
||||||
|
draggable="false"
|
||||||
/>
|
/>
|
||||||
<div v-else class="placeholder-text">点击选择式神</div>
|
<div v-else class="placeholder-text">点击选择式神</div>
|
||||||
<div class="name-text">{{ currentShikigami.name }}</div>
|
<div class="name-text">{{ currentShikigami.name }}</div>
|
||||||
|
@@ -32,6 +32,7 @@ onMounted(() => {
|
|||||||
:src="currentYuhun.avatar"
|
:src="currentYuhun.avatar"
|
||||||
:alt="currentYuhun.name"
|
:alt="currentYuhun.name"
|
||||||
class="yuhun-image"
|
class="yuhun-image"
|
||||||
|
draggable="false"
|
||||||
/>
|
/>
|
||||||
<div v-else class="placeholder-text">点击选择御魂</div>
|
<div v-else class="placeholder-text">点击选择御魂</div>
|
||||||
<div class="name-text">{{ currentYuhun.name }}</div>
|
<div class="name-text">{{ currentYuhun.name }}</div>
|
||||||
|
16
src/ts/useLogicFlow.ts
Normal file
16
src/ts/useLogicFlow.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import type LogicFlow from '@logicflow/core';
|
||||||
|
|
||||||
|
let logicFlowInstance: LogicFlow | null = null;
|
||||||
|
|
||||||
|
export function setLogicFlowInstance(lf: LogicFlow) {
|
||||||
|
logicFlowInstance = lf;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLogicFlowInstance(): LogicFlow | null {
|
||||||
|
return logicFlowInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function destroyLogicFlowInstance() {
|
||||||
|
logicFlowInstance?.destroy();
|
||||||
|
logicFlowInstance = null;
|
||||||
|
}
|
Reference in New Issue
Block a user