yangg 1 өдөр өмнө
parent
commit
c05c182b77

+ 7 - 0
src/router/menu.js

@@ -57,5 +57,12 @@ export const ROUTE = [
     name:'demo',
      hidden: true,
     component: () => import('@views/demo.vue')
+  },
+  {
+    path: '/document-preview',
+    name: 'document-preview',
+    title: '文档预览',
+    hidden: true,
+    component: () => import('@views/document-preview.vue')
   }
 ]

+ 533 - 0
src/views/document-preview.vue

@@ -0,0 +1,533 @@
+<template>
+  <div class="document-preview-page">
+    <div class="preview-header">
+      <div class="header-left">
+        <button class="back-btn" @click="goBack">
+          <LeftOutlined />
+          返回
+        </button>
+        <div class="document-info">
+          <h2>{{ documentTitle }}</h2>
+          <span class="document-url">{{ documentUrl }}</span>
+        </div>
+      </div>
+      <div class="header-right">
+        <button class="action-btn" @click="downloadDocument" title="下载文档">
+          <DownloadOutlined />
+          下载
+        </button>
+        <button class="action-btn" @click="refreshPreview" title="刷新预览">
+          <ReloadOutlined />
+          刷新
+        </button>
+      </div>
+    </div>
+    
+    <div class="preview-container">
+      <div v-if="loading" class="loading-container">
+        <a-spin size="large">
+          <template #indicator>
+            <LoadingOutlined style="font-size: 24px" spin />
+          </template>
+        </a-spin>
+        <p>正在加载文档预览...</p>
+      </div>
+      
+      <div v-else-if="error" class="error-container">
+        <ExclamationCircleOutlined class="error-icon" />
+        <h3>预览失败</h3>
+        <p>{{ error }}</p>
+        <div class="error-actions">
+          <a-button type="primary" @click="refreshPreview">重试</a-button>
+          <a-button @click="downloadDocument">下载文档</a-button>
+        </div>
+      </div>
+      
+      <div v-else class="preview-content">
+        <!-- Word文档预览 -->
+        <div v-if="isWordFile" class="word-preview">
+          <vue-office-docx
+            :src="documentUrl"
+            style="height: 100%;"
+            @rendered="handleRendered"
+            @error="handleError"
+          />
+        </div>
+        
+        <!-- Excel文档预览 -->
+        <div v-else-if="isExcelFile" class="excel-preview">
+          <vue-office-excel
+            :src="documentUrl"
+            :options="excelOptions"
+            style="height: 100%;"
+            @rendered="handleRendered"
+            @error="handleError"
+          />
+        </div>
+        
+        <!-- PDF预览 -->
+        <div v-else-if="isPdfFile" class="pdf-preview">
+          <iframe 
+            :src="documentUrl" 
+            style="width: 100%; height: 100%; border: none;"
+            frameborder="0"
+            @load="handleIframeLoad"
+            @error="handleIframeError"
+          />
+        </div>
+        
+        <!-- 图片预览 -->
+        <div v-else-if="isImageFile" class="image-preview">
+          <img 
+            :src="documentUrl" 
+            class="preview-image"
+            alt="文档预览"
+            @load="handleImageLoad"
+            @error="handleImageError"
+          />
+        </div>
+        
+        <!-- 视频预览 -->
+        <div v-else-if="isVideoFile" class="video-preview">
+          <video
+            :src="documentUrl"
+            controls
+            class="preview-video"
+            @loadeddata="handleVideoLoad"
+            @error="handleVideoError"
+          />
+        </div>
+        
+        <!-- 其他文件类型 -->
+        <div v-else class="unsupported-preview">
+          <FileOutlined class="file-icon" />
+          <h3>不支持预览</h3>
+          <p>该文件类型暂不支持在线预览,请下载后查看</p>
+          <a-button type="primary" @click="downloadDocument">下载文档</a-button>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { defineComponent, ref, computed, onMounted } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import { 
+  LeftOutlined, 
+  DownloadOutlined, 
+  ReloadOutlined,
+  LoadingOutlined,
+  ExclamationCircleOutlined,
+  FileOutlined
+} from '@ant-design/icons-vue';
+import { message } from 'ant-design-vue';
+import VueOfficeDocx from '@vue-office/docx';
+import VueOfficeExcel from '@vue-office/excel';
+
+export default defineComponent({
+  name: 'DocumentPreviewPage',
+  components: {
+    LeftOutlined,
+    DownloadOutlined,
+    ReloadOutlined,
+    LoadingOutlined,
+    ExclamationCircleOutlined,
+    FileOutlined,
+    VueOfficeDocx,
+    VueOfficeExcel
+  },
+  setup() {
+    const route = useRoute();
+    const router = useRouter();
+    
+    const loading = ref(true);
+    const error = ref('');
+    const documentUrl = ref('');
+    const documentTitle = ref('');
+    
+    // Excel预览配置
+    const excelOptions = ref({
+      xls: false,
+      minColLength: 0,
+      minRowLength: 0,
+      widthOffset: 10,
+      heightOffset: 10,
+      forceRender: true,
+      maxSheetLength: 10
+    });
+    
+    // 文件类型判断
+    const isWordFile = computed(() => {
+      return documentUrl.value.toLowerCase().includes('.docx') || 
+             documentUrl.value.toLowerCase().includes('.doc');
+    });
+    
+    const isExcelFile = computed(() => {
+      return documentUrl.value.toLowerCase().includes('.xlsx') || 
+             documentUrl.value.toLowerCase().includes('.xls');
+    });
+    
+    const isPdfFile = computed(() => {
+      return documentUrl.value.toLowerCase().includes('.pdf');
+    });
+    
+    const isImageFile = computed(() => {
+      const imageTypes = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp'];
+      return imageTypes.some(type => documentUrl.value.toLowerCase().includes(type));
+    });
+    
+    const isVideoFile = computed(() => {
+      const videoTypes = ['.mp4', '.webm', '.ogg', '.avi', '.mov'];
+      return videoTypes.some(type => documentUrl.value.toLowerCase().includes(type));
+    });
+    
+    // 初始化
+    onMounted(() => {
+      initDocument();
+    });
+    
+    const initDocument = () => {
+      const url = route.query.url;
+      const title = route.query.title || '文档预览';
+      
+      if (!url) {
+        error.value = '缺少文档链接参数';
+        loading.value = false;
+        return;
+      }
+      
+      documentUrl.value = decodeURIComponent(url);
+      documentTitle.value = decodeURIComponent(title);
+      
+      // 模拟加载时间
+      setTimeout(() => {
+        loading.value = false;
+      }, 1000);
+    };
+    
+    // 事件处理
+    const handleRendered = () => {
+      console.log('文档渲染完成');
+      loading.value = false;
+    };
+    
+    const handleError = (err) => {
+      console.error('文档渲染失败:', err);
+      error.value = '文档渲染失败,请检查文档格式或网络连接';
+      loading.value = false;
+    };
+    
+    const handleIframeLoad = () => {
+      console.log('iframe加载完成');
+      loading.value = false;
+    };
+    
+    const handleIframeError = () => {
+      error.value = '文档加载失败,请检查文档链接是否有效';
+      loading.value = false;
+    };
+    
+    const handleImageLoad = () => {
+      console.log('图片加载完成');
+      loading.value = false;
+    };
+    
+    const handleImageError = () => {
+      error.value = '图片加载失败,请检查图片链接是否有效';
+      loading.value = false;
+    };
+    
+    const handleVideoLoad = () => {
+      console.log('视频加载完成');
+      loading.value = false;
+    };
+    
+    const handleVideoError = () => {
+      error.value = '视频加载失败,请检查视频链接是否有效';
+      loading.value = false;
+    };
+    
+    // 操作函数
+    const goBack = () => {
+      router.go(-1);
+    };
+    
+    const downloadDocument = () => {
+      if (documentUrl.value) {
+        const link = document.createElement('a');
+        link.href = documentUrl.value;
+        link.download = documentTitle.value;
+        link.target = '_blank';
+        document.body.appendChild(link);
+        link.click();
+        document.body.removeChild(link);
+        message.success('开始下载文档');
+      }
+    };
+    
+    const refreshPreview = () => {
+      loading.value = true;
+      error.value = '';
+      // 重新加载页面
+      window.location.reload();
+    };
+    
+    return {
+      loading,
+      error,
+      documentUrl,
+      documentTitle,
+      excelOptions,
+      isWordFile,
+      isExcelFile,
+      isPdfFile,
+      isImageFile,
+      isVideoFile,
+      handleRendered,
+      handleError,
+      handleIframeLoad,
+      handleIframeError,
+      handleImageLoad,
+      handleImageError,
+      handleVideoLoad,
+      handleVideoError,
+      goBack,
+      downloadDocument,
+      refreshPreview
+    };
+  }
+});
+</script>
+
+<style lang="less" scoped>
+.document-preview-page {
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+  background: #f5f5f5;
+}
+
+.preview-header {
+  background: white;
+  padding: 16px 24px;
+  border-bottom: 1px solid #e8e8e8;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  z-index: 10;
+}
+
+.header-left {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+}
+
+.back-btn {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 8px 16px;
+  border: 1px solid #d9d9d9;
+  background: white;
+  border-radius: 6px;
+  cursor: pointer;
+  transition: all 0.3s;
+  
+  &:hover {
+    border-color: #1677ff;
+    color: #1677ff;
+  }
+}
+
+.document-info {
+  h2 {
+    margin: 0;
+    font-size: 18px;
+    font-weight: 500;
+    color: #262626;
+    max-width: 400px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+  
+  .document-url {
+    font-size: 12px;
+    color: #8c8c8c;
+    max-width: 500px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    display: block;
+    margin-top: 4px;
+  }
+}
+
+.header-right {
+  display: flex;
+  gap: 12px;
+}
+
+.action-btn {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  padding: 8px 16px;
+  border: 1px solid #d9d9d9;
+  background: white;
+  border-radius: 6px;
+  cursor: pointer;
+  transition: all 0.3s;
+  font-size: 14px;
+  
+  &:hover {
+    border-color: #1677ff;
+    color: #1677ff;
+  }
+}
+
+.preview-container {
+  flex: 1;
+  overflow: hidden;
+  position: relative;
+}
+
+.loading-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 100%;
+  gap: 16px;
+  
+  p {
+    margin: 0;
+    color: #8c8c8c;
+    font-size: 14px;
+  }
+}
+
+.error-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 100%;
+  gap: 16px;
+  text-align: center;
+  
+  .error-icon {
+    font-size: 48px;
+    color: #ff4d4f;
+  }
+  
+  h3 {
+    margin: 0;
+    color: #262626;
+    font-size: 18px;
+  }
+  
+  p {
+    margin: 0;
+    color: #8c8c8c;
+    font-size: 14px;
+  }
+  
+  .error-actions {
+    display: flex;
+    gap: 12px;
+    margin-top: 16px;
+  }
+}
+
+.preview-content {
+  height: 100%;
+  overflow: auto;
+  background: white;
+}
+
+.word-preview,
+.excel-preview,
+.pdf-preview,
+.image-preview,
+.video-preview {
+  height: 100%;
+  width: 100%;
+}
+
+.preview-image {
+  max-width: 100%;
+  max-height: 100%;
+  object-fit: contain;
+  display: block;
+  margin: 0 auto;
+}
+
+.preview-video {
+  width: 100%;
+  height: 100%;
+  object-fit: contain;
+}
+
+.unsupported-preview {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 100%;
+  gap: 16px;
+  text-align: center;
+  
+  .file-icon {
+    font-size: 64px;
+    color: #8c8c8c;
+  }
+  
+  h3 {
+    margin: 0;
+    color: #262626;
+    font-size: 18px;
+  }
+  
+  p {
+    margin: 0;
+    color: #8c8c8c;
+    font-size: 14px;
+  }
+}
+
+// 响应式设计
+@media screen and (max-width: 768px) {
+  .preview-header {
+    padding: 12px 16px;
+    flex-direction: column;
+    gap: 12px;
+    align-items: flex-start;
+  }
+  
+  .header-left {
+    width: 100%;
+  }
+  
+  .header-right {
+    width: 100%;
+    justify-content: flex-end;
+  }
+  
+  .document-info {
+    flex: 1;
+    min-width: 0;
+    
+    h2 {
+      max-width: 100%;
+    }
+    
+    .document-url {
+      max-width: 100%;
+    }
+  }
+}
+</style>

