findme-miniprogram-frontend/subpackages/profile/avatar-edit/avatar-edit.js

904 lines
28 KiB
JavaScript
Raw Permalink Normal View History

2025-12-27 17:16:03 +08:00
// 头像编辑页面
const cosManager = require('../../../utils/cos-manager.js');
const apiClient = require('../../../utils/api-client.js');
Page({
data: {
// 图片路径
originalImagePath: '', // 原始图片路径
currentImagePath: '', // 当前编辑的图片路径
imageInfo: null, // 图片信息
// 编辑状态
rotateAngle: 0, // 旋转角度
hasEdits: false, // 是否有编辑操作
editHistory: [], // 编辑历史(用于撤回)
// 裁剪相关
isCropping: false, // 是否在裁剪模式
cropSize: 300, // 裁剪框大小(圆形)
cropX: 0, // 裁剪框X坐标
cropY: 0, // 裁剪框Y坐标
// 模糊相关
isBlurring: false, // 是否在模糊模式
blurSize: 20, // 模糊块大小
blurData: [], // 模糊数据
// Canvas相关
canvasContext: null,
canvas: null,
canvasWidth: 0,
canvasHeight: 0,
// 系统信息
systemInfo: {}
},
onLoad(options) {
const imagePath = options.imagePath;
if (!imagePath) {
wx.showToast({
title: '图片路径无效',
icon: 'none'
});
setTimeout(() => {
wx.navigateBack();
}, 1500);
return;
}
// 获取系统信息
const systemInfo = wx.getSystemInfoSync();
this.setData({
systemInfo,
originalImagePath: decodeURIComponent(imagePath),
currentImagePath: decodeURIComponent(imagePath)
});
},
// 加载图片信息从image的bindload事件调用
onImageLoad(e) {
const { width, height } = e.detail;
const systemInfo = this.data.systemInfo || wx.getSystemInfoSync();
// 计算显示尺寸
const maxWidth = systemInfo.windowWidth;
const maxHeight = systemInfo.windowHeight * 0.7;
const scale = Math.min(maxWidth / width, maxHeight / height);
const displayWidth = width * scale;
const displayHeight = height * scale;
// 获取图片容器的实际位置,用于计算裁剪框位置
const query = wx.createSelectorQuery().in(this);
query.select('.image-wrapper').boundingClientRect((rect) => {
if (rect) {
// 缓存图片容器的位置信息
this._imageWrapperRect = rect;
// 初始化裁剪框位置(居中,相对于图片容器)
const cropSize = Math.min(rect.width, rect.height) * 0.8;
// 计算相对于图片容器的坐标
const cropX = Math.max(0, (rect.width - cropSize) / 2);
const cropY = Math.max(0, (rect.height - cropSize) / 2);
this.setData({
imageInfo: { width, height, displayWidth, displayHeight },
cropSize: Math.round(cropSize),
cropX: Math.round(cropX),
cropY: Math.round(cropY),
canvasWidth: Math.round(rect.width), // 使用容器的实际宽度
canvasHeight: Math.round(rect.height), // 使用容器的实际高度
systemInfo: systemInfo // 确保 systemInfo 已设置
}, () => {
this.initCanvas();
});
} else {
// 如果获取容器位置失败,使用默认值
const cropSize = Math.min(displayWidth, displayHeight) * 0.8;
const cropX = Math.max(0, (displayWidth - cropSize) / 2);
const cropY = Math.max(0, (displayHeight - cropSize) / 2);
// 使用估算的容器位置
this._imageWrapperRect = {
left: 0,
top: 0,
width: displayWidth,
height: displayHeight
};
this.setData({
imageInfo: { width, height, displayWidth, displayHeight },
cropSize: Math.round(cropSize),
cropX: Math.round(cropX),
cropY: Math.round(cropY),
canvasWidth: Math.round(displayWidth),
canvasHeight: Math.round(displayHeight),
systemInfo: systemInfo // 确保 systemInfo 已设置
}, () => {
this.initCanvas();
});
}
}).exec();
},
// 初始化Canvas
initCanvas() {
const query = wx.createSelectorQuery().in(this);
query.select('#blurCanvas')
.fields({ node: true, size: true })
.exec((res) => {
if (res && res[0] && res[0].node) {
const canvas = res[0].node;
const ctx = canvas.getContext('2d');
const { canvasWidth, canvasHeight } = this.data;
canvas.width = canvasWidth;
canvas.height = canvasHeight;
this.setData({
canvas: canvas,
canvasContext: ctx
});
}
});
},
// 旋转图片
rotateImage() {
const newAngle = (this.data.rotateAngle + 90) % 360;
this.saveEditState();
this.setData({
rotateAngle: newAngle,
hasEdits: true
});
},
// 切换裁剪模式
toggleCrop() {
this.setData({
isCropping: !this.data.isCropping,
isBlurring: false
}, () => {
// 如果开启裁剪模式,重新获取图片容器位置
if (this.data.isCropping) {
this._updateImageWrapperRect();
}
});
},
// 裁剪框拖动开始
onCropMoveStart(e) {
if (!this.data.isCropping) return;
const touch = e.touches[0];
if (!touch) return;
// 确保容器位置已获取
if (!this._imageWrapperRect) {
this._updateImageWrapperRect();
}
// 保存起始触摸位置
this._cropStartTouch = {
clientX: touch.clientX,
clientY: touch.clientY
};
},
// 裁剪框拖动(优化版本,使用相对坐标)
onCropMove(e) {
if (!this.data.isCropping) return;
const touch = e.touches[0];
if (!touch || !touch.clientX || !touch.clientY) return;
const { cropSize, canvasWidth, canvasHeight } = this.data;
// 保存触摸坐标,避免异步回调中失效
const touchX = touch.clientX;
const touchY = touch.clientY;
// 获取图片容器的实际位置(使用缓存的或重新获取)
let imageWrapperRect = this._imageWrapperRect;
if (!imageWrapperRect) {
// 如果没有缓存的容器位置,同步获取(使用估算值作为兜底)
const query = wx.createSelectorQuery().in(this);
query.select('.image-wrapper').boundingClientRect((rect) => {
if (rect) {
this._imageWrapperRect = rect;
// 使用保存的触摸坐标重新计算
const savedTouch = { clientX: touchX, clientY: touchY };
this._calculateCropPosition(savedTouch, cropSize, canvasWidth, canvasHeight, rect);
} else {
// 如果获取失败,使用系统信息估算(兜底方案)
const systemInfo = this.data.systemInfo || wx.getSystemInfoSync();
const estimatedRect = {
left: 0,
top: 0,
width: systemInfo.windowWidth || 375,
height: systemInfo.windowHeight * 0.7 || 400
};
const savedTouch = { clientX: touchX, clientY: touchY };
this._calculateCropPosition(savedTouch, cropSize, canvasWidth, canvasHeight, estimatedRect);
}
}).exec();
return;
}
// 使用缓存的容器位置计算裁剪框位置
this._calculateCropPosition(touch, cropSize, canvasWidth, canvasHeight, imageWrapperRect);
},
// 计算裁剪框位置(提取公共逻辑)
_calculateCropPosition(touch, cropSize, canvasWidth, canvasHeight, imageWrapperRect) {
// 确保 touch 对象有 clientX 和 clientY 属性
if (!touch || typeof touch.clientX === 'undefined' || typeof touch.clientY === 'undefined') {
console.warn('触摸事件坐标无效:', touch);
return;
}
// 计算相对于图片容器的坐标
const relativeX = touch.clientX - imageWrapperRect.left;
const relativeY = touch.clientY - imageWrapperRect.top;
// 计算裁剪框中心位置(相对于图片容器)
let newX = relativeX - cropSize / 2;
let newY = relativeY - cropSize / 2;
// 限制裁剪框在图片范围内(确保左右和上下都能正常拖动)
newX = Math.max(0, Math.min(newX, Math.max(0, canvasWidth - cropSize)));
newY = Math.max(0, Math.min(newY, Math.max(0, canvasHeight - cropSize)));
// 确保数值类型正确
this.setData({
cropX: Math.round(newX),
cropY: Math.round(newY)
});
},
// 更新图片容器位置(提取公共方法)
_updateImageWrapperRect() {
const query = wx.createSelectorQuery().in(this);
query.select('.image-wrapper').boundingClientRect((rect) => {
if (rect) {
this._imageWrapperRect = rect;
// 更新 canvasWidth 和 canvasHeight
this.setData({
canvasWidth: rect.width,
canvasHeight: rect.height
});
}
}).exec();
},
// 裁剪框拖动结束
onCropMoveEnd(e) {
// 可以在这里添加拖动结束的逻辑,比如保存位置等
// 目前不需要特殊处理
},
// 切换模糊模式
toggleBlur() {
this.setData({
isBlurring: !this.data.isBlurring,
isCropping: false
});
},
// 模糊触摸处理
onBlurTouch(e) {
if (!this.data.isBlurring || !this.data.canvasContext) return;
const touch = e.touches[0];
const { clientX, clientY } = touch;
const { blurSize, canvasContext } = this.data;
// 绘制模糊块
canvasContext.fillStyle = 'rgba(128, 128, 128, 0.6)';
canvasContext.beginPath();
canvasContext.arc(clientX, clientY, blurSize / 2, 0, 2 * Math.PI);
canvasContext.fill();
canvasContext.draw();
// 保存模糊数据
const blurData = this.data.blurData || [];
blurData.push({ x: clientX, y: clientY, size: blurSize });
this.saveEditState();
this.setData({
blurData: blurData,
hasEdits: true
});
},
// 保存编辑状态(用于撤回)
saveEditState() {
const history = this.data.editHistory || [];
history.push({
imagePath: this.data.currentImagePath,
rotateAngle: this.data.rotateAngle,
blurData: JSON.parse(JSON.stringify(this.data.blurData || []))
});
// 限制历史记录数量
if (history.length > 10) {
history.shift();
}
this.setData({
editHistory: history
});
},
// 撤回操作
undoEdit() {
const history = this.data.editHistory || [];
if (history.length === 0) {
wx.showToast({
title: '没有可撤回的操作',
icon: 'none'
});
return;
}
const lastState = history.pop();
this.setData({
currentImagePath: lastState.imagePath,
rotateAngle: lastState.rotateAngle,
blurData: lastState.blurData,
editHistory: history,
hasEdits: history.length > 0
});
// 重新渲染模糊
if (this.data.canvasContext && lastState.blurData.length > 0) {
this.reRenderBlur();
}
},
// 重新渲染模糊
reRenderBlur() {
const { canvasContext, blurData, canvasWidth, canvasHeight } = this.data;
if (!canvasContext) return;
canvasContext.clearRect(0, 0, canvasWidth, canvasHeight);
blurData.forEach(point => {
canvasContext.fillStyle = 'rgba(128, 128, 128, 0.6)';
canvasContext.beginPath();
canvasContext.arc(point.x, point.y, point.size / 2, 0, 2 * Math.PI);
canvasContext.fill();
});
canvasContext.draw();
},
// 应用裁剪
async applyCrop() {
if (!this.data.isCropping) {
return Promise.resolve();
}
wx.showLoading({ title: '裁剪中...' });
try {
const { currentImagePath, cropX, cropY, cropSize, imageInfo, rotateAngle } = this.data;
// 验证必要数据
if (!currentImagePath) {
throw new Error('图片路径无效');
}
if (!imageInfo || !imageInfo.width || !imageInfo.height) {
throw new Error('图片信息不完整');
}
if (!cropX || !cropY || !cropSize || cropSize <= 0) {
throw new Error('裁剪参数无效');
}
// 创建Canvas进行裁剪使用 Promise 包装 exec
const canvasRes = await new Promise((resolve, reject) => {
const query = wx.createSelectorQuery().in(this);
query.select('#cropCanvas')
.fields({ node: true, size: true })
.exec((res) => {
if (!res || !res[0] || !res[0].node) {
reject(new Error('Canvas初始化失败'));
} else {
resolve(res[0]);
}
});
});
const canvas = canvasRes.node;
const ctx = canvas.getContext('2d');
// 设置Canvas尺寸为裁剪大小
const roundedCropSize = Math.round(cropSize);
canvas.width = roundedCropSize;
canvas.height = roundedCropSize;
// 加载图片(添加超时处理)
const img = canvas.createImage();
const imageLoadPromise = new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('图片加载超时'));
}, 10000); // 10秒超时
img.onload = () => {
clearTimeout(timeout);
resolve();
};
img.onerror = (err) => {
clearTimeout(timeout);
reject(new Error('图片加载失败: ' + (err.message || '未知错误')));
};
});
img.src = currentImagePath;
await imageLoadPromise;
// 计算裁剪区域在原图中的比例
const scaleX = imageInfo.width / imageInfo.displayWidth;
const scaleY = imageInfo.height / imageInfo.displayHeight;
const sourceX = Math.max(0, Math.round(cropX * scaleX));
const sourceY = Math.max(0, Math.round(cropY * scaleY));
const sourceSize = Math.min(
Math.round(cropSize * scaleX),
imageInfo.width - sourceX,
imageInfo.height - sourceY
);
// 清空画布
ctx.clearRect(0, 0, roundedCropSize, roundedCropSize);
// 绘制圆形裁剪
ctx.save();
ctx.beginPath();
ctx.arc(roundedCropSize / 2, roundedCropSize / 2, roundedCropSize / 2, 0, 2 * Math.PI);
ctx.clip();
// 应用旋转
if (rotateAngle !== 0) {
ctx.translate(roundedCropSize / 2, roundedCropSize / 2);
ctx.rotate(rotateAngle * Math.PI / 180);
ctx.translate(-roundedCropSize / 2, -roundedCropSize / 2);
}
// 绘制裁剪后的图片
ctx.drawImage(img, sourceX, sourceY, sourceSize, sourceSize, 0, 0, roundedCropSize, roundedCropSize);
ctx.restore();
// 导出图片
const exportResult = await new Promise((resolve, reject) => {
wx.canvasToTempFilePath({
canvas: canvas,
success: resolve,
fail: reject
}, this);
});
if (!exportResult || !exportResult.tempFilePath) {
throw new Error('生成裁剪图片失败');
}
// 验证生成的文件
const fileInfo = await new Promise((resolve, reject) => {
wx.getFileInfo({
filePath: exportResult.tempFilePath,
success: resolve,
fail: reject
});
});
if (!fileInfo || !fileInfo.size || fileInfo.size === 0) {
throw new Error('裁剪后的文件无效');
}
// 更新状态
this.saveEditState();
this.setData({
currentImagePath: exportResult.tempFilePath,
isCropping: false,
hasEdits: true
});
wx.hideLoading();
console.log('裁剪成功,新图片路径:', exportResult.tempFilePath);
} catch (error) {
wx.hideLoading();
console.error('裁剪失败:', error);
wx.showToast({
title: error.message || '裁剪失败,请重试',
icon: 'none',
duration: 2000
});
throw error; // 重新抛出错误,让调用者处理
}
},
// 完成编辑并上传
async confirmEdit() {
wx.showLoading({ title: '处理中...' });
try {
let finalImagePath = this.data.currentImagePath;
// 如果有裁剪,先应用裁剪
if (this.data.isCropping) {
try {
await this.applyCrop();
finalImagePath = this.data.currentImagePath;
// 验证裁剪后的文件路径
if (!finalImagePath) {
throw new Error('裁剪后未获取到图片路径');
}
// 再次验证文件是否存在
const fileInfo = await new Promise((resolve, reject) => {
wx.getFileInfo({
filePath: finalImagePath,
success: resolve,
fail: reject
});
});
if (!fileInfo || !fileInfo.size || fileInfo.size === 0) {
throw new Error('裁剪后的文件无效,请重试');
}
console.log('裁剪完成,文件路径:', finalImagePath, '文件大小:', fileInfo.size);
} catch (cropError) {
wx.hideLoading();
console.error('裁剪失败:', cropError);
wx.showToast({
title: cropError.message || '裁剪失败,请重试',
icon: 'none',
duration: 2000
});
return; // 裁剪失败,直接返回,不继续上传
}
}
// 如果有旋转或模糊,需要合并处理
if (this.data.rotateAngle !== 0 || (this.data.blurData && this.data.blurData.length > 0)) {
finalImagePath = await this.mergeEdits();
}
// 验证文件路径
if (!finalImagePath) {
throw new Error('图片路径无效,请重新选择图片');
}
// 检查文件是否存在
try {
const fileInfo = await new Promise((resolve, reject) => {
wx.getFileInfo({
filePath: finalImagePath,
success: resolve,
fail: reject
});
});
console.log('文件信息:', fileInfo);
} catch (fileError) {
console.error('文件不存在或无法访问:', fileError);
throw new Error('图片文件不存在或已损坏,请重新选择');
}
// 上传头像
console.log('开始上传头像,文件路径:', finalImagePath);
// 更新加载提示
wx.showLoading({ title: '上传中...', mask: true });
// 添加上传超时处理60秒
const uploadPromise = cosManager.upload(finalImagePath, {
fileType: 'avatar',
enableDedup: true,
enableCompress: false,
compressOptions: {
maxWidth: 400,
maxHeight: 400,
targetSize: 30 * 1024
},
onProgress: (percent) => {
wx.showLoading({
title: `上传中 ${Math.round(percent)}%`,
mask: true
});
}
});
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => {
reject(new Error('上传超时,请检查网络后重试'));
}, 60000); // 60秒超时
});
const uploadResult = await Promise.race([uploadPromise, timeoutPromise]);
console.log('上传结果:', uploadResult);
// 检查上传结果
if (!uploadResult) {
throw new Error('上传失败:未获取到上传结果');
}
// 检查 success 字段
if (uploadResult.success !== true) {
const errorMsg = uploadResult?.error || uploadResult?.message || '上传失败';
console.error('上传失败:', errorMsg, uploadResult);
throw new Error(errorMsg);
}
// 检查 fileUrl 字段
const avatarUrl = uploadResult.fileUrl || uploadResult.originalUrl;
if (!avatarUrl) {
console.error('上传结果中没有 fileUrl:', uploadResult);
throw new Error('上传失败未获取到文件URL');
}
console.log('头像上传成功URL:', avatarUrl);
// 更新用户信息
await apiClient.updateUserProfile({ avatar: avatarUrl });
// 更新全局用户信息
const app = getApp();
if (app.globalData.userInfo && app.globalData.userInfo.user) {
app.globalData.userInfo.user.avatar = avatarUrl;
app.globalData.userInfo.user.avatarUrl = avatarUrl;
}
// 同步到 NIM
try {
const nimUserManager = require('../../../utils/nim-user-manager.js');
await nimUserManager.updateSelfUserInfo({
avatar: avatarUrl
});
console.log('✅ 头像已同步到 NIM');
} catch (nimError) {
console.error('⚠️ 同步头像到 NIM 失败:', nimError);
}
// 缓存头像
let cachedUrl = avatarUrl;
try {
const imageCacheManager = require('../../../utils/image-cache-manager.js');
cachedUrl = await imageCacheManager.cacheImage(avatarUrl, 'avatar');
console.log('✅ 头像已缓存:', cachedUrl);
} catch (cacheError) {
console.error('⚠️ 头像缓存失败:', cacheError);
}
wx.hideLoading();
wx.showToast({
title: '头像更新成功',
icon: 'success'
});
// 通知上一页更新头像
const pages = getCurrentPages();
console.log('当前页面栈:', pages.map(p => p.route));
// 查找 profile 页面
let profilePage = null;
for (let i = pages.length - 2; i >= 0; i--) {
if (pages[i].route === 'subpackages/profile/profile/profile') {
profilePage = pages[i];
break;
}
}
if (profilePage) {
// 如果找到 profile 页面,更新头像
if (typeof profilePage.onAvatarUpdated === 'function') {
profilePage.onAvatarUpdated({
avatarUrl: avatarUrl,
cachedUrl: cachedUrl,
serverUrl: avatarUrl,
userInfo: {
user: {
...app.globalData.userInfo?.user,
avatar: avatarUrl,
avatarUrl: avatarUrl
}
}
});
}
// 如果上一页有 syncGlobalUserInfo 方法,也调用它
if (typeof profilePage.syncGlobalUserInfo === 'function') {
profilePage.syncGlobalUserInfo(cachedUrl || avatarUrl);
}
// 如果上一页有 loadUserData 方法,调用它刷新数据
if (typeof profilePage.loadUserData === 'function') {
profilePage.loadUserData();
}
} else {
// 如果找不到 profile 页面,尝试从上一页更新
const prevPage = pages[pages.length - 2];
if (prevPage) {
if (typeof prevPage.onAvatarUpdated === 'function') {
prevPage.onAvatarUpdated({
avatarUrl: avatarUrl,
cachedUrl: cachedUrl,
serverUrl: avatarUrl,
userInfo: {
user: {
...app.globalData.userInfo?.user,
avatar: avatarUrl,
avatarUrl: avatarUrl
}
}
});
}
if (typeof prevPage.syncGlobalUserInfo === 'function') {
prevPage.syncGlobalUserInfo(cachedUrl || avatarUrl);
}
if (typeof prevPage.loadUserData === 'function') {
prevPage.loadUserData();
}
}
}
// 返回上一页(确保返回到 profile 页面)
setTimeout(() => {
// 检查页面栈,确保返回到正确的页面
const currentPages = getCurrentPages();
console.log('返回前的页面栈:', currentPages.map(p => p.route));
// 检查上一页是否是 profile 页面
if (currentPages.length >= 2) {
const prevPageRoute = currentPages[currentPages.length - 2].route;
console.log('上一页路由:', prevPageRoute);
// 如果上一页是 profile 页面,直接返回
if (prevPageRoute === 'subpackages/profile/profile/profile') {
wx.navigateBack({
delta: 1,
success: () => {
console.log('成功返回到 profile 页面');
},
fail: (err) => {
console.error('返回失败:', err);
// 如果返回失败,直接跳转到 profile 页面
wx.redirectTo({
url: '/subpackages/profile/profile/profile',
success: () => {
console.log('使用 redirectTo 成功跳转到 profile 页面');
},
fail: (redirectErr) => {
console.error('redirectTo 也失败:', redirectErr);
}
});
}
});
} else {
// 如果上一页不是 profile 页面,直接跳转到 profile 页面
console.log('上一页不是 profile 页面,直接跳转到 profile');
wx.redirectTo({
url: '/subpackages/profile/profile/profile',
success: () => {
console.log('成功跳转到 profile 页面');
},
fail: (err) => {
console.error('跳转到 profile 失败:', err);
}
});
}
} else {
// 如果页面栈中只有当前页面,直接跳转到 profile
console.log('页面栈中只有当前页面,直接跳转到 profile');
wx.redirectTo({
url: '/subpackages/profile/profile/profile',
success: () => {
console.log('成功跳转到 profile 页面');
},
fail: (err) => {
console.error('跳转到 profile 失败:', err);
}
});
}
}, 1000);
} catch (error) {
wx.hideLoading();
console.error('上传失败,详细错误:', error);
console.error('错误堆栈:', error.stack);
// 提供更详细的错误信息
let errorMessage = '上传失败';
if (error.message) {
errorMessage = error.message;
} else if (typeof error === 'string') {
errorMessage = error;
} else if (error.errMsg) {
errorMessage = error.errMsg;
}
wx.showToast({
title: errorMessage,
icon: 'none',
duration: 3000
});
}
},
// 合并所有编辑效果
async mergeEdits() {
return new Promise((resolve, reject) => {
const query = wx.createSelectorQuery().in(this);
query.select('#mergeCanvas')
.fields({ node: true, size: true })
.exec((res) => {
if (!res || !res[0] || !res[0].node) {
reject(new Error('Canvas初始化失败'));
return;
}
const canvas = res[0].node;
const ctx = canvas.getContext('2d');
const { imageInfo, rotateAngle, blurData } = this.data;
// 设置Canvas尺寸
canvas.width = imageInfo.width;
canvas.height = imageInfo.height;
// 加载图片
const img = canvas.createImage();
img.src = this.data.currentImagePath;
img.onload = () => {
// 应用旋转
ctx.save();
ctx.translate(imageInfo.width / 2, imageInfo.height / 2);
ctx.rotate(rotateAngle * Math.PI / 180);
ctx.translate(-imageInfo.width / 2, -imageInfo.height / 2);
// 绘制图片
ctx.drawImage(img, 0, 0, imageInfo.width, imageInfo.height);
ctx.restore();
// 应用模糊(如果有)
if (blurData && blurData.length > 0) {
const scaleX = imageInfo.width / imageInfo.displayWidth;
const scaleY = imageInfo.height / imageInfo.displayHeight;
blurData.forEach(point => {
ctx.fillStyle = 'rgba(128, 128, 128, 0.6)';
ctx.beginPath();
ctx.arc(point.x * scaleX, point.y * scaleY, point.size * scaleX / 2, 0, 2 * Math.PI);
ctx.fill();
});
}
// 导出图片
wx.canvasToTempFilePath({
canvas: canvas,
success: (res) => {
resolve(res.tempFilePath);
},
fail: reject
}, this);
};
img.onerror = reject;
});
});
},
// 取消编辑
cancelEdit() {
wx.navigateBack();
}
});