|
@@ -4,6 +4,7 @@ const router = useRouter()
|
|
|
|
|
|
|
|
|
|
const loading = ref(true)
|
|
const loading = ref(true)
|
|
|
|
+const fileLoading = ref(false)
|
|
const keywords = ref('')
|
|
const keywords = ref('')
|
|
const isSearch = ref(false)
|
|
const isSearch = ref(false)
|
|
interface KnowledgeItem {
|
|
interface KnowledgeItem {
|
|
@@ -74,7 +75,6 @@ const inputSearch = debounce(() => {
|
|
}
|
|
}
|
|
request.post(`/search`, params).then((res) => {
|
|
request.post(`/search`, params).then((res) => {
|
|
isSearch.value = true
|
|
isSearch.value = true
|
|
- console.log(res, 888)
|
|
|
|
if (res.data.code === 200) {
|
|
if (res.data.code === 200) {
|
|
knowledgeList.value = res.data.content
|
|
knowledgeList.value = res.data.content
|
|
} else {
|
|
} else {
|
|
@@ -84,12 +84,78 @@ const inputSearch = debounce(() => {
|
|
}, 500)
|
|
}, 500)
|
|
|
|
|
|
const clear = () => {
|
|
const clear = () => {
|
|
|
|
+ isSearch.value = false
|
|
knowledgeList.value = []
|
|
knowledgeList.value = []
|
|
- console.log(keywords.value, 122)
|
|
|
|
}
|
|
}
|
|
|
|
|
|
const openLink = (link: string) => {
|
|
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>
|
|
</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>
|
|
</LayoutCenterPanel>
|
|
</template>
|
|
</template>
|
|
|
|
|
|
@@ -395,6 +472,10 @@ const openLink = (link: string) => {
|
|
line-height: 1.5;
|
|
line-height: 1.5;
|
|
margin-bottom: 6px;
|
|
margin-bottom: 6px;
|
|
|
|
|
|
|
|
+ br {
|
|
|
|
+ display: none;
|
|
|
|
+ }
|
|
|
|
+
|
|
b {
|
|
b {
|
|
font-weight: normal;
|
|
font-weight: normal;
|
|
color: #079D55;
|
|
color: #079D55;
|
|
@@ -409,4 +490,65 @@ const openLink = (link: string) => {
|
|
.bottom-block .n-input.n-input--textarea .n-input__textarea-el {
|
|
.bottom-block .n-input.n-input--textarea .n-input__textarea-el {
|
|
max-height: 60px;
|
|
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>
|
|
</style>
|