lushixing 3 viikkoa sitten
vanhempi
commit
650f135298
3 muutettua tiedostoa jossa 147 lisäystä ja 4 poistoa
  1. 1 0
      components.d.ts
  2. 1 1
      src/components/MarkdownPreview/index.vue
  3. 145 3
      src/views/search.vue

+ 1 - 0
components.d.ts

@@ -25,6 +25,7 @@ declare module 'vue' {
     NavigationNavSideBar: typeof import('./src/components/Navigation/NavSideBar.vue')['default']
     NButton: typeof import('naive-ui')['NButton']
     NConfigProvider: typeof import('naive-ui')['NConfigProvider']
+    NDialog: typeof import('naive-ui')['NDialog']
     NDialogProvider: typeof import('naive-ui')['NDialogProvider']
     NEllipsis: typeof import('naive-ui')['NEllipsis']
     NEmpty: typeof import('naive-ui')['NEmpty']

+ 1 - 1
src/components/MarkdownPreview/index.vue

@@ -347,7 +347,7 @@ const handlePassClip = () => {
         v-if="showCopy"
         position="absolute"
         :top="0"
-        :right="30"
+        :right="0"
         color
         class="c-warning bg-#fff/80 hover:bg-#fff/90 transition-all-200 z-2"
         @click="handlePassClip()"

+ 145 - 3
src/views/search.vue

@@ -4,6 +4,7 @@ const router = useRouter()
 
 
 const loading = ref(true)
+const fileLoading = ref(false)
 const keywords = ref('')
 const isSearch = ref(false)
 interface KnowledgeItem {
@@ -74,7 +75,6 @@ const inputSearch = debounce(() => {
   }
   request.post(`/search`, params).then((res) => {
     isSearch.value = true
-    console.log(res, 888)
     if (res.data.code === 200) {
       knowledgeList.value = res.data.content
     } else {
@@ -84,12 +84,78 @@ const inputSearch = debounce(() => {
 }, 500)
 
 const clear = () => {
+  isSearch.value = false
   knowledgeList.value = []
-  console.log(keywords.value, 122)
 }
 
 const openLink = (link: string) => {
-  window.open(link.split('?')[0])
+  previewFile(link.split('?')[0])
+}
+
+/**
+ * 判断服务器响应头中的 Content-Disposition 类型
+ */
+async function checkContentDisposition(fileUrl: string): Promise<'inline' | 'attachment' | 'none'> {
+  try {
+    const response = await fetch(fileUrl, {
+      method: 'HEAD'
+    })
+    const disposition = response.headers.get('Content-Disposition')
+    if (!disposition) return 'none'
+    const disp = disposition.toLowerCase()
+    if (disp.includes('inline')) return 'inline'
+    if (disp.includes('attachment')) return 'attachment'
+    return 'none'
+  } catch (err) {
+    fileLoading.value = false
+    console.warn('Failed to check Content-Disposition:', err)
+    return 'none'
+  }
+}
+
+/**
+ * 强制以 Blob URL 的形式打开 PDF 文件(绕过浏览器的下载限制)
+ */
+async function openPdfAsBlob(fileUrl: string) {
+  const res = await fetch(fileUrl)
+  const blob = await res.blob()
+  const blobUrl = URL.createObjectURL(blob)
+  window.open(blobUrl, '_blank')
+}
+
+/**
+ * 通用文件预览函数
+ * @param fileUrl - 文件的公网 URL 地址
+ */
+async function previewFile(fileUrl: string) {
+  const ext = fileUrl.split('.').pop()?.toLowerCase().split('?')[0] || ''
+  const open = (url: string) => window.open(url, '_blank')
+
+  const isOffice = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'].includes(ext)
+  const isText = ['txt', 'csv'].includes(ext)
+
+  if (ext === 'pdf') {
+    fileLoading.value = true
+    const disposition = await checkContentDisposition(fileUrl)
+    if (disposition === 'inline') {
+      fileLoading.value = false
+      open(fileUrl)
+    } else {
+      await openPdfAsBlob(fileUrl) // fallback:强制以 blob 打开
+      fileLoading.value = false
+    }
+  } else if (isOffice) {
+    const encoded = encodeURIComponent(fileUrl)
+    open(`https://view.officeapps.live.com/op/view.aspx?src=${ encoded }`)
+  } else if (isText) {
+    open(fileUrl)
+  } else {
+    // fallback:不支持的格式可以提示或触发下载
+    const a = document.createElement('a')
+    a.href = fileUrl
+    a.download = ''
+    a.click()
+  }
 }
 
 
@@ -207,6 +273,17 @@ const openLink = (link: string) => {
         </div>
       </div>
     </div>
+    <div
+      v-if="fileLoading"
+      class="loading-block"
+    >
+      <div class="loading-items">
+        <div class="loading-container">
+          <div class="loading-overlay__spinner"></div>
+        </div>
+        <span class="loading-text">文件加载中...</span>
+      </div>
+    </div>
   </LayoutCenterPanel>
 </template>
 
@@ -395,6 +472,10 @@ const openLink = (link: string) => {
           line-height: 1.5;
           margin-bottom: 6px;
 
+          br {
+            display: none;
+          }
+
           b {
             font-weight: normal;
             color: #079D55;
@@ -409,4 +490,65 @@ const openLink = (link: string) => {
   .bottom-block .n-input.n-input--textarea .n-input__textarea-el {
     max-height: 60px;
   }
+  .loading-block {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    z-index: 99;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    .loading-items {
+      width: 150px;
+      height: 120px;
+      border-radius: 10px;
+      background: #4C4C4C;
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+    }
+    .loading-container {
+      display: flex;
+      justify-content: center;
+
+      .loading-overlay__spinner {
+        width: auto;
+        position: relative;
+        text-align: center;
+        font-size: 0;
+        display: flex;
+        justify-content: center;
+        &::before {
+          content: "";
+          width: 40px;
+          height: 40px;
+          box-sizing: border-box;
+          border-radius: 50%;
+          border: 3px solid #ccc;
+          border-right-color: #666;
+          border-bottom-color: #666;
+          border-left-color: #666;
+          animation: donutRotate 1s linear infinite;
+        }
+      }
+    }
+    .loading-text {
+      display: block;
+      text-align: center;
+      font-size: 16px;
+      color: #fff;
+      margin-top: 10px;
+    }
+  }
+  @keyframes donutRotate {
+      0% {
+          transform: rotate(0);
+      }
+
+      100% {
+          transform: rotate(360deg);
+      }
+  }
 </style>