全项目扫描修复: Docker数据卷修复+安全requireAdmin+12页SEO+API白名单+脚本超时+常量提取+假数据删除
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { Circle } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface ActiveNowBadgeProps {
|
||||
activeHours: { weekday: string[]; weekend: string[] } | null | undefined;
|
||||
className?: string;
|
||||
showNextLabel?: boolean;
|
||||
}
|
||||
|
||||
interface ActiveState {
|
||||
active: boolean;
|
||||
nextLabel: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据当前时间判断 Bot 是否处于活跃时段
|
||||
* 注意:依赖客户端时钟,会在挂载后立即计算并随后每分钟刷新
|
||||
*/
|
||||
function computeState(
|
||||
activeHours: ActiveNowBadgeProps["activeHours"],
|
||||
now: Date
|
||||
): ActiveState {
|
||||
if (!activeHours) return { active: false, nextLabel: null };
|
||||
|
||||
const day = now.getDay();
|
||||
const isWeekend = day === 0 || day === 6;
|
||||
const ranges = isWeekend ? activeHours.weekend : activeHours.weekday;
|
||||
if (!ranges || ranges.length === 0) return { active: false, nextLabel: null };
|
||||
|
||||
const currentMinutes = now.getHours() * 60 + now.getMinutes();
|
||||
|
||||
for (const range of ranges) {
|
||||
if (range === "全天" || range === "全天活跃") return { active: true, nextLabel: null };
|
||||
const m = range.match(/^(\d{1,2}):(\d{2})-(\d{1,2}):(\d{2})$/);
|
||||
if (!m) continue;
|
||||
const start = parseInt(m[1]) * 60 + parseInt(m[2]);
|
||||
const end = parseInt(m[3]) * 60 + parseInt(m[4]);
|
||||
if (start === end) continue;
|
||||
if (start < end) {
|
||||
if (currentMinutes >= start && currentMinutes < end) {
|
||||
return { active: true, nextLabel: null };
|
||||
}
|
||||
} else {
|
||||
if (currentMinutes >= start || currentMinutes < end) {
|
||||
return { active: true, nextLabel: null };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 不在活跃时段,计算下次活跃时间
|
||||
const tomorrowRanges = isWeekend ? activeHours.weekday : activeHours.weekend;
|
||||
const findNext = (rs: string[], label: string) => {
|
||||
for (const r of rs) {
|
||||
if (r === "全天" || r === "全天活跃") return `${label}全天`;
|
||||
const m = r.match(/^(\d{1,2}):(\d{2})-(\d{1,2}):(\d{2})$/);
|
||||
if (!m) continue;
|
||||
const start = parseInt(m[1]) * 60 + parseInt(m[2]);
|
||||
if (label === "今天" && start <= currentMinutes) continue;
|
||||
return `${label} ${m[1]}:${m[2]}`;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
return {
|
||||
active: false,
|
||||
nextLabel: findNext(ranges, "今天") || findNext(tomorrowRanges, "明天"),
|
||||
};
|
||||
}
|
||||
|
||||
export default function ActiveNowBadge({ activeHours, className, showNextLabel = false }: ActiveNowBadgeProps) {
|
||||
const [state, setState] = useState<ActiveState>({ active: false, nextLabel: null });
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
const tick = () => setState(computeState(activeHours, new Date()));
|
||||
tick();
|
||||
// 每分钟刷新一次
|
||||
const id = setInterval(tick, 60_000);
|
||||
return () => clearInterval(id);
|
||||
}, [activeHours]);
|
||||
|
||||
if (!mounted) {
|
||||
// 避免 hydration mismatch,先渲染一个不可见占位
|
||||
return <span className={cn("inline-block h-4 w-12", className)} aria-hidden />;
|
||||
}
|
||||
|
||||
if (state.active) {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] font-medium bg-emerald-50 text-emerald-700 border border-emerald-200/60",
|
||||
className
|
||||
)}
|
||||
title="现在正在活跃"
|
||||
>
|
||||
<Circle className="h-1.5 w-1.5 fill-emerald-500 text-emerald-500 animate-pulse" />
|
||||
活跃中
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (showNextLabel && state.nextLabel) {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-muted/40 text-muted-foreground border border-border/40",
|
||||
className
|
||||
)}
|
||||
title={`下次活跃:${state.nextLabel}`}
|
||||
>
|
||||
{state.nextLabel} 上线
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user