显卡逻辑完善

This commit is contained in:
1 2025-07-23 14:35:40 +08:00
parent ec7e9ff6b9
commit c470b9074e
4 changed files with 421 additions and 243 deletions

BIN
dist.zip

Binary file not shown.

View File

@ -76,10 +76,10 @@
:disabled="!basicSettings.screenEnabled">
</el-slider>
</el-form-item>
<el-form-item>
<!-- <el-form-item>
<el-button type="danger" @click="handleClearScreen"
:disabled="!basicSettings.screenEnabled">清除屏幕</el-button>
</el-form-item>
</el-form-item> -->
</el-form>
</el-card>
@ -110,7 +110,7 @@
<el-input-number v-model="screenParams.height" :min="1" :max="10000"></el-input-number>
</el-form-item>
<el-form-item label="屏幕角度">
<el-radio-group v-model="screenParams.angle">
<el-radio-group v-model="screenParams.angle" @change="drawAddProgramPreview">
<el-radio :label="0">0°</el-radio>
<el-radio :label="90">90°</el-radio>
<el-radio :label="180">180°</el-radio>
@ -356,11 +356,10 @@
<!-- 添加/编辑自定义节目对话框 -->
<el-dialog :title="isEditProgram ? '编辑自定义节目' : '添加自定义节目'" :visible.sync="addProgramDialogVisible" width="700px"
append-to-body>
<!-- <div
style="position:sticky;top:0;z-index:10;background:#fff;padding-top:24px;padding-bottom:32px;box-shadow:0 2px 8px #e0e0e0;text-align:center;margin-bottom:36px;width:600px;margin-left:auto;margin-right:auto;border-radius:16px;"> -->
<canvas ref="addProgramPreviewCanvas" width="640px" height="240px"
style="width:100%;height:240px;border:1px solid #e0e0e0;border-radius:16px;box-shadow:0 2px 8px #e0e0e0;background:#FFFFFF00; margin-bottom: 20px;"></canvas>
<!-- </div> -->
<div class="sticky-canvas-preview">
<canvas ref="addProgramPreviewCanvas" width="640px" height="240px"
style="width:100%;height:240px;border:1px solid #e0e0e0;border-radius:16px;box-shadow:0 2px 8px #e0e0e0;background:#FFFFFF00; margin-bottom: 20px;"></canvas>
</div>
<el-form :model="addProgramForm" ref="addProgramFormRef" label-width="60px" size="mini"
style="margin-bottom:0;">
<el-form-item label="节目模式" label-width="70px">
@ -442,6 +441,14 @@
</el-select>
</el-form-item>
</el-col>
<el-col :span="7">
<el-form-item label="字体(英)" label-width="70px">
<el-select v-model="zone.fontEn" placeholder="字体(英)" style="width:100%">
<el-option v-for="(item, idx) in addProgramFontEns" :key="idx" :label="item"
:value="idx" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="5">
<el-form-item label="字号" label-width="42px">
<el-select v-model="zone.fontSize" placeholder="字号" style="width:100%">
@ -522,22 +529,26 @@
<el-row :gutter="8">
<el-col :span="5">
<el-form-item label="X" label-width="20px">
<el-input-number v-model="zone.x" :min="0" style="width:100%" />
<el-input-number :value="zoneDisplay(zIdx).x" :min="0" style="width:100%"
@input="setZoneDisplay(zIdx, 'x', $event)" />
</el-form-item>
</el-col>
<el-col :span="5">
<el-form-item label="宽" label-width="24px">
<el-input-number v-model="zone.width" :min="1" style="width:100%" />
<el-input-number :value="zoneDisplay(zIdx).width" :min="1" style="width:100%"
@input="setZoneDisplay(zIdx, 'width', $event)" />
</el-form-item>
</el-col>
<el-col :span="5">
<el-form-item label="Y" label-width="20px">
<el-input-number v-model="zone.y" :min="0" style="width:100%" />
<el-input-number :value="zoneDisplay(zIdx).y" :min="0" style="width:100%"
@input="setZoneDisplay(zIdx, 'y', $event)" />
</el-form-item>
</el-col>
<el-col :span="5">
<el-form-item label="高" label-width="24px">
<el-input-number v-model="zone.height" :min="1" style="width:100%" />
<el-input-number :value="zoneDisplay(zIdx).height" :min="1" style="width:100%"
@input="setZoneDisplay(zIdx, 'height', $event)" />
</el-form-item>
</el-col>
</el-row>
@ -726,14 +737,15 @@ export default {
commonPhrase: 0,
displayText: '',
font: 0,
fontEn: 0, //
fontShape: 0,
fontSize: 0,
fontColor: 0,
fontColor: 0, //
fontBold: 0,
fontStretch: 0,
image: '',
imageSize: 1, // 32x32
imageColor: 0, // Red
imageColor: 0, //
effect: 0,
speed: 4,
stayTime: 5,
@ -767,7 +779,7 @@ export default {
addProgramFonts: ['宋体(中)', '黑体(中)', '楷体(中)'],
addProgramFontShapes: ['圆角(英)', '直角(英)'],
addProgramFontSizes: ['16px', '24px', '32px'],
addProgramFontColors: ['黑色', '红色', '绿色', '黄色'],
addProgramFontColors: ['红色', '绿色', '黄色'],
addProgramFontBold: ['不加粗', '加粗'],
addProgramFontStretch: ['不拉伸', '横向拉伸', '纵向拉伸'],
addProgramEffects: ['立即显示', '左移', '右移', '上移', '下移', '连续左移', '闪烁换页'],
@ -776,7 +788,10 @@ export default {
addProgramHorizontalAlign: ['居左对齐', '居中对齐', '居右对齐'],
addProgramVerticalAlign: ['顶部对齐', '居中对齐', '底部对齐'],
addProgramImageSizes: ['16x16', '32x32', '48x48', '64x64'],
addProgramImageColors: ['黑色', '红色', '绿色', '黄色'],
addProgramImageColors: ['红色', '绿色', '黄色'],
addProgramFontEns: [
'打字(英)', '长黑(英)', '白斜(英)', '线形(英)', '方斜(英)', '歌德(英)', '黑正(英)', '美术(英)', '手写(英)', '正圆(英)', '时钟(英)圆角(英)'
],
//
addProgramPreviewPage: [0], //
addProgramPreviewTimer: [], //
@ -961,13 +976,8 @@ export default {
const progListModel = this.deviceInfo.thingsModels.find(model => model.id === '102#progList');
if (progListModel) {
try {
const jsonStr = progListModel.shadow.replace('JSON=', '');
const data = JSON.parse(jsonStr);
//
data.del_prog = program.order;
progListModel.shadow = 'JSON=' + JSON.stringify(data);
//
progListModel.shadow = 'JSON=' + JSON.stringify({ del_prog: program.order });
this.mqttPublish(this.deviceInfo, progListModel).then(() => {
this.$message.success('删除指令已发送');
}).catch(error => {
@ -1014,28 +1024,37 @@ export default {
if (programData.aLst && programData.aLst.length > 0) {
console.log('解析分区数据:', JSON.stringify(programData.aLst));
this.addProgramForm.zones = programData.aLst.map(area => {
let x = area.size ? area.size.x : 0;
let y = area.size ? area.size.y : 0;
let w = area.size ? area.size.l : 32;
let h = area.size ? area.size.h : 64;
if (this.screenParams.angle === 90 || this.screenParams.angle === 270) {
[x, y] = [y, x];
[w, h] = [h, w];
}
const zone = {
playType: 0,
commonPhrase: 0,
displayText: '',
font: 0,
fontEn: 0, //
fontShape: 0,
fontSize: 0,
fontColor: 0,
fontColor: 0, //
fontBold: 0,
fontStretch: 0,
image: '',
imageSize: 1,
imageColor: 0,
imageColor: 0, //
effect: 0,
speed: 4,
stayTime: 5,
hAlign: 1,
vAlign: 1,
x: area.size ? area.size.x : 0,
y: area.size ? area.size.y : 0,
width: area.size ? area.size.l : 32,
height: area.size ? area.size.h : 64
x: x,
y: y,
width: w,
height: h
};
//
@ -1046,12 +1065,13 @@ export default {
zone.playType = 0;
zone.displayText = item.txt.str ? item.txt.str.replace(/\0/g, '') : '';
zone.font = item.txt.fCn ? item.txt.fCn - 1 : 0; //
zone.fontEn = item.txt.fEn ? item.txt.fEn - 1 : 0; //
zone.fontSize = item.txt.fS ? this.getFontSizeIndex(item.txt.fS) : 0; //
zone.fontColor = item.txt.col || 0; //
zone.fontBold = item.txt.fW || 0; //
zone.fontStretch = item.txt.stch || 0; //
zone.hAlign = item.txt.hPos || 1; //
zone.vAlign = item.txt.vPos || 1; //
zone.hAlign = typeof item.txt.hPos === 'number' ? Math.max(item.txt.hPos - 1, 0) : 1; //
zone.vAlign = typeof item.txt.vPos === 'number' ? Math.max(item.txt.vPos - 1, 0) : 1; //
} else if (item.typ === 1 && item.img) {
//
zone.playType = 1;
@ -1077,14 +1097,15 @@ export default {
commonPhrase: 0,
displayText: '',
font: 0,
fontEn: 0, //
fontShape: 0,
fontSize: 0,
fontColor: 0,
fontColor: 0, //
fontBold: 0,
fontStretch: 0,
image: '',
imageSize: 1,
imageColor: 0,
imageColor: 0, //
effect: 0,
speed: 4,
stayTime: 5,
@ -1099,6 +1120,7 @@ export default {
//
this.$nextTick(() => {
this.onAddProgramModeChange(this.addProgramForm.mode); //
this.drawAddProgramPreview();
});
},
@ -2087,6 +2109,7 @@ export default {
this.$set(this.screenParams, 'width', params.w);
this.$set(this.screenParams, 'height', params.h);
this.$set(this.screenParams, 'angle', params.angle);
this.drawAddProgramPreview(); //
}
},
handleOePolarityChange(val) {
@ -2187,14 +2210,15 @@ export default {
commonPhrase: 0,
displayText: '',
font: 0,
fontEn: 0, //
fontShape: 0,
fontSize: 0,
fontColor: 0,
fontColor: 0, //
fontBold: 0,
fontStretch: 0,
image: '',
imageSize: 1,
imageColor: 0,
imageColor: 0, //
effect: 0,
speed: 4,
stayTime: 5,
@ -2222,14 +2246,15 @@ export default {
commonPhrase: 0,
displayText: '',
font: 0,
fontEn: 0, //
fontShape: 0,
fontSize: 0,
fontColor: 0,
fontColor: 0, //
fontBold: 0,
fontStretch: 0,
image: '',
imageSize: 1,
imageColor: 0,
imageColor: 0, //
effect: 0,
speed: 4,
stayTime: 5,
@ -2282,77 +2307,97 @@ export default {
const progListModel = this.deviceInfo.thingsModels.find(model => model.id === '102#progList');
if (progListModel) {
try {
//
const programData = {
del_prog: 0, //
prog_list: [{
order: this.isEditProgram ? this.editingProgram.order : 0, // 使
rem: (this.addProgramForm.remark || `自定义节目${Date.now()}`) + '\0', // \0
dur: this.addProgramForm.duration, // ()
areaM: this.addProgramForm.mode, //
aLst: this.addProgramForm.zones.map(zone => {
//
const area = {
size: {
x: zone.x, // X
y: zone.y, // Y
l: zone.width, //
h: zone.height //
// order
let order;
if (this.isEditProgram && this.editingProgram) {
order = this.editingProgram.order;
} else {
// order
const usedOrders = this.programList.map(p => p.order);
order = 0;
while (usedOrders.includes(order) && order < 10) {
order++;
}
}
//
const programObj = {
order: order,
rem: (this.addProgramForm.remark || `自定义节目${Date.now()}`) + '\0', // \0
dur: this.addProgramForm.duration, // ()
areaM: this.addProgramForm.mode, //
aLst: this.addProgramForm.zones.map(zone => {
//
let x = zone.x, y = zone.y, w = zone.width, h = zone.height;
if (this.screenParams.angle === 90 || this.screenParams.angle === 270) {
[x, y] = [y, x];
[w, h] = [h, w];
}
const area = {
size: {
x: x, // X
y: y, // Y
l: w, //
h: h //
},
pLst: []
};
//
if (zone.playType === 0) {
//
const textItem = {
typ: 0,
anim: {
typ: zone.effect + 1, //
spd: zone.speed, //
pauseT: this.getPauseTime(zone.stayTime), //
playT: 2000 //
},
pLst: []
txt: {
str: zone.displayText + '\0', // \0
col: zone.fontColor + 1, //
fS: this.getFontSize(zone.fontSize), //
fCn: zone.font + 1, //
fEn: zone.fontEn + 1, //
fW: zone.fontBold, //
stch: zone.fontStretch, //
hPos: zone.hAlign - 1, //
vPos: zone.vAlign - 1 //
}
};
area.pLst.push(textItem);
} else if (zone.playType === 1) {
//
const imageItem = {
typ: 1,
anim: {
typ: zone.effect + 1,
spd: zone.speed,
pauseT: this.getPauseTime(zone.stayTime),
playT: 2000
},
img: {
num: this.getImageNumber(zone.image), //
col: zone.imageColor, //
data: '', //
w: this.getImageSize(zone.imageSize), //
h: this.getImageSize(zone.imageSize) //
}
};
area.pLst.push(imageItem);
}
//
if (zone.playType === 0) {
//
const textItem = {
typ: 0,
anim: {
typ: zone.effect + 1, //
spd: zone.speed, //
pauseT: this.getPauseTime(zone.stayTime), //
playT: 2000 //
},
txt: {
str: zone.displayText + '\0', // \0
col: zone.fontColor, //
fS: this.getFontSize(zone.fontSize), //
fCn: zone.font + 1, //
fEn: 7, //
fW: zone.fontBold, //
stch: zone.fontStretch, //
hPos: zone.hAlign, //
vPos: zone.vAlign //
}
};
area.pLst.push(textItem);
} else if (zone.playType === 1) {
//
const imageItem = {
typ: 1,
anim: {
typ: zone.effect + 1,
spd: zone.speed,
pauseT: this.getPauseTime(zone.stayTime),
playT: 2000
},
img: {
num: this.getImageNumber(zone.image), //
col: zone.imageColor, //
data: '', //
w: this.getImageSize(zone.imageSize), //
h: this.getImageSize(zone.imageSize) //
}
};
area.pLst.push(imageItem);
}
return area;
})
}]
return area;
})
};
//
const programData = {
del_prog: 0, //
prog_list: [programObj]
};
//
progListModel.shadow = 'JSON=' + JSON.stringify(programData);
this.mqttPublish(this.deviceInfo, progListModel).then(() => {
this.addProgramDialogVisible = false;
@ -2376,8 +2421,14 @@ export default {
const ctx = canvas.getContext('2d');
//
const screenW = this.screenParams.width || 32;
const screenH = this.screenParams.height || 64;
let screenW = this.screenParams.width || 32;
let screenH = this.screenParams.height || 64;
// 90270
const angle = this.screenParams.angle;
let swapWH = angle === 90 || angle === 270;
if (swapWH) {
[screenW, screenH] = [screenH, screenW];
}
//
const renderWidth = 640; //
@ -2414,11 +2465,19 @@ export default {
const asset = this.addProgramPreviewAssets[zIdx];
if (!asset) return;
//
const zoneX = offsetX + zone.x * scale;
const zoneY = offsetY + zone.y * scale;
const zoneWidth = zone.width * scale;
const zoneHeight = zone.height * scale;
// 90270x/y/width/height
let zoneX, zoneY, zoneWidth, zoneHeight;
if (swapWH) {
zoneX = offsetX + (zone.y || 0) * scale;
zoneY = offsetY + (zone.x || 0) * scale;
zoneWidth = (zone.height || 0) * scale;
zoneHeight = (zone.width || 0) * scale;
} else {
zoneX = offsetX + (zone.x || 0) * scale;
zoneY = offsetY + (zone.y || 0) * scale;
zoneWidth = (zone.width || 0) * scale;
zoneHeight = (zone.height || 0) * scale;
}
// 线
ctx.save();
@ -2443,10 +2502,19 @@ export default {
const pageLines = asset.pages[page] || [];
//
const colors = ['#000000', '#FF0000', '#00FF00', '#FFFF00'];
const colors = ['#FF0000', '#00FF00', '#FFFF00'];
ctx.fillStyle = colors[zone.fontColor] || '#000000';
ctx.textBaseline = 'top';
ctx.font = `${asset.fontWeight} ${asset.scaledFontSize}px ${asset.fontFamily}`;
// zone.fontEn
let fontFamily = ['SimSun', 'SimHei', 'KaiTi'][zone.font] || 'SimSun';
if (zone.fontEn !== undefined && zone.fontEn !== null) {
//
const enFontMap = [
'Courier New', 'Arial Black', 'Arial Italic', 'Lucida Console', 'Impact', 'Gothic', 'Arial Narrow', 'Comic Sans MS', 'Brush Script MT', 'Century Gothic', 'Times New Roman'
];
fontFamily = enFontMap[zone.fontEn] || fontFamily;
}
ctx.font = `${asset.fontWeight} ${asset.scaledFontSize}px ${fontFamily}`;
//
const lineHeight = asset.scaledFontSize * 1.2;
@ -2570,8 +2638,14 @@ export default {
if (!canvas) return;
//
const screenW = this.screenParams.width || 32;
const screenH = this.screenParams.height || 64;
let screenW = this.screenParams.width || 32;
let screenH = this.screenParams.height || 64;
// 90270
const angle = this.screenParams.angle;
const swapWH = angle === 90 || angle === 270;
if (swapWH) {
[screenW, screenH] = [screenH, screenW];
}
const renderWidth = 640; //
const scale = Math.min(renderWidth / screenW, 240 / screenH);
@ -2610,7 +2684,8 @@ export default {
measureCtx.font = `${fontWeight} ${scaledFontSize}px ${fontFamily}`;
//
const maxWidth = zone.width * scale - 4; // 4px
const zoneRenderWidth = swapWH ? zone.height : zone.width;
const maxWidth = zoneRenderWidth * scale - 4; // 4px
let currentLine = '';
const lines = [];
@ -2629,8 +2704,9 @@ export default {
if (currentLine) lines.push(currentLine);
//
const zoneRenderHeight = swapWH ? zone.width : zone.height;
const lineHeight = scaledFontSize * 1.2;
const maxLines = Math.max(1, Math.floor((zone.height * scale - 4) / lineHeight));
const maxLines = Math.max(1, Math.floor((zoneRenderHeight * scale - 4) / lineHeight));
//
const pages = [];
@ -2901,6 +2977,40 @@ export default {
}
},
// ================= END =================
// index
zoneDisplay(zIdx) {
const zone = this.addProgramForm.zones[zIdx];
if (!zone) return { x: 0, y: 0, width: 0, height: 0 };
if (this.screenParams.angle === 90 || this.screenParams.angle === 270) {
return {
x: zone.y,
y: zone.x,
width: zone.height,
height: zone.width
};
} else {
return {
x: zone.x,
y: zone.y,
width: zone.width,
height: zone.height
};
}
},
// UI
setZoneDisplay(zIdx, key, value) {
const zone = this.addProgramForm.zones[zIdx];
if (!zone) return;
if (this.screenParams.angle === 90 || this.screenParams.angle === 270) {
//
if (key === 'x') zone.y = value;
else if (key === 'y') zone.x = value;
else if (key === 'width') zone.height = value;
else if (key === 'height') zone.width = value;
} else {
zone[key] = value;
}
},
},
};
</script>
@ -3321,4 +3431,17 @@ export default {
background: #a8a8a8;
}
}
/* 弹窗内canvas预览区固定顶部 */
.sticky-canvas-preview {
position: sticky;
top: 0;
z-index: 10;
background: #fff;
padding-top: 24px;
padding-bottom: 16px;
box-shadow: 0 2px 8px #e0e0e0;
border-radius: 16px;
margin-bottom: 20px;
}
</style>

View File

@ -76,20 +76,21 @@
<!-- 子设备标题 -->
<el-tabs v-model="activeTab" type="card" @tab-click="handleTabClick">
<el-tab-pane label="显示屏控制" name="display" style="line-height: 42px; height: 42px;">
<el-tab-pane v-if="availableTabs.includes('display')" label="显示屏控制" name="display"
style="line-height: 42px; height: 42px;">
<div class="tab-content">
<display-component :device="device" ref="displayComponent" v-if="activeTab === 'display'">
</display-component>
</div>
</el-tab-pane>
<el-tab-pane label="声卡控制" name="voicecard">
<el-tab-pane v-if="availableTabs.includes('voicecard')" label="声卡控制" name="voicecard">
<div class="tab-content">
<voicecard-component :device="device" ref="voicecardComponent" v-if="activeTab === 'voicecard'">
</voicecard-component>
</div>
</el-tab-pane>
<el-tab-pane label="网关设置" name="gatewaySet">
<el-tab-pane v-if="availableTabs.includes('gatewaySet')" label="网关设置" name="gatewaySet">
<div class="tab-content">
<gatewaySet :device="device" ref="gatewaySet" v-if="activeTab === 'gatewaySet'">
</gatewaySet>
@ -145,17 +146,17 @@
$t('device.running-status.866086-12') }}</el-link>
</template>
<el-descriptions-item :label="$t('device.running-status.866086-13')">{{ firmware.firmwareName
}}</el-descriptions-item>
}}</el-descriptions-item>
<el-descriptions-item :label="$t('device.device-edit.148398-4')">{{ firmware.productName
}}</el-descriptions-item>
}}</el-descriptions-item>
<el-descriptions-item :label="$t('device.device-edit.148398-12')">Version {{ firmware.version
}}</el-descriptions-item>
}}</el-descriptions-item>
<el-descriptions-item :label="$t('device.running-status.866086-16')">
<el-link :href="getDownloadUrl(firmware.filePath)" :underline="false" type="primary">{{
getDownloadUrl(firmware.filePath) }}</el-link>
</el-descriptions-item>
<el-descriptions-item :label="$t('device.running-status.866086-17')">{{ firmware.remark
}}</el-descriptions-item>
}}</el-descriptions-item>
</el-descriptions>
<div slot="footer" class="dialog-footer">
<el-button type="success" @click="otaUpgrade"
@ -175,6 +176,14 @@ import { getOrderControl } from '@/api/iot/control';
import DisplayComponent from './display.vue';
import VoicecardComponent from './voicecard.vue';
import gatewaySet from './gateway-set.vue';
//
const PRODUCT_TYPE_MAP = {
102: 'display', //
103: 'voicecard', //
1: 'gatewaySet', //
};
export default {
name: 'gateway',
components: {
@ -242,6 +251,14 @@ export default {
},
};
},
computed: {
// Tab
availableTabs() {
// deviceInfo.product undefined/null
const arr = (this.deviceInfo.product || []).slice(1); //
return arr.map(type => PRODUCT_TYPE_MAP[type]).filter(Boolean);
}
},
watch: {
device: {
handler(newVal) {
@ -256,18 +273,60 @@ export default {
if (this.deviceInfo.chartList && this.deviceInfo.chartList.length > 0) {
this.deviceInfo.chartList = this.deviceInfo.chartList.sort((a, b) => b.order - a.order);
}
//
this.parseProductFromThingsModels();
}
// activeTab availableTabs Tab
this.$nextTick(() => {
if (!this.availableTabs.includes(this.activeTab) && this.availableTabs.length > 0) {
this.activeTab = this.availableTabs[0];
}
});
},
},
// availableTabs activeTab
availableTabs(newTabs) {
if (!newTabs.includes(this.activeTab) && newTabs.length > 0) {
this.activeTab = newTabs[0];
}
}
},
mounted() {
if (this.device && this.device.deviceId) {
this.handleDeviceChange(this.device);
this.initDataStatus();
this.initData();
// mounted
this.parseProductFromThingsModels();
}
},
methods: {
// thingsModels 1#productpram product
parseProductFromThingsModels() {
const productModel = (this.deviceInfo.thingsModels || []).find(
item => item.id === '1#productpram'
);
if (productModel) {
let valueStr = productModel.value || productModel.shadow || '';
let match = valueStr.match(/JSON=(.*)/);
if (match) {
try {
let json = JSON.parse(match[1]);
if (json.product) {
this.deviceInfo.product = json.product;
//
console.log('解析到的 product:', this.deviceInfo.product);
}
} catch (e) {
console.warn('解析 product JSON 失败:', e, valueStr);
}
}
} else {
console.warn('未找到 1#productpram 物模型');
}
},
// Tab
handleTabClick(tab) {
console.log('切换到:', tab.name);

View File

@ -12,12 +12,7 @@
</div>
<div class="status-item">
<span class="status-label">信号强度</span>
<el-rate
v-model="signalStrength"
disabled
:colors="['#99A9BF', '#F7BA2A', '#FF9900']"
:max="3"
/>
<el-rate v-model="signalStrength" disabled :colors="['#99A9BF', '#F7BA2A', '#FF9900']" :max="3" />
</div>
</div>
@ -27,20 +22,10 @@
<h3 class="section-title">
<i class="el-icon-s-platform"></i> 产品选择
</h3>
<el-select
v-model="selectedProduct"
placeholder="请选择产品"
class="enhanced-select"
size="medium"
filterable
@focus="loadProductList"
>
<el-option
v-for="product in productModels"
:key="product.id"
:label="`${product.name} (${product.id})`"
:value="product.id"
/>
<el-select v-model="selectedProduct" placeholder="请选择产品" class="enhanced-select" size="medium" filterable
@focus="loadProductList">
<el-option v-for="product in productModels" :key="product.id" :label="`${product.name} (${product.id})`"
:value="product.id" />
</el-select>
</div>
@ -49,13 +34,8 @@
<i class="el-icon-menu"></i> 功能选择
</h3>
<div class="page-control-grid">
<div
v-for="(page, index) in pageList"
:key="index"
class="page-item"
:class="{ 'active': selectedPages[index] }"
@click="togglePage(index)"
>
<div v-for="(page, index) in pageList" :key="index" class="page-item"
:class="{ 'active': selectedPages[index] }" @click="togglePage(index)">
<div class="page-icon">
<i :class="page.icon"></i>
</div>
@ -88,22 +68,11 @@
<!-- 操作按钮区 -->
<div class="action-buttons">
<el-button
type="primary"
icon="el-icon-s-promotion"
@click="sendControlCommand"
class="action-button"
:loading="sending"
:disabled="!selectedProduct || !deviceInfo.deviceId"
>
<el-button type="primary" icon="el-icon-s-promotion" @click="sendControlCommand" class="action-button"
:loading="sending" :disabled="!selectedProduct || !deviceInfo.deviceId">
发送指令
</el-button>
<el-button
type="info"
icon="el-icon-refresh"
@click="resetSettings"
class="action-button"
>
<el-button type="info" icon="el-icon-refresh" @click="resetSettings" class="action-button">
重置选择
</el-button>
</div>
@ -139,14 +108,14 @@ export default {
signalStrength: 2,
productModels: [],
pageList: [
{ name: '显示设置', icon: 'el-icon-data-line' },
{ name: '声卡设置', icon: 'el-icon-headset' },
{ name: '车牌识别', icon: 'el-icon-camera' },
{ name: '预警设置', icon: 'el-icon-warning' },
{ name: '远程喊话', icon: 'el-icon-microphone' },
{ name: '网关设置', icon: 'el-icon-connection' },
{ name: '路锥控制', icon: 'el-icon-place' },
{ name: '翻转屏', icon: 'el-icon-monitor' }
{ name: '显示单元', icon: 'el-icon-data-line', code: 102 },
{ name: '声卡设置', icon: 'el-icon-headset', code: 103 },
// { name: '', icon: 'el-icon-camera' },
// { name: '', icon: 'el-icon-warning' },
// { name: '', icon: 'el-icon-microphone' },
{ name: '网关设置', icon: 'el-icon-connection', code: 1 },
{ name: '路锥控制', icon: 'el-icon-place', code: 104 },
{ name: '激光单元', icon: 'el-icon-monitor', code: 105 }
],
loadingProducts: false,
deviceInfo: {
@ -188,6 +157,15 @@ export default {
if (!this.selectedProduct) return '未选择';
const product = this.productModels.find(p => p.id === this.selectedProduct);
return product ? `${product.name} (${product.id})` : this.selectedProduct;
},
productArray() {
const arr = [Number(this.selectedProduct)];
this.selectedPages.forEach((selected, idx) => {
if (selected && this.pageList[idx] && this.pageList[idx].code) {
arr.push(this.pageList[idx].code);
}
});
return arr;
}
},
watch: {
@ -205,7 +183,7 @@ export default {
isShadow: newVal.isShadow,
protocolCode: newVal.protocolCode
};
// distributeshadow
const distributeModel = this.deviceInfo.thingsModels.find(
model => model.id === 'distribute'
@ -225,7 +203,7 @@ export default {
methods: {
async loadProductList() {
if (this.productModels.length > 0 || this.loadingProducts) return;
this.loadingProducts = true;
try {
const params = {
@ -243,13 +221,13 @@ export default {
this.loadingProducts = false;
}
},
togglePage(index) {
this.$set(this.selectedPages, index, this.selectedPages[index] ? 0 : 1);
// distributeshadow
this.updateDistributeModel();
},
updateDistributeModel() {
const distributeModel = this.deviceInfo.thingsModels.find(
model => model.id === 'distribute'
@ -258,7 +236,8 @@ export default {
distributeModel.shadow = this.fullCommand;
}
},
async sendControlCommand() {
if (!this.selectedProduct) {
this.$message.warning('请选择产品');
@ -268,80 +247,97 @@ export default {
this.$message.warning('设备信息未初始化');
return;
}
this.sending = true;
const payload = { product: this.productArray };
try {
const distributeModel = this.deviceInfo.thingsModels.find(
model => model.id === 'productpram'
);
if (!distributeModel) {
throw new Error('未找到分发控制模型');
}
// 102#model1
const model = this.deviceInfo.thingsModels.find(m => m.id === '1#productpram');
if (model) {
model.shadow = 'JSON=' + JSON.stringify(payload);
await this.mqttPublish(this.deviceInfo, model);
this.$message.success('已发送刷新指令,编译信息将自动更新');
this.sending = false;
// shadow
distributeModel.shadow = this.fullCommand;
await this.mqttPublish(this.deviceInfo, distributeModel);
this.$message.success('控制指令发送成功');
} catch (error) {
console.error('发送控制指令失败:', error);
this.$message.error(`控制指令发送失败: ${error.message}`);
} finally {
this.sending = false;
}
},
async mqttPublish(device, model) {
try {
if (!device || !device.deviceId) {
throw new Error('无效的设备信息');
}
if (!model || !model.id) {
throw new Error('无效的模型信息');
}
const command = {};
command[model.id] = model.shadow;
const params = {
deviceId: device.deviceId,
modelId: model.modelId,
};
const response = await getOrderControl(params);
if (response.code != 200) {
throw new Error(response.msg || '获取控制指令失败');
}
const data = {
serialNumber: device.serialNumber,
productId: device.productId,
remoteCommand: command,
identifier: model.id,
modelName: model.name,
isShadow: device.status != 3,
type: model.type,
};
if (device.status !== 3 && device.isShadow !== 1) {
throw new Error('设备不在线且未启用影子模式');
}
if ((device.protocolCode === 'MODBUS-TCP' || device.protocolCode === 'MODBUS-RTU') && device.status === 3) {
await serviceInvokeReply(data);
} else {
await serviceInvoke(data);
this.$message.warning('未找到102#model物模型');
this.sending = false;
}
} catch (error) {
console.error('MQTT发布失败:', error);
throw error;
console.error('发送刷新指令失败:', error);
this.sending = false;
this.$message.error('发送刷新指令失败');
}
},
//
async mqttPublish(device, model) {
const command = {};
command[model.id] = model.shadow;
const params = {
deviceId: device.deviceId,
modelId: model.modelId,
};
const response = await getOrderControl(params);
if (response.code != 200) {
this.$message({
type: 'warning',
message: response.msg,
});
return;
}
const data = {
serialNumber: device.serialNumber,
productId: device.productId,
remoteCommand: command,
identifier: model.id,
modelName: model.name,
isShadow: device.status != 3,
type: model.type,
};
//线
if (this.device.status !== 3 && this.device.isShadow !== 1) {
let title = '';
if (this.device.status === 1) {
title = this.$t('device.device-variable.930930-0');
} else if (this.device.status === 2) {
title = this.$t('device.device-variable.930930-1');
} else {
title = this.$t('device.device-variable.930930-2');
}
this.$message({
type: 'warning',
message: title,
});
return;
}
if ((this.deviceInfo.protocolCode === 'MODBUS-TCP' || this.deviceInfo.protocolCode === 'MODBUS-RTU') && this.device.status === 3) {
await serviceInvokeReply(data).then((response) => {
if (response.code === 200) {
this.$message({
type: 'success',
message: this.$t('device.running-status.866086-25'),
});
} else {
this.$message.error(response.msg);
}
});
} else {
await serviceInvoke(data).then((response) => {
if (response.code === 200) {
this.$message({
type: 'success',
message: this.$t('device.running-status.866086-25'),
});
} else {
this.$message.error(response.msg);
}
});
}
},
resetSettings() {
this.selectedPages = [0, 0, 0, 0, 0, 0, 0, 0];
this.updateDistributeModel();