Sfoglia il codice sorgente

[Nod 0000] 删除无用文件

hongrunxia 1 settimana fa
parent
commit
bfac0ceb78

+ 0 - 3
.env.development

@@ -6,9 +6,6 @@ VITE_PUBLIC_PATH = /
 
 # 跨域代理,您可以配置多个 ,请注意,没有换行符
 #VITE_PROXY = [["/jeecgboot","http://localhost:8080/jeecg-boot"],["/upload","http://localhost:3300/upload"]]
-# [perf-base-v1] 卡7: 新增 /aiChat(AI 会话服务)与 /dify(Dify 服务)两条代理。
-# createProxy 会剥离前缀:/aiChat/xxx -> :6005/xxx;/dify/v1/xxx -> :8000/v1/xxx,
-# 因此前端调用 Dify 时必须写 ${difyBase}/v1/...(前缀剥离后路径需自带 /v1)。
 VITE_PROXY = [["/sw","http://182.92.126.35:6008"],["/jeecgsystem","http://182.92.126.35:9999"],["/upload","http://182.92.126.35:9999/upload"],["/documents", "http://182.92.126.35:9050"],["/modelreq", "http://182.92.126.35:9999"],["/webRtc", "http://182.92.126.35:8051"], ["/python", "http://127.0.0.1:8008"], ["/res", "http://182.92.126.35:9999/"],["/dataCenter", "http://39.97.59.228:10999"],["/ventAI", "http://182.92.126.35:8070"],["/aiChat", "http://182.92.126.35:6005"],["/dify", "http://39.97.59.228:8000"]]
 # VITE_PROXY = [["/jeecgsystem","http://192.168.183.88:9999"],["/upload","http://192.168.183.88:9999/upload"],["/documents", "http://192.168.183.88:9050"],["/modelreq", "http://192.168.183.88:9999"],["/webRtc", "http://192.168.183.88:8051"]]
 # VITE_PROXY = [["/jeecgsystem","http://10.10.150.72:9999"],["/upload","http://localhost:3300/upload"],["/documents", "http://10.10.150.72:9050"],["/modelreq", "http://10.10.150.72:9999"],["/webRtc", "http://192.168.183.216:8051"]]

+ 0 - 3
public/js/config.js

