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,16 @@
import { buildProps } from '../../../utils'
import type { ExtractPropTypes } from 'vue'
export const collapseItemProps = buildProps({
/**
* @description CollapseItem标题
*/
title: String,
/**
* @description CollapseItem是否禁用
*/
disabled: Boolean,
})
export type CollapseItemProps = ExtractPropTypes<typeof collapseItemProps>
@@ -0,0 +1,60 @@
<script lang="ts" setup>
import TnIcon from '../../icon/src/icon.vue'
import { collapseItemProps } from './collapse-item'
import { useCollapseItem, useCollapseItemCustomStyle } from './composables'
const props = defineProps(collapseItemProps)
const {
componentContentId,
showArrow,
isActive,
componentHeight,
collapseItemClick,
} = useCollapseItem(props)
const { ns, arrowClass, arrowStyle } = useCollapseItemCustomStyle()
</script>
// #ifdef MP-WEIXIN
<script lang="ts">
export default {
options: {
// 在微信小程序中将组件节点渲染为虚拟节点,更加接近Vue组件的表现(不会出现shadow节点下再去创建元素)
virtualHost: true,
},
}
</script>
// #endif
<template>
<view
:class="[ns.b(), ns.is('active', isActive), ns.is('disabled', disabled)]"
:style="{ height: componentHeight }"
>
<!-- 标题 -->
<view :class="[ns.e('title')]" @tap.stop="collapseItemClick">
<view class="content tn-text-ellipsis-1">
<slot name="title">
{{ title }}
</slot>
</view>
<view
v-if="showArrow"
class="arrow"
:class="[arrowClass]"
:style="arrowStyle"
>
<TnIcon name="right" />
</view>
</view>
<!-- 内容区域 -->
<view :id="componentContentId" :class="[ns.e('content')]">
<slot />
</view>
</view>
</template>
<style lang="scss" scoped>
@import '../../../theme-chalk/src/collapse-item.scss';
</style>
@@ -0,0 +1,50 @@
import { CHANGE_EVENT, UPDATE_MODEL_EVENT } from '../../../constants'
import {
buildProps,
definePropType,
isArray,
isNumber,
isString,
} from '../../../utils'
import type { ExtractPropTypes } from 'vue'
import type { Arrayable } from '../../../utils'
export type CollapseModelValue = Arrayable<number>
export const collapseProps = buildProps({
/**
* @description 当前激活的面板,如果是手风琴效果则传递string,否则传递string[]
*/
modelValue: {
type: definePropType<CollapseModelValue>([Number, Array]),
},
/**
* @description 是否开启手风琴效果,每次只能打开一个面板
*/
accordion: {
type: Boolean,
default: true,
},
/**
* @description 是否显示折叠面板的箭头
*/
showArrow: {
type: Boolean,
default: true,
},
/**
* @description 折叠面板箭头的颜色,show-arrow为true时生效,以tn开头的颜色使用图鸟内置的颜色
*/
arrowColor: String,
})
export const collapseEmits = {
[UPDATE_MODEL_EVENT]: (value: CollapseModelValue) =>
isArray(value) || isString(value) || isNumber(value),
[CHANGE_EVENT]: (value: CollapseModelValue) =>
isArray(value) || isString(value) || isNumber(value),
}
export type CollapseProps = ExtractPropTypes<typeof collapseProps>
export type CollapseEmits = typeof collapseEmits
@@ -0,0 +1,22 @@
<script lang="ts" setup>
import { useNamespace } from '../../../hooks'
import { collapseEmits, collapseProps } from './collapse'
import { useCollapse } from './composables'
const props = defineProps(collapseProps)
const emits = defineEmits(collapseEmits)
const ns = useNamespace('collapse')
useCollapse(props, emits)
</script>
<template>
<view :class="[ns.b()]">
<slot />
</view>
</template>
<style lang="scss" scoped>
@import '../../../theme-chalk/src/collapse.scss';
</style>
@@ -0,0 +1,41 @@
import { computed, inject, toRef } from 'vue'
import { collapseContextKey } from '../../../../tokens'
import { useComponentColor, useNamespace } from '../../../../hooks'
import type { CSSProperties } from 'vue'
export const useCollapseItemCustomStyle = () => {
const ns = useNamespace('collapse-item')
const collapse = inject(collapseContextKey)
// 解析颜色
const [arrowColorClass, arrowColorStyle] = useComponentColor(
toRef(collapse!, 'arrowColor'),
'text'
)
// 折叠面板图标对应样式
const arrowClass = computed<string>(() => {
const cls: string[] = []
if (arrowColorClass.value) cls.push(arrowColorClass.value)
return cls.join(' ')
})
// 折叠面板图标对应样式
const arrowStyle = computed<CSSProperties>(() => {
const style: CSSProperties = {}
if (!arrowColorClass.value)
style.color = arrowColorStyle.value || 'var(--tn-color-gray)'
return style
})
return {
ns,
arrowClass,
arrowStyle,
}
}
@@ -0,0 +1,3 @@
export * from './collapse-item-custom'
export * from './use-collapse'
export * from './use-collapse-item'
@@ -0,0 +1,98 @@
import {
computed,
getCurrentInstance,
inject,
nextTick,
onMounted,
ref,
} from 'vue'
import { collapseContextKey } from '../../../../tokens'
import { useSelectorQuery } from '../../../../hooks'
import {
debugWarn,
generateId,
isEmptyVariableInDefault,
} from '../../../../utils'
import type { CollapseItemProps } from '../collapse-item'
export const useCollapseItem = (props: CollapseItemProps) => {
const instance = getCurrentInstance()
if (!instance) {
debugWarn('TnCollapseItem', '请在 setup 函数中使用 TnCollapseItem')
}
const { uid } = instance!
const collapse = inject(collapseContextKey)
if (!collapse) {
debugWarn('TnCollapseItem', '请在 TnCollapse 中使用 TnCollapseItem')
}
collapse?.addItem({ uid })
const componentContentId = `tcic-${generateId()}`
const { getSelectorNodeInfo } = useSelectorQuery(instance)
// 当前组件是否为激活状态
const isActive = computed<boolean>(() => {
if (!collapse) return false
return collapse.activeUid.includes(uid)
})
// 是否显示折叠面板箭头
const showArrow = computed<boolean>(() =>
isEmptyVariableInDefault(collapse?.showArrow, false)
)
// 组件内容的高度
const compoenntContentDefaultHeight = ref<number>(0)
const componentTitleHeight = ref<number>(uni.upx2px(100))
const componentHeight = computed<string>(() => {
if (!isActive.value) return `${componentTitleHeight.value}px`
else
return `${
componentTitleHeight.value + compoenntContentDefaultHeight.value
}px`
})
let initCount = 0
// 获取内容容器高度
const getComponentContentHeight = async () => {
try {
const rectInfo = await getSelectorNodeInfo(`#${componentContentId}`)
initCount = 0
compoenntContentDefaultHeight.value = rectInfo.height || 0
} catch (err) {
if (initCount > 10) {
initCount = 0
debugWarn('TnCollapseItem', `获取内容高度失败: ${err}`)
return
}
initCount++
setTimeout(() => {
getComponentContentHeight()
}, 150)
}
}
// CollapseItem 点击事件
const collapseItemClick = () => {
if (props.disabled) return
collapse?.handleItemClick(uid)
}
onMounted(() => {
nextTick(() => {
getComponentContentHeight()
})
})
return {
componentContentId,
showArrow,
isActive,
componentHeight,
collapseItemClick,
}
}
@@ -0,0 +1,118 @@
import { nextTick, provide, reactive, ref, toRefs, watch } from 'vue'
import { CHANGE_EVENT, UPDATE_MODEL_EVENT } from '../../../../constants'
import { collapseContextKey } from '../../../../tokens'
import { useOrderedChildren } from '../../../../hooks'
import { isArray, isNumber } from '../../../../utils'
import type { SetupContext } from 'vue'
import type { CollapseItemContext } from '../../../../tokens'
import type {
CollapseEmits,
CollapseModelValue,
CollapseProps,
} from '../collapse'
export const useCollapse = (
props: CollapseProps,
emits: SetupContext<CollapseEmits>['emit']
) => {
const {
children: items,
addChild: addItem,
removeChild: removeItem,
} = useOrderedChildren<CollapseItemContext>()
// 当前已激活的面板对应的uid
const activeUid = ref<number[]>([])
const currentActiveIndex = ref<CollapseModelValue>()
// 根据modelValue更新activeUID的值
const updateActiveUIDWithModelValue = (val?: CollapseModelValue) => {
nextTick(() => {
let activeIndex: number[]
if (val === undefined || val === -1) {
activeIndex = []
} else if (isNumber(val)) {
activeIndex = [val]
} else {
activeIndex = [...val]
}
activeUid.value = items.value
.filter((uid, index) => activeIndex.includes(index))
.map((item) => item.uid)
})
}
let innerUpdate = false
watch(
() => props.modelValue,
(val) => {
if (innerUpdate) {
innerUpdate = false
return
}
currentActiveIndex.value = val
updateActiveUIDWithModelValue(val)
},
{
immediate: true,
}
)
// 处理CollapseItem点击事件
const handleItemClick = (uid: number) => {
const { accordion } = props
// 获取对应uid对应的索引
const uidIndex = items.value.findIndex((item) => item.uid === uid)
const isActive = activeUid.value.includes(uid)
let value: CollapseModelValue
// 判断是否为手风琴效果
if (accordion) {
// 判断是否已经为激活状态,如果是激活状态则取消激活,否则设置为其他面板激活
if (isActive) {
value = -1
} else {
value = uidIndex
}
} else {
// 判断是否存在于激活面板中,如果存在则取消激活,否则添加到激活面板中
if (isActive) {
value = (currentActiveIndex.value as number[]).filter(
(item) => item !== uidIndex
)
} else {
if (currentActiveIndex.value && isArray(currentActiveIndex.value)) {
value = [...currentActiveIndex.value, uidIndex]
} else {
value = !currentActiveIndex.value
? [uidIndex]
: [currentActiveIndex.value, uidIndex]
}
}
}
// 触发更新事件
innerUpdate = true
currentActiveIndex.value = value
emits(UPDATE_MODEL_EVENT, value)
updateActiveUIDWithModelValue(value)
nextTick(() => {
emits(CHANGE_EVENT, value)
})
}
provide(
collapseContextKey,
reactive({
...toRefs(props),
items,
addItem,
removeItem,
activeUid,
handleItemClick,
})
)
}
@@ -0,0 +1,5 @@
import type Collapse from './collapse.vue'
import type CollapseItem from './collapse-item.vue'
export type TnCollapseInstance = InstanceType<typeof Collapse>
export type TnCollapseItemInstance = InstanceType<typeof CollapseItem>