475 lines
		
	
	
		
			9.8 KiB
		
	
	
	
		
			Vue
		
	
	
			
		
		
	
	
			475 lines
		
	
	
		
			9.8 KiB
		
	
	
	
		
			Vue
		
	
	
| <template>
 | |
|   <view
 | |
|     class="ui-tab"
 | |
|     ref="tabRef"
 | |
|     :id="'tab-' + vm.uid"
 | |
|     :class="[
 | |
|       props.ui,
 | |
|       props.tpl,
 | |
|       props.bg,
 | |
|       props.align,
 | |
|       { 'ui-tab-inline': props.inline },
 | |
|       { 'ui-tab-scrolls': props.scroll },
 | |
|     ]"
 | |
|   >
 | |
|     <block v-if="scroll">
 | |
|       <view class="ui-tab-scroll-warp">
 | |
|         <scroll-view
 | |
|           scroll-x="true"
 | |
|           class="ui-tab-scroll"
 | |
|           :scroll-left="state.curValue > 1 ? state.tabNodeList[state.curValue - 1].left : 0"
 | |
|           scroll-with-animation
 | |
|           :style="{ width: `${state.content.width}px` }"
 | |
|         >
 | |
|           <view class="ss-flex ss-col-center">
 | |
|             <su-tab-item
 | |
|               v-for="(item, index) in props.tab"
 | |
|               :data="item"
 | |
|               :index="index"
 | |
|               :key="index"
 | |
|               @up="upitem"
 | |
|               @tap.native="click(index, item)"
 | |
|             ></su-tab-item>
 | |
|             <view
 | |
|               class="ui-tab-mark-warp"
 | |
|               :class="[{ over: state.over }]"
 | |
|               :style="[{ left: state.markLeft + 'px' }, { width: state.markWidth + 'px' }]"
 | |
|             >
 | |
|               <view
 | |
|                 class="ui-tab-mark"
 | |
|                 :class="[props.mark, { 'ui-btn': props.tpl == 'btn' || props.tpl == 'subtitle' }]"
 | |
|                 :style="[
 | |
|                   {
 | |
|                     background:
 | |
|                       props.tpl == 'btn' || props.tpl == 'subtitle' ? titleStyle.activeBg : 'none',
 | |
|                   },
 | |
|                 ]"
 | |
|               ></view>
 | |
|             </view>
 | |
|           </view>
 | |
|         </scroll-view>
 | |
|       </view>
 | |
|     </block>
 | |
|     <block v-else>
 | |
|       <su-tab-item
 | |
|         v-for="(item, index) in props.tab"
 | |
|         :data="item"
 | |
|         :index="index"
 | |
|         :key="index"
 | |
|         @up="upitem"
 | |
|         @tap.native="click(index, item)"
 | |
|       ></su-tab-item>
 | |
|       <view
 | |
|         class="ui-tab-mark-warp"
 | |
|         :class="[{ over: state.over }]"
 | |
|         :style="[{ left: state.markLeft + 'px' }, { width: state.markWidth + 'px' }]"
 | |
|       >
 | |
|         <view
 | |
|           class="ui-tab-mark"
 | |
|           :class="[props.mark, { 'ui-btn': props.tpl == 'btn' || props.tpl == 'subtitle' }]"
 | |
|         ></view>
 | |
|       </view>
 | |
|     </block>
 | |
|   </view>
 | |
| </template>
 | |
| 
 | |
| <script>
 | |
|   export default {
 | |
|     name: 'SuTab',
 | |
|   };
 | |
| </script>
 | |
| 
 | |
| <script setup>
 | |
|   /**
 | |
|    * 基础组件 - suTab
 | |
|    */
 | |
| 
 | |
|   import {
 | |
|     toRef,
 | |
|     ref,
 | |
|     reactive,
 | |
|     unref,
 | |
|     onMounted,
 | |
|     nextTick,
 | |
|     getCurrentInstance,
 | |
|     provide,
 | |
|   } from 'vue';
 | |
|   const vm = getCurrentInstance();
 | |
| 
 | |
|   // 数据
 | |
|   const state = reactive({
 | |
|     curValue: 0,
 | |
|     tabNodeList: [],
 | |
|     scrollLeft: 0,
 | |
|     markLeft: 0,
 | |
|     markWidth: 0,
 | |
|     content: {
 | |
|       width: 100,
 | |
|     },
 | |
|     over: false,
 | |
|   });
 | |
| 
 | |
|   const tabRef = ref(null);
 | |
|   // 参数
 | |
