tangjunhao 2 сар өмнө
parent
commit
6f60ace62c

+ 2 - 1
.env.development

@@ -1,4 +1,5 @@
 NODE_ENV = development
 VITE_NAME= '开发环境'
 VITE_BASE_URL = '/api'
-VITE_WEBSOCKET_URL='ws://192.168.0.131/epswebsocket?projectId='
+# VITE_WEBSOCKET_URL='ws://192.168.0.131/epswebsocket?projectId='
+VITE_WEBSOCKET_URL='wss://www.adicn.com/epswebsocket?projectId='

+ 11 - 1
src/style/index.css

@@ -294,4 +294,14 @@ img{
 }
 .el-table td.el-table__cell div .el-button{
   width: 100%;
-}
+}
+
+/* 时间线左对齐 */
+.el-timeline .el-timeline-item__center .el-timeline-item__wrapper{
+  text-align: left;
+}
+
+/* 文本区域 */
+.el-textarea__inner {
+  height: 100%;
+}

+ 207 - 0
src/views/model/dialog/SLDataDialog.vue

@@ -0,0 +1,207 @@
+<template>
+  <!-- 模拟数据弹窗 -->
+  <el-dialog
+    v-model="visible"
+    align-center
+    :append-to-body="true"
+    width="500"
+    class="dialog_class"
+    draggable
+  >
+    <template #header="{ titleId, titleClass }">
+      <div class="my-header">
+        <h4 :id="titleId" :class="titleClass">模拟数据</h4>
+      </div>
+    </template>
+
+    <el-table :data="tableSLData" border height="300" style="width: 100%">
+      <el-table-column type="index" width="40" label="" />
+      <el-table-column prop="name" label="属性"> </el-table-column>
+      <el-table-column prop="value" label="值">
+        <template #default="{ row }">
+          <el-select
+            v-if="row.valueType === 1"
+            v-model="row.value"
+            placeholder="请选择"
+            class="full-width-select"
+          >
+            <el-option
+              v-for="option in row.options"
+              :key="option.val"
+              :label="option.tag"
+              :value="option.val"
+            />
+          </el-select>
+          <el-input
+            v-else-if="row.valueType === 2"
+            v-model="row.value"
+            class="full-width-input"
+          />
+          <el-button v-else-if="row.valueType === 3" />
+          <div v-else>{{ row.valueDef }}</div>
+        </template>
+      </el-table-column>
+      <el-table-column prop="unit" label="单位" width="100">
+        <template #default="{ row }">
+          <el-select
+            v-if="row.unitType !== '无'"
+            v-model="row.unit"
+            placeholder="请选择"
+            class="full-width-select"
+          >
+            <el-option
+              v-for="option in row.unitoptions"
+              :key="option.val"
+              :label="option.tag"
+              :value="option.val"
+            />
+          </el-select>
+          <div v-else>{{ row.unitType }}</div>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <template #footer>
+      <span class="lastbtn">
+        <el-button @click="closeDialog">{{ $t('dialog.cancel') }}</el-button>
+        <el-button type="primary" @click="saveSLTabelDialog">
+          {{ $t('dialog.ok') }}
+        </el-button>
+      </span>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup>
+import { request, getImage } from "@/utils/request"
+import { ElMessage } from "element-plus"
+const visible = ref(false)
+const tableSLData = ref([])
+
+const runtype = ref('') // 用于分开不同求解运行
+
+let emit = defineEmits(['selectRunType']);
+
+function openDialog(pid) {
+  getSLData(pid);
+  visible.value = true
+}
+
+function closeDialog() {
+  visible.value = false
+}
+
+const getSLData = async ( pid ) => {
+  const params = {
+    transCode: "ES0012",
+    pid: pid
+  }
+
+  try {
+    const res = await request(params) // 使用 await 等待请求完成
+    tableSLData.value = res.rows.map((item) => ({
+      ...item,
+      isVisible: true // 默认所有行都可见
+    }))
+    console.log("tableSLData.value", tableSLData.value)
+
+    // 使用 for...of 循环以便使用 await
+    for (const item of tableSLData.value) {
+      if (!item.pcaId) continue
+
+      // 并行处理 value 和 unit 的初始化
+      const promises = []
+
+      if (item.valueType === 1) {
+        promises.push(getlistopt(item, "value"))
+      }
+
+      if (item.unitType !== "无") {
+        promises.push(getlistopt(item, "unit"))
+      } else {
+        item.unit = "无"
+      }
+
+      // 等待当前 item 的所有初始化完成
+      await Promise.all(promises)
+    }
+
+    console.log("所有数据初始化完成")
+  } catch (err) {
+    console.error("初始化失败:", err)
+    ElMessage.error("初始化失败")
+  }
+}
+
+const saveSLTabelDialog = () => {
+  console.log("tableSLData:", tableSLData.value)
+  runtype.value = tableSLData.value.find((item)=>item.code === "SimulationType")?.value || '';
+  emit('selectRunType',runtype.value);
+  // dataType 为 -1 的数据
+  const validItems = tableSLData.value.filter((item) => item.dataType === -1)
+  const pcavals = validItems
+    .map((item) => {
+      const pcaId = item.pcaId ?? ""
+      const value = item.value ?? ""
+      const unit = item.unit ?? ""
+      return `${pcaId},${value},${unit}`
+    })
+    .join(";")
+  const params = {
+    transCode: "ES0008",
+    pcavals: pcavals
+  }
+  request(params)
+    .then((res) => {
+      ElMessage.success("保存成功")
+      // 执行保存逻辑
+      closeDialog()
+    })
+    .catch((err) => {
+      console.error("err", err)
+      ElMessage.error("保存失败")
+    })
+}
+
+const getlistopt = async (item, gettype) => {
+  let params = {}
+  if (gettype === "value") {
+    params = {
+      transCode: "BES001",
+      type: item.valueDef
+    }
+  } else {
+    params = {
+      transCode: "BES001",
+      type: item.unitType
+    }
+  }
+
+  try {
+    const res = await request(params)
+    // console.log("选项获取成功", res)
+
+    if (gettype === "value") {
+      if (
+        item.value === undefined ||
+        item.value === null ||
+        item.value === ""
+      ) {
+        item.value = res.rows?.[0]?.val ?? ""
+      }
+      item.options = res.rows || []
+    } else if (gettype === "unit") {
+      if (item.unit === undefined || item.unit === null || item.unit === "") {
+        item.unit = res.rows?.[0]?.val ?? ""
+      }
+      item.unitoptions = res.rows || []
+    }
+  } catch (err) {
+    console.error("err", err)
+    ElMessage.error("选项初始化失败")
+  }
+}
+
+// 暴露给父组件
+defineExpose({ openDialog, closeDialog })
+</script>

