5bf98753f1
- 数据层: Prisma 7 + SQLite (better-sqlite3 adapter), Item 模型 - API: POST/GET/PATCH /api/items, 商品 CRUD 与状态流转 - 关押表单: 图片拖拽上传 + Canvas 压缩, Base64 存储 - 首页看守所: 72h 倒计时 Hook, 省钱看板, 冷却/释放卡片交互 - PWA: manifest.ts, apple-icon, viewport 沉浸配置, iOS 引导组件 - 部署: Dockerfile 多阶段构建, docker-compose, Jenkinsfile CI/CD - 图标: 全套专属像素风锁头+购物车图标 (favicon/96/180/192/512/OG)
44 lines
1.1 KiB
TypeScript
44 lines
1.1 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
|
|
const COOLING_HOURS = 72;
|
|
const COOLING_MS = COOLING_HOURS * 60 * 60 * 1000;
|
|
|
|
export interface CountdownResult {
|
|
hours: number;
|
|
minutes: number;
|
|
seconds: number;
|
|
isExpired: boolean;
|
|
display: string;
|
|
}
|
|
|
|
export function useCountdown(createdAt: string): CountdownResult {
|
|
const [now, setNow] = useState(Date.now());
|
|
|
|
useEffect(() => {
|
|
const timer = setInterval(() => setNow(Date.now()), 1000);
|
|
return () => clearInterval(timer);
|
|
}, []);
|
|
|
|
const created = new Date(createdAt).getTime();
|
|
const deadline = created + COOLING_MS;
|
|
const remaining = Math.max(0, deadline - now);
|
|
|
|
const totalSeconds = Math.floor(remaining / 1000);
|
|
const hours = Math.floor(totalSeconds / 3600);
|
|
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
const seconds = totalSeconds % 60;
|
|
|
|
const pad = (n: number) => n.toString().padStart(2, "0");
|
|
const display = `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
|
|
|
|
return {
|
|
hours,
|
|
minutes,
|
|
seconds,
|
|
isExpired: remaining === 0,
|
|
display,
|
|
};
|
|
}
|