feat(user): 新增收货地址管理及售后申请页面

- 新增地址类型定义,增强前端地址数据结构
- 新增地址编辑页面,支持地址智能识别和定位选点功能
- 地址编辑支持省市区选择及默认地址设置
- 新增地址列表页面,支持地址展示、删除、编辑和选择功能
- 实现售后申请页面,支持选择售后类型和退款原因
- 售后申请支持商品选择、退款金额计算和凭证上传
- 新增售后详情页面,支持售后状态展示及申请取消
- 优化页面加载和用户交互体验,增加错误提示和权限处理
This commit is contained in:
2026-07-01 12:11:56 +08:00
parent bf6ed504cc
commit 1fa58040f3
636 changed files with 58878 additions and 716 deletions

130
src_bak/hooks/useCountUp.ts Normal file
View File

@@ -0,0 +1,130 @@
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,
}
}