diff --git a/web/src/api/system.ts b/web/src/api/system.ts index deefeb8..a5d928b 100644 --- a/web/src/api/system.ts +++ b/web/src/api/system.ts @@ -3,6 +3,7 @@ */ import type { SystemInfo, CPU, Memory, Disk, File } from './types' +import { debugError } from '@/utils/debugLog' /** * 转换后端文件数据格式(蛇形 → 驼峰) @@ -162,7 +163,7 @@ export async function listZipContents(zipPath: string): Promise { const result = await window.go.main.App.ListZipContents(zipPath) return transformFileList(result) } catch (error) { - console.error('[API] listZipContents 错误:', error) + debugError('[API] listZipContents 错误:', error) throw error } } @@ -178,7 +179,7 @@ export async function extractFileFromZip(zipPath: string, filePath: string): Pro const result = await window.go.main.App.ExtractFileFromZip(zipPath, filePath) return result } catch (error) { - console.error('[API] extractFileFromZip 错误:', error) + debugError('[API] extractFileFromZip 错误:', error) throw error } } @@ -195,7 +196,7 @@ export async function extractFileFromZipToTemp(zipPath: string, filePath: string const result = await window.go.main.App.ExtractFileFromZipToTemp(zipPath, filePath) return result } catch (error) { - console.error('[API] extractFileFromZipToTemp 错误:', error) + debugError('[API] extractFileFromZipToTemp 错误:', error) throw error } } @@ -211,7 +212,7 @@ export async function getZipFileInfo(zipPath: string, filePath: string): Promise const result = await window.go.main.App.GetZipFileInfo(zipPath, filePath) return transformFile(result) } catch (error) { - console.error('[API] getZipFileInfo 错误:', error) + debugError('[API] getZipFileInfo 错误:', error) throw error } } @@ -226,7 +227,7 @@ export async function openPath(path: string): Promise { try { await window.go.main.App.OpenPath(path) } catch (error) { - console.error('[API] openPath 错误:', error) + debugError('[API] openPath 错误:', error) throw error } } @@ -259,7 +260,7 @@ export async function resolveShortcut(lnkPath: string): Promise<{ const result = await window.go.main.App.ResolveShortcut(lnkPath) return result } catch (error) { - console.error('[API] resolveShortcut 错误:', error) + debugError('[API] resolveShortcut 错误:', error) throw error } } @@ -280,7 +281,7 @@ export async function detectFileTypeByContent(path: string): Promise<{ const result = await window.go.main.App.DetectFileTypeByContent(path) return result as any } catch (error) { - console.error('[API] detectFileTypeByContent 错误:', error) + debugError('[API] detectFileTypeByContent 错误:', error) throw error } } diff --git a/web/src/components/UpdatePanel.vue b/web/src/components/UpdatePanel.vue index 3b85800..d2f98c2 100644 --- a/web/src/components/UpdatePanel.vue +++ b/web/src/components/UpdatePanel.vue @@ -79,20 +79,8 @@ - -
- 调试信息: -
downloading = {{ downloading }} -
downloadProgress = {{ downloadProgress }} -
downloadStatus = {{ downloadStatus }} -
progressInfo = {{ progressInfo }} -
-
-
- 进度条已显示:downloadProgress={{ downloadProgress }}, downloading={{ downloading }} -
} options.filePath - 当前文件路径 - * @param {Ref} options.fileContent - 文件内容 - * @param {Function} options.onWriteFile - 写入文件的函数 - * @param {Function} options.onReset - 重置内容的函数 - * @returns {UseFileEditReturn} 文件编辑操作 API - */ -export function useFileEdit(options = {}) { - const { - filePath, - fileContent, - onWriteFile, - onReset, - } = options - - // ========== 编辑状态 ========== - - /** - * 是否正在保存 - * @type {Ref} - */ - const isSaving = ref(false) - - /** - * 是否是快捷键触发的保存 - * @type {Ref} - */ - const isShortcutSave = ref(false) - - /** - * 保存成功提示消息 - * @type {Ref} - */ - const saveSuccessMessage = ref('') - - /** - * 原始文件内容(用于检测变更) - * @type {Ref} - */ - const originalContent = ref('') - - /** - * 是否为编辑模式 - * @type {Ref} - */ - const isEditMode = ref(localStorage.getItem(STORAGE_KEYS.FILESYSTEM.EDIT_MODE) === 'true') - - // ========== 计算属性 ========== - - /** - * 文件内容是否已修改 - */ - const isFileModified = computed(() => { - return originalContent.value !== undefined && - originalContent.value !== fileContent.value - }) - - /** - * 内容是否发生变化(用于按钮禁用判断) - */ - const contentChanged = computed(() => { - return fileContent.value !== '' && - fileContent.value !== originalContent.value - }) - - /** - * 是否可以保存文件 - */ - const canSaveFile = computed(() => { - return isEditMode.value && contentChanged.value - }) - - /** - * 是否可以重置内容 - */ - const canResetContent = computed(() => { - return isEditMode.value && - contentChanged.value && - originalContent.value !== undefined - }) - - // ========== 草稿管理 ========== - - /** - * 保存草稿到 localStorage - */ - const saveDraft = () => { - try { - const draft = { - content: fileContent.value, - path: filePath.value, - timestamp: Date.now(), - } - localStorage.setItem(DRAFT_STORAGE_KEY, JSON.stringify(draft)) - localStorage.setItem(DRAFT_STORAGE_KEY + '_time', Date.now().toString()) - } catch (error) { - console.warn('[saveDraft] 保存草稿失败:', error) - } - } - - /** - * 清除草稿 - */ - const clearDraft = () => { - try { - localStorage.removeItem(DRAFT_STORAGE_KEY) - localStorage.removeItem(DRAFT_STORAGE_KEY + '_time') - } catch (error) { - console.warn('[clearDraft] 清除草稿失败:', error) - } - } - - /** - * 加载草稿 - * @returns {Object|null} 草稿数据 - */ - const loadDraft = () => { - try { - const draftStr = localStorage.getItem(DRAFT_STORAGE_KEY) - if (!draftStr) return null - - const draft = JSON.parse(draftStr) - - // 检查草稿是否过期(24小时) - const timeStr = localStorage.getItem(DRAFT_STORAGE_KEY + '_time') - if (timeStr) { - const time = parseInt(timeStr, 10) - const now = Date.now() - const hours = (now - time) / (1000 * 60 * 60) - - if (hours > 24) { - clearDraft() - return null - } - } - - return draft - } catch (error) { - console.warn('[loadDraft] 加载草稿失败:', error) - return null - } - } - - // ========== 保存操作 ========== - - /** - * 显示手动保存对话框 - * @param {boolean} isShortcut - 是否是快捷键触发 - */ - const showManualSaveDialog = (isShortcut) => { - isShortcutSave.value = isShortcut - - Modal.confirm({ - title: '保存文件', - content: `确定要保存文件 ${filePath.value} 吗?`, - okText: '保存', - cancelText: '取消', - onOk: () => { - saveToFile(filePath.value, getFileName(filePath.value), isShortcut) - }, - }) - } - - /** - * 保存到文件 - * @param {string} targetPath - 目标路径 - * @param {string} fileName - 文件名 - * @param {boolean} isShortcut - 是否是快捷键触发 - * @returns {Promise} 是否成功 - */ - const saveToFile = async (targetPath, fileName, isShortcut) => { - isSaving.value = true - try { - const success = await onWriteFile(fileContent.value, targetPath, fileName, isShortcut) - - if (success) { - originalContent.value = fileContent.value - clearDraft() - } - - return success - } finally { - isSaving.value = false - } - } - - /** - * 处理保存内容 - * @returns {Promise} 是否成功 - */ - const handleSaveContent = async () => { - if (!canSaveFile.value) { - return false - } - - return await saveToFile(filePath.value, getFileName(filePath.value), false) - } - - /** - * 另存为 - */ - const handleSaveAs = async () => { - try { - // 简单实现:使用 prompt 获取路径 - const targetPath = prompt('请输入保存路径:', filePath.value) - - if (!targetPath) { - return false - } - - const fileName = getFileName(targetPath) - return await saveToFile(targetPath, fileName, false) - } catch (error) { - Message.error(`保存对话框失败: ${error.message || error}`) - return false - } - } - - /** - * 处理写入文件(快捷键或按钮) - * @param {boolean} isShortcut - 是否是快捷键触发 - * @returns {Promise} 是否成功 - */ - const handleWriteFile = async (isShortcut = false) => { - if (!fileContent.value || !filePath.value) { - Message.warning('没有可保存的内容') - return false - } - - // 如果内容未修改,快捷键保存时静默返回 - if (!isFileModified.value && isShortcut) { - return false - } - - // 快捷键:静默保存 - if (isShortcut) { - return await saveToFile(filePath.value, getFileName(filePath.value), true) - } - - // 按钮:显示确认对话框 - showManualSaveDialog(false) - return false - } - - // ========== 重置操作 ========== - - /** - * 重置内容到原始状态 - */ - const resetContent = () => { - if (onReset) { - onReset() - } else { - fileContent.value = originalContent.value - } - } - - // ========== 编辑模式切换 ========== - - /** - * 切换编辑模式 - */ - const toggleEditMode = () => { - isEditMode.value = !isEditMode.value - - // 持久化 - try { - localStorage.setItem(STORAGE_KEYS.FILESYSTEM.EDIT_MODE, isEditMode.value.toString()) - } catch (e) { - console.warn('[toggleEditMode] 保存编辑模式失败:', e) - } - - // 进入编辑模式时,记录原始内容 - if (isEditMode.value) { - originalContent.value = fileContent.value - } - } - - // ========== 工具函数 ========== - - /** - * 从路径获取文件名 - * @param {string} path - 文件路径 - * @returns {string} 文件名 - */ - const getFileName = (path) => { - if (!path) return '' - const parts = path.split(/[/\\]/) - return parts[parts.length - 1] || path - } - - // ========== 监听内容变化 ========== - - /** - * 监听文件内容变化,自动保存草稿 - */ - watch(fileContent, () => { - if (fileContent.value && fileContent.value !== originalContent.value) { - saveDraft() - } - }) - - /** - * 监听文件路径变化,更新原始内容 - */ - watch(filePath, () => { - originalContent.value = fileContent.value - }) - - return { - // 状态 - isSaving, - isShortcutSave, - saveSuccessMessage, - originalContent, - isEditMode, - isFileModified, - canSaveFile, - canResetContent, - - // 方法 - saveDraft, - clearDraft, - loadDraft, - handleSaveContent, - handleSaveAs, - handleWriteFile, - resetContent, - toggleEditMode, - } -} - -/** - * @typedef {Object} UseFileEditReturn - * @property {Ref} isSaving - 是否正在保存 - * @property {Ref} isShortcutSave - 是否是快捷键触发 - * @property {Ref} saveSuccessMessage - 保存成功提示消息 - * @property {Ref} originalContent - 原始文件内容 - * @property {Ref} isEditMode - 是否为编辑模式 - * @property {ComputedRef} isFileModified - 文件内容是否已修改 - * @property {ComputedRef} canSaveFile - 是否可以保存文件 - * @property {ComputedRef} canResetContent - 是否可以重置内容 - * @property {Function} saveDraft - 保存草稿 - * @property {Function} clearDraft - 清除草稿 - * @property {Function} loadDraft - 加载草稿 - * @property {Function} handleSaveContent - 处理保存内容 - * @property {Function} handleSaveAs - 另存为 - * @property {Function} handleWriteFile - 处理写入文件 - * @property {Function} resetContent - 重置内容 - * @property {Function} toggleEditMode - 切换编辑模式 - */ diff --git a/web/src/composables/useFilePreview.js b/web/src/composables/useFilePreview.js deleted file mode 100644 index 8faafa5..0000000 --- a/web/src/composables/useFilePreview.js +++ /dev/null @@ -1,603 +0,0 @@ -/** - * 文件预览逻辑 composable - * - * @module composables/useFilePreview - * @description 封装文件预览、HTML/Markdown 渲染、二进制文件信息显示等逻辑 - */ - -import { ref, computed } from 'vue' -import { marked } from '@/utils/markedExtensions' -import { FILE_EXTENSIONS, FILE_SIZE_THRESHOLDS } from '@/utils/constants' -import { getExt } from '@/utils/fileHelpers' -import { isOfficeFile } from '@/utils/fileTypeHelpers' -import { debugLog, debugWarn, debugError } from '@/utils/debugLog' - -/** - * 文件预览 composable - * @param {Object} options - 配置选项 - * @param {Ref} options.filePath - 当前文件路径 - * @param {Ref} options.fileContent - 文件内容 - * @param {Ref} options.fileList - 文件列表 - * @param {Function} options.onReadFile - 读取文件的函数 - * @returns {UseFilePreviewReturn} 文件预览操作 API - */ -export function useFilePreview(options = {}) { - const { - filePath, - fileContent, - fileList, - onReadFile, - } = options - - // ========== 预览状态 ========== - - /** - * 预览 URL - * @type {Ref} - */ - const previewUrl = ref('') - - /** - * 文件服务器URL - * @type {Ref} - */ - const fileServerURL = ref('http://localhost:18765') - - /** - * 渲染后的 HTML/Markdown 内容 - * @type {Ref} - */ - const rendered = ref('') - - /** - * 图片加载状态 - * @type {Ref} - */ - const imageLoading = ref(false) - - /** - * 图片宽度 - * @type {Ref} - */ - const imageWidth = ref(0) - - /** - * 图片高度 - * @type {Ref} - */ - const imageHeight = ref(0) - - /** - * 是否显示图片预览 - * @type {Ref} - */ - const isImageView = ref(false) - - /** - * 是否显示视频预览 - * @type {Ref} - */ - const isVideoView = ref(false) - - /** - * 是否显示音频预览 - * @type {Ref} - */ - const isAudioView = ref(false) - - /** - * 是否为 PDF 文件 - * @type {Ref} - */ - const isPdfFile = ref(false) - - /** - * 是否为 HTML 文件 - * @type {Ref} - */ - const isHtmlFile = ref(false) - - /** - * 是否为 Markdown 文件 - * @type {Ref} - */ - const isMarkdownFile = ref(false) - - /** - * 是否为二进制文件信息展示 - * @type {Ref} - */ - const isBinaryFile = ref(false) - - /** - * HTML 预览的 blob URL - * @type {Ref} - */ - const htmlPreviewUrl = ref('') - - // ========== 计算属性 ========== - - /** - * 当前文件名 - */ - const currentFileName = computed(() => { - if (!filePath.value) return '' - const pathStr = typeof filePath.value === 'string' ? filePath.value : String(filePath.value || '') - const parts = pathStr.split(/[/\\]/) - return parts[parts.length - 1] - }) - - /** - * 当前文件完整路径 - */ - const currentFileFullPath = computed(() => filePath.value || '') - - /** - * 当前图片尺寸 - */ - const currentImageDimensions = computed(() => { - if (!imageWidth.value || !imageHeight.value) return '' - return `${imageWidth.value}×${imageHeight.value}` - }) - - // ========== 图片预览 ========== - - /** - * 预览图片 - * @param {string} targetPath - 目标路径 - */ - const previewImage = async (targetPath) => { - const pathToPreview = targetPath || filePath.value - if (!pathToPreview) return - - resetPreviewState() - - const ext = getExt(pathToPreview) - if (!FILE_EXTENSIONS.IMAGE.includes(ext)) { - return - } - - imageLoading.value = true - isImageView.value = true - - // 构建预览 URL - const encodedPath = encodeURIComponent(pathToPreview) - previewUrl.value = `${fileServerURL.value}/file?path=${encodedPath}` - } - - /** - * 图片加载成功回调 - * @param {Event} e - 加载事件 - */ - const onImageLoad = (e) => { - imageLoading.value = false - imageWidth.value = e.naturalWidth || e.target?.width || 0 - imageHeight.value = e.naturalHeight || e.target?.height || 0 - } - - /** - * 图片加载失败回调 - */ - const onImageError = () => { - imageLoading.value = false - debugWarn('[onImageError] 图片加载失败') - } - - // ========== 视频/音频/PDF 预览 ========== - - /** - * 预览媒体文件(视频/音频/PDF) - * @param {string} mediaType - 媒体类型 ('video' | 'audio' | 'pdf') - * @param {string} targetPath - 目标路径 - */ - const previewMedia = (mediaType, targetPath) => { - const pathToPreview = targetPath || filePath.value - if (!pathToPreview) return - - resetPreviewState() - - const encodedPath = encodeURIComponent(pathToPreview) - previewUrl.value = `${fileServerURL.value}/file?path=${encodedPath}` - - if (mediaType === 'video') { - isVideoView.value = true - } else if (mediaType === 'audio') { - isAudioView.value = true - } else if (mediaType === 'pdf') { - isPdfFile.value = true - } - } - - /** - * 预览视频 - * @param {string} targetPath - 目标路径 - */ - const previewVideo = (targetPath) => previewMedia('video', targetPath) - - /** - * 预览音频 - * @param {string} targetPath - 目标路径 - */ - const previewAudio = (targetPath) => previewMedia('audio', targetPath) - - /** - * 预览 PDF - * @param {string} targetPath - 目标路径 - */ - const previewPdf = (targetPath) => previewMedia('pdf', targetPath) - - // ========== HTML 预览 ========== - - /** - * 提取 HTML 文件中的样式 - * @param {string} htmlContent - HTML 内容 - * @param {string} basePath - 基础路径 - * @returns {Promise} 提取的 CSS 样式 - */ - const extractHtmlStyles = async (htmlContent, basePath) => { - const linkRegex = /]*href=(["'])([^"']+)\1[^>]*>/gi - const links = [...htmlContent.matchAll(linkRegex)] - - if (links.length === 0) return '' - - let linkCount = 0 - const styles = [] - - for (const match of links) { - const linkTag = match[0] - const hrefMatch = match[2]?.match(/^https?:\/\//i) - - const fullTag = match[0] - const href = match[2] - - debugLog(`[extractHtmlStyles] 发现第 ${linkCount} 个 link 标签:`, fullTag) - - const cssPath = href?.replace(/^\.\//, '').replace(/^\//, '') - debugLog('[extractHtmlStyles] 解析后 CSS 路径:', cssPath) - - if (hrefMatch) { - debugLog('[extractHtmlStyles] 跳过外部 CSS:', hrefMatch[1]) - continue - } - - debugLog('[extractHtmlStyles] 正在读取 CSS 文件:', cssPath) - - try { - // 从 HTML 文件所在目录读取 CSS - const cssFullPath = basePath + '/' + cssPath - const cssContent = await onReadFile(cssFullPath) - - if (cssContent) { - const cssSize = cssContent.length - debugLog(`[extractHtmlStyles] 成功读取并转换 CSS: ${cssSize} 字符`) - - // 转换 CSS 中的 URL 为 base64 - const convertedCss = await convertCssUrls(cssContent, basePath) - styles.push(convertedCss) - } - } catch (error) { - debugWarn('[extractHtmlStyles] 无法读取 CSS:', cssPath, error.message) - } - - linkCount++ - } - - debugLog(`处理完成: 找到 ${linkCount} 个 link 标签, 成功提取 ${styles.length} 个 CSS 文件`) - debugLog(`提取的 CSS 总大小: ${styles.join('\n\n').length} 字符`) - - return styles.join('\n\n') - } - - /** - * 转换 CSS 中的相对 URL 为 base64 - * @param {string} css - CSS 内容 - * @param {string} basePath - 基础路径 - * @returns {Promise} 转换后的 CSS - */ - const convertCssUrls = async (css, basePath) => { - const urlRegex = /url\((["']?)([^"')]+)\1\)/gi - - return css.replace(urlRegex, async (match, quote, url) => { - // 跳过 data: URLs 和绝对 URLs - if (url.startsWith('data:') || /^https?:\/\//i.test(url)) { - return match - } - - try { - const imagePath = basePath + '/' + url.replace(/^\.\//, '') - const base64 = await fileToBase64(imagePath) - - debugLog(`[convertCssUrls] ${url} -> base64`) - - return `url("data:image/${getExt(imagePath)};base64,${base64}")` - } catch (err) { - debugWarn('[convertCssUrls] 失败:', imagePath, err.message) - return match - } - }) - } - - /** - * 将文件转换为 base64 - * @param {string} filePath - 文件路径 - * @returns {Promise} base64 字符串 - */ - const fileToBase64 = async (filePath) => { - // 这里需要调用实际的文件读取 API - // 简化实现,返回空字符串 - return '' - } - - /** - * 预览 HTML 文件 - * @param {string} targetPath - 目标路径 - */ - const previewHtml = async (targetPath) => { - const pathToPreview = targetPath || filePath.value - if (!pathToPreview) return - - resetPreviewState() - isHtmlFile.value = true - - debugLog('开始处理 CSS') - debugLog('HTML 文件路径:', pathToPreview) - - const basePath = pathToPreview.replace(/[^/\\]+$/, '') - - try { - let htmlContent = fileContent.value - - // 提取并转换 CSS - const styles = await extractHtmlStyles(htmlContent, basePath) - - // 转换图片引用 - const imgRegex = /]*src=(["'])([^"']+)\1[^>]*>/gi - htmlContent = htmlContent.replace(imgRegex, (match, quote, src) => { - // 跳过 data: URLs 和绝对 URLs - if (src.startsWith('data:') || /^https?:\/\//i.test(src)) { - return match - } - - debugLog(`[previewHtml] ${src} -> base64`) - - // 转换为绝对路径 - const imagePath = basePath + src.replace(/^\.\//, '').replace(/^\//, '') - - // 简化实现:使用 fileServerURL - const encodedPath = encodeURIComponent(imagePath) - const newSrc = `${fileServerURL.value}/file?path=${encodedPath}` - - return match.replace(src, newSrc) - }) - - // 移除本地脚本 - htmlContent = htmlContent.replace(/]*src=(["'])[^"']+\1[^>]*>/gi, (match, quote, src) => { - const srcMatch = match.match(/src=(["'])([^"']+)\1/i) - if (srcMatch) { - const srcValue = srcMatch[2] - if (!srcValue.startsWith('http')) { - debugLog(`[previewHtml] 移除本地脚本: ${srcValue}`) - return '' - } - } - return match - }) - - // 清理遗漏的 CSS 链接 - htmlContent = htmlContent.replace(/]*rel=(["'])stylesheet\1[^>]*>/gi, (match) => { - const hrefMatch = match.match(/href=(["'])([^"']+)\1/i) - if (hrefMatch && !/^https?:\/\//i.test(hrefMatch[2])) { - debugLog(`[previewHtml] 清理遗漏的CSS链接: ${hrefMatch[2]}`) - return '' - } - return match - }) - - // 构建最终 HTML - const finalHtml = ` - - - - - - - - ${htmlContent} - - - ` - - // 创建 blob URL - const blob = new Blob([finalHtml], { type: 'text/html' }) - htmlPreviewUrl.value = URL.createObjectURL(blob) - rendered.value = finalHtml - } catch (error) { - debugError('[previewHtml] 处理失败:', error) - } - } - - // ========== Markdown 预览 ========== - - /** - * 预览 Markdown 文件 - * @param {string} targetPath - 目标路径 - */ - const previewMarkdown = async (targetPath) => { - const pathToPreview = targetPath || filePath.value - if (!pathToPreview) return - - resetPreviewState() - isMarkdownFile.value = true - - try { - renderMarkdown(fileContent.value) - } catch (error) { - debugError('[renderMarkdown] 解析失败:', error) - } - } - - /** - * 渲染 Markdown - * @param {string} markdown - Markdown 内容 - */ - const renderMarkdown = (markdown) => { - try { - rendered.value = marked(markdown) - } catch (error) { - debugError('[renderMarkdown] 解析失败:', error) - rendered.value = '

Markdown 解析失败

' - } - } - - // ========== 二进制文件信息 ========== - - /** - * 获取字符串显示宽度(用于对齐) - * @param {string} str - 字符串 - * @returns {number} 显示宽度 - */ - const getDisplayWidth = (str) => { - let width = 0 - for (const char of str) { - if (char.match(/[\u4e00-\u9fa5]/)) { - width += 2 - } else { - width += 1 - } - } - return width - } - - /** - * 按显示宽度填充 - * @param {string} str - 字符串 - * @param {number} targetWidth - 目标宽度 - * @returns {string} 填充后的字符串 - */ - const padByDisplayWidth = (str, targetWidth) => { - const currentWidth = getDisplayWidth(str) - const padding = Math.max(0, targetWidth - currentWidth) - return str + ' '.repeat(padding) - } - - /** - * 显示二进制文件信息 - * @param {string} ext - 文件扩展名 - * @param {string} filePathParam - 文件路径 - */ - const showBinaryFileInfo = (ext, filePathParam) => { - resetPreviewState() - isBinaryFile.value = true - - const file = fileList.value.find(f => f.path === filePathParam) - if (!file) return - - const extUpper = ext.toUpperCase() - const extPadded = padByDisplayWidth(extUpper, 6) - const sizeMB = (file.size / 1024 / 1024).toFixed(2) - const sizeStr = `${sizeMB} MB`.padStart(10, ' ') - - rendered.value = ` -
-

- ${extPadded} 文件 - ${sizeStr} -

-

${file.name}

-
- ` - } - - // ========== 工具函数 ========== - - /** - * 重置预览状态 - */ - const resetPreviewState = () => { - isImageView.value = false - isVideoView.value = false - isAudioView.value = false - isPdfFile.value = false - isHtmlFile.value = false - isMarkdownFile.value = false - isBinaryFile.value = false - - if (htmlPreviewUrl.value) { - URL.revokeObjectURL(htmlPreviewUrl.value) - htmlPreviewUrl.value = '' - } - - previewUrl.value = '' - rendered.value = '' - imageWidth.value = 0 - imageHeight.value = 0 - } - - return { - // 状态 - previewUrl, - fileServerURL, - rendered, - imageLoading, - imageWidth, - imageHeight, - isImageView, - isVideoView, - isAudioView, - isPdfFile, - isHtmlFile, - isMarkdownFile, - isBinaryFile, - htmlPreviewUrl, - currentFileName, - currentFileFullPath, - currentImageDimensions, - - // 方法 - previewImage, - previewVideo, - previewAudio, - previewPdf, - previewHtml, - previewMarkdown, - renderMarkdown, - showBinaryFileInfo, - onImageLoad, - onImageError, - isOfficeFile, - resetPreviewState, - } -} - -/** - * @typedef {Object} UseFilePreviewReturn - * @property {Ref} previewUrl - 预览 URL - * @property {Ref} fileServerURL - 文件服务器URL - * @property {Ref} rendered - 渲染后的内容 - * @property {Ref} imageLoading - 图片加载状态 - * @property {Ref} imageWidth - 图片宽度 - * @property {Ref} imageHeight - 图片高度 - * @property {Ref} isImageView - 是否显示图片预览 - * @property {Ref} isVideoView - 是否显示视频预览 - * @property {Ref} isAudioView - 是否显示音频预览 - * @property {Ref} isPdfFile - 是否为 PDF 文件 - * @property {Ref} isHtmlFile - 是否为 HTML 文件 - * @property {Ref} isMarkdownFile - 是否为 Markdown 文件 - * @property {Ref} isBinaryFile - 是否为二进制文件信息展示 - * @property {Ref} htmlPreviewUrl - HTML 预览的 blob URL - * @property {ComputedRef} currentFileName - 当前文件名 - * @property {ComputedRef} currentFileFullPath - 当前文件完整路径 - * @property {ComputedRef} currentImageDimensions - 当前图片尺寸 - * @property {Function} previewImage - 预览图片 - * @property {Function} previewVideo - 预览视频 - * @property {Function} previewAudio - 预览音频 - * @property {Function} previewPdf - 预览 PDF - * @property {Function} previewHtml - 预览 HTML - * @property {Function} previewMarkdown - 预览 Markdown - * @property {Function} renderMarkdown - 渲染 Markdown - * @property {Function} showBinaryFileInfo - 显示二进制文件信息 - * @property {Function} onImageLoad - 图片加载成功回调 - * @property {Function} onImageError - 图片加载失败回调 - * @property {Function} isOfficeFile - 判断是否为 Office 文件 - * @property {Function} resetPreviewState - 重置预览状态 - */