From 2812ee9935895524e37415a056baa65b845191ff Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期四, 08 十月 2026 13:56:31 +0800
Subject: [PATCH] 实现 Ticket04:采集器登记改为手工填写并链接来源,拆除模拟器残留控制入口

---
 src/views/DeviceCollectorForm.vue |  128 ++++++++++--------------------------------
 1 files changed, 30 insertions(+), 98 deletions(-)

diff --git a/src/views/DeviceCollectorForm.vue b/src/views/DeviceCollectorForm.vue
index 3937edb..292476a 100644
--- a/src/views/DeviceCollectorForm.vue
+++ b/src/views/DeviceCollectorForm.vue
@@ -2,7 +2,8 @@
 import { computed, onMounted, reactive, ref } from 'vue'
 import { ElMessage } from 'element-plus'
 import { useRoute, useRouter } from 'vue-router'
-import { createDeviceCollector, getDeviceCollector, getDiscoveredCollectorList, updateDeviceCollector } from '@/api/deviceCollectors'
+import { createDeviceCollector, getDeviceCollector, updateDeviceCollector } from '@/api/deviceCollectors'
+import { getDeviceAdapterOptions, type DeviceAdapterOption } from '@/api/deviceAdapters'
 import type { CollectorPayload } from '@/stores/deviceCollectors'
 import DeviceRelatedTests from '@/components/DeviceRelatedTests.vue'
 
@@ -10,18 +11,13 @@
 const router = useRouter()
 const loading = ref(false)
 const saving = ref(false)
-const deviceDialogVisible = ref(false)
-const deviceLoading = ref(false)
-const devicePage = ref(1)
-const devicePageSize = 5
-const devices = ref<CollectorPayload[]>([])
-const deviceTotal = ref(0)
+const adapterOptions = ref<DeviceAdapterOption[]>([])
 const channelOptions = Array.from({ length: 99 }, (_, index) => index + 1)
 const editingId = computed(() => Number(route.params.id) || null)
 const isEditing = computed(() => editingId.value !== null)
 
 const form = reactive<CollectorPayload>({
-  deviceCode: '', ip: '', mac: '', name: '', manufacturer: '', model: '', channelCount: 1,
+  adapterId: null, deviceCode: '', ip: '', mac: '', name: '', manufacturer: '', model: '', channelCount: 1,
   remark: '', onlineStatus: '离线', relatedTests: [], status: '启用',
 })
 
@@ -38,55 +34,27 @@
   }
 }
 
