From 833ea86e82a016f50737a726ce69eb4cd896196c Mon Sep 17 00:00:00 2001 From: MyPrototypeWhat Date: Thu, 31 Jul 2025 12:46:25 +0800 Subject: [PATCH] feat(discover): enhance Discover page with Tailwind CSS integration and routing improvements - Added Tailwind CSS import to the entry point for styling. - Updated the ThemeProvider to dynamically apply Tailwind themes based on user selection. - Refactored Discover page to utilize new ROUTERS structure for better routing management. - Simplified category handling in useDiscoverCategories hook by leveraging ROUTERS_ENTRIES. - Introduced InternalCategory interface for better type management in Discover components. - Cleaned up unused code and comments for improved readability. --- src/renderer/src/assets/styles/index.scss | 1 + src/renderer/src/context/ThemeProvider.tsx | 8 +- src/renderer/src/entryPoint.tsx | 2 +- .../discover/components/DiscoverMain.tsx | 14 +-- .../discover/components/DiscoverSidebar.tsx | 2 +- .../discover/hooks/useDiscoverCategories.ts | 32 +++---- src/renderer/src/pages/discover/index.tsx | 26 ++---- .../discover/pages/agents/AgentsPage.tsx | 5 +- .../discover/pages/minapps/MinAppsPage.tsx | 86 +++++++++---------- src/renderer/src/pages/discover/routers.ts | 34 +++----- src/renderer/src/pages/discover/type.ts | 7 ++ .../src/pages/discover/utils/index.ts | 0 src/renderer/src/pages/discover/utils/util.ts | 0 src/renderer/src/types/cherryStore.ts | 16 ++-- src/renderer/src/ui/vercel-tabs.tsx | 73 +++++++++------- 15 files changed, 151 insertions(+), 155 deletions(-) create mode 100644 src/renderer/src/pages/discover/type.ts delete mode 100644 src/renderer/src/pages/discover/utils/index.ts delete mode 100644 src/renderer/src/pages/discover/utils/util.ts diff --git a/src/renderer/src/assets/styles/index.scss b/src/renderer/src/assets/styles/index.scss index 0a6696bd9b..d1c32c17e3 100644 --- a/src/renderer/src/assets/styles/index.scss +++ b/src/renderer/src/assets/styles/index.scss @@ -49,6 +49,7 @@ body { font-family: var(--font-family); text-rendering: optimizeLegibility; transition: background-color 0.3s linear; + background-color: unset; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; diff --git a/src/renderer/src/context/ThemeProvider.tsx b/src/renderer/src/context/ThemeProvider.tsx index 9e4d6d4572..21dc9e0b6a 100644 --- a/src/renderer/src/context/ThemeProvider.tsx +++ b/src/renderer/src/context/ThemeProvider.tsx @@ -23,6 +23,12 @@ interface ThemeProviderProps extends PropsWithChildren { defaultTheme?: ThemeMode } +const tailwindThemeChange = (theme: ThemeMode) => { + const root = window.document.documentElement + root.classList.remove('light', 'dark') + root.classList.add(theme) +} + export const ThemeProvider: React.FC = ({ children }) => { // 用户设置的主题 const { theme: settedTheme, setTheme: setSettedTheme } = useSettings() @@ -64,7 +70,7 @@ export const ThemeProvider: React.FC = ({ children }) => { useEffect(() => { window.api.setTheme(settedTheme) - // tailwindThemeChange(settedTheme) + tailwindThemeChange(settedTheme) }, [settedTheme]) return ( diff --git a/src/renderer/src/entryPoint.tsx b/src/renderer/src/entryPoint.tsx index ad7b213778..835f4830b1 100644 --- a/src/renderer/src/entryPoint.tsx +++ b/src/renderer/src/entryPoint.tsx @@ -1,5 +1,5 @@ -import './assets/styles/tailwind.css' import './assets/styles/index.scss' +import './assets/styles/tailwind.css' import '@ant-design/v5-patch-for-react-19' import { createRoot } from 'react-dom/client' diff --git a/src/renderer/src/pages/discover/components/DiscoverMain.tsx b/src/renderer/src/pages/discover/components/DiscoverMain.tsx index e1940e30d5..9505a88887 100644 --- a/src/renderer/src/pages/discover/components/DiscoverMain.tsx +++ b/src/renderer/src/pages/discover/components/DiscoverMain.tsx @@ -1,29 +1,29 @@ import React, { Suspense } from 'react' import { Navigate, Route, Routes, useLocation } from 'react-router-dom' -import { discoverRouters, InternalCategory } from '../routers' +import { ROUTERS } from '../routers' +import { InternalCategory } from '../type' export interface DiscoverContentProps { - activeTabId: string // This should be one of the CherryStoreType values, e.g., "Assistant" - // selectedSubcategoryId: string + activeTabId: string currentCategory: InternalCategory | undefined } const DiscoverContent: React.FC = ({ activeTabId, currentCategory }) => { - const location = useLocation() // To see the current path for debugging or more complex logic + const location = useLocation() if (!currentCategory || !activeTabId) { return
Loading: Category or Tab ID missing...
} if (!activeTabId && !location.pathname.startsWith('/discover/')) { - return // Fallback redirect, adjust as needed + return } return ( - Loading...}> + - {discoverRouters.map((_Route) => { + {ROUTERS.map((_Route) => { if (!_Route.component) return null return } /> })} diff --git a/src/renderer/src/pages/discover/components/DiscoverSidebar.tsx b/src/renderer/src/pages/discover/components/DiscoverSidebar.tsx index aa60d965ef..05744b364b 100644 --- a/src/renderer/src/pages/discover/components/DiscoverSidebar.tsx +++ b/src/renderer/src/pages/discover/components/DiscoverSidebar.tsx @@ -9,7 +9,7 @@ import { SidebarProvider } from '@renderer/ui/sidebar' -import { InternalCategory } from '../hooks/useDiscoverCategories' +import { InternalCategory } from '../type' interface DiscoverSidebarProps { activeCategory: InternalCategory | undefined diff --git a/src/renderer/src/pages/discover/hooks/useDiscoverCategories.ts b/src/renderer/src/pages/discover/hooks/useDiscoverCategories.ts index 067a2b272b..8d2cd641ec 100644 --- a/src/renderer/src/pages/discover/hooks/useDiscoverCategories.ts +++ b/src/renderer/src/pages/discover/hooks/useDiscoverCategories.ts @@ -1,18 +1,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useLocation, useNavigate } from 'react-router-dom' -import { CATEGORY_REGISTRY, InternalCategory } from '../routers' - -// 导出接口供其他文件使用 -export type { InternalCategory } - -// Helper to find category by path -const findCategoryByPath = (path: string | undefined): InternalCategory | undefined => - CATEGORY_REGISTRY.find((cat) => cat.path === path) - -// Helper to find category by id (activeTab) -const findCategoryById = (id: string | undefined): InternalCategory | undefined => - CATEGORY_REGISTRY.find((cat) => cat.id === id) +import { ROUTERS, ROUTERS_ENTRIES } from '../routers' export function useDiscoverCategories() { const [activeTab, setActiveTab] = useState('') @@ -34,8 +23,8 @@ export function useDiscoverCategories() { // 处理基础路径重定向 if (location.pathname === '/discover' || location.pathname === '/discover/') { - if (CATEGORY_REGISTRY.length > 0) { - const firstCategory = CATEGORY_REGISTRY[0] + if (ROUTERS.length > 0) { + const firstCategory = ROUTERS[0] navigate(`/discover/${firstCategory.path}?category=${firstCategory.id}&subcategory=all`, { replace: true }) } return @@ -46,14 +35,14 @@ export function useDiscoverCategories() { // 如果没有 category 参数,尝试从路径推断 if (!targetCategoryId && currentCategoryPath) { - const categoryFromPath = findCategoryByPath(currentCategoryPath) + const categoryFromPath = ROUTERS_ENTRIES[currentCategoryPath] targetCategoryId = categoryFromPath?.id || null } // 处理无效分类重定向 - if (!targetCategoryId || !findCategoryById(targetCategoryId)) { - if (CATEGORY_REGISTRY.length > 0) { - const firstCategory = CATEGORY_REGISTRY[0] + if (!targetCategoryId || !ROUTERS_ENTRIES[targetCategoryId]) { + if (ROUTERS.length > 0) { + const firstCategory = ROUTERS[0] navigate(`/discover/${firstCategory.path}?category=${firstCategory.id}&subcategory=all`, { replace: true }) } return @@ -74,7 +63,7 @@ export function useDiscoverCategories() { }, [location.pathname, location.search, navigate]) // 故意不包含 activeTab 和 selectedSubcategory 以避免重复渲染 const currentCategory = useMemo(() => { - return findCategoryById(activeTab) + return ROUTERS_ENTRIES[activeTab] }, [activeTab]) // 优化的 Tab 选择处理,使用 useCallback 避免重复渲染 @@ -83,7 +72,7 @@ export function useDiscoverCategories() { (tabId: string) => { if (activeTab === tabId) return // 如果已经是当前 tab,直接返回 - const categoryToSelect = findCategoryById(tabId) + const categoryToSelect = ROUTERS_ENTRIES[tabId] if (categoryToSelect?.path) { isUserNavigationRef.current = true navigate(`/discover/${categoryToSelect.path}?category=${tabId}&subcategory=all`) @@ -97,7 +86,7 @@ export function useDiscoverCategories() { (subcategoryId: string) => { if (selectedSubcategory === subcategoryId) return // 如果已经是当前子分类,直接返回 - const currentCatDetails = findCategoryById(activeTab) + const currentCatDetails = ROUTERS_ENTRIES[activeTab] if (currentCatDetails?.path) { isUserNavigationRef.current = true navigate(`/discover/${currentCatDetails.path}?category=${activeTab}&subcategory=${subcategoryId}`) @@ -107,7 +96,6 @@ export function useDiscoverCategories() { ) return { - categories: CATEGORY_REGISTRY, // 直接返回静态注册表 activeTab, selectedSubcategory, currentCategory, diff --git a/src/renderer/src/pages/discover/index.tsx b/src/renderer/src/pages/discover/index.tsx index ca7837e546..e39d1faf4a 100644 --- a/src/renderer/src/pages/discover/index.tsx +++ b/src/renderer/src/pages/discover/index.tsx @@ -6,19 +6,14 @@ import { useTranslation } from 'react-i18next' import DiscoverMain from './components/DiscoverMain' import DiscoverSidebar from './components/DiscoverSidebar' import { useDiscoverCategories } from './hooks/useDiscoverCategories' +import { ROUTERS } from './routers' export default function DiscoverPage() { const { t } = useTranslation() - const { categories, activeTab, selectedSubcategory, currentCategory, handleSelectTab, handleSelectSubcategory } = + const { activeTab, selectedSubcategory, currentCategory, handleSelectTab, handleSelectSubcategory } = useDiscoverCategories() - // 使用 useMemo 优化 tabs 数据,避免每次渲染都创建新数组 - const vercelTabsData = useMemo(() => { - return categories.map((category) => ({ - id: category.id, - label: category.title - })) - }, [categories]) + const tabs = useMemo(() => ROUTERS.map((router) => ({ id: router.id, label: router.title })), []) return (
@@ -29,9 +24,9 @@ export default function DiscoverPage() { - {categories.length > 0 && ( -
- + {ROUTERS.length > 0 && ( +
+
)} @@ -45,16 +40,9 @@ export default function DiscoverPage() { />
)} - {/* {!currentCategory && categories.length > 0 && ( -
Select a category...
- )} */}
- +
diff --git a/src/renderer/src/pages/discover/pages/agents/AgentsPage.tsx b/src/renderer/src/pages/discover/pages/agents/AgentsPage.tsx index 267163e915..ea9ebbf99c 100644 --- a/src/renderer/src/pages/discover/pages/agents/AgentsPage.tsx +++ b/src/renderer/src/pages/discover/pages/agents/AgentsPage.tsx @@ -185,7 +185,7 @@ const AgentsPage: FC = () => { {/* */} {/* */} {/* {t('agents.title')} */} -
+ {/*
{ onPressEnter={handleSearch} onBlur={handleSearchInputBlur} /> -
-
+
*/} {/* */} {/* */} diff --git a/src/renderer/src/pages/discover/pages/minapps/MinAppsPage.tsx b/src/renderer/src/pages/discover/pages/minapps/MinAppsPage.tsx index b10c7281ab..39ff89a1bc 100644 --- a/src/renderer/src/pages/discover/pages/minapps/MinAppsPage.tsx +++ b/src/renderer/src/pages/discover/pages/minapps/MinAppsPage.tsx @@ -1,8 +1,6 @@ -import { Navbar, NavbarMain } from '@renderer/components/app/Navbar' import App from '@renderer/components/MinApp/MinApp' import Scrollbar from '@renderer/components/Scrollbar' import { useMinapps } from '@renderer/hooks/useMinapps' -import { useNavbarPosition } from '@renderer/hooks/useSettings' import { Button, Input } from 'antd' import { Search, SettingsIcon } from 'lucide-react' import React, { FC, useState } from 'react' @@ -16,7 +14,7 @@ const AppsPage: FC = () => { const { t } = useTranslation() const [search, setSearch] = useState('') const { minapps } = useMinapps() - const { isTopNavbar } = useNavbarPosition() + // const { isTopNavbar } = useNavbarPosition() const filteredApps = search ? minapps.filter( @@ -37,40 +35,40 @@ const AppsPage: FC = () => { return ( - - - {/* {t('minapp.title')} */} - {/*
*/} - } - value={search} - onChange={(e) => setSearch(e.target.value)} - /> -
*/} -
-
+ {/* */} + {/* */} + {/* {t('minapp.title')} */} +
+ } + value={search} + onChange={(e) => setSearch(e.target.value)} + /> +
+ {/*
*/} + {/*
*/} - {isTopNavbar && ( + {/* {isTopNavbar && ( { onClick={() => MinappSettingsPopup.show()} /> - )} + )} */} {filteredApps.map((app) => ( @@ -119,15 +117,15 @@ const ContentContainer = styled.div` height: 100%; ` -const HeaderContainer = styled.div` - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - height: 60px; - width: 100%; - gap: 10px; -` +// const HeaderContainer = styled.div` +// display: flex; +// flex-direction: row; +// justify-content: center; +// align-items: center; +// height: 60px; +// width: 100%; +// gap: 10px; +// ` const MainContainer = styled.div` display: flex; diff --git a/src/renderer/src/pages/discover/routers.ts b/src/renderer/src/pages/discover/routers.ts index 8be3f1ae52..3b85f70a79 100644 --- a/src/renderer/src/pages/discover/routers.ts +++ b/src/renderer/src/pages/discover/routers.ts @@ -2,18 +2,22 @@ import i18n from '@renderer/i18n' import { CherryStoreType } from '@renderer/types/cherryStore' import { lazy } from 'react' -export const discoverRouters = [ +export const ROUTERS = [ { id: CherryStoreType.ASSISTANT, title: i18n.t('assistants.title'), path: 'assistant', - component: lazy(() => import('./pages/agents/AgentsPage')) + component: lazy(() => import('./pages/agents/AgentsPage')), + hasSidebar: false, // 目前都没有侧边栏 + items: [{ id: 'all', name: `All ${i18n.t('assistants.title')}` }] // 预设 "All" 子分类 }, { id: CherryStoreType.MINI_APP, title: i18n.t('minapp.title'), path: 'mini-app', - component: lazy(() => import('./pages/minapps/MinAppsPage')) + component: lazy(() => import('./pages/minapps/MinAppsPage')), + hasSidebar: false, // 目前都没有侧边栏 + items: [{ id: 'all', name: `All ${i18n.t('minapp.title')}` }] // 预设 "All" 子分类 } // { // id: CherryStoreType.TRANSLATE, @@ -43,20 +47,10 @@ export const discoverRouters = [ // } ] -// 静态注册表 - 避免每次渲染都重新生成 -export interface InternalCategory { - id: string - title: string - path: string - hasSidebar?: boolean - items: Array<{ id: string; name: string; count?: number }> -} - -// 预生成的分类注册表 -export const CATEGORY_REGISTRY: InternalCategory[] = discoverRouters.map((router) => ({ - id: router.id, - title: router.title, - path: router.path, - hasSidebar: false, // 目前都没有侧边栏 - items: [{ id: 'all', name: `All ${router.title}` }] // 预设 "All" 子分类 -})) +export const ROUTERS_ENTRIES = ROUTERS.reduce( + (acc, { id, ...rest }) => { + acc[id] = rest + return acc + }, + {} as Record<(typeof ROUTERS)[number]['id'], Omit<(typeof ROUTERS)[number], 'id'>> +) diff --git a/src/renderer/src/pages/discover/type.ts b/src/renderer/src/pages/discover/type.ts new file mode 100644 index 0000000000..79c1dd367b --- /dev/null +++ b/src/renderer/src/pages/discover/type.ts @@ -0,0 +1,7 @@ +export interface InternalCategory { + id: string + title: string + path: string + hasSidebar?: boolean + items: Array<{ id: string; name: string; count?: number }> +} diff --git a/src/renderer/src/pages/discover/utils/index.ts b/src/renderer/src/pages/discover/utils/index.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/renderer/src/pages/discover/utils/util.ts b/src/renderer/src/pages/discover/utils/util.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/renderer/src/types/cherryStore.ts b/src/renderer/src/types/cherryStore.ts index 3dd2910186..38f31e0c5a 100644 --- a/src/renderer/src/types/cherryStore.ts +++ b/src/renderer/src/types/cherryStore.ts @@ -1,13 +1,13 @@ export enum CherryStoreType { ASSISTANT = 'Assistant', - MINI_APP = 'Mini-App', - KNOWLEDGE = 'Knowledge', - MCP_SERVER = 'MCP-Server', - MODEL_PROVIDER = 'Model-Provider', - AGENT = 'Agent', - TRANSLATE = 'Translate', - PAINTINGS = 'Paintings', - FILES = 'Files' + MINI_APP = 'Mini-App' + // KNOWLEDGE = 'Knowledge', + // MCP_SERVER = 'MCP-Server', + // MODEL_PROVIDER = 'Model-Provider', + // AGENT = 'Agent', + // TRANSLATE = 'Translate', + // PAINTINGS = 'Paintings', + // FILES = 'Files' } export interface SubCategoryItem { diff --git a/src/renderer/src/ui/vercel-tabs.tsx b/src/renderer/src/ui/vercel-tabs.tsx index 1419a23a39..74848c50ce 100644 --- a/src/renderer/src/ui/vercel-tabs.tsx +++ b/src/renderer/src/ui/vercel-tabs.tsx @@ -1,6 +1,6 @@ import { cn } from '@renderer/utils' import * as React from 'react' -import { useEffect, useRef, useState } from 'react' +import { useEffect, useMemo, useRef, useState } from 'react' interface Tab { id: string @@ -13,73 +13,86 @@ interface TabsProps extends React.HTMLAttributes { onTabChange?: (tabId: string) => void } -const Tabs = ({ +// 提取常用的性能优化类 +const PERFORMANCE_CLASSES = 'will-change-transform [backface-visibility:hidden] [transform-style:preserve-3d]' + +const TabsComponent = ({ ref, className, tabs, - activeTab: _, + activeTab, onTabChange, ...props }: TabsProps & { ref?: React.RefObject }) => { const [hoveredIndex, setHoveredIndex] = useState(null) - const [activeIndex, setActiveIndex] = useState(0) - const [hoverStyle, setHoverStyle] = useState({}) - const [activeStyle, setActiveStyle] = useState({ left: '0px', width: '0px' }) + const [hoverStyle, setHoverStyle] = useState({ transform: 'translate3d(0px, 0px, 0px)', width: '0px' }) + const [activeStyle, setActiveStyle] = useState({ transform: 'translate3d(0px, 0px, 0px)', width: '0px' }) const tabRefs = useRef<(HTMLDivElement | null)[]>([]) + const activeIndex = useMemo(() => { + if (activeTab) { + const index = tabs.findIndex((tab) => tab.id === activeTab) + return index !== -1 ? index : 0 + } + return 0 + }, [activeTab, tabs]) + useEffect(() => { if (hoveredIndex !== null) { const hoveredElement = tabRefs.current[hoveredIndex] if (hoveredElement) { const { offsetLeft, offsetWidth } = hoveredElement setHoverStyle({ - left: `${offsetLeft}px`, + transform: `translate3d(${offsetLeft}px, 0px, 0px)`, width: `${offsetWidth}px` }) } } }, [hoveredIndex]) - useEffect(() => { - const activeElement = tabRefs.current[activeIndex] - if (activeElement) { - const { offsetLeft, offsetWidth } = activeElement - setActiveStyle({ - left: `${offsetLeft}px`, - width: `${offsetWidth}px` - }) - } - }, [activeIndex]) - useEffect(() => { requestAnimationFrame(() => { - const firstElement = tabRefs.current[0] - if (firstElement) { - const { offsetLeft, offsetWidth } = firstElement + const activeElement = tabRefs.current[activeIndex] + if (activeElement) { + const { offsetLeft, offsetWidth } = activeElement setActiveStyle({ - left: `${offsetLeft}px`, + transform: `translate3d(${offsetLeft}px, 0px, 0px)`, width: `${offsetWidth}px` }) } }) - }, []) + }, [activeIndex]) // 使用 translate3d 强制启用硬件加速 return (
{/* Hover Highlight */}
{/* Active Indicator */}
{/* Tabs */} @@ -97,7 +110,6 @@ const Tabs = ({ onMouseEnter={() => setHoveredIndex(index)} onMouseLeave={() => setHoveredIndex(null)} onClick={() => { - setActiveIndex(index) onTabChange?.(tab.id) }}>
@@ -110,6 +122,9 @@ const Tabs = ({
) } + +const Tabs = React.memo(TabsComponent) + Tabs.displayName = 'Tabs' export { Tabs }