@@ -3,9 +3,6 @@ const VUE_APP_URL = {
   webRtcUrl: '/webRtc', // rtsp服务器IP地址
   wsUrl: 'http://182.92.126.35:9999', // WebSocket 地址
   onlyOfficeUrl: '/documents', // [perf-base-v1] 卡1: OnlyOffice Document Server 反向代理前缀(api.js 按需加载时拼接)
-  aiChatUrl: '/aiChat', // [perf-base-v1] 卡7: AI 会话服务反向代理前缀(原 http://182.92.126.35:6005)
-  difyUrl: '/dify', // [perf-base-v1] 卡7: Dify 服务反向代理前缀(原 http://39.97.59.228:8000,剥离前缀后路径自带 /v1)
-  difyApiKey: 'app-tSFRUnv0Qkbtik1dwtlhnpkd' // [perf-base-v1] 卡7: Dify 应用 Key,生产环境应由后端代理下发,勿硬编码在前端
 }
 const History_Type = {
   // type: 'remote', // remote、vent  (remote 代表的是历史查询走的装备院的接口,vent是走的咱们的,目前神东的项目都用remote, 其他矿用vent)

+ 0 - 1759
src/components/AIChat/MiniChat.vue

@@ -1,1759 +0,0 @@
-<!-- eslint-disable vue/no-v-html -->
-<template>
-  <div class="btn" @click="showAIChat">
-    <div style="display: flex; flex-direction: row" class="btn-header">
-      <img src="@/assets/images/vent/home/wakeBtn.png" />
-    </div>
-  </div>
-  <div class="mini-container">
-    <div v-if="isShowChatBroad" class="mini-chat">
-      <!-- 左侧折叠区域 -->
-      <div class="left-side" :class="{ collapsed: isFold }" id="leftSide">
-        <div
-          class="addBtn"
-          :style="{
-            backgroundColor: isFold ? '' : '#2cb6ff',
-            width: isFold ? '20px' : 'auto',
-          }"
-          @click="addNew"
-        >
-          <SvgIcon v-if="isFold" name="add" size="20" :color="''" />
-          <span v-if="!isFold" class="btn-text">添加新对话</span>
-        </div>
-        <div v-if="isFold" class="historyBtn" @click="addNew">
-          <SvgIcon v-if="isFold" name="history" size="20" :color="''" />
-        </div>
-        <div v-else class="historyBtn1">
-          <span v-if="!isFold" class="btn-text">历史对话</span>
-          <a-list style="width: 136px" :split="false" :data-source="sessionHistory" :scroll="190" class="custom-list">
-            <template #renderItem="{ item }">
-              <a-list-item
-                class="session-item"
-                :style="{
-                  padding: '8px 10px 0 8px',
-                  color: '#5e7081',
-                  fontSize: '10px',
-                  position: 'relative', // 新增定位
-                }"
-              >
-                <!-- 新增flex布局容器 -->
-                <div style="width: 100%">
-                  <div v-if="editingId !== item.id" class="text-container">
-                    <span class="edit-text" @click="sessionsHistory(item.id)">{{ item.name }}</span>
-                    <div class="btn-container">
-                      <EditOutlined class="edit-icon" @click="startEditing(item)" />
-                      <DeleteOutlined class="delete-icon" @click="startDelete(item)" />
-                    </div>
-                  </div>
-                  <!-- 输入框 -->
-                  <a-input
-                    size="small"
-                    v-else
-                    v-model:value="editText"
-                    v-focus
-                    @blur="handleSave(item)"
-                    @keyup.enter="handleSave(item)"
-                    class="edit-input"
-                  />
-                </div>
-              </a-list-item>
-            </template>
-          </a-list>
-        </div>
-        <div class="foldBtn" @click="fold">
-          <SvgIcon v-if="isFold" name="Fold1" size="20" :color="''" />
-          <SvgIcon v-else name="unfold" size="20" :color="''" />
-        </div>
-      </div>
-      <!-- 右侧对话框 -->
-      <div class="right-side">
-        <div class="title"> 智能问答 </div>
-        <!-- 对话区域 -->
-        <div class="dialog-area" ref="dialogAreaRef">
-          <div
-            v-for="message in messageHistory"
-            :key="message.id"
-            class="flex items-center w-100%"
-            :style="{ alignSelf: message.type === 'user' ? 'flex-end' : 'flex-start' }"
-          >
-            <template v-if="message.type === 'user'">
-              <div class="flex-grow-1"></div>
-              <div class="message-wrapper user-message-wrapper">
-                <!-- <div class="ask-message" v-if="message.message_files && message.message_files.length > 0" @click="showPreview()">
-                  <div class="img-preview" v-if="askFileType == 'image'">
-                    <img :src="askFile.source_url" alt="" />
-                  </div>
-                  <div class="file-preview" v-else-if="askFileType !== 'image' && askFileType != ''">
-                    <div class="file-info"> 📄{{ askFile.name }} (大小:{{ (askFile.size / 1024).toFixed(2) }}KB) </div>
-                  </div>
-                  <div>
-                    {{ message.parsedContent }}
-                  </div>
-                </div>
-                <div class="ask-message" v-else @click="showPreview()">
-                  <div>
-                    {{ message.parsedContent }}
-                  </div>
-                </div> -->
-                <div class="ask-message">
-                  <!-- 渲染当前消息的文件 -->
-                  <div v-if="message.message_files && message.message_files.length > 0" @click="showPreview(message.message_files)">
-                    <div v-for="(file, fIdx) in message.message_files" :key="fIdx">
-                      <div class="img-preview" v-if="file.mime_type?.toLowerCase().includes('image')">
-                        <img :src="file.source_url ? file.source_url : file.url" alt="" />
-                      </div>
-                      <div class="file-preview" v-else>
-                        <div class="file-info"> 📄{{ file.name }} (大小:{{ (file.size / 1024).toFixed(2) }}KB) </div></div
-                      >
-                    </div>
-                  </div>
-                  <!-- 消息内容 -->
-                  <div>{{ message.parsedContent }}</div>
-                </div>
-                <div class="copy-icon-container">
-                  <CopyOutlined class="copy-icon" @click="copyToClipboard(message.parsedContent)" title="复制消息" />
-                  <EditOutlined class="copy-icon" @click="editAsk(message.parsedContent)" title="重新编辑" />
-                </div>
-              </div>
-            </template>
-            <template v-else>
-              <SvgIcon size="40" class="answerIcon" name="ai-logo" />
-              <div class="message-wrapper ai-message-wrapper">
-                <div class="answer-message">
-                  <div v-if="message.parsedContentR1" class="thinking-section">
-                    <div class="thinking-header" @click="isShow(message)">
-                      <span class="thinking-title"
-                        >思考过程:<RightOutlined v-if="!message.isShowThink" /> <DownOutlined v-if="message.isShowThink"
-                      /></span>
-                    </div>
-                    <div v-show="message.isShowThink" class="color-gray font-size-12px" v-html="formatMessage(message.parsedContentR1)"></div>
-                  </div>
-                  <div v-if="message.parsedContent" v-html="formatMessage(message.parsedContent)"> </div>
-                  <div
-                    id="chart-target"
-                    class="echarts-container"
-                    style="width: 400px; height: 400px"
-                    v-if="message.echartsOption && Object.keys(echartsOption).length > 0"
-                  ></div>
-                </div>
-                <div class="copy-icon-container">
-                  <CopyOutlined class="copy-icon" @click="copyToClipboard(message.parsedContent)" title="复制消息" />
-                  <RedoOutlined class="copy-icon" @click="refresh()" title="重新生成" />
-                </div>
-              </div>
-            </template>
-          </div>
-          <!-- 建议信息 -->
-          <div v-for="(item, index) in suggestList" :key="index" class="suggestion-item" @click="handleSuggestClick(item)">
-            <span class="suggestion-text">{{ item }}</span>
-            <a-icon type="right" class="suggestion-arrow" />
-          </div>
-        </div>
-        <!-- 底部输入区 -->
-        <div class="input-area">
-          <div class="img-preview" v-if="uploadfileType == 'image'">
-            <img :src="uploadedFiles.source_url" alt="" />
-          </div>
-          <div class="file-preview" v-else-if="uploadedFiles.name && uploadedFiles.size && uploadfileType !== 'image'">
-            <div class="file-info">
-              📄{{ uploadedFiles.name }} (大小:{{ (uploadedFiles.size / 1024).toFixed(2) }}KB)
-              <DeleteOutlined @click="deleteFile()" />
-            </div>
-          </div>
-          <a-textarea
-            auto-size
-            v-model:value="inputText"
-            placeholder="请输入你的问题"
-            @keyup.enter="handleSend(inputText, uploadedFiles)"
-            class="ant-input"
-          />
-          <div class="ctrl-btn">
-            <div class="input-controls">
-              <button class="control-btn" :class="{ active: isThinking }" @click="toggleThinking">深度思考</button>
-              <button class="control-btn" @click="stopReq()">停止响应</button>
-            </div>
-            <div class="action-bar">
-              <Space>
-                <Button class="control-btn1" size="small" @mouseenter="showModal(true)">
-                  <template #icon>
-                    <SvgIcon name="send-file" />
-                  </template>
-                </Button>
-                <Button class="control-btn2" size="small" @click="handleSend(inputText, uploadedFiles)">
-                  <template #icon>
-                    <SvgIcon name="send" />
-                  </template>
-                </Button>
-              </Space>
-            </div>
-          </div>
-          <!-- 右侧文件上传区 -->
-          <div v-if="open" @mouseenter="showModal(true)" @mouseleave="showModal(false)" class="file-upload">
-            <!-- 上传按钮 -->
-            <a-upload
-              class="custom-upload"
-              name="file"
-              :multiple="false"
-              :before-upload="handleBeforeUpload"
-              :file-list="fileList"
-              :remove="handleRemove"
-            >
-              <a-button class="upload-btn">
-                <UploadOutlined />
-                从本地上传
-              </a-button>
-            </a-upload>
-          </div>
-        </div>
-      </div>
-    </div>
-    <div class="doc" v-if="isShowDoc">
-      <div class="close"> <button class="closeBtn" @click="close">关闭</button></div>
-      <!-- 预览内容 -->
-      <div class="pre-container">
-        <!-- PDF预览 -->
-        <VueOfficePdf v-if="fileType === 'pdf'" :src="fileUrl" @rendered="handleRendered" @error="handleError" />
-        <!-- Word文档预览 -->
-        <VueOfficeDocx v-else-if="fileType === 'docx'" :src="fileUrl" @rendered="handleRendered" @error="handleError" />
-        <!-- Excel预览 -->
-        <VueOfficeExcel
-          v-else-if="fileType === 'xlsx' || fileType === 'xls'"
-          :options="{ xls: true }"
-          :src="fileUrl"
-          @rendered="handleRendered"
-          @error="handleError"
-        />
-        <img
-          class="img-container"
-          v-else-if="
-            fileType === 'jpg' || fileType === 'jpeg' || fileType === 'png' || fileType === 'gjf' || fileType === 'webp' || fileType === 'svg'
-          "
-          :src="fileUrl"
-          alt=""
-        />
-        <!-- 不支持的文件类型 -->
-        <div v-else class="unsupported">
-          <p>不支持预览该文件类型: {{ fileType }}</p>
-        </div>
-        <!-- 加载状态 -->
-        <div v-if="loading" class="loading">
-          <p>文件加载中...</p>
-        </div>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script lang="ts" setup>
-  import { ref, onMounted, nextTick, watch, onUnmounted, defineAsyncComponent } from 'vue';
-  import { SvgIcon } from '../Icon';
-  import { Space, Button, Modal, Input, message } from 'ant-design-vue';
-  // import AIChat from './index.vue';
-  import { useUserStore } from '/@/store/modules/user';
-  import {
-    EditOutlined,
-    DeleteOutlined,
-    UploadOutlined,
-    CopyOutlined,
-    RightOutlined,
-    DownOutlined,
-    FastForwardFilled,
-    RedoOutlined,
-  } from '@ant-design/icons-vue';
-  import { createVNode } from 'vue';
-  import { marked } from 'marked';
-  import katex from 'katex';
-  import 'katex/dist/katex.min.css';
-  // [perf-base-v1] 卡2b: echarts 改按需动态导入(见 initChart),此处仅保留类型导入(编译期擦除,无运行时依赖)
-  import type { EChartsType } from 'echarts';
-  // [perf-base-v1] 卡6: v-html 消毒——marked/katex 输出经公共 DOMPurify 白名单消毒后再进 formatMessage
-  import { sanitizeHtml } from '/@/utils/markdownSafe';
-  // [perf-base-v1] 卡7: Dify 接口地址配置化,原硬编码 http://39.97.59.228:8000/v1 改走 VUE_APP_URL.difyUrl(开发经 vite proxy /dify 转发)
-  import { difyBase, difyApiKey } from '/@/utils/aiChatApi';
-  // [perf-base-v1] 卡2b: @vue-office 三组件改异步加载,仅 isShowDoc 预览时才会拉取 office-preview chunk
-  const VueOfficePdf = defineAsyncComponent(() => import('@vue-office/pdf/lib/v3/vue-office-pdf.mjs'));
-  const VueOfficeDocx = defineAsyncComponent(() => import('@vue-office/docx/lib/v3/vue-office-docx.mjs'));
-  const VueOfficeExcel = defineAsyncComponent(() => import('@vue-office/excel/lib/v3/vue-office-excel.mjs'));
-  const TextArea = Input.TextArea; // 直接导入TextArea组件使用时打包报错
-  const inputText = ref(''); // 输入框内容
-  const refreshText = ref(''); //重新生成文本
-  const sessionHistory = ref([]);
-  const isShowChatBroad = ref(false);
-  const editingId = ref<number | null>(null);
-  const editText = ref('');
-  const currentSessionID = ref('');
-  const taskID = ref('');
-  const messageID = ref('');
-  const open = ref<boolean>(false);
-  const isThinking = ref(false); //深度思考是否开启
-  const Thinking = ref(false);
-  const isShowDoc = ref(false);
-  const fileType = ref('');
-  const fileUrl = ref('');
-  // [perf-base-v1] 卡7: API Key 改从 config.js(VUE_APP_URL.difyApiKey)读取,生产环境应由后端代理下发,勿依赖前端硬编码兜底值
-  const APIKEY = ref('Bearer ' + difyApiKey);
-  interface ListItem {
-    id: number;
-    name?: string;
-  }
-  interface Message {
-    id: string; // 唯一标识(可用时间戳生成)
-    type: 'user' | 'system' | 'response';
-    content: String; // 原始 Markdown 字符串(用于拼接)
-    parsedContent: String; // 解析后的 HTML(用于渲染)
-    contentR1: String; // 原始思考过程 Markdown
-    parsedContentR1: String; // 解析后的思考过程 HTML
-    timestamp: number; // 排序依据
-    isShowThink: boolean; //深度思考展示
-    message_files?: Array<[]>; //文件信息
-    echartsOption?: Object; //echart图表
-  }
-  // 定义消息历史数组类型
-  const messageHistory = ref<Message[]>([]);
-  const isFold = ref(true); // 是否折叠
-  const userid = useUserStore().getUserInfo.id as string;
-  const uploadedFiles = ref({}); //接口获取文件信息
-  const askFile = ref({});
-  const uploadfileType = ref(''); //提问框文件展示
-  const askFileType = ref(''); //回答区域文件展示
-  const message_files = ref([]);
-  const fileList = ref([]);
-  const suggestList = ref([]); //建议列表
-  const loading = ref(false);
-  // const chartRef = ref(null);
-  const validEchartsOption = ref(null);
-  const optionTextRef = ref(null);
-  const echartsOption = ref({});
-  let myChart: EChartsType | null = null;
-  const chartDom = ref<HTMLDivElement | null>(null);
-  // 初始化图表
-  const initChart = async () => {
-    const targetNode = document.getElementById('chart-target');
-    if (!targetNode) {
-      console.error('目标插入节点不存在!');
-      return;
-    }
-
-    // [perf-base-v1] 卡2b: 仅在真正需要渲染图表时才动态加载 echarts
-    const echarts = await import('echarts');
-
-    // 2. 创建 ECharts 容器(带唯一 id,方便后续清理)
-    const chartContainer = document.createElement('div');
-    chartContainer.id = 'dynamic-echarts-container';
-    // 必须设置宽高(继承目标节点宽高,或自定义)
-    chartContainer.style.width = '100%';
-    chartContainer.style.height = '100%';
-    chartContainer.style.border = '1px solid #eee';
-
-    // 3. 初始化 ECharts 实例到新建容器
-    myChart = echarts.init(chartContainer);
-    myChart.setOption(echartsOption.value);
-
-    // 4. 将容器插入目标节点(清空原有内容,可选)
-    targetNode.innerHTML = ''; // 清空目标节点原有内容(如需保留可注释)
-    targetNode.appendChild(chartContainer);
-
-    // 5. 监听窗口resize,自适应图表
-    window.addEventListener('resize', handleResize);
-  };
-  // 图表自适应方法
-  const handleResize = () => {
-    myChart?.resize();
-  };
-  // 监听配置变化,确保DOM更新后初始化
-  watch(
-    () => echartsOption.value,
-    async (newOption) => {
-      if (!newOption || Object.keys(newOption).length === 0) return;
-      await nextTick(); // 等待 DOM 渲染
-      await initChart(); // [perf-base-v1] initChart 已异步化(内部动态 import echarts)
-    },
-    { deep: true, immediate: true }
-  );
-
-  // 文件预览
-  const handleRendered = () => {
-    loading.value = false;
-    console.log('文件渲染完成');
-  };
-  const handleError = (error) => {
-    loading.value = false;
-    console.error('文件预览错误:', error);
-  };
-  // function previewFile(data) {
-  //   fileType.value = data.extension;
-  //   fileUrl.value = data.source_url;
-  // }
-  //启用深度思考
-  const toggleThinking = () => {
-    isThinking.value = !isThinking.value;
-    if (isThinking.value) {
-      APIKEY.value = 'Bearer app-7iLQR9T77YtwDYUYGPk1PFbi';
-    } else {
-      APIKEY.value = 'Bearer app-tSFRUnv0Qkbtik1dwtlhnpkd';
-    }
-  };
-  // 折叠思考过程
-  const isShow = (message) => {
-    message.isShowThink = !message.isShowThink;
-  };
-  const dialogAreaRef = ref(null);
-  // 滚动到底部的方法
-  const scrollToBottom = async () => {
-    // 等待 DOM 更新(如消息渲染完成)
-    await nextTick();
-    if (dialogAreaRef.value) {
-      const el = dialogAreaRef.value;
-      // 关键:scrollTop = scrollHeight(滚动内容总高度)
-      el.scrollTop = el.scrollHeight;
-    }
-  };
-
-  // 点击建议项时的处理函数
-  const handleSuggestClick = (text) => {
-    // 将选中的建议填充到文本框
-    inputText.value = text;
-  };
-  function showAIChat() {
-    isShowChatBroad.value = !isShowChatBroad.value;
-    if (isShowChatBroad.value) {
-      isShowDoc.value = false;
-    }
-  }
-  //复制消息
-  function copyToClipboard(text) {
-    if (!text || text.trim() === '') {
-      message.warn('没有可复制的内容');
-      return;
-    }
-    // 2. 创建临时textarea 元素
-    const textarea = document.createElement('textarea');
-    textarea.value = text;
-    textarea.style.position = 'fixed';
-    textarea.style.top = '-999px';
-    textarea.style.left = '-999px';
-    textarea.style.width = '200px';
-    textarea.style.height = '200px';
-    document.body.appendChild(textarea);
-    try {
-      textarea.select();
-      textarea.setSelectionRange(0, text.length);
-      const isSuccessful = document.execCommand('copy');
-      if (isSuccessful) {
-        message.success('复制成功!');
-      } else {
-        throw new Error('复制命令执行失败');
-      }
-    } catch (err) {
-      console.error('复制失败:', err);
-      message.error('复制失败,请手动复制');
-    } finally {
-      document.body.removeChild(textarea);
-    }
-  }
-  const initMarked = () => {
-    marked.setOptions({
-      gfm: true, // 启用GitHub风格的Markdown,包含表格
-      breaks: false, // 禁用换行符转换
-    });
-  };
-  // LaTeX 公式渲染
-  const renderLatexInHtml = (html) => {
-    // 匹配块级公式($$...$$)
-    const blockRegex = /\$\$(.*?)\$\$/gs;
-    // 匹配行内公式($...$)
-    const inlineRegex = /\$(.*?)\$/g;
-    // // 替换块级公式(居中显示)
-    html = html.replace(blockRegex, (match, formula) => {
-      return katex.renderToString(formula.trim(), {
-        displayMode: true,
-        throwOnError: false,
-        strict: false,
-        trust: true,
-      });
-    });
-    // 替换行内公式(行内显示)
-    html = html.replace(inlineRegex, (match, formula) => {
-      return katex.renderToString(formula.trim(), {
-        displayMode: false,
-        throwOnError: false,
-        strict: false,
-      });
-    });
-    return html;
-  };
-  // Markdown + LaTeX 解析
-  const parseMarkdownWithLatex = (mdStr) => {
-    if (!mdStr) return '';
-    try {
-      let html = marked.parse(mdStr); // Markdown → HTML
-      html = renderLatexInHtml(html); // 替换公式
-      // [perf-base-v1] 卡6: marked.parse 结果包 DOMPurify 消毒,防 v-html XSS
-      return sanitizeHtml(html);
-    } catch (error) {
-      console.error('解析失败:', error);
-      return mdStr; // 降级显示原始字符串
-    }
-  };
-  // echart 数据解析
-  const parseEchart = (data) => {
-    const reg = /```[\s\S]*?\n([\s\S]*?)```/;
-    const match = data.match(reg);
-
-    if (match && match[1]) {
-      // 提取匹配到的JSON部分并解析
-      echartsOption.value = JSON.parse(match[1].trim());
-    }
-    return match;
-  };
-  const triggerChart = async () => {
-    await nextTick();
-    await initChart(); // [perf-base-v1] initChart 已异步化(内部动态 import echarts)
-  };
-  //重新生成
-  const refresh = () => {
-    handleSend(refreshText.value);
-  };
-  //重新编辑
-  const editAsk = (data) => {
-    inputText.value = data;
-  };
-  //获取消息列表
-  async function handleSend(data, files?) {
-    refreshText.value = data;
-    inputText.value = '';
-    if (files) {
-      askFileType.value = files?.mime_type?.startsWith('image') ? 'image' : 'document';
-    }
-    // message_files.value.push(files);
-    const fileList = files ? [files] : [];
-    uploadfileType.value = '';
-    if (isThinking.value) {
-      const newMessage = {
-        id: `user_${Date.now()}`,
-        type: 'user',
-        content: '',
-        parsedContent: data,
-        contentR1: '',
-        parsedContentR1: '',
-        timestamp: Date.now(),
-        isShowThink: true,
-        message_files: fileList,
-      };
-      messageHistory.value.push(newMessage);
-      message_files.value.push(files);
-    } else {
-      const newMessage = {
-        id: `user_${Date.now()}`,
-        type: 'user',
-        content: '',
-        parsedContent: data,
-        contentR1: '',
-        parsedContentR1: '',
-        timestamp: Date.now(),
-        isShowThink: false,
-        message_files: fileList,
-      };
-      messageHistory.value.push(newMessage);
-      message_files.value.push(files);
-    }
-    try {
-      const response = await fetch(`${difyBase}/v1/chat-messages`, {
-        method: 'POST',
-        headers: {
-          'Content-Type': 'application/json',
-          Authorization: APIKEY.value,
-        },
-        body: JSON.stringify({
-          conversation_id: currentSessionID.value ? currentSessionID.value : '',
-          query: data,
-          ...(askFile.value?.id
-            ? {
-                files: [
-                  {
-                    type: askFileType.value == 'image' ? 'image' : 'document',
-                    transfer_method: 'local_file',
-                    upload_file_id: askFile.value.id,
-                    url: '',
-                  },
-                ],
-              }
-            : {}),
-          response_mode: 'streaming',
-          user: userid,
-          inputs: {},
-        }),
-      });
-      if (!response.ok) {
-        throw new Error(`HTTP error! status: ${response.status}`);
-      }
-      uploadedFiles.value = {};
-      const decoder = new TextDecoder('utf-8');
-      const reader = response.body.getReader();
-      let textBuffer = ''; // 使用字符串缓冲区来累积数据
-      // 在组件中定义
-      const currentProcessingMessage = ref(null);
-      if (isThinking.value) {
-        const newMessage = {
-          id: `response_${Date.now()}`,
-          type: 'response',
-          content: '',
-          parsedContent: '',
-          contentR1: '',
-          parsedContentR1: '',
-          timestamp: Date.now(),
-          isShowThink: true,
-        };
-        messageHistory.value.push(newMessage);
-        currentProcessingMessage.value = newMessage; // 保存引用
-      } else {
-        const newMessage = {
-          id: `response_${Date.now()}`,
-          type: 'response',
-          content: '',
-          parsedContent: '',
-          contentR1: '',
-          parsedContentR1: '',
-          timestamp: Date.now(),
-          isShowThink: false,
-        };
-        messageHistory.value.push(newMessage);
-        currentProcessingMessage.value = newMessage; // 保存引用
-      }
-      while (true) {
-        const { done, value } = await reader.read();
-        if (done) {
-          if (textBuffer) {
-            processLine(textBuffer);
-          }
-          break;
-        }
-        textBuffer += decoder.decode(value, { stream: true });
-        // 处理每一行数据
-        let lineIndex;
-        while ((lineIndex = textBuffer.indexOf('\n')) !== -1) {
-          const line = textBuffer.substring(0, lineIndex).trim();
-          textBuffer = textBuffer.substring(lineIndex + 1);
-
-          if (line) {
-            processLine(line);
-          }
-        }
-      }
-      function processLine(line) {
-        if (line.startsWith('data: ')) {
-          try {
-            const jsonStr = line.substring('data: '.length);
-            const data = JSON.parse(jsonStr);
-            switch (data.event) {
-              case 'message':
-                if (data.answer) {
-                  const targetMessage = messageHistory.value.find((msg) => msg.id === currentProcessingMessage.value.id);
-                  if (!targetMessage) break;
-                  let currentChunk = data.answer;
-
-                  // 检查是否包含起始标签
-                  const startIndex = currentChunk.indexOf('<think>');
-                  if (startIndex !== -1) {
-                    // 找到起始标签:将标签前的内容作为正文
-                    if (startIndex > 0) {
-                      targetMessage.content += currentChunk.substring(0, startIndex);
-                      targetMessage.parsedContent = parseMarkdownWithLatex(targetMessage.content);
-                    }
-                    // 进入思考模式,将标签后的内容追加到contentR1
-                    Thinking.value = true;
-                    const remainingContent = currentChunk.substring(startIndex + '<think>'.length);
-                    if (remainingContent) {
-                      targetMessage.contentR1 += remainingContent;
-                      targetMessage.parsedContentR1 = parseMarkdownWithLatex(targetMessage.contentR1);
-                    }
-                  } else if (Thinking.value) {
-                    // 结束标签
-                    const endIndex = currentChunk.indexOf('</think>');
-                    if (endIndex !== -1) {
-                      // 找到结束标签:标签前的内容追加到contentR1
-                      if (endIndex > 0) {
-                        targetMessage.contentR1 += currentChunk.substring(0, endIndex);
-                        targetMessage.parsedContentR1 = parseMarkdownWithLatex(targetMessage.contentR1);
-                      }
-                      // 将标签后的内容作为正文
-                      const remainingContent = currentChunk.substring(endIndex + '</think>'.length);
-                      if (remainingContent) {
-                        targetMessage.content += remainingContent;
-                        targetMessage.parsedContent = parseMarkdownWithLatex(targetMessage.content);
-                      }
-                      targetMessage.content += currentChunk;
-                      Thinking.value = false;
-                      targetMessage.isShowThink = false;
-                    } else {
-                      // 没有结束标签,继续追加到contentR1
-                      targetMessage.contentR1 += currentChunk;
-                      targetMessage.parsedContentR1 = parseMarkdownWithLatex(targetMessage.contentR1);
-                    }
-                  } else {
-                    targetMessage.content += currentChunk;
-                    targetMessage.parsedContent = parseMarkdownWithLatex(targetMessage.content);
-                  }
-                  scrollToBottom(); // 每次接收消息都滚动
-                }
-                if (data.task_id && !taskID.value) taskID.value = data.task_id;
-                if (data.conversation_id && !currentSessionID.value) currentSessionID.value = data.conversation_id;
-                if (data.message_id && !messageID.value) {
-                  messageID.value = data.message_id;
-                }
-                break;
-            }
-          } catch (error) {
-            console.warn('Error parsing stream chunk:', error, 'Chunk:', line);
-          }
-        }
-      }
-      getNextSuggest();
-    } catch (error) {
-      console.error('Error in handleSend:', error);
-      // 在 UI 上显示错误信息
-      messageHistory.value.push({
-        id: `system_${Date.now()}`,
-        type: 'system',
-        content: '',
-        parsedContent: '请求错误',
-        contentR1: '',
-        parsedContentR1: '',
-        timestamp: Date.now(),
-        isShowThink: false,
-      });
-    }
-  }
-  //创建新对话
-  async function addNew() {
-    messageHistory.value = [];
-    currentSessionID.value = '';
-    taskID.value = '';
-    messageID.value = '';
-  }
-  // 上传文件
-  const showModal = (data) => {
-    open.value = data;
-  };
-  const close = () => {
-    isShowDoc.value = false;
-  };
-  const showPreview = (data) => {
-    fileType.value = data[0].extension ? data[0].extension : data[0].filename.split('.').pop();
-    fileUrl.value = data[0].source_url ? data[0].source_url : data[0].url;
-    isShowDoc.value = true;
-  };
-  //删除文件
-  const deleteFile = () => {
-    uploadedFiles.value = {};
-  };
-  // 上传文件
-  const handleBeforeUpload = async (file) => {
-    const formData = new FormData();
-    formData.append('file', file);
-    formData.append('user', userid);
-
-    try {
-      const response = await fetch(`${difyBase}/v1/files/upload`, {
-        method: 'POST',
-        headers: {
-          Authorization: APIKEY.value,
-        },
-        body: formData,
-      });
-
-      const result = await response.json();
-      if (response.ok) {
-        uploadedFiles.value = result;
-        askFile.value = result;
-        uploadfileType.value = result.mime_type.startsWith('image') ? 'image' : 'document';
-        // previewFile(result);
-      } else {
-        message.error(`上传失败: ${result.message || '网络错误'}`);
-      }
-    } catch (error) {
-      console.error('保存失败:', error);
-      message.error('上传失败,请重试');
-    }
-    return false;
-  };
-
-  const handleRemove = (file) => {
-    const index = fileList.value.findIndex((item) => item.uid === file.uid);
-    if (index !== -1) {
-      fileList.value.splice(index, 1);
-    }
-  };
-  //停止响应
-  async function stopReq() {
-    try {
-      let response = await fetch(`${difyBase}/v1/chat-messages/${taskID.value}/stop`, {
-        method: 'POST',
-        headers: {
-          'Content-Type': 'application/json',
-          Authorization: APIKEY.value,
-        },
-        body: JSON.stringify({
-          user: userid,
-        }),
-      });
-      if (!response) {
-        throw new Error('Network response was not ok');
-      }
-    } catch (error) {
-      console.error('保存失败:', error);
-    }
-  }
-  //获取具体会话记录
-  async function sessionsHistory(id: string, retryCount = 0) {
-    const API_KEYS = ['Bearer app-tSFRUnv0Qkbtik1dwtlhnpkd', 'Bearer app-7iLQR9T77YtwDYUYGPk1PFbi'];
-    // 最大重试次数
-    const maxRetries = API_KEYS.length - 1;
-    try {
-      let response = await fetch(`${difyBase}/v1/messages?conversation_id=${id}&user=${userid}`, {
-        method: 'GET',
-        headers: {
-          'Content-Type': 'application/json',
-          Authorization: API_KEYS[retryCount],
-        },
-      });
-      if (!response.ok) {
-        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
-      }
-      const data = await response.json();
-      if (data.data.length > 0) {
-        messageHistory.value = [];
-        data.data.forEach((item: any) => {
-          currentSessionID.value = item.conversation_id;
-          messageHistory.value.push({
-            id: `user_${Date.now()}`,
-            type: 'user',
-            content: '',
-            parsedContent: item.query,
-            contentR1: '',
-            parsedContentR1: '',
-            message_files: item.message_files,
-            timestamp: Date.now(),
-            isShowThink: false,
-          });
-          const answer = item.answer;
-          // 查找<think>
-          const startIndex = answer.indexOf('<think>');
-          const endIndex = answer.indexOf('</think>');
-          const isChart = answer.indexOf('echarts');
-          let content = '';
-          let contentR1 = '';
-          // 根据标签判断
-          if (startIndex !== -1 && endIndex !== -1) {
-            content = answer.substring(0, startIndex) + answer.substring(endIndex + '</think>'.length);
-            contentR1 = answer.substring(startIndex + '<think>'.length, endIndex);
-          } else if (startIndex !== -1) {
-            // 只有起始标签
-            content = answer.substring(0, startIndex);
-            contentR1 = answer.substring(startIndex + '<think>'.length);
-          } else if (endIndex !== -1) {
-            // 只有结束标签
-            content = answer.substring(endIndex + '</think>'.length);
-            contentR1 = answer.substring(0, endIndex);
-          } else {
-            // 没有标签
-            content = answer;
-          }
-          if (isChart != -1) {
-            // 添加到消息历史
-            messageHistory.value.push({
-              id: `system_${Date.now()}`,
-              type: 'system',
-              content: '',
-              parsedContent: '',
-              contentR1: '',
-              parsedContentR1: parseMarkdownWithLatex(contentR1.trim()),
-              timestamp: Date.now(),
-              isShowThink: contentR1.length > 0, // 如果有思考内容则显示
-              echartsOption: parseEchart(content.trim()),
-            });
-            triggerChart();
-          } else {
-            // 添加到消息历史
-            messageHistory.value.push({
-              id: `system_${Date.now()}`,
-              type: 'system',
-              content: '',
-              parsedContent: parseMarkdownWithLatex(content.trim()),
-              contentR1: '',
-              parsedContentR1: parseMarkdownWithLatex(contentR1.trim()),
-              timestamp: Date.now(),
-              isShowThink: contentR1.length > 0, // 如果有思考内容则显示
-            });
-          }
-        });
-      }
-    } catch (error) {
-      // 判断是否达到最大重试次数
-      if (retryCount < maxRetries) {
-        console.warn(`请求失败,正在尝试第 ${retryCount + 2} 个 API Key...`);
-        return sessionsHistory(id, retryCount + 1);
-      } else {
-        console.error('所有 API Key 均尝试失败:', error);
-        throw error;
-      }
-    }
-    editingId.value = null;
-  }
-  //获取下一轮建议问题列表
-  //停止响应
-  async function getNextSuggest() {
-    try {
-      let response = await fetch(`${difyBase}/v1/messages/${messageID.value}/suggested?user=${userid}`, {
-        method: 'GET',
-        headers: {
-          'Content-Type': 'application/json',
-          Authorization: APIKEY.value,
-        },
-      });
-      const data = await response.json();
-      suggestList.value = data.data;
-      if (!response) {
-        throw new Error('Network response was not ok');
-      }
-    } catch (error) {
-      console.error('保存失败:', error);
-    }
-  }
-  //编辑标题
-  const startEditing = (item: ListItem) => {
-    editingId.value = item.id;
-    editText.value = item.name || '';
-  };
-  // 保存修改
-  const handleSave = async (item: ListItem) => {
-    try {
-      let response = await fetch(`${difyBase}/v1/conversations/${item.id}/name`, {
-        method: 'POST',
-        headers: {
-          'Content-Type': 'application/json',
-          Authorization: APIKEY.value,
-        },
-        body: JSON.stringify({
-          name: editText.value,
-          user: userid,
-        }),
-      });
-      if (!response.ok) {
-        throw new Error('Network response was not ok');
-      }
-      item.name = editText.value;
-    } catch (error) {
-      console.error('保存失败:', error);
-    }
-    editingId.value = null;
-  };
-  // 删除会话
-  const startDelete = async (item: ListItem) => {
-    Modal.confirm({
-      title: '确认删除',
-      content: `确定要删除会话 "${item.name || '新会话'}" 吗?此操作不可撤销。`,
-      okText: '确认',
-      cancelText: '取消',
-      onOk: async () => {
-        // 原有删除逻辑不变
-        try {
-          let response = await fetch(`${difyBase}/v1/conversations/${item.id}`, {
-            method: 'DELETE',
-            headers: {
-              'Content-Type': 'application/json',
-              Authorization: APIKEY.value,
-            },
-            body: JSON.stringify({
-              user: userid,
-            }),
-          });
-          if (!response.ok) {
-            throw new Error('Network response was not ok');
-          }
-          getHistoryList();
-        } catch (error) {
-          console.error('删除失败:', error);
-          Modal.error({
-            title: '删除失败',
-            content: '删除会话时出现错误,请稍后重试。',
-          });
-        }
-      },
-    });
-  };
-  const fold = () => {
-    isFold.value = !isFold.value;
-    if (!isFold.value) {
-      getHistoryList();
-    }
-  };
-  // 获取历史会话列表
-  async function getHistoryList() {
-    sessionHistory.value = [];
-    try {
-      const fetch1 = fetch(`${difyBase}/v1/conversations?user=${userid}`, {
-        method: 'get',
-        headers: {
-          'Content-Type': 'application/json',
-          Authorization: 'Bearer app-tSFRUnv0Qkbtik1dwtlhnpkd',
-        },
-      })
-        .then((res) => {
-          return res.json();
-        })
-        .catch((err) => {
-          console.error('第一个请求错误:', err);
-          return { data: [] }; // 失败时返回空数组
-        });
-
-      const fetch2 = fetch(`${difyBase}/v1/conversations?user=${userid}`, {
-        method: 'get',
-        headers: {
-          'Content-Type': 'application/json',
-          Authorization: 'Bearer app-7iLQR9T77YtwDYUYGPk1PFbi',
-        },
-      })
-        .then((res) => {
-          return res.json();
-        })
-        .catch((err) => {
-          console.error('第二个请求错误:', err);
-          return { data: [] }; // 失败时返回空数组
-        });
-
-      // 等待两个请求完成
-      const [data1, data2] = await Promise.all([fetch1, fetch2]);
-      // 合并两个数组(确保是数组)
-      const list1 = Array.isArray(data1.data) ? data1.data : [];
-      const list2 = Array.isArray(data2.data) ? data2.data : [];
-      // 合并数组并赋值给 sessionHistory
-      sessionHistory.value = [...list1, ...list2];
-      // 设置当前会话ID(如果有数据)
-      if (sessionHistory.value.length > 0) {
-        currentSessionID.value = sessionHistory.value[0].id;
-      }
-    } catch (error) {
-      console.error('获取历史数据失败:', error);
-      // 错误处理(比如提示用户)
-    }
-  }
-  //格式化消息
-  const formatMessage = (text) => {
-    if (!text) return '';
-    let formatted = text
-      // 处理换行
-      .replace(/\n\n/g, '<br>')
-      .replace(/\n###/g, '<br>')
-      .replace(/###/g, '')
-      .replace(/---/g, '')
-      .replace(/^- /gm, '<br> - ')
-      // 处理粗体
-      .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
-      // 处理斜体
-      .replace(/\*(.*?)\*/g, '<em>$1</em>')
-      // 处理行内代码
-      .replace(/`([^`]+)`/g, '<code>$1</code>');
-    return formatted;
-  };
-  // 初始化按钮定位
-  onMounted(() => {
-    getHistoryList();
-    // 初始化(仅执行一次)
-    initMarked();
-    triggerChart();
-  });
-  onUnmounted(() => {
-    // 卸载时销毁实例,清理 DOM
-    if (myChart) {
-      myChart.dispose();
-      myChart = null;
-    }
-    const chartContainer = document.getElementById('dynamic-echarts-container');
-    if (chartContainer && chartContainer.parentNode) {
-      chartContainer.parentNode.removeChild(chartContainer);
-    }
-    window.removeEventListener('resize', handleResize);
-  });
-</script>
-
-<style lang="less" scoped>
-  .btn-header {
-    width: 30px;
-    height: 30px;
-    margin-right: 5px;
-    margin-bottom: 5px;
-  }
-  .mini-container {
-    display: flex;
-    flex-direction: row;
-  }
-  .mini-chat {
-    display: flex;
-    min-width: none;
-    width: 950px;
-    height: 85%;
-    border-radius: 4px;
-    position: fixed;
-    top: 50px;
-    right: 20px;
-    padding: 10px;
-    background-color: #0a1a2f;
-    background-image:
-      linear-gradient(to bottom, #0b69b6 1%, #0a1a2f 100%), linear-gradient(to left, #0b69b6, #0a1a2f), linear-gradient(to top, #0b69b6, #0a1a2f),
-      linear-gradient(to right, #0b69b6, #0a1a2f);
-    background-size:
-      100% 5px,
-      5px 100%,
-      100% 5px,
-      5px 100%;
-    background-position:
-      top left,
-      top right,
-      bottom right,
-      bottom left;
-    background-repeat: no-repeat;
-    z-index: 9999999;
-    color: #fff;
-  }
-  .doc {
-    display: flex;
-    flex-direction: column;
-    min-width: 600px;
-    height: 85%;
-    border-radius: 4px;
-    position: fixed;
-    top: 50px;
-    right: 51%;
-    padding: 10px;
-    background-color: #0a1a2f;
-    background-image:
-      linear-gradient(to bottom, #0b69b6 1%, #0a1a2f 100%), linear-gradient(to left, #0b69b6, #0a1a2f), linear-gradient(to top, #0b69b6, #0a1a2f),
-      linear-gradient(to right, #0b69b6, #0a1a2f);
-    background-size:
-      100% 5px,
-      5px 100%,
-      100% 5px,
-      5px 100%;
-    background-position:
-      top left,
-      top right,
-      bottom right,
-      bottom left;
-    background-repeat: no-repeat;
-    z-index: 9999999;
-    color: #fff;
-  }
-
-  .close {
-    display: flex;
-    justify-content: flex-end;
-    align-items: center;
-    width: 100%;
-    padding: 1px;
-    border-radius: 8px;
-  }
-
-  .closeBtn {
-    background-color: #007bff;
-    color: white;
-    border: none;
-    border-radius: 4px;
-    cursor: pointer;
-    transition: all 0.3s ease;
-  }
-
-  .closeBtn:hover {
-    background-color: #0056b3;
-  }
-  .left-side {
-    background: #0c2842;
-    transition: width 0.5s ease; /* 平滑过渡动画 */
-    width: 140px; /* 展开时宽度 */
-    position: relative; /* 用于按钮定位 */
-  }
-  .left-side.collapsed {
-    width: 40px; /* 折叠时宽度 */
-  }
-
-  .addBtn {
-    height: 30px;
-    position: absolute;
-    background-size: 100% 100%;
-    background-position: center;
-    padding: 2px;
-    right: 10px;
-    bottom: 40px;
-    left: 10px;
-    align-items: center;
-    border-radius: 3px;
-    cursor: pointer;
-  }
-  .custom-list {
-    height: 650px;
-    overflow-y: auto;
-  }
-  .text-container {
-    display: flex;
-    justify-content: space-between;
-    width: 100%;
-    overflow: hidden;
-  }
-  .btn-container {
-    display: flex;
-  }
-  .jeecg-layout-header-action span[role='img'] {
-    padding: 0;
-  }
-  .text-ellipsis {
-    flex: 1;
-  }
-  .edit-text {
-    overflow: hidden;
-    text-overflow: ellipsis;
-    white-space: nowrap;
-    width: 90px;
-    color: #fff;
-    font-size: 12px;
-    cursor: pointer;
-  }
-  .edit-icon {
-    flex-shrink: 0;
-    margin-left: auto;
-    line-height: 23px;
-  }
-  .delete-icon {
-    flex-shrink: 0;
-    margin-left: auto;
-    line-height: 23px;
-  }
-  .edit-icon:hover {
-    color: #1890ff !important;
-    cursor: pointer;
-  }
-  .delete-icon:hover {
-    color: #1890ff !important;
-    cursor: pointer;
-  }
-  .edit-input {
-    font-size: 10px;
-  }
-  .btn-text-bg {
-    width: 14px;
-    height: 14px;
-    position: absolute;
-    background-size: 100% 100%;
-    right: 10px;
-    top: 10px;
-    left: 10px;
-    bottom: 10px;
-  }
-  .btn-text {
-    margin-left: 3px;
-    font-size: 12px;
-    color: #fff;
-    white-space: nowrap;
-    margin-left: 30px;
-    line-height: 29px;
-  }
-  .historyBtn {
-    width: 20px;
-    height: 20px;
-    position: absolute;
-    background-size: 100% 100%;
-    background-position: center;
-    padding: 2px;
-    right: 10px;
-    top: 10px;
-  }
-  .historyBtn1 {
-    width: 20px;
-    height: 20px;
-    position: absolute;
-    background-size: 100% 100%;
-    background-position: center;
-    left: 3px;
-  }
-  .divider0 {
-    border-bottom: 1px solid #1074c1;
-    width: auto;
-    margin: 0 10px;
-    height: 13%;
-    display: block;
-    background: transparent;
-  }
-  .foldBtn {
-    width: 20px;
-    height: 20px;
-    position: absolute;
-    background-size: 100% 100%;
-    background-position: center;
-    padding: 2px;
-    right: 10px;
-    bottom: 10px;
-    cursor: pointer;
-  }
-
-  .right-side {
-    flex: 1;
-    display: flex;
-    flex-direction: column;
-    width: calc(100% - 134px) !important;
-    .title {
-      text-align: center;
-      font-size: 14px;
-      padding: 5px;
-    }
-    .dialog-area {
-      flex: 1;
-      gap: 30px;
-      overflow-y: auto;
-      padding: 5px;
-      display: flex;
-      flex-direction: column;
-      color: #fff;
-
-      .ask-message {
-        padding: 10px;
-        border-radius: 5px;
-        background: #0c2842;
-        max-width: 300px;
-        min-width: 50px;
-        white-space: pre-wrap;
-        word-wrap: break-word;
-        overflow-wrap: break-word;
-        overflow: auto;
-        line-height: 1.5;
-      }
-      .answer-message {
-        padding: 10px;
-        border-radius: 5px;
-        background: #0c2842;
-        max-width: 90%;
-      }
-    }
-
-    .input-area {
-      padding: 5px;
-      background-color: #043256;
-      border: 1px solid #2cb6ff;
-      border-radius: 5px;
-      display: flex;
-      flex-direction: column;
-      justify-content: space-around;
-      gap: 5px;
-      min-height: 170px;
-      height: auto;
-      margin: 5px;
-      position: relative;
-    }
-    /* 文件展示区域 */
-    .img-preview {
-      position: relative;
-      width: 50px;
-      height: 50px;
-    }
-    .img-preview img {
-      width: 100%;
-      height: 100%;
-      object-fit: contain;
-      display: block;
-    }
-    .file-preview {
-      position: relative;
-      margin-bottom: 10px;
-      padding: 8px;
-      border: 1px solid #e5e7eb;
-      border-radius: 4px;
-      background: #f9fafb;
-      box-sizing: border-box;
-    }
-    .file-info {
-      display: flex;
-      justify-content: space-between;
-      align-items: center;
-      font-size: 14px;
-      color: #333;
-    }
-    .clear-btn {
-      border: none;
-      background: #ff4d4f;
-      color: white;
-      border-radius: 50%;
-      width: 20px;
-      height: 20px;
-      cursor: pointer;
-      font-size: 12px;
-    }
-    .file-content {
-      margin-top: 6px;
-      font-size: 13px;
-      color: #666;
-      line-height: 1.4;
-    }
-    /* 文件列表容器 */
-    .uploaded-files {
-      padding: 8px;
-      background-color: #f5f5f5;
-      border-radius: 4px;
-      min-height: 40px;
-    }
-
-    /* 单个文件项 */
-    .file-item {
-      display: inline-flex;
-      align-items: center;
-      padding: 4px 8px;
-      margin-right: 8px;
-      margin-bottom: 8px;
-      background-color: #fff;
-      border: 1px solid #e9e9e9;
-      border-radius: 4px;
-    }
-
-    /* 文件名 */
-    .file-name {
-      margin-left: 8px;
-      margin-right: 8px;
-      max-width: 150px;
-      white-space: nowrap;
-      overflow: hidden;
-      text-overflow: ellipsis;
-    }
-
-    .ant-input {
-      border: none;
-      background-color: rgba(255, 255, 255, 0) !important;
-      color: #fff;
-    }
-    .ant-input:focus {
-      border: none; /* 聚焦时无边框 */
-      outline: none; /* 聚焦时无轮廓 */
-      box-shadow: none; /* 移除可能存在的阴影效果 */
-    }
-    .ctrl-btn {
-      display: flex;
-      flex-direction: row;
-      justify-content: space-between;
-    }
-    .question-input {
-      background-color: #1e293b !important;
-      border-color: #334155 !important;
-      color: #e2e8f0 !important;
-      border-radius: 8px !important;
-      padding: 12px 16px !important;
-      font-size: 14px !important;
-    }
-
-    .question-input::placeholder {
-      color: #64748b !important;
-    }
-
-    .control-btn {
-      height: 25px;
-      background-color: #043256;
-      border: 1px solid #2cb6ff;
-      color: #fff;
-      font-size: 10px;
-      margin-right: 10px;
-      cursor: pointer;
-      transition: background-color 0.2s ease; /* 平滑过渡效果 */
-    }
-    /* 激活状态样式(点击后) */
-    .control-btn.active {
-      background-color: #2cb6ff; /* 蓝色背景(Ant Design 主色) */
-      color: white; /* 白色文字 */
-    }
-    .control-btn1 {
-      height: 20px;
-      background-color: #234a6b;
-      border: 1px solid #234a6b;
-      color: #fff;
-      font-size: 10px;
-      margin-right: 10px;
-      cursor: pointer;
-      transition: all 0.2s;
-    }
-    .control-btn2 {
-      height: 20px;
-      background-color: #2cb6ff;
-      border: 1px solid #2cb6ff;
-      color: #fff;
-      font-size: 10px;
-      margin-right: 10px;
-      cursor: pointer;
-      transition: all 0.2s;
-    }
-    /* 文件上传区 */
-    .file-upload {
-      position: absolute;
-      right: 20px;
-      bottom: 70px;
-      width: 180px;
-      display: flex;
-      flex-direction: column;
-      gap: 10px;
-      border: 1px solid #2cb6ff;
-      background-color: #234a6b;
-      border-radius: 6px;
-      padding: 10px;
-    }
-
-    .input-container {
-      position: relative;
-      display: flex;
-      align-items: center;
-      width: 100%;
-    }
-
-    .file-input {
-      flex: 1;
-      background-color: #234a6b;
-      border-color: #2cb6ff !important;
-      color: #e2e8f0 !important;
-      border-radius: 6px !important;
-      font-size: 10px !important;
-      padding-right: 70px !important;
-      height: 36px !important;
-      width: 100% !important;
-    }
-
-    .confirm-btn {
-      position: absolute;
-      right: 5px;
-      background-color: #2cb6ff;
-      border: none;
-      color: #fff;
-      border-radius: 4px;
-      font-size: 12px;
-      padding: 4px 10px;
-      cursor: pointer;
-      transition: all 0.2s;
-      height: 28px;
-    }
-
-    .confirm-btn:hover {
-      background-color: #2cb6ff;
-    }
-
-    .custom-upload {
-      width: 100%;
-      padding: 0 !important;
-    }
-
-    .upload-btn {
-      background-color: #234a6b !important;
-      border: 1px solid #2188c3 !important;
-      color: #dbeafe !important;
-      border-radius: 6px !important;
-      font-size: 12px !important;
-      cursor: pointer;
-      transition: all 0.2s;
-      padding: 8px 0 !important;
-      width: 190% !important;
-      height: 36px !important;
-      box-sizing: border-box !important;
-    }
-    .upload-btn:hover {
-      background-color: #1f84bd !important;
-      color: #fff !important;
-    }
-    .custom-upload .ant-upload-select:hover .ant-btn {
-      border-color: #1f84bd !important;
-    }
-    .message-wrapper.ai-message-wrapper {
-      display: flex;
-      align-items: flex-start;
-    }
-    .answerIcon {
-      flex: 0 0 45px;
-    }
-
-    .suggestion-item {
-      height: 30px;
-      margin-left: 45px;
-      display: flex;
-      align-items: center;
-      justify-content: space-between;
-      padding: 10px 16px;
-      border: 1px solid #1890ff;
-      color: white;
-      border-radius: 4px;
-      cursor: pointer;
-      width: 33%;
-      font-size: 12px;
-    }
-    .thinking-section {
-      border-left: 3px solid #e5e7eb;
-      padding-left: 12px;
-      margin-bottom: 16px;
-    }
-
-    .thinking-header {
-      display: flex;
-      justify-content: space-between;
-      align-items: center;
-      padding: 8px 0;
-      cursor: pointer;
-      user-select: none;
-    }
-
-    .thinking-title {
-      font-size: 14px;
-      font-weight: 500;
-      color: #6b7280;
-    }
-  }
-</style>
-<style scoped>
-  .zxm-popover-inner-content {
-    padding: 1px;
-  }
-  .message-wrapper {
-    display: flex;
-    align-items: flex-start;
-    position: relative;
-  }
-  .user-message-wrapper {
-    flex-direction: row-reverse;
-  }
-  .ai-message-wrapper {
-    flex-direction: row;
-  }
-  /* 鼠标滑过 .message-wrapper 时,显示图标 */
-  .ai-message-wrapper:hover .copy-icon-container {
-    opacity: 1;
-    visibility: visible;
-  }
-  .user-message-wrapper:hover .copy-icon-container {
-    opacity: 1;
-    visibility: visible;
-  }
-  .copy-icon {
-    font-size: 16px;
-    color: #666;
-    cursor: pointer;
-  }
-  /* 默认隐藏,hover 时显示 */
-  .copy-icon-container {
-    position: absolute;
-    display: flex;
-    flex-direction: row;
-    bottom: -20px;
-    right: 15%;
-    opacity: 0;
-    visibility: hidden;
-    transition: all 0.3s ease;
-    z-index: 10;
-  }
-  /* 复制图标样式 */
-  .copy-icon {
-    font-size: 16px;
-    cursor: pointer;
-    margin-right: 20%;
-    float: right;
-    color: #fff;
-  }
-  .message-wrapper:hover .copy-icon {
-    opacity: 1;
-  }
-  .copy-icon:hover {
-    color: #1890ff;
-  }
-  ::v-deep table {
-    border-collapse: collapse;
-    width: 100%;
-    margin: 10px 0;
-    border: 1px solid #333;
-  }
-  ::v-deep th {
-    border: 1px solid #333;
-    background-color: #234a6b;
-    padding: 5px;
-    text-align: center;
-  }
-  ::v-deep td {
-    border: 1px solid #ddd;
-    padding: 8px 12px;
-    text-align: center;
-  }
-</style>
-<style scoped>
-  /* 已上传文件列表 */
-  .file-list {
-    margin-top: 20px;
-  }
-  .pre-container {
-    flex: 1;
-    width: 100%;
-    height: 100%;
-    overflow: auto;
-  }
-  .img-container {
-  }
-  .vue-office-excel {
-    background: #fff !important;
-  }
-
-  .file-item {
-    display: flex;
-    align-items: center;
-    padding: 12px 15px;
-    background-color: #ddd;
-    border-radius: 6px;
-    margin-bottom: 10px;
-    transition: background-color 0.2s ease;
-  }
-
-  .file-item:hover {
-    background-color: #f0f2f5;
-  }
-
-  .file-info {
-    flex: 1;
-    overflow: hidden;
-  }
-
-  .file-name {
-    font-size: 15px;
-    color: #1d2129;
-    white-space: nowrap;
-    overflow: hidden;
-    text-overflow: ellipsis;
-    margin-bottom: 3px;
-  }
-
-  .file-actions {
-    display: flex;
-    gap: 10px;
-  }
-
-  .btn {
-    padding: 0px 15px;
-    margin-left: 10px;
-    border-radius: 4px;
-    border: none;
-    font-size: 14px;
-    cursor: pointer;
-    transition: all 0.2s ease;
-  }
-
-  .btn-preview {
-    background-color: #165dff;
-    color: white;
-  }
-
-  .btn-preview:hover {
-    background-color: #0d47a1;
-  }
-
-  .btn-delete {
-    background-color: #f2f3f5;
-    color: #4e5969;
-  }
-
-  .btn-delete:hover {
-    background-color: #e5e6eb;
-    color: #1d2129;
-  }
-</style>

+ 0 - 598
src/components/AIChat/index.vue

@@ -1,598 +0,0 @@
-<!-- eslint-disable vue/multi-word-component-names -->
-<!-- eslint-disable vue/no-v-html -->
-<template>
-  <transition name="fade">
-    <div v-if="visible" class="dialog-overlay">
-      <!-- 左侧折叠区域 -->
-      <div class="left-side" :class="{ collapsed: isFold }" id="leftSide">
-        <div
-          class="addBtn"
-          :style="{
-            backgroundImage: `url(${isFold ? '/src/assets/images/vent/home/add.svg' : ''})`,
-            backgroundColor: isFold ? '' : '#2cb6ff',
-            width: isFold ? '20px' : 'auto',
-          }"
-          @click="addNew"
-        >
-          <span
-            class="btn-text-bg"
-            :style="{
-              backgroundImage: `url(${!isFold ? '/src/assets/images/vent/home/addB.svg' : ''})`,
-            }"
-          ></span>
-          <span v-if="!isFold" class="btn-text">添加新对话</span>
-        </div>
-        <div class="divider0"></div>
-        <div
-          v-if="isFold"
-          class="historyBtn"
-          :style="{ backgroundImage: `url(${isFold ? '/src/assets/images/vent/home/history.svg' : '/src/assets/images/vent/home/history.svg'})` }"
-          @click="addNew"
-        ></div>
-        <div v-else class="historyBtn1">
-          <span
-            class="btn-text-bg"
-            :style="{
-              backgroundImage: `url(${!isFold ? '/src/assets/images/vent/home/history.svg' : ''})`,
-            }"
-          ></span>
-          <span v-if="!isFold" class="btn-text">历史对话</span>
-          <a-list style="width: 110px" :split="false" :data-source="store.sessionHistory" :scroll="200" class="custom-list">
-            <template #renderItem="{ item }">
-              <a-list-item
-                :style="{
-                  padding: '8px 10px 0 8px',
-                  color: '#5e7081',
-                  fontSize: '10px',
-                  position: 'relative', // 新增定位
-                }"
-                @click="sessionsHistory(item.id)"
-              >
-                <!-- 新增flex布局容器 -->
-                <div style="display: flex; justify-content: space-between; width: 100%">
-                  <div v-if="editingId !== item.id" class="text-container">
-                    <span :class="{ 'color-white': item.id === store.currentSessionID }" class="edit-text">{{ item.title || '新会话' }}</span>
-                    <edit-outlined class="edit-icon" @click="startEditing(item)" />
-                  </div>
-
-                  <!-- 输入框 -->
-                  <a-input
-                    size="small"
-                    v-else
-                    v-model:value="editText"
-                    v-focus
-                    @blur="handleSave(item)"
-                    @keyup.enter="handleSave(item)"
-                    class="edit-input"
-                  />
-                </div>
-              </a-list-item>
-            </template>
-          </a-list>
-        </div>
-        <div
-          class="foldBtn"
-          :style="{ backgroundImage: `url(${isFold ? '/src/assets/images/vent/home/Fold.svg' : '/src/assets/images/vent/home/unfold.svg'})` }"
-          @click="fold"
-        ></div>
-      </div>
-      <!-- 右侧对话框 -->
-      <div class="right-side">
-        <div class="input-content">
-          <!-- 对话区域 -->
-          <div ref="dialogRef" class="dialog-area">
-            <div v-for="message in store.getMessageHistory" :key="message.id" :class="['message-item', message.type]">
-              <!-- 用户提问样式 -->
-              <div v-if="message.type === 'user'" class="ask-message">
-                <span>{{ message.content }}</span>
-              </div>
-
-              <!-- 系统回答样式 -->
-              <div v-else class="system-message">
-                <div class="answerIcon"></div>
-                <div class="answer-message">
-                  <div v-if="message.contentR1" class="thinking-text" v-html="formatMessage(message.contentR1)"> </div>
-                  <div class="answer-text" v-html="formatMessage(message.content)"> </div>
-                </div>
-              </div>
-            </div>
-          </div>
-          <!-- 文本输入区域 -->
-          <div v-if="store.streaming" class="thinking-area">
-            <span style="color: #fff">思考中···</span>
-            <a-spin :spinning="store.streaming" />
-          </div>
-          <div class="input-area">
-            <textarea v-model="inputText" placeholder="请输入你的问题"> </textarea>
-            <!-- 底部操作栏 -->
-            <div class="action-bar">
-              <!-- 左侧深度思考按钮 -->
-              <div class="think-btn" :class="{ active: store.deepseekR1Enable }" @click="toggleThinking"> <span>深度思考</span> </div>
-
-              <!-- 右侧操作按钮 -->
-              <div class="right-actions">
-                <label class="upload-btn">
-                  <div class="send-file"></div>
-                  <span class="divider"> | </span>
-                  <div class="send-img"></div>
-                  <div class="send-btn" @click="handleSend"></div>
-                </label>
-              </div>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
-  </transition>
-</template>
-
-<script lang="ts" setup>
-  import { ref, onMounted } from 'vue';
-  import { EditOutlined } from '@ant-design/icons-vue';
-  import { useAIChat } from '/@/store/modules/AIChat';
-  // [perf-base-v1] 卡6: formatMessage 输出进 v-html 前经 DOMPurify 白名单消毒
-  import { sanitizeHtml } from '/@/utils/markdownSafe';
-  // 响应式变量声明
-  const store = useAIChat(); //获取用户信息
-  const dialogRef = ref<HTMLElement | null>(null);
-  const isFold = ref(false); // 是否折叠
-  const inputText = ref(''); // 输入框内容
-  const hasCreated = ref(false); // 标志位,防止重复调用create接口
-  const editingId = ref<number | null>(null);
-  const editText = ref('');
-
-  const props = defineProps({
-    visible: {
-      type: Boolean,
-      required: true,
-    },
-  });
-
-  const vFocus = {
-    mounted: (el: HTMLElement) => el.querySelector('input')?.focus(),
-  };
-
-  const openMenu = () => {
-    if (props.visible) {
-      hasCreated.value = true;
-    }
-  };
-  const fold = () => {
-    isFold.value = !isFold.value;
-    if (!isFold.value) {
-      store.getSessionHistory();
-    }
-  };
-
-  const addNew = () => {
-    store.createSession();
-  };
-
-  //编辑标题
-  const startEditing = (item) => {
-    editingId.value = item.id;
-    editText.value = item.title || '';
-  };
-
-  // 保存修改
-  const handleSave = (item) => {
-    store.changeSessionTitle(editText.value, item.id).then(() => {
-      editingId.value = null;
-      store.getSessionHistory();
-    });
-  };
-
-  //启用深度思考
-  const toggleThinking = () => {
-    store.deepseekR1Enable = !store.deepseekR1Enable;
-  };
-
-  //获取消息列表
-  async function handleSend() {
-    store
-      .sendQuestion(inputText.value, () => {
-        if (dialogRef.value) {
-          dialogRef.value.scrollTop = dialogRef.value.scrollHeight;
-        }
-      })
-      .then(() => {
-        inputText.value = '';
-      });
-  }
-
-  //获取具体会话记录
-  function sessionsHistory(id: string) {
-    store.getSessionHistoryByID(id);
-  }
-  //格式化消息
-  function formatMessage(text: string) {
-    let formatted = text
-      // 处理换行
-      .replace(/\n\n/g, '<br>')
-      .replace(/\n###/g, '<br> ')
-      .replace(/###/g, '')
-      .replace(/---/g, '')
-      // 处理粗体
-      .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
-      // 处理斜体
-      .replace(/\*(.*?)\*/g, '<em>$1</em>')
-      // 处理行内代码
-      .replace(/`([^`]+)`/g, '<code>$1</code>');
-    // [perf-base-v1] 卡6: 本函数为正则直拼 HTML(非 marked),输出必须消毒后再供 v-html 使用
-    return sanitizeHtml(formatted);
-  }
-
-  // 初始化按钮定位
-  onMounted(() => {
-    store.getSessionHistory();
-    openMenu();
-  });
-</script>
-
-<style lang="less" scoped>
-  @keyframes menuShow {
-    0% {
-      width: 0;
-      height: 0;
-    }
-    100% {
-      width: 480px;
-      height: 100vh;
-    }
-  }
-  .custom-list {
-    height: 360px;
-    overflow-y: auto;
-  }
-  /* 穿透组件作用域 */
-  ::v-deep .custom-list {
-    scrollbar-width: thin;
-    scrollbar-color: #1890ff #f0f0f0;
-    &::-webkit-scrollbar {
-      width: 4px;
-      height: 6px;
-    }
-
-    &::-webkit-scrollbar-thumb {
-      background: #1890ff;
-      border-radius: 4px;
-    }
-
-    &::-webkit-scrollbar-track {
-      background: #f0f0f0;
-      border-radius: 4px;
-    }
-  }
-  ::v-deep .zxm-list-items {
-    color: #1890ff;
-  }
-  ::v-deep .zxm-list-item:hover {
-    text-decoration: underline;
-    color: #1890ff !important;
-  }
-  .text-container {
-    display: flex;
-    align-items: center;
-    width: 100%;
-    overflow: hidden;
-  }
-
-  .text-ellipsis {
-    flex: 1;
-  }
-  .edit-text {
-    overflow: hidden;
-    text-overflow: ellipsis;
-    white-space: nowrap;
-    min-width: 0;
-  }
-  .edit-icon {
-    flex-shrink: 0;
-    cursor: pointer;
-    margin-left: auto;
-  }
-  .edit-input {
-    font-size: 10px;
-  }
-  .trigger-button {
-    position: fixed;
-    bottom: 10px;
-    right: 10px;
-    z-index: 1000000;
-    .icon {
-      width: 60px;
-      height: 60px;
-      position: relative;
-      background-image: url('/@/assets/images/vent/home/wakeBtn.png');
-      background-position: center;
-      background-size: 100% 100%;
-    }
-  }
-  .dialog-overlay {
-    display: flex;
-    background-color: #09172c;
-  }
-
-  /* 遮罩层淡入淡出 */
-  .fade-enter-active,
-  .fade-leave-active {
-    transition: opacity 0.3s;
-  }
-  .fade-enter-from,
-  .fade-leave-to {
-    opacity: 0;
-  }
-
-  /* 弹窗缩放动画 */
-  .scale-enter-active,
-  .scale-leave-active {
-    transition: all 0.3s ease;
-  }
-  .scale-enter-from {
-    transform: scale(0.5) translate(-50%, -50%);
-    opacity: 0;
-  }
-  .scale-leave-to {
-    transform: scale(1.2) translate(-50%, -50%);
-    opacity: 0;
-  }
-
-  .left-side {
-    background: #0c2842;
-    transition: width 0.5s ease; /* 平滑过渡动画 */
-    width: 120px; /* 展开时宽度 */
-    position: relative; /* 用于按钮定位 */
-  }
-  .left-side.collapsed {
-    width: 40px; /* 折叠时宽度 */
-  }
-
-  .addBtn {
-    height: 30px;
-    position: absolute;
-    background-size: 100% 100%;
-    background-position: center;
-    padding: 2px;
-    right: 10px;
-    top: 10px;
-    left: 10px;
-    align-items: center;
-    border-radius: 3px;
-    cursor: pointer;
-  }
-  .btn-text-bg {
-    width: 14px;
-    height: 14px;
-    position: absolute;
-    background-size: 100% 100%;
-    right: 10px;
-    top: 9px;
-    left: 10px;
-    bottom: 10px;
-  }
-  .btn-text {
-    margin-left: 3px;
-    font-size: 12px;
-    color: #fff;
-    white-space: nowrap;
-    margin-left: 30px;
-    line-height: 26px;
-  }
-  .historyBtn {
-    width: 20px;
-    height: 20px;
-    position: absolute;
-    background-size: 100% 100%;
-    background-position: center;
-    padding: 2px;
-    right: 10px;
-    top: 100px;
-  }
-  .historyBtn1 {
-    width: 20px;
-    height: 20px;
-    position: absolute;
-    background-size: 100% 100%;
-    background-position: center;
-    left: 3px;
-    top: 80px;
-  }
-  .divider0 {
-    border-bottom: 1px solid #1074c1;
-    width: auto;
-    margin: 0 10px;
-    height: 13%;
-    display: block;
-    background: transparent;
-  }
-  .foldBtn {
-    width: 20px;
-    height: 20px;
-    position: absolute;
-    background-size: 100% 100%;
-    background-position: center;
-    padding: 2px;
-    right: 10px;
-    bottom: 10px;
-    cursor: pointer;
-  }
-
-  .right-side {
-    flex: 1; /* 占据剩余空间 */
-    background: #09172c;
-  }
-
-  .input-content {
-    display: flex;
-    flex-direction: column;
-    justify-content: flex-end; /* 内容底部对齐 */
-    height: 100%;
-    padding: 20px; /* 统一内边距 */
-  }
-  .ask-message {
-    align-self: flex-end;
-    float: right;
-    max-width: 70%;
-    padding: 10px;
-    margin: 10px;
-    border-radius: 5px;
-    color: #fff;
-    background: #0c2842;
-    align-self: flex-end; /* 右侧对齐‌:ml-citation{ref="2" data="citationList"} */
-  }
-  .answer {
-    display: flex;
-    flex-direction: row;
-  }
-  .answerIcon {
-    flex-shrink: 0;
-    margin-top: 10px;
-    width: 35px;
-    height: 35px;
-    background-image: url('/@/assets/images/vent/home/answerIcon.svg');
-    background-size: 100% 100%;
-  }
-  .answer-message {
-    float: left;
-    padding: 10px;
-    margin: 10px;
-    border-radius: 5px;
-    background: #0c2842;
-  }
-  .thinking-text {
-    color: gray;
-    font-size: 12px;
-  }
-  .answer-text {
-    color: #fff;
-  }
-  /** 系统返回信息**/
-  .system-message {
-    display: flex;
-    flex-direction: row;
-    align-self: flex-start;
-    width: 100%;
-    padding: 12px;
-    display: flex;
-  }
-  .answerIcon {
-    margin-top: 10px;
-    width: 35px;
-    height: 35px;
-    background-image: url('/@/assets/images/vent/home/answerIcon.svg');
-    background-size: 100% 100%;
-  }
-  .think-area {
-    color: #7979799f;
-  }
-  .answer-area {
-    color: #fff;
-  }
-  .dialog-area {
-    flex: 1; /* 占据剩余空间 */
-    gap: 50px; /* 消息块间隔统一控制 */
-    overflow-y: auto; /* 垂直滚动条 */
-    margin-bottom: 10px;
-  }
-  .loading-wrapper,
-  .content-wrapper {
-    min-height: 40px;
-  }
-  .message-item.user {
-    margin-bottom: 50px;
-  }
-  .input-area {
-    background-color: #043256 !important;
-    display: flex;
-    flex-direction: column;
-    gap: 10px;
-  }
-  textarea {
-    background-color: #043256 !important;
-    width: 100%;
-    height: 40px;
-    border: none;
-    resize: none;
-    outline: none;
-    overflow: hidden;
-    padding: 10px; /* 统一内边距 */
-    color: #fff;
-  }
-
-  .action-bar {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    padding: 8px 16px;
-  }
-
-  .think-btn {
-    border: 1px solid #ccc;
-    width: 120px;
-    height: 20px;
-    line-height: 20px;
-    text-align: center;
-    border-radius: 50px;
-    cursor: pointer;
-    background: white;
-    transition: background 0.3s;
-  }
-
-  .think-btn.active {
-    background: #1890ff;
-    color: white;
-    border-color: #1890ff;
-  }
-  .right-actions {
-    display: flex;
-    align-items: center;
-    gap: 8px;
-  }
-
-  .upload-btn {
-    display: flex;
-    align-items: center;
-    gap: 8px;
-  }
-  .upload-btn {
-    float: right;
-    display: flex;
-    cursor: pointer;
-    padding: 6px 12px;
-  }
-
-  .divider {
-    color: #ccc;
-    font-weight: 300;
-    margin: 0 10px;
-  }
-
-  .send-file {
-    width: 20px;
-    height: 20px;
-    background-image: url('/@/assets/images/vent/home/sendFile.svg');
-    background-size: 100% 100%;
-    border-radius: 4px;
-    cursor: pointer;
-  }
-  .send-img {
-    width: 20px;
-    height: 20px;
-    background-image: url('/@/assets/images/vent/home/sendImg.svg');
-    background-size: 100% 100%;
-    border-radius: 4px;
-    cursor: pointer;
-  }
-  .send-btn {
-    width: 20px;
-    height: 20px;
-    margin-left: 10px;
-    margin-right: 10px;
-    background-color: #1074c1;
-    background-image: url('/@/assets/images/vent/home/send.svg');
-    background-position: center;
-    background-size: 100% 100%;
-    border-radius: 2px;
-    cursor: pointer;
-  }
-</style>

+ 3 - 8
src/layouts/default/header/components/rightPositionBar.vue

@@ -11,9 +11,6 @@
         <VoiceBroadcast v-if="sysOrgCode != 'sdmtjtgsd' && portValue != '8062'" />
         <VoiceBroadcastGsd v-if="sysOrgCode == 'sdmtjtgsd'" />
       </template>
-      <div v-if="hasPermission('show:AIChat')" class="ai-chat-wrap">
-        <AIChat />
-      </div>
       <div v-if="hasPermission('show:AIDiAssistant')" class="ai-picker">
         <img
           :src="dataPickerHoverEnabled ? AIOpenIcon : AICloseIcon"
@@ -39,8 +36,6 @@
   import WeatherBroadcast from './weatherBroadcast.vue';
   import VoiceBroadcast from './VoiceBroadcast.vue';
   import VoiceBroadcastGsd from './VoiceBroadcastGsd.vue';
-  // [perf-base-v1] 卡2a: MiniChat 异步化,首屏不加载其重依赖(echarts/@vue-office/markdown-vendor)
-  const AIChat = defineAsyncComponent(() => import('/@/components/AIChat/MiniChat.vue'));
   import UserDropDown from './user-dropdown/index.vue';
   import AIOpenIcon from '/@/assets/images/ventAI/dataPicker/AI-open.svg';
   import AICloseIcon from '/@/assets/images/ventAI/dataPicker/AI-close.svg';
@@ -100,12 +95,12 @@
     // 子组件内部按钮自带的外边距/高度不一致,统一重置为撑满 48px 并垂直居中
     :deep(.btn),
     :deep(.btn-header) {
+      display: flex;
+      align-items: center;
+      height: 48px;
       margin: 0;
       padding: 0;
-      height: 48px;
       line-height: 48px;
-      display: flex;
-      align-items: center;
     }
 
     // UserDropDown 是多根节点组件,包一层容器隔离其弹窗根节点;

+ 1 - 1
src/layouts/default/sider/DragBar.vue

@@ -60,7 +60,7 @@
 
     &:hover {
       background-color: @primary-color;
-      box-shadow: 0 0 4px 0 rgba(28, 36, 56, 0.15);
+      box-shadow: 0 0 4px 0 rgb(28 36 56 / 15%);
     }
   }
 </style>

+ 5 - 8
src/layouts/default/sider/bottomSider2.vue

@@ -2,17 +2,13 @@
   <div>
     <div class="trigger-button">
       <div class="icon" @click="openMenu" :style="{ zIndex: 1000 }"></div>
-      <Aichat
-        style="width: 32%; height: 55%; z-index: 999; position: fixed; right: 90px; bottom: 20px; box-shadow: 0 0 3px 3px #1074c1"
-        :visible="dialogVisible"
-      />
     </div>
   </div>
 </template>
 
 <script lang="ts" setup>
   import { ref } from 'vue';
-  import Aichat from '/@/components/AIChat/index.vue';
+
   const dialogVisible = ref(false);
   const openMenu = () => {
     dialogVisible.value = !dialogVisible.value;
@@ -22,17 +18,18 @@
 <style lang="less" scoped>
   .trigger-button {
     position: fixed;
-    bottom: 10px;
     right: 10px;
+    bottom: 10px;
     z-index: 1000000;
+
     .icon {
+      position: relative;
       width: 60px;
       height: 60px;
-      position: relative;
+      cursor: pointer;
       background-image: url('/@/assets/images/vent/home/wakeBtn.png');
       background-position: center;
       background-size: 100% 100%;
-      cursor: pointer;
     }
   }
 </style>

+ 1 - 20
src/layouts/default/sider/index.vue

@@ -1,23 +1,5 @@
 <template>
-  <!-- <Drawer
-    v-if="getIsMobile && getShowSidebar"
-    placement="left"
-    :class="prefixCls"
-    :width="getMenuWidth"
-    :getContainer="null"
-    :visible="!getCollapsed"
-    @close="handleClose"
-  >
-    <Sider />
-  </Drawer>
-  <MixSider v-else-if="getIsMixSidebar" />
-  <BottomSider v-else-if="getIsBottomMenu" />
-  <Sider v-else /> -->
   <BottomSider v-if="!noSiderLink.includes(routePath)" />
-  <!-- 新版样式 -->
-  <!-- <bottomSider3 v-if="!noSiderLink.includes(routePath)" /> -->
-  <!-- <bottomSider2 v-if="!noChatLink.includes(routePath)" /> -->
-  <!-- <bottomSider2 /> -->
 </template>
 <script lang="ts">
   import { computed, defineComponent } from 'vue';
@@ -25,7 +7,6 @@
   import Sider from './LayoutSider.vue';
   import MixSider from './MixSider.vue';
   import BottomSider from './bottomSideder.vue';
-  import bottomSider2 from './bottomSider2.vue';
   import bottomSider3 from './bottomSideder3.vue';
   import { Drawer } from 'ant-design-vue';
 
@@ -36,7 +17,7 @@
   import { noSiderLink, noChatLink } from '../layout.data';
   export default defineComponent({
     name: 'SiderWrapper',
-    components: { Sider, Drawer, MixSider, BottomSider, bottomSider2, bottomSider3 },
+    components: { Sider, Drawer, MixSider, BottomSider, bottomSider3 },
     setup() {
       const route = useRoute();
       const { prefixCls } = useDesign('layout-sider-wrapper');

+ 0 - 257
src/store/modules/AIChat.ts

@@ -1,257 +0,0 @@
-import { defineStore } from 'pinia';
-import { useUserStore } from './user';
-// [perf-base-v1] 卡7: AI 接口地址配置化,原硬编码 http://182.92.126.35:6005 改走 VUE_APP_URL.aiChatUrl(开发经 vite proxy /aiChat 转发)
-import { aiFetch } from '/@/utils/aiChatApi';
-
-export const useAIChat = defineStore({
-  id: 'ai-chat',
-  state: (): {
-    /** 会话历史 */
-    sessionHistory: {
-      id: string;
-      user_id: string;
-      agent: string;
-      title: string;
-      title_type: string;
-      version: number;
-      current_message_id: number;
-      inserted_at: string;
-      updated_at: string;
-    }[];
-    /** 消息历史 */
-    messageHistory: {
-      id: string; // 唯一标识(可用时间戳生成)
-      type: 'user' | 'system' | 'response';
-      content: string;
-      /** 深度思考时的文本 */
-      contentR1: string;
-      timestamp: number; // 排序依据
-    }[];
-    /** 当前会话ID */
-    currentSessionID: string;
-    /** 当前会话是否有流正在传输 */
-    streaming: boolean;
-    /** 当前会话是否启用 deepseekR1 模型 */
-    deepseekR1Enable: boolean;
-    /** 当前用户ID */
-    userID: string;
-  } => {
-    const userid = useUserStore().getUserInfo.id as string;
-    return {
-      sessionHistory: [],
-      messageHistory: [],
-      currentSessionID: '',
-      streaming: false,
-      deepseekR1Enable: false,
-      userID: userid,
-    };
-  },
-  getters: {
-    getMessageHistory: (state) => {
-      return state.messageHistory.sort((a, b) => a.timestamp - b.timestamp);
-    },
-  },
-  actions: {
-    /** 创建新会话 */
-    async createSession() {
-      const response = await aiFetch('/sessions/create', {
-        method: 'post',
-        headers: {
-          'Content-Type': 'application/json',
-        },
-        body: JSON.stringify({
-          user_id: this.userID,
-        }),
-      });
-      const data = await response.json();
-      this.currentSessionID = data.id;
-      this.messageHistory = [];
-    },
-    /** 根据当前会话,创建聊天流,流里的数据将会更新到消息历史中 */
-    async createStream(question: string, onUpdate?: any) {
-      this.streaming = true;
-      this.messageHistory.push({
-        id: `user_${Date.now()}`,
-        type: 'user',
-        content: question,
-        contentR1: '',
-        timestamp: Date.now(),
-      });
-
-      try {
-        // 发送 POST 请求
-        const response = await aiFetch('/chat_stream', {
-          method: 'POST',
-          headers: {
-            'Content-Type': 'application/json',
-          },
-          body: JSON.stringify({
-            chat_session_id: this.currentSessionID,
-            prompt: question,
-            ref_file_ids: [],
-            thinking_enabled: this.deepseekR1Enable,
-          }),
-        });
-
-        // 检查响应是否成功
-        if (!response.ok || !response.body) {
-          throw new Error('Network response was not ok');
-        }
-
-        // 获取可读流
-        const reader = response.body.getReader();
-
-        // 创建一条新的消息对象
-        const newMessage = {
-          id: `response_${Date.now()}`,
-          type: 'response' as any,
-          content: '',
-          contentR1: '',
-          timestamp: Date.now(),
-        };
-
-        // 将新消息添加到消息列表
-        this.messageHistory.push(newMessage);
-
-        // 读取流式数据
-        while (true) {
-          const { done, value } = await reader.read();
-          if (done) {
-            break;
-          }
-
-          // 将流数据转换为字符串
-          const chunk = new TextDecoder().decode(value);
-
-          // 使用正则表达式匹配完整的 JSON 对象
-          const jsonRegex = /{.*?}/g;
-          const matches = chunk.match(jsonRegex);
-
-          if (!matches) continue;
-          matches.forEach((match) => {
-            const data = JSON.parse(match);
-            // 找到当前消息对象并更新 content
-            const targetMessage = this.messageHistory.find((msg) => msg.id === newMessage.id);
-            if (!targetMessage) return;
-            if (data.type === 'text') {
-              targetMessage.content += data.content; // 追加内容
-            }
-            if (data.type === 'thinking') {
-              targetMessage.contentR1 += data.content;
-            }
-            if (typeof onUpdate === 'function') {
-              onUpdate(value);
-            }
-          });
-        }
-      } catch (error) {
-        // 请求失败时设置系统消息
-        this.messageHistory.push({
-          id: `system_${Date.now()}`,
-          type: 'system',
-          content: '系统异常,请稍后再试',
-          contentR1: '',
-          timestamp: Date.now(),
-        });
-      } finally {
-        this.streaming = false;
-      }
-    },
-    /** 创建会话的标题 */
-    async createSessionTitle(title: string) {
-      await aiFetch('/sessions/title', {
-        method: 'post',
-        headers: {
-          'Content-Type': 'application/json',
-        },
-        body: JSON.stringify({
-          chat_session_id: this.currentSessionID,
-          prompt: title,
-        }),
-      });
-    },
-    /** 修改会话的标题 */
-    async changeSessionTitle(title: string, id: string) {
-      try {
-        const response = await aiFetch('/sessions/change_title', {
-          method: 'POST',
-          headers: {
-            'Content-Type': 'application/json',
-          },
-          body: JSON.stringify({
-            chat_session_id: id,
-            new_title: title,
-          }),
-        });
-        if (!response.ok) {
-          throw new Error('Network response was not ok');
-        }
-        this.sessionHistory.forEach((session) => {
-          if (session.id === id) {
-            session.title = title;
-          }
-        });
-      } catch (error) {
-        console.error('保存失败:', error);
-      }
-    },
-    /** 获取会话历史 */
-    async getSessionHistory() {
-      const response = await aiFetch(`/sessions`, {
-        method: 'post',
-        headers: {
-          'Content-Type': 'application/json',
-        },
-        body: JSON.stringify({
-          user_id: this.userID,
-        }),
-      });
-      const data = await response.json();
-      this.sessionHistory = data.chat_sessions;
-    },
-    /** 获取会话历史 */
-    async getSessionHistoryByID(id: string) {
-      const response = await aiFetch(`/sessions/history_chat/?chat_session_id=${id}`, {
-        method: 'get',
-        headers: {
-          'Content-Type': 'application/json',
-        },
-      });
-      const data = await response.json();
-      if (data.chat_messages.length === 0) return;
-      this.currentSessionID = id;
-      this.messageHistory = data.chat_messages.map((item: any) => {
-        if (item.role === 'user') {
-          // role== user 用户提问
-          return {
-            id: `user_${Date.now()}`,
-            type: 'user',
-            content: item.content,
-            contentR1: '',
-            timestamp: Date.now(),
-          };
-        } else {
-          // role== assistant 机器回答
-          return {
-            id: `system_${Date.now()}`,
-            type: 'system',
-            content: item.content,
-            contentR1: item.thinking_content,
-            timestamp: Date.now(),
-          };
-        }
-      });
-    },
-    /** 发出问题 */
-    async sendQuestion(question: string, onUpdate?: any) {
-      if (this.currentSessionID === '') {
-        await this.createSession();
-        this.createSessionTitle(question);
-        this.createStream(question, onUpdate);
-      } else {
-        this.createSessionTitle(question);
-        this.createStream(question, onUpdate);
-      }
-    },
-  },
-});

+ 0 - 24
src/utils/aiChatApi.ts

@@ -1,24 +0,0 @@
-// [perf-base-v1] 卡7: AI 接口地址配置化
-// 原实现:AIChat store 硬编码 http://182.92.126.35:6005,MiniChat 硬编码 http://39.97.59.228:8000/v1,
-// 生产/开发环境切换需改源码。现实现:统一走 public/js/config.js 的 VUE_APP_URL 配置
-// (开发环境经 vite proxy 前缀 /aiChat、/dify 转发,生产由 nginx 等反代同名前缀)。
-// 注意:VUE_APP_URL 是 config.js 经典脚本顶层 const(全局词法绑定,不在 window 上),按现有代码惯例直接引用。
-
-/** AI 会话服务基础路径(原 http://182.92.126.35:6005) */
-export const aiBase: string = (typeof VUE_APP_URL !== 'undefined' && VUE_APP_URL.aiChatUrl) || '/aiChat';
-
-/** Dify 服务基础路径(原 http://39.97.59.228:8000,调用方需自行拼 /v1 前缀) */
-export const difyBase: string = (typeof VUE_APP_URL !== 'undefined' && VUE_APP_URL.difyUrl) || '/dify';
-
-/** Dify 应用 API Key(不含 'Bearer ' 前缀)。生产环境应由后端代理下发,勿依赖前端硬编码兜底值。 */
-export const difyApiKey: string =
-  (typeof VUE_APP_URL !== 'undefined' && VUE_APP_URL.difyApiKey) || 'app-tSFRUnv0Qkbtik1dwtlhnpkd';
-
-/**
- * AI 会话服务 fetch 封装:拼接 aiBase + path 后调用原生 fetch
- * @param path 以 / 开头的接口路径,如 '/sessions/create'
- * @param init 原生 fetch 的 RequestInit
- */
-export function aiFetch(path: string, init?: RequestInit): Promise<Response> {
-  return fetch(`${aiBase}${path}`, init);
-}

+ 23 - 16
src/views/vent/home/configurable/blue/components/content-FireNew.vue

@@ -82,9 +82,6 @@
         <template v-if="config.name === 'qh_curve'">
           <QHCurve class="content__module" :mainfan="config.data" :fan1-prop="config.config.fan1Prop" :fan2-prop="config.config.fan2Prop" />
         </template>
-        <template v-if="config.name === 'ai_chat'">
-          <AIChat class="content__module" />
-        </template>
         <template v-if="config.name === 'device_alarm'">
           <DeviceAlarm class="content__module" :devicedata="config.data" />
         </template>
@@ -122,7 +119,7 @@
   import TimelineList from '../../components/detail/TimelineList.vue';
   import TimelineListNew from '../../components/detail/TimelineListNew.vue';
   import CustomList from '../../components/detail/CustomList-fire.vue';
-  import ModuleTopFire from '../../components/originalNew/moduleTopFire.vue';
+  // import ModuleTopFire from '../../components/originalNew/moduleTopFire.vue';
   import CustomGallery from '../../components/detail/CustomGallery.vue';
   import ComplexList from '../../components/detail/ComplexList-FireNew.vue';
   import GalleryList from '../../components/detail/GalleryList.vue';
@@ -134,10 +131,8 @@
   import QHCurve from '../../components/preset/QHCurve.vue';
   import MeasureDetail from '../../components/preset/MeasureDetail.vue';
   import CustomTabs from '../../components/preset/CustomTabs.vue';
-  // [perf-base-v1] 卡2a: MiniChat 异步化,首屏不加载其重依赖
-  const AIChat = defineAsyncComponent(() => import('/@/components/AIChat/MiniChat.vue'));
   import DeviceAlarm from '../../components/preset/DeviceAlarm.vue';
-  import MiniBoardNew from '../../components/detail/MiniBoard-New.vue';
+  // import MiniBoardNew from '../../components/detail/MiniBoard-New.vue';
   // import FIreWarn from './preset/FIreWarn.vue';
   // import FIreControl from './preset/FIreControl.vue';
 
@@ -375,54 +370,60 @@
   });
 </script>
 <style lang="less" scoped>
-  @import '@/design/theme.less';
+  @import url('@/design/theme.less');
 
   .content {
-    height: calc(100% - 30px);
     position: relative;
     // z-index: -2;
     display: flex;
     flex-direction: column;
+    height: calc(100% - 30px);
     overflow-x: hidden;
   }
+
   .content__background {
-    width: 100%;
-    height: 100%;
     position: absolute;
     top: 0;
     left: 0;
     z-index: 0;
+    width: 100%;
+    height: 100%;
     object-fit: fill;
   }
+
   .image__background {
+    left: 30%;
     width: 35%;
     height: 61%;
-    left: 30%;
   }
+
   .content__module {
     // margin-top: 5px;
     // margin-bottom: 5px;
     width: 100%;
     height: 100%;
   }
+
   .content__module1 {
+    height: 129px;
     background: url('@/assets/images/vent/homeNew/databg/4.png');
     background-repeat: no-repeat;
     background-size: 100% 100%;
-    height: 129px;
     margin-top: 20%;
   }
+
   .content__moduleFire {
     width: 100%;
     height: 100%;
     margin-left: -24% !important;
   }
+
   .content__module_dust {
+    width: 100%;
+    height: 100%;
     background: url('@/assets/images/vent/homeNew/bottomBg.png');
     background-repeat: no-repeat;
     background-size: 100% 100%;
-    width: 100%;
-    height: 100%;
   }
   // .content__module:first-of-type {
   //   margin-top: 0;
@@ -433,6 +434,7 @@
   ::-webkit-scrollbar {
     width: 5px !important;
   }
+
   ::-webkit-scrollbar-thumb {
     width: 5px !important;
   }
@@ -441,25 +443,30 @@
     /* background-color: transparent; */
     color: #fff;
   }
+
   :deep(.zxm-select-arrow) {
     color: #fff;
   }
+
   :deep(.zxm-select-selection-item) {
     color: #fff !important;
   }
+
   :deep(.zxm-select-selection-placeholder) {
     color: #fff !important;
   }
+
   :deep(.dialog-overlay) {
+    position: unset;
     width: 100%;
     height: 100%;
-    position: unset;
     box-shadow: unset;
   }
 
   ::-webkit-scrollbar {
     width: 5px !important;
   }
+
   ::-webkit-scrollbar-thumb {
     width: 5px !important;
   }

+ 19 - 12
src/views/vent/home/configurable/blue/components/content-New.vue

@@ -115,9 +115,6 @@
         <template v-if="config.name === 'qh_curve'">
           <QHCurve class="content__module" :mainfan="config.data" :fan1-prop="config.config.fan1Prop" :fan2-prop="config.config.fan2Prop" />
         </template>
-        <template v-if="config.name === 'ai_chat'">
-          <AIChat class="content__module" />
-        </template>
         <template v-if="config.name === 'device_alarm'">
           <DeviceAlarm class="content__module" :devicedata="config.data" />
         </template>
@@ -167,8 +164,6 @@
   import QHCurve from '../../components/preset/QHCurve.vue';
   import MeasureDetail from '../../components/preset/MeasureDetail.vue';
   import CustomTabs from '../../components/preset/CustomTabs.vue';
-  // [perf-base-v1] 卡2a: MiniChat 异步化,首屏不加载其重依赖
-  const AIChat = defineAsyncComponent(() => import('/@/components/AIChat/MiniChat.vue'));
   import DeviceAlarm from '../../components/preset/DeviceAlarm.vue';
   import MiniBoardNew from '../../components/detail/MiniBoard-New.vue';
   import CustomTableNew from '../../components/detail/CustomTable-New.vue';
@@ -409,48 +404,54 @@
   });
 </script>
 <style lang="less" scoped>
