import { computed, getCurrentInstance, nextTick, onMounted, provide, reactive, ref, toRefs, useSlots, watch, } from 'vue' import { CHANGE_EVENT, UPDATE_MODEL_EVENT } from '../../../../constants' import { tabsContextKey } from '../../../../tokens' import { useOrderedChildren, useSelectorQuery } from '../../../../hooks' import { debugWarn, generateId, isBoolean, isPromise } from '../../../../utils' import type { TabsItemContext } from '../../../../tokens' import type { TabsProps } from '../tabs' import type { TabsBarRect, TabsRect } from '../types' export const useTabs = (props: TabsProps) => { const instance = getCurrentInstance() if (!instance) { debugWarn('TnTabs', '请在 setup 函数中使用 useTabs ') } const { emit } = instance! const slots = useSlots() const { children: items, addChild, removeChild: removeItem, } = useOrderedChildren() const { getSelectorNodeInfo } = useSelectorQuery(instance) const componentId = `tt-${generateId()}` const barComponentId = `${componentId}-b` // 是否需要显示bar滑块 const showBar = computed(() => props.bar || !!slots.bar) // 当前被激活的ItemUid const activeUid = ref(-1) // 添加tabsItem到items容器中 const addItem = (item: TabsItemContext) => { if (props.modelValue !== undefined && activeUid.value === -1) { if ( props.modelValue === item.name || props.modelValue === items.value.length ) { nextTick(() => { updateActiveUid(item.uid) }) } } addChild(item) } // tabs容器节点信息 const tabsRect: TabsRect = { width: 0, height: 0, left: 0, } // bar滑块的容器节点信息 const barRect: TabsBarRect = { width: 0, height: 0, left: 0, } // 滑块滑动的距离 const barOffsetLeft = ref(0) // scrollView的滚动距离 const scrollLeft = ref(0) // 更新偏移位置信息 const updateOffsetPosition = (index: number) => { if (!props.scroll && !props.bar && !slots.bar) return // 获取当前Item const item = items.value[index].elementRect if (props.bar || slots.bar) { // 更新滑块的偏移位置 barOffsetLeft.value = item.left - tabsRect.left + (item.width - barRect.width) / 2 } if (props.scroll) { // 更新scrollView的偏移位置 const scrollLeftValue = item.left - tabsRect.left - (tabsRect.width - item.width) / 2 scrollLeft.value = scrollLeftValue < 0 ? 0 : scrollLeftValue } } // 更新当前激活的ItemUid const updateActiveUid = (uid: number, changeEmit = false) => { activeUid.value = uid const itemIndex = items.value.findIndex((item) => item.uid === uid) const value = items.value[itemIndex].name ? items.value[itemIndex].name : itemIndex updateOffsetPosition(itemIndex) emit(UPDATE_MODEL_EVENT, value) if (changeEmit) { emit(CHANGE_EVENT, value) } } // 设置当前被点击Item const setActiveItem = (uid: number) => { if (!props.beforeSwitch) { updateActiveUid(uid, true) return } const itemIndex = items.value.findIndex((item) => item.uid === uid) const shouldSwitch = props.beforeSwitch(itemIndex) const isPromiseOrBoolean = [ isPromise(shouldSwitch), isBoolean(shouldSwitch), ].includes(true) if (!isPromiseOrBoolean) { debugWarn('TnTabs', 'beforeSwitch返回值必须是Promise或者Boolean') return } if (isPromise(shouldSwitch)) { shouldSwitch .then((res) => { if (res) { updateActiveUid(uid, true) } }) .catch((err) => { debugWarn('TnTabs', `执行beforeSwitch出错:${err}`) }) } else { if (shouldSwitch) { updateActiveUid(uid, true) } } } // 通过索引更新当前激活的ActiveItem const updateActiveItemByValue = (value?: string | number) => { if (value === undefined) { // 如果没有传递任何值则设置第一个Item为激活状态 updateActiveUid(items.value[0].uid) return } let item: TabsItemContext | undefined // 如果类型是number,则先通过索引进行查找 if (typeof value === 'number') { item = items.value?.[value] } // 如果没有找到,则通过name查找 if (!item) { item = items.value.find((item) => item.name === value) } if (!item) { // 设置第一个Item为激活状态 updateActiveUid(items.value[0].uid) } else { updateActiveUid(item.uid) } } watch( () => props.modelValue, (val) => { updateActiveItemByValue(val) } ) let initCount = 0 // 获取Tabs容器节点信息 const getTabsRectInfo = async () => { try { const rectInfo = await getSelectorNodeInfo(`#${componentId}`) initCount = 0 tabsRect.width = rectInfo.width || 0 tabsRect.height = rectInfo.height || 0 tabsRect.left = rectInfo.left || 0 } catch (err) { if (initCount > 10) { initCount = 0 debugWarn('TnTabs', `获取Tabs容器节点信息出错: ${err}`) return } initCount++ setTimeout(() => { getTabsRectInfo() }, 150) } } // 获取Bar滑块的容器节点信息 const getBarRectInfo = async () => { if (!props.bar && !slots.bar) return try { const rectInfo = await getSelectorNodeInfo(`#${barComponentId}`) initCount = 0 barRect.width = rectInfo.width || 0 barRect.height = rectInfo.height || 0 barRect.left = rectInfo.left || 0 } catch (err) { if (initCount > 10) { initCount = 0 debugWarn('TnTabs', `获取Bar滑块节点信息出错: ${err}`) return } initCount++ setTimeout(() => { getBarRectInfo() }, 150) } } onMounted(() => { nextTick(() => { // #ifndef MP-ALIPAY getTabsRectInfo() // #endif // #ifdef MP-ALIPAY setTimeout(() => { getTabsRectInfo() }, 50) // #endif getBarRectInfo() }) }) provide( tabsContextKey, reactive({ ...toRefs(props), items, activeUid, showBar, addItem, removeItem, setActiveItem, }) ) return { tabItems: items, componentId, barComponentId, barOffsetLeft, scrollLeft, showBar, } }