Pārlūkot izejas kodu

[Mod 0000]AI助手增加平滑过渡的动画效果

wangkeyi 1 nedēļu atpakaļ
vecāks
revīzija
4a04cc8fc8

+ 144 - 33
src/views/ventAI/manageAssistent/components/AiAssistantModal.vue

@@ -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);