index.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818
  1. <template>
  2. <el-container>
  3. <splitpanes class="default-theme diysplitpanes">
  4. <pane min-size="10" size="20" max-size="50" class="custom-aside">
  5. <el-tabs
  6. v-model="activeName"
  7. type="border-card"
  8. class="full-height-tabs"
  9. @tab-click="handleTabclick"
  10. >
  11. <!-- 组件区域 -->
  12. <el-tab-pane label="Library" name="Liberal">
  13. <el-collapse v-model="colactiveNames">
  14. <el-collapse-item
  15. v-for="item in collapseData"
  16. :key="item.ftypecode"
  17. :title="item.ftypedesc"
  18. :name="String(item.ftypecode)"
  19. >
  20. <div class="coms-container">
  21. <div
  22. v-for="com in item.coms"
  23. :key="com.comId"
  24. class="com-item"
  25. @dragstart="onDragStart($event, 'default', com)"
  26. :draggable="true"
  27. >
  28. <img
  29. :src="getImage(com.image) || getImgPath('temp.png')"
  30. alt="component image"
  31. class="com-image"
  32. />
  33. <div class="com-name">{{ com.name }}</div>
  34. </div>
  35. </div>
  36. </el-collapse-item>
  37. </el-collapse>
  38. </el-tab-pane>
  39. <el-tab-pane label="Project" name="Project">
  40. <!-- 使用自定义图标和垂直虚线 -->
  41. <ProjectTree
  42. :data="treeData"
  43. :useCustomExpandIcon="true"
  44. :nodeIcon="systemIcon"
  45. :expandIcon="expandIcon"
  46. :collapseIcon="collapseIcon"
  47. :treeProps="{
  48. label: 'label',
  49. children: 'children',
  50. isLeaf: 'isLeaf'
  51. }"
  52. @node-click="handleNodeClick"
  53. />
  54. <!-- 使用 Element Plus 默认箭头(无前置图标和虚线) -->
  55. <!-- <ProjectTree :data="treeData" :useCustomExpandIcon="false" @node-click="handleNodeClick" /> -->
  56. </el-tab-pane>
  57. <el-tab-pane label="Result" name="Result">
  58. <el-empty v-if="activities.length === 0" description="暂无数据" />
  59. <el-timeline v-else>
  60. <el-timeline-item
  61. v-for="(activity, index) in activities"
  62. center
  63. :key="index"
  64. :color="selectedIndex === index ? '#67C23A' : ''"
  65. size="normal"
  66. :timestamp="activity.timestamp"
  67. @click="handleTimelineItemClick(index)"
  68. >
  69. {{ activity.content }}
  70. </el-timeline-item>
  71. </el-timeline>
  72. </el-tab-pane>
  73. </el-tabs>
  74. </pane>
  75. <!-- canvas区域 -->
  76. <pane class="custom-main">
  77. <splitpanes class="default-theme" horizontal>
  78. <pane min-size="50" size="75" max-size="100" class="flow-pane">
  79. <div class="main-header">
  80. <div class="header-content">
  81. <div class="image-container">
  82. <img :src="topoIcon" alt="Topology Image" />
  83. </div>
  84. <div class="text-container">
  85. <span>Topology</span>
  86. </div>
  87. </div>
  88. <TopoButtonBar
  89. @run-button-click="btnfunc"
  90. @open-simulationData-dialog="handleOpenSimulationDataDialog"
  91. @button-click="handleTopoButtonClick"
  92. @mode-switch="handleTopoModeSwitch"
  93. />
  94. </div>
  95. <!-- <div class="main-header">
  96. <div class="topologyStyle">Topology</div> -->
  97. <!-- <el-tabs type="border-card">
  98. <el-tab-pane label="Topology">
  99. <el-space :size="spacesize" class="spaceclass">
  100. <el-button
  101. title="放大"
  102. @click="zoomIn"
  103. class="custom-icon-button"
  104. >
  105. <el-icon><ZoomIn /></el-icon>
  106. </el-button>
  107. <el-button
  108. title="缩小"
  109. @click="zoomOut"
  110. class="custom-icon-button"
  111. >
  112. <el-icon><ZoomOut /></el-icon>
  113. </el-button>
  114. <el-button
  115. title="自适应"
  116. @click="fitView"
  117. class="custom-icon-button"
  118. >
  119. <el-icon><FullScreen /></el-icon>
  120. </el-button>
  121. <el-tooltip content="重置" placement="top">
  122. <el-button
  123. title="重置"
  124. @click="resetTransform"
  125. class="custom-icon-button"
  126. >
  127. <changebak name="reset" />
  128. </el-button>
  129. </el-tooltip>
  130. <el-tooltip content="背景切换" placement="top">
  131. <el-button
  132. title="背景切换"
  133. @click="toggleDarkMode"
  134. class="custom-icon-button"
  135. >
  136. <changebak v-if="dark" name="sun" />
  137. <changebak v-else name="moon" />
  138. </el-button>
  139. </el-tooltip>
  140. <el-tooltip content="保存" placement="top">
  141. <el-button
  142. @click="saveproject"
  143. class="custom-icon-button"
  144. >
  145. <el-icon class="btn-icon" :color="iconcolor"
  146. ><UploadFilled
  147. /></el-icon>
  148. </el-button>
  149. </el-tooltip>
  150. </el-space>
  151. </el-tab-pane>
  152. </el-tabs> -->
  153. <!-- </div> -->
  154. <div class="flow-content" ref="flowContentRef">
  155. <vueflow
  156. ref="vueflowRef"
  157. :jobId="jobId"
  158. :showGrid="buttonStates.showGrid"
  159. :fixedGrid="buttonStates.fixedGrid"
  160. :isSelectMode="modeSwitch"
  161. @save="handleSave"
  162. @copy="handleCopy"
  163. @cut="handleCut"
  164. @paste="handlePaste"
  165. />
  166. <Ruler :visible="buttonStates.showRuler" :container-ref="flowContentRef" />
  167. </div>
  168. </pane>
  169. <pane min-size="0" size="25" max-size="50">
  170. <el-tabs type="border-card" style="height: 100%">
  171. <el-tab-pane label="日志" style="height: 100%">
  172. <el-input
  173. v-model="logContent"
  174. type="textarea"
  175. id="textarea_id"
  176. spellcheck="false"
  177. style="height: 100%; font-size: 12px"
  178. :autosize="false"
  179. resize="none"
  180. />
  181. </el-tab-pane>
  182. </el-tabs>
  183. </pane>
  184. </splitpanes>
  185. </pane>
  186. </splitpanes>
  187. </el-container>
  188. <!-- 模拟数据弹窗 -->
  189. <SLDataDialog ref="SLdatadialogref" @selectRunType="handleSelectRunType" />
  190. <!-- 运行弹窗 -->
  191. <RunDialog ref="RunDialogRef" :runData="arrobj" />
  192. <!-- 单位系统弹窗 -->
  193. <SystemUnitDialog
  194. :visible="showSystemUnitDialog"
  195. @update:visible="showSystemUnitDialog = $event"
  196. @confirm="handleSystemUnitConfirm"
  197. />
  198. <!-- 个人单位系统弹窗 -->
  199. <PersonUnitDialog
  200. :visible="showPersonUnitDialog"
  201. @update:visible="showPersonUnitDialog = $event"
  202. @confirm="handlePersonUnitConfirm"
  203. />
  204. </template>
  205. <script setup>
  206. import { Splitpanes, Pane } from "splitpanes"
  207. import "splitpanes/dist/splitpanes.css"
  208. import { request, getImage } from "@/utils/request"
  209. import emitter from '@/utils/emitter';
  210. import { ElMessage } from "element-plus"
  211. import { useProjectStore } from "@/store/project"
  212. import { useI18n } from "vue-i18n"
  213. import vueflow from "./vueflow/index.vue"
  214. import useDragAndDrop from "./vueflow/useDnD"
  215. import { ProjectTree } from "@/components/ProjectTree"
  216. import TopoButtonBar from "@/components/layout/TopoButtonBar.vue"
  217. import Ruler from '@/components/Ruler.vue'
  218. import {
  219. ZoomIn,
  220. ZoomOut,
  221. FullScreen,
  222. Lock,
  223. Unlock,
  224. DocumentDelete,
  225. Delete,
  226. UploadFilled,
  227. Histogram,
  228. DeleteFilled,
  229. Crop,
  230. Close
  231. } from "@element-plus/icons-vue"
  232. import changebak from "./vueflow/changebak.vue"
  233. import SLDataDialog from "./dialog/SLDataDialog.vue"
  234. import RunDialog from "./dialog/RunDialog.vue"
  235. import { useVueFlow } from "@vue-flow/core"
  236. import SystemUnitDialog from "@/components/SystemUnitDialog.vue"
  237. import PersonUnitDialog from "@/components/PersonUnitDialog.vue"
  238. import systemIcon from "@/assets/img/treeSystemIcon.png"
  239. import topoIcon from "@/assets/icons/topo.png"
  240. import expandIcon from "@/assets/img/treeExpand.png"
  241. import collapseIcon from "@/assets/img/treeCollapse.png"
  242. const { zoomIn, zoomOut, fitView, setInteractive } = useVueFlow()
  243. const { t, locale } = useI18n()
  244. const projectStore = useProjectStore()
  245. const { onDragStart, onDragLeave, treeobj, onDrop } = useDragAndDrop()
  246. // 树中单元系统的弹窗显示控制
  247. const showSystemUnitDialog = ref(false)
  248. const showPersonUnitDialog = ref(false)
  249. // 存储 SystemUnitDialog 的 tableData
  250. const systemUnitData = ref([])
  251. const flowContentRef = ref(null)
  252. // 项目树数据
  253. const treeData = ref([
  254. {
  255. id: "root",
  256. label: t("treeData.system"),
  257. children: [
  258. {
  259. id: "systemSetup",
  260. label: t("treeData.systemSetup"),
  261. children: [
  262. {
  263. id: "systemUnit",
  264. label: t("treeData.systemUnitView"),
  265. children: []
  266. },
  267. {
  268. id: "personUnit",
  269. label: t("treeData.personUnitSetting"),
  270. children: []
  271. }
  272. ]
  273. }
  274. ]
  275. }
  276. ])
  277. // 项目树节点点击事件
  278. const handleNodeClick = ({ node, data, event }) => {
  279. console.log("Node clicked:", node, data, event)
  280. if (node.id === "systemUnit") {
  281. console.log("System Unit clicked")
  282. showSystemUnitDialog.value = true
  283. } else if (node.id === "personUnit") {
  284. showPersonUnitDialog.value = true
  285. }
  286. }
  287. // 单元系统弹窗确认事件
  288. const handleSystemUnitConfirm = () => {
  289. showSystemUnitDialog.value = false
  290. }
  291. // 个人单元弹窗确认事件
  292. const handlePersonUnitConfirm = () => {
  293. showPersonUnitDialog.value = false
  294. }
  295. const getImgPath = (url) => {
  296. return new URL(`../../assets/img/${url}`, import.meta.url).href
  297. }
  298. let pid = computed(() => projectStore.pid || "")
  299. let spacesize = ref(10)
  300. let colactiveNames = ref(["1"])
  301. let collapseData = ref()
  302. const vueflowRef = ref()
  303. const dark = ref(false)
  304. let iconcolor = ref("#000")
  305. const activeName = ref("Liberal")
  306. const selectedIndex = ref(null)
  307. const activities = ref([])
  308. const jobId = ref()
  309. const SLdatadialogref = ref(null)
  310. const RunDialogRef = ref(null)
  311. const modeSwitch = ref(true) // 连接模式开关
  312. const buttonStates = ref({ // 拓扑按钮状态
  313. showGrid: false,
  314. fixedGrid: false
  315. })
  316. const headerbuttons = ref([
  317. { type: "button", img: "newproject.png", name: "temp" },
  318. { type: "button", img: "save.png", name: "temp" },
  319. { type: "button", img: "importpro.png", name: "temp" },
  320. { type: "button", img: "exportpro.png", name: "temp" },
  321. { type: "divider" },
  322. { type: "button", img: "back.png", name: "temp" },
  323. { type: "button", img: "goon.png", name: "temp" },
  324. { type: "divider" },
  325. { type: "button", img: "temp.png", name: "temp" },
  326. { type: "divider" },
  327. { type: "button", img: "monidata.png", name: "SLdata" },
  328. { type: "divider" },
  329. { type: "button", img: "run.png", name: "run" },
  330. { type: "button", img: "stop.png", name: "stop" },
  331. { type: "button", img: "monitor.png", name: "monitor" },
  332. { type: "button", img: "temp.png", name: "temp" },
  333. { type: "button", img: "temp.png", name: "temp" },
  334. { type: "button", img: "temp.png", name: "temp" },
  335. { type: "button", img: "temp.png", name: "temp" },
  336. { type: "divider" },
  337. { type: "button", img: "text.png", name: "temp" },
  338. { type: "button", img: "text2.png", name: "temp" },
  339. { type: "button", img: "text3.png", name: "temp" },
  340. { type: "divider" },
  341. { type: "button", img: "temp.png", name: "temp" }
  342. ])
  343. let mainbuttons = ref([
  344. { img: "temp.png" },
  345. { img: "temp.png" },
  346. { img: "temp.png" },
  347. { img: "temp.png" },
  348. { img: "temp.png" },
  349. { img: "temp.png" },
  350. { img: "temp.png" },
  351. { img: "temp.png" },
  352. { img: "temp.png" }
  353. ])
  354. const logContent = ref("")
  355. const arrobj = ref([])
  356. let websock = ref(null)
  357. let times = ref({
  358. lockReconnect: false, //是否真正建立连接
  359. timeout: 28 * 1000, //30秒一次心跳
  360. timeoutObj: null, //心跳倒计时
  361. serverTimeoutObj: null, //
  362. timeoutnum: null //断开重连倒计时
  363. })
  364. // 用于分开不同求解运行
  365. const runtype = ref("")
  366. // 获取组件数据
  367. const getComponent = () => {
  368. const params = {
  369. transCode: "ES1001",
  370. pid: pid.value
  371. }
  372. request(params)
  373. .then((res) => {
  374. console.log(res)
  375. collapseData.value = res.rows
  376. console.log("zujian:", collapseData.value)
  377. })
  378. .catch((err) => {
  379. ElMessage.error(err.returnMsg)
  380. })
  381. }
  382. const resetTransform = () => {
  383. if (vueflowRef.value) {
  384. vueflowRef.value.resetTransform()
  385. }
  386. }
  387. const toggleDarkMode = () => {
  388. if (vueflowRef.value) {
  389. vueflowRef.value.toggleDarkMode()
  390. }
  391. }
  392. const saveproject = () => {
  393. if (vueflowRef.value) {
  394. vueflowRef.value.saveproject()
  395. }
  396. }
  397. const removeNode = () => {
  398. if (vueflowRef.value) {
  399. vueflowRef.value.removeNode()
  400. }
  401. }
  402. const removeEdge = () => {
  403. if (vueflowRef.value) {
  404. vueflowRef.value.removeEdge()
  405. }
  406. }
  407. const confirmDelete = () => {
  408. if (vueflowRef.value) {
  409. vueflowRef.value.confirmDelete()
  410. }
  411. }
  412. // 处理 topoButtonBar 触发的 open-data-dialog 事件
  413. const handleOpenSimulationDataDialog = (pid) => {
  414. if (pid) {
  415. SLdatadialogref.value?.openDialog?.(pid);
  416. }
  417. };
  418. const btnfunc = (name) => {
  419. if (name === "run") {
  420. if (runtype.value === "") {
  421. ElMessage.error("请先设置模拟类型")
  422. return
  423. } else if (runtype.value === "Incompressible Transient") {
  424. // 处理不可压缩瞬态的逻辑
  425. RunDialogRef.value?.openDialog?.()
  426. } else if (runtype.value === "Incompressible Steady State") {
  427. // 处理不可压缩稳态的逻辑
  428. runProject()
  429. } else if (runtype.value === "Compressible Transient") {
  430. // 处理可压缩瞬态的逻辑
  431. } else if (runtype.value === "Compressible Steady State") {
  432. // 处理可压缩稳态的逻辑
  433. runProject()
  434. } else {
  435. ElMessage.error("未知的模拟类型")
  436. return
  437. }
  438. } else if (name === "SLdata") {
  439. nextTick(() => {
  440. SLdatadialogref.value?.openDialog?.(pid.value)
  441. })
  442. }
  443. }
  444. const runProject = () => {
  445. const params = {
  446. transCode: "ES0013",
  447. pid: pid.value
  448. }
  449. request(params)
  450. .then((res) => {
  451. ElMessage.success("开始运行")
  452. })
  453. .catch((err) => {
  454. ElMessage.error(err.returnMsg)
  455. })
  456. }
  457. const handleSelectRunType = (type) => {
  458. runtype.value = type
  459. projectStore.setruntype(type)
  460. }
  461. // 获取结果列表
  462. const getresultlist = () => {
  463. const params = {
  464. transCode: "ES0014",
  465. pid: pid.value
  466. }
  467. request(params)
  468. .then((res) => {
  469. console.log("获取结果列表成功", res)
  470. // 处理结果列表数据
  471. activities.value = res.rows.map((item) => ({
  472. jobId: item.jobId,
  473. content: "结果" + item.ser,
  474. timestamp: item.startTime.split(" +")[0]
  475. }))
  476. })
  477. .catch((err) => {
  478. console.error("获取结果列表失败", err)
  479. ElMessage.error("获取结果列表失败")
  480. })
  481. }
  482. const handleTabclick = (tab, event) => {
  483. console.log("tab clicked:", tab.props.name)
  484. if (tab.props.name === "Result") {
  485. getresultlist()
  486. }
  487. }
  488. const handleTimelineItemClick = (index) => {
  489. selectedIndex.value = index
  490. console.log("Timeline item clicked:", activities.value[index])
  491. jobId.value = activities.value[index].jobId
  492. vueflowRef.value?.asideDataref?.getresultData(jobId.value)
  493. }
  494. // 处理 topoButtonBar 触发的 button-click 事件
  495. const handleTopoButtonClick = ({ action, isActive }) => {
  496. buttonStates.value[action] = isActive !== undefined ? isActive : buttonStates.value[action]
  497. console.log(`Button clicked: ${action}, isActive: ${buttonStates.value[action]}`)
  498. }
  499. // 处理 topoButtonBar 触发的 mode-switch 事件
  500. const handleTopoModeSwitch = (value) => {
  501. modeSwitch.value = value
  502. // setInteractive(!value)
  503. console.log(value ? "切换到选择模式" : "切换到连接模式")
  504. }
  505. // 新增事件处理函数
  506. const handleSave = () => {
  507. if (vueflowRef.value) {
  508. vueflowRef.value.saveproject();
  509. }
  510. };
  511. const handleCopy = () => {
  512. if (vueflowRef.value) {
  513. vueflowRef.value.copyNodes();
  514. }
  515. };
  516. const handleCut = () => {
  517. if (vueflowRef.value) {
  518. vueflowRef.value.cutNodes();
  519. }
  520. };
  521. const handlePaste = () => {
  522. if (vueflowRef.value) {
  523. vueflowRef.value.pasteNodes();
  524. }
  525. };
  526. //websockct的连接
  527. function initWebSocket() {
  528. //初始化weosocket
  529. const wsurl = import.meta.env.VITE_WEBSOCKET_URL + pid.value
  530. websock = new WebSocket(wsurl)
  531. websock.onopen = websocketonopen
  532. websock.onmessage = websocketonmessage
  533. websock.onerror = websocketonerror
  534. websock.onclose = websocketclose
  535. }
  536. // Websoket连接成功事件
  537. const websocketonopen = (res) => {
  538. console.log("WebSocket连接成功", res)
  539. start()
  540. }
  541. // Websoket接收消息事件
  542. const websocketonmessage = (res) => {
  543. console.log("websocket接受消息:", res.data)
  544. if (res.data.indexOf("{") !== -1) {
  545. } else {
  546. if (res.data.indexOf("——开始") !== -1) {
  547. arrobj.value = [] // 清空数据点数组
  548. }
  549. if (res.data.indexOf("——成功") !== -1) {
  550. getresultlist() //刷新结果列表
  551. const timer = setTimeout(function () {
  552. console.log("关闭定时器")
  553. }, 10000)
  554. }
  555. if (res.data.indexOf("msg=heartChec") == -1) {
  556. // 去除空行
  557. const cleanedLog = res.data
  558. .split("\n")
  559. .filter((line) => line.trim() !== "")
  560. .join("\n")
  561. logContent.value = logContent.value + "\n" + cleanedLog
  562. let textarea = document.getElementById("textarea_id")
  563. textarea.scrollTop = textarea.scrollHeight
  564. }
  565. // 提取数据点
  566. const lines = res.data.split("\n")
  567. for (const line of lines) {
  568. const match = line.match(/value:\s*([\d.]+);\s*([\d.]+);/)
  569. if (match) {
  570. const step = parseFloat(match[1])
  571. const value = parseFloat(match[2])
  572. arrobj.value.push({ step, value })
  573. }
  574. }
  575. // console.log('arrobjvalue',arrobj.value)
  576. }
  577. reset()
  578. }
  579. // Websoket连接错误事件
  580. const websocketonerror = (res) => {
  581. console.log("连接错误", res)
  582. websock.close()
  583. reconnect()
  584. }
  585. // Websoket断开事件
  586. const websocketclose = (res) => {
  587. console.log("断开连接", res)
  588. }
  589. // 心跳包
  590. const reconnect = () => {
  591. if (times.value.lockReconnect) return
  592. times.value.lockReconnect = true
  593. //没连接上会一直重连,设置延迟避免请求过多
  594. times.value.timeoutnum && clearTimeout(times.value.timeoutnum)
  595. times.value.timeoutnum = setTimeout(function () {
  596. //新连接
  597. initWebSocket()
  598. times.value.lockReconnect = false
  599. }, 10000)
  600. }
  601. const reset = () => {
  602. //重置心跳
  603. clearTimeout(times.value.timeoutObj)
  604. clearTimeout(times.value.serverTimeoutObj)
  605. start()
  606. }
  607. const start = () => {
  608. //开启心跳
  609. times.value.timeoutObj && clearTimeout(times.value.timeoutObj)
  610. times.value.serverTimeoutObj && clearTimeout(times.value.serverTimeoutObj)
  611. times.value.timeoutObj = setTimeout(function () {
  612. //这里发送一个心跳,后端收到后,返回一个心跳消息
  613. if (websock.readyState == 1) {
  614. //如果连接正常
  615. websock.send("heartCheck")
  616. } else {
  617. //否则重连
  618. reconnect()
  619. }
  620. times.value.serverTimeoutObj = setTimeout(function () {
  621. // 超时关闭
  622. websock.close() //如果onclose会执行reconnect,我们执行ws.close()就行了.如果直接执行reconnect 会触发onclose导致重连两次
  623. }, times.value.timeout)
  624. }, times.value.timeout)
  625. }
  626. const getlogs = () => {
  627. const params = {
  628. transCode: "ES0017",
  629. pid: pid.value
  630. }
  631. request(params)
  632. .then((res) => {
  633. // console.log("获取日志成功", res)
  634. logContent.value = res.logs
  635. .split("\n")
  636. .filter((line) => line.trim() !== "")
  637. .join("\n")
  638. // 自动滚动日志到底部
  639. let textarea = document.getElementById("textarea_id")
  640. textarea.scrollTop = textarea.scrollHeight
  641. })
  642. .catch((err) => {
  643. console.error("获取日志失败", err)
  644. ElMessage.error("获取日志失败")
  645. })
  646. }
  647. onMounted(() => {
  648. runtype.value = projectStore.runtype || ""
  649. getComponent()
  650. setTimeout(function () {
  651. initWebSocket()
  652. getlogs()
  653. }, 1500)
  654. emitter.on('save', handleSave);
  655. emitter.on('copy', handleCopy);
  656. emitter.on('cut', handleCut);
  657. emitter.on('paste', handlePaste);
  658. });
  659. onUnmounted(() => {
  660. emitter.off('save', handleSave);
  661. emitter.off('copy', handleCopy);
  662. emitter.off('cut', handleCut);
  663. emitter.off('paste', handlePaste);
  664. });
  665. // onBeforeUnmount(() => {
  666. // websock?.close()
  667. // clearTimeout(times.value.timeoutObj)
  668. // clearTimeout(times.value.serverTimeoutObj)
  669. // })
  670. </script>
  671. <style scoped>
  672. .flow-pane {
  673. display: flex;
  674. flex-direction: column;
  675. height: 100%;
  676. }
  677. .main-header {
  678. background: #edf2fa;
  679. border-radius: 0px 0px 0px 0px;
  680. border: 2px solid #eeeeee;
  681. }
  682. .header-content {
  683. display: flex;
  684. align-items: center;
  685. justify-content: space-between;
  686. max-width: 110px;
  687. background-color: #ffffff;
  688. padding: 3px;
  689. margin: 0;
  690. border-top: #075679 3px solid;
  691. }
  692. .image-container {
  693. flex: 1;
  694. max-width: 45%;
  695. padding-right: 5px;
  696. }
  697. .text-container{
  698. padding: 0 5px 0 0;
  699. font-size: 16px;
  700. }
  701. .diysplitpanes {
  702. overflow: auto;
  703. }
  704. .main-header :deep(.el-tabs--border-card > .el-tabs__content) {
  705. padding: 5px 15px;
  706. }
  707. .main-header :deep(.el-tabs__content) {
  708. background: #ffffff;
  709. border-radius: 0px 0px 0px 0px;
  710. border: 1px solid #d8d8d8;
  711. }
  712. .coms-container {
  713. display: flex;
  714. flex-wrap: wrap;
  715. gap: 10px;
  716. }
  717. .com-item {
  718. display: flex;
  719. justify-content: start;
  720. align-items: center;
  721. width: 130px;
  722. }
  723. .com-image {
  724. width: 24px;
  725. height: 24px;
  726. object-fit: contain;
  727. }
  728. .com-name {
  729. font-size: 11px;
  730. }
  731. .flow-content {
  732. width: 100%;
  733. flex: 1;
  734. background: #ffffff;
  735. position: relative;
  736. overflow: hidden;
  737. }
  738. .btn-icon {
  739. font-size: 16px;
  740. }
  741. .topologyStyle {
  742. width: 80px;
  743. height: 30px;
  744. font-size: 18px;
  745. border-top: 3px solid #77c5e6;
  746. background-color: #ffffff;
  747. }
  748. </style>