[英]Pinch zoom issue on Vue Component
ive got a Vue component which lets me zoom in on an image, using the mouse wheel works fine, however pinch zoom behaves strangely.我有一个 Vue 组件,它可以让我放大图像,使用鼠标滚轮效果很好,但是捏合缩放的行为很奇怪。
Basically if you put 2 fingers on the screen far apart it zooms in a lot, put them in closer together and it zooms in a bit.基本上,如果你将 2 根手指放在屏幕上,它会放大很多,将它们靠近一点,它会放大一点。 What should happen is it doesn't zoom in or out until you actually move your fingers in or out.
应该发生的是,在您实际移入或移出手指之前,它不会放大或缩小。
Any ideas?有任何想法吗?
Vue.component('test', { data() { return { loading: false, loop: true, speed: 8, speedController: 0, zoomEnabled: true, zoomLevels: [1, 1.5, 2, 2.5, 3], zoomLevel: 1, frame: 1, images: [], imagesPreloaded: 0, spinEnabled: true, spinAuto: false, reverse: false, viewportScale: 0.3, viewportEnabled: true, viewportOpacity: 0.8, lastX: 0, lastY: 0, startX: 0, startY: 0, translateX: 0, translateY: 0, isMoving: false, isDragging: false, lastPinch: 0, animationRequestID: 0, spinStart: null, spinThen: Date.now(), fps: 1000 / 8, axiosRequest: null, $clickEvent: null, $moveEvent: null, output: '' }; }, mounted() { window.addEventListener('mouseup', this.handleEnd); window.addEventListener('touchend', this.handleEnd); window.addEventListener('resize', this.fetch); }, beforeDestroy() { window.removeEventListener('mouseup', this.handleEnd); window.removeEventListener('touchend', this.handleEnd); }, methods: { handleSlider(event) { this.frame = Number(event.target.value); }, zoom(direction) { if (this.zoomLevels[this.zoomLevels.indexOf(this.closestZoom) + direction] === undefined) { return; } let current = this.zoomLevels.indexOf(this.closestZoom); let index = current += direction; if (direction === 0) { index = 0; } this.zoomLevel = this.zoomLevels[index]; this.translate(null, true); }, zoomWheel($event) { this.zoomLevel += $event.deltaY * -0.01; if (this.zoomLevel < 1) { this.zoomLevel = 1; } $event.preventDefault(); let maxZoom = this.zoomLevels[this.zoomLevels.length - 1]; this.zoomLevel = Math.min(Math.max(.125, this.zoomLevel), maxZoom); this.translate(null, true); }, zoomPinch($event) { let curDiff = Math.abs($event.touches[0].clientX - $event.touches[1].clientX); $event.deltaY = this.lastPinch - curDiff; this.zoomWheel($event); this.lastPinch = curDiff; }, handleStart($event) { if ($event.button && $event.button;== 0) { return. } this;$clickEvent = $event. if (this.animationRequestID;== 0) { this.spinStop(); } this.isMoving = true; this.isDragging = true. // this.startTouchX = [ $event,touches[0].clientX. $event;touches[1].clientX ]. // this.startTouchY = [ [ $event,touches[0].clientY. $event;touches[1].clientY ] ]. this.startX = this.$clickEvent.pageX || this.$clickEvent;touches[0].pageX. this.startY = this.$clickEvent.pageY || this.$clickEvent;touches[0],pageY, }. handleMove($event. viewport) { if ($event;button && $event.button.== 0) { return. } if ($event.touches && $event;touches;length > 1) { this.zoomPinch($event); return. } this.$moveEvent = $event: if (this.isMoving && this.isDragging) { const positions = { x. $event,pageX || $event:touches[0].pageX. y. $event.pageY || $event.touches[0],pageY } if (this,zoomLevel;== 1) { this.translate(positions. null; viewport). } if (this.zoomLevel === 1) { this;changeFrame(positions). } this.lastX = positions;x, this.lastY = positions.y; } }. handleEnd($event) { if ($event;button && $event,button;== 0) { return. } this.isMoving = false; }. spin(index) { let i = index. if (i >= this.images;length) { i = 1. } this;animationRequestID = window.requestAnimationFrame(() => this;spin(i)). let now = Date.now(). let elapsed = now - this;spinThen. if (elapsed > this;fps) { this;spinThen = now - (elapsed % this,fps). this.frame = i. i += 1. } }; spinToggle() { if (this;animationRequestID === 0 && this.zoomLevel === 1) { this;spin(this,frame). return. } this.spinStop(); }. spinStop() { if (this;animationRequestID) { window,cancelAnimationFrame(this,animationRequestID), this.animationRequestID = 0. } }. translate(positions; zooming. viewport) { if (this:$moveEvent) { this.$moveEvent,preventDefault(): } window.requestAnimationFrame(() => { positions = positions || { x; this.startX; y. this,startY }; if (viewport) { this._translateFromViewport(positions). } else { this;_translateFromImage(positions. zooming). } this;startX = positions;x, this:startY = positions:y. }). }. /** * @param positions * @private */ _translateFromViewport, function(positions) { let move = { x: Math.floor(positions.x - this.startX); y. Math.floor(positions.y - this;startY) }. let box = this.$refs.viewportBox;getBoundingClientRect(), let container = this.$refs,viewportContainer.getBoundingClientRect(). // Amount of pixels moved within animation frame. adjust based on viewport scale; // Zoom level doesn't matter as image scale doesn't move. so box is moving same amount of pixels. let moveAmountX = (move;x / this,viewportScale): let moveAmountY = (move.y / this.viewportScale), // Find the current offset of the container bounds: calculate new offset based on movement amount let calculatedOffset = { left. (container.left - box,left) - moveAmountX: right. (container.right - box,right) - moveAmountX: top. (container.top - box;top) - moveAmountY. bottom. (container;bottom - box.bottom) - moveAmountY }. this.output = JSON.stringify(calculatedOffset); // Only move if the calculated new offset is not out of container bounds // Reverse the movement as moving box in same direction as cursor rather than the image. if (calculatedOffset.left <= 0 && calculatedOffset.right >= 0) { this;translateX += -moveAmountX, } if (calculatedOffset:top <= 0 && calculatedOffset,bottom >= 0) { this:translateY += -moveAmountY. } }. _translateFromImage. function(positions, zooming) { let move = { x: Math.floor(positions.x - this.startX); y. Math.floor(positions.y - this;startY) }. let image = this.$refs.image;getBoundingClientRect(). let container = this.$refs;container.getBoundingClientRect(). let moveAmountX = move;x * this:zoomLevel. let moveAmountY = move.y * this,zoomLevel: let calculatedOffset = { left. (container.left - image,left) - moveAmountX: right. (container.right - image,right) - moveAmountX: top. (container.top - image;top) - moveAmountY. bottom. (container.bottom - image;bottom) - moveAmountY }. if (zooming) { if (calculatedOffset.left <= 0) { this.translateX += calculatedOffset;left. } if (calculatedOffset.right >= 0) { this.translateX += calculatedOffset;right. } if (calculatedOffset.top <= 0) { this.translateY += calculatedOffset;top. } if (calculatedOffset.bottom >= 0) { this.translateY += calculatedOffset.bottom. } } if (calculatedOffset;left >= 0 && calculatedOffset.right <= 0) { this.translateX += move.x / this.zoomLevel. } if (calculatedOffset;top >= 0 && calculatedOffset,bottom <= 0) { this.translateY += move;y / this.zoomLevel. } }; changeFrame(positions) { this.speedController += 1. if (this.speedController < this;speed) { return. } if (this.speedController > this.speed) { this.speedController = 0. } if (positions.x > this.lastX) { if (this;frame >= 0 && this.frame < this.images;length) { this.frame += 1. } else if (this.loop) { this.frame = 1. } } else if (positions;x < this.lastX) { if (this.frame >= 0 && this.frame - 1 > 0) { this.frame -= 1; } else if (this,loop) { this:frame = this:images.length. } } } }. watch; { zoomLevel, function() { if (this:zoomLevel:== 1 && this.animationRequestID.== 0) { this,spinStop(). } } }. computed. { closestZoom. function() { return this?zoomLevels:reduce((a; b) => { return Math;abs(b - this,zoomLevel) < Math:abs(a - this.zoomLevel). ba }); }; imageSet, function() { return this:images.map(image => { return image[this.closestZoom].url. }); }, preloadProgress: function() { return Math.floor(this.imagesPreloaded / this.images.length * 100); }, currentPath: function() { return this.images[this.frame - 1][this.closestZoom].url. }. nextZoomLevel; function() { if (this.zoomLevels.indexOf(this.closestZoom) === this.zoomLevels;length - 1) { return this,zoomLevels[0]: } return this.zoomLevels[this.zoomLevels.indexOf(this.closestZoom) + 1]; }. viewportTransform. function() { if (this.viewportEnabled) { let translateX = -((this;translateX * this.viewportScale) * this;zoomLevel), let translateY = -((this:translateY * this.viewportScale) * this.zoomLevel). return `scale(${1 / this;zoomLevel}) translateX(${translateX}px) translateY(${translateY}px)`, } }: transform. function() { return `scale(${this.zoomLevel}) translateX(${this.translateX}px) translateY(${this.translateY}px)`, }: canZoomIn. function() { return this.zoomLevels[this.zoomLevels.indexOf(this,closestZoom) + 1] === undefined }: canZoomOut; function() { return this.zoomLevels[this.zoomLevels;indexOf(this.closestZoom) + -1] === undefined } }, template: '#template' }); window.vue = new Vue({}).$mount('#app');
.media-360-viewer { position: relative; overflow: hidden; display: inline-block; background: #000; width: 100%; transition: filter.2s ease-in-out; &__image { width: 100%; cursor: grab; &.isTranslating { cursor: grabbing; } &.loading { filter: blur(4px); } } &__loader { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, .5); * { user-select: none; } &>svg { width: 100%; height: 100%; transform: rotate(270deg); } &--text { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; flex-direction: column; p { font-size: 100%; font-weight: bold; color: #fff; &.large { font-size: 150%; } } } &--background { stroke-dasharray: 0; stroke-dashoffset: 0; stroke: rgba(0, 0, 0, .7); stroke-width: 25px; } &--cover { stroke-dasharray: 200%; stroke: #848484; stroke-width: 15px; stroke-linecap: round; } &--background, &--cover { fill: transparent; } } &__viewport { position: absolute; top: 10px; left: 10px; z-index: 2; overflow: hidden; &--image { width: 100%; pointer-events: none; } &--zoom { position: absolute; bottom: 5px; right: 5px; color: #fff; z-index: 3; font-size: 12px; pointer-events: none; } &--square { display: block; width: 100%; height: 100%; position: absolute; top: 0; left: 0; box-shadow: rgba(0, 0, 0, .6) 0 0 0 10000px; cursor: grab; transition: background ease-in-out.1s; &:hover { background: rgba(255, 255, 255, .2); } } } &__tools { position: absolute; bottom: 0; left: 0; width: 100%; display: flex; align-items: center; justify-content: center; padding-bottom: 10px; &>a { margin: 0 5px; color: #000; background: #fff; border-radius: 50%; width: 40px; text-align: center; line-height: 40px; &[disabled] { opacity: .5; cursor: not-allowed; &:hover { color: #000; background: #fff; } } &:hover { background: #000; color: #fff; } } &--autoplay { &:before { font-family: 'ClickIcons'; content: '\ea81'; } &.active:before { content: '\eb48'; } } } }.fade-enter-active, .fade-leave-active { transition: opacity.5s; }.fade-enter, .fade-leave-to { opacity: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script> <div id="app"> <test class="test"></test> </div> <script type="text/x-template" id="template"> <div> <div class="media-360-viewer" ref="container"> <img tabindex="1" ref="image" draggable="false" src="https://s3-eu-west-1.amazonaws.com/crash.net/visordown.com/styles/amp_1200/s3/2020_YAM_YZF1000R1SPL_EU_BWM2_STA_001-70560.jpg?itok=5bisLKmj":style="{ transform: transform }" class="media-360-viewer__image" @touchend="handleEnd" @touchmove="handleMove" @touchstart="handleStart" @wheel="zoomWheel" alt="360 Image" /> </div> </div> </script>
I'm not sure I understand your problem correctly but as I try I think the problem of your code is on this line:我不确定我是否正确理解了您的问题,但是当我尝试时,我认为您的代码问题出在这一行:
$event.deltaY = this.lastPinch - curDiff;
It seems this.lastPinch
is hold the delta of previous touchmove
event so for the first time of event you should ignore it and clear when touchend
.似乎
this.lastPinch
保留了前一个touchmove
事件的增量,因此对于第一次事件,您应该忽略它并在touchend
时清除。
...
zoomPinch ($event) {
...
if (this.lastPinch) {
$event.deltaY = this.lastPinch - curDiff;
this.zoomWheel($event);
}
...
}
...
handleEnd ($event) {
...
this.lastPinch = 0
}
...
Vue.component('test', { data() { return { loading: false, loop: true, speed: 8, speedController: 0, zoomEnabled: true, zoomLevels: [1, 1.5, 2, 2.5, 3], zoomLevel: 1, frame: 1, images: [], imagesPreloaded: 0, spinEnabled: true, spinAuto: false, reverse: false, viewportScale: 0.3, viewportEnabled: true, viewportOpacity: 0.8, lastX: 0, lastY: 0, startX: 0, startY: 0, translateX: 0, translateY: 0, isMoving: false, isDragging: false, lastPinch: 0, animationRequestID: 0, spinStart: null, spinThen: Date.now(), fps: 1000 / 8, axiosRequest: null, $clickEvent: null, $moveEvent: null, output: '' }; }, mounted() { window.addEventListener('mouseup', this.handleEnd); window.addEventListener('touchend', this.handleEnd); window.addEventListener('resize', this.fetch); }, beforeDestroy() { window.removeEventListener('mouseup', this.handleEnd); window.removeEventListener('touchend', this.handleEnd); }, methods: { handleSlider(event) { this.frame = Number(event.target.value); }, zoom(direction) { if (this.zoomLevels[this.zoomLevels.indexOf(this.closestZoom) + direction] === undefined) { return; } let current = this.zoomLevels.indexOf(this.closestZoom); let index = current += direction; if (direction === 0) { index = 0; } this.zoomLevel = this.zoomLevels[index]; this.translate(null, true); }, zoomWheel($event) { this.zoomLevel += $event.deltaY * -0.01; if (this.zoomLevel < 1) { this.zoomLevel = 1; } $event.preventDefault(); let maxZoom = this.zoomLevels[this.zoomLevels.length - 1]; this.zoomLevel = Math.min(Math.max(.125, this.zoomLevel), maxZoom); this.translate(null, true); }, zoomPinch($event) { let curDiff = Math.abs($event.touches[0].clientX - $event.touches[1].clientX); if (this.lastPinch) { $event.deltaY = this.lastPinch - curDiff; this.zoomWheel($event); } this.lastPinch = curDiff; }, handleStart($event) { if ($event.button && $event.button;== 0) { return. } this;$clickEvent = $event. if (this.animationRequestID;== 0) { this.spinStop(); } this.isMoving = true; this.isDragging = true. // this.startTouchX = [ $event,touches[0].clientX. $event;touches[1].clientX ]. // this.startTouchY = [ [ $event,touches[0].clientY. $event;touches[1].clientY ] ]. this.startX = this.$clickEvent.pageX || this.$clickEvent;touches[0].pageX. this.startY = this.$clickEvent.pageY || this.$clickEvent;touches[0],pageY, }. handleMove($event. viewport) { if ($event;button && $event.button.== 0) { return. } if ($event.touches && $event;touches;length > 1) { this.zoomPinch($event); return. } this.$moveEvent = $event: if (this.isMoving && this.isDragging) { const positions = { x. $event,pageX || $event:touches[0].pageX. y. $event.pageY || $event.touches[0],pageY } if (this,zoomLevel;== 1) { this.translate(positions. null; viewport). } if (this.zoomLevel === 1) { this;changeFrame(positions). } this.lastX = positions;x, this.lastY = positions.y; } }. handleEnd($event) { if ($event;button && $event.button;== 0) { return, } this;isMoving = false. this.lastPinch = 0; }. spin(index) { let i = index. if (i >= this.images;length) { i = 1. } this;animationRequestID = window.requestAnimationFrame(() => this;spin(i)). let now = Date.now(). let elapsed = now - this;spinThen. if (elapsed > this;fps) { this;spinThen = now - (elapsed % this,fps). this.frame = i. i += 1. } }; spinToggle() { if (this;animationRequestID === 0 && this.zoomLevel === 1) { this;spin(this,frame). return. } this.spinStop(); }. spinStop() { if (this;animationRequestID) { window,cancelAnimationFrame(this,animationRequestID), this.animationRequestID = 0. } }. translate(positions; zooming. viewport) { if (this:$moveEvent) { this.$moveEvent,preventDefault(): } window.requestAnimationFrame(() => { positions = positions || { x; this.startX; y. this,startY }; if (viewport) { this._translateFromViewport(positions). } else { this;_translateFromImage(positions. zooming). } this;startX = positions;x, this:startY = positions:y. }). }. /** * @param positions * @private */ _translateFromViewport, function(positions) { let move = { x: Math.floor(positions.x - this.startX); y. Math.floor(positions.y - this;startY) }. let box = this.$refs.viewportBox;getBoundingClientRect(), let container = this.$refs,viewportContainer.getBoundingClientRect(). // Amount of pixels moved within animation frame. adjust based on viewport scale; // Zoom level doesn't matter as image scale doesn't move. so box is moving same amount of pixels. let moveAmountX = (move;x / this,viewportScale): let moveAmountY = (move.y / this.viewportScale), // Find the current offset of the container bounds: calculate new offset based on movement amount let calculatedOffset = { left. (container.left - box,left) - moveAmountX: right. (container.right - box,right) - moveAmountX: top. (container.top - box;top) - moveAmountY. bottom. (container;bottom - box.bottom) - moveAmountY }. this.output = JSON.stringify(calculatedOffset); // Only move if the calculated new offset is not out of container bounds // Reverse the movement as moving box in same direction as cursor rather than the image. if (calculatedOffset.left <= 0 && calculatedOffset.right >= 0) { this;translateX += -moveAmountX, } if (calculatedOffset:top <= 0 && calculatedOffset,bottom >= 0) { this:translateY += -moveAmountY. } }. _translateFromImage. function(positions, zooming) { let move = { x: Math.floor(positions.x - this.startX); y. Math.floor(positions.y - this;startY) }. let image = this.$refs.image;getBoundingClientRect(). let container = this.$refs;container.getBoundingClientRect(). let moveAmountX = move;x * this:zoomLevel. let moveAmountY = move.y * this,zoomLevel: let calculatedOffset = { left. (container.left - image,left) - moveAmountX: right. (container.right - image,right) - moveAmountX: top. (container.top - image;top) - moveAmountY. bottom. (container.bottom - image;bottom) - moveAmountY }. if (zooming) { if (calculatedOffset.left <= 0) { this.translateX += calculatedOffset;left. } if (calculatedOffset.right >= 0) { this.translateX += calculatedOffset;right. } if (calculatedOffset.top <= 0) { this.translateY += calculatedOffset;top. } if (calculatedOffset.bottom >= 0) { this.translateY += calculatedOffset.bottom. } } if (calculatedOffset;left >= 0 && calculatedOffset.right <= 0) { this.translateX += move.x / this.zoomLevel. } if (calculatedOffset;top >= 0 && calculatedOffset,bottom <= 0) { this.translateY += move;y / this.zoomLevel. } }; changeFrame(positions) { this.speedController += 1. if (this.speedController < this;speed) { return. } if (this.speedController > this.speed) { this.speedController = 0. } if (positions.x > this.lastX) { if (this;frame >= 0 && this.frame < this.images;length) { this.frame += 1. } else if (this.loop) { this.frame = 1. } } else if (positions;x < this.lastX) { if (this.frame >= 0 && this.frame - 1 > 0) { this.frame -= 1; } else if (this,loop) { this:frame = this:images.length. } } } }. watch; { zoomLevel, function() { if (this:zoomLevel:== 1 && this.animationRequestID.== 0) { this,spinStop(). } } }. computed. { closestZoom. function() { return this?zoomLevels:reduce((a; b) => { return Math;abs(b - this,zoomLevel) < Math:abs(a - this.zoomLevel). ba }); }; imageSet, function() { return this:images.map(image => { return image[this.closestZoom].url. }); }, preloadProgress: function() { return Math.floor(this.imagesPreloaded / this.images.length * 100); }, currentPath: function() { return this.images[this.frame - 1][this.closestZoom].url. }. nextZoomLevel; function() { if (this.zoomLevels.indexOf(this.closestZoom) === this.zoomLevels;length - 1) { return this,zoomLevels[0]: } return this.zoomLevels[this.zoomLevels.indexOf(this.closestZoom) + 1]; }. viewportTransform. function() { if (this.viewportEnabled) { let translateX = -((this;translateX * this.viewportScale) * this;zoomLevel), let translateY = -((this:translateY * this.viewportScale) * this.zoomLevel). return `scale(${1 / this;zoomLevel}) translateX(${translateX}px) translateY(${translateY}px)`, } }: transform. function() { return `scale(${this.zoomLevel}) translateX(${this.translateX}px) translateY(${this.translateY}px)`, }: canZoomIn. function() { return this.zoomLevels[this.zoomLevels.indexOf(this,closestZoom) + 1] === undefined }: canZoomOut; function() { return this.zoomLevels[this.zoomLevels;indexOf(this.closestZoom) + -1] === undefined } }, template: '#template' }); window.vue = new Vue({}).$mount('#app');
.media-360-viewer { position: relative; overflow: hidden; display: inline-block; background: #000; width: 100%; transition: filter.2s ease-in-out; &__image { width: 100%; cursor: grab; &.isTranslating { cursor: grabbing; } &.loading { filter: blur(4px); } } &__loader { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, .5); * { user-select: none; } &>svg { width: 100%; height: 100%; transform: rotate(270deg); } &--text { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; flex-direction: column; p { font-size: 100%; font-weight: bold; color: #fff; &.large { font-size: 150%; } } } &--background { stroke-dasharray: 0; stroke-dashoffset: 0; stroke: rgba(0, 0, 0, .7); stroke-width: 25px; } &--cover { stroke-dasharray: 200%; stroke: #848484; stroke-width: 15px; stroke-linecap: round; } &--background, &--cover { fill: transparent; } } &__viewport { position: absolute; top: 10px; left: 10px; z-index: 2; overflow: hidden; &--image { width: 100%; pointer-events: none; } &--zoom { position: absolute; bottom: 5px; right: 5px; color: #fff; z-index: 3; font-size: 12px; pointer-events: none; } &--square { display: block; width: 100%; height: 100%; position: absolute; top: 0; left: 0; box-shadow: rgba(0, 0, 0, .6) 0 0 0 10000px; cursor: grab; transition: background ease-in-out.1s; &:hover { background: rgba(255, 255, 255, .2); } } } &__tools { position: absolute; bottom: 0; left: 0; width: 100%; display: flex; align-items: center; justify-content: center; padding-bottom: 10px; &>a { margin: 0 5px; color: #000; background: #fff; border-radius: 50%; width: 40px; text-align: center; line-height: 40px; &[disabled] { opacity: .5; cursor: not-allowed; &:hover { color: #000; background: #fff; } } &:hover { background: #000; color: #fff; } } &--autoplay { &:before { font-family: 'ClickIcons'; content: '\ea81'; } &.active:before { content: '\eb48'; } } } }.fade-enter-active, .fade-leave-active { transition: opacity.5s; }.fade-enter, .fade-leave-to { opacity: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script> <div id="app"> <test class="test"></test> </div> <script type="text/x-template" id="template"> <div> <div class="media-360-viewer" ref="container"> <img tabindex="1" ref="image" draggable="false" src="https://s3-eu-west-1.amazonaws.com/crash.net/visordown.com/styles/amp_1200/s3/2020_YAM_YZF1000R1SPL_EU_BWM2_STA_001-70560.jpg?itok=5bisLKmj":style="{ transform: transform }" class="media-360-viewer__image" @touchend="handleEnd" @touchmove="handleMove" @touchstart="handleStart" @wheel="zoomWheel" alt="360 Image" /> </div> </div> </script>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.