@tuniao/tnui-vue3-uniapp
Version:
TuniaoUI for vue3 uniapp
185 lines (163 loc) • 4.67 kB
text/typescript
import {
getCurrentInstance,
nextTick,
provide,
reactive,
ref,
toRefs,
watch,
} from 'vue'
import { tabbarContextKey } from '../../../../tokens'
import { CHANGE_EVENT, UPDATE_MODEL_EVENT } from '../../../../constants'
import { useOrderedChildren, useSelectorQuery } from '../../../../hooks'
import { debugWarn, generateId, isBoolean, isPromise } from '../../../../utils'
import type { TabbarItemContext } from '../../../../tokens'
import type { TabbarProps } from '../tabbar'
import type { TabbarItemRect } from '../types'
export const useTabbar = (props: TabbarProps) => {
const { emit } = getCurrentInstance()!
const {
children: items,
addChild,
removeChild: removeItem,
} = useOrderedChildren<TabbarItemContext>()
const { getSelectorNodeInfo } = useSelectorQuery()
const rectId = `tt-${generateId()}`
const activeUid = ref<number>(-1)
// 添加Item
const addItem = (item: TabbarItemContext) => {
if (props.modelValue !== undefined && activeUid.value === -1) {
if (
props.modelValue === item.name ||
props.modelValue === items.value.length
) {
nextTick(() => {
updateActiveId(item.uid)
setTimeout(() => {
if (activeUid.value !== -1) return
activeUid.value = item.uid
}, 50)
})
}
}
addChild(item)
}
// 更新当前激活的Uid
const updateActiveId = (uid: number, changeEmit = false) => {
activeUid.value = uid
const itemIndex = items.value.findIndex((item) => item.uid === uid)
const value = items.value[itemIndex]?.name || itemIndex
emit(UPDATE_MODEL_EVENT, value)
if (changeEmit) {
nextTick(() => {
emit(CHANGE_EVENT, value)
})
}
}
// 设置当前被点击Item
const setActiveItem = (uid: number) => {
// 是否有切换拦截
if (!props.beforeSwitch) {
updateActiveId(uid, true)
return
}
const index = items.value.findIndex((item) => item.uid === uid)
const shouldSwitch = props.beforeSwitch(index)
const isPromiseOrBoolean = [
isPromise(shouldSwitch),
isBoolean(shouldSwitch),
].includes(true)
if (!isPromiseOrBoolean) {
debugWarn(
'TnTabbar',
'beforeSwitch切换前拦截函数必须返回Promise或者Boolean'
)
return
}
if (isPromise(shouldSwitch)) {
shouldSwitch.then((res) => {
if (res) updateActiveId(uid, true)
})
} else {
if (shouldSwitch) updateActiveId(uid, true)
}
}
// 根据modelValue设置当前激活的Item
const setActiveItemByValue = (value?: string | number) => {
if (value === undefined) {
// 如果没有传递任何值则设置第一个Item为激活状态
updateActiveId(items.value[0].uid)
return
}
let item: TabbarItemContext | undefined
// 如果类型是number,则先通过索引进行查找
if (typeof value === 'number') {
item = items.value?.[value]
}
// 如果没有找到,则通过name查找
if (!item) {
item = items.value.find((item) => item.name === value)
}
if (!item) {
// 设置第一个Item为激活状态
updateActiveId(items.value[0].uid)
} else {
updateActiveId(item.uid)
}
}
watch(
() => props.modelValue,
(val) => {
nextTick(() => {
setActiveItemByValue(val)
})
}
)
// 凸起按钮节点信息
const bulgeRectInfo = ref<TabbarItemRect>({
width: 0,
height: 0,
left: 0,
})
// 是否设置凸起按钮
const hasBulgeButton = ref<boolean>(false)
// 设置凸起按钮的位置
const setBulgeCircle = async (itemRectInfo: TabbarItemRect) => {
const { left } = itemRectInfo
try {
const rectInfo = await getSelectorNodeInfo(`#${rectId}`)
const { left: tabbarRectLeft } = rectInfo
let width = itemRectInfo.width
if (itemRectInfo?.maxWidth) {
width = itemRectInfo.maxWidth
}
bulgeRectInfo.value.width = width * props.bulgeSizePercent
bulgeRectInfo.value.height = bulgeRectInfo.value.width
bulgeRectInfo.value.left =
left - (tabbarRectLeft || 0) + itemRectInfo.width / 2
hasBulgeButton.value = true
} catch (err) {
debugWarn('TnTabbar', `获取Tabbar节点信息失败: ${err}`)
}
}
// provide
provide(
tabbarContextKey,
reactive({
...toRefs(props),
items,
activeUid,
addItem,
removeItem,
setActiveItem,
setBulgeCircle,
})
)
return {
rectId,
hasBulgeButton,
bulgeRectInfo,
setActiveItem,
setActiveItemByValue,
}
}