feat(user): 新增收货地址管理及售后申请页面
- 新增地址类型定义,增强前端地址数据结构 - 新增地址编辑页面,支持地址智能识别和定位选点功能 - 地址编辑支持省市区选择及默认地址设置 - 新增地址列表页面,支持地址展示、删除、编辑和选择功能 - 实现售后申请页面,支持选择售后类型和退款原因 - 售后申请支持商品选择、退款金额计算和凭证上传 - 新增售后详情页面,支持售后状态展示及申请取消 - 优化页面加载和用户交互体验,增加错误提示和权限处理
This commit is contained in:
145
src_bak/hooks/useCountDown.ts
Normal file
145
src_bak/hooks/useCountDown.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import { useState, useEffect, useCallback, useRef } from 'react'
|
||||
|
||||
export interface CountDownResult {
|
||||
/** 剩余秒数 */
|
||||
seconds: number
|
||||
/** 剩余分钟 */
|
||||
minutes: number
|
||||
/** 剩余小时 */
|
||||
hours: number
|
||||
/** 剩余天数 */
|
||||
days: number
|
||||
/** 总剩余秒数 */
|
||||
totalSeconds: number
|
||||
/** 是否在运行 */
|
||||
isRunning: boolean
|
||||
/** 是否已结束 */
|
||||
isExpired: boolean
|
||||
/** 格式化后的字符串 */
|
||||
formatted: string
|
||||
/** 暂停 */
|
||||
pause: () => void
|
||||
/** 继续 */
|
||||
resume: () => void
|
||||
/** 重置 */
|
||||
reset: (newTargetDate?: number) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* 倒计时 Hook
|
||||
* @param targetDate 目标时间戳(毫秒),0 表示立即结束
|
||||
* @param options 配置选项
|
||||
*/
|
||||
export function useCountDown(
|
||||
targetDate: number,
|
||||
options: {
|
||||
/** 触发间隔(毫秒),默认 1000 */
|
||||
interval?: number
|
||||
/** 结束时的回调 */
|
||||
onComplete?: () => void
|
||||
/** 是否自动开始,默认 true */
|
||||
autoStart?: boolean
|
||||
/** 格式化函数,默认返回 HH:MM:SS 或 DD天HH:MM:SS */
|
||||
format?: (result: Pick<CountDownResult, 'days' | 'hours' | 'minutes' | 'seconds'>) => string
|
||||
} = {}
|
||||
): CountDownResult {
|
||||
const { interval = 1000, onComplete, autoStart = true, format } = options
|
||||
|
||||
const targetRef = useRef(targetDate)
|
||||
const onCompleteRef = useRef(onComplete)
|
||||
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null)
|
||||
|
||||
const [state, setState] = useState(() => calculateTimeLeft(targetDate))
|
||||
const [isRunning, setIsRunning] = useState(autoStart)
|
||||
|
||||
// 更新回调引用
|
||||
useEffect(() => {
|
||||
onCompleteRef.current = onComplete
|
||||
}, [onComplete])
|
||||
|
||||
// 更新目标时间
|
||||
useEffect(() => {
|
||||
targetRef.current = targetDate
|
||||
setState(calculateTimeLeft(targetDate))
|
||||
}, [targetDate])
|
||||
|
||||
// 计算剩余时间
|
||||
function calculateTimeLeft(target: number) {
|
||||
const now = Date.now()
|
||||
const diff = Math.max(0, Math.floor((target - now) / 1000))
|
||||
return {
|
||||
totalSeconds: diff,
|
||||
days: Math.floor(diff / 86400),
|
||||
hours: Math.floor((diff % 86400) / 3600),
|
||||
minutes: Math.floor((diff % 3600) / 60),
|
||||
seconds: diff % 60,
|
||||
}
|
||||
}
|
||||
|
||||
// 默认格式化
|
||||
const defaultFormat = useCallback((r: Pick<CountDownResult, 'days' | 'hours' | 'minutes' | 'seconds'>) => {
|
||||
const pad = (n: number) => String(n).padStart(2, '0')
|
||||
if (r.days > 0) {
|
||||
return `${r.days}天${pad(r.hours)}:${pad(r.minutes)}:${pad(r.seconds)}`
|
||||
}
|
||||
return `${pad(r.hours)}:${pad(r.minutes)}:${pad(r.seconds)}`
|
||||
}, [])
|
||||
|
||||
// 启动定时器
|
||||
const startInterval = useCallback(() => {
|
||||
if (intervalRef.current) clearInterval(intervalRef.current)
|
||||
intervalRef.current = setInterval(() => {
|
||||
const left = calculateTimeLeft(targetRef.current)
|
||||
setState(left)
|
||||
if (left.totalSeconds <= 0) {
|
||||
if (intervalRef.current) clearInterval(intervalRef.current)
|
||||
setIsRunning(false)
|
||||
onCompleteRef.current?.()
|
||||
}
|
||||
}, interval)
|
||||
}, [interval])
|
||||
|
||||
// 控制定时器
|
||||
useEffect(() => {
|
||||
if (isRunning) {
|
||||
// 立即执行一次
|
||||
const left = calculateTimeLeft(targetRef.current)
|
||||
setState(left)
|
||||
if (left.totalSeconds <= 0) {
|
||||
setIsRunning(false)
|
||||
onCompleteRef.current?.()
|
||||
} else {
|
||||
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(() => {
|
||||
if (state.totalSeconds > 0) setIsRunning(true)
|
||||
}, [state.totalSeconds])
|
||||
const reset = useCallback((newTarget?: number) => {
|
||||
const target = newTarget ?? targetDate
|
||||
targetRef.current = target
|
||||
setState(calculateTimeLeft(target))
|
||||
setIsRunning(true)
|
||||
}, [targetDate])
|
||||
|
||||
return {
|
||||
...state,
|
||||
isRunning,
|
||||
isExpired: state.totalSeconds <= 0,
|
||||
formatted: (format ?? defaultFormat)(state),
|
||||
pause,
|
||||
resume,
|
||||
reset,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user