-  @import '@/design/theme.less';
+  @import url('@/design/theme.less');
 
   .content {
-    height: calc(100% - 30px);
     position: relative;
     // z-index: -2;
     display: flex;
     flex-direction: column;
+    height: calc(100% - 30px);
     overflow-x: hidden;
   }
+
   .content__background {
-    width: 100%;
-    height: 100%;
     position: absolute;
     top: 0;
     left: 0;
     z-index: 0;
+    width: 100%;
+    height: 100%;
     object-fit: fill;
   }
+
   .image__background {
+    left: 30%;
     width: 35%;
     height: 61%;
-    left: 30%;
   }
+
   .content__module {
     // margin-top: 5px;
     // margin-bottom: 5px;
     width: 100%;
     height: 100%;
   }
+
   .content__module1 {
+    height: 129px;
     background: url('@/assets/images/vent/homeNew/databg/4.png');
     background-repeat: no-repeat;
     background-size: 100% 100%;
-    height: 129px;
     margin-top: 20%;
   }
+
   .content__moduleFire {
     width: 100%;
     height: 100%;
     margin-left: -24% !important;
   }
+
   .content__module_dust {
     // background: url('@/assets/images/vent/homeNew/bottomBg.png');
     // background-repeat: no-repeat;
@@ -468,6 +469,7 @@
   ::-webkit-scrollbar {
     width: 5px !important;
   }
+
   ::-webkit-scrollbar-thumb {
     width: 5px !important;
   }
@@ -476,25 +478,30 @@
     /* background-color: transparent; */
     color: #fff;
   }
