'use client'; import Link from 'next/link'; import { useEffect, useEffectEvent, useState } from 'react'; import { ExternalLink, RefreshCw } from 'lucide-react'; import { SettingsSection } from './SettingsSection'; import type { AppReleaseEntry, AppUpdateResponse } from '@/lib/types/app-update'; const DEFAULT_SOURCE = { repository: 'KuekHaoYang/KVideo', branch: 'main', manifestUrl: 'https://raw.githubusercontent.com/KuekHaoYang/KVideo/main/app-release.json', changelogUrl: 'https://github.com/KuekHaoYang/KVideo/blob/main/CHANGELOG.md', repositoryUrl: 'https://github.com/KuekHaoYang/KVideo', }; function formatDateLabel(value?: string) { if (!value) { return '未记录'; } const parsed = new Date(value); if (Number.isNaN(parsed.getTime())) { return value; } return new Intl.DateTimeFormat('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', }).format(parsed); } function getStatusMeta(data: AppUpdateResponse | null) { switch (data?.status) { case 'update-available': return { label: '发现新版本', tone: 'text-amber-500 border-amber-500/30 bg-amber-500/10', description: `GitHub 最新版本为 ${data.latestVersion},当前实例仍是 ${data.currentVersion}。`, }; case 'ahead-of-remote': return { label: '本地版本较新', tone: 'text-sky-500 border-sky-500/30 bg-sky-500/10', description: `当前实例版本 ${data.currentVersion} 新于 GitHub 检查结果 ${data.latestVersion}。`, }; case 'check-failed': return { label: '检查失败', tone: 'text-red-500 border-red-500/30 bg-red-500/10', description: data.error || '远程版本检查失败,当前仅显示本地版本信息。', }; case 'up-to-date': default: return { label: '已是最新版本', tone: 'text-emerald-500 border-emerald-500/30 bg-emerald-500/10', description: data ? `当前实例版本 ${data.currentVersion} 与 GitHub 最新版本一致。` : '正在获取最新版本信息。', }; } } function ReleaseNotesBlock({ title, release, emptyText, }: { title: string; release: AppReleaseEntry | null; emptyText: string; }) { return (

{title}

{release ? (

{release.version} · {release.title} · {release.publishedAt}

) : (

{emptyText}

)}
{release ? ( ) : null}
); } export function AppVersionSettings() { const [data, setData] = useState(null); const [hasLoaded, setHasLoaded] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false); const fetchUpdateInfo = useEffectEvent(async (manual: boolean = false) => { if (manual) { setIsRefreshing(true); } try { const response = await fetch('/api/app-update', { cache: 'no-store', headers: { Accept: 'application/json', }, }); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const payload = (await response.json()) as AppUpdateResponse; setData(payload); setHasLoaded(true); } catch (error) { setHasLoaded(true); setData((previous) => ({ currentVersion: previous?.currentVersion || '未知', currentRelease: previous?.currentRelease || null, latestVersion: previous?.latestVersion || previous?.currentVersion || '未知', latestRelease: previous?.latestRelease || previous?.currentRelease || null, status: 'check-failed', updateAvailable: false, checkedAt: new Date().toISOString(), checkedRemotely: false, usedRemoteManifest: false, source: previous?.source || DEFAULT_SOURCE, error: error instanceof Error ? error.message : '未知错误', })); } finally { if (manual) { setIsRefreshing(false); } } }); useEffect(() => { void fetchUpdateInfo(false); }, []); const handleRefresh = () => { void fetchUpdateInfo(true); }; const statusMeta = getStatusMeta(data); const currentRelease = data?.currentRelease ?? null; const latestRelease = data?.latestRelease ?? null; const shouldShowLatestRelease = Boolean( latestRelease && (!currentRelease || latestRelease.version !== currentRelease.version), ); return ( 检查更新 } >

当前版本

{data?.currentVersion || '加载中...'}

{currentRelease ? `${currentRelease.title} · ${currentRelease.publishedAt}` : '正在读取本地版本说明'}

{statusMeta.label}

{statusMeta.description}

上次检查:{hasLoaded ? formatDateLabel(data?.checkedAt) : '正在检查'}

检查来源:{data?.source.repository || 'KuekHaoYang/KVideo'} · {data?.source.branch || 'main'} 查看更新日志 查看仓库
{shouldShowLatestRelease ? ( ) : null}
); }