|
@@ -1,52 +1,79 @@
|
|
|
<template>
|
|
<template>
|
|
|
<template v-if="mode == 'scroll-x'">
|
|
<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'
|
|
|
|
|
- }">
|
|
|
|
|
|
|
+ <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 gap-30">
|
|
|
|
|
+ <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 == 'scroll-x-card'">
|
|
|
|
|
+ <scroll-view scroll-x class="ut-tabs-scroll" show-scrollbar="false" :scroll-into-view-offset="SCROLL_OFFSET" :scroll-into-view="scrollIntoView" ref="scrollViewCardRef" scroll-with-animation>
|
|
|
|
|
+ <view class="ut-tabs-row gap-30">
|
|
|
|
|
+ <view v-for="(tab, idx) in tabs" :key="tab.value" :id="'tab-' + idx" class="ut-tab-item-card gap-30 p-rtv" :class="{ active: idx === activeIndex }" @click="selectTab(idx)" ref="tabCardRefs">
|
|
|
|
|
+ <view
|
|
|
|
|
+ class="tab-label-card d-flex j-c a-c pd2-0-24 min-w-150"
|
|
|
|
|
+ :style="{
|
|
|
|
|
+ fontSize: fontSize,
|
|
|
|
|
+ color: idx === activeIndex ? '#fff' : '#333',
|
|
|
|
|
+ background: idx === activeIndex ? themeColor : '#fff',
|
|
|
|
|
+ }"
|
|
|
|
|
+ >
|
|
|
<span>{{ tab.label }}</span>
|
|
<span>{{ tab.label }}</span>
|
|
|
- <view v-if="tab.badge && tab.num as number > 0" class="tab-badge">{{ tab.num }}</view>
|
|
|
|
|
</view>
|
|
</view>
|
|
|
- <view v-if="idx === activeIndex" class="tab-underline" :style="{
|
|
|
|
|
- background: themeColor,
|
|
|
|
|
- height: lineHeight
|
|
|
|
|
- }"></view>
|
|
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
</scroll-view>
|
|
</scroll-view>
|
|
|
</template>
|
|
</template>
|
|
|
<template v-if="mode == 'btw'">
|
|
<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'
|
|
|
|
|
- }">
|
|
|
|
|
|
|
+ <view class="ut-tabs-row gap-30" :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>
|
|
<span>{{ tab.label }}</span>
|
|
|
- <view v-if="tab.badge && tab.num as number > 0" class="tab-badge">{{ tab.num }}</view>
|
|
|
|
|
|
|
+ <view v-if="tab.badge && (tab.num as number) > 0" class="tab-badge">{{ tab.num }}</view>
|
|
|
</view>
|
|
</view>
|
|
|
- <view v-if="idx === activeIndex" class="tab-underline" :style="{
|
|
|
|
|
- background: themeColor,
|
|
|
|
|
- height: lineHeight
|
|
|
|
|
- }"></view>
|
|
|
|
|
|
|
+ <view
|
|
|
|
|
+ v-if="idx === activeIndex"
|
|
|
|
|
+ class="tab-underline"
|
|
|
|
|
+ :style="{
|
|
|
|
|
+ background: themeColor,
|
|
|
|
|
+ height: lineHeight,
|
|
|
|
|
+ }"
|
|
|
|
|
+ ></view>
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
</template>
|
|
</template>
|
|
|
<template v-if="mode == 'subsection'">
|
|
<template v-if="mode == 'subsection'">
|
|
|
<view class="tabs-subsection-wrap d-flex">
|
|
<view class="tabs-subsection-wrap d-flex">
|
|
|
<template v-for="(item, idx) in tabs" :key="idx">
|
|
<template v-for="(item, idx) in tabs" :key="idx">
|
|
|
- <view class="flex1 subsection-item d-flex p-rtv j-c a-c f-s-32 c-999" @click="selectTab(idx)" :class="{ active: idx === activeIndex }">
|
|
|
|
|
- <slot :name="item.value" :active="idx === activeIndex">
|
|
|
|
|
- {{ item.label }}
|
|
|
|
|
- </slot>
|
|
|
|
|
|
|
+ <view class="flex1 subsection-item d-flex p-rtv j-c a-c f-s-32 c-999" @click="selectTab(idx)" :class="{ active: idx === activeIndex }">
|
|
|
|
|
+ <slot :name="item.value" :active="idx === activeIndex">{{ item.label }}</slot>
|
|
|
</view>
|
|
</view>
|
|
|
</template>
|
|
</template>
|
|
|
</view>
|
|
</view>
|
|
@@ -54,60 +81,68 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
-
|
|
|
|
|
const props = defineProps({
|
|
const props = defineProps({
|
|
|
tabs: {
|
|
tabs: {
|
|
|
- type: Array as () => Array<{ label: string; value: string; badge?: boolean; num?: number }>,
|
|
|
|
|
- default: () => []
|
|
|
|
|
|
|
+ type: Array as () => Array<{
|
|
|
|
|
+ label: string;
|
|
|
|
|
+ value: string;
|
|
|
|
|
+ badge?: boolean;
|
|
|
|
|
+ num?: number;
|
|
|
|
|
+ }>,
|
|
|
|
|
+ default: () => [],
|
|
|
},
|
|
},
|
|
|
modelValue: {
|
|
modelValue: {
|
|
|
type: [String, Number],
|
|
type: [String, Number],
|
|
|
- default: ''
|
|
|
|
|
|
|
+ default: '',
|
|
|
},
|
|
},
|
|
|
themeColor: {
|
|
themeColor: {
|
|
|
type: String,
|
|
type: String,
|
|
|
- default: '#37A954'
|
|
|
|
|
|
|
+ default: '#37A954',
|
|
|
},
|
|
},
|
|
|
fontSize: {
|
|
fontSize: {
|
|
|
type: String,
|
|
type: String,
|
|
|
- default: '30rpx'
|
|
|
|
|
|
|
+ default: '30rpx',
|
|
|
},
|
|
},
|
|
|
lineHeight: {
|
|
lineHeight: {
|
|
|
type: String,
|
|
type: String,
|
|
|
- default: '6rpx'
|
|
|
|
|
|
|
+ default: '6rpx',
|
|
|
},
|
|
},
|
|
|
mode: {
|
|
mode: {
|
|
|
type: String,
|
|
type: String,
|
|
|
- default: 'scroll-x' // btw subsection
|
|
|
|
|
- }
|
|
|
|
|
-})
|
|
|
|
|
|
|
+ default: 'scroll-x', // btw subsection scroll-x-card
|
|
|
|
|
+ },
|
|
|
|
|
+});
|
|
|
const SCROLL_OFFSET = ref(-(uni as any).$u?.getPx('200rpx'));
|
|
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 emit = defineEmits(['update:modelValue', 'change']);
|
|
|
|
|
+
|
|
|
|
|
+const activeIndex = ref(0);
|
|
|
|
|
+const scrollIntoView = ref('');
|
|
|
|
|
+const scrollViewRef = ref(null);
|
|
|
|
|
+const scrollViewCardRef = 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) => {
|
|
const selectTab = (idx: number) => {
|
|
|
- activeIndex.value = idx
|
|
|
|
|
- emit('update:modelValue', props.tabs[idx].value)
|
|
|
|
|
- emit('change', props.tabs[idx])
|
|
|
|
|
- scrollToTab(idx)
|
|
|
|
|
-}
|
|
|
|
|
|
|
+ activeIndex.value = idx;
|
|
|
|
|
+ emit('update:modelValue', props.tabs[idx].value);
|
|
|
|
|
+ emit('change', props.tabs[idx]);
|
|
|
|
|
+ scrollToTab(idx);
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
// 主动滚动到选中tab
|
|
// 主动滚动到选中tab
|
|
|
const scrollToTab = (idx: number) => {
|
|
const scrollToTab = (idx: number) => {
|
|
|
- scrollIntoView.value = 'tab-' + idx
|
|
|
|
|
-}
|
|
|
|
|
|
|
+ scrollIntoView.value = 'tab-' + idx;
|
|
|
|
|
+};
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
<style lang="scss" scoped>
|
|
@@ -132,7 +167,6 @@ const scrollToTab = (idx: number) => {
|
|
|
font-weight: 500;
|
|
font-weight: 500;
|
|
|
color: #666;
|
|
color: #666;
|
|
|
cursor: pointer;
|
|
cursor: pointer;
|
|
|
- padding: 0 24rpx;
|
|
|
|
|
|
|
|
|
|
.tab-label {
|
|
.tab-label {
|
|
|
position: relative;
|
|
position: relative;
|
|
@@ -160,20 +194,30 @@ const scrollToTab = (idx: number) => {
|
|
|
box-sizing: border-box;
|
|
box-sizing: border-box;
|
|
|
z-index: 2;
|
|
z-index: 2;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.tab-underline {
|
|
.tab-underline {
|
|
|
position: absolute;
|
|
position: absolute;
|
|
|
- left: 24rpx;
|
|
|
|
|
- right: 24rpx;
|
|
|
|
|
|
|
+ left: 0rpx;
|
|
|
|
|
+ right: 0rpx;
|
|
|
bottom: 0;
|
|
bottom: 0;
|
|
|
border-radius: 3rpx;
|
|
border-radius: 3rpx;
|
|
|
transition: width 0.2s;
|
|
transition: width 0.2s;
|
|
|
z-index: 1;
|
|
z-index: 1;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+.ut-tab-item-card {
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ .tab-label-card {
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+ border-radius: 28rpx;
|
|
|
|
|
+ height: 54rpx;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
.tabs-subsection-wrap {
|
|
.tabs-subsection-wrap {
|
|
|
height: 88rpx;
|
|
height: 88rpx;
|
|
|
border-radius: 10rpx;
|
|
border-radius: 10rpx;
|
|
@@ -181,7 +225,7 @@ const scrollToTab = (idx: number) => {
|
|
|
overflow: hidden;
|
|
overflow: hidden;
|
|
|
|
|
|
|
|
.active.subsection-item {
|
|
.active.subsection-item {
|
|
|
- background-color: #37A954;
|
|
|
|
|
|
|
+ background-color: #37a954;
|
|
|
color: #fff;
|
|
color: #fff;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|