2025/2/8第一次更新

This commit is contained in:
爱吃咸鱼小猫咪
2025-02-08 18:50:38 +08:00
commit d7af560866
26519 changed files with 5046029 additions and 0 deletions
@@ -0,0 +1,4 @@
export * from './tabbar-custom'
export * from './tabbar-item-custom'
export * from './use-tabbar'
export * from './use-tabbar-item'
@@ -0,0 +1,136 @@
import { computed, toRef } from 'vue'
import { useComponentColor, useNamespace } from '../../../../hooks'
import { formatDomSizeValue } from '../../../../utils'
import type { CSSProperties } from 'vue'
import type { TabbarProps } from '../tabbar'
export const useTabbarCustomStyle = (props: TabbarProps) => {
const ns = useNamespace('tabbar')
// 解析颜色
const [bgColorClass, bgColorStyle] = useComponentColor(
toRef(props, 'bgColor'),
'bg'
)
// tabbar对应的类
const tabbarClass = computed<string>(() => {
const cls: string[] = [ns.b()]
// 是否固定在底部
if (props.fixed) cls.push(ns.m('fixed'))
// 是否预留安全距离
if (props.safeAreaInsetBottom) cls.push('tn-u-safe-area')
// 是否有顶部阴影
if (props.topShadow) cls.push(ns.m('top-shadow'))
return cls.join(' ')
})
// tabbar对应的样式
const tabbarStyle = computed<CSSProperties>(() => {
const style: CSSProperties = {}
// 设置zIndex
if (props.zIndex) style.zIndex = props.zIndex
// 设置高度
if (props.height) style.height = formatDomSizeValue(props.height)
return style
})
// 背景颜色对应的类
const bgClass = computed<string>(() => {
const cls: string[] = [ns.e('bg')]
// 设置背景颜色
if (bgColorClass.value && !props.frosted) cls.push(bgColorClass.value)
// 设置毛玻璃效果
// #ifndef MP-ALIPAY
if (props.frosted) cls.push(ns.em('bg', 'frosted'))
// #endif
return cls.join(' ')
})
// 背景颜色对应的样式
const bgStyle = computed<CSSProperties>(() => {
const style: CSSProperties = {}
// 设置zIndex
if (props.zIndex) style.zIndex = props.zIndex - 1
// 设置背景颜色
if (!bgColorClass.value)
style.backgroundColor = bgColorStyle.value || 'var(--tn-color-white)'
// #ifndef MP-ALIPAY
if (props.frosted)
style.backgroundColor = bgColorStyle.value || 'rgba(255, 255, 255, 0.5)'
// #endif
return style
})
// 占位容器对应的样式
const placeholderStyle = computed<CSSProperties>(() => {
const style: CSSProperties = {}
// 设置zIndex
if (props.zIndex) style.zIndex = props.zIndex - 2
// 设置高度
if (props.height) style.height = formatDomSizeValue(props.height)
return style
})
// 突起按钮对应类和样式
const bulgeClass = computed<string>(() => {
const cls: string[] = [ns.e('bulge')]
if (props.topShadow) {
cls.push(ns.em('bulge', 'top-shadow'))
}
// 设置背景颜色
if (bgColorClass.value && !props.frosted) cls.push(bgColorClass.value)
// 设置毛玻璃效果
// #ifndef MP-ALIPAY
if (props.frosted) cls.push(ns.em('bulge', 'frosted'))
// #endif
return cls.join(' ')
})
const bulgeStyle = computed<CSSProperties>(() => {
const style: CSSProperties = {}
// 设置zIndex
if (props.zIndex) style.zIndex = props.zIndex - 1
else style.zIndex = 'inherit'
// 设置背景颜色
if (!bgColorClass.value)
style.backgroundColor = bgColorStyle.value || 'var(--tn-color-white)'
// #ifndef MP-ALIPAY
if (props.frosted)
style.backgroundColor = bgColorStyle.value || 'rgba(255, 255, 255, 0.5)'
// #endif
return style
})
return {
ns,
tabbarClass,
tabbarStyle,
bgClass,
bgStyle,
placeholderStyle,
bulgeClass,
bulgeStyle,
}
}
@@ -0,0 +1,178 @@
import { computed, inject, toRef } from 'vue'
import { tabbarContextKey } from '../../../../tokens'
import { useComponentColor, useNamespace } from '../../../../hooks'
import { formatDomSizeValue } from '../../../../utils'
import type { CSSProperties, Ref } from 'vue'
import type { TabbarItemProps } from '../tabbar-item'
import type { TabbarItemRect } from '../types'
type TabbarItemElementStyleValue = (type: 'icon' | 'text') => CSSProperties
type TabbarItemBulgeElementStyleValue = (
rectInfo: TabbarItemRect
) => CSSProperties
export const useTabbarItemCustomStyle = (
props: TabbarItemProps,
isActive: Ref<boolean>
) => {
const ns = useNamespace('tabbar-item')
const tabbarContext = inject(tabbarContextKey)
const activeColor = computed<string | undefined>(
() => props.activeColor || tabbarContext?.activeColor
)
const inactiveColor = computed<string | undefined>(
() => props.inactiveColor || tabbarContext?.inactiveColor
)
// 解析颜色
const [inactiveColorClass, inactiveColorStyle] = useComponentColor(
inactiveColor,
'text'
)
const [activeColorClass, activeColorStyle] = useComponentColor(
activeColor,
'text'
)
const [inactiveBgClass, inactiveBgStyle] = useComponentColor(
inactiveColor,
'bg'
)
const [activebgClass, activebgStyle] = useComponentColor(activeColor, 'bg')
const [bulgeBgClass, bulgeBgStyle] = useComponentColor(
toRef(props, 'bulgeBgColor'),
'bg'
)
const [bulgeTextColorClass, bulgeTextColorStyle] = useComponentColor(
toRef(props, 'bulgeTextColor'),
'text'
)
// 文字尺寸
const fontSize = computed<string>(() =>
formatDomSizeValue(props.fontSize || tabbarContext?.fontSize || '')
)
// tabbarItem对应的类
const tabbarItemClass = computed<string>(() => {
const cls: string[] = [ns.b()]
// 设置文字,图标颜色
if (isActive.value) {
if (activeColorClass.value) cls.push(activeColorClass.value)
} else {
if (inactiveColorClass.value) cls.push(inactiveColorClass.value)
}
// 判断是否只有图标或者文字
if (
(props.icon && props.activeIcon && !props.text) ||
(props.text && !props.icon && !props.activeIcon)
)
cls.push(ns.is('only-element'))
// 选中的状态
if (isActive.value) cls.push(ns.is('active'))
// 点击切换是是否有动画
if (tabbarContext?.switchAnimation) cls.push(ns.is('animation'))
return cls.join(' ')
})
// tabbarItem对应的样式
const tabbarItemStyle = computed<CSSProperties>(() => {
const style: CSSProperties = {}
// 设置文字,图标颜色
if (isActive.value) {
if (!activeColorClass.value)
style.color = activeColorStyle.value || 'var(--tn-color-primary)'
} else {
if (!inactiveColorClass.value)
style.color = inactiveColorStyle.value || 'var(--tn-color-gray)'
}
return style
})
// tabbarItem元素对应的样式
const tabbarItemElementStyle = computed<TabbarItemElementStyleValue>(() => {
return (type: 'icon' | 'text') => {
const style: CSSProperties = {}
// 设置文字尺寸
if (type === 'text') {
if (fontSize.value) style.fontSize = fontSize.value
}
return style
}
})
// 凸起按钮对应的类
const bulgeClass = computed<string>(() => {
const cls: string[] = [ns.e('bulge')]
// 设置背景颜色
if (props.bulgeBgColor) {
if (bulgeBgClass.value) cls.push(bulgeBgClass.value)
} else {
if (isActive.value) {
if (activebgClass.value) cls.push(activebgClass.value)
} else {
if (inactiveBgClass.value) cls.push(inactiveBgClass.value)
}
}
// 设置文字颜色
if (bulgeTextColorClass.value) cls.push(bulgeTextColorClass.value)
return cls.join(' ')
})
// 凸起按钮对应的样式
const bulgeStyle = computed<TabbarItemBulgeElementStyleValue>(() => {
return (rectInfo: TabbarItemRect) => {
const style: CSSProperties = {}
// 设置容器信息
let width = rectInfo.width
if (rectInfo?.maxWidth) {
width = rectInfo.maxWidth
}
style.width = `${width * 0.5}px`
style.height = style.width
style.top = `-${width * 0.16}px`
// 设置背景
if (props.bulgeBgColor) {
if (!bulgeBgClass.value) style.backgroundColor = bulgeBgStyle.value
} else {
if (isActive.value) {
if (!activebgClass.value) {
style.backgroundColor =
activebgStyle.value || 'var(--tn-color-primary)'
}
} else {
if (!inactiveBgClass.value) {
style.backgroundColor =
inactiveBgStyle.value || 'var(--tn-color-gray)'
}
}
}
// 设置文字颜色
if (bulgeTextColorStyle.value) style.color = bulgeTextColorStyle.value
return style
}
})
return {
ns,
tabbarItemClass,
tabbarItemStyle,
tabbarItemElementStyle,
bulgeClass,
bulgeStyle,
}
}
@@ -0,0 +1,116 @@
import {
computed,
getCurrentInstance,
inject,
nextTick,
onMounted,
onUnmounted,
ref,
} from 'vue'
import { tabbarContextKey } from '../../../../tokens'
import { useSelectorQuery } from '../../../../hooks'
import { debugWarn, generateId } from '../../../../utils'
import type { SetupContext } from 'vue'
import type { TabbarItemEmits, TabbarItemProps } from '../tabbar-item'
import type { TabbarItemRect } from '../types'
export const useTabbarItem = (
props: TabbarItemProps,
emit: SetupContext<TabbarItemEmits>['emit']
) => {
const tabbarContext = inject(tabbarContextKey)
// instance
const instance = getCurrentInstance()!
if (!tabbarContext) {
debugWarn('TnTabbarItem', 'TnTabbarItem必须在TnTabbar中使用')
}
if (!instance) {
debugWarn('TnTabbarItem', 'TnTabbarItem必须在setup中使用')
}
const uid = instance.uid || generateId()
const itemId = `tti-${uid}`
const { getSelectorNodeInfo } = useSelectorQuery(instance)
// 当前Item是否已激活
const isActive = computed<boolean>(() => tabbarContext?.activeUid === uid)
// 图标尺寸
const iconSize = computed<string>(
() => props.iconSize || tabbarContext?.iconSize || ''
)
// 是否有角标
const hasBadge = computed<boolean>(() => !!props.badge)
// item点击事件
const itemClick = () => {
if (isActive.value || props.disabled) return
tabbarContext?.setActiveItem(uid)
emit('click')
}
const itemRectInfo = ref<TabbarItemRect>({
width: 0,
height: 0,
left: 0,
})
let initRectCount = 0
// 获取容器节点信息
const getItemRectInfo = async () => {
try {
const rectInfo = await getSelectorNodeInfo(`#${itemId}`)
if (rectInfo.width && rectInfo.width < 30) {
throw new Error('获取TabbarItem节点宽度失败')
}
initRectCount = 0
itemRectInfo.value.width = rectInfo.width || 0
itemRectInfo.value.height = rectInfo.height || 0
itemRectInfo.value.left = rectInfo.left || 0
if (itemRectInfo.value.width > 80) {
itemRectInfo.value.maxWidth = 80
}
tabbarContext?.setBulgeCircle(itemRectInfo.value)
} catch (err) {
if (initRectCount > 10) {
initRectCount = 0
debugWarn('TnTabbarItem', `获取TabbarItem节点信息失败: ${err}`)
return
}
initRectCount++
setTimeout(() => {
getItemRectInfo()
}, 150)
}
}
tabbarContext?.addItem({
uid,
name: props.name,
})
onMounted(() => {
nextTick(() => {
if (props.bulge) getItemRectInfo()
})
})
onUnmounted(() => {
tabbarContext?.removeItem(uid)
})
return {
itemId,
isActive,
itemRectInfo,
iconSize,
hasBadge,
itemClick,
}
}
@@ -0,0 +1,184 @@
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 * 0.75
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,
}
}