fix: 修复标签预览弹窗位置,固定显示在标签上方并防止超出边界
This commit is contained in:
parent
e314edb283
commit
976e8de4c7
@ -1425,98 +1425,107 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 🆕 标签悬停预览弹窗 */}
|
||||
{tagPreview && (
|
||||
<div
|
||||
data-tag-preview="true"
|
||||
className="fixed z-[9999] bg-white dark:bg-zinc-900 rounded-xl shadow-2xl border border-border/50 overflow-hidden animate-in fade-in zoom-in-95 duration-150"
|
||||
style={{
|
||||
left: tagPreview.x,
|
||||
top: tagPreview.y - 8,
|
||||
transform: 'translate(-50%, -100%)',
|
||||
maxWidth: '280px',
|
||||
}}
|
||||
onMouseEnter={() => {
|
||||
// 鼠标进入预览弹窗,取消隐藏
|
||||
if (previewTimeoutRef.current) {
|
||||
clearTimeout(previewTimeoutRef.current);
|
||||
previewTimeoutRef.current = null;
|
||||
}
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
// 鼠标离开预览弹窗,隐藏
|
||||
setTagPreview(null);
|
||||
}}
|
||||
>
|
||||
{/* 图片全貌 */}
|
||||
<div className="relative">
|
||||
<img
|
||||
src={tagPreview.url}
|
||||
alt={tagPreview.name}
|
||||
className="w-full h-auto max-h-[200px] object-contain bg-zinc-100 dark:bg-zinc-800"
|
||||
/>
|
||||
{/* 如果是物品标签,显示标注位置 */}
|
||||
{tagPreview.pickedObject && (
|
||||
<>
|
||||
{/* 标注点 */}
|
||||
<div
|
||||
className="absolute pointer-events-none"
|
||||
style={{
|
||||
left: `${tagPreview.pickedObject.clickX * 100}%`,
|
||||
top: `${tagPreview.pickedObject.clickY * 100}%`,
|
||||
transform: 'translate(-50%, -50%)',
|
||||
}}
|
||||
>
|
||||
{/* 序号标记 */}
|
||||
<div className="relative flex items-center justify-center">
|
||||
{/* 脉冲动画背景 */}
|
||||
<div className="absolute w-8 h-8 rounded-full bg-blue-500/30 animate-ping" />
|
||||
{/* 主标记点 */}
|
||||
<div className="relative w-6 h-6 rounded-full bg-blue-500 border-2 border-white shadow-lg flex items-center justify-center">
|
||||
<svg className="w-3 h-3 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3">
|
||||
<path d="M12 2v20M2 12h20" />
|
||||
</svg>
|
||||
{/* 🆕 标签悬停预览弹窗 - 固定显示在标签上方 */}
|
||||
{tagPreview && (() => {
|
||||
// 计算弹窗位置,确保不超出视口
|
||||
const previewWidth = 240;
|
||||
const previewHeight = 280; // 预估高度
|
||||
const padding = 16;
|
||||
|
||||
// 水平位置:居中对齐标签,但确保不超出左右边界
|
||||
let left = tagPreview.x - previewWidth / 2;
|
||||
if (left < padding) left = padding;
|
||||
if (left + previewWidth > window.innerWidth - padding) {
|
||||
left = window.innerWidth - previewWidth - padding;
|
||||
}
|
||||
|
||||
// 垂直位置:显示在标签上方
|
||||
let top = tagPreview.y - previewHeight - 12;
|
||||
if (top < padding) {
|
||||
// 如果上方空间不够,显示在标签下方
|
||||
top = tagPreview.y + 30;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
data-tag-preview="true"
|
||||
className="fixed z-[9999] bg-white dark:bg-zinc-900 rounded-xl shadow-2xl border border-border/50 overflow-hidden animate-in fade-in zoom-in-95 duration-150"
|
||||
style={{
|
||||
left: left,
|
||||
top: top,
|
||||
width: previewWidth,
|
||||
}}
|
||||
onMouseEnter={() => {
|
||||
// 鼠标进入预览弹窗,取消隐藏
|
||||
if (previewTimeoutRef.current) {
|
||||
clearTimeout(previewTimeoutRef.current);
|
||||
previewTimeoutRef.current = null;
|
||||
}
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
// 鼠标离开预览弹窗,隐藏
|
||||
setTagPreview(null);
|
||||
}}
|
||||
>
|
||||
{/* 图片全貌 */}
|
||||
<div className="relative">
|
||||
<img
|
||||
src={tagPreview.url}
|
||||
alt={tagPreview.name}
|
||||
className="w-full h-auto max-h-[180px] object-contain bg-zinc-100 dark:bg-zinc-800"
|
||||
/>
|
||||
{/* 如果是物品标签,显示标注位置 */}
|
||||
{tagPreview.pickedObject && (
|
||||
<>
|
||||
{/* 标注点 */}
|
||||
<div
|
||||
className="absolute pointer-events-none"
|
||||
style={{
|
||||
left: `${tagPreview.pickedObject.clickX * 100}%`,
|
||||
top: `${tagPreview.pickedObject.clickY * 100}%`,
|
||||
transform: 'translate(-50%, -50%)',
|
||||
}}
|
||||
>
|
||||
{/* 序号标记 */}
|
||||
<div className="relative flex items-center justify-center">
|
||||
{/* 脉冲动画背景 */}
|
||||
<div className="absolute w-6 h-6 rounded-full bg-blue-500/30 animate-ping" />
|
||||
{/* 主标记点 */}
|
||||
<div className="relative w-5 h-5 rounded-full bg-blue-500 border-2 border-white shadow-lg flex items-center justify-center">
|
||||
<svg className="w-2.5 h-2.5 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3">
|
||||
<path d="M12 2v20M2 12h20" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* 物品名称标签 */}
|
||||
<div
|
||||
className="absolute bg-blue-500 text-white text-xs px-2 py-0.5 rounded shadow-lg whitespace-nowrap"
|
||||
style={{
|
||||
left: `${tagPreview.pickedObject.clickX * 100}%`,
|
||||
top: `${tagPreview.pickedObject.clickY * 100}%`,
|
||||
transform: 'translate(-50%, -150%)',
|
||||
}}
|
||||
>
|
||||
{tagPreview.pickedObject.objectName}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{/* 底部信息 */}
|
||||
<div className="px-3 py-2 bg-zinc-50 dark:bg-zinc-800/50 border-t border-border/30">
|
||||
<div className="text-xs font-medium text-foreground/80 truncate">
|
||||
{tagPreview.pickedObject ? tagPreview.pickedObject.objectName : tagPreview.name}
|
||||
{/* 物品名称标签 */}
|
||||
<div
|
||||
className="absolute bg-blue-500 text-white text-xs px-1.5 py-0.5 rounded shadow-lg whitespace-nowrap"
|
||||
style={{
|
||||
left: `${tagPreview.pickedObject.clickX * 100}%`,
|
||||
top: `${tagPreview.pickedObject.clickY * 100}%`,
|
||||
transform: 'translate(-50%, -140%)',
|
||||
}}
|
||||
>
|
||||
{tagPreview.pickedObject.objectName}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{tagPreview.pickedObject && (
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{tagPreview.pickedObject.objectPosition}
|
||||
{/* 底部信息 */}
|
||||
<div className="px-3 py-2 bg-zinc-50 dark:bg-zinc-800/50 border-t border-border/30">
|
||||
<div className="text-xs font-medium text-foreground/80 truncate">
|
||||
{tagPreview.pickedObject ? tagPreview.pickedObject.objectName : tagPreview.name}
|
||||
</div>
|
||||
)}
|
||||
{tagPreview.pickedObject && (
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{tagPreview.pickedObject.objectPosition}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* 小三角箭头 */}
|
||||
<div
|
||||
className="absolute left-1/2 bottom-0 -translate-x-1/2 translate-y-full"
|
||||
style={{
|
||||
width: 0,
|
||||
height: 0,
|
||||
borderLeft: '8px solid transparent',
|
||||
borderRight: '8px solid transparent',
|
||||
borderTop: '8px solid rgb(250 250 250)',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user