Skip to content
vue
<template>
    <div>
        <el-dialog title="图片编辑" :visible.sync="imageDialog" :before-close="cancelDialog" width="650px" append-to-body>
            <div>
                <div id="box" style="max-height: 50vh;overflow-y: auto;">
                    <canvas id="canvas" />
                    <div id="cur" />
                </div>
                <div style="background:#dfe1eb;width:100%;text-align: center;margin-top:10px;border-radius:5px">
                    <img src="@/assets/img/cut.png" style="width:20px;margin: 2px 10px 2px 0;cursor: pointer;"
                        @click="cutImage()" />
                    <img src="@/assets/img/mosaic.png" style="width:20px;margin: 2px 10px 2px 0;cursor: pointer;"
                        @click="mosaicImage()" />
                    <img src="@/assets/img/rotate-left.png" style="width:20px;margin: 2px 10px 2px 0;cursor: pointer;"
                        @click="rotateLeftImage()" />
                    <img src="@/assets/img/rotate-right.png" style="width:20px;margin: 2px 10px 2px 0;cursor: pointer;"
                        @click="rotateRightImage()" />
                    <!-- <img @click="directionRight(index, key, value)" src="@/assets/img/direction-right.png" style="width:20px;margin: 2px 10px 2px 0;cursor: pointer;"> -->
                </div>
            </div>
            <span slot="footer" class="dialog-footer">
                <el-button type="primary" @click="submitDialog">保 存</el-button>
                <el-button @click="cancelDialog">取 消</el-button>
            </span>
        </el-dialog>
        <OnmcCropperImage ref="OnmcCropperImage"></OnmcCropperImage>
    </div>
