369 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Vue
		
	
	
			
		
		
	
	
			369 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Vue
		
	
	
| <template>
 | |
|   <!-- 包裹层 -->
 | |
|   <view
 | |
|     class="ui-swiper"
 | |
|     :class="[props.mode, props.bg, props.ui]"
 | |
|     :style="[{ height: swiperHeight + (menuList.length > 1 ? 50 : 0) + 'rpx' }]"
 | |
|   >
 | |
|     <!-- 轮播 -->
 | |
|     <swiper
 | |
|       :circular="props.circular"
 | |
|       :current="state.cur"
 | |
|       :autoplay="props.autoplay"
 | |
|       :interval="props.interval"
 | |
|       :duration="props.duration"
 | |
|       :style="[{ height: swiperHeight + 'rpx' }]"
 | |
|       @change="swiperChange"
 | |
|     >
 | |
|       <swiper-item
 | |
|         v-for="(arr, index) in menuList"
 | |
|         :key="index"
 | |
|         :class="{ cur: state.cur == index }"
 | |
|       >
 | |
|         <!-- 宫格 -->
 | |
|         <view class="grid-wrap" :col="data.rowNum">
 | |
|           <view
 | |
|             v-for="(item, index) in arr"
 | |
|             :key="index"
 | |
|             class="grid-item ss-flex ss-flex-col ss-col-center ss-row-center"
 | |
|             :style="[{ width: clWidth + 'px', height: '200rpx' }]"
 | |
|             hover-class="ss-hover-btn"
 | |
|             @tap="sheep.$router.go(item.url)"
 | |
|           >
 | |
|             <view class="menu-box ss-flex ss-flex-col ss-col-center ss-row-center">
 | |
|               <view
 | |
|                 v-if="item.badge.show"
 | |
|                 class="tag-box"
 | |
|                 :style="[{ background: item.badge.bgColor, color: item.badge.color }]"
 | |
|               >
 | |
|                 {{ item.badge.text }}
 | |
|               </view>
 | |
|               <image
 | |
|                 v-if="item.src"
 | |
|                 class="menu-icon"
 | |
|                 :style="[
 | |
|                   {
 | |
|                     width: props.iconSize + 'rpx',
 | |
|                     height: props.iconSize + 'rpx',
 | |
|                   },
 | |
|                 ]"
 | |
|                 :src="sheep.$url.cdn(item.src)"
 | |
|                 mode="aspectFill"
 | |
|               ></image>
 | |
|               <view
 | |
|                 v-if="data.layout == 1"
 | |
|                 class="menu-title"
 | |
|                 :style="[{ color: item.title.color }]"
 | |
|               >
 | |
|                 {{ item.title.text }}
 | |
|               </view>
 | |
|             </view>
 | |
|           </view>
 | |
|         </view>
 | |
|       </swiper-item>
 | |
|     </swiper>
 | |
|     <!-- 指示点 -->
 | |
|     <template v-if="menuList.length > 1">
 | |
|       <view class="ui-swiper-dot" :class="props.dotStyle" v-if="props.dotStyle != 'tag'">
 | |
|         <view
 | |
|           class="line-box"
 | |
|           v-for="(item, index) in menuList.length"
 | |
|           :key="index"
 | |
|           :class="[state.cur == index ? 'cur' : '', props.dotCur]"
 | |
|         ></view>
 | |
|       </view>
 | |
|       <view class="ui-swiper-dot" :class="props.dotStyle" v-if="props.dotStyle == 'tag'">
 | |
|         <view class="ui-tag radius" :class="[props.dotCur]" style="pointer-events: none">
 | |
|           <view style="transform: scale(0.7)">{{ state.cur + 1 }} / {{ menuList.length }}</view>
 | |
|         </view>
 | |
|       </view>
 | |
|     </template>
 | |
|   </view>
 | |
| </template>
 | |
| 
 | |
| <script setup>
 | |
