index.vue 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. <template>
  2. <div class="banner-box" :style="isIndicator ? 'position: relative' : ''">
  3. <swiper :circular="props.circular" :autoplay="props.autoplay" :interval="props.interval" :current="swiperIndex"
  4. :previous-margin="previousMargin" :next-margin="nextMargin" @change="swiperChange"
  5. @animationfinish="swiperFinish" :style="{ height: props.height, 'border-radius': props.radius }">
  6. <swiper-item v-for="(item, index) in bannerList" :key="index" @click="clickSwiper(item)">
  7. <template v-if="props.type == 1">
  8. <image :src="item[props.proxyField.url]" mode="scaleToFill" v-if="item.urlType == 'image'" />
  9. <video class="banner-video" :src="item[props.proxyField.url]"
  10. :poster="item[props.proxyField.poster]" object-fit="fill"
  11. v-if="item.urlType == 'video'"></video>
  12. </template>
  13. <template v-if="props.type == 2">
  14. <view class="main-card" :class="swiperIndex == index ? 'current-card' : 'side-card'" :style="{
  15. 'border-radius': props.radius,
  16. margin: swiperIndex == index ? `0 ${props.spacing}` : '',
  17. }">
  18. <image :src="item[props.proxyField.url]" mode="scaleToFill" v-if="item.urlType == 'image'" />
  19. <video class="banner-video" :src="item[props.proxyField.url]"
  20. :poster="item[props.proxyField.poster]" object-fit="fill"
  21. v-if="item.urlType == 'video'"></video>
  22. </view>
  23. </template>
  24. </swiper-item>
  25. </swiper>
  26. <view class="banner-indicator indicator1" v-if="isIndicator && props.indicatorType == 1">
  27. <view class="indicator-item" :class="{ 'indicator-item-active': indicatorIndex === swiperIndex }"
  28. v-for="(img, indicatorIndex) in bannerList.length" :key="indicatorIndex"
  29. @click="swiperIndex = indicatorIndex">
  30. </view>
  31. </view>
  32. <view class="banner-indicator indicator2" :style="`width: calc(${40}rpx * ${bannerList.length})`"
  33. v-if="isIndicator && props.indicatorType == 2">
  34. <div class="banner-indicator-box">
  35. <view class="indicator-item" :style="`transform: translateX(calc(${40}rpx * ${swiperIndex}))`"></view>
  36. </div>
  37. </view>
  38. </div>
  39. </template>
  40. <script setup>
  41. import { ref, onMounted, computed, watch, getCurrentInstance } from 'vue';
  42. import { onLoad } from '@dcloudio/uni-app';
  43. const { proxy } = getCurrentInstance();
  44. const example = proxy;
  45. const props = defineProps({
  46. bannerList: { //轮播图数据
  47. type: Array,
  48. required: true,
  49. },
  50. proxyField: { // 代理字段
  51. type: Object,
  52. default: {
  53. url: 'imageUrl', // 图片或视频资源的字段
  54. poster: 'poster', // 视频封面的图片网络资源的字段
  55. urlType: '', // 资源类型(image/video)
  56. },
  57. },
  58. height: { // 轮播图高度
  59. type: String,
  60. default: '500rpx',
  61. },
  62. circular: { // 是否采用衔接滑动
  63. type: Boolean,
  64. default: true,
  65. },
  66. autoplay: { // 是否自动切换
  67. type: Boolean,
  68. default: true,
  69. },
  70. interval: { // 自动切换时间间隔
  71. type: Number,
  72. default: 3000,
  73. },
  74. type: { // 轮播模式(1:默认;2:卡片)
  75. type: String,
  76. default: '1',
  77. },
  78. previousMargin: { // 前边距
  79. type: String,
  80. default: '',
  81. },
  82. nextMargin: { // 后边距
  83. type: String,
  84. default: '',
  85. },
  86. spacing: { // 卡片间距
  87. type: String,
  88. default: '20rpx',
  89. },
  90. radius: { // 圆角
  91. type: String,
  92. default: '20rpx',
  93. },
  94. isIndicator: { // 是否显示指示器
  95. type: Boolean,
  96. default: true,
  97. },
  98. indicatorType: { // 指示器类型(1:点状;2:条状)
  99. type: String,
  100. default: '1',
  101. },
  102. });
  103. let bannerList = ref([]);
  104. let previousMargin = ref('');
  105. let nextMargin = ref('');
  106. let isIndicator = ref(''); // 是否显示指示器
  107. watch(
  108. () => props.bannerList,
  109. () => {
  110. isIndicator.value = props.isIndicator;
  111. let osName = uni.getDeviceInfo().osName;
  112. if (props.type == 2) {
  113. previousMargin.value = props.previousMargin || '60rpx';
  114. nextMargin.value = props.nextMargin || '60rpx';
  115. }
  116. if (props.bannerList) {
  117. bannerList.value = props.bannerList;
  118. if (bannerList.value.length) {
  119. bannerList.value = bannerList.value.map((item) => {
  120. let urlType = props.proxyField.urlType || judgeFileType(item[props.proxyField.url]);
  121. if (urlType == 'video' && osName == 'ios') {
  122. isIndicator.value = false;
  123. }
  124. return {
  125. ...item,
  126. urlType,
  127. };
  128. });
  129. }
  130. }
  131. },
  132. {
  133. immediate: true,
  134. }
  135. );
  136. // 根据url判断文件类型
  137. function judgeFileType(url) {
  138. let imageType = [
  139. 'jpg',
  140. 'jpeg',
  141. 'png',
  142. 'gif',
  143. 'bmp',
  144. 'tiff',
  145. 'tif',
  146. 'webp',
  147. 'heic',
  148. 'ico',
  149. 'svg',
  150. 'eps',
  151. 'ai',
  152. 'raw',
  153. 'psd',
  154. 'xcf',
  155. 'tga',
  156. 'dds',
  157. ];
  158. let videoType = [
  159. 'mp4',
  160. 'mkv',
  161. 'avi',
  162. 'mov',
  163. 'wmv',
  164. 'flv',
  165. 'webm',
  166. 'mpg',
  167. 'mpeg',
  168. '3gp',
  169. 'm4v',
  170. 'vob',
  171. 'rmvb',
  172. 'f4v',
  173. 'ts',
  174. 'ogv',
  175. 'mxf',
  176. 'asf',
  177. 'swf',
  178. ];
  179. if (imageType.includes(url.split('.').pop())) {
  180. return 'image';
  181. } else if (videoType.includes(url.split('.').pop())) {
  182. return 'video';
  183. }
  184. }
  185. const emit = defineEmits(['clickSwiper']);
  186. let swiperIndex = ref(0); // 轮播图当前所在滑块的 index
  187. // 轮播图切换
  188. function swiperChange(e) {
  189. if (props.type == 2) {
  190. swiperIndex.value = e.detail.current;
  191. }
  192. }
  193. // 轮播图切换
  194. function swiperFinish(e) {
  195. if (props.type == 1) {
  196. swiperIndex.value = e.detail.current;
  197. }
  198. }
  199. // 点击轮播图
  200. function clickSwiper(item) {
  201. emit('clickSwiper', item);
  202. }
  203. </script>
  204. <style lang="scss" scoped>
  205. .banner-box {
  206. swiper {
  207. overflow: hidden;
  208. swiper-item {
  209. display: grid;
  210. align-items: center;
  211. overflow: hidden;
  212. transform: rotate(0deg);
  213. -webkit-transform: rotate(0deg);
  214. image {
  215. width: 100%;
  216. height: 100%;
  217. }
  218. video {
  219. width: 100%;
  220. height: 100%;
  221. }
  222. }
  223. }
  224. .banner-video {
  225. overflow: hidden;
  226. transform: rotate(0deg);
  227. -webkit-transform: rotate(0deg);
  228. }
  229. .main-card {
  230. overflow: hidden;
  231. transition: all 0.6s;
  232. }
  233. .current-card {
  234. height: 100%;
  235. }
  236. .side-card {
  237. height: 80%;
  238. }
  239. .banner-indicator {
  240. position: absolute;
  241. bottom: 20rpx;
  242. left: 50%;
  243. transform: translateX(-50%);
  244. }
  245. .indicator1 {
  246. display: flex;
  247. justify-content: center;
  248. align-items: center;
  249. .indicator-item {
  250. width: 15rpx;
  251. height: 15rpx;
  252. background: #fff;
  253. border-radius: 50%;
  254. margin-right: 14rpx;
  255. }
  256. .indicator-item-active {
  257. background: #fdd100;
  258. }
  259. }
  260. .indicator2 {
  261. .banner-indicator-box {
  262. background: #d4d4d4;
  263. border-radius: 15rpx;
  264. height: 20rpx;
  265. position: relative;
  266. .indicator-item {
  267. position: absolute;
  268. width: 40rpx;
  269. height: 20rpx;
  270. background: #2d99a1;
  271. border-radius: 15rpx;
  272. transition: all 0.3s;
  273. }
  274. }
  275. }
  276. }
  277. </style>