|   const props = defineProps({
 | |
|     modelValue: {
 | |
|       type: Number,
 | |
|       default: 0,
 | |
|     },
 | |
|     ui: {
 | |
|       type: String,
 | |
|       default: '',
 | |
|     },
 | |
|     bg: {
 | |
|       type: String,
 | |
|       default: '',
 | |
|     },
 | |
|     tab: {
 | |
|       type: Array,
 | |
|       default() {
 | |
|         return [];
 | |
|       },
 | |
|     },
 | |
|     // line dot long,subtitle,trapezoid
 | |
|     tpl: {
 | |
|       type: String,
 | |
|       default: 'line',
 | |
|     },
 | |
|     mark: {
 | |
|       type: String,
 | |
|       default: '',
 | |
|     },
 | |
|     align: {
 | |
|       type: String,
 | |
|       default: '',
 | |
|     },
 | |
|     curColor: {
 | |
|       type: String,
 | |
|       default: 'ui-TC',
 | |
|     },
 | |
|     defaultColor: {
 | |
|       type: String,
 | |
|       default: 'ui-TC',
 | |
|     },
 | |
|     scroll: {
 | |
|       type: Boolean,
 | |
|       default: false,
 | |
|     },
 | |
|     inline: {
 | |
|       type: Boolean,
 | |
|       default: false,
 | |
|     },
 | |
|     titleStyle: {
 | |
|       type: Object,
 | |
|       default: () => ({
 | |
|         activeBg: '#DA2B10',
 | |
|         activeColor: '#FEFEFE',
 | |
|         color: '#D70000',
 | |
|       }),
 | |
|     },
 | |
|     subtitleStyle: {
 | |
|       type: Object,
 | |
|       default: () => ({
 | |
|         activeColor: '#333',
 | |
|         color: '#C42222',
 | |
|       }),
 | |
|     },
 | |
|   });
 | |
| 
 | |
|   const emits = defineEmits(['update:modelValue', 'change']);
 | |
| 
 | |