</template>
<script>
import { getFileUrl, ossUploadFile } from '@/utils/ossUtils.js'
import OnmcCropperImage from '@/components/Onmc-CropperImage/index.vue'
export default {
    components: {
        OnmcCropperImage
    },
    data() {
        return {
            imageDialog: false,
            newImage: {}, // 图片内容
            base64: '', // 文件base64编码
            imageFile: '', // 文件file
            imageName: '', // 文件名称
            // 父组件数组位置,为了定位旧值
            parentIndex: '', // 父组件位置
            parentKey: '', // 父组件key
            // canvas
            canvas: '',
            ctx: '',
            // oss
            ossClientInfo: '',
            fileKey: '',
        }
    },
    methods: {
        openDialog(index, key, value, item) {
            console.log(index, key, value, item)
            this.fileKey = value.itemAttachment.fileKey
            this.imageDialog = true
            this.imageName = value.itemAttachment.showName
            this.parentIndex = index
            this.parentKey = key
            this.ossClientInfo = item.ossClientInfo
            this.$nextTick(() => {
                this.onLoadCanvas(value.itemAttachment.fileUrl)
                this.mosaicImage()
            })
        },

        
        onLoadCanvas(fileUrl) {
            const img = new Image()
            img.src = fileUrl
            img.setAttribute('crossOrigin', 'Anonymous') // 设置图片跨域
            img.onload = () => {
                this.newImage.img = img
                this.newImage.width = img.width
                this.newImage.height = img.height
                this.newImage.r = 20
                // canvas渲染图片
                this.canvas = document.getElementById('canvas')
                this.ctx = this.canvas.getContext('2d')
                const cur = document.getElementById('cur')
                // 图片等比缩小
                if (img.width < 600) {
                    this.canvas.width = img.width
                    this.canvas.height = img.height
                    cur.style.width = img.width
                    cur.style.height = img.height
                    this.ctx.drawImage(img, 0, 0, img.width, img.height)
                }
                if (img.width >= 600) {
                    const scale = img.width / img.height
                    this.canvas.width = 600
                    this.canvas.height = 600 / scale
                    cur.style.width = 600
                    cur.style.height = 600 / scale
                    this.ctx.drawImage(img, 0, 0, 600, 600 / scale)
                }
                // 获取整个画布的像素数据
                this.newImage.imageData = this.ctx.getImageData(0, 0, img.width, img.height)
                // 像素点数组
                this.newImage.pixels = this.newImage.imageData.data
            }
        },

        // 输出canvas文件
        findCanvasFile() {
            const canvas = document.getElementById('canvas')
            const base64 = canvas.toDataURL('image/png')
            return base64
        },

        // 图片旋转
        async rotateLeftImage() {
            this.rotateImage('left')
        },
        async rotateRightImage() {
            this.rotateImage('right')
        },
        rotateImage(orientation) {
            const img = new Image()
            img.src = this.findCanvasFile() // 渲染新的图片
            img.setAttribute('crossOrigin', 'Anonymous') // 设置图片跨域
            img.onload = () => {
                if (img.width > img.height) {
                    this.canvas.width = img.height // 设置canvas的宽高,否则图片大小会不对
                    this.canvas.height = img.width
                }
                if (img.width < img.height) {
                    this.canvas.width = img.height // 设置canvas的宽高,否则图片大小会不对
                    this.canvas.height = img.width
                }
                if (img.width == img.height) {
                    this.canvas.width = img.width // 设置canvas的宽高,否则图片大小会不对
                    this.canvas.height = img.height
                }
                this.ctx.width = img.width // canvas 图像的宽高
                this.ctx.height = img.height
                if (orientation == 'left') {
                    this.ctx.rotate(270 * Math.PI / 180)
                    this.ctx.drawImage(img, -img.width, 0)
                }
                if (orientation == 'right') {
                    this.ctx.rotate(90 * Math.PI / 180)
                    this.ctx.drawImage(img, 0, -img.height)
                }
                this.onLoadCanvas(this.findCanvasFile())
            }
        },

       
        mosaicImage() {
            const box = document.getElementById('box')
            let isDown = false
            box.addEventListener('mousedown', () => {
                isDown = true
            })
            box.addEventListener('mouseup', () => {
                isDown = false
            })
            // 鼠标在外层盒子区域移动时,监听
            box.addEventListener('mousemove', (e) => {
                /**
                 * 获取外层盒子距离屏幕两边的距离
                 * 区域出现滚动条设置
                 * 马赛克,鼠标出现的位置
                 * +20 解决马赛克高度和鼠标高低不一样问题
                 */
                const top = box.getBoundingClientRect().top - document.getElementById('box').scrollTop + 20
                const left = box.getBoundingClientRect().left + document.body.scrollLeft
                // 没有滚动条
                // let top = box.getBoundingClientRect().top + document.body.scrollTop + 20

                const absPageX = e.pageX - left
                const absPageY = e.pageY - top
                const x = absPageX - this.newImage.r / 20
                const y = absPageY - this.newImage.r / 20
                const cur = document.getElementById('cur')
                cur.style.left = x
                cur.style.top = y
                // 鼠标按下,进行马赛克绘制
                if (isDown) {
                    this.fnDraw(x, y)
                }
            })
        },

        fnDraw(x, y) {
            const tileR = 8
            const arrPos = this.getPos(x, y)
            for (let i = 0; i < arrPos.length; i++) {
                // 获取像素中心点坐标
                const tmp = arrPos[i]
                const ty = tmp[0] * tileR + tileR / 2
                const tx = tmp[1] * tileR + tileR / 2

                const pos = (Math.floor(ty) * (this.newImage.imageData.width * 4)) + (Math.floor(tx) * 4)
                const red = this.newImage.pixels[pos]
                const green = this.newImage.pixels[pos + 1]
                const blue = this.newImage.pixels[pos + 2]
                this.ctx.fillStyle = 'rgb(' + red + ',' + green + ',' + blue + ')'
                this.ctx.fillRect(tx, ty, tileR, tileR)
            }
        },

        getPos(x, y) {
            const tileR = 8
            const curR = 20
            const rs = Math.floor(y / tileR)
            const cs = Math.floor(x / tileR)
            const number = Math.floor(curR / tileR)
            const tmp = []
            for (let i = rs; i < rs + number; i++) {
                for (let j = cs; j < cs + number; j++) {
                    tmp.push([i, j])
                }
            }
            return tmp
        },

        dataURLtoFile(dataUrl, imageName) {
            const arr = dataUrl.split(',')
            const bstr = atob(arr[1])
            let n = bstr.length
            const u8arr = new Uint8Array(n)
            while (n--) {
                u8arr[n] = bstr.charCodeAt(n)
            }
            return new File([u8arr], imageName, {
                type: 'image'
            })
        },

        // 裁剪图片
        cutImage() {
            const imageUrl = this.findCanvasFile()
            this.$refs.OnmcCropperImage.openDialog(imageUrl)
        },

      
        async submitDialog() {
            this.findImageFile()
            // 图片同步上传,返回存储路径和图片地址
            const newImageObject = await this.uploadRotateImage(this.imageFile)
            this.$emit('findImageInfo', newImageObject, this.parentIndex, this.parentKey)
            this.cancelDialog()
        },

     
        findImageFile() {
            this.base64 = this.canvas.toDataURL('image/png')
            this.imageFile = this.dataURLtoFile(this.base64, this.imageName)
        },

     
        async uploadRotateImage(file) {
            // 调用oss上传方法, 获取token
            const { ossClient, uploadPath } = this.ossClientInfo
            // eslint-disable-next-line no-unused-vars
            const uploadFile = await ossUploadFile(ossClient, this.fileKey, file)
            const newImageUrl = await getFileUrl(ossClient, this.fileKey)
            // 上传成功后,获取文件路径
            const imageObj = {
                bucketName: uploadPath.bucketName,
                keyPrefix: uploadPath.keyPrefix,
                fileKey: this.fileKey,
                fileUrl: newImageUrl
            }
            return imageObj
        },

        cancelDialog() {
            this.newImage = {}
            this.base64 = ''
            this.imageFile = ''
            this.imageName = ''
            this.parentIndex = ''
            this.parentKey = ''
            this.imageDialog = false
        }
    }
}
</script>
<style lang="stylus" scoped>
.cur
    width 10px
    height 10px
    position absolute
    background-color rgba(0,0,0,0.8)
    left 0
    top 0
    display none
</style>
vue
<template>
    <div>
        <el-dialog title="图片编辑" :visible.sync="cutDialog" :before-close="cancelDialog" width="650px" append-to-body>
            <div style="width: 100%;height: 500px">
                <vueCropper ref="vueCropper" auto-crop center-box :img="imageUrl"></vueCropper>
            </div>
            <span slot="footer" class="dialog-footer">
                <el-button type="primary" @click="submitDialog">保 存</el-button>
                <el-button @click="cancelDialog">取 消</el-button>
            </span>
        </el-dialog>
    </div>
</template>
<script>
import { VueCropper } from 'vue-cropper'
export default {
    components: {
        VueCropper
    },
    data() {
        return {
            cutDialog: false,
            imageUrl: '',
        }
    },
    methods: {
        openDialog(imageUrl) {
            this.imageUrl = imageUrl
            this.cutDialog = true
        },

        submitDialog() {
            this.$refs.vueCropper.getCropData(data => {
                this.$parent.onLoadCanvas(data)
                this.cancelDialog()
            })
        },

        cancelDialog() {
            this.imageUrl = ''
            this.cutDialog = false
        }
    }
}
</script>

Released under the MIT License.