-onMounted(loadEditingCollector)
-
-async function loadDiscoveredCollectors() {
-  deviceLoading.value = true
+/**
+ * 采集器不再有发现候选:真实来源不产出采集器候选,登记由操作员手工填写来源身份。
+ * 这里只加载可选的数据来源实例(采集器的辅助测量由它提供);读不到时给出可见错误,
+ * 不能把权限拒绝或网络失败显示成"没有来源"。
+ */
+async function loadAdapterOptions() {
   try {
-    const result = await getDiscoveredCollectorList(devicePage.value, devicePageSize)
-    devices.value = result.rows
-    deviceTotal.value = result.total
+    adapterOptions.value = await getDeviceAdapterOptions()
   } catch (error) {
-    devices.value = []
-    deviceTotal.value = 0
-    ElMessage.error(error instanceof Error ? error.message : '可发现采集器加载失败')
-  } finally {
-    deviceLoading.value = false
+    adapterOptions.value = []
+    ElMessage.error(error instanceof Error ? error.message : '数据来源列表加载失败')
   }
 }
 
-async function openDeviceDialog() {
-  devicePage.value = 1
-  deviceDialogVisible.value = true
-  await loadDiscoveredCollectors()
-}
-
-async function handleDevicePageChange(page: number) {
-  devicePage.value = page
-  await loadDiscoveredCollectors()
-}
-
-function selectDevice(device: CollectorPayload) {
-  Object.assign(form, {
-    deviceCode: device.deviceCode,
-    ip: device.ip,
-    mac: device.mac,
-    name: device.name,
-    manufacturer: device.manufacturer,
-    model: device.model,
-    channelCount: device.channelCount,
-    onlineStatus: device.onlineStatus,
-    relatedTests: [],
-  })
-  deviceDialogVisible.value = false
-  ElMessage.success(`已选择设备 ${device.deviceCode}`)
-}
+onMounted(async () => {
+  await Promise.all([loadEditingCollector(), loadAdapterOptions()])
+})
 
 function validate() {
-  if (!form.deviceCode) return '请先选择设备'
+  if (!form.deviceCode.trim()) return '请输入设备编码'
   if (!form.name.trim()) return '请输入采集器名称'
-  if (!form.manufacturer.trim()) return '请输入制造商'
-  if (!form.model.trim()) return '请输入型号'
   if (!Number.isInteger(form.channelCount) || form.channelCount < 1 || form.channelCount > 99) return '请选择 1~99 的通道数'
   return ''
 }
@@ -118,40 +86,25 @@
 
     <section v-loading="loading" class="detail-panel">
       <header class="panel-heading"><div><i></i><strong>采集器详情</strong><span>TEST COLLECTOR DETAILS</span></div><small><b>*</b> 为必填项</small></header>
-      <div class="device-select-row"><button class="primary-button" type="button" @click="openDeviceDialog"><el-icon><Connection /></el-icon>选择设备</button><span>显示当前网段全部可发现设备</span></div>
-
-      <div class="device-summary">
-        <div><span>设备编码</span><strong>{{ form.deviceCode || '-' }}</strong></div>
-        <div><span>IP</span><strong class="mono-value">{{ form.ip || '-' }}</strong></div>
-        <div><span>MAC</span><strong class="mono-value">{{ form.mac || '-' }}</strong></div>
-        <div><span>在线状态</span><strong :class="form.onlineStatus === '在线' ? 'online-value' : 'offline-value'">{{ form.deviceCode ? form.onlineStatus : '-' }}</strong></div>
-        <div><span>关联测试</span><DeviceRelatedTests :tests="form.relatedTests" /></div>
-      </div>
+      <div class="device-select-row"><span>采集器的测量按「同一来源的辅助测量」表达:选择它所属的数据来源,在线状态由该来源的连接结果决定。设备编码由操作员按现场叫法填写,系统不自动改写。</span></div>
 
       <div class="detail-form">
+        <label><span><b>*</b>设备编码</span><el-input v-model="form.deviceCode" maxlength="64" :disabled="isEditing" placeholder="输入设备编码" /></label>
+        <label><span>所属数据来源</span><el-select v-model="form.adapterId" clearable placeholder="选择来源实例"><el-option v-for="item in adapterOptions" :key="item.id" :label="`${item.name}(${item.type})`" :value="item.id" /></el-select></label>
+        <label><span>IP</span><el-input v-model="form.ip" maxlength="45" placeholder="可留空" /></label>
+        <label><span>MAC</span><el-input v-model="form.mac" maxlength="17" placeholder="可留空" /></label>
         <label><span><b>*</b>采集器名称</span><el-input v-model="form.name" maxlength="30" show-word-limit placeholder="输入采集器名称" /></label>
-        <label><span><b>*</b>制造商</span><el-input v-model="form.manufacturer" maxlength="30" placeholder="输入制造商" /></label>
-        <label><span><b>*</b>型号</span><el-input v-model="form.model" maxlength="30" placeholder="输入型号" /></label>
+        <label><span>制造商</span><el-input v-model="form.manufacturer" maxlength="30" placeholder="输入制造商" /></label>
+        <label><span>型号</span><el-input v-model="form.model" maxlength="30" placeholder="输入型号" /></label>
         <label><span>通道数</span><el-select v-model="form.channelCount"><el-option v-for="item in channelOptions" :key="item" :label="`${item} 通道`" :value="item" /></el-select></label>
+        <label><span>在线状态</span><el-input :model-value="form.deviceCode ? form.onlineStatus : '-'" disabled /></label>
+        <label class="related-field"><span>关联测试</span><DeviceRelatedTests :tests="form.relatedTests" /></label>
         <label><span>状态</span><el-radio-group v-model="form.status"><el-radio-button value="启用">启用</el-radio-button><el-radio-button value="禁用">禁用</el-radio-button></el-radio-group></label>
         <label class="remark-field"><span>备注</span><el-input v-model="form.remark" type="textarea" :rows="4" maxlength="200" show-word-limit placeholder="输入内容" /></label>
       </div>
 
       <footer class="form-footer"><button class="secondary-button" type="button" @click="router.push('/device-collector')">取消</button><button class="primary-button save-button" type="button" :disabled="loading || saving" @click="submit"><el-icon><Check /></el-icon>{{ saving ? '保存中' : '保存' }}</button></footer>
     </section>
-
-    <el-dialog v-model="deviceDialogVisible" class="device-dialog" title="选择设备" width="720px" align-center>
-      <div v-loading="deviceLoading" class="device-table-wrap">
-        <table class="device-table">
-          <thead><tr><th>设备编码</th><th>IP</th><th>MAC</th><th>操作</th></tr></thead>
-          <tbody>
-            <tr v-for="device in devices" :key="device.deviceCode"><td>{{ device.deviceCode }}</td><td>{{ device.ip }}</td><td>{{ device.mac }}</td><td><button type="button" @click="selectDevice(device)">选择</button></td></tr>
-            <tr v-if="!deviceLoading && !devices.length"><td colspan="4" class="empty-cell">暂无可发现采集器</td></tr>
-          </tbody>
-        </table>
-      </div>
-      <footer class="device-pagination"><span>共 {{ deviceTotal }} 个设备</span><el-pagination :current-page="devicePage" :page-size="devicePageSize" :total="deviceTotal" layout="prev, pager, next" background @current-change="handleDevicePageChange" /></footer>
-    </el-dialog>
   </div>
 </template>
 
@@ -176,13 +129,6 @@
 .primary-button:disabled { opacity: .55; cursor: wait; }
 .secondary-button { color: #52606d; background: #fff; border-color: var(--line-strong); }
 .secondary-button:hover { color: var(--blue); border-color: #9dc8f5; }
-.device-summary { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 18px 30px; padding: 16px 18px 20px; border-bottom: 1px solid var(--line); }
-.device-summary > div { min-width: 0; display: grid; grid-template-columns: 82px minmax(0, 1fr); gap: 8px; align-items: center; }
-.device-summary span { color: #7b8794; font-size: 12px; }
-.device-summary strong { min-width: 0; color: #44515d; font-size: 12px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-.device-summary .online-value { color: #23865e; }
-.device-summary .offline-value { color: #a64a4a; }
-.mono-value { font-family: 'Cascadia Mono', Consolas, monospace; }
 .detail-form { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); align-items: start; gap: 20px 24px; padding: 22px 18px 28px; }
 .detail-form label { min-width: 0; display: flex; flex-direction: column; gap: 7px; }
 .detail-form label > span { color: #52606d; font-size: 12px; font-weight: 500; }
@@ -190,30 +136,16 @@
 .collector-form-page :deep(.el-input__wrapper), .collector-form-page :deep(.el-select__wrapper), .collector-form-page :deep(.el-textarea__inner) { border-radius: 3px; box-shadow: 0 0 0 1px #d8e1e9 inset; background: #f8fafc; }
 .collector-form-page :deep(.el-input__wrapper), .collector-form-page :deep(.el-select__wrapper) { min-height: 34px; }
 .remark-field { grid-column: span 2; }
+.related-field { grid-column: span 2; }
 .form-footer { min-height: 70px; display: flex; align-items: center; gap: 10px; padding: 0 18px; border-top: 1px solid var(--line); }
 .save-button { min-width: 82px; }
-.collector-form-page :deep(.device-dialog) { max-width: calc(100vw - 28px); border-radius: 4px; }
-.collector-form-page :deep(.el-dialog__header) { margin: 0; padding: 17px 20px; border-bottom: 1px solid var(--line); }
-.collector-form-page :deep(.el-dialog__body) { padding: 20px; }
-.device-table-wrap { overflow-x: auto; }
-.device-table { width: 100%; min-width: 560px; table-layout: fixed; border-collapse: collapse; color: #52606d; font-size: 13px; }
-.device-table th, .device-table td { height: 42px; padding: 0 12px; border: 1px solid #d8e1e9; text-align: left; }
-.device-table th { height: 38px; color: #52606d; background: #eaf4ff; font-size: 12px; font-weight: 600; }
-.device-table th:last-child, .device-table td:last-child { width: 88px; text-align: center; }
-.device-table td:last-child button { border: 0; color: var(--blue); background: transparent; font-size: 12px; font-weight: 500; cursor: pointer; }
-.device-table tbody tr:hover { background: #f7fbff; }
-.device-table .empty-cell { height: 96px; color: #7b8794; text-align: center; }
-.device-pagination { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding-top: 18px; }
-.device-pagination > span { color: #7b8794; font-size: 11px; }
-.device-pagination :deep(.el-pagination) { --el-pagination-button-bg-color: #edf4fa; --el-pagination-hover-color: var(--blue); }
-@media (max-width: 960px) { .device-summary, .detail-form { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
+@media (max-width: 960px) { .detail-form { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
 @media (max-width: 620px) {
   .panel-heading { padding: 0 12px; }
   .panel-heading span, .panel-heading small, .device-select-row > span { display: none; }
-  .device-summary, .detail-form { grid-template-columns: minmax(0, 1fr); padding-left: 12px; padding-right: 12px; }
-  .remark-field { grid-column: auto; }
+  .detail-form { grid-template-columns: minmax(0, 1fr); padding-left: 12px; padding-right: 12px; }
+  .remark-field, .related-field { grid-column: auto; }
   .form-footer { padding: 0 12px; }
   .form-footer button { flex: 1; }
-  .collector-form-page :deep(.el-dialog__body) { padding: 14px 12px; }
 }
 </style>

--
Gitblit v1.7.1