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 './subsection-custom'
export * from './subsection-item-custom'
export * from './use-subsection'
export * from './use-subsection-item'
@@ -0,0 +1,117 @@
import { computed, toRef, watch } from 'vue'
import {
useComponentColor,
useComponentSize,
useNamespace,
} from '../../../../hooks'
import { formatDomSizeValue } from '../../../../utils'
import type { CSSProperties, Ref } from 'vue'
import type { SubsectionProps } from '../subsection'
import type { SubsectionSliderRect } from '../types'
export const useSubsectionCustomStyle = (
props: SubsectionProps,
activeColor: Ref<string>,
sliderRectInfo: Ref<SubsectionSliderRect>
) => {
const ns = useNamespace('subsection')
// 解析颜色
const [activeBgColorClass, activeBgColorStyle, updateActiveBgColor] =
useComponentColor(activeColor, 'bg')
const [activeBorderColorClass, activeBorderColorStyle] = useComponentColor(
toRef(props, 'activeColor'),
'border'
)
watch(
() => activeColor.value,
(val) => {
updateActiveBgColor(val)
}
)
// 解析尺寸
const { sizeType } = useComponentSize(props.size)
// 分段器的类
const subsectionClass = computed<string>(() => {
const cls: string[] = [ns.b(), ns.m(props.mode)]
// 设置尺寸
if (props.size && sizeType.value === 'inner') cls.push(ns.m(props.size))
// 如果是默认模式,设置激活颜色的边框
if (props.mode === 'default') {
if (activeBorderColorClass.value) cls.push(activeBorderColorClass.value)
}
return cls.join(' ')
})
// 分段器的样式
const subsectionStyle = computed<CSSProperties>(() => {
const style: CSSProperties = {}
// 如果是按钮模式,则设置背景颜色
if (props.mode === 'button') {
style.backgroundColor = 'var(--tn-color-gray-light)'
}
// 如果是默认模式,设置激活颜色的边框
if (props.mode === 'default') {
if (!activeBorderColorClass.value)
style.borderColor =
activeBorderColorStyle.value || 'var(--tn-color-primary)'
}
// 设置尺寸
if (props.size && sizeType.value === 'custom')
style.fontSize = formatDomSizeValue(props.size)
// 设置圆角
if (props.radius) {
style.borderRadius = formatDomSizeValue(props.radius)
}
return style
})
// 滑块的类
const subsectionSliderClass = computed<string>(() => {
const cls: string[] = [ns.e('slider'), props.mode]
if (props.mode === 'default') {
if (activeBgColorClass.value) cls.push(activeBgColorClass.value)
}
return cls.join(' ')
})
// 滑块的样式
const subsectionSliderStyle = computed<CSSProperties>(() => {
const style: CSSProperties = {}
if (props.mode === 'default') {
if (!activeBgColorClass.value)
style.backgroundColor =
activeBgColorStyle.value || 'var(--tn-color-primary)'
} else {
style.backgroundColor = 'var(--tn-color-white)'
}
// 滑动的位置
if (sliderRectInfo.value) {
style.left = `${sliderRectInfo.value.left}px`
style.width = `${sliderRectInfo.value.width}px`
}
return style
})
return {
ns,
subsectionClass,
subsectionStyle,
subsectionSliderClass,
subsectionSliderStyle,
}
}
@@ -0,0 +1,94 @@
import { computed, inject, toRef } from 'vue'
import { subsectionContextKey } from '../../../../tokens'
import { useComponentColor, useNamespace } from '../../../../hooks'
import {
isEmptyDoubleVariableInDefault,
isEmptyVariableInDefault,
} from '../../../../utils'
import type { CSSProperties, Ref } from 'vue'
import type { SubsectionMode } from '../subsection'
import type { SubsectionItemProps } from '../subsection-item'
export const useSubsectionItemCustomStyle = (
props: SubsectionItemProps,
active: Ref<boolean>
) => {
const subsectionContext = inject(subsectionContextKey)
const ns = useNamespace('subsection-item')
const activeColor = computed<string>(() =>
isEmptyDoubleVariableInDefault(
props.activeColor,
subsectionContext?.activeColor,
''
)
)
const subsectionMode = computed<SubsectionMode>(() =>
isEmptyVariableInDefault(subsectionContext?.mode, 'default')
)
const disabled = computed<boolean>(
() => subsectionContext?.disabled || props.disabled || false
)
// 解析颜色
const [activeTextColorClass, activeTextColorStyle] = useComponentColor(
activeColor,
'text'
)
const [activeBorderColorClass, activeBorderColorStyle] = useComponentColor(
toRef(subsectionContext!, 'activeColor'),
'border'
)
// 分段器Item的类
const subsectionItemClass = computed<string>(() => {
const cls: string[] = [
ns.b(),
ns.m(subsectionMode.value),
ns.is('active', active.value),
ns.is('disabled', disabled.value),
]
if (subsectionMode.value === 'default') {
if (activeBorderColorClass.value) cls.push(activeBorderColorClass.value)
}
if (subsectionMode.value === 'button') {
if (active.value) {
if (activeTextColorClass.value) cls.push(activeTextColorClass.value)
}
}
return cls.join(' ')
})
// 分段器Item的样式
const subsectionItemStyle = computed<CSSProperties>(() => {
const style: CSSProperties = {}
if (subsectionMode.value === 'default') {
if (!activeBorderColorClass.value)
style.borderColor =
activeBorderColorStyle.value || 'var(--tn-color-primary)'
if (active.value) {
style.color = 'var(--tn-color-white)'
}
}
if (subsectionMode.value === 'button') {
if (active.value) {
if (!activeTextColorClass.value)
style.color = activeTextColorStyle.value || 'var(--tn-color-primary)'
}
}
return style
})
return {
ns,
subsectionItemClass,
subsectionItemStyle,
}
}
@@ -0,0 +1,97 @@
import {
computed,
getCurrentInstance,
inject,
nextTick,
onMounted,
onUnmounted,
} from 'vue'
import { subsectionContextKey } from '../../../../tokens'
import { useSelectorQuery } from '../../../../hooks'
import { debugWarn, generateId } from '../../../../utils'
import type { SetupContext } from 'vue'
import type {
SubsectionItemEmits,
SubsectionItemProps,
} from '../subsection-item'
import type { SubsectionItemRect } from '../types'
export const useSubsectionItem = (
props: SubsectionItemProps,
emits: SetupContext<SubsectionItemEmits>['emit']
) => {
const instance = getCurrentInstance()
if (!instance) {
debugWarn('TnSubsectionItem', '请在 setup 中使用 useSubsectionItem')
}
const { uid } = instance!
const subsectionContext = inject(subsectionContextKey)
if (!subsectionContext) {
debugWarn('TnSubsectionItem', '请在 TnSubsection 中使用 TnSubsectionItem')
}
const componentId = `tsi-${generateId()}`
const { getSelectorNodeInfo } = useSelectorQuery(instance)
// 标记当前item是否激活
const active = computed(() => subsectionContext?.activeUid === uid)
// 获取节点信息
const componentRectInfo: SubsectionItemRect = {
left: 0,
width: 0,
}
let initCount = 0
const getSubsectionItemRectInfo = async () => {
try {
const rectInfo = await getSelectorNodeInfo(`#${componentId}`)
initCount = 0
componentRectInfo.left = rectInfo.left || 0
componentRectInfo.width = rectInfo.width || 0
// 添加item信息
subsectionContext?.addItem({
...props,
uid,
element: componentRectInfo,
})
} catch (err) {
if (initCount > 10) {
initCount = 0
debugWarn('TnSubsectionItem', `获取节点信息失败: ${err}`)
return
}
initCount++
setTimeout(() => {
getSubsectionItemRectInfo()
}, 150)
}
}
// item点击事件
const itemClickEvent = () => {
if (subsectionContext?.disabled || props.disabled) return
subsectionContext?.setActiveItem(uid)
emits('click', props.title!)
}
onMounted(() => {
nextTick(() => {
getSubsectionItemRectInfo()
})
})
onUnmounted(() => {
subsectionContext?.removeItem(uid)
})
return {
componentId,
active,
itemClickEvent,
}
}
@@ -0,0 +1,181 @@
import {
getCurrentInstance,
nextTick,
onMounted,
provide,
reactive,
ref,
toRefs,
watch,
} from 'vue'
import { subsectionContextKey } from '../../../../tokens'
import { useOrderedChildren, useSelectorQuery } from '../../../../hooks'
import {
debugWarn,
generateId,
isEmptyDoubleVariableInDefault,
} from '../../../../utils'
import type { SetupContext } from 'vue'
import type { SubsectionItemContext } from '../../../../tokens'
import type { SubsectionEmits, SubsectionProps } from '../subsection'
import type { SubsectionSliderRect } from '../types'
export const useSubsection = (
props: SubsectionProps,
emits: SetupContext<SubsectionEmits>['emit']
) => {
const instance = getCurrentInstance()
if (!instance) {
debugWarn('TnSubsection', '请在 setup 中使用 useSubsection')
}
const {
children: items,
addChild,
removeChild: removeItem,
} = useOrderedChildren<SubsectionItemContext>()
const componentId = `ts-${generateId()}`
const { getSelectorNodeInfo } = useSelectorQuery(instance)
// 当前激活的item的uid
const activeUid = ref<number>(0)
// 当前选中的Index
const currentActiveIndex = ref<number>(0)
// 添加ChildrenItem
const addItem = (item: SubsectionItemContext) => {
if (props.modelValue !== undefined) {
if (props.modelValue === items.value.length) {
currentActiveIndex.value = props.modelValue
activeUid.value = item.uid
// #ifndef APP-PLUS || MP-ALIPAY
nextTick(() => {
updateSliderRectInfo(item)
})
// #endif
// #ifdef APP-PLUS || MP-ALIPAY
setTimeout(() => {
updateSliderRectInfo(item)
}, 50)
// #endif
}
}
addChild(item)
}
// 根据当前激活的Index更新activeUid
const updateActiveUidWithIndex = () => {
nextTick(() => {
const item = items.value[currentActiveIndex.value]
if (item) {
activeUid.value = item.uid
updateSliderRectInfo(item)
}
})
}
let innerUpdate = false
watch(
() => props.modelValue,
(val) => {
if (innerUpdate) {
innerUpdate = false
return
}
if (val === undefined || val > items.value.length - 1) {
currentActiveIndex.value = 0
} else {
currentActiveIndex.value = val
}
updateActiveUidWithIndex()
}
)
// 获取组件节点信息
let componentLeft = 0
let initCount = 0
const getComponentRectInfo = async () => {
try {
const rectInfo = await getSelectorNodeInfo(`#${componentId}`)
initCount = 0
componentLeft = rectInfo.left || 0
} catch (err) {
if (initCount > 10) {
initCount = 0
debugWarn('TnSubsection', `获取组件节点信息失败: ${err}`)
return
}
initCount++
setTimeout(() => {
getComponentRectInfo()
}, 150)
}
}
// 设置当前被点击的activeUID
const setActiveItem = (uid: number) => {
const index = items.value.findIndex((item) => item.uid === uid)
if (index !== -1) {
innerUpdate = true
activeUid.value = uid
currentActiveIndex.value = index
updateSliderRectInfo(items.value[index])
emits('update:modelValue', index)
nextTick(() => {
emits('change', index)
})
}
}
// 更新滑动样式容器信息
const activeColor = ref<string>('')
const sliderRectInfo = ref<SubsectionSliderRect>({
left: 0,
width: 0,
})
const updateSliderRectInfo = (item: SubsectionItemContext) => {
if (!item) return
sliderRectInfo.value.left = item.element.left - componentLeft
sliderRectInfo.value.width = item.element.width
if (props.mode === 'default') {
sliderRectInfo.value.left -= 1
if (props.modelValue === items.value.length - 1) {
sliderRectInfo.value.width += 2
}
}
activeColor.value = isEmptyDoubleVariableInDefault(
item.activeColor,
props.activeColor,
''
)
}
onMounted(() => {
nextTick(() => {
getComponentRectInfo()
})
})
provide(
subsectionContextKey,
reactive({
...toRefs(props),
items,
addItem,
removeItem,
activeUid,
setActiveItem,
})
)
return {
componentId,
activeColor,
sliderRectInfo,
}
}
@@ -0,0 +1,5 @@
import type Subsection from './subsection.vue'
import type SubsectionItem from './subsection-item.vue'
export type TnSubsectionInstance = InstanceType<typeof Subsection>
export type TnSubsectionItemInstance = InstanceType<typeof SubsectionItem>
@@ -0,0 +1,22 @@
import { buildProps, isString } from '../../../utils'
import { subsectionBaseProps } from '../../base/common-props/subsection'
import type { ExtractPropTypes } from 'vue'
export const subsectionItemProps = buildProps({
...subsectionBaseProps,
/**
* @description 标题
*/
title: String,
})
export const subsectionItemEmits = {
/**
* @description 点击事件
*/
click: (title: string) => isString(title),
}
export type SubsectionItemProps = ExtractPropTypes<typeof subsectionItemProps>
export type SubsectionItemEmits = typeof subsectionItemEmits
@@ -0,0 +1,39 @@
<script lang="ts" setup>
import { subsectionItemEmits, subsectionItemProps } from './subsection-item'
import { useSubsectionItem, useSubsectionItemCustomStyle } from './composables'
const props = defineProps(subsectionItemProps)
const emits = defineEmits(subsectionItemEmits)
const { componentId, active, itemClickEvent } = useSubsectionItem(props, emits)
const { subsectionItemClass, subsectionItemStyle } =
useSubsectionItemCustomStyle(props, active)
</script>
// #ifdef MP-WEIXIN
<script lang="ts">
export default {
options: {
// 在微信小程序中将组件节点渲染为虚拟节点,更加接近Vue组件的表现(不会出现shadow节点下再去创建元素)
virtualHost: true,
},
}
</script>
// #endif
<template>
<view
:id="componentId"
:class="[subsectionItemClass]"
:style="subsectionItemStyle"
@tap.stop="itemClickEvent"
>
<slot>
{{ title }}
</slot>
</view>
</template>
<style lang="scss" scoped>
@import '../../../theme-chalk/src/subsection-item.scss';
</style>
@@ -0,0 +1,47 @@
import { CHANGE_EVENT, UPDATE_MODEL_EVENT } from '../../../constants'
import { buildProps, isNumber } from '../../../utils'
import { subsectionBaseProps } from '../../base/common-props/subsection'
import type { ExtractPropTypes } from 'vue'
export const subsectionMode = ['default', 'button'] as const
export const subsectionProps = buildProps({
...subsectionBaseProps,
/**
* @description 当前选中item索引的绑定值
*/
modelValue: {
type: Number,
default: 0,
},
/**
* @description 模式,`default`为默认模式,`button`为按钮模式
*/
mode: {
type: String,
values: subsectionMode,
default: 'default',
},
/**
* @description 尺寸,默认可以设置 `sm`、`lg`、`xl`,也可以传入指定的尺寸设置字体的尺寸大小
*/
size: String,
/**
* @description 圆角的值,只作用在最外层的两个选择器上,默认单位 `rpx`
*/
radius: {
type: String,
default: '8',
},
})
export const subsectionEmits = {
[UPDATE_MODEL_EVENT]: (index: number) => isNumber(index),
[CHANGE_EVENT]: (index: number) => isNumber(index),
}
export type SubsectionProps = ExtractPropTypes<typeof subsectionProps>
export type SubsectionEmits = typeof subsectionEmits
export type SubsectionMode = (typeof subsectionMode)[number]
@@ -0,0 +1,31 @@
<script lang="ts" setup>
import { subsectionEmits, subsectionProps } from './subsection'
import { useSubsection, useSubsectionCustomStyle } from './composables'
const props = defineProps(subsectionProps)
const emits = defineEmits(subsectionEmits)
const { componentId, activeColor, sliderRectInfo } = useSubsection(props, emits)
const {
ns,
subsectionClass,
subsectionStyle,
subsectionSliderClass,
subsectionSliderStyle,
} = useSubsectionCustomStyle(props, activeColor, sliderRectInfo)
</script>
<template>
<view :id="componentId" :class="[subsectionClass]" :style="subsectionStyle">
<!-- 滑块 -->
<view :class="[subsectionSliderClass]" :style="subsectionSliderStyle" />
<!-- 内容区域 -->
<view :class="[ns.e('content')]">
<slot />
</view>
</view>
</template>
<style lang="scss" scoped>
@import '../../../theme-chalk/src/subsection.scss';
</style>
@@ -0,0 +1,6 @@
export interface SubsectionItemRect {
left: number
width: number
}
export type SubsectionSliderRect = SubsectionItemRect