+ 349 - 261
src/views/model/index.vue

@@ -19,8 +19,13 @@
     </el-header>
     <splitpanes class="default-theme diysplitpanes">
       <pane min-size="10" size="20" max-size="50" class="custom-aside">
-        <el-tabs type="border-card" class="full-height-tabs">
-          <el-tab-pane label="Liberal">
+        <el-tabs
+          v-model="activeName"
+          type="border-card"
+          class="full-height-tabs"
+          @tab-click="handleTabclick"
+        >
+          <el-tab-pane label="Liberal" name="Liberal">
             <el-collapse v-model="colactiveNames">
               <el-collapse-item
                 v-for="item in collapseData"
@@ -48,180 +53,147 @@
             </el-collapse>
           </el-tab-pane>
 
-          <el-tab-pane label="Project"></el-tab-pane>
-          <el-tab-pane label="Result"></el-tab-pane>
+          <el-tab-pane label="Project" name="Project"></el-tab-pane>
+          <el-tab-pane label="Result" name="Result">
+            <el-empty v-if="activities.length === 0" description="暂无数据" />
+            <el-timeline v-else>
+              <el-timeline-item
+                v-for="(activity, index) in activities"
+                center
+                :key="index"
+                :color="selectedIndex === index ? '#67C23A' : ''"
+                size="normal"
+                :timestamp="activity.timestamp"
+                @click="handleTimelineItemClick(index)"
+              >
+                {{ activity.content }}
+              </el-timeline-item>
+            </el-timeline>
+          </el-tab-pane>
         </el-tabs>
       </pane>
       <pane class="custom-main">
