'use client'; import { useMemo, useState } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { EmptyState } from '@/components/feedback/empty-state'; import { ErrorState } from '@/components/feedback/error-state'; import { InlineMessage } from '@/components/feedback/inline-message'; import { LoadingState } from '@/components/feedback/loading-state'; import { PageHeader } from '@/components/layout/page-header'; import { AppModal } from '@/components/ui/app-modal'; import { AppCard } from '@/components/ui/app-card'; import { StatusBadge } from '@/components/ui/status-badge'; import { activateConfigVersion, getConfigVersionDiff, getConfigVersionPreview, getConfigVersions, publishConfigVersion, } from '@/features/config-versions/api/config-versions'; import type { ConfigDiffResult, ConfigPreviewResult, ConfigVersionItem, SupportFile, } from '@/features/config-versions/types'; import { CodeBlock, PrimaryButton, SecondaryButton, } from '@/features/shared/components/resource-primitives'; import { formatDateTime } from '@/lib/utils/date'; const versionsQueryKey = ['config-versions']; type FeedbackState = { tone: 'info' | 'success' | 'danger'; message: string; }; function getErrorMessage(error: unknown) { return error instanceof Error ? error.message : '请求失败,请稍后重试。'; } function truncateChecksum(checksum: string) { if (!checksum) { return '—'; } return checksum.length > 16 ? `${checksum.slice(0, 16)}...` : checksum; } function hasConfigDiff(diff: ConfigDiffResult) { return ( diff.added_domains.length > 0 || diff.removed_domains.length > 0 || diff.modified_domains.length > 0 || !diff.active_version ); } function DiffList({ title, items }: { title: string; items: string[] }) { return (

{title}

0 ? 'info' : 'warning'} />
{items.length > 0 ? (
{items.map((item) => ( {item} ))}
) : (

当前无相关变更。

)}
); } function SupportFilesList({ files }: { files: SupportFile[] }) { if (files.length === 0) { return (

当前发布不需要额外支持文件。

); } return (
{files.map((file) => (
{file.path} {file.content}
))}
); } function SnapshotModal({ version, onClose, }: { version: ConfigVersionItem | null; onClose: () => void; }) { return ( 关闭 } > {version ? (

Checksum

{version.checksum}

创建人

{version.created_by || '系统'}

创建时间

{formatDateTime(version.created_at)}

快照 JSON

{version.snapshot_json}

渲染结果

{version.rendered_config}
) : null}
); } function PublishPreviewCard({ preview, diff, isPublishing, onConfirm, onCancel, }: { preview: ConfigPreviewResult; diff: ConfigDiffResult; isPublishing: boolean; onConfirm: () => void; onCancel: () => void; }) { const canPublish = preview.route_count > 0 && hasConfigDiff(diff); return ( } >

当前激活版本

{diff.active_version || '无'}

新增域名

{diff.added_domains.length}

删除域名

{diff.removed_domains.length}

修改域名

{diff.modified_domains.length}

{!canPublish ? ( ) : null}

渲染结果

Checksum:{preview.checksum}

{preview.rendered_config}

支持文件

{isPublishing ? '发布中...' : '确认发布'} 取消预览
); } export function ConfigVersionsPage() { const queryClient = useQueryClient(); const [feedback, setFeedback] = useState(null); const [selectedVersionId, setSelectedVersionId] = useState( null, ); const [publishPreview, setPublishPreview] = useState<{ preview: ConfigPreviewResult; diff: ConfigDiffResult; } | null>(null); const [isPreviewLoading, setIsPreviewLoading] = useState(false); const versionsQuery = useQuery({ queryKey: versionsQueryKey, queryFn: getConfigVersions, }); const versions = useMemo( () => versionsQuery.data ?? [], [versionsQuery.data], ); const selectedVersion = useMemo( () => versions.find((item) => item.id === selectedVersionId) ?? null, [selectedVersionId, versions], ); const publishMutation = useMutation({ mutationFn: publishConfigVersion, onSuccess: async (version) => { setFeedback({ tone: 'success', message: `发布成功,版本 ${version.version}`, }); setPublishPreview(null); setSelectedVersionId(version.id); await queryClient.invalidateQueries({ queryKey: versionsQueryKey }); }, onError: (error) => { setFeedback({ tone: 'danger', message: getErrorMessage(error) }); }, }); const activateMutation = useMutation({ mutationFn: activateConfigVersion, onSuccess: async (version) => { setFeedback({ tone: 'success', message: `已激活版本 ${version.version}`, }); setSelectedVersionId(version.id); await queryClient.invalidateQueries({ queryKey: versionsQueryKey }); }, onError: (error) => { setFeedback({ tone: 'danger', message: getErrorMessage(error) }); }, }); const handleOpenPublishPreview = async () => { setFeedback(null); setIsPreviewLoading(true); try { const [preview, diff] = await Promise.all([ getConfigVersionPreview(), getConfigVersionDiff(), ]); setPublishPreview({ preview, diff }); } catch (error) { setFeedback({ tone: 'danger', message: getErrorMessage(error) }); } finally { setIsPreviewLoading(false); } }; const handleActivate = (version: ConfigVersionItem) => { if (version.is_active) { return; } if (!window.confirm(`确认激活版本 ${version.version} 吗?`)) { return; } setFeedback(null); activateMutation.mutate(version.id); }; return ( <>
{isPreviewLoading ? '加载预览中...' : '预览并发布'} } /> {feedback ? ( ) : null} {publishPreview ? ( publishMutation.mutate()} onCancel={() => setPublishPreview(null)} /> ) : null} void queryClient.invalidateQueries({ queryKey: versionsQueryKey, }) } > 刷新列表 } > {versionsQuery.isLoading ? ( ) : versionsQuery.isError ? ( ) : versions.length === 0 ? ( ) : (
{versions.map((version) => ( ))}
版本号 状态 创建人 Checksum 创建时间 操作
{version.version} {version.created_by || '系统'} {truncateChecksum(version.checksum)} {formatDateTime(version.created_at)}
setSelectedVersionId(version.id)} className="px-3 py-2 text-xs" > 查看快照 {!version.is_active ? ( handleActivate(version)} disabled={activateMutation.isPending} className="px-3 py-2 text-xs" > 重新激活 ) : null}
)}
setSelectedVersionId(null)} /> ); }