615 lines
17 KiB
Vue
615 lines
17 KiB
Vue
<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> |