|   /**
 | |
|    * 轮播menu
 | |
|    *
 | |
|    * @property {Boolean} circular = false  		- 是否采用衔接滑动,即播放到末尾后重新回到开头
 | |
|    * @property {Boolean} autoplay = true  		- 是否自动切换
 | |
|    * @property {Number} interval = 5000  			- 自动切换时间间隔
 | |
|    * @property {Number} duration = 500  			- 滑动动画时长,app-nvue不支持
 | |
|    * @property {Array} list = [] 					- 轮播数据
 | |
|    * @property {String} ui = ''  					- 样式class
 | |
|    * @property {String} mode  					- 模式
 | |
|    * @property {String} dotStyle  				- 指示点样式
 | |
|    * @property {String} dotCur= 'ui-BG-Main' 		- 当前指示点样式,默认主题色
 | |
|    * @property {String} bg  						- 背景
 | |
|    *
 | |
|    * @property {String|Number} col = 4  			- 一行数量
 | |
|    *  @property {String|Number} row = 1 			- 几行
 | |
|    * @property {String} hasBorder 				- 是否有边框
 | |
|    * @property {String} borderColor 				- 边框颜色
 | |
|    * @property {String} background		  		- 背景
 | |
|    * @property {String} hoverClass 				- 按压样式类
 | |
|    * @property {String} hoverStayTime 		  	- 动画时间
 | |
|    *
 | |
|    * @property {Array} list 		  				- 导航列表
 | |
|    * @property {Number} iconSize 		  			- 图标大小
 | |
|    * @property {String} color 		  			- 标题颜色
 | |
|    *
 | |
|    */
 | |
| 
 | |
|   import { reactive, computed } from 'vue';
 | |
|   import sheep from '@/sheep';
 | |
| 
 | |
|   // 数据
 | |
|   const state = reactive({
 | |
|     cur: 0,
 | |
|   });
 | |
| 
 | |
|   // 接收参数
 | |
| 
 | |
|   const props = defineProps({
 | |
|     data: {
 | |
|       type: Object,
 | |
|       default() {},
 | |
|     },
 | |
|     styles: {
 | |
|       type: Object,
 | |
|       default() {},
 | |
|     },
 | |
|     circular: {
 | |
|       type: Boolean,
 | |
|       default: true,
 | |
|     },
 | |
|     autoplay: {
 | |
|       type: Boolean,
 | |
|       default: false,
 | |
|     },
 | |
|     interval: {
 | |
|       type: Number,
 | |
|       default: 5000,
 | |
|     },
 | |
|     duration: {
 | |
|       type: Number,
 | |
|       default: 500,
 | |
|     },
 | |
| 
 | |
|     ui: {
 | |
|       type: String,
 | |
|       default: '',
 | |
|     },
 | |
|     mode: {
 | |
|       //default
 | |
|       type: String,
 | |
|       default: 'default',
 | |
|     },
 | |
|     dotStyle: {
 | |
|       type: String,
 | |
|       default: 'long', //default long tag
 | |
|     },
 | |
|     dotCur: {
 | |
|       type: String,
 | |
|       default: 'ui-BG-Main',
 | |
|     },
 | |
|     bg: {
 | |
|       type: String,
 | |
|       default: 'bg-none',
 | |
|     },
 | |
|     height: {
 | |
|       type: Number,
 | |
|       default: 300,
 | |
|     },
 | |
| 
 | |
|     // 是否有边框
 | |
|     hasBorder: {
 | |
|       type: Boolean,
 | |
|       default: true,
 | |
|     },
 | |
|     // 边框颜色
 | |
|     borderColor: {
 | |
|       type: String,
 | |
|       default: 'red',
 | |
|     },
 | |
|     background: {
 | |
|       type: String,
 | |
|       default: 'blue',
 | |
|     },
 | |
|     hoverClass: {
 | |
|       type: String,
 | |
|       default: 'ss-hover-class', //'none'为没有hover效果
 | |
|     },
 | |
|     // 一排宫格数
 | |
|     col: {
 | |
|       type: [Number, String],
 | |
|       default: 3,
 | |
|     },
 | |
|     iconSize: {
 | |
|       type: Number,
 | |
|       default: 96,
 | |
|     },
 | |
|     color: {
 | |
|       type: String,
 | |
|       default: '#000',
 | |
|     },
 | |
|   });
 | |
| 
 | |
|   // 生成数据
 | |
|   const menuList = computed(() => splitData(props.data.list, props.data.row * props.data.col));
 | |
|   const swiperHeight = computed(() => props.data.row * (props.data.layout == 1 ? 200 : 180));
 | |
|   const windowWidth = sheep.$platform.device.windowWidth;
 | |
|   const clWidth = computed(
 | |
|     () =>
 | |
|       (windowWidth -
 | |
|         (props.styles.marginLeft + props.styles.marginRight + props.styles.padding * 2)) /
 | |
|       props.data.col,
 | |
|   );
 | |
| 
 | |
|   // current 改变时会触发 change 事件
 | |
