wd-swiper.vue 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. <template>
  2. <view :class="`wd-swiper ${customClass}`" :style="customStyle">
  3. <!-- #ifdef MP-WEIXIN -->
  4. <scroll-view scroll-x scroll-y style="width: 100%; height: 100%">
  5. <!-- #endif -->
  6. <swiper
  7. :adjust-height="adjustHeight"
  8. :adjust-vertical-height="adjustVerticalHeight"
  9. class="wd-swiper__track"
  10. :autoplay="autoplay && !videoPlaying"
  11. :current="navCurrent"
  12. :interval="interval"
  13. :duration="duration"
  14. :circular="loop"
  15. :vertical="direction == 'vertical'"
  16. :easing-function="easingFunction"
  17. :previous-margin="addUnit(previousMargin)"
  18. :next-margin="addUnit(nextMargin)"
  19. :snap-to-edge="snapToEdge"
  20. :display-multiple-items="displayMultipleItems"
  21. :style="{ height: addUnit(height) }"
  22. @change="handleChange"
  23. @animationfinish="handleAnimationfinish"
  24. >
  25. <swiper-item v-for="(item, index) in list" :key="index" :class="swiperItemClass">
  26. <slot :item="item" :index="index">
  27. <video
  28. v-if="isVideo(item)"
  29. :id="`video-${index}-${uid}`"
  30. :style="{ height: addUnit(height) }"
  31. :src="isObj(item) ? item[valueKey] : item"
  32. :poster="isObj(item) ? item.poster : ''"
  33. :class="`wd-swiper__video ${customItemClass} ${getCustomItemClass(currentValue, index, list)}`"
  34. @play="handleVideoPaly"
  35. @pause="handleVideoPause"
  36. :enable-progress-gesture="false"
  37. :loop="videoLoop"
  38. :muted="muted"
  39. :autoplay="autoplayVideo"
  40. objectFit="cover"
  41. @click="handleClick(index, item)"
  42. />
  43. <image
  44. v-else
  45. :src="isObj(item) ? item[valueKey] : item"
  46. :class="`wd-swiper__image ${customImageClass} ${customItemClass} ${getCustomItemClass(currentValue, index, list)}`"
  47. :style="{ height: addUnit(height) }"
  48. :mode="imageMode"
  49. @click="handleClick(index, item)"
  50. />
  51. <text v-if="isObj(item) && item[textKey]" :class="`wd-swiper__text ${customTextClass}`" :style="customTextStyle">
  52. {{ item[textKey] }}
  53. </text>
  54. </slot>
  55. </swiper-item>
  56. </swiper>
  57. <!-- #ifdef MP-WEIXIN -->
  58. </scroll-view>
  59. <!-- #endif -->
  60. <template v-if="indicator">
  61. <slot name="indicator" :current="currentValue" :total="list.length"></slot>
  62. <wd-swiper-nav
  63. v-if="!$slots.indicator"
  64. :custom-class="customIndicatorClass"
  65. :type="swiperIndicator.type"
  66. :current="swiperIndicator.current"
  67. :total="swiperIndicator.total"
  68. :direction="swiperIndicator.direction"
  69. :indicator-position="swiperIndicator.indicatorPosition"
  70. :min-show-num="swiperIndicator.minShowNum"
  71. :show-controls="swiperIndicator.showControls"
  72. @change="handleIndicatorChange"
  73. />
  74. </template>
  75. </view>
  76. </template>
  77. <script lang="ts">
  78. export default {
  79. name: 'wd-swiper',
  80. options: {
  81. addGlobalClass: true,
  82. virtualHost: true,
  83. styleIsolation: 'shared'
  84. }
  85. }
  86. </script>
  87. <script lang="ts" setup>
  88. import wdSwiperNav from '../wd-swiper-nav/wd-swiper-nav.vue'
  89. import { computed, watch, ref, getCurrentInstance, useSlots } from 'vue'
  90. import { addUnit, isObj, isImageUrl, isVideoUrl, uuid, isDef } from '../common/util'
  91. import { swiperProps, type SwiperList } from './types'
  92. import type { SwiperNavProps } from '../wd-swiper-nav/types'
  93. const slots = useSlots()
  94. const props = defineProps(swiperProps)
  95. const emit = defineEmits(['click', 'change', 'animationfinish', 'update:current'])
  96. const navCurrent = ref<number>(props.current) // 当前滑块 swiper使用
  97. const currentValue = ref<number>(props.current) // 当前滑块
  98. /**
  99. * 更新当前滑块
  100. * @param current 当前滑块索引
  101. * @param force 是否强制更新swiper绑定的的current
  102. */
  103. const updateCurrent = (current: number, force: boolean = false) => {
  104. currentValue.value = current
  105. if (force) {
  106. navCurrent.value = current
  107. }
  108. emit('update:current', current)
  109. }
  110. const videoPlaying = ref<boolean>(false) // 当前是否在播放视频
  111. const { proxy } = getCurrentInstance() as any
  112. const uid = ref<string>(uuid())
  113. watch(
  114. () => props.current,
  115. (val) => {
  116. if (val < 0) {
  117. props.loop ? goToEnd() : goToStart()
  118. } else if (val >= props.list.length) {
  119. props.loop ? goToStart() : goToEnd()
  120. } else {
  121. navTo(val)
  122. }
  123. }
  124. )
  125. const swiperItemClass = computed(() => {
  126. return `wd-swiper__item ${slots.default ? 'wd-swiper__item--slot' : ''}`
  127. })
  128. const swiperIndicator = computed(() => {
  129. const { list, direction, indicatorPosition, indicator } = props
  130. const swiperIndicator: Partial<SwiperNavProps> = {
  131. current: currentValue.value || 0,
  132. total: list.length || 0,
  133. direction: direction || 'horizontal',
  134. indicatorPosition: indicatorPosition || 'bottom'
  135. }
  136. if (isObj(indicator)) {
  137. swiperIndicator.type = indicator.type || 'dots'
  138. swiperIndicator.minShowNum = indicator.minShowNum || 2
  139. swiperIndicator.showControls = indicator.showControls || false
  140. }
  141. return swiperIndicator
  142. })
  143. const getMediaType = (item: string | SwiperList, type: 'video' | 'image') => {
  144. const checkType = (url: string) => (type === 'video' ? isVideoUrl(url) : isImageUrl(url))
  145. if (isObj(item)) {
  146. return item.type && ['video', 'image'].includes(item.type) ? item.type === type : checkType(item[props.valueKey])
  147. } else {
  148. return checkType(item)
  149. }
  150. }
  151. const isVideo = (item: string | SwiperList) => {
  152. return getMediaType(item, 'video')
  153. }
  154. const isImage = (item: string | SwiperList) => {
  155. return getMediaType(item, 'image')
  156. }
  157. function navTo(index: number) {
  158. if (index === currentValue.value) return
  159. updateCurrent(index, true)
  160. }
  161. function goToStart() {
  162. navTo(0)
  163. }
  164. function goToEnd() {
  165. navTo(props.list.length - 1)
  166. }
  167. // 视频播放
  168. function handleVideoPaly() {
  169. props.stopAutoplayWhenVideoPlay && (videoPlaying.value = true)
  170. }
  171. // 视频暂停
  172. function handleVideoPause() {
  173. videoPlaying.value = false
  174. }
  175. /**
  176. * 是否为当前滑块的前一个滑块
  177. * @param current
  178. * @param index
  179. * @param list
  180. */
  181. function isPrev(current: number, index: number, list: string[] | SwiperList[]) {
  182. return (current - 1 + list.length) % list.length === index
  183. }
  184. /**
  185. * 是否为当前滑块的后一个滑块
  186. * @param current
  187. * @param index
  188. * @param list
  189. */
  190. function isNext(current: number, index: number, list: string[] | SwiperList[]) {
  191. return (current + 1 + list.length) % list.length === index
  192. }
  193. function getCustomItemClass(current: number, index: number, list: string[] | SwiperList[]) {
  194. let customItemClass: string = ''
  195. if (isPrev(current, index, list)) {
  196. customItemClass = props.customPrevClass || props.customPrevImageClass
  197. }
  198. if (isNext(current, index, list)) {
  199. customItemClass = props.customNextClass || props.customNextImageClass
  200. }
  201. return customItemClass
  202. }
  203. /**
  204. * 轮播滑块切换时触发
  205. */
  206. function handleChange(e: { detail: { current: number; source: string } }) {
  207. const { current, source } = e.detail
  208. const previous = currentValue.value
  209. emit('change', { current, source })
  210. if (current !== currentValue.value) {
  211. const forceUpdate = source === 'autoplay' || source === 'touch'
  212. updateCurrent(current, forceUpdate)
  213. }
  214. handleVideoChange(previous, current)
  215. }
  216. /**
  217. * 处理视频切换
  218. */
  219. function handleVideoChange(previous: number, current: number) {
  220. handleStopVideoPaly(previous)
  221. handleStartVideoPaly(current)
  222. }
  223. /**
  224. * 开始播放指定视频
  225. * @param index
  226. */
  227. function handleStartVideoPaly(index: number) {
  228. if (props.autoplayVideo) {
  229. const currentItem = props.list[index]
  230. if (isDef(currentItem) && isVideo(currentItem)) {
  231. const video = uni.createVideoContext(`video-${index}-${uid.value}`, proxy)
  232. video.play()
  233. }
  234. }
  235. }
  236. /**
  237. * 停止播放指定视频
  238. * @param index
  239. */
  240. function handleStopVideoPaly(index: number) {
  241. if (props.stopPreviousVideo) {
  242. const previousItem = props.list[index]
  243. if (isDef(previousItem) && isVideo(previousItem)) {
  244. const video = uni.createVideoContext(`video-${index}-${uid.value}`, proxy)
  245. video.pause()
  246. }
  247. } else if (props.stopAutoplayWhenVideoPlay) {
  248. handleVideoPause()
  249. }
  250. }
  251. /**
  252. * 滑块动画结束
  253. */
  254. function handleAnimationfinish(e: { detail: { current: any; source: string } }) {
  255. const { current, source } = e.detail
  256. if (current !== currentValue.value) {
  257. const forceUpdate = source === 'autoplay' || source === 'touch'
  258. updateCurrent(current, forceUpdate)
  259. }
  260. /**
  261. * 滑块动画结束时触发
  262. */
  263. emit('animationfinish', { current, source })
  264. }
  265. /**
  266. * 点击滑块事件
  267. * @param index 点击的滑块下标
  268. * @param item 点击的滑块内容
  269. */
  270. function handleClick(index: number, item: string | SwiperList) {
  271. emit('click', { index, item })
  272. }
  273. function handleIndicatorChange({ dir }: { dir: 'prev' | 'next' }) {
  274. const { list, loop } = props
  275. const total = list.length
  276. let nextPos = dir === 'next' ? currentValue.value + 1 : currentValue.value - 1
  277. if (loop) {
  278. nextPos = dir === 'next' ? (currentValue.value + 1) % total : (currentValue.value - 1 + total) % total
  279. } else {
  280. nextPos = nextPos < 0 || nextPos >= total ? currentValue.value : nextPos
  281. }
  282. if (nextPos === currentValue.value) return
  283. navTo(nextPos)
  284. }
  285. </script>
  286. <style lang="scss" scoped>
  287. @import './index.scss';
  288. </style>