Browse Source

修改内容

yangg 3 weeks ago
parent
commit
d66637fe2c

+ 1 - 1
.env.development

@@ -1,3 +1,3 @@
 NODE_ENV=development
 NODE_ENV=development
-VITE_BASE_AI_API=http://192.168.100.187:8082
+VITE_BASE_AI_API=http://58.246.234.210:8062
 VITE_BASE_AI_F_API=http://58.246.234.210:8082
 VITE_BASE_AI_F_API=http://58.246.234.210:8082

+ 2 - 2
.env.production

@@ -1,3 +1,3 @@
 NODE_ENV=production
 NODE_ENV=production
-VITE_BASE_AI_API=http://58.246.234.210:8082 #https://ylaiapi.raycos.com.cn
-VITE_BASE_AI_F_API=http://58.246.234.210:8082 #http://183.195.216.54:8084
+VITE_BASE_AI_API=http://58.246.234.210:8062 #https://ylaiapi.raycos.com.cn
+VITE_BASE_AI_F_API=http://58.246.234.210:8062 #http://183.195.216.54:8084

+ 0 - 1
.gitignore

@@ -8,7 +8,6 @@ pnpm-debug.log*
 lerna-debug.log*
 lerna-debug.log*
 
 
 node_modules
 node_modules
-dist
 dist-ssr
 dist-ssr
 *.local
 *.local
 
 

BIN
dist/assets/avter-DUp3luTf.png


+ 1 - 0
dist/assets/forget-password-D0cXA08j.css

@@ -0,0 +1 @@
+.forget-password-page[data-v-0e9ffc51]{display:flex;justify-content:center;align-items:center;min-height:100vh;background:linear-gradient(135deg,#667eea,#764ba2)}.forget-password-page .forget-password-container[data-v-0e9ffc51]{width:100%;max-width:420px;margin:0 20px}.forget-password-page .forget-password-content[data-v-0e9ffc51]{background:#fffffff2;padding:40px;border-radius:16px;box-shadow:0 8px 32px #1f268726;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);animation:slideUp-0e9ffc51 .6s ease-out}.forget-password-page .forget-password-header[data-v-0e9ffc51]{text-align:center;margin-bottom:32px}.forget-password-page .forget-password-header h2[data-v-0e9ffc51]{font-size:28px;color:#1a1a1a;margin-bottom:8px;font-weight:600}.forget-password-page .forget-password-header .sub-title[data-v-0e9ffc51]{color:#666;font-size:14px}.forget-password-page .verification-code-input[data-v-0e9ffc51]{display:flex;gap:8px}.forget-password-page .verification-code-input .ant-input-affix-wrapper[data-v-0e9ffc51]{flex:1}.forget-password-page .verification-code-input .ant-btn[data-v-0e9ffc51]{min-width:120px}.forget-password-page .form-footer[data-v-0e9ffc51]{text-align:center;margin-top:16px}.forget-password-page .form-footer .back-to-login[data-v-0e9ffc51]{color:#667eea;cursor:pointer}.forget-password-page .form-footer .back-to-login[data-v-0e9ffc51]:hover{color:#764ba2}@keyframes slideUp-0e9ffc51{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}@media (max-width: 480px){.forget-password-content[data-v-0e9ffc51]{padding:24px}.forget-password-header h2[data-v-0e9ffc51]{font-size:24px}}

File diff suppressed because it is too large
+ 0 - 0
dist/assets/forget-password-DDdXz6od.js


File diff suppressed because it is too large
+ 0 - 1
dist/assets/index-CVrWfcbZ.css


File diff suppressed because it is too large
+ 1 - 0
dist/assets/index-gaVcwU-1.js


File diff suppressed because it is too large
+ 0 - 0
dist/assets/login-B0OC3zWY.js


File diff suppressed because it is too large
+ 0 - 0
dist/assets/login-ds9nBxP7.css


BIN
dist/assets/logo-BC4F3nQv.png


+ 1 - 0
dist/assets/register-8YOADkA9.css

@@ -0,0 +1 @@
+.register-page[data-v-b734f605]{display:flex;justify-content:center;align-items:center;min-height:100vh;background:linear-gradient(135deg,#667eea,#764ba2)}.register-page .register-container[data-v-b734f605]{width:100%;max-width:420px;margin:0 20px}.register-page .register-content[data-v-b734f605]{background:#fffffff2;padding:40px;border-radius:16px;box-shadow:0 8px 32px #1f268726;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);animation:slideUp-b734f605 .6s ease-out}.register-page .register-header[data-v-b734f605]{text-align:center;margin-bottom:32px}.register-page .register-header h2[data-v-b734f605]{font-size:28px;color:#1a1a1a;margin-bottom:8px;font-weight:600}.register-page .register-header .sub-title[data-v-b734f605]{color:#666;font-size:14px}.register-page .form-footer[data-v-b734f605]{text-align:center;margin-top:24px}.register-page .form-footer .login-tip[data-v-b734f605]{color:#666;font-size:14px}.register-page .form-footer .login-tip .login-link[data-v-b734f605]{color:#667eea;font-weight:500}.register-page .form-footer .login-tip .login-link[data-v-b734f605]:hover{color:#764ba2}@keyframes slideUp-b734f605{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}@media (max-width: 480px){.register-content[data-v-b734f605]{padding:24px}.register-header h2[data-v-b734f605]{font-size:24px}}

File diff suppressed because it is too large
+ 0 - 0
dist/assets/register-ClhMfzmN.js


File diff suppressed because it is too large
+ 0 - 0
dist/assets/report-B2OphzgW.css


File diff suppressed because it is too large
+ 0 - 0
dist/assets/report-C_vlTzT2.js


File diff suppressed because it is too large
+ 4 - 0
dist/assets/vendor-CsNcCXs6.js


+ 3 - 3
dist/index.html

@@ -5,9 +5,9 @@
     <link rel="icon" type="image/svg+xml" href="/vite.svg" />
     <link rel="icon" type="image/svg+xml" href="/vite.svg" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title></title>
     <title></title>
-    <script type="module" crossorigin src="/assets/index-B6pcXFaV.js"></script>
-    <link rel="modulepreload" crossorigin href="/assets/vendor-CtJFwJv-.js">
-    <link rel="stylesheet" crossorigin href="/assets/index-CphMMiEx.css">
+    <script type="module" crossorigin src="/assets/index-gaVcwU-1.js"></script>
+    <link rel="modulepreload" crossorigin href="/assets/vendor-CsNcCXs6.js">
+    <link rel="stylesheet" crossorigin href="/assets/index-CVrWfcbZ.css">
   </head>
   </head>
   <body>
   <body>
     <div id="app"></div>

     <div id="app"></div>


+ 7 - 2
package.json

@@ -12,6 +12,9 @@
   },
   },
   "dependencies": {
   "dependencies": {
     "@ant-design/icons-vue": "^7.0.1",
     "@ant-design/icons-vue": "^7.0.1",
+    "@vue-office/docx": "^1.6.3",
+    "@vue-office/excel": "^1.7.14",
+    "@vue-office/pdf": "^2.0.10",
     "ant-design-vue": "^4.0.0-rc.6",
     "ant-design-vue": "^4.0.0-rc.6",
     "axios": "^1.7.9",
     "axios": "^1.7.9",
     "dayjs": "^1.11.13",
     "dayjs": "^1.11.13",
@@ -20,10 +23,12 @@
     "html2pdf.js": "^0.10.2",
     "html2pdf.js": "^0.10.2",
     "less": "^4.2.1",
     "less": "^4.2.1",
     "less-loader": "^12.2.0",
     "less-loader": "^12.2.0",
+    "mammoth": "^1.9.1",
     "markdown-it": "^14.1.0",
     "markdown-it": "^14.1.0",
     "vue": "^3.5.13",
     "vue": "^3.5.13",
     "vue-router": "^4.5.0",
     "vue-router": "^4.5.0",
-    "vuex": "^4.0.2"
+    "vuex": "^4.0.2",
+    "xlsx": "^0.18.5"
   },
   },
   "devDependencies": {
   "devDependencies": {
     "@vitejs/plugin-vue": "^5.2.1",
     "@vitejs/plugin-vue": "^5.2.1",
@@ -66,4 +71,4 @@
     "name": "Raycos Tech. Co., Ltd.",
     "name": "Raycos Tech. Co., Ltd.",
     "email": "hongrui@raycos.com.cn"
     "email": "hongrui@raycos.com.cn"
   }
   }
-}
+}

