feat: add banner settings to admin panel for configuring ad banner text and link
This commit is contained in:
parent
0c4a2a455e
commit
ee4bb14736
@ -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" /> 保存设置
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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>
|
||||
)}
|
||||
|
||||
|
||||
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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),
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user