- 新增地址类型定义,增强前端地址数据结构 - 新增地址编辑页面,支持地址智能识别和定位选点功能 - 地址编辑支持省市区选择及默认地址设置 - 新增地址列表页面,支持地址展示、删除、编辑和选择功能 - 实现售后申请页面,支持选择售后类型和退款原因 - 售后申请支持商品选择、退款金额计算和凭证上传 - 新增售后详情页面,支持售后状态展示及申请取消 - 优化页面加载和用户交互体验,增加错误提示和权限处理
131 lines
3.4 KiB
TypeScript
131 lines
3.4 KiB
TypeScript
import { useState, useEffect, useCallback, useRef } from 'react'
|
|
|
|
export interface CountUpResult {
|
|
/** 当前秒数 */
|
|
seconds: number
|
|
/** 当前分钟 */
|
|
minutes: number
|
|
/** 当前小时 */
|
|
hours: number
|
|
/** 总秒数 */
|
|
totalSeconds: number
|
|
/** 是否在运行 */
|
|
isRunning: boolean
|
|
/** 格式化后的字符串 */
|
|
formatted: string
|
|
/** 暂停 */
|
|
pause: () => void
|
|
/** 继续 */
|
|
resume: () => void
|
|
/** 重置 */
|
|
reset: (newStartFrom?: number) => void
|
|
}
|
|
|
|
export interface CountUpOptions {
|
|
/** 起始值(秒),默认 0 */
|
|
startFrom?: number
|
|
/** 触发间隔(毫秒),默认 1000 */
|
|
interval?: number
|
|
/** 最大值,超出后停止 */
|
|
maxValue?: number
|
|
/** 格式化函数 */
|
|
format?: (result: Pick<CountUpResult, 'hours' | 'minutes' | 'seconds'>) => string
|
|
}
|
|
|
|
/**
|
|
* 正向计时 Hook
|
|
* @param options 配置选项
|
|
*/
|
|
export function useCountUp(options: CountUpOptions = {}): CountUpResult {
|
|
const { startFrom = 0, interval = 1000, maxValue, format } = options
|
|
|
|
const startRef = useRef(startFrom)
|
|
const maxRef = useRef(maxValue)
|
|
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null)
|
|
|
|
const [totalSeconds, setTotalSeconds] = useState(startFrom)
|
|
const [isRunning, setIsRunning] = useState(false)
|
|
|
|
// 计算时分秒
|
|
const calcTime = useCallback((total: number) => ({
|
|
totalSeconds: total,
|
|
hours: Math.floor(total / 3600),
|
|
minutes: Math.floor((total % 3600) / 60),
|
|
seconds: total % 60,
|
|
}), [])
|
|
|
|
const [time, setTime] = useState(() => calcTime(startFrom))
|
|
|
|
// 默认格式化
|
|
const defaultFormat = useCallback((r: Pick<CountUpResult, 'hours' | 'minutes' | 'seconds'>) => {
|
|
const pad = (n: number) => String(n).padStart(2, '0')
|
|
return `${pad(r.hours)}:${pad(r.minutes)}:${pad(r.seconds)}`
|
|
}, [])
|
|
|
|
const startInterval = useCallback(() => {
|
|
if (intervalRef.current) clearInterval(intervalRef.current)
|
|
intervalRef.current = setInterval(() => {
|
|
setTotalSeconds(prev => {
|
|
const next = prev + Math.floor(interval / 1000)
|
|
const max = maxRef.current
|
|
if (max !== undefined && next >= max) {
|
|
if (intervalRef.current) clearInterval(intervalRef.current)
|
|
setIsRunning(false)
|
|
return max
|
|
}
|
|
return next
|
|
})
|
|
}, interval)
|
|
}, [interval])
|
|
|
|
// 更新 refs
|
|
useEffect(() => {
|
|
startRef.current = startFrom
|
|
setTotalSeconds(startFrom)
|
|
setTime(calcTime(startFrom))
|
|
}, [startFrom, calcTime])
|
|
|
|
useEffect(() => {
|
|
maxRef.current = maxValue
|
|
}, [maxValue])
|
|
|
|
// 同步 totalSeconds 到 time
|
|
useEffect(() => {
|
|
setTime(calcTime(totalSeconds))
|
|
}, [totalSeconds, calcTime])
|
|
|
|
// 控制定时器
|
|
useEffect(() => {
|
|
if (isRunning) {
|
|
startInterval()
|
|
} else {
|
|
if (intervalRef.current) {
|
|
clearInterval(intervalRef.current)
|
|
intervalRef.current = null
|
|
}
|
|
}
|
|
return () => {
|
|
if (intervalRef.current) clearInterval(intervalRef.current)
|
|
}
|
|
}, [isRunning, startInterval])
|
|
|
|
const pause = useCallback(() => setIsRunning(false), [])
|
|
const resume = useCallback(() => setIsRunning(true), [])
|
|
const reset = useCallback((newStart?: number) => {
|
|
const start = newStart ?? startFrom
|
|
startRef.current = start
|
|
setTotalSeconds(start)
|
|
setTime(calcTime(start))
|
|
setIsRunning(true)
|
|
}, [startFrom, calcTime])
|
|
|
|
return {
|
|
...time,
|
|
isRunning,
|
|
formatted: (format ?? defaultFormat)(time),
|
|
pause,
|
|
resume,
|
|
reset,
|
|
}
|
|
}
|