From 9906bb7ea9e6684aff02e5d303a3a1d53d272f64 Mon Sep 17 00:00:00 2001 From: mtvpls Date: Thu, 26 Feb 2026 10:08:28 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8B=E8=BD=BD=E6=8C=89=E9=92=AE=E5=8F=AF?= =?UTF-8?q?=E6=8B=96=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/DownloadBubble.tsx | 117 +++++++++++++++++++++++++++++- 1 file changed, 114 insertions(+), 3 deletions(-) diff --git a/src/components/DownloadBubble.tsx b/src/components/DownloadBubble.tsx index 628d72f..2cd4658 100644 --- a/src/components/DownloadBubble.tsx +++ b/src/components/DownloadBubble.tsx @@ -1,20 +1,131 @@ 'use client'; -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { useDownload } from '@/contexts/DownloadContext'; export function DownloadBubble() { const { tasks, downloadingCount, setShowDownloadPanel } = useDownload(); + // 拖动状态 + const [position, setPosition] = useState({ x: 0, y: 0 }); + const [isDragging, setIsDragging] = useState(false); + const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 }); + + // 初始化位置(右下角) + useEffect(() => { + const initPosition = () => { + setPosition({ + x: window.innerWidth - 80, + y: window.innerHeight - 80 + }); + }; + + initPosition(); + window.addEventListener('resize', initPosition); + return () => window.removeEventListener('resize', initPosition); + }, []); + + // 处理拖动 + useEffect(() => { + const handleMouseMove = (e: MouseEvent) => { + if (!isDragging) return; + + const newX = e.clientX - dragOffset.x; + const newY = e.clientY - dragOffset.y; + + // 限制在视口范围内 + const maxX = window.innerWidth - 64; + const maxY = window.innerHeight - 64; + + setPosition({ + x: Math.max(0, Math.min(newX, maxX)), + y: Math.max(0, Math.min(newY, maxY)) + }); + }; + + const handleTouchMove = (e: TouchEvent) => { + if (!isDragging) return; + e.preventDefault(); + + const touch = e.touches[0]; + const newX = touch.clientX - dragOffset.x; + const newY = touch.clientY - dragOffset.y; + + // 限制在视口范围内 + const maxX = window.innerWidth - 64; + const maxY = window.innerHeight - 64; + + setPosition({ + x: Math.max(0, Math.min(newX, maxX)), + y: Math.max(0, Math.min(newY, maxY)) + }); + }; + + const handleEnd = () => { + setIsDragging(false); + }; + + if (isDragging) { + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleEnd); + document.addEventListener('touchmove', handleTouchMove, { passive: false }); + document.addEventListener('touchend', handleEnd); + } + + return () => { + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleEnd); + document.removeEventListener('touchmove', handleTouchMove); + document.removeEventListener('touchend', handleEnd); + }; + }, [isDragging, dragOffset]); + + const handleMouseDown = (e: React.MouseEvent) => { + if (e.button !== 0) return; + setIsDragging(true); + const rect = e.currentTarget.getBoundingClientRect(); + setDragOffset({ + x: e.clientX - rect.left, + y: e.clientY - rect.top + }); + }; + + const handleTouchStart = (e: React.TouchEvent) => { + setIsDragging(true); + const touch = e.touches[0]; + const rect = e.currentTarget.getBoundingClientRect(); + setDragOffset({ + x: touch.clientX - rect.left, + y: touch.clientY - rect.top + }); + }; + if (tasks.length === 0) { return null; } return ( -
+