From 024ec19c70d737176ce2331428f6aa1032ffadaa Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 31 Jan 2026 20:37:29 +0800 Subject: [PATCH] feat: add tutorial video button and ranking visibility toggle in navbar settings Co-authored-by: Cursor --- app/(admin)/layout.tsx | 3 + .../settings/admin-settings-client.tsx | 54 +++++- app/(chat)/layout.tsx | 3 + app/api/site-settings/route.ts | 9 + app/canvas/[id]/layout.tsx | 3 + components/top-navbar.tsx | 178 +++++++++++++----- lib/db/queries.ts | 3 + lib/db/schema.ts | 5 + 8 files changed, 205 insertions(+), 53 deletions(-) diff --git a/app/(admin)/layout.tsx b/app/(admin)/layout.tsx index 14e1523..28b38f8 100644 --- a/app/(admin)/layout.tsx +++ b/app/(admin)/layout.tsx @@ -31,6 +31,9 @@ export default async function AdminLayout({ toolbox1Enabled={siteSettings.toolbox1Enabled ?? true} toolbox2Name={siteSettings.toolbox2Name || "工具箱2"} toolbox2Enabled={siteSettings.toolbox2Enabled ?? false} + tutorialVideoUrl={siteSettings.tutorialVideoUrl} + tutorialEnabled={siteSettings.tutorialEnabled ?? false} + rankingEnabled={siteSettings.rankingEnabled ?? true} />
{children}
diff --git a/app/(admin)/settings/admin-settings-client.tsx b/app/(admin)/settings/admin-settings-client.tsx index b179b7f..1ce59d6 100644 --- a/app/(admin)/settings/admin-settings-client.tsx +++ b/app/(admin)/settings/admin-settings-client.tsx @@ -159,6 +159,9 @@ type SiteSettings = { toolbox1Enabled: boolean; toolbox2Name: string; toolbox2Enabled: boolean; + tutorialVideoUrl: string | null; + tutorialEnabled: boolean; + rankingEnabled: boolean; }; type NavLink = { @@ -357,6 +360,9 @@ export function AdminSettingsClient() { toolbox1Enabled: true, toolbox2Name: "工具箱2", toolbox2Enabled: false, + tutorialVideoUrl: null, + tutorialEnabled: false, + rankingEnabled: true, }); useEffect(() => { @@ -379,6 +385,9 @@ export function AdminSettingsClient() { toolbox1Enabled: siteSettingsData.toolbox1Enabled ?? true, toolbox2Name: siteSettingsData.toolbox2Name || "工具箱2", toolbox2Enabled: siteSettingsData.toolbox2Enabled ?? false, + tutorialVideoUrl: siteSettingsData.tutorialVideoUrl || null, + tutorialEnabled: siteSettingsData.tutorialEnabled ?? false, + rankingEnabled: siteSettingsData.rankingEnabled ?? true, }); } }, [siteSettingsData]); @@ -2537,8 +2546,51 @@ export function AdminSettingsClient() { /> + {/* 精选榜设置 */} +
+
+ +
+ 控制是否在导航栏显示精选榜按钮 +
+
+ setSiteSettings({ ...siteSettings, rankingEnabled: checked })} + /> +
+ + {/* 功能教程设置 */} +
+
+
+ +
+ 启用后在导航栏显示功能教程按钮,点击后弹窗播放视频 +
+
+ setSiteSettings({ ...siteSettings, tutorialEnabled: checked })} + /> +
+ {siteSettings.tutorialEnabled && ( +
+ + setSiteSettings({ ...siteSettings, tutorialVideoUrl: e.target.value || null })} + placeholder="输入视频播放链接,支持 MP4、YouTube、Bilibili 等" + /> +
+ 支持直接视频链接(如 .mp4)或嵌入式播放链接 +
+
+ )} +
+ diff --git a/app/(chat)/layout.tsx b/app/(chat)/layout.tsx index 842c96e..faa6134 100644 --- a/app/(chat)/layout.tsx +++ b/app/(chat)/layout.tsx @@ -51,6 +51,9 @@ async function SidebarWrapper({ children }: { children: React.ReactNode }) { toolbox1Enabled={siteSettings.toolbox1Enabled ?? true} toolbox2Name={siteSettings.toolbox2Name || "工具箱2"} toolbox2Enabled={siteSettings.toolbox2Enabled ?? false} + tutorialVideoUrl={siteSettings.tutorialVideoUrl} + tutorialEnabled={siteSettings.tutorialEnabled ?? false} + rankingEnabled={siteSettings.rankingEnabled ?? true} />
{/* Add padding-top for fixed navbar */} diff --git a/app/api/site-settings/route.ts b/app/api/site-settings/route.ts index bda1627..a9faae4 100644 --- a/app/api/site-settings/route.ts +++ b/app/api/site-settings/route.ts @@ -38,6 +38,9 @@ export async function GET() { toolbox1Enabled: true, toolbox2Name: "工具箱2", toolbox2Enabled: false, + tutorialVideoUrl: null, + tutorialEnabled: false, + rankingEnabled: true, }; const response = NextResponse.json({ @@ -78,6 +81,9 @@ const updateSchema = z.object({ toolbox1Enabled: z.boolean().optional(), toolbox2Name: z.string().max(64).optional(), toolbox2Enabled: z.boolean().optional(), + tutorialVideoUrl: z.string().nullable().optional(), + tutorialEnabled: z.boolean().optional(), + rankingEnabled: z.boolean().optional(), }); // PUT /api/site-settings - 更新网站设置(仅管理员) @@ -134,6 +140,9 @@ export async function PUT(request: NextRequest) { toolbox1Enabled: data.toolbox1Enabled ?? true, toolbox2Name: data.toolbox2Name ?? "工具箱2", toolbox2Enabled: data.toolbox2Enabled ?? false, + tutorialVideoUrl: data.tutorialVideoUrl, + tutorialEnabled: data.tutorialEnabled ?? false, + rankingEnabled: data.rankingEnabled ?? true, updatedBy: session.user.id, }) .returning(); diff --git a/app/canvas/[id]/layout.tsx b/app/canvas/[id]/layout.tsx index cdb8e28..f574350 100644 --- a/app/canvas/[id]/layout.tsx +++ b/app/canvas/[id]/layout.tsx @@ -47,6 +47,9 @@ async function CanvasLayoutWrapper({ children }: { children: React.ReactNode }) toolbox1Enabled={siteSettings.toolbox1Enabled ?? true} toolbox2Name={siteSettings.toolbox2Name || "工具箱2"} toolbox2Enabled={siteSettings.toolbox2Enabled ?? false} + tutorialVideoUrl={siteSettings.tutorialVideoUrl} + tutorialEnabled={siteSettings.tutorialEnabled ?? false} + rankingEnabled={siteSettings.rankingEnabled ?? true} />
{children} diff --git a/components/top-navbar.tsx b/components/top-navbar.tsx index 9943aa9..ae37db5 100644 --- a/components/top-navbar.tsx +++ b/components/top-navbar.tsx @@ -2,7 +2,7 @@ import { useRouter } from "next/navigation"; import Link from "next/link"; -import { Settings, ExternalLink, Briefcase, Images, Sparkles, Trophy, Building2, Crown, Medal, Award, Edit, LayoutGrid } from "lucide-react"; +import { Settings, ExternalLink, Briefcase, Images, Sparkles, Trophy, Building2, Crown, Medal, Award, Edit, LayoutGrid, PlayCircle, X } from "lucide-react"; import { Button } from "@/components/ui/button"; import { useTheme } from "next-themes"; import { useState, useEffect, useRef } from "react"; @@ -40,6 +40,9 @@ type TopNavbarProps = { toolbox1Enabled?: boolean; toolbox2Name?: string; toolbox2Enabled?: boolean; + tutorialVideoUrl?: string | null; + tutorialEnabled?: boolean; + rankingEnabled?: boolean; }; export function TopNavbar({ @@ -52,12 +55,16 @@ export function TopNavbar({ toolbox1Enabled = true, toolbox2Name = "工具箱2", toolbox2Enabled = false, + tutorialVideoUrl, + tutorialEnabled = false, + rankingEnabled = true, }: TopNavbarProps) { const router = useRouter(); const { resolvedTheme } = useTheme(); const [mounted, setMounted] = useState(false); const [ranking, setRanking] = useState([]); const [rankingOpen, setRankingOpen] = useState(false); + const [showTutorialModal, setShowTutorialModal] = useState(false); const [showImageEditor, setShowImageEditor] = useState(false); const [editorImageUrl, setEditorImageUrl] = useState(""); // 移除 fileInputRef,不再需要外部上传 @@ -242,60 +249,77 @@ export function TopNavbar({ AI精选 + {/* 功能教程 */} + {tutorialEnabled && tutorialVideoUrl && ( + + )} + {/* 精选榜单 */} - - - - - -
- - 精选榜单 TOP5 -
- {ranking.length === 0 ? ( -
- 暂无精选数据 + {rankingEnabled && ( + + + + + +
+ + 精选榜单 TOP5
- ) : ( -
- {ranking.map((item) => ( -
-
- {getRankIcon(item.rank)} + {ranking.length === 0 ? ( +
+ 暂无精选数据 +
+ ) : ( +
+ {ranking.map((item) => ( +
+
+ {getRankIcon(item.rank)} +
+
+

{item.userName}

+

+ + {item.department} +

+
+
+ {item.featuredCount} + 精选 +
-
-

{item.userName}

-

- - {item.department} -

-
-
- {item.featuredCount} - 精选 -
-
- ))} -
- )} - - + ))} +
+ )} + + + )} {/* 图片编辑 */} + + {/* 视频播放器 */} +
+ {tutorialVideoUrl.includes('youtube.com') || tutorialVideoUrl.includes('youtu.be') ? ( + // YouTube 嵌入 +