index.vue 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. <template>
  2. <div class="hamster-ppt-screen">
  3. <div
  4. class="slide-list"
  5. @mousewheel="$event => mousewheelListener($event)"
  6. v-contextmenu="contextmenus"
  7. >
  8. <div
  9. :class="[
  10. 'slide-item',
  11. {
  12. 'show': index === slideIndex,
  13. 'prev': index < slideIndex,
  14. 'next': index > slideIndex,
  15. }
  16. ]"
  17. v-for="(slide, index) in slides"
  18. :key="slide.id"
  19. >
  20. <div
  21. class="slide-content"
  22. :style="{
  23. width: slideWidth + 'px',
  24. height: slideHeight + 'px',
  25. }"
  26. >
  27. <ScreenSlide :scale="scale" :slide="slide" />
  28. </div>
  29. </div>
  30. </div>
  31. </div>
  32. </template>
  33. <script lang="ts">
  34. import { computed, defineComponent, onMounted, onUnmounted, ref } from 'vue'
  35. import { useStore } from 'vuex'
  36. import throttle from 'lodash/throttle'
  37. import { MutationTypes, State } from '@/store'
  38. import { exitFullscreen, isFullscreen } from '@/utils/fullscreen'
  39. import { VIEWPORT_ASPECT_RATIO, VIEWPORT_SIZE } from '@/configs/canvas'
  40. import { KEYS } from '@/configs/hotkey'
  41. import { ContextmenuItem } from '@/components/Contextmenu/types'
  42. import ScreenSlide from './ScreenSlide.vue'
  43. export default defineComponent({
  44. name: 'screen',
  45. components: {
  46. ScreenSlide,
  47. },
  48. setup() {
  49. const store = useStore<State>()
  50. const slides = computed(() => store.state.slides)
  51. const slideIndex = computed(() => store.state.slideIndex)
  52. const slideWidth = ref(0)
  53. const slideHeight = ref(0)
  54. const scale = computed(() => slideWidth.value / VIEWPORT_SIZE)
  55. const setSlideContentSize = () => {
  56. const winWidth = document.body.clientWidth
  57. const winHeight = document.body.clientHeight
  58. let width, height
  59. if(winHeight / winWidth === VIEWPORT_ASPECT_RATIO) {
  60. width = winWidth
  61. height = winHeight
  62. }
  63. else if(winHeight / winWidth > VIEWPORT_ASPECT_RATIO) {
  64. width = winWidth
  65. height = winWidth * VIEWPORT_ASPECT_RATIO
  66. }
  67. else {
  68. width = winHeight / VIEWPORT_ASPECT_RATIO
  69. height = winHeight
  70. }
  71. slideWidth.value = width
  72. slideHeight.value = height
  73. }
  74. const windowResizeListener = () => {
  75. setSlideContentSize()
  76. if(!isFullscreen()) store.commit(MutationTypes.SET_SCREENING, false)
  77. }
  78. const turnPrevSlide = () => {
  79. if(slideIndex.value <= 0) return
  80. store.commit(MutationTypes.UPDATE_SLIDE_INDEX, slideIndex.value - 1)
  81. }
  82. const turnNextSlide = () => {
  83. if(slideIndex.value >= slides.value.length - 1) return
  84. store.commit(MutationTypes.UPDATE_SLIDE_INDEX, slideIndex.value + 1)
  85. }
  86. const keydownListener = (e: KeyboardEvent) => {
  87. const key = e.key.toUpperCase()
  88. if(key === KEYS.UP || key === KEYS.LEFT) turnPrevSlide()
  89. else if(key === KEYS.DOWN || key === KEYS.RIGHT) turnNextSlide()
  90. }
  91. const mousewheelListener = throttle(function(e: WheelEvent) {
  92. if(e.deltaY > 0) turnNextSlide()
  93. else if(e.deltaY < 0) turnPrevSlide()
  94. }, 500, { leading: true, trailing: false })
  95. onMounted(() => {
  96. window.addEventListener('resize', windowResizeListener)
  97. document.addEventListener('keydown', keydownListener)
  98. })
  99. onUnmounted(() => {
  100. window.removeEventListener('resize', windowResizeListener)
  101. document.removeEventListener('keydown', keydownListener)
  102. })
  103. const contextmenus = (): ContextmenuItem[] => {
  104. return [
  105. {
  106. text: '上一页',
  107. disable: slideIndex.value <= 0,
  108. handler: () => turnPrevSlide(),
  109. },
  110. {
  111. text: '下一页',
  112. disable: slideIndex.value >= slides.value.length - 1,
  113. handler: () => turnNextSlide(),
  114. },
  115. { divider: true },
  116. {
  117. text: '结束放映',
  118. subText: 'ESC',
  119. handler: exitFullscreen,
  120. },
  121. ]
  122. }
  123. return {
  124. slides,
  125. slideIndex,
  126. slideWidth,
  127. slideHeight,
  128. scale,
  129. mousewheelListener,
  130. contextmenus,
  131. }
  132. },
  133. })
  134. </script>
  135. <style lang="scss" scoped>
  136. .hamster-ppt-screen {
  137. width: 100%;
  138. height: 100%;
  139. position: relative;
  140. background-color: #111;
  141. }
  142. .slide-list {
  143. background: #1d1d1d;
  144. position: relative;
  145. width: 100%;
  146. height: 100%;
  147. }
  148. .slide-item {
  149. position: absolute;
  150. top: 0;
  151. left: 0;
  152. width: 100%;
  153. height: 100%;
  154. transition-property: transform;
  155. transition-duration: .4s;
  156. &.show {
  157. z-index: 2;
  158. }
  159. &.prev {
  160. transform: translateY(-100%);
  161. }
  162. &.next {
  163. transform: translateY(100%);
  164. }
  165. }
  166. .slide-content {
  167. background-color: #fff;
  168. position: absolute;
  169. top: 50%;
  170. left: 50%;
  171. transform: translate(-50%, -50%);
  172. display: flex;
  173. justify-content: center;
  174. align-items: center;
  175. }
  176. </style>