Kaynağa Gözat

[Mod 0000]修改数据解读按钮和弹框样式

wangkeyi 4 gün önce
ebeveyn
işleme
a618e7a2ab

+ 70 - 32
src/views/ventAI/dataPicker/components/DataPickerModal.vue

@@ -10,39 +10,48 @@
     @cancel="$emit('close')"
   >
     <div class="di-header">
-      <span class="di-header__title">数据解读</span>
+      <span class="di-header__title"><img :src="aiIcon" width="20" height="20" alt="" class="di-header__icon" />数据解读</span>
       <span class="di-header__close" @click="$emit('close')">✕</span>
     </div>
     <div ref="bodyRef" class="di-body">
-      <div v-if="showProcess" class="di-process">
-        <div v-if="processCollapsed" class="di-process__summary" @click="processCollapsed = false">
-          <span class="di-process__summary-icon">▸</span>
-          <span>查看处理过程</span>
-        </div>
-        <template v-else>
-          <div v-if="!props.loading" class="di-process__summary" @click="processCollapsed = true">
-            <span class="di-process__summary-icon">▾</span>
-            <span>收起处理过程</span>
-          </div>
-          <div v-if="thinking" class="di-process__row di-process__thinking"><a-spin size="small" /><span>AI 正在思考...</span></div>
-          <div v-if="todos.length > 0" class="di-process__todos">
-            <div v-for="(todo, i) in todos" :key="i" class="di-todo-item" :class="`di-todo-item--${todo.status}`">
-              <span class="di-todo-item__icon">
-                <span v-if="todo.status === 'completed'" class="di-todo-check">✓</span>
-                <span v-else-if="todo.status === 'in_progress'" class="di-todo-spinner"></span>
-                <span v-else class="di-todo-dot"></span>
-              </span>
-              <span class="di-todo-item__text">{{ todo.content }}</span>
+      <div class="di-section">
+        <div class="di-section__title">监测对象</div>
+        <div class="di-section__content di-section__content--target">{{ props.itemName }}</div>
+      </div>
+      <div class="di-section">
+        <div class="di-section__title">分析结论</div>
+        <div class="di-section__content">
+          <div v-if="showProcess" class="di-process">
+            <div v-if="processCollapsed" class="di-process__summary" @click="processCollapsed = false">
+              <span class="di-process__summary-icon">▸</span>
+              <span>查看处理过程</span>
             </div>
+            <template v-else>
+              <div v-if="!props.loading" class="di-process__summary" @click="processCollapsed = true">
+                <span class="di-process__summary-icon">▾</span>
+                <span>收起处理过程</span>
+              </div>
+              <div v-if="thinking" class="di-process__row di-process__thinking"><a-spin size="small" /><span>AI 正在思考...</span></div>
+              <div v-if="todos.length > 0" class="di-process__todos">
+                <div v-for="(todo, i) in todos" :key="i" class="di-todo-item" :class="`di-todo-item--${todo.status}`">
+                  <span class="di-todo-item__icon">
+                    <span v-if="todo.status === 'completed'" class="di-todo-check">✓</span>
+                    <span v-else-if="todo.status === 'in_progress'" class="di-todo-spinner"></span>
+                    <span v-else class="di-todo-dot"></span>
+                  </span>
+                  <span class="di-todo-item__text">{{ todo.content }}</span>
+                </div>
+              </div>
+              <div v-if="props.latestMessage && loading" class="di-process__row di-process__message">
+                <span class="di-process__dot"></span><span>{{ props.latestMessage }}</span>
+              </div>
+            </template>
           </div>
-          <div v-if="props.latestMessage && loading" class="di-process__row di-process__message">
-            <span class="di-process__dot"></span><span>{{ props.latestMessage }}</span>
-          </div>
-        </template>
+          <div v-if="loading && !hasContent" class="di-loading"><a-spin tip="正在分析数据..." /></div>
+          <div v-if="hasContent" class="di-markdown" v-html="renderedText"></div>
+          <div v-else-if="error && !loading" class="di-error">数据解读失败:{{ error }}</div>
+        </div>
       </div>
-      <div v-if="loading && !hasContent" class="di-loading"><a-spin tip="正在分析数据..." /></div>
-      <div v-if="hasContent" class="di-markdown" v-html="renderedText"></div>
-      <div v-else-if="error && !loading" class="di-error">数据解读失败:{{ error }}</div>
     </div>
     <div v-if="loading && hasContent" class="di-streaming-bar"><a-spin size="small" /><span class="di-streaming-bar__tip">AI 正在生成...</span></div>
   </a-modal>
@@ -52,6 +61,7 @@
   import { computed, ref, watch, nextTick, onMounted, onBeforeUnmount } from 'vue';
   import { marked } from 'marked';
   import type { TodoItem, ToolCallRecord } from '../types';
+  import aiIcon from '@/assets/images/ventAI/dataPicker/AI-icon.svg';
 
   const props = defineProps<{
     visible: boolean;
@@ -63,6 +73,7 @@
     executingTools: string[];
     toolCalls: ToolCallRecord[];
     latestMessage: string;
+    itemName: string;
   }>();
 
   defineEmits<{ close: [] }>();