+ 236 - 107
src/components/DocumentPreview/index.vue

@@ -1,59 +1,62 @@
 <template>
 <template>
   <div class="document-preview">
   <div class="document-preview">
-    <!-- 文档列表 -->
-    <div class="document-list">
-      <div v-if="documents.length === 0" class="empty-state">
-        <FileOutlined class="empty-icon" />
-        <p>暂无文档</p>
-      </div>
-      <div v-else v-for="doc in documents" :key="doc.name" class="document-item">
-        <div class="document-info" @click="handleSelect(doc)">
-          <!-- 文档图标 -->
-          <div class="doc-icon">
-            <img 
-              v-if="isImageFile(doc)"
-              :src="doc.url" 
-              class="preview-thumbnail"
-              alt="preview"
-            />
-            <video
-              v-else-if="isVideoFile(doc)"
-              :src="doc.url"
-              class="preview-thumbnail"
-              preload="metadata"
-            />
-            <img 
-              v-else 
-              :src="getFileIcon(doc)" 
-              alt="file"
-              class="file-icon"
-            />
+    <div class="preview-container">
+      <!-- 文档列表 -->
+    <!--   <div class="document-list">
+        <div v-if="documents.length === 0" class="empty-state">
+          <FileOutlined class="empty-icon" />
+          <p>暂无文档</p>
+        </div>
+        <div v-else v-for="doc in documents" :key="doc.name" class="document-item" 
+          :class="{ 'active': currentDoc?.name === doc.name }">
+          <div class="document-info">
+            
+            <div class="doc-icon">
+              <img 
+                v-if="isImageFile(doc)"
+                :src="doc.url" 
+                class="preview-thumbnail"
+                alt="preview"
+              />
+              <video
+                v-else-if="isVideoFile(doc)"
+                :src="doc.url"
+                class="preview-thumbnail"
+                preload="metadata"
+              />
+              <img 
+                v-else 
+                :src="getFileIcon(doc)" 
+                alt="file"
+                class="file-icon"
+              />
+            </div>
+            
+            
+            <div class="info">
+              <div class="name" :title="doc.name">{{ doc.name }}</div>
+              <div class="size">{{ doc.sizeFormatted || formatFileSize(doc.size) }}</div>
+            </div>
           </div>
           </div>
+
           
           
-          <!-- 文档信息 -->
-          <div class="info">
-            <div class="name" :title="doc.name">{{ doc.name }}</div>
-            <div class="size">{{ doc.sizeFormatted || formatFileSize(doc.size) }}</div>
+          <div class="actions">
+            <button class="action-btn" @click.stop="handleDownload(doc)" title="下载">
+              <DownloadOutlined />
+            </button>
           </div>
           </div>
         </div>
         </div>
-
-        <!-- 操作按钮 -->
-        <div class="actions">
-          <button class="action-btn" @click="handlePreview(doc)" title="预览">
-            <EyeOutlined />
-          </button>
-          <button class="action-btn" @click="handleDownload(doc)" title="下载">
-            <DownloadOutlined />
-          </button>
-        </div>
-      </div>
-    </div>
-
-    <!-- 预览弹窗 -->
-    <div v-if="previewVisible" class="preview-modal" @click="closePreview">
-      <div class="preview-content" @click.stop>
+      </div> -->
+      <vue-office-docx
+            src="https://501351981.github.io/vue-office/examples/dist/static/test-files/test.docx"
+            style="height: 100vh;"
+            @rendered="renderedHandler"
+            @error="errorHandler"
+        /> 
+      <!-- 直接预览区域 -->
+      <div v-if="currentDoc" class="direct-preview">
         <div class="preview-header">
         <div class="preview-header">
