feat 搭建架构骨架

This commit is contained in:
Ironsp
2026-09-24 09:09:47 +08:00
parent dd9f6b77a4
commit 7a2efa3fea
180 changed files with 18967 additions and 9 deletions

View File

@@ -0,0 +1,32 @@
import { computed, onScopeDispose, ref } from 'vue'
/** 倒计时(验证码 60s、二维码失效 4:32 等通用) */
export function useCountDown(defaultSeconds = 60) {
const seconds = ref(0)
let timer: ReturnType<typeof setInterval> | null = null
const isCounting = computed(() => seconds.value > 0)
function start(total: number = defaultSeconds): void {
reset()
seconds.value = total
timer = setInterval(() => {
seconds.value -= 1
if (seconds.value <= 0) {
reset()
}
}, 1000)
}
function reset(): void {
if (timer !== null) {
clearInterval(timer)
timer = null
}
seconds.value = 0
}
onScopeDispose(reset)
return { seconds, isCounting, start, reset }
}

View File

@@ -0,0 +1,29 @@
import { onMounted, onUnmounted, ref, type Ref } from 'vue'
/** 元素可见性(配合图片懒加载/曝光统计) */
export function useIntersection(
target: Ref<Element | null | undefined>,
options?: IntersectionObserverInit,
): {
isVisible: Ref<boolean>
} {
const isVisible = ref(false)
let observer: IntersectionObserver | null = null
onMounted(() => {
const el = target.value
if (!el) return
observer = new IntersectionObserver((entries) => {
const entry = entries[0]
if (entry) isVisible.value = entry.isIntersecting
}, options)
observer.observe(el)
})
onUnmounted(() => {
observer?.disconnect()
observer = null
})
return { isVisible }
}

View File

@@ -0,0 +1,21 @@
import { ref, type Ref } from 'vue'
/** 请求 loading 状态管理 */
export function useLoading(initial = false): {
loading: Ref<boolean>
withLoading: <T>(fn: () => Promise<T>) => Promise<T>
} {
const loading = ref(initial)
async function withLoading<T>(fn: () => Promise<T>): Promise<T> {
loading.value = true
try {
return await fn()
}
finally {
loading.value = false
}
}
return { loading, withLoading }
}

View File

@@ -0,0 +1,26 @@
import { computed, onMounted, onUnmounted, ref, type ComputedRef, type Ref } from 'vue'
/** 滚动监听/吸顶/回到顶部 */
export function useScroll(threshold = 200): {
scrollY: Ref<number>
isAtTop: ComputedRef<boolean>
showBackTop: ComputedRef<boolean>
scrollToTop: (smooth?: boolean) => void
} {
const scrollY = ref(0)
const isAtTop = computed(() => scrollY.value <= 0)
const showBackTop = computed(() => scrollY.value > threshold)
function onScroll(): void {
scrollY.value = window.scrollY
}
function scrollToTop(smooth = true): void {
window.scrollTo({ top: 0, behavior: smooth ? 'smooth' : 'auto' })
}
onMounted(() => window.addEventListener('scroll', onScroll, { passive: true }))
onUnmounted(() => window.removeEventListener('scroll', onScroll))
return { scrollY, isAtTop, showBackTop, scrollToTop }
}