+
   :deep(.zxm-select-arrow) {
     color: #fff;
   }
+
   :deep(.zxm-select-selection-item) {
     color: #fff !important;
   }
+
   :deep(.zxm-select-selection-placeholder) {
     color: #fff !important;
   }
+
   :deep(.dialog-overlay) {
+    position: unset;
     width: 100%;
     height: 100%;
-    position: unset;
     box-shadow: unset;
   }
 
   ::-webkit-scrollbar {
     width: 5px !important;
   }
+
   ::-webkit-scrollbar-thumb {
     width: 5px !important;
   }

+ 10 - 16
src/views/vent/home/configurable/blue/components/content.vue

@@ -117,9 +117,6 @@
         <template v-if="config.name === 'qh_curve'">
           <QHCurve class="content__module" :mainfan="config.data" :fan1-prop="config.config.fan1Prop" :fan2-prop="config.config.fan2Prop" />
         </template>
-        <template v-if="config.name === 'ai_chat'">
-          <AIChat class="content__module" />
-        </template>
         <template v-if="config.name === 'device_alarm'">
           <DeviceAlarm class="content__module" :devicedata="config.data" :config="config.config" />
         </template>
@@ -196,8 +193,6 @@
   import QHCurve from '../../components/preset/QHCurve.vue';
   import MeasureDetail from '../../components/preset/MeasureDetail.vue';
   import CustomTabs from '../../components/preset/CustomTabs.vue';
