From 7236a9523600f2a888004cadec0f02cb28111bec Mon Sep 17 00:00:00 2001 From: 1 <13958863+jayjiajun@user.noreply.gitee.com> Date: Fri, 25 Jul 2025 19:55:12 +0800 Subject: [PATCH] =?UTF-8?q?=E6=98=BE=E5=8D=A1=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/iot/device/display.vue | 588 ++++++++++++++++++------------- 1 file changed, 352 insertions(+), 236 deletions(-) diff --git a/src/views/iot/device/display.vue b/src/views/iot/device/display.vue index bd2526e..92244b6 100644 --- a/src/views/iot/device/display.vue +++ b/src/views/iot/device/display.vue @@ -333,17 +333,17 @@ $t('device.running-status.866086-12') }} {{ firmware.firmwareName - }} + }} {{ firmware.productName - }} + }} Version {{ firmware.version - }} + }} {{ getDownloadUrl(firmware.filePath) }} {{ firmware.remark - }} + }} @@ -2483,6 +2483,7 @@ export default { this.addProgramForm.zones.forEach((zone, zIdx) => { const asset = this.addProgramPreviewAssets[zIdx]; if (!asset) return; + const state = this.addProgramPreviewAnimState[zIdx] || {}; // 计算分区在画布中的位置(角度为90或270时,x/y/width/height互换) let zoneX, zoneY, zoneWidth, zoneHeight; @@ -2512,12 +2513,9 @@ export default { ctx.rect(zoneX, zoneY, zoneWidth, zoneHeight); ctx.clip(); - // 获取动画状态 - const state = this.addProgramPreviewAnimState[zIdx]; - if (zone.playType === 0 && asset.isText) { - // 文本类型分区 - const page = state && typeof state.currentPage === 'number' ? state.currentPage : 0; + // 获取当前页(使用状态中的当前页) + const page = state.currentPage || 0; const pageLines = asset.pages[page] || []; // 设置字体样式 @@ -2589,7 +2587,7 @@ export default { ctx.fillText(line, x1, y); ctx.fillText(line, x2, y); } else { - ctx.fillText(line, startX + (state ? state.currentX : 0), startY + lineIdx * lineHeight + (state ? state.currentY : 0)); + ctx.fillText(line, startX + animOffsetX, startY + lineIdx * lineHeight + animOffsetY); } }); } else if (zone.playType === 1 && asset.isImage) { @@ -2678,124 +2676,124 @@ export default { }); }, prepareAddProgramPreviewAssets() { - const canvas = this.$refs.addProgramPreviewCanvas; - if (!canvas) return; + const canvas = this.$refs.addProgramPreviewCanvas; + if (!canvas) return; - // 动态获取屏幕参数 - let screenW = this.screenParams.width || 32; - let screenH = this.screenParams.height || 64; - // 如果角度为90或270,渲染时宽高互换 - 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); - - this.addProgramPreviewAssets = this.addProgramForm.zones.map(zone => { - if (zone.playType === 1 && zone.image) { - // 图片类型,异步加载图片 - const img = new window.Image(); - const asset = { img, isImage: true, loaded: false }; - img.onload = () => { - asset.loaded = true; - this.drawAddProgramPreview(); - }; - img.onerror = (e) => { - console.error("Preview image failed to load:", e); - }; - img.src = zone.image; - return asset; - } - if (zone.playType !== 0 || !zone.displayText) { - return { pages: [[]], isText: false }; - } - - // 获取原始字体大小(像素值) - const fontSize = parseInt(this.addProgramFontSizes[zone.fontSize] || '16px'); - - // 计算缩放后的字体大小 - const scaledFontSize = Math.max(10, Math.round(fontSize * scale * 0.8)); // 添加0.8缩放系数 - - const fontFamily = [ - 'SimSun, 宋体, Songti SC, serif', - 'SimHei, 黑体, Heiti SC, sans-serif', - 'KaiTi, 楷体, Kaiti SC, serif' - ]; - const fontWeight = zone.fontBold ? 'bold' : 'normal'; - - // 创建临时canvas测量文本 - const measureCanvas = document.createElement('canvas'); - const measureCtx = measureCanvas.getContext('2d'); - 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' - ]; - let fontFamilyUsed; - if (/^[A-Za-z0-9\s]+$/.test(zone.displayText)) { - fontFamilyUsed = enFontMap[zone.fontEn] || fontFamily[0]; - } else { - fontFamilyUsed = fontFamily[zone.font] || fontFamily[0]; - } - measureCtx.font = `${fontWeight} ${scaledFontSize}px ${fontFamilyUsed}`; - - // 分行逻辑 - 特殊处理连续左移效果 - let lines = []; - if (zone.effect === 5) { // 连续左移效果 - // 强制不分行,整个文本作为一行 - lines = [zone.displayText]; - } else { - // 正常的分行逻辑 - const zoneRenderWidth = swapWH ? zone.height : zone.width; - const maxWidth = zoneRenderWidth * scale - 4; // 预留4px边距 - let currentLine = ''; - - for (const char of zone.displayText) { - const testLine = currentLine + char; - const metrics = measureCtx.measureText(testLine); - - if (metrics.width > maxWidth) { - if (currentLine) lines.push(currentLine); - currentLine = char; - } else { - currentLine = testLine; - } + // 动态获取屏幕参数 + let screenW = this.screenParams.width || 32; + let screenH = this.screenParams.height || 64; + // 如果角度为90或270,渲染时宽高互换 + 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); - if (currentLine) lines.push(currentLine); - } + this.addProgramPreviewAssets = this.addProgramForm.zones.map(zone => { + if (zone.playType === 1 && zone.image) { + // 图片类型,异步加载图片 + const img = new window.Image(); + const asset = { img, isImage: true, loaded: false }; + img.onload = () => { + asset.loaded = true; + this.drawAddProgramPreview(); + }; + img.onerror = (e) => { + console.error("Preview image failed to load:", e); + }; + img.src = zone.image; + return asset; + } + if (zone.playType !== 0 || !zone.displayText) { + return { pages: [[]], isText: false }; + } - // 计算每页最大行数 - const zoneRenderHeight = swapWH ? zone.width : zone.height; - const lineHeight = scaledFontSize * 1.2; - const maxLines = Math.max(1, Math.floor((zoneRenderHeight * scale - 4) / lineHeight)); + // 获取原始字体大小(像素值) + const fontSize = parseInt(this.addProgramFontSizes[zone.fontSize] || '16px'); - // 分页 - const pages = []; - for (let i = 0; i < lines.length; i += maxLines) { - pages.push(lines.slice(i, i + maxLines)); - } + // 计算缩放后的字体大小 + const scaledFontSize = Math.max(10, Math.round(fontSize * scale * 0.8)); // 添加0.8缩放系数 - if (pages.length === 0) pages.push([]); + const fontFamily = [ + 'SimSun, 宋体, Songti SC, serif', + 'SimHei, 黑体, Heiti SC, sans-serif', + 'KaiTi, 楷体, Kaiti SC, serif' + ]; + const fontWeight = zone.fontBold ? 'bold' : 'normal'; - // 计算总宽高和区域宽高,供动画用 - const totalWidth = Math.max(...pages.flat().map(line => measureCtx.measureText(line).width), 0); - const totalHeight = Math.min(lines.length, maxLines) * lineHeight; - - return { - pages, - isText: true, - scaledFontSize, - fontFamily: fontFamilyUsed, - fontWeight, - lineHeight, - totalWidth, - totalHeight, - zoneRenderWidth: (swapWH ? zone.height : zone.width) * scale, - zoneRenderHeight: (swapWH ? zone.width : zone.height) * scale - }; - }); -}, + // 创建临时canvas测量文本 + const measureCanvas = document.createElement('canvas'); + const measureCtx = measureCanvas.getContext('2d'); + 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' + ]; + let fontFamilyUsed; + if (/^[A-Za-z0-9\s]+$/.test(zone.displayText)) { + fontFamilyUsed = enFontMap[zone.fontEn] || fontFamily[0]; + } else { + fontFamilyUsed = fontFamily[zone.font] || fontFamily[0]; + } + measureCtx.font = `${fontWeight} ${scaledFontSize}px ${fontFamilyUsed}`; + + // 分行逻辑 - 特殊处理连续左移效果 + let lines = []; + if (zone.effect === 5) { // 连续左移效果 + // 强制不分行,整个文本作为一行 + lines = [zone.displayText]; + } else { + // 正常的分行逻辑 + const zoneRenderWidth = swapWH ? zone.height : zone.width; + const maxWidth = zoneRenderWidth * scale - 4; // 预留4px边距 + let currentLine = ''; + + for (const char of zone.displayText) { + const testLine = currentLine + char; + const metrics = measureCtx.measureText(testLine); + + if (metrics.width > maxWidth) { + if (currentLine) lines.push(currentLine); + currentLine = char; + } else { + currentLine = testLine; + } + } + + if (currentLine) lines.push(currentLine); + } + + // 计算每页最大行数 + const zoneRenderHeight = swapWH ? zone.width : zone.height; + const lineHeight = scaledFontSize * 1.2; + const maxLines = Math.max(1, Math.floor((zoneRenderHeight * scale - 4) / lineHeight)); + + // 分页 + const pages = []; + for (let i = 0; i < lines.length; i += maxLines) { + pages.push(lines.slice(i, i + maxLines)); + } + + if (pages.length === 0) pages.push([]); + + // 计算总宽高和区域宽高,供动画用 + const totalWidth = Math.max(...pages.flat().map(line => measureCtx.measureText(line).width), 0); + const totalHeight = Math.min(lines.length, maxLines) * lineHeight; + + return { + pages, + isText: true, + scaledFontSize, + fontFamily: fontFamilyUsed, + fontWeight, + lineHeight, + totalWidth, + totalHeight, + zoneRenderWidth: (swapWH ? zone.height : zone.width) * scale, + zoneRenderHeight: (swapWH ? zone.width : zone.height) * scale + }; + }); + }, resetAddProgramPreviewPage() { this.addProgramPreviewPage = this.addProgramForm.zones.map(() => 0); }, @@ -2864,17 +2862,20 @@ export default { return { lines, maxLines }; }, resetAddProgramPreviewAnimState() { - // 初始化每个分区动画状态 - this.addProgramPreviewAnimState = this.addProgramForm.zones.map((zone, idx) => { - return { - currentX: 0, - currentY: 0, - currentPage: this.addProgramPreviewPage[idx] || 0, - pageTimer: null, - effect: zone.effect, - }; - }); - }, + this.addProgramPreviewAnimState = this.addProgramForm.zones.map((zone, idx) => { + return { + currentX: 0, + currentY: 0, + currentPage: 0, + isMoving: false, + isPausing: false, + moveStart: 0, + pauseStart: 0, + currentChar: 0, // 当前显示的字符索引 + effect: zone.effect, + }; + }); +}, stopAddProgramPreviewAnimLoop() { if (this.addProgramPreviewAnimFrame) { cancelAnimationFrame(this.addProgramPreviewAnimFrame); @@ -2891,118 +2892,233 @@ export default { this.addProgramPreviewAnimFrame = requestAnimationFrame(loop); }, updateAddProgramPreviewAnimState() { - // 动画主循环,更新每个分区的动画状态 - const renderWidth = 640; - const renderHeight = 240; - let screenW = this.screenParams.width || 32; - let screenH = this.screenParams.height || 64; - const angle = this.screenParams.angle; - const swapWH = angle === 90 || angle === 270; - if (swapWH) { - [screenW, screenH] = [screenH, screenW]; - } - const scale = Math.min(renderWidth / screenW, renderHeight / screenH); + // 动画主循环,更新每个分区的动画状态 + const renderWidth = 640; + const renderHeight = 240; + let screenW = this.screenParams.width || 32; + let screenH = this.screenParams.height || 64; + const angle = this.screenParams.angle; + const swapWH = angle === 90 || angle === 270; + if (swapWH) { + [screenW, screenH] = [screenH, screenW]; + } + const scale = Math.min(renderWidth / screenW, renderHeight / screenH); - this.addProgramForm.zones.forEach((zone, idx) => { - const asset = this.addProgramPreviewAssets[idx]; - const state = this.addProgramPreviewAnimState[idx]; + this.addProgramForm.zones.forEach((zone, idx) => { + const asset = this.addProgramPreviewAssets[idx]; + const state = this.addProgramPreviewAnimState[idx]; - if (!asset || !state || !asset.isText) return; + if (!asset || !state || !asset.isText) return; - const effect = zone.effect; - const page = this.addProgramPreviewPage[idx] || 0; - const pageLines = asset.pages[page] || []; - const lineHeight = asset.lineHeight; - const totalHeight = asset.totalHeight; - const totalWidth = asset.totalWidth; + const effect = zone.effect; + const pageLines = asset.pages[state.currentPage] || []; + const lineHeight = asset.lineHeight; + const totalHeight = asset.totalHeight; + const totalWidth = asset.totalWidth; - // 动画速度 - const speedMap = [1, 2, 3, 4, 5]; - const animSpeed = (speedMap[zone.speed] || 3) * scale * 0.5; // 像素/帧 + // 动画速度 + const speedMap = [1, 2, 3, 4, 5]; + const animSpeed = (speedMap[zone.speed] || 3) * scale * 0.5; // 像素/帧 - // 区域宽高 (使用预先计算并缩放的值) - const zoneWidth = asset.zoneRenderWidth; - const zoneHeight = asset.zoneRenderHeight; + // 区域宽高 (使用预先计算并缩放的值) + const zoneWidth = asset.zoneRenderWidth; + const zoneHeight = asset.zoneRenderHeight; - // 暂停时间 (毫秒) - const pauseMs = this.getPauseTime(zone.stayTime); + // 暂停时间 (毫秒) + const pauseMs = this.getPauseTime(zone.stayTime); - // 确保初始状态值存在 - if (typeof state.isPausing !== 'boolean') state.isPausing = false; - if (typeof state.pauseStart !== 'number') state.pauseStart = 0; - if (typeof state.pausePhase !== 'string') state.pausePhase = 'start'; // 'start' | 'move' + // 确保初始状态值存在 + if (typeof state.isMoving !== 'boolean') state.isMoving = false; + if (typeof state.isPausing !== 'boolean') state.isPausing = false; + if (typeof state.pauseStart !== 'number') state.pauseStart = 0; + if (typeof state.moveStart !== 'number') state.moveStart = 0; - switch (effect) { - case 1: // 左移 - if (typeof state.currentX !== 'number') state.currentX = zoneWidth; - state.currentX -= animSpeed; - if (state.currentX < -totalWidth) { - if (asset.pages.length > 1) { - state.currentPage = (state.currentPage + 1) % asset.pages.length; - } - state.currentX = zoneWidth; + switch (effect) { + case 0: // 立即显示 + if (!state.isPausing && !state.isMoving) { + // 初始状态:直接进入停留阶段 + state.isPausing = true; + state.pauseStart = Date.now(); + } else if (state.isPausing) { + // 检查停留时间是否结束 + if (Date.now() - state.pauseStart >= pauseMs) { + state.isPausing = false; + state.isMoving = true; // 准备翻页 + } + } else if (state.isMoving) { + // 翻页操作 + state.currentPage = (state.currentPage + 1) % asset.pages.length; + state.isMoving = false; + state.isPausing = true; + state.pauseStart = Date.now(); + } + // 位置保持不变 + state.currentX = 0; + state.currentY = 0; + break; + + case 1: // 左移 + if (!state.isMoving && !state.isPausing) { + // 初始状态:设置初始位置(右侧外部) + state.currentX = zoneWidth; + state.isMoving = true; + state.moveStart = Date.now(); + } else if (state.isMoving) { + // 移动阶段 + const elapsed = Date.now() - state.moveStart; + const progress = Math.min(1, elapsed / (totalWidth * 1000 / animSpeed)); + + // 计算移动位置(从右侧移动到左侧) + state.currentX = zoneWidth - progress * (zoneWidth + totalWidth); + + // 检查是否到达目标位置 + if (progress >= 1) { + state.isMoving = false; + state.isPausing = true; + state.pauseStart = Date.now(); + } + } else if (state.isPausing) { + // 停留阶段 + if (Date.now() - state.pauseStart >= pauseMs) { + state.isPausing = false; + state.isMoving = true; // 准备翻页 + } + } else if (!state.isPausing && state.isMoving) { + // 翻页操作 + state.currentPage = (state.currentPage + 1) % asset.pages.length; + state.currentX = zoneWidth; // 重置位置 + state.isMoving = true; + state.moveStart = Date.now(); + } + state.currentY = 0; + break; + + case 2: // 右移 + if (!state.isMoving && !state.isPausing) { + state.currentX = -totalWidth; + state.isMoving = true; + state.moveStart = Date.now(); + } else if (state.isMoving) { + const elapsed = Date.now() - state.moveStart; + const progress = Math.min(1, elapsed / (totalWidth * 1000 / animSpeed)); + + state.currentX = -totalWidth + progress * (zoneWidth + totalWidth); + + if (progress >= 1) { + state.isMoving = false; + state.isPausing = true; + state.pauseStart = Date.now(); + } + } else if (state.isPausing) { + if (Date.now() - state.pauseStart >= pauseMs) { + state.isPausing = false; + state.isMoving = true; // 准备翻页 + } + } else if (!state.isPausing && state.isMoving) { + state.currentPage = (state.currentPage + 1) % asset.pages.length; + state.currentX = -totalWidth; + state.isMoving = true; + state.moveStart = Date.now(); + } + state.currentY = 0; + break; + + case 3: // 上移 + if (!state.isMoving && !state.isPausing) { + state.currentY = zoneHeight; + state.isMoving = true; + state.moveStart = Date.now(); + } else if (state.isMoving) { + const elapsed = Date.now() - state.moveStart; + const progress = Math.min(1, elapsed / (totalHeight * 1000 / animSpeed)); + + state.currentY = zoneHeight - progress * (zoneHeight + totalHeight); + + if (progress >= 1) { + state.isMoving = false; + state.isPausing = true; + state.pauseStart = Date.now(); + } + } else if (state.isPausing) { + if (Date.now() - state.pauseStart >= pauseMs) { + state.isPausing = false; + state.isMoving = true; // 准备翻页 + } + } else if (!state.isPausing && state.isMoving) { + state.currentPage = (state.currentPage + 1) % asset.pages.length; + state.currentY = zoneHeight; + state.isMoving = true; + state.moveStart = Date.now(); + } + state.currentX = 0; + break; + + case 4: // 下移 + if (!state.isMoving && !state.isPausing) { + state.currentY = -totalHeight; + state.isMoving = true; + state.moveStart = Date.now(); + } else if (state.isMoving) { + const elapsed = Date.now() - state.moveStart; + const progress = Math.min(1, elapsed / (totalHeight * 1000 / animSpeed)); + + state.currentY = -totalHeight + progress * (zoneHeight + totalHeight); + + if (progress >= 1) { + state.isMoving = false; + state.isPausing = true; + state.pauseStart = Date.now(); + } + } else if (state.isPausing) { + if (Date.now() - state.pauseStart >= pauseMs) { + state.isPausing = false; + state.isMoving = true; // 准备翻页 + } + } else if (!state.isPausing && state.isMoving) { + state.currentPage = (state.currentPage + 1) % asset.pages.length; + state.currentY = -totalHeight; + state.isMoving = true; + state.moveStart = Date.now(); + } + state.currentX = 0; + break; + + case 5: // 连续左移 (跑马灯效果) + // 特殊处理 - 不分页 + if (typeof state.currentX !== 'number') state.currentX = zoneWidth; + state.currentX -= animSpeed; + if (state.currentX < -totalWidth) { + state.currentX += totalWidth; + } + state.currentY = (zoneHeight - totalHeight) / 2; + break; + + case 6: // 闪烁换页 + if (!state.isPausing && !state.isMoving) { + state.isPausing = true; + state.pauseStart = Date.now(); + } else if (state.isPausing) { + if (Date.now() - state.pauseStart >= pauseMs) { + state.isPausing = false; + state.isMoving = true; // 准备翻页 + } + } else if (state.isMoving) { + state.currentPage = (state.currentPage + 1) % asset.pages.length; + state.isMoving = false; + state.isPausing = true; + state.pauseStart = Date.now(); + } + state.currentX = 0; + state.currentY = 0; + break; + + default: // 立即显示 (effect === 0) 或其他未知效果 + state.currentX = 0; + state.currentY = 0; + break; } - state.currentY = 0; - break; - case 2: // 右移 - if (typeof state.currentX !== 'number') state.currentX = -totalWidth; - state.currentX += animSpeed; - if (state.currentX > zoneWidth) { - if (asset.pages.length > 1) { - state.currentPage = (state.currentPage + 1) % asset.pages.length; - } - state.currentX = -totalWidth; - } - state.currentY = 0; - break; - case 3: // 上移 - if (typeof state.currentY !== 'number') state.currentY = zoneHeight; - state.currentY -= animSpeed; - if (state.currentY < -totalHeight) { - if (asset.pages.length > 1) { - state.currentPage = (state.currentPage + 1) % asset.pages.length; - } - state.currentY = zoneHeight; - } - state.currentX = 0; - break; - case 4: // 下移 - if (typeof state.currentY !== 'number') state.currentY = -totalHeight; - state.currentY += animSpeed; - if (state.currentY > zoneHeight) { - if (asset.pages.length > 1) { - state.currentPage = (state.currentPage + 1) % asset.pages.length; - } - state.currentY = -totalHeight; - } - state.currentX = 0; - break; - case 5: // 连续左移 (跑马灯效果) - // 确保初始位置在分区右侧外部 - if (typeof state.currentX !== 'number') state.currentX = zoneWidth; - // 向左移动文本 - state.currentX -= animSpeed; - // 当文本完全移出左侧边界时,重置到右侧重新开始(无缝循环) - if (state.currentX < -totalWidth) { - state.currentX += totalWidth; - } - // Y轴居中显示 - state.currentY = (zoneHeight - totalHeight) / 2; - break; - - case 6: // 闪烁换页 - // 由自动翻页定时器控制,此处不处理动画位移 - state.currentX = 0; - state.currentY = 0; - break; - - default: // 立即显示 (effect === 0) 或其他未知效果 - state.currentX = 0; - state.currentY = 0; - break; - } - }); -}, + }); + }, measureTextWidth(text, asset) { // 用于动画宽度测量 const canvas = document.createElement('canvas');