<template> 
 | 
    <view class="image-cropper" @wheel="cropper.mousewheel" v-if="show"> 
 | 
        <canvas v-if="use2d" type="2d" id="imgCanvas" class="img-canvas" :style="{ 
 | 
            width: `${canvansWidth}px`, 
 | 
            height: `${canvansHeight}px` 
 | 
        }"></canvas> 
 | 
        <canvas v-else id="imgCanvas" canvas-id="imgCanvas" class="img-canvas" :style="{ 
 | 
            width: `${canvansWidth}px`, 
 | 
            height: `${canvansHeight}px` 
 | 
        }"></canvas> 
 | 
        <view class="pic-preview" :change:init="cropper.initObserver" :init="initData" @touchstart="cropper.touchstart" @touchmove="cropper.touchmove" @touchend="cropper.touchend"> 
 | 
            <image v-if="imgSrc" id="crop-image" class="crop-image" :style="cropper.imageStyles" :src="imgSrc" webp></image> 
 | 
            <view v-for="(item, index) in maskList" :key="item.id" :id="item.id" class="crop-mask-block" :style="cropper.maskStylesList[index]"></view> 
 | 
            <view v-if="showBorder" id="crop-border" class="crop-border" :style="cropper.borderStyles"></view> 
 | 
            <view v-if="radius > 0" id="crop-circle-box" class="crop-circle-box" :style="cropper.circleBoxStyles"> 
 | 
                <view class="crop-circle" id="crop-circle" :style="cropper.circleStyles"></view> 
 | 
            </view> 
 | 
            <block v-if="showGrid"> 
 | 
                <view v-for="(item, index) in gridList" :key="item.id" :id="item.id" class="crop-grid" :style="cropper.gridStylesList[index]"></view> 
 | 
            </block> 
 | 
            <block v-if="showAngle"> 
 | 
                <view v-for="(item, index) in angleList" :key="item.id" :id="item.id" class="crop-angle" :style="cropper.angleStylesList[index]"> 
 | 
                    <view :style="[{ 
 | 
                        width: `${angleSize}px`, 
 | 
                        height: `${angleSize}px` 
 | 
                    }]"></view> 
 | 
                </view> 
 | 
            </block> 
 | 
        </view> 
 | 
        <view class="fixed-bottom safe-area-inset-bottom"> 
 | 
            <view v-if="rotatable && !!imgSrc" class="rotate-icon" @click="cropper.rotateImage"></view> 
 | 
            <view v-if="!choosable" class="choose-btn" @click="cropClick">确定</view> 
 | 
            <block v-else-if="!!imgSrc"> 
 | 
                <view class="rechoose" @click="chooseImage">重选</view> 
 | 
                <button class="button" size="mini" @click="cropClick">确定</button> 
 | 
            </block> 
 | 
            <view v-else class="choose-btn" @click="chooseImage">录入人脸</view> 
 | 
        </view> 
 | 
    </view> 
 | 
</template> 
 | 
  
 | 
<!-- #ifdef APP-VUE || H5 --> 
 | 
<script module="cropper" lang="renderjs"> 
 | 
    import cropper from './qf-image-cropper.render.js'; 
 | 
    export default { 
 | 
        mixins: [ cropper ] 
 | 
    } 
 | 
</script> 
 | 
<!-- #endif --> 
 | 
<!-- #ifdef MP-WEIXIN || MP-QQ --> 
 | 
<script module="cropper" lang="wxs" src="./qf-image-cropper.wxs"></script> 
 | 
<!-- #endif --> 
 | 