+ 63 - 2
src/views/report.vue

@@ -85,6 +85,12 @@
        <!--  <span>Raycos Tech</span> -->
       </div>
       <div class="user-info-bar">
+        <!-- 添加文档预览测试按钮 -->
+        <div class="test-preview-btn" @click="previewSprayPaintGuide" title="预览喷漆作业指导书">
+          <span>📄</span>
+          <span class="btn-text">预览文档</span>
+        </div>
+        
         <!-- 添加主题切换按钮 -->
         <div class="theme-switch" @click="toggleTheme" :title="isDarkTheme ? '切换到浅色模式' : '切换到深色模式'">
           <transition name="theme-icon" mode="out-in">
@@ -1633,13 +1639,40 @@ const previewDocument = (source) => {
   console.log('预览文档:', source);
   
   if (source.minio_url) {
-    // 在新窗口中打开文档预览
-    window.open(source.minio_url, '_blank');
+    // 使用新的文档预览页面
+    const url = encodeURIComponent(source.minio_url);
+    const title = encodeURIComponent(source.name || '文档预览');
+    const previewUrl = `${window.location.origin}/#/document-preview?url=${url}&title=${title}`;
+    
+    // 在新窗口中打开文档预览页面
+    window.open(previewUrl, '_blank');
   } else {
     ElMessage.warning('该文档暂无预览链接');
   }
 };
 
