mirror of
https://github.com/Powerful-517/yys-editor.git
synced 2025-08-23 08:04:50 +00:00
持久化导入导出支持
This commit is contained in:
@@ -27,7 +27,7 @@ const flowEditorRefs = ref({});
|
|||||||
const lastActiveFile = ref(filesStore.activeFile);
|
const lastActiveFile = ref(filesStore.activeFile);
|
||||||
|
|
||||||
const handleTabsEdit = (
|
const handleTabsEdit = (
|
||||||
targetName: String | undefined,
|
targetName: string | undefined,
|
||||||
action: 'remove' | 'add'
|
action: 'remove' | 'add'
|
||||||
) => {
|
) => {
|
||||||
if (action === 'remove') {
|
if (action === 'remove') {
|
||||||
@@ -60,6 +60,9 @@ onMounted(() => {
|
|||||||
window.addEventListener('resize', () => {
|
window.addEventListener('resize', () => {
|
||||||
windowHeight.value = window.innerHeight;
|
windowHeight.value = window.innerHeight;
|
||||||
});
|
});
|
||||||
|
// 初始化自动保存功能
|
||||||
|
filesStore.initializeWithPrompt();
|
||||||
|
filesStore.setupAutoSave();
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
@@ -80,7 +80,6 @@ import {ref, reactive, onMounted} from 'vue';
|
|||||||
import html2canvas from "html2canvas";
|
import html2canvas from "html2canvas";
|
||||||
import {useI18n} from 'vue-i18n';
|
import {useI18n} from 'vue-i18n';
|
||||||
import updateLogs from "../data/updateLog.json"
|
import updateLogs from "../data/updateLog.json"
|
||||||
import filesStoreExample from "../data/filesStoreExample.json"
|
|
||||||
import {useFilesStore} from "@/ts/useStore";
|
import {useFilesStore} from "@/ts/useStore";
|
||||||
import {ElMessageBox} from "element-plus";
|
import {ElMessageBox} from "element-plus";
|
||||||
import {useGlobalMessage} from "@/ts/useGlobalMessage";
|
import {useGlobalMessage} from "@/ts/useGlobalMessage";
|
||||||
@@ -110,7 +109,29 @@ const loadExample = () => {
|
|||||||
type: 'warning',
|
type: 'warning',
|
||||||
}
|
}
|
||||||
).then(() => {
|
).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', '数据已恢复');
|
showMessage('success', '数据已恢复');
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
showMessage('info', '选择了不恢复旧数据');
|
showMessage('info', '选择了不恢复旧数据');
|
||||||
@@ -139,14 +160,7 @@ const showFeedbackForm = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleExport = () => {
|
const handleExport = () => {
|
||||||
const dataStr = JSON.stringify(filesStore.fileList, null, 2);
|
filesStore.exportData();
|
||||||
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);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleImport = () => {
|
const handleImport = () => {
|
||||||
@@ -154,27 +168,18 @@ const handleImport = () => {
|
|||||||
input.type = 'file';
|
input.type = 'file';
|
||||||
input.accept = '.json';
|
input.accept = '.json';
|
||||||
input.onchange = (e) => {
|
input.onchange = (e) => {
|
||||||
const file = e.target.files[0];
|
const target = e.target as HTMLInputElement;
|
||||||
|
const file = target.files?.[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(e.target.result as string);
|
const target = e.target as FileReader;
|
||||||
if (data[0].visible === true) {
|
const data = JSON.parse(target.result as string);
|
||||||
// 新版本格式:直接替换 fileList
|
filesStore.importData(data);
|
||||||
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);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to import file', error);
|
console.error('Failed to import file', error);
|
||||||
|
showMessage('error', '文件格式错误');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
reader.readAsText(file);
|
reader.readAsText(file);
|
||||||
|
@@ -5,7 +5,7 @@ import { useFilesStore } from './useStore'
|
|||||||
let id = 0
|
let id = 0
|
||||||
|
|
||||||
function getId() {
|
function getId() {
|
||||||
return `dndnode_${id++}`
|
return `dndnode_${Date.now()}_${Math.floor(Math.random() * 10000)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
|
@@ -1,6 +1,63 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
import type { Edge, Node, ViewportTransform } from '@vue-flow/core';
|
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 {
|
interface FileGroup {
|
||||||
@@ -158,6 +215,123 @@ export const useFilesStore = defineStore('files', () => {
|
|||||||
return file?.flowData;
|
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 {
|
return {
|
||||||
fileList,
|
fileList,
|
||||||
activeFile,
|
activeFile,
|
||||||
@@ -177,5 +351,9 @@ export const useFilesStore = defineStore('files', () => {
|
|||||||
getFileViewport,
|
getFileViewport,
|
||||||
updateFileFlowData,
|
updateFileFlowData,
|
||||||
getFileFlowData,
|
getFileFlowData,
|
||||||
|
initializeWithPrompt,
|
||||||
|
setupAutoSave,
|
||||||
|
exportData,
|
||||||
|
importData,
|
||||||
};
|
};
|
||||||
});
|
});
|
Reference in New Issue
Block a user