diff --git a/src/App.vue b/src/App.vue
index 870be6c..e5d6569 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -2,16 +2,17 @@
import Toolbar from './components/Toolbar.vue';
import ProjectExplorer from './components/ProjectExplorer.vue';
import ComponentsPanel from './components/flow/ComponentsPanel.vue';
-import { computed, ref, onMounted, onUnmounted, onBeforeUpdate, reactive, provide, inject, watch } from "vue";
-import { useFilesStore } from "@/ts/useStore";
+import {computed, ref, onMounted, onUnmounted, onBeforeUpdate, reactive, provide, inject, watch} from "vue";
+import {useFilesStore} from "@/ts/useStore";
import Vue3DraggableResizable from 'vue3-draggable-resizable';
-import { TabPaneName, TabsPaneContext } from "element-plus";
+import {TabPaneName, TabsPaneContext} from "element-plus";
import FlowEditor from './components/flow/FlowEditor.vue';
import ShikigamiSelect from './components/flow/nodes/yys/ShikigamiSelect.vue';
import YuhunSelect from './components/flow/nodes/yys/YuhunSelect.vue';
import PropertySelect from './components/flow/nodes/yys/PropertySelect.vue';
// import { useVueFlow } from '@vue-flow/core';
import DialogManager from './components/DialogManager.vue';
+import {getLogicFlowInstance} from "@/ts/useLogicFlow";
const filesStore = useFilesStore();
// const { updateNode,toObject,fromObject } = useVueFlow();
@@ -22,122 +23,53 @@ const toolbarHeight = 48; // 工具栏的高度
const windowHeight = ref(window.innerHeight);
const contentHeight = computed(() => `${windowHeight.value - toolbarHeight}px`);
-const flowEditorRef = ref(null);
-const flowEditorRefs = ref({});
-const lastActiveFile = ref(filesStore.activeFile);
-
const handleTabsEdit = (
targetName: string | undefined,
action: 'remove' | 'add'
) => {
if (action === 'remove') {
- filesStore.closeTab(targetName);
+ filesStore.removeTab(targetName);
} else if (action === 'add') {
- const newFileName = `File ${filesStore.fileList.length + 1}`;
-
- filesStore.addFile({
- label: newFileName,
- name: newFileName,
- visible: true,
- type: 'FLOW',
- groups: [
- {
- shortDescription: " ",
- groupInfo: [{}, {}, {}, {}, {}],
- details: ''
- },
- {
- shortDescription: '',
- groupInfo: [{}, {}, {}, {}, {}],
- details: ''
- }
- ]
- });
+ filesStore.addTab();
}
};
onMounted(() => {
- window.addEventListener('resize', () => {
- windowHeight.value = window.innerHeight;
- });
// 初始化自动保存功能
filesStore.initializeWithPrompt();
filesStore.setupAutoSave();
});
onUnmounted(() => {
- window.removeEventListener('resize', () => {
- windowHeight.value = window.innerHeight;
- });
+
});
-const activeFileGroups = computed(() => {
- const activeFile = filesStore.fileList.find(file => file.name === filesStore.activeFile);
- return activeFile ? activeFile.groups : [];
-});
-
-onBeforeUpdate(() => {
- flowEditorRefs.value = {};
-});
-
-const handleAddNode = (nodeData) => {
- const activeEditor = flowEditorRefs.value[filesStore.activeFile];
- if (activeEditor) {
- const { x, y, zoom } = activeEditor.getViewport();
- const position = { x: -x / zoom + 150, y: -y / zoom + 150 };
- activeEditor.handleAddNode({ ...nodeData, position });
- }
-};
watch(
- () => filesStore.activeFile,
- async (newVal, oldVal) => {
- // 切换前保存旧 tab 的数据和视口
- if (oldVal && flowEditorRef.value) {
- if (flowEditorRef.value.getGraphRawData) {
- const rawData = flowEditorRef.value.getGraphRawData();
- filesStore.updateFileFlowData(oldVal, rawData);
+ () => filesStore.activeFile,
+ async (newVal, oldVal) => {
+ // 保存旧 tab 数据
+ if (oldVal) {
+ filesStore.updateTab(oldVal);
}
- if (flowEditorRef.value.getViewport) {
- const viewport = flowEditorRef.value.getViewport();
- console.log(`[Tab切换] 切换前保存 tab "${oldVal}" 的视口信息:`, viewport);
- filesStore.updateFileViewport(oldVal, viewport);
- }
- }
- lastActiveFile.value = newVal;
- // 切换后恢复新 tab 的数据和视口
- if (newVal && flowEditorRef.value) {
- if (flowEditorRef.value.renderRawData) {
- const newRawData = filesStore.getFileFlowData(newVal);
- if (newRawData) flowEditorRef.value.renderRawData(newRawData);
- }
- if (flowEditorRef.value.setViewport) {
- const newViewport = filesStore.getFileViewport(newVal);
- console.log(`[Tab切换] 切换后恢复 tab "${newVal}" 的视口信息:`, newViewport);
- requestAnimationFrame(() => {
- flowEditorRef.value.setViewport(newViewport);
- });
+ // 渲染新 tab 数据
+ if (newVal) {
+ const logicFlowInstance = getLogicFlowInstance();
+ const currentTab = filesStore.getTab(newVal);
+
+ if (logicFlowInstance && currentTab?.graphRawData) {
+ try {
+ logicFlowInstance.render(currentTab.graphRawData);
+ logicFlowInstance.zoom(currentTab.transform.SCALE_X, [currentTab.transform.TRANSLATE_X, currentTab.transform.TRANSLATE_Y]);
+ } catch (error) {
+ console.warn('渲染画布数据失败:', error);
+ }
+ }
}
}
- }
);
-const handleDropOnCanvas = (event: DragEvent) => {
- event.preventDefault();
- const nodeData = JSON.parse(event.dataTransfer?.getData('application/json') || '{}');
- // 计算画布坐标(这里简单用鼠标坐标,后续可结合 LogicFlow 视口变换优化)
- const rect = (event.target as HTMLElement).getBoundingClientRect();
- const x = event.clientX - rect.left;
- const y = event.clientY - rect.top;
- const id = `node-${Date.now()}-${Math.floor(Math.random() * 10000)}`;
- filesStore.addNode({ id, type: nodeData.type, x, y, ...nodeData.data });
-};
-
-const handleDragOverOnCanvas = (event: DragEvent) => {
- event.preventDefault();
-};
-
@@ -147,7 +79,7 @@ const handleDragOverOnCanvas = (event: DragEvent) => {
-
+
{
:name="file.name.toString()"
/>
-
-
+
diff --git a/src/components/Toolbar.vue b/src/components/Toolbar.vue
index 401b871..7a4fc31 100644
--- a/src/components/Toolbar.vue
+++ b/src/components/Toolbar.vue
@@ -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', '文件格式错误');
diff --git a/src/components/flow/FlowEditor.vue b/src/components/flow/FlowEditor.vue
index 705f4f9..d266eaa 100644
--- a/src/components/flow/FlowEditor.vue
+++ b/src/components/flow/FlowEditor.vue
@@ -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(null);
const lf = ref(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,
-});
\ No newline at end of file
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/flow/nodes/common/TextNode.vue b/src/components/flow/nodes/common/TextNode.vue
index d9d9a49..c2d1703 100644
--- a/src/components/flow/nodes/common/TextNode.vue
+++ b/src/components/flow/nodes/common/TextNode.vue
@@ -1,51 +1,51 @@
-
-
-
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ts/files.ts b/src/ts/files.ts
deleted file mode 100644
index c5ec2f7..0000000
--- a/src/ts/files.ts
+++ /dev/null
@@ -1,130 +0,0 @@
-import {defineStore} from 'pinia';
-import {ElMessageBox} from "element-plus";
-import {useGlobalMessage} from "./useGlobalMessage";
-
-const { showMessage } = useGlobalMessage();
-
-function getDefaultState() {
- return {
- fileList: [{
- "label": "File 1",
- "name": "1",
- "visible": true,
- "type":"PVE",
- "groups": [
- {
- "shortDescription": "",
- "groupInfo": [
- {}, {}, {}, {}, {}
- ],
- "details": ""
- }
- ]
- }],
- activeFile: "1",
- };
-}
-
-function saveStateToLocalStorage(state) {
- localStorage.setItem('filesStore', JSON.stringify(state));
-}
-
-function clearFilesStoreLocalStorage() {
- localStorage.removeItem('filesStore')
-}
-
-function loadStateFromLocalStorage() {
- return JSON.parse(localStorage.getItem('filesStore'));
-}
-
-export const useFilesStore = defineStore('files', {
- state: () => getDefaultState(),
- getters: {
- visibleFiles: (state) => state.fileList.filter(file => file.visible),
- },
- actions: {
- initializeWithPrompt() {
- const savedState = loadStateFromLocalStorage();
- const defaultState = getDefaultState();
-
- const isSame = JSON.stringify(savedState) === JSON.stringify(defaultState);
- if (savedState && !isSame) {
- ElMessageBox.confirm(
- '检测到有未保存的旧数据,是否恢复?',
- '提示',
- {
- confirmButtonText: '恢复',
- cancelButtonText: '不恢复',
- type: 'warning',
- }
- ).then(() => {
- this.fileList = savedState.fileList || [];
- this.activeFile = savedState.activeFile || "1";
- showMessage('success', '数据已恢复');
- }).catch(() => {
- clearFilesStoreLocalStorage();
- showMessage('info', '选择了不恢复旧数据');
- });
- }
- },
- setupAutoSave() {
- setInterval(() => {
- saveStateToLocalStorage(this.$state);
- }, 30000); // 设置间隔时间为30秒
- },
- addFile(file) {
- this.fileList.push({...file, visible: true});
- this.activeFile = file.name;
- },
- setActiveFile(fileId: number) {
- this.activeFile = fileId;
- },
- setVisible(fileId: number, visibility: boolean) {
- const file = this.fileList.find(file => file.name === fileId);
- if (file) {
- file.visible = visibility;
- }
- },
- closeTab(fileName: String) {
- const file = this.fileList.find(file => file.name === fileName);
- if (file) {
- file.visible = false;
- if (this.activeFile === fileName) {
- const nextVisibleFile = this.visibleFiles[0];
- this.activeFile = nextVisibleFile ? nextVisibleFile.name : -1;
- }
- }
- },
- async deleteFile(fileId: string) {
- try {
- if (this.fileList.length === 1) {
- showMessage('warning', '无法删除');
- return;
- }
- await ElMessageBox.confirm('确定要删除此文件吗?', '提示', {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning',
- });
-
- const index = this.fileList.findIndex(file => file.name === fileId);
- if (index > -1) {
- this.fileList.splice(index, 1);
- if (this.activeFile === fileId) {
- const nextVisibleFile = this.visibleFiles[0];
- this.activeFile = nextVisibleFile ? nextVisibleFile.name : "-1";
- }
- }
- showMessage('success', '删除成功!');
- } catch (error) {
- showMessage('info', '已取消删除');
- }
- },
- renameFile(fileId, newName) {
- const file = this.fileList.find(file => file.name === fileId);
- if (file) {
- file.label = newName;
- }
- },
- },
-});
\ No newline at end of file
diff --git a/src/ts/useStore.ts b/src/ts/useStore.ts
index 41d1a9c..9c058a1 100644
--- a/src/ts/useStore.ts
+++ b/src/ts/useStore.ts
@@ -1,183 +1,54 @@
-import { defineStore } from 'pinia';
-import { ref, computed } from 'vue';
+import {defineStore} from 'pinia';
+import {ref, computed} from 'vue';
// import type { Edge, Node, ViewportTransform } from '@vue-flow/core';
-import { ElMessageBox } from "element-plus";
-import { useGlobalMessage } from "./useGlobalMessage";
+import {ElMessageBox} from "element-plus";
+import {useGlobalMessage} from "./useGlobalMessage";
+import {getLogicFlowInstance} from "./useLogicFlow";
-const { showMessage } = useGlobalMessage();
+const {showMessage} = useGlobalMessage();
-// LogicFlow 实例全局引用
-let logicFlowInstance: any = null;
-function setLogicFlowInstance(lf: any) {
- logicFlowInstance = lf;
+// localStorage 防抖定时器
+let localStorageDebounceTimer: NodeJS.Timeout | null = null;
+const LOCALSTORAGE_DEBOUNCE_DELAY = 1000; // 1秒防抖
+
+interface FlowFile {
+ label: string;
+ name: string;
+ visible: boolean;
+ type: string;
+ graphRawData?: object;
+ transform?: {
+ "SCALE_X": number,
+ "SCALE_Y": number,
+ "TRANSLATE_X": number,
+ "TRANSLATE_Y": number
+ };
}
function getDefaultState() {
return {
- fileList: [
+ "fileList": [
{
- label: "File 1",
- name: "1",
- visible: true,
- type: "FLOW",
- groups: [
- {
- shortDescription: "File 1 Group",
- groupInfo: [{}, {}, {}, {}, {}],
- details: "File 1 详情"
- }
- ],
- flowData: {
- nodes: [
- {
- id: "node-1",
- type: "rect",
- x: 100,
- y: 100,
- text: "File1-矩形节点"
- },
- {
- id: "node-2",
- type: "ellipse",
- x: 350,
- y: 120,
- text: "File1-圆形节点"
- },
- {
- id: "node-3",
- type: "shikigamiSelect",
- x: 200,
- y: 300,
- properties: {
- shikigami: {
- name: "时曜泷夜叉姬",
- avatar: "/assets/Shikigami/sp/584.png",
- rarity: "SP"
- },
- width: 100,
- height: 80
- }
- },
- {
- id: "node-yuhun-1",
- type: "yuhunSelect",
- x: 300,
- y: 200,
- properties: {
- yuhun: {
- name: "针女",
- avatar: "/assets/Yuhun/针女.png",
- type: "攻击类"
- },
- width: 100,
- height: 80
- }
- },
- {
- id: "node-property-1",
- type: "propertySelect",
- x: 500,
- y: 300,
- properties: {
- property: {
- type: "attack",
- priority: "required",
- attackType: "fixed",
- attackValue: 3000,
- description: "主输出式神,需高攻击",
- levelRequired: "40",
- skillRequiredMode: "all",
- skillRequired: ["5", "5", "5"],
- yuhun: {
- yuhunSetEffect: [
- { name: "破势", avatar: "/assets/Yuhun/破势.png" },
- { name: "荒骷髅", avatar: "/assets/Yuhun/荒骷髅.png" }
- ],
- target: "1",
- property2: ["Attack"],
- property4: ["Attack"],
- property6: ["Crit", "CritDamage"]
- },
- expectedDamage: 10000,
- survivalRate: 50,
- damageType: "balanced"
- },
- width: 120,
- height: 100
- }
- }
- ],
- edges: [
- {
- id: "edge-1",
- type: "polyline",
- sourceNodeId: "node-1",
- targetNodeId: "node-2"
- }
- ],
- viewport: { x: 0, y: 0, zoom: 1 }
- }
- },
- {
- label: "File 2",
- name: "2",
- visible: true,
- type: "FLOW",
- groups: [
- {
- shortDescription: "File 2 Group",
- groupInfo: [{}, {}, {}, {}, {}],
- details: "File 2 详情"
- }
- ],
- flowData: {
- nodes: [
- {
- id: "node-1",
- type: "rect",
- x: 100,
- y: 100,
- text: "File2-矩形节点"
- },
- {
- id: "node-2",
- type: "ellipse",
- x: 350,
- y: 120,
- text: "File2222-圆形节点"
- }
- ],
- edges: [
- {
- id: "edge-1",
- type: "polyline",
- sourceNodeId: "node-1",
- targetNodeId: "node-2"
- }
- ],
- viewport: { x: 0, y: 0, zoom: 1 }
+ "label": "File 1",
+ "name": "File 1",
+ "visible": true,
+ "type": "FLOW",
+ "graphRawData": {
+ "nodes": [],
+ "edges": []
+ },
+ "transform": {
+ "SCALE_X": 1,
+ "SCALE_Y": 1,
+ "TRANSLATE_X": 0,
+ "TRANSLATE_Y": 0
}
}
],
- activeFile: "1",
+ "activeFile": "File 1"
};
}
-function saveStateToLocalStorage(state: any) {
- try {
- localStorage.setItem('filesStore', JSON.stringify(state));
- } catch (error) {
- console.error('保存到 localStorage 失败:', error);
- // 如果 localStorage 满了,尝试清理一些数据
- try {
- localStorage.clear();
- localStorage.setItem('filesStore', JSON.stringify(state));
- } catch (clearError) {
- console.error('清理 localStorage 后仍无法保存:', clearError);
- }
- }
-}
-
function clearFilesStoreLocalStorage() {
localStorage.removeItem('filesStore');
}
@@ -192,34 +63,30 @@ function loadStateFromLocalStorage() {
}
}
-// 文件相关的类型定义
-interface FileGroup {
- shortDescription: string;
- groupInfo: Record[];
- details: string;
+function saveStateToLocalStorage(state: any) {
+ // 清除之前的防抖定时器
+ if (localStorageDebounceTimer) {
+ clearTimeout(localStorageDebounceTimer);
+ }
+
+ // 设置新的防抖定时器
+ localStorageDebounceTimer = setTimeout(() => {
+ try {
+ localStorage.setItem('filesStore', JSON.stringify(state));
+ console.log('数据已防抖保存到 localStorage');
+ } catch (error) {
+ console.error('保存到 localStorage 失败:', error);
+ // 如果 localStorage 满了,尝试清理一些数据
+ try {
+ localStorage.clear();
+ localStorage.setItem('filesStore', JSON.stringify(state));
+ } catch (clearError) {
+ console.error('清理 localStorage 后仍无法保存:', clearError);
+ }
+ }
+ }, LOCALSTORAGE_DEBOUNCE_DELAY);
}
-interface LogicFlowNode {
- id: string;
- type: string;
- x: number;
- y: number;
- text?: string | object;
- properties?: Record;
-}
-
-interface FlowFile {
- label: string;
- name: string;
- visible: boolean;
- type: string;
- groups: FileGroup[];
- flowData?: {
- nodes: LogicFlowNode[];
- edges: any[];
- viewport: any;
- };
-}
export const useFilesStore = defineStore('files', () => {
// 文件列表状态
@@ -231,149 +98,67 @@ export const useFilesStore = defineStore('files', () => {
return fileList.value.filter(file => file.visible);
});
- // 获取当前活动文件的节点和边
- const activeFileNodes = computed(() => {
- const file = fileList.value.find(f => f.name === activeFile.value);
- return file?.flowData?.nodes || [];
- });
-
- const activeFileEdges = computed(() => {
- const file = fileList.value.find(f => f.name === activeFile.value);
- return file?.flowData?.edges || [];
- });
-
- // 添加新文件
- const addFile = (file: FlowFile) => {
- const newFile = {
- ...file,
- flowData: {
- nodes: [],
- edges: [],
- viewport: { x: 0, y: 0, zoom: 1 }
+ // 导入数据
+ const importData = (data: any) => {
+ try {
+ if (data.fileList && Array.isArray(data.fileList)) {
+ // 新版本格式:包含 fileList 和 activeFile
+ fileList.value = data.fileList;
+ activeFile.value = data.activeFile || data[0]?.name;
+ showMessage('success', '数据导入成功');
+ } else if (Array.isArray(data) && data[0]?.visible === true) {
+ // 兼容旧版本格式:直接是 fileList 数组
+ fileList.value = data;
+ activeFile.value = data[0]?.name || "1";
+ showMessage('success', '数据导入成功');
+ } else {
+ // 兼容更旧版本格式:仅包含 groups 数组
+ const newFile = {
+ label: `File ${fileList.value.length + 1}`,
+ name: String(fileList.value.length + 1),
+ visible: true,
+ type: "FLOW",
+ groups: data,
+ graphRawData: {
+ nodes: [],
+ edges: []
+ },
+ transform: {
+ SCALE_X: 1,
+ SCALE_Y: 1,
+ TRANSLATE_X: 0,
+ TRANSLATE_Y: 0
+ }
+ };
+ fileList.value.push(newFile);
+ activeFile.value = newFile.name;
+ showMessage('success', '数据导入成功');
}
- };
- fileList.value.push(newFile);
- activeFile.value = file.name;
- };
-
- // 关闭文件标签
- const closeTab = (fileName: string | undefined) => {
- if (!fileName) return;
-
- const index = fileList.value.findIndex(file => file.name === fileName);
- if (index === -1) return;
-
- fileList.value.splice(index, 1);
-
- // 如果关闭的是当前活动文件,则切换到其他文件
- if (activeFile.value === fileName) {
- activeFile.value = fileList.value[Math.max(0, index - 1)]?.name || '';
+ } catch (error) {
+ console.error('Failed to import file', error);
+ showMessage('error', '数据导入失败');
}
};
- // 添加节点
- const addNode = (node: LogicFlowNode) => {
- const file = fileList.value.find(f => f.name === activeFile.value);
- if (!file) return;
- if (!file.flowData) file.flowData = { nodes: [], edges: [], viewport: { x: 0, y: 0, zoom: 1 } };
- file.flowData.nodes.push(node);
- };
-
- // 更新节点
- const updateNode = (nodeId: string, updateData: Partial) => {
- const file = fileList.value.find(f => f.name === activeFile.value);
- if (!file || !file.flowData || !file.flowData.nodes) return;
- const nodeIndex = file.flowData.nodes.findIndex((n: LogicFlowNode) => n.id === nodeId);
- if (nodeIndex === -1) return;
-
- const oldNode = file.flowData.nodes[nodeIndex];
- const mergedNode = { ...oldNode, ...updateData };
-
- // Deep merge properties
- if (updateData.properties && oldNode.properties) {
- mergedNode.properties = { ...oldNode.properties, ...updateData.properties };
+ // 导出数据
+ const exportData = () => {
+ try {
+ const dataStr = JSON.stringify({
+ fileList: fileList.value,
+ activeFile: activeFile.value
+ }, null, 2);
+ const blob = new Blob([dataStr], {type: 'application/json;charset=utf-8'});
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = url;
+ link.download = 'yys-editor-files.json';
+ link.click();
+ URL.revokeObjectURL(url);
+ showMessage('success', '数据导出成功');
+ } catch (error) {
+ console.error('导出数据失败:', error);
+ showMessage('error', '数据导出失败');
}
- file.flowData.nodes[nodeIndex] = mergedNode;
-
- // 同步 LogicFlow 画布
- if (logicFlowInstance && mergedNode.properties) {
- // setProperties overwrites, so we pass the fully merged properties object
- logicFlowInstance.setProperties(nodeId, mergedNode.properties);
- }
- };
-
- // 删除节点
- const removeNode = (nodeId: string) => {
- const file = fileList.value.find(f => f.name === activeFile.value);
- if (!file || !file.flowData || !file.flowData.nodes) return;
- file.flowData.nodes = file.flowData.nodes.filter(n => n.id !== nodeId);
- // 同时删除相关的边
- if (file.flowData.edges) {
- file.flowData.edges = file.flowData.edges.filter(e => e.source !== nodeId && e.target !== nodeId);
- }
- };
-
- // 添加边
- const addEdge = (edge) => {
- const file = fileList.value.find(f => f.name === activeFile.value);
- if (!file) return;
- if (!file.flowData) file.flowData = { nodes: [], edges: [], viewport: { x: 0, y: 0, zoom: 1 } };
- file.flowData.edges.push(edge);
- };
-
- // 删除边
- const removeEdge = (edgeId: string) => {
- const file = fileList.value.find(f => f.name === activeFile.value);
- if (!file || !file.flowData || !file.flowData.edges) return;
- file.flowData.edges = file.flowData.edges.filter(e => e.id !== edgeId);
- };
-
- // 更新节点位置
- const updateNodePosition = (nodeId: string, position: { x: number; y: number }) => {
- const file = fileList.value.find(f => f.name === activeFile.value);
- if (!file || !file.flowData || !file.flowData.nodes) return;
- const node = file.flowData.nodes.find((n: LogicFlowNode) => n.id === nodeId);
- if (node) {
- node.x = position.x;
- node.y = position.y;
- }
- };
-
- // 更新节点顺序
- const updateNodesOrder = (nodes: LogicFlowNode[]) => {
- const file = fileList.value.find(f => f.name === activeFile.value);
- if (!file || !file.flowData) return;
- file.flowData.nodes = nodes;
- };
-
- // 更新文件的 viewport
- const updateFileViewport = (fileName: string, viewport: { x: number; y: number; zoom: number }) => {
- const file = fileList.value.find(f => f.name === fileName);
- if (file && file.flowData) {
- console.log(`[updateFileViewport] 保存 tab "${fileName}" 的视口信息:`, viewport);
- file.flowData.viewport = viewport;
- }
- };
-
- const getFileViewport = (fileName: string) => {
- const file = fileList.value.find(f => f.name === fileName);
- const v = file?.flowData?.viewport;
- if (v && typeof v.x === 'number' && typeof v.y === 'number' && typeof v.zoom === 'number') {
- return v ;
- }
- return { x: 0, y: 0, zoom: 1 };
- };
-
- // 更新文件的 flowData
- const updateFileFlowData = (fileName: string, flowData: any) => {
- const file = fileList.value.find(f => f.name === fileName);
- if (file) file.flowData = flowData;
- };
-
- // 获取文件的 flowData
- const getFileFlowData = (fileName: string): any => {
- const file = fileList.value.find(f => f.name === fileName);
- return file?.flowData;
};
// 初始化时检查是否有未保存的数据
@@ -415,107 +200,127 @@ export const useFilesStore = defineStore('files', () => {
}
};
- // 设置自动保存
+ // 设置自动更新
const setupAutoSave = () => {
- console.log('自动保存功能已启动,每30秒保存一次');
+ console.log('自动更新功能已启动,每30秒更新一次');
setInterval(() => {
- try {
- saveStateToLocalStorage({
- fileList: fileList.value,
- activeFile: activeFile.value
- });
- console.log('数据已自动保存到 localStorage');
- } catch (error) {
- console.error('自动保存失败:', error);
- }
+ updateTab(); // 使用统一的更新方法
}, 30000); // 设置间隔时间为30秒
};
- // 导出数据
- const exportData = () => {
+ // 添加新文件
+ const addTab = () => {
+ // 添加文件前先保存
+ updateTab();
+
+ requestAnimationFrame(() => {
+ const newFileName = `File ${fileList.value.length + 1}`;
+ const newFile = {
+ label: newFileName,
+ name: newFileName,
+ visible: true,
+ type: 'FLOW',
+ graphRawData: {},
+ transform: {
+ SCALE_X: 1,
+ SCALE_Y: 1,
+ TRANSLATE_X: 0,
+ TRANSLATE_Y: 0
+ }
+ };
+ fileList.value.push(newFile);
+ activeFile.value = newFileName;
+ });
+ };
+
+ // 关闭文件标签
+ const removeTab = (fileName: string | undefined) => {
+ if (!fileName) return;
+
+ const index = fileList.value.findIndex(file => file.name === fileName);
+ if (index === -1) return;
+
+ fileList.value.splice(index, 1);
+
+ // 如果关闭的是当前活动文件,则切换到其他文件
+ if (activeFile.value === fileName) {
+ activeFile.value = fileList.value[Math.max(0, index - 1)]?.name || '';
+ }
+
+ // 关闭文件后立即更新
+ updateTab();
+ };
+
+ // 更新指定 Tab - 内存操作即时,localStorage 操作防抖
+ const updateTab = (fileName?: string) => {
try {
- const dataStr = JSON.stringify({
+ const targetFile = fileName || activeFile.value;
+
+ // 先同步 LogicFlow 数据到内存
+ syncLogicFlowDataToStore(targetFile);
+
+ // 再保存到 localStorage(带防抖)
+ const state = {
fileList: fileList.value,
activeFile: activeFile.value
- }, null, 2);
- const blob = new Blob([dataStr], {type: 'application/json;charset=utf-8'});
- const url = URL.createObjectURL(blob);
- const link = document.createElement('a');
- link.href = url;
- link.download = 'yys-editor-files.json';
- link.click();
- URL.revokeObjectURL(url);
- showMessage('success', '数据导出成功');
+ };
+ saveStateToLocalStorage(state);
} catch (error) {
- console.error('导出数据失败:', error);
- showMessage('error', '数据导出失败');
+ console.error('更新 Tab 失败:', error);
+ showMessage('error', '数据更新失败');
}
};
- // 导入数据
- const importData = (data: any) => {
- try {
- if (data.fileList && Array.isArray(data.fileList)) {
- // 新版本格式:包含 fileList 和 activeFile
- fileList.value = data.fileList;
- activeFile.value = data.activeFile || "1";
- showMessage('success', '数据导入成功');
- } else if (Array.isArray(data) && data[0]?.visible === true) {
- // 兼容旧版本格式:直接是 fileList 数组
- fileList.value = data;
- activeFile.value = data[0]?.name || "1";
- showMessage('success', '数据导入成功');
- } else {
- // 兼容更旧版本格式:仅包含 groups 数组
- const newFile = {
- label: `File ${fileList.value.length + 1}`,
- name: String(fileList.value.length + 1),
- visible: true,
- type: "FLOW",
- groups: data,
- flowData: {
- nodes: [],
- edges: [],
- viewport: { x: 0, y: 0, zoom: 1 }
+ // 获取当前 Tab 数据
+ const getTab = (fileName?: string) => {
+ const targetFile = fileName || activeFile.value;
+ return fileList.value.find(f => f.name === targetFile);
+ };
+
+ // 同步 LogicFlow 画布数据到 store 的内部方法
+ const syncLogicFlowDataToStore = (fileName?: string) => {
+ const logicFlowInstance = getLogicFlowInstance();
+ const targetFile = fileName || activeFile.value;
+
+ if (logicFlowInstance && targetFile) {
+ try {
+ // 获取画布最新数据
+ const graphData = logicFlowInstance.getGraphRawData();
+ const transform = logicFlowInstance.getTransform();
+
+ if (graphData) {
+ // 直接保存原始数据到 GraphRawData
+ const file = fileList.value.find(f => f.name === targetFile);
+ if (file) {
+ file.graphRawData = graphData;
+ file.transform = transform;
+ console.log(`已同步画布数据到文件 "${targetFile}"`);
}
- };
- addFile(newFile);
- showMessage('success', '数据导入成功');
+ }
+ } catch (error) {
+ console.warn('同步画布数据失败:', error);
}
- // 导入后立即保存到 localStorage
- saveStateToLocalStorage({
- fileList: fileList.value,
- activeFile: activeFile.value
- });
- } catch (error) {
- console.error('Failed to import file', error);
- showMessage('error', '数据导入失败');
}
};
+
+
+
+
return {
+ importData,
+ exportData,
+
+ initializeWithPrompt,
+ setupAutoSave,
+
+ addTab,
+ removeTab,
+ updateTab,
+ getTab,
+
fileList,
activeFile,
visibleFiles,
- activeFileNodes,
- activeFileEdges,
- addFile,
- closeTab,
- addNode,
- updateNode,
- removeNode,
- addEdge,
- removeEdge,
- updateNodePosition,
- updateNodesOrder,
- updateFileViewport,
- getFileViewport,
- updateFileFlowData,
- getFileFlowData,
- initializeWithPrompt,
- setupAutoSave,
- exportData,
- importData,
- setLogicFlowInstance,
};
});
\ No newline at end of file