<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>
|
|
<div v-else-if="errorMsg">
|
<div class="alert alert-danger">{{ errorMsg }}</div>
|
<router-link to="/" class="btn btn-primary">返回列表</router-link>
|
</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>实例详情
|
</h2>
|
<p class="text-muted mb-0 mt-1">{{ 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>
|
</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>
|
|
<!-- 详细信息 -->
|
<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>
|
|
<!-- 操作按钮 -->
|
<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>
|
</div>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<script setup lang="ts">
|
import { ref, onMounted, onUnmounted } from 'vue'
|
import { useRoute } from 'vue-router'
|
import * as api from '../api'
|
import type { InstanceState, InstanceStatus } from '../types'
|
|
const route = useRoute()
|
|
const instance = ref<InstanceState | null>(null)
|
const loading = ref(true)
|
const errorMsg = ref('')
|
|
let refreshTimer: number | null = null
|
|
const id = route.params.id as string
|
|
async function loadInstance() {
|
try {
|
instance.value = await api.getInstance(id)
|
if (!instance.value) {
|
errorMsg.value = `实例 "${id}" 不存在`
|
}
|
} catch (err) {
|
console.error('加载实例失败:', err)
|
errorMsg.value = '加载实例失败,请查看控制台'
|
} finally {
|
loading.value = false
|
}
|
}
|
|
onMounted(() => {
|
loadInstance()
|
// 每2秒刷新一次状态
|
refreshTimer = window.setInterval(() => {
|
if (instance.value && instance.value.status !== 'Stopped') {
|
loadInstance()
|
}
|
}, 2000)
|
})
|
|
onUnmounted(() => {
|
if (refreshTimer !== null) {
|
clearInterval(refreshTimer)
|
}
|
})
|
|
async function handleStart() {
|
if (confirm(`确定要启动实例 "${id}" 吗?`)) {
|
try {
|
await api.startInstance(id)
|
await loadInstance()
|
} catch (err) {
|
console.error('启动实例失败:', err)
|
alert('启动失败,请查看控制台')
|
}
|
}
|
}
|
|
async function handleStop() {
|
if (confirm(`确定要停止实例 "${id}" 吗?`)) {
|
try {
|
await api.stopInstance(id)
|
await loadInstance()
|
} catch (err) {
|
console.error('停止实例失败:', err)
|
alert('停止失败,请查看控制台')
|
}
|
}
|
}
|
|
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'
|
}
|
return map[status] || ''
|
}
|
|
function getStatusText(status: InstanceStatus): string {
|
const map: Record<InstanceStatus, string> = {
|
'Stopped': '已停止',
|
'Starting': '启动中',
|
'Running': '运行中',
|
'Stopping': '停止中',
|
'Error': '错误'
|
}
|
return map[status] || status
|
}
|
|
function getPlcTypeText(plcType: string): string {
|
const map: Record<string, string> = {
|
'S7200Smart': 'S7-200 Smart',
|
'S71200': 'S7-1200',
|
'S71500': 'S7-1500',
|
'S7300': 'S7-300',
|
'S7400': 'S7-400'
|
}
|
return map[plcType] || plcType
|
}
|
|
function formatDate(dateString: string | null): string {
|
if (!dateString) return '-'
|
const date = new Date(dateString)
|
return date.toLocaleString('zh-CN', {
|
year: 'numeric',
|
month: '2-digit',
|
day: '2-digit',
|
hour: '2-digit',
|
minute: '2-digit',
|
second: '2-digit'
|
})
|
}
|
</script>
|
|
<style scoped>
|
.card-subtitle {
|
font-size: 0.875rem;
|
font-weight: 600;
|
}
|
|
table th {
|
background-color: #f8f9fa;
|
font-weight: 600;
|
}
|
</style>
|