diff --git a/app/(admin)/settings/admin-settings-client.tsx b/app/(admin)/settings/admin-settings-client.tsx index 0fbdcdf48..16d43265f 100644 --- a/app/(admin)/settings/admin-settings-client.tsx +++ b/app/(admin)/settings/admin-settings-client.tsx @@ -67,6 +67,12 @@ import { } from "@/components/ui/dialog"; import { Textarea } from "@/components/ui/textarea"; import { Switch } from "@/components/ui/switch"; +import { + defaultMembershipConfig, + defaultMembershipUserOverrides, + normalizeMembershipConfig, + normalizeMembershipUserOverrides, +} from "@/lib/membership/config"; const fetcher = (url: string) => fetch(url).then((res) => res.json()); @@ -170,6 +176,10 @@ type SiteSettings = { canvasEnabled: boolean; canvasDefaultModelId: string | null; canvasAutoModeDefaultModelId: string | null; + homeVariant: "classic" | "lovart"; + surpriseCategories: string[]; + membershipConfig: ReturnType; + membershipUserOverrides: ReturnType; }; type NavLink = { @@ -388,10 +398,27 @@ export function AdminSettingsClient() { canvasEnabled: true, canvasDefaultModelId: null, canvasAutoModeDefaultModelId: null, + homeVariant: "classic", + surpriseCategories: ["全部", "惊喜"], + membershipConfig: defaultMembershipConfig, + membershipUserOverrides: defaultMembershipUserOverrides, }); + const [membershipConfigDraft, setMembershipConfigDraft] = useState( + JSON.stringify(defaultMembershipConfig, null, 2) + ); + const [membershipOverridesDraft, setMembershipOverridesDraft] = useState( + JSON.stringify(defaultMembershipUserOverrides, null, 2) + ); useEffect(() => { if (siteSettingsData) { + const normalizedMembershipConfig = normalizeMembershipConfig( + (siteSettingsData as any).membershipConfig + ); + const normalizedMembershipOverrides = normalizeMembershipUserOverrides( + (siteSettingsData as any).membershipUserOverrides + ); + setSiteSettings({ siteName: siteSettingsData.siteName || "AI Chatbot", siteLogo: siteSettingsData.siteLogo || "", @@ -416,7 +443,15 @@ export function AdminSettingsClient() { canvasEnabled: siteSettingsData.canvasEnabled ?? true, canvasDefaultModelId: siteSettingsData.canvasDefaultModelId || null, canvasAutoModeDefaultModelId: siteSettingsData.canvasAutoModeDefaultModelId || null, + homeVariant: siteSettingsData.homeVariant === "lovart" ? "lovart" : "classic", + surpriseCategories: Array.isArray(siteSettingsData.surpriseCategories) + ? siteSettingsData.surpriseCategories.map((item: any) => String(item).trim()).filter(Boolean) + : ["全部", "惊喜"], + membershipConfig: normalizedMembershipConfig, + membershipUserOverrides: normalizedMembershipOverrides, }); + setMembershipConfigDraft(JSON.stringify(normalizedMembershipConfig, null, 2)); + setMembershipOverridesDraft(JSON.stringify(normalizedMembershipOverrides, null, 2)); } }, [siteSettingsData]); @@ -895,12 +930,42 @@ export function AdminSettingsClient() { // --- 网站设置逻辑 --- const handleSaveSiteSettings = async () => { try { + let parsedMembershipConfig: ReturnType; + let parsedMembershipOverrides: ReturnType; + + try { + parsedMembershipConfig = normalizeMembershipConfig( + JSON.parse(membershipConfigDraft) + ); + } catch { + toast.error("会员配置 JSON 格式错误,请检查后再保存"); + return; + } + + try { + parsedMembershipOverrides = normalizeMembershipUserOverrides( + JSON.parse(membershipOverridesDraft) + ); + } catch { + toast.error("会员用户覆盖 JSON 格式错误,请检查后再保存"); + return; + } + + const payload = { + ...siteSettings, + membershipConfig: parsedMembershipConfig, + membershipUserOverrides: parsedMembershipOverrides, + }; + const response = await fetch("/api/site-settings", { method: "PUT", headers: { "Content-Type": "application/json" }, - body: JSON.stringify(siteSettings), + body: JSON.stringify(payload), }); if (!response.ok) throw new Error("保存失败"); + setSiteSettings(payload); + setMembershipConfigDraft(JSON.stringify(parsedMembershipConfig, null, 2)); + setMembershipOverridesDraft(JSON.stringify(parsedMembershipOverrides, null, 2)); toast.success("网站设置保存成功"); mutateSiteSettings(); } catch (error) { @@ -2500,6 +2565,95 @@ export function AdminSettingsClient() { 设置新用户首次访问时的默认主题模式 +
+ + +
+ 新版项目首页会启用全新的项目入口、文件列表与精选展示。 +
+
+
+ + { + const parsed = e.target.value + .split(",") + .map((item) => item.trim()) + .filter(Boolean); + const unique = Array.from(new Set(parsed)); + setSiteSettings({ + ...siteSettings, + surpriseCategories: unique.length > 0 ? unique : ["全部", "惊喜"], + }); + }} + placeholder="全部, 惊喜, 海报, 电商" + /> +
+ 新版首页“精选”模块会按这些分类显示筛选标签。 +
+
+
+
+
+ +
+ 支持配置套餐、FAQ、模型额度、用户套餐覆盖。保存后新版首页“升级”弹窗实时生效。 +
+
+ +
+
+ +