diff --git a/web/src/app/home/plugins/components/plugin-market/PluginMarketComponent.tsx b/web/src/app/home/plugins/components/plugin-market/PluginMarketComponent.tsx index 97c82881..46bc1e55 100644 --- a/web/src/app/home/plugins/components/plugin-market/PluginMarketComponent.tsx +++ b/web/src/app/home/plugins/components/plugin-market/PluginMarketComponent.tsx @@ -1,13 +1,6 @@ 'use client'; -import { - useState, - useEffect, - useCallback, - useRef, - Suspense, - useMemo, -} from 'react'; +import { useState, useEffect, useCallback, useRef, Suspense } from 'react'; import { Input } from '@/components/ui/input'; import { Select, @@ -70,7 +63,7 @@ function MarketPageContent({ RecommendationList[] >([]); - const pageSize = 16; // 每页16个,4行x4列 + const pageSize = 12; // 每页12个 const searchTimeoutRef = useRef(null); const scrollContainerRef = useRef(null); @@ -330,38 +323,7 @@ function MarketPageContent({ }; }, []); - // 计算所有推荐插件的 ID 集合 - const recommendedPluginIds = useMemo(() => { - const ids = new Set(); - recommendationLists.forEach((list) => { - list.plugins.forEach((plugin) => { - ids.add(`${plugin.author} / ${plugin.name}`); - }); - }); - return ids; - }, [recommendationLists]); - - // 过滤掉已在推荐列表中展示的插件 - // 仅在显示推荐列表的条件下(无搜索、无筛选、第一页或后续页的累积数据中)进行过滤 - // 注意:如果用户翻页,我们希望一直保持去重,否则推荐过的插件会在第二页出现 - // 但是推荐列表只在第一页且无筛选时显示。 - // 如果用户进行了筛选/搜索,推荐列表不显示,此时不需要去重。 - const visiblePlugins = useMemo(() => { - const showRecommendations = - !searchQuery && componentFilter === 'all' && selectedTags.length === 0; - - if (!showRecommendations) { - return plugins; - } - - return plugins.filter((p) => !recommendedPluginIds.has(p.pluginId)); - }, [ - plugins, - recommendedPluginIds, - searchQuery, - componentFilter, - selectedTags, - ]); + const visiblePlugins = plugins; // 加载更多 const loadMore = useCallback(() => { diff --git a/web/src/app/home/plugins/components/plugin-market/RecommendationLists.tsx b/web/src/app/home/plugins/components/plugin-market/RecommendationLists.tsx index d1da3436..dadadb4f 100644 --- a/web/src/app/home/plugins/components/plugin-market/RecommendationLists.tsx +++ b/web/src/app/home/plugins/components/plugin-market/RecommendationLists.tsx @@ -47,10 +47,12 @@ function RecommendationListRow({ list, tagNames, onInstall, + isLast, }: { list: RecommendationList; tagNames: Record; onInstall: (author: string, pluginName: string) => void; + isLast: boolean; }) { const { t } = useTranslation(); const [page, setPage] = useState(0); @@ -143,7 +145,9 @@ function RecommendationListRow({ /> ))} - {totalPages > 1 &&
} + {totalPages > 1 && !isLast && ( +
+ )}
); } @@ -161,12 +165,13 @@ export function RecommendationLists({ return (
- {lists.map((list) => ( + {lists.map((list, index) => ( ))}
diff --git a/web/src/app/home/plugins/components/plugin-market/plugin-market-card/PluginMarketCardComponent.tsx b/web/src/app/home/plugins/components/plugin-market/plugin-market-card/PluginMarketCardComponent.tsx index ae4130ac..56c0a074 100644 --- a/web/src/app/home/plugins/components/plugin-market/plugin-market-card/PluginMarketCardComponent.tsx +++ b/web/src/app/home/plugins/components/plugin-market/plugin-market-card/PluginMarketCardComponent.tsx @@ -17,7 +17,7 @@ import { FileText, Info, } from 'lucide-react'; -import { useState } from 'react'; +import { useState, useRef, useEffect } from 'react'; import { Button } from '@/components/ui/button'; export default function PluginMarketCardComponent({ @@ -31,6 +31,43 @@ export default function PluginMarketCardComponent({ }) { const { t } = useTranslation(); const [isHovered, setIsHovered] = useState(false); + const bottomRef = useRef(null); + const [visibleTags, setVisibleTags] = useState(2); + + // Measure how many tags fit in the bottom row + useEffect(() => { + const tags = cardVO.tags; + if (!bottomRef.current || !tags || tags.length === 0) return; + + const measure = () => { + const container = bottomRef.current; + if (!container) return; + const width = container.offsetWidth; + const availableForTags = width - 140 - 80; + if (availableForTags <= 0) { + setVisibleTags(0); + return; + } + const tagWidth = 80; + const plusBadgeWidth = 40; + const maxTags = Math.max( + 0, + Math.floor((availableForTags - plusBadgeWidth) / tagWidth), + ); + if (maxTags >= tags.length) { + setVisibleTags(tags.length); + } else { + setVisibleTags(Math.max(1, maxTags)); + } + }; + + measure(); + const observer = new ResizeObserver(measure); + observer.observe(bottomRef.current); + return () => observer.disconnect(); + }, [cardVO.tags]); + + const remainingTags = cardVO.tags ? cardVO.tags.length - visibleTags : 0; function handleInstallClick(e: React.MouseEvent) { e.stopPropagation(); @@ -135,10 +172,13 @@ export default function PluginMarketCardComponent({
{/* 下部分:下载量、标签和组件列表 */} -
-
+
+
{/* 下载数量 */} -
+
- {/* Tags */} - {cardVO.tags && cardVO.tags.length > 0 && ( -
- {cardVO.tags.slice(0, 2).map((tag) => ( + {/* Tags - adaptive */} + {cardVO.tags && cardVO.tags.length > 0 && visibleTags > 0 && ( +
+ {cardVO.tags.slice(0, visibleTags).map((tag) => ( - {tagNames[tag] || tag} + + {tagNames[tag] || tag} + ))} - {cardVO.tags.length > 2 && ( + {remainingTags > 0 && ( - +{cardVO.tags.length - 2} + +{remainingTags} )}