-          <span>{{ currentDoc?.name }}</span>
+          <span>{{ currentDoc.name }}</span>
           <button class="close-btn" @click="closePreview">
           <button class="close-btn" @click="closePreview">
             <CloseOutlined />
             <CloseOutlined />
           </button>
           </button>
@@ -62,17 +65,43 @@
           <!-- 图片预览 -->
           <!-- 图片预览 -->
           <img 
           <img 
             v-if="isImageFile(currentDoc)"
             v-if="isImageFile(currentDoc)"
-            :src="currentDoc?.url" 
+            :src="currentDoc.url" 
             class="preview-image"
             class="preview-image"
             alt="preview"
             alt="preview"
           />
           />
           <!-- 视频预览 -->
           <!-- 视频预览 -->
           <video
           <video
             v-else-if="isVideoFile(currentDoc)"
             v-else-if="isVideoFile(currentDoc)"
-            :src="currentDoc?.url"
+            :src="currentDoc.url"
             controls
             controls
             class="preview-video"
             class="preview-video"
           />
           />
+          <!-- Word文档预览 -->
+           <div v-else-if="isWordFile(currentDoc)">
+            <vue-office-docx
+              :src="currentDoc.url"
+              class="doc-preview"
+            />      
+           </div>
+          <!-- Excel文档预览 -->
+          <div v-else-if="isExcelFile(currentDoc)">
+            <vue-office-excel
+              :options="excelOptions"
+              @rendered="handleRendered"
+              @error="handleExcelError"
+              :src="currentDoc.url"
+              class="doc-preview"
+            />
+          </div>
+          <!-- PDF预览-->
+           <iframe v-else-if="isPdfFile(currentDoc)" :src="currentDoc.url" style="width: 100%; height: 100%;" frameborder="0"></iframe>
+          <!-- <vue-office-pdf
+            v-else-if="isPdfFile(currentDoc)"
+            @rendered="renderedHandler"
+             @error="errorHandler"
+            :src="currentDoc.url"
+            class="doc-preview"
+          /> -->
           <!-- 其他文件预览 -->
           <!-- 其他文件预览 -->
           <div v-else class="file-preview">
           <div v-else class="file-preview">
             <FileOutlined class="large-icon" />
             <FileOutlined class="large-icon" />
@@ -85,21 +114,29 @@
 </template>
 </template>
 
 
 <script>
 <script>
-import { defineComponent, ref } from 'vue';
+import { defineComponent, ref, watch, onMounted } from 'vue';
 import { 
 import { 
   FileOutlined, 
   FileOutlined, 
-  EyeOutlined, 
   DownloadOutlined,
   DownloadOutlined,
   CloseOutlined
   CloseOutlined
 } from '@ant-design/icons-vue';
 } from '@ant-design/icons-vue';
