'use client'; import { useEffect, useState } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { Loader2, Plus, Save, X } from 'lucide-react'; import { toast } from 'sonner'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card'; import { Label } from '@/components/ui/label'; import { Switch } from '@/components/ui/switch'; import { HtmlEditorWorkspace } from '@/components/common/html-editor-workspace'; import { OptionService, ZoneService, zoneQueryKey, } from '@/lib/services/openflare'; import { cn } from '@/lib/utils'; import { ScopeDomainDialog } from './scope-domain-dialog'; import { defaultContactPageFields, invalidateResponseQueries, KEY_SW_DOMAINS, KEY_SW_ENABLED, KEY_SW_HTML, mapOptionsToContactFields, type ContactPageFields, } from './shared'; export function ContactPageTab({ optionMap, }: { optionMap: Record; }) { const queryClient = useQueryClient(); const [fields, setFields] = useState( defaultContactPageFields, ); const [scopeOpen, setScopeOpen] = useState(false); useEffect(() => { setFields(mapOptionsToContactFields(optionMap)); }, [optionMap]); const zonesQuery = useQuery({ queryKey: [...zoneQueryKey, 'sw-scope'], queryFn: async () => { const zones = await ZoneService.list(); const overviews = await Promise.all( zones.map((zone) => ZoneService.getOverview(zone.id)), ); return overviews.map((ov) => ({ zoneDomain: ov.zone.domain, domains: [ov.zone.domain, ...ov.domains.map((d) => d.domain)], })); }, }); const saveMutation = useMutation({ mutationFn: async () => { await OptionService.updateBatch([ { key: KEY_SW_ENABLED, value: String(fields.enabled) }, { key: KEY_SW_HTML, value: fields.html }, { key: KEY_SW_DOMAINS, value: JSON.stringify(fields.domains) }, ]); }, onSuccess: async () => { toast.success('联系页已保存,请前往版本发布使配置生效'); await invalidateResponseQueries(queryClient); }, onError: (error) => { toast.error(error instanceof Error ? error.message : '保存失败'); }, }); return (
离线兜底 启用后给启用 HTTPS 的网站下发 Service Worker,域名被墙时浏览器从缓存展示此联系页。

仅对 HTTPS 网站生效;未启用的站点不受影响。

setFields((prev) => ({ ...prev, enabled })) } aria-label='启用离线兜底' className='mt-0.5 shrink-0' />

仅对选中的 HTTPS 域名生效;留空则不注入。

{fields.domains.length === 0 ? (

{fields.enabled ? '尚未选择生效域名,保存后不注入任何站点。' : '启用离线兜底后可选择生效域名。'}

) : (
{fields.domains.map((domain) => ( {domain} ))}
)}
联系页 HTML 留空则使用内置默认模板。 setFields((prev) => ({ ...prev, html: v }))} preview={(html) => html} footerHint={null} showPreviewLink={false} previewTitle='离线联系页实时预览' /> { setFields((prev) => ({ ...prev, domains })); setScopeOpen(false); }} />
); }