|
|
import {
|
|
|
hrDataArray
|
|
|
} from './hrdata.js'
|
|
|
const SendCommandUtil = require('../utils/SendCommandUtil.js'); // 引用 发送指令工具类
|
|
|
Page({
|
|
|
data: {
|
|
|
// 渲染物品的动态CSS,
|
|
|
xuanranCss:{
|
|
|
width:'0',
|
|
|
height:'0'
|
|
|
},
|
|
|
arrList: [],
|
|
|
totalReceivedNodes: 0, // 记录设备推送的总节点数
|
|
|
totalConsumedNodes: 0, // 记录消费的总节点数
|
|
|
setInter: null,
|
|
|
getDataIndex: 0,
|
|
|
heartRateData: [], // 当前绘制的数据
|
|
|
windowSize: 1800, // 可视窗口大小
|
|
|
canvasWidth: 800,
|
|
|
canvasHeight: 200,
|
|
|
maxLevel: 2000, // 最大电平值
|
|
|
minLevel: -2000, // 最小电平值
|
|
|
isNotifyBLEC: false, //是否订阅
|
|
|
dataQueue: [], // 缓存接收到的长数组数据
|
|
|
isDrawing: false, // 绘制状态标志,避免重复触发
|
|
|
// deviceId: "66:3A:12:88:01:20",
|
|
|
deviceId: "66:3A:12:88:01:20",
|
|
|
serviceId: "000001FF-3C17-D293-8E48-14FE2E4DA212",
|
|
|
ecgCharacteristicId: "0000FF0A-0000-1000-8000-00805F9B34FB",
|
|
|
canvasList: [], // Canvas 列表,用于动态生成 Canvas
|
|
|
pointsPerCanvas: 1800, // 每个 Canvas 绘制的点数
|
|
|
},
|
|
|
onReady() {
|
|
|
// 初始化画布
|
|
|
this.ctx = wx.createCanvasContext('ecgCanvas');
|
|
|
// this.startDrawing();
|
|
|
this.notifyBLEC();
|
|
|
},
|
|
|
|
|
|
// 滑动窗口更新数据
|
|
|
pushHeartRate(newData) {
|
|
|
let heartRateData = this.data.heartRateData;
|
|
|
|
|
|
// 添加新数据
|
|
|
heartRateData.push(...newData);
|
|
|
|
|
|
// 如果数据超过窗口大小,移除最早的数据
|
|
|
if (heartRateData.length > this.data.windowSize) {
|
|
|
heartRateData.splice(0, heartRateData.length - this.data.windowSize);
|
|
|
}
|
|
|
|
|
|
// 动态计算最大值和最小值
|
|
|
// const maxLevel = Math.max(...heartRateData);
|
|
|
// const minLevel = Math.min(...heartRateData);
|
|
|
|
|
|
this.setData({
|
|
|
heartRateData,
|
|
|
// maxLevel,
|
|
|
// minLevel
|
|
|
});
|
|
|
},
|
|
|
|
|
|
// 绘制 ECG 曲线
|
|
|
drawECG() {
|
|
|
const ctx = this.ctx;
|
|
|
const {
|
|
|
heartRateData,
|
|
|
canvasHeight,
|
|
|
maxLevel,
|
|
|
minLevel,
|
|
|
canvasWidth,
|
|
|
windowSize
|
|
|
} = this.data;
|
|
|
|
|
|
// 清空画布
|
|
|
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
|
|
|
|
|
|
// 绘制网格
|
|
|
this.drawGrid(ctx);
|
|
|
|
|
|
// 计算缩放比例
|
|
|
const scaleY = canvasHeight / (maxLevel - minLevel); // 数据缩放到画布高度范围
|
|
|
const step = canvasWidth / (windowSize - 1); // 每点的水平间隔
|
|
|
|
|
|
// 绘制心电图
|
|
|
ctx.beginPath();
|
|
|
heartRateData.forEach((value, index) => {
|
|
|
const x = index * step;
|
|
|
const y = canvasHeight - (value - minLevel) * scaleY;
|
|
|
if (index === 0) {
|
|
|
ctx.moveTo(x, y);
|
|
|
} else {
|
|
|
const prevX = (index - 1) * step;
|
|
|
const prevY = canvasHeight - (heartRateData[index - 1] - minLevel) * scaleY;
|
|
|
// 使用二次贝塞尔曲线平滑点之间的连接
|
|
|
const midX = (prevX + x) / 2;
|
|
|
const midY = (prevY + y) / 2;
|
|
|
ctx.quadraticCurveTo(prevX, prevY, midX, midY);
|
|
|
}
|
|
|
});
|
|
|
ctx.setStrokeStyle('red');
|
|
|
ctx.setLineWidth(0.5);
|
|
|
ctx.stroke();
|
|
|
ctx.draw();
|
|
|
|
|
|
},
|
|
|
|
|
|
// 绘制网格
|
|
|
drawGrid(ctx) {
|
|
|
const {
|
|
|
canvasWidth,
|
|
|
canvasHeight,
|
|
|
} = this.data;
|
|
|
var width = canvasWidth
|
|
|
var height = canvasHeight
|
|
|
var gridWidth = 5
|
|
|
const hLineNum = parseInt(height / gridWidth);
|
|
|
const vLineNum = parseInt(width / gridWidth);
|
|
|
console.log(hLineNum, vLineNum)
|
|
|
ctx.setLineWidth(1);
|
|
|
ctx.setStrokeStyle("#ccc");
|
|
|
|
|
|
// 横线
|
|
|
for (let i = 0; i < hLineNum; i++) {
|
|
|
if (i === 0 || i % 5 !== 0) {
|
|
|
ctx.beginPath();
|
|
|
ctx.moveTo(0, i * gridWidth);
|
|
|
ctx.lineTo(width, i * gridWidth);
|
|
|
ctx.stroke();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 竖线
|
|
|
for (let i = 0; i < vLineNum; i++) {
|
|
|
if (i === 0 || i % 5 !== 0) {
|
|
|
ctx.beginPath();
|
|
|
ctx.moveTo(i * gridWidth, 0);
|
|
|
ctx.lineTo(i * gridWidth, height);
|
|
|
ctx.stroke();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 粗线
|
|
|
ctx.setStrokeStyle("#9B9B9B");
|
|
|
for (let i = 5; i <= vLineNum; i += 5) {
|
|
|
ctx.beginPath();
|
|
|
ctx.moveTo(i * gridWidth, 0);
|
|
|
ctx.lineTo(i * gridWidth, height);
|
|
|
ctx.stroke();
|
|
|
}
|
|
|
for (let i = 5; i <= hLineNum; i += 5) {
|
|
|
ctx.beginPath();
|
|
|
ctx.moveTo(0, i * gridWidth);
|
|
|
ctx.lineTo(width, i * gridWidth);
|
|
|
ctx.stroke();
|
|
|
}
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 启动绘制逻辑
|
|
|
startDrawing() {
|
|
|
if (this.data.isDrawing) return; // 避免重复启动
|
|
|
this.setData({
|
|
|
isDrawing: true
|
|
|
});
|
|
|
|
|
|
const pointsPerRender = 50; // 每次绘制的点数,控制绘制速度
|
|
|
const interval = 100; // 绘制间隔(ms)
|
|
|
|
|
|
const drawInterval = setInterval(() => {
|
|
|
const {
|
|
|
dataQueue,
|
|
|
heartRateData,
|
|
|
windowSize
|
|
|
} = this.data;
|
|
|
|
|
|
// 如果队列为空,停止绘制
|
|
|
if (dataQueue.length === 0) {
|
|
|
clearInterval(drawInterval);
|
|
|
this.setData({
|
|
|
isDrawing: false
|
|
|
});
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 从队列中取出一批数据
|
|
|
const newData = dataQueue.splice(0, pointsPerRender);
|
|
|
// 更新消费的总节点数
|
|
|
this.setData({
|
|
|
totalConsumedNodes: this.data.totalConsumedNodes + newData.length
|
|
|
});
|
|
|
// 更新心率数据(保持滑动窗口)
|
|
|
const updatedData = heartRateData.concat(newData);
|
|
|
if (updatedData.length > windowSize) {
|
|
|
updatedData.splice(0, updatedData.length - windowSize);
|
|
|
}
|
|
|
|
|
|
this.setData({
|
|
|
heartRateData: updatedData,
|
|
|
dataQueue
|
|
|
});
|
|
|
this.drawECG(); // 绘制心电图
|
|
|
}, interval);
|
|
|
},
|
|
|
stopECG() {
|
|
|
clearInterval(this.data.setInter)
|
|
|
},
|
|
|
startECG() {
|
|
|
// this.startDrawing()
|
|
|
SendCommandUtil.getECGData(this.data.deviceId)
|
|
|
},
|
|
|
notifyBLEC() {
|
|
|
wx.notifyBLECharacteristicValueChange({
|
|
|
deviceId: this.data.deviceId, // 蓝牙设备 ID
|
|
|
serviceId: this.data.serviceId, // 蓝牙服务 UUID
|
|
|
characteristicId: this.data.ecgCharacteristicId, // 心电
|
|
|
state: true,
|
|
|
success: (res) => {
|
|
|
console.log('心电特征订阅成功', res);
|
|
|
this.setData({
|
|
|
isNotifyBLEC: true
|
|
|
})
|
|
|
this.subscribeAndReceiveData();
|
|
|
},
|
|
|
fail: (err) => {
|
|
|
console.error('特征订阅失败', err);
|
|
|
},
|
|
|
});
|
|
|
},
|
|
|
// QRS滤波器处理函数
|
|
|
applyQRSFilter(rawData) {
|
|
|
const filteredData = [];
|
|
|
|
|
|
// 高通滤波器移除基线漂移
|
|
|
const highPassData = rawData.map((value, index, arr) => {
|
|
|
if (index === 0) return value;
|
|
|
return value - arr[index - 1] + 0.95 * (arr[index - 1] || 0);
|
|
|
});
|
|
|
|
|
|
// 微分滤波器增强信号
|
|
|
const diffData = highPassData.map((value, index, arr) => {
|
|
|
if (index < 4) return 0; // 前几个点无法计算微分
|
|
|
return (
|
|
|
(2 * arr[index] +
|
|
|
arr[index - 1] -
|
|
|
arr[index - 3] -
|
|
|
2 * arr[index - 4]) /
|
|
|
8
|
|
|
);
|
|
|
});
|
|
|
|
|
|
// 平滑滤波器减少噪声
|
|
|
const smoothData = diffData.map((value, index, arr) => {
|
|
|
if (index < 5) return 0; // 前几个点无法平滑
|
|
|
return (
|
|
|
(arr[index] +
|
|
|
arr[index - 1] +
|
|
|
arr[index - 2] +
|
|
|
arr[index - 3] +
|
|
|
arr[index - 4]) /
|
|
|
5
|
|
|
);
|
|
|
});
|
|
|
|
|
|
// 阈值处理检测QRS波
|
|
|
const threshold = 0.6; // 自定义阈值(需要根据实际情况调整)
|
|
|
smoothData.forEach((value, index) => {
|
|
|
if (Math.abs(value) > threshold) {
|
|
|
filteredData.push(value); // 保留QRS特征
|
|
|
} else {
|
|
|
filteredData.push(0); // 非特征部分置零
|
|
|
}
|
|
|
});
|
|
|
|
|
|
return filteredData;
|
|
|
},
|
|
|
subscribeAndReceiveData() {
|
|
|
if (this.data.isNotifyBLEC) {
|
|
|
// 监听数据特征值变化
|
|
|
wx.onBLECharacteristicValueChange((res) => {
|
|
|
// console.log('------------------------', res)
|
|
|
if (res.characteristicId === this.data.ecgCharacteristicId) {
|
|
|
const dataView = new DataView(res.value);
|
|
|
const hexStr = this.uint8ArrayToHex(new Uint8Array(res.value))
|
|
|
// console.log("心电报文:", hexStr);
|
|
|
// console.log("心电报文View:", dataView);
|
|
|
// 获取下标 9-10 的字节 (dataType)
|
|
|
let cmdType = dataView.getUint16(8);
|
|
|
if (cmdType == 0x01) {
|
|
|
// console.log("心电指令,ECG数据返回:", cmdType);
|
|
|
}
|
|
|
let dataType = dataView.getUint8(10);
|
|
|
// console.log("dataType:", dataType);
|
|
|
switch (dataType) {
|
|
|
case 0x00:
|
|
|
// console.log(this.data.arrList.join(','))
|
|
|
// console.log("ECG 测试结束:");
|
|
|
break;
|
|
|
case 0x01:
|
|
|
// console.log("ECG 测试开始:");
|
|
|
break;
|
|
|
case 0x02:
|
|
|
var bodyData = this.uint8ArrayToHex(new Uint8Array(res.value))
|
|
|
var bodyFor = []
|
|
|
for (let i = 0; i <= bodyData.length; i++) {
|
|
|
if (bodyData[i] == ' ') {
|
|
|
bodyFor.push(',')
|
|
|
} else {
|
|
|
bodyFor.push(bodyData[i])
|
|
|
}
|
|
|
}
|
|
|
bodyData = bodyFor.join('').split(',').splice(-8)
|
|
|
var BPM = parseInt(bodyData[0], 16);
|
|
|
var EcgProgress = parseInt(bodyData[7], 16)
|
|
|
// console.log("ECG 参数同步:", BPM, EcgProgress)
|
|
|
break;
|
|
|
case 0x03:
|
|
|
const rawData = new Uint8Array(res.value);
|
|
|
console.log("原始数据:",rawData)
|
|
|
let filterSigns = [];
|
|
|
if (rawData.length >= 50) {
|
|
|
for (let i = 0; i < 25; i++) {
|
|
|
let filter = ((rawData[i * 2 + 1] & 0xff) << 8) | (rawData[i * 2 + 2] & 0xff);
|
|
|
if (filter > 32767) {
|
|
|
filter = filter - 65536;
|
|
|
}
|
|
|
filterSigns.push(filter);
|
|
|
}
|
|
|
}
|
|
|
console.log('处理后的数据:',filterSigns)
|
|
|
filterSigns = filterSigns.splice(5)
|
|
|
this.setData({
|
|
|
arrList: [...this.data.arrList, ...filterSigns],
|
|
|
dataQueue: this.data.dataQueue.concat(filterSigns),
|
|
|
totalReceivedNodes: this.data.totalReceivedNodes + filterSigns.length,
|
|
|
});
|
|
|
|
|
|
this.startDrawing();
|
|
|
break;
|
|
|
case 0x04:
|
|
|
// console.log("ECG 测试状态返回:");
|
|
|
break;
|
|
|
default:
|
|
|
// console.warn('未知数据类型', dataType);
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
} else {
|
|
|
this.notifyBLEC();
|
|
|
wx.showLoading({
|
|
|
title: '等待订阅中',
|
|
|
})
|
|
|
setTimeout(() => {
|
|
|
wx.hideLoading()
|
|
|
this.subscribeAndReceiveData()
|
|
|
}, 5000);
|
|
|
}
|
|
|
},
|
|
|
uint8ArrayToHex(array) {
|
|
|
return Array.from(array)
|
|
|
.map(byte => byte.toString(16).padStart(2, '0')) // 转换每个字节为 2 位的 16 进制,并补零
|
|
|
.join(' '); // 用空格连接所有字节
|
|
|
},
|
|
|
/**
|
|
|
* 将十六进制字符串转换为十进制数组
|
|
|
* @param {string} hexString - 输入的十六进制字符串,每两个字符代表一个字节
|
|
|
* @returns {number[]} - 转换后的十进制数组
|
|
|
*/
|
|
|
hexStringToDecimalArray(hexString) {
|
|
|
if (!hexString || typeof hexString !== "string") {
|
|
|
throw new Error("输入必须是有效的十六进制字符串!");
|
|
|
}
|
|
|
|
|
|
// 确保字符串长度是偶数
|
|
|
if (hexString.length % 2 !== 0) {
|
|
|
throw new Error("十六进制字符串长度必须为偶数!");
|
|
|
}
|
|
|
|
|
|
const decimalArray = [];
|
|
|
for (let i = 0; i < hexString.length; i += 2) {
|
|
|
// 截取每两个字符并转换为十进制
|
|
|
const hexByte = hexString.substr(i, 2);
|
|
|
const decimalValue = parseInt(hexByte, 16);
|
|
|
|
|
|
// 检查转换结果是否有效
|
|
|
if (isNaN(decimalValue)) {
|
|
|
throw new Error(`无效的十六进制字节:${hexByte}`);
|
|
|
}
|
|
|
|
|
|
decimalArray.push(decimalValue);
|
|
|
}
|
|
|
|
|
|
return decimalArray;
|
|
|
},
|
|
|
// 将两个字节合并成一个有符号的 16 位整数
|
|
|
// 将每两个字节解析为一个有符号的 16 位整数
|
|
|
hexStringToSignedDecimalArray(hexString) {
|
|
|
if (!hexString || typeof hexString !== "string") {
|
|
|
throw new Error("输入必须是有效的十六进制字符串!");
|
|
|
}
|
|
|
|
|
|
// 确保字符串长度是偶数
|
|
|
if (hexString.length % 2 !== 0) {
|
|
|
throw new Error("十六进制字符串的长度必须是偶数!");
|
|
|
}
|
|
|
|
|
|
const signedDecimalArray = [];
|
|
|
for (let i = 0; i < hexString.length; i += 4) { // 每次处理两个字节
|
|
|
// 取出两个字节,拼接为一个 16 位整数(16 进制)
|
|
|
const hexByte1 = hexString.substr(i, 2); // 第一个字节
|
|
|
const hexByte2 = hexString.substr(i + 2, 2); // 第二个字节
|
|
|
|
|
|
// 将这两个字节转为十进制数
|
|
|
const int16 = (parseInt(hexByte2, 16) << 8) | parseInt(hexByte1, 16);
|
|
|
|
|
|
// 检查是否需要转换为负数
|
|
|
if (int16 >= 0x8000) {
|
|
|
// 转换为负数(补码转换)
|
|
|
signedDecimalArray.push(int16 - 0x10000);
|
|
|
} else {
|
|
|
signedDecimalArray.push(int16);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return signedDecimalArray;
|
|
|
},
|
|
|
// =============================
|
|
|
drawGridNews(ctx) {
|
|
|
const {
|
|
|
canvasWidth,
|
|
|
canvasHeight,
|
|
|
} = this.data;
|
|
|
var width = 1000
|
|
|
var height = canvasHeight
|
|
|
var gridWidth = 5
|
|
|
const hLineNum = parseInt(height / gridWidth);
|
|
|
const vLineNum = parseInt(width / gridWidth);
|
|
|
// console.log(hLineNum, vLineNum)
|
|
|
ctx.setLineWidth(1);
|
|
|
ctx.setStrokeStyle("#ccc");
|
|
|
|
|
|
// 横线
|
|
|
for (let i = 0; i < hLineNum; i++) {
|
|
|
if (i === 0 || i % 5 !== 0) {
|
|
|
ctx.beginPath();
|
|
|
ctx.moveTo(0, i * gridWidth);
|
|
|
ctx.lineTo(width, i * gridWidth);
|
|
|
ctx.stroke();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 竖线
|
|
|
for (let i = 0; i < vLineNum; i++) {
|
|
|
if (i === 0 || i % 5 !== 0) {
|
|
|
ctx.beginPath();
|
|
|
ctx.moveTo(i * gridWidth, 0);
|
|
|
ctx.lineTo(i * gridWidth, height);
|
|
|
ctx.stroke();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 粗线
|
|
|
ctx.setStrokeStyle("#9B9B9B");
|
|
|
for (let i = 5; i <= vLineNum; i += 5) {
|
|
|
ctx.beginPath();
|
|
|
ctx.moveTo(i * gridWidth, 0);
|
|
|
ctx.lineTo(i * gridWidth, height);
|
|
|
ctx.stroke();
|
|
|
}
|
|
|
for (let i = 5; i <= hLineNum; i += 5) {
|
|
|
ctx.beginPath();
|
|
|
ctx.moveTo(0, i * gridWidth);
|
|
|
ctx.lineTo(width, i * gridWidth);
|
|
|
ctx.stroke();
|
|
|
}
|
|
|
},
|
|
|
drawEcgSegment(canvasId, index) {
|
|
|
const {
|
|
|
arrList, // 原始数据
|
|
|
pointsPerCanvas, // 每个 Canvas 显示的点数
|
|
|
canvasHeight, // 画布高度
|
|
|
maxLevel, // 最大电平值
|
|
|
minLevel // 最小电平值
|
|
|
} = this.data;
|
|
|
|
|
|
// 确定当前分片的起始和结束索引
|
|
|
const startIndex = index * pointsPerCanvas;
|
|
|
const endIndex = Math.min(startIndex + pointsPerCanvas, arrList.length);
|
|
|
const segmentData = arrList.slice(startIndex, endIndex); // 取当前分片的数据
|
|
|
|
|
|
// 水平缩放比例 (每点的水平间隔)
|
|
|
const step = this.data.canvasWidth / pointsPerCanvas;
|
|
|
|
|
|
// 垂直缩放比例
|
|
|
const scaleY = canvasHeight / (maxLevel - minLevel);
|
|
|
|
|
|
// 获取 Canvas Context
|
|
|
const ctx = wx.createCanvasContext(canvasId, this);
|
|
|
|
|
|
// 清空画布
|
|
|
ctx.clearRect(0, 0, this.data.canvasWidth, canvasHeight);
|
|
|
|
|
|
// 绘制网格
|
|
|
this.drawGridNews(ctx);
|
|
|
|
|
|
// 绘制心电图
|
|
|
ctx.setStrokeStyle('red');
|
|
|
ctx.setLineWidth(1);
|
|
|
ctx.beginPath();
|
|
|
|
|
|
segmentData.forEach((value, i) => {
|
|
|
const x = i * step;
|
|
|
const y = canvasHeight - (value - minLevel) * scaleY;
|
|
|
|
|
|
if (i === 0) {
|
|
|
ctx.moveTo(x, y);
|
|
|
} else {
|
|
|
const prevX = (i - 1) * step;
|
|
|
const prevY = canvasHeight - (segmentData[i - 1] - minLevel) * scaleY;
|
|
|
|
|
|
// 使用二次贝塞尔曲线平滑连接点
|
|
|
const midX = (prevX + x) / 2;
|
|
|
const midY = (prevY + y) / 2;
|
|
|
ctx.quadraticCurveTo(prevX, prevY, midX, midY);
|
|
|
}
|
|
|
});
|
|
|
|
|
|
ctx.stroke();
|
|
|
ctx.draw();
|
|
|
},
|
|
|
drawFullECG() {
|
|
|
const {
|
|
|
arrList,
|
|
|
pointsPerCanvas
|
|
|
} = this.data;
|
|
|
const totalLength = arrList.length;
|
|
|
const canvasCount = Math.ceil(totalLength / pointsPerCanvas); // 计算需要的 Canvas 数量
|
|
|
|
|
|
// 生成 Canvas 列表
|
|
|
const canvasList = Array.from({
|
|
|
length: canvasCount
|
|
|
}, (_, index) => ({
|
|
|
canvasId: `ecgCanvas-${index}`,
|
|
|
}));
|
|
|
this.setData({
|
|
|
canvasList
|
|
|
}, () => {
|
|
|
// // 循环绘制每个 Canvas
|
|
|
canvasList.forEach((item, index) => {
|
|
|
this.drawEcgSegment(item.canvasId, index);
|
|
|
});
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
},
|
|
|
// ===============================================
|
|
|
// 绘制并保存 canvas 为图片
|
|
|
saveCanvasAsImage() {
|
|
|
const that = this;
|
|
|
const {
|
|
|
canvasList,
|
|
|
canvasWidth,
|
|
|
canvasHeight
|
|
|
} = that.data;
|
|
|
|
|
|
// 创建一个目标 canvas
|
|
|
const targetCanvas = wx.createCanvasContext('targetCanvas', this);
|
|
|
|
|
|
// 设置目标 canvas 宽度和高度
|
|
|
const targetWidth = canvasList.length * 1000;
|
|
|
const targetHeight = 200; // 假设每个 canvas 高度一致
|
|
|
|
|
|
// 将目标 canvas 的宽高设置好
|
|
|
console.log(targetCanvas,'-------------------')
|
|
|
// targetCanvas.canvas.width = targetWidth;
|
|
|
// targetCanvas.canvas.height = targetHeight;
|
|
|
this.setData({
|
|
|
xuanranCss:{
|
|
|
width:targetWidth,
|
|
|
height:targetHeight
|
|
|
}
|
|
|
})
|
|
|
|
|
|
// 循环通过每个 canvas 将图像拼接到目标 canvas
|
|
|
canvasList.forEach((item, index) => {
|
|
|
// 获取每个 canvas 的上下文并将其转为图片
|
|
|
wx.canvasToTempFilePath({
|
|
|
canvasId: item.canvasId,
|
|
|
success(res) {
|
|
|
// 每次拿到一个 canvas 的图片路径后,画到目标 canvas 上
|
|
|
targetCanvas.drawImage(res.tempFilePath, index * canvasWidth, 0, canvasWidth, canvasHeight);
|
|
|
|
|
|
// 如果是最后一个 canvas,就绘制完毕并导出图片
|
|
|
if (index === canvasList.length - 1) {
|
|
|
targetCanvas.draw(false, () => {
|
|
|
// 合成完成后导出最终拼接的图像
|
|
|
wx.canvasToTempFilePath({
|
|
|
canvasId: 'targetCanvas',
|
|
|
success(res) {
|
|
|
// 这里可以获取到拼接后的图片路径
|
|
|
console.log('拼接后的图片路径:', res.tempFilePath);
|
|
|
// 可以在页面中展示拼接后的图片
|
|
|
that.setData({
|
|
|
combinedImagePath: res.tempFilePath
|
|
|
});
|
|
|
const tempFilePath = res.tempFilePath;
|
|
|
|
|
|
// 选择保存到相册
|
|
|
wx.saveImageToPhotosAlbum({
|
|
|
filePath: tempFilePath,
|
|
|
success: (saveRes) => {
|
|
|
wx.showToast({
|
|
|
title: '保存成功',
|
|
|
icon: 'success',
|
|
|
});
|
|
|
},
|
|
|
fail: (saveErr) => {
|
|
|
wx.showToast({
|
|
|
title: '保存失败',
|
|
|
icon: 'none',
|
|
|
});
|
|
|
console.error('保存失败', saveErr);
|
|
|
},
|
|
|
});
|
|
|
},
|
|
|
fail(error) {
|
|
|
console.log('导出拼接图失败', error);
|
|
|
}
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
},
|
|
|
fail(error) {
|
|
|
console.log('转换 canvas 为临时文件失败', error);
|
|
|
}
|
|
|
});
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
}); |