@@ -147,10 +158,9 @@
     }
     .ant-modal-content {
       color: var(--vent-font-color) !important;
-      background-color: var(--vent-modal-bg) !important;
-      border: 1px solid var(--vent-modal-border) !important;
-      -webkit-box-shadow: 0px 0px 10px var(--vent-modal-box-shadow) inset !important;
-      box-shadow: 0px 0px 10px var(--vent-modal-box-shadow) inset !important;
+      background-color: rgba(9, 23, 44, 0.3);
+      border: 1px solid #3df6ff;
+      box-shadow: inset 0 0 12px #2cb6ff;
       backdrop-filter: blur(30px);
     }
     .ant-modal-body {
@@ -169,14 +179,21 @@
     display: flex;
     justify-content: space-between;
     align-items: center;
-    padding: 18px 24px;
+    padding: 8px;
     border-bottom: 1px solid rgba(30, 144, 255, 0.15);
     flex-shrink: 0;
+    background-color: rgba(57, 163, 255, 0.2);
     &__title {
       font-size: 18px;
       font-weight: bold;
       color: #fff;
       letter-spacing: 2px;
+      display: flex;
+      align-items: center;
+      gap: 8px;
+    }
+    &__icon {
+      display: inline-block;
     }
     &__close {
       cursor: pointer;
@@ -193,6 +210,27 @@
     max-height: 700px;
     overflow-y: auto;
   }
+  .di-section {
+    margin-bottom: 16px;
+    &:last-child {
+      margin-bottom: 0;
+    }
+    &__title {
+      color: #c9f0ff;
+      font-size: 15px;
+      font-weight: bold;
+      margin-bottom: 8px;
+    }
+    &__content {
+      border-top: 2px solid #2cb6ff;
+      background: rgba(36, 138, 191, 0.1);
+      padding: 12px;
+      &--target {
+        color: #fff;
+        font-size: 14px;
+      }
+    }
+  }
   .di-loading {
     display: flex;
     justify-content: center;

+ 10 - 8
src/views/ventAI/dataPicker/useDataPicker.ts

@@ -3,6 +3,7 @@ import Antd from 'ant-design-vue';
 import { fetchTunnelInterpretation, fetchDeviceInterpretation } from './api';
 import type { TodoItem, ToolCallRecord, SSERawEvent } from './types';
 import DataPickerModal from './components/DataPickerModal.vue';
+import aiIcon from '@/assets/images/ventAI/dataPicker/AI-icon.svg';
 
 // ─── 按钮 DOM 构建(悬停模式专用)──────────────────────────────────────────
 const BUTTON_CLASS = 'di-floating-btn';
@@ -10,24 +11,21 @@ const BUTTON_CLASS = 'di-floating-btn';
 function createButtonEl(): HTMLElement {
   const btn = document.createElement('div');
   btn.className = BUTTON_CLASS;
-  btn.innerHTML = '<span>数据解读</span>';
+  btn.title = '数据解读';
+  btn.innerHTML = `<img src="${aiIcon}" width="24" height="24" alt="数据解读" />`;
   Object.assign(btn.style, {
     position: 'fixed',
     zIndex: '99999',
-    padding: '8px 12px',
+    padding: '8px',
     background: 'linear-gradient(135deg, #1e90ff, #4da6ff)',
-    borderRadius: '3px',
+    borderRadius: '50%',
     cursor: 'pointer',
-    fontSize: '16px',
-    color: '#fff',
-    letterSpacing: '1px',
-    whiteSpace: 'nowrap',
-    boxShadow: '0 0 10px rgba(30,144,255,0.4)',
     display: 'none',
     pointerEvents: 'auto',
     transition: 'transform 0.15s ease-out, opacity 0.15s ease-out',
     transform: 'scale(0.9)',
     opacity: '0',
+    boxShadow: '0 0 10px rgba(30,144,255,0.4)',
   });
   btn.addEventListener('mouseenter', () => {
     const owner = (btn as any).__di_owner;
@@ -188,6 +186,7 @@ export function useDataPicker(
   const toolCalls = ref<ToolCallRecord[]>([]);
   const latestMessage = ref('');
   const modalVisible = ref(false);
+  const itemName = ref('');
 
   // 弹框容器 + 独立 Vue 应用
   const modalContainer = document.createElement('div');
@@ -216,6 +215,7 @@ export function useDataPicker(
         executingTools,
         toolCalls,
         latestMessage,
+        itemName,
         onClose: () => {
           modalVisible.value = false;
           streamingText.value = '';
@@ -239,6 +239,7 @@ export function useDataPicker(
             executingTools: this.executingTools,
             toolCalls: this.toolCalls,
             latestMessage: this.latestMessage,
+            itemName: this.itemName,
             onClose: this.onClose,
           }),
         ]);
@@ -297,6 +298,7 @@ export function useDataPicker(
     latestMessage.value = '';
 
     const params = mapParams(item);
+    itemName.value = 'tun_name' in params ? params.tun_name : params.device_name;
     const request = 'tun_id' in params ? fetchTunnelInterpretation(params, handleEvent) : fetchDeviceInterpretation(params, handleEvent);
 
     request