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
+8
View File
@@ -0,0 +1,8 @@
import { withNoopInstall } from '../../utils'
import LazyLoad from './src/lazy-load.vue'
export const TnLazyLoad = withNoopInstall(LazyLoad)
export default TnLazyLoad
export * from './src/lazy-load'
export type { TnLazyLoadInstance } from './src/instance'
@@ -0,0 +1,2 @@
export * from './lazy-load-custom'
export * from './use-lazy-load'
@@ -0,0 +1,26 @@
import { computed } from 'vue'
import { useNamespace } from '../../../../hooks'
import { formatDomSizeValue } from '../../../../utils'
import type { CSSProperties } from 'vue'
import type { LazyLoadProps } from '../lazy-load'
export const useLazyLoadCustomStyle = (props: LazyLoadProps) => {
const ns = useNamespace('lazy-load')
// lazyLoad的样式
const lazyLoadStyle = computed<CSSProperties>(() => {
const style: CSSProperties = {}
// 设置宽高
if (props.width) style.width = formatDomSizeValue(props.width)
if (props.height) style.height = formatDomSizeValue(props.height)
return style
})
return {
ns,
lazyLoadStyle,
}
}
@@ -0,0 +1,116 @@
import {
computed,
getCurrentInstance,
nextTick,
onMounted,
onUnmounted,
ref,
} from 'vue'
import { useObserver, useSelectorQuery } from '../../../../hooks'
import {
debugWarn,
generateId,
isEmptyVariableInDefault,
} from '../../../../utils'
import type { LazyLoadProps } from '../lazy-load'
import type { LazyLoadStatus } from '../types'
export const useLazyLoad = (props: LazyLoadProps) => {
const instance = getCurrentInstance()
if (!instance) {
debugWarn('TnLazyLoad', '请在 setup 中使用 useLazyLoad')
}
const { emit } = instance!
const { getSelectorNodeInfo } = useSelectorQuery(instance)
const { connectObserver, disconnectObserver } = useObserver(instance)
const componentId = `tll-${generateId()}`
// 图片触发加载位置
const threshold = computed<number>(() =>
isEmptyVariableInDefault(props.threshold, 100)
)
// 图片加载状态
const imageStatus = ref<LazyLoadStatus>('waiting')
// 开始显示图片
const showImage = ref<boolean>(false)
let initCount = 0
// 初始化监听事件
const initObserver = async () => {
disconnectObserver()
try {
await getSelectorNodeInfo(`#${componentId}`)
initCount = 0
const bottomThreshold =
threshold.value < 0
? -Math.abs(threshold.value)
: Math.abs(threshold.value)
connectObserver(
`#${componentId}`,
(res) => {
if (res.intersectionRatio > 0) {
// 开始显示图片
showImage.value = true
imageStatus.value = 'loading'
// 图片已加载关闭监听,减少资源消耗
disconnectObserver()
}
},
{
type: 'relativeToViewport',
margins: {
bottom: bottomThreshold,
},
}
)
} catch (err) {
if (initCount > 10) {
initCount = 0
debugWarn('TnLazyLoad', `获取图片节点信息失败:${err}`)
return
}
initCount++
setTimeout(() => {
initObserver()
}, 150)
}
}
// 图片加载完成事件
const handleImageLoadedSuccess = () => {
imageStatus.value = 'loaded'
emit('loaded')
}
// 图片加载失败事件
const handleImageLoadedFailed = (err: any) => {
debugWarn('TnLazyLoad', `图片加载失败: ${err}`)
imageStatus.value = 'error'
emit('error')
}
onMounted(() => {
nextTick(() => {
initObserver()
})
})
onUnmounted(() => {
disconnectObserver()
})
return {
componentId,
imageStatus,
showImage,
handleImageLoadedSuccess,
handleImageLoadedFailed,
}
}
@@ -0,0 +1,3 @@
import type LazyLoad from './lazy-load.vue'
export type TnLazyLoadInstance = InstanceType<typeof LazyLoad>
@@ -0,0 +1,55 @@
import { componentImgModes } from '../../../constants'
import { buildProps } from '../../../utils'
import type { ExtractPropTypes } from 'vue'
export const lazyLoadProps = buildProps({
/**
* @description 图片地址
*/
src: String,
/**
* @description 图片高度
*/
height: String,
/**
* @description 图片宽度
*/
width: String,
/**
* @description 图片裁剪模式
*/
mode: {
type: String,
values: componentImgModes,
default: 'aspectFill',
},
/**
* @description 开始加载图片的位置,单位为 px,如果设置为负数表示距离底部还有多少个像素就开始加载
*/
threshold: {
type: Number,
default: 100,
},
/**
* @description 是否开启过度效果
*/
transition: {
type: Boolean,
default: true,
},
})
export const lazyLoadEmits = {
/**
* @description 图片加载完成
*/
loaded: () => true,
/**
* @description 图片加载失败
*/
error: () => true,
}
export type LazyLoadProps = ExtractPropTypes<typeof lazyLoadProps>
export type LazyLoadEmits = typeof lazyLoadEmits
@@ -0,0 +1,79 @@
<script lang="ts" setup>
import TnIcon from '../../icon/src/icon.vue'
import { lazyLoadEmits, lazyLoadProps } from './lazy-load'
import { useLazyLoad, useLazyLoadCustomStyle } from './composables'
const props = defineProps(lazyLoadProps)
defineEmits(lazyLoadEmits)
const {
componentId,
imageStatus,
showImage,
handleImageLoadedSuccess,
handleImageLoadedFailed,
} = useLazyLoad(props)
const { ns, lazyLoadStyle } = useLazyLoadCustomStyle(props)
</script>
// #ifdef MP-WEIXIN
<script lang="ts">
export default {
options: {
// 在微信小程序中将组件节点渲染为虚拟节点,更加接近Vue组件的表现(不会出现shadow节点下再去创建元素)
virtualHost: true,
},
}
</script>
// #endif
<template>
<view
:id="componentId"
:class="[
ns.b(),
ns.is('show-image', showImage && imageStatus === 'loaded'),
]"
:style="lazyLoadStyle"
>
<!-- 加载中 -->
<view v-if="imageStatus === 'loading'" :class="[ns.e('container')]">
<slot name="loading">
<view :class="[ns.e('loading')]">
<view :class="[ns.e('loading__icon')]">
<TnIcon name="loading" />
</view>
</view>
</slot>
</view>
<!-- 正式显示的图片 -->
<view
v-if="showImage && imageStatus !== 'error'"
:class="[ns.e('container')]"
>
<image
:class="[
ns.e('image'),
ns.is('animation', imageStatus === 'loaded' && transition),
ns.is('no-animation', imageStatus === 'loaded' && !transition),
]"
:src="src"
:mode="mode"
@load="handleImageLoadedSuccess"
@error="handleImageLoadedFailed"
/>
</view>
<!-- 图片加载失败 -->
<view v-if="imageStatus === 'error'" :class="[ns.e('container')]">
<slot name="error">
<view :class="[ns.e('error')]">
<TnIcon name="image-fill" />
</view>
</slot>
</view>
</view>
</template>
<style lang="scss" scoped>
@import '../../../theme-chalk/src/lazy-load.scss';
</style>
@@ -0,0 +1 @@
export type LazyLoadStatus = 'waiting' | 'loading' | 'loaded' | 'error'