| <template> |
| <div class="mobile-player" |
| :style="{ |
| width: playerSize.width + 'px', |
| height: playerSize.height + 'px', |
| transform: `rotate(90deg) translateY(-${playerSize.height}px)`, |
| }" |
| > |
| <div |
| class="screen-slide-list" |
| @click="toolVisible = !toolVisible" |
| @touchstart="$event => touchStartListener($event)" |
| @touchend="$event => touchEndListener($event)" |
| > |
| <div |
| :class="[ |
| 'slide-item', |
| `turning-mode-${slide.turningMode || 'slideY'}`, |
| { |
| 'current': index === slideIndex, |
| 'before': index < slideIndex, |
| 'after': index > slideIndex, |
| 'hide': (index === slideIndex - 1 || index === slideIndex + 1) && slide.turningMode !== slidesWithTurningMode[slideIndex].turningMode, |
| 'last': index === slideIndex - 1, |
| 'next': index === slideIndex + 1, |
| } |
| ]" |
| v-for="(slide, index) in slidesWithTurningMode" |
| :key="slide.id" |
| > |
| <div |
| class="slide-content" |
| :style="{ |
| width: slideSize.width + 'px', |
| height: slideSize.height + 'px', |
| }" |
| v-if="Math.abs(slideIndex - index) < 2" |
| > |
| <ThumbnailSlide |
| :slide="slide" |
| :size="slideSize.width" |
| /> |
| </div> |
| </div> |
| </div> |
| |
| <template v-if="toolVisible"> |
| <div class="header"> |
| <div class="back" @click="changeMode('preview')"><IconLogout /> 退出播放</div> |
| </div> |
| <MobileThumbnails class="thumbnails" /> |
| </template> |
| </div> |
| </template> |
| |
| <script lang="ts" setup> |
| import { computed, onMounted, ref } from 'vue' |
| import { storeToRefs } from 'pinia' |
| import { useSlidesStore } from '@/store' |
| import type { Mode } from '@/types/mobile' |
| import useSlidesWithTurningMode from '../Screen/hooks/useSlidesWithTurningMode' |
| |
| import ThumbnailSlide from '@/views/components/ThumbnailSlide/index.vue' |
| import MobileThumbnails from './MobileThumbnails.vue' |
| |
| defineProps<{ |
| changeMode: (mode: Mode) => void |
| }>() |
| |
| const slidesStore = useSlidesStore() |
| const { slides, slideIndex, viewportRatio } = storeToRefs(slidesStore) |
| |
| const { slidesWithTurningMode } = useSlidesWithTurningMode() |
| |
| const toolVisible = ref(false) |
| |
| const playerSize = ref({ width: 0, height: 0 }) |
| |
| onMounted(() => { |
| if (slideIndex.value !== 0) slidesStore.updateSlideIndex(0) |
| |
| playerSize.value = { |
| width: document.body.clientHeight, |
| height: document.body.clientWidth, |
| } |
| }) |
| |
| const slideSize = computed(() => { |
| const playerRatio = playerSize.value.height / playerSize.value.width |
| |
| let slideWidth = 0 |
| let slideHeight = 0 |
| |
| if (playerRatio >= viewportRatio.value) { |
| slideWidth = playerSize.value.width |
| slideHeight = slideWidth * viewportRatio.value |
| } |
| else { |
| slideHeight = playerSize.value.height |
| slideWidth = slideHeight / viewportRatio.value |
| } |
| |
| return { |
| width: slideWidth, |
| height: slideHeight, |
| } |
| }) |
| |
| const touchInfo = ref<{ x: number; y: number; } | null>(null) |
| const touchStartListener = (e: TouchEvent) => { |
| touchInfo.value = { |
| x: e.changedTouches[0].pageX, |
| y: e.changedTouches[0].pageY, |
| } |
| } |
| const touchEndListener = (e: TouchEvent) => { |
| if (!touchInfo.value) return |
| |
| const offsetY = Math.abs(touchInfo.value.y - e.changedTouches[0].pageY) |
| const offsetX = e.changedTouches[0].pageX - touchInfo.value.x |
| |
| if ( Math.abs(offsetX) > offsetY && Math.abs(offsetX) > 50 ) { |
| touchInfo.value = null |
| |
| if (offsetX < 0 && slideIndex.value > 0) slidesStore.updateSlideIndex(slideIndex.value - 1) |
| if (offsetX > 0 && slideIndex.value < slides.value.length - 1) slidesStore.updateSlideIndex(slideIndex.value + 1) |
| } |
| } |
| </script> |
| |
| <style lang="scss" scoped> |
| .mobile-player { |
| transform-origin: 0 0; |
| background-color: #1d1d1d; |
| position: relative; |
| } |
| .screen-slide-list { |
| position: relative; |
| width: 100%; |
| height: 100%; |
| } |
| .slide-item { |
| position: absolute; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| |
| &:not(.last, .next) { |
| z-index: -1; |
| } |
| |
| &.current { |
| z-index: 2; |
| } |
| |
| &.hide { |
| opacity: 0; |
| } |
| |
| &.turning-mode-no { |
| &.before { |
| transform: translateY(-100%); |
| } |
| &.after { |
| transform: translateY(100%); |
| } |
| } |
| &.turning-mode-fade { |
| transition: opacity .75s; |
| &.before { |
| pointer-events: none; |
| opacity: 0; |
| } |
| &.after { |
| pointer-events: none; |
| opacity: 0; |
| } |
| } |
| &.turning-mode-slideX { |
| transition: transform .35s; |
| &.before { |
| transform: translateX(-100%); |
| } |
| &.after { |
| transform: translateX(100%); |
| } |
| } |
| &.turning-mode-slideY { |
| transition: transform .35s; |
| &.before { |
| transform: translateY(-100%); |
| } |
| &.after { |
| transform: translateY(100%); |
| } |
| } |
| } |
| .slide-content { |
| background-color: #fff; |
| position: absolute; |
| top: 50%; |
| left: 50%; |
| transform: translate(-50%, -50%); |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| } |
| |
| .header { |
| width: 100%; |
| height: 40px; |
| line-height: 40px; |
| padding: 0 15px; |
| position: absolute; |
| top: 0; |
| left: 0; |
| z-index: 99; |
| background-color: rgba($color: #1d1d1d, $alpha: .7); |
| text-align: right; |
| font-size: 13px; |
| color: #fff; |
| animation: slideInDown .15s; |
| |
| .back { |
| height: 100%; |
| } |
| } |
| .thumbnails { |
| width: 100%; |
| position: absolute; |
| bottom: 0; |
| left: 0; |
| z-index: 99; |
| background-color: rgba($color: #1d1d1d, $alpha: .7); |
| overflow: auto !important; |
| animation: slideInUp .15s; |
| } |
| |
| @keyframes slideInUp { |
| from { |
| transform: translateY(100%); |
| } |
| to { |
| transform: translateY(0); |
| } |
| } |
| @keyframes slideInDown { |
| from { |
| transform: translateY(-100%); |
| } |
| to { |
| transform: translateY(0); |
| } |
| } |
| </style> |