From 3a4a295523958348fd5ab069c0ff5273af21ee7a Mon Sep 17 00:00:00 2001 From: QAyong <131734913+QAyong@users.noreply.github.com> Date: Wed, 26 Aug 2026 19:22:02 +0800 Subject: [PATCH] =?UTF-8?q?fix(web):=20=E6=8F=92=E4=BB=B6=E4=B8=AD?= =?UTF-8?q?=E5=BF=83=20-=20=E4=BF=AE=E5=A4=8D=E5=BA=94=E7=94=A8=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E7=AD=9B=E9=80=89=E4=B8=8E=E8=87=AA=E5=8A=A8=E5=AE=9A?= =?UTF-8?q?=E4=BD=8D=20(#314)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/pages/plugins/index.tsx | 42 +++++++++++++++++++++++++------ web/src/pages/plugins/plugins.css | 1 + 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/web/src/pages/plugins/index.tsx b/web/src/pages/plugins/index.tsx index a9f1bdb2e..994e1f12e 100644 --- a/web/src/pages/plugins/index.tsx +++ b/web/src/pages/plugins/index.tsx @@ -1,6 +1,6 @@ import { App, Button, Input, Modal, Select, Switch, Typography } from "antd"; import { AudioLines, CalendarDays, CheckCircle2, Clock3, CloudUpload, ExternalLink, Film, FolderOpen, Image as ImageIcon, MessageSquareText, PlugZap, RefreshCw, Search, Settings2, ShieldCheck, SlidersHorizontal } from "lucide-react"; -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { useNavigate } from "react-router"; import { listRegisteredPlugins } from "@/lib/plugins/plugin-registry"; @@ -75,6 +75,7 @@ export default function PluginsPage() { const [uploadModalOpen, setUploadModalOpen] = useState(false); const [search, setSearch] = useState(""); const [categoryFilter, setCategoryFilter] = useState("all"); + const [scrollTarget, setScrollTarget] = useState(null); const [statusFilter, setStatusFilter] = useState<"all" | "enabled" | "disabled">("all"); const [trustFilter, setTrustFilter] = useState<"all" | "trusted">("all"); const [eagleBaseUrl, setEagleBaseUrl] = useState("http://localhost:41595"); @@ -83,6 +84,7 @@ export default function PluginsPage() { const [eagleFolders, setEagleFolders] = useState([]); const [eagleFoldersLoading, setEagleFoldersLoading] = useState(false); const [eagleFoldersError, setEagleFoldersError] = useState(""); + const sectionRefs = useRef>({}); useEffect(() => { for (const plugin of builtinPlugins) ensurePlugin(plugin.manifest); @@ -131,8 +133,9 @@ export default function PluginsPage() { const searchableText = [manifest.name, manifest.description, manifest.author, manifest.id, ...contributionKinds.map((kind) => categoryLabels[kind] || kind)].filter(Boolean).join(" ").toLocaleLowerCase(); if (normalizedSearch && !searchableText.includes(normalizedSearch)) return false; if (categoryFilter !== "all") { - const matchesCapability = providerCapabilitiesFor(manifest).includes(categoryFilter as "text" | "image" | "video" | "audio"); - const matchesApp = categoryFilter === "other" && contributionKinds.length > 0 && !matchesCapability; + const providerCapabilities = providerCapabilitiesFor(manifest); + const matchesCapability = providerCapabilities.includes(categoryFilter as "text" | "image" | "video" | "audio"); + const matchesApp = categoryFilter === "other" && contributionKinds.length > 0 && providerCapabilities.length === 0; if (!matchesCapability && !matchesApp) return false; } if (trustFilter === "trusted" && !manifest.trusted) return false; @@ -150,6 +153,24 @@ export default function PluginsPage() { [filteredPlugins], ); + const selectCategory = (key: string) => { + setCategoryFilter(key); + setScrollTarget(key === "all" ? null : key); + }; + + useEffect(() => { + if (!scrollTarget) return; + const section = sectionRefs.current[scrollTarget]; + if (!section) return; + + const frame = window.requestAnimationFrame(() => { + const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches; + section.scrollIntoView({ behavior: reducedMotion ? "auto" : "smooth", block: "start" }); + setScrollTarget(null); + }); + return () => window.cancelAnimationFrame(frame); + }, [pluginSections, scrollTarget]); + const categoryCounts = useMemo(() => { const visiblePlugins = registeredPlugins.filter((plugin) => user?.role === "admin" || features.systemPluginsVisibleToUsers || plugin.source === "uploaded"); const counts: Record = { all: visiblePlugins.length, text: 0, image: 0, video: 0, audio: 0, other: 0 }; @@ -234,7 +255,7 @@ export default function PluginsPage() {

统一管理 provider、工作流、画布节点和其他扩展能力。