|
|
@@ -2,7 +2,7 @@
|
|
|
<a-modal
|
|
|
:visible="props.visible"
|
|
|
@cancel="emit('close')"
|
|
|
- :width="isFullscreen ? '100vw' : 1400"
|
|
|
+ :width="modalWidth"
|
|
|
:footer="null"
|
|
|
destroyOnClose
|
|
|
:class="['ai-assistant-modal', { fullscreen: isFullscreen }]"
|
|
|
@@ -111,37 +111,43 @@
|
|
|
/>
|
|
|
</div>
|
|
|
|
|
|
- <!-- 文件预览面板(与 chat-panel 并列) -->
|
|
|
- <div v-if="previewFile" class="right-panel">
|
|
|
- <FilePreviewPanel
|
|
|
- :file="previewFile"
|
|
|
- :editable="previewEditable"
|
|
|
- :save-filename="previewFile?.name || ''"
|
|
|
- @close="removePreviewFile"
|
|
|
- @save-success="handleSaveSuccess"
|
|
|
- />
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- Agent 详情面板(与 chat-panel 并列) -->
|
|
|
- <div v-if="activeAgentDetail" class="right-panel">
|
|
|
- <AgentDetailPanel
|
|
|
- :messageIndex="activeAgentDetail.messageIndex"
|
|
|
- :agentName="activeAgentDetail.agent"
|
|
|
- :messages="messages"
|
|
|
- @close="closeAgentDetail"
|
|
|
- />
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 定时任务详情面板(与 chat-panel 并列) -->
|
|
|
- <div v-if="currentScheduleTask" class="right-panel narrow">
|
|
|
- <ScheduleDetailPanel
|
|
|
- :task="currentScheduleTask"
|
|
|
- @close="showScheduleDetail = false"
|
|
|
- @deleted="handleScheduleDeleted"
|
|
|
- @updated="handleScheduleUpdated"
|
|
|
- @open-session="handleOpenScheduleSession"
|
|
|
- />
|
|
|
- </div>
|
|
|
+ <!-- 文件预览面板(与 chat-panel 并列,带滑入滑出动画) -->
|
|
|
+ <Transition name="right-panel-slide" @after-leave="onPanelLeaveEnd">
|
|
|
+ <div v-if="previewFile" class="right-panel">
|
|
|
+ <FilePreviewPanel
|
|
|
+ :file="previewFile"
|
|
|
+ :editable="previewEditable"
|
|
|
+ :save-filename="previewFile?.name || ''"
|
|
|
+ @close="removePreviewFile"
|
|
|
+ @save-success="handleSaveSuccess"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </Transition>
|
|
|
+
|
|
|
+ <!-- Agent 详情面板(与 chat-panel 并列,带滑入滑出动画) -->
|
|
|
+ <Transition name="right-panel-slide" @after-leave="onPanelLeaveEnd">
|
|
|
+ <div v-if="activeAgentDetail" class="right-panel">
|
|
|
+ <AgentDetailPanel
|
|
|
+ :messageIndex="activeAgentDetail.messageIndex"
|
|
|
+ :agentName="activeAgentDetail.agent"
|
|
|
+ :messages="messages"
|
|
|
+ @close="closeAgentDetail"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </Transition>
|
|
|
+
|
|
|
+ <!-- 定时任务详情面板(与 chat-panel 并列,带滑入滑出动画) -->
|
|
|
+ <Transition name="right-panel-slide" @after-leave="onPanelLeaveEnd">
|
|
|
+ <div v-if="currentScheduleTask" class="right-panel narrow">
|
|
|
+ <ScheduleDetailPanel
|
|
|
+ :task="currentScheduleTask"
|
|
|
+ @close="showScheduleDetail = false"
|
|
|
+ @deleted="handleScheduleDeleted"
|
|
|
+ @updated="handleScheduleUpdated"
|
|
|
+ @open-session="handleOpenScheduleSession"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </Transition>
|
|
|
</template>
|
|
|
|
|
|
<!-- 技能面板 -->
|
|
|
@@ -435,9 +441,58 @@
|
|
|
};
|
|
|
|
|
|
const isFullscreen = ref(false);
|
|
|
+
|
|
|
+ // 响应式视口宽度,用于计算 modal 最大宽度不超过屏幕
|
|
|
+ const viewportWidth = ref(window.innerWidth);
|
|
|
+ const onResize = () => {
|
|
|
+ viewportWidth.value = window.innerWidth;
|
|
|
+ };
|
|
|
+ window.addEventListener('resize', onResize);
|
|
|
+
|
|
|
+ // 是否有右侧辅助面板(文件预览/Agent详情/定时任务详情)与 chat-panel 并列显示
|
|
|
+ const hasRightPanel = computed(() => {
|
|
|
+ if (activePanel.value !== 'chat') return false;
|
|
|
+ return !!(previewFile.value || activeAgentDetail.value || currentScheduleTask.value);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 面板离开动画标记:确保 modal 在面板退出动画结束后才收缩宽度
|
|
|
+ const isPanelLeaving = ref(false);
|
|
|
+ const modalShouldExpand = computed(() => hasRightPanel.value || isPanelLeaving.value);
|
|
|
+
|
|
|
+ // modal 宽度:全屏100vw / 有右侧面板时1600px(不超视口)/ 默认1400px
|
|
|
+ const modalWidth = computed(() => {
|
|
|
+ if (isFullscreen.value) return '100vw';
|
|
|
+ if (modalShouldExpand.value) return Math.min(1600, viewportWidth.value);
|
|
|
+ return 1400;
|
|
|
+ });
|
|
|
+
|
|
|
+ // 右侧面板离开动画结束后,允许 modal 宽度收缩
|
|
|
+ const onPanelLeaveEnd = () => {
|
|
|
+ isPanelLeaving.value = false;
|
|
|
+ };
|
|
|
+
|
|
|
+ // 监听右侧面板从有到无,标记正在离开状态,防止 modal 宽度提前收缩
|
|
|
+ watch(hasRightPanel, (newVal, oldVal) => {
|
|
|
+ if (oldVal && !newVal) {
|
|
|
+ isPanelLeaving.value = true;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 全屏切换,附带缩放动画效果
|
|
|
const toggleFullscreen = () => {
|
|
|
- isFullscreen.value = !isFullscreen.value;
|
|
|
+ const enteringFs = !isFullscreen.value;
|
|
|
+ isFullscreen.value = enteringFs;
|
|
|
nextTick(() => {
|
|
|
+ // 给 modal-content 添加缩放动画类,动画结束后自动移除
|
|
|
+ const content = document.querySelector<HTMLElement>('.ai-assistant-modal .zxm-modal-content');
|
|
|
+ if (content) {
|
|
|
+ const animClass = enteringFs ? 'modal-zoom-in-anim' : 'modal-zoom-out-anim';
|
|
|
+ content.classList.remove('modal-zoom-in-anim', 'modal-zoom-out-anim');
|
|
|
+ void content.offsetWidth; // 强制重排,确保动画重新触发
|
|
|
+ content.classList.add(animClass);
|
|
|
+ content.addEventListener('animationend', () => content.classList.remove(animClass), { once: true });
|
|
|
+ }
|
|
|
+
|
|
|
const wraps = document.querySelectorAll<HTMLElement>('.zxm-modal-wrap, .ant-modal-wrap, [class*="modal-wrap"]');
|
|
|
wraps.forEach((wrap) => {
|
|
|
if (isFullscreen.value) {
|
|
|
@@ -1926,6 +1981,7 @@
|
|
|
onBeforeUnmount(() => {
|
|
|
document.removeEventListener('mousemove', onDragMove);
|
|
|
document.removeEventListener('mouseup', onDragEnd);
|
|
|
+ window.removeEventListener('resize', onResize);
|
|
|
// 组件卸载时主动断开所有任务的流式连接
|
|
|
taskAbortControllers.forEach((controller) => controller.abort());
|
|
|
// Abort in-flight FileReader if any
|
|
|
@@ -2168,6 +2224,29 @@
|
|
|
flex-shrink: 0;
|
|
|
}
|
|
|
|
|
|
+ /* 右侧面板滑入滑出过渡动画 */
|
|
|
+ :deep(.right-panel-slide-enter-active) {
|
|
|
+ transition:
|
|
|
+ opacity 0.3s ease,
|
|
|
+ transform 0.3s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.right-panel-slide-leave-active) {
|
|
|
+ transition:
|
|
|
+ opacity 0.25s ease,
|
|
|
+ transform 0.25s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.right-panel-slide-enter-from) {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateX(30px);
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.right-panel-slide-leave-to) {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateX(30px);
|
|
|
+ }
|
|
|
+
|
|
|
.draggable-title {
|
|
|
cursor: grab;
|
|
|
user-select: none;
|
|
|
@@ -2196,6 +2275,38 @@
|
|
|
box-shadow: none;
|
|
|
padding: 0;
|
|
|
border: none !important;
|
|
|
+ transition: width 0.3s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 全屏切换缩放动画 */
|
|
|
+ @keyframes modal-zoom-in {
|
|
|
+ from {
|
|
|
+ transform: scale(0.85);
|
|
|
+ opacity: 0.6;
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ transform: scale(1);
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @keyframes modal-zoom-out {
|
|
|
+ from {
|
|
|
+ transform: scale(1.15);
|
|
|
+ opacity: 0.6;
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ transform: scale(1);
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .zxm-modal-content.modal-zoom-in-anim {
|
|
|
+ animation: modal-zoom-in 0.3s ease forwards;
|
|
|
+ }
|
|
|
+
|
|
|
+ .zxm-modal-content.modal-zoom-out-anim {
|
|
|
+ animation: modal-zoom-out 0.3s ease forwards;
|
|
|
}
|
|
|
.zxm-modal-close {
|
|
|
background-image: var(--img-chat-close-bg);
|