持久化导入导出支持

This commit is contained in:
2025-07-09 14:39:34 +08:00
parent 7d07e98e76
commit 6f5a1304a6
4 changed files with 213 additions and 27 deletions

View File

@@ -27,7 +27,7 @@ const flowEditorRefs = ref({});
const lastActiveFile = ref(filesStore.activeFile);
const handleTabsEdit = (
targetName: String | undefined,
targetName: string | undefined,
action: 'remove' | 'add'
) => {
if (action === 'remove') {
@@ -60,6 +60,9 @@ onMounted(() => {
window.addEventListener('resize', () => {
windowHeight.value = window.innerHeight;
});
// 初始化自动保存功能
filesStore.initializeWithPrompt();
filesStore.setupAutoSave();
});
onUnmounted(() => {

View File

@@ -80,7 +80,6 @@ import {ref, reactive, onMounted} from 'vue';
import html2canvas from "html2canvas";
import {useI18n} from 'vue-i18n';
import updateLogs from "../data/updateLog.json"
import filesStoreExample from "../data/filesStoreExample.json"
import {useFilesStore} from "@/ts/useStore";
import {ElMessageBox} from "element-plus";
import {useGlobalMessage} from "@/ts/useGlobalMessage";
@@ -110,7 +109,29 @@ const loadExample = () => {
type: 'warning',
}
).then(() => {
filesStore.$patch({fileList: filesStoreExample});
// 使用默认状态作为示例
const defaultState = {
fileList: [{
"label": "示例文件",
"name": "example",
"visible": true,
"type": "FLOW",
"groups": [
{
"shortDescription": "示例组",
"groupInfo": [{}, {}, {}, {}, {}],
"details": "这是一个示例文件"
}
],
"flowData": {
"nodes": [],
"edges": [],
"viewport": { "x": 0, "y": 0, "zoom": 1 }
}
}],
activeFile: "example"
};
filesStore.importData(defaultState);
showMessage('success', '数据已恢复');
}).catch(() => {
showMessage('info', '选择了不恢复旧数据');
@@ -139,14 +160,7 @@ const showFeedbackForm = () => {
};
const handleExport = () => {
const dataStr = JSON.stringify(filesStore.fileList, 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 = 'files.json';
link.click();
URL.revokeObjectURL(url);
filesStore.exportData();
};
const handleImport = () => {
@@ -154,27 +168,18 @@ const handleImport = () => {
input.type = 'file';
input.accept = '.json';
input.onchange = (e) => {
const file = e.target.files[0];
const target = e.target as HTMLInputElement;
const file = target.files?.[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
try {
const data = JSON.parse(e.target.result as string);
if (data[0].visible === true) {
// 新版本格式:直接替换 fileList
filesStore.$patch({fileList: data});
} else {
// 旧版本格式:仅包含 groups 数组
const newFile = {
label: `File ${filesStore.fileList.length + 1}`,
name: String(filesStore.fileList.length + 1),
visible: true,
groups: data
};
filesStore.addFile(newFile);
}
const target = e.target as FileReader;
const data = JSON.parse(target.result as string);
filesStore.importData(data);
} catch (error) {
console.error('Failed to import file', error);
showMessage('error', '文件格式错误');
}
};
reader.readAsText(file);

View File

@@ -5,7 +5,7 @@ import { useFilesStore } from './useStore'
let id = 0
function getId() {
return `dndnode_${id++}`
return `dndnode_${Date.now()}_${Math.floor(Math.random() * 10000)}`;
}
const state = {

View File

@@ -1,6 +1,63 @@
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";
const { showMessage } = useGlobalMessage();
function getDefaultState() {
return {
fileList: [{
"label": "File 1",
"name": "1",
"visible": true,
"type": "FLOW",
"groups": [
{
"shortDescription": "",
"groupInfo": [{}, {}, {}, {}, {}],
"details": ""
}
],
"flowData": {
"nodes": [],
"edges": [],
"viewport": { "x": 0, "y": 0, "zoom": 1 }
}
}],
activeFile: "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');
}
function loadStateFromLocalStorage() {
try {
const data = localStorage.getItem('filesStore');
return data ? JSON.parse(data) : null;
} catch (error) {
console.error('从 localStorage 加载数据失败:', error);
return null;
}
}
// 文件相关的类型定义
interface FileGroup {
@@ -158,6 +215,123 @@ export const useFilesStore = defineStore('files', () => {
return file?.flowData;
};
// 初始化时检查是否有未保存的数据
const initializeWithPrompt = () => {
const savedState = loadStateFromLocalStorage();
const defaultState = getDefaultState();
// 如果没有保存的数据,使用默认状态
if (!savedState) {
fileList.value = defaultState.fileList;
activeFile.value = defaultState.activeFile;
return;
}
const isSame = JSON.stringify(savedState) === JSON.stringify(defaultState);
if (savedState && !isSame) {
ElMessageBox.confirm(
'检测到有未保存的旧数据,是否恢复?',
'提示',
{
confirmButtonText: '恢复',
cancelButtonText: '不恢复',
type: 'warning',
}
).then(() => {
fileList.value = savedState.fileList || [];
activeFile.value = savedState.activeFile || "1";
showMessage('success', '数据已恢复');
}).catch(() => {
clearFilesStoreLocalStorage();
fileList.value = defaultState.fileList;
activeFile.value = defaultState.activeFile;
showMessage('info', '选择了不恢复旧数据');
});
} else {
// 如果有保存的数据且与默认状态相同,直接使用保存的数据
fileList.value = savedState.fileList || defaultState.fileList;
activeFile.value = savedState.activeFile || defaultState.activeFile;
}
};
// 设置自动保存
const setupAutoSave = () => {
console.log('自动保存功能已启动每30秒保存一次');
setInterval(() => {
try {
saveStateToLocalStorage({
fileList: fileList.value,
activeFile: activeFile.value
});
console.log('数据已自动保存到 localStorage');
} catch (error) {
console.error('自动保存失败:', error);
}
}, 30000); // 设置间隔时间为30秒
};
// 导出数据
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', '数据导出失败');
}
};
// 导入数据
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 }
}
};
addFile(newFile);
showMessage('success', '数据导入成功');
}
// 导入后立即保存到 localStorage
saveStateToLocalStorage({
fileList: fileList.value,
activeFile: activeFile.value
});
} catch (error) {
console.error('Failed to import file', error);
showMessage('error', '数据导入失败');
}
};
return {
fileList,
activeFile,
@@ -177,5 +351,9 @@ export const useFilesStore = defineStore('files', () => {
getFileViewport,
updateFileFlowData,
getFileFlowData,
initializeWithPrompt,
setupAutoSave,
exportData,
importData,
};
});