Browse Source

导入项目功能开发 刷新画布

lichunyang 2 weeks ago
parent
commit
f21ded5167

+ 419 - 0
src/components/dialog/ProjectListDialog.vue

@@ -0,0 +1,419 @@
+<!-- ProjectListDialog.vue -->
+<template>
+  <el-dialog
+    v-model="dialogVisible"
+    align-center
+    :append-to-body="true"
+    width="800"
+    class="dialog_class"
+    draggable
+    @close="handleClose"
+  >
+    <template #header="{ titleId, titleClass }">
+      <div class="my-header">
+        <h4 :id="titleId" :class="titleClass">
+          {{ $t("dialog.projectlist") }}
+        </h4>
+      </div>
+    </template>
+
+    <template #default>
+      <div class="project-main-content custom-table">
+        <el-table
+          :data="projectLists"
+          style="width: 100%; height: 540px; overflow: auto"
+          @row-click="handleRowClick"
+          :row-class-name="tableRowClassName"
+        >
+          <el-table-column
+            type="index"
+            :label="$t('project.number')"
+            width="100"
+          ></el-table-column>
+          <el-table-column
+            prop="name"
+            :label="$t('project.name')"
+          ></el-table-column>
+          <el-table-column
+            prop="dirsize"
+            :label="$t('project.dirsize')"
+          ></el-table-column>
+          <el-table-column
+            prop="updateTime"
+            :label="$t('project.updateTime')"
+            min-width="100"
+          ></el-table-column>
+          <el-table-column
+            prop="uname"
+            :label="$t('project.uname')"
+          ></el-table-column>
+          <el-table-column
+            prop="keywords"
+            :label="$t('project.keywords')"
+          ></el-table-column>
+          <el-table-column
+            prop="remark"
+            :label="$t('project.description')"
+          ></el-table-column>
+        </el-table>
+      </div>
+      <div class="project-main-pagination">
+        <div class="custom-pagination">
+          <el-pagination
+            v-model:current-page="pagination.currentPage"
+            v-model:page-size="pagination.pageSize"
+            :page-sizes="[5, 10, 20, 50]"
+            background
+            size="small"
+            layout="prev, slot, sizes, pager, next"
+            :total="parseInt(pagination.total)"
+            class="mt-4"
+            @size-change="handleSizeChange"
+            @current-change="handleCurrentChange"
+          >
+            <template #default>
+              <span>{{ $t("project.total") }} {{ pagination.total }}</span>
+            </template>
+          </el-pagination>
+        </div>
+      </div>
+    </template>
+
+    <template #footer>
+      <span class="lastbtn">
+        <el-button @click="handleCancel">{{ $t("dialog.cancel") }}</el-button>
+        <el-button
+          @click="confirmSelected"
+          :disabled="selectedRows.length === 0"
+        >
+          {{ $t("dialog.ok") }}
+        </el-button>
+      </span>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup>
+import { ref, watch } from "vue"
+import { useI18n } from "vue-i18n"
+import { useRouter } from "vue-router"
+import { useProjectStore } from "@/store/project"
+import { ElMessage } from "element-plus"
+import { request } from "@/utils/request"
+import emitter from '@/utils/emitter';
+
+const { t } = useI18n()
+const router = useRouter()
+const projectStore = useProjectStore()
+
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    default: false
+  },
+  identify: {
+    type: String,
+    default: "default"
+  }
+})
+
+const emit = defineEmits(["update:visible", "confirm"])
+
+// 本地状态控制对话框显示
+const dialogVisible = ref(props.visible)
+
+// 监听 props.visible 变化
+watch(
+  () => props.visible,
+  (newVal) => {
+    dialogVisible.value = newVal
+    if (newVal) {
+      getProjectList()
+    }
+  }
+)
+
+const pagination = ref({
+  total: 1,
+  currentPage: 1,
+  pageSize: 5,
+  searchtag: ""
+})
+
+const projectLists = ref([])
+const selectedRows = ref([])
+
+// 获取项目列表
+const getProjectList = () => {
+  const params = {
+    transCode: "ES0001",
+    count: pagination.value.pageSize,
+    page: pagination.value.currentPage,
+    searchtag: pagination.value.searchtag
+  }
+  request(params)
+    .then((res) => {
+      pagination.value.total = res.total
+      projectLists.value = res.rows.map((item) => ({
+        ...item,
+        updateTime: item.updateTime.split(" +")[0]
+      }))
+    })
+    .catch((err) => {
+      console.error(err)
+      ElMessage.error(err.returnMsg)
+    })
+}
+
+// 处理行点击事件
+const handleRowClick = (row) => {
+  const index = selectedRows.value.findIndex((item) => item.pid === row.pid)
+  if (index === -1) {
+    selectedRows.value = [row] // 单选模式
+  } else {
+    selectedRows.value.splice(index, 1) // 取消选中
+  }
+}
+
+// 为行添加类名
+const tableRowClassName = ({ row }) => {
+  return selectedRows.value.some((item) => item.pid === row.pid)
+    ? "selected-row"
+    : ""
+}
+
+// 处理分页变化
+const handleSizeChange = (newSize) => {
+  pagination.value.pageSize = newSize
+  pagination.value.currentPage = 1
+  getProjectList()
+}
+
+const handleCurrentChange = () => {
+  getProjectList()
+}
+
+// 确认选中行
+const confirmSelected = () => {
+  if (selectedRows.value.length !== 1) {
+    ElMessage.warning(t("message.selectOneProject"))
+    return
+  }
+
+  const selected = selectedRows.value[0]
+  const project = {
+    projectId: selected.pid,
+    projectName: selected.name || `Project ${projectStore.projects.length + 1}`,
+    keywords: selected.keywords || "",
+    remark: selected.remark || "",
+    flow: selected.flow ? selected.flow : { nodes: [], edges: [] }
+  }
+
+  // 根据 identify 处理不同的确认逻辑
+  if (props.identify === "openProject") {
+    // HeaderButtonBar.vue 的逻辑:更新项目并跳转
+    if (projectStore.projects.some((p) => p.projectId === selected.pid)) {
+      projectStore.updateProjectInfo(selected.pid, project)
+    } else {
+      projectStore.addProject(project)
+    }
+    projectStore.setActiveProject(selected.pid)
+    router.push({ path: "/home" })
+  } else if (props.identify === "importProject") {
+    // 获取项目编码
+    const params = {
+      transCode: "ES0030",
+      pid: selected.pid,
+      npid: projectStore.pid
+    };
+    request(params)
+    .then((res) => {
+      console.log("res===>:", res);
+      
+      // 解析 cpjson 和 oldflow
+      const cpjson = JSON.parse(res.cpjson);
+      const oldflow = JSON.parse(res.oldflow);
+      
+      // 创建映射表,便于快速查找
+      const idMapping = {};
+      cpjson.forEach(mapping => {
+        idMapping[mapping.oldId] = mapping.newId;
+      });
+      
+      // 创建节点ID映射表(旧节点ID -> 新节点ID)
+      const nodeIdMapping = {};
+      
+      // 复制并处理节点数据
+      const processedNodes = oldflow.nodes.map(node => {
+        const newNode = { ...node };
+        
+        // 处理节点的 pcId
+        if (newNode.data && newNode.data.pcId && idMapping[newNode.data.pcId]) {
+          newNode.data.pcId = idMapping[newNode.data.pcId];
+        }
+        
+        // 生成新的节点ID并建立映射关系
+        const oldNodeId = newNode.id;
+        const timestamp = Date.now();
+        const randomStr = Math.random().toString(36).substr(2, 9);
+        
+        if (newNode.type === 'point-only') {
+          // point-only 节点保持类似的命名格式
+          newNode.id = `point-${timestamp}_${randomStr}`;
+        } else {
+          // 其他节点保持 componentId_timestamp_random 格式
+          const parts = newNode.id.split('_');
+          if (parts.length > 0) {
+            newNode.id = `${parts[0]}_${timestamp}_${randomStr}`;
+          } else {
+            newNode.id = `node_${timestamp}_${randomStr}`;
+          }
+        }
+        
+        // 保存节点ID映射关系
+        nodeIdMapping[oldNodeId] = newNode.id;
+        
+        return newNode;
+      });
+      
+      // 复制并处理边数据
+      const processedEdges = oldflow.edges.map(edge => {
+        const newEdge = { ...edge };
+        
+        // 处理边的 pcid
+        if (newEdge.data && newEdge.data.pcid && idMapping[newEdge.data.pcid]) {
+          newEdge.data.pcid = idMapping[newEdge.data.pcid];
+        }
+        
+        // 处理边的 frompcId
+        if (newEdge.data && newEdge.data.frompcId && idMapping[newEdge.data.frompcId]) {
+          newEdge.data.frompcId = idMapping[newEdge.data.frompcId];
+        }
+        
+        // 处理边的 topcId
+        if (newEdge.data && newEdge.data.topcId && idMapping[newEdge.data.topcId]) {
+          newEdge.data.topcId = idMapping[newEdge.data.topcId];
+        }
+        
+        // 更新边的源节点和目标节点ID
+        if (newEdge.source && nodeIdMapping[newEdge.source]) {
+          newEdge.source = nodeIdMapping[newEdge.source];
+        }
+        if (newEdge.target && nodeIdMapping[newEdge.target]) {
+          newEdge.target = nodeIdMapping[newEdge.target];
+        }
+        
+        // 更新边的ID
+        if (newEdge.id) {
+          newEdge.id = `imported_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
+        }
+        
+        // 更新边的 sourceHandle 和 targetHandle(如果需要)
+        // 这里可以根据需要处理连接点的映射
+        
+        return newEdge;
+      });
+      
+      // 创建处理后的 flow 数据
+      const processedFlow = {
+        nodes: processedNodes,
+        edges: processedEdges
+      };
+      
+      console.log("处理后的节点和边:", processedFlow);
+      
+      // 获取当前激活的项目
+      const currentProject = projectStore.getActiveProject();
+      if (currentProject) {
+        // 确保当前项目的 flow 数据结构正确
+        const currentNodes = Array.isArray(currentProject.flow?.nodes) ? currentProject.flow.nodes : [];
+        const currentEdges = Array.isArray(currentProject.flow?.edges) ? currentProject.flow.edges : [];
+        
+        // 合并到当前项目的 flow 中
+        const mergedFlow = {
+          nodes: [...currentNodes, ...processedFlow.nodes],
+          edges: [...currentEdges, ...processedFlow.edges]
+        };
+        
+        console.log("合并后的flow:", mergedFlow);
+        
+        // 更新当前项目的 flow 数据
+        projectStore.updateProjectInfo(currentProject.projectId, {
+          ...currentProject,
+          flow: mergedFlow
+        });
+        emitter.emit('refresh-flow', currentProject.projectId);
+        emit("refresh-flow", mergedFlow);
+        
+        ElMessage.success(t("message.importSuccess"));
+      } else {
+        ElMessage.error("无法获取当前项目信息");
+      }
+      
+    })
+    .catch((err) => {
+      console.error(err);
+      ElMessage.error(err.returnMsg);
+    });
+  }
+
+  // 触发确认事件,传递 project 和 identify
+  emit("confirm", { project: selected, identify: props.identify });
+  dialogVisible.value = false;
+  emit("update:visible", false);
+};
+
+// 取消
+const handleCancel = () => {
+  dialogVisible.value = false
+  emit("update:visible", false)
+}
+
+// 关闭对话框
+const handleClose = () => {
+  selectedRows.value = []
+  dialogVisible.value = false
+  emit("update:visible", false)
+}
+</script>
+
+<style scoped>
+.project-main-content.custom-table {
+  max-height: 540px;
+  overflow: auto;
+}
+
+.project-main-pagination {
+  display: flex;
+  justify-content: center;
+  margin-top: 10px;
+}
+
+.custom-pagination {
+  display: flex;
+  align-items: center;
+}
+
+.lastbtn {
+  display: flex;
+  justify-content: center;
+}
+
+:deep(.el-table .selected-row) {
+  background-color: #f3f8fb !important;
+  position: relative;
+}
+
+:deep(.el-table .selected-row td:first-child) {
+  border-left: 4px solid #12739e !important;
+}
+
+:deep(.el-table .selected-row td) {
+  border-top: 1px solid #12739e !important;
+  border-bottom: 1px solid #12739e !important;
+}
+
+:deep(.el-table .selected-row td:last-child) {
+  border-right: 1px solid #12739e !important;
+}
+</style>

+ 24 - 93
src/components/layout/HeaderButtonBar.vue

@@ -218,98 +218,12 @@
     </template>
     </template>
   </el-dialog>
   </el-dialog>
 
 
-  <el-dialog
-    v-model="dialog.projectListDialog"
-    align-center
-    :append-to-body="true"
-    width="800"
-    class="dialog_class"
-    draggable
-  >
-    <template #header="{ titleId, titleClass }">
-      <div class="my-header">
-        <h4 :id="titleId" :class="titleClass">
-          {{ $t("dialog.projectlist") }}
-        </h4>
-      </div>
-    </template>
-
-    <template #default>
-      <div class="project-main-content custom-table">
-        <el-table
-          :data="projectlists"
-          style="width: 100%; height: 540px; overflow: auto"
-          @row-click="handleRowClick"
-          :row-class-name="tableRowClassName"
-        >
-          <el-table-column
-            type="index"
-            :label="$t('project.number')"
-            width="100"
-          ></el-table-column>
-          <el-table-column
-            prop="name"
-            :label="$t('project.name')"
-          ></el-table-column>
-          <el-table-column
-            prop="dirsize"
-            :label="$t('project.dirsize')"
-          ></el-table-column>
-          <el-table-column
-            prop="updateTime"
-            :label="$t('project.updateTime')"
-            min-width="100"
-          ></el-table-column>
-          <el-table-column
-            prop="uname"
-            :label="$t('project.uname')"
-          ></el-table-column>
-          <el-table-column
-            prop="keywords"
-            :label="$t('project.keywords')"
-          ></el-table-column>
-          <el-table-column
-            prop="remark"
-            :label="$t('project.description')"
-          ></el-table-column>
-        </el-table>
-      </div>
-      <div class="project-main-pagination">
-        <div class="custom-pagination">
-          <el-pagination
-            v-model:current-page="gd.currentPage4"
-            v-model:page-size="gd.pageSize4"
-            :page-sizes="[5, 10, 20, 50]"
-            background
-            size="small"
-            layout="prev, slot, sizes, pager, next"
-            :total="parseInt(gd.total)"
-            class="mt-4"
-            @size-change="handleSizeChange"
-            @current-change="handleCurrentChange2"
-          >
-            <template #default>
-              <span>{{ $t("project.total") }} {{ gd.total }}</span>
-            </template>
-          </el-pagination>
-        </div>
-      </div>
-    </template>
-
-    <template #footer>
-      <span class="lastbtn">
-        <el-button @click="dialog.projectListDialog = false">{{
-          $t("dialog.cancel")
-        }}</el-button>
-        <el-button
-          @click="confirmSelected"
-          :disabled="selectedRows.length === 0"
-        >
-          {{ $t("dialog.ok") }}
-        </el-button>
-      </span>
-    </template>
-  </el-dialog>
+<ProjectListDialog
+      v-model:visible="dialog.projectListDialog"
+      identify="openProject"
+      @confirm="handleProjectConfirm"
+      @flow-imported="handleFlowImported"
+    />
 </template>
 </template>
 
 
 <script setup>
 <script setup>
@@ -322,6 +236,7 @@ import { removeToken, removeUserId } from "@/utils/token"
 import { ElMessageBox, ElMessage } from "element-plus"
 import { ElMessageBox, ElMessage } from "element-plus"
 import { ArrowDown, User, Key, SwitchButton } from "@element-plus/icons-vue"
 import { ArrowDown, User, Key, SwitchButton } from "@element-plus/icons-vue"
 import { useProjectStore } from "@/store/project"
 import { useProjectStore } from "@/store/project"
+import ProjectListDialog from "@/components/dialog/ProjectListDialog.vue";
 import exitIcon from "@/assets/icons/exit.png"
 import exitIcon from "@/assets/icons/exit.png"
 const userStore = useUserStore()
 const userStore = useUserStore()
 const router = useRouter()
 const router = useRouter()
@@ -378,7 +293,7 @@ const fakeTabs = [
     type: "",
     type: "",
     icon: openIcon,
     icon: openIcon,
     onClick: () => {
     onClick: () => {
-      handleOpenProject()
+      dialog.value.projectListDialog = true;
     }
     }
   },
   },
   {
   {
@@ -632,6 +547,22 @@ const confirmSelected = () => {
   router.push({ path: "/home" })
   router.push({ path: "/home" })
   dialog.value.projectListDialog = false
   dialog.value.projectListDialog = false
 }
 }
+
+// 处理项目选择确认
+const handleProjectConfirm = (project) => {
+  router.push({ path: "/home" });
+  dialog.value.projectListDialog = false;
+};
+
+const handleFlowImported = (newFlow) => {
+  // 这里可以调用 VueFlow 的刷新方法
+  // 或者直接重新加载当前项目
+  // 例如:重新调用 flowInit() 或其他刷新方法
+  console.log('Flow imported, need to refresh VueFlow', newFlow);
+  
+  // 如果你有访问 vueflow.vue 组件的方法,可以调用它的刷新方法
+  // 例如:vueFlowRef.value.flowInit();
+}
 </script>
 </script>
 
 
 <style scoped>
 <style scoped>

+ 22 - 5
src/components/layout/home.vue

@@ -105,11 +105,13 @@
       </span>
       </span>
     </template>
     </template>
   </el-dialog>
   </el-dialog>
-  <FileUploadDialog
-      v-model:visible="showFileSelector"
-      accept-types=".json"
-      @file-selected="handleFileSelected"
+  <ProjectListDialog
+    v-model:visible="dialog.projectListDialog"
+    identify="importProject"
+    @confirm="handleProjectConfirm"
+    @refresh-flow="handleRefreshFlow"
   />
   />
+
 </template>
 </template>
 
 
 <script setup>
 <script setup>
@@ -125,6 +127,7 @@ import { ref, computed, watch, onMounted, nextTick } from 'vue';
 import { request } from "@/utils/request"
 import { request } from "@/utils/request"
 import emitter from "@/utils/emitter"
 import emitter from "@/utils/emitter"
 import FileUploadDialog from '@/components/dialog/FileUploadDialog.vue';
 import FileUploadDialog from '@/components/dialog/FileUploadDialog.vue';
+import ProjectListDialog from "@/components/dialog/ProjectListDialog.vue";
 const { t, locale } = useI18n()
 const { t, locale } = useI18n()
 const router = useRouter();
 const router = useRouter();
 const route = useRoute();
 const route = useRoute();
@@ -143,12 +146,20 @@ const projectForm = ref(null);
 // 控制 FileSelector 对话框显示
 // 控制 FileSelector 对话框显示
 const showFileSelector = ref(false);
 const showFileSelector = ref(false);
 
 
+// 添加当前操作标识
+const currentIdentify = ref("openProject")
+
 let newproject = ref({
 let newproject = ref({
   name: "",
   name: "",
   description: "",
   description: "",
   keywords: ""
   keywords: ""
 })
 })
 
 
+// 弹窗开关
+let dialog = ref({
+  projectListDialog: false
+})
+
 const labelWidth = computed(() => {
 const labelWidth = computed(() => {
   return locale.value === "zh-CN" ? "80px" : "120px"
   return locale.value === "zh-CN" ? "80px" : "120px"
 })
 })
@@ -239,7 +250,7 @@ const handleButtonClick = (action) => {
         emitter.emit('save');
         emitter.emit('save');
         break;
         break;
       case 'importProject':
       case 'importProject':
-        showFileSelector.value = true;
+        dialog.value.projectListDialog = true;
         // const newProject = {
         // const newProject = {
         //   projectId: `proj-${Date.now()}`,
         //   projectId: `proj-${Date.now()}`,
         //   projectName: `Project ${projectStore.projects.length + 1}`,
         //   projectName: `Project ${projectStore.projects.length + 1}`,
@@ -254,6 +265,12 @@ const handleButtonClick = (action) => {
     }
     }
 };
 };
 
 
+// 处理项目选择确认
+const handleProjectConfirm = (project) => {
+  router.push({ path: "/home" });
+  dialog.value.projectListDialog = false;
+};
+
 const handleProjectTabClick = (tab) => {
 const handleProjectTabClick = (tab) => {
   projectStore.setActiveProject(tab.props.name);
   projectStore.setActiveProject(tab.props.name);
 };
 };

+ 16 - 62
src/views/model/index.vue

@@ -93,68 +93,6 @@
               @mode-switch="handleTopoModeSwitch"
               @mode-switch="handleTopoModeSwitch"
               />
               />
             </div>
             </div>
-            <!-- <div class="main-header">
-              <div class="topologyStyle">Topology</div> -->
-              <!-- <el-tabs type="border-card">
-                <el-tab-pane label="Topology">
-                  <el-space :size="spacesize" class="spaceclass">
-                    <el-button
-                      title="放大"
-                      @click="zoomIn"
-                      class="custom-icon-button"
-                    >
-                      <el-icon><ZoomIn /></el-icon>
-                    </el-button>
-                    <el-button
-                      title="缩小"
-                      @click="zoomOut"
-                      class="custom-icon-button"
-                    >
-                      <el-icon><ZoomOut /></el-icon>
-                    </el-button>
-                    <el-button
-                      title="自适应"
-                      @click="fitView"
-                      class="custom-icon-button"
-                    >
-                      <el-icon><FullScreen /></el-icon>
-                    </el-button>
-
-                    <el-tooltip content="重置" placement="top">
-                      <el-button
-                        title="重置"
-                        @click="resetTransform"
-                        class="custom-icon-button"
-                      >
-                        <changebak name="reset" />
-                      </el-button>
-                    </el-tooltip>
-
-                    <el-tooltip content="背景切换" placement="top">
-                      <el-button
-                        title="背景切换"
-                        @click="toggleDarkMode"
-                        class="custom-icon-button"
-                      >
-                        <changebak v-if="dark" name="sun" />
-                        <changebak v-else name="moon" />
-                      </el-button>
-                    </el-tooltip>
-
-                    <el-tooltip content="保存" placement="top">
-                      <el-button
-                        @click="saveproject"
-                        class="custom-icon-button"
-                      >
-                        <el-icon class="btn-icon" :color="iconcolor"
-                          ><UploadFilled
-                        /></el-icon>
-                      </el-button>
-                    </el-tooltip>
-                  </el-space>
-                </el-tab-pane>
-              </el-tabs> -->
-            <!-- </div> -->
             <div class="flow-content" ref="flowContentRef">
             <div class="flow-content" ref="flowContentRef">
               <vueflow 
               <vueflow 
                 ref="vueflowRef" 
                 ref="vueflowRef" 
@@ -166,6 +104,7 @@
                 @copy="handleCopy"
                 @copy="handleCopy"
                 @cut="handleCut"
                 @cut="handleCut"
                 @paste="handlePaste"
                 @paste="handlePaste"
+                @refresh-flow="handleRefreshFlow"
               />
               />
               <Ruler :visible="buttonStates.showRuler" :container-ref="flowContentRef" />
               <Ruler :visible="buttonStates.showRuler" :container-ref="flowContentRef" />
             </div>
             </div>
@@ -308,6 +247,14 @@ const handlePersonUnitConfirm = () => {
   showPersonUnitDialog.value = false
   showPersonUnitDialog.value = false
 }
 }
 
 
+// 处理 VueFlow 组件的刷新事件
+const handleRefreshFlow = () => {
+  if (vueflowRef.value) {
+    // 调用 vueflow 子组件的 flowInit 方法
+    vueflowRef.value.flowInit();
+  }
+};
+
 const getImgPath = (url) => {
 const getImgPath = (url) => {
   return new URL(`../../assets/img/${url}`, import.meta.url).href
   return new URL(`../../assets/img/${url}`, import.meta.url).href
 }
 }
@@ -724,6 +671,7 @@ const getlogs = () => {
     })
     })
 }
 }
 
 
+
 onMounted(() => {
 onMounted(() => {
   runtype.value = projectStore.runtype || ""
   runtype.value = projectStore.runtype || ""
 
 
@@ -738,6 +686,11 @@ onMounted(() => {
   emitter.on('copy', handleCopy);
   emitter.on('copy', handleCopy);
   emitter.on('cut', handleCut);
   emitter.on('cut', handleCut);
   emitter.on('paste', handlePaste);
   emitter.on('paste', handlePaste);
+  emitter.on('refresh-flow', () => {
+    if (vueflowRef.value) {
+      vueflowRef.value.flowInit();
+    }
+  });
 });
 });
 
 
 onUnmounted(() => {
 onUnmounted(() => {
@@ -745,6 +698,7 @@ onUnmounted(() => {
   emitter.off('copy', handleCopy);
   emitter.off('copy', handleCopy);
   emitter.off('cut', handleCut);
   emitter.off('cut', handleCut);
   emitter.off('paste', handlePaste);
   emitter.off('paste', handlePaste);
+  emitter.off('refresh-flow');
 });
 });
 
 
 // onBeforeUnmount(() => {
 // onBeforeUnmount(() => {

+ 17 - 5
src/views/model/vueflow/index.vue

@@ -887,10 +887,20 @@ const flowInit = async () => {
     }
     }
 
 
     let flow
     let flow
-    try {
-      flow = JSON.parse(activeProject.flow || '{"nodes":[], "edges":[] }')
-    } catch (error) {
-      console.error('解析 flow 数据失败:', activeProject.flow, error)
+    // 检查 activeProject.flow 的类型
+    if (typeof activeProject.flow === 'string') {
+      // 如果是字符串,尝试解析
+      try {
+        flow = JSON.parse(activeProject.flow || '{"nodes":[], "edges":[] }')
+      } catch (error) {
+        console.error('解析 flow 数据失败:', activeProject.flow, error)
+        flow = { nodes: [], edges: [] }
+      }
+    } else if (typeof activeProject.flow === 'object' && activeProject.flow !== null) {
+      // 如果已经是对象,直接使用
+      flow = activeProject.flow
+    } else {
+      // 其他情况使用默认值
       flow = { nodes: [], edges: [] }
       flow = { nodes: [], edges: [] }
     }
     }
 
 
@@ -964,7 +974,9 @@ defineExpose({
     .finally(() => {
     .finally(() => {
       isCutting.value = false;
       isCutting.value = false;
     }),
     }),
-  pasteNodes: () => pasteNodes(pid.value, nodes.value, edges.value, saveproject, createEdge, addNodes, addEdges, updateNodeInternals)
+  pasteNodes: () => pasteNodes(pid.value, nodes.value, edges.value, saveproject, createEdge, addNodes, addEdges, updateNodeInternals),
+  flowInit,
+  refreshFlow: flowInit
 })
 })
 </script>
 </script>