wanshenmean
2026-03-19 c493779a8504fe1eb548c865ff268a7f7436ec01
Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/HomeView.vue
@@ -1,5 +1,6 @@
<template>
  <div
    class="admin-page"
    v-loading.fullscreen.lock="startActionLoading"
    element-loading-text="正在启动实例,请稍候..."
  >
@@ -12,16 +13,43 @@
        <p class="text-muted">管理和监控 S7 PLC 模拟器实例</p>
      </div>
      <div class="header-right">
        <div class="stats">
          <span>运行中: {{ runningCount }} | 已停止: {{ stoppedCount }}</span>
          <span v-if="errorCount > 0" class="error-text">| 错误: {{ errorCount }}</span>
        </div>
        <el-button type="primary" @click="$router.push('/create')">
        <el-button type="primary" class="create-btn" @click="$router.push('/create')">
          <el-icon><Plus /></el-icon>
          创建实例
        </el-button>
      </div>
    </div>
    <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="summary-row">
          <el-col :xs="24" :sm="8">
            <el-card shadow="hover" class="summary-card running-card">
              <div class="summary-title">运行中</div>
              <div class="summary-value">{{ runningCount }}</div>
            </el-card>
          </el-col>
          <el-col :xs="24" :sm="8">
            <el-card shadow="hover" class="summary-card stopped-card">
              <div class="summary-title">已停止</div>
              <div class="summary-value">{{ stoppedCount }}</div>
            </el-card>
          </el-col>
          <el-col :xs="24" :sm="8">
            <el-card shadow="hover" class="summary-card error-card">
              <div class="summary-title">错误实例</div>
              <div class="summary-value">{{ errorCount }}</div>
            </el-card>
          </el-col>
        </el-row>
      </div>
    </section>
    <!-- Loading state -->
    <div v-if="loading && instances.length === 0" class="loading-container">
@@ -35,45 +63,75 @@
    </el-empty>
    <!-- Instances grid -->
    <el-row v-else :gutter="20">
      <el-col v-for="instance in instances" :key="instance.instanceId" :xs="24" :sm="12" :xl="8">
        <el-card class="instance-card" :class="getStatusClass(instance.status)" shadow="hover">
          <template #header>
            <div class="card-header">
              <span class="instance-id">{{ instance.instanceId }}</span>
              <el-tag :type="getStatusTagType(instance.status)">
                {{ getStatusText(instance.status) }}
              </el-tag>
    <section v-else 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="14" class="instances-grid">
          <el-col
            v-for="instance in instances"
            :key="instance.instanceId"
            :xs="24"
            :sm="12"
            :md="12"
            :lg="8"
            :xl="6"
          >
            <el-card class="instance-card panel-card" :class="getStatusClass(instance.status)" shadow="hover">
          <div class="card-glow"></div>
          <div class="card-top">
            <div class="card-title">
              <div class="instance-id-line">
                <span class="instance-id">{{ instance.instanceId }}</span>
                <span class="instance-name">{{ instance.name || '未命名实例' }}</span>
                <span class="instance-sub">PLC</span>
              </div>
            </div>
          </template>
          <div class="instance-info">
            <el-descriptions :column="2" size="small">
              <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 label="客户端">
                <el-icon><User /></el-icon>
                {{ instance.clientCount || 0 }}
              </el-descriptions-item>
              <el-descriptions-item v-if="instance.startTime" label="启动时间" :span="2">
                {{ formatDate(instance.startTime) }}
              </el-descriptions-item>
            </el-descriptions>
            <el-alert
              v-if="instance.errorMessage"
              type="error"
              :closable="false"
              class="mt-2"
              show-icon
            >
              {{ instance.errorMessage }}
            </el-alert>
            <el-tag :type="getStatusTagType(instance.status)" effect="dark" round>
              {{ getStatusText(instance.status) }}
            </el-tag>
          </div>
          <template #footer>
            <div class="card-footer">
          <div class="meta-row">
              <div class="meta-chip">
                <span class="chip-label">PLC</span>
                <span class="chip-value">{{ getPlcTypeText(instance.plcType) }}</span>
              </div>
            <div class="meta-chip">
              <span class="chip-label">端口</span>
              <span class="chip-value">{{ instance.port || '-' }}</span>
            </div>
            <div class="meta-chip">
              <span class="chip-label">客户端</span>
              <span class="chip-value">
                <el-icon><User /></el-icon>
                {{ instance.clientCount || 0 }}
              </span>
            </div>
          </div>
          <div class="time-row">
            <span class="time-label">启动时间</span>
            <span class="time-value">{{ instance.startTime ? formatDate(instance.startTime) : '-' }}</span>
          </div>
          <el-alert
            v-if="instance.errorMessage"
            type="error"
            :closable="false"
            class="mt-2"
            show-icon
          >
            {{ instance.errorMessage }}
          </el-alert>
          <div class="card-footer">
            <div class="main-actions">
              <el-button
                v-if="instance.status === 'Running'"
                type="warning"
