<template>
|
<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">
|
<el-result icon="error" :title="errorMsg">
|
<template #extra>
|
<el-button type="primary" @click="$router.push('/')">返回列表</el-button>
|
</template>
|
</el-result>
|
</div>
|
|
<div v-else>
|
<div class="page-header">
|
<div class="header-left">
|
<h2>
|
<el-icon :size="24"><Edit /></el-icon>
|
编辑实例
|
</h2>
|
<p class="text-muted">编辑实例配置: {{ form.id }}</p>
|
</div>
|
<el-button @click="$router.push('/')">
|
<el-icon><Back /></el-icon>
|
返回列表
|
</el-button>
|
</div>
|
|
<el-alert
|
v-if="isRunning"
|
type="warning"
|
:closable="false"
|
class="mb-4"
|
show-icon
|
>
|
实例正在运行中,修改配置后需要重启实例才能生效
|
</el-alert>
|
|
<el-row justify="center">
|
<el-col :lg="24">
|
<el-card shadow="never">
|
<el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
|
<!-- 基本信息 -->
|
<el-divider content-position="left">
|
<h3>基本信息</h3>
|
</el-divider>
|
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-form-item label="实例ID">
|
<el-input v-model="form.id" disabled />
|
<div class="form-tip">实例ID创建后不可修改</div>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="实例名称" prop="name">
|
<el-input v-model="form.name" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-form-item label="PLC型号" prop="plcType">
|
<el-select v-model="form.plcType" style="width: 100%">
|
<el-option label="S7-200 Smart" value="S7200Smart" />
|
<el-option label="S7-1200" value="S71200" />
|
<el-option label="S7-1500" value="S71500" />
|
<el-option label="S7-300" value="S7300" />
|
<el-option label="S7-400" value="S7400" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="监听端口" prop="port">
|
<el-input-number
|
v-model="form.port"
|
:min="1"
|
:max="65535"
|
style="width: 100%"
|
/>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-form-item label="HSL激活码">
|
<el-input v-model="form.activationKey" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="自动启动">
|
<el-switch v-model="form.autoStart" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
|
<el-row :gutter="20">
|
<el-col :span="12">
|
<el-form-item label="协议模板" prop="protocolTemplateId">
|
<el-select v-model="form.protocolTemplateId" style="width: 100%">
|
<el-option
|
v-for="tpl in protocolTemplates"
|
:key="tpl.id"
|
:label="`${tpl.name} (${tpl.id})`"
|
:value="tpl.id"
|
/>
|
</el-select>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
|
<!-- 内存配置 -->
|
<el-divider content-position="left">
|
<h3>内存配置</h3>
|
</el-divider>
|
|
<el-row :gutter="20">
|
<el-col :span="8">
|
<el-form-item label="M区大小">
|
<el-input-number
|
v-model="form.mRegionSize"
|
:min="0"
|
style="width: 100%"
|
/>
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="I区大小">
|
<el-input-number
|
v-model="form.iRegionSize"
|
:min="0"
|
style="width: 100%"
|
/>
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="Q区大小">
|
<el-input-number
|
v-model="form.qRegionSize"
|
:min="0"
|
style="width: 100%"
|
/>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
|
<el-row :gutter="20">
|
<el-col :span="8">
|
<el-form-item label="DB块列表">
|
<el-select
|
v-model="form.dbBlockNumbers"
|
multiple
|
filterable
|
allow-create
|
default-first-option
|
:reserve-keyword="false"
|
style="width: 100%"
|
placeholder="输入块号后回车,例如 50、900、901"
|
/>
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="DB块大小">
|
<el-input-number
|
v-model="form.dbBlockSize"
|
:min="0"
|
style="width: 100%"
|
/>
|
</el-form-item>
|
</el-col>
|
<el-col :span="4">
|
<el-form-item label="定时器数量">
|
<el-input-number
|
v-model="form.tRegionCount"
|
:min="0"
|
style="width: 100%"
|
/>
|
</el-form-item>
|
</el-col>
|
<el-col :span="4">
|
<el-form-item label="计数器数量">
|
<el-input-number
|
v-model="form.cRegionCount"
|
:min="0"
|
style="width: 100%"
|
/>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
|
<!-- 提交按钮 -->
|
<el-form-item>
|
<el-button type="primary" @click="handleSubmit" :loading="submitting">
|
{{ submitting ? '保存中...' : '保存更改' }}
|
</el-button>
|
<el-button @click="$router.push('/')">取消</el-button>
|
</el-form-item>
|
</el-form>
|
</el-card>
|
</el-col>
|
</el-row>
|
</div>
|
</div>
|
</template>
|
|
<script setup lang="ts">
|
import { ref, onMounted } from 'vue'
|
import { useRouter, useRoute } from 'vue-router'
|
import { ElMessage } from 'element-plus'
|
import type { FormInstance, FormRules } from 'element-plus'
|
import { Edit, Back, Loading } from '@element-plus/icons-vue'
|
import * as api from '../api'
|
import type { InstanceConfig, MemoryRegionConfig, ProtocolTemplate, SiemensPLCType } from '../types'
|
|
const router = useRouter()
|
const route = useRoute()
|
const formRef = ref<FormInstance>()
|
|
const form = ref({
|
id: '',
|
name: '',
|
plcType: 'S71200' as SiemensPLCType,
|
port: 102,
|
activationKey: '',
|
autoStart: false,
|
protocolTemplateId: '',
|
mRegionSize: 1024,
|
dbBlockCount: 0,
|
dbBlockNumbers: [] as Array<number | string>,
|
dbBlockSize: 65536,
|
iRegionSize: 256,
|
qRegionSize: 256,
|
tRegionCount: 64,
|
cRegionCount: 64
|
})
|
|
const loading = ref(true)
|
const errorMsg = ref('')
|
const submitting = ref(false)
|
const isRunning = ref(false)
|
const protocolTemplates = ref<ProtocolTemplate[]>([])
|
|
const id = route.params.id as string
|
|
const rules: FormRules = {
|
name: [
|
{ required: true, message: '请输入实例名称', trigger: 'blur' }
|
],
|
plcType: [
|
{ required: true, message: '请选择PLC型号', trigger: 'change' }
|
],
|
port: [
|
{ required: true, message: '请输入监听端口', trigger: 'blur' },
|
{ type: 'number', min: 1, max: 65535, message: '端口必须在 1-65535 之间', trigger: 'blur' }
|
],
|
protocolTemplateId: [
|
{ required: true, message: '请选择协议模板', trigger: 'change' }
|
]
|
}
|
|
onMounted(async () => {
|
try {
|
protocolTemplates.value = await api.getProtocolTemplates()
|
|
// 获取实例状态
|
const state = await api.getInstance(id)
|
if (!state) {
|
errorMsg.value = `实例 "${id}" 不存在`
|
loading.value = false
|
return
|
}
|
|
isRunning.value = state.status === 'Running'
|
|
// 获取实例配置
|
const config = await api.getInstanceConfig(id)
|
if (!config) {
|
errorMsg.value = `无法加载实例 "${id}" 的配置`
|
loading.value = false
|
return
|
}
|
|
form.value = {
|
id: config.id,
|
name: config.name,
|
plcType: config.plcType,
|
port: config.port,
|
activationKey: config.activationKey,
|
autoStart: config.autoStart,
|
protocolTemplateId: config.protocolTemplateId || '',
|
mRegionSize: config.memoryConfig.mRegionSize,
|
dbBlockCount: 0,
|
dbBlockNumbers: toDbBlockNumbers(config.memoryConfig.dbBlockNumbers, config.memoryConfig.dbBlockCount),
|
dbBlockSize: config.memoryConfig.dbBlockSize,
|
iRegionSize: config.memoryConfig.iRegionSize,
|
qRegionSize: config.memoryConfig.qRegionSize,
|
tRegionCount: config.memoryConfig.tRegionCount,
|
cRegionCount: config.memoryConfig.cRegionCount
|
}
|
} catch (err) {
|
console.error('加载实例配置失败:', err)
|
errorMsg.value = '加载实例配置失败,请查看控制台'
|
} finally {
|
loading.value = false
|
}
|
})
|
|
async function handleSubmit() {
|
if (!formRef.value) return
|
|
await formRef.value.validate(async (valid) => {
|
if (!valid) return
|
|
submitting.value = true
|
|
try {
|
const dbBlockNumbers = normalizeDbBlockNumbers(form.value.dbBlockNumbers)
|
if (dbBlockNumbers.length === 0) {
|
ElMessage.error('请至少配置一个DB块号,例如 50,900,901')
|
return
|
}
|
|
const memoryConfig: MemoryRegionConfig = {
|
mRegionSize: form.value.mRegionSize > 0 ? form.value.mRegionSize : 1024,
|
dbBlockCount: 0,
|
dbBlockNumbers,
|
dbBlockSize: form.value.dbBlockSize > 0 ? form.value.dbBlockSize : 1024,
|
iRegionSize: form.value.iRegionSize > 0 ? form.value.iRegionSize : 256,
|
qRegionSize: form.value.qRegionSize > 0 ? form.value.qRegionSize : 256,
|
tRegionCount: form.value.tRegionCount > 0 ? form.value.tRegionCount : 64,
|
cRegionCount: form.value.cRegionCount > 0 ? form.value.cRegionCount : 64
|
}
|
|
const config: InstanceConfig = {
|
id: form.value.id,
|
name: form.value.name,
|
plcType: form.value.plcType,
|
port: form.value.port,
|
activationKey: form.value.activationKey,
|
autoStart: form.value.autoStart,
|
protocolTemplateId: form.value.protocolTemplateId,
|
memoryConfig
|
}
|
|
const result = await api.updateInstance(form.value.id, config)
|
|
if (result) {
|
ElMessage.success(`实例 "${form.value.id}" 更新成功!`)
|
router.push('/')
|
} else {
|
ElMessage.error('更新实例失败')
|
}
|
} catch (err) {
|
console.error('更新实例失败:', err)
|
ElMessage.error('更新实例失败,请查看控制台')
|
} finally {
|
submitting.value = false
|
}
|
})
|
}
|
|
function normalizeDbBlockNumbers(input: Array<number | string>): number[] {
|
return Array.from(new Set(
|
input
|
.map(x => Number(String(x).trim()))
|
.filter(x => Number.isInteger(x) && x > 0)
|
)).sort((a, b) => a - b)
|
}
|
|
function toDbBlockNumbers(dbBlockNumbers: number[] | undefined, dbBlockCount: number): Array<number | string> {
|
if (dbBlockNumbers && dbBlockNumbers.length > 0) {
|
return dbBlockNumbers
|
}
|
|
if (dbBlockCount > 0) {
|
return Array.from({ length: dbBlockCount }, (_, idx) => idx + 1)
|
}
|
|
return []
|
}
|
</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);
|
}
|
}
|
|
.form-tip {
|
font-size: 12px;
|
color: #909399;
|
margin-top: 4px;
|
}
|
|
.el-divider h3 {
|
margin: 0;
|
font-size: 16px;
|
font-weight: 600;
|
color: #303133;
|
}
|
|
.mb-4 {
|
margin-bottom: 16px;
|
}
|
</style>
|