161 lines
8.1 KiB
Plaintext
161 lines
8.1 KiB
Plaintext
<!-- 设备详情页:上部信息区(可滚动) + 底部固定收发控制台(串口调试工具风格) -->
|
||
<view class="page">
|
||
<!-- ===== 上半屏:设备信息与配置(可滚动) ===== -->
|
||
<scroll-view class="body-scroll" scroll-y>
|
||
<!-- 设备信息 -->
|
||
<view class="device-info card">
|
||
<view class="info-row">
|
||
<text class="info-label">设备编号</text>
|
||
<text class="info-value">{{device.serialNumber || '-'}}</text>
|
||
</view>
|
||
<view class="info-row">
|
||
<text class="info-label">BLE 地址</text>
|
||
<text class="info-value">{{device.deviceId || '-'}}</text>
|
||
</view>
|
||
<view class="info-row">
|
||
<text class="info-label">BLE 状态</text>
|
||
<!-- 未连接时可点击手动重连:自动重连卡死(连续连接超时后 native 半开连接)时的确定性出口 -->
|
||
<view class="status-tag {{bleConnected ? 'online' : 'offline'}} {{canRetryBle ? 'retryable' : ''}}"
|
||
bindtap="onRetryBle">
|
||
{{bleConnected ? '已连接' : (canRetryBle ? '未连接 · 点击重连' : '未连接')}}
|
||
</view>
|
||
</view>
|
||
<view class="info-row">
|
||
<text class="info-label">MQTT 状态</text>
|
||
<!-- MQTT 为账号/产品级自动连接(凭证在「我的」页):
|
||
正常情况下点击小程序即自动连上,这里只在异常/手动断开时提供一键重连 -->
|
||
<view class="status-tag {{mqttConnected ? 'online' : 'offline'}} {{canRetryMqtt ? 'retryable' : ''}}"
|
||
bindtap="onRetryMqtt">
|
||
<block wx:if="{{mqttConnected}}">已连接</block>
|
||
<block wx:elif="{{mqttConnecting}}">连接中…</block>
|
||
<block wx:elif="{{mqttUserDisconnected}}">已手动断开 · 点击重连</block>
|
||
<block wx:elif="{{canRetryMqtt}}">未连接 · 点击重连</block>
|
||
<block wx:else>未连接</block>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 测点与模式 -->
|
||
<view class="measure-card card">
|
||
<view class="measure-item">
|
||
<text class="measure-label">测点ID</text>
|
||
<!-- 只用正则把关,**刻意不用 type="number"**:测点ID 是数据唯一归属("0001" ≠ "1"),
|
||
数字键盘在部分平台/输入法会做数值归一化或吞前导零,静默改写主键的代价大于键盘便利 -->
|
||
<input class="measure-input {{measurePointIdValid ? '' : 'invalid'}}"
|
||
maxlength="64"
|
||
placeholder="纯数字, 1-64位"
|
||
value="{{measurePointId}}"
|
||
bindinput="onMeasurePointInput" />
|
||
</view>
|
||
<view class="measure-hint error" wx:if="{{!measurePointIdValid}}">测点ID格式非法:仅支持纯数字,长度1-64</view>
|
||
<view class="measure-item">
|
||
<text class="measure-label">采集模式</text>
|
||
<radio-group class="mode-group" bindchange="onModeChange">
|
||
<label class="mode-option">
|
||
<radio value="1" checked="{{collectMode === 1}}" color="#1890ff" />
|
||
<text>手动确认</text>
|
||
</label>
|
||
<label class="mode-option">
|
||
<radio value="2" checked="{{collectMode === 2}}" color="#1890ff" />
|
||
<text>自动上传</text>
|
||
</label>
|
||
</radio-group>
|
||
</view>
|
||
<view class="measure-hint" wx:if="{{collectMode === 1}}">手动模式:采集到的数据需点击「确认」后才上传</view>
|
||
<view class="measure-hint" wx:else>自动模式:采集到数据即上传到平台</view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<!-- ===== 底部:固定收发区 ===== -->
|
||
<view class="console">
|
||
<view wx:if="{{lastError}}" class="notice-text">{{lastError}}</view>
|
||
|
||
<!-- 数据接收:标题 + 工具区(清空 / 滚动 / Hex / 上传离线)同排,控件统一 52rpx 高。
|
||
注意:button 的宽度会被基础库强制成 184px,靠 wxss 里的「父容器+类名」提权压回,详见 detail.wxss -->
|
||
<view class="console-header">
|
||
<text class="console-title">数据接收:</text>
|
||
<view class="console-tools">
|
||
<button class="btn-clear-mini" bindtap="handleClearData">清空</button>
|
||
<!-- ⚠️ 勾选框必须由 checkbox-group 包裹:bindchange 是 checkbox-group 的事件,
|
||
checkbox 自身没有该事件(裸挂要么不派发、要么 detail.value 是布尔值而非数组,
|
||
两种情况都会让开关恒定失效)。group 的 detail.value 才是「选中项 value 数组」 -->
|
||
<checkbox-group class="chk-item" bindchange="onAutoScrollToggle">
|
||
<label class="chk-item">
|
||
<checkbox class="chk-box" value="scroll" checked="{{autoScroll}}" color="#07c160" />
|
||
<text>滚动</text>
|
||
</label>
|
||
</checkbox-group>
|
||
<checkbox-group class="chk-item" bindchange="onRecvHexToggle">
|
||
<label class="chk-item">
|
||
<checkbox class="chk-box" value="hex" checked="{{recvHex}}" color="#07c160" />
|
||
<text>Hex</text>
|
||
</label>
|
||
</checkbox-group>
|
||
<!-- 离线补传:排在接收行末位;禁用态走 class(button[disabled]:not([type]) 会压掉属性选择器) -->
|
||
<button class="btn-flush {{canFlush ? '' : 'is-disabled'}}"
|
||
bindtap="handleFlushOffline"
|
||
disabled="{{!canFlush}}">
|
||
上传离线({{offlineCount}})
|
||
</button>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="log-wrap">
|
||
<scroll-view class="log-scroll" scroll-y
|
||
scroll-into-view="{{scrollIntoView}}"
|
||
bindscroll="onLogScroll"
|
||
bindscrolltolower="onLogToLower"
|
||
lower-threshold="60">
|
||
<block wx:if="{{displayList.length > 0}}">
|
||
<!-- 最新在后:自动滚动锚定最后一行 -->
|
||
<view class="log-line" wx:for="{{displayList}}" wx:key="id" id="log-{{item.id}}">
|
||
<text class="log-text">[{{item.time}}]:{{item.text}}</text>
|
||
<text wx:if="{{item.uploaded}}" class="log-flag uploaded">↑已上报</text>
|
||
<text wx:elif="{{item.offline}}" class="log-flag cached">已缓存</text>
|
||
<text wx:elif="{{modeManual && !item.confirmed}}" class="log-flag confirm-btn" catchtap="onConfirmData" data-id="{{item.id}}">确认</text>
|
||
<text wx:elif="{{modeManual && item.confirmed}}" class="log-flag confirmed">已确认</text>
|
||
<text wx:elif="{{item.pendingReason}}" class="log-flag pending">{{item.pendingReason}}</text>
|
||
</view>
|
||
</block>
|
||
<view wx:else class="log-empty">
|
||
<text>{{enabled ? '等待设备上报数据…' : '启用网关采集后可查看实时数据'}}</text>
|
||
</view>
|
||
</scroll-view>
|
||
<!-- 手动上翻回看时的"回到底部"快捷入口 -->
|
||
<view class="follow-btn" wx:if="{{autoScroll && !logFollowing && displayList.length > 0}}"
|
||
bindtap="onFollowTap">↓ 最新</view>
|
||
</view>
|
||
|
||
<!-- 统计 -->
|
||
<view class="console-stats">
|
||
<text class="stat-chip">接收 {{stats.received}}</text>
|
||
<text class="stat-chip">已上报 {{stats.uploaded}}</text>
|
||
<text class="stat-chip">失败 {{stats.failed}}</text>
|
||
<text class="stat-chip">离线 {{offlineCount}}</text>
|
||
</view>
|
||
|
||
<!-- 数据发送 -->
|
||
<view class="console-header send-header">
|
||
<text class="console-title">数据发送:</text>
|
||
<checkbox-group class="chk-item" bindchange="onSendHexToggle">
|
||
<label class="chk-item">
|
||
<checkbox class="chk-box" value="hex" checked="{{sendHex}}" color="#07c160" />
|
||
<text>Hex</text>
|
||
</label>
|
||
</checkbox-group>
|
||
</view>
|
||
<view class="send-row">
|
||
<input class="send-input"
|
||
placeholder="{{sendHex ? '如:00 60 00 FF 00 FF F0' : '发送 ASCII 文本'}}"
|
||
placeholder-class="send-placeholder"
|
||
value="{{sendInput}}"
|
||
bindinput="onSendInput"
|
||
confirm-type="send" confirm-hold
|
||
bindconfirm="handleSend" />
|
||
<button class="btn-send {{canSend ? '' : 'is-disabled'}}"
|
||
bindtap="handleSend"
|
||
disabled="{{!canSend}}">发送</button>
|
||
</view>
|
||
</view>
|
||
</view>
|