feat: add banner settings to admin panel for configuring ad banner text and link

This commit is contained in:
Your Name 2026-01-26 22:26:44 +08:00
parent 0c4a2a455e
commit ee4bb14736
5 changed files with 111 additions and 10 deletions

View File

@ -152,6 +152,9 @@ type SiteSettings = {
usageTimeEnd: string;
usageTimeMessage: string;
defaultTheme: "light" | "dark" | "system";
bannerEnabled: boolean;
bannerText: string;
bannerLink: string | null;
};
type NavLink = {
@ -342,6 +345,9 @@ export function AdminSettingsClient() {
usageTimeEnd: "20:00",
usageTimeMessage: "由壹点累了也是需要休息的",
defaultTheme: "system",
bannerEnabled: true,
bannerText: "升级会员, Nano Banana Pro 免费365天!",
bannerLink: null,
});
useEffect(() => {
@ -357,6 +363,9 @@ export function AdminSettingsClient() {
usageTimeEnd: siteSettingsData.usageTimeEnd || "20:00",
usageTimeMessage: siteSettingsData.usageTimeMessage || "由壹点累了也是需要休息的",
defaultTheme: siteSettingsData.defaultTheme || "system",
bannerEnabled: siteSettingsData.bannerEnabled ?? true,
bannerText: siteSettingsData.bannerText || "升级会员, Nano Banana Pro 免费365天!",
bannerLink: siteSettingsData.bannerLink || null,
});
}
}, [siteSettingsData]);
@ -2394,6 +2403,48 @@ export function AdminSettingsClient() {
</div>
</>
)}
{/* 广告横幅设置 */}
<div className="border-t pt-4">
<div className="flex items-center justify-between mb-4">
<div className="space-y-0.5">
<Label htmlFor="bannerEnabled">广告横幅</Label>
<div className="text-sm text-muted-foreground">
在无限画布等页面的聊天面板中显示广告横幅
</div>
</div>
<Switch
id="bannerEnabled"
checked={siteSettings.bannerEnabled}
onCheckedChange={(checked) => setSiteSettings({ ...siteSettings, bannerEnabled: checked })}
/>
</div>
{siteSettings.bannerEnabled && (
<>
<div className="space-y-2">
<Label htmlFor="bannerText">横幅文字</Label>
<Input
id="bannerText"
value={siteSettings.bannerText}
onChange={(e) => setSiteSettings({ ...siteSettings, bannerText: e.target.value })}
placeholder="输入横幅显示的文字"
/>
</div>
<div className="space-y-2 mt-3">
<Label htmlFor="bannerLink">横幅链接(可选)</Label>
<Input
id="bannerLink"
value={siteSettings.bannerLink || ""}
onChange={(e) => setSiteSettings({ ...siteSettings, bannerLink: e.target.value || null })}
placeholder="输入点击横幅后跳转的链接"
/>
<div className="text-xs text-muted-foreground">
如果不填写链接,点击横幅将不会跳转
</div>
</div>
</>
)}
</div>
</div>
<Button onClick={handleSaveSiteSettings}>
<Save className="mr-2 h-4 w-4" /> 保存设置

View File

