<template>
|
<div class="admin-page">
|
<div class="page-header">
|
<div class="header-left">
|
<h2>
|
<el-icon :size="24"><Plus /></el-icon>
|
创建实例
|
</h2>
|
<p class="text-muted">创建新的 S7 PLC 仿真实例</p>
|
</div>
|
<el-button @click="$router.push('/')">
|
<el-icon><Back /></el-icon>
|
返回列表
|
</el-button>
|
</div>
|
|
<el-row justify="center">
|
<el-col :lg="24">
|
<el-card shadow="never" class="panel-card">
|
<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" prop="id">
|
<el-input
|
v-model="form.id"
|
pattern="[a-zA-Z0-9_-]+"
|
placeholder="例如: PLC_001"
|
>
|
<template #append>
|
<el-tooltip content="只能包含字母、数字、下划线和连字符" placement="top">
|
<el-icon><QuestionFilled /></el-icon>
|
</el-tooltip>
|
</template>
|
</el-input>
|
</el-form-item>
|
</el-col>
|
<el-col :span="12">
|
<el-form-item label="实例名称" prop="name">
|
<el-input v-model="form.name" placeholder="例如: 1号PLC" />
|
</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" placeholder="可选,留空使用默认" />
|
</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>
|
</template>
|
|
<script setup lang="ts">
|
import { onMounted, ref } from 'vue'
|
import { useRouter } from 'vue-router'
|
import { ElMessage } from 'element-plus'
|
import type { FormInstance, FormRules } from 'element-plus'
|
import { Plus, Back, QuestionFilled } from '@element-plus/icons-vue'
|
import * as api from '../api'
|
import type { InstanceConfig, MemoryRegionConfig, ProtocolTemplate, SiemensPLCType } from '../types'
|
|
const router = useRouter()
|
const formRef = ref<FormInstance>()
|
|
const form = ref({
|
id: 'GWSC1',
|
name: '高温1号堆垛机',
|
plcType: 'S71200' as SiemensPLCType,
|
port: 102,
|
activationKey: '4b86f3fc-f650-3b08-5924-b0f8278d6ed2',
|
autoStart: false,
|
protocolTemplateId: '',
|
mRegionSize: 1024,
|
dbBlockCount: 0,
|
dbBlockNumbers: [] as Array<number | string>,
|
dbBlockSize: 65536,
|
iRegionSize: 256,
|
qRegionSize: 256,
|
tRegionCount: 64,
|
cRegionCount: 64
|
})
|
|
const submitting = ref(false)
|
const protocolTemplates = ref<ProtocolTemplate[]>([])
|
|
const rules: FormRules = {
|
id: [
|
{ required: true, message: '请输入实例ID', trigger: 'blur' },
|
{ pattern: /^[a-zA-Z0-9_-]+$/, message: '只能包含字母、数字、下划线和连字符', trigger: 'blur' }
|
],
|
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 () => {
|
protocolTemplates.value = await api.getProtocolTemplates()
|
if (protocolTemplates.value.length > 0) {
|
form.value.protocolTemplateId = protocolTemplates.value[0].id
|
}
|
})
|
|
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.createInstance(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)
|
}
|
</script>
|
|
<style scoped>
|
.el-divider h3 {
|
margin: 0;
|
font-size: 16px;
|
font-weight: 600;
|
color: #303133;
|
}
|
</style>
|