diff --git a/App.vue b/App.vue index a481fd6..5b6413d 100644 --- a/App.vue +++ b/App.vue @@ -35,6 +35,8 @@ onLaunch: function() { console.log('App Launch'); //判断用户是否首次打开 + this.requestRecordPermission(); + var isFirstOpen = uni.getStorageSync('isFirstOpen'); // 如果isFirstOpen不存在,说明是首次打开系统 if (!isFirstOpen) { @@ -97,7 +99,7 @@ checkLoginStatus(route) { let token = uni.getStorageSync('token'); if ((route.path === '/pages/tabBar/scene/index' || route.path === '/pages/tabBar/alert/index') && (token == - '' || token == null)) { + '' || token == null)) { uni.showModal({ title: this.$tt('common.tips'), content: this.$tt('common.loginTips'), @@ -115,6 +117,56 @@ } }); } + }, + // 新增:进入小程序时自动检测并申请录音权限 + requestRecordPermission() { + // #ifdef APP-PLUS + if (uni.getSystemInfoSync().platform === 'android') { + plus.android.requestPermissions([ + 'android.permission.RECORD_AUDIO' + ], function(resultObj) { + let result = 0; + for (let i = 0; i < resultObj.granted.length; i++) { + result = 1; + } + for (let i = 0; i < resultObj.deniedPresent.length; i++) { + result = 0; + } + for (let i = 0; i < resultObj.deniedAlways.length; i++) { + result = -1; + } + if (result !== 1) { + uni.showModal({ + title: '提示', + content: '录音权限未开启,部分功能将无法使用,请前往设置开启权限。', + confirmText: '去设置', + success: (res) => { + if (res.confirm) { + plus.runtime.openApp({ + action: 'android.settings.APPLICATION_DETAILS_SETTINGS' + }); + } + } + }); + } + }); + } else if (uni.getSystemInfoSync().platform === 'ios') { + // iOS首次调用录音API会自动弹出授权,无需手动申请 + // 这里可以做一次录音尝试以触发授权弹窗 + const recorder = uni.getRecorderManager(); + recorder.start({ duration: 1000 }); + recorder.onStart(() => { + recorder.stop(); + }); + recorder.onError(() => { + // 用户拒绝或未授权 + uni.showModal({ + title: '提示', + content: '录音权限未开启,部分功能将无法使用,请前往设置开启权限。' + }); + }); + } + // #endif } }, onHide: function() { diff --git a/pages/tabBar/home/index.vue b/pages/tabBar/home/index.vue index 3a274a1..e67c52a 100644 --- a/pages/tabBar/home/index.vue +++ b/pages/tabBar/home/index.vue @@ -266,6 +266,7 @@ }; }, onLoad() { + this.requestRecordPermission(); this.getToken(); if (this.token != '' && this.token != null) { this.connectMqtt(); @@ -658,6 +659,32 @@ uni.navigateTo({ url: '/pagesB/user/message/detail?noticeId=' + this.notices.noticeId }); + }, + // 新增:进入首页时自动检测并申请录音权限 + requestRecordPermission() { + try { + const recorder = uni.getRecorderManager(); + let triggered = false; + recorder.onStart(() => { + triggered = true; + recorder.stop(); + }); + recorder.onError(() => { + triggered = true; + uni.showModal({ + title: '提示', + content: '录音权限未开启,部分功能将无法使用,请前往设置开启权限。' + }); + }); + recorder.start({ duration: 1000 }); + setTimeout(() => { + if (!triggered) { + recorder.stop(); + } + }, 1500); + } catch (e) { + // 不支持录音 + } } } }; diff --git a/pagesA/home/device/status/addProgram.vue b/pagesA/home/device/status/addProgram.vue index cefb7d1..d5236e0 100644 --- a/pagesA/home/device/status/addProgram.vue +++ b/pagesA/home/device/status/addProgram.vue @@ -1,995 +1,880 @@ - - + + this.form.zones = newZones; + if (this.currentZoneTab >= newZones.length) { + this.currentZoneTab = newZones.length - 1; + } + }, + async initializeAnimations() { + if (this.animationFrameId) { + cancelAnimationFrame(this.animationFrameId); + } + this.animationStates.forEach(state => { + if (state.standTimer) clearTimeout(state.standTimer); + if (state.pageTimer) clearTimeout(state.pageTimer); + }); + + this.animationStates = this.form.zones.map(zone => { + const assetInfo = this.prepareZoneAssets(zone); + return { + ...assetInfo, + currentPage: 0, + currentX: 0, + currentY: 0, + standTimer: null, + pageTimer: null, + ...this.getInitialPosition(zone, assetInfo) + }; + }); + + this.animationLoop(); + }, + getInitialPosition(zone, assetInfo) { + const pos = { + currentX: 0, + currentY: 0 + }; + const effect = zone.effect; + if (effect === 1) pos.currentX = zone.width; + if (effect === 2) pos.currentX = -assetInfo.assetWidth; + if (effect === 3) pos.currentY = zone.height; + if (effect === 4) pos.currentY = -assetInfo.assetHeight; + if (effect === 5) pos.currentX = 0; + return pos; + }, + animationLoop() { + this.drawCanvas(); + this.animationFrameId = requestAnimationFrame(this.animationLoop.bind(this)); + }, + drawCanvas() { + const ctx = uni.createCanvasContext('previewCanvas', this); + const query = uni.createSelectorQuery().in(this); + query.select('.preview-canvas').boundingClientRect(rect => { + if (!rect) return; + const canvasWidth = rect.width; + const canvasHeight = rect.height; + ctx.clearRect(0, 0, canvasWidth, canvasHeight); + const scale = Math.min(canvasWidth / this.screenWidth, canvasHeight / this.screenHeight); + const screenDrawWidth = this.screenWidth * scale; + const screenDrawHeight = this.screenHeight * scale; + const offsetX = (canvasWidth - screenDrawWidth) / 2; + const offsetY = (canvasHeight - screenDrawHeight) / 2; + ctx.fillStyle = '#000000'; + ctx.fillRect(offsetX, offsetY, screenDrawWidth, screenDrawHeight); + + this.form.zones.forEach((zone, index) => { + const state = this.animationStates[index]; + if (!state) return; + + if (zone.playType === 1 && state.isImage && state.imageUrl) { + const zoneDrawX = offsetX + zone.x * scale; + const zoneDrawY = offsetY + zone.y * scale; + const zoneDrawWidth = zone.width * scale; + const zoneDrawHeight = zone.height * scale; + + ctx.save(); + ctx.beginPath(); + ctx.rect(zoneDrawX, zoneDrawY, zoneDrawWidth, zoneDrawHeight); + ctx.clip(); + + const cachePath = this.imageCache[state.imageUrl]; + if (cachePath) { + ctx.drawImage(cachePath, zoneDrawX, zoneDrawY, zoneDrawWidth, zoneDrawHeight); + if (zone.imageColor !== undefined) { + ctx.globalAlpha = 0.4; + ctx.setFillStyle(this.mapColor(zone.imageColor)); + ctx.fillRect(zoneDrawX, zoneDrawY, zoneDrawWidth, zoneDrawHeight); + ctx.globalAlpha = 1; + } + ctx.restore(); + return; + } + + uni.getImageInfo({ + src: state.imageUrl, + success: (res) => { + this.imageCache[state.imageUrl] = res.path; + this.drawCanvas(); + } + }); + ctx.restore(); + return; + } + + ctx.save(); + const zoneDrawX = offsetX + zone.x * scale; + const zoneDrawY = offsetY + zone.y * scale; + const zoneDrawWidth = zone.width * scale; + const zoneDrawHeight = zone.height * scale; + + ctx.beginPath(); + ctx.rect(zoneDrawX, zoneDrawY, zoneDrawWidth, zoneDrawHeight); + ctx.clip(); + + const fontSize = parseInt(this.fontSizes[zone.fontSize] || '16px'); + const scaledFontSize = Math.max(1, Math.round(fontSize * scale)); + ctx.font = `${this.mapBold(zone.fontBold)} ${scaledFontSize}px ${this.mapFont(zone.font)}`; + ctx.fillStyle = this.mapColor(zone.fontColor); + + const pageLines = state.pages[state.currentPage] || []; + const blockHeight = pageLines.length * scaledFontSize; + let blockStartY = zoneDrawY + state.currentY * scale; + + const verticalAlign = this.mapVAlign(zone.vAlign); + if (verticalAlign === 'middle') { + blockStartY = zoneDrawY + state.currentY * scale + (zoneDrawHeight - blockHeight) / 2; + } else if (verticalAlign === 'bottom') { + blockStartY = zoneDrawY + state.currentY * scale + zoneDrawHeight - blockHeight; + } + + ctx.textBaseline = 'top'; + + pageLines.forEach((line, lineIndex) => { + let xPos = zoneDrawX + state.currentX * scale; + const horizontalAlign = this.mapHAlign(zone.hAlign); + if (horizontalAlign === 'center') { + const lineWidth = this.calculateTextWidth(line, scaledFontSize); + xPos = zoneDrawX + state.currentX * scale + (zoneDrawWidth - lineWidth) / 2; + } else if (horizontalAlign === 'right') { + const lineWidth = this.calculateTextWidth(line, scaledFontSize); + xPos = zoneDrawX + state.currentX * scale + zoneDrawWidth - lineWidth; + } + + const yPos = blockStartY + (lineIndex * scaledFontSize); + ctx.fillText(line, xPos, yPos); + }); + + if (zone.effect === 5) { + const secondCopyX = zoneDrawX + state.currentX * scale + state.assetWidth * scale; + pageLines.forEach((line, lineIndex) => { + const yPos = blockStartY + (lineIndex * scaledFontSize); + ctx.fillText(line, secondCopyX, yPos); + }); + } + + ctx.restore(); + this.updateAnimationState(zone, state, index); + }); + + ctx.strokeStyle = '#FFFFFF'; + ctx.lineWidth = 1; + ctx.save(); + ctx.translate(offsetX, offsetY); + const mode = this.form.mode; + if (mode === 1) { + ctx.beginPath(); + ctx.moveTo(0, screenDrawHeight / 2); + ctx.lineTo(screenDrawWidth, screenDrawHeight / 2); + ctx.stroke(); + } else if (mode === 2) { + ctx.beginPath(); + ctx.moveTo(screenDrawWidth / 2, 0); + ctx.lineTo(screenDrawWidth / 2, screenDrawHeight); + ctx.stroke(); + } else if (mode === 3) { + ctx.beginPath(); + ctx.moveTo(0, screenDrawHeight / 3); + ctx.lineTo(screenDrawWidth, screenDrawHeight / 3); + ctx.moveTo(0, (screenDrawHeight / 3) * 2); + ctx.lineTo(screenDrawWidth, (screenDrawHeight / 3) * 2); + ctx.stroke(); + } + ctx.restore(); + ctx.draw(); + }).exec(); + }, + updateAnimationState(zone, state) { + if (state.standTimer) return; + const speedMap = [1, 2, 3, 4, 5]; + const animationSpeed = (speedMap[zone.speed] || 5) / 5; + const effect = zone.effect; + + if (effect !== 5 && state.pages.length > 1) { + if (!state.pageTimer) { + const stayTimeMap = [0, 100, 500, 1000, 2000, 3000, 4000, 5000, 6000, 8000, 10000]; + const stayTime = stayTimeMap[zone.stayTime] || 3000; + state.pageTimer = setTimeout(() => { + state.currentPage = (state.currentPage + 1) % state.pages.length; + state.pageTimer = null; + }, stayTime); + } + } + + switch (effect) { + case 0: + break; + case 1: + if (state.currentX > 0) state.currentX = Math.max(0, state.currentX - animationSpeed); + break; + case 2: + if (state.currentX < 0) state.currentX = Math.min(0, state.currentX + animationSpeed); + break; + case 3: + if (state.currentY > 0) state.currentY = Math.max(0, state.currentY - animationSpeed); + break; + case 4: + if (state.currentY < 0) state.currentY = Math.min(0, state.currentY + animationSpeed); + break; + case 5: + state.currentX -= animationSpeed; + if (state.currentX <= -state.assetWidth) { + state.currentX = 0; + } + break; + case 6: + if (!state.pageTimer) { + const stayTimeMap = [0, 100, 500, 1000, 2000, 3000, 4000, 5000, 6000, 8000, 10000]; + const stayTime = stayTimeMap[zone.stayTime] || 3000; + state.pageTimer = setTimeout(() => { + state.currentPage = (state.currentPage + 1) % state.pages.length; + state.pageTimer = null; + }, stayTime); + } + break; + } + }, + prepareZoneAssets(zone) { + if (zone.playType === 1 && zone.image) { + return { + isImage: true, + imageUrl: zone.image, + assetWidth: zone.width || 32, + assetHeight: zone.height || 32, + pages: [[]], + currentPage: 0 + }; + } + if (zone.playType !== 0 || !zone.displayText) { + return { + pages: [], + assetWidth: 0, + assetHeight: 0, + isText: false + }; + } + const ctx = uni.createCanvasContext('__temp_prepare_canvas', this); + const fontSize = parseInt(this.fontSizes[zone.fontSize] || '16px'); + ctx.font = `${this.mapBold(zone.fontBold)} ${fontSize}px ${this.mapFont(zone.font)}`; + + if (zone.effect === 5) { + const textWidth = this.calculateTextWidth(zone.displayText, fontSize); + return { + pages: [[zone.displayText]], + assetWidth: textWidth, + assetHeight: fontSize, + isText: true + }; + } + + const lines = []; + let currentLine = ''; + const text = zone.displayText; + for (let i = 0; i < text.length; i++) { + const char = text[i]; + const testLine = currentLine + char; + const lineWidth = this.calculateTextWidth(testLine, fontSize); + if (lineWidth > zone.width && currentLine !== '') { + lines.push(currentLine); + currentLine = char; + } else { + currentLine = testLine; + } + } + if (currentLine) lines.push(currentLine); + + const lineHeight = fontSize; + const maxLinesPerPage = zone.height > 0 ? Math.floor(zone.height / lineHeight) : 1; + const pages = []; + + if (maxLinesPerPage > 0) { + for (let i = 0; i < lines.length; i += maxLinesPerPage) { + pages.push(lines.slice(i, i + maxLinesPerPage)); + } + } else { + pages.push(lines); + } + + if (pages.length === 0) { + pages.push([]); + } + + return { + pages: pages, + assetWidth: zone.width, + assetHeight: zone.height, + isText: true + }; + }, + calculateTextWidth(text, fontSize) { + let width = 0; + for (let i = 0; i < text.length; i++) { + if (text.charCodeAt(i) > 127) { + width += fontSize; + } else { + width += fontSize / 2; + } + } + return width; + }, + openPicker(pickerFlag) { + this[pickerFlag] = true; + }, + onPlayTypeConfirm(e) { + this.updateZoneField('playType', this.getIndex(e)); + this.showPlayTypePicker = false; + }, + onPhraseConfirm(e) { + const index = this.getIndex(e); + const phrase = this.commonPhrases[index]; + this.updateZoneField('commonPhrase', index); + if (index > 0) { + this.updateZoneField('displayText', phrase); + } + this.showPhrasePicker = false; + }, + onFontConfirm(e) { + this.updateZoneField('font', this.getIndex(e)); + this.showFontPicker = false; + }, + onFontShapeConfirm(e) { + this.updateZoneField('fontShape', this.getIndex(e)); + this.showFontShapePicker = false; + }, + onFontSizeConfirm(e) { + this.updateZoneField('fontSize', this.getIndex(e)); + this.showFontSizePicker = false; + }, + onFontColorConfirm(e) { + this.updateZoneField('fontColor', this.getIndex(e)); + this.showFontColorPicker = false; + }, + onFontBoldConfirm(e) { + this.updateZoneField('fontBold', this.getIndex(e)); + this.showFontBoldPicker = false; + }, + onFontStretchConfirm(e) { + this.updateZoneField('fontStretch', this.getIndex(e)); + this.showFontStretchPicker = false; + }, + onEffectConfirm(e) { + this.updateZoneField('effect', this.getIndex(e)); + this.showEffectPicker = false; + }, + onHAlignConfirm(e) { + this.updateZoneField('hAlign', this.getIndex(e)); + this.showHAlignPicker = false; + }, + onVAlignConfirm(e) { + this.updateZoneField('vAlign', this.getIndex(e)); + this.showVAlignPicker = false; + }, + onSpeedConfirm(e) { + this.updateZoneField('speed', this.getIndex(e)); + this.showSpeedPicker = false; + }, + onStayTimeConfirm(e) { + this.updateZoneField('stayTime', this.getIndex(e)); + this.showStayTimePicker = false; + }, + onRead() { + this.$u.toast('读取功能待实现'); + }, + onSet() { + const programData = { + form: this.form, + device: this.device, + deviceInfo: this.deviceInfo + }; + uni.$emit('programDataReady', programData); + this.$u.toast('设置成功'); + uni.navigateBack(); + }, + onImageConfirm(e) { + const index = this.getIndex(e); + const fileName = this.imageFiles[index]; + this.updateZoneField('image', `https://xaznkj.cn/doc/traffic/ ${fileName}.png`); + this.showImagePicker = false; + }, + onImageSizeConfirm(e) { + const idx = this.getIndex(e); + const sizeMap = [ + { w: 16, h: 16 }, + { w: 32, h: 32 }, + { w: 64, h: 64 } + ]; + this.updateZoneField('imageSize', idx); + this.updateZoneField('width', sizeMap[idx].w); + this.updateZoneField('height', sizeMap[idx].h); + this.showImageSizePicker = false; + }, + onImageColorConfirm(e) { + this.updateZoneField('imageColor', this.getIndex(e)); + this.showImageColorPicker = false; + }, + onTabClick(item) { + this.currentZoneTab = item.index; + console.log(this.currentZoneTab); + }, + mapFont(index) { + return ['SimSun', 'SimHei', 'KaiTi'][index] || 'SimSun'; + }, + mapColor(index) { + return ['#FF0000', '#00FF00', '#0000FF'][index] || '#FF0000'; + }, + mapBold(index) { + return ['normal', 'bold'][index] || 'normal'; + }, + mapHAlign(index) { + return ['left', 'center', 'right'][index] || 'left'; + }, + mapVAlign(index) { + return ['top', 'middle', 'bottom'][index] || 'top'; + } + } + }; + + + \ No newline at end of file diff --git a/pagesA/home/device/status/base.vue b/pagesA/home/device/status/base.vue index efb2521..e89d4ee 100644 --- a/pagesA/home/device/status/base.vue +++ b/pagesA/home/device/status/base.vue @@ -798,21 +798,22 @@ this.deviceInfo = newVal; if (this.deviceInfo.deviceType != 3) { //分-属性和操作 - this.operateList = this.deviceInfo.thingsModels && this.deviceInfo.thingsModels.filter(( - item) => - item.isReadonly == '0'); - this.attributeList = this.deviceInfo.thingsModels && this.deviceInfo.thingsModels.filter(( - item) => - item.isReadonly == '1'); + this.operateList = this.deviceInfo.thingsModels && this.deviceInfo.thingsModels.filter( + (item) => String(item.isReadonly) === '0' || item.isReadonly === 0 + ); + this.attributeList = this.deviceInfo.thingsModels && this.deviceInfo.thingsModels.filter( + (item) => String(item.isReadonly) === '1' || item.isReadonly === 1 + ); //排序 this.attributeList = this.attributeList.sort((a, b) => b.order - a.order); this.operateList = this.operateList.sort((a, b) => b.order - a.order); } this.updateDeviceStatus(this.deviceInfo); this.initChart(); - console.log("wumoxing", JSON.stringify(this.deviceInfo.thingsModels)) - - + // 打印调试 + console.log('【base.vue watch】deviceInfo:', JSON.stringify(this.deviceInfo)); + console.log('【base.vue watch】attributeList:', JSON.stringify(this.attributeList)); + console.log('【base.vue watch】operateList:', JSON.stringify(this.operateList)); } } }, @@ -865,11 +866,27 @@ // 获取设备状态(兼容H5和APP) if (this.device !== null && Object.keys(this.device).length !== 0) { this.deviceInfo = this.device; + if (this.deviceInfo.deviceType != 3) { + this.operateList = this.deviceInfo.thingsModels && this.deviceInfo.thingsModels.filter( + (item) => String(item.isReadonly) === '0' || item.isReadonly === 0 + ); + this.attributeList = this.deviceInfo.thingsModels && this.deviceInfo.thingsModels.filter( + (item) => String(item.isReadonly) === '1' || item.isReadonly === 1 + ); + //排序 + this.attributeList = this.attributeList.sort((a, b) => b.order - a.order); + this.operateList = this.operateList.sort((a, b) => b.order - a.order); + } this.updateDeviceStatus(this.deviceInfo); this.initChart(); - }; + } // 回调处理 this.mqttCallback(); + + // 打印调试 + console.log('【base.vue created】deviceInfo:', JSON.stringify(this.deviceInfo)); + console.log('【base.vue created】attributeList:', JSON.stringify(this.attributeList)); + console.log('【base.vue created】operateList:', JSON.stringify(this.operateList)); }, methods: { /* Mqtt回调处理 */ diff --git a/pagesA/home/device/status/display.vue b/pagesA/home/device/status/display.vue index 40eb39f..183d071 100644 --- a/pagesA/home/device/status/display.vue +++ b/pagesA/home/device/status/display.vue @@ -1111,392 +1111,168 @@ \ No newline at end of file diff --git a/pagesA/home/device/status/gateway.vue b/pagesA/home/device/status/gateway.vue index 004994d..d2864c3 100644 --- a/pagesA/home/device/status/gateway.vue +++ b/pagesA/home/device/status/gateway.vue @@ -59,16 +59,8 @@ - @@ -78,11 +70,18 @@ - + + + + + + + + @@ -90,12 +89,13 @@ - - diff --git a/pagesA/home/device/status/index.vue b/pagesA/home/device/status/index.vue index 57fdb84..57ceca9 100644 --- a/pagesA/home/device/status/index.vue +++ b/pagesA/home/device/status/index.vue @@ -8,29 +8,29 @@ - - - - - - - + - + - - + - + + + + + + + + + + 暂无数据 + +