|
@@ -0,0 +1,167 @@
|
|
|
|
|
+<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>
|
|
|
|
|
+
|
|
|
|
|
+<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
|
|
|
|
|
+ }
|
|
|
|
|
+})
|
|
|
|
|
+const SCROLL_OFFSET = ref(-uni.$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) => {
|
|
|
|
|
+ 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;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.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;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|