diff --git a/UPDATELOG.md b/UPDATELOG.md index e510a8b4b..b8c7bf07e 100644 --- a/UPDATELOG.md +++ b/UPDATELOG.md @@ -17,6 +17,7 @@ - 多版本画布流量图表,丰富可视化选项 - 新增强制刷新 `Clash` 配置/节点缓存功能,提升更新响应速度 - 增加代理请求缓存机制,减少重复 `API` 调用 +- 添加首页卡片移动 (暂测) ### 🚀 性能优化 diff --git a/package.json b/package.json index eaae96d46..d0a4c9557 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "monaco-yaml": "^5.4.0", "nanoid": "^5.1.5", "react": "19.1.0", + "react-beautiful-dnd": "^13.1.1", "react-chartjs-2": "^5.3.0", "react-dom": "19.1.0", "react-error-boundary": "6.0.0", @@ -84,6 +85,7 @@ "@types/js-yaml": "^4.0.9", "@types/lodash-es": "^4.17.12", "@types/react": "19.1.8", + "@types/react-beautiful-dnd": "^13.1.8", "@types/react-dom": "19.1.6", "@vitejs/plugin-legacy": "^7.1.0", "@vitejs/plugin-react": "4.7.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ab9c62458..d53265469 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -116,6 +116,9 @@ importers: react: specifier: 19.1.0 version: 19.1.0 + react-beautiful-dnd: + specifier: ^13.1.1 + version: 13.1.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react-chartjs-2: specifier: ^5.3.0 version: 5.3.0(chart.js@4.5.0)(react@19.1.0) @@ -174,6 +177,9 @@ importers: '@types/react': specifier: 19.1.8 version: 19.1.8 + '@types/react-beautiful-dnd': + specifier: ^13.1.8 + version: 13.1.8 '@types/react-dom': specifier: 19.1.6 version: 19.1.6(@types/react@19.1.8) @@ -1586,6 +1592,11 @@ packages: '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/hoist-non-react-statics@3.3.7': + resolution: {integrity: sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g==} + peerDependencies: + '@types/react': '*' + '@types/js-yaml@4.0.9': resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} @@ -1610,11 +1621,17 @@ packages: '@types/prop-types@15.7.15': resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + '@types/react-beautiful-dnd@13.1.8': + resolution: {integrity: sha512-E3TyFsro9pQuK4r8S/OL6G99eq7p8v29sX0PM7oT8Z+PJfZvSQTx4zTQbUJ+QZXioAF0e7TGBEcA1XhYhCweyQ==} + '@types/react-dom@19.1.6': resolution: {integrity: sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==} peerDependencies: '@types/react': ^19.0.0 + '@types/react-redux@7.1.34': + resolution: {integrity: sha512-GdFaVjEbYv4Fthm2ZLvj1VSCedV7TqE5y1kNwnjSdBOTXuRSgowux6J8TAct15T3CKBr63UMk+2CO7ilRhyrAQ==} + '@types/react-transition-group@4.4.12': resolution: {integrity: sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==} peerDependencies: @@ -1846,6 +1863,9 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + css-box-model@1.2.1: + resolution: {integrity: sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==} + csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} @@ -2262,6 +2282,9 @@ packages: mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + memoize-one@5.2.1: + resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} + memoizee@0.4.17: resolution: {integrity: sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA==} engines: {node: '>=0.12'} @@ -2500,6 +2523,16 @@ packages: proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + raf-schd@4.0.3: + resolution: {integrity: sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==} + + react-beautiful-dnd@13.1.1: + resolution: {integrity: sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==} + deprecated: 'react-beautiful-dnd is now deprecated. Context and options: https://github.com/atlassian/react-beautiful-dnd/issues/2672' + peerDependencies: + react: ^16.8.5 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.5 || ^17.0.0 || ^18.0.0 + react-chartjs-2@5.3.0: resolution: {integrity: sha512-UfZZFnDsERI3c3CZGxzvNJd02SHjaSJ8kgW1djn65H1KK8rehwTjyrRKOG3VTMG8wtHZ5rgAO5oTHtHi9GCCmw==} peerDependencies: @@ -2544,6 +2577,9 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + react-is@19.1.0: resolution: {integrity: sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==} @@ -2560,6 +2596,18 @@ packages: react: '>=16.8.0 <20.0.0' react-dom: '>=16.8.0 <20.0.0' + react-redux@7.2.9: + resolution: {integrity: sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==} + peerDependencies: + react: ^16.8.3 || ^17 || ^18 + react-dom: '*' + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + react-refresh@0.17.0: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} @@ -2601,6 +2649,9 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} + redux@4.2.1: + resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==} + regenerate-unicode-properties@10.2.0: resolution: {integrity: sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==} engines: {node: '>=4'} @@ -2762,6 +2813,9 @@ packages: resolution: {integrity: sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==} engines: {node: '>=0.12'} + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tinyglobby@0.2.14: resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} engines: {node: '>=12.0.0'} @@ -2841,6 +2895,11 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + use-memo-one@1.1.3: + resolution: {integrity: sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + use-sync-external-store@1.5.0: resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==} peerDependencies: @@ -4404,6 +4463,11 @@ snapshots: dependencies: '@types/unist': 3.0.3 + '@types/hoist-non-react-statics@3.3.7(@types/react@19.1.8)': + dependencies: + '@types/react': 19.1.8 + hoist-non-react-statics: 3.3.2 + '@types/js-yaml@4.0.9': {} '@types/json-schema@7.0.15': {} @@ -4424,10 +4488,21 @@ snapshots: '@types/prop-types@15.7.15': {} + '@types/react-beautiful-dnd@13.1.8': + dependencies: + '@types/react': 19.1.8 + '@types/react-dom@19.1.6(@types/react@19.1.8)': dependencies: '@types/react': 19.1.8 + '@types/react-redux@7.1.34': + dependencies: + '@types/hoist-non-react-statics': 3.3.7(@types/react@19.1.8) + '@types/react': 19.1.8 + hoist-non-react-statics: 3.3.2 + redux: 4.2.1 + '@types/react-transition-group@4.4.12(@types/react@19.1.8)': dependencies: '@types/react': 19.1.8 @@ -4666,6 +4741,10 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + css-box-model@1.2.1: + dependencies: + tiny-invariant: 1.3.3 + csstype@3.1.3: {} d@1.0.2: @@ -5153,6 +5232,8 @@ snapshots: dependencies: '@types/mdast': 4.0.4 + memoize-one@5.2.1: {} + memoizee@0.4.17: dependencies: d: 1.0.2 @@ -5456,6 +5537,22 @@ snapshots: proxy-from-env@1.1.0: {} + raf-schd@4.0.3: {} + + react-beautiful-dnd@13.1.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.6 + css-box-model: 1.2.1 + memoize-one: 5.2.1 + raf-schd: 4.0.3 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-redux: 7.2.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + redux: 4.2.1 + use-memo-one: 1.1.3(react@19.1.0) + transitivePeerDependencies: + - react-native + react-chartjs-2@5.3.0(chart.js@4.5.0)(react@19.1.0): dependencies: chart.js: 4.5.0 @@ -5489,6 +5586,8 @@ snapshots: react-is@16.13.1: {} + react-is@17.0.2: {} + react-is@19.1.0: {} react-markdown@10.1.0(@types/react@19.1.8)(react@19.1.0): @@ -5515,6 +5614,18 @@ snapshots: react: 19.1.0 react-dom: 19.1.0(react@19.1.0) + react-redux@7.2.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.6 + '@types/react-redux': 7.1.34 + hoist-non-react-statics: 3.3.2 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 19.1.0 + react-is: 17.0.2 + optionalDependencies: + react-dom: 19.1.0(react@19.1.0) + react-refresh@0.17.0: {} react-router-dom@7.7.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): @@ -5549,6 +5660,10 @@ snapshots: readdirp@4.1.2: {} + redux@4.2.1: + dependencies: + '@babel/runtime': 7.27.6 + regenerate-unicode-properties@10.2.0: dependencies: regenerate: 1.4.2 @@ -5741,6 +5856,8 @@ snapshots: es5-ext: 0.10.64 next-tick: 1.1.0 + tiny-invariant@1.3.3: {} + tinyglobby@0.2.14: dependencies: fdir: 6.4.6(picomatch@4.0.3) @@ -5821,6 +5938,10 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + use-memo-one@1.1.3(react@19.1.0): + dependencies: + react: 19.1.0 + use-sync-external-store@1.5.0(react@19.1.0): dependencies: react: 19.1.0 diff --git a/src/pages/home.tsx b/src/pages/home.tsx index 1d9fbaa86..e28151b21 100644 --- a/src/pages/home.tsx +++ b/src/pages/home.tsx @@ -4,7 +4,6 @@ import { Button, IconButton, useTheme, - keyframes, Dialog, DialogTitle, DialogContent, @@ -29,7 +28,7 @@ import { useNavigate } from "react-router-dom"; import { ProxyTunCard } from "@/components/home/proxy-tun-card"; import { ClashModeCard } from "@/components/home/clash-mode-card"; import { EnhancedTrafficStats } from "@/components/home/enhanced-traffic-stats"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import { HomeProfileCard } from "@/components/home/home-profile-card"; import { EnhancedCard } from "@/components/home/enhanced-card"; import { CurrentProxyCard } from "@/components/home/current-proxy-card"; @@ -40,19 +39,14 @@ import { useLockFn } from "ahooks"; import { entry_lightweight_mode, openWebUrl } from "@/services/cmds"; import { TestCard } from "@/components/home/test-card"; import { IpInfoCard } from "@/components/home/ip-info-card"; - -// 定义旋转动画 -const round = keyframes` - from { transform: rotate(0deg); } - to { transform: rotate(360deg); } -`; - -// 辅助函数解析URL和过期时间 -function parseUrl(url?: string) { - if (!url) return "-"; - if (url.startsWith("http")) return new URL(url).host; - return "local"; -} +import { + DragDropContext, + Droppable, + Draggable, + DropResult, + DroppableProvided, + DraggableProvided, +} from "react-beautiful-dnd"; // 定义首页卡片设置接口 interface HomeCardsSettings { @@ -69,6 +63,13 @@ interface HomeCardsSettings { [key: string]: boolean; } +// 卡片配置接口,包含排序信息 +interface CardConfig { + id: string; + size: number; + enabled: boolean; +} + // 首页设置对话框组件接口 interface HomeSettingsDialogProps { open: boolean; @@ -77,6 +78,35 @@ interface HomeSettingsDialogProps { onSave: (cards: HomeCardsSettings) => void; } +// 确保对象符合HomeCardsSettings类型的辅助函数 +const ensureHomeCardsSettings = (obj: any): HomeCardsSettings => { + const defaultSettings: HomeCardsSettings = { + profile: true, + proxy: true, + network: true, + mode: true, + traffic: true, + info: false, + clashinfo: true, + systeminfo: true, + test: true, + ip: true, + }; + + if (!obj || typeof obj !== "object") return defaultSettings; + + // 合并默认值和传入对象,确保所有必要属性都存在 + return Object.keys(defaultSettings).reduce((acc, key) => { + return { + ...acc, + [key]: + typeof obj[key] === "boolean" + ? obj[key] + : defaultSettings[key as keyof HomeCardsSettings], + }; + }, {} as HomeCardsSettings); +}; + // 首页设置对话框组件 const HomeSettingsDialog = ({ open, @@ -88,15 +118,16 @@ const HomeSettingsDialog = ({ const [cards, setCards] = useState(homeCards); const { patchVerge } = useVerge(); - const handleToggle = (key: string) => { - setCards((prev: HomeCardsSettings) => ({ + const handleToggle = (key: keyof HomeCardsSettings) => { + setCards((prev) => ({ ...prev, [key]: !prev[key], })); }; const handleSave = async () => { - await patchVerge({ home_cards: cards }); + // 明确类型为HomeCardsSettings + await patchVerge({ home_cards: cards as HomeCardsSettings }); onSave(cards); onClose(); }; @@ -109,7 +140,7 @@ const HomeSettingsDialog = ({ handleToggle("profile")} /> } @@ -118,7 +149,7 @@ const HomeSettingsDialog = ({ handleToggle("proxy")} /> } @@ -127,7 +158,7 @@ const HomeSettingsDialog = ({ handleToggle("network")} /> } @@ -136,7 +167,7 @@ const HomeSettingsDialog = ({ handleToggle("mode")} /> } @@ -145,7 +176,7 @@ const HomeSettingsDialog = ({ handleToggle("traffic")} /> } @@ -154,7 +185,7 @@ const HomeSettingsDialog = ({ handleToggle("test")} /> } @@ -163,7 +194,7 @@ const HomeSettingsDialog = ({ handleToggle("ip")} /> } @@ -172,7 +203,7 @@ const HomeSettingsDialog = ({ handleToggle("clashinfo")} /> } @@ -181,7 +212,7 @@ const HomeSettingsDialog = ({ handleToggle("systeminfo")} /> } @@ -201,41 +232,74 @@ const HomeSettingsDialog = ({ export const HomePage = () => { const { t } = useTranslation(); - const { verge } = useVerge(); + const { verge, patchVerge } = useVerge(); const { current, mutateProfiles } = useProfiles(); - const navigate = useNavigate(); const theme = useTheme(); // 设置弹窗的状态 const [settingsOpen, setSettingsOpen] = useState(false); - // 卡片显示状态 + // 卡片显示状态 - 确保类型正确 const [homeCards, setHomeCards] = useState( - (verge?.home_cards as HomeCardsSettings) || { - profile: true, - proxy: true, - network: true, - mode: true, - traffic: true, - clashinfo: true, - systeminfo: true, - test: true, - ip: true, - }, + ensureHomeCardsSettings(verge?.home_cards), ); - // 导航到订阅页面 - const goToProfiles = () => { - navigate("/profile"); - }; + // 卡片排序配置 - 默认为初始顺序 + const [cardOrder, setCardOrder] = useState( + // 明确断言类型 + (verge?.card_order as string[]) || [ + "profile", + "proxy", + "network", + "mode", + "traffic", + "test", + "ip", + "clashinfo", + "systeminfo", + ], + ); - // 导航到代理页面 - const goToProxies = () => { - navigate("/"); - }; + // 当homeCards变化时,确保cardOrder中只包含启用的卡片 + useEffect(() => { + const enabledCards = Object.entries(homeCards) + .filter(([_, enabled]) => enabled) + .map(([id]) => id); - // 导航到设置页面 - const goToSettings = () => { - navigate("/settings"); + // 过滤掉已禁用的卡片 + const filteredOrder = cardOrder.filter((id) => enabledCards.includes(id)); + + // 添加新启用但不在排序中的卡片 + const newCards = enabledCards.filter((id) => !filteredOrder.includes(id)); + + setCardOrder([...filteredOrder, ...newCards]); + }, [homeCards]); + + // 保存卡片排序 + const saveCardOrder = useLockFn(async (order: string[]) => { + await patchVerge({ card_order: order } as any); + setCardOrder(order); + }); + + // 处理拖拽结束 + const handleDragEnd = (result: DropResult) => { + const { destination, source, draggableId } = result; + + // 拖拽到无效位置或原位置,不做处理 + if ( + !destination || + (destination.droppableId === source.droppableId && + destination.index === source.index) + ) { + return; + } + + // 重新排序 + const newOrder = Array.from(cardOrder); + newOrder.splice(source.index, 1); + newOrder.splice(destination.index, 0, draggableId); + + // 保存新顺序 + saveCardOrder(newOrder); }; // 文档链接函数 @@ -243,12 +307,12 @@ export const HomePage = () => { return openWebUrl("https://clash-verge-rev.github.io/index.html"); }); - // 新增:打开设置弹窗 + // 卡片设置弹窗 const openSettings = () => { setSettingsOpen(true); }; - // 新增:保存设置时用requestIdleCallback/setTimeout + // 保存勾选设置 const handleSaveSettings = (newCards: HomeCardsSettings) => { if (window.requestIdleCallback) { window.requestIdleCallback(() => setHomeCards(newCards)); @@ -257,6 +321,68 @@ export const HomePage = () => { } }; + // 获取卡片配置信息 + const getCardConfig = (id: string): CardConfig => { + const configs: Record = { + profile: { id: "profile", size: 6, enabled: homeCards.profile }, + proxy: { id: "proxy", size: 6, enabled: homeCards.proxy }, + network: { id: "network", size: 6, enabled: homeCards.network }, + mode: { id: "mode", size: 6, enabled: homeCards.mode }, + traffic: { id: "traffic", size: 12, enabled: homeCards.traffic }, + test: { id: "test", size: 6, enabled: homeCards.test }, + ip: { id: "ip", size: 6, enabled: homeCards.ip }, + clashinfo: { id: "clashinfo", size: 6, enabled: homeCards.clashinfo }, + systeminfo: { id: "systeminfo", size: 6, enabled: homeCards.systeminfo }, + }; + + if (!configs[id]) { + console.warn(`检测到未知卡片ID: ${id},使用默认配置`); + return { id, size: 6, enabled: false }; + } + + return configs[id]; + }; + + // 渲染卡片内容 + const renderCardContent = (id: string) => { + switch (id) { + case "profile": + return ( + + ); + case "proxy": + return ; + case "network": + return ; + case "mode": + return ; + case "traffic": + return ( + } + iconColor="secondary" + > + + + ); + case "test": + return ; + case "ip": + return ; + case "clashinfo": + return ; + case "systeminfo": + return ; + default: + console.warn(`无法渲染未知卡片: ${id}`); + return null; + } + }; + return ( { } > - - {/* 订阅和当前节点部分 */} - {homeCards.profile && ( - - - - )} - - {homeCards.proxy && ( - - - - )} - - {/* 代理和网络设置区域 */} - {homeCards.network && ( - - - - )} - - {homeCards.mode && ( - - - - )} - - {/* 增强的流量统计区域 */} - {homeCards.traffic && ( - - } - iconColor="secondary" + {/* 拖拽上下文 */} + + + {(provided: DroppableProvided) => ( + - - - - )} - {/* 测试网站部分 */} - {homeCards.test && ( - - - - )} - {/* IP信息卡片 */} - {homeCards.ip && ( - - - - )} - {/* Clash信息 */} - {homeCards.clashinfo && ( - - - - )} - {/* 系统信息 */} - {homeCards.systeminfo && ( - - - - )} - + {cardOrder + .filter((id) => { + const config = getCardConfig(id); + return homeCards[id] && config.enabled; + }) + .map((id, index) => { + const config = getCardConfig(id); + if (!config) return null; + + return ( + + {(provided: DraggableProvided) => ( + + {renderCardContent(id)} + + )} + + ); + })} + {provided.placeholder} + + )} + + {/* 首页设置弹窗 */}