77 lines
2.5 KiB
TypeScript
77 lines
2.5 KiB
TypeScript
'use client';
|
||
|
||
import { useEffect, useMemo, useState } from 'react';
|
||
import { RefreshCw, X } from 'lucide-react';
|
||
import { api } from '@/lib/api';
|
||
import {
|
||
getClientRuntimeVersion,
|
||
shouldPromptForNewRuntimeVersion,
|
||
type ServerRuntimeVersion,
|
||
} from '@/lib/runtime-version';
|
||
|
||
const VERSION_POLL_MS = 60_000;
|
||
|
||
export function RuntimeVersionBanner() {
|
||
const clientVersion = useMemo(() => getClientRuntimeVersion(), []);
|
||
const [serverVersion, setServerVersion] = useState<ServerRuntimeVersion | null>(null);
|
||
const [dismissedVersion, setDismissedVersion] = useState('');
|
||
|
||
useEffect(() => {
|
||
let cancelled = false;
|
||
let timer: ReturnType<typeof setInterval> | undefined;
|
||
|
||
const load = async () => {
|
||
try {
|
||
const next = await api.get<ServerRuntimeVersion>('/health/version');
|
||
if (!cancelled) setServerVersion(next);
|
||
} catch {
|
||
if (!cancelled) setServerVersion(null);
|
||
}
|
||
};
|
||
|
||
void load();
|
||
timer = setInterval(() => void load(), VERSION_POLL_MS);
|
||
|
||
return () => {
|
||
cancelled = true;
|
||
if (timer) clearInterval(timer);
|
||
};
|
||
}, []);
|
||
|
||
const shouldShow =
|
||
serverVersion &&
|
||
serverVersion.version !== dismissedVersion &&
|
||
shouldPromptForNewRuntimeVersion(clientVersion.version, serverVersion.version);
|
||
|
||
if (!shouldShow || !serverVersion) return null;
|
||
|
||
return (
|
||
<div className="fixed bottom-4 right-4 z-[70] w-[320px] rounded-lg border border-blue-200 bg-blue-50 px-4 py-3 text-blue-900 shadow-lg">
|
||
<div className="flex items-start gap-3">
|
||
<RefreshCw className="mt-0.5 h-4 w-4 shrink-0" />
|
||
<div className="min-w-0 flex-1">
|
||
<p className="text-[13px] font-semibold">发现新版本</p>
|
||
<p className="mt-1 text-[12px] leading-5 text-blue-700">
|
||
服务器已更新到 {serverVersion.version.slice(0, 7)},刷新后可使用最新前端。
|
||
</p>
|
||
<button
|
||
type="button"
|
||
onClick={() => window.location.reload()}
|
||
className="mt-2 h-7 rounded-md bg-blue-600 px-3 text-[12px] font-medium text-white hover:bg-blue-700"
|
||
>
|
||
刷新页面
|
||
</button>
|
||
</div>
|
||
<button
|
||
type="button"
|
||
onClick={() => setDismissedVersion(serverVersion.version)}
|
||
className="rounded-md p-1 text-blue-500 hover:bg-blue-100 hover:text-blue-700"
|
||
aria-label="关闭新版本提示"
|
||
>
|
||
<X className="h-3.5 w-3.5" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|