index.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. <template>
  2. <view class="swiperContent">
  3. <swiper
  4. @transition='transition'
  5. @change="change"
  6. @animationfinish="animationfinish"
  7. :indicator-dots="indicatorDots"
  8. :indicator-active-color="indicatorActiveColor"
  9. :indicator-color="indicatorColor"
  10. :autoplay="autoplay && !videoPlaySataus"
  11. :current="activeCurrent"
  12. :interval="interval"
  13. :duration="duration"
  14. :circular="circular"
  15. :vertical="vertical"
  16. :previous-margin="effect3d ? effect3dMargin : previousMargin"
  17. :next-margin="effect3d ? effect3dMargin : nextMargin"
  18. :display-multiple-items="displayMultipleItems"
  19. :skip-hidden-item-layout="skipHiddenItemLayout"
  20. :style="{'height':swiperHeight+'px','border-radius': contentRadius,'overflow': 'hidden'}"
  21. :class="{
  22. 'effect3D':effect3d && displayMultipleItems ==1,
  23. 'effect3D-X':effect3d && !vertical,
  24. 'effect3D-Y':effect3d && vertical}"
  25. class="z-screen-swiper">
  26. <swiper-item
  27. class="z-swiper-item"
  28. v-for="(item,index) in swiperList"
  29. :key="index"
  30. :class="{'active-swiper':activeCurrent===index}"
  31. :style="{'backgroundColor':`${bgColor}`}"
  32. @tap="clickItem(index)">
  33. <view class="swiper-box" :style="{'backgroundColor':`${bgColorItem}`}">
  34. <view
  35. class="swiper-top"
  36. :class="{'isFloat':topFloat}"
  37. @click="$emit('clickTop',{item,index})">
  38. <slot :row='item' :index='index' name="top">
  39. <!-- 顶部占位 无值则不占位 -->
  40. <view v-if="item[topTextKey]"
  41. :style="{'text-align':`${topTextAlign}`,
  42. 'color':`${topColor}`,
  43. 'background-color':`${topBackground}`}">
  44. <text v-text="item[topTextKey]"></text>
  45. </view>
  46. </slot>
  47. </view>
  48. <view class="swiper-content">
  49. <slot :row='item' :index='index'>
  50. <image v-if="item.type==='image'" :src="item[imageKey]" :mode='imgMode'>
  51. <template v-if="item.type==='video'">
  52. <!-- :style="{'height':fullScreen ? '':'100%' }" 解决非全屏video显示问题 -->
  53. <video
  54. :style="{'height':fullScreen ? '':'100%'}"
  55. @play='play'
  56. @pause='pause'
  57. @timeupdate='timeupdate'
  58. :initial-time='item.currentTime || 0 '
  59. enable-play-gesture
  60. :enable-progress-gesture='false'
  61. x5-video-player-type="h5"
  62. x-webkit-airplay="allow"
  63. objectFit="contain"
  64. webkit-playsinline="true"
  65. :id="`video_${index}`"
  66. :src="item[videoKey]"
  67. :poster="item.poster"
  68. controls>
  69. <!-- #ifdef APP-PLUS -->
  70. <cover-view :style="{ transform: 'translateX(' + moveX + 'px)'}"></cover-view>
  71. <!-- #endif -->
  72. </video>
  73. </template>
  74. </slot>
  75. </view>
  76. <view
  77. class="swiper-fotter"
  78. :class="{'isFloat':fotterFloat}"
  79. @click="$emit('clickBottom',{item,index})">
  80. <slot :row='item' :index='index' name="fotter">
  81. <!-- 底部盒子 不传值则不占位 -->
  82. <view v-if="item[bottomTextKey]"
  83. :style="{'text-align':`${bottomTextAlign}`,
  84. 'color':`${bottomColor}`,
  85. 'background-color':`${bottomBackground}`}">
  86. <text v-text="item[bottomTextKey]"></text>
  87. </view>
  88. </slot>
  89. </view>
  90. </view>
  91. </swiper-item>
  92. </swiper>
  93. <!-- indicatorDots 原生指示器开启时不显示下面自定义指示器
  94. vertical为true 垂直方向 只写了支持右侧定位
  95. -->
  96. <view
  97. v-if="!indicatorDots"
  98. :style="{'margin':`0 ${effect3dMargin}`}"
  99. :class="['dot',vertical ? 'verticalDot':`${indicatorPos}`]"
  100. >
  101. <slot :list='list' :total='list.length' :activeIndex='activeCurrent' name="dot">
  102. <!-- 指示器自定义-返回列表数据list 数组长度total 选中项索引activeIndex -->
  103. <template v-if="mode==='number'">
  104. <view>
  105. {{activeCurrent+1}}/{{list.length}}
  106. </view>
  107. </template>
  108. <template v-else>
  109. <view
  110. v-for="(item,index) in list"
  111. :key="index"
  112. @click="activeCurrent=index"
  113. :style="{'background-color': activeCurrent==index ? indicatorActiveColor:indicatorColor}"
  114. :class="['dotItem',`${mode}`, `${ activeCurrent==index ? 'defautActive':'' }` ]">
  115. <span v-if="mode ==='index'">{{index+1}}</span>
  116. </view>
  117. </template>
  118. </slot>
  119. </view>
  120. </view>
  121. </template>
  122. <script>
  123. export default {
  124. name:'z-swiper',
  125. props: {
  126. list:{//滑块视图容器数据
  127. type:Array,
  128. default:_=>[
  129. {
  130. type:'video',
  131. topTip:'顶部提示',
  132. poster:'https://img2.baidu.com/it/u=2141851239,1037607188&fm=26&fmt=auto&gp=0.jpg',
  133. src:'https://bjetxgzv.cdn.bspapp.com/VKCEYUGU-uni-app-doc/a876efc0-4f35-11eb-97b7-0dc4655d6e68.mp4',
  134. bottomTip:'底部提示',
  135. },
  136. {type:'image', src:'https://img0.baidu.com/it/u=875130878,1196408569&fm=11&fmt=auto&gp=0.jpg'},
  137. {type:'image', src:'https://img2.baidu.com/it/u=293682470,1155349941&fm=26&fmt=auto&gp=0.jpg'},
  138. {type:'image', src:'https://img0.baidu.com/it/u=3464142916,229554071&fm=26&fmt=auto&gp=0.jpg'},
  139. {
  140. type:'video',
  141. currentTime:120,//初始帧时间---默认缓存存储
  142. poster:'https://img1.baidu.com/it/u=1297253752,1185196455&fm=26&fmt=auto&gp=0.jpg',
  143. src:'https://bjetxgzv.cdn.bspapp.com/VKCEYUGU-uni-app-doc/a876efc0-4f35-11eb-97b7-0dc4655d6e68.mp4',
  144. },
  145. {type:'image', src:'https://img1.baidu.com/it/u=2057763469,3313822915&fm=26&fmt=auto&gp=0.jpg'},
  146. {type:'image', src:'https://img0.baidu.com/it/u=1570602913,157918019&fm=26&fmt=auto&gp=0.jpg'},
  147. ]
  148. },
  149. videoKey:{ type:String, default:'src'},// 视频映射的键
  150. imageKey:{ type:String, default:'src'},//图片映射的键
  151. indicatorPos:{ type:String, default:'bottomCenter'},//指示器的位置:topLeft/topCenter/topRight/bottomLeft/bottomCenter/bottomRight
  152. mode:{ type:String, default:'round' },//指示器样式: default circle round index number none时不显示
  153. fullScreen:{ type:Boolean, default:false }, //是否全屏
  154. navHeight:{type:Number, default:44},//顶部导航高度,默认44---垂直全屏状态无导航栏可设置为0
  155. height:{ type:Number, default:160 },//swiper 高度单位px
  156. contentRadius:{ type:String, default:'0rpx' },//盒子圆角设置
  157. topFloat:{ type:Boolean, default:true },//顶部不占位-浮动定位
  158. fotterFloat:{ type:Boolean, default:true },//底部不占位-浮动定位
  159. effect3d:{ type:Boolean, default:false },//是否开启3D效果 注:只有在displayMultipleItems=1时有效
  160. effect3dMargin:{type: String, default: '40rpx'},//effect3d=true模式下前后间距接受px和rpx值
  161. imgMode:{type: String, default: 'scaleToFill'},//图片的裁剪模式,详见https://uniapp.dcloud.io/component/image
  162. bgColor:{type: String, default: '#f3f4f6'},//swiper背景色
  163. bgColorItem:{type: String, default: 'rgba(0,0,0,0)'},//swiper当前项背景色
  164. //顶部与底部设置-注:-顶部与底部根据需求自己拓展---也可用插槽自定义内容
  165. topTextKey:{ type:String, default:'topTip'},//顶部文字说明映射的键
  166. topColor:{ type:String, default:'#FFF'},//顶部文字颜色
  167. topBackground:{ type:String, default:'rgba(0, 0, 0, 0)'},//顶部背景色
  168. topTextAlign:{ type:String, default:'left'},//顶部文字位置
  169. bottomTextKey:{ type:String, default:'bottomTip'},//底部文字说明映射的键
  170. bottomColor:{ type:String, default:'#00F'},//底部文字颜色
  171. bottomBackground:{ type:String, default:'rgba(0, 0, 0, 0)'},//底部背景色
  172. bottomTextAlign:{ type:String, default:'left'},//底部文字位置
  173. //---end
  174. // ---swiper原生属性-参考https://uniapp.dcloud.io/component/swiper
  175. skipHiddenItemLayout:{ //是否跳过未显示的滑块布局,设为 true 可优化复杂情况下的滑动性能,但会丢失隐藏状态滑块的布局信息
  176. type:Boolean,
  177. default:false
  178. },
  179. displayMultipleItems:{ type:Number,default:1 },//同时显示的滑块数量
  180. nextMargin:{ // 后边距,可用于露出后一项的一小部分,接受 px 和 rpx 值 头条小程序不支持
  181. type:String,
  182. default:'0rpx'
  183. },
  184. previousMargin:{//前边距,可用于露出前一项的一小部分,接受 px 和 rpx 值头条小程序不支持
  185. type:String,
  186. default:'0rpx'
  187. },
  188. vertical:{ type:Boolean, default:false },//滑动方向是否为纵向 卡牌不支持纵向以及同时显示的2块以上滑块数量
  189. circular:{ type:Boolean, default:true },//是否采用衔接滑动
  190. duration:{ type:Number, default:400 },// 滑动动画时长
  191. interval:{ type:Number, default:2500 },// 自动切换时间间隔
  192. current:{ type:Number, default:0 },// 初始化时,默认显示第几项
  193. autoplay:{ type:Boolean, default:true },// 是否自动切换
  194. indicatorDots: { type: Boolean, default: false },//是否显示面板指示点--默认关闭使用自定义指示器mode设置指示器,原生指示器为true时 则不显示自定义指示器
  195. indicatorColor:{ type:String, default:'rgba(0,0,0,0.3)' },// 指示点颜色
  196. indicatorActiveColor: { type: String, default: '#F1F1F1' },// 选中项指示点颜色
  197. },
  198. data() {
  199. return {
  200. swiperList:[],//列表数据
  201. videoContent:'',//视频实例
  202. videoPlaySataus:false, //视频播放状态---默认禁用
  203. activeCurrent:0,//当前选中索引
  204. swiperHeight:0, //轮播图高度
  205. moveX:0,
  206. }
  207. },
  208. watch: {
  209. height:{//swiper高度
  210. handler(newValue) {
  211. this.swiperHeight = newValue
  212. },
  213. immediate:true
  214. },
  215. current:{//初始化选中项
  216. handler(newValue) {
  217. this.activeCurrent = newValue
  218. },
  219. immediate:true
  220. },
  221. list:{//初始化数据列表--- 处理vue不能直接改变prpos属性
  222. handler(newValue) {
  223. this.swiperList = newValue || []
  224. },
  225. immediate:true
  226. },
  227. fullScreen:{
  228. handler(newValue) {
  229. if(this.fullScreen){//全屏设置---默认初始化设置一次
  230. uni.getSystemInfo({
  231. success:(e)=>{
  232. console.log('e',e);
  233. this.swiperHeight = e.screenHeight - this.navHeight
  234. // #ifdef APP-PLUS || MP-WEIXIN
  235. this.swiperHeight = e.screenHeight - this.navHeight - e.statusBarHeight
  236. // #endif
  237. }
  238. })
  239. }else{
  240. this.swiperHeight = this.height
  241. }
  242. },
  243. immediate:true
  244. }
  245. },
  246. methods: {
  247. play(e){
  248. this.videoPlaySataus = true
  249. },
  250. pause(){
  251. this.videoPlaySataus = false
  252. },
  253. timeupdate(e){//播放进度变化时触发--更新播放缓存
  254. this.$set(this.swiperList[this.activeCurrent],'currentTime',e.detail.currentTime)
  255. },
  256. clickItem(index){
  257. if(this.list.length>0){
  258. this.$emit('clickItem',this.list[index])
  259. }
  260. },
  261. change(e){//轮播改变触发
  262. try{// 切换前暂停之前视频
  263. let preSwiper = this.swiperList[this.activeCurrent]
  264. if(preSwiper.type==='video'){
  265. uni.createVideoContext(`video_${this.activeCurrent}`,this).pause();
  266. }
  267. }catch(e){
  268. //TODO handle the exception
  269. }
  270. this.videoPlaySataus = false //自动切换关闭视频播放状态
  271. this.activeCurrent = e.detail.current;
  272. this.$emit('change',e)
  273. },
  274. animationfinish(e){//动画结束后调用
  275. this.moveX = 0
  276. this.$emit('animationfinish',e)
  277. },
  278. transition(e){//滑动
  279. // #ifdef APP-PLUS
  280. this.moveX = e.detail.dx
  281. // #endif
  282. }
  283. }
  284. }
  285. </script>
  286. <style lang="scss" scoped>
  287. .swiperContent{//容器
  288. width:100%;
  289. position: relative;
  290. // background-color: #ccc;
  291. .z-screen-swiper {//轮播图
  292. min-height: 320rpx;
  293. // background-color: rgb(211, 235, 107); //--调试样式
  294. box-sizing: border-box;
  295. .z-swiper-item{
  296. box-sizing: border-box;
  297. overflow: initial;
  298. .swiper-box{//轮播图内容
  299. // background-color: #e7ca8f;//--调试样式
  300. // background-color: rgba(0,0,0,0.1);//swiper当前项 背景色---已改为配置
  301. box-sizing: border-box;
  302. width: 100%;
  303. height: 100%;
  304. overflow: hidden;
  305. display: flex;
  306. flex-direction: column;
  307. justify-content: space-between;
  308. position: relative;
  309. color: #FFF;
  310. .swiper-top{
  311. top: 0;
  312. // background-color: rgba(0,0,0,0.2);//背景色---已改为配置
  313. }
  314. .swiper-content{
  315. // background-color: rgb(61, 41, 175);
  316. flex: 1;
  317. display: flex;
  318. flex-direction: column;
  319. justify-content: center;
  320. image{
  321. width:100%;
  322. height: 100%;
  323. max-height: 100%;
  324. }
  325. video {// 视频默认不全屏高度---防止全屏swiper滑动切换
  326. width: 100%;
  327. max-height: 100%;
  328. // pointer-events: auto;
  329. }
  330. }
  331. .swiper-fotter{
  332. bottom: 0;
  333. // background-color: rgba(0, 0, 0, 0.2); //背景色---已改为配置
  334. }
  335. .isFloat{//是否浮动 顶部、底部定位
  336. position: absolute;
  337. left: 0;
  338. right: 0;
  339. z-index: 999;
  340. }
  341. }
  342. }
  343. }
  344. .effect3D{//3d模式样式
  345. .z-swiper-item{//3d模式基础样式
  346. .swiper-box{
  347. border-radius: 10rpx;
  348. opacity: 0.7;
  349. transition: all 0.1s ease-in 0s;
  350. }
  351. }
  352. &.effect3D-X{
  353. .z-swiper-item{ //选项卡间隔
  354. padding: 0 10rpx;
  355. }
  356. .swiper-box{
  357. transform: scale(1,0.9);
  358. }
  359. }
  360. &.effect3D-Y{
  361. .z-swiper-item{ //选项卡间隔
  362. padding:10rpx 0;
  363. }
  364. .swiper-box{
  365. transform: scale(0.9,1);
  366. }
  367. }
  368. .active-swiper{//选中样式恢复
  369. .swiper-box{
  370. transform:initial;
  371. opacity: 1;
  372. transition: all 0.1s ease-in 0s;
  373. }
  374. }
  375. }
  376. .dot{//指示器
  377. position: absolute;
  378. // z-index: 9999;
  379. display: flex;
  380. color: #FFF;
  381. .dotItem{//指示器 颜色与形状
  382. background-color: #fff;
  383. font-size: 24rpx;
  384. color: #e2e2e2;
  385. margin-right: 10rpx;
  386. &.default{ /*默认条状 */
  387. height: 8rpx;
  388. width: 40rpx;
  389. }
  390. &.circle{ /* 圆 */
  391. height: 20rpx;
  392. width: 20rpx;
  393. border-radius: 50%;
  394. }
  395. &.index{ /* 数字索引 */
  396. height: 30rpx;
  397. width: 30rpx;
  398. display: flex;
  399. justify-content: center;
  400. align-content: center;
  401. border-radius: 50%;
  402. }
  403. &.defautActive{//选中项设置
  404. transition: background-color 0.3s ease-out 0s; //选中动画
  405. }
  406. &.round{ /* 弧形 */
  407. height: 20rpx;
  408. width: 20rpx;
  409. border-radius: 50%;
  410. &.defautActive{//弧形选中项设置
  411. width: 60rpx;
  412. height: 20rpx;
  413. border-radius: 10px;
  414. transition: background-color 0.3s ease-out 0s; //
  415. }
  416. }
  417. }
  418. // 定位位置
  419. &.verticalDot{//垂直方向 只写了支持右侧定位
  420. right: 20rpx;
  421. top: 50%;
  422. transform: translateY(-50%);
  423. display: block;
  424. margin: 0 !important;
  425. .dotItem{
  426. margin: 0;
  427. margin-bottom: 10rpx;
  428. &.round{ /* 弧形 */
  429. height: 20rpx;
  430. width: 20rpx;
  431. border-radius: 50%;
  432. &.defautActive{//弧形选中
  433. width: 20rpx;
  434. height: 60rpx;
  435. border-radius: 10px;
  436. }
  437. }
  438. }
  439. }
  440. &.bottomLeft{//左上角
  441. left: 20rpx;
  442. bottom: 20rpx;
  443. }
  444. &.bottomCenter{//
  445. left: 50%;
  446. bottom: 20rpx;
  447. transform: translateX(-50%);
  448. }
  449. &.bottomRight{
  450. right: 20rpx;
  451. bottom: 20rpx;
  452. }
  453. &.topLeft{
  454. left: 20rpx;
  455. top: 10rpx;
  456. }
  457. &.topCenter{
  458. left: 50%;
  459. top: 10rpx;
  460. transform: translateX(-50%);
  461. }
  462. &.topRight{
  463. right: 20rpx;
  464. top: 10rpx;
  465. }
  466. }
  467. }
  468. </style>