<template>
<div class="canvas_content">
<div class="canvas_back">
<div class="edit_content">
<div class="paint_brush paint_color">
<input type="color" v-model="colorModel" />
</div>
<div class="paint_brush paint_color">
<img src="../../assets/images/画笔.png" alt="" />
<img src="../../assets/images/画笔加粗.png" alt="" @click="add" />
<div
:style="{
border: lineWidth + 'px solid #FFFFFF',
}"
class="border"
></div>
<img src="../../assets/images/画笔减粗.png" alt="" @click="reduce" />
</div>
<div class="paint_brush paint_color">
<img src="../../assets/images/文字.png" alt="" @click="handleEdit" />
<a-select v-model="fontValue">
<a-select-option
v-for="(item, index) in fontSizeValue"
:key="index"
:value="item.name"
>
{{ item.name }}
</a-select-option>
</a-select>
</div>
<div class="paint_brush paint_color">
<img src="../../assets/images/加粗.png" alt="" @click="handelBold" />
</div>
<div class="paint_brush paint_color">
<img
@click="rotateImg"
src="../../assets/images/旋转@2x.png"
alt=""
/>
</div>
<div class="paint_brush paint_color">
<img
src="../../assets/images/反撤销.png"
alt=""
@click="eraserReduce"
/>
<img src="../../assets/images/撤销.png" alt="" @click="eraserAdd" />
</div>
<div class="paint_brush paint_color">
<img src="../../assets/images/放大.png" alt="" @click="enlarge" />
<img src="../../assets/images/缩小.png" alt="" @click="narrow" />
</div>
</div>
<div class="canvas-box">
<div class="canvas" :style="{ zoom }">
<canvas
@mousedown="mousedown($event)"
ref="refCanvas"
id="paintingId"
:width="width"
:height="height"
></canvas>
<canvas
@mousedown="mousedown($event)"
ref="imgCanvas"
id="imgCanvas"
:width="width"
:height="height"
></canvas>
<div
class="text-box"
:class="boldStatus ? 'bold_font' : 'no-bold'"
v-if="textBoxRect"
v-focus
contenteditable
@input="textValueInput"
:style="{
left: textBoxRect.left + 'px',
top: textBoxRect.top + 'px',
height: fontHeight + 'px',
fontSize: fontValue + 'px',
fontWeight: boldStatus ? '600' : 'normal',
color: colorModel,
}"
></div>
</div>
</div>
<div class="tips" v-if="showTipsStatus">
编辑后的图片将保存于“批阅作业”区域
<img
@click="closeShowTips"
src="../../assets/images/close-circle.png"
alt=""
/>
</div>
<div class="button">
<a-button type="link" style="color: #aeaeae" @click="$emit('close')">
取消
</a-button>
<a-button type="link" @click="defineImg"> 保存批阅 </a-button>
</div>
</div>
</div>
</template>
<script type="text/ecmascript-6">
import Shadow from '@/components/common/Shadow'
import BoxHeader from '@/components/common/BoxHeader'
import html2canvas from 'html2canvas'
import { getOssSign } from '@/apis/common'
import { formatUploadFile } from '@/assets/js/Common'
import { getRecordEdit } from '@/apis/homework'
import { State, Getter, Action, Mutation, namespace } from 'vuex-class'
import file from '@/mixin/common/file'
export default {
name: '',
components: { Shadow, BoxHeader },
data() {
return {
title: '编辑',
eraser: null,
canvas: null,
ctx: null,
imgCtx: null,
imgCanvas: null,
lineWidth: 1,
mode: '',
textBoxRect: null,
showTipsStatus: true,
textValue: '',
width: 800,
height: 600,
zoom: 1,
imgStatus: {
rotate: -1,
width: 0,
height: 0,
originWidth: 0,
originHeight: 0,
},
imgRatio: 1,
colorModel: '#F00505',
step: -1,
canvasHistory: [],
fontValue: '14',
fontSizeValue: [
{
name: '8',
id: 1,
},
{
name: '10',
id: 2,
},
{
name: '12',
id: 3,
},
{
name: '14',
id: 4,
},
{
name: '16',
id: 5,
},
{
name: '18',
id: 6,
},
{
name: '24',
id: 7,
},
{
name: '36',
id: 8,
},
],
boldStatus: false,
uploadSign: null,
fontHeight: null,
ossConfig: {
path: 'homeworkdetail/',
host: '上传oss的地址',
bucket: '名字',
},
rotateStatua: null,
}
},
mixins: [file],
computed: {},
head() {
return {}
},
props: ['imgPainting', 'imgObject', 'modifyImgObj'],
watch: {},
created() {},
mounted() {
this.initState()
if (
sessionStorage.getItem('showTipsStatus') &&
!JSON.parse(sessionStorage.getItem('showTipsStatus'))
) {
this.showTipsStatus = JSON.parse(sessionStorage.getItem('showTipsStatus'))
}
},
watch: {
fontValue(val) {
if (val === 18) {
this.fontHeight = 30
}
},
},
directives: {
focus: {
inserted(el) {
setTimeout(() => {
el.focus()
}, 100)
},
componentUpdated(el) {
setTimeout(() => {
el.focus()
}, 100)
},
},
},
methods: {
calcImg() {
const status = this.imgStatus
const { originWidth, originHeight } = status
status.rotate = (status.rotate + 1) % 4
const baseWidth = status.rotate % 2 ? this.height : this.width
const baseHeight = status.rotate % 2 ? this.width : this.height
const ratio = originWidth / (originHeight || 1)
status.width = ratio > 1 ? baseWidth : baseHeight * ratio
status.height = status.width / ratio
switch (status.rotate) {
case 0:
status.x = (this.width - status.width) / 2
status.y = (this.height - status.height) / 2
break
case 1:
status.x = (this.height - status.width) / 2
status.y = -(status.height + this.width) / 2
break
case 2:
status.x = -(this.width + status.width) / 2
status.y = -(this.height + status.height) / 2
break
case 3:
status.x = -(this.height + status.width) / 2
status.y = (this.width - status.height) / 2
break
}
console.log('status', status)
},
initState() {
this.imgCanvas = this.$refs.imgCanvas
this.canvas = this.$refs.refCanvas
this.imgCtx = this.imgCanvas.getContext('2d')
this.ctx = this.canvas.getContext('2d')
const image = new Image()
image.onload = () => {
this.imgStatus.originWidth = image.width
this.imgStatus.originHeight = image.height
this.calcImg()
this.imgCtx.translate(this.imgStatus.x, this.imgStatus.y)
this.imgCtx.drawImage(
image,
0,
0,
this.imgStatus.width,
this.imgStatus.height
)
}
image.setAttribute('crossOrigin', 'anonymous')
image.src = this.imgPainting
},
narrow() {
if (this.zoom < 1.1) return
this.zoom -= 0.1
},
enlarge() {
this.zoom += 0.1
},
rotateImg() {
const image = new Image()
image.onload = () => {
this.imgCtx.clearRect(
-16,
-16,
this.imgStatus.width + 32,
this.imgStatus.height + 32
)
this.imgCtx.translate(-this.imgStatus.x, -this.imgStatus.y)
this.imgCtx.rotate(Math.PI / 2)
this.calcImg()
this.imgCtx.translate(this.imgStatus.x, this.imgStatus.y)
this.imgCtx.drawImage(
image,
0,
0,
this.imgStatus.width,
this.imgStatus.height
)
}
image.setAttribute('crossOrigin', 'anonymous')
image.src = this.imgPainting
if (this.rotateStatua >= 3) {
this.rotateStatua = null
} else {
this.rotateStatua += 1
}
},
closeShowTips() {
this.showTipsStatus = false
JSON.stringify(
sessionStorage.setItem('showTipsStatus', this.showTipsStatus)
)
},
mousedown(e) {
if (this.step < 0) {
this.step++
this.canvasHistory.push(this.canvas.toDataURL())
}
const disX = e.offsetX / this.zoom
const disY = e.offsetY / this.zoom
if (this.mode === 'text') {
if (this.textValue) {
this.drawText()
this.canvasHistory.push(this.canvas.toDataURL())
} else {
this.textBoxRect = {
left: disX,
top: disY,
}
}
}
this.ctx.beginPath()
this.ctx.lineWidth = this.lineWidth
this.ctx.strokeStyle = this.colorModel
this.ctx.moveTo(disX, disY)
document.addEventListener('mousemove', this.mousemove)
document.addEventListener('mouseup', this.mouseup)
},
canvasTextAutoLit() {
let lineWidth = 0
let lastSubStrIndex = 0
let initHeight = 15
let maxWidth = 200
for (let i = 0; i < this.textValue.length; i++) {
lineWidth += this.ctx.measureText(this.textValue[i]).width
console.log(this.textBoxRect.left, lineWidth)
if (this.textBoxRect.left > 710) {
maxWidth = 40
}
if (this.textBoxRect.left > 660 && this.textBoxRect.left < 710) {
maxWidth = 80
}
if (this.textBoxRect.left < 670 && this.textBoxRect.left > 600) {
maxWidth = 150
}
if (this.textBoxRect.left > 730) {
this.ctx.fillText(
this.textValue.substring(lastSubStrIndex, i),
this.textBoxRect.left,
this.textBoxRect.top + initHeight
)
initHeight += 20
lineWidth = 0
lastSubStrIndex = i
}
if (lineWidth > maxWidth) {
this.ctx.fillText(
this.textValue.substring(lastSubStrIndex, i),
this.textBoxRect.left,
this.textBoxRect.top + initHeight
)
initHeight += 20
lineWidth = 0
lastSubStrIndex = i
}
if (i == this.textValue.length - 1) {
this.ctx.fillText(
this.textValue.substring(lastSubStrIndex, i + 1),
this.textBoxRect.left,
this.textBoxRect.top + initHeight
)
}
}
},
drawText() {
this.ctx.moveTo(this.textBoxRect.left, this.textBoxRect.top)
this.ctx.fillStyle = this.colorModel
this.ctx.font = `${this.boldStatus ? '600' : 'normal'} ${
this.fontValue
}px "微软雅黑"`
this.ctx.textBaseline = 'top'
this.ctx.textAlign = 'left'
this.ctx.lineWidth = 1
this.canvasTextAutoLit()
this.textBoxRect = null
this.mode = ''
this.textValue = ''
this.boldStatus = false
},
handelBold() {
this.boldStatus = !this.boldStatus
console.log(this.boldStatus)
},
mousemove(e) {
const disX = e.offsetX / this.zoom
const disY = e.offsetY / this.zoom
this.ctx.lineTo(disX, disY)
this.ctx.stroke()
},
mouseup() {
this.step++
if (this.step < (this.canvasHistory && this.canvasHistory.length)) {
this.canvasHistory.length = this.step
}
document.removeEventListener('mousemove', this.mousemove)
document.removeEventListener('mouseup', this.mouseup)
this.canvasHistory.push(this.canvas.toDataURL())
},
add() {
if (this.lineWidth >= 6) return
this.lineWidth++
},
reduce() {
if (this.lineWidth > 1) {
this.lineWidth--
}
},
eraserAdd() {
if (this.step <= this.canvasHistory.length - 1) {
this.step++
let canvasPic = new Image()
canvasPic.src = this.canvasHistory[this.step]
canvasPic.addEventListener('load', () => {
this.ctx.clearRect(0, 0, this.width, this.height)
this.ctx.drawImage(canvasPic, 0, 0)
})
} else {
this.$message.info('已经是最新的操作了')
}
},
eraserReduce() {
if (this.step > 0) {
this.step--
this.ctx.clearRect(0, 0, this.width, this.height)
let canvasPic = new Image()
canvasPic.src = this.canvasHistory[this.step]
canvasPic.addEventListener('load', () => {
this.ctx.drawImage(canvasPic, 0, 0)
})
} else {
this.$message.info('不能再继续撤销了')
}
},
async defineImg() {
let flag = true
if (flag) {
flag = false
this.imgCtx.translate(-this.imgStatus.x, -this.imgStatus.y)
this.imgCtx.rotate((-this.imgStatus.rotate * Math.PI) / 2)
this.imgCtx.drawImage(this.canvas, 0, 0)
let blob = this.getBlob(this.imgCanvas)
if (
!this.fileTypes.imgTypes.some(
(type) => String(this.imgObject.fileName).indexOf(type) !== -1
)
) {
this.imgObject.fileName =
this.imgObject.fileName + '.' + this.imgObject.fileType
}
let fileOfBlob = new window.File([blob], this.imgObject.fileName, {
type: this.imgObject.fileType,
})
let fileUploadInfo = await this.ossSign(fileOfBlob)
let obj = {
file: {
name: this.imgObject.fileName,
size: this.imgObject.fileSize,
type: this.imgObject.fileType,
},
uuid: fileUploadInfo.uuid,
url: fileUploadInfo.fileList,
fileUrl:
'oss地址' +
fileUploadInfo.fileList,
}
this.$emit('defineEditImg', obj)
this.getRecordEdit()
flag = true
}
},
async getRecordEdit() {
let res = await this.$axios.post(getRecordEdit, this.modifyImgObj)
if (res.status === 200 && res.data.code === 200) {
console.log('埋点操作成功')
}
},
async ossSign(list) {
const res = await this.$axios.post(getOssSign, this.ossConfig)
const promiseArr = []
const fileName = list.name
const formatFile = formatUploadFile(list, res.data.data, fileName)
const resp = this.$axios.post(res.data.data.host, formatFile, {
headers: { 'Content-Type': 'multipart/form-data' },
withCredentials: false,
})
promiseArr.push(resp)
try {
const fileList = await Promise.all(promiseArr).then((arr) => {
return `${res.data.data.dir}/${this.imgObject.fileName}`
})
console.log(res.data.data.dir, this.imgObject.fileName)
return { fileList, uuid: res.data.data.dir.split('/')[1] }
} catch (error) {
console.log(error)
}
},
getBlob(canvas) {
var data = canvas.toDataURL('image/png', 1)
this.picdown = data
data = data.split(',')[1]
data = window.atob(data)
var ia = new Uint8Array(data.length)
for (var i = 0; i < data.length; i++) {
ia[i] = data.charCodeAt(i)
}
return new Blob([ia], {
type: 'image/png',
})
},
downloadFile(blob, fileName) {
let aLink = document.createElement('a')
let evt = document.createEvent('HTMLEvents')
evt.initEvent('click', true, true)
aLink.download = fileName
aLink.href = URL.createObjectURL(blob)
aLink.click()
},
handleEdit() {
if (this.mode === 'text') {
this.mode = ''
this.textBoxRect = null
} else {
this.mode = 'text'
}
},
textValueInput(e) {
this.textValue = e.target.innerText
},
},
}
</script>
<style scoped lang="scss">
.canvas_content {
position: fixed;
top: 0;
left: 0;
z-index: 999;
}
.canvas_back {
width: 800px;
background: rgba(51, 51, 51, 1);
}
.edit_content {
height: 51px;
background: #333333;
box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.5);
width: 100%;
display: flex;
align-items: center;
justify-content: center;
.paint_brush {
display: flex;
align-items: center;
img {
cursor: pointer;
margin-right: 10px;
}
.ant-select-selection {
width: 60px;
}
}
.paint_color {
margin-right: 24px;
}
}
.canvas-box {
width: 800px;
height: 610px;
overflow: auto;
}
@media only screen and (max-height: 768px) {
.canvas-box {
height: 520px;
}
}
.canvas {
position: relative;
#paintingId {
position: relative;
z-index: 1;
}
.text-box {
border: 1px solid #333;
position: absolute;
outline: none;
background-color: transparent;
max-width: 200px;
}
#imgCanvas {
position: absolute;
top: 0px;
left: 0px;
z-index: 0;
}
}
.tips {
position: relative;
background: rgba(253, 204, 119, 0.52);
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
img {
cursor: pointer;
position: absolute;
right: 10px;
top: 2px;
}
}
.button {
display: flex;
align-items: center;
justify-content: center;
}
.border {
width: 15px;
color: #ffffff;
border: 1px solid #ffffff;
margin-right: 10px;
}
</style>