findme-miniprogram-frontend/subpackages/profile/avatar-edit/avatar-edit.js
2025-12-27 17:16:03 +08:00

903 lines
28 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 头像编辑页面
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();
}
});