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