1
wanshenmean
2026-03-16 689dd676fc0efb31236d989334122590b7198d61
Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/DetailsView.vue
@@ -1,137 +1,118 @@
<template>
  <div>
    <div v-if="loading" class="text-center py-5">
      <div class="spinner-border text-primary" role="status">
        <span class="visually-hidden">加载中...</span>
      </div>
    <div v-if="loading" class="loading-container">
      <el-icon class="loading-icon" :size="40"><Loading /></el-icon>
      <p>加载中...</p>
    </div>
    <div v-else-if="errorMsg">
      <div class="alert alert-danger">{{ errorMsg }}</div>
      <router-link to="/" class="btn btn-primary">返回列表</router-link>
      <el-result icon="error" :title="errorMsg">
        <template #extra>
          <el-button type="primary" @click="$router.push('/')">返回列表</el-button>
        </template>
      </el-result>
    </div>
    <div v-else-if="instance">
      <div class="d-flex justify-content-between align-items-center mb-4">
        <div>
          <h2 class="mb-0">
            <i class="bi bi-info-circle me-2"></i>实例详情
      <div class="page-header">
        <div class="header-left">
          <h2>
            <el-icon :size="24"><InfoFilled /></el-icon>
            实例详情
          </h2>
          <p class="text-muted mb-0 mt-1">{{ instance.name }} ({{ instance.instanceId }})</p>
          <p class="text-muted">{{ instance.name }} ({{ instance.instanceId }})</p>
        </div>
        <router-link to="/" class="btn btn-outline-secondary">
          <i class="bi bi-arrow-left me-1"></i>返回列表
        </router-link>
        <el-button @click="$router.push('/')">
          <el-icon><Back /></el-icon>
          返回列表
        </el-button>
      </div>
      <!-- 状态卡片 -->
      <div class="row mb-4">
        <div class="col-md-3">
          <div class="card text-center">
            <div class="card-body">
              <h6 class="card-subtitle mb-2 text-muted">状态</h6>
              <h4 :class="['mb-0', getStatusClass(instance.status)]">
                {{ getStatusText(instance.status) }}
              </h4>
            </div>
          </div>
        </div>
        <div class="col-md-3">
          <div class="card text-center">
            <div class="card-body">
              <h6 class="card-subtitle mb-2 text-muted">连接客户端</h6>
              <h4 class="mb-0">
                <i class="bi bi-people-fill me-1"></i>{{ instance.clientCount }}
              </h4>
            </div>
          </div>
        </div>
        <div class="col-md-3">
          <div class="card text-center">
            <div class="card-body">
              <h6 class="card-subtitle mb-2 text-muted">总请求数</h6>
              <h4 class="mb-0">{{ instance.totalRequests }}</h4>
            </div>
          </div>
        </div>
        <div class="col-md-3">
          <div class="card text-center">
            <div class="card-body">
              <h6 class="card-subtitle mb-2 text-muted">端口</h6>
              <h4 class="mb-0">{{ instance.port }}</h4>
            </div>
          </div>
        </div>
      </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>
      <!-- 详细信息 -->
      <div class="card mb-4">
        <div class="card-header">
          <h5 class="mb-0">基本信息</h5>
        </div>
        <div class="card-body">
          <table class="table table-bordered">
            <tbody>
              <tr>
                <th style="width: 30%">实例ID</th>
                <td>{{ instance.instanceId }}</td>
              </tr>
              <tr>
                <th>实例名称</th>
                <td>{{ instance.name }}</td>
              </tr>
              <tr>
                <th>PLC型号</th>
                <td>{{ getPlcTypeText(instance.plcType) }}</td>
              </tr>
              <tr>
                <th>监听端口</th>
                <td>{{ instance.port }}</td>
              </tr>
              <tr v-if="instance.startTime">
                <th>启动时间</th>
                <td>{{ formatDate(instance.startTime) }}</td>
              </tr>
              <tr v-if="instance.lastActivityTime">
                <th>最后活动时间</th>
                <td>{{ formatDate(instance.lastActivityTime) }}</td>
              </tr>
              <tr v-if="instance.errorMessage">
                <th>错误信息</th>
                <td class="text-danger">{{ instance.errorMessage }}</td>
              </tr>
            </tbody>
          </table>
        </div>
      </div>
      <el-card class="mt-4" shadow="never">
        <template #header>
          <span class="card-header-title">基本信息</span>
        </template>
        <el-descriptions :column="2" 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>
          <el-descriptions-item label="监听端口">{{ instance.port }}</el-descriptions-item>
          <el-descriptions-item v-if="instance.startTime" label="启动时间">
            {{ formatDate(instance.startTime) }}
          </el-descriptions-item>
          <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-text type="danger">{{ instance.errorMessage }}</el-text>
          </el-descriptions-item>
        </el-descriptions>
      </el-card>
      <!-- 操作按钮 -->
      <div class="card">
        <div class="card-body">
          <div class="d-flex gap-2">
            <button
              v-if="instance.status === 'Stopped' || instance.status === 'Error'"
              class="btn btn-success"
              @click="handleStart"
            >
              <i class="bi bi-play-fill me-1"></i>启动
            </button>
            <button
              v-if="instance.status === 'Running'"
              class="btn btn-warning"
              @click="handleStop"
            >
              <i class="bi bi-stop-fill me-1"></i>停止
            </button>
            <router-link :to="`/edit/${instance.instanceId}`" class="btn btn-primary">
              <i class="bi bi-pencil-fill me-1"></i>编辑
            </router-link>
            <router-link to="/" class="btn btn-outline-secondary">
              <i class="bi bi-arrow-left me-1"></i>返回列表
            </router-link>
          </div>
      <el-card class="mt-4" 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>
            启动
          </el-button>
          <el-button
            v-if="instance.status === 'Running'"
            type="warning"
            @click="handleStop"
          >
            <el-icon><VideoPause /></el-icon>
            停止
          </el-button>
          <el-button type="primary" @click="$router.push(`/edit/${instance.instanceId}`)">
            <el-icon><Edit /></el-icon>
            编辑
          </el-button>
          <el-button @click="$router.push('/')">
            <el-icon><Back /></el-icon>
            返回列表
          </el-button>
        </div>
      </div>
      </el-card>
    </div>
  </div>
