|
|
@@ -83,14 +83,14 @@
|
|
|
</view>
|
|
|
|
|
|
<!-- 底部按钮组 -->
|
|
|
- <view class="cl-cropper__actions" :class="[pt.actions?.className]">
|
|
|
+ <view class="cl-cropper__op" :class="[pt.op?.className]" :style="opStyle" @touchmove.stop>
|
|
|
<!-- 关闭 -->
|
|
|
- <view class="cl-cropper__actions-item">
|
|
|
+ <view class="cl-cropper__op-item">
|
|
|
<cl-icon name="close-line" color="white" :size="50" @tap="close"></cl-icon>
|
|
|
</view>
|
|
|
|
|
|
<!-- 旋转 -->
|
|
|
- <view class="cl-cropper__actions-item">
|
|
|
+ <view class="cl-cropper__op-item">
|
|
|
<cl-icon
|
|
|
name="anticlockwise-line"
|
|
|
color="white"
|
|
|
@@ -100,22 +100,17 @@
|
|
|
</view>
|
|
|
|
|
|
<!-- 重置 -->
|
|
|
- <view class="cl-cropper__actions-item">
|
|
|
- <cl-icon
|
|
|
- name="reset-right-line"
|
|
|
- color="white"
|
|
|
- :size="40"
|
|
|
- @tap="resetCropper"
|
|
|
- ></cl-icon>
|
|
|
+ <view class="cl-cropper__op-item">
|
|
|
+ <cl-icon name="reset-right-line" color="white" :size="40" @tap="reset"></cl-icon>
|
|
|
</view>
|
|
|
|
|
|
<!-- 重新选择 -->
|
|
|
- <view class="cl-cropper__actions-item">
|
|
|
+ <view class="cl-cropper__op-item">
|
|
|
<cl-icon name="image-line" color="white" :size="40" @tap="chooseImage"></cl-icon>
|
|
|
</view>
|
|
|
|
|
|
<!-- 确定 -->
|
|
|
- <view class="cl-cropper__actions-item">
|
|
|
+ <view class="cl-cropper__op-item">
|
|
|
<cl-icon name="check-line" color="white" :size="50" @tap="toPng"></cl-icon>
|
|
|
</view>
|
|
|
</view>
|
|
|
@@ -248,20 +243,19 @@ function toPixel(value: number): number {
|
|
|
type PassThrough = {
|
|
|
className?: string; // 组件根元素类名
|
|
|
image?: PassThroughProps; // 图片元素透传属性
|
|
|
- op?: PassThroughProps; // 操作按钮组透传属性
|
|
|
- actions?: PassThroughProps; // 底部按钮组透传属性
|
|
|
+ op?: PassThroughProps; // 底部按钮组透传属性
|
|
|
mask?: PassThroughProps; // 遮罩层透传属性
|
|
|
cropBox?: PassThroughProps; // 裁剪框透传属性
|
|
|
button?: PassThroughProps; // 按钮透传属性
|
|
|
};
|
|
|
|
|
|
-// 解析透传样式配置的计算属性
|
|
|
+// 解析透传样式配置
|
|
|
const pt = computed(() => parsePt<PassThrough>(props.pt));
|
|
|
|
|
|
// 创建容器尺寸响应式对象
|
|
|
const container = reactive<Size>({
|
|
|
- height: page.getViewHeight(), // 获取视图高度
|
|
|
- width: page.getViewWidth() // 获取视图宽度
|
|
|
+ height: 0, // 获取视图高度
|
|
|
+ width: 0 // 获取视图宽度
|
|
|
});
|
|
|
|
|
|
// 创建图片信息响应式对象
|
|
|
@@ -320,7 +314,7 @@ const flipVertical = ref(false); // 垂直翻转状态
|
|
|
// 图片旋转状态
|
|
|
const rotate = ref(0); // 旋转状态
|
|
|
|
|
|
-// 计算图片样式的计算属性
|
|
|
+// 计算图片样式
|
|
|
const imageStyle = computed(() => {
|
|
|
// 构建翻转变换
|
|
|
const flipX = flipHorizontal.value ? "scaleX(-1)" : "scaleX(1)";
|
|
|
@@ -342,7 +336,7 @@ const imageStyle = computed(() => {
|
|
|
return style;
|
|
|
});
|
|
|
|
|
|
-// 计算裁剪框样式的计算属性
|
|
|
+// 计算裁剪框样式
|
|
|
const cropBoxStyle = computed(() => {
|
|
|
// 返回裁剪框定位和尺寸样式
|
|
|
return {
|
|
|
@@ -353,7 +347,7 @@ const cropBoxStyle = computed(() => {
|
|
|
};
|
|
|
});
|
|
|
|
|
|
-// 计算遮罩层样式的计算属性
|
|
|
+// 计算遮罩层样式
|
|
|
const maskStyle = computed<MaskStyle>(() => {
|
|
|
// 返回四个方向的遮罩样式
|
|
|
return {
|
|
|
@@ -386,6 +380,19 @@ const maskStyle = computed<MaskStyle>(() => {
|
|
|
};
|
|
|
});
|
|
|
|
|
|
+// 底部按钮组样式
|
|
|
+const opStyle = computed(() => {
|
|
|
+ let bottom = page.getSafeAreaHeight("bottom");
|
|
|
+
|
|
|
+ if (bottom == 0) {
|
|
|
+ bottom = 10;
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ bottom: bottom + "px"
|
|
|
+ };
|
|
|
+});
|
|
|
+
|
|
|
// 计算旋转后图片的有效尺寸的函数
|
|
|
function getRotatedImageSize(): Size {
|
|
|
// 获取旋转角度(转换为0-360度范围内的正值)
|
|
|
@@ -508,9 +515,18 @@ function getMinImageSize(): Size {
|
|
|
|
|
|
// 初始化裁剪框的函数
|
|
|
function initCrop() {
|
|
|
+ const { windowHeight, windowWidth } = uni.getWindowInfo();
|
|
|
+
|
|
|
+ // 设置容器尺寸为视口尺寸
|
|
|
+ container.height = windowHeight;
|
|
|
+ container.width = windowWidth;
|
|
|
+
|
|
|
+ console.log(container);
|
|
|
+
|
|
|
// 设置裁剪框尺寸为传入的初始值
|
|
|
cropBox.width = props.cropWidth; // 设置裁剪框宽度
|
|
|
cropBox.height = props.cropHeight; // 设置裁剪框高度
|
|
|
+
|
|
|
// 计算裁剪框居中位置
|
|
|
cropBox.x = toPixel((container.width - cropBox.width) / 2); // 水平居中
|
|
|
cropBox.y = toPixel((container.height - cropBox.height) / 2); // 垂直居中
|
|
|
@@ -615,10 +631,12 @@ function onImageLoaded(e: UniImageLoadEvent) {
|
|
|
imageInfo.height = e.detail.height; // 保存图片原始高度
|
|
|
imageInfo.isLoaded = true; // 标记图片已加载
|
|
|
|
|
|
- // 执行初始化流程
|
|
|
- initCrop(); // 初始化裁剪框位置和尺寸
|
|
|
- setInitialImageSize(); // 设置图片初始显示尺寸
|
|
|
- adjustBounds(); // 调整图片边界确保覆盖裁剪框
|
|
|
+ nextTick(() => {
|
|
|
+ // 执行初始化流程
|
|
|
+ initCrop(); // 初始化裁剪框位置和尺寸
|
|
|
+ setInitialImageSize(); // 设置图片初始显示尺寸
|
|
|
+ adjustBounds(); // 调整图片边界确保覆盖裁剪框
|
|
|
+ });
|
|
|
|
|
|
// 触发加载完成事件
|
|
|
emit("load", e); // 向父组件发送加载事件
|
|
|
@@ -971,7 +989,7 @@ function onTouchEnd() {
|
|
|
}
|
|
|
|
|
|
// 重置裁剪器到初始状态的函数
|
|
|
-function resetCropper() {
|
|
|
+function reset() {
|
|
|
// 重新初始化裁剪框
|
|
|
initCrop(); // 恢复裁剪框到初始位置和尺寸
|
|
|
|
|
|
@@ -980,6 +998,10 @@ function resetCropper() {
|
|
|
flipVertical.value = false; // 重置垂直翻转状态
|
|
|
rotate.value = 0; // 重置旋转角度
|
|
|
|
|
|
+ // 重置图片位移
|
|
|
+ transform.translateX = 0;
|
|
|
+ transform.translateY = 0;
|
|
|
+
|
|
|
// 根据图片加载状态进行不同处理
|
|
|
if (imageInfo.isLoaded) {
|
|
|
setInitialImageSize(); // 重新设置图片初始尺寸
|
|
|
@@ -1084,14 +1106,9 @@ async function toPng(): Promise<string> {
|
|
|
// 获取设备像素比
|
|
|
const dpr = uni.getDeviceInfo().devicePixelRatio ?? 1;
|
|
|
|
|
|
- // #ifndef H5
|
|
|
- // 设置缩放比例
|
|
|
- ctx!.scale(dpr, dpr);
|
|
|
- // #endif
|
|
|
-
|
|
|
// 设置宽高
|
|
|
- ctx!.canvas.width = cropBox.width;
|
|
|
- ctx!.canvas.height = cropBox.height;
|
|
|
+ ctx!.canvas.width = cropBox.width * dpr;
|
|
|
+ ctx!.canvas.height = cropBox.height * dpr;
|
|
|
|
|
|
let img: Image;
|
|
|
|
|
|
@@ -1102,25 +1119,67 @@ async function toPng(): Promise<string> {
|
|
|
|
|
|
// 其他环境创建图片
|
|
|
// #ifndef MP-WEIXIN || APP-HARMONY
|
|
|
- img = new Image(cropBox.width, cropBox.height);
|
|
|
+ img = new Image();
|
|
|
// #endif
|
|
|
|
|
|
// 设置图片源并在加载完成后绘制
|
|
|
img.src = imageUrl.value;
|
|
|
img.onload = () => {
|
|
|
- ctx!.drawImage(img, cropBox.x, cropBox.y, cropBox.width, cropBox.height);
|
|
|
+ let x: number;
|
|
|
+ let y: number;
|
|
|
+
|
|
|
+ // 根据旋转角度计算裁剪位置
|
|
|
+ switch (Math.abs(rotate.value) % 360) {
|
|
|
+ case 270:
|
|
|
+ // 旋转270度时的位置计算
|
|
|
+ x = (imageSize.width - cropBox.height) / 2 - transform.translateY;
|
|
|
+ y = (imageSize.height + cropBox.width) / 2 + transform.translateX;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 180:
|
|
|
+ // 旋转180度时的位置计算
|
|
|
+ x = (imageSize.width + cropBox.width) / 2 + transform.translateX;
|
|
|
+ y = (imageSize.height + cropBox.height) / 2 + transform.translateY;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 90:
|
|
|
+ // 旋转90度时的位置计算
|
|
|
+ x = (imageSize.width + cropBox.height) / 2 + transform.translateY;
|
|
|
+ y = (imageSize.height - cropBox.width) / 2 - transform.translateX;
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ // 不旋转时的位置计算
|
|
|
+ x = (imageSize.width - cropBox.width) / 2 - transform.translateX;
|
|
|
+ y = (imageSize.height - cropBox.height) / 2 - transform.translateY;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (x < 0) {
|
|
|
+ x = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (y < 0) {
|
|
|
+ y = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 图片旋转
|
|
|
+ ctx!.rotate((rotate.value * Math.PI) / 180);
|
|
|
+
|
|
|
+ // 绘制图片
|
|
|
+ ctx!.drawImage(
|
|
|
+ img,
|
|
|
+ -x * dpr,
|
|
|
+ -y * dpr,
|
|
|
+ imageSize.width * dpr,
|
|
|
+ imageSize.height * dpr
|
|
|
+ );
|
|
|
|
|
|
setTimeout(() => {
|
|
|
- canvasToPng({
|
|
|
- proxy,
|
|
|
- canvasId,
|
|
|
- canvasRef: canvasRef.value!
|
|
|
- })
|
|
|
- .then((url) => {
|
|
|
- emit("crop", url);
|
|
|
- resolve(url);
|
|
|
- })
|
|
|
- .catch(() => {});
|
|
|
+ canvasToPng(canvasRef.value!).then((url) => {
|
|
|
+ emit("crop", url);
|
|
|
+ resolve(url);
|
|
|
+ });
|
|
|
}, 10);
|
|
|
};
|
|
|
}
|
|
|
@@ -1263,14 +1322,13 @@ defineExpose({
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- &__actions {
|
|
|
- @apply absolute left-0 w-full flex flex-row items-center justify-between;
|
|
|
- z-index: 10;
|
|
|
- height: 50px;
|
|
|
- bottom: env(safe-area-inset-bottom);
|
|
|
+ &__op {
|
|
|
+ @apply absolute left-0 bottom-0 w-full flex flex-row justify-between;
|
|
|
+ z-index: 30;
|
|
|
+ height: 40px;
|
|
|
|
|
|
&-item {
|
|
|
- @apply flex flex-row justify-center items-center flex-1;
|
|
|
+ @apply flex flex-row justify-center items-center flex-1 h-full;
|
|
|
}
|
|
|
}
|
|
|
|