|
@@ -4,19 +4,10 @@
|
|
|
<div class="d-flex black align-center justify-center" style="height: 100%; position: relative; overflow: hidden;">
|
|
|
<div class="contentBox">
|
|
|
<v-card :width="size.width" :height="size.height" class="content" ref="content">
|
|
|
- <!-- <div class="point" :style="`left: ${coordinate.left.x}px;top: ${coordinate.left.y}px`"></div>
|
|
|
- <div class="point" :style="`left: ${coordinate.right.x}px;top: ${coordinate.right.y}px`"></div>
|
|
|
- <div class="point" :style="`left: ${coordinate.bridge.x}px;top: ${coordinate.bridge.y}px`"></div> -->
|
|
|
- <img
|
|
|
- v-if="chooseItem"
|
|
|
- :src="chooseItem.pic_url"
|
|
|
- alt=""
|
|
|
- class="cover noMove"
|
|
|
- :class="{ 'cursor-pointer': canMove}"
|
|
|
+ <canvas :width="size.width" :height="size.height"
|
|
|
ref="cover"
|
|
|
- :width="tryGlassesSize.width * scale"
|
|
|
- :style="`--deg: ${tryGlassesSize.deg}deg;`"
|
|
|
- >
|
|
|
+ class="cover noMove"
|
|
|
+ ></canvas>
|
|
|
<img class="noMove" :src="src" alt="" :width="size.width" :height="size.height">
|
|
|
</v-card>
|
|
|
|
|
@@ -110,7 +101,6 @@ export default {
|
|
|
hidePanel: false,
|
|
|
loading: false,
|
|
|
isMobile: isMobile(),
|
|
|
- scale: 1,
|
|
|
clear: false,
|
|
|
active: 0,
|
|
|
canMove: false,
|
|
@@ -132,14 +122,21 @@ export default {
|
|
|
modifier: 1,
|
|
|
slideShadows: false
|
|
|
}
|
|
|
+ },
|
|
|
+ canvasCtx: null,
|
|
|
+ imgEntity: new Image(),
|
|
|
+ imgProps: {
|
|
|
+ x: 0,
|
|
|
+ y: 0,
|
|
|
+ width: 10,
|
|
|
+ height: 10,
|
|
|
+ angle: 0,
|
|
|
+ scale: 1
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
inject: ['size'],
|
|
|
computed: {
|
|
|
- // faceType () {
|
|
|
- // return programType.find(e => e.value === this.chooseItem.facetype)?.face ?? ''
|
|
|
- // },
|
|
|
chooseItem () {
|
|
|
const obj = this.item.items[this.active] ?? {}
|
|
|
obj.faceType = programType.find(e => e.value === obj.facetype)?.face ?? ''
|
|
@@ -171,12 +168,12 @@ export default {
|
|
|
handle: this.onMinus
|
|
|
},
|
|
|
{
|
|
|
- icon: 'mdi-swap-horizontal',
|
|
|
- handle: this.onChange
|
|
|
+ icon: 'mdi-reload',
|
|
|
+ handle: this.onReset
|
|
|
},
|
|
|
{
|
|
|
- icon: 'mdi-reload',
|
|
|
- handle: this.onReload
|
|
|
+ icon: 'mdi-swap-horizontal',
|
|
|
+ handle: this.onChange
|
|
|
},
|
|
|
{
|
|
|
icon: 'mdi-camera-flip-outline',
|
|
@@ -193,46 +190,108 @@ export default {
|
|
|
}
|
|
|
},
|
|
|
mounted () {
|
|
|
- this.$nextTick(() => {
|
|
|
- this.onReload()
|
|
|
+ this.$nextTick(async () => {
|
|
|
if (this.isMobile) {
|
|
|
- this.$refs.content.$el.addEventListener('touchmove', this.onTouchMove)
|
|
|
- this.$refs.content.$el.addEventListener('touchend', this.onTouchLeave)
|
|
|
this.$refs.cover.addEventListener('touchstart', this.onTouchStart)
|
|
|
this.$refs.cover.addEventListener('touchend', this.onTouchEnd)
|
|
|
- return
|
|
|
+ this.$refs.cover.addEventListener('touchmove', this.onTouchMove)
|
|
|
+ this.$refs.cover.addEventListener('touchleave', this.onTouchEnd)
|
|
|
+ } else {
|
|
|
+ this.$refs.cover.addEventListener('mousedown', this.onTouchStart)
|
|
|
+ this.$refs.cover.addEventListener('mouseup', this.onTouchEnd)
|
|
|
+ this.$refs.cover.addEventListener('mousemove', this.onTouchMove)
|
|
|
+ this.$refs.cover.addEventListener('mouseleave', this.onTouchEnd)
|
|
|
+ }
|
|
|
+ this.canvasCtx = this.$refs.cover.getContext('2d')
|
|
|
+ try {
|
|
|
+ await this.loadingImg()
|
|
|
+ this.onReset()
|
|
|
+ } catch (error) {
|
|
|
+ this.loading = false
|
|
|
+ this.$emit('error', '加载失败')
|
|
|
}
|
|
|
- this.$refs.content.$el.addEventListener('mousemove', this.onTouchMove)
|
|
|
- this.$refs.content.$el.addEventListener('mouseleave', this.onTouchLeave)
|
|
|
- this.$refs.cover.addEventListener('mousedown', this.onTouchStart)
|
|
|
- this.$refs.cover.addEventListener('mouseup', this.onTouchEnd)
|
|
|
})
|
|
|
},
|
|
|
beforeDestroy () {
|
|
|
if (this.isMobile) {
|
|
|
- this.$refs.content.$el.removeEventListener('touchmove', this.onTouchMove)
|
|
|
- this.$refs.content.$el.removeEventListener('touchend', this.onTouchLeave)
|
|
|
this.$refs.cover.removeEventListener('touchstart', this.onTouchStart)
|
|
|
this.$refs.cover.removeEventListener('touchend', this.onTouchEnd)
|
|
|
+ this.$refs.cover.removeEventListener('touchmove', this.onTouchMove)
|
|
|
+ this.$refs.cover.removeEventListener('touchleave', this.onTouchEnd)
|
|
|
return
|
|
|
}
|
|
|
- this.$refs.content.$el.removeEventListener('mousemove', this.onTouchMove)
|
|
|
- this.$refs.content.$el.removeEventListener('mouseleave', this.onTouchLeave)
|
|
|
- this.$refs.cover.addEventListener('touchstart', this.onTouchMove)
|
|
|
- this.$refs.cover.addEventListener('touchend', this.onTouchLeave)
|
|
|
+ this.$refs.cover.removeEventListener('mousedown', this.onTouchStart)
|
|
|
+ this.$refs.cover.removeEventListener('mouseup', this.onTouchEnd)
|
|
|
+ this.$refs.cover.removeEventListener('mousemove', this.onTouchMove)
|
|
|
+ this.$refs.cover.removeEventListener('mouseleave', this.onTouchEnd)
|
|
|
},
|
|
|
methods: {
|
|
|
+ loadingImg () {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ this.imgEntity.src = this.chooseItem.pic_url // 替换为您的 PNG 图片路径
|
|
|
+ this.imgEntity.onload = () => {
|
|
|
+ resolve()
|
|
|
+ }
|
|
|
+ this.imgEntity.onerror = (error) => {
|
|
|
+ reject(error)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ draw () {
|
|
|
+ const ctx = this.canvasCtx
|
|
|
+ const img = this.imgEntity
|
|
|
+ const canvas = this.$refs.cover
|
|
|
+ const { x, y, width, height, angle } = this.imgProps
|
|
|
+ ctx.clearRect(0, 0, canvas.width, canvas.height)
|
|
|
+ ctx.save()
|
|
|
+ ctx.translate(x + width / 2, y + height / 2) // 旋转中心点
|
|
|
+ ctx.rotate(angle * Math.PI / 180)
|
|
|
+ ctx.drawImage(
|
|
|
+ img,
|
|
|
+ -width / 2, // 左上角 x 坐标
|
|
|
+ -height / 2, // 左上角 y 坐标
|
|
|
+ width,
|
|
|
+ height
|
|
|
+ )
|
|
|
+
|
|
|
+ // 绘制描边
|
|
|
+ // ctx.strokeStyle = 'red' // 可以根据需要更改颜色
|
|
|
+ // ctx.lineWidth = 5 // 设置描边的宽度
|
|
|
+ // ctx.strokeRect(-this.imgProps.width / 2, // 左上角 x 坐标
|
|
|
+ // -this.imgProps.height / 2, // 左上角 y 坐标
|
|
|
+ // this.imgProps.width,
|
|
|
+ // this.imgProps.height)
|
|
|
+
|
|
|
+ ctx.restore()
|
|
|
+ this.loading = false
|
|
|
+ },
|
|
|
onPlus () {
|
|
|
- if (this.scale > 1.4) {
|
|
|
+ if (this.imgProps.scale > 1.4) {
|
|
|
return
|
|
|
}
|
|
|
- this.scale += 0.05
|
|
|
+ const RATE = 0.05
|
|
|
+ Object.assign(this.imgProps, {
|
|
|
+ x: this.imgProps.x - this.imgProps.width * RATE / 2,
|
|
|
+ y: this.imgProps.y - this.imgProps.height * RATE / 2,
|
|
|
+ scale: this.imgProps.scale + RATE,
|
|
|
+ width: this.imgProps.width * (1 + RATE),
|
|
|
+ height: this.imgProps.height * (1 + RATE)
|
|
|
+ })
|
|
|
+ this.draw()
|
|
|
},
|
|
|
onMinus () {
|
|
|
- if (this.scale < 0.6) {
|
|
|
+ if (this.imgProps.scale < 0.6) {
|
|
|
return
|
|
|
}
|
|
|
- this.scale -= 0.05
|
|
|
+ const RATE = 0.05
|
|
|
+ Object.assign(this.imgProps, {
|
|
|
+ x: this.imgProps.x + this.imgProps.width * RATE / 2,
|
|
|
+ y: this.imgProps.y + this.imgProps.height * RATE / 2,
|
|
|
+ scale: this.imgProps.scale - RATE,
|
|
|
+ width: this.imgProps.width * (1 - RATE),
|
|
|
+ height: this.imgProps.height * (1 - RATE)
|
|
|
+ })
|
|
|
+ this.draw()
|
|
|
},
|
|
|
// 清屏
|
|
|
onClear () {
|
|
@@ -247,10 +306,15 @@ export default {
|
|
|
this.$emit('retake')
|
|
|
},
|
|
|
// 重置眼镜
|
|
|
- onReload () {
|
|
|
- this.scale = 1
|
|
|
- this.$refs.cover.style.left = this.tryGlassesSize.center.x + 'px'
|
|
|
- this.$refs.cover.style.top = this.tryGlassesSize.center.y + 'px'
|
|
|
+ onReset () {
|
|
|
+ this.imgProps.scale = 1
|
|
|
+ this.imgProps.angle = this.tryGlassesSize.deg
|
|
|
+ this.imgProps.width = this.tryGlassesSize.width
|
|
|
+ const { height, width } = this.imgEntity
|
|
|
+ this.imgProps.height = this.imgProps.width / width * height
|
|
|
+ this.imgProps.x = this.tryGlassesSize.center.x - this.imgProps.width / 2
|
|
|
+ this.imgProps.y = this.tryGlassesSize.center.y - this.imgProps.height / 2
|
|
|
+ this.draw()
|
|
|
},
|
|
|
calculateAngle (x1, y1, x2, y2) {
|
|
|
// 计算斜率
|
|
@@ -264,17 +328,14 @@ export default {
|
|
|
|
|
|
return degrees
|
|
|
},
|
|
|
- onSlideChange () {
|
|
|
+ async onSlideChange () {
|
|
|
+ if (this.loading) {
|
|
|
+ return
|
|
|
+ }
|
|
|
this.loading = true
|
|
|
this.active = this.$refs.mySwiper.swiper.activeIndex
|
|
|
- const timer = setTimeout(() => {
|
|
|
- this.loading = false
|
|
|
- this.$emit('error', '加载失败')
|
|
|
- }, 3000)
|
|
|
- this.$nextTick(() => {
|
|
|
- this.loading = false
|
|
|
- clearTimeout(timer)
|
|
|
- })
|
|
|
+ await this.loadingImg()
|
|
|
+ this.draw()
|
|
|
},
|
|
|
onTouchMove (v) {
|
|
|
if (!this.canMove) {
|
|
@@ -289,23 +350,38 @@ export default {
|
|
|
const left = this.moveObj.left + moveX
|
|
|
const top = this.moveObj.top + moveY
|
|
|
// 更新点位位置
|
|
|
- this.$refs.cover.style.left = left + 'px'
|
|
|
- this.$refs.cover.style.top = top + 'px'
|
|
|
- },
|
|
|
- onTouchLeave () {
|
|
|
- this.onTouchEnd()
|
|
|
+ this.imgProps.x = left
|
|
|
+ this.imgProps.y = top
|
|
|
+ this.draw()
|
|
|
+ // this.$refs.cover.style.left = left + 'px'
|
|
|
+ // this.$refs.cover.style.top = top + 'px'
|
|
|
},
|
|
|
onTouchStart (e) {
|
|
|
+ // 获取 Canvas 元素的边界信息
|
|
|
+ const rect = this.$refs.cover.getBoundingClientRect()
|
|
|
+ let mouseX, mouseY
|
|
|
+ // 根据设备类型计算坐标
|
|
|
+ if (this.isMobile) {
|
|
|
+ mouseX = e.touches[0].clientX - rect.left
|
|
|
+ mouseY = e.touches[0].clientY - rect.top
|
|
|
+ } else {
|
|
|
+ mouseX = e.offsetX
|
|
|
+ mouseY = e.offsetY
|
|
|
+ }
|
|
|
+ if (!this.isMouseOnImage(mouseX, mouseY)) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
this.canMove = true
|
|
|
- const { left, top } = this.$refs.content.$el.getBoundingClientRect()
|
|
|
- const { left: glassLeft, top: glassTop, height: glassHeight, width: glassWidth } = this.$refs.cover.getBoundingClientRect()
|
|
|
+ // const { left, top } = this.$refs.content.$el.getBoundingClientRect()
|
|
|
const startX = this.isMobile ? e.touches[0].clientX : e.clientX
|
|
|
const startY = this.isMobile ? e.touches[0].clientY : e.clientY
|
|
|
+
|
|
|
this.moveObj = {
|
|
|
startX,
|
|
|
startY,
|
|
|
- left: glassLeft - left + glassWidth / 2,
|
|
|
- top: glassTop - top + glassHeight / 2
|
|
|
+ left: this.imgProps.x,
|
|
|
+ top: this.imgProps.y
|
|
|
}
|
|
|
},
|
|
|
onTouchEnd (e) {
|
|
@@ -316,6 +392,36 @@ export default {
|
|
|
left: null,
|
|
|
top: null
|
|
|
})
|
|
|
+ },
|
|
|
+ // 计算点位是否在图片上
|
|
|
+ isMouseOnImage (mouseX, mouseY) {
|
|
|
+ const { x, y, width, height, angle } = this.imgProps
|
|
|
+ // 计算图片中心点坐标
|
|
|
+ const centerX = x + width / 2
|
|
|
+ const centerY = y + height / 2
|
|
|
+
|
|
|
+ // 将坐标转换为相对于中心点的坐标
|
|
|
+ const localX = mouseX - centerX
|
|
|
+ const localY = mouseY - centerY
|
|
|
+
|
|
|
+ // 将旋转角度转换为弧度,并计算反向旋转(用于坐标转换)
|
|
|
+ const radians = angle * Math.PI / 180
|
|
|
+ const cos = Math.cos(-radians) // 反向旋转
|
|
|
+ const sin = Math.sin(-radians)
|
|
|
+
|
|
|
+ // 应用旋转矩阵,将坐标转换到未旋转的坐标系
|
|
|
+ const rotatedX = localX * cos - localY * sin
|
|
|
+ const rotatedY = localX * sin + localY * cos
|
|
|
+
|
|
|
+ // 计算图片未旋转时的边界范围(半宽和半高)
|
|
|
+ const halfWidth = width / 2
|
|
|
+ const halfHeight = height / 2
|
|
|
+
|
|
|
+ // 判断转换后的坐标是否在图片范围内
|
|
|
+ return (
|
|
|
+ Math.abs(rotatedX) <= halfWidth &&
|
|
|
+ Math.abs(rotatedY) <= halfHeight
|
|
|
+ )
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -395,11 +501,7 @@ export default {
|
|
|
border-radius: 50%;
|
|
|
background-color: red;
|
|
|
}
|
|
|
- .cover {
|
|
|
- transform-origin: 50% 50%;
|
|
|
- transform: translate(-50%, -50%) rotate(var(--deg));
|
|
|
- cursor: pointer;
|
|
|
- }
|
|
|
+
|
|
|
}
|
|
|
.cursor-pointer {
|
|
|
cursor: pointer;
|