-  // [perf-base-v1] 卡2a: MiniChat 异步化,首屏不加载其重依赖
-  const AIChat = defineAsyncComponent(() => import('/@/components/AIChat/MiniChat.vue'));
   import DeviceAlarm from '../../components/preset/DeviceAlarm.vue';
   import SelectCs from '../../components/preset/SelectCs.vue';
   import MiniBoardNew from '../../components/detail/MiniBoard-New.vue';
@@ -459,42 +454,41 @@
   });
 </script>
 <style lang="less" scoped>
-  @import '@/design/theme.less';
+  @import url('@/design/theme.less');
 
   .content {
-    height: calc(100% - 30px);
     position: relative;
     // z-index: -2;
     display: flex;
     flex-direction: column;
-    overflow-y: auto; // 这里会导致样式无故添加滚动条
-    overflow-x: hidden;
+    height: calc(100% - 30px);
+    overflow: hidden auto; // 这里会导致样式无故添加滚动条
   }
 
   .content__background {
-    width: 100%;
-    height: 100%;
     position: absolute;
     top: 0;
     left: 0;
     z-index: 0;
+    width: 100%;
+    height: 100%;
     object-fit: fill;
   }
 
   .content__background_1 {
-    width: 100%;
-    height: calc(100% - 10px);
     position: absolute;
     top: 10px;
     left: 0;
     z-index: 0;
+    width: 100%;
+    height: calc(100% - 10px);
     object-fit: fill;
   }
 
   .image__background {
+    left: 30%;
     width: 35%;
     height: 61%;
-    left: 30%;
   }
 
   .content__module {
@@ -505,10 +499,10 @@
   }
 
   .content__module1 {
+    height: 129px;
     background: url('@/assets/images/vent/homeNew/databg/4.png');
     background-repeat: no-repeat;
     background-size: 100% 100%;
-    height: 129px;
     margin-top: 20%;
   }
 
