+
+
@@ -367,6 +373,7 @@ import type { FileEditorPanelConfig } from '@/types/file-system'
import { renderMermaidDiagrams, rerenderMermaidDiagrams } from '@/utils/markedExtensions'
import { useThemeStore } from '@/stores/theme'
import { previewExcel, previewWord, previewCsv } from '@/utils/filePreviewHandlers'
+import { connectionManager } from '@/api/connection-manager'
// 异步加载 CodeEditor 组件,减少初始包大小
const AsyncCodeEditor = defineAsyncComponent({
@@ -432,13 +439,27 @@ interface Emits {
const emit = defineEmits
()
-// HTML 预览 URL(使用后端接口)
+// HTML 预览 URL(实时从 connectionManager 读取,不缓存)
+function resolveHtmlPreviewBase(): string {
+ if (!connectionManager.isRemote()) return 'http://localhost:8073'
+ const base = connectionManager.getFileServerBaseURL()
+ if (!base) return 'http://localhost:8073'
+ // 远程模式需要完整代理路径前缀 /api/v1/proxy/localfs(htmlPreviewUrl 会替换为 html-preview)
+ return base.replace(/\/$/, '') + '/api/v1/proxy/localfs'
+}
+
const htmlPreviewUrl = computed(() => {
- if (!props.config.currentFileFullPath || !props.config.isHtmlFile) {
- return ''
- }
+ if (!props.config.currentFileFullPath || !props.config.isHtmlFile) return ''
const encodedPath = encodeURIComponent(props.config.currentFileFullPath)
- return `http://localhost:8073/localfs/html-preview?path=${encodedPath}`
+ const isRemote = connectionManager.isRemote()
+ const base = resolveHtmlPreviewBase()
+ if (isRemote) {
+ // 远程模式:走 /api/v1/proxy/html-preview 路由
+ const baseUrl = base.replace(/\/proxy\/localfs\/?$/, '')
+ return `${baseUrl}/proxy/html-preview?path=${encodedPath}`
+ }
+ // 本地模式:直连文件服务器
+ return `${base}/localfs/html-preview?path=${encodedPath}`
})
// 计算属性:判断文件是否在当前目录
@@ -498,6 +519,30 @@ const handleImageError = () => {
emit('imageError')
}
+const mediaErrorMsg = ref('')
+const handleMediaError = (type: string) => {
+ mediaErrorMsg.value = `${type}文件加载失败,请检查网络连接或文件权限`
+}
+const handlePdfLoad = (event: Event) => {
+ const iframe = event.target as HTMLIFrameElement
+ try {
+ // iframe 加载后检查内容是否为空(401/404 等错误页面通常内容很少)
+ if (!iframe.contentDocument || iframe.contentDocument.body.innerHTML.length < 100) {
+ mediaErrorMsg.value = 'PDF 文件加载失败,请检查网络连接或文件权限'
+ }
+ } catch {
+ // 跨域时无法访问 contentDocument,忽略
+ }
+}
+
+// 带认证的 fetch(远程模式自动附加 Bearer token)
+const authFetch = async (url: string): Promise => {
+ const token = connectionManager.activeProfile?.token
+ const headers: Record = {}
+ if (token) headers['Authorization'] = `Bearer ${token}`
+ return fetch(url, { headers })
+}
+
// 打印窗口导出 PDF 公共函数
const openPrintWindow = (title: string, bodyHtml: string, extraStyle = '') => {
const printWindow = window.open('', '_blank')
@@ -650,7 +695,7 @@ const loadExcelPreview = async (filePath: string) => {
// 直接从本地文件服务器获取(不走 base64)
const fileUrl = props.config.previewUrl
- const response = await fetch(fileUrl)
+ const response = await authFetch(fileUrl)
const blob = await response.blob()
const file = new File([blob], getFileName(filePath), { type: blob.type || 'application/vnd.ms-excel' })
@@ -679,7 +724,7 @@ const loadWordPreview = async (filePath: string) => {
wordPreviewRef.value.innerHTML = '加载中...
'
const fileUrl = props.config.previewUrl
- const response = await fetch(fileUrl)
+ const response = await authFetch(fileUrl)
const blob = await response.blob()
const file = new File([blob], getFileName(filePath), { type: blob.type || 'application/vnd.ms-word' })
@@ -709,7 +754,7 @@ const loadCsvPreview = async (filePath: string) => {
const blob = props.config.fileContent && !props.config.isBinaryFile
? new Blob([props.config.fileContent], { type: 'text/csv' })
- : await (await fetch(props.config.previewUrl)).blob()
+ : await (await authFetch(props.config.previewUrl)).blob()
const file = new File([blob], getFileName(filePath), { type: 'text/csv' })
const result = await previewCsv(file, csvPreviewRef.value)
@@ -786,8 +831,8 @@ const handleHtmlIframeMessage = (event: MessageEvent) => {
// Wails 应用的 origin 可能是 wails://...,而 iframe 来自 http://localhost:8073
const allowedOrigins = [
window.location.origin,
- 'null', // about:blank 或 data: URL
- 'http://localhost:8073', // 本地文件服务器
+ 'null',
+ resolveHtmlPreviewBase(), // 动态:本地 localhost:8073 或远程代理地址
]
if (!allowedOrigins.includes(event.origin)) {
return
@@ -835,11 +880,9 @@ onUnmounted(() => {
display: flex;
align-items: center;
justify-content: space-between;
- padding: 8px 12px;
- background: var(--color-fill-1);
+ padding: 3px 12px;
+ background: var(--color-bg-2);
border-bottom: 1px solid var(--color-border);
- font-size: 13px;
- font-weight: 500;
flex-shrink: 0;
gap: 12px;
}
@@ -944,6 +987,13 @@ onUnmounted(() => {
transform: translate(-50%, -50%);
}
+.media-error {
+ color: var(--color-danger-6);
+ font-size: 12px;
+ padding: 4px 0;
+ text-align: center;
+}
+
.media-meta {
display: flex;
gap: 8px;
diff --git a/web/src/components/FileSystem/components/FileListPanel.vue b/web/src/components/FileSystem/components/FileListPanel.vue
index fbb1380..24948cb 100644
--- a/web/src/components/FileSystem/components/FileListPanel.vue
+++ b/web/src/components/FileSystem/components/FileListPanel.vue
@@ -3,7 +3,6 @@
+
+
+