@ -31,6 +31,9 @@ export async function GET() {
usageTimeStart: "08:00",
usageTimeEnd: "20:00",
usageTimeMessage: "由壹点累了也是需要休息的",
bannerEnabled: true,
bannerText: "升级会员, Nano Banana Pro 免费365天!",
bannerLink: null,
};
const response = NextResponse.json({
@ -64,6 +67,9 @@ const updateSchema = z.object({
usageTimeStart: z.string().length(5).optional(),
usageTimeEnd: z.string().length(5).optional(),
usageTimeMessage: z.string().optional(),
bannerEnabled: z.boolean().optional(),
bannerText: z.string().optional(),
bannerLink: z.string().nullable().optional(),
});
// PUT /api/site-settings - 更新网站设置(仅管理员)
@ -113,6 +119,9 @@ export async function PUT(request: NextRequest) {
usageTimeStart: data.usageTimeStart ?? "08:00",
usageTimeEnd: data.usageTimeEnd ?? "20:00",
usageTimeMessage: data.usageTimeMessage ?? "由壹点累了也是需要休息的",
bannerEnabled: data.bannerEnabled ?? true,
bannerText: data.bannerText ?? "升级会员, Nano Banana Pro 免费365天!",
bannerLink: data.bannerLink,
updatedBy: session.user.id,
})
.returning();

View File

@ -109,6 +109,13 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
supportsImageGen?: boolean;
}>;
}>("/api/models", fetcher);
// 获取网站设置(包含广告横幅配置)
const { data: siteSettings } = useSWR<{
bannerEnabled?: boolean;
bannerText?: string;
bannerLink?: string | null;
}>("/api/site-settings", fetcher);
// 过滤支持图片生成的模型
const imageModels = modelsData?.models?.filter(m => m.supportsImageGen) || [];
@ -375,16 +382,28 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
{/* Refresh - Removed */}
{/* Banner */}
<div className="bg-blue-50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-300 p-3 rounded-xl text-sm flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="p-1 bg-blue-100 dark:bg-blue-800 rounded">
<Box className="h-4 w-4" />
{/* Banner - 从系统设置中获取 */}
{(siteSettings?.bannerEnabled ?? true) && (siteSettings?.bannerText || "升级会员, Nano Banana Pro 免费365天!") && (
<div
className={cn(
"bg-blue-50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-300 p-3 rounded-xl text-sm flex items-center justify-between",
siteSettings?.bannerLink && "cursor-pointer hover:bg-blue-100 dark:hover:bg-blue-900/30 transition-colors"
)}
onClick={() => {
if (siteSettings?.bannerLink) {
window.open(siteSettings.bannerLink, '_blank');
}
}}
>
<div className="flex items-center gap-2">
<div className="p-1 bg-blue-100 dark:bg-blue-800 rounded">
<Box className="h-4 w-4" />
</div>
<span>{siteSettings?.bannerText || "升级会员, Nano Banana Pro 免费365天!"}</span>
</div>
<span>升级会员, Nano Banana Pro 免费365天!</span>
<X className="h-4 w-4 cursor-pointer opacity-50 hover:opacity-100" onClick={(e) => e.stopPropagation()} />
</div>
<X className="h-4 w-4 cursor-pointer opacity-50 hover:opacity-100" />
</div>
)}
</div>
)}

View File

@ -1510,9 +1510,27 @@ export async function getSubModelsByParentModelId(parentModelId: string) {
export async function getSiteSettings() {
try {
const [settings] = await db.select().from(siteSettings).limit(1);
return settings || { siteName: "AI Chatbot", siteLogo: null, siteLogoDark: null, favicon: null, defaultTheme: "system" };
return settings || {
siteName: "AI Chatbot",
siteLogo: null,
siteLogoDark: null,
favicon: null,
defaultTheme: "system",
bannerEnabled: true,
bannerText: "升级会员, Nano Banana Pro 免费365天!",
bannerLink: null
};
} catch (_error) {
return { siteName: "AI Chatbot", siteLogo: null, siteLogoDark: null, favicon: null, defaultTheme: "system" };
return {
siteName: "AI Chatbot",
siteLogo: null,
siteLogoDark: null,
favicon: null,
defaultTheme: "system",
bannerEnabled: true,
bannerText: "升级会员, Nano Banana Pro 免费365天!",
bannerLink: null
};
}
}

View File

@ -292,6 +292,10 @@ export const siteSettings = pgTable("SiteSettings", {
usageTimeStart: varchar("usageTimeStart", { length: 5 }).default("08:00"), // 开始时间(HH:mm)
usageTimeEnd: varchar("usageTimeEnd", { length: 5 }).default("20:00"), // 结束时间(HH:mm)
usageTimeMessage: text("usageTimeMessage").default("由壹点累了也是需要休息的"), // 超时提示词
// 广告横幅设置
bannerEnabled: boolean("bannerEnabled").notNull().default(true), // 是否显示广告横幅
bannerText: text("bannerText").default("升级会员, Nano Banana Pro 免费365天!"), // 广告横幅文字
bannerLink: text("bannerLink"), // 广告横幅链接(可选)
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
updatedBy: uuid("updatedBy").references(() => user.id),
});