@@ -558,9 +552,9 @@
   }
 
   :deep(.dialog-overlay) {
+    position: unset;
     width: 100%;
     height: 100%;
-    position: unset;
     box-shadow: unset;
   }
 

+ 19 - 12
src/views/vent/home/configurable/components/content-New.vue

@@ -115,9 +115,6 @@
         <template v-if="config.name === 'qh_curve'">
           <QHCurve class="content__module" :mainfan="config.data" :fan1-prop="config.config.fan1Prop" :fan2-prop="config.config.fan2Prop" />
         </template>
-        <template v-if="config.name === 'ai_chat'">
-          <AIChat class="content__module" />
-        </template>
         <template v-if="config.name === 'device_alarm'">
           <DeviceAlarm class="content__module" :devicedata="config.data" :config="config.config" />
         </template>
@@ -167,8 +164,6 @@
   import QHCurve from './preset/QHCurve.vue';
   import MeasureDetail from './preset/MeasureDetail.vue';
   import CustomTabs from './preset/CustomTabs.vue';
-  // [perf-base-v1] 卡2a: MiniChat 异步化,首屏不加载其重依赖
-  const AIChat = defineAsyncComponent(() => import('/@/components/AIChat/MiniChat.vue'));
   import DeviceAlarm from './preset/DeviceAlarm.vue';
   import MiniBoardNew from './detail/MiniBoard-New.vue';
   import CustomTableNew from './detail/CustomTable-New.vue';
@@ -409,48 +404,54 @@
   });
 </script>
 <style lang="less" scoped>
-  @import '@/design/theme.less';
+  @import url('@/design/theme.less');
 
   .content {
-    height: calc(100% - 30px);
     position: relative;
     // z-index: -2;
     display: flex;
     flex-direction: column;
+    height: calc(100% - 30px);
     overflow-x: hidden;
   }
+
   .content__background {
-    width: 100%;
-    height: 100%;
     position: absolute;
     top: 0;
     left: 0;
     z-index: 0;
+    width: 100%;
+    height: 100%;
     object-fit: fill;
   }
+
   .image__background {
+    left: 30%;
     width: 35%;
     height: 61%;
-    left: 30%;
   }
+
   .content__module {
     // margin-top: 5px;
     // margin-bottom: 5px;
     width: 100%;
     height: 100%;
   }
+
   .content__module1 {
+    height: 129px;
     background: url('@/assets/images/vent/homeNew/databg/4.png');
     background-repeat: no-repeat;
     background-size: 100% 100%;
-    height: 129px;
     margin-top: 20%;
   }
+
   .content__moduleFire {
     width: 100%;
     height: 100%;
     margin-left: -24% !important;
   }
+
   .content__module_dust {
     // background: url('@/assets/images/vent/homeNew/bottomBg.png');
     // background-repeat: no-repeat;
@@ -468,6 +469,7 @@
   ::-webkit-scrollbar {
     width: 5px !important;
   }
+
   ::-webkit-scrollbar-thumb {
     width: 5px !important;
   }
@@ -476,25 +478,30 @@
     /* background-color: transparent; */
     color: #fff;
   }
+
   :deep(.zxm-select-arrow) {
     color: #fff;
   }
+
   :deep(.zxm-select-selection-item) {
     color: #fff !important;
   }
+
   :deep(.zxm-select-selection-placeholder) {
     color: #fff !important;
   }
+
   :deep(.dialog-overlay) {
+    position: unset;
     width: 100%;
     height: 100%;
-    position: unset;
     box-shadow: unset;
   }
 
   ::-webkit-scrollbar {
     width: 5px !important;
   }
+
   ::-webkit-scrollbar-thumb {
     width: 5px !important;
   }

+ 17 - 20
src/views/vent/home/configurable/components/content.vue

@@ -154,9 +154,6 @@
         <template v-if="config.name === 'qh_curve'">
           <QHCurve class="content__module" :mainfan="config.data" :fan1-prop="config.config.fan1Prop" :fan2-prop="config.config.fan2Prop" />
         </template>
-        <template v-if="config.name === 'ai_chat'">
-          <AIChat class="content__module" />
-        </template>
         <template v-if="config.name === 'device_alarm'">
           <DeviceAlarm class="content__module" :devicedata="config.data" :config="config.config" />
         </template>
@@ -272,7 +269,6 @@
   const QHCurve = defineAsyncComponent(() => import('./preset/QHCurve.vue'));
   const MeasureDetail = defineAsyncComponent(() => import('./preset/MeasureDetail.vue'));
   const CustomTabs = defineAsyncComponent(() => import('./preset/CustomTabs.vue'));
-  const AIChat = defineAsyncComponent(() => import('/@/components/AIChat/MiniChat.vue'));
   const DeviceAlarm = defineAsyncComponent(() => import('./preset/DeviceAlarm.vue'));
   const SelectCs = defineAsyncComponent(() => import('./preset/SelectCs.vue'));
   const MiniBoardNew = defineAsyncComponent(() => import('./detail/MiniBoard-New.vue'));
@@ -560,42 +556,43 @@
   });
 </script>
 <style lang="less" scoped>
-  @import '@/design/theme.less';
+  @import url('@/design/theme.less');
 
   .content {
-    height: calc(100% - 30px);
     position: relative;
     display: flex;
     flex-direction: column;
-    overflow-y: auto;
-    overflow-x: hidden;
+    height: calc(100% - 30px);
+    overflow: hidden auto;
   }
+
   .content__background {
-    width: 100%;
-    // height: 100%;
-    height: calc(100% - 65px);
     position: absolute;
     top: 65px;
     left: 0;
     z-index: 0;
-    object-fit: fill;
+    width: 100%;
+    // height: 100%;
+    height: calc(100% - 65px);
     padding: 5px;
+    object-fit: fill;
     box-sizing: border-box;
   }
+
   .content__background_1 {
-    width: 100%;
-    height: 100%;
     position: absolute;
-    top: 0px;
+    top: 0;
     left: 0;
     z-index: 0;
+    width: 100%;
+    height: 100%;
     object-fit: fill;
   }
 
   .image__background {
+    left: 30%;
     width: 35%;
     height: 61%;
-    left: 30%;
   }
 
   .content__module {
@@ -604,10 +601,10 @@
   }
 
   .content__module1 {
+    height: 129px;
     background: url('@/assets/images/vent/homeNew/databg/4.png');
     background-repeat: no-repeat;
     background-size: 100% 100%;
-    height: 129px;
     margin-top: 20%;
   }
 
@@ -618,11 +615,11 @@
   }
 
   .content__module_dust {
+    width: 100%;
+    height: 100%;
     background: url('@/assets/images/vent/homeNew/bottomBg.png');
     background-repeat: no-repeat;
     background-size: 100% 100%;
-    width: 100%;
-    height: 100%;
   }
 
   ::-webkit-scrollbar {
@@ -650,9 +647,9 @@
   }
 
   :deep(.dialog-overlay) {
+    position: unset;
     width: 100%;
     height: 100%;
-    position: unset;
     box-shadow: unset;
   }
 </style>

+ 5 - 10
src/views/vent/home/configurable/green/components/content-green.vue

@@ -75,9 +75,6 @@
         <template v-if="config.name === 'qh_curve'">
           <QHCurve class="content__module" :mainfan="config.data" :fan1-prop="config.config.fan1Prop" :fan2-prop="config.config.fan2Prop" />
         </template>
-        <template v-if="config.name === 'ai_chat'">
-          <AIChat class="content__module" />
-        </template>
         <template v-if="config.name === 'device_alarm'">
           <DeviceAlarm class="content__module" :devicedata="config.data" />
         </template>
@@ -135,8 +132,6 @@
   import QHCurve from '../../components/preset/QHCurve.vue';
   import MeasureDetail from '../../components/preset/MeasureDetail.vue';
   import CustomTabs from '../../components/preset/CustomTabs.vue';
-  // [perf-base-v1] 卡2a: MiniChat 异步化,首屏不加载其重依赖
-  const AIChat = defineAsyncComponent(() => import('/@/components/AIChat/MiniChat.vue'));
   import DeviceAlarm from '../../components/preset/DeviceAlarm.vue';
   import { config } from 'dotenv';
 
@@ -374,23 +369,23 @@
   });
 </script>
 <style lang="less" scoped>
-  @import '@/design/theme.less';
+  @import url('@/design/theme.less');
 
   .content {
-    height: calc(100% - 30px);
     position: relative;
     // z-index: -2;
     display: flex;
     flex-direction: column;
+    height: calc(100% - 30px);
   }
 
   .content__background {
-    width: 100%;
-    height: 100%;
     position: absolute;
     top: 0;
     left: 0;
     z-index: 0;
+    width: 100%;
+    height: 100%;
     object-fit: fill;
   }
 
@@ -435,9 +430,9 @@
   }
 
   :deep(.dialog-overlay) {
+    position: unset;
     width: 100%;
     height: 100%;
-    position: unset;
     box-shadow: unset;
   }
 

+ 156 - 166
src/views/vent/monitorManager/mainFanMonitor/index.vue

@@ -34,7 +34,7 @@
     }}</div>
 
     <div v-if="hasPermission('btn:show')" class="top-box control-group">
-      <div style="width: 100%; display: flex; justify-content: center">
+      <div style="display: flex; justify-content: center; width: 100%">
         <template v-for="(item, index) in modalTypeArr.centerBtnArr" :key="index">
           <div v-if="hasPermission(item.permission)" class="button-box" @click="showModal(item)">{{ item.value }}</div>
         </template>
@@ -78,22 +78,22 @@
                     selectData['Fan1StartStatus'] == '0' && globalConfig?.simulatedPassword
                       ? '-'
                       : selectData[data.dataIndex.replace('Fan', 'Fan1')]
-                      ? selectData[data.dataIndex.replace('Fan', 'Fan1')]
-                      : '-'
+                        ? selectData[data.dataIndex.replace('Fan', 'Fan1')]
+                        : '-'
                   }}</div>
                   <div class="item-value" v-if="dataMonitorRowIndex == 1">{{
                     selectData['Fan2StartStatus'] == '0' && globalConfig?.simulatedPassword
                       ? '-'
                       : selectData[data.dataIndex.replace('Fan', 'Fan2')]
-                      ? selectData[data.dataIndex.replace('Fan', 'Fan2')]
-                      : '-'
+                        ? selectData[data.dataIndex.replace('Fan', 'Fan2')]
+                        : '-'
                   }}</div>
                   <div class="item-value" v-if="dataMonitorRowIndex == 2">{{
                     selectData['Fan3StartStatus'] == '0' && globalConfig?.simulatedPassword
                       ? '-'
                       : selectData[data.dataIndex.replace('Fan', 'Fan3')]
-                      ? selectData[data.dataIndex.replace('Fan', 'Fan3')]
-                      : '-'
+                        ? selectData[data.dataIndex.replace('Fan', 'Fan3')]
+                        : '-'
                   }}</div>
                 </div>
                 <div v-else>
@@ -153,8 +153,8 @@
                           selectData[state.dataIndex.replace('Fan', 'Fan1')] === undefined
                             ? '无状态'
                             : selectData[state.dataIndex.replace('Fan', 'Fan1')] == '0'
-                            ? '正常'
-                            : '异常'
+                              ? '正常'
+                              : '异常'
                         }}</div>
                       </template>
                     </div>
@@ -178,8 +178,8 @@
                           selectData[state.dataIndex.replace('Fan', 'Fan2')] == undefined
                             ? '无状态'
                             : selectData[state.dataIndex.replace('Fan', 'Fan2')] == '0'
-                            ? '正常'
-                            : '异常'
+                              ? '正常'
+                              : '异常'
                         }}</div>
                       </template>
                     </div>
@@ -203,8 +203,8 @@
                           selectData[state.dataIndex.replace('Fan', 'Fan3')] == undefined
                             ? '无状态'
                             : selectData[state.dataIndex.replace('Fan', 'Fan3')] == '0'
-                            ? '正常'
-                            : '异常'
+                              ? '正常'
+                              : '异常'
                         }}</div>
                       </template>
                     </div>
@@ -337,7 +337,7 @@
           <a-tab-pane key="5" tab="实时曲线" v-if="hasPermission('show:fanEcharts')">
             <div v-if="activeKey == '5'" class="tab-item" style="display: flex">
               <div style="width: 50%; height: 100%">
-                <div style="color: var(--vent-font-color); width: 100%; text-align: center; margin: 5px 0px">主机</div>
+                <div style="width: 100%; margin: 5px 0; color: var(--vent-font-color); text-align: center">主机</div>
                 <BarAndLine
                   class="echarts-line"
                   xAxisPropType="readTime"
@@ -350,7 +350,7 @@
                 />
               </div>
               <div style="width: 50%; height: 100%">
-                <div style="color: var(--vent-font-color); width: 100%; text-align: center; margin: 5px 0px">备机</div>
+                <div style="width: 100%; margin: 5px 0; color: var(--vent-font-color); text-align: center">备机</div>
                 <BarAndLine
                   class="echarts-line"
                   xAxisPropType="readTime"
@@ -439,7 +439,7 @@
     ref="playerRef"
     v-show="showPlay"
     :class="{ 'to-right': rightColumns.length < 1 || leftColumns.length < 1, 'to-no-right': rightColumns.length > 0 && leftColumns.length > 0 }"
-    style="z-index: 999; position: absolute; width: 100%; height: 100%; overflow-x: auto; overflow-y: hidden; margin: auto; pointer-events: none"
+    style="position: absolute; z-index: 999; width: 100%; height: 100%; margin: auto; overflow: auto hidden; pointer-events: none"
     :style="{ top: hasPermission('btn:show') ? '155px' : '100px' }"
   >
   </div>
@@ -1280,16 +1280,16 @@
         selectData['modalTyoe'] === 'xiejing'
           ? 'mainXjWindRect'
           : selectData['modalTyoe'] === 'lijing1'
-          ? 'mainLjWindRect'
-          : selectData['modalTyoe'] === 'lijing_3'
-          ? 'mainWindRect3'
-          : selectData['modalTyoe'] === 'lijing_1'
-          ? 'mainLjDtWindRect'
-          : selectData['modalTyoe'] === 'lijing'
-          ? 'mainWindRect'
-          : selectData['modalTyoe']
-          ? selectData['modalTyoe']
-          : 'mainWindRect';
+            ? 'mainLjWindRect'
+            : selectData['modalTyoe'] === 'lijing_3'
+              ? 'mainWindRect3'
+              : selectData['modalTyoe'] === 'lijing_1'
+                ? 'mainLjDtWindRect'
+                : selectData['modalTyoe'] === 'lijing'
+                  ? 'mainWindRect'
+                  : selectData['modalTyoe']
+                    ? selectData['modalTyoe']
+                    : 'mainWindRect';
 
       frontMonitorIsShow.value = false;
       centerMonitorIsShow.value = false;
@@ -1988,11 +1988,11 @@
 </script>
 
 <style scoped lang="less">
-  @import '/@/design/theme.less';
-  @import '/@/design/vent/modal.less';
+  @import url('/@/design/theme.less');
+  @import url('/@/design/vent/modal.less');
 
   @font-face {
-    font-family: 'douyuFont';
+    font-family: douyuFont;
     src: url('../../../../assets/font/douyuFont.otf');
   }
 
@@ -2005,7 +2005,7 @@
       --image-r-tab-active: url('/@/assets/images/themify/deepblue/vent/r-tab-active.png');
       --image-plane-bottom: url('/@/assets/images/themify/deepblue/vent/plane-bottom.png');
       --image-plane-icon-bg: url('/@/assets/images/themify/deepblue/vent/plane-icon-bg.png');
-      --container-group-bg: linear-gradient(to right, #ffffff22, #53576305);
+      --container-group-bg: linear-gradient(to right, #fff2, #53576305);
     }
   }
 
