fix: 修复标签预览弹窗显示到聊天面板外的问题,使用容器边界限制位置
This commit is contained in:
parent
1b5bd33b54
commit
a5319b5346
@ -28,8 +28,15 @@ import { ImageParamsBar, type ImageGenerationParams } from "@/components/image-p
|
|||||||
interface TagPreviewInfo {
|
interface TagPreviewInfo {
|
||||||
url: string;
|
url: string;
|
||||||
name: string;
|
name: string;
|
||||||
x: number; // 弹窗位置
|
x: number; // 标签中心x位置
|
||||||
y: number;
|
y: number; // 标签顶部y位置
|
||||||
|
// 聊天面板容器边界
|
||||||
|
containerBounds?: {
|
||||||
|
left: number;
|
||||||
|
right: number;
|
||||||
|
top: number;
|
||||||
|
bottom: number;
|
||||||
|
};
|
||||||
// 物品标签额外信息
|
// 物品标签额外信息
|
||||||
pickedObject?: {
|
pickedObject?: {
|
||||||
objectName: string;
|
objectName: string;
|
||||||
@ -406,11 +413,25 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
|
|||||||
(isPickedObject ? a.pickedObject?.objectName === objectName : true)
|
(isPickedObject ? a.pickedObject?.objectName === objectName : true)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 获取聊天面板容器边界(向上查找最近的面板容器)
|
||||||
|
const panelContainer = tagSpan.closest('.flex.flex-col.h-full') as HTMLElement | null;
|
||||||
|
let containerBounds: TagPreviewInfo['containerBounds'] = undefined;
|
||||||
|
if (panelContainer) {
|
||||||
|
const panelRect = panelContainer.getBoundingClientRect();
|
||||||
|
containerBounds = {
|
||||||
|
left: panelRect.left,
|
||||||
|
right: panelRect.right,
|
||||||
|
top: panelRect.top,
|
||||||
|
bottom: panelRect.bottom,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
setTagPreview({
|
setTagPreview({
|
||||||
url,
|
url,
|
||||||
name: objectName || tagSpan.textContent || '图片',
|
name: objectName || tagSpan.textContent || '图片',
|
||||||
x: rect.left + rect.width / 2,
|
x: rect.left + rect.width / 2,
|
||||||
y: rect.top,
|
y: rect.top,
|
||||||
|
containerBounds,
|
||||||
pickedObject: attachment?.pickedObject ? {
|
pickedObject: attachment?.pickedObject ? {
|
||||||
objectName: attachment.pickedObject.objectName,
|
objectName: attachment.pickedObject.objectName,
|
||||||
objectPosition: attachment.pickedObject.objectPosition,
|
objectPosition: attachment.pickedObject.objectPosition,
|
||||||
@ -1427,49 +1448,60 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
|
|||||||
|
|
||||||
{/* 🆕 标签悬停预览弹窗 - 固定显示在标签上方 */}
|
{/* 🆕 标签悬停预览弹窗 - 固定显示在标签上方 */}
|
||||||
{tagPreview && (() => {
|
{tagPreview && (() => {
|
||||||
// 计算弹窗位置,确保不超出视口
|
// 计算弹窗位置,确保不超出容器边界
|
||||||
const previewWidth = 240;
|
const previewWidth = 240;
|
||||||
const previewHeight = 280; // 预估高度
|
const previewHeight = 280; // 预估高度
|
||||||
const padding = 16;
|
const padding = 16;
|
||||||
|
|
||||||
// 获取聊天面板的边界(弹窗应该在面板内显示)
|
// 使用容器边界(如果有),否则使用视口
|
||||||
const viewportWidth = typeof window !== 'undefined' ? window.innerWidth : 800;
|
const bounds = tagPreview.containerBounds || {
|
||||||
const viewportHeight = typeof window !== 'undefined' ? window.innerHeight : 600;
|
left: 0,
|
||||||
|
right: typeof window !== 'undefined' ? window.innerWidth : 800,
|
||||||
|
top: 0,
|
||||||
|
bottom: typeof window !== 'undefined' ? window.innerHeight : 600,
|
||||||
|
};
|
||||||
|
|
||||||
// 水平位置:居中对齐标签,但确保不超出左右边界
|
const containerWidth = bounds.right - bounds.left;
|
||||||
|
const containerHeight = bounds.bottom - bounds.top;
|
||||||
|
|
||||||
|
// 水平位置:居中对齐标签,但确保不超出容器边界
|
||||||
let left = tagPreview.x - previewWidth / 2;
|
let left = tagPreview.x - previewWidth / 2;
|
||||||
// 确保不超出左边界
|
// 确保不超出左边界
|
||||||
if (left < padding) left = padding;
|
if (left < bounds.left + padding) {
|
||||||
// 确保不超出右边界(考虑弹窗宽度)
|
left = bounds.left + padding;
|
||||||
if (left + previewWidth > viewportWidth - padding) {
|
}
|
||||||
left = viewportWidth - previewWidth - padding;
|
// 确保不超出右边界
|
||||||
|
if (left + previewWidth > bounds.right - padding) {
|
||||||
|
left = bounds.right - previewWidth - padding;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 垂直位置:优先显示在标签上方
|
// 垂直位置:优先显示在标签上方
|
||||||
let top = tagPreview.y - previewHeight - 12;
|
let top = tagPreview.y - previewHeight - 12;
|
||||||
|
|
||||||
// 如果上方空间不够(弹窗会超出顶部),显示在标签下方
|
// 如果上方空间不够(弹窗会超出顶部),显示在标签下方
|
||||||
if (top < padding) {
|
if (top < bounds.top + padding) {
|
||||||
top = tagPreview.y + 30;
|
top = tagPreview.y + 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 再次检查:如果下方也会超出底部,则尽量靠近底部显示
|
// 再次检查:如果下方也会超出底部,则尽量靠近底部显示
|
||||||
if (top + previewHeight > viewportHeight - padding) {
|
if (top + previewHeight > bounds.bottom - padding) {
|
||||||
top = viewportHeight - previewHeight - padding;
|
top = bounds.bottom - previewHeight - padding;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 最终确保 top 不为负数
|
// 最终确保 top 不超出顶部
|
||||||
if (top < padding) top = padding;
|
if (top < bounds.top + padding) {
|
||||||
|
top = bounds.top + padding;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-tag-preview="true"
|
data-tag-preview="true"
|
||||||
className="fixed z-[10001] 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"
|
className="fixed z-[10001] 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={{
|
style={{
|
||||||
left: Math.max(padding, Math.min(left, viewportWidth - previewWidth - padding)),
|
left: left,
|
||||||
top: Math.max(padding, Math.min(top, viewportHeight - previewHeight - padding)),
|
top: top,
|
||||||
width: previewWidth,
|
width: Math.min(previewWidth, containerWidth - padding * 2),
|
||||||
maxHeight: viewportHeight - padding * 2,
|
maxHeight: containerHeight - padding * 2,
|
||||||
}}
|
}}
|
||||||
onMouseEnter={() => {
|
onMouseEnter={() => {
|
||||||
// 鼠标进入预览弹窗,取消隐藏
|
// 鼠标进入预览弹窗,取消隐藏
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user