| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845 |
- <template>
- <el-container>
- <splitpanes class="default-theme diysplitpanes">
- <pane min-size="10" size="20" max-size="50" class="custom-aside">
- <el-tabs
- v-model="activeName"
- type="border-card"
- class="full-height-tabs"
- @tab-click="handleTabclick"
- >
- <!-- 组件区域 -->
- <el-tab-pane label="Library" name="Liberal">
- <el-collapse v-model="colactiveNames">
- <el-collapse-item
- v-for="item in collapseData"
- :key="item.ftypecode"
- :title="item.ftypedesc"
- :name="String(item.ftypecode)"
- >
- <div class="coms-container">
- <div
- v-for="com in item.coms"
- :key="com.comId"
- class="com-item"
- @dragstart="onDragStart($event, 'default', com)"
- :draggable="true"
- >
- <img
- :src="getImage(com.image) || getImgPath('temp.png')"
- alt="component image"
- class="com-image"
- />
- <div class="com-name">{{ com.name }}</div>
- </div>
- </div>
- </el-collapse-item>
- </el-collapse>
- </el-tab-pane>
- <el-tab-pane label="Project" name="Project">
- <!-- 使用自定义图标和垂直虚线 -->
- <ProjectTree
- :data="treeData"
- :useCustomExpandIcon="true"
- :nodeIcon="systemIcon"
- :expandIcon="expandIcon"
- :collapseIcon="collapseIcon"
- :treeProps="{
- label: 'label',
- children: 'children',
- isLeaf: 'isLeaf'
- }"
- @node-click="handleNodeClick"
- />
- <!-- 使用 Element Plus 默认箭头(无前置图标和虚线) -->
- <!-- <ProjectTree :data="treeData" :useCustomExpandIcon="false" @node-click="handleNodeClick" /> -->
- </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>
- <!-- canvas区域 -->
- <pane class="custom-main">
- <splitpanes class="default-theme" horizontal>
- <pane min-size="50" size="75" max-size="100" class="flow-pane">
- <div class="main-header">
- <div class="header-content">
- <div class="image-container">
- <img :src="topoIcon" alt="Topology Image" />
- </div>
- <div class="text-container">
- <span>Topology</span>
- </div>
- </div>
- <TopoButtonBar
- @run-button-click="btnfunc"
- @open-simulationData-dialog="handleOpenSimulationDataDialog"
- @button-click="handleTopoButtonClick"
- @mode-switch="handleTopoModeSwitch"
- />
- </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">
- <vueflow
- ref="vueflowRef"
- :jobId="jobId"
- :showGrid="buttonStates.showGrid"
- :fixedGrid="buttonStates.fixedGrid"
- :isSelectMode="modeSwitch"
- @save="handleSave"
- @copy="handleCopy"
- @cut="handleCut"
- @paste="handlePaste"
- />
- <Ruler :visible="buttonStates.showRuler" :container-ref="flowContentRef" />
- </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-container>
- <!-- 模拟数据弹窗 -->
- <SLDataDialog ref="SLdatadialogref" @selectRunType="handleSelectRunType" />
- <!-- 运行弹窗 -->
- <RunDialog ref="RunDialogRef" :runData="arrobj" />
- <!-- 单位系统弹窗 -->
- <SystemUnitDialog
- :visible="showSystemUnitDialog"
- @update:visible="showSystemUnitDialog = $event"
- @confirm="handleSystemUnitConfirm"
- />
- <!-- 个人单位系统弹窗 -->
- <PersonUnitDialog
- :visible="showPersonUnitDialog"
- @update:visible="showPersonUnitDialog = $event"
- @confirm="handlePersonUnitConfirm"
- />
- </template>
- <script setup>
- import { Splitpanes, Pane } from "splitpanes"
- import "splitpanes/dist/splitpanes.css"
- import { request, getImage } from "@/utils/request"
- import emitter from '@/utils/emitter';
- import { ElMessage } from "element-plus"
- import { useProjectStore } from "@/store/project"
- import { useI18n } from "vue-i18n"
- import vueflow from "./vueflow/index.vue"
- import useDragAndDrop from "./vueflow/useDnD"
- import { ProjectTree } from "@/components/ProjectTree"
- import TopoButtonBar from "@/components/layout/TopoButtonBar.vue"
- import Ruler from '@/components/Ruler.vue'
- import {
- ZoomIn,
- ZoomOut,
- FullScreen,
- Lock,
- Unlock,
- DocumentDelete,
- Delete,
- UploadFilled,
- Histogram,
- DeleteFilled,
- Crop,
- Close
- } from "@element-plus/icons-vue"
- import changebak from "./vueflow/changebak.vue"
- import SLDataDialog from "./dialog/SLDataDialog.vue"
- import RunDialog from "./dialog/RunDialog.vue"
- import { useVueFlow } from "@vue-flow/core"
- import SystemUnitDialog from "@/components/SystemUnitDialog.vue"
- import PersonUnitDialog from "@/components/PersonUnitDialog.vue"
- import systemIcon from "@/assets/img/treeSystemIcon.png"
- import topoIcon from "@/assets/icons/topo.png"
- import expandIcon from "@/assets/img/treeExpand.png"
- import collapseIcon from "@/assets/img/treeCollapse.png"
- const { zoomIn, zoomOut, fitView, setInteractive } = useVueFlow()
- const { t, locale } = useI18n()
- const projectStore = useProjectStore()
- const { onDragStart, onDragLeave, treeobj, onDrop } = useDragAndDrop()
- // 树中单元系统的弹窗显示控制
- const showSystemUnitDialog = ref(false)
- const showPersonUnitDialog = ref(false)
- // 存储 SystemUnitDialog 的 tableData
- const systemUnitData = ref([])
- const flowContentRef = ref(null)
- // 项目树数据
- const treeData = ref([
- {
- id: "root",
- label: t("treeData.system"),
- children: [
- {
- id: "systemSetup",
- label: t("treeData.systemSetup"),
- children: [
- {
- id: "systemUnit",
- label: t("treeData.systemUnitView"),
- children: []
- },
- {
- id: "personUnit",
- label: t("treeData.personUnitSetting"),
- children: []
- }
- ]
- }
- ]
- }
- ])
- // 项目树节点点击事件
- const handleNodeClick = ({ node, data, event }) => {
- console.log("Node clicked:", node, data, event)
- if (node.id === "systemUnit") {
- console.log("System Unit clicked")
- showSystemUnitDialog.value = true
- } else if (node.id === "personUnit") {
- showPersonUnitDialog.value = true
- }
- }
- // 单元系统弹窗确认事件
- const handleSystemUnitConfirm = () => {
- showSystemUnitDialog.value = false
- }
- // 个人单元弹窗确认事件
- const handlePersonUnitConfirm = () => {
- showPersonUnitDialog.value = false
- }
- const getImgPath = (url) => {
- return new URL(`../../assets/img/${url}`, import.meta.url).href
- }
- let pid = computed(() => projectStore.pid || "")
- let spacesize = ref(10)
- let colactiveNames = ref(["1"])
- let collapseData = ref()
- const vueflowRef = ref()
- const dark = ref(false)
- let iconcolor = ref("#000")
- const activeName = ref("Liberal")
- const selectedIndex = ref(null)
- const activities = ref([])
- const jobId = ref()
- const SLdatadialogref = ref(null)
- const RunDialogRef = ref(null)
- const modeSwitch = ref(true) // 连接模式开关
- const buttonStates = ref({ // 拓扑按钮状态
- showGrid: false,
- fixedGrid: false
- })
- const headerbuttons = ref([
- { type: "button", img: "newproject.png", name: "temp" },
- { type: "button", img: "save.png", name: "temp" },
- { type: "button", img: "importpro.png", name: "temp" },
- { type: "button", img: "exportpro.png", name: "temp" },
- { type: "divider" },
- { type: "button", img: "back.png", name: "temp" },
- { type: "button", img: "goon.png", name: "temp" },
- { type: "divider" },
- { type: "button", img: "temp.png", name: "temp" },
- { type: "divider" },
- { type: "button", img: "monidata.png", name: "SLdata" },
- { type: "divider" },
- { type: "button", img: "run.png", name: "run" },
- { type: "button", img: "stop.png", name: "stop" },
- { type: "button", img: "monitor.png", name: "monitor" },
- { type: "button", img: "temp.png", name: "temp" },
- { type: "button", img: "temp.png", name: "temp" },
- { type: "button", img: "temp.png", name: "temp" },
- { type: "button", img: "temp.png", name: "temp" },
- { type: "divider" },
- { type: "button", img: "text.png", name: "temp" },
- { type: "button", img: "text2.png", name: "temp" },
- { type: "button", img: "text3.png", name: "temp" },
- { type: "divider" },
- { type: "button", img: "temp.png", name: "temp" }
- ])
- let mainbuttons = ref([
- { img: "temp.png" },
- { img: "temp.png" },
- { img: "temp.png" },
- { img: "temp.png" },
- { img: "temp.png" },
- { img: "temp.png" },
- { img: "temp.png" },
- { img: "temp.png" },
- { 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",
- pid: pid.value
- }
- request(params)
- .then((res) => {
- console.log(res)
- collapseData.value = res.rows
- console.log("zujian:", collapseData.value)
- })
- .catch((err) => {
- ElMessage.error(err.returnMsg)
- })
- }
- const resetTransform = () => {
- if (vueflowRef.value) {
- vueflowRef.value.resetTransform()
- }
- }
- const toggleDarkMode = () => {
- if (vueflowRef.value) {
- vueflowRef.value.toggleDarkMode()
- }
- }
- const saveproject = () => {
- if (vueflowRef.value) {
- vueflowRef.value.saveproject()
- }
- }
- const removeNode = () => {
- if (vueflowRef.value) {
- vueflowRef.value.removeNode()
- }
- }
- const removeEdge = () => {
- if (vueflowRef.value) {
- vueflowRef.value.removeEdge()
- }
- }
- const confirmDelete = () => {
- if (vueflowRef.value) {
- vueflowRef.value.confirmDelete()
- }
- }
- // 处理 topoButtonBar 触发的 open-data-dialog 事件
- const handleOpenSimulationDataDialog = (pid) => {
- if (pid) {
- SLdatadialogref.value?.openDialog?.(pid);
- }
- };
- const btnfunc = (name) => {
- if (name === "run") {
- if (runtype.value === "") {
- ElMessage.error("请先设置模拟类型")
- return
- } else if (runtype.value === "Incompressible Transient") {
- // 处理不可压缩瞬态的逻辑
- RunDialogRef.value?.openDialog?.()
- } else if (runtype.value === "Incompressible Steady State") {
- // 处理不可压缩稳态的逻辑
- runProject()
- } else if (runtype.value === "Compressible Transient") {
- // 处理可压缩瞬态的逻辑
- } else if (runtype.value === "Compressible Steady State") {
- // 处理可压缩稳态的逻辑
- runProject()
- } else {
- ElMessage.error("未知的模拟类型")
- return
- }
- } else if (name === "SLdata") {
- nextTick(() => {
- SLdatadialogref.value?.openDialog?.(pid.value)
- })
- }
- }
- const runProject = () => {
- const params = {
- transCode: "ES0013",
- pid: pid.value
- }
- request(params)
- .then((res) => {
- ElMessage.success("开始运行")
- })
- .catch((err) => {
- ElMessage.error(err.returnMsg)
- })
- }
- const handleSelectRunType = (type) => {
- runtype.value = type
- projectStore.setruntype(type)
- }
- // 获取结果列表
- const getresultlist = () => {
- const params = {
- 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.split(" +")[0]
- }))
- })
- .catch((err) => {
- console.error("获取结果列表失败", err)
- ElMessage.error("获取结果列表失败")
- })
- }
- const handleTabclick = (tab, event) => {
- console.log("tab clicked:", tab.props.name)
- if (tab.props.name === "Result") {
- getresultlist()
- }
- }
- 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)
- }
- // 处理 topoButtonBar 触发的 button-click 事件
- const handleTopoButtonClick = ({ action, isActive }) => {
- buttonStates.value[action] = isActive !== undefined ? isActive : buttonStates.value[action]
- // 根据 action 调用 VueFlow 对应方法
- switch (action) {
- case 'zoomIn':
- zoomIn()
- break
- case 'zoomOut':
- zoomOut()
- break
- case 'fitView':
- fitView()
- break
- case 'actualSize':
- // 重置为实际大小(缩放 1x,位置 0,0)
- if (vueflowRef.value) {
- vueflowRef.value.resetTransform() // 假设你的 vueflow 组件暴露了 resetTransform 方法
- } else {
- // 备选:直接使用 useVueFlow 的 setViewport
- const { setViewport } = useVueFlow()
- setViewport({ x: 0, y: 0, zoom: 1 })
- }
- break
- case 'drag':
- setInteractive(true)
- break
- default:
- console.log(`Unhandled action: ${action}`)
- break
- }
- }
- // 处理 topoButtonBar 触发的 mode-switch 事件
- const handleTopoModeSwitch = (value) => {
- modeSwitch.value = value
- // setInteractive(!value)
- console.log(value ? "切换到选择模式" : "切换到连接模式")
- }
- // 新增事件处理函数
- const handleSave = () => {
- if (vueflowRef.value) {
- vueflowRef.value.saveproject();
- }
- };
- const handleCopy = () => {
- if (vueflowRef.value) {
- vueflowRef.value.copyNodes();
- }
- };
- const handleCut = () => {
- if (vueflowRef.value) {
- vueflowRef.value.cutNodes();
- }
- };
- const handlePaste = () => {
- if (vueflowRef.value) {
- vueflowRef.value.pasteNodes();
- }
- };
- //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
- }
- // Websoket连接成功事件
- const websocketonopen = (res) => {
- console.log("WebSocket连接成功", res)
- start()
- }
- // Websoket接收消息事件
- const websocketonmessage = (res) => {
- console.log("websocket接受消息:", res.data)
- if (res.data.indexOf("{") !== -1) {
- } else {
- if (res.data.indexOf("——开始") !== -1) {
- arrobj.value = [] // 清空数据点数组
- }
- if (res.data.indexOf("——成功") !== -1) {
- getresultlist() //刷新结果列表
- 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
- }
- // 提取数据点
- const lines = res.data.split("\n")
- for (const line of lines) {
- const match = line.match(/value:\s*([\d.]+);\s*([\d.]+);/)
- if (match) {
- const step = parseFloat(match[1])
- const value = parseFloat(match[2])
- arrobj.value.push({ step, value })
- }
- }
- // console.log('arrobjvalue',arrobj.value)
- }
- 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()
- }
- times.value.serverTimeoutObj = setTimeout(function () {
- // 超时关闭
- websock.close() //如果onclose会执行reconnect,我们执行ws.close()就行了.如果直接执行reconnect 会触发onclose导致重连两次
- }, times.value.timeout)
- }, times.value.timeout)
- }
- const getlogs = () => {
- const params = {
- transCode: "ES0017",
- pid: pid.value
- }
- request(params)
- .then((res) => {
- // 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)
- ElMessage.error("获取日志失败")
- })
- }
- onMounted(() => {
- runtype.value = projectStore.runtype || ""
- getComponent()
- setTimeout(function () {
- initWebSocket()
- getlogs()
- }, 1500)
- emitter.on('save', handleSave);
- emitter.on('copy', handleCopy);
- emitter.on('cut', handleCut);
- emitter.on('paste', handlePaste);
- });
- onUnmounted(() => {
- emitter.off('save', handleSave);
- emitter.off('copy', handleCopy);
- emitter.off('cut', handleCut);
- emitter.off('paste', handlePaste);
- });
- // onBeforeUnmount(() => {
- // websock?.close()
- // clearTimeout(times.value.timeoutObj)
- // clearTimeout(times.value.serverTimeoutObj)
- // })
- </script>
- <style scoped>
- .flow-pane {
- display: flex;
- flex-direction: column;
- height: 100%;
- }
- .main-header {
- background: #edf2fa;
- border-radius: 0px 0px 0px 0px;
- border: 2px solid #eeeeee;
- }
- .header-content {
- display: flex;
- align-items: center;
- justify-content: space-between;
- max-width: 110px;
- background-color: #ffffff;
- padding: 3px;
- margin: 0;
- border-top: #075679 3px solid;
- }
- .image-container {
- flex: 1;
- max-width: 45%;
- padding-right: 5px;
- }
- .text-container{
- padding: 0 5px 0 0;
- font-size: 16px;
- }
- .diysplitpanes {
- overflow: auto;
- }
- .main-header :deep(.el-tabs--border-card > .el-tabs__content) {
- padding: 5px 15px;
- }
- .main-header :deep(.el-tabs__content) {
- background: #ffffff;
- border-radius: 0px 0px 0px 0px;
- border: 1px solid #d8d8d8;
- }
- .coms-container {
- display: flex;
- flex-wrap: wrap;
- gap: 10px;
- }
- .com-item {
- display: flex;
- justify-content: start;
- align-items: center;
- width: 130px;
- }
- .com-image {
- width: 24px;
- height: 24px;
- object-fit: contain;
- }
- .com-name {
- font-size: 11px;
- }
- .flow-content {
- width: 100%;
- flex: 1;
- background: #ffffff;
- position: relative;
- overflow: hidden;
- }
- .btn-icon {
- font-size: 16px;
- }
- .topologyStyle {
- width: 80px;
- height: 30px;
- font-size: 18px;
- border-top: 3px solid #77c5e6;
- background-color: #ffffff;
- }
- </style>
|