wanshenmean
2026-03-19 c493779a8504fe1eb548c865ff268a7f7436ec01
Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/DetailsView.vue
@@ -1,5 +1,6 @@
<template>
  <div
    class="admin-page"
    v-loading.fullscreen.lock="startActionLoading"
    element-loading-text="正在启动实例,请稍候..."
  >
@@ -31,44 +32,64 @@
        </el-button>
      </div>
      <el-row :gutter="20" class="status-cards">
        <el-col :xs="12" :sm="6">
          <el-card shadow="hover" class="status-card">
            <el-statistic title="状态">
              <template #default>
                <el-tag :type="getStatusTagType(instance.status)" size="large">
                  {{ getStatusText(instance.status) }}
                </el-tag>
              </template>
            </el-statistic>
          </el-card>
        </el-col>
        <el-col :xs="12" :sm="6">
          <el-card shadow="hover" class="status-card">
            <el-statistic title="连接客户端" :value="instance.clientCount">
              <template #suffix>
                <el-icon><User /></el-icon>
              </template>
            </el-statistic>
          </el-card>
        </el-col>
        <el-col :xs="12" :sm="6">
          <el-card shadow="hover" class="status-card">
            <el-statistic title="总请求数" :value="instance.totalRequests" />
          </el-card>
        </el-col>
        <el-col :xs="12" :sm="6">
          <el-card shadow="hover" class="status-card">
            <el-statistic title="端口" :value="instance.port" />
          </el-card>
        </el-col>
      </el-row>
      <section class="section-block">
        <div class="section-head">
          <div>
            <h3 class="section-title">信息区</h3>
            <p class="section-desc">实例运行状态与连接概览</p>
          </div>
        </div>
        <div class="section-body">
          <el-row :gutter="12" class="status-cards">
            <el-col :xs="12" :sm="6">
              <el-card shadow="hover" class="status-card panel-card">
                <el-statistic title="状态">
                  <template #default>
                    <el-tag :type="getStatusTagType(instance.status)" size="large">
                      {{ getStatusText(instance.status) }}
                    </el-tag>
                  </template>
                </el-statistic>
              </el-card>
            </el-col>
            <el-col :xs="12" :sm="6">
              <el-card shadow="hover" class="status-card panel-card">
                <el-statistic title="连接客户端" :value="instance.clientCount">
                  <template #suffix>
                    <el-icon><User /></el-icon>
                  </template>
                </el-statistic>
              </el-card>
            </el-col>
            <el-col :xs="12" :sm="6">
              <el-card shadow="hover" class="status-card panel-card">
                <el-statistic title="总请求数" :value="instance.totalRequests" />
              </el-card>
            </el-col>
            <el-col :xs="12" :sm="6">
              <el-card shadow="hover" class="status-card panel-card">
                <el-statistic title="端口" :value="instance.port" />
              </el-card>
            </el-col>
          </el-row>
        </div>
      </section>
      <el-card class="mt-4" shadow="never">
      <section class="section-block detail-section">
        <div class="section-head">
          <div>
            <h3 class="section-title">操作区</h3>
            <p class="section-desc">左侧实例信息与操作,右侧实时 DB 数据</p>
          </div>
        </div>
        <div class="section-body">
      <el-row :gutter="16" class="detail-main">
        <el-col :xs="24" :lg="8">
      <el-card class="panel-card" shadow="never">
        <template #header>
          <span class="card-header-title">基本信息</span>
        </template>
        <el-descriptions :column="2" border>
        <el-descriptions :column="1" border>
          <el-descriptions-item label="实例ID">{{ instance.instanceId }}</el-descriptions-item>
          <el-descriptions-item label="实例名称">{{ instance.name }}</el-descriptions-item>
          <el-descriptions-item label="PLC型号">{{ getPlcTypeText(instance.plcType) }}</el-descriptions-item>
@@ -79,17 +100,18 @@
          <el-descriptions-item v-if="instance.lastActivityTime" label="最后活动时间">
            {{ formatDate(instance.lastActivityTime) }}
          </el-descriptions-item>
          <el-descriptions-item v-if="instance.errorMessage" label="错误信息" :span="2">
          <el-descriptions-item v-if="instance.errorMessage" label="错误信息">
            <el-text type="danger">{{ instance.errorMessage }}</el-text>
          </el-descriptions-item>
        </el-descriptions>
      </el-card>
      <el-card class="mt-4" shadow="never">
      <el-card class="panel-card left-actions" shadow="never">
        <div class="action-buttons">
          <el-button
            v-if="instance.status === 'Stopped' || instance.status === 'Error'"
            type="success"
            @click="handleStart"
          >
            <el-icon><VideoPlay /></el-icon>
