爱简收旧衣按件回收前端代码仓库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

229 lines
5.5 KiB

2 weeks ago
  1. <template>
  2. <view class="banner" :style="{ height: height }">
  3. <swiper
  4. :indicator-dots="false"
  5. :autoplay="true"
  6. :interval="3000"
  7. :duration="500"
  8. circular
  9. :style="{ width: '100%', height: height }"
  10. >
  11. <swiper-item v-for="(item, index) in bannerList" :key="item.id || index">
  12. <view v-if="item.type == 1" class="video-container">
  13. <!-- 预览状态显示封面图 -->
  14. <image
  15. v-if="!videoPlayingStates[index]"
  16. :src="item.image || ''"
  17. mode="aspectFill"
  18. style="width: 100%; height: 100%;"
  19. class="video-poster"
  20. @click="playVideoFullscreen(item, index)"
  21. />
  22. <!-- 播放状态显示视频 -->
  23. <video
  24. v-else
  25. :id="`${videoIdPrefix}-${index}`"
  26. :src="item.voUrl"
  27. :autoplay="true"
  28. :muted="false"
  29. :loop="false"
  30. :controls="true"
  31. :show-play-btn="true"
  32. :show-center-play-btn="false"
  33. :show-fullscreen-btn="true"
  34. :show-progress="true"
  35. :show-mute-btn="true"
  36. :enable-progress-gesture="true"
  37. :enable-play-gesture="true"
  38. object-fit="cover"
  39. style="width: 100%; height: 100%;"
  40. @fullscreenchange="onFullscreenChange"
  41. @play="onVideoPlay(index)"
  42. @pause="onVideoPause(index)"
  43. @ended="onVideoEnded(index)"
  44. ></video>
  45. <!-- 播放按钮覆盖层 -->
  46. <view v-if="!videoPlayingStates[index]" class="video-overlay" @click="playVideoFullscreen(item, index)">
  47. <view class="play-button-large">
  48. <view class="play-triangle"></view>
  49. </view>
  50. </view>
  51. </view>
  52. <image v-else :src="item.image" mode="aspectFill" style="width: 100%; height: 100%;" @click="onImageClick(item, index)" />
  53. </swiper-item>
  54. </swiper>
  55. <!-- 备用静态图片当没有轮播图数据时显示 -->
  56. <image v-if="!bannerList || bannerList.length === 0" :src="fallbackImage" mode="aspectFill" style="width: 100%; height: 100%;" />
  57. </view>
  58. </template>
  59. <script>
  60. export default {
  61. name: 'BannerSwiper',
  62. props: {
  63. // 轮播图数据
  64. bannerList: {
  65. type: Array,
  66. default: () => []
  67. },
  68. // 轮播图高度
  69. height: {
  70. type: String,
  71. default: '400rpx'
  72. },
  73. // 备用图片
  74. fallbackImage: {
  75. type: String,
  76. default: ''
  77. },
  78. // 视频ID前缀,用于区分不同页面的视频
  79. videoIdPrefix: {
  80. type: String,
  81. default: 'video'
  82. }
  83. },
  84. data() {
  85. return {
  86. videoPlayingStates: {} // 记录每个视频的播放状态
  87. }
  88. },
  89. methods: {
  90. // 视频全屏播放
  91. playVideoFullscreen(item, index) {
  92. if (!this.videoPlayingStates[index]) {
  93. // 第一次点击:显示视频并开始播放
  94. this.$set(this.videoPlayingStates, index, true)
  95. // 等待视频元素渲染后再进行操作
  96. this.$nextTick(() => {
  97. setTimeout(() => {
  98. const videoContext = uni.createVideoContext(`${this.videoIdPrefix}-${index}`, this)
  99. if (videoContext) {
  100. // 直接进入全屏播放,强制竖屏
  101. videoContext.requestFullScreen({
  102. direction: 0 // 竖屏方向,0代表竖屏,1代表横屏,-1自动
  103. })
  104. }
  105. }, 200)
  106. })
  107. }
  108. },
  109. // 图片点击事件
  110. onImageClick(item, index) {
  111. this.$emit('image-click', { item, index })
  112. },
  113. // 视频播放事件
  114. onVideoPlay(index) {
  115. this.$set(this.videoPlayingStates, index, true)
  116. },
  117. // 视频暂停事件
  118. onVideoPause(index) {
  119. this.$set(this.videoPlayingStates, index, false)
  120. },
  121. // 视频结束事件
  122. onVideoEnded(index) {
  123. // 视频播放结束,回到预览状态
  124. this.$set(this.videoPlayingStates, index, false)
  125. },
  126. // 全屏状态改变事件
  127. onFullscreenChange(e) {
  128. console.log('全屏状态改变:', e.detail)
  129. const videoIndex = e.target.id.replace(`${this.videoIdPrefix}-`, '')
  130. if (e.detail.fullScreen) {
  131. // 进入全屏时,不做任何操作
  132. console.log('进入全屏模式,方向:', e.detail.direction)
  133. } else {
  134. // 退出全屏时,回到预览状态
  135. console.log('退出全屏模式,回到预览状态')
  136. this.$set(this.videoPlayingStates, videoIndex, false)
  137. }
  138. }
  139. }
  140. }
  141. </script>
  142. <style lang="scss" scoped>
  143. .banner {
  144. width: 100%;
  145. position: relative;
  146. overflow: hidden;
  147. border-radius: 0 0 30rpx 30rpx;
  148. image {
  149. width: 100%;
  150. height: 100%;
  151. }
  152. .video-container {
  153. position: relative;
  154. width: 100%;
  155. height: 100%;
  156. .video-poster {
  157. cursor: pointer;
  158. transition: transform 0.2s ease;
  159. &:active {
  160. transform: scale(0.98);
  161. }
  162. }
  163. .video-overlay {
  164. position: absolute;
  165. top: 0;
  166. left: 0;
  167. right: 0;
  168. bottom: 0;
  169. background: rgba(0, 0, 0, 0.4);
  170. z-index: 10;
  171. cursor: pointer;
  172. display: flex;
  173. justify-content: center;
  174. align-items: center;
  175. .play-button-large {
  176. width: 120rpx;
  177. height: 120rpx;
  178. background: transparent;
  179. border-radius: 50%;
  180. display: flex;
  181. justify-content: center;
  182. align-items: center;
  183. backdrop-filter: blur(10rpx);
  184. transition: all 0.3s ease;
  185. box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.3);
  186. &:active {
  187. transform: scale(0.9);
  188. background: rgba(255, 255, 255, 0.1);
  189. }
  190. .play-triangle {
  191. width: 0;
  192. height: 0;
  193. border-left: 24rpx solid #fff;
  194. border-top: 18rpx solid transparent;
  195. border-bottom: 18rpx solid transparent;
  196. margin-left: 8rpx;
  197. transition: all 0.3s ease;
  198. filter: drop-shadow(0 2rpx 4rpx rgba(0, 0, 0, 0.3));
  199. }
  200. }
  201. &:hover .play-button-large {
  202. transform: scale(1.1);
  203. box-shadow: 0 12rpx 40rpx rgba(0, 0, 0, 0.4);
  204. .play-triangle {
  205. border-left-color: #ff8917;
  206. }
  207. }
  208. }
  209. }
  210. }
  211. </style>