-        <div class="main-header">
-          <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-tooltip content="删除节点" placement="top">
-                  <el-button @click="removeNode" class="custom-icon-button">
-                    <el-icon class="btn-icon" :color="iconcolor"
-                      ><DocumentDelete
-                    /></el-icon>
-                  </el-button>
-                </el-tooltip>
-
-                <el-tooltip content="删除线" placement="top">
-                  <el-button @click="removeEdge" class="custom-icon-button">
-                    <el-icon class="btn-icon" :color="iconcolor"
-                      ><Crop
-                    /></el-icon>
-                  </el-button>
-                </el-tooltip>
-
-                <el-tooltip content="清空全部" placement="top">
-                  <el-button @click="confirmDelete" class="custom-icon-button">
-                    <el-icon class="btn-icon" :color="iconcolor"
-                      ><DeleteFilled
-                    /></el-icon>
-                  </el-button>
-                </el-tooltip>
-              </el-space>
-            </el-tab-pane>
-          </el-tabs>
-        </div>
-        <div class="flow-content">
-          <vueflow ref="vueflowref" />
-        </div>
+        <splitpanes class="default-theme" horizontal>
+          <pane min-size="50" size="75" max-size="100" class="flow-pane">
+            <div class="main-header">
+              <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-tooltip content="删除节点" placement="top">
+                      <el-button @click="removeNode" class="custom-icon-button">
+                        <el-icon class="btn-icon" :color="iconcolor"
+                          ><DocumentDelete
+                        /></el-icon>
+                      </el-button>
+                    </el-tooltip>
+
+                    <el-tooltip content="删除线" placement="top">
+                      <el-button @click="removeEdge" class="custom-icon-button">
+                        <el-icon class="btn-icon" :color="iconcolor"
+                          ><Crop
+                        /></el-icon>
+                      </el-button>
+                    </el-tooltip>
+
+                    <el-tooltip content="清空全部" placement="top">
+                      <el-button
+                        @click="confirmDelete"
+                        class="custom-icon-button"
+                      >
+                        <el-icon class="btn-icon" :color="iconcolor"
+                          ><DeleteFilled
+                        /></el-icon>
+                      </el-button>
+                    </el-tooltip>
+                  </el-space>
+                </el-tab-pane>
+              </el-tabs>
+            </div>
+            <div class="flow-content">
+              <vueflow ref="vueflowref" :jobId="jobId" />
+            </div>
+          </pane>
+          <pane min-size="0" size="25" max-size="50">
+            <el-tabs type="border-card" style="height: 100%">
+              <el-tab-pane label="日志" style="height: 100%">
+                <el-input
+                  v-model="logContent"
+                  type="textarea"
+                  id="textarea_id"
+                  spellcheck="false"
+                  style="height: 100%;font-size: 12px;"
+                  :autosize="false"
+                  resize="none"
+                />
+              </el-tab-pane>
+            </el-tabs>
+          </pane>
+        </splitpanes>
       </pane>
     </splitpanes>
 
-    <el-dialog
-      v-model="SLdatadialog"
-      align-center
-      :append-to-body="true"
-      width="500"
-      class="dialog_class"
-      draggable
-    >
-      <template #header="{ titleId, titleClass }">
-        <div class="my-header">
-          <!-- <el-image :src="icon" fit="contain"></el-image> -->
-          <h4 :id="titleId" :class="titleClass">模拟数据</h4>
-        </div>
-      </template>
-      <el-table :data="tableSLData" border height="300" style="width: 100%">
-        <el-table-column type="index" width="40" label="" />
-        <el-table-column prop="name" label="属性"> </el-table-column>
-        <el-table-column prop="value" label="值">
-          <template #default="{ row }">
-            <el-select
-              v-if="row.valueType === 1"
-              v-model="row.value"
-              placeholder="请选择"
-              class="full-width-select"
-            >
-              <el-option
-                v-for="option in row.options"
-                :key="option.val"
-                :label="option.tag"
-                :value="option.val"
-              >
-              </el-option>
-            </el-select>
-            <el-input
-              v-else-if="row.valueType === 2"
-              v-model="row.value"
-              class="full-width-input"
-            />
-            <el-button
-              v-else-if="row.valueType === 3"
-            ></el-button>
-            <div v-else>{{ row.valueDef }}</div>
-          </template>
-        </el-table-column>
-        <el-table-column prop="unit" label="单位" width="100">
-          <template #default="{ row }">
-            <el-select
-              v-if="row.unitType !== '无'"
-              v-model="row.unit"
-              placeholder="请选择"
-              class="full-width-select"
-            >
-              <el-option
-                v-for="option in row.unitoptions"
-                :key="option.val"
-                :label="option.tag"
-                :value="option.val"
-              >
-              </el-option>
-            </el-select>
-            <div v-else>{{ row.unitType }}</div>
-          </template>
-        </el-table-column>
-      </el-table>
-
-      <template #footer>
-        <span class="lastbtn">
-          <el-button @click="SLdatadialog = false">{{
-            $t("dialog.cancel")
-          }}</el-button>
-          <el-button type="primary" @click="saveSLTabelDialog">
-            {{ $t("dialog.ok") }}
-          </el-button>
-        </span>
-      </template>
-    </el-dialog>
+    <!-- 模拟数据弹窗 -->
+    <SLDataDialog ref="SLdatadialogref" @selectRunType="handleSelectRunType" />
+
   </el-container>
 </template>
 
