318 lines
9.4 KiB
TypeScript
Raw Permalink Normal View History

2026-06-22 14:43:46 +08:00
'use client';
import { useEffect, useState } from 'react';
import {
Building2,
Phone,
Mail,
MapPin,
ShieldCheck,
FileText,
Loader2,
} from 'lucide-react';
import { AdminHeader } from '@/components/admin/AdminHeader';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import { Card, CardContent } from '@/components/ui/card';
import { ImageUpload } from '@/components/admin/ImageUpload';
import { RichEditor } from '@/components/admin/RichEditor';
import { adminApi } from '@/lib/admin-services';
import type { SiteConfig } from '@/lib/types';
interface FormState {
siteName: string;
logo: string;
tel: string;
address: string;
email: string;
copyright: string;
icp: string;
aboutTitle: string;
aboutContent: string;
}
const DEFAULT: FormState = {
siteName: '',
logo: '',
tel: '',
address: '',
email: '',
copyright: '',
icp: '',
aboutTitle: '',
aboutContent: '',
};
function SectionHead({
icon,
title,
desc,
}: {
icon: React.ReactNode;
title: string;
desc: string;
}) {
return (
<div className="mb-5 flex items-center gap-3">
<span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br from-slate-800 to-slate-900 text-white shadow-md shadow-slate-900/20 ring-1 ring-inset ring-white/10">
{icon}
</span>
<div>
<div className="text-sm font-semibold text-slate-900">{title}</div>
<div className="text-xs text-slate-400">{desc}</div>
</div>
</div>
);
}
function Field({
label,
required,
children,
}: {
label: string;
required?: boolean;
children: React.ReactNode;
}) {
return (
<div className="space-y-1.5">
<Label className="text-xs font-medium text-slate-600">
{label}
{required && <span className="ml-0.5 text-brand-600">*</span>}
</Label>
{children}
</div>
);
}
export default function SiteConfigAdminPage() {
const [hydrated, setHydrated] = useState(false);
const [form, setForm] = useState<FormState>(DEFAULT);
const [saving, setSaving] = useState(false);
const [loaded, setLoaded] = useState(false);
useEffect(() => setHydrated(true), []);
useEffect(() => {
if (!hydrated) return;
adminApi
.siteConfigGet()
.then((c: SiteConfig) => {
setForm({
siteName: c.siteName,
logo: c.logo,
tel: c.tel,
address: c.address,
email: c.email,
copyright: c.copyright,
icp: c.icp,
aboutTitle: c.aboutTitle,
aboutContent: c.aboutContent ?? '',
});
setLoaded(true);
})
.catch((e) => alert((e as Error).message));
}, [hydrated]);
const onSave = async () => {
if (!form.siteName) {
alert('请填写网站名称');
return;
}
setSaving(true);
try {
await adminApi.siteConfigUpdate(form);
alert('保存成功');
} catch (e) {
alert((e as Error).message);
} finally {
setSaving(false);
}
};
if (!loaded) {
return (
<div className="flex h-40 items-center justify-center text-sm text-slate-400">
<Loader2 className="mr-2 h-5 w-5 animate-spin" />
</div>
);
}
const cardCls =
'border-slate-200/70 bg-white shadow-[0_1px_3px_rgba(15,23,42,0.04),0_8px_24px_-12px_rgba(79,70,229,0.12)]';
return (
<div>
<AdminHeader
title="网站配置"
description="管理全站基础信息、联系方式、底部版权与企业简介"
actions={
<Button onClick={onSave} disabled={saving}>
{saving ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
</>
) : (
'保存配置'
)}
</Button>
}
/>
<div className="grid gap-5 lg:grid-cols-2">
{/* ===== 品牌标识 ===== */}
<Card className={cardCls}>
<CardContent className="p-6">
<SectionHead
icon={<Building2 className="h-4 w-4" />}
title="品牌标识"
desc="站点名称与 Logo"
/>
<div className="space-y-4">
<Field label="网站名称" required>
<Input
className="h-10"
value={form.siteName}
onChange={(e) =>
setForm({ ...form, siteName: e.target.value })
}
placeholder="如:智管物业"
/>
</Field>
<Field label="Logo 图片">
<ImageUpload
value={form.logo}
onChange={(url) => setForm({ ...form, logo: url })}
hint="透明背景 PNG 最佳,建议 200x48"
/>
</Field>
</div>
</CardContent>
</Card>
{/* ===== 联系方式 ===== */}
<Card className={cardCls}>
<CardContent className="p-6">
<SectionHead
icon={<Phone className="h-4 w-4" />}
title="联系方式"
desc="客户咨询与对外联络"
/>
<div className="space-y-4">
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<Field label="联系电话">
<div className="relative">
<Phone className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400" />
<Input
className="h-10 pl-9"
value={form.tel}
onChange={(e) =>
setForm({ ...form, tel: e.target.value })
}
placeholder="400-000-0000"
/>
</div>
</Field>
<Field label="商务邮箱">
<div className="relative">
<Mail className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400" />
<Input
className="h-10 pl-9"
value={form.email}
onChange={(e) =>
setForm({ ...form, email: e.target.value })
}
placeholder="business@example.com"
/>
</div>
</Field>
</div>
<Field label="公司地址">
<div className="relative">
<MapPin className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400" />
<Input
className="h-10 pl-9"
value={form.address}
onChange={(e) =>
setForm({ ...form, address: e.target.value })
}
placeholder="省市区详细地址"
/>
</div>
</Field>
</div>
</CardContent>
</Card>
{/* ===== 版权与备案 ===== */}
<Card className={cardCls}>
<CardContent className="p-6">
<SectionHead
icon={<ShieldCheck className="h-4 w-4" />}
title="版权与备案"
desc="底部声明与合规信息"
/>
<div className="space-y-4">
<Field label="底部版权">
<Textarea
rows={2}
className="resize-none"
value={form.copyright}
onChange={(e) =>
setForm({ ...form, copyright: e.target.value })
}
placeholder="© 2026 企业名称 版权所有"
/>
</Field>
<Field label="备案号">
<Input
className="h-10"
value={form.icp}
onChange={(e) => setForm({ ...form, icp: e.target.value })}
placeholder="如京ICP备 00000000 号"
/>
</Field>
</div>
</CardContent>
</Card>
{/* ===== 企业简介 ===== */}
<Card className={`${cardCls} lg:col-span-2`}>
<CardContent className="p-6">
<SectionHead
icon={<FileText className="h-4 w-4" />}
title="企业简介"
desc="关于我们页面的标题与正文"
/>
<div className="space-y-4">
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<Field label="简介标题">
<Input
className="h-10"
value={form.aboutTitle}
onChange={(e) =>
setForm({ ...form, aboutTitle: e.target.value })
}
placeholder="如:关于我们"
/>
</Field>
</div>
<Field label="简介正文(富文本)">
<RichEditor
value={form.aboutContent}
onChange={(html) =>
setForm({ ...form, aboutContent: html })
}
/>
</Field>
</div>
</CardContent>
</Card>
</div>
</div>
);
}