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,65 @@
import { computed, toRef } from 'vue'
import {
useComponentColor,
useComponentSize,
useNamespace,
} from '../../../../hooks'
import { formatDomSizeValue } from '../../../../utils'
import type { CSSProperties } from 'vue'
import type { IndexListProps } from '../index-list'
export const useIndexListCustomStyle = (props: IndexListProps) => {
const ns = useNamespace('index-list')
// 解析颜色
const [titleBgColorClass, titleBgColorStyle] = useComponentColor(
toRef(props, 'titleBgColor'),
'bg'
)
const [titleTextColorClass, titleTextColorStyle] = useComponentColor(
toRef(props, 'titleColor'),
'text'
)
// 解析尺寸
const { sizeType } = useComponentSize(props.titleSize)
// 标题的类
const titleClass = computed<string>(() => {
const cls: string[] = [ns.e('title')]
if (props.titleSize && sizeType.value === 'inner')
cls.push(ns.em('title', props.titleSize))
if (titleBgColorClass.value) cls.push(titleBgColorClass.value)
if (titleTextColorClass.value) cls.push(titleTextColorClass.value)
return cls.join(' ')
})
// 标题的样式
const titleStyle = computed<CSSProperties>(() => {
const style: CSSProperties = {}
if (!titleBgColorClass.value)
style.backgroundColor =
titleBgColorStyle.value || 'var(--tn-color-gray-disabled)'
if (titleTextColorStyle.value) {
style.color = titleTextColorStyle.value
} else if (!titleBgColorClass.value && !titleTextColorClass.value) {
style.color = 'var(--tn-color-gray)'
}
if (props.titleSize && sizeType.value === 'custom')
style.fontSize = formatDomSizeValue(props.titleSize)
return style
})
return {
ns,
titleClass,
titleStyle,
}
}
@@ -0,0 +1,2 @@
export * from './index-list-custom'
export * from './use-index-list'
@@ -0,0 +1,241 @@
import {
computed,
getCurrentInstance,
nextTick,
onMounted,
ref,
watch,
} from 'vue'
import {
useSelectorQuery,
useTouch,
useUniAppSystemRectInfo,
} from '../../../../hooks'
import { debugWarn, generateId } from '../../../../utils'
import type { SetupContext } from 'vue'
import type {
IndexListEmits,
IndexListKeys,
IndexListProps,
} from '../index-list'
import type {
IndexListData,
IndexListDataItem,
KeyListRectInfo,
} from '../types'
export const useIndexList = (
props: IndexListProps,
emits: SetupContext<IndexListEmits>['emit']
) => {
const instance = getCurrentInstance()
if (!instance) {
debugWarn('TnIndexList', '请在 setup 中使用 useIndexList')
}
const componentId = generateId()
const componentContentClass = `tilc-${componentId}`
const componentKeyListId = `tilkl-${componentId}`
const { getSelectorNodeInfo, getSelectorNodeInfos } =
useSelectorQuery(instance)
// 索引列表数据
const keysData = computed<string[]>(() => Object.keys(props.data))
// 列表数据
const listData = ref<IndexListData>([])
watch(
() => props.data,
(val) => {
// 带星标的数据
const starData: IndexListDataItem = {
key: '★',
title: '★',
data: [],
}
listData.value = Object.entries(val).map(([key, value]) => {
starData.data = starData.data.concat(
value.data.filter((item) => item?.star)
)
return {
key,
title: value.title,
data: value.data,
}
})
if (starData.data.length) {
if (!keysData.value.includes('★')) {
keysData.value.unshift('★')
}
listData.value.unshift(starData)
}
},
{
immediate: true,
}
)
// 容器的高度
const { systemScreenInfo } = useUniAppSystemRectInfo()
const contentContainerHeight = computed<number>(
() => props.height || systemScreenInfo.height - props.stickyOffsetTop
)
// 内容节点的top值
let contentTopValues: number[] = []
// scroll-view的top值
const scrollViewTopValue = ref<number>(0)
// 获取全部列表项的节点信息
let initCount = 0
const getContentItemNodeInfo = async () => {
try {
const contentNodeInfos = await getSelectorNodeInfos(
`.${componentContentClass}`
)
initCount = 0
contentTopValues = contentNodeInfos.map((item) => item.top || 0)
} catch (err) {
if (initCount > 10) {
initCount = 0
debugWarn('TnIndexList', `获取全部列表项的节点信息失败:${err}`)
return
}
initCount++
setTimeout(() => {
getContentItemNodeInfo()
}, 150)
}
}
const {
currentY: keyListTouchCurrentY,
updateOptions: updateKeyListTouchOptions,
onTouchStart: keyListTouchStartEvent,
onTouchMove: keyListTouchMoveEvent,
onTouchEnd: keyListTouchEndEvent,
} = useTouch()
// 获取索引列表的容器信息
let keyListTop = 0
let keyListItemRectInfo: KeyListRectInfo[] = []
const getKeyListNodeInfo = async () => {
try {
const keyListNodeInfo = await getSelectorNodeInfo(
`#${componentKeyListId}`
)
const keyListItemNodeInfo = await getSelectorNodeInfos(
`#${componentKeyListId} .key-value`
)
initCount = 0
keyListTop = keyListNodeInfo.top || 0
keyListItemRectInfo = keyListItemNodeInfo.map((item) => {
return {
top: (item.top || 0) - keyListTop,
height: item.height || 0,
}
})
updateKeyListTouchOptions({
left: keyListNodeInfo.left,
right: keyListNodeInfo.right,
top: keyListNodeInfo.top,
bottom: keyListNodeInfo.bottom,
})
} catch (err) {
if (initCount > 10) {
initCount = 0
debugWarn('TnIndexList', `获取索引列表的容器信息失败:${err}`)
return
}
initCount++
setTimeout(() => {
getKeyListNodeInfo()
}, 150)
}
}
// 提示框的top值
const keyListTipsTopValue = ref<number>(0)
const currentTouchKeyIndex = ref<number>(-1)
const currentTouchKeyValue = computed<string>(
() => keysData.value[currentTouchKeyIndex.value]
)
// 设置当前滑动、点击索引的值
const updateKeyListIndexValue = () => {
// 判断当前滑动位置在哪里
let index = -1
// #ifndef APP-PLUS || MP-ALIPAY
index = keyListItemRectInfo.findLastIndex(
(item) => item.top < keyListTouchCurrentY.value
)
// #endif
// #ifdef APP-PLUS || MP-ALIPAY
index = keyListItemRectInfo.findIndex(
(item) => item.top > keyListTouchCurrentY.value
)
index = index - 1
// #endif
if (index !== -1) {
const keyListRectItem = keyListItemRectInfo[index]
keyListTipsTopValue.value =
keyListRectItem.top + keyListRectItem.height / 2
const top = contentTopValues[index]
scrollViewTopValue.value = top - contentTopValues[0]
// #ifdef MP-ALIPAY
scrollViewTopValue.value = scrollViewTopValue.value + 1
// #endif
currentTouchKeyIndex.value = index
}
}
// 触摸事件
const onKeyListTouchStart = (event: any) => {
keyListTouchStartEvent(event)
}
const onKeyListTouchMove = (event: any) => {
keyListTouchMoveEvent(event)
updateKeyListIndexValue()
}
const onKeyListTouchEnd = (event: any) => {
keyListTouchEndEvent(event)
updateKeyListIndexValue()
emits('click', currentTouchKeyValue.value as IndexListKeys)
currentTouchKeyIndex.value = -1
}
onMounted(() => {
// #ifndef APP-PLUS || MP-ALIPAY
nextTick(() => {
getContentItemNodeInfo()
if (props.showKeysList) getKeyListNodeInfo()
})
// #endif
// #ifdef APP-PLUS || MP-ALIPAY
setTimeout(() => {
getContentItemNodeInfo()
if (props.showKeysList) getKeyListNodeInfo()
}, 500)
// #endif
})
return {
componentContentClass,
contentContainerHeight,
keysData,
listData,
scrollViewTopValue,
componentKeyListId,
keyListTipsTopValue,
currentTouchKeyIndex,
currentTouchKeyValue,
onKeyListTouchStart,
onKeyListTouchMove,
onKeyListTouchEnd,
}
}
@@ -0,0 +1,97 @@
import { buildProps, definePropType, isString } from '../../../utils'
import type { ExtractPropTypes } from 'vue'
export type IndexListDataItemData<T = any> = T & {
star?: boolean
}
export interface IndexListDataItem<T = any> {
title: string
data: IndexListDataItemData<T>[]
}
const indexListKeys = [
'a',
'b',
'c',
'd',
'e',
'f',
'g',
'h',
'i',
'j',
'k',
'l',
'm',
'n',
'o',
'p',
'q',
'r',
's',
't',
'u',
'v',
'w',
'x',
'y',
'z',
'#',
] as const
export type IndexListKeys = (typeof indexListKeys)[number]
export type IndexListData<T = any> = Partial<
Record<IndexListKeys, IndexListDataItem<T>>
>
export const indexListProps = buildProps({
/**
* @description 数据源
*/
data: {
type: definePropType<IndexListData>(Object),
default: () => ({}),
},
/**
* @description 索引列表的高度,单位 `px`,不传值则以当前窗口的高度为准
*/
height: Number,
/**
* @description 标题吸顶距离,单位 `px`
*/
stickyOffsetTop: {
type: Number,
default: 0,
},
/**
* @description 标题背景颜色,以tn开头使用图鸟内置的颜色
*/
titleBgColor: String,
/**
* @description 标题文字颜色,以tn开头使用图鸟内置的颜色
*/
titleColor: String,
/**
* @description 标题尺寸,内置 `sm`、`lg`、`xl`,同时也可以传递指定的尺寸的值
*/
titleSize: String,
/**
* @description 是否显示右侧索引列表
*/
showKeysList: {
type: Boolean,
default: true,
},
})
export const indexListEmits = {
/**
* @description 点击索引列表时触发
*/
click: (key: IndexListKeys) => isString(key),
}
export type IndexListProps = ExtractPropTypes<typeof indexListProps>
export type IndexListEmits = typeof indexListEmits
@@ -0,0 +1,105 @@
<script lang="ts" setup>
import TnSticky from '../../sticky/src/sticky.vue'
import { indexListEmits, indexListProps } from './index-list'
import { useIndexList, useIndexListCustomStyle } from './composables'
const props = defineProps(indexListProps)
const emits = defineEmits(indexListEmits)
const {
componentContentClass,
contentContainerHeight,
keysData,
listData,
scrollViewTopValue,
componentKeyListId,
keyListTipsTopValue,
currentTouchKeyIndex,
currentTouchKeyValue,
onKeyListTouchStart,
onKeyListTouchMove,
onKeyListTouchEnd,
} = useIndexList(props, emits)
const { ns, titleClass, titleStyle } = useIndexListCustomStyle(props)
</script>
// #ifdef MP-WEIXIN
<script lang="ts">
export default {
options: {
// 在微信小程序中将组件节点渲染为虚拟节点,更加接近Vue组件的表现(不会出现shadow节点下再去创建元素)
virtualHost: true,
},
}
</script>
// #endif
<template>
<view :class="[ns.b()]" :style="{ height: `${contentContainerHeight}px` }">
<!-- 列表内容 -->
<scroll-view
:class="[ns.e('scroll-view')]"
scroll-y
:scroll-top="scrollViewTopValue"
>
<view
:style="{
paddingTop: `${stickyOffsetTop || 0}px`,
}"
>
<view
v-for="(item, index) in listData"
:key="index"
:class="[componentContentClass]"
>
<TnSticky :offset-top="stickyOffsetTop">
<view :class="[titleClass]" :style="titleStyle">
{{ item.title }}
</view>
</TnSticky>
<view :class="[ns.e('content')]">
<view
v-for="(dataItem, dataIndex) in item.data"
:key="dataIndex"
:class="[ns.e('content-item')]"
>
<slot :data="dataItem" />
</view>
</view>
</view>
</view>
</scroll-view>
<!-- 右侧索引 -->
<view
v-if="showKeysList"
:id="componentKeyListId"
:class="[ns.e('key-list')]"
@touchstart.stop.prevent="onKeyListTouchStart"
@touchmove.stop.prevent="onKeyListTouchMove"
@touchend.stop.prevent="onKeyListTouchEnd"
>
<view
v-for="(item, index) in keysData"
:key="index"
:class="[ns.e('key-list-value'), 'key-value']"
>
{{ item }}
</view>
<!-- 提示框 -->
<view
v-if="currentTouchKeyIndex !== -1"
:class="[ns.e('key-list-tips-value')]"
:style="{ top: `${keyListTipsTopValue}px` }"
>
{{ currentTouchKeyValue }}
<view class="auxiliary-element" />
</view>
</view>
</view>
</template>
<style lang="scss" scoped>
@import '../../../theme-chalk/src/index-list.scss';
</style>
@@ -0,0 +1,3 @@
import type IndexList from './index-list.vue'
export type TnIndexListInstance = InstanceType<typeof IndexList>
@@ -0,0 +1,12 @@
export interface IndexListDataItem {
key: string
title: string
data: any[]
}
export type IndexListData = IndexListDataItem[]
export interface KeyListRectInfo {
top: number
height: number
}