@@ -249,6 +221,7 @@ import {
   Close
 } from "@element-plus/icons-vue"
 import changebak from "./vueflow/changebak.vue"
+import SLDataDialog from "./dialog/SLDataDialog.vue"
 import { useVueFlow } from "@vue-flow/core"
 
 const { zoomIn, zoomOut, fitView } = useVueFlow()
@@ -272,8 +245,14 @@ const vueflowref = ref()
 const dark = ref(false)
 let iconcolor = ref("#000")
 
-const SLdatadialog = ref(false)
-const tableSLData = ref([])
+const activeName = ref("Liberal")
+
+const selectedIndex = ref(null)
+const activities = ref([])
+
+const jobId = ref()
+
+const SLdatadialogref = ref(null)
 
 const headerbuttons = ref([
   { type: "button", img: "newproject.png", name: "temp" },
@@ -315,6 +294,20 @@ let mainbuttons = ref([
   { img: "temp.png" }
 ])
 
+const logContent = ref("")
+const arrobj = ref([])
+let websock = ref(null);
+let times = ref({
+  lockReconnect: false, //是否真正建立连接
+  timeout: 28 * 1000, //30秒一次心跳
+  timeoutObj: null, //心跳倒计时
+  serverTimeoutObj: null, //
+  timeoutnum: null, //断开重连倒计时
+})
+
+// 用于分开不同求解运行
+const runtype = ref('');
+
 const getComponent = () => {
   const params = {
     transCode: "ES1001",
@@ -367,10 +360,27 @@ const confirmDelete = () => {
 
 const btnfunc = (name) => {
   if (name === "run") {
+    if(runtype.value === '') {
+      ElMessage.error("请先设置模拟类型")
+      return
+    }else if(runtype.value === 'Incompressible Transient'){
+      // 处理不可压缩瞬态的逻辑
+
+    }else if(runtype.value === 'Incompressible Steady State'){
+      // 处理不可压缩稳态的逻辑
+    }else if(runtype.value === 'Compressible Transient'){
+      // 处理可压缩瞬态的逻辑
+    }else if(runtype.value === 'Compressible Steady State'){
+      // 处理可压缩稳态的逻辑
+    }else{
+      ElMessage.error("未知的模拟类型")
+      return
+    }
     runProject()
-  }else if(name === "SLdata"){
-    getSLData();
-    SLdatadialog.value = true;
+  } else if (name === "SLdata") {
+    nextTick(() => {
+      SLdatadialogref.value?.openDialog?.(pid.value)
+    })
   }
 }
 
@@ -388,122 +398,200 @@ const runProject = () => {
     })
 }
 
-const getSLData =  async () => {
+const handleSelectRunType = (type) => {
+  runtype.value = type
+}
+
+// 获取结果列表
+const getresultlist = () => {
   const params = {
-    transCode: "ES0012",
+    transCode: "ES0014",
     pid: pid.value
   }
+  request(params)
+    .then((res) => {
+      console.log("获取结果列表成功", res)
+      // 处理结果列表数据
+      activities.value = res.rows.map((item) => ({
+        jobId: item.jobId,
+        content: item.ser,
+        timestamp: item.startTime
+      }))
+    })
+    .catch((err) => {
+      console.error("获取结果列表失败", err)
+      ElMessage.error("获取结果列表失败")
+    })
+}
+
+const handleTabclick = (tab, event) => {
+  console.log("tab clicked:", tab.props.name)
+  if (tab.props.name === "Result") {
+    getresultlist()
+  }
+}
 
-  try {
-    const res = await request(params) // 使用 await 等待请求完成
-    tableSLData.value = res.rows.map((item) => ({
-      ...item,
-      isVisible: true // 默认所有行都可见
-    }))
-    console.log("tableSLData.value", tableSLData.value)
-
-    // 使用 for...of 循环以便使用 await
-    for (const item of tableSLData.value) {
-      if (!item.pcaId) continue
-
-      // 并行处理 value 和 unit 的初始化
-      const promises = []
-
-      if (item.valueType === 1) {
-        promises.push(getlistopt(item, "value"))
-      }
-
-      if (item.unitType !== "无") {
-        promises.push(getlistopt(item, "unit"))
-      } else {
-        item.unit = "无"
-      }
-
-      // 等待当前 item 的所有初始化完成
-      await Promise.all(promises)
-    }
+const handleTimelineItemClick = (index) => {
+  selectedIndex.value = index
+  console.log("Timeline item clicked:", activities.value[index])
+  jobId.value = activities.value[index].jobId
+  vueflowref.value?.asideDataref?.getresultData(jobId.value)
+}
 
-    console.log("所有数据初始化完成")
 
-  } catch (err) {
-    console.error("初始化失败:", err)
-    ElMessage.error("初始化失败")
-  }
+//websockct的连接
+function initWebSocket() {
+  //初始化weosocket
+  const wsurl = import.meta.env.VITE_WEBSOCKET_URL + pid.value
+  websock = new WebSocket(wsurl)
+  websock.onopen = websocketonopen
+  websock.onmessage = websocketonmessage
+  websock.onerror = websocketonerror
+  websock.onclose = websocketclose
 }
 
-const getlistopt = async (item, gettype) => {
-  let params = {}
-  if (gettype === "value") {
-    params = {
-      transCode: "BES001",
-      type: item.valueDef
-    }
+// Websoket连接成功事件
+const websocketonopen = (res) => {
+  console.log("WebSocket连接成功", res)
+  start()
+}
+
+// Websoket接收消息事件
+const websocketonmessage = (res) => {
+  console.log('websocket接受消息:',res.data)
+  arrobj.value = []
+  if (res.data.indexOf("{") !== -1) {
+    // console.log("websocket接受消息:", res.data)
+    // 解析 WebSocket 接收到的消息数据
+    
+
+    // 新增:记录结构化 JSON 数据到日志(仅打印 step, vars, vals)
+    const lines = res.data.split("\n")
+    const filteredLogs = lines
+      .filter((line) => line.trim() !== "")
+      .map((line) => {
+        try {
+          const json = JSON.parse(line)
+          const { step, vars, vals } = json
+          return JSON.stringify({ step, vars, vals })
+        } catch (e) {
+          // 如果不是合法 JSON,就原样返回
+          return line
+        }
+      })
+      .join("\n")
+
+    logContent.value = logContent.value + "\n" + filteredLogs
+
+    // 自动滚动日志到底部
+    let textarea = document.getElementById("textarea_id")
+    textarea.scrollTop = textarea.scrollHeight
   } else {
-    params = {
-      transCode: "BES001",
-      type: item.unitType
+    if (res.data.indexOf("——成功") !== -1) {
+      const timer = setTimeout(function () {
+        console.log("关闭定时器")
+      }, 10000)
+    }
+    if (res.data.indexOf("msg=heartChec") == -1) {
+      // 去除空行
+      const cleanedLog = res.data
+        .split("\n")
+        .filter((line) => line.trim() !== "")
+        .join("\n")
+      logContent.value = logContent.value + "\n" + cleanedLog
+      let textarea = document.getElementById("textarea_id")
+      textarea.scrollTop = textarea.scrollHeight
     }
   }
 
-  try {
-    const res = await request(params)
-    // console.log("选项获取成功", res)
-
-    if (gettype === "value") {
-      if (
-        item.value === undefined ||
-        item.value === null ||
-        item.value === ""
-      ) {
-        item.value = res.rows?.[0]?.val ?? ""
-      }
-      item.options = res.rows || []
-    } else if (gettype === "unit") {
-      if (item.unit === undefined || item.unit === null || item.unit === "") {
-        item.unit = res.rows?.[0]?.val ?? ""
-      }
-      item.unitoptions = res.rows || []
+  reset()
+}
+
+// Websoket连接错误事件
+const websocketonerror = (res) => {
+  console.log("连接错误", res);
+  websock.close();
+  reconnect();
+};
+// Websoket断开事件
+const websocketclose = (res) => {
+  console.log("断开连接", res);
+};
+
+// 心跳包
+const reconnect = () => {
+  if (times.value.lockReconnect) return;
+  times.value.lockReconnect = true;
+  //没连接上会一直重连,设置延迟避免请求过多
+  times.value.timeoutnum && clearTimeout(times.value.timeoutnum);
+  times.value.timeoutnum = setTimeout(function () {
+    //新连接
+    initWebSocket();
+    times.value.lockReconnect = false;
+  }, 10000);
+}
+const reset = () => {
+  //重置心跳
+  clearTimeout(times.value.timeoutObj);
+  clearTimeout(times.value.serverTimeoutObj);
+  start();
+}
+const start = () => {
+  //开启心跳
+  times.value.timeoutObj && clearTimeout(times.value.timeoutObj);
+  times.value.serverTimeoutObj && clearTimeout(times.value.serverTimeoutObj);
+  times.value.timeoutObj = setTimeout(function () {
+    //这里发送一个心跳,后端收到后,返回一个心跳消息
+    if (websock.readyState == 1) {
+      //如果连接正常
+      websock.send("heartCheck");
+    } else {
+      //否则重连
+      reconnect();
     }
-  } catch (err) {
-    console.error("err", err)
-    ElMessage.error("选项初始化失败")
-  }
+    times.value.serverTimeoutObj = setTimeout(function () {
+      // 超时关闭
+      websock.close(); //如果onclose会执行reconnect,我们执行ws.close()就行了.如果直接执行reconnect 会触发onclose导致重连两次
+    }, times.value.timeout);
+  }, times.value.timeout);
 }
 
-const saveSLTabelDialog = () => {
-  SLdatadialog.value = false
-  console.log("tableSLData:", tableSLData.value)
-  // dataType 为 -1 的数据
-  const validItems = tableSLData.value.filter((item) => item.dataType === -1)
-  const pcavals = validItems
-    .map((item) => {
-      const pcaId = item.pcaId ?? ""
-      const value = item.value ?? ""
-      const unit = item.unit ?? ""
-      return `${pcaId},${value},${unit}`
-    })
-    .join(";")
+const getlogs = () => {
   const params = {
-    transCode: "ES0008",
-    pcavals: pcavals
+    transCode: "ES0017",
+    pid: pid.value
   }
   request(params)
     .then((res) => {
-      ElMessage.success("保存成功")
+      // console.log("获取日志成功", res)
+      logContent.value = res.logs.split('\n').filter(line => line.trim() !== '').join('\n');
+      // 自动滚动日志到底部
+      let textarea = document.getElementById("textarea_id")
+      textarea.scrollTop = textarea.scrollHeight
     })
     .catch((err) => {
-      console.error("err", err)
-      ElMessage.error("保存失败")
+      console.error("获取日志失败", err)
+      ElMessage.error("获取日志失败")
     })
 }
 
-
 onMounted(() => {
   getComponent()
+
+  setTimeout(function () {
+    initWebSocket();
+    getlogs();
+  }, 1500);
 })
 </script>
 
 <style scoped>
+.flow-pane {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+}
+
 .main-header {
   background: #eeeeee;
   border-radius: 0px 0px 0px 0px;

+ 45 - 44
src/views/model/vueflow/aside/asideData.vue

@@ -75,51 +75,13 @@
                 <el-table-column prop="name" label="属性"> </el-table-column>
                 <el-table-column prop="value" label="值">
                   <template #default="{ row }">
-                    <el-select
-                      v-if="row.valueType === 1"
-                      v-model="row.value"
-                      placeholder="请选择"
-                      class="full-width-select"
-                    >
-                      <el-option
-                        v-for="option in row.options"
-                        :key="option.val"
-                        :label="option.tag"
-                        :value="option.val"
-                      >
-                      </el-option>
-                    </el-select>
-                    <el-input
-                      v-else-if="row.valueType === 2"
-                      v-model="row.value"
-                      class="full-width-input"
-                    />
                     <el-button
-                      v-else-if="row.valueType === 3"
-                      :loading="loadingStates[row.pcaId] || false"
+                      v-if="row.coms.length > 1"
                     ></el-button>
-                    <div v-else>{{ row.valueDef }}</div>
-                  </template>
-                </el-table-column>
-                <el-table-column prop="unit" label="单位" width="100">
-                  <template #default="{ row }">
-                    <el-select
-                      v-if="row.unitType !== '无'"
-                      v-model="row.unit"
-                      placeholder="请选择"
-                      class="full-width-select"
-                    >
-                      <el-option
-                        v-for="option in row.unitoptions"
-                        :key="option.val"
-                        :label="option.tag"
-                        :value="option.val"
-                      >
-                      </el-option>
-                    </el-select>
-                    <div v-else>{{ row.unitType }}</div>
+                    <div v-else>{{ row.value }}</div>
                   </template>
                 </el-table-column>
+                <el-table-column prop="unit" label="单位"> </el-table-column>
               </el-table>
             </Pane>
           </Splitpanes>
@@ -311,6 +273,7 @@ const loadingStates = ref({})
 // data表格按钮点击弹窗表格
 const tableColumns = ref([])
 const tableData = ref([])
+let pcId = ref("") // 项目组件id
 let pcaId = ref("") //项目组件的属性id
 
 const tableKVData = ref([])
@@ -382,10 +345,11 @@ const handleShapeChange = (row, val) => {
   }
 }
 
-const getcomdata = async (pcId) => {
+const getcomdata = async (onpcId) => {
+  pcId.value = onpcId // 将传入的pcId赋值给响应式变量
   const params = {
     transCode: "ES0009",
-    pcId: pcId
+    pcId: onpcId
   }
 
   try {
@@ -879,8 +843,45 @@ const saveKVTabelDialog = () => {
     })
 }
 
+function parseKey(key, type) {
+  if (typeof key !== 'string') {
+    return type === 'name' || type === 'unit' ? '' : { name: '', unit: '' }
+  }
+
+  const match = key.match(/^(.+?)\((.+?)\)$/)
+  const name = match ? match[1] : key
+  const unit = match ? match[2] : ''
+
+  if (type === 'name') return name
+  if (type === 'unit') return unit
+  return { name, unit }
+}
+
+const getresultData = (jobId) => {
+  const params = {
+    transCode: "ES0015",
+    pcId: pcId.value,
+    jobId: jobId
+  }
+  request(params)
+    .then((res) => {
+      console.log("jieguo:",res);
+      resultData.value = res.rows.map((item) => ({
+        name: parseKey(item.keyZh, 'name'),
+        unit: parseKey(item.keyZh, 'unit'),
+        value: item.coms[0]?.value,
+        coms: item.coms
+      }))
+      
+    })
+    .catch((err) => {
+      console.error("err", err)
+    })
+}
+
 defineExpose({
-  getcomdata
+  getcomdata,
+  getresultData
 })
 </script>
 <style scoped>

+ 12 - 1
src/views/model/vueflow/index.vue

@@ -147,6 +147,13 @@ let pid = computed(() => projectStore.pid || "")
 const showPanel = ref(false)
 const asideDataref = ref()
 
+const props = defineProps({
+  jobId: {
+    type: String,
+    default: ""
+  }
+})
+
 // 旧数据存储
 let prevNodes = [...nodes.value]
 let prevEdges = [...edges.value]
@@ -401,6 +408,9 @@ const onNodeDoubleClick = (event) => {
     console.log("diaoy", pcId)
     if (asideDataref.value) {
       asideDataref.value.getcomdata(pcId)
+      if (props.jobId){
+        asideDataref.value.getresultData(props.jobId)
+      }
     }
   })
 }
@@ -733,7 +743,8 @@ defineExpose({
   saveproject,
   removeNode,
   removeEdge,
-  confirmDelete
+  confirmDelete,
+  asideDataref
 })
 </script>