Appearance
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>