@@ -98,6 +120,7 @@
          <el-button
            v-if="instance.status === 'Running'"
            type="warning"
            @click="handleStop"
          >
            <el-icon><VideoPause /></el-icon>
@@ -114,13 +137,15 @@
        </div>
      </el-card>
      <el-card class="mt-4" shadow="never">
        </el-col>
        <el-col :xs="24" :lg="16">
      <el-card class="panel-card" shadow="never">
        <template #header>
          <div class="db-header">
            <span class="card-header-title">DB块实时数据</span>
            <div class="db-toolbar">
              <el-switch v-model="autoRefreshDb" active-text="自动刷新" />
              <el-button size="small" @click="loadMemoryData(true)">手动刷新</el-button>
              <el-button @click="loadMemoryData(true)">手动刷新</el-button>
            </div>
          </div>
        </template>
@@ -131,7 +156,7 @@
            <template #default>
              <div v-if="deviceDbViews.length === 0" class="text-muted">当前设备模板未匹配到可显示的DB块</div>
              <div v-else>
                <el-tabs type="border-card" class="db-tabs">
                    <el-tabs type="border-card" class="db-tabs">
                  <el-tab-pane
                    v-for="view in deviceDbViews"
                    :key="view.templateDbNumber"
@@ -149,25 +174,27 @@
                        :key="`${view.templateDbNumber}-${group.key}`"
                      >
                        <template #label>
                          <el-tag :type="getFieldGroupTagType(group.key)" size="small">{{ group.key }}</el-tag>
                          <el-tag :type="getFieldGroupTagType(group.key)">{{ group.key }}</el-tag>
                        </template>
                        <div class="field-table-wrap">
                        <el-table
                          :data="group.fields"
                          border
                          size="small"
                          class="field-table"
                          table-layout="auto"
                          empty-text="当前分组无字段映射"
                        >
                          <el-table-column prop="fieldKey" label="字段" min-width="140" />
                          <el-table-column prop="address" label="地址" width="130" />
                          <el-table-column prop="mappedDb" label="映射块" width="120">
                          <el-table-column prop="fieldKey" label="字段" />
                          <el-table-column prop="address" label="地址" />
                          <el-table-column prop="mappedDb" label="映射块">
                            <template #default="{ row }">
                              <el-tag :type="getDbTagType(row.mappedDb)" size="small">{{ row.mappedDb }}</el-tag>
                              <el-tag :type="getDbTagType(row.mappedDb)">{{ row.mappedDb }}</el-tag>
                            </template>
                          </el-table-column>
                          <el-table-column prop="dataType" label="类型" width="90" />
                          <el-table-column prop="direction" label="方向" width="130" />
                          <el-table-column prop="value" label="当前值" min-width="220" />
                          <el-table-column label="修改值" min-width="220">
                          <el-table-column prop="dataType" label="类型" />
                          <el-table-column prop="direction" label="方向" />
                          <el-table-column prop="value" label="当前值" />
                          <el-table-column label="修改值">
                            <template #default="{ row }">
                              <el-switch
                                v-if="row.dataType === 'Bool'"
@@ -181,17 +208,18 @@
                                @change="markFieldDirty(row)"
                                :disabled="!isFieldWritable(row)"
                                :controls="false"
                                style="width: 100%"
                                class="editable-control"
                              />
                              <el-input
                                v-else
                                v-model="fieldEditValues[getFieldEditKey(row)]"
                                @input="markFieldDirty(row)"
                                :disabled="!isFieldWritable(row)"
                                class="editable-control"
                              />
                            </template>
                          </el-table-column>
                          <el-table-column label="操作" width="90" fixed="right">
                          <el-table-column label="操作" width="88" fixed="right">
                            <template #default="{ row }">
                              <el-button
                                type="primary"
