2026-06-22 14:43:46 +08:00

318 lines
9.4 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'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>
);
}