From a097debf4046abef302d0b3e6ae62aac932197b9 Mon Sep 17 00:00:00 2001 From: Ahao Date: Sat, 26 Jul 2025 15:06:03 +0800 Subject: [PATCH] add home page drag --- src/pages/home.tsx | 335 ++++++++++++++++++++++++++++------------ src/services/types.d.ts | 7 + 2 files changed, 242 insertions(+), 100 deletions(-) diff --git a/src/pages/home.tsx b/src/pages/home.tsx index 22560316..a3590b14 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,13 +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"; - -// 辅助函数解析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 { @@ -63,6 +63,28 @@ interface HomeCardsSettings { [key: string]: boolean; } +// 扩展IVergeConfig类型以包含所需属性 +declare module "@/hooks/use-verge" { + interface IVergeConfig { + home_cards?: HomeCardsSettings; + card_order?: string[]; + } +} + +// 辅助函数解析URL和过期时间 +function parseUrl(url?: string) { + if (!url) return "-"; + if (url.startsWith("http")) return new URL(url).host; + return "local"; +} + +// 卡片配置接口,包含排序信息 +interface CardConfig { + id: string; + size: number; + enabled: boolean; +} + // 首页设置对话框组件接口 interface HomeSettingsDialogProps { open: boolean; @@ -71,6 +93,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, @@ -82,15 +133,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(); }; @@ -103,7 +155,7 @@ const HomeSettingsDialog = ({ handleToggle("profile")} /> } @@ -112,7 +164,7 @@ const HomeSettingsDialog = ({ handleToggle("proxy")} /> } @@ -121,7 +173,7 @@ const HomeSettingsDialog = ({ handleToggle("network")} /> } @@ -130,7 +182,7 @@ const HomeSettingsDialog = ({ handleToggle("mode")} /> } @@ -139,7 +191,7 @@ const HomeSettingsDialog = ({ handleToggle("traffic")} /> } @@ -148,7 +200,7 @@ const HomeSettingsDialog = ({ handleToggle("test")} /> } @@ -157,7 +209,7 @@ const HomeSettingsDialog = ({ handleToggle("ip")} /> } @@ -166,7 +218,7 @@ const HomeSettingsDialog = ({ handleToggle("clashinfo")} /> } @@ -175,7 +227,7 @@ const HomeSettingsDialog = ({ handleToggle("systeminfo")} /> } @@ -195,28 +247,77 @@ 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 [cardOrder, setCardOrder] = useState( + // 明确断言类型 + (verge?.card_order as string[]) || [ + "profile", + "proxy", + "network", + "mode", + "traffic", + "test", + "ip", + "clashinfo", + "systeminfo", + ], + ); + + // 当homeCards变化时,确保cardOrder中只包含启用的卡片 + useEffect(() => { + const enabledCards = Object.entries(homeCards) + .filter(([_, enabled]) => enabled) + .map(([id]) => id); + + // 过滤掉已禁用的卡片 + 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); + }; + // 文档链接函数 const toGithubDoc = useLockFn(() => { return openWebUrl("https://clash-verge-rev.github.io/index.html"); @@ -236,6 +337,61 @@ 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 }, + }; + 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: + 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) => homeCards[id]) + .map((id, index) => { + const config = getCardConfig(id); + return ( + + {(provided: DraggableProvided) => ( + + {renderCardContent(id)} + + )} + + ); + })} + {provided.placeholder} + + )} + + {/* 首页设置弹窗 */}