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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{index+1}}:{{ item.name }}
+
+ {{item.createTime}}
+ {{item.time}}秒
+
+
+
+
+
+ {{item.isApply ?'使用中':'使用'}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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 @@
+
+
+
+
+
+
+ 导入文档
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{getFormatSeconds(cEdit.currTime)}}
+
+
+ {{getFormatSeconds(cEdit.time)}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 保存
+ 取消
+
+
+
+
+
+
+ 点击下方红色录制按钮开始录音,吐字清晰,语句流畅
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pages/common/login/index.vue b/pages/common/login/index.vue
index 1bafbc8..aa2ee4b 100644
--- a/pages/common/login/index.vue
+++ b/pages/common/login/index.vue
@@ -252,7 +252,7 @@
uni.showToast({
title: res.msg || '服务器异常,请稍后重试',
icon: 'none'
- })
+ })
}
} catch (error) {
console.log('捕获错误:', error);
diff --git a/static/images/100/light.png b/static/images/100/light.png
new file mode 100644
index 0000000..b4e4fad
Binary files /dev/null and b/static/images/100/light.png differ
diff --git a/static/images/100/lightActive.png b/static/images/100/lightActive.png
new file mode 100644
index 0000000..016b55b
Binary files /dev/null and b/static/images/100/lightActive.png differ
diff --git a/static/images/100/record.png b/static/images/100/record.png
new file mode 100644
index 0000000..b3ec5d5
Binary files /dev/null and b/static/images/100/record.png differ
diff --git a/static/images/100/txtToAudio.png b/static/images/100/txtToAudio.png
new file mode 100644
index 0000000..10e7b5d
Binary files /dev/null and b/static/images/100/txtToAudio.png differ
diff --git a/static/images/100/upload.png b/static/images/100/upload.png
new file mode 100644
index 0000000..62aa192
Binary files /dev/null and b/static/images/100/upload.png differ
diff --git a/static/images/100/volume.png b/static/images/100/volume.png
new file mode 100644
index 0000000..7d51a10
Binary files /dev/null and b/static/images/100/volume.png differ
diff --git a/static/images/6075/cq.png b/static/images/6075/cq.png
new file mode 100644
index 0000000..53e3da8
Binary files /dev/null and b/static/images/6075/cq.png differ
diff --git a/static/images/6075/fg.png b/static/images/6075/fg.png
new file mode 100644
index 0000000..d98249b
Binary files /dev/null and b/static/images/6075/fg.png differ
diff --git a/static/images/6075/gz.png b/static/images/6075/gz.png
new file mode 100644
index 0000000..7dcef7f
Binary files /dev/null and b/static/images/6075/gz.png differ
diff --git a/static/images/6075/jn.png b/static/images/6075/jn.png
new file mode 100644
index 0000000..4122b8e
Binary files /dev/null and b/static/images/6075/jn.png differ
diff --git a/static/images/6075/jsg.png b/static/images/6075/jsg.png
new file mode 100644
index 0000000..f73ba59
Binary files /dev/null and b/static/images/6075/jsg.png differ
diff --git a/static/images/6075/sos.png b/static/images/6075/sos.png
new file mode 100644
index 0000000..90a6745
Binary files /dev/null and b/static/images/6075/sos.png differ
diff --git a/static/images/common/BJQ4877.png b/static/images/common/BJQ4877.png
new file mode 100644
index 0000000..dff8be2
Binary files /dev/null and b/static/images/common/BJQ4877.png differ
diff --git a/static/images/common/BJQ6331.png b/static/images/common/BJQ6331.png
new file mode 100644
index 0000000..29d8b22
Binary files /dev/null and b/static/images/common/BJQ6331.png differ
diff --git a/static/images/common/HBY100J.png b/static/images/common/HBY100J.png
new file mode 100644
index 0000000..f763f4b
Binary files /dev/null and b/static/images/common/HBY100J.png differ
diff --git a/static/images/common/HBY6155.png b/static/images/common/HBY6155.png
new file mode 100644
index 0000000..2835c0c
Binary files /dev/null and b/static/images/common/HBY6155.png differ
diff --git a/static/images/common/HBY650.png b/static/images/common/HBY650.png
new file mode 100644
index 0000000..4f866fd
Binary files /dev/null and b/static/images/common/HBY650.png differ
diff --git a/static/images/common/HBY670.png b/static/images/common/HBY670.png
new file mode 100644
index 0000000..8f83e53
Binary files /dev/null and b/static/images/common/HBY670.png differ
diff --git a/static/images/common/gou.png b/static/images/common/gou.png
new file mode 100644
index 0000000..6486254
Binary files /dev/null and b/static/images/common/gou.png differ
diff --git a/static/images/common/next.png b/static/images/common/next.png
new file mode 100644
index 0000000..e4fd98b
Binary files /dev/null and b/static/images/common/next.png differ
diff --git a/static/images/common/pause.png b/static/images/common/pause.png
new file mode 100644
index 0000000..0b24061
Binary files /dev/null and b/static/images/common/pause.png differ
diff --git a/static/images/common/pauseActive.png b/static/images/common/pauseActive.png
new file mode 100644
index 0000000..cd9ecd4
Binary files /dev/null and b/static/images/common/pauseActive.png differ
diff --git a/static/images/common/play.png b/static/images/common/play.png
new file mode 100644
index 0000000..2864745
Binary files /dev/null and b/static/images/common/play.png differ
diff --git a/static/images/common/prev.png b/static/images/common/prev.png
new file mode 100644
index 0000000..0f8454e
Binary files /dev/null and b/static/images/common/prev.png differ
diff --git a/static/images/common/uploadCloud.png b/static/images/common/uploadCloud.png
new file mode 100644
index 0000000..f5a06a3
Binary files /dev/null and b/static/images/common/uploadCloud.png differ
diff --git a/uni_modules/xe-upload/changelog.md b/uni_modules/xe-upload/changelog.md
new file mode 100644
index 0000000..386ab44
--- /dev/null
+++ b/uni_modules/xe-upload/changelog.md
@@ -0,0 +1,7 @@
+## 1.0.2(2023-11-01)
+更换App端转换本地链接的方法;
+添加App端文件拓展名过滤;
+## 1.0.1(2023-09-04)
+优化部分逻辑
+## 1.0.0(2023-09-03)
+支持图片、视频选择上传;H5、微信小程序,App支持文件选择上传
diff --git a/uni_modules/xe-upload/components/xe-upload/xe-upload.vue b/uni_modules/xe-upload/components/xe-upload/xe-upload.vue
new file mode 100644
index 0000000..5480de4
--- /dev/null
+++ b/uni_modules/xe-upload/components/xe-upload/xe-upload.vue
@@ -0,0 +1,285 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/uni_modules/xe-upload/package.json b/uni_modules/xe-upload/package.json
new file mode 100644
index 0000000..46f27a7
--- /dev/null
+++ b/uni_modules/xe-upload/package.json
@@ -0,0 +1,81 @@
+{
+ "id": "xe-upload",
+ "displayName": "文件选择、文件上传组件(图片,视频,文件等)",
+ "version": "1.0.2",
+ "description": "H5、微信小程序、App端支持图片,视频,文件选择上传;其他端暂不支持文件选择上传",
+ "keywords": [
+ "App、H5、微信小程序、图片,视频,文件上传"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "无",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "u"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "u"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "u",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "y",
+ "快手": "y",
+ "飞书": "y",
+ "京东": "y"
+ },
+ "快应用": {
+ "华为": "y",
+ "联盟": "y"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/xe-upload/readme.md b/uni_modules/xe-upload/readme.md
new file mode 100644
index 0000000..7f63c5a
--- /dev/null
+++ b/uni_modules/xe-upload/readme.md
@@ -0,0 +1,78 @@
+# xe-upload
+
+## 说明
+
+不占用页面位置的上传组件;
+
+H5、APP、微信小程序中可上传图片,视频和文件;其他端暂时只能上传图片和视频
+
+> 上传图片通过[chooseMedia](https://uniapp.dcloud.net.cn/api/media/video.html#choosemedia)及[chooseImage](https://uniapp.dcloud.net.cn/api/media/image.html#chooseimage)实现
+
+> 上传视频通过[chooseMedia](https://uniapp.dcloud.net.cn/api/media/video.html#choosemedia)及[chooseVideo](https://uniapp.dcloud.net.cn/api/media/video.html#choosevideo)实现
+
+> H5端上传文件通过[chooseFile](https://uniapp.dcloud.net.cn/api/media/file.html#wx-choosemessagefile)实现
+
+> APP上传文件通过[renderjs](https://uniapp.dcloud.net.cn/tutorial/renderjs.html#renderjs)实现
+
+> 微信小程序上传文件通过[chooseMessageFile](https://developers.weixin.qq.com/miniprogram/dev/api/media/image/wx.chooseMessageFile.html)实现
+
+
+## 使用
+
+Attributes
+
+| 参数 | 说明 | 类型 | 默认值 |
+| ----------- | ----------- | ----------- | ----------- |
+| options | 请求配置(参数与uni.uploadFile的参数一致) | object | { name: 'file' } |
+
+Events
+
+| 事件名 | 说明 | 参数 |
+| ----------- | ----------- | ----------- |
+| callback | 接收数据 | { type, data } |
+
+callback type
+
+| 参数 | 说明 |
+| ----------- | ----------- |
+| warning | 提示信息(下文称warning回调) |
+| success | 上传成功(下文称success回调) |
+| choose | 选择文件(下文称choose回调) |
+
+callback data
+
+```
+'callback.type === success' : [
+ {
+ "size": 176579, // 选择的文件的大小
+ "name": "Kafka.pdf", // 选择的文件的名称(小程序端可能会没有)
+ "type": "application/pdf",
+ "tempFilePath": "blob:http://192.168.137.1:8080/2585769b-3195-4f3d-b9f8-d9e99f55deec", // 临时路路径
+ "fileType": "file", // 文件类型[image, video, file]
+ "response": {
+ "result": {
+ "fileName": "Kafka.pdf",
+ "filePath": `http://localhost:3000/upload/e51d814b649122fc64892d0bc6383d07.pdf`,
+ },
+ "success": true,
+ }, // 上传返回的信息
+ }
+]
+
+'callback.type === choose' : [
+ {
+ "size": 176579, // 选择的文件的大小
+ "name": "Kafka.pdf", // 选择的文件的名称(小程序端可能会没有)
+ "type": "application/pdf",
+ "tempFilePath": "blob:http://192.168.137.1:8080/4204e460-f185-4fc9-9f4d-1bc50ab06981", // 文件临时路径
+ "fileType": "file", // 文件类型[image, video, file]
+ }
+]
+```
+
+## 注意事项
+#### 1、options入参中如果url为空,则choose回调的data列表中只有选择文件能得到的信息和临时路径,临时路径可用于自定义上传方法(APP除外);传入url选择文件后会自动上传到服务器,此时choose回调不会触发,而是执行success回调,success回调的data列表会包括选择文件能得到的信息
+#### 2、APP端文件建议直接上传到服务器,拿到文件上传后的地址再进行其他操作(目前测试APP端file转换后的Blob Url无法用于uni.uploadFile,所以建议APP文件直接上传)
+#### 3、APP端文件暂时支持单个上传
+#### 4、当uni.chooseMedia可用时,会优先使用uni.chooseMedia
+#### 5、具体使用可下载示例项目运行查看完整示例
diff --git a/uni_modules/xe-upload/tools/apis.js b/uni_modules/xe-upload/tools/apis.js
new file mode 100644
index 0000000..5fc1371
--- /dev/null
+++ b/uni_modules/xe-upload/tools/apis.js
@@ -0,0 +1,177 @@
+// eslint-disable
+import { awaitWrap } from './tools';
+/**
+ * 从本地相册选择图片或使用相机拍照
+ * @param {object} config 参数详情 => https://uniapp.dcloud.net.cn/api/media/image.html#chooseimage
+ * @returns
+ */
+export const chooseImage = (config) => {
+ return awaitWrap(
+ new Promise((r, j) => {
+ uni.chooseImage({
+ ...config,
+ success: (res) => {
+ const tmpFiles = res?.tempFiles.map((e) => ({
+ tempFilePath: e.path,
+ tempFile: e,
+ size: e.size,
+ name: e.name,
+ type: e.type,
+ fileType: 'image',
+ }));
+ return r({ type: 'image', ...res, tempFiles: tmpFiles });
+ },
+ fail: (err) => j({ mode: 'chooseImage', data: err }),
+ });
+ })
+ );
+};
+
+/**
+ * 拍摄视频或从手机相册中选视频,返回视频的临时文件路径
+ * @param {object} config 参数详情 => https://uniapp.dcloud.net.cn/api/media/video.html#choosevideo
+ * @returns
+ */
+export const chooseVideo = (config) => {
+ return awaitWrap(
+ new Promise((r, j) => {
+ uni.chooseVideo({
+ ...config,
+ success: (res) => {
+ const tmpFiles = [{
+ ...res,
+ tempFilePath: res.tempFilePath,
+ tempFile: res.tempFile ?? {},
+ size: res.size,
+ name: res.name,
+ type: res.tempFile?.type,
+ fileType: 'video',
+ }];
+ return r({ type: 'video', tempFiles: tmpFiles });
+ },
+ fail: (err) => j({ mode: 'chooseVideo', data: err }),
+ });
+ })
+ );
+};
+
+/**
+ * 拍摄或从手机相册中选择图片或视频
+ * @param {object} config 参数详情 => https://uniapp.dcloud.net.cn/api/media/video.html#choosemedia
+ * @returns
+ */
+export const chooseMedia = (type, config) => {
+ if (!type) return console.error('chooseMedia type cannot be empty');
+ if (!uni.chooseMedia && type === 'image') return chooseImage(config);
+ if (!uni.chooseMedia && type === 'video') return chooseVideo(config);
+ return awaitWrap(
+ new Promise((r, j) => {
+ uni.chooseMedia({
+ ...config,
+ mediaType: [type],
+ success: (res) => r(res),
+ fail: (err) => j({ mode: 'chooseMedia', data: err }),
+ });
+ })
+ );
+};
+
+/**
+ * 从本地选择文件(h5)
+ * @param {object} config 参数详情 => https://uniapp.dcloud.net.cn/api/media/file.html#wx-choosemessagefile
+ * @returns
+ */
+export const chooseFile = (config) => {
+ return awaitWrap(
+ new Promise((r, j) => {
+ uni.chooseFile({
+ ...config,
+ success: (res) => {
+ const tmpFiles = res?.tempFiles.map((e) => {
+ let tmpType = 'file';
+ if (e.type.includes('image')) {
+ tmpType = 'image';
+ }
+ if (e.type.includes('video')) {
+ tmpType = 'video';
+ }
+ return {
+ tempFilePath: e.path,
+ tempFile: e,
+ size: e.size,
+ name: e.name,
+ type: e.type,
+ fileType: tmpType,
+ };
+ });
+ return r({ type: 'file', ...res, tempFiles: tmpFiles });
+ },
+ fail: (err) => j({ mode: 'chooseFile', data: err }),
+ });
+ })
+ );
+};
+
+/**
+ * 从本地选择文件(微信小程序)
+ * @param {object} config 参数详情 => https://developers.weixin.qq.com/miniprogram/dev/api/media/image/wx.chooseMessageFile.html
+ * @returns
+ */
+export const chooseMessageFile = (config) => {
+ return awaitWrap(
+ new Promise((r, j) => {
+ wx.chooseMessageFile({
+ ...config,
+ success: (res) => {
+ const tmpFiles = res?.tempFiles.map((e) => ({
+ ...e,
+ tempFilePath: e.path,
+ fileType: e.type ?? 'file',
+ }));
+ return r({ type: 'file', ...res, tempFiles: tmpFiles });
+ },
+ fail: (err) => j({ mode: 'chooseMessageFile', data: err }),
+ });
+ })
+ );
+};
+
+/**
+ * 上传
+ * @param {object} config 参数详情 => https://uniapp.dcloud.net.cn/api/request/network-file.html#uploadfile
+ * @param {object} exts 选择的文件的数据
+ * @returns {object} exts + response
+ */
+export const uploadFile = (config, exts = {}) => {
+ return new Promise((r, j) => {
+ uni.uploadFile({
+ ...config,
+ success: (res) => r({ ...exts, response: JSON.parse(res.data) }),
+ fail: (err) => j({ mode: 'uploadFile', data: err }),
+ });
+ });
+};
+
+export const appUploadFile = (config, exts = {}, onprogress) => {
+ const { url, header, formData } = config;
+ return new Promise((r, j) => {
+ const xhr = new XMLHttpRequest();
+ xhr.open('POST', url, true);
+ for (let key in header) {
+ xhr.setRequestHeader(key, header[key]);
+ }
+ if (onprogress) {
+ xhr.upload.onprogress = onprogress;
+ }
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState === 4) {
+ if (xhr.status === 200) {
+ r({ ...exts, response: JSON.parse(xhr.responseText) });
+ } else {
+ j({ mode: 'uploadFile', data: { data: xhr.responseText, errMsg: 'uploadFile fail.' } });
+ }
+ }
+ }
+ xhr.send(formData);
+ });
+};
diff --git a/uni_modules/xe-upload/tools/tools.js b/uni_modules/xe-upload/tools/tools.js
new file mode 100644
index 0000000..27d2fa6
--- /dev/null
+++ b/uni_modules/xe-upload/tools/tools.js
@@ -0,0 +1,180 @@
+// eslint-disable
+export const isObject = (obj) => {
+ return obj
+ ? Object.prototype.toString.call(obj) === "[object Object]"
+ : false;
+};
+export const isArray = (arr) => {
+ return arr ? Array.isArray(arr) : false;
+};
+/**
+ * handle async await
+ * @param {*} promise promise
+ */
+export const awaitWrap = (promise) =>
+ promise.then((res) => [null, res]).catch((err) => [err, {}]);
+/**
+ * 深拷贝
+ * @param {*} source
+ */
+export const deepClone = (source) => {
+ if (!isObject(source) && !isArray(source)) return source;
+ const targetObj = isArray(source) ? [] : {}; // 判断复制的目标是数组还是对象
+ for (let keys in source) {
+ // 遍历目标
+ if (source.hasOwnProperty(keys)) {
+ if (source[keys] && typeof source[keys] === "object") {
+ // 如果值是对象,就递归一下
+ targetObj[keys] = isArray(source[keys]) ? [] : {};
+ targetObj[keys] = deepClone(source[keys]);
+ } else {
+ // 如果不是,就直接赋值
+ targetObj[keys] = source[keys];
+ }
+ }
+ }
+ return targetObj;
+};
+/**
+ * @description JS对象深度合并
+ * @param {object} target 需要拷贝的对象
+ * @param {object} source 拷贝的来源对象
+ * @returns {object|boolean} 深度合并后的对象或者false(入参有不是对象)
+ */
+export const deepMerge = (target = {}, source = {}) => {
+ target = deepClone(target);
+ if (typeof target !== "object" || typeof source !== "object") return false;
+ for (const prop in source) {
+ if (!source.hasOwnProperty(prop)) continue;
+ if (prop in target) {
+ if (typeof target[prop] !== "object") {
+ target[prop] = source[prop];
+ } else if (typeof source[prop] !== "object") {
+ target[prop] = source[prop];
+ } else if (target[prop].concat && source[prop].concat) {
+ target[prop] = target[prop].concat(source[prop]);
+ } else {
+ target[prop] = deepMerge(target[prop], source[prop]);
+ }
+ } else {
+ target[prop] = source[prop];
+ }
+ }
+ return target;
+};
+/**
+ * 将File对象转为 Blob Url
+ * @param {File} File对象
+ * @returns Blob Url
+ */
+export const fileToBlob = (file) => {
+ if (!file) return;
+ const fileType = file.type;
+ const blob = new Blob([file], { type: fileType || 'application/*' });
+ const blobUrl = window.URL.createObjectURL(blob);
+ return blobUrl;
+};
+/**
+ * 将File对象转为 base64
+ * @param {File} File对象
+ * @returns base64
+ */
+export const fileToBase64 = (file) => {
+ if (!file) return;
+ return new Promise((r, j) => {
+ const reader = new FileReader();
+ reader.onloadend = () => {
+ const base64String = reader.result;
+ r(base64String);
+ };
+ reader.onerror = () => {
+ j({ mode: 'fileToBase64', data: { errMsg: 'File to base64 fail.' } });
+ };
+ reader.readAsDataURL(file);
+ });
+};
+/**
+ * base64转临时路径(改自https://github.com/zhetengbiji/image-tools/blob/master/index.js)
+ * @param base64
+ * @returns
+ */
+function dataUrlToBase64(str) {
+ var array = str.split(',');
+ return array[array.length - 1];
+};
+function biggerThan(v1, v2) {
+ var v1Array = v1.split('.');
+ var v2Array = v2.split('.');
+ var update = false;
+ for (var index = 0; index < v2Array.length; index++) {
+ var diff = v1Array[index] - v2Array[index];
+ if (diff !== 0) {
+ update = diff > 0;
+ break;
+ }
+ }
+ return update;
+};
+var index = 0;
+function getNewFileId() {
+ return Date.now() + String(index++);
+};
+export const base64ToPath = (base64, name = '') => {
+ return new Promise((r, j) => {
+ if (typeof plus !== 'object') {
+ return j(new Error('not support'));
+ }
+ var fileName = '';
+ if (name) {
+ const names = name.split('.');
+ const extName = names.splice(-1);
+ fileName = `${names.join('.')}-${getNewFileId()}.${extName}`;
+ } else {
+ const names = base64.split(',')[0].match(/data\:\S+\/(\S+);/);
+ if (!names) {
+ j(new Error('base64 error'));
+ }
+ const extName = names[1];
+ fileName = `${getNewFileId()}.${extName}`;
+ }
+ var basePath = '_doc';
+ var dirPath = 'uniapp_temp';
+ var filePath = `${basePath}/${dirPath}/${fileName}`;
+ if (!biggerThan(plus.os.name === 'Android' ? '1.9.9.80627' : '1.9.9.80472', plus.runtime.innerVersion)) {
+ plus.io.resolveLocalFileSystemURL(basePath, function (entry) {
+ entry.getDirectory(dirPath, {
+ create: true,
+ exclusive: false,
+ }, function (entry) {
+ entry.getFile(fileName, {
+ create: true,
+ exclusive: false,
+ }, function (entry) {
+ entry.createWriter(function (writer) {
+ writer.onwrite = function () {
+ r(filePath);
+ }
+ writer.onerror = j;
+ writer.seek(0);
+ writer.writeAsBinary(dataUrlToBase64(base64));
+ }, j)
+ }, j)
+ }, j)
+ }, j)
+ return;
+ }
+ var bitmap = new plus.nativeObj.Bitmap(fileName);
+ bitmap.loadBase64Data(base64, function () {
+ bitmap.save(filePath, {}, function () {
+ bitmap.clear();
+ r(filePath);
+ }, function (error) {
+ bitmap.clear();
+ j(error);
+ });
+ }, function (error) {
+ bitmap.clear();
+ j(error);
+ });
+ });
+};
diff --git a/utils/BleHelper.js b/utils/BleHelper.js
index dc2619c..6c77356 100644
--- a/utils/BleHelper.js
+++ b/utils/BleHelper.js
@@ -754,30 +754,35 @@ class BleHelper {
}
try {
- let receivJson = JSON.parse(str);
- let key = "sta_address"; //HBY100以此方式上传mac地址
- if (key in receivJson) {
- this.data.LinkedList.find((v) => {
- if (v.deviceId == receive
- .deviceId) {
- let macStr = receivJson[
- key];
- if (macStr.includes(':')) {
- v.macAddress = macStr;
- } else {
- v.macAddress = macStr
- .replace(/(.{2})/g,
- '$1:').slice(0,
- -1)
+ let trimmedStr = str.trim();
+ if (trimmedStr && (trimmedStr.startsWith('{') || trimmedStr.startsWith('['))) {
+ let receivJson = JSON.parse(str);
+ let key = "sta_address"; //HBY100以此方式上传mac地址
+ if (key in receivJson) {
+ this.data.LinkedList.find((v) => {
+ if (v.deviceId == receive
+ .deviceId) {
+ let macStr = receivJson[
+ key];
+ if (macStr.includes(':')) {
+ v.macAddress = macStr;
+ } else {
+ v.macAddress = macStr
+ .replace(/(.{2})/g,
+ '$1:').slice(0,
+ -1)
+ }
+
+ isUpdate = true;
}
+ });
- isUpdate = true;
- }
- });
-
+ }
}
} catch (convertException) {
- console.error("文本无法转json", convertException)
+ if (str && (str.trim().startsWith('{') || str.trim().startsWith('['))) {
+ console.error("JSON解析失败(可能是格式错误的数据)", convertException);
+ }
}
if (isUpdate) {
@@ -1033,7 +1038,7 @@ class BleHelper {
//订阅消息
subScribe(deviceId, state) {
- // console.log("开始订阅消息", state);
+ console.log("开始订阅消息", deviceId, state);
return new Promise((resolve, reject) => {
setTimeout(() => {
@@ -1154,13 +1159,12 @@ class BleHelper {
results.forEach((result, index) => {
if (result.status === "fulfilled") {
- // console.log(`操作${index + 1}成功:`, result.value);
+ console.log(`订阅消息操作${index + 1}成功:`, result.value);
} else {
- // console.log(`操作${index + 1}失败:`, result.reason
- // .message);
+ console.error(`订阅消息操作${index + 1}失败:`, result.reason);
}
});
- // console.log("订阅消息成功");
+ console.log("订阅消息完成,deviceId:", deviceId);
resolve();
}).catch((ex) => {
console.error("异常,ex=", ex);
@@ -1553,9 +1557,19 @@ class BleHelper {
}
} else { //已连接过,直接订阅消息
// console.log("11111111");
- if (fIndex > -1 && f && !f.notifyState) {
-
- this.subScribe(deviceId, true);
+ if (fIndex > -1 && f) {
+ if (!f.notifyState) {
+ console.log("设备已连接但未订阅,开始订阅消息");
+ return this.subScribe(deviceId, true).then(() => {
+ console.log("订阅消息完成");
+ return Promise.resolve(true);
+ }).catch(err => {
+ console.error("订阅消息失败:", err);
+ return Promise.resolve(true);
+ });
+ } else {
+ console.log("设备已连接且已订阅消息");
+ }
}
return Promise.resolve(true); //已连接过的设备无需获取服务
}
@@ -1703,8 +1717,32 @@ class BleHelper {
if (this.data.platform == 'web') {
return Promise.resolve("h5平台默认成功");
}
- // console.log("deviceid=" + deviceid + ",writeServiceId=" + writeServiceId + ",wirteCharactId=" +
- // wirteCharactId + ",timeout=" + ms)
+
+ // 打印发送的蓝牙指令
+ let bufferHex = '';
+ if (buffer) {
+ let bytes = [];
+ // 处理不同类型的buffer(ArrayBuffer、Uint8Array等)
+ if (buffer instanceof ArrayBuffer) {
+ let dataView = new DataView(buffer);
+ for (let i = 0; i < buffer.byteLength; i++) {
+ bytes.push(dataView.getUint8(i));
+ }
+ } else if (buffer.byteLength !== undefined) {
+ // 如果是 Uint8Array 或其他类型
+ for (let i = 0; i < buffer.byteLength; i++) {
+ bytes.push(buffer[i] || 0);
+ }
+ } else if (Array.isArray(buffer)) {
+ bytes = buffer;
+ }
+ if (bytes.length > 0) {
+ bufferHex = bytes.map(b => '0x' + b.toString(16).padStart(2, '0').toUpperCase()).join(' ');
+ }
+ }
+ console.log("准备发送蓝牙指令 - deviceId:", deviceid, "writeServiceId:", writeServiceId, "writeCharactId:", wirteCharactId);
+ console.log("发送数据(Hex):", bufferHex || "(空数据)");
+ console.log("发送数据(原始buffer长度):", buffer ? (buffer.byteLength || buffer.length || 0) : 0);
if (ms === undefined) {
ms = 50;
}
@@ -1748,13 +1786,14 @@ class BleHelper {
serviceId: device.writeServiceId,
characteristicId: device.wirteCharactId,
value: buffer,
+ writeType: 'write',
success: () => {
- // console.log("发送数据成功");
+ console.log("✓ 蓝牙指令发送成功 - deviceId:", device.deviceId);
succ();
},
fail: (ex) => {
ex = this.getError(ex);
- console.error("发送数据失败", ex);
+ console.error("✗ 蓝牙指令发送失败 - deviceId:", device.deviceId, "错误:", ex);
err(ex);
}
@@ -1792,10 +1831,10 @@ class BleHelper {
}
if (c.Linked) {
- // console.log("蓝牙已连接,直接发送");
+ console.log("蓝牙已连接,直接发送数据");
return sendBuffer();
} else {
- // console.log("先连接蓝牙再发送");
+ console.log("蓝牙未连接,先连接蓝牙再发送数据");
return new Promise((resolve, reject) => {
let f = this.data.LinkedList.find((v) => {
return v.deviceId == deviceid;
diff --git a/utils/BleReceive.js b/utils/BleReceive.js
index c0c2fa6..6f558ac 100644
--- a/utils/BleReceive.js
+++ b/utils/BleReceive.js
@@ -44,25 +44,28 @@ class BleReceive {
if (f && f.macAddress && f.device && f.device.id) {
let handler = null;
let keys = Object.keys(this.HandlerMap);
+ let devKey = f.device.detailPageUrl ? f.device.detailPageUrl.replace(/\//g, '').toLowerCase() : '';
+ console.log("查找handler - detailPageUrl:", f.device.detailPageUrl, "转换后:", devKey);
for (let index = 0; index < keys.length; index++) {
-
- let key = keys[index].replace(/\//g, "").toLowerCase();
let devKey = f.device.detailPageUrl ? f.device.detailPageUrl.replace(/\//g, "").toLowerCase() : '';
+ let key = keys[index].replace(/\//g, '').toLowerCase();
if (key == devKey) {
handler = this.HandlerMap[keys[index]];
+ console.log("找到匹配的handler:", keys[index]);
break;
}
}
if (handler) {
let data = handler(receive, f, path, recArr);
+ console.log("handler返回的数据:", data);
return data;
} else {
- console.log("已收到消息,但无指定处理程序", receive);
+ console.log("已收到消息,但无指定处理程序, deviceUrl:", f.device.detailPageUrl, "可用handlers:", keys);
}
} else {
- console.log("已收到该消息,但无法处理", receive);
+ console.log("已收到该消息,但无法处理", receive, "f:", f);
}
return receive;
@@ -467,12 +470,16 @@ class BleReceive {
formData.battary = batteryLevel;
formData.statu = warn;
formData.xuhang = lightingTime;
-
+
+ console.log("7305解析结果 - 电量:", batteryLevel, "续航:", lightingTime, "完整数据:", formData);
let recCnt = recArr.find(v => {
return v.key.replace(/\//g, "").toLowerCase() === f.device.detailPageUrl.replaceAll(
'/', '').toLowerCase();
+
+
+
});
if (!recCnt) {
if (batteryLevel <= 20) {
@@ -488,9 +495,11 @@ class BleReceive {
this.setBleFormData(formData, f);
return formData;
} catch (error) {
- console.log('数据解析错误:', error);
+ console.log('7305数据解析错误:', error);
+ return null;
}
}
+ return null;
}
Receive_4877(receive,f,path,recArr){