From 7dbd57a8b6ae8ab12ae521c8728495df8c56ea8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=9D=E5=B0=98?= <237809796@qq.com> Date: Sat, 11 Apr 2026 16:46:43 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=EF=BC=9AWails=E5=8D=87?= =?UTF-8?q?=E7=BA=A7/mermaid=E4=B8=BB=E9=A2=98=E5=88=87=E6=8D=A2/=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E9=AB=98=E4=BA=AE=E4=BF=AE=E5=A4=8D/=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E7=B3=BB=E7=BB=9FUI=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Wails v2.12.0升级(App绑定新增API、runtime类型扩展) - 修复mermaid暗色主题切换渲染失败(SVG textContent污染→data-mermaid-src保存源码) - 修复代码高亮全语言失效(languageMap静态白名单替代运行时hljs检查) - 文件系统:FileListPanel重写、FileItemRow合并删除、Toolbar简化 - 新增剪贴板图片粘贴(Ctrl+V粘贴图片到当前目录) - 死代码清理:DeviceTest/errorHandler/useLocalStorage移除 - MarkdownEditor优化、theme store增强、CodeMirror加载器精简 --- app.go | 67 +- go.mod | 5 +- go.sum | 6 +- internal/filesystem/asset_handler.go | 27 +- internal/filesystem/path_validator.go | 22 +- internal/filesystem/service.go | 41 +- web/index.html | 1 + web/package-lock.json | 6 +- web/package.json | 4 +- web/package.json.md5 | 2 +- web/src/App.vue | 2 + web/src/api/system.ts | 19 +- web/src/api/types.ts | 1 + web/src/components/DeviceTest.vue | 725 ------------------ .../FileSystem/components/FileEditorPanel.vue | 15 +- .../FileSystem/components/FileItemRow.vue | 255 ------ .../FileSystem/components/FileListPanel.vue | 543 ++++++++++--- .../FileSystem/components/Toolbar.vue | 1 - web/src/components/FileSystem/index.vue | 216 ++++-- web/src/components/MarkdownEditor.vue | 68 +- web/src/composables/index.ts | 1 - web/src/main.js | 3 +- web/src/stores/theme.ts | 9 + web/src/stores/update.ts | 19 + web/src/style.css | 6 + web/src/types/file-system.ts | 4 + web/src/utils/codeMirrorLoader.js | 72 +- web/src/utils/codemirrorExports.js | 20 +- web/src/utils/constants.js | 3 + web/src/utils/errorHandler.js | 63 -- web/src/utils/fileUtils.js | 52 +- web/src/utils/languageMap.ts | 31 +- web/src/utils/markedExtensions.ts | 101 ++- web/src/utils/resize.ts | 107 +++ web/src/wailsjs/wailsjs/go/main/App.d.ts | 2 + web/src/wailsjs/wailsjs/go/main/App.js | 4 + web/src/wailsjs/wailsjs/go/models.ts | 14 + web/src/wailsjs/wailsjs/runtime/runtime.d.ts | 83 +- web/src/wailsjs/wailsjs/runtime/runtime.js | 56 ++ web/vite.config.js | 2 +- 40 files changed, 1274 insertions(+), 1404 deletions(-) delete mode 100644 web/src/components/DeviceTest.vue delete mode 100644 web/src/components/FileSystem/components/FileItemRow.vue delete mode 100644 web/src/utils/errorHandler.js create mode 100644 web/src/utils/resize.ts diff --git a/app.go b/app.go index e49f4ff..5b07bf6 100644 --- a/app.go +++ b/app.go @@ -6,11 +6,13 @@ import ( "net/http" "os" "path/filepath" + "regexp" stdruntime "runtime" "strings" "time" "github.com/jung-kurt/gofpdf" + "golang.org/x/sys/windows/registry" "u-desk/internal/api" "u-desk/internal/common" "u-desk/internal/database" @@ -22,6 +24,9 @@ import ( "github.com/wailsapp/wails/v2/pkg/runtime" ) +// PDF 有序列表正则(包级变量,避免循环内重复编译) +var orderedListRe = regexp.MustCompile(`^\d+\.\s+`) + // App 应用结构体 type App struct { ctx context.Context @@ -37,6 +42,10 @@ type App struct { isAlwaysOnTop bool } +// App 方法命名约定: +// - 多参数操作 → XxxRequest 结构体(Wails 自动生成 TS 类型) +// - 单参数查询/简单操作 → 直接参数 + // NewApp 创建新的应用实例 func NewApp() *App { return &App{} @@ -286,6 +295,17 @@ func (a *App) WriteFile(req WriteFileRequest) error { return a.filesystem.WriteFile(req.Path, req.Content) } +// SaveBase64FileRequest 保存 Base64 编码的二进制文件 +type SaveBase64FileRequest struct { + Path string `json:"path"` + Content string `json:"content"` // base64 编码的文件内容 +} + +// SaveBase64File 将 base64 内容解码后写入文件(用于图片等二进制数据) +func (a *App) SaveBase64File(req SaveBase64FileRequest) error { + return a.filesystem.SaveBase64File(req.Path, req.Content) +} + // ListDir 列出目录 func (a *App) ListDir(path string) ([]map[string]interface{}, error) { return a.filesystem.ListDir(path) @@ -393,6 +413,31 @@ func (a *App) ResolveShortcut(lnkPath string) (map[string]interface{}, error) { }, nil } +// getWindowsSpecialFolder 从注册表读取 Windows 特殊文件夹的真实路径 +// 用户可通过系统设置修改下载/桌面/文档等目录位置,注册表记录实际路径 +func getWindowsSpecialFolder(guid string, fallbackName string) string { + key, err := registry.OpenKey(registry.CURRENT_USER, + `Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders`, + registry.READ) + if err != nil { + return "" + } + defer key.Close() + + val, _, err := key.GetStringValue(guid) + if err != nil || val == "" { + return "" + } + + // 展开 %USERPROFILE% 等环境变量 + path := os.ExpandEnv(val) + // 验证路径存在 + if _, err := os.Stat(path); err != nil { + return "" + } + return path +} + // GetCommonPaths 获取常用系统路径 func (a *App) GetCommonPaths() (map[string]string, error) { homeDir, err := os.UserHomeDir() @@ -401,10 +446,22 @@ func (a *App) GetCommonPaths() (map[string]string, error) { } paths := map[string]string{ - "home": homeDir, - "desktop": filepath.Join(homeDir, "Desktop"), - "documents": filepath.Join(homeDir, "Documents"), - "downloads": filepath.Join(homeDir, "Downloads"), + "home": homeDir, + } + + // Windows: 从注册表读取特殊文件夹真实路径(用户可能已修改位置) + folderGUIDs := map[string]string{ + "desktop": "{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}", + "documents": "{D20B4C7F-5EA7-40D4B25E-039F6F1FCC8A}", + "downloads": "{374DE290-123F-4565-9164-39C4925E467B}", + } + for name, guid := range folderGUIDs { + if p := getWindowsSpecialFolder(guid, name); p != "" { + paths[name] = p + } else { + // folderGUIDs 的 key 均为 ASCII,无需 Unicode 处理 + paths[name] = filepath.Join(homeDir, strings.ToUpper(name[:1])+name[1:]) + } } // Windows: 动态添加所有盘符 @@ -978,7 +1035,7 @@ func (a *App) ExportMarkdownToPDF(markdownContent string) (string, error) { pdf.Cell(10, 7, "•") pdf.Cell(0, 7, strings.TrimPrefix(line, "- ")) pdf.Ln(7) - } else if strings.HasPrefix(line, "1. ") || strings.HasPrefix(line, "2. ") || strings.HasPrefix(line, "3. ") { + } else if orderedListRe.MatchString(line) { // 有序列表 pdf.SetFont("Arial", "", 12) pdf.Cell(10, 7, strings.TrimSpace(strings.SplitN(line, ".", 2)[0]) + ".") diff --git a/go.mod b/go.mod index 8143602..5336583 100644 --- a/go.mod +++ b/go.mod @@ -10,15 +10,17 @@ require ( github.com/jung-kurt/gofpdf v1.16.2 github.com/redis/go-redis/v9 v9.17.3 github.com/shirou/gopsutil/v3 v3.24.5 - github.com/wailsapp/wails/v2 v2.11.0 + github.com/wailsapp/wails/v2 v2.12.0 github.com/yuin/goldmark v1.8.2 go.mongodb.org/mongo-driver/v2 v2.5.0 + golang.org/x/sys v0.40.0 gorm.io/driver/mysql v1.6.0 gorm.io/gorm v1.31.1 ) require ( filippo.io/edwards25519 v1.1.0 // indirect + git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 // indirect github.com/bep/debounce v1.2.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chromedp/sysutil v1.1.0 // indirect @@ -70,7 +72,6 @@ require ( golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect golang.org/x/net v0.49.0 // indirect golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.40.0 // indirect golang.org/x/text v0.33.0 // indirect modernc.org/libc v1.67.7 // indirect modernc.org/mathutil v1.7.1 // indirect diff --git a/go.sum b/go.sum index 2866750..509ec70 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 h1:N3IGoHHp9pb6mj1cbXbuaSXV/UMKwmbKLf53nQmtqMA= +git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3/go.mod h1:QtOLZGz8olr4qH2vWK0QH0w0O4T9fEIjMuWpKUsH7nc= github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= @@ -134,8 +136,8 @@ github.com/wailsapp/go-webview2 v1.0.23 h1:jmv8qhz1lHibCc79bMM/a/FqOnnzOGEisLav+ github.com/wailsapp/go-webview2 v1.0.23/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= -github.com/wailsapp/wails/v2 v2.11.0 h1:seLacV8pqupq32IjS4Y7V8ucab0WZwtK6VvUVxSBtqQ= -github.com/wailsapp/wails/v2 v2.11.0/go.mod h1:jrf0ZaM6+GBc1wRmXsM8cIvzlg0karYin3erahI4+0k= +github.com/wailsapp/wails/v2 v2.12.0 h1:BHO/kLNWFHYjCzucxbzAYZWUjub1Tvb4cSguQozHn5c= +github.com/wailsapp/wails/v2 v2.12.0/go.mod h1:mo1bzK1DEJrobt7YrBjgxvb5Sihb1mhAY09hppbibQg= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs= diff --git a/internal/filesystem/asset_handler.go b/internal/filesystem/asset_handler.go index 6fe1a85..544df87 100644 --- a/internal/filesystem/asset_handler.go +++ b/internal/filesystem/asset_handler.go @@ -41,8 +41,14 @@ var ( es6ImportFromRegex = regexp.MustCompile(`import\s+([\s\S]*?)\s+from\s+["']([^"']+)["']`) es6DynamicImport = regexp.MustCompile(`import\s*\(\s*["']([^"']+)["']\s*\)`) es6BareImport = regexp.MustCompile(`(?m)^\s*import\s+["']([^"']+)["']`) + + // HTML 预览路径修复 + locationPathRegex = regexp.MustCompile(`\blocation\.pathname\b`) ) +// HTML 属性正则缓存(避免 replaceHtmlTagAttribute 中重复编译) +var attrRegexCache sync.Map // map[string]*regexp.Regexp + // LocalFileServer 本地文件服务器(独立的 HTTP 服务器) type LocalFileServer struct { server *http.Server @@ -501,6 +507,11 @@ func handleHtmlPreviewRequest(w http.ResponseWriter, r *http.Request) { // 解析参数 filePath := r.URL.Query().Get("path") + var err error + if filePath, err = url.QueryUnescape(filePath); err != nil { + http.Error(w, "Invalid path encoding", http.StatusBadRequest) + return + } theme := r.URL.Query().Get("theme") if theme == "" { theme = "light" @@ -536,6 +547,12 @@ func handleHtmlPreviewRequest(w http.ResponseWriter, r *http.Request) { // 转换资源路径(将相对路径和绝对路径都转换为完整的本地文件服务器 URL) processedContent := transformHtmlResourcePaths(string(content), baseDir) + // 修复 JS 中基于 location.pathname 的相对路径计算 + // 预览模式下 location.pathname = "/localfs/html-preview",与实际文件路径不一致 + // ⚠️ 会替换所有出现位置(含JS字符串内),HTML预览场景下可接受 + correctPathname := `"/localfs/` + strings.ReplaceAll(baseDir, "\\", "/") + `/` + processedContent = locationPathRegex.ReplaceAllString(processedContent, correctPathname) + // 注入链接点击拦截脚本 finalContent := injectLinkInterceptor(processedContent) @@ -616,8 +633,14 @@ func transformHtmlResourcePaths(htmlContent string, baseDir string) string { // replaceHtmlTagAttribute 替换 HTML 标签中的属性路径 func replaceHtmlTagAttribute(html string, pattern *regexp.Regexp, attrName string, baseDir string) string { return pattern.ReplaceAllStringFunc(html, func(match string) string { - // 提取属性值 - attrRegex := regexp.MustCompile(fmt.Sprintf(`%s=["']([^"']+)["']`, attrName)) + // 提取属性值(使用缓存的正则) + var attrRegex *regexp.Regexp + if v, ok := attrRegexCache.Load(attrName); ok { + attrRegex = v.(*regexp.Regexp) + } else { + attrRegex = regexp.MustCompile(fmt.Sprintf(`%s=["']([^"']+)["']`, attrName)) + attrRegexCache.Store(attrName, attrRegex) + } attrMatch := attrRegex.FindStringSubmatch(match) if attrMatch == nil { return match diff --git a/internal/filesystem/path_validator.go b/internal/filesystem/path_validator.go index 5b9513e..9b9f3dc 100644 --- a/internal/filesystem/path_validator.go +++ b/internal/filesystem/path_validator.go @@ -6,6 +6,7 @@ import ( "path/filepath" "runtime" "strings" + "sync" ) // PathValidator 路径验证器接口 @@ -180,16 +181,25 @@ func (v *DefaultPathValidator) isSensitivePath(path string) bool { return false } +// 默认路径验证器(缓存,避免每次调用重复初始化) +var ( + defaultValidatorOnce sync.Once + defaultValidator PathValidator +) + +func getDefaultValidator() PathValidator { + defaultValidatorOnce.Do(func() { + defaultValidator = NewPathValidator(DefaultConfig()) + }) + return defaultValidator +} + // isSafePath 兼容函数:保持向后兼容 -// 使用默认配置的路径验证器 func isSafePath(path string) bool { - validator := NewPathValidator(DefaultConfig()) - return validator.IsSafe(path) + return getDefaultValidator().IsSafe(path) } // isSensitivePath 兼容函数:保持向后兼容 -// 使用默认配置检查敏感路径 func isSensitivePath(path string) bool { - validator := NewPathValidator(DefaultConfig()) - return validator.IsSensitive(path) + return getDefaultValidator().IsSensitive(path) } diff --git a/internal/filesystem/service.go b/internal/filesystem/service.go index c3dc866..040056e 100644 --- a/internal/filesystem/service.go +++ b/internal/filesystem/service.go @@ -2,17 +2,22 @@ package filesystem import ( "context" + "encoding/base64" + "errors" "fmt" "os" "os/exec" "path/filepath" "runtime" + "strings" "sync" "time" "u-desk/internal/common" ) +const maxReadWriteSize = 10 * 1024 * 1024 // 10MB 读写上限 + // FileOperationResult 文件操作结果 type FileOperationResult struct { Path string `json:"path"` @@ -131,9 +136,8 @@ func (s *FileSystemService) ReadFile(path string) (string, error) { if err != nil { return "", fmt.Errorf("获取文件信息失败: %v", err) } - const maxReadSize = 10 * 1024 * 1024 // 10MB - if info.Size() > maxReadSize { - return "", fmt.Errorf("文件过大 (%.1f MB),超过读取上限 (%d MB)", float64(info.Size())/1024/1024, maxReadSize/1024/1024) + if info.Size() > maxReadWriteSize { + return "", fmt.Errorf("文件过大 (%.1f MB),超过读取上限 (%d MB)", float64(info.Size())/1024/1024, maxReadWriteSize/1024/1024) } // 读取文件 @@ -151,30 +155,43 @@ func (s *FileSystemService) Write(path, content string) error { return s.WriteFile(path, content) } -// WriteFile 写入文件 -func (s *FileSystemService) WriteFile(path, content string) error { - // 路径验证 +// writeFile 内部写入实现(路径验证+大小检查+写入+日志) +func (s *FileSystemService) writeFileWithLog(path string, data []byte) error { if err := s.validatePath(path); err != nil { return err } - - // 创建目录 dir := filepath.Dir(path) if err := os.MkdirAll(dir, DefaultDirPermissions); err != nil { return fmt.Errorf("创建目录失败: %v", err) } - - // 写入文件 - data := []byte(content) + if len(data) > maxReadWriteSize { + return fmt.Errorf("文件过大 (%.1f MB),超过写入上限 (%d MB)", float64(len(data))/1024/1024, maxReadWriteSize/1024/1024) + } if err := os.WriteFile(path, data, DefaultFilePermissions); err != nil { s.logWrite(path, int64(len(data)), err) return fmt.Errorf("写入文件失败: %v", err) } - s.logWrite(path, int64(len(data)), nil) return nil } +// WriteFile 写入文件 +func (s *FileSystemService) WriteFile(path, content string) error { + return s.writeFileWithLog(path, []byte(content)) +} + +// SaveBase64File 将 base64 编码内容解码后写入二进制文件 +func (s *FileSystemService) SaveBase64File(path, base64Content string) error { + if strings.TrimSpace(base64Content) == "" { + return errors.New("base64 内容不能为空") + } + data, err := base64.StdEncoding.DecodeString(base64Content) + if err != nil { + return fmt.Errorf("base64 解码失败: %v", err) + } + return s.writeFileWithLog(path, data) +} + // List 列出目录内容(实现 FileService 接口) func (s *FileSystemService) List(path string) ([]map[string]interface{}, error) { return s.ListDir(path) diff --git a/web/index.html b/web/index.html index 2133f8a..45e6897 100644 --- a/web/index.html +++ b/web/index.html @@ -4,6 +4,7 @@ U-Desk +
diff --git a/web/package-lock.json b/web/package-lock.json index 3c74c0c..744cd85 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -28,8 +28,6 @@ "@codemirror/state": "^6.5.3", "@codemirror/theme-one-dark": "^6.1.3", "@codemirror/view": "^6.39.8", - "@types/highlight.js": "^9.12.4", - "@types/mermaid": "^9.1.0", "highlight.js": "^11.11.1", "mammoth": "^1.11.0", "marked": "^17.0.1", @@ -39,6 +37,8 @@ "xlsx": "^0.18.5" }, "devDependencies": { + "@types/highlight.js": "^9.12.4", + "@types/mermaid": "^9.1.0", "@vitejs/plugin-vue": "^6.0.3", "unplugin-auto-import": "^0.18.3", "unplugin-vue-components": "^0.27.4", @@ -1440,12 +1440,14 @@ "version": "9.12.4", "resolved": "https://registry.npmmirror.com/@types/highlight.js/-/highlight.js-9.12.4.tgz", "integrity": "sha512-t2szdkwmg2JJyuCM20e8kR2X59WCE5Zkl4bzm1u1Oukjm79zpbiAv+QjnwLnuuV0WHEcX2NgUItu0pAMKuOPww==", + "dev": true, "license": "MIT" }, "node_modules/@types/mermaid": { "version": "9.1.0", "resolved": "https://registry.npmmirror.com/@types/mermaid/-/mermaid-9.1.0.tgz", "integrity": "sha512-rc8QqhveKAY7PouzY/p8ljS+eBSNCv7o79L97RSub/Ic2SQ34ph1Ng3s8wFLWVjvaEt6RLOWtSCsgYWd95NY8A==", + "dev": true, "license": "MIT" }, "node_modules/@types/trusted-types": { diff --git a/web/package.json b/web/package.json index 60c6f56..d45585b 100644 --- a/web/package.json +++ b/web/package.json @@ -28,8 +28,6 @@ "@codemirror/state": "^6.5.3", "@codemirror/theme-one-dark": "^6.1.3", "@codemirror/view": "^6.39.8", - "@types/highlight.js": "^9.12.4", - "@types/mermaid": "^9.1.0", "highlight.js": "^11.11.1", "mammoth": "^1.11.0", "marked": "^17.0.1", @@ -39,6 +37,8 @@ "xlsx": "^0.18.5" }, "devDependencies": { + "@types/highlight.js": "^9.12.4", + "@types/mermaid": "^9.1.0", "@vitejs/plugin-vue": "^6.0.3", "unplugin-auto-import": "^0.18.3", "unplugin-vue-components": "^0.27.4", diff --git a/web/package.json.md5 b/web/package.json.md5 index 8f418c8..74e7cb4 100644 --- a/web/package.json.md5 +++ b/web/package.json.md5 @@ -1 +1 @@ -11e4d92d4ca3da6546d1516713da71a8 \ No newline at end of file +0e1fafcbb6b28922a38f6c5316932015 \ No newline at end of file diff --git a/web/src/App.vue b/web/src/App.vue index 9ca2b39..8d5fd67 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -168,6 +168,8 @@ onMounted(() => { onUnmounted(() => { document.removeEventListener('wheel', preventZoom) updateStore.removeEventListeners() + // 兜底清除所有 Wails 事件监听器,防止泄漏 + window.runtime?.EventsOffAll?.() }) // 窗口控制方法 diff --git a/web/src/api/system.ts b/web/src/api/system.ts index 2fbc289..36ce5a2 100644 --- a/web/src/api/system.ts +++ b/web/src/api/system.ts @@ -12,7 +12,8 @@ import { debugError } from '@/utils/debugLog' function transformFile(file: any): File { return { ...file, - isDir: file.is_dir + isDir: file.is_dir, + modified_time: file.mod_time } } @@ -99,6 +100,22 @@ export async function writeFile(path: string, content: string): Promise { }) } +/** + * 保存 Base64 编码的二进制文件(图片等) + */ +export async function saveBase64File(path: string, base64Content: string): Promise { + if (!window.go?.main?.App?.SaveBase64File) { + throw new Error('SaveBase64File API 不可用') + } + if (!base64Content) { + throw new Error('无效的 base64 内容') + } + await window.go.main.App.SaveBase64File({ + path: String(path), + content: base64Content + }) +} + /** * 删除文件或目录 */ diff --git a/web/src/api/types.ts b/web/src/api/types.ts index 31e0f64..63f6123 100644 --- a/web/src/api/types.ts +++ b/web/src/api/types.ts @@ -105,4 +105,5 @@ export interface File { size: number isDir: boolean modified?: string + modified_time?: string } diff --git a/web/src/components/DeviceTest.vue b/web/src/components/DeviceTest.vue deleted file mode 100644 index 4ae6785..0000000 --- a/web/src/components/DeviceTest.vue +++ /dev/null @@ -1,725 +0,0 @@ - - - - - diff --git a/web/src/components/FileSystem/components/FileEditorPanel.vue b/web/src/components/FileSystem/components/FileEditorPanel.vue index 858edf1..60a829c 100644 --- a/web/src/components/FileSystem/components/FileEditorPanel.vue +++ b/web/src/components/FileSystem/components/FileEditorPanel.vue @@ -349,7 +349,8 @@ 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, escapeHtml } from '@/utils/fileUtils' import type { FileEditorPanelConfig } from '@/types/file-system' -import { renderMermaidDiagrams } from '@/utils/markedExtensions' +import { renderMermaidDiagrams, rerenderMermaidDiagrams } from '@/utils/markedExtensions' +import { useThemeStore } from '@/stores/theme' import { previewExcel, previewWord, previewCsv } from '@/utils/filePreviewHandlers' // 异步加载 CodeEditor 组件,减少初始包大小 @@ -589,6 +590,18 @@ watch(() => props.config.isEditMode, async (newVal, oldVal) => { } }) +// 监听主题变化,重新渲染 mermaid 图表 +const themeStore = useThemeStore() +watch(() => themeStore.isDark, async () => { + if (!props.config.isEditMode && markdownPreviewRef.value) { + try { + // 等 DOM 更新完成后再重新渲染 + await nextTick() + await rerenderMermaidDiagrams(markdownPreviewRef.value) + } catch { /* 忽略 */ } + } +}) + // 监听 Excel 文件变化,触发预览 watch(() => [props.config.isExcelFile, props.config.currentFileFullPath] as const, async ([isExcel, filePath]) => { if (isExcel && filePath && excelPreviewRef.value) { diff --git a/web/src/components/FileSystem/components/FileItemRow.vue b/web/src/components/FileSystem/components/FileItemRow.vue deleted file mode 100644 index 7a0e60a..0000000 --- a/web/src/components/FileSystem/components/FileItemRow.vue +++ /dev/null @@ -1,255 +0,0 @@ - - - - - diff --git a/web/src/components/FileSystem/components/FileListPanel.vue b/web/src/components/FileSystem/components/FileListPanel.vue index 3b119c7..b71ca6a 100644 --- a/web/src/components/FileSystem/components/FileListPanel.vue +++ b/web/src/components/FileSystem/components/FileListPanel.vue @@ -2,40 +2,62 @@
📋 文件列表 - {{ config.fileList.length }} 项 +
+ {{ config.fileList.length }} 项 + + + + + + +
- - + - - + :bordered="false" + :show-header="showHeader" + size="mini" + :row-class-name="getRowClassName" + :scroll="{ y: 'auto' }" + class="file-table" + @row-click="handleRowClick" + @row-dblclick="handleRowDoubleClick" + @row-contextmenu="handleRowContextMenu" + />
@@ -47,8 +69,11 @@ diff --git a/web/src/components/FileSystem/components/Toolbar.vue b/web/src/components/FileSystem/components/Toolbar.vue index f1fcdd3..c084739 100644 --- a/web/src/components/FileSystem/components/Toolbar.vue +++ b/web/src/components/FileSystem/components/Toolbar.vue @@ -109,7 +109,6 @@