@@ -98,14 +156,16 @@
                <el-icon><Edit /></el-icon>
                编辑
              </el-button>
              <el-button type="danger" @click="handleDelete(instance.instanceId)">
                <el-icon><Delete /></el-icon>
              </el-button>
            </div>
          </template>
        </el-card>
      </el-col>
    </el-row>
            <el-button class="delete-btn" type="danger" @click="handleDelete(instance.instanceId)">
              <el-icon><Delete /></el-icon>
            </el-button>
          </div>
            </el-card>
          </el-col>
        </el-row>
      </div>
    </section>
  </div>
</template>
@@ -246,106 +306,220 @@
</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;
}
.header-right {
  display: flex;
  align-items: center;
  gap: 16px;
  gap: 10px;
  flex-wrap: wrap;
}
.stats {
  color: #606266;
  font-size: 14px;
.create-btn {
  padding-inline: 18px;
}
.error-text {
  color: #f56c6c;
.summary-row {
  margin-top: -2px;
  margin-bottom: 2px;
}
.loading-container {
  text-align: center;
  padding: 60px 0;
  color: #909399;
.instances-grid {
  max-width: 1500px;
}
.loading-icon {
  animation: spin 1s linear infinite;
.summary-card {
  border-radius: 12px;
  border: 1px solid #dbe2ea;
  overflow: hidden;
}
@keyframes spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
.summary-title {
  color: #64748b;
  font-size: 13px;
  margin-bottom: 6px;
}
.summary-value {
  font-size: 28px;
  font-weight: 700;
  line-height: 1;
}
.running-card {
  background: linear-gradient(180deg, #f0fdf4 0%, #ffffff 100%);
}
.running-card .summary-value {
  color: #15803d;
}
.stopped-card {
  background: linear-gradient(180deg, #f8fafc 0%, #ffffff 100%);
}
.stopped-card .summary-value {
  color: #334155;
}
.error-card {
  background: linear-gradient(180deg, #fef2f2 0%, #ffffff 100%);
}
.error-card .summary-value {
  color: #b91c1c;
}
.instance-card {
  margin-bottom: 20px;
  transition: all 0.3s;
  margin-bottom: 10px;
  position: relative;
  border-radius: 12px;
  border: 1px solid #dbe2ea;
  transition: all 0.25s;
  overflow: hidden;
}
.instance-card:hover {
  transform: translateY(-4px);
  transform: translateY(-3px);
  box-shadow: 0 14px 26px rgba(15, 23, 42, 0.1);
}
.card-header {
.card-glow {
  position: absolute;
  top: -56px;
  right: -56px;
  width: 100px;
  height: 100px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(14, 116, 244, 0.16), transparent 70%);
  pointer-events: none;
}
.card-title {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}
.instance-id {
  font-weight: 600;
  font-size: 16px;
  font-size: 15px;
  color: #0f172a;
  white-space: nowrap;
}
.instance-info {
  margin-bottom: 16px;
.instance-name {
  color: #64748b;
  font-size: 12px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.card-footer {
.instance-id-line {
  display: flex;
  align-items: baseline;
  gap: 6px;
  min-width: 0;
}
.instance-sub {
  color: #94a3b8;
  font-size: 10px;
}
.card-top {
  margin-top: 2px;
  display: flex;
  justify-content: space-between;
  gap: 8px;
  align-items: flex-start;
}
.meta-row {
  margin-top: 8px;
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}
.card-footer .el-button {
  flex: 1;
  min-width: 60px;
.meta-chip {
  border: 1px solid #e2e8f0;
  border-radius: 999px;
  padding: 4px 8px;
  background: #f8fafc;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.status-stopped { border-left: 4px solid #909399; }
.status-starting { border-left: 4px solid #409eff; }
.status-running { border-left: 4px solid #67c23a; }
.status-stopping { border-left: 4px solid #e6a23c; }
.status-error { border-left: 4px solid #f56c6c; }
.chip-label {
  font-size: 10px;
  line-height: 1;
  color: #64748b;
  padding: 1px 5px;
  border-radius: 999px;
  background: #e2e8f0;
}
.chip-value {
  font-size: 12px;
  line-height: 1;
  color: #0f172a;
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.time-row {
  margin-top: 7px;
  margin-bottom: 8px;
  display: flex;
  justify-content: space-between;
  gap: 8px;
  font-size: 11px;
}
.time-label {
  color: #64748b;
}
.time-value {
  color: #0f172a;
}
.card-footer {
  border-top: 1px dashed #dbe2ea;
  padding-top: 8px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
}
.main-actions {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}
.main-actions .el-button {
  border-radius: 8px;
  height: 28px;
  padding: 0 10px;
}
.delete-btn {
  border-radius: 8px;
  height: 28px;
  padding: 0 10px;
}
.mt-2 {
  margin-top: 8px;
}
</style>