import { useEffect, useMemo, useState } from "react"; import { createPortal } from "react-dom"; import { Check } from "lucide-react"; import { IconKitSvg } from "./IconKitSvg"; import { useBodyScrollLock } from "./overlays"; import type { Product, Project, Team, User } from "../types"; import type { Notice, Page } from "../routes/route-config"; const SIDEBAR_COLLAPSED_KEY = "airshelf:sidebar-collapsed"; // 全局命令面板(Ctrl K / 点搜索框)—— 忠实搬设计稿 SHELL_COMMANDS,href 改成真路由导航 type Command = { id: string; group: string; label: string; sub: string; page: Page; icon: string; key?: string }; const SHELL_COMMANDS: Command[] = [ { id: "dashboard", group: "导航", label: "工作台", sub: "任务队列、今日消耗、项目进度", page: "dashboard", icon: "dashboard", key: "D" }, { id: "products", group: "导航", label: "商品库", sub: "管理 SKU、商品图册、卖点信息", page: "products", icon: "package", key: "P" }, { id: "projects", group: "导航", label: "视频项目", sub: "查看五阶段短视频流水线", page: "projects", icon: "clapperboard", key: "V" }, { id: "asset-factory", group: "导航", label: "图片生成", sub: "模特上身图、平台套图、图片创作", page: "assetFactory", icon: "sparkles", key: "I" }, { id: "library", group: "导航", label: "资产库", sub: "素材、人物、场景、成片统一管理", page: "library", icon: "folder", key: "A" }, { id: "team", group: "导航", label: "团队", sub: "成员、权限、额度、协作记录", page: "team", icon: "users" }, { id: "account", group: "导航", label: "消费", sub: "余额、充值、账单流水", page: "account", icon: "creditCard" }, { id: "settings", group: "导航", label: "设置", sub: "个人信息、通知、安全、偏好", page: "settings", icon: "settings" }, { id: "messages", group: "常用动作", label: "消息中心", sub: "任务提醒、协作评论、系统通知", page: "messages", icon: "bell", key: "M" }, { id: "new-product", group: "常用动作", label: "新建商品", sub: "从商品信息开始生成素材与视频", page: "productCreateUpload", icon: "productPlus" }, { id: "new-project", group: "常用动作", label: "新建视频项目", sub: "选择商品并进入脚本配置", page: "projectWizard", icon: "clapperboard" }, { id: "model-photo", group: "常用动作", label: "生成模特上身图", sub: "快速生成 3:4 商品展示素材", page: "modelPhoto", icon: "users" }, { id: "platform-cover", group: "常用动作", label: "生成平台套图", sub: "适配电商平台封面与详情图", page: "platformCover", icon: "images" }, { id: "image-optimize", group: "常用动作", label: "图片创作", sub: "对话式生成、编辑、加入资产库", page: "imageOptimize", icon: "images" } ]; function CommandPalette({ open, onClose, navigate }: { open: boolean; onClose: () => void; navigate: Navigate }) { const [query, setQuery] = useState(""); useBodyScrollLock(open); useEffect(() => { if (open) setQuery(""); }, [open]); const items = useMemo(() => { const q = query.trim().toLowerCase(); return SHELL_COMMANDS.filter((cmd) => !q || [cmd.label, cmd.sub, cmd.group, cmd.id].join(" ").toLowerCase().includes(q)); }, [query]); const run = (cmd: Command) => { onClose(); navigate(cmd.page); }; if (!open) return null; let lastGroup = ""; return createPortal(
{ if (event.target === event.currentTarget) onClose(); }} >
setQuery(event.target.value)} onKeyDown={(event) => { if (event.key === "Escape") { event.preventDefault(); onClose(); } else if (event.key === "Enter" && items[0]) { event.preventDefault(); run(items[0]); } }} /> {items.length} 项
{items.length === 0 && (
没有匹配的入口 // 换个关键词试试
)} {items.map((cmd, i) => { const section = cmd.group !== lastGroup ?
{cmd.group}
: null; lastGroup = cmd.group; return (
{section}
); })}
, document.body ); } type Navigate = (page: Page) => void; type NavDef = { id: string; page: Page; label: string; icon: string; badge?: number }; const NAV: NavDef[] = [ { id: "dashboard", page: "dashboard", label: "工作台", icon: "dashboard" }, { id: "products", page: "products", label: "商品库", icon: "package" }, { id: "projects", page: "projects", label: "视频项目", icon: "clapperboard" }, { id: "asset-factory", page: "assetFactory", label: "图片生成", icon: "sparkles" }, { id: "library", page: "library", label: "资产库", icon: "library" }, { id: "team", page: "team", label: "团队", icon: "users" }, { id: "account", page: "account", label: "消费", icon: "creditCard" }, { id: "settings", page: "settings", label: "设置", icon: "settings" } ]; const PAGE_TO_NAV: Partial> = { dashboard: "dashboard", products: "products", productDetail: "products", productCreateUpload: "products", projects: "projects", projectWizard: "projects", pipeline: "projects", assetFactory: "assetFactory", imageOptimize: "assetFactory", modelPhoto: "assetFactory", modelPhotoDemoA: "assetFactory", modelPhotoDemoB: "assetFactory", platformCover: "assetFactory", library: "library", team: "team", account: "account", settings: "settings", settingsNotify: "settings" }; export function Sidebar({ page, navigate, user, team, products, projects, productTotal, projectTotal }: { page: Page; navigate: Navigate; user: User; team: Team | null; products: Product[]; projects: Project[]; productTotal?: number; projectTotal?: number; }) { const activeNav = PAGE_TO_NAV[page]; // 徽标用后端真实总数(分页 count),不用已加载的页内条数 const badges: Partial> = { products: productTotal ?? products.length, projects: projectTotal ?? projects.length }; const avatar = (team?.name || user.username || "A").slice(0, 1).toUpperCase(); // 收窄/展开导航:与设计稿 Shell.toggleSidebarCollapse 一致 —— 切 body.sidebar-collapsed // 类(CSS 在 design-restraint.css),并持久化到 localStorage。 const [collapsed, setCollapsed] = useState(() => localStorage.getItem(SIDEBAR_COLLAPSED_KEY) === "1"); useEffect(() => { document.body.classList.toggle("sidebar-collapsed", collapsed); localStorage.setItem(SIDEBAR_COLLAPSED_KEY, collapsed ? "1" : "0"); return () => document.body.classList.remove("sidebar-collapsed"); }, [collapsed]); // 移动端导航抽屉:窄屏侧栏默认收起(display:none),靠左上汉堡键拉出为浮层 + 背景遮罩。 // 点导航项 / 遮罩 / Esc 关闭。桌面端(>1100px)汉堡键 CSS 隐藏,不影响原有收窄逻辑。 const [mobileNavOpen, setMobileNavOpen] = useState(false); useEffect(() => { if (!mobileNavOpen) return; const onKey = (event: KeyboardEvent) => { if (event.key === "Escape") setMobileNavOpen(false); }; document.addEventListener("keydown", onKey); return () => document.removeEventListener("keydown", onKey); }, [mobileNavOpen]); // 命令面板:Ctrl/Cmd K 开关,点搜索框打开 const [paletteOpen, setPaletteOpen] = useState(false); const openCommandPalette = () => setPaletteOpen(true); useEffect(() => { const onKey = (event: KeyboardEvent) => { if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") { event.preventDefault(); setPaletteOpen((value) => !value); } }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, []); return ( <> {/* 移动端汉堡键(桌面 CSS 隐藏)+ 抽屉遮罩 */} {mobileNavOpen &&
setMobileNavOpen(false)} />} setPaletteOpen(false)} navigate={navigate} /> ); } export function Decorations() { // 与设计稿 shell.js 一致:grid-bg 底纹 + 4 个 sq-mark(写死坐标,不压内容) return ( <>
); } export function CornerMarks() { return ( <> ); } export function ToastLike({ notice }: { notice: NonNullable }) { return (
{notice.text}[ {notice.type.toUpperCase()} ]
); } export function CornerMark({ pos }: { pos: "tl" | "tr" | "bl" | "br" }) { return ( ); }