CNCEC_APP/uni_modules/uview-pro/components/u-avatar-cropper/u-avatar-cropper.vue

300 lines
8.4 KiB
Vue
Raw Normal View History

2026-03-25 14:54:15 +08:00
<template>
<view class="content">
<view class="cropper-wrapper" :style="{ height: cropperOpt.height + 'px' }">
<canvas
class="cropper"
:disable-scroll="true"
@touchstart="touchStart"
@touchmove="touchMove"
@touchend="touchEnd"
:style="{ width: cropperOpt.width, height: cropperOpt.height, backgroundColor: 'rgba(0, 0, 0, 0.8)' }"
canvas-id="cropper"
id="cropper"
></canvas>
<canvas
class="cropper"
:disable-scroll="true"
:style="{
position: 'fixed',
top: `-${cropperOpt.width * cropperOpt.pixelRatio}px`,
left: `-${cropperOpt.height * cropperOpt.pixelRatio}px`,
width: `${cropperOpt.width * cropperOpt.pixelRatio}px`,
height: `${cropperOpt.height * cropperOpt.pixelRatio}`
}"
canvas-id="targetId"
id="targetId"
></canvas>
</view>
<view class="cropper-buttons safe-area-padding" :style="{ height: bottomNavHeight + 'px' }">
<!-- #ifdef H5 -->
<view class="upload" @tap="uploadTap">{{ t('avatarCropper.select') }}</view>
<!-- #endif -->
<!-- #ifndef H5 -->
<view class="upload" @tap="uploadTap">{{ t('avatarCropper.reselect') }}</view>
<!-- #endif -->
<view class="getCropperImage" @tap="() => getCropperImage(false)">{{ t('avatarCropper.confirm') }}</view>
</view>
</view>
</template>
<script lang="ts">
export default {
name: 'u-avatar-cropper',
options: {
addGlobalClass: true,
// #ifndef MP-TOUTIAO
virtualHost: true,
// #endif
styleIsolation: 'shared'
}
};
</script>
<script setup lang="ts">
// @ts-nocheck
import { ref, reactive, onMounted } from 'vue';
import { $u, useLocale } from '../..';
// 兼容 UMD/ESM 导入 weCropper.js
import WeCropper from './weCropper';
import { AvatarCropperProps } from './types';
/**
* 裁剪矩形框的样式其中可包含的属性为lineWidth-边框宽度(单位rpx)color: 边框颜色
* mask-遮罩颜色一般设置为一个rgba的透明度"rgba(0, 0, 0, 0.35)"
*/
const props = defineProps(AvatarCropperProps);
const { t } = useLocale();
/**
* 组合式API变量声明
* 保留所有说明注释
*/
const bottomNavHeight = ref(50); // 底部导航的高度
const originWidth = ref(200);
const width = ref(0);
const height = ref(0);
const src = ref(''); // 选择的图片路径,用于在点击确定时,判断是否选择了图片
const destWidth = ref(200); // 输出图片宽度单位px
const rectWidth = ref(200); // 裁剪框宽度单位px
const fileType = ref('jpg'); // 输出的图片类型,如果'png'类型发现裁剪的图片太大,改成"jpg"即可
// cropperOpt 需响应式,且类型安全
const cropperOpt = reactive({
id: 'cropper',
targetId: 'targetCropper',
pixelRatio: 1,
width: 0,
height: 0,
scale: 2.5,
zoom: 8,
cut: {
x: 0,
y: 0,
width: originWidth.value,
height: originWidth.value
},
boundStyle: {
lineWidth: 0,
mask: '',
color: ''
}
});
let cropper: any = null; // WeCropper 实例,类型 any建议后续补充类型声明
/**
* 组件挂载时初始化 cropper
* 保留所有说明注释
*/
onMounted(() => {
// 获取系统信息
const rectInfo = uni.getSystemInfoSync();
width.value = rectInfo.windowWidth;
height.value = rectInfo.windowHeight - bottomNavHeight.value;
cropperOpt.width = width.value;
cropperOpt.height = height.value;
cropperOpt.pixelRatio = rectInfo.pixelRatio;
// 初始化裁剪框
cropperOpt.cut = {
x: (width.value - originWidth.value) / 2,
y: (height.value - originWidth.value) / 2,
width: originWidth.value,
height: originWidth.value
};
cropperOpt.boundStyle = {
lineWidth: uni.upx2px(props.boundStyle.lineWidth),
mask: props.boundStyle.mask,
color: props.boundStyle.borderColor
};
// 设置导航栏样式以免用户在page.json中没有设置为黑色背景
uni.setNavigationBarColor({
frontColor: 'var(--u-white-color)',
backgroundColor: 'var(--u-bg-black)'
});
// 初始化 cropper 实例
cropper = new WeCropper(cropperOpt)
.on('ready', (ctx: any) => {
// wecropper is ready for work!
})
.on('beforeImageLoad', (ctx: any) => {
// before picture loaded, i can do something
})
.on('imageLoad', (ctx: any) => {
// picture loaded
})
.on('beforeDraw', (ctx: any, instance: any) => {
// before canvas draw,i can do something
});
// 选择图片
uni.chooseImage({
count: 1, // 默认9
sizeType: ['compressed'], // 可以指定是原图还是压缩图,默认二者都有
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
success: (res: UniApp.ChooseImageSuccessCallbackResult) => {
src.value = res.tempFilePaths[0];
// 获取裁剪图片资源后给data添加src属性及其值
cropper.pushOrign(src.value);
}
});
});
/**
* 触摸开始事件
* @param e 事件对象
*/
function touchStart(e: any) {
cropper && cropper.touchStart(e);
}
/**
* 触摸移动事件
* @param e 事件对象
*/
function touchMove(e: any) {
cropper && cropper.touchMove(e);
}
/**
* 触摸结束事件
* @param e 事件对象
*/
function touchEnd(e: any) {
cropper && cropper.touchEnd(e);
}
/**
* 获取裁剪后的图片
* @param isPre 是否预览
*/
function getCropperImage(isPre = false) {
if (!src.value) return $u.toast(t('avatarCropper.noSelect'));
const cropper_opt = {
destHeight: Number(destWidth.value), // uni.canvasToTempFilePath要求这些参数为数值
destWidth: Number(destWidth.value),
fileType: fileType.value
};
cropper.getCropperImage(cropper_opt, (path: string, err: any) => {
if (err) {
uni.showModal({
title: t('avatarCropper.modalTitle'),
content: err.message
});
} else {
if (isPre) {
uni.previewImage({
current: '', // 当前显示图片的 http 链接
urls: [path] // 需要预览的图片 http 链接列表
});
} else {
uni.$emit('uAvatarCropper', path);
$u.route({
type: 'back'
});
}
}
});
}
/**
* 重新选择图片
*/
function uploadTap() {
uni.chooseImage({
count: 1, // 默认9
sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
success: (res: UniApp.ChooseImageSuccessCallbackResult) => {
src.value = res.tempFilePaths[0];
// 获取裁剪图片资源后给data添加src属性及其值
cropper.pushOrign(src.value);
}
});
}
</script>
<style scoped lang="scss">
@import '../../libs/css/style.components.scss';
.content {
background: rgba(255, 255, 255, 1);
}
.cropper {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 11;
}
.cropper-buttons {
background-color: var(--u-bg-black);
color: var(--u-divider-color);
}
.cropper-wrapper {
position: relative;
@include vue-flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
width: 100%;
background-color: var(--u-bg-black);
}
.cropper-buttons {
width: 100vw;
@include vue-flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
position: fixed;
bottom: 0;
left: 0;
font-size: 28rpx;
}
.cropper-buttons .upload,
.cropper-buttons .getCropperImage {
width: 50%;
text-align: center;
}
.cropper-buttons .upload {
text-align: left;
padding-left: 50rpx;
}
.cropper-buttons .getCropperImage {
text-align: right;
padding-right: 50rpx;
}
</style>