20260823:H5页面支持数据调试
This commit is contained in:
parent
1e15ae7400
commit
3f5bba9a02
@ -63,7 +63,8 @@
|
||||
"Bash(done)",
|
||||
"Bash(file /tmp/tile_*.png)",
|
||||
"Bash(for REF in \"http://localhost:8080/\" \"https://dev.fastbee.cn/\" \"https://www.tianditu.gov.cn/\")",
|
||||
"Read(//tmp/**)"
|
||||
"Read(//tmp/**)",
|
||||
"Bash(node -e ' *)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
42
yack-app/apis/modules/mqttTest.js
Normal file
42
yack-app/apis/modules/mqttTest.js
Normal file
@ -0,0 +1,42 @@
|
||||
const http = uni.$u.http;
|
||||
|
||||
// 指令编码
|
||||
export function encode(params) {
|
||||
return http.get('/iot/message/encode', {
|
||||
params: params
|
||||
});
|
||||
}
|
||||
|
||||
// 指令解码
|
||||
export function decode(params) {
|
||||
return http.get('/iot/message/decode', {
|
||||
params: params
|
||||
});
|
||||
}
|
||||
|
||||
// 平台下发指令
|
||||
export function messagePost(data) {
|
||||
return http.post('/iot/message/post', data);
|
||||
}
|
||||
|
||||
// 查询设备指令偏好设置列表
|
||||
export function preferencesList(params) {
|
||||
return http.get('/iot/preferences/list', {
|
||||
params: params
|
||||
});
|
||||
}
|
||||
|
||||
// 新增设备指令偏好设置
|
||||
export function addPreferences(data) {
|
||||
return http.post('/iot/preferences', data);
|
||||
}
|
||||
|
||||
// 修改设备指令偏好设置
|
||||
export function editPreferences(data) {
|
||||
return http.put('/iot/preferences', data);
|
||||
}
|
||||
|
||||
// 删除设备指令偏好设置
|
||||
export function delPreferences(data) {
|
||||
return http.delete('/iot/preferences/' + data.id);
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
// H5端开发和生产环境协议
|
||||
let protocalDev = "wss://";
|
||||
let protocalDev = "ws://";
|
||||
let protocalProd = "wss://";
|
||||
|
||||
// 条件编译,微信端和移动端使用wxs协议
|
||||
@ -19,7 +19,7 @@ const CONFIG = {
|
||||
development: {
|
||||
officialWebUrl: 'http://localhost:8080/',
|
||||
baseUrl: 'http://localhost:8080/',
|
||||
mqttServer: protocalDev + 'localhost/mqtt',
|
||||
mqttServer: protocalDev + '47.121.180.229:8083/mqtt',
|
||||
decoderUrl: 'http://localhost:8080/',
|
||||
qqmapKey,
|
||||
xinzhiKey,
|
||||
|
||||
@ -49,6 +49,8 @@
|
||||
"navBar.updateDevice": "Device Update",
|
||||
"navBar.eventLog": "Event Log",
|
||||
"navBar.orderLog": "Instruction log",
|
||||
"navBar.dataDebug": "Data Debug",
|
||||
"navBar.commandBuilder": "Command Builder",
|
||||
"navBar.player": "Player",
|
||||
"common.add": "add",
|
||||
"common.delete": "Delete",
|
||||
@ -240,6 +242,72 @@
|
||||
"createBtn": "Create Device",
|
||||
"content": "This feature requires login to use. Do you want to log in?"
|
||||
},
|
||||
"debug": {
|
||||
"title": "Data Debug",
|
||||
"send": "Send",
|
||||
"reveiceType": "Reply",
|
||||
"reportType": "Report",
|
||||
"sendType": "Send",
|
||||
"hex": "HEX",
|
||||
"json": "JSON",
|
||||
"ascii": "ASCII",
|
||||
"inputPlaceholder": "Please enter the content to send",
|
||||
"typePlaceholder": "Select format",
|
||||
"analysis": "Parse",
|
||||
"analysised": "Parsed",
|
||||
"name": "Name",
|
||||
"identifier": "Identifier",
|
||||
"value": "Value",
|
||||
"more": "More",
|
||||
"quickCommand": "Quick Commands",
|
||||
"addCommand": "Add Command",
|
||||
"editCommand": "Edit Command",
|
||||
"slaveAddress": "Slave Address",
|
||||
"issueType": "Command Type",
|
||||
"functionCode": "Function Code",
|
||||
"startAddress": "Start Register Address",
|
||||
"registerCount": "Register Count",
|
||||
"coilCount": "Coil Count",
|
||||
"writeCoilValue": "Coil Value",
|
||||
"writeRegisterValue": "Register Value",
|
||||
"registerValue": "Register Value",
|
||||
"reportValue": "Report Value",
|
||||
"coilValue": "Coil Value",
|
||||
"generateCommand": "Generate",
|
||||
"generatedCommand": "Generated Command",
|
||||
"saveAsCommand": "Save As Command",
|
||||
"copy": "Copy",
|
||||
"cancel": "Cancel",
|
||||
"confirm": "Confirm",
|
||||
"commandName": "Command Name",
|
||||
"inputCommandName": "Please enter command name",
|
||||
"commandContent": "Command Content",
|
||||
"commandScope": "Command Scope",
|
||||
"productCommand": "Product Command (shared by same product)",
|
||||
"deviceCommand": "Device Command (this device only)",
|
||||
"deleteCommand": "Delete Command",
|
||||
"deleteConfirm": "Are you sure to delete this command?",
|
||||
"reportCommand": "Device Report",
|
||||
"issueCommand": "Platform Issue",
|
||||
"code01": "01 Read Coils",
|
||||
"code02": "02 Read Discrete Inputs",
|
||||
"code03": "03 Read Holding Registers",
|
||||
"code04": "04 Read Input Registers",
|
||||
"code05": "05 Write Single Coil",
|
||||
"code06": "06 Write Single Register",
|
||||
"code15": "15 Write Multiple Coils",
|
||||
"code16": "16 Write Multiple Registers",
|
||||
"sendEmpty": "Please enter the content to send",
|
||||
"sendFail": "Send failed",
|
||||
"analysisEmpty": "No data to parse",
|
||||
"analysisFail": "Parse failed",
|
||||
"generateFail": "Generate failed",
|
||||
"saveSuccess": "Saved successfully",
|
||||
"saveFail": "Save failed",
|
||||
"deleteSuccess": "Deleted successfully",
|
||||
"deleteFail": "Delete failed",
|
||||
"copySuccess": "Copied successfully"
|
||||
},
|
||||
"about": {
|
||||
"about": "about",
|
||||
"authorQQ": "Author QQ : 164770707",
|
||||
|
||||
@ -46,6 +46,8 @@
|
||||
"navBar.updateDevice": "设备修改",
|
||||
"navBar.eventLog": "事件日志",
|
||||
"navBar.orderLog": "指令日志",
|
||||
"navBar.dataDebug": "数据调试",
|
||||
"navBar.commandBuilder": "指令生成",
|
||||
"navBar.player": "播放器",
|
||||
"common.fastbee": "岩安测控",
|
||||
"common.add": "添加",
|
||||
@ -241,6 +243,72 @@
|
||||
"createBtn": "创建设备",
|
||||
"content": "该功能需要登录才可使用,是否去登录?"
|
||||
},
|
||||
"debug": {
|
||||
"title": "数据调试",
|
||||
"send": "发送",
|
||||
"reveiceType": "回复",
|
||||
"reportType": "上报",
|
||||
"sendType": "下发",
|
||||
"hex": "HEX",
|
||||
"json": "JSON",
|
||||
"ascii": "ASCII",
|
||||
"inputPlaceholder": "请输入要发送的内容",
|
||||
"typePlaceholder": "选择格式",
|
||||
"analysis": "解析",
|
||||
"analysised": "已解析",
|
||||
"name": "参数名",
|
||||
"identifier": "标识符",
|
||||
"value": "参数值",
|
||||
"more": "更多",
|
||||
"quickCommand": "快捷指令",
|
||||
"addCommand": "新建指令",
|
||||
"editCommand": "编辑指令",
|
||||
"slaveAddress": "从机地址",
|
||||
"issueType": "指令类型",
|
||||
"functionCode": "功能码",
|
||||
"startAddress": "起始寄存器地址",
|
||||
"registerCount": "寄存器个数",
|
||||
"coilCount": "线圈个数",
|
||||
"writeCoilValue": "线圈值",
|
||||
"writeRegisterValue": "寄存器值",
|
||||
"registerValue": "寄存器值",
|
||||
"reportValue": "上报值",
|
||||
"coilValue": "线圈值",
|
||||
"generateCommand": "生成指令",
|
||||
"generatedCommand": "生成的指令",
|
||||
"saveAsCommand": "存为指令",
|
||||
"copy": "复制",
|
||||
"cancel": "取消",
|
||||
"confirm": "确定",
|
||||
"commandName": "指令名称",
|
||||
"inputCommandName": "请输入指令名称",
|
||||
"commandContent": "指令内容",
|
||||
"commandScope": "指令范围",
|
||||
"productCommand": "产品指令(同类产品通用)",
|
||||
"deviceCommand": "设备指令(仅本设备)",
|
||||
"deleteCommand": "删除指令",
|
||||
"deleteConfirm": "确认删除该指令吗?",
|
||||
"reportCommand": "设备上报",
|
||||
"issueCommand": "平台下发",
|
||||
"code01": "01 读线圈",
|
||||
"code02": "02 读离散输入",
|
||||
"code03": "03 读保持寄存器",
|
||||
"code04": "04 读输入寄存器",
|
||||
"code05": "05 写单个线圈",
|
||||
"code06": "06 写单个寄存器",
|
||||
"code15": "15 写多个线圈",
|
||||
"code16": "16 写多个寄存器",
|
||||
"sendEmpty": "请输入要发送的内容",
|
||||
"sendFail": "发送失败",
|
||||
"analysisEmpty": "暂无可解析的数据",
|
||||
"analysisFail": "解析失败",
|
||||
"generateFail": "生成失败",
|
||||
"saveSuccess": "保存成功",
|
||||
"saveFail": "保存失败",
|
||||
"deleteSuccess": "删除成功",
|
||||
"deleteFail": "删除失败",
|
||||
"copySuccess": "复制成功"
|
||||
},
|
||||
"about": {
|
||||
"about": "关于",
|
||||
"authorQQ": "作者企鹅:164770707",
|
||||
|
||||
@ -533,6 +533,26 @@
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "home/device/debug/index",
|
||||
"meta": {
|
||||
"title": "%navBar.dataDebug%"
|
||||
},
|
||||
"style": {
|
||||
"navigationBarTitleText": "%navBar.dataDebug%",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "home/device/debug/edit",
|
||||
"meta": {
|
||||
"title": "%navBar.commandBuilder%"
|
||||
},
|
||||
"style": {
|
||||
"navigationBarTitleText": "%navBar.commandBuilder%",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "login/register",
|
||||
"style": {
|
||||
|
||||
@ -242,7 +242,8 @@
|
||||
pageSize: 10,
|
||||
groupId: 0,
|
||||
deviceName: null,
|
||||
showChild: true
|
||||
showChild: true,
|
||||
entrance: 2
|
||||
},
|
||||
loadStatus: 'loadmore', // 加载更多
|
||||
deviceList: [], // 设备列表
|
||||
|
||||
@ -331,12 +331,17 @@
|
||||
this.deviceForm.rssid = message.rssid;
|
||||
}
|
||||
};
|
||||
//不是modbus不转发到子页面,其他设备的页面有回调方法
|
||||
if (this.isSubDev) {
|
||||
/*发送设备上报到子模块*/
|
||||
if (topic.endsWith('ws/service')) {
|
||||
console.log('接收到【设备数据上报】主题:', topic)
|
||||
console.log('接收到【设备数据上报】主题:', message)
|
||||
/*发送设备上报到数据调试页面*/
|
||||
if (topic.endsWith('ws/service')) {
|
||||
console.log('接收到【设备数据上报】主题:', topic)
|
||||
console.log('接收到【设备数据上报】主题:', message)
|
||||
this.$bus.$emit("updateData", {
|
||||
serialNumber: topics[2],
|
||||
productId: this.deviceForm.productId,
|
||||
data: message,
|
||||
})
|
||||
//不是modbus不转发到子页面,其他设备的页面有回调方法
|
||||
if (this.isSubDev) {
|
||||
this.$bus.$emit("updateDeviceStatus", {
|
||||
serialNumber: topics[2],
|
||||
productId: this.deviceForm.productId,
|
||||
|
||||
@ -6,6 +6,8 @@
|
||||
@click="handleEventLogClick"></u-cell>
|
||||
<u-cell icon="bookmark" :title="$tt('home.Instructionlog')" :isLink="true" size="large"
|
||||
@click="handleOrderLogClick"></u-cell>
|
||||
<u-cell icon="server-man" :title="$tt('debug.title')" :isLink="true" size="large"
|
||||
@click="handleDebugClick"></u-cell>
|
||||
</u-cell-group>
|
||||
</view>
|
||||
</view>
|
||||
@ -58,6 +60,17 @@
|
||||
uni.navigateTo({
|
||||
url: `/pagesB/home/device/log/order?deviceId=${this.deviceId}&serialNumber=${this.serialNumber}`
|
||||
});
|
||||
},
|
||||
handleDebugClick () {
|
||||
const device = {
|
||||
deviceId: this.deviceInfo.deviceId,
|
||||
serialNumber: this.deviceInfo.serialNumber,
|
||||
productId: this.deviceInfo.productId,
|
||||
protocolCode: this.deviceInfo.protocolCode,
|
||||
};
|
||||
uni.navigateTo({
|
||||
url: `/pagesB/home/device/debug/index?device=${encodeURIComponent(JSON.stringify(device))}`
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
615
yack-app/pagesB/home/device/debug/edit.vue
Normal file
615
yack-app/pagesB/home/device/debug/edit.vue
Normal file
@ -0,0 +1,615 @@
|
||||
<template>
|
||||
<view class="edit-wrap">
|
||||
<!-- 表单区域 -->
|
||||
<view class="form-section">
|
||||
<u-cell-group :border="false">
|
||||
<!-- 从机地址 -->
|
||||
<u-cell :title="$tt('debug.slaveAddress')" required>
|
||||
<u--input slot="value" v-model="form.slaveId" type="number" border="none" placeholder="01" inputAlign="right"></u--input>
|
||||
</u-cell>
|
||||
|
||||
<!-- 指令类型 -->
|
||||
<u-cell :title="$tt('debug.issueType')" required isLink @click="showIssueTypeSheet = true">
|
||||
<text slot="value">{{currentIssueTypeLabel}}</text>
|
||||
</u-cell>
|
||||
|
||||
<!-- 功能码 -->
|
||||
<u-cell :title="$tt('debug.functionCode')" required isLink @click="showFunctionCodeSheet = true" :disabled="form.issueType === '0xFFAA'">
|
||||
<text slot="value">{{currentFunctionCodeLabel}}</text>
|
||||
</u-cell>
|
||||
|
||||
<!-- 起始地址 -->
|
||||
<u-cell :title="$tt('debug.startAddress')" required>
|
||||
<view slot="value" class="hex-input-wrap">
|
||||
<input v-model="form.startAddress" type="number" class="input" @input="syncStartAddressHex" />
|
||||
<text class="hex-hint">0x{{form.startAddressHex}}</text>
|
||||
</view>
|
||||
</u-cell>
|
||||
|
||||
<!-- 个数(根据功能码显示) -->
|
||||
<u-cell :title="countLabel" v-if="showCount">
|
||||
<u-number-box slot="value" v-model="form.count" :min="1" :max="100"></u-number-box>
|
||||
</u-cell>
|
||||
|
||||
<!-- 写值(05/06功能码) -->
|
||||
<u-cell :title="writeValueLabel" v-if="showWriteValue">
|
||||
<view slot="value" class="hex-input-wrap">
|
||||
<input v-model="form.writeValue" type="number" class="input" @input="syncWriteValueHex" />
|
||||
<text class="hex-hint">0x{{form.writeValueHex}}</text>
|
||||
</view>
|
||||
</u-cell>
|
||||
</u-cell-group>
|
||||
|
||||
<!-- 批量寄存器值(功能码16) -->
|
||||
<view class="batch-section" v-if="showRegisterValues">
|
||||
<view class="batch-title">{{$tt('debug.registerValue')}}</view>
|
||||
<view class="batch-list">
|
||||
<view class="batch-item" v-for="(item, index) in registerValues" :key="index">
|
||||
<text class="batch-label">#{{index}}</text>
|
||||
<input v-model="item.value" type="number" class="batch-input" @input="syncRegisterHex(index)" />
|
||||
<text class="hex-hint">0x{{item.valueHex}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 批量线圈值(功能码15) -->
|
||||
<view class="batch-section" v-if="showCoilValues">
|
||||
<view class="batch-title">{{$tt('debug.coilValue')}}</view>
|
||||
<view class="batch-list">
|
||||
<view class="batch-item" v-for="(item, index) in coilValues" :key="index">
|
||||
<text class="batch-label">#{{index}}</text>
|
||||
<u-switch v-model="item.value" activeValue="1" inactiveValue="0"></u-switch>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 批量上报值(0xFFAA) -->
|
||||
<view class="batch-section" v-if="showReportValues">
|
||||
<view class="batch-title">{{$tt('debug.reportValue')}}</view>
|
||||
<view class="batch-list">
|
||||
<view class="batch-item" v-for="(item, index) in reportValues" :key="index">
|
||||
<text class="batch-label">#{{index}}</text>
|
||||
<input v-model="item.value" type="number" class="batch-input" @input="syncReportHex(index)" />
|
||||
<text class="hex-hint">0x{{item.valueHex}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 生成结果 -->
|
||||
<view class="result-section">
|
||||
<view class="result-header">
|
||||
<text class="result-title">{{$tt('debug.generatedCommand')}}</text>
|
||||
<view class="result-actions">
|
||||
<text class="action-btn" @click="handleSave">{{$tt('debug.saveAsCommand')}}</text>
|
||||
<text class="action-btn copy" @click="handleCopy">{{$tt('debug.copy')}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="result-content" v-if="generatedCommand">
|
||||
<text>{{generatedCommand}}</text>
|
||||
</view>
|
||||
<view class="result-empty" v-else>
|
||||
<text>点击下方按钮生成指令</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<view class="bottom-btns">
|
||||
<u-button type="default" @click="handleCancel">{{$tt('debug.cancel')}}</u-button>
|
||||
<u-button type="primary" @click="handleGenerate" :loading="generating">{{$tt('debug.generateCommand')}}</u-button>
|
||||
</view>
|
||||
|
||||
<!-- 指令类型选择 -->
|
||||
<u-action-sheet :show="showIssueTypeSheet" :actions="issueTypeActions" @select="onIssueTypeSelect"
|
||||
@close="showIssueTypeSheet = false"></u-action-sheet>
|
||||
|
||||
<!-- 功能码选择 -->
|
||||
<u-action-sheet :show="showFunctionCodeSheet" :actions="functionCodeActions" @select="onFunctionCodeSelect"
|
||||
@close="showFunctionCodeSheet = false"></u-action-sheet>
|
||||
|
||||
<!-- 保存弹窗 -->
|
||||
<u-popup :show="showSavePopup" mode="bottom" :round="10" @close="showSavePopup = false">
|
||||
<view class="save-popup">
|
||||
<view class="popup-title">{{$tt('debug.saveAsCommand')}}</view>
|
||||
<u-cell-group :border="false">
|
||||
<u-cell :title="$tt('debug.commandName')" required>
|
||||
<u--input slot="value" v-model="saveForm.name" :placeholder="$tt('debug.inputCommandName')" border="none" inputAlign="right"></u--input>
|
||||
</u-cell>
|
||||
<u-cell :title="$tt('debug.commandContent')">
|
||||
<text slot="value" class="cmd-text">{{saveForm.command}}</text>
|
||||
</u-cell>
|
||||
<u-cell :title="$tt('debug.commandScope')">
|
||||
<u-radio-group slot="value" v-model="saveForm.isProductCommand" placement="row">
|
||||
<u-radio :name="true" :label="$tt('debug.productCommand')" iconSize="16"></u-radio>
|
||||
<u-radio :name="false" :label="$tt('debug.deviceCommand')" iconSize="16"></u-radio>
|
||||
</u-radio-group>
|
||||
</u-cell>
|
||||
</u-cell-group>
|
||||
<view class="popup-btns">
|
||||
<u-button type="default" @click="showSavePopup = false">{{$tt('debug.cancel')}}</u-button>
|
||||
<u-button type="primary" @click="handleConfirmSave" :loading="saving">{{$tt('debug.confirm')}}</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { int2hex } from '@/utils/common.js';
|
||||
import { encode, addPreferences, editPreferences } from '@/apis/modules/mqttTest.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
device: {},
|
||||
editItem: null,
|
||||
form: {
|
||||
slaveId: '01',
|
||||
issueType: '0xFFDD',
|
||||
functionCode: '01',
|
||||
startAddress: '0',
|
||||
startAddressHex: '0000',
|
||||
count: 1,
|
||||
writeValue: '0',
|
||||
writeValueHex: '0000'
|
||||
},
|
||||
registerValues: [],
|
||||
coilValues: [],
|
||||
reportValues: [],
|
||||
showIssueTypeSheet: false,
|
||||
showFunctionCodeSheet: false,
|
||||
issueTypeActions: [
|
||||
{ name: this.$tt('debug.issueCommand'), value: '0xFFDD' },
|
||||
{ name: this.$tt('debug.reportCommand'), value: '0xFFAA' }
|
||||
],
|
||||
functionCodeActions: [
|
||||
{ name: this.$tt('debug.code01'), value: '01' },
|
||||
{ name: this.$tt('debug.code02'), value: '02' },
|
||||
{ name: this.$tt('debug.code03'), value: '03' },
|
||||
{ name: this.$tt('debug.code04'), value: '04' },
|
||||
{ name: this.$tt('debug.code05'), value: '05' },
|
||||
{ name: this.$tt('debug.code06'), value: '06' },
|
||||
{ name: this.$tt('debug.code15'), value: '15' },
|
||||
{ name: this.$tt('debug.code16'), value: '16' }
|
||||
],
|
||||
generatedCommand: '',
|
||||
generating: false,
|
||||
showSavePopup: false,
|
||||
saveForm: {
|
||||
id: null,
|
||||
name: '',
|
||||
command: '',
|
||||
isProductCommand: false
|
||||
},
|
||||
saving: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
currentIssueTypeLabel() {
|
||||
const item = this.issueTypeActions.find(i => i.value === this.form.issueType);
|
||||
return item ? item.name : this.$tt('debug.issueCommand');
|
||||
},
|
||||
currentFunctionCodeLabel() {
|
||||
const item = this.functionCodeActions.find(i => i.value === this.form.functionCode);
|
||||
return item ? item.name : this.$tt('debug.code01');
|
||||
},
|
||||
showCount() {
|
||||
if (this.form.issueType === '0xFFAA') return false;
|
||||
return ['01', '02', '03', '04', '15', '16'].includes(this.form.functionCode);
|
||||
},
|
||||
showWriteValue() {
|
||||
if (this.form.issueType === '0xFFAA') return false;
|
||||
return ['05', '06'].includes(this.form.functionCode);
|
||||
},
|
||||
showRegisterValues() {
|
||||
if (this.form.issueType === '0xFFAA') return false;
|
||||
return this.form.functionCode === '16';
|
||||
},
|
||||
showCoilValues() {
|
||||
if (this.form.issueType === '0xFFAA') return false;
|
||||
return this.form.functionCode === '15';
|
||||
},
|
||||
showReportValues() {
|
||||
return this.form.issueType === '0xFFAA';
|
||||
},
|
||||
countLabel() {
|
||||
switch (this.form.functionCode) {
|
||||
case '01':
|
||||
case '02':
|
||||
case '15':
|
||||
return this.$tt('debug.coilCount');
|
||||
default:
|
||||
return this.$tt('debug.registerCount');
|
||||
}
|
||||
},
|
||||
writeValueLabel() {
|
||||
return this.form.functionCode === '05' ? this.$tt('debug.writeCoilValue') : this.$tt('debug.writeRegisterValue');
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'form.count'() {
|
||||
this.updateBatchValues();
|
||||
},
|
||||
'form.issueType'(val) {
|
||||
if (val === '0xFFAA') {
|
||||
this.form.functionCode = '03';
|
||||
}
|
||||
this.updateBatchValues();
|
||||
},
|
||||
'form.functionCode'() {
|
||||
this.updateBatchValues();
|
||||
}
|
||||
},
|
||||
onLoad(option) {
|
||||
if (option.device) {
|
||||
this.device = JSON.parse(decodeURIComponent(option.device));
|
||||
}
|
||||
if (option.item) {
|
||||
this.editItem = JSON.parse(decodeURIComponent(option.item));
|
||||
this.saveForm.id = this.editItem.id;
|
||||
this.saveForm.name = this.editItem.name;
|
||||
this.saveForm.command = this.editItem.command;
|
||||
this.saveForm.isProductCommand = this.editItem.productId != null;
|
||||
this.generatedCommand = this.editItem.command;
|
||||
}
|
||||
this.updateBatchValues();
|
||||
},
|
||||
methods: {
|
||||
// 更新批量值数组
|
||||
updateBatchValues() {
|
||||
// 批量寄存器值
|
||||
if (this.showRegisterValues) {
|
||||
const len = this.form.count;
|
||||
while (this.registerValues.length < len) {
|
||||
this.registerValues.push({ value: '0', valueHex: '0000' });
|
||||
}
|
||||
while (this.registerValues.length > len) {
|
||||
this.registerValues.pop();
|
||||
}
|
||||
} else {
|
||||
this.registerValues = [];
|
||||
}
|
||||
|
||||
// 批量线圈值
|
||||
if (this.showCoilValues) {
|
||||
const len = this.form.count;
|
||||
while (this.coilValues.length < len) {
|
||||
this.coilValues.push({ value: '0' });
|
||||
}
|
||||
while (this.coilValues.length > len) {
|
||||
this.coilValues.pop();
|
||||
}
|
||||
} else {
|
||||
this.coilValues = [];
|
||||
}
|
||||
|
||||
// 批量上报值
|
||||
if (this.showReportValues) {
|
||||
const len = this.form.count;
|
||||
while (this.reportValues.length < len) {
|
||||
this.reportValues.push({ value: '0', valueHex: '0000' });
|
||||
}
|
||||
while (this.reportValues.length > len) {
|
||||
this.reportValues.pop();
|
||||
}
|
||||
} else {
|
||||
this.reportValues = [];
|
||||
}
|
||||
},
|
||||
// 同步起始地址的十六进制值
|
||||
syncStartAddressHex() {
|
||||
this.form.startAddressHex = int2hex(this.form.startAddress);
|
||||
},
|
||||
// 同步写值的十六进制值
|
||||
syncWriteValueHex() {
|
||||
this.form.writeValueHex = int2hex(this.form.writeValue);
|
||||
},
|
||||
// 同步寄存器值的十六进制
|
||||
syncRegisterHex(index) {
|
||||
const item = this.registerValues[index];
|
||||
item.valueHex = int2hex(item.value);
|
||||
this.$set(this.registerValues, index, item);
|
||||
},
|
||||
// 同步上报值的十六进制
|
||||
syncReportHex(index) {
|
||||
const item = this.reportValues[index];
|
||||
item.valueHex = int2hex(item.value);
|
||||
this.$set(this.reportValues, index, item);
|
||||
},
|
||||
// 选择指令类型
|
||||
onIssueTypeSelect(item) {
|
||||
this.form.issueType = item.value;
|
||||
this.showIssueTypeSheet = false;
|
||||
},
|
||||
// 选择功能码
|
||||
onFunctionCodeSelect(item) {
|
||||
this.form.functionCode = item.value;
|
||||
this.showFunctionCodeSheet = false;
|
||||
},
|
||||
// 生成指令
|
||||
handleGenerate() {
|
||||
this.generating = true;
|
||||
const params = {
|
||||
slaveId: parseInt(this.form.slaveId) || 1,
|
||||
address: parseInt(this.form.startAddress) || 0,
|
||||
code: parseInt(this.form.functionCode),
|
||||
start: this.form.issueType,
|
||||
protocolCode: this.device.protocolCode,
|
||||
serialNumber: this.device.serialNumber
|
||||
};
|
||||
|
||||
// 上报模式
|
||||
if (this.form.issueType === '0xFFAA') {
|
||||
params.address = parseInt(this.form.startAddress) || 0;
|
||||
params.bitCount = this.form.count * 2;
|
||||
params.data = this.reportValues.map(i => parseInt(i.value) || 0);
|
||||
} else {
|
||||
switch (this.form.functionCode) {
|
||||
case '01':
|
||||
case '02':
|
||||
case '03':
|
||||
case '04':
|
||||
params.count = this.form.count;
|
||||
break;
|
||||
case '05':
|
||||
case '06':
|
||||
params.writeData = parseInt(this.form.writeValue) || 0;
|
||||
break;
|
||||
case '15':
|
||||
params.count = this.form.count;
|
||||
params.bitString = this.coilValues.map(i => i.value).join('');
|
||||
break;
|
||||
case '16':
|
||||
params.count = this.form.count;
|
||||
params.tenWriteData = this.registerValues.map(i => parseInt(i.value) || 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
encode(params).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.generatedCommand = res.msg;
|
||||
uni.showToast({ icon: 'success', title: this.$tt('debug.generateCommand') + '成功' });
|
||||
} else {
|
||||
uni.showToast({ icon: 'none', title: res.msg || this.$tt('debug.generateFail') });
|
||||
}
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
uni.showToast({ icon: 'none', title: this.$tt('debug.generateFail') });
|
||||
}).finally(() => {
|
||||
this.generating = false;
|
||||
});
|
||||
},
|
||||
// 保存为快捷指令
|
||||
handleSave() {
|
||||
if (!this.generatedCommand) {
|
||||
uni.showToast({ icon: 'none', title: '请先生成指令' });
|
||||
return;
|
||||
}
|
||||
this.saveForm.command = this.generatedCommand;
|
||||
this.showSavePopup = true;
|
||||
},
|
||||
// 确认保存
|
||||
handleConfirmSave() {
|
||||
if (!this.saveForm.name) {
|
||||
uni.showToast({ icon: 'none', title: this.$tt('debug.inputCommandName') });
|
||||
return;
|
||||
}
|
||||
this.saving = true;
|
||||
const data = {
|
||||
command: this.saveForm.command,
|
||||
name: this.saveForm.name,
|
||||
serialNumber: this.saveForm.isProductCommand ? null : this.device.serialNumber,
|
||||
productId: this.saveForm.isProductCommand ? this.device.productId : null
|
||||
};
|
||||
const apiCall = this.saveForm.id ? editPreferences({ ...data, id: this.saveForm.id }) : addPreferences(data);
|
||||
apiCall.then(res => {
|
||||
if (res.code === 200) {
|
||||
uni.showToast({ icon: 'success', title: this.$tt('debug.saveSuccess') });
|
||||
this.showSavePopup = false;
|
||||
uni.navigateBack();
|
||||
} else {
|
||||
uni.showToast({ icon: 'none', title: res.msg || this.$tt('debug.saveFail') });
|
||||
}
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
uni.showToast({ icon: 'none', title: this.$tt('debug.saveFail') });
|
||||
}).finally(() => {
|
||||
this.saving = false;
|
||||
});
|
||||
},
|
||||
// 复制指令
|
||||
handleCopy() {
|
||||
if (!this.generatedCommand) {
|
||||
uni.showToast({ icon: 'none', title: '请先生成指令' });
|
||||
return;
|
||||
}
|
||||
uni.setClipboardData({
|
||||
data: this.generatedCommand,
|
||||
success: () => {
|
||||
uni.showToast({ icon: 'success', title: this.$tt('debug.copySuccess') });
|
||||
}
|
||||
});
|
||||
},
|
||||
// 取消
|
||||
handleCancel() {
|
||||
uni.navigateBack();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
page {
|
||||
background: #eef3f7;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.edit-wrap {
|
||||
padding-bottom: 140rpx;
|
||||
|
||||
.form-section {
|
||||
margin: 20rpx;
|
||||
background: #fff;
|
||||
border-radius: 10rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hex-input-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.input {
|
||||
width: 120rpx;
|
||||
text-align: right;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.hex-hint {
|
||||
margin-left: 12rpx;
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
}
|
||||
|
||||
.batch-section {
|
||||
margin: 20rpx;
|
||||
padding: 20rpx;
|
||||
background: #fff;
|
||||
border-radius: 10rpx;
|
||||
|
||||
.batch-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.batch-list {
|
||||
.batch-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16rpx 0;
|
||||
border-bottom: 1rpx solid #f4f4f5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.batch-label {
|
||||
font-size: 26rpx;
|
||||
color: #606266;
|
||||
min-width: 80rpx;
|
||||
}
|
||||
|
||||
.batch-input {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.hex-hint {
|
||||
margin-left: 12rpx;
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
font-family: 'Courier New', monospace;
|
||||
min-width: 100rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.result-section {
|
||||
margin: 20rpx;
|
||||
padding: 20rpx;
|
||||
background: #fff;
|
||||
border-radius: 10rpx;
|
||||
|
||||
.result-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
.result-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.result-actions {
|
||||
.action-btn {
|
||||
font-size: 26rpx;
|
||||
color: #409eff;
|
||||
margin-left: 24rpx;
|
||||
|
||||
&.copy {
|
||||
color: #67c23a;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.result-content {
|
||||
padding: 20rpx;
|
||||
background: #f4f4f5;
|
||||
border-radius: 8rpx;
|
||||
word-wrap: break-word;
|
||||
|
||||
text {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
}
|
||||
|
||||
.result-empty {
|
||||
padding: 20rpx;
|
||||
text-align: center;
|
||||
|
||||
text {
|
||||
font-size: 26rpx;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-btns {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
padding: 20rpx;
|
||||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||||
background: #fff;
|
||||
border-top: 1rpx solid #eee;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.save-popup {
|
||||
padding: 30rpx;
|
||||
|
||||
.popup-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.cmd-text {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.popup-btns {
|
||||
display: flex;
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
655
yack-app/pagesB/home/device/debug/index.vue
Normal file
655
yack-app/pagesB/home/device/debug/index.vue
Normal file
@ -0,0 +1,655 @@
|
||||
<template>
|
||||
<view class="debug-wrap">
|
||||
<!-- 设备信息 -->
|
||||
<view class="device-info">
|
||||
<text class="label">{{$tt('home.serialNumber')}}:</text>
|
||||
<text class="value">{{device.serialNumber}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 日志列表 -->
|
||||
<view class="log-section">
|
||||
<view class="log-item" v-for="(item, index) in logList" :key="index"
|
||||
:class="{'send-item': item.type === 0, 'receive-item': item.type === 1, 'report-item': item.type === 2}">
|
||||
<view class="item-head">
|
||||
<text class="type-tag" :class="'type-' + item.type">
|
||||
{{item.type === 0 ? $tt('debug.sendType') : item.type === 1 ? $tt('debug.reveiceType') : $tt('debug.reportType')}}
|
||||
</text>
|
||||
<text class="time">{{item.time}}</text>
|
||||
<text class="analysis-btn" v-if="item.type !== 0 && !item.analysis" @click="handleAnalysis(item, index)">
|
||||
{{$tt('debug.analysis')}}
|
||||
</text>
|
||||
<text class="analysised-tag" v-if="item.type !== 0 && item.analysis">
|
||||
{{$tt('debug.analysised')}}
|
||||
</text>
|
||||
</view>
|
||||
<view class="item-content">
|
||||
<!-- JSON格式 -->
|
||||
<view v-if="isValidJson(item.valueAscii || item.value)" class="json-wrap">
|
||||
<text class="format-label">JSON:</text>
|
||||
<text class="json-text">{{formatJsonValue(item.value, item.valueAscii)}}</text>
|
||||
</view>
|
||||
<!-- HEX + ASCII -->
|
||||
<block v-else>
|
||||
<view v-if="item.value" class="hex-wrap">
|
||||
<text class="format-label">HEX:</text>
|
||||
<text class="hex-text">{{item.value}}</text>
|
||||
</view>
|
||||
<view v-if="item.valueAscii && hasReadableAscii(item.valueAscii)" class="ascii-wrap">
|
||||
<text class="format-label">ASCII:</text>
|
||||
<text class="ascii-text">{{item.valueAscii}}</text>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
<!-- 解析结果 -->
|
||||
<view class="analysis-result" v-if="item.type !== 0 && item.analysis">
|
||||
<view class="result-row">
|
||||
<text class="result-label">{{$tt('debug.name')}}:</text>
|
||||
<text class="result-value">{{item.analysisVal.name || '--'}}</text>
|
||||
</view>
|
||||
<view class="result-row">
|
||||
<text class="result-label">{{$tt('debug.identifier')}}:</text>
|
||||
<text class="result-value">{{item.analysisVal.id || '--'}}</text>
|
||||
</view>
|
||||
<view class="result-row">
|
||||
<text class="result-label">{{$tt('debug.value')}}:</text>
|
||||
<text class="result-value">{{item.analysisVal.value || '--'}}</text>
|
||||
</view>
|
||||
<view class="result-row" v-if="item.analysisList && item.analysisList.length > 1">
|
||||
<text class="more-btn" @click="showMoreAnalysis(item)">{{$tt('debug.more')}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<u-empty mode="list" :show="logList.length === 0" marginTop="30"></u-empty>
|
||||
</view>
|
||||
|
||||
<!-- 快捷指令区域 -->
|
||||
<view class="quick-command-wrap">
|
||||
<view class="section-title">
|
||||
<text>{{$tt('debug.quickCommand')}}</text>
|
||||
<text class="add-btn" @click="handleAddCommand">{{$tt('debug.addCommand')}}</text>
|
||||
</view>
|
||||
<u-swipe-action>
|
||||
<u-swipe-action-item v-for="(item, index) in quickList" :key="index"
|
||||
:options="swipeOptions" @click="handleSwipeClick($event, item)">
|
||||
<view class="quick-item" @click="handleQuickClick(item)">
|
||||
<view class="quick-item-name">{{item.name}}</view>
|
||||
<view class="quick-item-cmd">{{item.command}}</view>
|
||||
</view>
|
||||
</u-swipe-action-item>
|
||||
</u-swipe-action>
|
||||
<u-empty mode="list" :show="quickList.length === 0" marginTop="20"></u-empty>
|
||||
</view>
|
||||
|
||||
<!-- 底部输入区域 -->
|
||||
<view class="bottom-input">
|
||||
<view class="input-row">
|
||||
<view class="type-picker" @click="showDataTypePicker = true">
|
||||
<text>{{currentDataTypeLabel}}</text>
|
||||
<u-icon name="arrow-down" size="12" color="#606266"></u-icon>
|
||||
</view>
|
||||
<input class="send-input" v-model="sendVal" :placeholder="$tt('debug.inputPlaceholder')"
|
||||
@confirm="handleSend" />
|
||||
<view class="send-btn" @click="handleSend">{{$tt('debug.send')}}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 数据类型选择器 -->
|
||||
<u-action-sheet :show="showDataTypePicker" :actions="dataTypeActions" @select="onDataTypeSelect"
|
||||
@close="showDataTypePicker = false"></u-action-sheet>
|
||||
|
||||
<!-- 删除确认弹窗 -->
|
||||
<u-modal :show="showDeleteModal" showCancelButton :confirmText="$tt('common.confirm')"
|
||||
@cancel="() => showDeleteModal = false" :title="$tt('debug.deleteCommand')" :content="deleteContent"
|
||||
@confirm="handleConfirmDelete">
|
||||
</u-modal>
|
||||
|
||||
<!-- 更多解析弹窗 -->
|
||||
<u-popup :show="showMorePopup" mode="bottom" :round="10" @close="() => showMorePopup = false">
|
||||
<view class="more-popup">
|
||||
<view class="popup-title">{{$tt('debug.analysis')}}</view>
|
||||
<scroll-view scroll-y style="max-height: 400px;">
|
||||
<view class="more-item" v-for="(item, idx) in moreList" :key="idx">
|
||||
<view class="more-row">
|
||||
<text class="more-label">{{$tt('debug.name')}}:</text>
|
||||
<text>{{item.name}}</text>
|
||||
</view>
|
||||
<view class="more-row">
|
||||
<text class="more-label">{{$tt('debug.identifier')}}:</text>
|
||||
<text>{{item.id}}</text>
|
||||
</view>
|
||||
<view class="more-row">
|
||||
<text class="more-label">{{$tt('debug.value')}}:</text>
|
||||
<text>{{item.value}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { formatDate } from '@/utils/common.js';
|
||||
import { messagePost, preferencesList, delPreferences } from '@/apis/modules/mqttTest.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
device: {},
|
||||
logList: [],
|
||||
sendVal: '',
|
||||
dataType: 'hex',
|
||||
dataTypeActions: [
|
||||
{ name: 'HEX', value: 'hex' },
|
||||
{ name: 'JSON', value: 'json' },
|
||||
{ name: 'ASCII', value: 'ascii' }
|
||||
],
|
||||
showDataTypePicker: false,
|
||||
quickList: [],
|
||||
swipeOptions: [
|
||||
{ text: this.$tt('debug.editCommand'), style: { backgroundColor: '#3c9cff' } },
|
||||
{ text: this.$tt('debug.deleteCommand'), style: { backgroundColor: '#ff7e7e' } }
|
||||
],
|
||||
showDeleteModal: false,
|
||||
deleteContent: '',
|
||||
deleteItem: null,
|
||||
showMorePopup: false,
|
||||
moreList: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
currentDataTypeLabel() {
|
||||
const item = this.dataTypeActions.find(i => i.value === this.dataType);
|
||||
return item ? item.name : 'HEX';
|
||||
}
|
||||
},
|
||||
onLoad(option) {
|
||||
this.device = JSON.parse(decodeURIComponent(option.device));
|
||||
this.getQuickList();
|
||||
this.registerBus();
|
||||
},
|
||||
onUnload() {
|
||||
this.unregisterBus();
|
||||
},
|
||||
onShow() {
|
||||
this.getQuickList();
|
||||
},
|
||||
methods: {
|
||||
// 注册事件总线
|
||||
registerBus() {
|
||||
this.$bus.$on('updateData', this.handleUpdateData);
|
||||
},
|
||||
// 注销事件总线
|
||||
unregisterBus() {
|
||||
this.$bus.$off('updateData', this.handleUpdateData);
|
||||
},
|
||||
// 处理MQTT数据上报
|
||||
handleUpdateData(params) {
|
||||
const { serialNumber, data } = params;
|
||||
if (serialNumber !== this.device.serialNumber) return;
|
||||
this.logList.push({
|
||||
type: 2,
|
||||
time: formatDate(new Date()),
|
||||
value: data.sources || '',
|
||||
valueAscii: data.sourcesAscii || '',
|
||||
loading: false,
|
||||
analysis: false,
|
||||
analysisList: data.message || [],
|
||||
analysisVal: {}
|
||||
});
|
||||
},
|
||||
// 获取快捷指令列表
|
||||
getQuickList() {
|
||||
preferencesList({
|
||||
serialNumber: this.device.serialNumber,
|
||||
productId: this.device.productId
|
||||
}).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.quickList = res.rows || [];
|
||||
}
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
});
|
||||
},
|
||||
// 数据类型选择
|
||||
onDataTypeSelect(item) {
|
||||
this.dataType = item.value;
|
||||
this.showDataTypePicker = false;
|
||||
},
|
||||
// 发送指令
|
||||
handleSend() {
|
||||
if (!this.sendVal) {
|
||||
uni.showToast({ icon: 'none', title: this.$tt('debug.sendEmpty') });
|
||||
return;
|
||||
}
|
||||
messagePost({
|
||||
message: this.sendVal,
|
||||
serialNumber: this.device.serialNumber,
|
||||
topicName: '/function/get',
|
||||
dataType: this.dataType
|
||||
}).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.logList.push({
|
||||
type: 0,
|
||||
time: formatDate(new Date()),
|
||||
value: this.sendVal,
|
||||
valueAscii: '',
|
||||
loading: false,
|
||||
analysis: false,
|
||||
analysisList: [],
|
||||
analysisVal: {}
|
||||
});
|
||||
} else {
|
||||
uni.showToast({ icon: 'none', title: res.msg || this.$tt('debug.sendFail') });
|
||||
}
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
uni.showToast({ icon: 'none', title: this.$tt('debug.sendFail') });
|
||||
});
|
||||
},
|
||||
// 解析数据
|
||||
handleAnalysis(item, index) {
|
||||
if (item.analysisList && item.analysisList.length > 0) {
|
||||
item.analysis = true;
|
||||
item.analysisVal = {
|
||||
name: item.analysisList[0].name,
|
||||
value: item.analysisList[0].value,
|
||||
id: item.analysisList[0].id
|
||||
};
|
||||
this.$set(this.logList, index, item);
|
||||
} else {
|
||||
uni.showToast({ icon: 'none', title: this.$tt('debug.analysisEmpty') });
|
||||
}
|
||||
},
|
||||
// 显示更多解析
|
||||
showMoreAnalysis(item) {
|
||||
this.moreList = item.analysisList || [];
|
||||
this.showMorePopup = true;
|
||||
},
|
||||
// 判断是否有效JSON
|
||||
isValidJson(value) {
|
||||
if (!value) return false;
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
return typeof parsed === 'object' && parsed !== null;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
// 格式化JSON
|
||||
formatJsonValue(hexValue, asciiValue) {
|
||||
if (asciiValue) {
|
||||
try {
|
||||
const json = JSON.parse(asciiValue);
|
||||
return JSON.stringify(json, null, 2);
|
||||
} catch (e) {}
|
||||
}
|
||||
if (hexValue) {
|
||||
try {
|
||||
const json = JSON.parse(hexValue);
|
||||
return JSON.stringify(json, null, 2);
|
||||
} catch (e) {
|
||||
try {
|
||||
const hexStr = hexValue.replace(/\s+/g, '');
|
||||
let jsonStr = '';
|
||||
for (let i = 0; i < hexStr.length; i += 2) {
|
||||
const hexByte = hexStr.substr(i, 2);
|
||||
const charCode = parseInt(hexByte, 16);
|
||||
if (charCode >= 32 && charCode <= 126) {
|
||||
jsonStr += String.fromCharCode(charCode);
|
||||
}
|
||||
}
|
||||
if (jsonStr) {
|
||||
const json = JSON.parse(jsonStr);
|
||||
return JSON.stringify(json, null, 2);
|
||||
}
|
||||
} catch (e2) {}
|
||||
}
|
||||
}
|
||||
return '数据不是有效的JSON格式';
|
||||
},
|
||||
// 判断是否有可读ASCII
|
||||
hasReadableAscii(asciiValue) {
|
||||
if (!asciiValue) return false;
|
||||
const printableRegex = /[\x20-\x7E]/;
|
||||
return printableRegex.test(asciiValue) && asciiValue.trim().length > 0;
|
||||
},
|
||||
// 快捷指令点击
|
||||
handleQuickClick(item) {
|
||||
this.sendVal = item.command;
|
||||
},
|
||||
// 滑动按钮点击
|
||||
handleSwipeClick(e, item) {
|
||||
const { index } = e;
|
||||
if (index === 0) {
|
||||
uni.navigateTo({
|
||||
url: `/pagesB/home/device/debug/edit?device=${encodeURIComponent(JSON.stringify(this.device))}&item=${encodeURIComponent(JSON.stringify(item))}`
|
||||
});
|
||||
} else if (index === 1) {
|
||||
this.deleteItem = item;
|
||||
this.deleteContent = this.$tt('debug.deleteConfirm');
|
||||
this.showDeleteModal = true;
|
||||
}
|
||||
},
|
||||
// 确认删除
|
||||
handleConfirmDelete() {
|
||||
if (!this.deleteItem) return;
|
||||
const deleteId = this.deleteItem.id;
|
||||
this.showDeleteModal = false;
|
||||
delPreferences({ id: deleteId }).then(res => {
|
||||
if (res.code === 200) {
|
||||
uni.showToast({ icon: 'success', title: this.$tt('debug.deleteSuccess') });
|
||||
this.getQuickList();
|
||||
} else {
|
||||
uni.showToast({ icon: 'none', title: res.msg || this.$tt('debug.deleteFail') });
|
||||
}
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
uni.showToast({ icon: 'none', title: this.$tt('debug.deleteFail') });
|
||||
}).finally(() => {
|
||||
this.deleteItem = null;
|
||||
});
|
||||
},
|
||||
// 新建指令
|
||||
handleAddCommand() {
|
||||
uni.navigateTo({
|
||||
url: `/pagesB/home/device/debug/edit?device=${encodeURIComponent(JSON.stringify(this.device))}`
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
page {
|
||||
background: #eef3f7;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.debug-wrap {
|
||||
padding-bottom: 140rpx;
|
||||
|
||||
.device-info {
|
||||
padding: 24rpx 30rpx;
|
||||
background: #fff;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.label {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: #303133;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.log-section {
|
||||
padding: 0 20rpx;
|
||||
|
||||
.log-item {
|
||||
background: #fff;
|
||||
border-radius: 10rpx;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
&.send-item {
|
||||
border-left: 6rpx solid #e6a23c;
|
||||
}
|
||||
|
||||
&.receive-item {
|
||||
border-left: 6rpx solid #67c23a;
|
||||
}
|
||||
|
||||
&.report-item {
|
||||
border-left: 6rpx solid #409eff;
|
||||
}
|
||||
|
||||
.item-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
.type-tag {
|
||||
font-size: 24rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 4rpx;
|
||||
margin-right: 16rpx;
|
||||
|
||||
&.type-0 {
|
||||
background: rgba(230, 162, 60, 0.1);
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
&.type-1 {
|
||||
background: rgba(103, 194, 58, 0.1);
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
&.type-2 {
|
||||
background: rgba(64, 158, 255, 0.1);
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.time {
|
||||
flex: 1;
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.analysis-btn {
|
||||
font-size: 24rpx;
|
||||
color: #409eff;
|
||||
padding: 4rpx 12rpx;
|
||||
background: rgba(64, 158, 255, 0.1);
|
||||
border-radius: 4rpx;
|
||||
}
|
||||
|
||||
.analysised-tag {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
padding: 4rpx 12rpx;
|
||||
background: #f4f4f5;
|
||||
border-radius: 4rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.item-content {
|
||||
.format-label {
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
margin-right: 12rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.json-wrap, .hex-wrap, .ascii-wrap {
|
||||
margin-bottom: 12rpx;
|
||||
word-wrap: break-word;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.json-text, .hex-text, .ascii-text {
|
||||
font-size: 26rpx;
|
||||
color: #303133;
|
||||
font-family: 'Courier New', monospace;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
|
||||
.analysis-result {
|
||||
margin-top: 16rpx;
|
||||
padding-top: 16rpx;
|
||||
border-top: 1rpx solid #eee;
|
||||
|
||||
.result-row {
|
||||
display: flex;
|
||||
margin-bottom: 10rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.result-label {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
min-width: 120rpx;
|
||||
}
|
||||
|
||||
.result-value {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.more-btn {
|
||||
font-size: 24rpx;
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.quick-command-wrap {
|
||||
margin: 20rpx 20rpx 40rpx;
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
font-size: 28rpx;
|
||||
color: #409eff;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.quick-item {
|
||||
background: #fff;
|
||||
padding: 20rpx;
|
||||
border-radius: 10rpx;
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
.quick-item-name {
|
||||
font-size: 28rpx;
|
||||
color: #303133;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.quick-item-cmd {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-input {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #fff;
|
||||
padding: 20rpx;
|
||||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||||
border-top: 1rpx solid #eee;
|
||||
z-index: 100;
|
||||
|
||||
.input-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.type-picker {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100rpx;
|
||||
height: 70rpx;
|
||||
background: #f4f4f5;
|
||||
border-radius: 8rpx;
|
||||
margin-right: 16rpx;
|
||||
|
||||
text {
|
||||
font-size: 26rpx;
|
||||
color: #606266;
|
||||
margin-right: 4rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.send-input {
|
||||
flex: 1;
|
||||
height: 70rpx;
|
||||
background: #f4f4f5;
|
||||
border-radius: 8rpx;
|
||||
padding: 0 20rpx;
|
||||
font-size: 26rpx;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.send-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 120rpx;
|
||||
height: 70rpx;
|
||||
background: #3c9cff;
|
||||
border-radius: 8rpx;
|
||||
font-size: 26rpx;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.more-popup {
|
||||
padding: 30rpx;
|
||||
|
||||
.popup-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.more-item {
|
||||
background: #f4f4f5;
|
||||
padding: 20rpx;
|
||||
border-radius: 10rpx;
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
.more-row {
|
||||
display: flex;
|
||||
margin-bottom: 10rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.more-label {
|
||||
min-width: 120rpx;
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
text {
|
||||
font-size: 24rpx;
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -26,7 +26,7 @@ export function tansParams (params) {
|
||||
|
||||
/**
|
||||
* 参数处理,避免小程序跳转10层后无法跳转
|
||||
* @param url
|
||||
* @param url
|
||||
*/
|
||||
export function navigateBackTo (url) {
|
||||
const backUrl = url.slice(1);
|
||||
@ -35,4 +35,130 @@ export function navigateBackTo (url) {
|
||||
const index = pages.findIndex(item => item.route === backUrl);
|
||||
const backnum = num - index - 1;
|
||||
uni.navigateBack({ delta: backnum });
|
||||
}
|
||||
|
||||
/**
|
||||
* 深复制
|
||||
* @param {any} obj 需要复制的对象
|
||||
*/
|
||||
export function deepClone (obj) {
|
||||
if (typeof obj == 'object') {
|
||||
const objClone = Array.isArray(obj) ? [] : {}
|
||||
for (const key in obj) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
if (obj[key] && typeof obj[key] == 'object') {
|
||||
objClone[key] = deepClone(obj[key])
|
||||
} else {
|
||||
objClone[key] = obj[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
return objClone
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc 时间戳转日期毫秒
|
||||
* @param {Date} format 时间格式 Y:年,M:月,D:天 h:小时,m:分钟,s:秒 默认:'Y/M/D h:m'
|
||||
*/
|
||||
export function formatDate (arg, format) {
|
||||
let now
|
||||
if (!arg) {
|
||||
return null
|
||||
} else {
|
||||
now = new Date(arg)
|
||||
}
|
||||
format = format || 'Y.M.D h:m'
|
||||
|
||||
const year = now.getFullYear()
|
||||
let month = now.getMonth() + 1
|
||||
month = month >= 10 ? month : '0' + month
|
||||
let date = now.getDate()
|
||||
date = date >= 10 ? date : '0' + date
|
||||
let hour = now.getHours()
|
||||
hour = hour >= 10 ? hour : '0' + hour
|
||||
let minute = now.getMinutes()
|
||||
minute = minute >= 10 ? minute : '0' + minute
|
||||
let second = now.getSeconds()
|
||||
second = second >= 10 ? second : '0' + second
|
||||
return format
|
||||
.replace('Y', year)
|
||||
.replace('M', month)
|
||||
.replace('D', date)
|
||||
.replace('h', hour)
|
||||
.replace('m', minute)
|
||||
.replace('s', second)
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制文本(uni-app 剪贴板)
|
||||
* @param {string} copytext
|
||||
*/
|
||||
export function copyText (copytext) {
|
||||
uni.setClipboardData({
|
||||
data: copytext,
|
||||
success: () => {},
|
||||
})
|
||||
return { type: 'success', message: '复制成功' }
|
||||
}
|
||||
|
||||
/**
|
||||
* hex转10进制
|
||||
* @param hexStr
|
||||
*/
|
||||
export function hex2int (hexStr) {
|
||||
let twoStr = parseInt(hexStr, 16).toString(2) // 将十六转十进制,再转2进制
|
||||
let bitNum = hexStr.length * 4 // 1个字节 = 8bit ,0xff 一个 "f"就是4位
|
||||
if (twoStr.length < bitNum) {
|
||||
while (twoStr.length < bitNum) {
|
||||
twoStr = "0" + twoStr
|
||||
}
|
||||
}
|
||||
if (twoStr.substring(0, 1) == "0") {
|
||||
// 正数
|
||||
const two = parseInt(twoStr, 2) // 二进制转十进制
|
||||
return two
|
||||
} else {
|
||||
// 负数
|
||||
let twoStr_unsign = ""
|
||||
let two = parseInt(twoStr, 2) - 1 // 补码:(负数)反码+1,符号位不变;相对十进制来说也是 +1,但这里是负数,+1就是绝对值数据-1
|
||||
twoStr = two.toString(2)
|
||||
twoStr_unsign = twoStr.substring(1, bitNum) // 舍弃首位(符号位)
|
||||
// 去除首字符,将0转为1,将1转为0 反码
|
||||
twoStr_unsign = twoStr_unsign.replace(/0/g, "z")
|
||||
twoStr_unsign = twoStr_unsign.replace(/1/g, "0")
|
||||
twoStr_unsign = twoStr_unsign.replace(/z/g, "1")
|
||||
two = -parseInt(twoStr_unsign, 2)
|
||||
return two
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 10进制转16进制
|
||||
* @param hexStr
|
||||
*/
|
||||
export function int2hex (hexStr) {
|
||||
let hexInt = typeof hexStr == 'string' ? parseInt(hexStr, 10) : hexStr
|
||||
let hex = ''
|
||||
const LENGTH_04 = 4
|
||||
const LENGTH_15 = 15
|
||||
if (hexInt >= 0) {
|
||||
hex = hexInt.toString(16).toLocaleLowerCase()
|
||||
} else {
|
||||
let twoStr = (-hexInt - 1).toString(2)
|
||||
let sb = '000000000000000'
|
||||
twoStr = sb.slice(0, LENGTH_15 - twoStr.length) + twoStr
|
||||
// 将0转为1,将1转为0 反码
|
||||
twoStr = twoStr.replace(/0/g, "z")
|
||||
twoStr = twoStr.replace(/1/g, "0")
|
||||
twoStr = twoStr.replace(/z/g, "1")
|
||||
hex = parseInt(('1' + twoStr), 2).toString(16).toLocaleLowerCase()
|
||||
}
|
||||
// 长度不等于4 补全
|
||||
if (hex.length != LENGTH_04) {
|
||||
let sb = "0000"
|
||||
hex = sb.slice(0, LENGTH_04 - hex.length) + hex
|
||||
}
|
||||
return hex.toUpperCase()
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user