+// 处理特定URL的文档预览
+const previewSpecificDocument = (url, title = '文档预览') => {
+  console.log('预览特定文档:', url);
+  
+  if (url) {
+    // 使用新的文档预览页面
+    const encodedUrl = encodeURIComponent(url);
+    const encodedTitle = encodeURIComponent(title);
+    const previewUrl = `/document-preview?url=${encodedUrl}&title=${encodedTitle}`;
+    
+    // 在新窗口中打开文档预览页面
+    window.open(previewUrl, '_blank');
+  } else {
+    ElMessage.warning('缺少文档链接');
+  }
+};
+
+// 预览您提供的特定链接
+const previewSprayPaintGuide = () => {
+  console.log(window.location.origin)
+};
+
 // 处理步骤点击事件
 const handleStepClick = (step) => {
   // 显示进度条弹窗
@@ -6883,6 +6916,34 @@ button,
               box-shadow var(--theme-transition);
 }
 
+/* 文档预览测试按钮样式 */
+.test-preview-btn {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  padding: 6px 12px;
+  background: linear-gradient(135deg, #4096ff, #1677ff);
+  color: white;
+  border-radius: 16px;
+  cursor: pointer;
+  margin-right: 12px;
+  transition: all 0.3s ease;
+  font-size: 12px;
+  font-weight: 500;
+  box-shadow: 0 2px 6px rgba(64, 150, 255, 0.3);
+}
+
+.test-preview-btn:hover {
+  background: linear-gradient(135deg, #1677ff, #0958d9);
+  transform: translateY(-1px);
+  box-shadow: 0 4px 10px rgba(64, 150, 255, 0.4);
+}
+
+.test-preview-btn .btn-text {
+  font-size: 11px;
+  white-space: nowrap;
+}
+
 /* 主题切换按钮样式 */
 .theme-switch {
   display: flex;