| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 |
- <template>
- <template v-if="mode == 'scroll-x'">
- <scroll-view scroll-x class="ut-tabs-scroll" show-scrollbar="false" :scroll-into-view-offset="SCROLL_OFFSET"
- :scroll-into-view="scrollIntoView" ref="scrollViewRef" scroll-with-animation>
- <view class="ut-tabs-row">
- <view v-for="(tab, idx) in tabs" :key="tab.value" :id="'tab-' + idx" class="ut-tab-item p-rtv"
- :class="{ active: idx === activeIndex }" @click="selectTab(idx)" ref="tabRefs">
- <view class="tab-label" :style="{
- fontSize: fontSize,
- color: idx === activeIndex ? themeColor : '#999',
- fontWeight: idx === activeIndex ? 'bold' : '500'
- }">
- <span>{{ tab.label }}</span>
- <view v-if="tab.badge && tab.num as number > 0" class="tab-badge">{{ tab.num }}</view>
- </view>
- <view v-if="idx === activeIndex" class="tab-underline" :style="{
- background: themeColor,
- height: lineHeight
- }"></view>
- </view>
- </view>
- </scroll-view>
- </template>
- <template v-if="mode == 'btw'">
- <view class="ut-tabs-row" :style="{ justifyContent: 'space-between' }">
- <view v-for="(tab, idx) in tabs" :key="tab.value" class="ut-tab-item p-rtv"
- :class="{ active: idx === activeIndex }" @click="selectTab(idx)" ref="tabRefs">
- <view class="tab-label" :style="{
- fontSize: fontSize,
- color: idx === activeIndex ? themeColor : '#999',
- fontWeight: idx === activeIndex ? 'bold' : '500'
- }">
- <span>{{ tab.label }}</span>
- <view v-if="tab.badge && tab.num as number > 0" class="tab-badge">{{ tab.num }}</view>
- </view>
- <view v-if="idx === activeIndex" class="tab-underline" :style="{
- background: themeColor,
- height: lineHeight
- }"></view>
- </view>
- </view>
- </template>
- <template v-if="mode == 'subsection'">
- <view class="tabs-subsection-wrap d-flex">
- <template v-for="(item, idx) in tabs" :key="idx">
- <view class="flex1 subsection-item d-flex p-rtv j-c a-c" :class="{ active: idx === activeIndex }">
- <slot :name="item.value">
- <text class="f-s-32 c-999">{{ item.label }}</text>
- </slot>
- </view>
- </template>
- </view>
- </template>
- </template>
- <script setup lang="ts">
- const props = defineProps({
- tabs: {
- type: Array as () => Array<{ label: string; value: string; badge?: boolean; num?: number }>,
- default: () => []
- },
- modelValue: {
- type: [String, Number],
- default: ''
- },
- themeColor: {
- type: String,
- default: '#37A954'
- },
- fontSize: {
- type: String,
- default: '30rpx'
- },
- lineHeight: {
- type: String,
- default: '6rpx'
- },
- mode: {
- type: String,
- default: 'scroll-x' // btw subsection
- }
- })
- const SCROLL_OFFSET = ref(-(uni as any).$u?.getPx('200rpx'));
- const emit = defineEmits(['update:modelValue', 'change'])
- const activeIndex = ref(0)
- const scrollIntoView = ref('')
- const scrollViewRef = ref(null)
- const tabRefs = ref([])
- watch(() => props.modelValue, (val: any) => {
- const idx = props.tabs.findIndex(tab => tab.value === val)
- if (idx !== -1) {
- activeIndex.value = idx
- scrollToTab(idx)
- }
- })
- const selectTab = (idx: number) => {
- activeIndex.value = idx
- emit('update:modelValue', props.tabs[idx].value)
- emit('change', props.tabs[idx])
- scrollToTab(idx)
- }
- // 主动滚动到选中tab
- const scrollToTab = (idx: number) => {
- scrollIntoView.value = 'tab-' + idx
- }
- </script>
- <style lang="scss" scoped>
- .ut-tabs-scroll {
- width: 100%;
- white-space: nowrap;
- }
- .ut-tabs-row {
- display: flex;
- flex-direction: row;
- align-items: center;
- min-height: 66rpx;
- }
- .ut-tab-item {
- position: relative;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- height: 78rpx;
- font-weight: 500;
- color: #666;
- cursor: pointer;
- padding: 0 24rpx;
- .tab-label {
- position: relative;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- span {
- display: inline-block;
- }
- .tab-badge {
- position: absolute;
- top: -10rpx;
- left: 90%;
- background: #fa3534;
- color: #fff;
- font-size: 22rpx;
- border-radius: 20rpx;
- padding: 0 12rpx;
- min-width: 32rpx;
- text-align: center;
- line-height: 32rpx;
- height: 32rpx;
- box-sizing: border-box;
- z-index: 2;
- }
- }
- .tab-underline {
- position: absolute;
- left: 24rpx;
- right: 24rpx;
- bottom: 0;
- border-radius: 3rpx;
- transition: width 0.2s;
- z-index: 1;
- }
- }
- .tabs-subsection-wrap {
- height: 88rpx;
- border-radius: 10rpx;
- background-color: #fff;
- overflow: hidden;
- &.active .subsection-item {
- background-color: #f0f9f4;
- }
- }
- </style>
|