|   const swiperChange = (e) => {
 | |
|     state.cur = e.detail.current;
 | |
|   };
 | |
| 
 | |
|   // 重组数据
 | |
|   const splitData = (oArr = [], length = 1) => {
 | |
|     let arr = [];
 | |
|     let minArr = [];
 | |
|     oArr.forEach((c) => {
 | |
|       if (minArr.length === length) {
 | |
|         minArr = [];
 | |
|       }
 | |
|       if (minArr.length === 0) {
 | |
|         arr.push(minArr);
 | |
|       }
 | |
|       minArr.push(c);
 | |
|     });
 | |
| 
 | |
|     return arr;
 | |
|   };
 | |
| </script>
 | |
| 
 | |
| <style lang="scss" scoped>
 | |
|   .grid-wrap {
 | |
|     width: 100%;
 | |
|     display: flex;
 | |
|     position: relative;
 | |
|     box-sizing: border-box;
 | |
|     overflow: hidden;
 | |
|     flex-wrap: wrap;
 | |
|     align-items: center;
 | |
|   }
 | |
|   .menu-box {
 | |
|     position: relative;
 | |
|     z-index: 1;
 | |
|     transform: translate(0, 0);
 | |
| 
 | |
|     .tag-box {
 | |
|       position: absolute;
 | |
|       z-index: 2;
 | |
|       top: 0;
 | |
|       right: -6rpx;
 | |
|       font-size: 2em;
 | |
|       line-height: 1;
 | |
|       padding: 0.4em 0.6em 0.3em;
 | |
|       transform: scale(0.4) translateX(0.5em) translatey(-0.6em);
 | |
|       transform-origin: 100% 0;
 | |
|       border-radius: 200rpx;
 | |
|       white-space: nowrap;
 | |
|     }
 | |
| 
 | |
|     .menu-icon {
 | |
|       transform: translate(0, 0);
 | |
|       width: 98rpx;
 | |
|       height: 98rpx;
 | |
|       padding-bottom: 10rpx;
 | |
|     }
 | |
| 
 | |
|     .menu-title {
 | |
|       font-size: 30rpx;
 | |
|       color: #333;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   ::v-deep(.ui-swiper) {
 | |
|     position: relative;
 | |
|     z-index: 1;
 | |
| 
 | |
|     .ui-swiper-dot {
 | |
|       position: absolute;
 | |
|       width: 100%;
 | |
|       bottom: 20rpx;
 | |
|       height: 30rpx;
 | |
|       display: flex;
 | |
|       align-items: center;
 | |
|       justify-content: center;
 | |
|       z-index: 2;
 | |
| 
 | |
|       &.default .line-box {
 | |
|         display: inline-flex;
 | |
|         border-radius: 50rpx;
 | |
|         width: 6px;
 | |
|         height: 6px;
 | |
|         border: 2px solid transparent;
 | |
|         margin: 0 10rpx;
 | |
|         opacity: 0.3;
 | |
|         position: relative;
 | |
|         justify-content: center;
 | |
|         align-items: center;
 | |
| 
 | |
|         &.cur {
 | |
|           width: 8px;
 | |
|           height: 8px;
 | |
|           opacity: 1;
 | |
|           border: 0px solid transparent;
 | |
|         }
 | |
| 
 | |
|         &.cur::after {
 | |
|           content: '';
 | |
|           border-radius: 50rpx;
 | |
|           width: 4px;
 | |
|           height: 4px;
 | |
|           background-color: #fff;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       &.long .line-box {
 | |
|         display: inline-block;
 | |
|         border-radius: 100rpx;
 | |
|         width: 6px;
 | |
|         height: 6px;
 | |
|         margin: 0 10rpx;
 | |
|         opacity: 0.3;
 | |
|         position: relative;
 | |
| 
 | |
|         &.cur {
 | |
|           width: 24rpx;
 | |
|           opacity: 1;
 | |
|         }
 | |
| 
 | |
|         &.cur::after {
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       &.line {
 | |
|         bottom: 20rpx;
 | |
| 
 | |
|         .line-box {
 | |
|           display: inline-block;
 | |
|           width: 30px;
 | |
|           height: 3px;
 | |
|           opacity: 0.3;
 | |
|           position: relative;
 | |
| 
 | |
|           &.cur {
 | |
|             opacity: 1;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       &.tag {
 | |
|         justify-content: flex-end;
 | |
|         position: absolute;
 | |
|         bottom: 20rpx;
 | |
|         right: 20rpx;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| </style>
 |