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;
|
usageTimeEnd: string;
|
||||||
usageTimeMessage: string;
|
usageTimeMessage: string;
|
||||||
defaultTheme: "light" | "dark" | "system";
|
defaultTheme: "light" | "dark" | "system";
|
||||||
|
bannerEnabled: boolean;
|
||||||
|
bannerText: string;
|
||||||
|
bannerLink: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
type NavLink = {
|
type NavLink = {
|
||||||
@ -342,6 +345,9 @@ export function AdminSettingsClient() {
|
|||||||
usageTimeEnd: "20:00",
|
usageTimeEnd: "20:00",
|
||||||
usageTimeMessage: "由壹点累了也是需要休息的",
|
usageTimeMessage: "由壹点累了也是需要休息的",
|
||||||
defaultTheme: "system",
|
defaultTheme: "system",
|
||||||
|
bannerEnabled: true,
|
||||||
|
bannerText: "升级会员, Nano Banana Pro 免费365天!",
|
||||||
|
bannerLink: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -357,6 +363,9 @@ export function AdminSettingsClient() {
|
|||||||
usageTimeEnd: siteSettingsData.usageTimeEnd || "20:00",
|
usageTimeEnd: siteSettingsData.usageTimeEnd || "20:00",
|
||||||
usageTimeMessage: siteSettingsData.usageTimeMessage || "由壹点累了也是需要休息的",
|
usageTimeMessage: siteSettingsData.usageTimeMessage || "由壹点累了也是需要休息的",
|
||||||
defaultTheme: siteSettingsData.defaultTheme || "system",
|
defaultTheme: siteSettingsData.defaultTheme || "system",
|
||||||
|
bannerEnabled: siteSettingsData.bannerEnabled ?? true,
|
||||||
|
bannerText: siteSettingsData.bannerText || "升级会员, Nano Banana Pro 免费365天!",
|
||||||
|
bannerLink: siteSettingsData.bannerLink || null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [siteSettingsData]);
|
}, [siteSettingsData]);
|
||||||
@ -2394,6 +2403,48 @@ export function AdminSettingsClient() {
|
|||||||
</div>
|
</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>
|
</div>
|
||||||
<Button onClick={handleSaveSiteSettings}>
|
<Button onClick={handleSaveSiteSettings}>
|
||||||
<Save className="mr-2 h-4 w-4" /> 保存设置
|
<Save className="mr-2 h-4 w-4" /> 保存设置
|
||||||
|
|||||||
@ -31,6 +31,9 @@ export async function GET() {
|
|||||||
usageTimeStart: "08:00",
|
usageTimeStart: "08:00",
|
||||||
usageTimeEnd: "20:00",
|
usageTimeEnd: "20:00",
|
||||||
usageTimeMessage: "由壹点累了也是需要休息的",
|
usageTimeMessage: "由壹点累了也是需要休息的",
|
||||||
|
bannerEnabled: true,
|
||||||
|
bannerText: "升级会员, Nano Banana Pro 免费365天!",
|
||||||
|
bannerLink: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = NextResponse.json({
|
const response = NextResponse.json({
|
||||||
@ -64,6 +67,9 @@ const updateSchema = z.object({
|
|||||||
usageTimeStart: z.string().length(5).optional(),
|
usageTimeStart: z.string().length(5).optional(),
|
||||||
usageTimeEnd: z.string().length(5).optional(),
|
usageTimeEnd: z.string().length(5).optional(),
|
||||||
usageTimeMessage: z.string().optional(),
|
usageTimeMessage: z.string().optional(),
|
||||||
|
bannerEnabled: z.boolean().optional(),
|
||||||
|
bannerText: z.string().optional(),
|
||||||
|
bannerLink: z.string().nullable().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// PUT /api/site-settings - 更新网站设置(仅管理员)
|
// PUT /api/site-settings - 更新网站设置(仅管理员)
|
||||||
@ -113,6 +119,9 @@ export async function PUT(request: NextRequest) {
|
|||||||
usageTimeStart: data.usageTimeStart ?? "08:00",
|
usageTimeStart: data.usageTimeStart ?? "08:00",
|
||||||
usageTimeEnd: data.usageTimeEnd ?? "20:00",
|
usageTimeEnd: data.usageTimeEnd ?? "20:00",
|
||||||
usageTimeMessage: data.usageTimeMessage ?? "由壹点累了也是需要休息的",
|
usageTimeMessage: data.usageTimeMessage ?? "由壹点累了也是需要休息的",
|
||||||
|
bannerEnabled: data.bannerEnabled ?? true,
|
||||||
|
bannerText: data.bannerText ?? "升级会员, Nano Banana Pro 免费365天!",
|
||||||
|
bannerLink: data.bannerLink,
|
||||||
updatedBy: session.user.id,
|
updatedBy: session.user.id,
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|||||||
@ -109,6 +109,13 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
|
|||||||
supportsImageGen?: boolean;
|
supportsImageGen?: boolean;
|
||||||
}>;
|
}>;
|
||||||
}>("/api/models", fetcher);
|
}>("/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) || [];
|
const imageModels = modelsData?.models?.filter(m => m.supportsImageGen) || [];
|
||||||
@ -375,16 +382,28 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
|
|||||||
|
|
||||||
{/* Refresh - Removed */}
|
{/* Refresh - Removed */}
|
||||||
|
|
||||||
{/* Banner */}
|
{/* 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">
|
{(siteSettings?.bannerEnabled ?? true) && (siteSettings?.bannerText || "升级会员, Nano Banana Pro 免费365天!") && (
|
||||||
<div className="flex items-center gap-2">
|
<div
|
||||||
<div className="p-1 bg-blue-100 dark:bg-blue-800 rounded">
|
className={cn(
|
||||||
<Box className="h-4 w-4" />
|
"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>
|
</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>
|
</div>
|
||||||
<X className="h-4 w-4 cursor-pointer opacity-50 hover:opacity-100" />
|
)}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@ -1510,9 +1510,27 @@ export async function getSubModelsByParentModelId(parentModelId: string) {
|
|||||||
export async function getSiteSettings() {
|
export async function getSiteSettings() {
|
||||||
try {
|
try {
|
||||||
const [settings] = await db.select().from(siteSettings).limit(1);
|
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) {
|
} 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)
|
usageTimeStart: varchar("usageTimeStart", { length: 5 }).default("08:00"), // 开始时间(HH:mm)
|
||||||
usageTimeEnd: varchar("usageTimeEnd", { length: 5 }).default("20:00"), // 结束时间(HH:mm)
|
usageTimeEnd: varchar("usageTimeEnd", { length: 5 }).default("20:00"), // 结束时间(HH:mm)
|
||||||
usageTimeMessage: text("usageTimeMessage").default("由壹点累了也是需要休息的"), // 超时提示词
|
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(),
|
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
|
||||||
updatedBy: uuid("updatedBy").references(() => user.id),
|
updatedBy: uuid("updatedBy").references(() => user.id),
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user