</template>
@@ -139,6 +120,16 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { useRoute } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
  InfoFilled,
  Back,
  Loading,
  User,
  VideoPlay,
  VideoPause,
  Edit
} from '@element-plus/icons-vue'
import * as api from '../api'
import type { InstanceState, InstanceStatus } from '../types'
@@ -183,38 +174,50 @@
})
async function handleStart() {
  if (confirm(`确定要启动实例 "${id}" 吗?`)) {
    try {
      await api.startInstance(id)
      await loadInstance()
    } catch (err) {
  try {
    await ElMessageBox.confirm(`确定要启动实例 "${id}" 吗?`, '确认', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'info'
    })
    await api.startInstance(id)
    await loadInstance()
    ElMessage.success('启动命令已发送')
  } catch (err) {
    if (err !== 'cancel') {
      console.error('启动实例失败:', err)
      alert('启动失败,请查看控制台')
      ElMessage.error('启动失败,请查看控制台')
    }
  }
}
async function handleStop() {
  if (confirm(`确定要停止实例 "${id}" 吗?`)) {
    try {
      await api.stopInstance(id)
      await loadInstance()
    } catch (err) {
  try {
    await ElMessageBox.confirm(`确定要停止实例 "${id}" 吗?`, '确认', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    })
    await api.stopInstance(id)
    await loadInstance()
    ElMessage.success('停止命令已发送')
  } catch (err) {
    if (err !== 'cancel') {
      console.error('停止实例失败:', err)
      alert('停止失败,请查看控制台')
      ElMessage.error('停止失败,请查看控制台')
    }
  }
}
function getStatusClass(status: InstanceStatus): string {
  const map: Record<InstanceStatus, string> = {
    'Stopped': 'text-secondary',
    'Starting': 'text-info',
    'Running': 'text-success',
    'Stopping': 'text-warning',
    'Error': 'text-danger'
function getStatusTagType(status: InstanceStatus): 'success' | 'info' | 'warning' | 'danger' {
  const map: Record<InstanceStatus, 'success' | 'info' | 'warning' | 'danger'> = {
    'Stopped': 'info',
    'Starting': 'info',
    'Running': 'success',
    'Stopping': 'warning',
    'Error': 'danger'
  }
  return map[status] || ''
  return map[status] || 'info'
}
function getStatusText(status: InstanceStatus): string {
@@ -254,13 +257,66 @@
</script>
<style scoped>
.card-subtitle {
  font-size: 0.875rem;
  font-weight: 600;
.page-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 20px;
  flex-wrap: wrap;
  gap: 16px;
}
table th {
  background-color: #f8f9fa;
.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;
}
.status-card {
  text-align: center;
}
.card-header-title {
  font-weight: 600;
  font-size: 16px;
}
.mt-4 {
  margin-top: 16px;
}
.action-buttons {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
}
</style>