fastbee/yack-app/pagesB/home/device/debug/edit.vue
2026-08-23 16:27:10 +08:00

615 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>