固定useStore核心功能,调用解耦,优化代码

This commit is contained in:
2025-07-30 17:04:36 +08:00
parent b904b257e5
commit 7a87ca6c03
7 changed files with 386 additions and 816 deletions

View File

@@ -82,6 +82,7 @@ import updateLogs from "../data/updateLog.json"
import {useFilesStore} from "@/ts/useStore";
import {ElMessageBox} from "element-plus";
import {useGlobalMessage} from "@/ts/useGlobalMessage";
import { getLogicFlowInstance } from "@/ts/useLogicFlow";
// import { useScreenshot } from '@/ts/useScreenshot';
import { getCurrentInstance } from 'vue';
@@ -100,6 +101,23 @@ const state = reactive({
showFeedbackFormDialog: false, // 控制反馈表单对话框的显示状态
});
// 重新渲染 LogicFlow 画布的通用方法
const refreshLogicFlowCanvas = (message?: string) => {
setTimeout(() => {
const logicFlowInstance = getLogicFlowInstance();
if (logicFlowInstance) {
// 获取当前活动文件的数据
const currentFileData = filesStore.getTab(filesStore.activeFile);
if (currentFileData) {
// 清空画布并重新渲染
logicFlowInstance.clearData();
logicFlowInstance.render(currentFileData);
console.log(message || 'LogicFlow 画布已重新渲染');
}
}
}, 100); // 延迟一点确保数据更新完成
};
const loadExample = () => {
ElMessageBox.confirm(
'加载样例会覆盖当前数据,是否覆盖?',
@@ -133,6 +151,7 @@ const loadExample = () => {
activeFile: "example"
};
filesStore.importData(defaultState);
refreshLogicFlowCanvas('LogicFlow 画布已重新渲染(示例数据)');
showMessage('success', '数据已恢复');
}).catch(() => {
showMessage('info', '选择了不恢复旧数据');
@@ -161,7 +180,13 @@ const showFeedbackForm = () => {
};
const handleExport = () => {
filesStore.exportData();
// 导出前先更新当前数据,确保不丢失最新修改
filesStore.updateTab();
// 延迟一点确保更新完成后再导出
setTimeout(() => {
filesStore.exportData();
}, 2000);
};
const handleImport = () => {
@@ -178,6 +203,7 @@ const handleImport = () => {
const target = e.target as FileReader;
const data = JSON.parse(target.result as string);
filesStore.importData(data);
// refreshLogicFlowCanvas('LogicFlow 画布已重新渲染(导入数据)');
} catch (error) {
console.error('Failed to import file', error);
showMessage('error', '文件格式错误');

View File

@@ -36,13 +36,9 @@ import { useFilesStore } from "@/ts/useStore";
import { setLogicFlowInstance, destroyLogicFlowInstance } from '@/ts/useLogicFlow';
const props = defineProps<{
nodes: any[];
edges: any[];
viewport?: { x: number; y: number; zoom: number };
height?: string;
}>();
const filesStore = useFilesStore();
const containerRef = ref<HTMLElement | null>(null);
const lf = ref<LogicFlow | null>(null);
@@ -70,13 +66,17 @@ function registerNodes(lfInstance: LogicFlow) {
// 初始化 LogicFlow
onMounted(() => {
lf.value = new LogicFlow({
container: containerRef.value as HTMLElement,
container: containerRef.value,
// container: document.querySelector('#container'),
grid: true,
allowResize: true,
allowRotate : true
});
registerNodes(lf.value);
renderFlow();
setLogicFlowInstance(lf.value);
lf.value.render({
// 渲染的数据
})
// 监听节点点击事件,更新 selectedNode
lf.value.on(EventType.NODE_CLICK, ({ data }) => {
selectedNode.value = data;
@@ -112,46 +112,8 @@ onBeforeUnmount(() => {
destroyLogicFlowInstance();
});
// 响应式更新 nodes/edges
// watch(
// () => [props.nodes, props.edges],
// () => {
// renderFlow();
// },
// { deep: true }
// );
// 响应式更新 viewport
watch(
() => props.viewport,
(val) => {
if (val) setViewport(val);
}
);
function renderFlow() {
if (!lf.value) return;
lf.value.render({
nodes: props.nodes,
edges: props.edges,
});
}
function setViewport(viewport?: { x: number; y: number; zoom: number }) {
if (!lf.value || !viewport) return;
lf.value.zoom(viewport.zoom);
// lf.value.focusOn({ x: viewport.x, y: viewport.y });
}
function getViewport() {
if (!lf.value) return { x: 0, y: 0, zoom: 1 };
const t = lf.value.getTransform();
return {
x: t.TRANSLATE_X,
y: t.TRANSLATE_Y,
zoom: t.SCALE_X
};
}
// 右键菜单相关
function handleNodeContextMenu({ data, e }: { data: any; e: MouseEvent }) {
@@ -174,23 +136,6 @@ function handleLayerOrder(action: string) {
contextMenu.value.show = false;
}
function getGraphRawData() {
if (!lf) return null;
return lf.value.getGraphRawData();
}
function renderRawData(data: any) {
if (!lf) return;
lf.value.renderRawData(data);
}
defineExpose({
getViewport,
setViewport,
renderFlow,
getGraphRawData,
renderRawData,
});
</script>
<style scoped>

View File

@@ -1,65 +1,65 @@
<script setup lang="ts">
import {ref, watch} from 'vue';
import {Handle, useVueFlow} from '@vue-flow/core';
import {NodeResizer} from '@vue-flow/node-resizer';
import '@vue-flow/node-resizer/dist/style.css';
<!--<script setup lang="ts">-->
<!--import {ref, watch} from 'vue';-->
<!--import {Handle, useVueFlow} from '@vue-flow/core';-->
<!--import {NodeResizer} from '@vue-flow/node-resizer';-->
<!--import '@vue-flow/node-resizer/dist/style.css';-->
const props = defineProps({
data: Object,
id: String,
selected: Boolean
});
<!--const props = defineProps({-->
<!-- data: Object,-->
<!-- id: String,-->
<!-- selected: Boolean-->
<!--});-->
const nodeWidth = ref(180);
const nodeHeight = ref(120);
<!--const nodeWidth = ref(180);-->
<!--const nodeHeight = ref(120);-->
// 监听props.data变化支持外部更新图片
watch(() => props.data, (newData) => {
if (newData && newData.width) nodeWidth.value = newData.width;
if (newData && newData.height) nodeHeight.value = newData.height;
}, {immediate: true});
<!--// 监听props.data变化支持外部更新图片-->
<!--watch(() => props.data, (newData) => {-->
<!-- if (newData && newData.width) nodeWidth.value = newData.width;-->
<!-- if (newData && newData.height) nodeHeight.value = newData.height;-->
<!--}, {immediate: true});-->
</script>
<template>
<NodeResizer v-if="selected" :min-width="60" :min-height="60" :max-width="400" :max-height="400"/>
<div class="image-node">
<Handle type="target" position="left" :id="`${id}-target`"/>
<div class="image-content">
<img v-if="props.data && props.data.url" :src="props.data.url" alt="图片节点"
style="width:100%;height:100%;object-fit:contain;"/>
<div v-else class="image-placeholder">未上传图片</div>
</div>
<Handle type="source" position="right" :id="`${id}-source`"/>
</div>
</template>
<style scoped>
.image-node {
background: #fff;
border: 1px solid #dcdfe6;
border-radius: 4px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
overflow: hidden;
position: relative;
width: 100%;
height: 100%;
min-width: 180px;
min-height: 180px;
}
<!--</script>-->
<!--<template>-->
<!-- <NodeResizer v-if="selected" :min-width="60" :min-height="60" :max-width="400" :max-height="400"/>-->
<!-- <div class="image-node">-->
<!-- <Handle type="target" position="left" :id="`${id}-target`"/>-->
<!-- <div class="image-content">-->
<!-- <img v-if="props.data && props.data.url" :src="props.data.url" alt="图片节点"-->
<!-- style="width:100%;height:100%;object-fit:contain;"/>-->
<!-- <div v-else class="image-placeholder">未上传图片</div>-->
<!-- </div>-->
<!-- <Handle type="source" position="right" :id="`${id}-source`"/>-->
<!-- </div>-->
<!--</template>-->
<!--<style scoped>-->
<!--.image-node {-->
<!-- background: #fff;-->
<!-- border: 1px solid #dcdfe6;-->
<!-- border-radius: 4px;-->
<!-- display: flex;-->
<!-- flex-direction: column;-->
<!-- align-items: center;-->
<!-- justify-content: center;-->
<!-- overflow: hidden;-->
<!-- position: relative;-->
<!-- width: 100%;-->
<!-- height: 100%;-->
<!-- min-width: 180px;-->
<!-- min-height: 180px;-->
<!--}-->
.image-content {
position: relative;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
<!--.image-content {-->
<!-- position: relative;-->
<!-- width: 100%;-->
<!-- height: 100%;-->
<!-- display: flex;-->
<!-- align-items: center;-->
<!-- justify-content: center;-->
<!--}-->
.image-placeholder {
color: #bbb;
font-size: 14px;
}
</style>
<!--.image-placeholder {-->
<!-- color: #bbb;-->
<!-- font-size: 14px;-->
<!--}-->
<!--</style>-->

View File

@@ -1,51 +1,51 @@
<script setup lang="ts">
import { ref, watch } from 'vue';
import { Handle, useVueFlow } from '@vue-flow/core';
import { NodeResizer } from '@vue-flow/node-resizer';
import '@vue-flow/node-resizer/dist/style.css';
<!--<script setup lang="ts">-->
<!--import { ref, watch } from 'vue';-->
<!--import { Handle, useVueFlow } from '@vue-flow/core';-->
<!--import { NodeResizer } from '@vue-flow/node-resizer';-->
<!--import '@vue-flow/node-resizer/dist/style.css';-->
const props = defineProps({
data: Object,
id: String,
selected: Boolean
});
<!--const props = defineProps({-->
<!-- data: Object,-->
<!-- id: String,-->
<!-- selected: Boolean-->
<!--});-->
const nodeWidth = ref(200);
const nodeHeight = ref(120);
const html = ref('');
<!--const nodeWidth = ref(200);-->
<!--const nodeHeight = ref(120);-->
<!--const html = ref('');-->
watch(() => props.data, (newData) => {
if (newData && newData.html !== undefined) html.value = newData.html;
if (newData && newData.width) nodeWidth.value = newData.width;
if (newData && newData.height) nodeHeight.value = newData.height;
}, { immediate: true });
</script>
<template>
<div class="text-node" :style="{ width: `${nodeWidth}px`, height: `${nodeHeight}px` }">
<NodeResizer v-if="selected" :min-width="80" :min-height="40" :max-width="400" :max-height="400" />
<Handle type="target" position="left" :id="`${id}-target`" />
<div class="text-content" v-html="html"></div>
<Handle type="source" position="right" :id="`${id}-source`" />
</div>
</template>
<style scoped>
.text-node {
background: #fff;
border: 1px solid #dcdfe6;
border-radius: 4px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
overflow: hidden;
}
.text-content {
width: 100%;
height: 100%;
padding: 8px;
font-size: 15px;
color: #333;
word-break: break-all;
overflow: auto;
}
</style>
<!--watch(() => props.data, (newData) => {-->
<!-- if (newData && newData.html !== undefined) html.value = newData.html;-->
<!-- if (newData && newData.width) nodeWidth.value = newData.width;-->
<!-- if (newData && newData.height) nodeHeight.value = newData.height;-->
<!--}, { immediate: true });-->
<!--</script>-->
<!--<template>-->
<!-- <div class="text-node" :style="{ width: `${nodeWidth}px`, height: `${nodeHeight}px` }">-->
<!-- <NodeResizer v-if="selected" :min-width="80" :min-height="40" :max-width="400" :max-height="400" />-->
<!-- <Handle type="target" position="left" :id="`${id}-target`" />-->
<!-- <div class="text-content" v-html="html"></div>-->
<!-- <Handle type="source" position="right" :id="`${id}-source`" />-->
<!-- </div>-->
<!--</template>-->
<!--<style scoped>-->
<!--.text-node {-->
<!-- background: #fff;-->
<!-- border: 1px solid #dcdfe6;-->
<!-- border-radius: 4px;-->
<!-- display: flex;-->
<!-- flex-direction: column;-->
<!-- align-items: center;-->
<!-- justify-content: center;-->
<!-- overflow: hidden;-->
<!--}-->
<!--.text-content {-->
<!-- width: 100%;-->
<!-- height: 100%;-->
<!-- padding: 8px;-->
<!-- font-size: 15px;-->
<!-- color: #333;-->
<!-- word-break: break-all;-->
<!-- overflow: auto;-->
<!--}-->
<!--</style>-->