From c493779a8504fe1eb548c865ff268a7f7436ec01 Mon Sep 17 00:00:00 2001
From: wanshenmean <cathay_xy@163.com>
Date: 星期四, 19 三月 2026 11:43:36 +0800
Subject: [PATCH] feat: 集成机械手客户端并重构模拟器前端工作台
---
Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/EditView.vue | 524 +++++++++++++++++++++++++++++++++-------------------------
1 files changed, 299 insertions(+), 225 deletions(-)
diff --git a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/EditView.vue b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/EditView.vue
index c1c857d..ecc26b1 100644
--- a/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/EditView.vue
+++ b/Code/WCS/WIDESEAWCS_S7Simulator/WIDESEAWCS_S7Simulator.Web/src/views/EditView.vue
@@ -1,202 +1,208 @@
-<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>
+锘�<template>
+ <div class="admin-page">
+ <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>
- <div class="d-flex justify-content-between align-items-center mb-4">
- <div>
- <h2 class="mb-0">
- <i class="bi bi-pencil me-2"></i>缂栬緫瀹炰緥
+ <div class="page-header">
+ <div class="header-left">
+ <h2>
+ <el-icon :size="24"><Edit /></el-icon>
+ 缂栬緫瀹炰緥
</h2>
- <p class="text-muted mb-0 mt-1">缂栬緫瀹炰緥閰嶇疆: {{ form.id }}</p>
+ <p class="text-muted">缂栬緫瀹炰緥閰嶇疆: {{ form.id }}</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 v-if="isRunning" class="alert alert-warning">
- <i class="bi bi-exclamation-triangle-fill me-2"></i>
+ <el-alert
+ v-if="isRunning"
+ type="warning"
+ :closable="false"
+ class="mb-4"
+ show-icon
+ >
瀹炰緥姝e湪杩愯涓紝淇敼閰嶇疆鍚庨渶瑕侀噸鍚疄渚嬫墠鑳界敓鏁�
- </div>
+ </el-alert>
- <div class="row justify-content-center">
- <div class="col-lg-8">
- <div class="card">
- <div class="card-body">
- <form @submit.prevent="handleSubmit">
- <!-- 鍩烘湰淇℃伅 -->
- <h5 class="card-title mb-3">鍩烘湰淇℃伅</h5>
- <div class="row mb-3">
- <div class="col-md-6">
- <label for="id" class="form-label">瀹炰緥ID</label>
- <input
- type="text"
- class="form-control"
- id="id"
- v-model="form.id"
- disabled
- >
- <div class="form-text">瀹炰緥ID鍒涘缓鍚庝笉鍙慨鏀�</div>
- </div>
- <div class="col-md-6">
- <label for="name" class="form-label">瀹炰緥鍚嶇О *</label>
- <input
- type="text"
- class="form-control"
- id="name"
- v-model="form.name"
- required
- >
- </div>
- </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>
- <div class="row mb-3">
- <div class="col-md-6">
- <label for="plcType" class="form-label">PLC鍨嬪彿 *</label>
- <select class="form-select" id="plcType" v-model="form.plcType" required>
- <option value="S7200Smart">S7-200 Smart</option>
- <option value="S71200">S7-1200</option>
- <option value="S71500">S7-1500</option>
- <option value="S7300">S7-300</option>
- <option value="S7400">S7-400</option>
- </select>
- </div>
- <div class="col-md-6">
- <label for="port" class="form-label">鐩戝惉绔彛 *</label>
- <input
- type="number"
- class="form-control"
- id="port"
- v-model.number="form.port"
- min="1"
- max="65535"
- required
- >
- </div>
- </div>
+ <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>
- <div class="row mb-3">
- <div class="col-md-6">
- <label for="activationKey" class="form-label">HSL婵�娲荤爜</label>
- <input
- type="text"
- class="form-control"
- id="activationKey"
- v-model="form.activationKey"
- >
- </div>
- <div class="col-md-6 d-flex align-items-center">
- <div class="form-check">
- <input class="form-check-input" type="checkbox" id="autoStart" v-model="form.autoStart">
- <label class="form-check-label" for="autoStart">
- 鑷姩鍚姩
- </label>
- </div>
- </div>
- </div>
+ <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>
- <!-- 鍐呭瓨閰嶇疆 -->
- <h5 class="card-title mb-3 mt-4">鍐呭瓨閰嶇疆</h5>
- <div class="row mb-3">
- <div class="col-md-4">
- <label for="mRegionSize" class="form-label">M鍖哄煙澶у皬</label>
- <input
- type="number"
- class="form-control"
- id="mRegionSize"
- v-model.number="form.mRegionSize"
- min="0"
- >
- </div>
- <div class="col-md-4">
- <label for="iRegionSize" class="form-label">I鍖哄煙澶у皬</label>
- <input
- type="number"
- class="form-control"
- id="iRegionSize"
- v-model.number="form.iRegionSize"
- min="0"
- >
- </div>
- <div class="col-md-4">
- <label for="qRegionSize" class="form-label">Q鍖哄煙澶у皬</label>
- <input
- type="number"
- class="form-control"
- id="qRegionSize"
- v-model.number="form.qRegionSize"
- min="0"
- >
- </div>
- </div>
+ <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>
- <div class="row mb-3">
- <div class="col-md-4">
- <label for="dbBlockCount" class="form-label">DB鍧楁暟閲�</label>
- <input
- type="number"
- class="form-control"
- id="dbBlockCount"
- v-model.number="form.dbBlockCount"
- min="0"
- >
- </div>
- <div class="col-md-4">
- <label for="dbBlockSize" class="form-label">DB鍧楀ぇ灏�</label>
- <input
- type="number"
- class="form-control"
- id="dbBlockSize"
- v-model.number="form.dbBlockSize"
- min="0"
- >
- </div>
- <div class="col-md-2">
- <label for="tRegionCount" class="form-label">瀹氭椂鍣ㄦ暟閲�</label>
- <input
- type="number"
- class="form-control"
- id="tRegionCount"
- v-model.number="form.tRegionCount"
- min="0"
- >
- </div>
- <div class="col-md-2">
- <label for="cRegionCount" class="form-label">璁℃暟鍣ㄦ暟閲�</label>
- <input
- type="number"
- class="form-control"
- id="cRegionCount"
- v-model.number="form.cRegionCount"
- min="0"
- >
- </div>
- </div>
+ <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>
- <!-- 鎻愪氦鎸夐挳 -->
- <div class="d-flex gap-2 mt-4">
- <button type="submit" class="btn btn-primary" :disabled="submitting">
- <span v-if="submitting" class="spinner-border spinner-border-sm me-1"></span>
- <i v-else class="bi bi-check-lg me-1"></i>
- {{ submitting ? '淇濆瓨涓�...' : '淇濆瓨鏇存敼' }}
- </button>
- <router-link to="/" class="btn btn-outline-secondary">鍙栨秷</router-link>
- </div>
- </form>
- </div>
- </div>
- </div>
- </div>
+ <!-- 鍐呭瓨閰嶇疆 -->
+ <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>
@@ -204,11 +210,15 @@
<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, SiemensPLCType } from '../types'
+import type { InstanceConfig, MemoryRegionConfig, ProtocolTemplate, SiemensPLCType } from '../types'
const router = useRouter()
const route = useRoute()
+const formRef = ref<FormInstance>()
const form = ref({
id: '',
@@ -217,9 +227,11 @@
port: 102,
activationKey: '',
autoStart: false,
+ protocolTemplateId: '',
mRegionSize: 1024,
- dbBlockCount: 100,
- dbBlockSize: 1024,
+ dbBlockCount: 0,
+ dbBlockNumbers: [] as Array<number | string>,
+ dbBlockSize: 65536,
iRegionSize: 256,
qRegionSize: 256,
tRegionCount: 64,
@@ -230,11 +242,30 @@
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) {
@@ -260,9 +291,11 @@
port: config.port,
activationKey: config.activationKey,
autoStart: config.autoStart,
+ protocolTemplateId: config.protocolTemplateId || '',
mRegionSize: config.memoryConfig.mRegionSize,
- dbBlockCount: config.memoryConfig.dBBBlockCount,
- dbBlockSize: config.memoryConfig.dBBBlockSize,
+ 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,
@@ -277,54 +310,95 @@
})
async function handleSubmit() {
- submitting.value = true
+ if (!formRef.value) return
- try {
- const memoryConfig: MemoryRegionConfig = {
- mRegionSize: form.value.mRegionSize > 0 ? form.value.mRegionSize : 1024,
- dBBBlockCount: form.value.dbBlockCount > 0 ? form.value.dbBlockCount : 100,
- dBBBlockSize: 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
+ 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('璇疯嚦灏戦厤缃竴涓狣B鍧楀彿锛屼緥濡� 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
}
+ })
+}
- 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,
- memoryConfig
- }
+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)
+}
- const result = await api.updateInstance(form.value.id, config)
-
- if (result) {
- alert(`瀹炰緥 "${form.value.id}" 鏇存柊鎴愬姛!`)
- router.push('/')
- } else {
- alert('鏇存柊瀹炰緥澶辫触')
- }
- } catch (err) {
- console.error('鏇存柊瀹炰緥澶辫触:', err)
- alert('鏇存柊瀹炰緥澶辫触锛岃鏌ョ湅鎺у埗鍙�')
- } finally {
- submitting.value = false
+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>
-.card-title {
- color: #495057;
- font-weight: 600;
+.form-tip {
+ font-size: 12px;
+ color: #909399;
+ margin-top: 4px;
}
-.form-label {
- font-weight: 500;
- color: #495057;
+.el-divider h3 {
+ margin: 0;
+ font-size: 16px;
+ font-weight: 600;
+ color: #303133;
+}
+
+.mb-4 {
+ margin-bottom: 16px;
}
</style>
--
Gitblit v1.9.3