فهرست منبع

feat: 设备远程控制支持读取信号值,admin-h5 设备详情展示网络信号与主板信息

- admin-web 远程控制弹窗新增「读取信号值」按钮,调用 queryHardwareInfo 展示 RSSI/BER/IMEI 及主板固件信息

- admin-h5 设备详情页自动读取并展示网络信号与主板信息,列表页不展示信号值
skyline 3 هفته پیش
والد
کامیت
9f5ac9b918
3فایلهای تغییر یافته به همراه231 افزوده شده و 2 حذف شده
  1. 9 0
      admin-h5/src/api/device.js
  2. 97 1
      admin-h5/src/pages/device/detail.vue
  3. 125 1
      admin-web/src/views/admin/station/device/remote.vue

+ 9 - 0
admin-h5/src/api/device.js

@@ -97,4 +97,13 @@ export const modifyDeviceConfig = (config) => {
  */
 export const batchModifyDeviceConfig = (params) => {
   return post('/device-config/batchModify', params)
+}
+
+/**
+ * 查询设备硬件信息(网络信号/主板信息)
+ * @param {Object} params - { productKey, deviceName }
+ * @returns {Promise} 结果
+ */
+export const queryDeviceHardwareInfo = (params) => {
+  return post('/washDevice/remote/queryHardwareInfo', params)
 }

+ 97 - 1
admin-h5/src/pages/device/detail.vue

@@ -79,6 +79,50 @@
       </view>
     </view>
 
+    <view class="info-card" v-if="hardwareInfo">
+      <view class="card-title">网络信号</view>
+      <view class="info-list">
+        <view class="info-item">
+          <text class="item-label">信号质量(RSSI)</text>
+          <text class="item-value" :class="rssiClass">{{ rssiDisplay }}</text>
+        </view>
+        <view class="info-item">
+          <text class="item-label">网络类型</text>
+          <text class="item-value">{{ hardwareInfo.network?.type || '--' }}</text>
+        </view>
+        <view class="info-item">
+          <text class="item-label">误码率(BER)</text>
+          <text class="item-value">{{ hardwareInfo.network?.ber ?? '--' }}</text>
+        </view>
+        <view class="info-item">
+          <text class="item-label">IMEI</text>
+          <text class="item-value">{{ hardwareInfo.network?.imei || '--' }}</text>
+        </view>
+      </view>
+    </view>
+
+    <view class="info-card" v-if="hardwareInfo">
+      <view class="card-title">主板信息</view>
+      <view class="info-list">
+        <view class="info-item">
+          <text class="item-label">固件版本</text>
+          <text class="item-value">{{ hardwareInfo.board?.firmware_version || '--' }}</text>
+        </view>
+        <view class="info-item">
+          <text class="item-label">硬件版本</text>
+          <text class="item-value">{{ hardwareInfo.board?.hard_version || '--' }}</text>
+        </view>
+        <view class="info-item">
+          <text class="item-label">编译时间</text>
+          <text class="item-value">{{ hardwareInfo.board?.build_time || '--' }}</text>
+        </view>
+        <view class="info-item">
+          <text class="item-label">芯片ID</text>
+          <text class="item-value item-value-chip">{{ hardwareInfo.board?.chip_id || '--' }}</text>
+        </view>
+      </view>
+    </view>
+
     <view class="action-section">
       <button
         v-if="isOnline"
@@ -114,13 +158,14 @@
 
 <script setup>
 import { ref, onMounted, computed } from 'vue'
-import { getDeviceDetail, stopDevice } from '../../api/device.js'
+import { getDeviceDetail, stopDevice, queryDeviceHardwareInfo } from '../../api/device.js'
 import { formatTime, showToast, fmtDictName, getDictColor, copyText } from '../../utils/index.js'
 import dictUtil, { loadDicts } from '../../utils/dict.js'
 
 const deviceId = ref('')
 const deviceDetail = ref({})
 const loading = ref(true)
+const hardwareInfo = ref(null)
 
 const isOnline = computed(() => {
   const state = deviceDetail.value.state
@@ -153,6 +198,36 @@ const formatDuration = (seconds) => {
   return `${minutes}分钟`
 }
 
+// ===== 信号值展示 =====
+const rssiDisplay = computed(() => {
+  const rssi = hardwareInfo.value?.network?.rssi
+  return rssi != null ? `${rssi}` : '--'
+})
+
+const rssiClass = computed(() => {
+  const rssi = hardwareInfo.value?.network?.rssi
+  if (rssi == null) return ''
+  if (rssi >= 20) return 'signal-good'
+  if (rssi >= 10) return 'signal-mid'
+  return 'signal-bad'
+})
+
+const loadHardwareInfo = async () => {
+  const productKey = deviceDetail.value.productKey
+  const deviceName = deviceDetail.value.deviceName
+  if (!productKey || !deviceName) return
+  try {
+    const res = await queryDeviceHardwareInfo({ productKey, deviceName })
+    if (res && res.code === 200) {
+      hardwareInfo.value = res.data || null
+    } else {
+      hardwareInfo.value = null
+    }
+  } catch (error) {
+    hardwareInfo.value = null
+  }
+}
+
 const loadDeviceDetail = async () => {
   if (!deviceId.value) {
     showToast('设备ID不存在')
@@ -164,6 +239,7 @@ const loadDeviceDetail = async () => {
     const res = await getDeviceDetail(deviceId.value)
     if (res && res.code === 200) {
       deviceDetail.value = res.data || {}
+      loadHardwareInfo()
     } else {
       showToast(res.msg || '获取设备详情失败')
     }
@@ -198,6 +274,7 @@ const handleStartDevice = () => {
 
 const refreshDeviceInfo = () => {
   loadDeviceDetail()
+  loadHardwareInfo()
 }
 
 const goBack = () => {
@@ -339,6 +416,25 @@ onMounted(async () => {
   color: #F44336;
 }
 
+.item-value-chip {
+  max-width: 320rpx;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.signal-good {
+  color: #52C41A;
+}
+
+.signal-mid {
+  color: #E6A23C;
+}
+
+.signal-bad {
+  color: #F44336;
+}
+
 /* ===== Action Section ===== */
 .action-section {
   position: fixed;

+ 125 - 1
admin-web/src/views/admin/station/device/remote.vue

@@ -173,6 +173,62 @@
     opacity: 0.8;
   }
 }
+
+.signal-info-card {
+  margin-top: 8px;
+  padding: 12px 16px;
+  background: #f5f7fa;
+  border-radius: 8px;
+
+  .signal-title {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    font-weight: 600;
+    margin-bottom: 10px;
+    color: #333;
+  }
+
+  .signal-grid {
+    display: grid;
+    grid-template-columns: repeat(2, 1fr);
+    gap: 6px 24px;
+  }
+
+  .signal-item {
+    display: flex;
+    justify-content: space-between;
+    font-size: 13px;
+    padding: 5px 0;
+    border-bottom: 1px dashed #e4e7ed;
+
+    .signal-label {
+      color: #909399;
+      flex-shrink: 0;
+      margin-right: 12px;
+    }
+
+    .signal-value {
+      color: #303133;
+      font-weight: 600;
+      word-break: break-all;
+      text-align: right;
+    }
+
+    .signal-value.chip-id {
+      font-size: 12px;
+      max-width: 220px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+  }
+
+  .signal-good { color: #67c23a; }
+  .signal-mid { color: #e6a23c; }
+  .signal-bad { color: #f56c6c; }
+}
+
 </style>
 
 <template>
@@ -224,6 +280,9 @@
           <el-button class="action-btn" type="primary" plain @click="handleReadConfig" :loading="state.loading">
             <el-icon><Reading /></el-icon> 读取配置
           </el-button>
+          <el-button class="action-btn" type="info" plain @click="handleReadHardwareInfo" :loading="state.loading">
+            <el-icon><Odometer /></el-icon> 读取信号值
+          </el-button>
           <el-button class="action-btn" type="warning" plain @click="handleShowMsgbox" :loading="state.loading">
             <el-icon><ChatDotSquare /></el-icon> 屏幕消息
           </el-button>
@@ -249,6 +308,48 @@
           </div>
         </div>
 
+        <!-- 信号值信息(点击「读取信号值」后显示) -->
+        <div class="signal-info-card" v-if="state.hardwareInfo">
+          <div class="signal-title">
+            <span>设备信号值</span>
+            <el-button size="small" text type="primary" @click="state.hardwareInfo = null">收起</el-button>
+          </div>
+          <div class="signal-grid">
+            <div class="signal-item">
+              <span class="signal-label">网络类型</span>
+              <span class="signal-value">{{ state.hardwareInfo.network?.type || '--' }}</span>
+            </div>
+            <div class="signal-item">
+              <span class="signal-label">信号质量(RSSI)</span>
+              <span class="signal-value" :class="rssiLevel">{{ state.hardwareInfo.network?.rssi ?? '--' }}</span>
+            </div>
+            <div class="signal-item">
+              <span class="signal-label">误码率(BER)</span>
+              <span class="signal-value">{{ state.hardwareInfo.network?.ber ?? '--' }}</span>
+            </div>
+            <div class="signal-item">
+              <span class="signal-label">IMEI</span>
+              <span class="signal-value">{{ state.hardwareInfo.network?.imei || '--' }}</span>
+            </div>
+            <div class="signal-item">
+              <span class="signal-label">硬件版本</span>
+              <span class="signal-value">{{ state.hardwareInfo.board?.hard_version || '--' }}</span>
+            </div>
+            <div class="signal-item">
+              <span class="signal-label">固件版本</span>
+              <span class="signal-value">{{ state.hardwareInfo.board?.firmware_version || '--' }}</span>
+            </div>
+            <div class="signal-item">
+              <span class="signal-label">编译时间</span>
+              <span class="signal-value">{{ state.hardwareInfo.board?.build_time || '--' }}</span>
+            </div>
+            <div class="signal-item">
+              <span class="signal-label">芯片ID</span>
+              <span class="signal-value chip-id">{{ state.hardwareInfo.board?.chip_id || '--' }}</span>
+            </div>
+          </div>
+        </div>
+
         <!-- 配置编辑区 -->
         <div class="config-editor" v-if="state.showConfigEditor">
           <div class="config-title">设备配置编辑</div>
@@ -359,7 +460,7 @@ import {Msg} from "/@/utils/message";
 import {$body} from "/@/utils/request";
 import u from '/@/utils/u';
 import {ElMessage} from 'element-plus';
-import {Refresh, Reading, ChatDotSquare, CloseBold, SwitchButton, Loading} from '@element-plus/icons-vue';
+import {Refresh, Reading, ChatDotSquare, CloseBold, SwitchButton, Loading, Odometer} from '@element-plus/icons-vue';
 
 const emit = defineEmits(['refresh']);
 
@@ -371,6 +472,7 @@ const initState = () => ({
   uptimeMs: '',
   hasPa: false,
   orderInfo: null as any,
+  hardwareInfo: null as any,
   showConfigEditor: false,
   configForm: {} as any,
   priceDialog: {
@@ -430,6 +532,15 @@ const temperatureDisplay = computed(() => {
   return state.temperatureChip != null ? `${state.temperatureChip}°C` : '--';
 });
 
+// 信号质量等级(rssi 越大信号越好)
+const rssiLevel = computed(() => {
+  const rssi = state.hardwareInfo?.network?.rssi;
+  if (rssi == null) return '';
+  if (rssi >= 20) return 'signal-good';
+  if (rssi >= 10) return 'signal-mid';
+  return 'signal-bad';
+});
+
 const open = (row: any) => {
   productKey = row.productKey;
   deviceName = row.deviceName;
@@ -513,6 +624,19 @@ const handleWriteConfig = async () => {
   }
 };
 
+const handleReadHardwareInfo = async () => {
+  state.loading = true;
+  try {
+    const res: any = await api('queryHardwareInfo');
+    state.hardwareInfo = res || null;
+    Msg.message('信号值读取成功', 'success');
+  } catch (e) {
+    Msg.message('读取信号值失败', 'error');
+  } finally {
+    state.loading = false;
+  }
+};
+
 const editPrice = (fn: any) => {
   if (fn.price == null) {
     Msg.message('配置加载失败,无法修改价格,请点击「读取配置」重试', 'warning');