@@ -2022,14 +2022,14 @@
 
   .detail-btn {
     display: flex;
-    justify-content: center;
     align-items: center;
+    justify-content: center;
     width: 100%;
     height: 40px;
-    margin: 5px 0px;
+    margin: 5px 0;
     background: url('@/assets/images/vent/detail-btn.png') no-repeat;
-    background-size: 100% 100%;
     cursor: pointer;
+    background-size: 100% 100%;
   }
 
   .detail-btn:active {
@@ -2038,36 +2038,36 @@
   }
 
   .faultbg {
-    margin-top: 20px;
-    background: url('/@/assets/images/vent/fault1.png') no-repeat;
-    background-size: 100% 100%;
-    width: auto;
-    height: 140px;
     display: flex;
     flex-direction: column;
+    width: auto;
+    height: 140px;
+    background: url('/@/assets/images/vent/fault1.png') no-repeat;
+    margin-top: 20px;
+    background-size: 100% 100%;
     gap: 20px;
     /* 关键属性 */
   }
 
   .faultname {
-    font-family: 'douyuFont';
+    width: auto;
+    height: 30px;
+    background: url('/@/assets/images/vent/fault2.png') no-repeat;
     color: #32d9e7;
+    font-family: douyuFont;
     margin-top: 10px;
     margin-left: 10px;
     font-size: 12px;
     float: left;
-    background: url('/@/assets/images/vent/fault2.png') no-repeat;
     background-size: 33% 80%;
-    height: 30px;
-    width: auto;
   }
 
   .faultreason {
+    width: auto;
     height: 50px;
-    line-height: 50px;
     background: url('/@/assets/images/vent/fault3.png') no-repeat;
+    line-height: 50px;
     background-size: 100% 100%;
-    width: auto;
 
     .miaoshu {
       margin-left: 10px;
@@ -2075,7 +2075,7 @@
     }
 
     .faultdata {
-      font-family: 'douyuFont';
+      font-family: douyuFont;
       margin-right: 10px;
       font-size: 12px;
       color: #32d9e7;
@@ -2091,54 +2091,51 @@
 
     .button-box {
       position: relative;
-      padding: 5px;
-      border: 1px transparent solid;
-      border-radius: 5px;
-      width: auto;
-      height: 34px;
-      border: 1px solid var(--vent-base-border);
       display: flex;
       align-items: center;
       justify-content: center;
-      color: var(--vent-font-color);
+      width: auto;
+      height: 34px;
+      padding: 5px;
       padding: 0 10px;
+      border: 1px transparent solid;
+      border: 1px solid var(--vent-base-border);
+      color: var(--vent-font-color);
       cursor: pointer;
+      border-radius: 5px;
 
       &:hover {
         background: linear-gradient(#3eb2ff55, #00c3ff55);
       }
 
       &::before {
+        position: absolute;
+        inset: 3px 0 0 3px;
+        z-index: -1;
         width: calc(100% - 6px);
         height: 26px;
+        /* important */
+        background: linear-gradient(#014978, #3bf3fc);
         content: '';
-        position: absolute;
-        top: 3px;
-        right: 0;
-        left: 3px;
-        bottom: 0;
-        z-index: -1;
         border-radius: inherit;
-        /*important*/
-        background: linear-gradient(#014978, #3bf3fc);
       }
     }
   }
 
   .control-group,
   .top-box {
-    display: flex !important;
-    align-items: center !important;
     // justify-content: space-around !important;
     position: absolute !important;
+    top: 85px !important;
+    left: 50% !important;
+    display: flex !important;
+    align-items: center !important;
     width: 1165px !important;
     height: 75px !important;
-    left: 50% !important;
-    top: 85px !important;
-    transform: translateX(-50%) !important;
+    padding: 0 30px !important;
     background: var(--image-tab-group-bg) no-repeat !important;
+    transform: translateX(-50%) !important;
     background-size: 100% 75px !important;
-    padding: 0 30px !important;
 
     .button-box {
       height: 35px !important;
@@ -2168,7 +2165,7 @@
         background: none !important;
       }
 
-      &:before {
+      &::before {
         background: linear-gradient(#5897c299, #4a92a899) !important;
       }
     }
@@ -2176,38 +2173,38 @@
 
   .top-right {
     position: absolute;
-    display: flex;
-    right: 0px;
     top: 55px;
+    right: 0;
+    z-index: 1;
+    display: flex;
     align-items: center;
     color: var(--vent-font-color);
-    z-index: 1;
   }
 
   .data-show-box {
     position: relative;
+    top: 70px;
+    z-index: 999;
     display: flex;
     flex-direction: row;
     justify-content: space-between;
     padding: 10px;
     color: var(--vent-font-color);
-    z-index: 999;
-    top: 70px;
 
     .data-item {
       pointer-events: auto;
 
       .item-header {
         width: 374px;
-        background: var(--image-lr-top-bg) no-repeat;
-        background-size: cover;
         height: 32px;
+        background: var(--image-lr-top-bg) no-repeat;
+        color: #fafafa;
         text-align: center;
+        cursor: pointer;
+        background-size: cover;
         line-height: 34px;
         font-size: 15px;
         font-weight: 600;
-        color: #fafafa;
-        cursor: pointer;
       }
 
       .item-container {
@@ -2218,18 +2215,18 @@
         backdrop-filter: blur(2px);
 
         .tab {
+          display: flex;
           width: 323px;
           background: var(--image-lr-tab-bg) no-repeat;
           background-size: cover;
-          display: flex;
 
           .tab-item {
             flex: 1;
+            height: calc(100% - 10px);
+            color: #fff9;
             text-align: center;
-            padding-top: 2px;
-            color: #ffffff99;
             cursor: pointer;
-            height: calc(100% - 10px);
+            padding-top: 2px;
             overflow-y: auto;
 
             &:hover {
@@ -2255,7 +2252,7 @@
 
         .container-group {
           width: 314px;
-          margin: 0px 4px;
+          margin: 0 4px;
           padding: 10px 0;
           min-height: 472px;
           background: var(--container-group-bg);
@@ -2265,7 +2262,7 @@
 
         .container-group-r {
           width: 314px;
-          margin: 0px 4px;
+          margin: 0 4px;
           padding: 10px 0;
           min-height: 422px;
           background: var(--container-group-bg);
@@ -2275,24 +2272,24 @@
 
         .container-group-l {
           .container-item {
+            position: relative;
+            display: flex;
             width: 100%;
             height: 63px;
-            display: flex;
             padding: 10px 0 0 20px;
-            margin-bottom: 5px;
-            position: relative;
             background: var(--image-plane-bottom) no-repeat;
+            margin-bottom: 5px;
             background-size: auto;
             background-position: bottom;
 
             &::before {
-              content: '';
-              display: block;
-              width: 100%;
-              height: 5px;
               position: absolute;
               top: 62px;
               left: 0;
+              display: block;
+              width: 100%;
+              height: 5px;
+              content: '';
               background-color: #73f4ff66;
               backdrop-filter: blur(5px);
             }
@@ -2315,23 +2312,23 @@
 
             .item-value {
               position: relative;
+              width: 80px;
               height: 26px;
-              line-height: 24px;
               margin: 15px 0;
-              text-align: center;
-              width: 80px;
               border: 1px solid var(--vent-base-border);
-              border-radius: 13px;
               background: linear-gradient(to right, #00f5fe44, #0090ff44);
+              text-align: center;
+              line-height: 24px;
+              border-radius: 13px;
 
               &::before {
-                width: 6px;
-                height: 6px;
-                content: '';
                 position: absolute;
-                left: -3px;
                 top: 8px;
+                left: -3px;
+                width: 6px;
+                height: 6px;
                 background: #ffa500;
+                content: '';
                 border-radius: 3px;
               }
             }
@@ -2343,12 +2340,12 @@
           font-size: 14px;
 
           .header-item {
-            flex: 1;
             display: flex;
+            flex: 1;
             flex-direction: column;
-            margin: 2px 10px;
-            justify-content: space-between;
             align-items: center;
+            justify-content: space-between;
+            margin: 2px 10px;
 
             .header-title {
               color: var(--vent-font-action-link);
@@ -2356,15 +2353,15 @@
 
             .header-value {
               position: relative;
+              display: flex;
+              align-items: center;
+              justify-content: center;
               height: 56px;
+              color: #ffa500;
               padding-left: 5px;
               font-weight: 600;
-              font-family: 'douyuFont';
+              font-family: douyuFont;
               font-size: 16px;
-              color: #ffa500;
-              display: flex;
-              justify-content: center;
-              align-items: center;
             }
           }
         }
@@ -2386,46 +2383,46 @@
         }
 
         .warning-group {
-          padding: 0 10px;
           position: relative;
+          padding: 0 10px;
 
           &::before {
-            content: '';
+            position: absolute;
+            left: 12px;
             display: block;
             width: 1px;
             height: 100%;
-            position: absolute;
-            left: 12px;
+            content: '';
             background-color: var(--vent-base-border);
           }
 
           .warning-item {
             display: flex;
             flex-direction: row;
-            justify-content: space-between;
             align-items: center;
+            justify-content: space-between;
             height: 38px;
 
             .item-name {
               .icon {
+                position: relative;
+                display: inline-block;
                 width: 6px;
                 height: 6px;
-                display: inline-block;
                 background-color: var(--vent-base-light-bg);
                 border-radius: 3px;
-                position: relative;
                 margin-right: 5px;
 
                 &::before {
-                  content: '';
+                  position: absolute;
+                  top: -2px;
+                  left: -2px;
+                  display: block;
                   width: 10px;
                   height: 10px;
-                  display: block;
                   border: 1px solid #34edff99;
+                  content: '';
                   border-radius: 5px;
-                  position: absolute;
-                  top: -2px;
-                  left: -2px;
                 }
               }
             }
@@ -2436,8 +2433,8 @@
           display: flex;
 
           .item {
-            text-align: center;
             color: #aff3ffee;
+            text-align: center;
           }
         }
 
@@ -2448,8 +2445,8 @@
 
           .fault-item {
             display: flex;
-            padding: 3px 0px;
-            border-bottom: 1px solid #ffffff22;
+            padding: 3px 0;
+            border-bottom: 1px solid #fff2;
 
             .item {
               padding: 0 2px;
@@ -2492,74 +2489,75 @@
 
     .bd-control {
       position: absolute;
+      top: 10px;
+      right: 0;
       z-index: 9999;
       display: flex;
-      right: 0px;
-      top: 10px;
     }
   }
 
   /** 保德在线监测和故障诊断切换组件 */
   .monitor-tab {
-    display: flex;
     position: absolute;
     top: 600px;
     left: 395px;
+    display: flex;
     align-items: center;
 
     .item {
-      font-size: 15px;
-      color: var(--vent-font-color);
       padding: 0 5px;
+      color: var(--vent-font-color);
       cursor: pointer;
+      font-size: 15px;
     }
 
     .item-active {
-      transform: skew(-15deg, 0);
       color: var(--vent-font-action-link);
+      transform: skew(-15deg, 0);
       border-bottom: 1px solid var(--vent-base-border);
     }
   }
 
   .icon-box {
-    width: 140px;
-    height: 38px;
     position: absolute;
     top: 600px;
     right: 395px;
-    color: var(--vent-font-color);
     z-index: 1;
-    font-size: 28px;
     display: flex;
     justify-content: space-between;
+    width: 140px;
+    height: 38px;
+    color: var(--vent-font-color);
+    font-size: 28px;
   }
 
   .video-icon {
+    position: relative;
     width: 30px;
     height: 38px;
-    position: relative;
   }
 
   .no-play {
     &::after {
       position: absolute;
+      top: -5px;
+      left: 12%;
       width: 80%;
       height: 100%;
-      content: '';
-      left: 12%;
-      top: -5px;
       background: linear-gradient(
         to bottom left,
         transparent 0%,
         transparent calc(50% - 2px),
-        #ffffff 50%,
+        #fff 50%,
         transparent calc(50% + 2px),
         transparent 100%
       );
+      content: '';
     }
   }
 
   .warn-icon {
+    z-index: 1;
     width: 30px;
     height: 38px;
     // position: absolute;
@@ -2567,27 +2565,26 @@
     // right: 460px;
     background: url('/@/assets/images/vent/normal-mainfan.png') no-repeat;
     background-size: contain;
-    z-index: 1;
     font-size: 28px;
   }
 
   .btn {
-    padding: 4px 12px;
     position: relative;
-    border-radius: 2px;
+    padding: 4px 12px;
     color: var(--vent-font-color);
     cursor: pointer;
+    border-radius: 2px;
 
     &::before {
       position: absolute;
+      top: 2px;
+      left: 2px;
+      z-index: -1;
       display: block;
-      content: '';
       width: calc(100% - 4px);
       height: calc(100% - 4px);
-      top: 2px;
-      left: 2px;
+      content: '';
       border-radius: 2px;
-      z-index: -1;
     }
   }
 
@@ -2624,53 +2621,46 @@
 
   :deep(.button-box) {
     position: relative;
-    padding: 5px;
-    border-radius: 5px;
-    margin-left: 8px;
-    margin-right: 8px;
-    width: auto;
-    height: 35px !important;
     display: flex;
     align-items: center;
     justify-content: center;
+    width: auto;
+    height: 35px !important;
+    padding: 5px;
+    padding: 0 15px 5px;
     color: var(--vent-font-color);
-    padding: 0 15px 5px 15px;
     cursor: pointer;
+    border-radius: 5px;
+    margin-left: 8px;
+    margin-right: 8px;
 
     &:hover {
       background: var(--vent-device-manager-control-btn-hover);
     }
 
     &::before {
+      position: absolute;
+      inset: 3px 0 0 3px;
+      z-index: -1;
       width: calc(100% - 6px);
       height: 27px;
+      /* important */
+      background: var(--vent-device-manager-control-btn);
       content: '';
-      position: absolute;
-      top: 3px;
-      right: 0;
-      left: 3px;
-      bottom: 0;
-      z-index: -1;
       border-radius: inherit;
-      /*important*/
-      background: var(--vent-device-manager-control-btn);
     }
 
     &::after {
+      position: absolute;
+      inset: 28px 0 0 -16px;
+      z-index: 999;
       width: calc(100% + 32px);
       height: 10px;
       content: '';
-      position: absolute;
-      top: 28px;
-      right: 0;
-      left: -16px;
-      bottom: 0;
-      z-index: -1;
       border-radius: inherit;
-      /*important*/
+      /* important */
       background-position: center;
       background-size: 100%;
-      z-index: 999;
     }
   }
 
@@ -2683,10 +2673,10 @@
   }
 
   ::v-deep(#LivePlayerBox) {
-    width: 100%;
     display: flex;
     flex-direction: row;
     justify-content: flex-end;
+    width: 100%;
     // padding-right: 380px;
     // padding-left: 380px;
     pointer-events: none;

+ 33 - 32
src/views/ventAI/manageAssistent/components/chatModal/FilePreviewPanel.vue

@@ -441,54 +441,54 @@
       .header-action-btn {
         display: inline-flex;
         align-items: center;
-        gap: 5px;
         padding: 4px 12px;
+        border: none;
+        cursor: pointer;
+        transition: all 0.2s ease;
+        gap: 5px;
         border-radius: 6px;
         font-size: 12px;
         font-weight: 500;
-        cursor: pointer;
-        border: none;
-        transition: all 0.2s ease;
         white-space: nowrap;
 
         &.edit-btn {
-          background: linear-gradient(135deg, #0a84ff 0%, #0066cc 100%);
+          border: 1px solid rgb(10 132 255 / 50%);
+          background: linear-gradient(135deg, #0a84ff 0%, #06c 100%);
           color: #fff;
-          border: 1px solid rgba(10, 132, 255, 0.5);
-          box-shadow: 0 2px 6px rgba(10, 132, 255, 0.2);
+          box-shadow: 0 2px 6px rgb(10 132 255 / 20%);
 
           &:hover {
             background: linear-gradient(135deg, #3a9bfd 0%, #0a84ff 100%);
-            box-shadow: 0 3px 10px rgba(10, 132, 255, 0.35);
+            box-shadow: 0 3px 10px rgb(10 132 255 / 35%);
           }
         }
 
         &.cancel-btn {
-          background: rgba(255, 255, 255, 0.06);
+          border: 1px solid rgb(63 80 106 / 40%);
+          background: rgb(255 255 255 / 6%);
           color: #8b949e;
-          border: 1px solid rgba(63, 80, 106, 0.4);
 
           &:hover {
-            background: rgba(255, 255, 255, 0.1);
+            background: rgb(255 255 255 / 10%);
             color: #c9d1d9;
-            border-color: rgba(63, 80, 106, 0.6);
+            border-color: rgb(63 80 106 / 60%);
           }
         }
 
         &.save-btn {
-          background: linear-gradient(135deg, #0a84ff 0%, #0066cc 100%);
+          border: 1px solid rgb(10 132 255 / 50%);
+          background: linear-gradient(135deg, #0a84ff 0%, #06c 100%);
           color: #fff;
-          border: 1px solid rgba(10, 132, 255, 0.5);
-          box-shadow: 0 2px 6px rgba(10, 132, 255, 0.25);
+          box-shadow: 0 2px 6px rgb(10 132 255 / 25%);
 
           &:hover:not(:disabled) {
             background: linear-gradient(135deg, #3a9bfd 0%, #0a84ff 100%);
-            box-shadow: 0 3px 10px rgba(10, 132, 255, 0.35);
+            box-shadow: 0 3px 10px rgb(10 132 255 / 35%);
           }
 
           &:disabled {
-            opacity: 0.6;
             cursor: not-allowed;
+            opacity: 0.6;
           }
 
           &.saving {
@@ -504,6 +504,7 @@
           from {
             transform: rotate(0deg);
           }
+
           to {
             transform: rotate(360deg);
           }
@@ -860,11 +861,11 @@
             position: absolute;
             top: 4px;
             right: 4px;
+            z-index: 1;
             display: flex;
-            gap: 2px;
             opacity: 0;
             transition: opacity 0.2s;
-            z-index: 1;
+            gap: 2px;
           }
 
           &:hover .table-actions {
@@ -872,21 +873,21 @@
           }
 
           .table-action-btn {
-            width: 26px;
-            height: 26px;
             display: flex;
             align-items: center;
             justify-content: center;
-            border-radius: 4px;
+            width: 26px;
+            height: 26px;
+            background: rgb(22 27 34 / 80%);
             color: #8b949e;
             cursor: pointer;
-            background: rgba(22, 27, 34, 0.8);
-            backdrop-filter: blur(4px);
             transition: all 0.2s;
+            border-radius: 4px;
+            backdrop-filter: blur(4px);
 
             &:hover {
+              background: rgb(10 132 255 / 30%);
               color: #e0e6ed;
-              background: rgba(10, 132, 255, 0.3);
             }
           }
         }
@@ -904,21 +905,21 @@
     }
 
     .edit-mode {
-      height: 100%;
       display: flex;
       flex-direction: column;
+      height: 100%;
 
       .edit-textarea {
         flex: 1;
         width: 100%;
-        min-height: 100%;
         padding: 12px;
-        font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
+        border: 1px solid rgb(63 80 106 / 40%);
+        background: rgb(0 0 0 / 20%);
+        color: #e0e6ed;
+        min-height: 100%;
+        font-family: 'Cascadia Code', 'Fira Code', Consolas, monospace;
         font-size: 13px;
         line-height: 1.6;
-        color: #e0e6ed;
-        background: rgba(0, 0, 0, 0.2);
-        border: 1px solid rgba(63, 80, 106, 0.4);
         border-radius: 6px;
         outline: none;
         resize: none;
@@ -926,7 +927,7 @@
 
         &:focus {
           border-color: #0a84ff;
-          background: rgba(0, 0, 0, 0.3);
+          background: rgb(0 0 0 / 30%);
         }
       }
     }

+ 42 - 42
src/views/ventAI/manageAssistent/components/chatModal/RightPanelTabs.vue

@@ -182,7 +182,7 @@
       gap: 8px;
       padding: 10px 12px;
       flex-shrink: 0;
-      border-bottom: 1px solid rgba(63, 80, 106, 0.25);
+      border-bottom: 1px solid rgb(63 80 106 / 25%);
 
       .panel-tab-toggle-wrapper {
         position: relative;
@@ -191,21 +191,21 @@
         align-items: center;
 
         .panel-tab-toggle {
-          width: 28px;
-          height: 28px;
           display: flex;
           align-items: center;
           justify-content: center;
-          border-radius: 4px;
-          border: 1px solid rgba(63, 80, 106, 0.35);
-          background: rgba(63, 80, 106, 0.12);
+          width: 28px;
+          height: 28px;
+          border: 1px solid rgb(63 80 106 / 35%);
+          background: rgb(63 80 106 / 12%);
           cursor: pointer;
           transition: all 0.2s;
+          border-radius: 4px;
 
           &:hover,
           &.active {
-            background: rgba(10, 132, 255, 0.15);
-            border-color: rgba(10, 132, 255, 0.4);
+            background: rgb(10 132 255 / 15%);
+            border-color: rgb(10 132 255 / 40%);
           }
 
           .panel-tab-toggle-icon {
@@ -223,35 +223,35 @@
           position: absolute;
           top: calc(100% + 6px);
           left: 0;
-          width: 240px;
-          max-height: 320px;
+          z-index: 1000;
           display: flex;
           flex-direction: column;
-          background: #0a2a3f;
+          width: 240px;
           border: 1px solid #1a4a6f;
+          background: #0a2a3f;
+          max-height: 320px;
           border-radius: 8px;
-          box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
-          z-index: 1000;
+          box-shadow: 0 4px 20px rgb(0 0 0 / 40%);
 
           .tab-list-search {
-            margin: 10px 10px 4px;
             display: flex;
             align-items: center;
-            gap: 8px;
             height: 34px;
+            margin: 10px 10px 4px;
             padding: 0 12px;
             border: none;
-            border-bottom: 1px solid rgba(63, 80, 106, 0.5);
             background: transparent;
-            flex-shrink: 0;
             transition: border-bottom-color 0.2s;
+            gap: 8px;
+            border-bottom: 1px solid rgb(63 80 106 / 50%);
+            flex-shrink: 0;
 
             &:hover {
-              border-bottom-color: rgba(63, 80, 106, 0.7);
+              border-bottom-color: rgb(63 80 106 / 70%);
             }
 
             &:focus-within {
-              border-bottom-color: rgba(10, 132, 255, 0.7);
+              border-bottom-color: rgb(10 132 255 / 70%);
             }
 
             .tab-list-search-icon {
@@ -267,12 +267,12 @@
 
             .tab-list-search-input {
               flex: 1;
-              min-width: 0;
               height: 100%;
-              border: none;
               padding: 0;
+              border: none;
               background: transparent;
               color: #e0e6ed;
+              min-width: 0;
               font-size: 12px;
               outline: none;
 
@@ -297,11 +297,11 @@
               transition: background 0.2s;
 
               &:hover {
-                background: rgba(10, 132, 255, 0.15);
+                background: rgb(10 132 255 / 15%);
               }
 
               &.active {
-                background: rgba(10, 132, 255, 0.2);
+                background: rgb(10 132 255 / 20%);
               }
 
               .tab-list-item-icon {
@@ -328,20 +328,20 @@
 
               .tab-list-item-title {
                 flex: 1;
-                min-width: 0;
+                color: #c9d1d9;
                 overflow: hidden;
+                min-width: 0;
                 text-overflow: ellipsis;
                 white-space: nowrap;
                 font-size: 12px;
-                color: #c9d1d9;
               }
             }
 
             .tab-list-empty {
               padding: 16px 0;
+              color: #6b7684;
               text-align: center;
               font-size: 12px;
-              color: #6b7684;
             }
           }
         }
@@ -372,32 +372,32 @@
 
       .panel-tab {
         display: flex;
+        flex: 0 0 150px;
         align-items: center;
-        gap: 6px;
         height: 28px;
-        flex: 0 0 150px;
-        min-width: 0;
         padding: 6px 12px;
-        border-radius: 4px;
-        border: 1px solid rgba(63, 80, 106, 0.35);
-        background: rgba(63, 80, 106, 0.12);
+        border: 1px solid rgb(63 80 106 / 35%);
+        background: rgb(63 80 106 / 12%);
+        color: #8b949e;
         cursor: pointer;
+        transition: all 0.2s;
+        gap: 6px;
+        min-width: 0;
+        border-radius: 4px;
         white-space: nowrap;
         font-size: 12px;
-        color: #8b949e;
-        transition: all 0.2s;
 
         &:hover {
-          background: rgba(10, 132, 255, 0.08);
-          border-color: rgba(10, 132, 255, 0.3);
+          background: rgb(10 132 255 / 8%);
+          border-color: rgb(10 132 255 / 30%);
           color: #c9d1d9;
         }
 
         &.active {
-          background: rgba(10, 132, 255, 0.18);
-          border-color: rgba(10, 132, 255, 0.5);
+          background: rgb(10 132 255 / 18%);
+          border-color: rgb(10 132 255 / 50%);
           color: #e0e6ed;
-          box-shadow: 0 1px 4px rgba(10, 132, 255, 0.25);
+          box-shadow: 0 1px 4px rgb(10 132 255 / 25%);
         }
 
         .panel-tab-icon {
@@ -431,7 +431,7 @@
 
           &:hover {
             opacity: 1;
-            background-color: rgba(255, 77, 79, 0.2);
+            background-color: rgb(255 77 79 / 20%);
           }
         }
       }
@@ -450,11 +450,11 @@
     }
 
     .panel-tab-content {
-      flex: 1;
-      min-height: 0;
       display: flex;
+      flex: 1;
       flex-direction: column;
       overflow: hidden;
+      min-height: 0;
     }
   }
 </style>

+ 44 - 44
src/views/ventAI/manageAssistent/components/chatModal/ScheduleDetailPanel.vue

@@ -233,9 +233,9 @@
 
 <style scoped lang="less">
   .schedule-detail-panel {
-    height: 100%;
     display: flex;
     flex-direction: column;
+    height: 100%;
     overflow: hidden;
     // background-image: var(--img-chat-panel-bg);
     // background-repeat: no-repeat;
@@ -248,7 +248,7 @@
     align-items: center;
     justify-content: space-between;
     padding: 12px 16px;
-    border-bottom: 1px solid rgba(63, 80, 106, 0.25);
+    border-bottom: 1px solid rgb(63 80 106 / 25%);
     flex-shrink: 0;
 
     .schedule-detail-title {
@@ -259,17 +259,17 @@
     }
 
     .schedule-detail-close {
-      width: 28px;
-      height: 28px;
       display: flex;
       align-items: center;
       justify-content: center;
+      width: 28px;
+      height: 28px;
       cursor: pointer;
-      border-radius: 4px;
       transition: background 0.2s;
+      border-radius: 4px;
 
       &:hover {
-        background: rgba(255, 77, 79, 0.15);
+        background: rgb(255 77 79 / 15%);
       }
 
       .close-icon {
@@ -283,22 +283,22 @@
   }
 
   .schedule-detail-body {
-    flex: 1;
-    overflow: hidden;
-    padding: 16px;
     display: flex;
+    flex: 1;
     flex-direction: column;
+    padding: 16px;
+    overflow: hidden;
     gap: 16px;
   }
 
   /* 任务卡片 */
   .detail-task-card {
-    padding: 12px;
-    border-radius: 8px;
-    border: 1px solid rgba(63, 80, 106, 0.15);
-    background: rgba(10, 132, 255, 0.03);
     display: flex;
     flex-direction: column;
+    padding: 12px;
+    border: 1px solid rgb(63 80 106 / 15%);
+    background: rgb(10 132 255 / 3%);
+    border-radius: 8px;
     gap: 10px;
   }
 
@@ -338,7 +338,7 @@
     transition: background-color 0.2s;
 
     &:hover {
-      background-color: rgba(10, 132, 255, 0.15);
+      background-color: rgb(10 132 255 / 15%);
     }
   }
 
@@ -346,21 +346,21 @@
     :deep(.zxm-switch) {
       min-width: 36px;
       height: 20px;
-      background: rgba(63, 80, 106, 0.5);
+      background: rgb(63 80 106 / 50%);
 
       &::after {
-        width: 16px;
-        height: 16px;
         top: 2px;
         left: 2px;
-        border-radius: 50%;
+        width: 16px;
+        height: 16px;
         background: #fff;
-        box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
         transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
+        border-radius: 50%;
+        box-shadow: 0 1px 3px rgb(0 0 0 / 30%);
       }
 
       &.zxm-switch-checked {
-        background: rgba(10, 132, 255, 0.6);
+        background: rgb(10 132 255 / 60%);
 
         &::after {
           left: calc(100% - 18px);
@@ -410,18 +410,18 @@
     }
 
     &::-webkit-scrollbar-thumb {
-      background: rgba(63, 80, 106, 0.4);
+      background: rgb(63 80 106 / 40%);
       border-radius: 2px;
     }
   }
 
   /* 执行历史 */
   .detail-history-section {
-    flex: 1;
     display: flex;
+    flex: 1;
     flex-direction: column;
-    gap: 10px;
     overflow: hidden;
+    gap: 10px;
   }
 
   .detail-history-title {
@@ -445,8 +445,8 @@
       }
 
       .stats-sep {
-        color: #4a5568;
         margin: 0 4px;
+        color: #4a5568;
       }
 
       .stats-success {
@@ -460,8 +460,8 @@
   }
 
   .detail-history-list {
-    flex: 1;
     display: flex;
+    flex: 1;
     flex-direction: column;
     gap: 8px;
     overflow-y: auto;
@@ -471,7 +471,7 @@
     }
 
     &::-webkit-scrollbar-thumb {
-      background: rgba(63, 80, 106, 0.4);
+      background: rgb(63 80 106 / 40%);
       border-radius: 2px;
     }
   }
@@ -484,8 +484,8 @@
 
   .history-item {
     padding: 10px 12px;
-    background: rgba(10, 132, 255, 0.04);
-    border: 1px solid rgba(63, 80, 106, 0.15);
+    border: 1px solid rgb(63 80 106 / 15%);
+    background: rgb(10 132 255 / 4%);
     border-radius: 6px;
   }
 
@@ -512,13 +512,13 @@
   }
 
   .history-output {
-    font-size: 12px;
+    display: -webkit-box;
     color: #c9d1d9;
+    overflow: hidden;
+    font-size: 12px;
     line-height: 1.6;
     max-height: 60px;
-    overflow: hidden;
     text-overflow: ellipsis;
-    display: -webkit-box;
     -webkit-line-clamp: 3;
     -webkit-box-orient: vertical;
   }
@@ -530,10 +530,10 @@
   }
 
   .history-empty {
-    font-size: 12px;
+    padding: 8px 0;
     color: #6a7a8a;
     text-align: center;
-    padding: 8px 0;
+    font-size: 12px;
   }
 </style>
 
@@ -546,12 +546,12 @@
     position: absolute;
     top: 100%;
     right: 0;
-    margin-top: 4px;
+    z-index: 100;
+    padding: 4px;
+    border: 1px solid rgb(63 80 106 / 40%);
     background: linear-gradient(135deg, #0a1628 0%, #0d2137 50%, #0a1e35 100%);
-    border: 1px solid rgba(63, 80, 106, 0.4);
+    margin-top: 4px;
     border-radius: 8px;
-    padding: 4px;
-    z-index: 100;
     min-width: 120px;
   }
 
@@ -563,29 +563,29 @@
     .more-menu-item {
       display: flex;
       align-items: center;
-      gap: 8px;
       padding: 8px 12px;
-      border-radius: 6px;
-      cursor: pointer;
-      font-size: 13px;
       color: #e0e6ed;
+      cursor: pointer;
       transition: background 0.2s;
+      gap: 8px;
+      border-radius: 6px;
+      font-size: 13px;
 
       &:hover {
-        background: rgba(10, 132, 255, 0.15);
+        background: rgb(10 132 255 / 15%);
       }
 
       &.delete {
         color: #ff7875;
 
         &:hover {
-          background: rgba(255, 77, 79, 0.1);
+          background: rgb(255 77 79 / 10%);
         }
       }
 
       &.disabled {
-        opacity: 0.4;
         cursor: not-allowed;
+        opacity: 0.4;
         pointer-events: none;
       }
 

+ 0 - 1
types/global.d.ts

@@ -7,7 +7,6 @@ declare global {
     webRtcUrl: string; // rtsp 服务器地址前缀
     wsUrl: string; // WebSocket 地址
     onlyOfficeUrl?: string; // OnlyOffice Document Server 反向代理前缀
-    aiChatUrl?: string; // [perf-base-v1] 卡7: AI 会话服务反向代理前缀
     difyUrl?: string; // [perf-base-v1] 卡7: Dify 服务反向代理前缀
     difyApiKey?: string; // [perf-base-v1] 卡7: Dify 应用 Key(生产应由后端代理下发)
   };

+ 13 - 17
改动说明-perf-base-v1.md

@@ -1,10 +1,6 @@
 # 改动说明:perf-base-v1(基座稳定性与性能优化轮)
 
-> 日期:2026-09-11 | 分支:`perf-base-v1` | 范围:**仅 mky-vent-base**(基座门户)
-> 基线:origin/fix-subapp-loading(含 fix-subapp-loading 三项修复)+ origin/master `497477be`(测风拓扑/ventAI 弹框等新功能)合并,起点 `cb285f47`
-> 依据:docs/19《基座深度审查与稳定性提速方案》(三代理分域深审:30+ 稳定性缺陷/速度新根因/8 张改进设计卡)
-> 原则:小步修改、不改业务逻辑与子应用通信协议、逐项独立 commit 可单独 revert、修改处带 `[perf-base-v1]` 注释标记
-> 提交总量:31 个(15 批次1 + 10 批次2 + D1/D2 修复 + 文档 + 合并),**未推送前已经 QA 全量验证**,QA 后补交 D1/D2 修复与本文档
+> 日期:2026-09-11 | 分支:`perf-base-v1` | 范围:**仅 mky-vent-base**(基座门户)基线:origin/fix-subapp-loading(含 fix-subapp-loading 三项修复)+ origin/master `497477be`(测风拓扑/ventAI 弹框等新功能)合并,起点 `cb285f47` 依据:docs/19《基座深度审查与稳定性提速方案》(三代理分域深审:30+ 稳定性缺陷/速度新根因/8 张改进设计卡)原则:小步修改、不改业务逻辑与子应用通信协议、逐项独立 commit 可单独 revert、修改处带 `[perf-base-v1]` 注释标记提交总量:31 个(15 批次1 + 10 批次2 + D1/D2 修复 + 文档 + 合并),**未推送前已经 QA 全量验证**,QA 后补交 D1/D2 修复与本文档
 
 ---
 
@@ -19,7 +15,7 @@ docs/19 深审确认基座存在成体系的"挂而不摘"缺陷(20 处定时
 ### 第一组:批次 1 稳定性速赢(15 提交)
 
 | 编号 | commit | 文件 | 改动 | 为什么 |
-|---|---|---|---|---|
+| --- | --- | --- | --- | --- |
 | base-S-T1/L1 | `266d8bfc` | configurable/ 下 20 个分版页面 | `setInterval` 返回值补赋值到 `interval`/`timer` | 原代码裸调用 + `clearInterval(undefined)` 无效——每进一次页面永久新增一条 60s 轮询器("门户挂一天后请求风暴"的主因,20 处同款复制粘贴 bug) |
 | base-S-T2 | `09baba85` | monitorManager/nitrogenMonitor/index.vue | 同上补赋值 + onUnmounted 清理 | 裸 60s 轮询永久泄漏 |
 | base-S-T3 | `2d402cd2` | vent/home/colliery/index.vue | 1s 时钟补 `clockTimer` + 卸载清理 | 每秒 dayjs 格式化永久运行 |
@@ -39,7 +35,7 @@ docs/19 深审确认基座存在成体系的"挂而不摘"缺陷(20 处定时
 ### 第二组:批次 2 安全与性能主线(8 提交)
 
 | 编号 | commit | 文件 | 改动 | 为什么 |
-|---|---|---|---|---|
+| --- | --- | --- | --- | --- |
 | base-卡3 | `6292f7aa` | vite.config.ts | build.modulePreload.resolveDependencies:html 入口剔除 office-preview/markdown-vendor/tinymce-vendor/html2canvas/codemirror/cron-parser 预载 | 这些重型 chunk 对多数页面是纯预载浪费;保留 echarts-vendor(首页图表在用)不误杀 |
 | base-卡2a | `1e683e3c` | rightPositionBar.vue、content-New.vue、blue/content.vue、blue/content-New.vue、blue/content-FireNew.vue、green/content-green.vue | MiniChat 静态 import 改 defineAsyncComponent | 布局头部静态链把 4.46MB office-preview + markdown/echarts vendor 拖进登录后首帧 |
 | base-卡2b | `308b8226` | components/AIChat/MiniChat.vue | vue-office 三组件改 defineAsyncComponent(isShowDoc 时才加载);echarts 改 initChart 内 await import | 文件预览/图表非必现路径,按需加载 |
@@ -54,7 +50,7 @@ docs/19 深审确认基座存在成体系的"挂而不摘"缺陷(20 处定时
 ### 第三组:QA 缺陷修复(1 提交)
 
 | 编号 | commit | 文件 | 改动 | 为什么 |
-|---|---|---|---|---|
+| --- | --- | --- | --- | --- |
 | base-D1 | `7617a39e`(并入) | pnpm-lock.yaml | 重新生成本地 lockfile(plyr 清零) | 删 plyr 后 lockfile 未同步,CI frozen-lockfile 场景会失败(QA 实测发现并提供再生 diff) |
 | base-D2 | `7617a39e`(并入) | configurable/index.vue:90 | 60s 轮询补 `interval =` 赋值 | S-T1 同款模式的最后一处遗漏(QA A3 复核发现) |
 
@@ -62,19 +58,19 @@ docs/19 深审确认基座存在成体系的"挂而不摘"缺陷(20 处定时
 
 ## 验证记录
 
-| 项 | 结果 |
-|---|---|
-| QA 静态核验矩阵 | **9/9 PASS**(24 个治理点逐一到位,证据 docs/audit/验证-基座perf-base-v1-20260911.md) |
-| 语法校验 | 14 个 .ts + 42 个 .vue 全量校验 0 真实错误 |
-| `pnpm build` 生产构建 | **成功**(dist 343MB;office-preview/tinymce 等为独立异步 chunk) |
-| dist 产物 | modulepreload 收敛为 5 个 vendor;6005/8000 硬编码 IP 不再进入运行时代码;`:9050` 仅剩代理表字符串;应用自有 console 清除 |
-| 消毒链路 | 4 处高危 v-html 全部经 DOMPurify 白名单管线 |
-| WS 消费方兼容 | 7 个 useWebSocket 消费方 API 兼容(导出签名未变) |
+| 项                    | 结果                                                                                                                      |
+| --------------------- | ------------------------------------------------------------------------------------------------------------------------- |
+| QA 静态核验矩阵       | **9/9 PASS**(24 个治理点逐一到位,证据 docs/audit/验证-基座perf-base-v1-20260911.md)                                    |
+| 语法校验              | 14 个 .ts + 42 个 .vue 全量校验 0 真实错误                                                                                |
+| `pnpm build` 生产构建 | **成功**(dist 343MB;office-preview/tinymce 等为独立异步 chunk)                                                         |
+| dist 产物             | modulepreload 收敛为 5 个 vendor;6005/8000 硬编码 IP 不再进入运行时代码;`:9050` 仅剩代理表字符串;应用自有 console 清除 |
+| 消毒链路              | 4 处高危 v-html 全部经 DOMPurify 白名单管线                                                                               |
+| WS 消费方兼容         | 7 个 useWebSocket 消费方 API 兼容(导出签名未变)                                                                         |
 
 ### E2E GUI 冒烟(本地 nginx:新基座 dist + 修复版子应用 dist + 生产后端反代)
 
 | 场景 | 结果 |
-|---|---|
+| --- | --- |
 | 登录 → 首页三维场景 | 渲染完整(Cesium 地形/巷道/设备/实时数据),**console 0 错误、9050 请求 0 次** |
 | 首页 → 设备监测页(tunMonitor&windrect&tabtype=monitor,生产上**永久加载**的变体) | **遮罩 19s 正常收起**,页面完整渲染(36 条实时数据),零报错——生产上该页 15 分钟永不完成 |
 | 内存 | 加载期峰值 ~1.2GB(含在途 GLB),稳定后正常 |