|
|
@@ -7,6 +7,7 @@
|
|
|
destroyOnClose
|
|
|
:closable="showControls"
|
|
|
:zIndex="props.zIndex"
|
|
|
+ :get-container="getModalContainer"
|
|
|
:class="['ai-assistant-modal', { fullscreen: isFullscreen }]"
|
|
|
>
|
|
|
<template #title>
|
|
|
@@ -192,6 +193,8 @@
|
|
|
// 是否显示窗口控制按钮(最大化/还原、关闭)
|
|
|
showControls?: boolean;
|
|
|
zIndex?: number;
|
|
|
+ // 挂载容器:不传则默认挂到 body;首页等场景传入 adaptive-container,使其与页面同坐标系、同缩放
|
|
|
+ getContainer?: () => HTMLElement;
|
|
|
}
|
|
|
|
|
|
interface Emits {
|
|
|
@@ -204,6 +207,20 @@
|
|
|
});
|
|
|
const emit = defineEmits<Emits>();
|
|
|
|
|
|
+ // 弹框真正渲染/被 antd 滚动锁锁定的目标:包一层 size-0 空壳,
|
|
|
+ // 避免 antd Modal 打开时滚动锁直接改写 #adaptive-container 的宽度(导致整页缩放错乱、露出白边)
|
|
|
+ const portalWrapper = ref<HTMLDivElement | null>(null);
|
|
|
+
|
|
|
+ const getModalContainer = (): HTMLElement => {
|
|
|
+ if (portalWrapper.value) return portalWrapper.value;
|
|
|
+ const target = props.getContainer ? props.getContainer() : document.body;
|
|
|
+ const wrapper = document.createElement('div');
|
|
|
+ wrapper.style.cssText = 'position:absolute;top:0;left:0;width:0;height:0;';
|
|
|
+ target.appendChild(wrapper);
|
|
|
+ portalWrapper.value = wrapper;
|
|
|
+ return wrapper;
|
|
|
+ };
|
|
|
+
|
|
|
const taskList = ref<Task[]>([]);
|
|
|
|
|
|
// 已从接口拉取过的会话 id,用于识别流式期间后端新建的会话
|
|
|
@@ -415,13 +432,6 @@
|
|
|
|
|
|
const isFullscreen = ref(!!props.defaultFullscreen);
|
|
|
|
|
|
- // 响应式视口宽度,用于计算 modal 最大宽度不超过屏幕
|
|
|
- const viewportWidth = ref(window.innerWidth);
|
|
|
- const onResize = () => {
|
|
|
- viewportWidth.value = window.innerWidth;
|
|
|
- };
|
|
|
- window.addEventListener('resize', onResize);
|
|
|
-
|
|
|
const currentTask = computed(() => taskList.value.find((t) => t.id === currentTaskId.value));
|
|
|
|
|
|
const currentRightPanels = computed<RightPanelState>(() => {
|
|
|
@@ -439,10 +449,14 @@
|
|
|
const isPanelLeaving = ref(false);
|
|
|
const modalShouldExpand = computed(() => hasRightPanel.value || isPanelLeaving.value);
|
|
|
|
|
|
- // modal 宽度:全屏100vw / 有右侧面板时1600px(不超视口)/ 默认1400px
|
|
|
+ // modal 宽度:全屏时用容器实际宽度(容器自带 scale 统一缩放适配视口),挂 body 时用 100vw;
|
|
|
+ // 有右侧面板时 1600 / 默认 1400(设计稿像素)。高度由 CSS 的 height:100% 撑满容器
|
|
|
const modalWidth = computed(() => {
|
|
|
- if (isFullscreen.value) return '100vw';
|
|
|
- if (modalShouldExpand.value) return Math.min(1600, viewportWidth.value);
|
|
|
+ if (isFullscreen.value) {
|
|
|
+ const container = props.getContainer ? props.getContainer() : document.body;
|
|
|
+ return container === document.body ? '100vw' : `${container.clientWidth}px`;
|
|
|
+ }
|
|
|
+ if (modalShouldExpand.value) return 1600;
|
|
|
return 1400;
|
|
|
});
|
|
|
|
|
|
@@ -1125,13 +1139,7 @@
|
|
|
const msgIndex = messages.value.findIndex((m) => m.attachedFile?.id === file.id);
|
|
|
if (msgIndex !== -1) {
|
|
|
nextTick(() => {
|
|
|
- const messageElements = chatMessagesRef.value?.messagesRef?.querySelectorAll('.message-wrapper');
|
|
|
- if (messageElements && messageElements[msgIndex]) {
|
|
|
- (messageElements[msgIndex] as HTMLElement).scrollIntoView({
|
|
|
- behavior: 'smooth',
|
|
|
- block: 'center',
|
|
|
- });
|
|
|
- }
|
|
|
+ chatMessagesRef.value?.scrollToMessage(msgIndex, 'center');
|
|
|
});
|
|
|
}
|
|
|
};
|
|
|
@@ -1987,6 +1995,9 @@
|
|
|
let dragStartY = 0;
|
|
|
let modalStartLeft = 0;
|
|
|
let modalStartTop = 0;
|
|
|
+ // 容器 scale 缩放比(视觉尺寸/布局尺寸),用于把鼠标视口坐标换算回布局坐标
|
|
|
+ let dragScaleX = 1;
|
|
|
+ let dragScaleY = 1;
|
|
|
let draggedEl: HTMLElement | null = null;
|
|
|
|
|
|
const findModalWrap = (target: HTMLElement): HTMLElement | null =>
|
|
|
@@ -2023,17 +2034,20 @@
|
|
|
draggedEl = findModalWrap(e.target as HTMLElement) || queryModalWrap();
|
|
|
if (!draggedEl) return;
|
|
|
|
|
|
+ // 记录容器缩放比,并把鼠标/弹框的视口坐标换算回布局坐标,避免不同分辨率下拖拽位移失真
|
|
|
+ const rect = draggedEl.getBoundingClientRect();
|
|
|
+ dragScaleX = draggedEl.offsetWidth ? rect.width / draggedEl.offsetWidth : 1;
|
|
|
+ dragScaleY = draggedEl.offsetHeight ? rect.height / draggedEl.offsetHeight : 1;
|
|
|
+
|
|
|
isDragging = true;
|
|
|
dragStartX = e.clientX;
|
|
|
dragStartY = e.clientY;
|
|
|
-
|
|
|
- const rect = draggedEl.getBoundingClientRect();
|
|
|
- modalStartLeft = rect.left;
|
|
|
- modalStartTop = rect.top;
|
|
|
+ modalStartLeft = rect.left / dragScaleX;
|
|
|
+ modalStartTop = rect.top / dragScaleY;
|
|
|
|
|
|
draggedEl.style.position = 'fixed';
|
|
|
- draggedEl.style.left = `${rect.left}px`;
|
|
|
- draggedEl.style.top = `${rect.top}px`;
|
|
|
+ draggedEl.style.left = `${modalStartLeft}px`;
|
|
|
+ draggedEl.style.top = `${modalStartTop}px`;
|
|
|
draggedEl.style.margin = '0';
|
|
|
draggedEl.style.transform = 'none';
|
|
|
|
|
|
@@ -2043,8 +2057,8 @@
|
|
|
|
|
|
const onDragMove = (e: MouseEvent) => {
|
|
|
if (!isDragging || !draggedEl) return;
|
|
|
- draggedEl.style.left = `${modalStartLeft + (e.clientX - dragStartX)}px`;
|
|
|
- draggedEl.style.top = `${modalStartTop + (e.clientY - dragStartY)}px`;
|
|
|
+ draggedEl.style.left = `${modalStartLeft + (e.clientX - dragStartX) / dragScaleX}px`;
|
|
|
+ draggedEl.style.top = `${modalStartTop + (e.clientY - dragStartY) / dragScaleY}px`;
|
|
|
};
|
|
|
|
|
|
const onDragEnd = () => {
|
|
|
@@ -2065,7 +2079,9 @@
|
|
|
onBeforeUnmount(() => {
|
|
|
document.removeEventListener('mousemove', onDragMove);
|
|
|
document.removeEventListener('mouseup', onDragEnd);
|
|
|
- window.removeEventListener('resize', onResize);
|
|
|
+ // 移除挂载空壳,避免残留
|
|
|
+ portalWrapper.value?.remove();
|
|
|
+ portalWrapper.value = null;
|
|
|
// 组件卸载时主动断开所有任务的流式连接
|
|
|
taskAbortControllers.forEach((controller) => controller.abort());
|
|
|
// Abort in-flight FileReader if any
|
|
|
@@ -2297,8 +2313,9 @@
|
|
|
|
|
|
&.fullscreen {
|
|
|
top: 0 !important;
|
|
|
+ height: 100% !important;
|
|
|
padding: 0 !important;
|
|
|
- max-width: 100vw !important;
|
|
|
+ max-width: none !important;
|
|
|
margin: 0 !important;
|
|
|
background-color: #09172c;
|
|
|
|
|
|
@@ -2308,11 +2325,15 @@
|
|
|
margin: 0 !important;
|
|
|
top: 0 !important;
|
|
|
}
|
|
|
+ .zxm-modal-close,
|
|
|
+ .maximize-btn {
|
|
|
+ top: 40px;
|
|
|
+ }
|
|
|
|
|
|
.zxm-modal-content {
|
|
|
- width: 100vw !important;
|
|
|
- height: 100vh !important;
|
|
|
- max-width: 100vw !important;
|
|
|
+ width: 100% !important;
|
|
|
+ height: 100% !important;
|
|
|
+ max-width: 100% !important;
|
|
|
margin: 0;
|
|
|
border-radius: 0;
|
|
|
overflow: hidden;
|
|
|
@@ -2333,7 +2354,7 @@
|
|
|
height: 90px;
|
|
|
}
|
|
|
.zxm-modal-body {
|
|
|
- height: calc(100vh - 120px);
|
|
|
+ height: calc(100% - 120px);
|
|
|
overflow: hidden;
|
|
|
padding: 0 24px 12px;
|
|
|
}
|