<script> 
 | 
    /** 裁剪区域最大宽高所占屏幕宽度百分比 */ 
 | 
    const AREA_SIZE = 75; 
 | 
    /** 图片默认宽高 */ 
 | 
    const IMG_SIZE = 300; 
 | 
  
 | 
    export default { 
 | 
        name:"qf-image-cropper", 
 | 
        // #ifdef MP-WEIXIN 
 | 
        options: { 
 | 
            // 表示启用样式隔离,在自定义组件内外,使用 class 指定的样式将不会相互影响 
 | 
            styleIsolation: "isolated" 
 | 
        }, 
 | 
        // #endif 
 | 
        props: { 
 | 
            /** 图片资源地址 */ 
 | 
            src: { 
 | 
                type: String, 
 | 
                default: '' 
 | 
            }, 
 | 
            /** 裁剪宽度,有些平台或设备对于canvas的尺寸有限制,过大可能会导致无法正常绘制 */ 
 | 
            width: { 
 | 
                type: Number, 
 | 
                default: IMG_SIZE 
 | 
            }, 
 | 
            /** 裁剪高度,有些平台或设备对于canvas的尺寸有限制,过大可能会导致无法正常绘制 */ 
 | 
            height: { 
 | 
                type: Number, 
 | 
                default: IMG_SIZE 
 | 
            }, 
 | 
            /** 是否绘制裁剪区域边框 */ 
 | 
            showBorder: { 
 | 
                type: Boolean, 
 | 
                default: true 
 | 
            }, 
 | 
            /** 是否绘制裁剪区域网格参考线 */ 
 | 
            showGrid: { 
 | 
                type: Boolean, 
 | 
                default: true 
 | 
            }, 
 | 
            /** 是否展示四个支持伸缩的角 */ 
 | 
            showAngle: { 
 | 
                type: Boolean, 
 | 
                default: true 
 | 
            }, 
 | 
            /** 裁剪区域最小缩放倍数 */ 
 | 
            areaScale: { 
 | 
                type: Number, 
 | 
                default: 0.3 
 | 
            }, 
 | 
            /** 图片最大缩放倍数 */ 
 | 
            maxScale: { 
 | 
                type: Number, 
 | 
                default: 5 
 | 
            }, 
 | 
            /** 是否有回弹效果:拖动时可以拖出边界,释放时会弹回边界 */ 
 | 
            bounce: { 
 | 
                type: Boolean, 
 | 
                default: true 
 | 
            }, 
 | 
            /** 是否支持翻转 */ 
 | 
            rotatable: { 
 | 
                type: Boolean, 
 | 
                default: true 
 | 
            }, 
 | 
            /** 是否支持从本地选择素材 */ 
 | 
            choosable: { 
 | 
                type: Boolean, 
 | 
                default: true 
 | 
            }, 
 | 
            /** 四个角尺寸,单位px */ 
 | 
            angleSize: { 
 | 
                type: Number, 
 | 
                default: 20 
 | 
            }, 
 | 
            /** 四个角边框宽度,单位px */ 
 | 
            angleBorderWidth: { 
 | 
                type: Number, 
 | 
                default: 2 
 | 
            }, 
 | 
            /** 裁剪图片圆角半径,单位px */ 
 | 
            radius: { 
 | 
                type: Number, 
 | 
                default: 0 
 | 
            }, 
 | 
            /** 生成文件的类型,只支持 'jpg' 或 'png'。默认为 'png' */ 
 | 
            fileType: { 
 | 
                type: String, 
 | 
                default: 'png' 
 | 
            }, 
 | 
            /** 
 | 
             * 图片从绘制到生成所需时间,单位ms 
 | 
             * 微信小程序平台使用 `Canvas 2D` 绘制时有效 
 | 
             * 如绘制大图或出现裁剪图片空白等情况应适当调大该值,因 `Canvas 2d` 采用同步绘制,需自己把控绘制完成时间 
 | 
             */ 
 | 
            delay: { 
 | 
                type: Number, 
 | 
                default: 1000 
 | 
            }, 
 | 
            // #ifdef H5 
 | 
            /**  
 | 
             * 页面是否是原生标题栏 
 | 
             * H5平台当 showAngle 为 true 时,使用插件的页面在 `page.json` 中配置了 "navigationStyle": "custom" 时,必须将此值设为 false ,否则四个可拉伸角的触发位置会有偏差。 
 | 
             * 注:因H5平台的窗口高度是包含标题栏的,而屏幕触摸点的坐标是不包含的 
 | 
             */ 
 | 
            navigation: { 
 | 
                type: Boolean, 
 | 
                default: true 
 | 
            } 
 | 
            // #endif 
 | 
        }, 
 | 
        emits: ["crop"], 
 | 
        data() { 
 | 
            return { 
 | 
                // 用不同 id 使 v-for key 不重复 
 | 
                maskList: [ 
 | 
                    { id: 'crop-mask-block-1' }, 
 | 
                    { id: 'crop-mask-block-2' }, 
 | 
                    { id: 'crop-mask-block-3' }, 
 | 
                    { id: 'crop-mask-block-4' }, 
 | 
                ], 
 | 
                gridList: [ 
 | 
                    { id: 'crop-grid-1' }, 
 | 
                    { id: 'crop-grid-2' }, 
 | 
                    { id: 'crop-grid-3' }, 
 | 
                    { id: 'crop-grid-4' }, 
 | 
                ], 
 | 
                angleList: [ 
 | 
                    { id: 'crop-angle-1' }, 
 | 
                    { id: 'crop-angle-2' }, 
 | 
                    { id: 'crop-angle-3' }, 
 | 
                    { id: 'crop-angle-4' }, 
 | 
                ], 
 | 
                /** 本地缓存的图片路径 */ 
 | 
                imgSrc: '', 
 | 
                /** 图片的裁剪宽度 */ 
 | 
                imgWidth: IMG_SIZE, 
 | 
                /** 图片的裁剪高度 */ 
 | 
                imgHeight: IMG_SIZE, 
 | 
                /** 裁剪区域最大宽度所占屏幕宽度百分比 */ 
 | 
                widthPercent: AREA_SIZE, 
 | 
                /** 裁剪区域最大高度所占屏幕宽度百分比 */ 
 | 
                heightPercent: AREA_SIZE, 
 | 
                /** 裁剪区域布局信息 */ 
 | 
                area: {}, 
 | 
                /** 未被缩放过的图片宽 */ 
 | 
                oldWidth: 0, 
 | 
                /** 未被缩放过的图片高 */ 
 | 
                oldHeight: 0, 
 | 
                /** 系统信息 */ 
 | 
                sys: uni.getSystemInfoSync(), 
 | 
                scaleWidth: 0, 
 | 
                scaleHeight: 0, 
 | 
                rotate: 0, 
 | 
                offsetX: 0, 
 | 
                offsetY: 0, 
 | 
                use2d: false, 
 | 
                canvansWidth: 0, 
 | 
                canvansHeight: 0, 
 | 
                show: false 
 | 
                // imageStyles: {}, 
 | 
                // maskStylesList: [{}, {}, {}, {}], 
 | 
                // borderStyles: {}, 
 | 
                // gridStylesList: [{}, {}, {}, {}], 
 | 
                // angleStylesList: [{}, {}, {}, {}], 
 | 
                // circleBoxStyles: {}, 
 | 
                // circleStyles: {}, 
 | 
            } 
 | 
        }, 
 | 
        computed: { 
 | 
            initData() { 
 | 
                // console.log('initData') 
 | 
                return { 
 | 
                    timestamp: new Date().getTime(), 
 | 
                    area: { 
 | 
                        ...this.area, 
 | 
                        bounce: this.bounce, 
 | 
                        showBorder: this.showBorder, 
 | 
                        showGrid: this.showGrid, 
 | 
                        showAngle: this.showAngle, 
 | 
                        angleSize: this.angleSize, 
 | 
                        angleBorderWidth: this.angleBorderWidth, 
 | 
                        minScale: this.areaScale, 
 | 
                        widthPercent: this.widthPercent, 
 | 
                        heightPercent: this.heightPercent, 
 | 
                        radius: this.radius 
 | 
                    }, 
 | 
                    sys: this.sys, 
 | 
                    img: { 
 | 
                        maxScale: this.maxScale, 
 | 
                        src: this.imgSrc, 
 | 
                        width: this.oldWidth, 
 | 
                        height: this.oldHeight, 
 | 
                        oldWidth: this.oldWidth, 
 | 
                        oldHeight: this.oldHeight, 
 | 
                    } 
 | 
                } 
 | 
            }, 
 | 
            imgProps() { 
 | 
                return { 
 | 
                    width: this.width, 
 | 
                    height: this.height, 
 | 
                    src: this.src, 
 | 
                } 
 | 
            } 
 | 
        }, 
 | 
        watch: { 
 | 
            imgProps: { 
 | 
                handler(val) { 
 | 
                    // console.log('imgProps', val) 
 | 
                    // 自定义裁剪尺,示例如下: 
 | 
                    this.imgWidth = Number(val.width) || IMG_SIZE; 
 | 
                    this.imgHeight = Number(val.height) || IMG_SIZE; 
 | 
                    let use2d = true; 
 | 
                    // #ifndef MP-WEIXIN 
 | 
                    use2d = false; 
 | 
                    // #endif 
 | 
                    // if(use2d && (this.imgWidth > 1365 || this.imgHeight > 1365)) { 
 | 
                    //     use2d = false; 
 | 
                    // } 
 | 
                    let canvansWidth = this.imgWidth; 
 | 
                    let canvansHeight = this.imgHeight; 
 | 
                    let size = Math.max(canvansWidth, canvansHeight) 
 | 
                    let scalc = 1; 
 | 
                    if(size > 1365) { 
 | 
                        scalc = 1365 / size; 
 | 
                    } 
 | 
                    this.canvansWidth = canvansWidth * scalc; 
 | 
                    this.canvansHeight = canvansHeight * scalc; 
 | 
                    this.use2d = use2d; 
 | 
                    this.initArea(); 
 | 
                    val.src && this.initImage(val.src); 
 | 
                }, 
 | 
                immediate: true 
 | 
            }, 
 | 
        }, 
 | 
        methods: { 
 | 
            open() { 
 | 
                this.show = true 
 | 
            }, 
 | 
            close() { 
 | 
                this.show = false 
 | 
            }, 
 | 
            /** 提供给wxs调用,用来接收图片变更数据 */ 
 | 
            dataChange(e) { 
 | 
                // console.log('dataChange', e) 
 | 
                this.scaleWidth = e.width; 
 | 
                this.scaleHeight = e.height; 
 | 
                this.rotate = e.rotate; 
 | 
                this.offsetX = e.x; 
 | 
                this.offsetY = e.y; 
 | 
            }, 
 | 
            /** 初始化裁剪区域布局信息 */ 
 | 
            initArea() { 
 | 
                // 底部操作栏高度 = 底部底部操作栏内容高度 + 设备底部安全区域高度 
 | 
                this.sys.offsetBottom = uni.upx2px(100) + this.sys.safeAreaInsets.bottom; 
 | 
                // #ifndef H5 
 | 
                this.sys.windowTop = 0; 
 | 
                this.sys.navigation = true; 
 | 
                // #endif 
 | 
                // #ifdef H5 
 | 
                // h5平台的窗口高度是包含标题栏的 
 | 
                this.sys.windowTop = this.sys.windowTop || 44; 
 | 
                this.sys.navigation = this.navigation; 
 | 
                // #endif 
 | 
                let wp = this.widthPercent; 
 | 
                let hp = this.heightPercent; 
 | 
                if (this.imgWidth > this.imgHeight) { 
 | 
                    hp = hp * this.imgHeight / this.imgWidth; 
 | 
                } else if (this.imgWidth < this.imgHeight) { 
 | 
                    wp = wp * this.imgWidth / this.imgHeight; 
 | 
                } 
 | 
                const size = this.sys.windowWidth > this.sys.windowHeight ? this.sys.windowHeight : this.sys.windowWidth; 
 | 
                const width = size * wp / 100; 
 | 
                const height = size * hp / 100; 
 | 
                const left = (this.sys.windowWidth - width) / 2; 
 | 
                const right = left + width; 
 | 
                const top = (this.sys.windowHeight + this.sys.windowTop - this.sys.offsetBottom - height) / 2; 
 | 
                const bottom = this.sys.windowHeight + this.sys.windowTop - this.sys.offsetBottom - top; 
 | 
                this.area = { width, height, left, right, top, bottom }; 
 | 
                this.scaleWidth = width; 
 | 
                this.scaleHeight = height; 
 | 
            }, 
 | 
            /** 从本地选取图片 */ 
 | 
            chooseImage() { 
 | 
                // #ifdef MP-WEIXIN || MP-JD 
 | 
                if(uni.chooseMedia) { 
 | 
                    uni.chooseMedia({ 
 | 
                        count: 1, 
 | 
                        mediaType: ['image'], 
 | 
                        success: (res) => { 
 | 
                            this.resetData(); 
 | 
                            this.initImage(res.tempFiles[0].tempFilePath); 
 | 
                        } 
 | 
                    }); 
 | 
                    return; 
 | 
                } 
 | 
                // #endif 
 | 
                uni.chooseImage({ 
 | 
                    count: 1, 
 | 
                    success: (res) => { 
 | 
                        this.resetData(); 
 | 
                        this.initImage(res.tempFiles[0].path); 
 | 
                    } 
 | 
                }); 
 | 
            }, 
 | 
            /** 重置数据 */ 
 | 
            resetData() { 
 | 
                this.imgSrc = ''; 
 | 
                this.rotate = 0; 
 | 
                this.offsetX = 0; 
 | 
                this.offsetY = 0; 
 | 
                this.initArea(); 
 | 
            }, 
 | 
            /** 
 | 
             * 初始化图片信息 
 | 
             * @param {String} url 图片链接 
 | 
             */ 
 | 
            initImage(url) { 
 | 
                uni.getImageInfo({ 
 | 
                    src: url, 
 | 
                    success: (res) => { 
 | 
                        this.imgSrc = res.path; 
 | 
                        let scale = res.width / res.height; 
 | 
                        let areaScale = this.area.width / this.area.height; 
 | 
                        if (scale > 1) { // 横向图片 
 | 
                            if (scale >= areaScale) { // 图片宽不小于目标宽,则高固定,宽自适应 
 | 
                                this.scaleWidth = (this.scaleHeight / res.height) * this.scaleWidth * (res.width / this.scaleWidth); 
 | 
                            } else { // 否则宽固定、高自适应 
 | 
                                this.scaleHeight = res.height * this.scaleWidth / res.width; 
 | 
                            } 
 | 
                        } else { // 纵向图片 
 | 
                            if (scale <= areaScale) { // 图片高不小于目标高,宽固定,高自适应 
 | 
                                this.scaleHeight = (this.scaleWidth / res.width) * this.scaleHeight / (this.scaleHeight / res.height); 
 | 
                            } else { // 否则高固定,宽自适应 
 | 
                                this.scaleWidth = res.width * this.scaleHeight / res.height; 
 | 
                            } 
 | 
                        } 
 | 
                        // 记录原始宽高,为缩放比列做限制 
 | 
                        this.oldWidth = this.scaleWidth; 
 | 
                        this.oldHeight = this.scaleHeight; 
 | 
                    }, 
 | 
                    fail: (err) => { 
 | 
                        console.error(err) 
 | 
                    } 
 | 
                }); 
 | 
            }, 
 | 
            /** 
 | 
             * 剪切图片圆角 
 | 
             * @param {Object} ctx canvas 的绘图上下文对象 
 | 
             * @param {Number} radius 圆角半径 
 | 
             * @param {Number} scale 生成图片的实际尺寸与截取区域比 
 | 
             * @param {Function} drawImage 执行剪切时所调用的绘图方法,入参为是否执行了剪切 
 | 
             */ 
 | 
            drawClipImage(ctx, radius, scale, drawImage) { 
 | 
                if(radius > 0) { 
 | 
                    ctx.save(); 
 | 
                    ctx.beginPath(); 
 | 
                    const w = this.canvansWidth; 
 | 
                    const h = this.canvansHeight; 
 | 
                    if(w === h && radius >= w / 2) { // 圆形 
 | 
                        ctx.arc(w / 2, h / 2, w / 2, 0, 2 * Math.PI); 
 | 
                    } else { // 圆角矩形 
 | 
                        if(w !== h) { // 限制圆角半径不能超过短边的一半 
 | 
                            radius = Math.min(w / 2, h / 2, radius); 
 | 
                            // radius = Math.min(Math.max(w, h) / 2, radius); 
 | 
                        } 
 | 
                        ctx.moveTo(radius, 0); 
 | 
                        ctx.arcTo(w, 0, w, h, radius); 
 | 
                        ctx.arcTo(w, h, 0, h, radius); 
 | 
                        ctx.arcTo(0, h, 0, 0, radius); 
 | 
                        ctx.arcTo(0, 0, w, 0, radius); 
 | 
                        ctx.closePath(); 
 | 
                    } 
 | 
                    ctx.clip(); 
 | 
                    drawImage && drawImage(true); 
 | 
                    ctx.restore(); 
 | 
                } else { 
 | 
                    drawImage && drawImage(false); 
 | 
                } 
 | 
            }, 
 | 
            /** 
 | 
             * 旋转图片 
 | 
             * @param {Object} ctx canvas 的绘图上下文对象 
 | 
             * @param {Number} rotate 旋转角度 
 | 
             * @param {Number} scale 生成图片的实际尺寸与截取区域比 
 | 
             */ 
 | 
            drawRotateImage(ctx, rotate, scale) { 
 | 
                if(rotate !== 0) { 
 | 
                    // 1. 以图片中心点为旋转中心点 
 | 
                    const x = this.scaleWidth * scale / 2; 
 | 
                    const y = this.scaleHeight * scale / 2; 
 | 
                    ctx.translate(x, y); 
 | 
                    // 2. 旋转画布 
 | 
                    ctx.rotate(rotate * Math.PI / 180); 
 | 
                    // 3. 旋转完画布后恢复设置旋转中心时所做的偏移 
 | 
                    ctx.translate(-x, -y); 
 | 
                } 
 | 
            }, 
 | 
            drawImage(ctx, image, callback) { 
 | 
                // 生成图片的实际尺寸与截取区域比 
 | 
                const scale = this.canvansWidth / this.area.width; 
 | 
                this.drawClipImage(ctx, this.radius, scale, () => { 
 | 
                    this.drawRotateImage(ctx, this.rotate, scale); 
 | 
                    const r = this.rotate / 90; 
 | 
                    ctx.drawImage( 
 | 
                        image, 
 | 
                        [ 
 | 
                            (this.offsetX - this.area.left), 
 | 
                            (this.offsetY - this.area.top), 
 | 
                            -(this.offsetX - this.area.left), 
 | 
                            -(this.offsetY - this.area.top) 
 | 
                        ][r] * scale, 
 | 
                        [ 
 | 
                            (this.offsetY - this.area.top), 
 | 
                            -(this.offsetX - this.area.left), 
 | 
                            -(this.offsetY - this.area.top), 
 | 
                            (this.offsetX - this.area.left) 
 | 
                        ][r] * scale, 
 | 
                        this.scaleWidth * scale, 
 | 
                        this.scaleHeight * scale 
 | 
                    ); 
 | 
                }); 
 | 
            }, 
 | 
            /** 
 | 
             * 绘图 
 | 
             * @param {Object} canvas  
 | 
             * @param {Object} ctx canvas 的绘图上下文对象 
 | 
             * @param {String} src 图片路径 
 | 
             * @param {Function} callback 开始绘制时回调 
 | 
             */ 
 | 
            draw2DImage(canvas, ctx, src, callback) { 
 | 
                // console.log('draw2DImage', canvas, ctx, src, callback) 
 | 
                if(canvas) { 
 | 
                    const image = canvas.createImage(); 
 | 
                    image.onload = () => { 
 | 
                        this.drawImage(ctx, image); 
 | 
                        // 如果觉得`生成时间过长`或`出现生成图片空白`可尝试调整延迟时间 
 | 
                        callback && setTimeout(callback, this.delay); 
 | 
                    }; 
 | 
                    image.onerror = (err) => { 
 | 
                        console.error(err) 
 | 
                        uni.hideLoading(); 
 | 
                    }; 
 | 
                    image.src = src; 
 | 
                } else { 
 | 
                    this.drawImage(ctx, src); 
 | 
                    setTimeout(() => { 
 | 
                        ctx.draw(false, callback); 
 | 
                    }, 200); 
 | 
                } 
 | 
            }, 
 | 
            /** 
 | 
             * 画布转图片到本地缓存 
 | 
             * @param {Object} canvas  
 | 
             * @param {String} canvasId  
 | 
             */ 
 | 
            canvasToTempFilePath(canvas, canvasId) { 
 | 
                // console.log('canvasToTempFilePath', canvas, canvasId) 
 | 
                uni.canvasToTempFilePath({ 
 | 
                    canvas, 
 | 
                    canvasId, 
 | 
                    x: 0, 
 | 
                    y: 0, 
 | 
                    width: this.canvansWidth, 
 | 
                    height: this.canvansHeight, 
 | 
                    destWidth: this.imgWidth, // 必要,保证生成图片宽度不受设备分辨率影响 
 | 
                    destHeight: this.imgHeight, // 必要,保证生成图片高度不受设备分辨率影响 
 | 
                    fileType: this.fileType, // 目标文件的类型,默认png 
 | 
                    success: (res) => { 
 | 
                        // 生成的图片临时文件路径 
 | 
                        this.handleImage(res.tempFilePath); 
 | 
                    }, 
 | 
                    fail: (err) => { 
 | 
                        uni.hideLoading(); 
 | 
                        uni.showToast({ title: '裁剪失败,生成图片异常!', icon: 'none' }); 
 | 
                    } 
 | 
                }, this); 
 | 
            }, 
 | 
            /** 确认裁剪 */ 
 | 
            cropClick() { 
 | 
                uni.showLoading({ title: '裁剪中...', mask: true }); 
 | 
                if(!this.use2d) { 
 | 
                    const ctx = uni.createCanvasContext('imgCanvas', this); 
 | 
                    ctx.clearRect(0, 0, this.canvansWidth, this.canvansHeight); 
 | 
                    this.draw2DImage(null, ctx, this.imgSrc, () => { 
 | 
                        this.canvasToTempFilePath(null, 'imgCanvas'); 
 | 
                    }); 
 | 
                    return; 
 | 
                } 
 | 
                // #ifdef MP-WEIXIN 
 | 
                const query = uni.createSelectorQuery().in(this); 
 | 
                query.select('#imgCanvas') 
 | 
                    .fields({ node: true, size: true }) 
 | 
                    .exec((res) => { 
 | 
                        const canvas = res[0].node; 
 | 
                                         
 | 
                        const dpr = uni.getSystemInfoSync().pixelRatio; 
 | 
                        canvas.width = res[0].width * dpr; 
 | 
                        canvas.height = res[0].height * dpr; 
 | 
                        const ctx = canvas.getContext('2d'); 
 | 
                        ctx.scale(dpr, dpr); 
 | 
                        ctx.clearRect(0, 0, this.canvansWidth, this.canvansHeight); 
 | 
                         
 | 
                        this.draw2DImage(canvas, ctx, this.imgSrc, () => { 
 | 
                            this.canvasToTempFilePath(canvas); 
 | 
                        }); 
 | 
                    }); 
 | 
                // #endif 
 | 
            }, 
 | 
            handleImage(tempFilePath){ 
 | 
                // 在H5平台下,tempFilePath 为 base64 
 | 
                // console.log(tempFilePath) 
 | 
                uni.hideLoading(); 
 | 
                this.$emit('crop', { tempFilePath }); 
 | 
            } 
 | 
        } 
 | 
    } 
 | 