@@ -205,26 +233,29 @@
                            </template>
                          </el-table-column>
                        </el-table>
                        </div>
                      </el-tab-pane>
                    </el-tabs>
                    <template v-else-if="view.fields.length > 0">
                    <div class="field-table-wrap">
                    <el-table
                      v-else-if="view.fields.length > 0"
                      :data="view.fields"
                      border
                      size="small"
                      class="field-table"
                      table-layout="auto"
                      empty-text="当前DB块无字段映射"
                    >
                      <el-table-column prop="fieldKey" label="字段" min-width="140" />
                      <el-table-column prop="address" label="地址" width="130" />
                      <el-table-column prop="mappedDb" label="映射块" width="120">
                      <el-table-column prop="fieldKey" label="字段" />
                      <el-table-column prop="address" label="地址" />
                      <el-table-column prop="mappedDb" label="映射块">
                        <template #default="{ row }">
                          <el-tag :type="getDbTagType(row.mappedDb)" size="small">{{ row.mappedDb }}</el-tag>
                          <el-tag :type="getDbTagType(row.mappedDb)">{{ row.mappedDb }}</el-tag>
                        </template>
                      </el-table-column>
                      <el-table-column prop="dataType" label="类型" width="90" />
                      <el-table-column prop="direction" label="方向" width="130" />
                      <el-table-column prop="value" label="当前值" min-width="220" />
                      <el-table-column label="修改值" min-width="220">
                      <el-table-column prop="dataType" label="类型" />
                      <el-table-column prop="direction" label="方向" />
                      <el-table-column prop="value" label="当前值" />
                      <el-table-column label="修改值">
                        <template #default="{ row }">
                          <el-switch
                            v-if="row.dataType === 'Bool'"
@@ -238,17 +269,18 @@
                            @change="markFieldDirty(row)"
                            :disabled="!isFieldWritable(row)"
                            :controls="false"
                            style="width: 100%"
                            class="editable-control"
                          />
                          <el-input
                            v-else
                            v-model="fieldEditValues[getFieldEditKey(row)]"
                            @input="markFieldDirty(row)"
                            :disabled="!isFieldWritable(row)"
                            class="editable-control"
                          />
                        </template>
                      </el-table-column>
                      <el-table-column label="操作" width="90" fixed="right">
                      <el-table-column label="操作" width="88" fixed="right">
                        <template #default="{ row }">
                          <el-button
                            type="primary"
@@ -262,6 +294,8 @@
                        </template>
                      </el-table-column>
                    </el-table>
                    </div>
                    </template>
                    <div v-else class="text-muted">当前DB块无字段映射</div>
                    <div class="card-header-title field-title">原始数据</div>
@@ -300,6 +334,10 @@
          />
        </div>
      </el-card>
        </el-col>
      </el-row>
        </div>
      </section>
    </div>
  </div>
</template>
@@ -1065,48 +1103,8 @@
</script>
<style scoped>
.page-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 20px;
  flex-wrap: wrap;
  gap: 16px;
}
.header-left h2 {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 8px 0;
}
.text-muted {
  color: #909399;
  margin: 0;
}
.loading-container {
  text-align: center;
  padding: 60px 0;
  color: #909399;
}
.loading-icon {
  animation: spin 1s linear infinite;
}
@keyframes spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}
.status-cards {
  margin-bottom: 20px;
  margin-bottom: 8px;
}
.status-card {
@@ -1119,13 +1117,17 @@
}
.mt-4 {
  margin-top: 16px;
  margin-top: 14px;
}
.action-buttons {
  display: flex;
  gap: 12px;
  gap: 10px;
  flex-wrap: wrap;
}
.left-actions {
  margin-top: 14px;
}
.db-header {
@@ -1142,7 +1144,7 @@
.db-content {
  margin: 0;
  max-height: 180px;
  max-height: 168px;
  overflow: auto;
  white-space: pre-wrap;
  font-family: Consolas, Monaco, 'Courier New', monospace;
@@ -1168,11 +1170,11 @@
}
.db-tabs {
  margin-top: 8px;
  margin-top: 10px;
}
.data-view-tabs {
  margin-top: 8px;
  margin-top: 10px;
}
.db-raw-toolbar {
@@ -1189,4 +1191,35 @@
  margin-top: 12px;
  margin-bottom: 8px;
}
.field-table-wrap {
  width: 100%;
  overflow-x: auto;
}
:deep(.field-table) {
  width: max-content;
  min-width: 100%;
}
:deep(.editable-control) {
  width: 100%;
}
:deep(.status-card .el-card__body) {
  padding: 14px 16px;
}
:deep(.action-buttons .el-button) {
  min-width: 82px;
}
:deep(.panel-card > .el-card__header) {
  padding: 14px 18px;
}
:deep(.panel-card > .el-card__body) {
  padding: 14px 18px;
}
</style>