+/* import '@vue-office/docx/lib/index.css';
+import '@vue-office/excel/lib/index.css';
+import '@vue-office/pdf/lib/index.css'; */
+// 导入 vue-office 组件
+import VueOfficeDocx from '@vue-office/docx';
+import VueOfficeExcel from '@vue-office/excel';
+import VueOfficePdf  from '@vue-office/pdf';
 
 
 export default defineComponent({
 export default defineComponent({
   name: 'DocumentPreview',
   name: 'DocumentPreview',
   components: {
   components: {
     FileOutlined,
     FileOutlined,
-    EyeOutlined,
     DownloadOutlined,
     DownloadOutlined,
-    CloseOutlined
+    CloseOutlined,
+/*     VueOfficeDocx,
+    VueOfficeExcel,
+    VueOfficePdf */
   },
   },
   props: {
   props: {
     documents: {
     documents: {
@@ -109,9 +146,75 @@ export default defineComponent({
   },
   },
   emits: ['select'],
   emits: ['select'],
   setup(props, { emit }) {
   setup(props, { emit }) {
-    const previewVisible = ref(false);
     const currentDoc = ref(null);
     const currentDoc = ref(null);
 
 
+    // 监听文档列表变化,自动预览第一个文档
+    watch(() => props.documents, (newDocs) => {
+      console.log('文档列表变化:', newDocs);
+      if (newDocs && newDocs.length > 0) {
+        currentDoc.value = newDocs[newDocs.length - 1]; // 预览最新上传的文档
+      } else {
+        currentDoc.value = null;
+      }
+    }, { 
+      immediate: true, 
+      deep: true  // 添加深层监听
+    });
+
+    // 额外监听文档列表长度变化
+    watch(() => props.documents.length, (newLength, oldLength) => {
+      console.log('文档数量变化:', newLength, oldLength);
+      if (newLength > oldLength && props.documents.length > 0) {
+        // 当新增文档时,预览最新的文档
+        currentDoc.value = props.documents[props.documents.length - 1];
+      } else if (newLength === 0) {
+        currentDoc.value = null;
+      }
+    });
+// Excel预览配置选项
+const excelOptions = ref({
+  xls: false,       // 预览xlsx文件设为false;预览xls文件设为true
+  minColLength: 0,  // excel最少渲染多少列,设为0表示根据实际内容渲染
+  minRowLength: 0,  // excel最少渲染多少行,设为0表示根据实际内容渲染
+  widthOffset: 10,  // 在默认渲染的列表宽度上再加10px宽
+  heightOffset: 10, // 在默认渲染的列表高度上再加10px高
+  forceRender: true, // 强制渲染,即使文件可能有问题
+  maxSheetLength: 10, // 最多渲染10个工作表
+  beforeTransformData: (workbookData) => {
+    console.log('Excel数据预处理:', workbookData);
+    return workbookData;
+  }, 
+  transformData: (workbookData) => {
+    return workbookData;
+  }
+});
+
+// 处理渲染完成事件
+const handleRendered = () => {
+ 
+  console.log('文件渲染完成');
+};
+
+// 处理渲染错误事件
+const handleError = (error) => {
+
+  console.error('文件渲染失败:', error);
+  ElMessage.error('文件预览失败,请尝试下载查看');
+};
+
+    // 组件挂载时,如果有文档就预览第一个
+    onMounted(() => {
+      console.log('组件挂载,当前文档:', props.documents);
+      if (props.documents && props.documents.length > 0) {
+        currentDoc.value = props.documents[0];
+      }
+    });
+    const renderedHandler = () => {
+            console.log("渲染完成")
+        }
+    const errorHandler = () => {
+            console.log("渲染失败")
+        }
     const isImageFile = (file) => {
     const isImageFile = (file) => {
       if (!file) return false;
       if (!file) return false;
       const imageTypes = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
       const imageTypes = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
@@ -126,6 +229,25 @@ export default defineComponent({
       return videoTypes.includes(ext);
       return videoTypes.includes(ext);
     };
     };
 
 
+    const isWordFile = (file) => {
+      if (!file) return false;
+      const wordTypes = ['doc', 'docx'];
+      const ext = file.name?.split('.')?.pop()?.toLowerCase() || '';
+      return wordTypes.includes(ext);
+    };
+
+    const isExcelFile = (file) => {
+      if (!file) return false;
+      const excelTypes = ['xls', 'xlsx'];
+      const ext = file.name?.split('.')?.pop()?.toLowerCase() || '';
+      return excelTypes.includes(ext);
+    };
+
+    const isPdfFile = (file) => {
+      if (!file) return false;
+      return file.name?.toLowerCase()?.endsWith('.pdf') || false;
+    };
+
     const getFileIcon = (file) => {
     const getFileIcon = (file) => {
       // 根据文件类型返回对应的图标
       // 根据文件类型返回对应的图标
       return '/src/assets/svg/word.svg'; // 默认文档图标
       return '/src/assets/svg/word.svg'; // 默认文档图标
@@ -139,15 +261,6 @@ export default defineComponent({
       return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
       return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
     };
     };
 
 
-    const handleSelect = (doc) => {
-      emit('select', doc);
-    };
-
-    const handlePreview = (doc) => {
-      currentDoc.value = doc;
-      previewVisible.value = true;
-    };
-
     const handleDownload = (doc) => {
     const handleDownload = (doc) => {
       if (doc.url) {
       if (doc.url) {
         const link = document.createElement('a');
         const link = document.createElement('a');
@@ -160,21 +273,22 @@ export default defineComponent({
     };
     };
 
 
     const closePreview = () => {
     const closePreview = () => {
-      previewVisible.value = false;
       currentDoc.value = null;
       currentDoc.value = null;
     };
     };
 
 
     return {
     return {
-      previewVisible,
       currentDoc,
       currentDoc,
       isImageFile,
       isImageFile,
       isVideoFile,
       isVideoFile,
+      isWordFile,
+      isExcelFile,
+      isPdfFile,
       getFileIcon,
       getFileIcon,
       formatFileSize,
       formatFileSize,
-      handleSelect,
-      handlePreview,
       handleDownload,
       handleDownload,
-      closePreview
+      closePreview,
+      renderedHandler,
+      errorHandler
     };
     };
   }
   }
 });
 });
@@ -188,10 +302,27 @@ export default defineComponent({
   flex-direction: column;
   flex-direction: column;
 }
 }
 
 
+.preview-container {
+  display: flex;
+  height: 100%;
+  gap: 16px;
+}
+
 .document-list {
 .document-list {
-  flex: 1;
+  width: 300px;
+  flex-shrink: 0;
   overflow-y: auto;
   overflow-y: auto;
   padding: 16px;
   padding: 16px;
+  border-right: 1px solid var(--border-color);
+}
+
+.direct-preview {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  background: var(--secondary-bg);
+  border-radius: 8px;
 }
 }
 
 
 .empty-state {
 .empty-state {
@@ -200,7 +331,7 @@ export default defineComponent({
   align-items: center;
   align-items: center;
   justify-content: center;
   justify-content: center;
   padding: 48px 0;
   padding: 48px 0;
-  color: rgba(0, 0, 0, 0.45);
+  color: var(--primary-text);
 
 
   .empty-icon {
   .empty-icon {
     font-size: 48px;
     font-size: 48px;
@@ -220,11 +351,13 @@ export default defineComponent({
   padding: 12px;
   padding: 12px;
   border-radius: 8px;
   border-radius: 8px;
   margin-bottom: 8px;
   margin-bottom: 8px;
-  background: #fff;
+  background: var(--secondary-bg);
   transition: all 0.3s;
   transition: all 0.3s;
+  color: var(--primary-color);
 
 
-  &:hover {
-    background: #f5f5f5;
+  &.active {
+    background: var(--primary-bg);
+    border: 1px solid var(--primary-color);
   }
   }
 }
 }
 
 
@@ -233,7 +366,6 @@ export default defineComponent({
   align-items: center;
   align-items: center;
   gap: 12px;
   gap: 12px;
   flex: 1;
   flex: 1;
-  cursor: pointer;
 }
 }
 
 
 .doc-icon {
 .doc-icon {
@@ -262,7 +394,7 @@ export default defineComponent({
 
 
   .name {
   .name {
     font-size: 14px;
     font-size: 14px;
-    color: rgba(0, 0, 0, 0.85);
+    color: var(--primary-text);
     margin-bottom: 4px;
     margin-bottom: 4px;
     white-space: nowrap;
     white-space: nowrap;
     overflow: hidden;
     overflow: hidden;
@@ -271,7 +403,7 @@ export default defineComponent({
 
 
   .size {
   .size {
     font-size: 12px;
     font-size: 12px;
-    color: rgba(0, 0, 0, 0.45);
+    color: var(--primary-text);
   }
   }
 }
 }
 
 
@@ -290,7 +422,7 @@ export default defineComponent({
   display: flex;
   display: flex;
   align-items: center;
   align-items: center;
   justify-content: center;
   justify-content: center;
-  color: rgba(0, 0, 0, 0.45);
+  color: var(--primary-text);
   transition: all 0.3s;
   transition: all 0.3s;
 
 
   &:hover {
   &:hover {
@@ -299,32 +431,9 @@ export default defineComponent({
   }
   }
 }
 }
 
 
-.preview-modal {
-  position: fixed;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  background: rgba(0, 0, 0, 0.5);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  z-index: 1000;
-}
-
-.preview-content {
-  background: #fff;
-  border-radius: 8px;
-  width: 90%;
-  max-width: 800px;
-  max-height: 90vh;
-  display: flex;
-  flex-direction: column;
-}
-
 .preview-header {
 .preview-header {
   padding: 16px;
   padding: 16px;
-  border-bottom: 1px solid #f0f0f0;
+  border-bottom: 1px solid var(--border-color);
   display: flex;
   display: flex;
   justify-content: space-between;
   justify-content: space-between;
   align-items: center;
   align-items: center;
@@ -332,13 +441,14 @@ export default defineComponent({
   span {
   span {
     font-size: 16px;
     font-size: 16px;
     font-weight: 500;
     font-weight: 500;
+    color: var(--primary-text);
   }
   }
 
 
   .close-btn {
   .close-btn {
     border: none;
     border: none;
     background: transparent;
     background: transparent;
     cursor: pointer;
     cursor: pointer;
-    color: rgba(0, 0, 0, 0.45);
+    color: var(--primary-text);
     display: flex;
     display: flex;
     align-items: center;
     align-items: center;
     justify-content: center;
     justify-content: center;
@@ -348,7 +458,7 @@ export default defineComponent({
 
 
     &:hover {
     &:hover {
       background: rgba(0, 0, 0, 0.04);
       background: rgba(0, 0, 0, 0.04);
-      color: rgba(0, 0, 0, 0.88);
+      color: var(--primary-color);
     }
     }
   }
   }
 }
 }
@@ -358,7 +468,7 @@ export default defineComponent({
   overflow: auto;
   overflow: auto;
   padding: 24px;
   padding: 24px;
   display: flex;
   display: flex;
-  align-items: center;
+  align-items: flex-start;
   justify-content: center;
   justify-content: center;
 }
 }
 
 
@@ -373,9 +483,15 @@ export default defineComponent({
   max-height: 70vh;
   max-height: 70vh;
 }
 }
 
 
+.doc-preview {
+  width: 100%;
+  height: 100%;
+  min-height: 500px;
+}
+
 .file-preview {
 .file-preview {
   text-align: center;
   text-align: center;
-  color: rgba(0, 0, 0, 0.45);
+  color: var(--primary-text);
 
 
   .large-icon {
   .large-icon {
     font-size: 64px;
     font-size: 64px;
@@ -387,4 +503,17 @@ export default defineComponent({
     font-size: 14px;
     font-size: 14px;
   }
   }
 }
 }
+
+// 响应式布局
+@media screen and (max-width: 768px) {
+  .preview-container {
+    flex-direction: column;
+  }
+
+  .document-list {
+    width: 100%;
+    border-right: none;
+    border-bottom: 1px solid var(--border-color);
+  }
+}
 </style> 
 </style> 

+ 490 - 23
src/views/report.vue

@@ -599,10 +599,102 @@
           <div class="preview-header">
           <div class="preview-header">
             <span>文档预览</span>
             <span>文档预览</span>
           </div>
           </div>
-          <DocumentPreview 
-            :documents="attachedFiles"
-            @select="handleDocumentSelect"
-          />
+          <div class="document-preview">
+            <div class="preview-container">
+              <!-- 文档列表 -->
+              <!-- <div class="document-list">
+                <div v-if="attachedFiles.length === 0" class="empty-state">
+                  <FileOutlined class="empty-icon" />
+                  <p>暂无文档</p>
+                </div>
+                <div v-else v-for="doc in attachedFiles" :key="doc.name" class="document-item" 
+                  :class="{ 'active': currentDoc?.name === doc.name }"
+                  @click="handleDocClick(doc)">
+                  <div class="document-info">
+                    <div class="doc-icon">
+                      <img 
+                        v-if="isImageFile(doc)"
+                        :src="doc.url" 
+                        class="preview-thumbnail"
+                        alt="preview"
+                      />
+                      <video
+                        v-else-if="isVideoFile(doc)"
+                        :src="doc.url"
+                        class="preview-thumbnail"
+                        preload="metadata"
+                      />
+                      <img 
+                        v-else 
+                        :src="getFileIcon(doc)" 
+                        alt="file"
+                        class="file-icon"
+                      />
+                    </div>
+                    <div class="info">
+                      <div class="name" :title="doc.name">{{ doc.name }}</div>
+                      <div class="size">{{ doc.sizeFormatted || formatFileSize(doc.size) }}</div>
+                    </div>
+                  </div>
+                  <div class="actions">
+                    <button class="action-btn" @click.stop="handleDownload(doc)" title="下载">
+                      <DownloadOutlined />
+                    </button>
+                  </div>
+                </div>
+              </div> -->
+
+              <!-- 直接预览区域 -->
+              <div v-if="currentDoc" class="direct-preview">
+                <div class="preview-header">
+                  <span>{{ currentDoc.name }}</span>
+                  <button class="close-btn" @click="closePreview">
+                    <CloseOutlined />
+                  </button>
+                </div>
+                <div class="preview-body">
+                  <!-- 图片预览 -->
+                  <img 
+                    v-if="isImageFile(currentDoc)"
+                    :src="currentDoc.url" 
+                    class="preview-image"
+                    alt="preview"
+                  />
+                  <!-- 视频预览 -->
+                  <video
+                    v-else-if="isVideoFile(currentDoc)"
+                    :src="currentDoc.url"
+                    controls
+                    class="preview-video"
+                  />
+                  <!-- Word文档预览 -->
+                  <div  v-else-if="isWordFile(currentDoc)">
+                    <vue-office-docx
+                      :src="currentDoc.url"
+                      class="doc-preview"
+                    />      
+                  </div>
+                  <!-- Excel文档预览 -->
+                  <div v-else-if="isExcelFile(currentDoc)">
+                    <vue-office-excel
+                      :options="excelOptions"
+                      @rendered="handleRendered"
+                      @error="handleExcelError"
+                      :src="currentDoc.url"
+                      class="doc-preview"
+                    />
+                  </div>
+                  <!-- PDF预览-->
+                  <iframe v-else-if="isPdfFile(currentDoc)" :src="currentDoc.url" style="width: 100%; height: 100%;" frameborder="0"></iframe>
+                  <!-- 其他文件预览 -->
+                  <div v-else class="file-preview">
+                    <FileOutlined class="large-icon" />
+                    <p>该文件类型暂不支持预览</p>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
         </div>
         </div>
 
 
         <!-- 添加知识库结果面板 -->
         <!-- 添加知识库结果面板 -->
@@ -645,6 +737,7 @@ import {
   DatabaseOutlined,
   DatabaseOutlined,
   FolderOutlined,
   FolderOutlined,
   SelectOutlined,
   SelectOutlined,
+  DownloadOutlined,
 } from "@ant-design/icons-vue";
 } from "@ant-design/icons-vue";
 import leftIcon from "../assets/svg/left.svg";
 import leftIcon from "../assets/svg/left.svg";
 import rightIcon from "../assets/svg/right.svg";
 import rightIcon from "../assets/svg/right.svg";
@@ -672,7 +765,8 @@ import KnowledgeResults from "../components/KnowledgeResults/index.vue";
 import ChangePasswordDialog from '../components/ChangePasswordDialog.vue'
 import ChangePasswordDialog from '../components/ChangePasswordDialog.vue'
 import { useRouter } from 'vue-router'
 import { useRouter } from 'vue-router'
 import { message } from 'ant-design-vue';
 import { message } from 'ant-design-vue';
-
+import  VueOfficeDocx  from '@vue-office/docx';
+import VueOfficeExcel from '@vue-office/excel'
 // 初始化 markdown-it
 // 初始化 markdown-it
 const md = new MarkdownIt({
 const md = new MarkdownIt({
   html: true,
   html: true,
@@ -1020,9 +1114,14 @@ const handleFileUpload = async (event) => {
     
     
     // 修改文件上传后的处理逻辑
     // 修改文件上传后的处理逻辑
     if (attachedFiles.value.length > 0) {
     if (attachedFiles.value.length > 0) {
-      // 切换到预览标签页,不自动展开右侧菜单
+      // 切换到预览标签页
       activeTab.value = 'preview';
       activeTab.value = 'preview';
-      /* expandRightMenu(); */
+      // 展开右侧菜单
+      isRightMenuCollapsed.value = false;
+      // 如果在移动端,调整布局
+      if (document.querySelector('.chat')) {
+        document.querySelector('.chat').style.right = `${rightMenuWidth.value}px`;
+      }
     }
     }
   }
   }
 
 
@@ -1382,9 +1481,14 @@ const handleFileDrop = async (event) => {
     
     
     // 修改文件上传后的处理逻辑
     // 修改文件上传后的处理逻辑
     if (attachedFiles.value.length > 0) {
     if (attachedFiles.value.length > 0) {
-      // 切换到预览标签页,不自动展开右侧菜单
+      // 切换到预览标签页
       activeTab.value = 'preview';
       activeTab.value = 'preview';
-      /* expandRightMenu(); */
+      // 展开右侧菜单
+      isRightMenuCollapsed.value = false;
+      // 如果在移动端,调整布局
+      if (document.querySelector('.chat')) {
+        document.querySelector('.chat').style.right = `${rightMenuWidth.value}px`;
+      }
     }
     }
   }
   }
 
 
@@ -1980,6 +2084,90 @@ const updateCollapsedState = () => {
     // ... existing return values ...
     // ... existing return values ...
   }; */
   }; */
 
 
+  const excelOptions = ref({
+    xls: false,       // 预览xlsx文件设为false;预览xls文件设为true
+    minColLength: 15,  // excel最少渲染列数
+    minRowLength: 50,  // excel最少渲染行数,增加以确保显示更多内容
+    widthOffset: 50,  // 增加列宽偏移量,使内容显示更完整
+    heightOffset: 30, // 增加行高偏移量
+    forceRender: true, // 强制渲染
+    maxSheetLength: 10, // 最多渲染工作表数量
+    showGridLines: true, // 显示网格线
+    defaultFontSize: 12, // 默认字体大小
+    defaultRowHeight: 24, // 默认行高
+    autoFitColumn: true, // 自动适应列宽
+    overflowColumns: true, // 允许列内容溢出
+    showToolbar: true, // 显示工具栏
+  });
+
+  // 添加Excel渲染完成的处理函数
+  const handleRendered = () => {
+    console.log('Excel文件渲染完成');
+    nextTick(() => {
+      // 手动触发一次重新计算大小
+      window.dispatchEvent(new Event('resize'));
+    });
+  };
+
+  // 添加Excel错误处理函数的详细信息
+  const handleExcelError = (error) => {
+    console.error('Excel文件渲染失败:', error);
+    message.error('Excel预览失败:' + (error.message || '请尝试下载后查看'));
+  };
+
+  const isWordFile = (file) => {
+    if (!file) return false;
+    const wordTypes = ['doc', 'docx'];
+    const ext = file.name?.split('.')?.pop()?.toLowerCase() || '';
+    return wordTypes.includes(ext);
+  };
+
+  const isExcelFile = (file) => {
+    if (!file) return false;
+    const excelTypes = ['xls', 'xlsx'];
+    const ext = file.name?.split('.')?.pop()?.toLowerCase() || '';
+    return excelTypes.includes(ext);
+  };
+
+  const isPdfFile = (file) => {
+    if (!file) return false;
+    return file.name?.toLowerCase()?.endsWith('.pdf') || false;
+  };
+
+  const currentDoc = ref(null);
+
+  // 监听文档列表变化
+  watch(() => attachedFiles.value, (newFiles) => {
+    if (newFiles && newFiles.length > 0) {
+      currentDoc.value = newFiles[newFiles.length - 1]; // 预览最新上传的文档
+    } else {
+      currentDoc.value = null;
+    }
+  }, { 
+    immediate: true, 
+    deep: true
+  });
+
+  const handleDownload = (doc) => {
+    if (doc.url) {
+      const link = document.createElement('a');
+      link.href = doc.url;
+      link.download = doc.name;
+      document.body.appendChild(link);
+      link.click();
+      document.body.removeChild(link);
+    }
+  };
+
+  const closePreview = () => {
+    currentDoc.value = null;
+  };
+
+  // 添加文档点击事件处理
+  const handleDocClick = (doc) => {
+    currentDoc.value = doc;
+  };
+
  </script>
  </script>
 
 
 <style scoped>
 <style scoped>
@@ -2329,6 +2517,7 @@ const updateCollapsedState = () => {
   border: 1px solid var(--border-color);
   border: 1px solid var(--border-color);
   border-radius: 8px;
   border-radius: 8px;
   background: var(--primary-bg);
   background: var(--primary-bg);
+  color: var(--primary-text);
 }
 }
 
 
 .message-input {
 .message-input {
@@ -2338,6 +2527,7 @@ const updateCollapsedState = () => {
   padding: 8px;
   padding: 8px;
   font-size: 14px;
   font-size: 14px;
   background: transparent;
   background: transparent;
+  color: var(--primary-text);
 }
 }
 
 
 .message-input::placeholder {
 .message-input::placeholder {
@@ -2361,18 +2551,18 @@ const updateCollapsedState = () => {
 .attachment-btn:hover,
 .attachment-btn:hover,
 .send-btn:hover {
 .send-btn:hover {
   background: rgba(0, 0, 0, 0.04);
   background: rgba(0, 0, 0, 0.04);
-  color: rgba(0, 0, 0, 0.88);
+  color: var(--primary-text);
 }
 }
 
 
 .attachments-panel {
 .attachments-panel {
-  background: white;
+  background: var(--primary-bg);
   border-radius: 12px;
   border-radius: 12px;
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
   overflow: hidden;
   overflow: hidden;
 }
 }
 
 
 .panel-content {
 .panel-content {
-  background: white;
+  background: var(--primary-bg);
 }
 }
 
 
 .attachments-header {
 .attachments-header {
@@ -2386,7 +2576,7 @@ const updateCollapsedState = () => {
 .attachments-header span {
 .attachments-header span {
   font-size: 14px;
   font-size: 14px;
   font-weight: 500;
   font-weight: 500;
-  color: rgba(0, 0, 0, 0.88);
+  color: var(--primary-text);
 }
 }
 
 
 .close-btn {
 .close-btn {
@@ -2405,12 +2595,13 @@ const updateCollapsedState = () => {
   border-radius: 8px;
   border-radius: 8px;
   text-align: center;
   text-align: center;
   cursor: pointer;
   cursor: pointer;
+  color: var(--primary-text);
   transition: all 0.3s;
   transition: all 0.3s;
 }
 }
 
 
 .upload-area.has-files {
 .upload-area.has-files {
   padding: 12px;
   padding: 12px;
-  background: #fff;
+  background: var(--primary-bg);
 }
 }
 
 
 .upload-icon {
 .upload-icon {
@@ -2421,13 +2612,13 @@ const updateCollapsedState = () => {
 
 
 .upload-text {
 .upload-text {
   font-size: 14px;
   font-size: 14px;
-  color: rgba(0, 0, 0, 0.88);
+  color: var(--primary-text);
   margin-bottom: 4px;
   margin-bottom: 4px;
 }
 }
 
 
 .upload-hint {
 .upload-hint {
   font-size: 12px;
   font-size: 12px;
-  color: rgba(0, 0, 0, 0.45);
+  color: var(--primary-text);
 }
 }
 
 
 .file-list {
 .file-list {
@@ -2442,7 +2633,7 @@ const updateCollapsedState = () => {
   width: 260px;
   width: 260px;
   padding: 8px 12px;
   padding: 8px 12px;
   margin: 4px 0;
   margin: 4px 0;
-  background: #f5f5f5;
+  background: var(--secondary-bg);
   border-radius: 8px;
   border-radius: 8px;
 }
 }
 
 
@@ -2500,7 +2691,7 @@ const updateCollapsedState = () => {
 .file-name {
 .file-name {
   display: block;
   display: block;
   max-width: 150px;
   max-width: 150px;
-  color: rgba(0, 0, 0, 0.88);
+  color: var(--primary-text);
   font-size: 14px;
   font-size: 14px;
   white-space: nowrap;
   white-space: nowrap;
   overflow: hidden;
   overflow: hidden;
@@ -2509,6 +2700,7 @@ const updateCollapsedState = () => {
 
 
 .file-info .file-name {
 .file-info .file-name {
   max-width: 150px;
   max-width: 150px;
+  color: var(--primary-text);
 }
 }
 
 
 .file-name:hover {
 .file-name:hover {
@@ -2517,7 +2709,7 @@ const updateCollapsedState = () => {
 
 
 .file-size {
 .file-size {
   display: block;
   display: block;
-  color: rgba(0, 0, 0, 0.45);
+  color: var(--primary-text);
   font-size: 12px;
   font-size: 12px;
   margin-top: 4px;
   margin-top: 4px;
 }
 }
@@ -2697,7 +2889,7 @@ const updateCollapsedState = () => {
 }
 }
 
 
 .file-item:hover {
 .file-item:hover {
-  background: #f5f5f5;
+  background: var(--secondary-bg);
 }
 }
 
 
 .remove-btn {
 .remove-btn {
@@ -2706,11 +2898,11 @@ const updateCollapsedState = () => {
   border: none;
   border: none;
   background: transparent;
   background: transparent;
   cursor: pointer;
   cursor: pointer;
-  color: rgba(0, 0, 0, 0.45);
+  color: var(--primary-text);
 }
 }
 
 
 .remove-btn:hover {
 .remove-btn:hover {
-  color: rgba(0, 0, 0, 0.88);
+  color: var(--primary-text);
 }
 }
 
 
 .send-btn:disabled {
 .send-btn:disabled {
@@ -2739,7 +2931,8 @@ const updateCollapsedState = () => {
   height: 68px;
   height: 68px;
   padding: 0;
   padding: 0;
   margin: 4px 0;
   margin: 4px 0;
-  background: #f5f5f5;
+  background: var(--secondary-bg);
+  color: var(--primary-text);
   border-radius: 4px;
   border-radius: 4px;
 }
 }
 
 
@@ -4603,4 +4796,278 @@ button,
 :root.dark-theme {
 :root.dark-theme {
   --icon-filter: brightness(0) invert(1);
   --icon-filter: brightness(0) invert(1);
 }
 }
+</style>
+
+<style lang="less" scoped>
+// ... existing code ...
+
+/* 文档预览样式 */
+.document-preview {
+  height: 100%;
+  overflow: hidden;
+  display: flex;
+  flex-direction: column;
+}
+
+.preview-container {
+  display: flex;
+  height: 100%;
+  gap: 16px;
+}
+
+.document-list {
+  width: 300px;
+  flex-shrink: 0;
+  overflow-y: auto;
+  padding: 16px;
+  border-right: 1px solid var(--border-color);
+}
+
+.empty-state {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 48px 0;
+  color: var(--primary-text);
+
+  .empty-icon {
+    font-size: 48px;
+    margin-bottom: 16px;
+  }
+
+  p {
+    font-size: 14px;
+    margin: 0;
+  }
+}
+
+.document-item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 12px;
+  border-radius: 8px;
+  margin-bottom: 8px;
+  background: var(--secondary-bg);
+  transition: all 0.3s;
+  cursor: pointer;
+
+  &:hover {
+    background: var(--hover-bg);
+  }
+
+  &.active {
+    background: var(--primary-bg);
+    border: 1px solid var(--primary-color);
+  }
+}
+
+.document-info {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  flex: 1;
+}
+
+.doc-icon {
+  width: 40px;
+  height: 40px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.preview-thumbnail {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+  border-radius: 4px;
+}
+
+/* .file-icon {
+  width: 24px;
+  height: 24px;
+} */
+
+.info {
+  flex: 1;
+  min-width: 0;
+
+  .name {
+    font-size: 14px;
+    color: var(--primary-text);
+    margin-bottom: 4px;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+
+  .size {
+    font-size: 12px;
+    color: var(--secondary-text);
+  }
+}
+
+.actions {
+  display: flex;
+  gap: 8px;
+}
+
+.action-btn {
+  width: 32px;
+  height: 32px;
+  border: none;
+  background: transparent;
+  border-radius: 4px;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: var(--primary-text);
+  transition: all 0.3s;
+
+  &:hover {
+    background: rgba(0, 0, 0, 0.04);
+    color: var(--primary-color);
+  }
+}
+
+.direct-preview {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  background: var(--secondary-bg);
+  border-radius: 8px;
+}
+
+.preview-header {
+  padding: 16px;
+  border-bottom: 1px solid var(--border-color);
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+
+  span {
+    font-size: 16px;
+    font-weight: 500;
+    color: var(--primary-text);
+  }
+
+  .close-btn {
+    border: none;
+    background: transparent;
+    cursor: pointer;
+    color: var(--primary-text);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 8px;
+    border-radius: 4px;
+    transition: all 0.3s;
+
+    &:hover {
+      background: rgba(0, 0, 0, 0.04);
+      color: var(--primary-color);
+    }
+  }
+}
+
+.preview-body {
+  flex: 1;
+  overflow: auto;
+  padding: 24px;
+  display: flex;
+  align-items: flex-start;
+  justify-content: center;
+}
+
+.preview-image {
+  max-width: 100%;
+  max-height: 70vh;
+  object-fit: contain;
+}
+
+.preview-video {
+  max-width: 100%;
+  max-height: 70vh;
+}
+
+.doc-preview {
+  width: 100%;
+  height: 100%;
+  min-height: 500px;
+}
+
+:deep(.docx-wrapper) {
+  width: 100%;
+  height: 100%;
+  .docx{
+    width: 100% !important;
+  }
+}
+
+:deep(.excel-wrapper) {
+  width: 100%;
+  height: 100%;
+  overflow: auto;
+  
+  .excel-container {
+    min-width: 100%;
+    min-height: 100%;
+  }
+
+  .excel-toolbar {
+    position: sticky;
+    top: 0;
+    z-index: 1;
+    background: #fff;
+    padding: 8px;
+    border-bottom: 1px solid #e8e8e8;
+  }
+
+  table {
+    width: 100%;
+    border-collapse: collapse;
+    
+    th, td {
+      padding: 8px;
+      border: 1px solid #e8e8e8;
+      white-space: nowrap;
+      overflow: hidden;
+      text-overflow: ellipsis;
+    }
+  }
+}
+
+.file-preview {
+  text-align: center;
+  color: var(--primary-text);
+
+  .large-icon {
+    font-size: 64px;
+    margin-bottom: 16px;
+  }
+
+  p {
+    margin: 0;
+    font-size: 14px;
+  }
+}
+
+// 响应式布局
+@media screen and (max-width: 768px) {
+  .preview-container {
+    flex-direction: column;
+  }
+
+  .document-list {
+    width: 100%;
+    border-right: none;
+    border-bottom: 1px solid var(--border-color);
+  }
+}
+
+// ... existing code ...
 </style>
 </style>

Some files were not shown because too many files changed in this diff