1
This commit is contained in:
@ -70,9 +70,16 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<canvas
|
||||
id="myCanvas"
|
||||
type="2d"
|
||||
:style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"
|
||||
style="position: fixed; left: -9999px; top: -9999px;"
|
||||
></canvas>
|
||||
|
||||
<!-- 隐藏的 wxml-to-canvas 组件用于生成证书图片 -->
|
||||
<wxml-to-canvas class="widget" :width="canvasWidth" :height="canvasHeight" style="position: fixed; left: -9999px; top: -9999px;"></wxml-to-canvas>
|
||||
<!-- <wxml-to-canvas class="widget" :width="canvasWidth" :height="canvasHeight" style="position: fixed; left: -9999px; top: -9999px;"></wxml-to-canvas> -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@ -107,7 +114,7 @@ export default {
|
||||
},
|
||||
onReady() {
|
||||
// 获取 wxml-to-canvas 组件实例
|
||||
this.widget = this.$refs.widget || this.selectComponent('.widget');
|
||||
// this.widget = this.$refs.widget || this.selectComponent('.widget');
|
||||
},
|
||||
methods: {
|
||||
getCertificates(data) {
|
||||
@ -125,6 +132,27 @@ export default {
|
||||
closeModal() {
|
||||
this.showModal = false;
|
||||
},
|
||||
wrapText(ctx, text, x, y, maxWidth, lineHeight) {
|
||||
const words = text.split('');
|
||||
let line = '';
|
||||
let currentY = y;
|
||||
|
||||
for (let i = 0; i < words.length; i++) {
|
||||
const testLine = line + words[i];
|
||||
const metrics = ctx.measureText(testLine);
|
||||
const testWidth = metrics.width;
|
||||
|
||||
if (testWidth > maxWidth && i > 0) {
|
||||
ctx.fillText(line, x, currentY);
|
||||
line = words[i];
|
||||
currentY += lineHeight;
|
||||
} else {
|
||||
line = testLine;
|
||||
}
|
||||
}
|
||||
|
||||
ctx.fillText(line, x, currentY);
|
||||
},
|
||||
onScroll(e) {
|
||||
// 获取滚动位置(单位:px)
|
||||
const scrollLeft = e.detail.scrollLeft;
|
||||
@ -141,138 +169,172 @@ export default {
|
||||
this.currentPage = newPage;
|
||||
}
|
||||
},
|
||||
async saveCurrentImage() {
|
||||
// 保存当前图片 - 使用 wxml-to-canvas 生成
|
||||
const currentIndex = this.currentPage - 1;
|
||||
const currentItem = this.certificateList[currentIndex];
|
||||
if (!currentItem) {
|
||||
uni.showToast({
|
||||
title: '证书信息加载中,请稍候',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 存储当前证书信息到 data,供模板使用
|
||||
this.currentCertificateItem = currentItem;
|
||||
|
||||
// 获取组件实例 - 多种方式尝试
|
||||
if (!this.widget) {
|
||||
this.widget = this.$refs.widget;
|
||||
}
|
||||
if (!this.widget) {
|
||||
this.widget = this.selectComponent('.widget');
|
||||
}
|
||||
if (!this.widget) {
|
||||
// 延迟重试一次
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
this.widget = this.$refs.widget || this.selectComponent('.widget');
|
||||
}
|
||||
|
||||
if (!this.widget) {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: '组件初始化失败,请重试',
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
});
|
||||
console.error('wxml-to-canvas 组件获取失败');
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查组件方法是否可用
|
||||
if (typeof this.widget.renderToCanvas !== 'function') {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: '组件方法不可用,请重试',
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
});
|
||||
console.error('renderToCanvas 方法不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
uni.showLoading({
|
||||
title: '生成图片中...',
|
||||
mask: true
|
||||
});
|
||||
|
||||
try {
|
||||
// 固定画布尺寸
|
||||
const canvasWidth = 317;
|
||||
const canvasHeight = 224;
|
||||
|
||||
// 更新 canvas 尺寸
|
||||
this.canvasWidth = canvasWidth;
|
||||
this.canvasHeight = canvasHeight;
|
||||
|
||||
await this.$nextTick();
|
||||
|
||||
// 生成证书的 wxml 和 style(使用固定尺寸)
|
||||
const { wxml, style } = this.generateCertificateTemplate();
|
||||
|
||||
// 渲染到 canvas
|
||||
const container = await this.widget.renderToCanvas({ wxml, style });
|
||||
|
||||
// 转换为图片
|
||||
const res = await this.widget.canvasToTempFilePath({
|
||||
fileType: 'png',
|
||||
quality: 1,
|
||||
width: this.canvasWidth,
|
||||
height: this.canvasHeight
|
||||
});
|
||||
|
||||
if (!res || !res.tempFilePath) {
|
||||
throw new Error('生成图片路径失败');
|
||||
async loadImage(src) {
|
||||
console.log('正在加载图片:', src);
|
||||
return new Promise((resolve, reject) => {
|
||||
tt.downloadFile({
|
||||
url: src,
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
console.log('图片加载成功:', res.tempFilePath);
|
||||
resolve(res.tempFilePath);
|
||||
} else {
|
||||
console.error('图片加载失败,状态码:', res.statusCode);
|
||||
reject(new Error(`图片加载失败,状态码: ${res.statusCode}`));
|
||||
}
|
||||
|
||||
// 保存到相册
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: res.tempFilePath,
|
||||
success: () => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: '保存成功',
|
||||
icon: 'success'
|
||||
});
|
||||
},
|
||||
fail: (err) => {
|
||||
uni.hideLoading();
|
||||
const errMsg = err?.errMsg || '未知错误';
|
||||
uni.showToast({
|
||||
title: '保存失败,请检查相册权限',
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
});
|
||||
console.error('保存图片失败:', errMsg, err);
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('图片加载失败:', err.errMsg);
|
||||
reject(new Error(`图片加载失败: ${err.errMsg}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
async saveCurrentImage() {
|
||||
const currentIndex = this.currentPage - 1;
|
||||
const currentItem = this.certificateList[currentIndex];
|
||||
|
||||
if (!currentItem) {
|
||||
uni.showToast({
|
||||
title: '证书信息加载中,请稍候',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentCertificateItem = currentItem;
|
||||
|
||||
uni.showLoading({
|
||||
title: '生成图片中...',
|
||||
mask: true
|
||||
});
|
||||
|
||||
try {
|
||||
// 获取 Canvas 节点
|
||||
const query = tt.createSelectorQuery();
|
||||
const canvasNode = await new Promise((resolve, reject) => {
|
||||
query
|
||||
.select('#myCanvas')
|
||||
.fields({ node: true, size: true })
|
||||
.exec((res) => {
|
||||
if (res[0]) {
|
||||
resolve(res[0].node);
|
||||
} else {
|
||||
reject(new Error('Canvas 节点获取失败'));
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
});
|
||||
|
||||
// 获取 2D 上下文
|
||||
const ctx = canvasNode.getContext('2d');
|
||||
const dpr = tt.getSystemInfoSync().pixelRatio;
|
||||
canvasNode.width = this.canvasWidth * dpr;
|
||||
canvasNode.height = this.canvasHeight * dpr;
|
||||
ctx.scale(dpr, dpr);
|
||||
|
||||
// 清空画布
|
||||
ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
|
||||
|
||||
// 加载并绘制背景图
|
||||
const backgroundImgPath = await this.loadImage(`${this.imgPrefix}certificateGround.png`);
|
||||
const backgroundImg = canvasNode.createImage();
|
||||
backgroundImg.src = backgroundImgPath;
|
||||
await new Promise((resolve) => {
|
||||
backgroundImg.onload = () => {
|
||||
ctx.drawImage(backgroundImg, 0, 0, this.canvasWidth, this.canvasHeight);
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
|
||||
// 设置字体样式
|
||||
ctx.font = '20px sans-serif';
|
||||
ctx.fillStyle = '#FF19A0';
|
||||
ctx.textAlign = 'center';
|
||||
|
||||
// 绘制证书标题
|
||||
const title = currentItem.title || '为爱续航证书';
|
||||
ctx.fillText(title, this.canvasWidth / 2, 50);
|
||||
|
||||
// 绘制英文标题
|
||||
ctx.font = '12px sans-serif';
|
||||
const titleEn = currentItem.title_en || 'Certificate of Love Endurance';
|
||||
ctx.fillText(titleEn, this.canvasWidth / 2, 70);
|
||||
|
||||
// 绘制用户名
|
||||
ctx.font = '16px sans-serif';
|
||||
const userName = this.userName || '周佳佳';
|
||||
ctx.fillText(userName, this.canvasWidth / 2, 100);
|
||||
|
||||
// 绘制描述文本
|
||||
ctx.font = '12px sans-serif';
|
||||
ctx.fillStyle = '#333333';
|
||||
ctx.textAlign = 'left';
|
||||
const description = `您累计捐赠${currentItem.source_value || 0}克粮,为毛孩子奉献爱心,点燃希望,感谢您的捐赠让世界变得更温暖。`;
|
||||
this.wrapText(ctx, description, 20, 130, this.canvasWidth - 40, 16);
|
||||
|
||||
// 绘制结尾文字
|
||||
ctx.fillText('特发此证,以表谢忱!', 20, 200);
|
||||
|
||||
// 绘制证书编号
|
||||
ctx.font = '10px sans-serif';
|
||||
ctx.fillStyle = '#FF19A0';
|
||||
ctx.textAlign = 'right';
|
||||
const certificateNo = `证书编号: ${currentItem.certificate_no || currentItem.id || '234546678896666788'}`;
|
||||
ctx.fillText(certificateNo, this.canvasWidth - 20, this.canvasHeight - 20);
|
||||
|
||||
// 绘制证书图标
|
||||
const badgeImgPath = await this.loadImage(currentItem.certificate_url);
|
||||
const badgeImg = canvasNode.createImage();
|
||||
badgeImg.src = badgeImgPath;
|
||||
await new Promise((resolve) => {
|
||||
badgeImg.onload = () => {
|
||||
ctx.drawImage(badgeImg, this.canvasWidth - 60, this.canvasHeight - 80, 40, 40);
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
|
||||
// 导出为 base64 数据
|
||||
const dataURL = canvasNode.toDataURL('image/png');
|
||||
const base64Data = dataURL.replace(/^data:image\/\w+;base64,/, '');
|
||||
|
||||
// 将 base64 数据写入临时文件
|
||||
const tempFilePath = `${tt.env.USER_DATA_PATH}/temp_certificate.png`;
|
||||
const fs = tt.getFileSystemManager();
|
||||
fs.writeFileSync(tempFilePath, base64Data, 'base64');
|
||||
|
||||
// 保存到相册
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: tempFilePath,
|
||||
success: () => {
|
||||
uni.hideLoading();
|
||||
const errorMsg = error?.message || error?.toString() || '未知错误';
|
||||
const errorInfo = `生成图片失败: ${errorMsg}`;
|
||||
console.log(errorInfo, '--=')
|
||||
uni.showToast({
|
||||
title: errorInfo.length > 20 ? '生成图片失败,请重试' : errorInfo,
|
||||
title: '保存成功',
|
||||
icon: 'success'
|
||||
});
|
||||
},
|
||||
fail: (err) => {
|
||||
uni.hideLoading();
|
||||
const errMsg = err?.errMsg || '未知错误';
|
||||
uni.showToast({
|
||||
title: '保存失败,请检查相册权限',
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
});
|
||||
// 使用多种方式记录错误,确保线上环境能捕获
|
||||
console.error('生成证书图片失败:', error);
|
||||
console.error('错误详情:', {
|
||||
message: error?.message,
|
||||
stack: error?.stack,
|
||||
error: error
|
||||
});
|
||||
// 如果 console 不可用,尝试通过其他方式记录
|
||||
if (typeof uni.reportError === 'function') {
|
||||
uni.reportError({
|
||||
error: errorInfo,
|
||||
errorInfo: JSON.stringify(error)
|
||||
});
|
||||
}
|
||||
console.error('保存图片失败:', errMsg, err);
|
||||
}
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
uni.hideLoading();
|
||||
const errorMsg = error?.message || error?.toString() || '未知错误';
|
||||
uni.showToast({
|
||||
title: errorMsg.length > 20 ? '生成图片失败,请重试' : errorMsg,
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
});
|
||||
console.error('生成证书图片失败:', error);
|
||||
}
|
||||
},
|
||||
generateCertificateTemplate() {
|
||||
// 使用存储的证书信息
|
||||
const item = this.currentCertificateItem;
|
||||
|
||||
Reference in New Issue
Block a user