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,2 @@
export * from './navbar-custom'
export * from './use-navbar'
@@ -0,0 +1,216 @@
import { computed, toRef } from 'vue'
import {
useComponentColor,
useNamespace,
useUniAppSystemRectInfo,
} from '../../../../hooks'
import { formatDomSizeValue } from '../../../../utils'
import type { CSSProperties, Ref } from 'vue'
import type { NavbarProps } from '../navbar'
import type { NavbackButtonType } from '../types'
export const useNavbarCustomStyle = (
props: NavbarProps,
backButtonType: Ref<NavbackButtonType>,
hasRightOperation: Ref<boolean>
) => {
const ns = useNamespace('navbar')
const backNs = useNamespace('navbar-back')
const { navBarInfo, navBarBoundingInfo } = useUniAppSystemRectInfo()
// 解析颜色
const [bgColorClass, bgColorStyle] = useComponentColor(
toRef(props, 'bgColor'),
'bg'
)
const [textColorClass, textColorStyle] = useComponentColor(
toRef(props, 'textColor'),
'text'
)
// 状态栏的高度
const navbarHeight = computed<string>(() =>
props.height ? formatDomSizeValue(props.height) : `${navBarInfo.height}px`
)
// 导航栏对应的类
const navbarClass = computed<string>(() => {
const cls: string[] = [ns.b()]
// 固定在顶部
if (props.fixed) cls.push(ns.m('fixed'))
// 底部阴影
if (props.bottomShadow) cls.push('tn-shadow')
// 设置文字颜色
if (textColorClass.value) cls.push(textColorClass.value)
return cls.join(' ')
})
// 导航栏对应的样式
const navbarStyle = computed<CSSProperties>(() => {
const style: CSSProperties = {}
// 设置导航栏的高度
style.height = navbarHeight.value
// 设置导航栏的zIndex
if (props.zIndex) style.zIndex = props.zIndex
// 设置透明度
if (props?.opacity !== undefined) style.opacity = props.opacity
// 设置文字颜色
if (textColorStyle.value) {
style.color = textColorStyle.value
} else if (!bgColorClass.value && !textColorClass.value) {
style.color = 'var(--tn-text-color-primary)'
}
return style
})
// 背景颜色所属类
const navbarBgClass = 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 navbarBgStyle = computed<CSSProperties>(() => {
const style: CSSProperties = {}
if (props.zIndex) style.zIndex = props.zIndex - 2
// 设置背景颜色
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 navbarPlaceholderStyle = computed<CSSProperties>(() => {
const style: CSSProperties = {}
if (props.zIndex) style.zIndex = props.zIndex - 2
// 设置导航栏的高度
style.height = navbarHeight.value
return style
})
// 导航栏容器区域对应样式
const navbarWrapperStyle = computed<CSSProperties>(() => {
const style: CSSProperties = {}
if (props.zIndex) style.zIndex = props.zIndex
// 如果不是自定义高度则设置top值
if (!props.height) {
style.top = `${navBarInfo.statusHeight}px`
style.height = `${navBarInfo.height - navBarInfo.statusHeight}px`
}
return style
})
// 返回按钮对应的样式
const backStyle = computed<CSSProperties>(() => {
const style: CSSProperties = {}
if (props.zIndex) style.zIndex = props.zIndex
// 设置返回按钮的宽高
style.width = `${navBarBoundingInfo.width}px`
style.height = `${navBarBoundingInfo.height}px`
// 距离左边的距离
style.left = `${navBarBoundingInfo.marginRight}px`
return style
})
// 内容区域对应的样式
const contentStyle = computed<CSSProperties>(() => {
const style: CSSProperties = {}
if (props.zIndex) style.zIndex = props.zIndex - 1
// 设置高度
style.height = `${navBarInfo.height - navBarInfo.statusHeight}px`
if (props.height) style.height = formatDomSizeValue(props.height)
// 判断是否存在返回区域
if (backButtonType.value !== 'none') {
style.paddingLeft = `${
navBarBoundingInfo.width + navBarBoundingInfo.marginRight
}px`
}
// 如果设置了右边操作区域的宽度则以当前区域为准设置右边安全区域
if (hasRightOperation.value && props.rightOperationWidth) {
style.paddingRight = formatDomSizeValue(props.rightOperationWidth)
} else if (props.safeAreaInsetRight || hasRightOperation.value) {
// 判断是否预留右边胶囊安全距离
style.paddingRight = `${
navBarBoundingInfo.width + navBarBoundingInfo.marginRight
}px`
}
return style
})
// 右边操作区域对应的样式
const rightOperationStyle = computed<CSSProperties>(() => {
const style: CSSProperties = {}
// 设置最大宽度,防止出现右边操作区域的内容过多导致右边操作区域的宽度过大
style.maxWidth = `${navBarBoundingInfo.width}px`
if (props.zIndex) style.zIndex = props.zIndex
// 设置高度
style.height = `${navBarInfo.height - navBarInfo.statusHeight}px`
if (props.height) style.height = formatDomSizeValue(props.height)
// 设置宽度
if (props.rightOperationWidth)
style.width = formatDomSizeValue(props.rightOperationWidth)
return style
})
return {
ns,
backNs,
navBarInfo,
navbarClass,
navbarStyle,
navbarBgClass,
navbarBgStyle,
navbarPlaceholderStyle,
navbarWrapperStyle,
backStyle,
contentStyle,
rightOperationStyle,
}
}
@@ -0,0 +1,104 @@
import { computed, getCurrentInstance } from 'vue'
import {
debugWarn,
isBoolean,
isPromise,
throwError,
tnNavBack,
tnNavPage,
} from '../../../../utils'
import type { NavbarProps } from '../navbar'
import type { NavbackButtonType } from '../types'
export const useNavbar = (props: NavbarProps) => {
const { slots } = getCurrentInstance()!
// 返回按钮类型
const navbackButtonType = computed<NavbackButtonType>(() => {
if (slots?.back) return 'custom'
if (props?.backText) return 'text'
if (!!props?.backIcon && !!props?.homeIcon) return 'multi'
else if (!!props?.backIcon || !!props?.homeIcon) return 'single'
return 'none'
})
// 是否有右边操作区域
const hasRightOperation = computed<boolean>(() => !!slots?.right)
// 点击返回按钮
const clickBackEvent = () => {
const { beforeBack } = props
if (!beforeBack) {
tnNavBack(props.indexUrl)
return
}
const shouldBack = beforeBack()
const isPromiseOrBool = [
isPromise(shouldBack),
isBoolean(shouldBack),
].includes(true)
if (!isPromiseOrBool) {
throwError(
'TnNavbar',
'beforeBack 返回值必须是 Promise 或者 Boolean 类型'
)
}
if (isPromise(shouldBack)) {
shouldBack
.then((res) => {
if (res) tnNavBack(props.indexUrl)
})
.catch((err) => {
debugWarn('TnNavbar', `beforeBack 函数执行出错: ${err}`)
})
} else {
if (shouldBack) tnNavBack(props.indexUrl)
}
}
// 点击返回首页按钮
const clickHomeEvent = () => {
const { beforeHome } = props
if (!beforeHome) {
tnNavPage(props.indexUrl, 'reLaunch')
return
}
const shouldBack = beforeHome()
const isPromiseOrBool = [
isPromise(shouldBack),
isBoolean(shouldBack),
].includes(true)
if (!isPromiseOrBool) {
throwError(
'TnNavbar',
'beforeHome 返回值必须是 Promise 或者 Boolean 类型'
)
}
if (isPromise(shouldBack)) {
shouldBack
.then((res) => {
if (res) tnNavPage(props.indexUrl, 'reLaunch')
})
.catch((err) => {
debugWarn('TnNavbar', `beforeHome 函数执行出错: ${err}`)
})
} else {
if (shouldBack) tnNavPage(props.indexUrl, 'reLaunch')
}
}
return {
navbackButtonType,
hasRightOperation,
clickBackEvent,
clickHomeEvent,
}
}
@@ -0,0 +1,3 @@
import type Navbar from './navbar.vue'
export type TnNavbarInstance = InstanceType<typeof Navbar>
+127
View File
@@ -0,0 +1,127 @@
import { ZIndex } from '../../../constants'
import { buildProps, definePropType } from '../../../utils'
import type { ExtractPropTypes } from 'vue'
export type NavbarBeforeOperationFunc = () => Promise<boolean> | boolean
export type NavbarRectInfo = {
statusHeight: number
height: number
}
export const navBarProps = buildProps({
/**
* @description 导航栏高度,如果不传递这个值则会根据系统自动计算
*/
height: String,
/**
* @description 导航栏背景色,如果需要设置毛玻璃的背景颜色,只能传递rgba的颜色值
*/
bgColor: String,
/**
* @description 导航栏字体颜色
*/
textColor: String,
/**
* @description 开启毛玻璃效果
*/
frosted: Boolean,
/**
* @description 导航栏透明度,有效值为0~1
*/
opacity: {
type: Number,
default: 1,
},
/**
* @description 返回按钮图标
*/
backIcon: {
type: String,
default: 'left',
},
/**
* @description 返回按钮文字
*/
backText: String,
/**
* @description 返回首页图标
*/
homeIcon: {
type: String,
default: 'home-capsule-fill',
},
/**
* @description 是否显示底部阴影
*/
bottomShadow: {
type: Boolean,
default: true,
},
/**
* @description 是否预留右边胶囊安全距离
*/
safeAreaInsetRight: {
type: Boolean,
default: true,
},
/**
* @description 居中显示内容
*/
center: {
type: Boolean,
default: true,
},
/**
* @description 右边操作区域的宽度
*/
rightOperationWidth: String,
/**
* @description 返回前回调
*/
beforeBack: {
type: definePropType<NavbarBeforeOperationFunc>(Function),
},
/**
* @description 返回首页前回调
*/
beforeHome: {
type: definePropType<NavbarBeforeOperationFunc>(Function),
},
/**
* @description 首页地址
*/
indexUrl: {
type: String,
default: '/pages/index/index',
},
/**
* @description 是否固定在顶部
*/
fixed: Boolean,
/**
* @description 在固定模式下是否开启占位
*/
placeholder: {
type: Boolean,
default: true,
},
/**
* @description ZIndex
*/
zIndex: {
type: Number,
default: ZIndex.navbar,
},
})
export const navbarEmits = {
/**
* @description navbar初始化完成
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
initFinish: (info: NavbarRectInfo) => true,
}
export type NavbarProps = ExtractPropTypes<typeof navBarProps>
+130
View File
@@ -0,0 +1,130 @@
<script lang="ts" setup>
import { nextTick, onMounted } from 'vue'
import TnIcon from '../../icon/src/icon.vue'
import { navBarProps, navbarEmits } from './navbar'
import { useNavbar, useNavbarCustomStyle } from './composables'
const props = defineProps(navBarProps)
const emits = defineEmits(navbarEmits)
const { navbackButtonType, hasRightOperation, clickBackEvent, clickHomeEvent } =
useNavbar(props)
const {
ns,
backNs,
navBarInfo,
navbarClass,
navbarStyle,
navbarBgClass,
navbarBgStyle,
navbarPlaceholderStyle,
navbarWrapperStyle,
backStyle,
contentStyle,
rightOperationStyle,
} = useNavbarCustomStyle(props, navbackButtonType, hasRightOperation)
// 动态调用是返回首页还是返回上一页
const backEvent = (type: 'back' | 'home') => {
if (type === 'back') {
clickBackEvent()
} else {
clickHomeEvent()
}
}
// 组件初始化完成
onMounted(() => {
nextTick(() => {
emits('initFinish', navBarInfo)
})
})
</script>
<template>
<view :class="[navbarClass]" :style="navbarStyle">
<!-- 背景颜色 -->
<view :class="navbarBgClass" :style="navbarBgStyle" />
<!-- 容器 -->
<view :class="[ns.e('wrapper')]" :style="navbarWrapperStyle">
<!-- 返回按钮区域 -->
<view
v-if="navbackButtonType !== 'none'"
:class="[backNs.b()]"
:style="backStyle"
>
<slot name="back">
<!-- 双图标 -->
<view
v-if="navbackButtonType === 'multi'"
:class="[backNs.e('multi')]"
>
<view :class="[backNs.e('multi__item')]" @tap.stop="clickBackEvent">
<TnIcon :name="props.backIcon" />
</view>
<view :class="[backNs.e('multi__item')]" @tap.stop="clickHomeEvent">
<TnIcon :name="props.homeIcon" />
</view>
</view>
<!-- 单图标 -->
<view
v-if="navbackButtonType === 'single'"
:class="[backNs.e('single')]"
@tap.stop="backEvent(props.backIcon ? 'back' : 'home')"
>
<TnIcon v-if="props.backIcon" :name="props.backIcon" />
<TnIcon v-else-if="props.homeIcon" :name="props.homeIcon" />
</view>
<!-- 文字返回 -->
<view
v-if="navbackButtonType === 'text'"
:class="[backNs.e('text')]"
@tap.stop="clickBackEvent"
>
<view :class="[backNs.e('text__icon')]">
<TnIcon :name="backIcon || 'left'" />
</view>
<view class="tn-text-ellipsis-1" :class="[backNs.e('text__value')]">
{{ backText || '返回' }}
</view>
</view>
</slot>
</view>
<!-- 内容数据 -->
<view
v-if="$slots.default"
:class="[
ns.e('content'),
{
[ns.em('content', 'center')]: props.center,
},
]"
:style="contentStyle"
>
<slot />
</view>
<!-- 右边操作区域 -->
<view
v-if="$slots.right"
:class="[ns.e('right-operation')]"
:style="rightOperationStyle"
>
<slot name="right" />
</view>
</view>
</view>
<!-- 固定之后会导致容器塌陷 -->
<view
v-if="fixed && placeholder"
:class="[ns.e('placeholder')]"
:style="navbarPlaceholderStyle"
/>
</template>
<style lang="scss" scoped>
@import '../../../theme-chalk/src/navbar.scss';
</style>
+1
View File
@@ -0,0 +1 @@
export type NavbackButtonType = 'single' | 'multi' | 'text' | 'custom' | 'none'