<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>