</script> 
 | 
  
 | 
<style lang="scss" scoped> 
 | 
    .image-cropper { 
 | 
        position: fixed; 
 | 
        left: 0; 
 | 
        right: 0; 
 | 
        top: 0; 
 | 
        bottom: 0; 
 | 
        z-index: 999999; 
 | 
        overflow: hidden; 
 | 
        display: flex; 
 | 
        flex-direction: column; 
 | 
        background-color: #000; 
 | 
        .img-canvas { 
 | 
            position: absolute !important; 
 | 
            transform: translateX(-100%); 
 | 
        } 
 | 
        .pic-preview { 
 | 
            width: 100%; 
 | 
            flex: 1; 
 | 
            position: relative; 
 | 
  
 | 
            .crop-mask-block { 
 | 
                background-color: rgba(51, 51, 51, 0.8); 
 | 
                z-index: 2; 
 | 
                position: fixed; 
 | 
                box-sizing: border-box; 
 | 
                pointer-events: none; 
 | 
            } 
 | 
            .crop-circle-box { 
 | 
                position: fixed; 
 | 
                box-sizing: border-box; 
 | 
                z-index: 2; 
 | 
                pointer-events: none; 
 | 
                overflow: hidden; 
 | 
                .crop-circle { 
 | 
                    width: 100%; 
 | 
                    height: 100%; 
 | 
                } 
 | 
            } 
 | 
            .crop-image { 
 | 
                padding: 0 !important; 
 | 
                margin: 0 !important; 
 | 
                border-radius: 0 !important; 
 | 
                display: block !important; 
 | 
            } 
 | 
            .crop-border { 
 | 
                position: fixed; 
 | 
                border: 1px solid #fff; 
 | 
                box-sizing: border-box; 
 | 
                z-index: 3; 
 | 
                pointer-events: none; 
 | 
            } 
 | 
            .crop-grid { 
 | 
                position: fixed; 
 | 
                z-index: 3; 
 | 
                border-style: dashed; 
 | 
                border-color: #fff; 
 | 
                pointer-events: none; 
 | 
                opacity: 0.5; 
 | 
            } 
 | 
            .crop-angle { 
 | 
                position: fixed; 
 | 
                z-index: 3; 
 | 
                border-style: solid; 
 | 
                border-color: #fff; 
 | 
                pointer-events: none; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        .fixed-bottom { 
 | 
            position: fixed; 
 | 
            left: 0; 
 | 
            right: 0; 
 | 
            bottom: 0; 
 | 
            z-index: 99; 
 | 
            display: flex; 
 | 
            flex-direction: row; 
 | 
            background-color: $uni-bg-color-grey; 
 | 
  
 | 
            .rotate-icon { 
 | 
                background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAABCFJREFUaEPtml3IpVMUx3//ko/ChTIyiGFSMyhllI8bc4F85yuNC2FCqLmQC1+FZORiEkUMNW7UjKjJULgxV+NzSkxDhEkZgwsyigv119J63p7zvOc8z37OmXdOb51dz82711r7/99r7bXXXucVi3xokeNnRqCvB20fDmwAlgK/5bcD+FTSr33tHXQP2H4MeHQE0A+B5yRtLiUyDQJrgVc6AAaBpyV93kXkoBMIQLbfBS5NcK8BRwDXNcD+AdwnaVMbiWkRCPBBohpxHuK7M7865sclRdgNHVMhkF6IMIpwirFEUhzo8M7lwIvASTXEqyVtH8ZgagQSbOzsDknv18HZXpHn5IL8+94IOUm7miSmSqAttjPdbgGuTrnNktYsGgLpoYuAD2qg1zRTbG8P2D4SOC6/Q7vSHPALsE/S7wWy80RsPw/ckxMfSTq/LtRJwPbxwF3ASiCUTxwHCPAnEBfVF8AWSTtL7Ng+LfWOTfmlkn6udFsJ5K15R6a4kvX6yGyUFBvTOWzHXXFzCt4g6c1OArYj9iIGh43YgR+BvztXh1PSa4cMkd0jaVmXDduPAE+k3HpJD7cSGFKvfAc8FQUX8IOk/V2L1udtB/hTgdOBW4Aba/M7Ja1qs2f7euCNlHlZUlx4/495IWQ7Jl+qGbxX0gt9AHfJ2o6zFBVoNVrDKe+F3Sm8VdK1bQQ+A85JgXckXdkFaJx527cC9TpnVdvBtl3h2iapuhsGPdBw1b9xnUvaNw7AEh3bnwDnpuwGSfeP0rN9NvAMELXRXFkxEEK2nwQeSiOtRVQJwC4Z29cAW1Nuu6TVXTrN+SaBt4ErUug2Sa/2NdhH3vZy4NvU2S/p6D768w5xI3WOrAD7LtISFpGdIhVXKfaYvjd20wP13L9M0p4DBbaFRKToSLExVkr6qs+aIwlI6iwz+izUQqC+ab29PiMwqRcmPXczD8w8MFj1zg7xXEqbpdHCw7FgWSjafZL+KcQxtpjteCeflwYulFR/J3TabSslVkj6utPChAK2f6q9uZdLitKieLQRuExSvX9ZbLRUMFs09efpUZL+KtUfVo1GW/umNHC3pOhRLtiwfSbwZS6wV9IJfRdreuBBYH0a2STp9r4G+8jbXgc8mzoDT8VSO00ClwDv1ZR7XyylC4ec7ejaLUmdsV6Aw7oSbwFXpdFdks7qA6pU1na0aR6owgeIR/1cx63UzjAC0YXYVjMQHlkn6ZtSo21ytuPZGKFagQ/xsXZ/3iGuFrYdjafXG0DiQMeBi47c9/GV3BO247UV38n5o0UAP6xmu7jFOGxjRr66On5NPBDOCBsDTapxjHY1dyOcolNXnYlx1himE53p2PmNkxosevfavhg4Izt2k7TXPwZ2S6p6QZPin/2rwcQ7OKmBohCadJGF1P8PG6aaQBKVX/8AAAAASUVORK5CYII='); 
 | 
                background-size: 60% 60%; 
 | 
                background-repeat: no-repeat; 
 | 
                background-position:center; 
 | 
                width: 80rpx; 
 | 
                height: 80rpx; 
 | 
                position: absolute; 
 | 
                top: -90rpx; 
 | 
                left: 10rpx; 
 | 
                transform: rotateY(180deg); 
 | 
            } 
 | 
              
 | 
            .rechoose { 
 | 
                color: $uni-color-primary; 
 | 
                padding: 0 $uni-spacing-row-lg; 
 | 
                line-height: 100rpx; 
 | 
            } 
 | 
  
 | 
            .choose-btn { 
 | 
                color: $uni-color-primary; 
 | 
                text-align: center; 
 | 
                line-height: 100rpx; 
 | 
                flex: 1; 
 | 
            } 
 | 
  
 | 
            .button { 
 | 
                margin: auto $uni-spacing-row-lg auto auto; 
 | 
                background-color: $uni-color-primary; 
 | 
                color: #fff; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        .safe-area-inset-bottom { 
 | 
            padding-bottom: 0;   
 | 
            padding-bottom: constant(safe-area-inset-bottom); // 兼容 IOS<11.2 
 | 
            padding-bottom: env(safe-area-inset-bottom); // 兼容 IOS>=11.2 
 | 
        } 
 | 
  
 | 
    } 
 | 
</style> 
 |