Initial commit

This commit is contained in:
tanxin
2026-10-29 10:08:39 +08:00
commit 4c6243a274
124 changed files with 17359 additions and 0 deletions
@@ -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>