1568 lines
50 KiB
JavaScript
1568 lines
50 KiB
JavaScript
|
|
const config = require('../../../config/config.js');
|
|||
|
|
const apiClient = require('../../../utils/api-client.js');
|
|||
|
|
const cosManager = require('../../../utils/cos-manager.js');
|
|||
|
|
Page({
|
|||
|
|
data: {
|
|||
|
|
currentTool: '',
|
|||
|
|
isCanvasInit: false,
|
|||
|
|
canvasContext: null,
|
|||
|
|
canvas: null,
|
|||
|
|
systemInfo: {},
|
|||
|
|
|
|||
|
|
isRotated: false,
|
|||
|
|
|
|||
|
|
// 相机状态
|
|||
|
|
cameraPosition: 'back', // 默认后置摄像头
|
|||
|
|
flashState: 'off', // 默认关闭闪光灯
|
|||
|
|
hasTakenPhoto: false, // 是否已拍摄照片
|
|||
|
|
|
|||
|
|
// 图片状态
|
|||
|
|
originalPhotoPath: '', // 原始照片路径
|
|||
|
|
currentPhotoPath: '', // 当前编辑的照片路径
|
|||
|
|
imageInfo: null, // 图片信息
|
|||
|
|
imageScale: 1, // 图片缩放比例
|
|||
|
|
imagePosition: { // 图片在屏幕上的位置
|
|||
|
|
left: 0,
|
|||
|
|
top: 0,
|
|||
|
|
width: 0,
|
|||
|
|
height: 0
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 裁剪相关
|
|||
|
|
isCropping: false,
|
|||
|
|
cropBox: {
|
|||
|
|
left: 100,
|
|||
|
|
top: 200,
|
|||
|
|
width: 400,
|
|||
|
|
height: 400,
|
|||
|
|
right: 500,
|
|||
|
|
bottom: 600
|
|||
|
|
},
|
|||
|
|
cropStartPos: null,
|
|||
|
|
draggingHandle: null,
|
|||
|
|
isMovingCropBox: false,
|
|||
|
|
containerRect: null, // 图片容器的位置信息
|
|||
|
|
|
|||
|
|
// 旋转相关
|
|||
|
|
rotateAngle: 0,
|
|||
|
|
|
|||
|
|
// 马赛克相关
|
|||
|
|
mosaicData: [], // 存储马赛克数据
|
|||
|
|
hasMosaic: false, // 是否有马赛克
|
|||
|
|
mosaicSize: 25, // 初始马赛克大小
|
|||
|
|
sizePercent: 50, // 滑块初始位置百分比
|
|||
|
|
dragStartX: 0, // 触摸起始 X 坐标
|
|||
|
|
canvasWidth: 0, // 画布宽度
|
|||
|
|
canvasHeight: 0, // 画布高度
|
|||
|
|
|
|||
|
|
// 编辑状态
|
|||
|
|
hasEdits: false, // 是否有编辑操作
|
|||
|
|
|
|||
|
|
imageRect: {
|
|||
|
|
left: 0,
|
|||
|
|
top: 0,
|
|||
|
|
width: 0,
|
|||
|
|
height: 0
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 获取容器位置(在开启裁剪时调用)
|
|||
|
|
updateContainerRect() {
|
|||
|
|
return new Promise((resolve) => {
|
|||
|
|
const query = wx.createSelectorQuery().in(this);
|
|||
|
|
query.select('.image-container').boundingClientRect((rect) => {
|
|||
|
|
if (rect) {
|
|||
|
|
this.setData({ containerRect: rect });
|
|||
|
|
resolve(rect);
|
|||
|
|
} else {
|
|||
|
|
resolve(null);
|
|||
|
|
}
|
|||
|
|
}).exec();
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 拖动整个裁剪框(优化版本,使用缓存的容器位置)
|
|||
|
|
startCropMove(e) {
|
|||
|
|
if (!this.data.isCropping) return;
|
|||
|
|
|
|||
|
|
const touch = e.touches[0];
|
|||
|
|
const { cropBox, imagePosition, containerRect } = this.data;
|
|||
|
|
|
|||
|
|
if (!cropBox || !imagePosition) return;
|
|||
|
|
|
|||
|
|
// 保存触摸坐标,避免异步回调中失效
|
|||
|
|
const touchX = touch.clientX;
|
|||
|
|
const touchY = touch.clientY;
|
|||
|
|
|
|||
|
|
// 优先使用缓存的容器位置,避免异步延迟
|
|||
|
|
if (containerRect) {
|
|||
|
|
const relativeX = touchX - containerRect.left;
|
|||
|
|
const relativeY = touchY - containerRect.top;
|
|||
|
|
|
|||
|
|
// 检查是否点击在裁剪框内
|
|||
|
|
const isInsideCropBox = relativeX >= cropBox.left && relativeX <= cropBox.right &&
|
|||
|
|
relativeY >= cropBox.top && relativeY <= cropBox.bottom;
|
|||
|
|
|
|||
|
|
if (isInsideCropBox) {
|
|||
|
|
// 记录起始位置
|
|||
|
|
this.setData({
|
|||
|
|
cropStartPos: {
|
|||
|
|
x: touchX,
|
|||
|
|
y: touchY,
|
|||
|
|
relativeX: relativeX,
|
|||
|
|
relativeY: relativeY,
|
|||
|
|
cropX: cropBox.left,
|
|||
|
|
cropY: cropBox.top
|
|||
|
|
},
|
|||
|
|
draggingHandle: null,
|
|||
|
|
isMovingCropBox: true
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果没有缓存的容器位置,异步获取(兼容旧代码)
|
|||
|
|
const query = wx.createSelectorQuery().in(this);
|
|||
|
|
query.select('.image-container').boundingClientRect((rect) => {
|
|||
|
|
if (!rect) return;
|
|||
|
|
|
|||
|
|
const relativeX = touchX - rect.left;
|
|||
|
|
const relativeY = touchY - rect.top;
|
|||
|
|
|
|||
|
|
// 检查是否点击在裁剪框内
|
|||
|
|
const isInsideCropBox = relativeX >= cropBox.left && relativeX <= cropBox.right &&
|
|||
|
|
relativeY >= cropBox.top && relativeY <= cropBox.bottom;
|
|||
|
|
|
|||
|
|
if (isInsideCropBox) {
|
|||
|
|
// 记录起始位置,并缓存容器位置
|
|||
|
|
this.setData({
|
|||
|
|
cropStartPos: {
|
|||
|
|
x: touchX,
|
|||
|
|
y: touchY,
|
|||
|
|
relativeX: relativeX,
|
|||
|
|
relativeY: relativeY,
|
|||
|
|
cropX: cropBox.left,
|
|||
|
|
cropY: cropBox.top
|
|||
|
|
},
|
|||
|
|
draggingHandle: null,
|
|||
|
|
isMovingCropBox: true,
|
|||
|
|
containerRect: rect
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}).exec();
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 滑动 slider 时触发
|
|||
|
|
onSliderChange(e) {
|
|||
|
|
const percent = e.detail.value;
|
|||
|
|
const newSize = 5 + (percent / 100) * 25;
|
|||
|
|
this.setData({
|
|||
|
|
sizePercent: percent,
|
|||
|
|
mosaicSize: newSize
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
onLoad() {
|
|||
|
|
try {
|
|||
|
|
const systemInfo = wx.getSystemInfoSync();
|
|||
|
|
this.setData({ systemInfo });
|
|||
|
|
this.systemInfo = systemInfo;
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error('获取系统信息失败:', err);
|
|||
|
|
}
|
|||
|
|
// 初始化双击检测变量(不使用 setData,避免不必要的渲染)
|
|||
|
|
this.lastTapTime = 0;
|
|||
|
|
this.tapTimer = null;
|
|||
|
|
// 移除手动权限检查,让 <camera> 组件自动处理权限请求
|
|||
|
|
// 这样可以避免权限弹窗弹出两次(组件初始化时会自动请求权限)
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
toggleFlash() {
|
|||
|
|
const newState = this.data.flashState === 'off' ? 'torch' : 'off';
|
|||
|
|
this.setData({ flashState: newState });
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
switchCamera() {
|
|||
|
|
const newPosition = this.data.cameraPosition === 'back' ? 'front' : 'back';
|
|||
|
|
this.setData({ cameraPosition: newPosition });
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 双击切换摄像头(优化版本,使用局部变量,响应更快)
|
|||
|
|
onCameraDoubleTap(e) {
|
|||
|
|
const currentTime = Date.now();
|
|||
|
|
const timeDiff = currentTime - this.lastTapTime;
|
|||
|
|
|
|||
|
|
// 清除之前的定时器
|
|||
|
|
if (this.tapTimer) {
|
|||
|
|
clearTimeout(this.tapTimer);
|
|||
|
|
this.tapTimer = null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果两次点击间隔小于 250ms,认为是双击
|
|||
|
|
if (timeDiff < 250 && this.lastTapTime !== 0) {
|
|||
|
|
// 立即执行切换摄像头
|
|||
|
|
this.switchCamera();
|
|||
|
|
// 重置时间,防止连续触发
|
|||
|
|
this.lastTapTime = 0;
|
|||
|
|
} else {
|
|||
|
|
// 记录本次点击时间(使用实例变量,不触发 setData)
|
|||
|
|
this.lastTapTime = currentTime;
|
|||
|
|
// 250ms 后重置,防止误触发
|
|||
|
|
this.tapTimer = setTimeout(() => {
|
|||
|
|
this.lastTapTime = 0;
|
|||
|
|
this.tapTimer = null;
|
|||
|
|
}, 250);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
chooseFromAlbum() {
|
|||
|
|
wx.chooseMedia({
|
|||
|
|
count: 1,
|
|||
|
|
mediaType: ['image'],
|
|||
|
|
sourceType: ['album'],
|
|||
|
|
success: (res) => {
|
|||
|
|
const tempImagePath = res.tempFiles[0].tempFilePath;
|
|||
|
|
wx.getImageInfo({
|
|||
|
|
src: tempImagePath,
|
|||
|
|
success: (info) => {
|
|||
|
|
this.setData({
|
|||
|
|
originalPhotoPath: tempImagePath,
|
|||
|
|
currentPhotoPath: tempImagePath,
|
|||
|
|
imageInfo: info,
|
|||
|
|
hasTakenPhoto: true,
|
|||
|
|
hasEdits: false,
|
|||
|
|
rotateAngle: 0,
|
|||
|
|
mosaicData: [],
|
|||
|
|
hasMosaic: false,
|
|||
|
|
// 选择图片后初始化裁剪框
|
|||
|
|
cropBox: this.calculateInitialCropBox(info)
|
|||
|
|
}, () => {
|
|||
|
|
this.initMosaicCanvas();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
fail: (err) => {
|
|||
|
|
console.error('选择图片失败:', err);
|
|||
|
|
wx.showToast({
|
|||
|
|
title: '选择图片失败',
|
|||
|
|
icon: 'none'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
takePhoto() {
|
|||
|
|
const ctx = wx.createCameraContext();
|
|||
|
|
|
|||
|
|
ctx.takePhoto({
|
|||
|
|
quality: 'high',
|
|||
|
|
success: (res) => {
|
|||
|
|
wx.getImageInfo({
|
|||
|
|
src: res.tempImagePath,
|
|||
|
|
success: (info) => {
|
|||
|
|
this.setData({
|
|||
|
|
originalPhotoPath: res.tempImagePath,
|
|||
|
|
currentPhotoPath: res.tempImagePath,
|
|||
|
|
imageInfo: info,
|
|||
|
|
hasTakenPhoto: true,
|
|||
|
|
hasEdits: false,
|
|||
|
|
rotateAngle: 0,
|
|||
|
|
mosaicData: [],
|
|||
|
|
hasMosaic: false,
|
|||
|
|
// 拍照后初始化裁剪框
|
|||
|
|
cropBox: this.calculateInitialCropBox()
|
|||
|
|
}, () => {
|
|||
|
|
this.initMosaicCanvas();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
fail: (err) => {
|
|||
|
|
console.error('拍照失败:', err);
|
|||
|
|
wx.showToast({
|
|||
|
|
title: '拍照失败',
|
|||
|
|
icon: 'none'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 计算初始裁剪框(确保在可视区域内)
|
|||
|
|
calculateInitialCropBox(imageInfo) {
|
|||
|
|
const targetImageInfo = imageInfo || this.data.imageInfo;
|
|||
|
|
const imagePosition = this.data.imagePosition || { width: 0, height: 0 };
|
|||
|
|
|
|||
|
|
if (!targetImageInfo || !imagePosition.width || !imagePosition.height) {
|
|||
|
|
return { left: 0, top: 0, width: 100, height: 100, right: 100, bottom: 100 };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 使用图片显示尺寸(相对于容器)
|
|||
|
|
const displayWidth = imagePosition.width;
|
|||
|
|
const displayHeight = imagePosition.height;
|
|||
|
|
const minCropSize = 80; // 最小裁剪尺寸
|
|||
|
|
|
|||
|
|
// 初始裁剪框大小为图片的70%,但确保不小于最小尺寸
|
|||
|
|
const cropSize = Math.max(
|
|||
|
|
minCropSize,
|
|||
|
|
Math.min(displayWidth, displayHeight) * 0.7
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 居中显示,确保在图片范围内
|
|||
|
|
let cropX = (displayWidth - cropSize) / 2;
|
|||
|
|
let cropY = (displayHeight - cropSize) / 2;
|
|||
|
|
|
|||
|
|
// 确保裁剪框完全在图片可视区域内
|
|||
|
|
cropX = Math.max(0, Math.min(cropX, displayWidth - cropSize));
|
|||
|
|
cropY = Math.max(0, Math.min(cropY, displayHeight - cropSize));
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
left: cropX,
|
|||
|
|
top: cropY,
|
|||
|
|
width: cropSize,
|
|||
|
|
height: cropSize,
|
|||
|
|
right: cropX + cropSize,
|
|||
|
|
bottom: cropY + cropSize
|
|||
|
|
};
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
onImageLoaded(e) {
|
|||
|
|
const { width, height } = e.detail;
|
|||
|
|
const { systemInfo, imageScale } = this.data;
|
|||
|
|
|
|||
|
|
const scale = imageScale || Math.min(
|
|||
|
|
systemInfo.windowWidth / width,
|
|||
|
|
systemInfo.windowHeight * 0.8 / height
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
const displayWidth = width * scale;
|
|||
|
|
const displayHeight = height * scale;
|
|||
|
|
const imageLeft = (systemInfo.windowWidth - displayWidth) / 2;
|
|||
|
|
const imageTop = (systemInfo.windowHeight - displayHeight) / 2;
|
|||
|
|
|
|||
|
|
// 获取图片容器的实际尺寸(用于裁剪框计算)
|
|||
|
|
const query = wx.createSelectorQuery().in(this);
|
|||
|
|
query.select('.image-container').boundingClientRect((rect) => {
|
|||
|
|
// 容器尺寸(用于裁剪框计算,裁剪框相对于容器定位)
|
|||
|
|
const containerWidth = rect ? rect.width : displayWidth;
|
|||
|
|
const containerHeight = rect ? rect.height : displayHeight;
|
|||
|
|
|
|||
|
|
// 更新图片位置信息
|
|||
|
|
// imageRect: 绝对位置(用于显示)
|
|||
|
|
// imagePosition: 相对于容器的位置(用于裁剪框计算)
|
|||
|
|
this.setData({
|
|||
|
|
imageRect: { left: imageLeft, top: imageTop, width: displayWidth, height: displayHeight },
|
|||
|
|
imagePosition: {
|
|||
|
|
left: 0, // 相对于容器
|
|||
|
|
top: 0, // 相对于容器
|
|||
|
|
width: containerWidth,
|
|||
|
|
height: containerHeight
|
|||
|
|
},
|
|||
|
|
imageInfo: { width, height },
|
|||
|
|
originalImageWidth: width,
|
|||
|
|
originalImageHeight: height,
|
|||
|
|
rotateAngle: 0,
|
|||
|
|
imageScale: 1,
|
|||
|
|
containerRect: rect || { left: 0, top: 0, width: containerWidth, height: containerHeight }
|
|||
|
|
}, () => {
|
|||
|
|
// 图片加载后若处于裁剪模式,重新初始化裁剪框
|
|||
|
|
if (this.data.isCropping) {
|
|||
|
|
const newCropBox = this.calculateInitialCropBox();
|
|||
|
|
if (newCropBox.width > 0 && newCropBox.height > 0) {
|
|||
|
|
this.setData({ cropBox: newCropBox });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}).exec();
|
|||
|
|
|
|||
|
|
// 初始化马赛克画布
|
|||
|
|
if (this.data.currentTool === 'mosaic' || this.data.currentTool === 'eraser') {
|
|||
|
|
this.initMosaicCanvas();
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
initMosaicCanvas() {
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
const query = wx.createSelectorQuery().in(this);
|
|||
|
|
query.select('#mosaicCanvas')
|
|||
|
|
.fields({ node: true, size: true })
|
|||
|
|
.exec((res) => {
|
|||
|
|
if (!res || res.length === 0 || !res[0]) {
|
|||
|
|
reject('未找到 Canvas 元素');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
const canvasNode = res[0].node;
|
|||
|
|
const ctx = canvasNode.getContext('2d');
|
|||
|
|
const { width, height } = this.data.imagePosition;
|
|||
|
|
canvasNode.width = width;
|
|||
|
|
canvasNode.height = height;
|
|||
|
|
this.setData({
|
|||
|
|
canvasContext: ctx,
|
|||
|
|
canvas: canvasNode,
|
|||
|
|
canvasWidth: width,
|
|||
|
|
canvasHeight: height,
|
|||
|
|
isCanvasInit: true
|
|||
|
|
}, () => {
|
|||
|
|
// 初始化后重新渲染马赛克
|
|||
|
|
this.reRenderMosaic();
|
|||
|
|
resolve();
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
toggleCropMode() {
|
|||
|
|
this.setData({
|
|||
|
|
isCropping: !this.data.isCropping,
|
|||
|
|
currentTool: ''
|
|||
|
|
}, () => {
|
|||
|
|
// 切换裁剪模式后,若开启裁剪则重新初始化裁剪框
|
|||
|
|
if (this.data.isCropping) {
|
|||
|
|
// 先获取容器位置,确保 imagePosition 已更新
|
|||
|
|
this.updateContainerRect().then(() => {
|
|||
|
|
const newCropBox = this.calculateInitialCropBox();
|
|||
|
|
if (newCropBox.width > 0 && newCropBox.height > 0) {
|
|||
|
|
this.setData({ cropBox: newCropBox });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 开始拖动角点(优化版本,使用缓存的容器位置)
|
|||
|
|
startCropDrag(e) {
|
|||
|
|
if (!this.data.isCropping) return;
|
|||
|
|
|
|||
|
|
const handle = e.currentTarget.dataset.handle;
|
|||
|
|
if (!handle) {
|
|||
|
|
console.warn('未找到 handle 属性:', e.currentTarget);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const touch = e.touches[0];
|
|||
|
|
if (!touch || !touch.clientX || !touch.clientY) {
|
|||
|
|
console.warn('触摸事件无效:', touch);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const { containerRect, cropBox } = this.data;
|
|||
|
|
|
|||
|
|
// 保存触摸坐标,避免异步回调中失效
|
|||
|
|
const touchX = touch.clientX;
|
|||
|
|
const touchY = touch.clientY;
|
|||
|
|
|
|||
|
|
// 优先使用缓存的容器位置,避免异步延迟
|
|||
|
|
if (containerRect && containerRect.left !== undefined && containerRect.top !== undefined) {
|
|||
|
|
const relativeX = touchX - containerRect.left;
|
|||
|
|
const relativeY = touchY - containerRect.top;
|
|||
|
|
|
|||
|
|
this.setData({
|
|||
|
|
cropStartPos: {
|
|||
|
|
x: touchX,
|
|||
|
|
y: touchY,
|
|||
|
|
relativeX: relativeX,
|
|||
|
|
relativeY: relativeY,
|
|||
|
|
cropX: cropBox?.left || 0,
|
|||
|
|
cropY: cropBox?.top || 0,
|
|||
|
|
cropRight: cropBox?.right || 0,
|
|||
|
|
cropBottom: cropBox?.bottom || 0
|
|||
|
|
},
|
|||
|
|
draggingHandle: handle,
|
|||
|
|
isMovingCropBox: false
|
|||
|
|
});
|
|||
|
|
console.log(`开始拖动角点: ${handle}`, { touchX, touchY, relativeX, relativeY, cropBox });
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果没有缓存的容器位置,使用估算值并立即响应,同时异步更新
|
|||
|
|
const systemInfo = this.data.systemInfo || wx.getSystemInfoSync();
|
|||
|
|
const estimatedRect = {
|
|||
|
|
left: 0,
|
|||
|
|
top: 0,
|
|||
|
|
width: systemInfo.windowWidth || 375,
|
|||
|
|
height: systemInfo.windowHeight || 667
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const relativeX = touchX - estimatedRect.left;
|
|||
|
|
const relativeY = touchY - estimatedRect.top;
|
|||
|
|
|
|||
|
|
// 立即设置拖动状态,使用估算的容器位置
|
|||
|
|
this.setData({
|
|||
|
|
cropStartPos: {
|
|||
|
|
x: touchX,
|
|||
|
|
y: touchY,
|
|||
|
|
relativeX: relativeX,
|
|||
|
|
relativeY: relativeY,
|
|||
|
|
cropX: cropBox?.left || 0,
|
|||
|
|
cropY: cropBox?.top || 0,
|
|||
|
|
cropRight: cropBox?.right || 0,
|
|||
|
|
cropBottom: cropBox?.bottom || 0
|
|||
|
|
},
|
|||
|
|
draggingHandle: handle,
|
|||
|
|
isMovingCropBox: false,
|
|||
|
|
containerRect: estimatedRect // 临时使用估算值
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
console.log(`开始拖动角点: ${handle} (使用估算位置)`, { touchX, touchY, relativeX, relativeY, cropBox });
|
|||
|
|
|
|||
|
|
// 异步获取准确的容器位置,用于后续拖动
|
|||
|
|
const query = wx.createSelectorQuery().in(this);
|
|||
|
|
query.select('.image-container').boundingClientRect((rect) => {
|
|||
|
|
if (rect) {
|
|||
|
|
this.setData({ containerRect: rect });
|
|||
|
|
console.log('容器位置已更新:', rect);
|
|||
|
|
}
|
|||
|
|
}).exec();
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 处理裁剪框拖动(优化版本,确保容器位置可用)
|
|||
|
|
onCropTouchMove(e) {
|
|||
|
|
if (!this.data.isCropping || !this.data.cropStartPos) return;
|
|||
|
|
|
|||
|
|
const touch = e.touches[0];
|
|||
|
|
const { cropBox, imagePosition, containerRect } = this.data;
|
|||
|
|
|
|||
|
|
if (!cropBox || !imagePosition) return;
|
|||
|
|
|
|||
|
|
// 获取容器位置(优先使用缓存的)
|
|||
|
|
let currentRect = containerRect;
|
|||
|
|
if (!currentRect) {
|
|||
|
|
// 如果没有缓存的,使用默认值(基于系统信息估算)
|
|||
|
|
const systemInfo = this.data.systemInfo || wx.getSystemInfoSync();
|
|||
|
|
currentRect = {
|
|||
|
|
left: 0,
|
|||
|
|
top: 0,
|
|||
|
|
width: systemInfo.windowWidth || 375,
|
|||
|
|
height: systemInfo.windowHeight || 667
|
|||
|
|
};
|
|||
|
|
// 异步更新容器位置(用于下次使用)
|
|||
|
|
const query = wx.createSelectorQuery().in(this);
|
|||
|
|
query.select('.image-container').boundingClientRect((rect) => {
|
|||
|
|
if (rect) {
|
|||
|
|
this.setData({ containerRect: rect });
|
|||
|
|
}
|
|||
|
|
}).exec();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 直接使用clientX/clientY计算相对坐标
|
|||
|
|
const relativeX = touch.clientX - currentRect.left;
|
|||
|
|
const relativeY = touch.clientY - currentRect.top;
|
|||
|
|
|
|||
|
|
const maxWidth = imagePosition.width;
|
|||
|
|
const maxHeight = imagePosition.height;
|
|||
|
|
const minCropSize = 60; // 最小裁剪尺寸
|
|||
|
|
|
|||
|
|
let newCropBox = { ...cropBox };
|
|||
|
|
|
|||
|
|
// 拖动整个裁剪框(参考avatar-edit: touch.clientX - cropSize / 2)
|
|||
|
|
if (this.data.isMovingCropBox) {
|
|||
|
|
// 参考avatar-edit的简单计算方式
|
|||
|
|
const deltaX = relativeX - (this.data.cropStartPos.relativeX || 0);
|
|||
|
|
const deltaY = relativeY - (this.data.cropStartPos.relativeY || 0);
|
|||
|
|
|
|||
|
|
let newX = (this.data.cropStartPos.cropX || cropBox.left) + deltaX;
|
|||
|
|
let newY = (this.data.cropStartPos.cropY || cropBox.top) + deltaY;
|
|||
|
|
|
|||
|
|
// 参考avatar-edit的边界检查:Math.max(0, Math.min(newX, canvasWidth - cropSize))
|
|||
|
|
newX = Math.max(0, Math.min(newX, maxWidth - cropBox.width));
|
|||
|
|
newY = Math.max(0, Math.min(newY, maxHeight - cropBox.height));
|
|||
|
|
|
|||
|
|
newCropBox.left = newX;
|
|||
|
|
newCropBox.top = newY;
|
|||
|
|
newCropBox.right = newX + cropBox.width;
|
|||
|
|
newCropBox.bottom = newY + cropBox.height;
|
|||
|
|
|
|||
|
|
// 调整裁剪框大小(4个角点自由拖动)
|
|||
|
|
} else if (this.data.draggingHandle) {
|
|||
|
|
const handle = this.data.draggingHandle;
|
|||
|
|
|
|||
|
|
// 验证 handle 是否有效
|
|||
|
|
if (!handle || !['top-left', 'top-right', 'bottom-left', 'bottom-right'].includes(handle)) {
|
|||
|
|
console.warn('无效的拖动角点:', handle);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取初始裁剪框位置(从 cropStartPos 中获取,确保使用拖动开始时的位置)
|
|||
|
|
const startCropX = this.data.cropStartPos?.cropX ?? cropBox.left;
|
|||
|
|
const startCropY = this.data.cropStartPos?.cropY ?? cropBox.top;
|
|||
|
|
const startCropRight = this.data.cropStartPos?.cropRight ?? cropBox.right;
|
|||
|
|
const startCropBottom = this.data.cropStartPos?.cropBottom ?? cropBox.bottom;
|
|||
|
|
|
|||
|
|
// 获取初始触摸位置
|
|||
|
|
const startRelativeX = this.data.cropStartPos?.relativeX ?? relativeX;
|
|||
|
|
const startRelativeY = this.data.cropStartPos?.relativeY ?? relativeY;
|
|||
|
|
|
|||
|
|
// 计算相对于初始触摸点的偏移量
|
|||
|
|
const deltaX = relativeX - startRelativeX;
|
|||
|
|
const deltaY = relativeY - startRelativeY;
|
|||
|
|
|
|||
|
|
// 根据拖动的角点调整裁剪框
|
|||
|
|
switch (handle) {
|
|||
|
|
case 'top-left':
|
|||
|
|
// 左上角:调整 left 和 top,保持右下角不变
|
|||
|
|
newCropBox.left = Math.max(0, Math.min(startCropX + deltaX, startCropRight - minCropSize));
|
|||
|
|
newCropBox.top = Math.max(0, Math.min(startCropY + deltaY, startCropBottom - minCropSize));
|
|||
|
|
newCropBox.right = startCropRight;
|
|||
|
|
newCropBox.bottom = startCropBottom;
|
|||
|
|
break;
|
|||
|
|
case 'top-right':
|
|||
|
|
// 右上角:调整 right 和 top,保持左下角不变
|
|||
|
|
newCropBox.right = Math.max(startCropX + minCropSize, Math.min(startCropRight + deltaX, maxWidth));
|
|||
|
|
newCropBox.top = Math.max(0, Math.min(startCropY + deltaY, startCropBottom - minCropSize));
|
|||
|
|
newCropBox.left = startCropX;
|
|||
|
|
newCropBox.bottom = startCropBottom;
|
|||
|
|
break;
|
|||
|
|
case 'bottom-left':
|
|||
|
|
// 左下角:调整 left 和 bottom,保持右上角不变
|
|||
|
|
newCropBox.left = Math.max(0, Math.min(startCropX + deltaX, startCropRight - minCropSize));
|
|||
|
|
newCropBox.bottom = Math.max(startCropY + minCropSize, Math.min(startCropBottom + deltaY, maxHeight));
|
|||
|
|
newCropBox.right = startCropRight;
|
|||
|
|
newCropBox.top = startCropY;
|
|||
|
|
break;
|
|||
|
|
case 'bottom-right':
|
|||
|
|
// 右下角:调整 right 和 bottom,保持左上角不变
|
|||
|
|
newCropBox.right = Math.max(startCropX + minCropSize, Math.min(startCropRight + deltaX, maxWidth));
|
|||
|
|
newCropBox.bottom = Math.max(startCropY + minCropSize, Math.min(startCropBottom + deltaY, maxHeight));
|
|||
|
|
newCropBox.left = startCropX;
|
|||
|
|
newCropBox.top = startCropY;
|
|||
|
|
break;
|
|||
|
|
default:
|
|||
|
|
console.warn('未知的拖动角点:', handle);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 重新计算宽高
|
|||
|
|
newCropBox.width = newCropBox.right - newCropBox.left;
|
|||
|
|
newCropBox.height = newCropBox.bottom - newCropBox.top;
|
|||
|
|
|
|||
|
|
// 确保最小尺寸
|
|||
|
|
if (newCropBox.width < minCropSize) {
|
|||
|
|
if (handle.includes('left')) {
|
|||
|
|
newCropBox.left = newCropBox.right - minCropSize;
|
|||
|
|
} else {
|
|||
|
|
newCropBox.right = newCropBox.left + minCropSize;
|
|||
|
|
}
|
|||
|
|
newCropBox.width = minCropSize;
|
|||
|
|
}
|
|||
|
|
if (newCropBox.height < minCropSize) {
|
|||
|
|
if (handle.includes('top')) {
|
|||
|
|
newCropBox.top = newCropBox.bottom - minCropSize;
|
|||
|
|
} else {
|
|||
|
|
newCropBox.bottom = newCropBox.top + minCropSize;
|
|||
|
|
}
|
|||
|
|
newCropBox.height = minCropSize;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 最终边界检查:确保裁剪框完全在图片可视区域内
|
|||
|
|
// 如果超出左边界
|
|||
|
|
if (newCropBox.left < 0) {
|
|||
|
|
const offset = -newCropBox.left;
|
|||
|
|
newCropBox.left = 0;
|
|||
|
|
if (handle.includes('right')) {
|
|||
|
|
newCropBox.right = Math.min(newCropBox.right + offset, maxWidth);
|
|||
|
|
} else {
|
|||
|
|
newCropBox.right = Math.min(newCropBox.right - offset, maxWidth);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// 如果超出上边界
|
|||
|
|
if (newCropBox.top < 0) {
|
|||
|
|
const offset = -newCropBox.top;
|
|||
|
|
newCropBox.top = 0;
|
|||
|
|
if (handle.includes('bottom')) {
|
|||
|
|
newCropBox.bottom = Math.min(newCropBox.bottom + offset, maxHeight);
|
|||
|
|
} else {
|
|||
|
|
newCropBox.bottom = Math.min(newCropBox.bottom - offset, maxHeight);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// 如果超出右边界
|
|||
|
|
if (newCropBox.right > maxWidth) {
|
|||
|
|
const offset = newCropBox.right - maxWidth;
|
|||
|
|
newCropBox.right = maxWidth;
|
|||
|
|
if (handle.includes('left')) {
|
|||
|
|
newCropBox.left = Math.max(0, newCropBox.left - offset);
|
|||
|
|
} else {
|
|||
|
|
newCropBox.left = Math.max(0, newCropBox.left + offset);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// 如果超出下边界
|
|||
|
|
if (newCropBox.bottom > maxHeight) {
|
|||
|
|
const offset = newCropBox.bottom - maxHeight;
|
|||
|
|
newCropBox.bottom = maxHeight;
|
|||
|
|
if (handle.includes('top')) {
|
|||
|
|
newCropBox.top = Math.max(0, newCropBox.top - offset);
|
|||
|
|
} else {
|
|||
|
|
newCropBox.top = Math.max(0, newCropBox.top + offset);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 重新计算宽高(边界调整后)
|
|||
|
|
newCropBox.width = newCropBox.right - newCropBox.left;
|
|||
|
|
newCropBox.height = newCropBox.bottom - newCropBox.top;
|
|||
|
|
} else {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新裁剪框
|
|||
|
|
this.setData({ cropBox: newCropBox });
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 裁剪框拖动结束(参考avatar-edit的简单实现)
|
|||
|
|
onCropTouchEnd(e) {
|
|||
|
|
this.setData({
|
|||
|
|
cropStartPos: null,
|
|||
|
|
draggingHandle: null,
|
|||
|
|
isMovingCropBox: false
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 应用裁剪(裁剪完成后直接上传,不预览)
|
|||
|
|
async applyCrop() {
|
|||
|
|
const { cropBox, currentPhotoPath, imageInfo, imagePosition } = this.data;
|
|||
|
|
|
|||
|
|
if (!imagePosition || !imagePosition.width || !imagePosition.height || !imageInfo) {
|
|||
|
|
wx.showToast({
|
|||
|
|
title: '图片信息不完整',
|
|||
|
|
icon: 'none'
|
|||
|
|
});
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!cropBox || !cropBox.width || !cropBox.height) {
|
|||
|
|
wx.showToast({
|
|||
|
|
title: '裁剪框无效',
|
|||
|
|
icon: 'none'
|
|||
|
|
});
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
wx.showLoading({
|
|||
|
|
title: '裁剪中...',
|
|||
|
|
mask: true
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 参考avatar-edit:使用页面中的canvas进行裁剪
|
|||
|
|
const query = wx.createSelectorQuery().in(this);
|
|||
|
|
query.select('#mergeCanvas')
|
|||
|
|
.fields({ node: true, size: true })
|
|||
|
|
.exec((res) => {
|
|||
|
|
if (!res || !res[0] || !res[0].node) {
|
|||
|
|
wx.hideLoading();
|
|||
|
|
wx.showToast({
|
|||
|
|
title: 'Canvas初始化失败',
|
|||
|
|
icon: 'none'
|
|||
|
|
});
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const canvas = res[0].node;
|
|||
|
|
const ctx = canvas.getContext('2d');
|
|||
|
|
|
|||
|
|
// 参考avatar-edit:计算裁剪区域在原图中的比例
|
|||
|
|
const scaleX = imageInfo.width / imagePosition.width;
|
|||
|
|
const scaleY = imageInfo.height / imagePosition.height;
|
|||
|
|
|
|||
|
|
// 参考avatar-edit:转换为原始图片的实际像素坐标
|
|||
|
|
const sourceX = Math.max(0, cropBox.left * scaleX);
|
|||
|
|
const sourceY = Math.max(0, cropBox.top * scaleY);
|
|||
|
|
const sourceWidth = Math.max(1, Math.min(cropBox.width * scaleX, imageInfo.width - sourceX));
|
|||
|
|
const sourceHeight = Math.max(1, Math.min(cropBox.height * scaleY, imageInfo.height - sourceY));
|
|||
|
|
|
|||
|
|
// 设置Canvas尺寸为裁剪大小
|
|||
|
|
const canvasWidth = Math.ceil(sourceWidth);
|
|||
|
|
const canvasHeight = Math.ceil(sourceHeight);
|
|||
|
|
canvas.width = canvasWidth;
|
|||
|
|
canvas.height = canvasHeight;
|
|||
|
|
|
|||
|
|
console.log('裁剪参数:', {
|
|||
|
|
cropBox,
|
|||
|
|
imageInfo,
|
|||
|
|
imagePosition,
|
|||
|
|
scaleX,
|
|||
|
|
scaleY,
|
|||
|
|
sourceX,
|
|||
|
|
sourceY,
|
|||
|
|
sourceWidth,
|
|||
|
|
sourceHeight,
|
|||
|
|
canvasWidth,
|
|||
|
|
canvasHeight
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 加载图片
|
|||
|
|
const img = canvas.createImage();
|
|||
|
|
|
|||
|
|
img.onerror = (err) => {
|
|||
|
|
wx.hideLoading();
|
|||
|
|
console.error('图片加载失败:', err);
|
|||
|
|
wx.showToast({
|
|||
|
|
title: '图片加载失败',
|
|||
|
|
icon: 'none'
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
img.onload = () => {
|
|||
|
|
try {
|
|||
|
|
// 参考avatar-edit:仅绘制裁剪选中的区域
|
|||
|
|
// drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
|
|||
|
|
ctx.drawImage(
|
|||
|
|
img,
|
|||
|
|
sourceX, // 原始图片中裁剪区域的起点X
|
|||
|
|
sourceY, // 原始图片中裁剪区域的起点Y
|
|||
|
|
sourceWidth, // 原始图片中裁剪区域的宽度
|
|||
|
|
sourceHeight, // 原始图片中裁剪区域的高度
|
|||
|
|
0, 0, // 画布上的起点坐标
|
|||
|
|
canvasWidth, // 画布上的宽度
|
|||
|
|
canvasHeight // 画布上的高度
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 导出图片并直接上传
|
|||
|
|
wx.canvasToTempFilePath({
|
|||
|
|
canvas: canvas,
|
|||
|
|
success: async (res) => {
|
|||
|
|
try {
|
|||
|
|
// 验证文件
|
|||
|
|
const fileInfo = await new Promise((resolve, reject) => {
|
|||
|
|
wx.getFileInfo({
|
|||
|
|
filePath: res.tempFilePath,
|
|||
|
|
success: resolve,
|
|||
|
|
fail: reject
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (!fileInfo || !fileInfo.size || fileInfo.size === 0) {
|
|||
|
|
throw new Error('裁剪后的文件无效');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 裁剪完成,直接上传(不预览)
|
|||
|
|
wx.hideLoading();
|
|||
|
|
wx.showLoading({
|
|||
|
|
title: '上传中...',
|
|||
|
|
mask: true
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 调用原有的上传方法
|
|||
|
|
const uploadResult = await cosManager.upload(res.tempFilePath, {
|
|||
|
|
fileType: 'image',
|
|||
|
|
enableDedup: false,
|
|||
|
|
onProgress: (percent) => {
|
|||
|
|
wx.showLoading({
|
|||
|
|
title: `上传中 ${Math.round(percent)}%`,
|
|||
|
|
mask: true
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
enableCompress: false,
|
|||
|
|
compressOptions: {
|
|||
|
|
maxWidth: 400,
|
|||
|
|
maxHeight: 400,
|
|||
|
|
targetSize: 20 * 1024
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (!uploadResult || !uploadResult.success || !uploadResult.fileUrl) {
|
|||
|
|
throw new Error(uploadResult?.error || uploadResult?.message || '上传失败');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
wx.hideLoading();
|
|||
|
|
|
|||
|
|
// 跳转到edits页面
|
|||
|
|
wx.redirectTo({
|
|||
|
|
url: `/subpackages/media/edits/edits?imagePath=${encodeURIComponent(uploadResult.fileUrl)}`,
|
|||
|
|
fail: (err) => {
|
|||
|
|
console.error('跳转发布页失败:', err);
|
|||
|
|
wx.showToast({
|
|||
|
|
title: '跳转失败,请重试',
|
|||
|
|
icon: 'none',
|
|||
|
|
duration: 2000
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
wx.hideLoading();
|
|||
|
|
console.error('裁剪上传失败:', error);
|
|||
|
|
wx.showToast({
|
|||
|
|
title: error.message || '裁剪上传失败,请重试',
|
|||
|
|
icon: 'none',
|
|||
|
|
duration: 2000
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
fail: (err) => {
|
|||
|
|
wx.hideLoading();
|
|||
|
|
console.error('生成裁剪图片失败:', err);
|
|||
|
|
wx.showToast({
|
|||
|
|
title: '生成图片失败',
|
|||
|
|
icon: 'none'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}, this);
|
|||
|
|
} catch (error) {
|
|||
|
|
wx.hideLoading();
|
|||
|
|
console.error('绘制裁剪图片失败:', error, error.stack);
|
|||
|
|
wx.showToast({
|
|||
|
|
title: '绘制失败: ' + (error.message || '未知错误'),
|
|||
|
|
icon: 'none',
|
|||
|
|
duration: 3000
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
img.src = currentPhotoPath;
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 旋转
|
|||
|
|
rotateImage() {
|
|||
|
|
// 如果正在裁剪,先关闭裁剪模式
|
|||
|
|
if (this.data.isCropping) {
|
|||
|
|
this.setData({ isCropping: false, currentTool: '' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let { rotateAngle, cropBox, imageRect, mosaicData, imagePosition } = this.data;
|
|||
|
|
|
|||
|
|
// 计算新旋转角度
|
|||
|
|
const newRotateAngle = (rotateAngle - 90) % 360;
|
|||
|
|
const widthRatio = cropBox.width / imageRect.width;
|
|||
|
|
const heightRatio = cropBox.height / imageRect.height;
|
|||
|
|
const newWidth = cropBox.height;
|
|||
|
|
const newHeight = cropBox.width;
|
|||
|
|
const newLeft = imageRect.left + (imageRect.width - newWidth) / 2;
|
|||
|
|
const newTop = imageRect.top + (imageRect.height - newHeight) / 2;
|
|||
|
|
|
|||
|
|
// 旋转历史马赛克坐标
|
|||
|
|
const centerX = imagePosition.width / 2;
|
|||
|
|
const centerY = imagePosition.height / 2;
|
|||
|
|
const rad = -90 * Math.PI / 180;
|
|||
|
|
const cos = Math.cos(rad);
|
|||
|
|
const sin = Math.sin(rad);
|
|||
|
|
const rotatedMosaicData = mosaicData.map(point => {
|
|||
|
|
// 转换为相对图片中心的坐标
|
|||
|
|
const x = point.x - centerX;
|
|||
|
|
const y = point.y - centerY;
|
|||
|
|
// 旋转坐标
|
|||
|
|
const rotatedX = x * cos - y * sin + centerX;
|
|||
|
|
const rotatedY = x * sin + y * cos + centerY;
|
|||
|
|
return { ...point, x: rotatedX, y: rotatedY };
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 更新数据
|
|||
|
|
this.setData({
|
|||
|
|
rotateAngle: newRotateAngle,
|
|||
|
|
isRotated: true,
|
|||
|
|
hasEdits: true,
|
|||
|
|
cropBox: { left: newLeft, top: newTop, width: newWidth, height: newHeight, right: newLeft + newWidth, bottom: newTop + newHeight },
|
|||
|
|
mosaicData: rotatedMosaicData
|
|||
|
|
}, () => {
|
|||
|
|
// 重新渲染马赛克
|
|||
|
|
this.reRenderMosaic();
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 重新渲染马赛克
|
|||
|
|
reRenderMosaic() {
|
|||
|
|
const { canvasContext, mosaicData, canvasWidth, canvasHeight } = this.data;
|
|||
|
|
if (!canvasContext) return;
|
|||
|
|
|
|||
|
|
// 清空 Canvas 原有内容
|
|||
|
|
canvasContext.clearRect(0, 0, canvasWidth, canvasHeight);
|
|||
|
|
// 重新绘制所有马赛克
|
|||
|
|
mosaicData.forEach(point => {
|
|||
|
|
canvasContext.save();
|
|||
|
|
canvasContext.fillStyle = 'rgba(128, 128, 128, 0.8)';
|
|||
|
|
canvasContext.beginPath();
|
|||
|
|
canvasContext.arc(point.x, point.y, point.size / 2, 0, 2 * Math.PI);
|
|||
|
|
canvasContext.fill();
|
|||
|
|
canvasContext.restore();
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 合并图片(支持裁剪后的图片)
|
|||
|
|
async mergeImages() {
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
try {
|
|||
|
|
const { currentPhotoPath, imageInfo, rotateAngle, mosaicData, systemInfo } = this.data;
|
|||
|
|
|
|||
|
|
// 验证必要数据
|
|||
|
|
if (!currentPhotoPath) {
|
|||
|
|
reject(new Error('图片路径不存在'));
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!imageInfo || !imageInfo.width || !imageInfo.height) {
|
|||
|
|
reject(new Error('图片信息不完整'));
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!systemInfo || !systemInfo.windowWidth || !systemInfo.windowHeight) {
|
|||
|
|
reject(new Error('系统信息不完整'));
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取页面内的辅助 Canvas 节点
|
|||
|
|
const query = wx.createSelectorQuery().in(this);
|
|||
|
|
query.select('#mergeCanvas')
|
|||
|
|
.fields({ node: true, size: true })
|
|||
|
|
.exec((res) => {
|
|||
|
|
if (!res || res.length === 0 || !res[0] || !res[0].node) {
|
|||
|
|
reject(new Error('未找到辅助 Canvas 节点'));
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const mergeCanvas = res[0].node;
|
|||
|
|
const mergeCtx = mergeCanvas.getContext('2d');
|
|||
|
|
|
|||
|
|
// 直接使用当前图片的原始尺寸(裁剪后已经是最终尺寸)
|
|||
|
|
const canvasWidth = imageInfo.width;
|
|||
|
|
const canvasHeight = imageInfo.height;
|
|||
|
|
|
|||
|
|
// 设置 Canvas 尺寸为图片原始尺寸
|
|||
|
|
mergeCanvas.width = canvasWidth;
|
|||
|
|
mergeCanvas.height = canvasHeight;
|
|||
|
|
|
|||
|
|
console.log('合并图片参数:', {
|
|||
|
|
currentPhotoPath,
|
|||
|
|
imageInfo,
|
|||
|
|
rotateAngle,
|
|||
|
|
mosaicDataLength: mosaicData?.length || 0,
|
|||
|
|
canvasWidth,
|
|||
|
|
canvasHeight
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 加载图片并绘制
|
|||
|
|
const img = mergeCanvas.createImage();
|
|||
|
|
|
|||
|
|
// 设置超时,避免图片加载卡死
|
|||
|
|
const timeoutId = setTimeout(() => {
|
|||
|
|
reject(new Error('图片加载超时'));
|
|||
|
|
}, 10000); // 10秒超时
|
|||
|
|
|
|||
|
|
img.onload = () => {
|
|||
|
|
clearTimeout(timeoutId);
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 清空画布
|
|||
|
|
mergeCtx.clearRect(0, 0, canvasWidth, canvasHeight);
|
|||
|
|
|
|||
|
|
// 绘制旋转后的图片
|
|||
|
|
mergeCtx.save();
|
|||
|
|
mergeCtx.translate(canvasWidth / 2, canvasHeight / 2);
|
|||
|
|
mergeCtx.rotate(rotateAngle * Math.PI / 180);
|
|||
|
|
mergeCtx.drawImage(
|
|||
|
|
img,
|
|||
|
|
-canvasWidth / 2,
|
|||
|
|
-canvasHeight / 2,
|
|||
|
|
canvasWidth,
|
|||
|
|
canvasHeight
|
|||
|
|
);
|
|||
|
|
mergeCtx.restore();
|
|||
|
|
|
|||
|
|
// 如果有马赛克,绘制马赛克(需要根据图片尺寸比例调整坐标)
|
|||
|
|
if (mosaicData && mosaicData.length > 0) {
|
|||
|
|
// 计算缩放比例(从显示尺寸到原始尺寸)
|
|||
|
|
const imagePosition = this.data.imagePosition || {};
|
|||
|
|
let scaleX = 1;
|
|||
|
|
let scaleY = 1;
|
|||
|
|
|
|||
|
|
if (imagePosition.width && imagePosition.height) {
|
|||
|
|
scaleX = canvasWidth / imagePosition.width;
|
|||
|
|
scaleY = canvasHeight / imagePosition.height;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
mosaicData.forEach(point => {
|
|||
|
|
mergeCtx.fillStyle = 'rgba(128, 128, 128, 0.8)';
|
|||
|
|
mergeCtx.beginPath();
|
|||
|
|
mergeCtx.arc(
|
|||
|
|
point.x * scaleX,
|
|||
|
|
point.y * scaleY,
|
|||
|
|
(point.size / 2) * scaleX,
|
|||
|
|
0,
|
|||
|
|
2 * Math.PI
|
|||
|
|
);
|
|||
|
|
mergeCtx.fill();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 导出图片
|
|||
|
|
wx.canvasToTempFilePath({
|
|||
|
|
canvas: mergeCanvas,
|
|||
|
|
x: 0,
|
|||
|
|
y: 0,
|
|||
|
|
width: canvasWidth,
|
|||
|
|
height: canvasHeight,
|
|||
|
|
destWidth: canvasWidth,
|
|||
|
|
destHeight: canvasHeight,
|
|||
|
|
success: (res) => {
|
|||
|
|
console.log('合并图片成功:', res.tempFilePath);
|
|||
|
|
resolve(res.tempFilePath);
|
|||
|
|
},
|
|||
|
|
fail: (err) => {
|
|||
|
|
console.error('Canvas 导出临时路径失败:', err);
|
|||
|
|
reject(new Error('导出图片失败: ' + (err.errMsg || '未知错误')));
|
|||
|
|
}
|
|||
|
|
}, this);
|
|||
|
|
} catch (error) {
|
|||
|
|
clearTimeout(timeoutId);
|
|||
|
|
console.error('绘制图片失败:', error);
|
|||
|
|
reject(new Error('绘制图片失败: ' + (error.message || '未知错误')));
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
img.onerror = (err) => {
|
|||
|
|
clearTimeout(timeoutId);
|
|||
|
|
console.error('图片加载失败:', err, currentPhotoPath);
|
|||
|
|
reject(new Error('图片加载失败: ' + (err.errMsg || '未知错误')));
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
img.src = currentPhotoPath;
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('合并图片异常:', error);
|
|||
|
|
reject(new Error('合并图片失败: ' + (error.message || '未知错误')));
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
cancelCrop() {
|
|||
|
|
this.setData({
|
|||
|
|
isCropping: false,
|
|||
|
|
currentTool: ''
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
selectTool(e) {
|
|||
|
|
// 获取点击的工具
|
|||
|
|
const tool = e.currentTarget.dataset.tool;
|
|||
|
|
const currentActiveTool = this.data.currentTool;
|
|||
|
|
|
|||
|
|
// 判断是否为"第二次点击"
|
|||
|
|
if (currentActiveTool === tool) {
|
|||
|
|
// 第二次点击:取消激活,清空工具状态
|
|||
|
|
this.setData({
|
|||
|
|
// 清空当前工具
|
|||
|
|
currentTool: '',
|
|||
|
|
// 同步关闭裁剪
|
|||
|
|
isCropping: false,
|
|||
|
|
});
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 裁剪工具特殊处理
|
|||
|
|
if (tool === 'crop') {
|
|||
|
|
this.setData({
|
|||
|
|
currentTool: 'crop',
|
|||
|
|
isCropping: !this.data.isCropping
|
|||
|
|
}, () => {
|
|||
|
|
// 切换裁剪模式后,若开启裁剪则重新初始化裁剪框
|
|||
|
|
if (this.data.isCropping) {
|
|||
|
|
// 先获取容器位置
|
|||
|
|
this.updateContainerRect().then(() => {
|
|||
|
|
// 在 setData 回调中初始化,确保 imagePosition 已更新
|
|||
|
|
setTimeout(() => {
|
|||
|
|
const newCropBox = this.calculateInitialCropBox();
|
|||
|
|
if (newCropBox.width > 0 && newCropBox.height > 0) {
|
|||
|
|
this.setData({
|
|||
|
|
cropBox: newCropBox
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}, 50);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 第一次点击:激活工具,初始化 Canvas
|
|||
|
|
this.setData({
|
|||
|
|
currentTool: tool,
|
|||
|
|
isCropping: false
|
|||
|
|
}, async () => {
|
|||
|
|
if (tool === 'mosaic' || tool === 'eraser') {
|
|||
|
|
await this.initMosaicCanvas();
|
|||
|
|
// 重新绘制所有马赛克
|
|||
|
|
const { canvasContext, mosaicData } = this.data;
|
|||
|
|
if (canvasContext && mosaicData.length > 0) {
|
|||
|
|
canvasContext.clearRect(0, 0, this.data.canvasWidth, this.data.canvasHeight);
|
|||
|
|
mosaicData.forEach(point => {
|
|||
|
|
canvasContext.fillStyle = 'rgba(128, 128, 128, 0.8)';
|
|||
|
|
canvasContext.beginPath();
|
|||
|
|
canvasContext.arc(point.x, point.y, point.size / 2, 0, 2 * Math.PI);
|
|||
|
|
canvasContext.fill();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
onSizeTouchStart(e) {
|
|||
|
|
this.setData({
|
|||
|
|
dragStartX: e.touches[0].clientX
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
onSizeTouchMove(e) {
|
|||
|
|
const query = wx.createSelectorQuery().in(this);
|
|||
|
|
query.select('.mosaic-size-drag')
|
|||
|
|
.boundingClientRect((rect) => {
|
|||
|
|
if (!rect) return;
|
|||
|
|
|
|||
|
|
const containerWidth = rect.width;
|
|||
|
|
const touchX = e.touches[0].clientX;
|
|||
|
|
const startX = rect.left;
|
|||
|
|
const endX = rect.right;
|
|||
|
|
|
|||
|
|
// 计算滑块在容器内的百分比
|
|||
|
|
let percent = ((touchX - startX) / containerWidth) * 100;
|
|||
|
|
percent = Math.max(0, Math.min(100, percent)); // 限制在0-100
|
|||
|
|
|
|||
|
|
// 计算马赛克大小
|
|||
|
|
const newSize = 5 + (percent / 100) * 25;
|
|||
|
|
|
|||
|
|
this.setData({
|
|||
|
|
mosaicSize: newSize,
|
|||
|
|
thumbStyle: { left: `${percent}%` }
|
|||
|
|
});
|
|||
|
|
})
|
|||
|
|
.exec();
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
onCanvasTouchStart(e) {
|
|||
|
|
const { currentTool } = this.data;
|
|||
|
|
if (currentTool === 'mosaic' || currentTool === 'eraser') {
|
|||
|
|
this.handleCanvasDrawing(e);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
onCanvasTouchMove(e) {
|
|||
|
|
const { currentTool } = this.data;
|
|||
|
|
if (currentTool === 'mosaic' || currentTool === 'eraser') {
|
|||
|
|
this.handleCanvasDrawing(e);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
onCanvasTouchEnd() {
|
|||
|
|
// 触摸结束无需处理
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 画布绘制逻辑
|
|||
|
|
handleCanvasDrawing(e) {
|
|||
|
|
const {
|
|||
|
|
currentTool, canvasContext, mosaicSize, mosaicData,
|
|||
|
|
imagePosition
|
|||
|
|
} = this.data;
|
|||
|
|
|
|||
|
|
if (!canvasContext || !imagePosition) return;
|
|||
|
|
|
|||
|
|
// 获取触摸点原始坐标
|
|||
|
|
const touch = e.touches[0];
|
|||
|
|
const rawX = touch.clientX;
|
|||
|
|
const rawY = touch.clientY;
|
|||
|
|
|
|||
|
|
// Canvas在页面中的实际位置
|
|||
|
|
const query = wx.createSelectorQuery().in(this);
|
|||
|
|
query.select('#mosaicCanvas')
|
|||
|
|
.boundingClientRect()
|
|||
|
|
.exec((res) => {
|
|||
|
|
if (res && res[0]) {
|
|||
|
|
const canvasRect = res[0];
|
|||
|
|
|
|||
|
|
// 计算相对于Canvas的触摸坐标
|
|||
|
|
const canvasX = rawX - canvasRect.left;
|
|||
|
|
const canvasY = rawY - canvasRect.top;
|
|||
|
|
|
|||
|
|
// 计算Canvas的逻辑尺寸与物理尺寸的比例
|
|||
|
|
const realWidth = this.data.canvasWidth || imagePosition.width;
|
|||
|
|
const realHeight = this.data.canvasHeight || imagePosition.height;
|
|||
|
|
const scaleX = realWidth / canvasRect.width;
|
|||
|
|
const scaleY = realHeight / canvasRect.height;
|
|||
|
|
|
|||
|
|
// 计算最终绘制坐标
|
|||
|
|
const drawX = canvasX * scaleX;
|
|||
|
|
const drawY = canvasY * scaleY;
|
|||
|
|
|
|||
|
|
// 绘制马赛克或橡皮擦效果
|
|||
|
|
if (currentTool === 'mosaic') {
|
|||
|
|
const newMosaicData = [...mosaicData, { x: drawX, y: drawY, size: mosaicSize }];
|
|||
|
|
canvasContext.save();
|
|||
|
|
canvasContext.fillStyle = 'rgba(128, 128, 128, 0.8)';
|
|||
|
|
canvasContext.beginPath();
|
|||
|
|
canvasContext.arc(drawX, drawY, mosaicSize / 2, 0, 2 * Math.PI);
|
|||
|
|
canvasContext.fill();
|
|||
|
|
canvasContext.restore();
|
|||
|
|
this.setData({ mosaicData: newMosaicData, hasMosaic: true, hasEdits: true });
|
|||
|
|
} else if (currentTool === 'eraser') {
|
|||
|
|
const newMosaicData = mosaicData.filter(point => {
|
|||
|
|
const distance = Math.hypot(drawX - point.x, drawY - point.y);
|
|||
|
|
return distance > (point.size + mosaicSize) / 2;
|
|||
|
|
});
|
|||
|
|
canvasContext.clearRect(0, 0, realWidth, realHeight);
|
|||
|
|
newMosaicData.forEach(point => {
|
|||
|
|
canvasContext.save();
|
|||
|
|
canvasContext.fillStyle = 'rgba(128, 128, 128, 0.8)';
|
|||
|
|
canvasContext.beginPath();
|
|||
|
|
canvasContext.arc(point.x, point.y, point.size / 2, 0, 2 * Math.PI);
|
|||
|
|
canvasContext.fill();
|
|||
|
|
canvasContext.restore();
|
|||
|
|
});
|
|||
|
|
this.setData({ mosaicData: newMosaicData, hasMosaic: newMosaicData.length > 0, hasEdits: true });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
clearMosaic() {
|
|||
|
|
this.initMosaicCanvas();
|
|||
|
|
this.setData({
|
|||
|
|
mosaicData: [],
|
|||
|
|
hasMosaic: false,
|
|||
|
|
hasEdits: this.data.rotateAngle !== 0 || this.data.cropBox.width !== this.calculateInitialCropBox(this.data.imageInfo).width
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
restoreOriginal() {
|
|||
|
|
// 如果正在裁剪,先关闭裁剪模式
|
|||
|
|
if (this.data.isCropping) {
|
|||
|
|
this.setData({ isCropping: false, currentTool: '' });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const currentScale = this.data.imageScale;
|
|||
|
|
|
|||
|
|
wx.getImageInfo({
|
|||
|
|
src: this.data.originalPhotoPath,
|
|||
|
|
success: (info) => {
|
|||
|
|
this.setData({
|
|||
|
|
currentPhotoPath: this.data.originalPhotoPath,
|
|||
|
|
imageInfo: info,
|
|||
|
|
rotateAngle: 0,
|
|||
|
|
imageScale: currentScale,
|
|||
|
|
mosaicData: [],
|
|||
|
|
hasMosaic: false,
|
|||
|
|
hasEdits: false,
|
|||
|
|
isCropping: false,
|
|||
|
|
currentTool: '',
|
|||
|
|
cropBox: this.calculateInitialCropBox(info),
|
|||
|
|
isRotated: false
|
|||
|
|
}, () => {
|
|||
|
|
this.initMosaicCanvas();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
retryPhoto() {
|
|||
|
|
this.setData({
|
|||
|
|
hasTakenPhoto: false,
|
|||
|
|
currentTool: '',
|
|||
|
|
isCropping: false
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 添加裁剪逻辑
|
|||
|
|
async confirmPhoto() {
|
|||
|
|
// 显示加载提示
|
|||
|
|
wx.showLoading({
|
|||
|
|
title: '处理中...',
|
|||
|
|
mask: true
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 合并图片
|
|||
|
|
let tempFilePath;
|
|||
|
|
try {
|
|||
|
|
tempFilePath = await this.mergeImages();
|
|||
|
|
} catch (mergeError) {
|
|||
|
|
wx.hideLoading();
|
|||
|
|
console.error('合并图片失败:', mergeError);
|
|||
|
|
wx.showToast({
|
|||
|
|
title: '图片处理失败',
|
|||
|
|
icon: 'none',
|
|||
|
|
duration: 2000
|
|||
|
|
});
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!tempFilePath) {
|
|||
|
|
wx.hideLoading();
|
|||
|
|
wx.showToast({
|
|||
|
|
title: '图片处理失败',
|
|||
|
|
icon: 'none',
|
|||
|
|
duration: 2000
|
|||
|
|
});
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 上传图片
|
|||
|
|
try {
|
|||
|
|
const uploadResult = await cosManager.upload(tempFilePath, {
|
|||
|
|
fileType: 'image',
|
|||
|
|
enableDedup: false,
|
|||
|
|
onProgress: (percent) => {
|
|||
|
|
console.log('上传进度:', percent + '%');
|
|||
|
|
// 更新加载提示
|
|||
|
|
wx.showLoading({
|
|||
|
|
title: `上传中 ${Math.round(percent)}%`,
|
|||
|
|
mask: true
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
enableCompress: false,
|
|||
|
|
compressOptions: {
|
|||
|
|
maxWidth: 400,
|
|||
|
|
maxHeight: 400,
|
|||
|
|
targetSize: 20 * 1024
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 检查上传结果
|
|||
|
|
if (!uploadResult || !uploadResult.success) {
|
|||
|
|
throw new Error(uploadResult?.error || uploadResult?.message || '上传失败');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!uploadResult.fileUrl) {
|
|||
|
|
throw new Error('上传失败:未获取到文件URL');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取图片路径
|
|||
|
|
const imagePath = uploadResult.fileUrl;
|
|||
|
|
|
|||
|
|
// 确保隐藏loading,避免页面切换时残留
|
|||
|
|
wx.hideLoading();
|
|||
|
|
|
|||
|
|
// 跳转发布页并传递图片数据(不显示toast,避免与页面切换冲突)
|
|||
|
|
wx.redirectTo({
|
|||
|
|
url: `/subpackages/media/edits/edits?imagePath=${encodeURIComponent(imagePath)}`,
|
|||
|
|
fail: (err) => {
|
|||
|
|
console.error('跳转发布页失败:', err);
|
|||
|
|
wx.showToast({
|
|||
|
|
title: '跳转失败,请重试',
|
|||
|
|
icon: 'none',
|
|||
|
|
duration: 2000
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
} catch (uploadError) {
|
|||
|
|
wx.hideLoading();
|
|||
|
|
console.error('上传失败:', uploadError);
|
|||
|
|
|
|||
|
|
// 判断错误类型,提供更友好的提示
|
|||
|
|
let errorMessage = '上传失败,请检查网络后重试';
|
|||
|
|
if (uploadError && uploadError.message) {
|
|||
|
|
if (uploadError.message.includes('Failed to fetch') ||
|
|||
|
|
uploadError.message.includes('网络') ||
|
|||
|
|
uploadError.message.includes('timeout')) {
|
|||
|
|
errorMessage = '网络连接失败,请检查网络后重试';
|
|||
|
|
} else if (uploadError.message.includes('权限') ||
|
|||
|
|
uploadError.message.includes('AccessDenied')) {
|
|||
|
|
errorMessage = '上传权限不足,请重新登录';
|
|||
|
|
} else {
|
|||
|
|
errorMessage = uploadError.message || errorMessage;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
wx.showModal({
|
|||
|
|
title: '上传失败',
|
|||
|
|
content: errorMessage,
|
|||
|
|
showCancel: true,
|
|||
|
|
cancelText: '取消',
|
|||
|
|
confirmText: '重试',
|
|||
|
|
success: (res) => {
|
|||
|
|
if (res.confirm) {
|
|||
|
|
// 用户选择重试
|
|||
|
|
this.confirmPhoto();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
return; // 确保不再执行后续代码
|
|||
|
|
}
|
|||
|
|
} catch (err) {
|
|||
|
|
wx.hideLoading();
|
|||
|
|
console.error('图片处理失败:', err);
|
|||
|
|
|
|||
|
|
let errorMessage = '图片处理失败,请重试';
|
|||
|
|
if (err && err.message) {
|
|||
|
|
errorMessage = err.message;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
wx.showToast({
|
|||
|
|
title: errorMessage,
|
|||
|
|
icon: 'none',
|
|||
|
|
duration: 2000
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
|
|||
|
|
navigateBack() {
|
|||
|
|
wx.navigateBack({
|
|||
|
|
// 返回上一页
|
|||
|
|
delta: 1
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
saveAndReturn(imagePath) {
|
|||
|
|
const pages = getCurrentPages();
|
|||
|
|
const prevPage = pages[pages.length - 2];
|
|||
|
|
if (prevPage && prevPage.uploadImage) {
|
|||
|
|
prevPage.uploadImage(imagePath);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.navigateBack();
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
goBackToShoot() {
|
|||
|
|
wx.redirectTo({
|
|||
|
|
url: '/subpackages/media/camera/camera',
|
|||
|
|
success: () => {
|
|||
|
|
|
|||
|
|
},
|
|||
|
|
fail: (err) => {
|
|||
|
|
console.error("返回拍摄页面失败:", err);
|
|||
|
|
wx.showToast({ title: '返回失败,请重试', icon: 'none' });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
handleCameraError(e) {
|
|||
|
|
console.error('相机错误:', e.detail);
|
|||
|
|
const error = e.detail;
|
|||
|
|
|
|||
|
|
// 处理权限相关的错误
|
|||
|
|
if (error.errMsg && (error.errMsg.includes('permission') || error.errMsg.includes('权限'))) {
|
|||
|
|
wx.showModal({
|
|||
|
|
title: '权限不足',
|
|||
|
|
content: '请授予相机权限以使用拍摄功能',
|
|||
|
|
confirmText: '去设置',
|
|||
|
|
cancelText: '取消',
|
|||
|
|
success: (modalRes) => {
|
|||
|
|
if (modalRes.confirm) {
|
|||
|
|
wx.openSetting({
|
|||
|
|
success: (settingRes) => {
|
|||
|
|
// 如果用户在设置中授权了,可以尝试重新加载页面
|
|||
|
|
if (settingRes.authSetting['scope.camera']) {
|
|||
|
|
// 权限已授予,可以继续使用相机
|
|||
|
|
console.log('用户已授予相机权限');
|
|||
|
|
} else {
|
|||
|
|
// 用户仍未授权,返回上一页
|
|||
|
|
this.navigateBack();
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
fail: () => {
|
|||
|
|
this.navigateBack();
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
// 用户取消,返回上一页
|
|||
|
|
this.navigateBack();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
// 其他相机错误
|
|||
|
|
wx.showToast({
|
|||
|
|
title: '相机初始化失败',
|
|||
|
|
icon: 'none'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
setTimeout(() => {
|
|||
|
|
this.navigateBack();
|
|||
|
|
}, 1500);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|