新增站点设置与内容管理(含已删除恢复/清除),用户页改用独有运营指标,并按概览/内容/成员与经济/站点重组侧栏。 Co-authored-by: Cursor <cursoragent@cursor.com>
180 lines
5.4 KiB
TypeScript
180 lines
5.4 KiB
TypeScript
"use client";
|
||
|
||
import { useState } from "react";
|
||
import {
|
||
Globe,
|
||
Loader2,
|
||
Save,
|
||
UserPlus,
|
||
} from "lucide-react";
|
||
import {
|
||
apiUpdateSiteSettings,
|
||
type PublicSettings,
|
||
} from "@/lib/api";
|
||
import { toast } from "@/lib/toast";
|
||
|
||
function Switch({
|
||
checked,
|
||
disabled,
|
||
onChange,
|
||
label,
|
||
}: {
|
||
checked: boolean;
|
||
disabled?: boolean;
|
||
onChange: () => void;
|
||
label: string;
|
||
}) {
|
||
return (
|
||
<button
|
||
type="button"
|
||
role="switch"
|
||
aria-checked={checked}
|
||
aria-label={label}
|
||
disabled={disabled}
|
||
onClick={onChange}
|
||
className="shrink-0 relative h-7 w-12 rounded-full transition-colors disabled:opacity-60"
|
||
style={{ background: checked ? "var(--accent)" : "var(--line-2)" }}
|
||
>
|
||
<span
|
||
className="absolute top-0.5 left-0.5 h-6 w-6 rounded-full bg-[var(--panel)] shadow transition-transform"
|
||
style={{ transform: checked ? "translateX(1.25rem)" : "translateX(0)" }}
|
||
/>
|
||
</button>
|
||
);
|
||
}
|
||
|
||
export default function SettingsAdmin({ initial }: { initial: PublicSettings }) {
|
||
const [siteName, setSiteName] = useState(initial.site_name);
|
||
const [siteDescription, setSiteDescription] = useState(initial.site_description);
|
||
const [allowRegister, setAllowRegister] = useState(initial.allow_register);
|
||
const [savedName, setSavedName] = useState(initial.site_name);
|
||
const [savedDesc, setSavedDesc] = useState(initial.site_description);
|
||
const [saving, setSaving] = useState(false);
|
||
const [togglingReg, setTogglingReg] = useState(false);
|
||
|
||
const dirty =
|
||
siteName.trim() !== savedName || siteDescription.trim() !== savedDesc;
|
||
|
||
const saveIdentity = async () => {
|
||
if (saving) return;
|
||
const name = siteName.trim();
|
||
if (!name) {
|
||
toast("站点名称不能为空");
|
||
return;
|
||
}
|
||
setSaving(true);
|
||
try {
|
||
const res = await apiUpdateSiteSettings({
|
||
site_name: name,
|
||
site_description: siteDescription.trim(),
|
||
});
|
||
setSiteName(res.site_name);
|
||
setSiteDescription(res.site_description);
|
||
setSavedName(res.site_name);
|
||
setSavedDesc(res.site_description);
|
||
toast("站点信息已保存", "ok");
|
||
} catch (e) {
|
||
toast(e instanceof Error ? e.message : "保存失败");
|
||
} finally {
|
||
setSaving(false);
|
||
}
|
||
};
|
||
|
||
const toggleRegister = async () => {
|
||
if (togglingReg) return;
|
||
const next = !allowRegister;
|
||
setTogglingReg(true);
|
||
try {
|
||
const res = await apiUpdateSiteSettings({ allow_register: next });
|
||
setAllowRegister(res.allow_register);
|
||
toast(res.allow_register ? "已开放注册" : "已关闭注册", "ok");
|
||
} catch (e) {
|
||
toast(e instanceof Error ? e.message : "保存失败");
|
||
} finally {
|
||
setTogglingReg(false);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="max-w-3xl">
|
||
<div className="mb-5">
|
||
<h1
|
||
className="text-[22px] sm:text-[26px] font-extrabold tracking-tight flex items-center gap-2.5"
|
||
style={{ color: "var(--ink)" }}
|
||
>
|
||
<span
|
||
className="w-9 h-9 rounded-xl flex items-center justify-center"
|
||
style={{ background: "var(--accent-soft)", color: "var(--accent)" }}
|
||
>
|
||
<Globe size={18} />
|
||
</span>
|
||
站点设置
|
||
</h1>
|
||
<p className="meta mt-1.5 text-[12.5px]">
|
||
配置站点名称、简介与注册开关;主题色请到「外观」页调整
|
||
</p>
|
||
</div>
|
||
|
||
<section className="j13-module p-5 sm:p-6 mb-5">
|
||
<h2 className="text-[15px] font-semibold mb-1" style={{ color: "var(--ink)" }}>
|
||
基本信息
|
||
</h2>
|
||
<p className="meta text-[12.5px] mb-4">用于页眉品牌、页脚版权与 SEO 元信息</p>
|
||
|
||
<label className="field-label" htmlFor="site-name">
|
||
站点名称
|
||
</label>
|
||
<input
|
||
id="site-name"
|
||
className="field mb-4"
|
||
maxLength={32}
|
||
value={siteName}
|
||
onChange={(e) => setSiteName(e.target.value)}
|
||
placeholder="姜十三论坛"
|
||
/>
|
||
|
||
<label className="field-label" htmlFor="site-desc">
|
||
站点简介
|
||
</label>
|
||
<textarea
|
||
id="site-desc"
|
||
className="field min-h-[96px] mb-4"
|
||
maxLength={160}
|
||
value={siteDescription}
|
||
onChange={(e) => setSiteDescription(e.target.value)}
|
||
placeholder="一句话介绍社区定位…"
|
||
/>
|
||
|
||
<div className="flex justify-end">
|
||
<button
|
||
type="button"
|
||
className="btn btn-primary btn-sm"
|
||
disabled={!dirty || saving}
|
||
onClick={() => void saveIdentity()}
|
||
>
|
||
{saving ? <Loader2 size={14} className="animate-spin" /> : <Save size={14} />}
|
||
保存基本信息
|
||
</button>
|
||
</div>
|
||
</section>
|
||
|
||
<section className="j13-module p-5 sm:p-6 mb-5 flex items-start justify-between gap-4 flex-wrap">
|
||
<div className="min-w-0 flex-1">
|
||
<h2 className="text-[15px] font-semibold flex items-center gap-1.5" style={{ color: "var(--ink)" }}>
|
||
<UserPlus size={15} /> 开放注册
|
||
</h2>
|
||
<p className="meta text-[12.5px] mt-1 leading-relaxed">
|
||
关闭后,游客将无法提交注册;已有账号仍可登录。适用于内测或维护期。
|
||
</p>
|
||
</div>
|
||
<Switch
|
||
checked={allowRegister}
|
||
disabled={togglingReg}
|
||
onChange={() => void toggleRegister()}
|
||
label="开放注册"
|
||
/>
|
||
</section>
|
||
</div>
|
||
);
|
||
}
|