From 623a47466a6fef7c3fe40cfe5c1d52f85ac8d478 Mon Sep 17 00:00:00 2001 From: liub Date: Fri, 7 Nov 2025 12:16:10 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=88=E5=B9=B6=E7=BA=BF=E4=B8=8A=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TextToHex/textToDotMatrixFor7305.vue | 198 ++- pages.json | 19 + pages/100/HBY100.vue | 63 +- pages/6075/BJQ6075.vue | 1391 +++++++++++++++ pages/6170/deviceControl/index.vue | 9 - pages/common/audioManager/AudioList.vue | 1237 +++++++++++++ pages/common/audioManager/Recording.vue | 1557 +++++++++++++++++ pages/common/login/index.vue | 2 +- static/images/100/light.png | Bin 0 -> 1165 bytes static/images/100/lightActive.png | Bin 0 -> 1467 bytes static/images/100/record.png | Bin 0 -> 4091 bytes static/images/100/txtToAudio.png | Bin 0 -> 4435 bytes static/images/100/upload.png | Bin 0 -> 1874 bytes static/images/100/volume.png | Bin 0 -> 1759 bytes static/images/6075/cq.png | Bin 0 -> 1013 bytes static/images/6075/fg.png | Bin 0 -> 874 bytes static/images/6075/gz.png | Bin 0 -> 586 bytes static/images/6075/jn.png | Bin 0 -> 616 bytes static/images/6075/jsg.png | Bin 0 -> 997 bytes static/images/6075/sos.png | Bin 0 -> 745 bytes static/images/common/BJQ4877.png | Bin 0 -> 41350 bytes static/images/common/BJQ6331.png | Bin 0 -> 31009 bytes static/images/common/HBY100J.png | Bin 0 -> 29818 bytes static/images/common/HBY6155.png | Bin 0 -> 18597 bytes static/images/common/HBY650.png | Bin 0 -> 13493 bytes static/images/common/HBY670.png | Bin 0 -> 23782 bytes static/images/common/gou.png | Bin 0 -> 1031 bytes static/images/common/next.png | Bin 0 -> 2583 bytes static/images/common/pause.png | Bin 0 -> 424 bytes static/images/common/pauseActive.png | Bin 0 -> 433 bytes static/images/common/play.png | Bin 0 -> 867 bytes static/images/common/prev.png | Bin 0 -> 2548 bytes static/images/common/uploadCloud.png | Bin 0 -> 1188 bytes uni_modules/xe-upload/changelog.md | 7 + .../components/xe-upload/xe-upload.vue | 285 +++ uni_modules/xe-upload/package.json | 81 + uni_modules/xe-upload/readme.md | 78 + uni_modules/xe-upload/tools/apis.js | 177 ++ uni_modules/xe-upload/tools/tools.js | 180 ++ utils/BleHelper.js | 107 +- utils/BleReceive.js | 21 +- 41 files changed, 5293 insertions(+), 119 deletions(-) create mode 100644 pages/6075/BJQ6075.vue create mode 100644 pages/common/audioManager/AudioList.vue create mode 100644 pages/common/audioManager/Recording.vue create mode 100644 static/images/100/light.png create mode 100644 static/images/100/lightActive.png create mode 100644 static/images/100/record.png create mode 100644 static/images/100/txtToAudio.png create mode 100644 static/images/100/upload.png create mode 100644 static/images/100/volume.png create mode 100644 static/images/6075/cq.png create mode 100644 static/images/6075/fg.png create mode 100644 static/images/6075/gz.png create mode 100644 static/images/6075/jn.png create mode 100644 static/images/6075/jsg.png create mode 100644 static/images/6075/sos.png create mode 100644 static/images/common/BJQ4877.png create mode 100644 static/images/common/BJQ6331.png create mode 100644 static/images/common/HBY100J.png create mode 100644 static/images/common/HBY6155.png create mode 100644 static/images/common/HBY650.png create mode 100644 static/images/common/HBY670.png create mode 100644 static/images/common/gou.png create mode 100644 static/images/common/next.png create mode 100644 static/images/common/pause.png create mode 100644 static/images/common/pauseActive.png create mode 100644 static/images/common/play.png create mode 100644 static/images/common/prev.png create mode 100644 static/images/common/uploadCloud.png create mode 100644 uni_modules/xe-upload/changelog.md create mode 100644 uni_modules/xe-upload/components/xe-upload/xe-upload.vue create mode 100644 uni_modules/xe-upload/package.json create mode 100644 uni_modules/xe-upload/readme.md create mode 100644 uni_modules/xe-upload/tools/apis.js create mode 100644 uni_modules/xe-upload/tools/tools.js diff --git a/components/TextToHex/textToDotMatrixFor7305.vue b/components/TextToHex/textToDotMatrixFor7305.vue index 7160bf5..307b03b 100644 --- a/components/TextToHex/textToDotMatrixFor7305.vue +++ b/components/TextToHex/textToDotMatrixFor7305.vue @@ -1,6 +1,6 @@ @@ -27,6 +27,16 @@ color: { type: String, default: "#000000" + }, + // 二值化阈值(0~1),越大越细;默认偏清晰 + threshold: { + type: Number, + default: 0.45 + }, + // 是否启用轻度笔画修复(3x3 膨胀);默认关闭以避免变粗 + sharpen: { + type: Boolean, + default: false } }, data() { @@ -48,6 +58,16 @@ this.ctx = uni.createCanvasContext('reusableCanvas', this); }, methods: { + /** + * 外部可调用:复位画布为纯背景并立即提交 + */ + async resetCanvas() { + if (!this.ctx) return; + this.clearCanvas(); + await new Promise((resolve) => { + this.ctx.draw(true, () => setTimeout(resolve, 30)); + }); + }, /** * 估算单行文本所需的Canvas宽度 */ @@ -59,6 +79,8 @@ * 清除Canvas内容 */ clearCanvas() { + // 先清除,再用背景色填充,确保无残留 + this.ctx.clearRect(0, 0, this.currentCanvasWidth, this.currentCanvasHeight); this.ctx.setFillStyle(this.bgColor); this.ctx.fillRect(0, 0, this.currentCanvasWidth, this.currentCanvasHeight); }, @@ -67,6 +89,11 @@ * 复用单个Canvas处理所有文本行 */ async drawAndGetPixels() { + // 发送前:确保画布处于干净背景态 + await this.resetCanvas(); + // 超采样比例(提高分辨率再降采样,减少模糊) + const SCALE = 3; + const PADDING_X = 1 * SCALE; // 左侧预留像素,避免首字裁剪 let binaryToHex = (binaryArray) => { if (!Array.isArray(binaryArray) || binaryArray.length !== 8) { throw new Error("输入必须是包含8个元素的二进制数组"); @@ -90,25 +117,107 @@ return hexString; } - let convertCharToMatrix = (imageData, item) => { + let convertCharToMatrix = (drawResult, item) => { + // 设备端使用13x13点阵渲染(保持输出13列×13行),但只在内部采样12×12,保留右侧与底部1像素缓冲 const charWidth = 13; - const charHeight = 12; - const pixels = []; - for (let i = 0; i < imageData.length; i += 4) { - const R = imageData[i]; - pixels.push(R < 128 ? 1 : 0); + const charHeight = 13; + const effectiveWidth = 12; // 仅采样前12列 + const effectiveHeight = 12; // 仅采样前12行 + const { pixelData, width, height } = drawResult; + // 将高分辨率像素降采样为13x13的布尔矩阵 + const target = new Array(charWidth * charHeight).fill(0); + const threshold = Math.max(0.2, Math.min(0.8, this.threshold || 0.45)); + + // 确保采样区域严格对齐,从PADDING_X开始,只采样12列(右侧预留1px),12行(底部预留1px) + // 字符实际绘制区域:从PADDING_X开始,宽度为12*SCALE + const charStartX = PADDING_X; + const charEndX = PADDING_X + effectiveWidth * SCALE; // 仅采样到第12列 + + for (let y = 0; y < charHeight; y++) { + for (let x = 0; x < charWidth; x++) { + // 超出有效采样区域(第13列或第13行)直接置0,作为缓冲 + if (x >= effectiveWidth || y >= effectiveHeight) { + target[y * charWidth + x] = 0; + continue; + } + let onCount = 0; + let total = 0; + // 采样区域:字符从PADDING_X开始绘制,每列宽度为SCALE + const startX = PADDING_X + x * SCALE; + const startY = y * SCALE; + + for (let sy = 0; sy < SCALE; sy++) { + for (let sx = 0; sx < SCALE; sx++) { + const px = startX + sx; + const py = startY + sy; + + // 边界检查:确保不超出Canvas边界,且不超出字符实际绘制区域 + if (px < 0 || py < 0 || px >= width || py >= height) { + // 边界外视为背景(黑色) + continue; + } + // 额外检查:确保不采样到字符右侧的残留区域 + if (px >= charEndX) { + // 超出字符区域,视为背景 + continue; + } + + const idx = (py * width + px) * 4; + const R = pixelData[idx]; + const G = pixelData[idx + 1]; + const B = pixelData[idx + 2]; + const A = pixelData[idx + 3] || 255; + // 计算亮度 + const luminance = 0.299 * R + 0.587 * G + 0.114 * B; + const alpha = A / 255; + // 背景是黑色,文字是白色,所以判断亮色(>=128)为文字点 + if (luminance >= 128 && alpha > 0.5) onCount++; + total++; + } + } + // 当亮色占比超过阈值时判为1(至少需要采样到一些像素) + target[y * charWidth + x] = (total > 0 && onCount / total >= threshold) ? 1 : 0; + } } + // 轻度笔画修复:可选 3x3 膨胀(启用时阈值更严格,避免变粗) + if (this.sharpen) { + const dilated = target.slice(); + for (let y = 0; y < effectiveHeight; y++) { + for (let x = 0; x < effectiveWidth; x++) { + const idx = y * charWidth + x; + if (target[idx] === 1) continue; + let neighbors = 0; + for (let dy = -1; dy <= 1; dy++) { + for (let dx = -1; dx <= 1; dx++) { + if (dx === 0 && dy === 0) continue; + const nx = x + dx; + const ny = y + dy; + if (nx < 0 || ny < 0 || nx >= effectiveWidth || ny >= effectiveHeight) continue; + if (target[ny * charWidth + nx] === 1) neighbors++; + } + } + // 使用更严格的邻居门限,避免整体变粗 + if (neighbors >= 5) { + dilated[idx] = 1; + } + } + } + for (let i = 0; i < target.length; i++) target[i] = dilated[i]; + } + const lowBytes = new Array(charWidth).fill(0); const highBytes = new Array(charWidth).fill(0); - + // 按列打包,每列13行分成上下两字节:低字节0-7行(8行),高字节8-12行(5行) for (let col = 0; col < charWidth; col++) { for (let row = 0; row < charHeight; row++) { - const pixel = pixels[row * charWidth + col]; + const pixel = target[row * charWidth + col]; if (pixel === 1) { if (row < 8) { + // 低字节:0-7行,从低位到高位 lowBytes[col] |= (1 << row); } else { + // 高字节:8-12行,从低位到高位(使用5位,剩余3位未使用) highBytes[col] |= (1 << (row - 8)); } } @@ -121,34 +230,32 @@ let result = {}; let ctx = this.ctx; - // 1. 动态调整Canvas尺寸 - this.currentCanvasWidth = 13; - this.currentCanvasHeight = 12; + // 1. 动态调整Canvas尺寸(高分辨率) + // 设备端使用13x13点阵渲染 + this.currentCanvasWidth = 13 * SCALE + PADDING_X; + this.currentCanvasHeight = 13 * SCALE; // 2. 清空Canvas(绘制背景) this.clearCanvas(); - // 3. 设置文字样式 + // 3. 设置文字样式(整数像素对齐,顶部基线,避免首字裁剪) ctx.setFillStyle(this.color); - ctx.setTextBaseline('middle'); - // ctx.setTextAlign('center') - ctx.setFontSize(this.fontSize); - ctx.font = `${this.fontSize}px "PingFangBold", "PingFang SC", Arial, sans-serif`; + ctx.setTextBaseline('top'); + ctx.setTextAlign('left'); + const fs = Math.max(1, Math.round(this.fontSize)) * SCALE; + ctx.setFontSize(fs); + ctx.font = `${fs}px "PingFangBold", "PingFang SC", Arial, sans-serif`; - // 4. 绘制当前行文本 - let currentX = 0; - let currentY = this.fontSize / 2 + 1; - for (let j = 0; j < textLine.length; j++) { - let char = textLine[j]; - ctx.fillText(char, currentX, currentY); - // 按实际字符宽度计算间距 - let charWidth = ctx.measureText(char).width; - currentX += charWidth; - } + // 4. 绘制单个字符(每个字符独立绘制在固定位置) + // 确保字符始终从PADDING_X开始绘制,Y坐标为0,保证采样一致性 + const charX = PADDING_X; + const charY = 0; + ctx.fillText(textLine, charX, charY); // 5. 异步绘制并获取像素数据(串行处理避免冲突) - await new Promise((resolve, reject) => { - ctx.draw(false, () => { + const grabPixels = () => new Promise((resolve, reject) => { + // 立即绘制并给一点缓冲时间,避免取像素过早 + ctx.draw(true, () => { setTimeout(() => { uni.canvasGetImageData({ canvasId: 'reusableCanvas', @@ -165,14 +272,22 @@ }; resolve(); }, - fail: err => { - // console.error(`处理第${i+1}行失败:`, err); - reject(err) - } + fail: err => reject(err) }); - }, 100); + }, 70); }); }); + + await grabPixels(); + // 一次性校验:若像素全黑或明显异常,重绘重取一次 + let nonZero = false; + for (let i = 0; i < result.pixelData.length; i += 4) { + if (result.pixelData[i] || result.pixelData[i+1] || result.pixelData[i+2]) { nonZero = true; break; } + } + if (!nonZero) { + await new Promise(r => setTimeout(r, 50)); + await grabPixels(); + } return result; } @@ -184,13 +299,23 @@ let item = this.validTxts[i]; // console.log("item=", item); for (var j = 0; j < item.length; j++) { - let result = await drawTxt(item[j]); - linePixls.push(convertCharToMatrix(result.pixelData, item)); + let char = item[j]; + let result = await drawTxt(char); + let matrix = convertCharToMatrix(result, item); + // 调试:打印每个字符的点阵数据 + console.log(`[点阵生成] 字符"${char}" 点阵数据:`, matrix.map(b => '0x' + b.toString(16).padStart(2, '0')).join(' ')); + linePixls.push(matrix); + // 在字符间增加轻微延时,避免相邻提取竞争(尤其是末字符) + await new Promise(r => setTimeout(r, 20)); } // console.log("hexs=", linePixls.join(",")); arr.push(linePixls); + // 每行结束再等一会,提高末字符稳定性 + await new Promise(r => setTimeout(r, 40)); } + // 发送后:再次清空画布,避免残留影响下一次 + await this.resetCanvas(); return arr; } } @@ -202,6 +327,5 @@ position: fixed; left: -9999px; top: -9999px; - visibility: hidden; } \ No newline at end of file diff --git a/pages.json b/pages.json index c89a492..4498b63 100644 --- a/pages.json +++ b/pages.json @@ -164,6 +164,12 @@ "fullscreen": true } }, + { + "path": "pages/6075/BJQ6075", + "style": { + "navigationStyle": "custom" + } + }, { "path": "pages/common/map/index", "style": { @@ -315,6 +321,19 @@ { "navigationBarTitleText" : "录制语音" } + }, + + { + "path": "pages/4877/BJQ4877", + "style": { + "navigationBarTitleText": "BJQ 4877" + } + }, + { + "path": "pages/100/HBY100", + "style": { + "navigationBarTitleText": "HBY 100" + } } diff --git a/pages/100/HBY100.vue b/pages/100/HBY100.vue index 334b9ca..4dcc609 100644 --- a/pages/100/HBY100.vue +++ b/pages/100/HBY100.vue @@ -198,41 +198,39 @@ - - - - 产品信息 - - - - 产品参数 - - - - 操作说明 - - - - 操作视频 + + 产品信息 + + + + 产品参数 + + + + 操作说明 + + + + 操作视频 + - - - + + - - + - - + + + + + \ No newline at end of file diff --git a/pages/6170/deviceControl/index.vue b/pages/6170/deviceControl/index.vue index a5e8c61..4e84187 100644 --- a/pages/6170/deviceControl/index.vue +++ b/pages/6170/deviceControl/index.vue @@ -297,11 +297,8 @@ export default { data() { return { - lastBrightnessTime: 0, isCardSliding: false, cardRect: null, - touchStartX: 0, - touchStartY: 0, pageLoading: true, navBarHeight: 70 + uni.getSystemInfoSync().statusBarHeight, navTitle: "", @@ -338,7 +335,6 @@ isLaserOn: false, isSending: false, isProcessing: false, - isLoading: false, // 主加载状态 isPolling: false // 轮询状态 } }, @@ -557,7 +553,6 @@ }; lightModeSettings(data).then((res) => { if (res.code == 200) { - // 只有确认成功才更新实际模式,选中模式 this.currentMainMode = this.pendingMainMode; this.selectedItemIndex = selectedItem; uni.showToast({ @@ -566,15 +561,11 @@ }) uni.hideLoading(); this.lightModeA = false; - //this.isProcessing = false - //loadingShown = false } else { uni.showToast({ title: res.msg, icon: 'none' }) - //this.isProcessing = false - //loadingShown = false uni.hideLoading(); } }) diff --git a/pages/common/audioManager/AudioList.vue b/pages/common/audioManager/AudioList.vue new file mode 100644 index 0000000..21f05f1 --- /dev/null +++ b/pages/common/audioManager/AudioList.vue @@ -0,0 +1,1237 @@ + + + + + \ No newline at end of file diff --git a/pages/common/audioManager/Recording.vue b/pages/common/audioManager/Recording.vue new file mode 100644 index 0000000..5a283c6 --- /dev/null +++ b/pages/common/audioManager/Recording.vue @@ -0,0 +1,1557 @@ +