Initial commit
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
## 1.0.2(2026-07-16)
|
||||
- 修复微信小程序滚动内容穿透底部 tab-bar
|
||||
## 1.0.1(2026-07-07)
|
||||
- 优化组件的宽度计算方式
|
||||
## 1.0.0(2026-04-30)
|
||||
- 支持 uni-tab-bar 组件
|
||||
@@ -0,0 +1,139 @@
|
||||
<template>
|
||||
<view class="uni-tab-list-root" :class="tabBarRootClass">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 底部选项卡栏
|
||||
* @description 底部选项卡栏组件
|
||||
* @uniPlatform {
|
||||
* "app": {
|
||||
* "android": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.21"
|
||||
* },
|
||||
* "ios": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.11"
|
||||
* },
|
||||
* "harmony": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.07"
|
||||
* }
|
||||
* },
|
||||
* "mp": {
|
||||
* "weixin": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07"
|
||||
* },
|
||||
* "alipay": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.31"
|
||||
* }
|
||||
* },
|
||||
* "web": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07"
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
const tabContentHeightFull = inject<Ref<boolean>>('uniTabContentHeightFull', ref(false))
|
||||
const changeTab = inject<(index: number) => void>('uniTabBarChangeTab', (index: number) => {})
|
||||
|
||||
const itemCount = ref<number>(0)
|
||||
const hasMidButtonItem = ref<boolean>(false)
|
||||
|
||||
const tabBarRootClass = computed(() : string => {
|
||||
return tabContentHeightFull.value ? 'uni-tab-list-root-full' : 'uni-tab-list-root-normal'
|
||||
})
|
||||
|
||||
const validMidButtonLayout = computed(() : boolean => {
|
||||
return hasMidButtonItem.value == false || (itemCount.value > 0 && itemCount.value % 2 == 0)
|
||||
})
|
||||
|
||||
function registerItem() : number {
|
||||
const index = itemCount.value
|
||||
itemCount.value++
|
||||
return index
|
||||
}
|
||||
|
||||
|
||||
function registerMidButton(width : number, height : number) : void {
|
||||
hasMidButtonItem.value = true
|
||||
if (width <= 0) {
|
||||
console.warn('uni-tab-midbutton: width 必须大于 0')
|
||||
}
|
||||
if (height <= 0) {
|
||||
console.warn('uni-tab-midbutton: height 必须大于 0')
|
||||
}
|
||||
}
|
||||
|
||||
function syncOccupiedHeight() : void {
|
||||
if (hasMidButtonItem.value && validMidButtonLayout.value == false) {
|
||||
console.warn('uni-tab-bar: mid-button 需要偶数个普通 uni-tab-item,才能保持居中均分布局')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
syncOccupiedHeight()
|
||||
})
|
||||
|
||||
watch(() : number => itemCount.value, () => {
|
||||
syncOccupiedHeight()
|
||||
})
|
||||
|
||||
watch(() : boolean => hasMidButtonItem.value, () => {
|
||||
syncOccupiedHeight()
|
||||
})
|
||||
|
||||
provide('uniTabListRegisterItem', registerItem)
|
||||
provide('uniTabListRegisterMidButton', registerMidButton)
|
||||
provide('uniTabListChangeTab', changeTab)
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.uni-tab-list-root {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
height: var(--uni-tab-bar-height, 50px);
|
||||
flex-shrink: 0;
|
||||
box-sizing: content-box;
|
||||
padding-bottom: var(--uni-safe-area-inset-bottom);
|
||||
justify-content: flex-start;
|
||||
align-items: flex-end;
|
||||
overflow: visible;
|
||||
border-top-width: 0.5px;
|
||||
border-top-color: rgba(0,0,0,0.33);
|
||||
border-top-style: solid;
|
||||
}
|
||||
|
||||
.uni-tab-list-root-normal {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
/* #ifdef MP-ALIPAY */
|
||||
.uni-tab-list-root-normal {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.uni-tab-list-root-full {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 10;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<view v-if="hasRendered" class="uni-tab-content-root"
|
||||
:style="contentStyle">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 选项卡内容
|
||||
* @description 选项卡内容组件
|
||||
* @uniPlatform {
|
||||
* "app": {
|
||||
* "android": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.21"
|
||||
* },
|
||||
* "ios": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.11"
|
||||
* },
|
||||
* "harmony": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.07"
|
||||
* }
|
||||
* },
|
||||
* "mp": {
|
||||
* "weixin": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07"
|
||||
* },
|
||||
* "alipay": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.31"
|
||||
* }
|
||||
* },
|
||||
* "web": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07"
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
// 从父组件注入数据和方法
|
||||
const currentIndex = inject<Ref<number>>('uniTabBarCurrentIndex', ref(0))
|
||||
const registerContent = inject<() => number>('uniTabBarRegisterContent', () => 0)
|
||||
const tabContentHeightFull = inject<Ref<boolean>>('uniTabContentHeightFull', ref(false))
|
||||
|
||||
// 当前 content 的索引
|
||||
const contentIndex = ref<number>(-1)
|
||||
|
||||
// 是否已经渲染过(按需加载)
|
||||
const hasRendered = ref<boolean>(false)
|
||||
|
||||
// 组件挂载时分配索引
|
||||
onMounted(() => {
|
||||
contentIndex.value = registerContent()
|
||||
|
||||
// 如果是第一个激活的 tab-content,立即渲染
|
||||
if (contentIndex.value == currentIndex.value) {
|
||||
hasRendered.value = true
|
||||
}
|
||||
})
|
||||
|
||||
// 计算是否激活
|
||||
const isActive = computed((): boolean => {
|
||||
return currentIndex.value == contentIndex.value
|
||||
})
|
||||
|
||||
// 监听激活状态,首次激活时渲染
|
||||
watch((): boolean => {
|
||||
return isActive.value
|
||||
}, (newValue: boolean) => {
|
||||
if (newValue && hasRendered.value == false) {
|
||||
hasRendered.value = true
|
||||
}
|
||||
})
|
||||
|
||||
const contentStyle = computed(() => {
|
||||
const active = isActive.value
|
||||
return {
|
||||
visibility: active ? 'visible' : 'hidden',
|
||||
position: active ? 'relative' : 'absolute',
|
||||
left: active ? 'auto' : '0px',
|
||||
top: active ? 'auto' : '0px',
|
||||
right: active ? 'auto' : '0px',
|
||||
bottom: active ? 'auto' : (tabContentHeightFull.value ? '0px' : 'auto')
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.uni-tab-content-root {
|
||||
flex: 1;
|
||||
min-height: 0px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
z-index: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,187 @@
|
||||
<template>
|
||||
<view class="uni-tab-list-item-root" @click.stop="handleClick">
|
||||
<slot></slot>
|
||||
<text v-if="props.badgeText != null && props.badgeText != '0' && props.badgeText != ''" class="uni-tab-list-item-badge-position uni-tab-list-item-badge-text" :class="props.badgeClass">{{ props.badgeText }}</text>
|
||||
<view v-else-if="props.badgeText != null && props.badgeText != '0'" class="uni-tab-list-item-badge-position uni-tab-list-item-badge-dot" :class="props.badgeClass"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 选项卡项
|
||||
* @description 选项卡项组件
|
||||
* @uniPlatform {
|
||||
* "app": {
|
||||
* "android": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.21"
|
||||
* },
|
||||
* "ios": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.11"
|
||||
* },
|
||||
* "harmony": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.07"
|
||||
* }
|
||||
* },
|
||||
* "mp": {
|
||||
* "weixin": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07"
|
||||
* },
|
||||
* "alipay": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.31"
|
||||
* }
|
||||
* },
|
||||
* "web": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07"
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
defineOptions({
|
||||
externalClasses: ['badge-class']
|
||||
})
|
||||
|
||||
// 定义组件属性
|
||||
type Props = {
|
||||
/**
|
||||
* @description badge 的内容
|
||||
* @uniPlatform {
|
||||
* "app": {
|
||||
* "android": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.21"
|
||||
* },
|
||||
* "ios": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.11"
|
||||
* },
|
||||
* "harmony": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.07"
|
||||
* }
|
||||
* },
|
||||
* "mp": {
|
||||
* "weixin": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07"
|
||||
* },
|
||||
* "alipay": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.31"
|
||||
* }
|
||||
* },
|
||||
* "web": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07"
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
badgeText?: string,
|
||||
/**
|
||||
* @description badge 的样式类
|
||||
* @uniPlatform {
|
||||
* "app": {
|
||||
* "android": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.21"
|
||||
* },
|
||||
* "ios": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.11"
|
||||
* },
|
||||
* "harmony": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.07"
|
||||
* }
|
||||
* },
|
||||
* "mp": {
|
||||
* "weixin": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07"
|
||||
* },
|
||||
* "alipay": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.31"
|
||||
* }
|
||||
* },
|
||||
* "web": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07"
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
badgeClass?: string.ClassString,
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
badgeClass: ''
|
||||
})
|
||||
|
||||
const registerItem = inject<() => number>('uniTabListRegisterItem', () => 0)
|
||||
const changeTab = inject<(index: number) => void>('uniTabListChangeTab', (index: number) => {})
|
||||
const itemIndex = ref<number>(-1)
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
itemIndex.value = registerItem()
|
||||
})
|
||||
|
||||
function handleClick() : void {
|
||||
if (itemIndex.value >= 0) {
|
||||
changeTab(itemIndex.value)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.uni-tab-list-item-root {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.uni-tab-list-item-badge-position {
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
left: 50%;
|
||||
transform: translate(25%, -50%);
|
||||
}
|
||||
|
||||
.uni-tab-list-item-badge-text {
|
||||
background-color: rgb(225, 80, 80);
|
||||
min-width: 14px;
|
||||
height: 14px;
|
||||
line-height: 14px;
|
||||
border-radius: 7px;
|
||||
font-size: 10px;
|
||||
color: white;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.uni-tab-list-item-badge-dot {
|
||||
background-color: rgb(225, 80, 80);
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 4px;
|
||||
margin-left: 7px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,159 @@
|
||||
<template>
|
||||
<view :style="{ width: props.width + 'px', height: props.height + 'px' }" class="uni-tab-list-item-midbutton-root">
|
||||
<view class="uni-tab-list-item-midbutton-slot">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 选项卡中间按钮
|
||||
* @description 选项卡中间悬浮按钮组件
|
||||
* @uniPlatform {
|
||||
* "app": {
|
||||
* "android": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.21"
|
||||
* },
|
||||
* "ios": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.11"
|
||||
* },
|
||||
* "harmony": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.07"
|
||||
* }
|
||||
* },
|
||||
* "mp": {
|
||||
* "weixin": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07"
|
||||
* },
|
||||
* "alipay": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.31"
|
||||
* }
|
||||
* },
|
||||
* "web": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07"
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
type Props = {
|
||||
/**
|
||||
* @description 中间按钮宽度,单位 px
|
||||
* @default 90
|
||||
* @uniPlatform {
|
||||
* "app": {
|
||||
* "android": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.21"
|
||||
* },
|
||||
* "ios": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.11"
|
||||
* },
|
||||
* "harmony": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.07"
|
||||
* }
|
||||
* },
|
||||
* "mp": {
|
||||
* "weixin": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07"
|
||||
* },
|
||||
* "alipay": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.31"
|
||||
* }
|
||||
* },
|
||||
* "web": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07"
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
width?: number,
|
||||
/**
|
||||
* @description 中间按钮高度,单位 px
|
||||
* @default 90
|
||||
* @uniPlatform {
|
||||
* "app": {
|
||||
* "android": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.21"
|
||||
* },
|
||||
* "ios": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.11"
|
||||
* },
|
||||
* "harmony": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.07"
|
||||
* }
|
||||
* },
|
||||
* "mp": {
|
||||
* "weixin": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07"
|
||||
* },
|
||||
* "alipay": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.31"
|
||||
* }
|
||||
* },
|
||||
* "web": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07"
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
height?: number
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
width: 90,
|
||||
height: 90
|
||||
})
|
||||
const registerMidButton = inject<(width : number, height : number) => void>('uniTabListRegisterMidButton', (width : number, height : number) => {})
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
registerMidButton(props.width, props.height)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.uni-tab-list-item-midbutton-root {
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.uni-tab-list-item-midbutton-slot {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: visible;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,222 @@
|
||||
<template>
|
||||
<view class="uni-tab-bar-root"> <!-- 如果 tab 是一个非全屏的组件,需要套一层。如果是全屏场景,这一层可以省略 -->
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 选项卡
|
||||
* @description 选项卡容器组件
|
||||
* @uniPlatform {
|
||||
* "app": {
|
||||
* "android": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.21"
|
||||
* },
|
||||
* "ios": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.11"
|
||||
* },
|
||||
* "harmony": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.07"
|
||||
* }
|
||||
* },
|
||||
* "mp": {
|
||||
* "weixin": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07"
|
||||
* },
|
||||
* "alipay": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.31"
|
||||
* }
|
||||
* },
|
||||
* "web": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07"
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
type Props = {
|
||||
/**
|
||||
* @description 当前激活的选项卡索引
|
||||
* @uniPlatform {
|
||||
* "app": {
|
||||
* "android": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.21"
|
||||
* },
|
||||
* "ios": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.11"
|
||||
* },
|
||||
* "harmony": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.07"
|
||||
* }
|
||||
* },
|
||||
* "mp": {
|
||||
* "weixin": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07"
|
||||
* },
|
||||
* "alipay": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.31"
|
||||
* }
|
||||
* },
|
||||
* "web": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07"
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
activeIndex: number,
|
||||
/**
|
||||
* @description tab-content 的高度是否通到tab-bar下面
|
||||
* @uniPlatform {
|
||||
* "app": {
|
||||
* "android": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.21"
|
||||
* },
|
||||
* "ios": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.11"
|
||||
* },
|
||||
* "harmony": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.07"
|
||||
* }
|
||||
* },
|
||||
* "mp": {
|
||||
* "weixin": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07"
|
||||
* },
|
||||
* "alipay": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.31"
|
||||
* }
|
||||
* },
|
||||
* "web": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07"
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
tabContentHeightFull?: boolean
|
||||
}
|
||||
|
||||
// 定义组件属性
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
tabContentHeightFull: false
|
||||
})
|
||||
|
||||
// 定义事件
|
||||
const emit = defineEmits<{
|
||||
/**
|
||||
* @description 选项卡变化时触发,参数为当前激活的选项卡索引,类型为 number
|
||||
* @uniPlatform {
|
||||
* "app": {
|
||||
* "android": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.21"
|
||||
* },
|
||||
* "ios": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.11"
|
||||
* },
|
||||
* "harmony": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07",
|
||||
* "unixVaporVer": "5.07"
|
||||
* }
|
||||
* },
|
||||
* "mp": {
|
||||
* "weixin": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07"
|
||||
* },
|
||||
* "alipay": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.31"
|
||||
* }
|
||||
* },
|
||||
* "web": {
|
||||
* "uniVer": "x",
|
||||
* "unixVer": "5.07"
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
change: [index: number]
|
||||
}>()
|
||||
|
||||
// 当前激活的索引
|
||||
const currentIndex = ref<number>(props.activeIndex)
|
||||
|
||||
const tabContentHeightFull = ref<boolean>(props.tabContentHeightFull)
|
||||
|
||||
// tab-content 的数量计数器
|
||||
const contentCount = ref<number>(0)
|
||||
|
||||
// 监听 activeIndex 变化
|
||||
watch((): number => {
|
||||
return props.activeIndex
|
||||
}, (newIndex: number) => {
|
||||
currentIndex.value = newIndex
|
||||
})
|
||||
|
||||
watch(() : boolean => {
|
||||
return props.tabContentHeightFull
|
||||
}, () => {
|
||||
tabContentHeightFull.value = props.tabContentHeightFull
|
||||
})
|
||||
|
||||
// 切换选项卡
|
||||
function changeTab(index: number): void {
|
||||
if (currentIndex.value != index) {
|
||||
currentIndex.value = index
|
||||
emit('change', index)
|
||||
}
|
||||
}
|
||||
|
||||
// 注册 tab-content
|
||||
function registerContent() : number {
|
||||
const index = contentCount.value
|
||||
contentCount.value++
|
||||
return index
|
||||
}
|
||||
|
||||
// 通过 provide 向子组件提供数据和方法
|
||||
provide('uniTabBarCurrentIndex', currentIndex)
|
||||
provide('uniTabBarChangeTab', changeTab)
|
||||
provide('uniTabBarRegisterContent', registerContent)
|
||||
provide('uniTabContentHeightFull', tabContentHeightFull)
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.uni-tab-bar-root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
min-height: 0px;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,103 @@
|
||||
{
|
||||
"id": "uni-tab-bar",
|
||||
"displayName": "uni-tab-bar 选项卡",
|
||||
"version": "1.0.2",
|
||||
"description": "底部选项卡组件集",
|
||||
"isExt": true,
|
||||
"extUrl": "https://ext.dcloud.net.cn/plugin?id=27849",
|
||||
"examples": [
|
||||
{
|
||||
"name": "tab-bar-dark",
|
||||
"description": "暗色风格",
|
||||
"url": "https://gitcode.com/dcloud/hello-uni-app-x/blob/prod_alpha/pages/uni-ui/tab-bar/tab-bar-dark.uvue"
|
||||
},
|
||||
{
|
||||
"name": "tab-bar-custom",
|
||||
"description": "自定义样式",
|
||||
"url": "https://gitcode.com/dcloud/hello-uni-app-x/blob/prod_alpha/pages/uni-ui/tab-bar/tab-bar-custom.uvue"
|
||||
},
|
||||
{
|
||||
"name": "tab-bar-blur",
|
||||
"description": "半透明毛玻璃",
|
||||
"url": "https://gitcode.com/dcloud/hello-uni-app-x/blob/prod_alpha/pages/uni-ui/tab-bar/tab-bar-blur.uvue"
|
||||
},
|
||||
{
|
||||
"id": "uni-tab-midbutton",
|
||||
"name": "tab-bar-midbutton",
|
||||
"description": "中间按钮",
|
||||
"url": "https://gitcode.com/dcloud/hello-uni-app-x/blob/prod_alpha/pages/uni-ui/tab-bar/tab-bar-midbutton.uvue"
|
||||
},
|
||||
{
|
||||
"id": "uni-tab-midbutton",
|
||||
"name": "tab-bar-midbutton-notch",
|
||||
"description": "中间按钮镂空",
|
||||
"url": "https://gitcode.com/dcloud/hello-uni-app-x/blob/prod_alpha/pages/uni-ui/tab-bar/tab-bar-midbutton-notch.uvue"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
"uni-ui",
|
||||
"uniui",
|
||||
"自定义选项卡",
|
||||
"uni-app-x",
|
||||
"tab-bar"
|
||||
],
|
||||
"repository": "https://gitcode.com/dcloud/uni-ui-x",
|
||||
"engines": {
|
||||
"HBuilderX": "^5.07",
|
||||
"uni-app": "",
|
||||
"uni-app-x": "^5.07"
|
||||
},
|
||||
"directories": {
|
||||
"example": "../../temps/example_temps"
|
||||
},
|
||||
"dcloudext": {
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"type": "component-vue",
|
||||
"darkmode": "x",
|
||||
"i18n": "x",
|
||||
"widescreen": "x",
|
||||
"npmurl": ""
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "√",
|
||||
"aliyun": "x",
|
||||
"alipay": "x"
|
||||
},
|
||||
"client": {
|
||||
"uni-app-x": {
|
||||
"web": {
|
||||
"safari": "√",
|
||||
"chrome": "√"
|
||||
},
|
||||
"app": {
|
||||
"android": "√",
|
||||
"ios": "√",
|
||||
"harmony": "√"
|
||||
},
|
||||
"mp": {
|
||||
"weixin": "√"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
自定义tab-bar组件, 包括一组前端组件:
|
||||
|
||||
- `uni-tab`:最外层容器,负责激活索引、内容区高度和切换事件
|
||||
- `uni-tab-bar`:底部 tab 容器,负责底栏布局、安全区和中间按钮占位
|
||||
- `uni-tab-item`:普通 tab 项,负责插槽展示和 badge
|
||||
- `uni-tab-midbutton`:中间悬浮按钮占位容器
|
||||
- `uni-tab-content`:上方内容区域容器,负责内容注册、懒渲染和显示切换
|
||||
|
||||
每个选项卡内容,不再是页面,而变成了uni-tab-content组件的子组件。
|
||||
|
||||
自定义tab-bar组件的使用方式是纯前端的、也是跨平台的。这与pages.json中的tabBar完全不同:
|
||||
1. pages.json中的tabBar相关的api无法应用于自定义tab-bar,比如uni.switchTab、uni.showTabBar、uni.hideTabBar、uni.showTabBarRedDot、uni.hideTabBarRedDot、uni.setTabBarBadge、uni.removeTabBarBadge、uni.setTabBarStyle、uni.setTabBarItem。
|
||||
2. pages.json中的tabBar页面,生命周期仍然是页面生命周期。但自定义tab-bar的uni-tab-content内容不触发页面生命周期,只触发组件生命周期。
|
||||
|
||||
当然,自定义tab-bar组件拥有更强的功能、更灵活的控制方式。比如下凹式midbutton。在uni-app x的非小程序平台中,性能并不会比pages.json中的tabBar差。
|
||||
|
||||
|
||||
### 基础用法
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<uni-tab style="flex: 1" :active-index="activeIndex" @change="handleChange">
|
||||
<uni-tab-content>
|
||||
<scroll-view style="flex: 1">
|
||||
<view>
|
||||
<text>内容一</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</uni-tab-content>
|
||||
<uni-tab-content>
|
||||
<view>
|
||||
<text>内容二</text>
|
||||
</view>
|
||||
</uni-tab-content>
|
||||
|
||||
<uni-tab-bar>
|
||||
<uni-tab-item>
|
||||
<text>首页</text>
|
||||
</uni-tab-item>
|
||||
<uni-tab-item badge-text="8">
|
||||
<text>消息</text>
|
||||
</uni-tab-item>
|
||||
</uni-tab-bar>
|
||||
</uni-tab>
|
||||
</template>
|
||||
```
|
||||
|
||||
底栏主体高度默认为 `50px`。如果需要自定义,请在 `uni-tab` 上设置 `--uni-tab-bar-height` CSS 变量:
|
||||
|
||||
```vue
|
||||
<uni-tab style="flex: 1; --uni-tab-bar-height: 40px" :active-index="activeIndex" @change="handleChange">
|
||||
...
|
||||
</uni-tab>
|
||||
```
|
||||
|
||||
### 组件 API
|
||||
|
||||
#### uni-tab
|
||||
|
||||
##### 事件
|
||||
|
||||
| 事件名 | 返回参数 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `change` | `index: number` | 点击普通 tab 项切换后触发,返回新的激活索引 |
|
||||
|
||||
#### uni-tab-bar
|
||||
|
||||
无专用属性和API,可直接通过 `class` / `style` 自定义底栏外观。
|
||||
|
||||
#### uni-tab-content
|
||||
|
||||
无专用属性和API,可直接通过 `class` / `style` 自定义外观。
|
||||
|
||||
多个选项卡内容揉在一个uni-tab中比较乱,一般推荐uni-tab-content下放一个子组件,子组件作为独立uvue文件。
|
||||
|
||||
各个uni-tab-content的子组件,在显示和隐藏的没有生命周期。
|
||||
如需监听显示/隐藏需要通过provide/inject方式从外层传入响应式变量activeIndex,然后在子组件中watch这个activeIndex。
|
||||
|
||||
### 使用注意事项
|
||||
|
||||
1. `uni-tab` 所在的页面不应该滚动,应该在pages.json中配置页面style为`disableScroll:true`。
|
||||
2. 屏幕变化适配,Android需HBuilderX 5.09+支持。之前版本组件里无法监听页面的onResize。
|
||||
3. `uni-tab-content` 与 `uni-tab-item` 的数量必须一致,顺序也必须一一对应,否则切换后的内容会错位。
|
||||
4. `uni-tab` 默认占满页面的宽度和高度,即 `style="flex: 1"`
|
||||
5. 自定义底栏高度时,请在 `uni-tab` 上设置带长度单位的 `--uni-tab-bar-height` CSS 变量,例如 `--uni-tab-bar-height: 40px`。底栏和需要显式计算高度的内容区会共用该变量。
|
||||
6. 如果底栏存在镂空、下凹或透明边缘,需要让底部内容透出来,请在 `uni-tab` 上开启 `tab-content-height-full`,此时内容区会延伸到 `uni-tab-bar` 下方。
|
||||
7. `uni-tab-content` 采用“首次激活再渲染”的策略;第一次切入前不会创建实例,切走后只隐藏不销毁,适合保留 tab 内部状态。
|
||||
8. `uni-tab-item` 只负责普通 tab 项注册与展示,不提供单独点击事件;请统一监听 `uni-tab` 的 `change`。
|
||||
9. 使用 `uni-tab-midbutton` 时,中间按钮左右两侧的普通 `uni-tab-item` 数量需要相等,也就是普通项总数必须为偶数,才能保持居中均分布局。
|
||||
10. `uni-tab-midbutton` 只负责预留中间按钮位置,不参与激活索引计算;中间按钮点击行为需要在插槽内部自行处理。
|
||||
11. `badge-text` 传空字符串时显示红点,传 `'0'` 时不显示;如果需要自定义 badge 外观,请使用 `badge-class` 覆盖样式。
|
||||
12. `uni-tab-bar`、`uni-tab-item`、`uni-tab-content` 的根节点都支持直接挂 `class` / `style`,推荐把视觉差异放在页面侧处理,不要改组件内部逻辑。
|
||||
13. 微信小程序提供了一种webview方式渲染的底部tabBar方式,来进行tabBar自定义。这种方式仅微信小程序支持,与本组件无关。
|
||||
@@ -0,0 +1,10 @@
|
||||
## 中间凸起
|
||||
非下沉的,中间凸起的midbutton页面,缺少一个平滑的过渡曲线
|
||||
|
||||
## 左右滑
|
||||
Android常见,微信
|
||||
避免和页面内的内容冲突
|
||||
|
||||
## tab-bar高斯模糊
|
||||
等view支持高斯模糊的css后,补一个示例页面即可
|
||||
|
||||
Reference in New Issue
Block a user