代码
请忽略个别业务组件,关注裁切逻辑。
总结:利用 mousedown 确定鼠标的初始化位置,利用 mousemove 随时获取当前鼠标的位置,利用和初始化位置数据的差值,来移动容器。mouseup 重置。
<template>
<div class="cover-image-cropper">
<div class="cropper-container">
<div class="cropper-container-left">
<div class="origin-image">
<img-picker
ref="imgPicker"
customClass="cover-image-cropper-picker"
:imgKey="originPicKey"
:defaultImg="defaultImg"
:formDataKey="selfFormDataKey"
@picked="setImgUrl"
@delete-click="deleteCurrentSelectedImage"
/>
</div>
<div v-show="currentGraphIndex !== ''" class="cropper-bg" :style="originImgDomStyle"></div>
<img
v-show="currentGraphIndex !== ''"
:src="articleForm.origin_image_url"
class="clip-image"
:style="[originImgDomStyle, clipImageStyle]"
alt="原始图片"
/>
<div
v-show="currentGraphIndex !== ''"
class="drag-resize-box"
:style="dragResizeBoxStyle"
@mousedown.stop.self="commonMousedownEvent($event)"
@mousemove.stop.self="dragResizeBoxMousemoveEvent($event)"
@mouseup.stop.self="isMousedownType = false"
>
<i
v-for="(item, index) in zoomPoints"
class=" zoom-point"
:class="item.customClass"
:key="index"
@mousedown.stop.self="commonMousedownEvent($event)"
@mousemove.stop.self="zoomPointMousemoveEvent($event, index)"
@mouseup.stop.self="isMousedownType = false"
></i>
</div>
</div>
<div class="cropper-container-right">
<div class="ratio-graph-lists">
<div
v-for="(item, index) in ratioLists"
class="single-item"
:class="{
active: currentGraphIndex === index,
disabled: !hasOriginPic,
cropped: item.croppedPosition.length,
}"
:style="item.style"
:data-radio="item.text"
@click="handleSelectedClick(index)"
>
{{ item.text }}
</div>
</div>
<div class="save-btn" :class="{ disabled: !hasOriginPic }" @click="saveCropperImage">
保存图片
</div>
</div>
</div>
<!-- 图片选择弹窗 -->
<picture-select-dialog />
</div>
</template>
<script>
import { ImgPicker, PictureSelectDialog } from '@/components'
export default {
name: 'CoverImageCropper',
props: {
articleForm: {
type: Object || String,
},
},
data() {
return {
ratioLists: [
{
text: '1:1',
initPosition: [
{ x: 0, y: 0 },
{ x: 70, y: 0 },
{ x: 70, y: 70 },
{ x: 0, y: 70 },
],
croppedPosition: [],
initSize: { width: 70, height: 70 },
style: { width: '70px', height: '70px' },
key: '1_1',
extraValue: { prefix: '', suffix: '_1_1' },
},
{
text: '3:2',
initPosition: [
{ x: 0, y: 0 },
{ x: 105, y: 0 },
{ x: 105, y: 70 },
{ x: 0, y: 70 },
],
croppedPosition: [],
initSize: { width: 105, height: 70 },
style: { width: '105px', height: '70px' },
key: '3_2',
extraValue: { prefix: '', suffix: '_3_2' },
},
{
text: '7:5',
initPosition: [
{ x: 0, y: 0 },
{ x: 97, y: 0 },
{ x: 97, y: 70 },
{ x: 0, y: 70 },
],
croppedPosition: [],
initSize: { width: 97, height: 70 },
style: { width: '97px', height: '70px' },
key: '7_5',
extraValue: { prefix: '', suffix: '_7_5' },
},
{
text: '17:10',
initPosition: [
{ x: 0, y: 0 },
{ x: 118, y: 0 },
{ x: 118, y: 70 },
{ x: 0, y: 70 },
],
croppedPosition: [],
initSize: { width: 118, height: 70 },
style: { width: '118px', height: '70px' },
key: '17_10',
extraValue: { prefix: '', suffix: '_17_10' },
},
{
text: '21:10',
initPosition: [
{ x: 0, y: 0 },
{ x: 143, y: 0 },
{ x: 143, y: 70 },
{ x: 0, y: 70 },
],
croppedPosition: [],
initSize: { width: 143, height: 70 },
style: { width: '143px', height: '70px' },
key: '21_10',
extraValue: { prefix: '', suffix: '_21_10' },
},
],
// 当前比例尺下标
currentGraphIndex: '',
// imgPicker key
originPicKey: 'coverImageCropper',
// formdata keys
selfFormDataKey: ['cover_img_id', 'origin_image_url'],
// 原图尺寸
originImgSize: {},
// 原图初始化位置
originImgPosition: {},
// 原图 DOM 样式
originImgDomStyle: {},
// 裁切图片 DOM 样式
clipImageStyle: {},
// 拖拽、缩放容器 DOM 样式
dragResizeBoxStyle: {},
// 当前裁切后图片的位置信息
currentPositionCoordinates: [],
// 持续长按中
isMousedownType: false,
// 鼠标按下位置
originPosition: {
x: null,
y: null,
},
zoomPoints: [
{ customClass: 'top-left-point' },
{ customClass: 'top-right-point' },
{ customClass: 'bottom-right-point' },
{ customClass: 'bottom-left-point' },
],
// 待提交的数据
submitMsg: {},
}
},
components: {
ImgPicker,
PictureSelectDialog,
},
computed: {
defaultImg() {
return {
id: this.articleForm.cover_img_id,
file_path: this.articleForm.origin_image_url,
}
},
hasOriginPic() {
return this.articleForm.origin_image_url !== ''
},
},
watch: {
hasOriginPic: {
handler(newVal, oldVal) {
if (!newVal) {
this.currentGraphIndex = ''
this.isMousedownType = false
this.ratioLists.map((item) => {
item.croppedPosition.length = 0
})
}
},
immediate: true,
},
currentGraphIndex: {
handler(newVal, oldVal) {
if (newVal !== '') {
this.$nextTick(() => {
// 已经选取裁切图逻辑
const currentGraphCroppedPosition = this.ratioLists[newVal].croppedPosition
if (currentGraphCroppedPosition.length) {
this.updateCropperView(currentGraphCroppedPosition)
this.currentPositionCoordinates = this.ratioLists[newVal].croppedPosition
} else {
// 未选取裁切图逻辑
const originImgDom = this.$refs.imgPicker.$refs.img
const offsetWidth = originImgDom.offsetWidth
const offsetHeight = originImgDom.offsetHeight
const offsetTop = originImgDom.offsetTop
const offsetLeft = originImgDom.offsetLeft
/**
* originImgSize
* 原图长宽尺寸
*/
this.originImgSize = {
width: offsetWidth,
height: offsetHeight,
}
/**
* originImgPosition
* 原图初始化位置
*/
this.originImgPosition = {
top: offsetTop,
left: offsetLeft,
}
/**
* originImgDomStyle
* 原图长宽样式
*/
this.originImgDomStyle = {
top: offsetTop + 1 + 'px',
left: offsetLeft + 1 + 'px',
width: offsetWidth + 'px',
height: offsetHeight + 'px',
}
/**
* currentPositionCoordinates
* 当前位置坐标
*/
let currentInitPosition = JSON.parse(
JSON.stringify(this.ratioLists[newVal].initPosition)
)
if (offsetTop > 0 || offsetLeft > 0) {
currentInitPosition.map((item) => {
if (offsetTop > 0) {
item.y = item.y + offsetTop
}
if (offsetLeft > 0) {
item.x = item.x + offsetLeft
}
})
}
this.currentPositionCoordinates = currentInitPosition
/**
* 根据最新坐标信息更新视图
*/
this.updateCropperView(this.currentPositionCoordinates)
}
})
}
},
},
},
methods: {
setImgUrl(data) {
const { file_id, file_path } = data[this.originPicKey]
this.articleForm.cover_img_id = file_id
this.articleForm.origin_image_url = file_path
},
getClipImageStyle(currentPositionCoordinates) {
// polygon(0px 0px, 100px 0px, 100px 100px, 0px 100px)
const selfOriginImgPosition = this.originImgPosition
return {
'clip-path': `polygon(
${currentPositionCoordinates
.map(
(item, index) =>
item.x -
this.originImgPosition.left +
'px' +
' ' +
(item.y - this.originImgPosition.top) +
'px' +
(index < currentPositionCoordinates.length - 1 ? ', ' : '')
)
.join('')}
)`,
}
},
/**
* 获取拖动、缩放容器的位置样式
* @param {array} currentPositionCoordinates - 当前位置信息
*
* @return {object} 返回 style 对象
*/
getDragResizeBoxStyle(currentPositionCoordinates) {
return {
top: currentPositionCoordinates[0].y + 1 + 'px',
left: currentPositionCoordinates[0].x + 1 + 'px',
width: currentPositionCoordinates[1].x - currentPositionCoordinates[0].x + 'px',
height: currentPositionCoordinates[3].y - currentPositionCoordinates[0].y + 'px',
}
},
/**
* 获取 mousedown 事件出发点的位置坐标
* @param {object} evt - 鼠标点击事件对象
*/
commonMousedownEvent(evt) {
this.isMousedownType = true
this.originPosition.x = evt.offsetX
this.originPosition.y = evt.offsetY
},
/**
* 裁切图片拖拽事件
* @param {object} evt - 拖拽事件对象
*/
dragResizeBoxMousemoveEvent(evt) {
// console.log('***', evt.offsetX)
if (this.isMousedownType) {
const selfOriginImgSize = this.originImgSize
const selfOriginImgPosition = this.originImgPosition
const selfCurrentPositionCoordinates = this.currentPositionCoordinates
const x = evt.offsetX
const y = evt.offsetY
const distanceX = x - this.originPosition.x
const distanceY = y - this.originPosition.y
if (
distanceX + selfCurrentPositionCoordinates[0].x < selfOriginImgPosition.left ||
distanceY + selfCurrentPositionCoordinates[0].y < selfOriginImgPosition.top ||
distanceX + selfCurrentPositionCoordinates[1].x >
selfOriginImgSize.width + selfOriginImgPosition.left ||
distanceY + selfCurrentPositionCoordinates[3].y >
selfOriginImgSize.height + selfOriginImgPosition.top
) {
return false
} else {
this.currentPositionCoordinates = selfCurrentPositionCoordinates.map((item) => {
return {
x: item.x + distanceX,
y: item.y + distanceY,
}
})
this.updateCropperView(this.currentPositionCoordinates)
}
}
},
/**
* 裁切图片缩放事件
* 根据 mousemove 缩放图片
* @param {object} evt - 拖拽事件对象
* @param {number} currentPointIndex - 拖拽的 point,0: ↖️, 1: ↗️, 2: ↘️, 3: ↙️
*/
zoomPointMousemoveEvent(evt, currentPointIndex) {
if (this.isMousedownType) {
const x = evt.offsetX
const distance = x - this.originPosition.x
// 判读是否可以进行容器缩放
if (this.zoomCanPass(currentPointIndex, distance)) {
return false
} else {
this.currentPositionCoordinates = this.currentPositionCoordinates.map((item, index) => {
let isEven = (currentPointIndex + 1) % 2 === 0
// 当前缩放触发方向
// x,y 坐标都变动
if (currentPointIndex === index) {
return {
x: item.x + distance,
y: item.y + distance,
}
}
// 顺时针下一位
if ((currentPointIndex + 1) % 4 === index) {
return {
x: isEven ? item.x + distance : item.x,
y: isEven ? item.y : item.y + distance,
}
}
// 对角方向
if ((currentPointIndex + 2) % 4 === index) {
return {
x: item.x,
y: item.y,
}
}
// 逆时针上一位
if ((currentPointIndex + 3) % 4 === index)
return {
x: isEven ? item.x : item.x + distance,
y: isEven ? item.y + distance : item.y,
}
})
this.updateCropperView(this.currentPositionCoordinates)
}
}
},
/**
* 判断缩放是否可执行
* @param {number} currentPointIndex - 拖拽的 point,0: ↖️, 1: ↗️, 2: ↘️, 3: ↙️
* @param {number} distance - 缩放的大小值
*
* @return {boolean} 返回是否可执行的布尔值
*/
zoomCanPass(currentPointIndex, distance) {
const selfCurrentPositionCoordinates = this.currentPositionCoordinates
const selfOriginImgSize = this.originImgSize
const selfOriginImgPosition = this.originImgPosition
switch (currentPointIndex) {
case 0:
return
distance + selfCurrentPositionCoordinates[0].x < selfOriginImgPosition.left ||
distance + selfCurrentPositionCoordinates[0].y < selfOriginImgPosition.top
break
case 1:
distance + selfCurrentPositionCoordinates[1].x >
selfOriginImgSize.width + selfOriginImgPosition.left ||
distance + selfCurrentPositionCoordinates[1].y < selfOriginImgPosition.top
break
case 2:
distance + selfCurrentPositionCoordinates[2].x >
selfOriginImgSize.width + selfOriginImgPosition.left ||
distance + selfCurrentPositionCoordinates[2].y >
selfOriginImgSize.height + selfOriginImgPosition.top
break
case 3:
distance + selfCurrentPositionCoordinates[3].x < selfOriginImgPosition.left ||
distance + selfCurrentPositionCoordinates[3].y >
selfOriginImgSize.height + selfOriginImgPosition.top
break
default:
return false
}
},
/**
* 根据最新坐标信息更新视图
* @param {object} selfCurrentPositionCoordinates - 当前裁切后图片的位置信息
*/
updateCropperView(selfCurrentPositionCoordinates) {
/**
* clip-image
* 利用 css3 属性 clip-path
*/
this.clipImageStyle = this.getClipImageStyle(selfCurrentPositionCoordinates)
/**
* dragResizeBoxStyle
* 用于拖动、缩放的元素
*/
this.dragResizeBoxStyle = this.getDragResizeBoxStyle(selfCurrentPositionCoordinates)
},
/**
* 点击选中当前比例
* @param {number} index - 当前比例的下标
*/
handleSelectedClick(index) {
if (this.hasOriginPic && this.currentGraphIndex !== index) {
this.currentGraphIndex = index
this.isMousedownType = false
}
},
/**
* 保存当前裁切比例下的图片位置信息
*/
saveCropperImage() {
const currentGraph = this.ratioLists[this.currentGraphIndex]
const singleCoverScale = {}
// 保存裁切后的位置信息
currentGraph.croppedPosition = this.currentPositionCoordinates
// 表单提交所需要的比例信息
singleCoverScale.x = currentGraph.croppedPosition[0].x
singleCoverScale.y = currentGraph.croppedPosition[0].y
singleCoverScale.width = currentGraph.croppedPosition[1].x - currentGraph.croppedPosition[0].x
singleCoverScale.heigt = currentGraph.croppedPosition[3].y - currentGraph.croppedPosition[0].y
Object.assign(singleCoverScale, currentGraph.extraValue)
console.log('保存图片', singleCoverScale)
if (!this.submitMsg[currentGraph.key]) {
this.submitMsg[currentGraph.key] = singleCoverScale
} else {
this.$delete(this.submitMsg, currentGraph.key)
}
},
/**
* 删除当前选中图回调函数
* @param {array, string} formDataKey - 待删除图片表单项属性 key
*/
deleteCurrentSelectedImage(formDataKey) {
if (Array.isArray(formDataKey)) {
formDataKey.forEach((singleKey) => {
this.$set(this.articleForm, singleKey, '')
})
} else {
this.$set(this.articleForm, formDataKey, '')
}
},
},
}
</script>
<style lang="scss" scoped>
.cover-image-cropper {
position: relative;
width: 100%;
height: auto;
.cropper-container {
display: flex;
&-left {
position: relative;
.origin-image {
.img-picker.cover-image-cropper-picker {
width: 256px;
height: 176px;
line-height: 176px;
.delete-icon-zIndex {
z-index: 9;
}
}
}
.cropper-bg,
.clip-image {
position: absolute;
top: 1px;
left: 1px;
}
.cropper-bg {
width: 100%;
background: rgba(0, 0, 0, 0.35);
}
.clip-image {
width: 100%;
}
.drag-resize-box {
position: absolute;
cursor: move;
.zoom-point {
position: absolute;
width: 4px;
height: 4px;
background-color: #000;
&.top-left-point {
top: -2px;
left: -2px;
cursor: nwse-resize;
}
&.top-right-point {
top: -2px;
right: -2px;
cursor: nesw-resize;
}
&.bottom-right-point {
bottom: -2px;
right: -2px;
cursor: nwse-resize;
}
&.bottom-left-point {
bottom: -2px;
left: -2px;
cursor: nesw-resize;
}
}
}
}
&-right {
position: relative;
margin-left: 85px;
&::before {
content: ' ';
position: absolute;
top: 26px;
left: -42px;
width: 1px;
height: 112px;
background: rgba(240, 242, 245, 1);
}
.ratio-graph-lists {
position: relative;
display: flex;
flex-wrap: wrap;
.single-item {
position: relative;
margin-left: 22px;
border: 1px solid rgba(216, 220, 230, 1);
line-height: 70px;
text-align: center;
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
color: rgba(144, 147, 153, 1);
background: rgba(240, 242, 245, 1);
cursor: pointer;
&:nth-of-type(1) {
margin-left: 0;
}
/** 兼容小屏幕,换行处理 */
@media (max-width: 1443px) {
&:nth-of-type(4) {
margin-left: 0;
}
&:nth-of-type(4),
&:nth-of-type(5) {
margin-top: 35px;
}
}
&.active {
border-color: rgba(25, 137, 250, 1);
color: rgba(25, 137, 250, 1);
background-color: rgba(240, 242, 245, 1);
}
&.disabled {
pointer-events: none;
}
&.cropped {
&::after {
content: '已获取';
position: absolute;
bottom: -27px;
left: 50%;
margin-left: -26px;
border-radius: 9px;
width: 52px;
height: 18px;
line-height: 18px;
text-align: center;
font-size: 12px;
font-family: PingFangSC-Regular, PingFang SC;
color: rgba(4, 134, 254, 1);
background: rgba(224, 240, 255, 1);
}
}
}
}
.save-btn {
margin-top: 70px;
border-radius: 4px;
width: 96px;
height: 36px;
line-height: 36px;
text-align: center;
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
color: rgba(255, 255, 255, 1);
background: rgba(25, 137, 250, 1);
cursor: pointer;
&.disabled {
color: rgba(144, 147, 153, 1);
background-color: rgba(240, 242, 245, 1);
pointer-events: none;
}
@media (max-width: 1443px) {
margin-top: 35px;
}
}
}
}
}
</style>
vue-cropper
使用 vue-cropper 实现同样的需求
<template>
<div class="cropper-wrapper">
<div class="cropper-content">
<div class="cropper-content-left">
<div v-if="!hasOriginPic" class="origin-image">
<img-picker
ref="imgPicker"
customClass="cover-image-cropper-picker"
:imgKey="originPicKey"
:defaultImg="defaultImg"
:formDataKey="selfFormDataKey"
@picked="setImgUrl"
@delete-click="deleteCurrentSelectedImage"
/>
</div>
<div v-else class="my-cropper-box">
<svg-icon
iconName="icon-delete-file"
className="delete-icon"
:styleObject="deleteIconStyle"
@handleClickEvent="deleteCurrentSelectedImage"
/>
<vueCropper
ref="cropper"
:img="articleForm.cover_img"
:outputSize="option.size"
:info="true"
:full="option.full"
:canMove="option.canMove"
:canMoveBox="option.canMoveBox"
:fixedBox="option.fixedBox"
:original="option.original"
:autoCrop="option.autoCrop"
:autoCropWidth="option.autoCropWidth"
:autoCropHeight="option.autoCropHeight"
:centerBox="option.centerBox"
:high="option.high"
:infoTrue="option.infoTrue"
:maxImgSize="option.maxImgSize"
@cropMoving="cropMoving"
:enlarge="option.enlarge"
:mode="option.mode"
:limitMinSize="option.limitMinSize"
:fixed="option.fixed"
:fixedNumber="option.fixedNumber"
></vueCropper>
</div>
</div>
<div class="cropper-content-right">
<div class="ratio-graph-lists">
<div
v-for="(item, index) in ratioLists"
class="single-item"
:class="{
active: currentGraphIndex === index,
disabled: !hasOriginPic,
cropped: item.isCropped,
hasEditStyle: !item.isCropped && Object.keys(item.editStyle).length,
}"
:style="[item.style, !item.isCropped ? item.editStyle : '']"
:data-radio="item.text"
@click="handleSelectedClick(index)"
>
<div class="text-layer">
<span>{{ item.text }}</span>
</div>
</div>
</div>
<div class="save-btn" :class="{ disabled: !hasOriginPic }" @click="saveCropperImage">
保存图片
</div>
</div>
</div>
<!-- 图片选择弹窗 -->
<picture-select-dialog />
</div>
</template>
<script>
import { VueCropper } from 'vue-cropper'
import { ImgPicker, PictureSelectDialog } from '@/components'
export default {
name: 'CoverImageCropperPlus',
props: {
articleForm: {
type: Object || String,
},
},
data() {
return {
ratioLists: [
{
text: '1:1',
isCropped: false,
croppedPosition: {},
style: { width: '70px', height: '70px' },
editStyle: {},
scaleFactor: [1, 1],
key: '1_1',
extraValue: { prefix: '', suffix: '_1_1' },
},
{
text: '3:2',
isCropped: false,
croppedPosition: {},
style: { width: '105px', height: '70px' },
editStyle: {},
scaleFactor: [3, 2],
key: '3_2',
extraValue: { prefix: '', suffix: '_3_2' },
},
{
text: '7:5',
isCropped: false,
croppedPosition: {},
style: { width: '97px', height: '70px' },
editStyle: {},
scaleFactor: [7, 5],
key: '7_5',
extraValue: { prefix: '', suffix: '_7_5' },
},
{
text: '17:10',
isCropped: false,
croppedPosition: {},
style: { width: '118px', height: '70px' },
editStyle: {},
scaleFactor: [17, 10],
key: '17_10',
extraValue: { prefix: '', suffix: '_17_10' },
},
{
text: '21:10',
isCropped: false,
croppedPosition: {},
style: { width: '143px', height: '70px' },
editStyle: {},
scaleFactor: [21, 10],
key: '21_10',
extraValue: { prefix: '', suffix: '_21_10' },
},
],
// 当前比例尺
currentGraphIndex: '',
// imgPicker key
originPicKey: 'CoverImageCropperPlus',
// formdata keys
selfFormDataKey: ['cover_img_id', 'cover_img'],
// 待提交的数据
submitMsg: {},
deleteIconStyle: {
width: '14px',
height: '14px',
},
crap: false,
option: {
size: 1,
full: false,
outputType: 'png',
canMove: false,
fixedBox: false,
original: false,
canMoveBox: true,
autoCrop: false,
centerBox: true,
high: false,
cropData: {},
enlarge: 1,
mode: 'contain',
maxImgSize: 2000,
fixed: true,
fixedNumber: [1, 1],
},
}
},
components: {
VueCropper,
ImgPicker,
PictureSelectDialog,
},
computed: {
defaultImg() {
return {
id: this.articleForm.cover_img_id,
file_path: this.articleForm.cover_img,
}
},
hasOriginPic() {
return this.articleForm.cover_img !== ''
},
},
watch: {
hasOriginPic: {
handler(newVal, oldVal) {
if (!newVal) {
this.currentGraphIndex = ''
this.option.canMove = false
this.ratioLists.map((item) => {
item.isCropped = false
item.croppedPosition = {}
item.editStyle = {}
})
}
},
immediate: true,
},
currentGraphIndex: {
handler(newVal, oldVal) {
if (newVal !== '') {
this.option.canMove = true
this.$nextTick(() => {
const selfRatioItem = this.ratioLists[newVal]
const selfIsCropped = selfRatioItem.isCropped
const selfPositionMsg = selfRatioItem.croppedPosition
// update current cropper ratio
this.option.fixedNumber = selfRatioItem.scaleFactor
this.$nextTick(() => {
// auto crop
this.$refs.cropper.goAutoCrop()
this.$nextTick(() => {
// if cropped and has position message, update crop box
if (selfIsCropped && selfPositionMsg) {
this.$refs.cropper.cropOffsertX = selfPositionMsg.x
this.$refs.cropper.cropOffsertY = selfPositionMsg.y
this.$refs.cropper.cropW = selfPositionMsg.width
this.$refs.cropper.cropH = selfPositionMsg.height
}
})
})
})
}
},
immediate: true,
},
},
methods: {
/** 设置选中的图片*/
setImgUrl(data) {
const { file_id, file_path } = data[this.originPicKey]
this.articleForm.cover_img_id = file_id
this.articleForm.cover_img = file_path
// 裁切组件获取图片地址
this.option.img = file_path
},
/**
* 删除当前选中图回调函数
*/
deleteCurrentSelectedImage() {
if (Array.isArray(this.selfFormDataKey)) {
this.selfFormDataKey.forEach((singleKey) => {
this.$set(this.articleForm, singleKey, '')
})
} else {
this.$set(this.articleForm, this.selfFormDataKey, '')
}
},
/** 裁切图回显 */
setPreviewStyle() {
this.ratioLists.map((item, index) => {
const selfCoverImg = JSON.parse(JSON.stringify(this.articleForm.cover_img))
const coverImgSplitArr = selfCoverImg.split('.')
const covreImgSuffix = coverImgSplitArr[coverImgSplitArr.length - 1]
const coverImgPrefix = coverImgSplitArr.splice(0, coverImgSplitArr.length - 1).join('.')
const newICoverImg =
coverImgPrefix +
item.extraValue.suffix +
'.' +
covreImgSuffix +
'?timestamp=' +
new Date().getTime()
item.editStyle = {
background: `url(${newICoverImg}) no-repeat 0 0`,
backgroundSize: 'contain',
}
})
},
/**
* 点击选中当前比例
* @param {number} index - 当前比例的下标
*/
handleSelectedClick(index) {
if (this.hasOriginPic && this.currentGraphIndex !== index) {
this.currentGraphIndex = index
}
},
/**
* 保存当前裁切比例下的图片位置信息
*/
saveCropperImage() {
const imgScale = this.$refs.cropper.scale
const { x1: cropX1, x2: cropX2, y1: cropY1, y2: cropY2 } = this.$refs.cropper.getCropAxis()
const { x1: imgX1, x2: imgX2, y1: imgY1, y2: imgY2 } = this.$refs.cropper.getImgAxis()
// console.log('cropBox axis: ', cropX1, cropX2, cropY1, cropY1)
// console.log('image axis: ', imgX1, imgX2, imgY1, imgY2)
const currentGraph = this.ratioLists[this.currentGraphIndex]
currentGraph.isCropped = true
// 保存裁切后的位置信息
currentGraph.croppedPosition = Object.assign(
{},
{
x: cropX1,
y: cropY1,
width: cropX2 - cropX1,
height: cropY2 - cropY1,
}
)
// 表单提交所需要的比例信息
const singleCoverScale = {}
singleCoverScale.x = Math.round((cropX1 - imgX1) / imgScale)
singleCoverScale.y = Math.round((cropY1 - imgY1) / imgScale)
singleCoverScale.width = Math.round((cropX2 - cropX1) / imgScale)
singleCoverScale.height = Math.round((cropY2 - cropY1) / imgScale)
Object.assign(singleCoverScale, currentGraph.extraValue)
if (this.submitMsg[currentGraph.key]) {
this.$delete(this.submitMsg, currentGraph.key)
}
this.$set(this.submitMsg, currentGraph.key, singleCoverScale)
console.log('submitMsg', this.submitMsg)
},
},
}
</script>
<style lang="scss" scoped>
.test-button {
display: flex;
flex-wrap: wrap;
}
.btn {
display: inline-block;
line-height: 1;
white-space: nowrap;
cursor: pointer;
background: #fff;
border: 1px solid #c0ccda;
color: #1f2d3d;
text-align: center;
box-sizing: border-box;
outline: none;
margin: 20px 10px 0px 0px;
padding: 9px 15px;
font-size: 14px;
border-radius: 4px;
color: #fff;
background-color: #50bfff;
border-color: #50bfff;
transition: all 0.2s ease;
text-decoration: none;
user-select: none;
}
.cropper-content {
display: flex;
&-left {
position: relative;
width: 256px;
height: 176px;
.origin-image {
position: absolute;
top: 0;
left: 0;
z-index: 9;
.img-picker.cover-image-cropper-picker {
width: 256px;
height: 176px;
line-height: 176px;
.delete-icon-zIndex {
z-index: 9;
}
}
}
.my-cropper-box {
position: relative;
width: 256px;
height: 176px;
border: 1px dashed #dcdfe6;
&:hover {
border-color: #409eff;
.delete-icon {
visibility: visible;
}
}
.delete-icon {
visibility: hidden;
z-index: 99;
position: absolute;
top: -7px;
right: -7px;
cursor: pointer;
}
}
}
&-right {
position: relative;
margin-left: 85px;
&::before {
content: ' ';
position: absolute;
top: 26px;
left: -42px;
width: 1px;
height: 112px;
background: rgba(240, 242, 245, 1);
}
.ratio-graph-lists {
position: relative;
display: flex;
flex-wrap: wrap;
/** 兼容小屏幕,换行处理 */
@media (max-width: 1443px) {
width: 316px;
.single-item {
&:nth-of-type(4) {
margin-left: 0;
}
&:nth-of-type(4),
&:nth-of-type(5) {
margin-top: 35px;
}
}
}
.single-item {
position: relative;
margin-left: 22px;
border: 1px solid rgba(216, 220, 230, 1);
line-height: 70px;
text-align: center;
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
color: rgba(144, 147, 153, 1);
background: rgba(240, 242, 245, 1);
cursor: pointer;
&:nth-of-type(1) {
margin-left: 0;
}
&:hover,
&.active {
border-color: rgba(25, 137, 250, 1);
color: rgba(25, 137, 250, 1);
background-color: rgba(240, 242, 245, 1);
}
&.disabled {
pointer-events: none;
}
&.cropped {
&::after {
content: '已获取';
position: absolute;
bottom: -27px;
left: 50%;
margin-left: -26px;
border-radius: 9px;
width: 52px;
height: 18px;
line-height: 18px;
text-align: center;
font-size: 12px;
font-family: PingFangSC-Regular, PingFang SC;
color: rgba(4, 134, 254, 1);
background: rgba(224, 240, 255, 1);
}
}
&.hasEditStyle {
.text-layer {
color: #fff;
background-color: rgba(0, 0, 0, 0.15);
}
}
}
}
.save-btn {
margin-top: 70px;
border-radius: 4px;
width: 96px;
height: 36px;
line-height: 36px;
text-align: center;
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
color: rgba(255, 255, 255, 1);
background: rgba(25, 137, 250, 1);
cursor: pointer;
&:hover {
border-color: #369efe;
background-color: #369efe;
}
&.disabled {
color: rgba(144, 147, 153, 1);
background-color: rgba(240, 242, 245, 1);
pointer-events: none;
}
@media (max-width: 1443px) {
margin-top: 35px;
}
}
}
}
</style>