diff --git a/web/src/api/system.ts b/web/src/api/system.ts index a5d928b..2fbc289 100644 --- a/web/src/api/system.ts +++ b/web/src/api/system.ts @@ -110,23 +110,25 @@ export async function deletePath(path: string): Promise { } /** - * 创建目录 + * 创建目录(parentPath + dirname 拼接为完整路径) */ -export async function createDir(path: string): Promise { +export async function createDir(parentPath: string, dirname: string): Promise { if (!window.go?.main?.App?.CreateDir) { throw new Error('CreateDir API 不可用') } - return await window.go.main.App.CreateDir(path) + const fullPath = parentPath.replace(/\/$/, '') + '/' + dirname + return await window.go.main.App.CreateDir(fullPath) } /** - * 创建文件 + * 创建文件(dirPath + filename 拼接为完整路径) */ -export async function createFile(path: string): Promise { +export async function createFile(dirPath: string, filename: string): Promise { if (!window.go?.main?.App?.CreateFile) { throw new Error('CreateFile API 不可用') } - return await window.go.main.App.CreateFile(path) + const fullPath = dirPath.replace(/\/$/, '') + '/' + filename + return await window.go.main.App.CreateFile(fullPath) } /** diff --git a/web/src/components/DeviceTest.vue b/web/src/components/DeviceTest.vue index e8c1c44..4ae6785 100644 --- a/web/src/components/DeviceTest.vue +++ b/web/src/components/DeviceTest.vue @@ -221,7 +221,6 @@ import { formatBytes, sortFileList } from '@/utils/fileUtils' // 导入 composables import { useFileOperations } from '@/composables/useFileOperations' import { useFavoriteFiles } from '@/composables/useFavoriteFiles' -import { useLocalStorage } from '@/composables/useLocalStorage' // ========== 使用 Composables ========== @@ -251,20 +250,21 @@ const { } = useFavoriteFiles(STORAGE_KEYS.DEVICE_TEST.FAVORITE_FILES) // localStorage管理 -const { storedValue: fileContentHeight } = useLocalStorage( - STORAGE_KEYS.DEVICE_TEST.FILE_CONTENT_HEIGHT, - DEFAULTS.DEFAULT_CONTENT_HEIGHT -) +const fileContentHeight = ref(DEFAULTS.DEFAULT_CONTENT_HEIGHT) +const filePanelWidth = ref({ left: 50, right: 50 }) +const pathHistory = ref([]) -const { storedValue: filePanelWidth } = useLocalStorage( - STORAGE_KEYS.DEVICE_TEST.PANEL_WIDTH, - { left: 50, right: 50 } -) - -const { storedValue: pathHistory } = useLocalStorage( - STORAGE_KEYS.DEVICE_TEST.PATH_HISTORY, - [] -) +// 从 localStorage 恢复 +try { + const h = localStorage.getItem(STORAGE_KEYS.DEVICE_TEST.FILE_CONTENT_HEIGHT) + if (h) fileContentHeight.value = JSON.parse(h) + const w = localStorage.getItem(STORAGE_KEYS.DEVICE_TEST.PANEL_WIDTH) + if (w) filePanelWidth.value = JSON.parse(w) + const p = localStorage.getItem(STORAGE_KEYS.DEVICE_TEST.PATH_HISTORY) + if (p) pathHistory.value = JSON.parse(p) +} catch (e) { + console.error('[DeviceTest] 加载 localStorage 失败:', e) +} // ========== 立即清理旧的文件内容缓存 ========== // 在组件初始化之前清理,防止加载大文件导致空白 @@ -387,6 +387,11 @@ const addToHistory = (path) => { if (pathHistory.value.length > 20) { pathHistory.value = pathHistory.value.slice(0, 20) } + try { + localStorage.setItem(STORAGE_KEYS.DEVICE_TEST.PATH_HISTORY, JSON.stringify(pathHistory.value)) + } catch (e) { + console.error('[DeviceTest] 保存路径历史失败:', e) + } } // ========== 文件选择(重写以添加历史记录) ========== diff --git a/web/src/components/FileSystem/components/FileEditorPanel.vue b/web/src/components/FileSystem/components/FileEditorPanel.vue index fbdcfc8..858edf1 100644 --- a/web/src/components/FileSystem/components/FileEditorPanel.vue +++ b/web/src/components/FileSystem/components/FileEditorPanel.vue @@ -108,14 +108,54 @@ - -
-
+ +
+
+ + + + + + + + + + + + + + + + + + + + +
+ + +
+ + +
+ +
+
+
+
@@ -307,7 +347,7 @@ import { computed, watch, nextTick, defineAsyncComponent, ref, onMounted, onUnmounted } from 'vue' import { Message } from '@arco-design/web-vue' import { IconSave, IconEdit, IconEye, IconUndo, IconCopy, IconFilePdf, IconFullscreen, IconFullscreenExit } from '@arco-design/web-vue/es/icon' -import { getFileName } from '@/utils/fileUtils' +import { getFileName, escapeHtml } from '@/utils/fileUtils' import type { FileEditorPanelConfig } from '@/types/file-system' import { renderMermaidDiagrams } from '@/utils/markedExtensions' import { previewExcel, previewWord, previewCsv } from '@/utils/filePreviewHandlers' @@ -442,163 +482,98 @@ const handleImageError = () => { emit('imageError') } -// Markdown PDF 导出处理 -const handleExportPDF = async () => { - try { - // 获取 Markdown 预览容器 - const markdownContent = markdownPreviewRef.value - if (!markdownContent) { - Message.error('无法获取 Markdown 内容') - return - } +// 打印窗口导出 PDF 公共函数 +const openPrintWindow = (title: string, bodyHtml: string, extraStyle = '') => { + const printWindow = window.open('', '_blank') + if (!printWindow) { + Message.error('无法打开打印窗口,请检查浏览器设置') + return + } - // 打开打印窗口 - const printWindow = window.open('', '_blank') - if (!printWindow) { - Message.error('无法打开打印窗口,请检查浏览器设置') - return - } - - // 设置打印样式 - const style = ` - + + ${bodyHtml} + + `) + printWindow.document.close() - .markdown-content { - max-width: 100%; - margin: 0; - padding: 0; - } + setTimeout(() => { printWindow.print() }, 500) + Message.success('PDF 导出窗口已打开') +} - .markdown-content h1 { - font-size: 24pt; - margin-bottom: 12pt; - border-bottom: 2px solid #333; - } - - .markdown-content h2 { - font-size: 18pt; - margin-bottom: 10pt; - border-bottom: 1px solid #ccc; - } - - .markdown-content h3 { - font-size: 14pt; - margin-bottom: 8pt; - } - - .markdown-content p { - margin-bottom: 10pt; - } - - .markdown-content ul, - .markdown-content ol { - margin-bottom: 10pt; - } - - .markdown-content li { - margin-bottom: 4pt; - } - - .markdown-content table { - border-collapse: collapse; - margin-bottom: 12pt; - width: 100%; - } - - .markdown-content th, - .markdown-content td { - border: 1px solid #ddd; - padding: 8px; - text-align: left; - } - - .markdown-content th { - background-color: #f5f5f5; - font-weight: bold; - } - - .markdown-content img { - max-width: 100%; - height: auto; - } - - .markdown-content blockquote { - border-left: 4px solid #ddd; - margin: 16px 0; - padding: 10px 20px; - color: #666; - } - - .markdown-content code { - background-color: #f5f5f5; - padding: 2px 4px; - border-radius: 3px; - font-family: 'Consolas', 'Monaco', monospace; - } - - .markdown-content pre { - background-color: #f5f5f5; - padding: 12px; - border-radius: 4px; - overflow-x: auto; - } - - .markdown-content pre code { - background-color: transparent; - padding: 0; - } - } - - /* 优化屏幕显示 */ - .markdown-content { - background: white; - padding: 20px; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; - } - - ` - - // 构建打印页面 - printWindow.document.write(` - - - - - ${props.config.currentFileName || 'Markdown 导出 PDF'} - ${style} - - -
- ${markdownContent.innerHTML} -
- - - `) - printWindow.document.close() - - // 延迟一点时间让样式加载完成 - setTimeout(() => { - printWindow.print() - // 不关闭窗口,让用户可以手动关闭或继续打印 - }, 500) - - Message.success('PDF 导出窗口已打开') - } catch (error) { - console.error('[handleExportPDF] 导出失败:', error) - Message.error('PDF 导出失败,请重试') +// Markdown PDF 导出处理 +const handleExportPDF = async () => { + const markdownContent = markdownPreviewRef.value + if (!markdownContent) { + Message.error('无法获取 Markdown 内容') + return } + + openPrintWindow( + props.config.currentFileName || 'Markdown 导出 PDF', + `
${markdownContent.innerHTML}
`, + ` + .markdown-content { background: white; padding: 20px; max-width: 100%; } + @media print { + .markdown-content { padding: 0; } + .markdown-content h1 { font-size: 24pt; margin-bottom: 12pt; border-bottom: 2px solid #333; } + .markdown-content h2 { font-size: 18pt; margin-bottom: 10pt; border-bottom: 1px solid #ccc; } + .markdown-content h3 { font-size: 14pt; margin-bottom: 8pt; } + .markdown-content p { margin-bottom: 10pt; } + .markdown-content ul, .markdown-content ol { margin-bottom: 10pt; } + .markdown-content li { margin-bottom: 4pt; } + .markdown-content table { border-collapse: collapse; margin-bottom: 12pt; width: 100%; } + .markdown-content th, .markdown-content td { border: 1px solid #ddd; padding: 8px; text-align: left; } + .markdown-content th { background-color: #f5f5f5; font-weight: bold; } + .markdown-content img { max-width: 100%; height: auto; } + .markdown-content blockquote { border-left: 4px solid #ddd; margin: 16px 0; padding: 10px 20px; color: #666; } + .markdown-content code { background-color: #f5f5f5; padding: 2px 4px; border-radius: 3px; font-family: 'Consolas', 'Monaco', monospace; } + .markdown-content pre { background-color: #f5f5f5; padding: 12px; border-radius: 4px; overflow-x: auto; } + .markdown-content pre code { background-color: transparent; padding: 0; } + } + ` + ) +} + +// CSV PDF 导出处理 +const handleExportCsvPDF = async () => { + const csvContent = csvPreviewRef.value?.querySelector('.csv-content table') + if (!csvContent) { + Message.error('无法获取 CSV 内容') + return + } + + openPrintWindow( + props.config.currentFileName || 'CSV 导出 PDF', + csvContent.outerHTML, + ` + table { border-collapse: collapse; width: 100%; margin-bottom: 12pt; page-break-inside: auto; } + tr { page-break-inside: avoid; page-break-after: auto; } + th, td { border: 1px solid #dfe2e5; padding: 6px 10px; text-align: left; white-space: nowrap; } + th { background-color: #f6f8fa; font-weight: 600; } + tr:nth-child(even) { background-color: #f8f8f8; } + ` + ) } // 监听模式切换,切换到预览模式时渲染 Mermaid 图表 @@ -628,9 +603,9 @@ watch(() => [props.config.isWordFile, props.config.currentFileFullPath] as const } }, { immediate: true }) -// 监听 CSV 文件变化,触发预览 -watch(() => [props.config.isCsvFile, props.config.currentFileFullPath] as const, async ([isCsv, filePath]) => { - if (isCsv && filePath) { +// 监听 CSV 文件变化或编辑模式切换,触发预览 +watch(() => [props.config.isCsvFile, props.config.currentFileFullPath, props.config.isEditMode] as const, async ([isCsv, filePath, isEditMode]) => { + if (isCsv && filePath && !isEditMode) { await nextTick() if (csvPreviewRef.value) { await loadCsvPreview(filePath) @@ -704,9 +679,9 @@ const loadCsvPreview = async (filePath: string) => { try { csvPreviewRef.value.innerHTML = '
加载中...
' - const fileUrl = props.config.previewUrl - const response = await fetch(fileUrl) - const blob = await response.blob() + const blob = props.config.fileContent && !props.config.isBinaryFile + ? new Blob([props.config.fileContent], { type: 'text/csv' }) + : await (await fetch(props.config.previewUrl)).blob() const file = new File([blob], getFileName(filePath), { type: 'text/csv' }) const result = await previewCsv(file, csvPreviewRef.value) @@ -1203,6 +1178,22 @@ onUnmounted(() => { flex-direction: column; height: 100%; overflow: hidden; + position: relative; +} + +/* CSV 预览/编辑 */ +.csv-preview-wrapper { + display: flex; + flex-direction: column; + height: 100%; + position: relative; +} + +.csv-edit-wrapper { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; } .office-preview-container { diff --git a/web/src/components/FileSystem/composables/useFavorites.ts b/web/src/components/FileSystem/composables/useFavorites.ts index 65abb27..9d7c909 100644 --- a/web/src/components/FileSystem/composables/useFavorites.ts +++ b/web/src/components/FileSystem/composables/useFavorites.ts @@ -24,7 +24,7 @@ export function useFavorites() { * 排序收藏列表:置顶项在前(按 pinnedAt 降序),非置顶项按添加时间降序 */ const sortFavorites = () => { - favorites.value.sort((a, b) => { + favorites.value = [...favorites.value].sort((a, b) => { // 置顶项优先 if (a.pinnedAt && !b.pinnedAt) return -1 if (!a.pinnedAt && b.pinnedAt) return 1 @@ -135,7 +135,7 @@ export function useFavorites() { */ const togglePin = (path: string) => { const normalizedPath = normalizePath(path) - const fav = favorites.value.find(f => normalizePath(fav.path) === normalizedPath) + const fav = favorites.value.find(f => normalizePath(f.path) === normalizedPath) if (fav) { fav.pinnedAt = fav.pinnedAt ? undefined : Date.now() sortFavorites() @@ -148,7 +148,7 @@ export function useFavorites() { */ const isPinned = (path: string): boolean => { const normalizedPath = normalizePath(path) - const fav = favorites.value.find(f => normalizePath(fav.path) === normalizedPath) + const fav = favorites.value.find(f => normalizePath(f.path) === normalizedPath) return !!fav?.pinnedAt } @@ -157,7 +157,7 @@ export function useFavorites() { */ const updateFavoritePath = (oldPath: string, newName: string) => { const normalizedOld = normalizePath(oldPath) - const fav = favorites.value.find(f => normalizePath(fav.path) === normalizedOld) + const fav = favorites.value.find(f => normalizePath(f.path) === normalizedOld) if (!fav) return const separator = getPathSeparator(oldPath) diff --git a/web/src/components/FileSystem/composables/useFileOperations.ts b/web/src/components/FileSystem/composables/useFileOperations.ts index ca9dce0..ac46927 100644 --- a/web/src/components/FileSystem/composables/useFileOperations.ts +++ b/web/src/components/FileSystem/composables/useFileOperations.ts @@ -99,11 +99,10 @@ export function useFileOperations(options: UseFileOperationsOptions = {}) { */ const createNewFile = async ( dirPath: string, - filename: string, - content: string = '' + filename: string ): Promise => { try { - const result = await createFile(dirPath, filename, content) + const result = await createFile(dirPath, filename) onSuccess?.('createFile', { dirPath, filename, result }) return result as FileItem } catch (error) { diff --git a/web/src/composables/useFavoriteFiles.js b/web/src/composables/useFavoriteFiles.js index 4f66a73..35fc36c 100644 --- a/web/src/composables/useFavoriteFiles.js +++ b/web/src/composables/useFavoriteFiles.js @@ -7,7 +7,6 @@ import { ref, onMounted } from 'vue' import { Message } from '@arco-design/web-vue' -import { useLocalStorage } from './useLocalStorage' /** * 收藏夹 composable @@ -40,11 +39,27 @@ export function useFavoriteFiles(storageKey, options = {}) { onRemove = () => {}, } = options - // 使用 localStorage composable 管理收藏列表 - const { storedValue: favoriteFiles, load, save } = useLocalStorage( - storageKey, - [] - ) + // 收藏列表 + const favoriteFiles = ref([]) + + const load = () => { + try { + const stored = localStorage.getItem(storageKey) + if (stored) { + favoriteFiles.value = JSON.parse(stored) + } + } catch (e) { + console.error('加载收藏列表失败:', e) + } + } + + const save = (data) => { + try { + localStorage.setItem(storageKey, JSON.stringify(data || favoriteFiles.value)) + } catch (e) { + console.error('保存收藏列表失败:', e) + } + } /** * 判断文件/目录是否已收藏 @@ -70,6 +85,7 @@ export function useFavoriteFiles(storageKey, options = {}) { const timeB = b.addedAt || 0 return timeB - timeA // 倒序:最新的在上面 }) + save() } /** diff --git a/web/src/wailsjs/wailsjs/go/main/App.d.ts b/web/src/wailsjs/wailsjs/go/main/App.d.ts index ac083e0..13bc760 100644 --- a/web/src/wailsjs/wailsjs/go/main/App.d.ts +++ b/web/src/wailsjs/wailsjs/go/main/App.d.ts @@ -60,12 +60,6 @@ export function GetIndexes(arg1:number,arg2:string,arg3:string):Promise>; - - - - - - export function GetRecycleBinEntries():Promise>>; export function GetResultHistory(arg1:any,arg2:string,arg3:number,arg4:number):Promise>; @@ -118,7 +112,6 @@ export function SaveAppConfig(arg1:main.SaveAppConfigRequest):Promise; - export function SaveResult(arg1:number,arg2:string,arg3:string,arg4:string,arg5:any,arg6:Array,arg7:number,arg8:number):Promise>; export function SaveSqlTabs(arg1:Array>):Promise; @@ -127,7 +120,6 @@ export function SelectPDFSaveDirectory():Promise; export function SetUpdateConfig(arg1:boolean,arg2:number,arg3:string):Promise>; - export function TestDbConnection(arg1:number):Promise; export function TestDbConnectionWithParams(arg1:api.TestConnectionRequest):Promise; diff --git a/web/src/wailsjs/wailsjs/go/main/App.js b/web/src/wailsjs/wailsjs/go/main/App.js index 4415e61..828bc44 100644 --- a/web/src/wailsjs/wailsjs/go/main/App.js +++ b/web/src/wailsjs/wailsjs/go/main/App.js @@ -114,30 +114,6 @@ export function GetMemoryInfo() { return window['go']['main']['App']['GetMemoryInfo'](); } -export function GetOpenClawConfig() { - return window['go']['main']['App']['GetOpenClawConfig'](); -} - -export function GetOpenClawModelUsage() { - return window['go']['main']['App']['GetOpenClawModelUsage'](); -} - -export function GetOpenClawSessionHistory(arg1, arg2) { - return window['go']['main']['App']['GetOpenClawSessionHistory'](arg1, arg2); -} - -export function GetOpenClawSessions() { - return window['go']['main']['App']['GetOpenClawSessions'](); -} - -export function GetOpenClawStatus() { - return window['go']['main']['App']['GetOpenClawStatus'](); -} - -export function GetOpenClawSystemUsage() { - return window['go']['main']['App']['GetOpenClawSystemUsage'](); -} - export function GetRecycleBinEntries() { return window['go']['main']['App']['GetRecycleBinEntries'](); } @@ -242,10 +218,6 @@ export function SaveDbConnection(arg1) { return window['go']['main']['App']['SaveDbConnection'](arg1); } -export function SaveOpenClawConfig(arg1) { - return window['go']['main']['App']['SaveOpenClawConfig'](arg1); -} - export function SaveResult(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) { return window['go']['main']['App']['SaveResult'](arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); } @@ -262,10 +234,6 @@ export function SetUpdateConfig(arg1, arg2, arg3) { return window['go']['main']['App']['SetUpdateConfig'](arg1, arg2, arg3); } -export function SwitchOpenClawSession(arg1) { - return window['go']['main']['App']['SwitchOpenClawSession'](arg1); -} - export function TestDbConnection(arg1) { return window['go']['main']['App']['TestDbConnection'](arg1); }