Files
nofx/web/src/hooks/useCounterAnimation.ts
icy 7d58f56e49 feat: implement hybrid database architecture and frontend encryption
- Add PostgreSQL + SQLite hybrid database support with automatic switching
- Implement frontend AES-GCM + RSA-OAEP encryption for sensitive data
- Add comprehensive DatabaseInterface with all required methods
- Fix compilation issues with interface consistency
- Update all database method signatures to use DatabaseInterface
- Add missing UpdateTraderInitialBalance method to PostgreSQL implementation
- Integrate RSA public key distribution via /api/config endpoint
- Add frontend crypto service with proper error handling
- Support graceful degradation between encrypted and plaintext transmission
- Add directory creation for RSA keys and PEM parsing fixes
- Test both SQLite and PostgreSQL modes successfully
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
2025-11-06 01:50:06 +08:00

52 lines
1.3 KiB
TypeScript

import { useEffect, useState } from 'react'
interface UseCounterAnimationOptions {
start?: number
end: number
duration?: number
decimals?: number
}
export function useCounterAnimation({
start = 0,
end,
duration = 2000,
decimals = 0,
}: UseCounterAnimationOptions): number {
const [count, setCount] = useState(start)
useEffect(() => {
if (end === 0) return
let startTime: number | null = null
let animationFrame: number
const animate = (currentTime: number) => {
if (startTime === null) startTime = currentTime
const progress = Math.min((currentTime - startTime) / duration, 1)
// 使用 easeOutExpo 缓动函数,让数字快速启动后缓慢停止
const easeOutExpo = progress === 1 ? 1 : 1 - Math.pow(2, -10 * progress)
const currentCount = start + (end - start) * easeOutExpo
setCount(currentCount)
if (progress < 1) {
animationFrame = requestAnimationFrame(animate)
} else {
setCount(end)
}
}
animationFrame = requestAnimationFrame(animate)
return () => {
if (animationFrame) {
cancelAnimationFrame(animationFrame)
}
}
}, [start, end, duration])
return decimals > 0 ? parseFloat(count.toFixed(decimals)) : Math.floor(count)
}