feat 搭建架构骨架
This commit is contained in:
32
src/composables/useCountDown.ts
Normal file
32
src/composables/useCountDown.ts
Normal 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 }
|
||||
}
|
||||
29
src/composables/useIntersection.ts
Normal file
29
src/composables/useIntersection.ts
Normal 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 }
|
||||
}
|
||||
21
src/composables/useLoading.ts
Normal file
21
src/composables/useLoading.ts
Normal 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 }
|
||||
}
|
||||
26
src/composables/useScroll.ts
Normal file
26
src/composables/useScroll.ts
Normal 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 }
|
||||
}
|
||||
Reference in New Issue
Block a user