diff --git a/dist.zip b/dist.zip index 00b1811..e306d4d 100644 Binary files a/dist.zip and b/dist.zip differ diff --git a/src/views/iot/device/device-edit.vue b/src/views/iot/device/device-edit.vue index 7c02150..cd89fb9 100644 --- a/src/views/iot/device/device-edit.vue +++ b/src/views/iot/device/device-edit.vue @@ -69,7 +69,7 @@ + }} @@ -195,8 +195,8 @@ ref="voicecard" :device="form" @statusEvent="getDeviceStatusData($event)" /> - + @@ -293,10 +293,10 @@ - + {{ $t('device.device-edit.148398-81') }} @@ -415,7 +415,7 @@ @@ -461,7 +461,7 @@ import defaultSettings from '@/settings'; import gatewayRunningStatus from './gatewayrunning-status.vue'; import relay from './relay.vue' import gatewaypre from './gatewaypre.vue' -import acousto_optic from'./acousto_optic.vue' +import acousto_optic from './acousto_optic.vue' import voicecard from './voicecard.vue'; import display from './display.vue' import gateway from './gateway.vue' @@ -1382,8 +1382,8 @@ export default { color: #fff; background-color: #486ff2; border-radius: 4px; - height: 32px; - line-height: 34px; + /* height: 32px; */ + /* line-height: 34px; */ } .alert-wrap { diff --git a/src/views/iot/device/display.vue b/src/views/iot/device/display.vue index b067b2d..dfb241a 100644 --- a/src/views/iot/device/display.vue +++ b/src/views/iot/device/display.vue @@ -8,14 +8,14 @@
- + {{ $t('device.running-status.866086-0') }}
{{ title }} - +
@@ -24,7 +24,7 @@
- + {{ $t('device.running-status.866086-1') }}
@@ -36,6 +36,29 @@ + + +
+
+ + 编译信息 +
+ + 刷新 + +
+
+
+ 编译版本: + {{ buildVersion || '加载中...' }} +
+
+ 编译时间: + {{ buildTime || '加载中...' }} +
+
+
+
@@ -67,11 +90,9 @@
- - - - - + + @@ -149,6 +170,38 @@
+ + +
+ 传感器阈值设置 +
+ + + + + + + + + + + + + + + + + +
+ 下发阈值 +
+
+
+
+
- - - - - - - - - - - - - - - - - - - - + + +
+

显卡节目列表

+ + + + + + + + + + + + + + + + + + + + + +
+ +
@@ -287,125 +349,18 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - -
-
- 全天 -
-
- - - - - -
-
-
- - - - - - - -
- 全选 - - 周一 - 周二 - 周三 - 周四 - 周五 - 周六 - 周日 - -
-
- - - - - -
- -
- - - - - - - km/h -
-
-
-
-
- -
- - - -
- -
+ + + @@ -417,6 +372,9 @@ + + +
@@ -500,7 +458,7 @@ - +
取 消 - 确 定 + {{ isEditProgram ? '更新' : '确定' + }}
@@ -655,6 +614,17 @@ export default { this.stopAddProgramPreviewAnimLoop(); } }, + screenParams: { + handler() { + // 屏幕参数变化时,重置分区、刷新预览 + this.onAddProgramModeChange(this.addProgramForm.mode); + this.prepareAddProgramPreviewAssets(); + this.resetAddProgramPreviewPage(); + this.resetAddProgramPreviewAnimState(); + this.drawAddProgramPreview(); + }, + deep: true + }, }, data() { @@ -718,7 +688,7 @@ export default { }, ], }, - // 声卡相关数据 + // 基础设置 basicSettings: { screenEnabled: true, brightness: 50 @@ -727,132 +697,29 @@ export default { showBootScreen: false, resetEnabled: false }, - audioList: [], - defaultList: [], - // 录音相关数据 - isRecording: false, - recordingTime: 0, - recordingStatus: '准备就绪', - hasRecording: false, - mediaRecorder: null, - audioChunks: [], - recordings: [], - timer: null, - audioUrl: null, - // 音频列表相关数据 - addAudioDialogVisible: false, - newAudio: { - remark: '', - per: 0, - spd: 5, - pit: 5, - vol: 5, - tex_utf8: '', - filename: '' + // 传感器阈值设置 + sensorThreshold: { + speed: 60, + temp: 30, + humi: 50 }, - audioRules: { - remark: [ - { required: true, message: '请输入备注', trigger: 'blur' } - ], - per: [ - { required: true, message: '请选择主持人声音', trigger: 'change' } - ], - spd: [ - { required: true, message: '请设置合成语速', trigger: 'change' } - ], - pit: [ - { required: true, message: '请设置合成音调', trigger: 'change' } - ], - vol: [ - { required: true, message: '请设置合成音量', trigger: 'change' } - ], - tex_utf8: [ - { required: true, message: '请输入合成文本', trigger: 'blur' } - ] - }, - // 播放列表相关数据 - addPlaylistDialogVisible: false, - newPlaylist: { - name: '', - type: '用户', - status: '启用', - audioId: '', - playTimeStart: null, - playTimeEnd: null, - weekdays: [], - radarEnabled: false, - radarSpeedMin: 0, - radarSpeedMax: 120, - isAllDay: false, - isAllWeek: false, - play_time: 1, - pause_time: 0 - }, - playlistRules: { - audioId: [ - { required: true, message: '请选择音频', trigger: 'change' } - ], - playTime: [ - { - validator: (rule, value, callback) => { - if (!this.newPlaylist.playTimeStart || !this.newPlaylist.playTimeEnd) { - callback(new Error('请选择播放时间段')); - } else { - callback(); - } - }, - trigger: 'change' - } - ], - weekdays: [ - { required: true, message: '请选择重复日期', trigger: 'change' } - ], - radarSpeed: [ - { - validator: (rule, value, callback) => { - if (this.newPlaylist.radarEnabled) { - if (!this.newPlaylist.radarSpeedMin || !this.newPlaylist.radarSpeedMax) { - callback(new Error('请设置速度范围')); - } else if (this.newPlaylist.radarSpeedMin >= this.newPlaylist.radarSpeedMax) { - callback(new Error('最小速度必须小于最大速度')); - } else { - callback(); - } - } else { - callback(); - } - }, - trigger: 'change' - } - ], - play_time: [ - { required: true, message: '请输入播放时长', trigger: 'blur' } - ], - pause_time: [ - { required: true, message: '请输入停顿时长', trigger: 'blur' } - ] - }, - isEditPlaylist: false, - editingPlaylistIndex: null, - _mp3Encoder: null, - _audioChunks: [], - _audioContext: null, - _sourceNode: null, - _processorNode: null, - _stream: null, + screenParams: { template: '', // 屏幕模板 - oePolarity: 0, // OE极性 - dataPolarity: 0, // DATA极性 - width: 64, // 屏宽 - height: 32, // 屏高 - angle: 0 // 屏幕角度 + oePolarity: '', // OE极性 + dataPolarity: '', // DATA极性 + width: '', // 屏宽 + height: '', // 屏高 + angle: '' // 屏幕角度 }, // 新增:自定义节目相关 addProgramDialogVisible: false, + isEditProgram: false, // 是否为编辑模式 + editingProgram: null, // 当前编辑的节目 addProgramForm: { mode: 0, duration: 10, + remark: '', zones: [ { playType: 0, @@ -900,7 +767,7 @@ export default { addProgramFonts: ['宋体(中)', '黑体(中)', '楷体(中)'], addProgramFontShapes: ['圆角(英)', '直角(英)'], addProgramFontSizes: ['16px', '24px', '32px'], - addProgramFontColors: ['红色', '绿色', '蓝色'], + addProgramFontColors: ['黑色', '红色', '绿色', '黄色'], addProgramFontBold: ['不加粗', '加粗'], addProgramFontStretch: ['不拉伸', '横向拉伸', '纵向拉伸'], addProgramEffects: ['立即显示', '左移', '右移', '上移', '下移', '连续左移', '闪烁换页'], @@ -909,7 +776,7 @@ export default { addProgramHorizontalAlign: ['居左对齐', '居中对齐', '居右对齐'], addProgramVerticalAlign: ['顶部对齐', '居中对齐', '底部对齐'], addProgramImageSizes: ['16x16', '32x32', '48x48', '64x64'], - addProgramImageColors: ['红色', '绿色', '黄色'], + addProgramImageColors: ['黑色', '红色', '绿色', '黄色'], // 自动分页相关 addProgramPreviewPage: [0], // 每个分区当前页 addProgramPreviewTimer: [], // 每个分区定时器 @@ -917,6 +784,20 @@ export default { addProgramPreviewAnimState: [], // 每个分区动画状态 addProgramPreviewAnimFrame: null, // 动画主循环ID addProgramTrafficImages: [], + screenTemplates: [ + { label: '自定义', value: '', params: { oe: '', data: '', w: '', h: '', angle: '' } }, + { label: '伸缩屏', value: 'flex', params: { oe: 1, data: 1, w: 64, h: 32, angle: 270 } }, + { label: '大屏', value: 'big', params: { oe: 1, data: 1, w: 64, h: 32, angle: 180 } }, + { label: '小屏', value: 'small', params: { oe: 1, data: 1, w: 64, h: 16, angle: 0 } } + ], + // 编译信息 + buildVersion: '', + buildTime: '', + buildInfoLoading: false, + // 节目列表 + programList: [], + // 节目使能状态数组 + programEnableList: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], // 默认全部开启 }; }, mounted() { @@ -949,6 +830,428 @@ export default { this.addProgramTrafficImages = []; } }, + + // 获取暂停时间(毫秒) + getPauseTime(stayTimeIndex) { + const pauseTimeMap = [0, 100, 500, 1000, 2000, 3000, 4000, 5000, 6000, 8000, 10000]; + return pauseTimeMap[stayTimeIndex] || 1000; + }, + + // 获取字体大小 + getFontSize(fontSizeIndex) { + const fontSizeMap = [16, 24, 32]; + return fontSizeMap[fontSizeIndex] || 16; + }, + + // 获取图片编号(根据图片路径提取编号) + getImageNumber(imagePath) { + if (!imagePath) return 0; + // 从图片路径中提取编号,根据显卡.md文档中的图片编号映射 + const imageNameMap = { + '减速慢行.png': 0, // 注意安全 + '减速让行.png': 1, // 禁止驶入 + '掉头.png': 2, // 禁止掉头 + '禁止停车.png': 3, // 禁止停车 + '禁止通行.png': 4, // 禁止通行 + '限速40.png': 5, // 限速40 + '限速60.png': 6, // 限速60 + '限速80.png': 7, // 限速80 + '限速100.png': 8, // 限速100 + '限速120.png': 9, // 限速120 + '取消限速40.png': 25, // 取消限速40 + '取消限速60.png': 26, // 取消限速60 + '取消限速80.png': 27, // 取消限速80 + '取消限速100.png': 28, // 取消限速100 + '取消限速120.png': 29, // 取消限速120 + // 可以根据实际图片添加更多映射 + }; + const fileName = imagePath.split('/').pop(); + return imageNameMap[fileName] || 0; + }, + + // 获取图片尺寸 + getImageSize(imageSizeIndex) { + const imageSizeMap = [16, 32, 48, 64]; + return imageSizeMap[imageSizeIndex] || 32; + }, + + // 解析节目名称(去除\0结束符) + parseProgramName(rem) { + if (!rem) return '未命名节目'; + return rem.replace(/\0/g, ''); + }, + + // 获取模式名称 + getModeName(areaM) { + const modeNames = ['模式1', '模式2(上下)', '模式3(左右)', '模式4(上中下)', '模式5', '模式6']; + return modeNames[areaM] || `模式${areaM}`; + }, + + // 解析节目内容 + parseProgramContent(aLst) { + if (!aLst || aLst.length === 0) return '无内容'; + + const contents = []; + aLst.forEach((area, areaIndex) => { + if (area.pLst && area.pLst.length > 0) { + area.pLst.forEach((item, itemIndex) => { + if (item.typ === 0 && item.txt) { + // 文本类型 + const text = item.txt.str ? item.txt.str.replace(/\0/g, '') : ''; + if (text) { + contents.push(`分区${areaIndex + 1}: ${text}`); + } + } else if (item.typ === 1 && item.img) { + // 图片类型 + const imageName = this.getImageNameByNumber(item.img.num); + contents.push(`分区${areaIndex + 1}: 图片${item.img.num}(${imageName})`); + } + }); + } + }); + + return contents.length > 0 ? contents.join('; ') : '无内容'; + }, + + // 根据图片编号获取图片名称 + getImageNameByNumber(num) { + const imageNameMap = { + 0: '注意安全', + 1: '禁止驶入', + 2: '禁止掉头', + 3: '禁止停车', + 4: '禁止通行', + 5: '限速40', + 6: '限速60', + 7: '限速80', + 8: '限速100', + 9: '限速120', + 25: '取消限速40', + 26: '取消限速60', + 27: '取消限速80', + 28: '取消限速100', + 29: '取消限速120' + }; + return imageNameMap[num] || `图片${num}`; + }, + + // 查看节目详情 + viewProgram(program) { + this.$alert(` +
+

节目名称:${program.name}

+

播放时长:${program.duration}秒

+

显示模式:${program.mode}

+

分区数量:${program.zones}个

+

节目内容:${program.content}

+
+ `, '节目详情', { + dangerouslyUseHTMLString: true, + confirmButtonText: '确定' + }); + }, + + // 删除节目 + deleteProgram(program) { + this.$confirm(`确认删除节目"${program.name}"吗?`, '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }).then(() => { + 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); + this.mqttPublish(this.deviceInfo, progListModel).then(() => { + this.$message.success('删除指令已发送'); + }).catch(error => { + console.error('发送删除指令失败:', error); + this.$message.error('删除失败'); + }); + } catch (error) { + console.error('构建删除数据失败:', error); + this.$message.error('删除失败'); + } + } else { + this.$message.warning('未找到102#progList物模型'); + } + }).catch(() => { }); + }, + + // 编辑节目 + editProgram(program) { + this.isEditProgram = true; + this.editingProgram = program; + + // 解析节目数据并填充表单 + this.fillProgramForm(program.raw); + + // 显示弹窗 + this.addProgramDialogVisible = true; + }, + + // 填充节目表单数据 + fillProgramForm(programData) { + if (!programData) return; + + console.log('填充节目表单数据:', JSON.stringify(programData)); + + // 重置表单 + this.addProgramForm = { + mode: programData.areaM || 0, + duration: programData.dur || 10, + remark: this.parseProgramName(programData.rem) || '', + zones: [] + }; + + // 解析分区数据 + if (programData.aLst && programData.aLst.length > 0) { + console.log('解析分区数据:', JSON.stringify(programData.aLst)); + this.addProgramForm.zones = programData.aLst.map(area => { + const zone = { + playType: 0, + commonPhrase: 0, + displayText: '', + font: 0, + fontShape: 0, + fontSize: 0, + fontColor: 0, + fontBold: 0, + fontStretch: 0, + image: '', + imageSize: 1, + 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 + }; + + // 解析播放项 + if (area.pLst && area.pLst.length > 0) { + const item = area.pLst[0]; // 取第一个播放项 + if (item.typ === 0 && item.txt) { + // 文本类型 + 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.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; // 垂直对齐 + } else if (item.typ === 1 && item.img) { + // 图片类型 + zone.playType = 1; + zone.image = this.getImagePathByNumber(item.img.num); + zone.imageSize = this.getImageSizeIndex(item.img.w); // 使用宽度作为尺寸 + zone.imageColor = item.img.col || 0; // 图片颜色 + } + + // 解析动画效果 + if (item.anim) { + zone.effect = item.anim.typ ? item.anim.typ - 1 : 0; // 动画类型需要减1 + zone.speed = item.anim.spd || 4; // 动画速度 + zone.stayTime = item.anim.pauseT ? this.getStayTimeIndex(item.anim.pauseT) : 5; // 暂停时间索引 + } + } + + return zone; + }); + } else { + // 如果没有分区数据,创建默认分区 + this.addProgramForm.zones = [{ + playType: 0, + commonPhrase: 0, + displayText: '', + font: 0, + fontShape: 0, + fontSize: 0, + fontColor: 0, + fontBold: 0, + fontStretch: 0, + image: '', + imageSize: 1, + imageColor: 0, + effect: 0, + speed: 4, + stayTime: 5, + hAlign: 1, + vAlign: 1, + x: 0, + y: 0, + width: 32, + height: 64 + }]; + } + + // 更新预览 + this.$nextTick(() => { + this.drawAddProgramPreview(); + }); + }, + + // 根据图片编号获取图片路径 + getImagePathByNumber(imageNumber) { + const imageNumberMap = { + 0: '减速慢行.png', + 1: '减速让行.png', + 2: '掉头.png', + 3: '禁止停车.png', + 4: '禁止通行.png', + 5: '限速40.png', + 6: '限速60.png', + 7: '限速80.png', + 8: '限速100.png', + 9: '限速120.png', + 25: '取消限速40.png', + 26: '取消限速60.png', + 27: '取消限速80.png', + 28: '取消限速100.png', + 29: '取消限速120.png' + }; + const fileName = imageNumberMap[imageNumber] || '减速慢行.png'; + return this.addProgramTrafficImages.find(img => img.label === fileName)?.value || ''; + }, + + // 根据图片尺寸获取尺寸索引 + getImageSizeIndex(imageSize) { + const sizeMap = [16, 32, 48, 64]; + return sizeMap.indexOf(imageSize) || 1; + }, + + // 根据字体大小获取字体大小索引 + getFontSizeIndex(fontSize) { + const fontSizeMap = [16, 24, 32]; + return fontSizeMap.indexOf(fontSize) || 0; + }, + + // 根据暂停时间获取停留时间索引 + getStayTimeIndex(pauseTime) { + const pauseTimeMap = [0, 100, 500, 1000, 2000, 3000, 4000, 5000, 6000, 8000, 10000]; + return pauseTimeMap.indexOf(pauseTime) || 5; + }, + + // 根据图片编号获取图片名称 + getImageNameByNumber(imageNumber) { + const imageNameMap = { + 0: '减速慢行', + 1: '减速让行', + 2: '掉头', + 3: '禁止停车', + 4: '禁止通行', + 5: '限速40', + 6: '限速60', + 7: '限速80', + 8: '限速100', + 9: '限速120', + 25: '取消限速40', + 26: '取消限速60', + 27: '取消限速80', + 28: '取消限速100', + 29: '取消限速120' + }; + return imageNameMap[imageNumber] || `图片${imageNumber}`; + }, + + // 处理节目使能状态变化 + handleProgramEnableChange() { + // 构建10个元素的使能状态数组 + const enableArray = new Array(10).fill(0); // 默认全部为0 + + // 根据节目列表更新使能状态 + this.programList.forEach((program, index) => { + if (index < 10) { // 确保不超过10个 + enableArray[index] = program.enabled; + } + }); + + this.programEnableList = enableArray; + + // 发送使能状态到设备 + const progEnModel = this.deviceInfo.thingsModels.find(model => model.id === '102#progEn'); + if (progEnModel) { + try { + const progEnData = { + prog_en: this.programEnableList + }; + progEnModel.shadow = 'JSON=' + JSON.stringify(progEnData); + this.mqttPublish(this.deviceInfo, progEnModel).then(() => { + this.$message.success('节目使能状态已更新'); + }).catch(error => { + console.error('发送节目使能状态失败:', error); + this.$message.error('更新失败'); + }); + } catch (error) { + console.error('构建节目使能数据失败:', error); + this.$message.error('更新失败'); + } + } else { + this.$message.warning('未找到102#progEn物模型'); + } + }, + + // 刷新编译信息 + async refreshBuildInfo() { + console.log('programList', JSON.stringify(this.programList)) + try { + // 发送102#model指令,值为1 + const model = this.deviceInfo.thingsModels.find(m => m.id === '102#model'); + if (model) { + model.shadow = '1'; + await this.mqttPublish(this.deviceInfo, model); + this.$message.success('已发送刷新指令,编译信息将自动更新'); + } else { + this.$message.warning('未找到102#model物模型'); + } + } catch (error) { + console.error('发送刷新指令失败:', error); + this.$message.error('发送刷新指令失败'); + } + }, + + // 获取编译信息 + getBuildInfo() { + this.buildInfoLoading = true; + try { + // 从物模型102#info获取编译信息 + const infoModel = this.deviceInfo.thingsModels.find(model => model.id === '102#info'); + if (infoModel && infoModel.shadow) { + try { + const jsonStr = infoModel.shadow.replace('JSON=', ''); + const data = JSON.parse(jsonStr); + this.buildVersion = data.versions || '未知版本'; + this.buildTime = data.compile_time || '未知时间'; + } catch (parseError) { + console.error('解析编译信息JSON失败:', parseError); + this.buildVersion = '未知版本'; + this.buildTime = '未知时间'; + } + } else { + // 如果物模型不存在,使用默认值 + this.buildVersion = '未知版本'; + this.buildTime = '未知时间'; + } + } catch (error) { + console.error('获取编译信息失败:', error); + this.buildVersion = '未知版本'; + this.buildTime = '未知时间'; + } finally { + this.buildInfoLoading = false; + } + }, //发送指令 async mqttPublish(device, model) { const command = {}; @@ -1032,7 +1335,8 @@ export default { if (this.deviceInfo.thingsModels && this.deviceInfo.thingsModels.length > 0) { this.deviceInfo.thingsModels = this.deviceInfo.thingsModels.sort((a, b) => b.order - a.order); this.updateBasicSettings(); // 更新基础设置 - this.printThingsModels(); + // this.printThingsModels(); + this.getBuildInfo(); // 获取编译信息 } if (this.deviceInfo.chartList && this.deviceInfo.chartList.length > 0) { this.deviceInfo.chartList = this.deviceInfo.chartList.sort((a, b) => b.order - a.order); @@ -1044,79 +1348,124 @@ export default { updateBasicSettings() { if (!this.deviceInfo.thingsModels) return; - // 更新音频开关状态 - const playEnModel = this.deviceInfo.thingsModels.find(model => model.id === '103#playEn'); - if (playEnModel) { - this.basicSettings.audioEnabled = playEnModel.shadow === '1'; + // 屏幕开关 + const screenEnModel = this.deviceInfo.thingsModels.find(model => model.id === '102#screenEn'); + if (screenEnModel) { + this.basicSettings.screenEnabled = screenEnModel.shadow === '1'; } - // 更新音量设置 - const volumeModel = this.deviceInfo.thingsModels.find(model => model.id === '103#volume'); - if (volumeModel) { - this.basicSettings.volume = parseInt(volumeModel.shadow) || 50; + // 屏幕亮度 + const luminanceModel = this.deviceInfo.thingsModels.find(model => model.id === '102#luminance'); + if (luminanceModel) { + this.basicSettings.brightness = parseInt(luminanceModel.shadow) || 50; } - // 更新音频列表 - const mp3ListModel = this.deviceInfo.thingsModels.find(model => model.id === '103#mp3List'); - if (mp3ListModel && mp3ListModel.shadow) { + // 开机参数 + const bootEnModel = this.deviceInfo.thingsModels.find(model => model.id === '102#bootEn'); + if (bootEnModel) { + this.bootSettings.showBootScreen = bootEnModel.shadow === '1'; + } + const factoryEnModel = this.deviceInfo.thingsModels.find(model => model.id === '102#factoryEn'); + if (factoryEnModel) { + this.bootSettings.resetEnabled = factoryEnModel.shadow === '1'; + } + + // 屏幕参数 + const scrParamModel = this.deviceInfo.thingsModels.find(model => model.id === '102#scrParam'); + if (scrParamModel && scrParamModel.shadow) { try { - // 解析 JSON 字符串 - const jsonStr = mp3ListModel.shadow.replace('JSON=', ''); + const jsonStr = scrParamModel.shadow.replace('JSON=', ''); const data = JSON.parse(jsonStr); - - // 更新音频列表 - const mp3List = data.sound_card?.mp3_list || data.mp3_list; - if (mp3List) { - this.audioList = mp3List.map((item) => { - const [idStr, name] = item.split('_'); - return { - id: parseInt(idStr, 10), - name: name || item, - raw: item - }; - }).sort((a, b) => a.id - b.id); // 按id从小到大排序 + if (data.scr_param) { + this.screenParams.oePolarity = data.scr_param.oe; + this.screenParams.dataPolarity = data.scr_param.data; + this.screenParams.width = data.scr_param.w; + this.screenParams.height = data.scr_param.h; + this.screenParams.angle = data.scr_param.angle; } } catch (error) { - console.error('解析音频列表失败:', error); + console.error('解析屏幕参数失败:', error); } } - // 更新播放列表 - const playListModel = this.deviceInfo.thingsModels.find(model => model.id === '103#playList'); - if (playListModel && playListModel.shadow) { + // 传感器阈值设置 + const sensorThresholdModel = this.deviceInfo.thingsModels.find(model => model.id === '102#snsrThr'); + if (sensorThresholdModel && sensorThresholdModel.shadow) { try { - const jsonStr = playListModel.shadow.replace('JSON=', ''); + const jsonStr = sensorThresholdModel.shadow.replace('JSON=', ''); const data = JSON.parse(jsonStr); + if (data.snsr_thr) { + this.sensorThreshold.speed = data.snsr_thr.speed || 60; + this.sensorThreshold.temp = data.snsr_thr.temp || 30; + this.sensorThreshold.humi = data.snsr_thr.humi || 50; + } + } catch (error) { + console.error('解析传感器阈值失败:', error); + } + } - // 兼容 sound_card 层和根节点 - const playList = data.sound_card?.play_list || data.play_list; - if (playList) { - this.defaultList = playList.map((item, index) => { - // 转换时间格式 - const beginTime = this.formatSecondsToTime(item.time.begin); - const endTime = this.formatSecondsToTime(item.time.end); - - // 转换星期格式 - const weekdays = this.convertWeekToArray(item.time.week); - + // 更新节目列表(如果有102#progList物模型) + const progListModel = this.deviceInfo.thingsModels.find(model => model.id === '102#progList'); + console.log("progListModel", JSON.stringify(progListModel)) + if (progListModel && progListModel.shadow) { + try { + const jsonStr = progListModel.shadow.replace('JSON=', ''); + const data = JSON.parse(jsonStr); + if (data.prog_list && data.prog_list.length > 0) { + // 解析节目列表并更新显示 + this.programList = data.prog_list.map((program, index) => { return { id: index + 1, - name: item.play.filename, - playTime: (item.time.begin === 0 && item.time.end === 86400) - ? '全天' - : `${beginTime} - ${endTime}`, - weekdays: weekdays.join(', '), - radarEnabled: item.speed.en === 1, - status: item.play.en === 1 ? '启用' : '禁用', - radarSpeed: item.speed.en === 1 ? `${item.speed.min}-${item.speed.max}km/h` : '', - rawIndex: index // 新增 + order: program.order, + name: this.parseProgramName(program.rem), + duration: program.dur, + mode: this.getModeName(program.areaM), + zones: program.aLst ? program.aLst.length : 0, + content: this.parseProgramContent(program.aLst), + enabled: this.programEnableList[index] || 0, // 使用使能状态数组,默认为0 + raw: program }; }); + console.log('解析后的节目列表:', this.programList); + } else { + this.programList = []; } } catch (error) { - console.error('解析播放列表失败:', error); + console.error('解析节目列表失败:', error); + this.programList = []; } } + + // 更新节目使能状态(如果有102#progEn物模型) + const progEnModel = this.deviceInfo.thingsModels.find(model => model.id === '102#progEn'); + if (progEnModel && progEnModel.shadow) { + try { + const jsonStr = progEnModel.shadow.replace('JSON=', ''); + const data = JSON.parse(jsonStr); + if (data.prog_en && Array.isArray(data.prog_en)) { + // 确保使能状态数组有10个元素 + this.programEnableList = new Array(10).fill(0); + data.prog_en.forEach((enabled, index) => { + if (index < 10) { + this.programEnableList[index] = enabled; + } + }); + + // 更新节目列表中的使能状态 + this.programList.forEach((program, index) => { + program.enabled = this.programEnableList[index] || 0; + }); + console.log('节目使能状态:', this.programEnableList); + } + } catch (error) { + console.error('解析节目使能状态失败:', error); + // 如果解析失败,初始化为10个0 + this.programEnableList = new Array(10).fill(0); + } + } else { + // 如果没有物模型数据,初始化为10个0 + this.programEnableList = new Array(10).fill(0); + } }, printThingsModels() { @@ -1127,18 +1476,7 @@ export default { }); }, - // 声卡特有方法 - formatVolume(val) { - return val + '%'; - }, - handleVolumeChange(val) { - const volumeModel = this.deviceInfo.thingsModels.find(model => model.id === '103#volume'); - if (volumeModel) { - volumeModel.shadow = val.toString(); - this.mqttPublish(this.deviceInfo, volumeModel); - } - }, // 保留其他必要的方法 initData() { @@ -1291,6 +1629,7 @@ export default { } } this.updateBasicSettings(); + this.getBuildInfo(); // 更新编译信息 }, @@ -1688,496 +2027,34 @@ export default { }; }, - // 获取未使用的最小ID(从0开始) - getNextAvailableId() { - const mp3ListModel = this.deviceInfo.thingsModels.find(model => model.id === '103#mp3List'); - if (mp3ListModel) { - try { - const jsonStr = mp3ListModel.shadow.replace('JSON=', ''); - const data = JSON.parse(jsonStr); - const mp3List = data.sound_card?.mp3_list || data.mp3_list || []; - // 获取所有已使用的ID - const usedIds = mp3List.map(item => { - const id = parseInt(item.split('_')[0]); - return isNaN(id) ? 0 : id; - }); - // 找到最小的未使用ID(从0开始) - let nextId = 1; - while (usedIds.includes(nextId)) { - nextId++; - } - return nextId; - } catch (error) { - console.error('解析mp3_list失败:', error); - } - } - return 0; // 如果出错,返回0 - }, - submitAudioForm() { - this.$refs.audioForm.validate((valid) => { - if (valid) { - // 找到mp3_list物模型 - const mp3ListModel = this.deviceInfo.thingsModels.find(model => model.id === '103#mp3List'); - if (mp3ListModel) { - try { - // 获取下一个可用的ID - const nextId = this.getNextAvailableId(); - // 生成文件名 - const filename = `${nextId}_${this.newAudio.remark}`; - // 构建TTS对象 - const ttsData = { - JSON_id: 1, - TTS: { - per: this.newAudio.per, - spd: this.newAudio.spd, - pit: this.newAudio.pit, - vol: this.newAudio.vol, - tex_utf8: this.newAudio.tex_utf8, - filename: filename, - }, - "interrupt": 99 - }; - - // 更新物模型shadow值 - mp3ListModel.shadow = 'JSON=' + JSON.stringify(ttsData); - - // 发送更新 - this.mqttPublish(this.deviceInfo, mp3ListModel); - - this.addAudioDialogVisible = false; - this.$message.success('添加成功'); - } catch (error) { - console.error('添加音频失败:', error); - this.$message.error('添加失败'); - } - } - } - }); - }, - - handleDeleteAudio(row) { - this.$confirm('确认删除该音频吗?', '提示', { - confirmButtonText: '确定', - cancelButtonText: '取消', - type: 'warning' - }).then(() => { - // 找到mp3_list物模型 - const mp3ListModel = this.deviceInfo.thingsModels.find(model => model.id === '103#mp3List'); - if (mp3ListModel) { - try { - // 解析当前JSON - const jsonStr = mp3ListModel.shadow.replace('JSON=', ''); - const data = JSON.parse(jsonStr); - // 用 raw 字段精确匹配,只操作根节点 - const audioRaw = row.raw; - if (data.mp3_list) { - data.mp3_list = data.mp3_list.filter(item => item !== audioRaw); - } - // 更新物模型shadow值 - const newShadow = 'JSON=' + JSON.stringify(data); - mp3ListModel.shadow = newShadow; - // 发送更新 - this.mqttPublish(this.deviceInfo, mp3ListModel).then(() => { - this.$message.success('删除成功'); - }).catch(error => { - console.error('发送删除命令失败:', error); - this.$message.error('删除失败'); - }); - } catch (error) { - console.error('解析或更新mp3_list失败:', error); - this.$message.error('删除失败'); - } - } - }).catch(() => { }); - }, - - showAddPlaylistDialog() { - // 重置编辑状态 - this.isEditPlaylist = false; - this.editingPlaylistIndex = null; - - this.addPlaylistDialogVisible = true; - this.newPlaylist = { - name: '', - type: '用户', - status: '启用', - audioId: '', - playTimeStart: null, - playTimeEnd: null, - weekdays: [], - radarEnabled: false, - radarSpeedMin: 0, - radarSpeedMax: 120, - isAllDay: false, - isAllWeek: false, - play_time: 1, - pause_time: 0 - }; - }, - - submitPlaylistForm() { - this.$refs.playlistForm.validate((valid) => { - if (valid) { - // 找到play_list物模型 - const playListModel = this.deviceInfo.thingsModels.find(model => model.id === '103#playList'); - if (playListModel) { - try { - // 解析当前JSON,若为空则初始化 - let jsonStr = playListModel.shadow ? playListModel.shadow.replace('JSON=', '') : ''; - let data = {}; - if (jsonStr) { - data = JSON.parse(jsonStr); - } else { - data = { play_list: [] }; - } - - // 获取选中的音频信息 - const selectedAudio = this.audioList.find(audio => audio.id === this.newPlaylist.audioId); - if (!selectedAudio) { - this.$message.error('未找到选中的音频'); - return; - } - - // 构建播放项 - const playItem = { - play: { - en: this.newPlaylist.status === '启用' ? 1 : 0, - num: this.isEditPlaylist ? (this.editingPlaylistIndex + 1) : (this.defaultList.length + 1), - sou: 0, - filename: `${selectedAudio.id}_${selectedAudio.name}`, - play_time: this.newPlaylist.play_time, - pause_time: this.newPlaylist.pause_time - }, - time: { - en: 1, - begin: this.newPlaylist.isAllDay ? 0 : this.convertTimeToSeconds(this.newPlaylist.playTimeStart), - end: this.newPlaylist.isAllDay ? 86400 : this.convertTimeToSeconds(this.newPlaylist.playTimeEnd), - week: this.convertWeekArrayToValue(this.newPlaylist.weekdays) - }, - speed: { - en: this.newPlaylist.radarEnabled ? 1 : 0, - min: this.newPlaylist.radarEnabled ? this.newPlaylist.radarSpeedMin : 0, - max: this.newPlaylist.radarEnabled ? this.newPlaylist.radarSpeedMax : 0 - } - }; - - // 直接操作根节点的play_list - if (!data.play_list) { - data.play_list = []; - } - - if (this.isEditPlaylist) { - // 编辑模式:更新现有播放项 - if (data.play_list[this.editingPlaylistIndex]) { - data.play_list[this.editingPlaylistIndex] = playItem; - } else { - this.$message.error('未找到要编辑的播放项'); - return; - } - } else { - // 添加模式:添加新播放项 - data.play_list.push(playItem); - } - - // 更新物模型shadow值 - playListModel.shadow = 'JSON=' + JSON.stringify(data); - - // 发送更新 - this.mqttPublish(this.deviceInfo, playListModel).then(() => { - const isEdit = this.isEditPlaylist; - this.addPlaylistDialogVisible = false; - this.isEditPlaylist = false; - this.editingPlaylistIndex = null; - this.$message.success(isEdit ? '编辑成功' : '添加成功'); - }).catch(error => { - console.error('发送播放列表更新失败:', error); - this.$message.error(this.isEditPlaylist ? '编辑失败' : '添加失败'); - }); - } catch (error) { - console.error('解析或更新播放列表失败:', error); - this.$message.error(this.isEditPlaylist ? '编辑失败' : '添加失败'); - } - } - } - }); - }, - - // 将时间转换为秒数 - convertTimeToSeconds(time) { - if (!time) return 0; - return time.getHours() * 3600 + time.getMinutes() * 60; - }, - - // 将星期数组转换为位值 - convertWeekArrayToValue(weekdays) { - let value = 0; - weekdays.forEach(day => { - value |= (1 << parseInt(day)); - }); - return value; - }, - - formatSpeed(val) { - return val; - }, - - formatPitch(val) { - return val; - }, - - formatVolume(val) { - return val; - }, - - // 将秒数转换为时间格式 - formatSecondsToTime(seconds) { - const hours = Math.floor(seconds / 3600); - const minutes = Math.floor((seconds % 3600) / 60); - return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`; - }, - - // 将星期位转换为数组 - convertWeekToArray(week) { - const weekMap = { - 1: '周一', - 2: '周二', - 3: '周三', - 4: '周四', - 5: '周五', - 6: '周六', - 0: '周日' - }; - const result = []; - for (let i = 0; i < 7; i++) { - if (week & (1 << i)) { - result.push(weekMap[i]); - } - } - return result; - }, - - handleStatusChange(row) { - const playListModel = this.deviceInfo.thingsModels.find(model => model.id === '103#playList'); - if (playListModel) { - try { - let jsonStr = playListModel.shadow ? playListModel.shadow.replace('JSON=', '') : ''; - let data = {}; - if (jsonStr) { - data = JSON.parse(jsonStr); - } else { - data = { play_list: [] }; - } - - // 兼容 sound_card 和根节点 - const playListArr = data.sound_card?.play_list || data.play_list; - if (playListArr && playListArr[row.rawIndex]) { - playListArr[row.rawIndex].play.en = row.status === '启用' ? 1 : 0; - // 更新物模型shadow值 - if (data.sound_card) { - data.sound_card.play_list = playListArr; - } else { - data.play_list = playListArr; - } - playListModel.shadow = 'JSON=' + JSON.stringify(data); - // 发送更新 - this.mqttPublish(this.deviceInfo, playListModel).then(() => { - this.$message.success('状态更新成功'); - }).catch(error => { - console.error('发送状态更新失败:', error); - this.$message.error('状态更新失败'); - }); - } - } catch (error) { - console.error('解析或更新播放列表状态失败:', error); - this.$message.error('状态更新失败'); - } - } - }, - - handleEditPlaylist(row) { - // 设置编辑模式 - this.isEditPlaylist = true; - this.editingPlaylistIndex = row.rawIndex; - - // 找到对应的播放项数据 - const playListModel = this.deviceInfo.thingsModels.find(model => model.id === '103#playList'); - if (playListModel && playListModel.shadow) { - try { - const jsonStr = playListModel.shadow.replace('JSON=', ''); - const data = JSON.parse(jsonStr); - - if ((data.sound_card && data.sound_card.play_list && data.sound_card.play_list[row.rawIndex]) || - (data.play_list && data.play_list[row.rawIndex])) { - const playItem = (data.sound_card?.play_list || data.play_list)[row.rawIndex]; - - // 填充表单数据 - this.newPlaylist = { - name: playItem.play.filename, - type: '用户', - status: playItem.play.en === 1 ? '启用' : '禁用', - audioId: this.getAudioIdFromFilename(playItem.play.filename), - playTimeStart: this.convertSecondsToTime(playItem.time.begin), - playTimeEnd: this.convertSecondsToTime(playItem.time.end), - weekdays: this.convertWeekValueToArray(playItem.time.week), - radarEnabled: playItem.speed.en === 1, - radarSpeedMin: playItem.speed.min || 0, - radarSpeedMax: playItem.speed.max || 120, - isAllDay: playItem.time.begin === 0 && playItem.time.end === 86400, - isAllWeek: this.isAllWeekSelected(playItem.time.week), - play_time: playItem.play.play_time || 1, - pause_time: playItem.play.pause_time || 0 - }; - - this.addPlaylistDialogVisible = true; - } - } catch (error) { - console.error('解析播放列表数据失败:', error); - this.$message.error('获取播放列表数据失败'); - } - } - }, - - // 从文件名获取音频ID - getAudioIdFromFilename(filename) { - if (!filename) return ''; - const parts = filename.split('_'); - if (parts.length >= 2) { - const audioId = parseInt(parts[0]); - return isNaN(audioId) ? '' : audioId; - } - return ''; - }, - - // 将秒数转换为时间对象 - convertSecondsToTime(seconds) { - if (seconds === 0) return new Date(2000, 0, 1, 0, 0); - if (seconds === 86400) return new Date(2000, 0, 1, 0, 0); - - const hours = Math.floor(seconds / 3600); - const minutes = Math.floor((seconds % 3600) / 60); - return new Date(2000, 0, 1, hours, minutes); - }, - - // 将星期位值转换为数组 - convertWeekValueToArray(week) { - const result = []; - for (let i = 0; i < 7; i++) { - if (week & (1 << i)) { - result.push(i.toString()); - } - } - return result; - }, - - // 检查是否全选星期 - isAllWeekSelected(week) { - return week === 127; // 127 = 1111111 (二进制) - }, - - handleDeletePlaylist(row) { - this.$confirm('确认删除该播放项吗?', '提示', { - confirmButtonText: '确定', - cancelButtonText: '取消', - type: 'warning' - }).then(() => { - const playListModel = this.deviceInfo.thingsModels.find(model => model.id === '103#playList'); - if (playListModel) { - try { - let jsonStr = playListModel.shadow ? playListModel.shadow.replace('JSON=', '') : ''; - let data = {}; - if (jsonStr) { - data = JSON.parse(jsonStr); - } else { - data = { play_list: [] }; - } - - if ((data.sound_card && data.sound_card.play_list) || data.play_list) { - // 删除对应的播放项 - const playListArr = data.sound_card?.play_list || data.play_list; - playListArr.splice(row.rawIndex, 1); - - // 更新序号 - playListArr.forEach((item, index) => { - item.play.num = index + 1; - }); - - // 更新物模型shadow值 - if (data.sound_card) { - data.sound_card.play_list = playListArr; - } else { - data.play_list = playListArr; - } - playListModel.shadow = 'JSON=' + JSON.stringify(data); - - // 发送更新 - this.mqttPublish(this.deviceInfo, playListModel).then(() => { - this.$message.success('删除成功'); - }).catch(error => { - console.error('发送删除命令失败:', error); - this.$message.error('删除失败'); - }); - } - } catch (error) { - console.error('解析或更新播放列表失败:', error); - this.$message.error('删除失败'); - } - } - }).catch(() => { }); - }, - - /** 处理全天选择变化 */ - handleAllDayChange(val) { - if (val) { - // 设置为全天时,将时间设置为 00:00 到 23:59 - this.newPlaylist.playTimeStart = new Date(2000, 0, 1, 0, 0); - this.newPlaylist.playTimeEnd = new Date(2000, 0, 1, 0, 0); - } - }, - - /** 处理全选星期变化 */ - handleAllWeekChange(val) { - if (val) { - // 全选时,设置所有星期 - this.newPlaylist.weekdays = ['0', '1', '2', '3', '4', '5', '6']; - } else { - // 取消全选时,清空选择 - this.newPlaylist.weekdays = []; - } - }, - - cancelPlaylistDialog() { - this.addPlaylistDialogVisible = false; - this.isEditPlaylist = false; - this.editingPlaylistIndex = null; - }, formatBrightness(val) { return val + '%'; }, handleScreenSwitchChange(val) { - // 占位物模型ID: '201#screenEn' - const screenEnModel = this.deviceInfo.thingsModels.find(model => model.id === '201#screenEn'); + // 占位物模型ID: '102#screenEn' + const screenEnModel = this.deviceInfo.thingsModels.find(model => model.id === '102#screenEn'); if (screenEnModel) { screenEnModel.shadow = val ? '1' : '0'; this.mqttPublish(this.deviceInfo, screenEnModel); } }, handleBrightnessChange(val) { - // 占位物模型ID: '201#brightness' - const brightnessModel = this.deviceInfo.thingsModels.find(model => model.id === '201#brightness'); + // 占位物模型ID: '102#brightness' + const brightnessModel = this.deviceInfo.thingsModels.find(model => model.id === '102#luminance'); if (brightnessModel) { brightnessModel.shadow = val.toString(); this.mqttPublish(this.deviceInfo, brightnessModel); } }, handleClearScreen() { - // 占位物模型ID: '201#clearScreen',下发清除指令 - const clearScreenModel = this.deviceInfo.thingsModels.find(model => model.id === '201#clearScreen'); + // 占位物模型ID: '102#clearScreen',下发清除指令 + const clearScreenModel = this.deviceInfo.thingsModels.find(model => model.id === '102#clearScreen'); if (clearScreenModel) { clearScreenModel.shadow = '1'; this.mqttPublish(this.deviceInfo, clearScreenModel); @@ -2186,97 +2063,124 @@ export default { } }, handleBootScreenSwitchChange(val) { - // 占位物模型ID: '201#bootScreen' - const bootScreenModel = this.deviceInfo.thingsModels.find(model => model.id === '201#bootScreen'); + // 占位物模型ID: '102#bootScreen' + const bootScreenModel = this.deviceInfo.thingsModels.find(model => model.id === '102#bootEn'); if (bootScreenModel) { bootScreenModel.shadow = val ? '1' : '0'; this.mqttPublish(this.deviceInfo, bootScreenModel); } }, handleResetSwitchChange(val) { - // 占位物模型ID: '201#reset' - const resetModel = this.deviceInfo.thingsModels.find(model => model.id === '201#reset'); + // 占位物模型ID: '102#reset' + const resetModel = this.deviceInfo.thingsModels.find(model => model.id === '102#factoryEn'); if (resetModel) { resetModel.shadow = val ? '1' : '0'; this.mqttPublish(this.deviceInfo, resetModel); } }, handleTemplateChange(val) { - // 占位物模型ID: '201#template' - const model = this.deviceInfo.thingsModels.find(m => m.id === '201#template'); - if (model) { - model.shadow = val; - this.mqttPublish(this.deviceInfo, model); + const template = this.screenTemplates.find(t => t.value === val); + if (template) { + const params = template.params; + this.$set(this.screenParams, 'oePolarity', params.oe); + this.$set(this.screenParams, 'dataPolarity', params.data); + this.$set(this.screenParams, 'width', params.w); + this.$set(this.screenParams, 'height', params.h); + this.$set(this.screenParams, 'angle', params.angle); } }, handleOePolarityChange(val) { - // 占位物模型ID: '201#oePolarity' - const model = this.deviceInfo.thingsModels.find(m => m.id === '201#oePolarity'); + // 占位物模型ID: '102#oePolarity' + const model = this.deviceInfo.thingsModels.find(m => m.id === '102#oePolarity'); if (model) { model.shadow = val.toString(); this.mqttPublish(this.deviceInfo, model); } }, handleDataPolarityChange(val) { - // 占位物模型ID: '201#dataPolarity' - const model = this.deviceInfo.thingsModels.find(m => m.id === '201#dataPolarity'); + // 占位物模型ID: '102#dataPolarity' + const model = this.deviceInfo.thingsModels.find(m => m.id === '102#dataPolarity'); if (model) { model.shadow = val.toString(); this.mqttPublish(this.deviceInfo, model); } }, handleWidthChange(val) { - // 占位物模型ID: '201#width' - const model = this.deviceInfo.thingsModels.find(m => m.id === '201#width'); + // 占位物模型ID: '102#width' + const model = this.deviceInfo.thingsModels.find(m => m.id === '102#width'); if (model) { model.shadow = val.toString(); this.mqttPublish(this.deviceInfo, model); } }, handleHeightChange(val) { - // 占位物模型ID: '201#height' - const model = this.deviceInfo.thingsModels.find(m => m.id === '201#height'); + // 占位物模型ID: '102#height' + const model = this.deviceInfo.thingsModels.find(m => m.id === '102#height'); if (model) { model.shadow = val.toString(); this.mqttPublish(this.deviceInfo, model); } }, handleAngleChange(val) { - // 占位物模型ID: '201#angle' - const model = this.deviceInfo.thingsModels.find(m => m.id === '201#angle'); + // 占位物模型ID: '102#angle' + const model = this.deviceInfo.thingsModels.find(m => m.id === '102#angle'); if (model) { model.shadow = val.toString(); this.mqttPublish(this.deviceInfo, model); } }, handleSendScreenParams() { - // 占位物模型ID: '201#screenParam' - const model = this.deviceInfo.thingsModels.find(m => m.id === '201#screenParam'); + const model = this.deviceInfo.thingsModels.find(m => m.id === '102#scrParam'); if (model) { - // 组装参数对象 const param = { - template: this.screenParams.template, - oePolarity: this.screenParams.oePolarity, - dataPolarity: this.screenParams.dataPolarity, - width: this.screenParams.width, - height: this.screenParams.height, - angle: this.screenParams.angle + scr_param: { + oe: this.screenParams.oePolarity, + data: this.screenParams.dataPolarity, + w: this.screenParams.width, + h: this.screenParams.height, + angle: this.screenParams.angle + } }; - model.shadow = JSON.stringify(param); + model.shadow = 'JSON=' + JSON.stringify(param); this.mqttPublish(this.deviceInfo, model); this.$message.success('参数已下发'); } else { this.$message.warning('未找到屏幕参数物模型'); } }, + + // 下发传感器阈值 + handleSendSensorThreshold() { + const model = this.deviceInfo.thingsModels.find(m => m.id === '102#snsrThr'); + if (model) { + const param = { + snsr_thr: { + speed: this.sensorThreshold.speed, + temp: this.sensorThreshold.temp, + humi: this.sensorThreshold.humi + } + }; + model.shadow = 'JSON=' + JSON.stringify(param); + this.mqttPublish(this.deviceInfo, model); + this.$message.success('传感器阈值已下发'); + } else { + this.$message.warning('未找到传感器阈值物模型'); + } + }, showAddProgramDialog() { + this.isEditProgram = false; + this.editingProgram = null; this.addProgramDialogVisible = true; this.resetAddProgramForm(); }, resetAddProgramForm() { + // 使用当前屏幕参数 + const screenW = this.screenParams.width || 32; + const screenH = this.screenParams.height || 64; this.addProgramForm = { mode: 0, duration: 10, + remark: '', zones: [ { playType: 0, @@ -2294,12 +2198,12 @@ export default { effect: 0, speed: 4, stayTime: 5, - hAlign: 0, - vAlign: 0, + hAlign: 1, + vAlign: 1, x: 0, y: 0, - width: 32, - height: 64 + width: screenW, + height: screenH } ] }; @@ -2308,8 +2212,9 @@ export default { // 根据模式调整分区数量和布局 const zoneCounts = [1, 2, 2, 3, 1, 1]; const count = zoneCounts[val] || 1; - const screenW = 32; - const screenH = 64; + // 使用当前屏幕参数 + const screenW = this.screenParams.width || 32; + const screenH = this.screenParams.height || 64; const newZones = []; for (let i = 0; i < count; i++) { const templateZone = { @@ -2328,8 +2233,8 @@ export default { effect: 0, speed: 4, stayTime: 5, - hAlign: 0, - vAlign: 0, + hAlign: 1, + vAlign: 1, x: 0, y: 0, width: screenW, @@ -2373,55 +2278,95 @@ export default { } }, submitAddProgram() { - // 组装节目参数,集成到物模型下发 - const playListModel = this.deviceInfo.thingsModels.find(model => model.id === '103#playList'); - if (playListModel) { + // 根据显卡.md文档格式组装节目参数 + const progListModel = this.deviceInfo.thingsModels.find(model => model.id === '102#progList'); + if (progListModel) { try { - let jsonStr = playListModel.shadow ? playListModel.shadow.replace('JSON=', '') : ''; - let data = {}; - if (jsonStr) { - data = JSON.parse(jsonStr); - } else { - data = { play_list: [] }; - } - // 构建自定义节目项 - const customProgram = { - play: { - en: 1, - num: data.play_list.length + 1, - sou: 0, - filename: `custom_${Date.now()}`, - play_time: this.addProgramForm.duration, - pause_time: 0 - }, - time: { - en: 1, - begin: 0, - end: 86400, - week: 127 - }, - speed: { - en: 0, - min: 0, - max: 0 - }, - program: { - mode: this.addProgramForm.mode, - duration: this.addProgramForm.duration, - zones: this.addProgramForm.zones - } + // 构建符合显卡接口规范的节目数据 + 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 // 高度 + }, + pLst: [] + }; + + // 根据播放类型构建播放项 + 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; + }) + }] }; - data.play_list.push(customProgram); - playListModel.shadow = 'JSON=' + JSON.stringify(data); - this.mqttPublish(this.deviceInfo, playListModel).then(() => { + + // 发送节目数据 + progListModel.shadow = 'JSON=' + JSON.stringify(programData); + this.mqttPublish(this.deviceInfo, progListModel).then(() => { this.addProgramDialogVisible = false; - this.$message.success('自定义节目添加成功'); + this.$message.success(this.isEditProgram ? '自定义节目更新成功' : '自定义节目添加成功'); }).catch(error => { - this.$message.error('自定义节目添加失败'); + console.error('发送节目数据失败:', error); + this.$message.error(this.isEditProgram ? '自定义节目更新失败' : '自定义节目添加失败'); }); } catch (error) { - this.$message.error('自定义节目添加失败'); + console.error('构建节目数据失败:', error); + this.$message.error(this.isEditProgram ? '自定义节目更新失败' : '自定义节目添加失败'); } + } else { + this.$message.warning('未找到102#progList物模型'); } }, // 画面预览渲染 @@ -2430,9 +2375,13 @@ export default { if (!canvas) return; const ctx = canvas.getContext('2d'); + // 获取当前屏幕参数 + const screenW = this.screenParams.width || 32; + const screenH = this.screenParams.height || 64; + // 获取实际渲染尺寸 - const renderWidth = 320; // 固定渲染宽度 - const renderHeight = 120; // 固定渲染高度 + const renderWidth = 640; // 调大预览宽度 + const renderHeight = 240; // 调大预览高度 // 设置高清渲染 const devicePixelRatio = window.devicePixelRatio || 1; @@ -2440,24 +2389,25 @@ export default { canvas.height = renderHeight * devicePixelRatio; canvas.style.width = `${renderWidth}px`; canvas.style.height = `${renderHeight}px`; + ctx.setTransform(1, 0, 0, 1, 0, 0); // 重置变换 ctx.scale(devicePixelRatio, devicePixelRatio); // 清空整个画布为透明 ctx.clearRect(0, 0, renderWidth, renderHeight); - // 计算缩放比例(基于32x64的LED屏幕尺寸) + // 计算缩放比例(基于screenParams的LED屏幕尺寸) const scale = Math.min( - renderWidth / 32, - renderHeight / 64 + renderWidth / screenW, + renderHeight / screenH ); // 居中偏移量 - const offsetX = (renderWidth - 32 * scale) / 2; - const offsetY = (renderHeight - 64 * scale) / 2; + const offsetX = (renderWidth - screenW * scale) / 2; + const offsetY = (renderHeight - screenH * scale) / 2; // 绘制LED屏幕区域(黑色背景) ctx.fillStyle = '#000'; - ctx.fillRect(offsetX, offsetY, 32 * scale, 64 * scale); + ctx.fillRect(offsetX, offsetY, screenW * scale, screenH * scale); // 遍历所有分区 this.addProgramForm.zones.forEach((zone, zIdx) => { @@ -2493,7 +2443,8 @@ export default { const pageLines = asset.pages[page] || []; // 设置字体样式 - ctx.fillStyle = ['#FF0000', '#00FF00', '#0000FF'][zone.fontColor] || '#FF0000'; + const colors = ['#000000', '#FF0000', '#00FF00', '#FFFF00']; + ctx.fillStyle = colors[zone.fontColor] || '#000000'; ctx.textBaseline = 'top'; ctx.font = `${asset.fontWeight} ${asset.scaledFontSize}px ${asset.fontFamily}`; @@ -2565,7 +2516,7 @@ export default { // 获取像素数据并应用颜色 const imageData = tempCtx.getImageData(0, 0, newWidth, newHeight); const data = imageData.data; - const colors = [[255, 0, 0], [0, 255, 0], [255, 255, 0]]; // Red, Green, Yellow + const colors = [[0, 0, 0], [255, 0, 0], [0, 255, 0], [255, 255, 0]]; // Black, Red, Green, Yellow const selectedColor = colors[zone.imageColor]; if (selectedColor) { for (let i = 0; i < data.length; i += 4) { @@ -2618,8 +2569,11 @@ export default { const canvas = this.$refs.addProgramPreviewCanvas; if (!canvas) return; - const renderWidth = 320; // 与实际渲染宽度匹配 - const scale = Math.min(renderWidth / 32, 120 / 64); + // 动态获取屏幕参数 + const screenW = this.screenParams.width || 32; + const screenH = this.screenParams.height || 64; + const renderWidth = 640; // 与实际渲染宽度匹配 + const scale = Math.min(renderWidth / screenW, 240 / screenH); this.addProgramPreviewAssets = this.addProgramForm.zones.map(zone => { if (zone.playType === 1 && zone.image) { @@ -2788,8 +2742,8 @@ export default { }, updateAddProgramPreviewAnimState() { // 动画主循环,更新每个分区的动画状态 - const renderWidth = 320; - const renderHeight = 120; + const renderWidth = 640; + const renderHeight = 240; const scale = Math.min(renderWidth / 32, renderHeight / 64); this.addProgramForm.zones.forEach((zone, idx) => { const asset = this.addProgramPreviewAssets[idx]; @@ -2860,6 +2814,93 @@ export default { ctx.font = `${asset.fontWeight} ${asset.scaledFontSize}px ${asset.fontFamily}`; return ctx.measureText(text).width; }, + // ================= 显卡物模型适配方法 BEGIN ================= + // 屏幕总开关 + handleScreenSwitchChange(val) { + const model = this.deviceInfo.thingsModels.find(m => m.id === '102#screenEn'); + if (model) { + model.shadow = val ? '1' : '0'; + this.mqttPublish(this.deviceInfo, model); + } + }, + // 开机界面开关 + handleBootEnChange(val) { + const model = this.deviceInfo.thingsModels.find(m => m.id === '102#bootEn'); + if (model) { + model.shadow = val ? '1' : '0'; + this.mqttPublish(this.deviceInfo, model); + } + }, + // 恢复出厂 + handleFactoryEnChange(val) { + const model = this.deviceInfo.thingsModels.find(m => m.id === '102#factoryEn'); + if (model) { + model.shadow = val ? '1' : '0'; + this.mqttPublish(this.deviceInfo, model); + } + }, + // 屏幕亮度 + handleLuminanceChange(val) { + const model = this.deviceInfo.thingsModels.find(m => m.id === '102#luminance'); + if (model) { + model.shadow = val.toString(); + this.mqttPublish(this.deviceInfo, model); + } + }, + // 屏参设置 + handleScrParamChange(param) { + // param: { w, h, oe, data, angle } + const model = this.deviceInfo.thingsModels.find(m => m.id === '102#scrParam'); + if (model) { + model.shadow = JSON.stringify({ scr_param: param }); + this.mqttPublish(this.deviceInfo, model); + } + }, + // 节目列表 + handleProgListChange(progList) { + // progList: [{order, rem, dur, areaM, aLst: [...] }] + const model = this.deviceInfo.thingsModels.find(m => m.id === '102#progList'); + if (model) { + model.shadow = JSON.stringify({ prog_list: progList }); + this.mqttPublish(this.deviceInfo, model); + } + }, + // 节目开关列表 + handleProgEnChange(progEnArr) { + // progEnArr: [1,0,1,1,0] + const model = this.deviceInfo.thingsModels.find(m => m.id === '102#progEn'); + if (model) { + model.shadow = JSON.stringify(progEnArr); + this.mqttPublish(this.deviceInfo, model); + } + }, + // 删除节目 + handleDelProg(index) { + // index: 0=删第1条, -1=全部清空 + const model = this.deviceInfo.thingsModels.find(m => m.id === 'del_prog'); + if (model) { + model.shadow = index.toString(); + this.mqttPublish(this.deviceInfo, model); + } + }, + // 传感器阈值 + handleSnsrThrChange(thr) { + // thr: { speed: 60, temp: 30, humi: 50 } + const model = this.deviceInfo.thingsModels.find(m => m.id === '102#snsrThr'); + if (model) { + model.shadow = JSON.stringify({ snsr_thr: thr }); + this.mqttPublish(this.deviceInfo, model); + } + }, + // 触发一次全量上报 + handleRequestModelReport() { + const model = this.deviceInfo.thingsModels.find(m => m.id === '102#model'); + if (model) { + model.shadow = '1'; + this.mqttPublish(this.deviceInfo, model); + } + }, + // ================= 显卡物模型适配方法 END ================= }, }; @@ -2891,19 +2932,25 @@ export default { .mode-header { display: flex; align-items: center; + justify-content: space-between; margin-bottom: 15px; - i, - .svg-icon { - font-size: 20px; - margin-right: 8px; - color: #409EFF; - } + .header-left { + display: flex; + align-items: center; - .mode-title { - font-size: 16px; - font-weight: bold; - color: #303133; + i, + .svg-icon { + font-size: 30px; + margin-right: 8px; + color: #409EFF; + } + + .mode-title { + font-size: 16px; + font-weight: bold; + color: #303133; + } } } @@ -2915,6 +2962,40 @@ export default { font-size: 16px; font-weight: bold; } + + .info-item { + display: flex; + align-items: center; + justify-content: flex-start; + margin-bottom: 15px; + padding: 8px 12px; + background: #f8f9fa; + border-radius: 6px; + border-left: 3px solid #409EFF; + position: relative; + min-height: 40px; + + .info-label { + font-size: 14px; + color: #606266; + margin-right: 12px; + min-width: 80px; + } + + .info-value { + font-size: 14px; + color: #303133; + font-weight: 600; + flex: 1; + } + + &.el-loading-parent--relative { + .el-loading-mask { + background-color: rgba(255, 255, 255, 0.8); + border-radius: 6px; + } + } + } } } diff --git a/src/views/iot/device/gateway-set.vue b/src/views/iot/device/gateway-set.vue index c68bf97..27bfa1f 100644 --- a/src/views/iot/device/gateway-set.vue +++ b/src/views/iot/device/gateway-set.vue @@ -1,31 +1,33 @@ @@ -128,6 +232,15 @@ export default { power: [{ required: true, message: '请选择功率', trigger: 'change' }], deviceAddr: [{ required: true, message: '请输入设备地址', trigger: 'blur' }], }, + // 雷达设置表单 + radarForm: { + lowSpeed: [10, 30], + triggerSpeed: [40, 80], + overSpeed: [100, 150], + }, + timeList: [ + + ], // 以下为保留设备模式和OTA升级部分的相关变量 title: '设备控制', statusColor: { @@ -135,6 +248,73 @@ export default { color: '#fff', maxWidth: '200px', }, + openEventDialog: false, + eventForm: { + type: '', + source: '', + priority: 1, + program: '', + duration: 10, + pin: '', + level: '', + }, + eventRules: { + type: [{ required: true, message: '请选择事件类型', trigger: 'change' }], + source: [{ required: true, message: '请输入事件来源', trigger: 'blur' }], + priority: [{ required: true, message: '请输入优先级', trigger: 'change' }], + program: [{ required: true, message: '请输入显示节目', trigger: 'blur' }], + duration: [{ required: true, message: '请输入显示时长', trigger: 'change' }], + pin: [{ required: true, message: '请输入输出引脚', trigger: 'blur' }], + level: [{ required: true, message: '请选择输出电平', trigger: 'change' }], + }, + // 事件相关选项 + eventTypeOptions: [ + { label: '默认显示', value: '0' }, + { label: '雷达触发(RS485 雷达)', value: '1' }, + { label: '雷达超速(RS485 雷达)', value: '2' }, + { label: '车牌触发(摄像头)', value: '3' }, + { label: '车牌超速(摄像头)', value: '4' }, + { label: '行人检测(相机)', value: '5' }, + { label: 'IN1下降沿', value: '0x51' }, + { label: 'IN2上升沿', value: '0x52' }, + { label: 'IN3下降沿', value: '0x53' }, + { label: 'IN4上升沿', value: '0x54' }, + { label: 'IN1低电平', value: '0x55' }, + { label: 'IN2高电平', value: '0x56' }, + { label: 'IN3低电平', value: '0x57' }, + { label: 'IN4高电平', value: '0x58' }, + { label: '其他', value: '-1' }, + ], + eventSourceOptions: [ + { label: '本台设备', value: '0' }, + { label: '任意设备(不包含本设备)', value: '254' }, + { label: '任意设备', value: '255' }, + { label: '其他', value: '1' }, + ], + programOptions: [ + { label: '节目一', value: 'program1' }, + { label: '节目二', value: 'program2' }, + { label: '节目三', value: 'program3' }, + { label: '节目四', value: 'program4' }, + { label: '节目五', value: 'program5' }, + { label: '节目六', value: 'program6' }, + { label: '节目七', value: 'program7' }, + { label: '节目八', value: 'program8' }, + + + + ], + pinOptions: [ + { label: '引脚1', value: 'pin1' }, + { label: '引脚2', value: 'pin2' }, + { label: '引脚3', value: 'pin3' }, + { label: '引脚4', value: 'pin4' }, + { label: '引脚5', value: 'pin5' }, + { label: '引脚6', value: 'pin6' }, + { label: '引脚7', value: 'pin7' }, + { label: '引脚8', value: 'pin8' }, + { label: '引脚9', value: 'pin9' }, + ], }; }, methods: { @@ -152,6 +332,26 @@ export default { viewVersion() { this.$message.info('查看固件版本功能保留'); }, + onRadarSubmit() { + this.$message.success('雷达设置已保存'); + // TODO: 调用接口保存雷达设置 + }, + handleAddEvent() { + this.$refs.eventFormRef.validate((valid) => { + if (valid) { + // 获取事件类型和来源的label + const typeLabel = (this.eventTypeOptions.find(item => item.value === this.eventForm.type) || {}).label || this.eventForm.type; + const sourceLabel = (this.eventSourceOptions.find(item => item.value === this.eventForm.source) || {}).label || this.eventForm.source; + this.timeList.push({ + // time: new Date().toLocaleTimeString().slice(0, 5), + event: `${typeLabel} / ${sourceLabel}`, + plan: `优先级:${this.eventForm.priority} 节目:${this.programOptions.find(item => item.value === this.eventForm.program)?.label || this.eventForm.program} 时长:${this.eventForm.duration}s 引脚:${this.pinOptions.find(item => item.value === this.eventForm.pin)?.label || this.eventForm.pin} 电平:${this.eventForm.level}` + }); + this.openEventDialog = false; + this.$refs.eventFormRef.resetFields(); + } + }); + }, }, }; @@ -159,18 +359,80 @@ export default {