|   onMounted(() => {
 | |
|     state.curValue = props.modelValue;
 | |
|     setCurValue(props.modelValue);
 | |
|     nextTick(() => {
 | |
|       computedQuery();
 | |
|     });
 | |
|     uni.onWindowResize((res) => {
 | |
|       computedQuery();
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   const computedQuery = () => {
 | |
|     uni.createSelectorQuery()
 | |
|       .in(vm)
 | |
|       .select('#tab-' + vm.uid)
 | |
|       .boundingClientRect((data) => {
 | |
|         if (data != null) {
 | |
|           if (data.left == 0 && data.right == 0) {
 | |
|             // setTimeout(() => {
 | |
|             computedQuery();
 | |
|             // }, 300);
 | |
|           } else {
 | |
|             state.content = data;
 | |
|             setTimeout(() => {
 | |
|               state.over = true;
 | |
|             }, 300);
 | |
|           }
 | |
|         } else {
 | |
|           console.log('tab-' + vm.uid + ' data error');
 | |
|         }
 | |
|       })
 | |
|       .exec();
 | |
|   };
 | |
| 
 | |
|   const setCurValue = (value) => {
 | |
|     if (value == state.curValue) return;
 | |
|     state.curValue = value;
 | |
|     computedMark();
 | |
|   };
 | |
| 
 | |
|   const click = (index, item) => {
 | |
|     setCurValue(index);
 | |
|     emits('update:modelValue', index);
 | |
|     emits('change', {
 | |
|       index: index,
 | |
|       data: item,
 | |
|     });
 | |
|   };
 | |
| 
 | |
|   const upitem = (index, e) => {
 | |
|     state.tabNodeList[index] = e;
 | |
|     if (index == state.curValue) {
 | |
|       computedMark();
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   const computedMark = () => {
 | |
|     if (state.tabNodeList.length == 0) return;
 | |
|     let left = 0;
 | |
|     let list = unref(state.tabNodeList);
 | |
|     let cur = state.curValue;
 | |
|     state.markLeft = list[cur].left - state.content.left;
 | |
|     state.markWidth = list[cur].width;
 | |
|   };
 | |
| 
 | |
|   const computedScroll = () => {
 | |
|     if (state.curValue == 0 || state.curValue == state.tabNodeList.length - 1) {
 | |
|       return false;
 | |
|     }
 | |
|     let i = 0;
 | |
|     let left = 0;
 | |
|     let list = state.tabNodeList;
 | |
|     for (i in list) {
 | |
|       if (i == state.curValue && i != 0) {
 | |
|         left = left - list[i - 1].width;
 | |
|         break;
 | |
|       }
 | |
|       left = left + list[i].width;
 | |
|     }
 | |
|     state.scrollLeft = left;
 | |
|   };
 | |
| 
 | |
|   provide('suTabProvide', {
 | |
|     props,
 | |
|     curValue: toRef(state, 'curValue'),
 | |
|   });
 | |
| </script>
 | |
| 
 | |
| <style lang="scss">
 | |
|   .ui-tab {
 | |
|     position: relative;
 | |
|     display: flex;
 | |
|     height: 4em;
 | |
|     align-items: center;
 | |
| 
 | |
|     &.ui-tab-scrolls {
 | |
|       width: 100%;
 | |
|       /* #ifdef MP-WEIXIN */
 | |
|       padding-bottom: 10px;
 | |
|       /* #endif */
 | |
|       .ui-tab-scroll-warp {
 | |
|         overflow: hidden;
 | |
|         height: inherit;
 | |
|         width: 100%;
 | |
|         .ui-tab-scroll {
 | |
|           position: relative;
 | |
|           display: block;
 | |
|           white-space: nowrap;
 | |
|           overflow: auto;
 | |
|           min-height: 4em;
 | |
|           line-height: 4em;
 | |
|           width: 100% !important;
 | |
|           .ui-tab-mark-warp {
 | |
|             display: flex;
 | |
|             align-items: top;
 | |
|             justify-content: center;
 | |
|             .ui-tab-mark.ui-btn {
 | |
|               /* #ifndef MP-WEIXIN */
 | |
|               height: 2em;
 | |
|               width: calc(100% - 0.6em);
 | |
|               margin-top: 4px;
 | |
|               /* #endif */
 | |
|               /* #ifdef MP-WEIXIN */
 | |
|               height: 2em;
 | |
|               width: calc(100% - 0.6em);
 | |
|               margin-top: 4px;
 | |
|               /* #endif */
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     .ui-tab-mark-warp {
 | |
|       color: inherit;
 | |
|       position: absolute;
 | |
|       top: 0;
 | |
|       height: 100%;
 | |
|       z-index: 0;
 | |
| 
 | |
|       &.over {
 | |
|         transition: 0.3s;
 | |
|       }
 | |
| 
 | |
|       .ui-tab-mark {
 | |
|         color: var(--ui-BG-Main);
 | |
|         height: 100%;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     &.line {
 | |
|       .ui-tab-mark {
 | |
|         border-bottom: 2px solid currentColor;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     &.topline {
 | |
|       .ui-tab-mark {
 | |
|         border-top: 2px solid currentColor;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     &.dot {
 | |
|       .ui-tab-mark::after {
 | |
|         content: '';
 | |
|         width: 0.5em;
 | |
|         height: 0.5em;
 | |
|         background-color: currentColor;
 | |
|         border-radius: 50%;
 | |
|         display: block;
 | |
|         position: absolute;
 | |
|         bottom: 0.3em;
 | |
|         left: 0;
 | |
|         right: 0;
 | |
|         margin: auto;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     &.long {
 | |
|       .ui-tab-mark::after {
 | |
|         content: '';
 | |
|         width: 2em;
 | |
|         height: 0.35em;
 | |
|         background-color: currentColor;
 | |
|         border-radius: 5em;
 | |
|         display: block;
 | |
|         position: absolute;
 | |
|         bottom: 0.3em;
 | |
|         left: 0;
 | |
|         right: 0;
 | |
|         margin: auto;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     &.trapezoid {
 | |
|       .ui-tab-mark::after {
 | |
|         content: '';
 | |
|         width: calc(100% - 2em);
 | |
|         height: 0.35em;
 | |
|         background-color: currentColor;
 | |
|         border-radius: 5em 5em 0 0;
 | |
|         display: block;
 | |
|         position: absolute;
 | |
|         bottom: 0;
 | |
|         left: 0;
 | |
|         right: 0;
 | |
|         margin: auto;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     &.btn {
 | |
|       .ui-tab-mark-warp {
 | |
|         display: flex;
 | |
|         align-items: center;
 | |
|         justify-content: center;
 | |
| 
 | |
|         .ui-tab-mark.ui-btn {
 | |
|           height: calc(100% - 1.6em);
 | |
|           width: calc(100% - 0.6em);
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       &.sm .ui-tab-mark.ui-btn {
 | |
|         height: calc(100% - 2px);
 | |
|         width: calc(100% - 2px);
 | |
|         border-radius: #{$radius - 2};
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     &.subtitle {
 | |
|       .ui-tab-mark-warp {
 | |
|         display: flex;
 | |
|         align-items: top;
 | |
|         justify-content: center;
 | |
|         padding-top: 0.6em;
 | |
| 
 | |
|         .ui-tab-mark.ui-btn {
 | |
|           height: calc(100% - 2.8em);
 | |
|           width: calc(100% - 0.6em);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     &.ui-tab-inline {
 | |
|       display: inline-flex;
 | |
|       height: 3.5em;
 | |
| 
 | |
|       &.ui-tab-scrolls {
 | |
|         .ui-tab-scroll {
 | |
|           height: calc(3.5em + 17px);
 | |
|           line-height: 3.5em;
 | |
| 
 | |
|           .ui-tab-mark-warp {
 | |
|             height: 3.5em;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       &.btn {
 | |
|         .ui-tab-mark-warp {
 | |
|           .ui-tab-mark.ui-btn {
 | |
|             height: calc(100% - 10px);
 | |
|             width: calc(100% - 10px);
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     &.sm {
 | |
|       height: 70rpx !important;
 | |
| 
 | |
|       &.ui-tab-inline {
 | |
|         height: 70rpx;
 | |
| 
 | |
|         &.ui-tab-scrolls {
 | |
|           .ui-tab-scroll {
 | |
|             height: calc(70rpx + 17px);
 | |
|             line-height: 70rpx;
 | |
| 
 | |
|             .ui-tab-mark-warp {
 | |
|               height: 70rpx;
 | |
|             }
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         &.btn .ui-tab-mark.ui-btn {
 | |
|           height: calc(100% - 2px);
 | |
|           width: calc(100% - 2px);
 | |
|           border-radius: #{$radius - 2};
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| </style>
 |