108 lines
3.4 KiB
TypeScript
108 lines
3.4 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useEffect, useRef } from 'react'
|
|
|
|
export default function ShadowSystemIndicator() {
|
|
const [isShadowSystem, setIsShadowSystem] = useState(false)
|
|
const [isDragging, setIsDragging] = useState(false)
|
|
const [position, setPosition] = useState({ x: 0, y: 0 })
|
|
const [dragStart, setDragStart] = useState({ x: 0, y: 0 })
|
|
const indicatorRef = useRef<HTMLDivElement>(null)
|
|
|
|
// 检测是否为影子系统
|
|
useEffect(() => {
|
|
if (typeof window !== 'undefined') {
|
|
const isShadow = window.location.port === '5320' || window.location.href.includes(':5320')
|
|
setIsShadowSystem(isShadow)
|
|
|
|
// 从 localStorage 恢复位置
|
|
const savedPosition = localStorage.getItem('shadowIndicatorPosition')
|
|
if (savedPosition) {
|
|
try {
|
|
const pos = JSON.parse(savedPosition)
|
|
setPosition(pos)
|
|
} catch (e) {
|
|
// 默认位置:右上角
|
|
setPosition({ x: window.innerWidth - 120, y: 80 })
|
|
}
|
|
} else {
|
|
// 默认位置:右上角
|
|
setPosition({ x: window.innerWidth - 120, y: 80 })
|
|
}
|
|
}
|
|
}, [])
|
|
|
|
// 拖拽处理
|
|
const handleMouseDown = (e: React.MouseEvent) => {
|
|
if (e.button !== 0) return
|
|
setIsDragging(true)
|
|
setDragStart({
|
|
x: e.clientX - position.x,
|
|
y: e.clientY - position.y
|
|
})
|
|
}
|
|
|
|
useEffect(() => {
|
|
const handleMouseMove = (e: MouseEvent) => {
|
|
if (!isDragging) return
|
|
|
|
let newX = e.clientX - dragStart.x
|
|
let newY = e.clientY - dragStart.y
|
|
|
|
// 限制在视窗内
|
|
if (indicatorRef.current) {
|
|
const rect = indicatorRef.current.getBoundingClientRect()
|
|
const maxX = window.innerWidth - rect.width
|
|
const maxY = window.innerHeight - rect.height
|
|
|
|
newX = Math.max(0, Math.min(newX, maxX))
|
|
newY = Math.max(0, Math.min(newY, maxY))
|
|
}
|
|
|
|
setPosition({ x: newX, y: newY })
|
|
}
|
|
|
|
const handleMouseUp = () => {
|
|
if (isDragging) {
|
|
// 保存位置到 localStorage
|
|
const newPos = { x: position.x, y: position.y }
|
|
localStorage.setItem('shadowIndicatorPosition', JSON.stringify(newPos))
|
|
}
|
|
setIsDragging(false)
|
|
}
|
|
|
|
if (isDragging) {
|
|
document.addEventListener('mousemove', handleMouseMove)
|
|
document.addEventListener('mouseup', handleMouseUp)
|
|
}
|
|
|
|
return () => {
|
|
document.removeEventListener('mousemove', handleMouseMove)
|
|
document.removeEventListener('mouseup', handleMouseUp)
|
|
}
|
|
}, [isDragging, dragStart, position])
|
|
|
|
if (!isShadowSystem) return null
|
|
|
|
return (
|
|
<div
|
|
ref={indicatorRef}
|
|
className="fixed z-[100] cursor-move select-none"
|
|
style={{
|
|
left: `${position.x}px`,
|
|
top: `${position.y}px`,
|
|
cursor: isDragging ? 'grabbing' : 'grab'
|
|
}}
|
|
onMouseDown={handleMouseDown}
|
|
>
|
|
<div className="bg-gradient-to-r from-orange-500 to-red-500 text-white px-3 py-1.5 rounded-lg shadow-lg text-xs font-bold flex items-center gap-1.5">
|
|
<svg className="w-3 h-3 animate-pulse" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fillRule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
|
|
</svg>
|
|
<span>影子系统</span>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|