diff --git a/app.go b/app.go
index 7f9f213..9745cbb 100644
--- a/app.go
+++ b/app.go
@@ -14,6 +14,7 @@ import (
"u-desk/internal/common"
"u-desk/internal/database"
"u-desk/internal/filesystem"
+ "u-desk/internal/service"
"u-desk/internal/storage"
"u-desk/internal/system"
@@ -59,7 +60,11 @@ func (a *App) Startup(ctx context.Context) {
// 2.5. 迁移旧配置
_ = a.configAPI.MigrateTabConfig()
- // 3. 读取配置,获取可见的 Tabs
+ // 3. 初始化版本号(提前触发缓存,避免后续重复计算)
+ version := service.GetCurrentVersion()
+ fmt.Printf("[启动] 当前版本: %s\n", version)
+
+ // 4. 读取配置,获取可见的 Tabs
visibleTabs := a.getVisibleTabs()
fmt.Printf("[启动] 可用的模块: %v\n", visibleTabs)
@@ -173,28 +178,31 @@ func (a *App) startFileServer() {
return
}
- // 创建一个占位服务器用于保持引用(实际服务器由 StartLocalFileServer 管理)
- a.fileServer = &http.Server{
- Addr: "localhost:18765",
- }
-
fmt.Println("[文件服务器] 启动在 http://localhost:18765")
}
// Shutdown 应用关闭时调用
func (a *App) Shutdown(ctx context.Context) {
- // 关闭文件系统服务(优雅关闭,释放资源)
+ // 创建带超时的上下文(5秒超时)
+ shutdownCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
+ defer cancel()
+
+ // 1. 关闭文件系统服务(优雅关闭,释放资源)
if a.filesystem != nil {
fmt.Println("[文件系统服务] 正在关闭...")
- if err := a.filesystem.Close(ctx); err != nil {
+ if err := a.filesystem.Close(shutdownCtx); err != nil {
fmt.Printf("[文件系统服务] 关闭失败: %v\n", err)
+ } else {
+ fmt.Println("[文件系统服务] 已关闭")
}
}
- // 停止文件服务器
- if a.fileServer != nil {
- fmt.Println("[文件服务器] 正在关闭...")
- a.fileServer.Shutdown(ctx)
+ // 2. 停止文件服务器(使用全局服务器的关闭方法)
+ fmt.Println("[文件服务器] 正在关闭...")
+ if err := filesystem.ShutdownLocalFileServer(); err != nil {
+ fmt.Printf("[文件服务器] 关闭失败: %v\n", err)
+ } else {
+ fmt.Println("[文件服务器] 已关闭")
}
}
diff --git a/internal/api/update_api.go b/internal/api/update_api.go
index 4109a1f..b5de1b6 100644
--- a/internal/api/update_api.go
+++ b/internal/api/update_api.go
@@ -50,13 +50,6 @@ func (api *UpdateAPI) CheckUpdate() (map[string]interface{}, error) {
// GetCurrentVersion 获取当前版本号
func (api *UpdateAPI) GetCurrentVersion() (map[string]interface{}, error) {
version := service.GetCurrentVersion()
-
- // 同步配置中的版本号
- if config, err := service.LoadUpdateConfig(); err == nil && config.CurrentVersion != version {
- config.CurrentVersion = version
- service.SaveUpdateConfig(config)
- }
-
return successResponse(map[string]interface{}{
"version": version,
}), nil
@@ -69,13 +62,6 @@ func (api *UpdateAPI) GetUpdateConfig() (map[string]interface{}, error) {
return errorResponse(err.Error()), nil
}
- // 同步最新版本号
- latestVersion := service.GetCurrentVersion()
- if config.CurrentVersion != latestVersion {
- config.CurrentVersion = latestVersion
- service.SaveUpdateConfig(config)
- }
-
return successResponse(map[string]interface{}{
"current_version": config.CurrentVersion,
"last_check_time": config.LastCheckTime.Format("2006-01-02 15:04:05"),
diff --git a/internal/filesystem/asset_handler.go b/internal/filesystem/asset_handler.go
index 8d25527..911900a 100644
--- a/internal/filesystem/asset_handler.go
+++ b/internal/filesystem/asset_handler.go
@@ -1,6 +1,7 @@
package filesystem
import (
+ "context"
"encoding/base64"
"fmt"
"log"
@@ -10,12 +11,14 @@ import (
"path/filepath"
"strings"
"sync"
+ "time"
)
// LocalFileServer 本地文件服务器(独立的 HTTP 服务器)
type LocalFileServer struct {
server *http.Server
addr string
+ mu sync.RWMutex
}
var (
@@ -258,3 +261,35 @@ func HandleLocalFile(w http.ResponseWriter, r *http.Request) {
func isAllowedFileType(ext string) bool {
return defaultFileTypeManager.IsAllowed(ext)
}
+
+// Shutdown 优雅关闭文件服务器
+func (lfs *LocalFileServer) Shutdown() error {
+ if lfs == nil || lfs.server == nil {
+ return nil
+ }
+
+ lfs.mu.Lock()
+ defer lfs.mu.Unlock()
+
+ // 创建带超时的上下文
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+
+ log.Printf("[LocalFileServer] 正在关闭...")
+
+ if err := lfs.server.Shutdown(ctx); err != nil {
+ log.Printf("[LocalFileServer] 关闭失败: %v", err)
+ return err
+ }
+
+ log.Printf("[LocalFileServer] 已关闭")
+ return nil
+}
+
+// ShutdownLocalFileServer 关闭全局文件服务器
+func ShutdownLocalFileServer() error {
+ if localFileServer != nil {
+ return localFileServer.Shutdown()
+ }
+ return nil
+}
diff --git a/internal/filesystem/service.go b/internal/filesystem/service.go
index 7b2a50f..1b2fb05 100644
--- a/internal/filesystem/service.go
+++ b/internal/filesystem/service.go
@@ -262,7 +262,7 @@ func (s *FileSystemService) DeletePathWithContext(ctx context.Context, path stri
// 返回被删除的文件信息,用于前端更新
return &FileOperationResult{
- Path: path,
+ Path: filepath.ToSlash(path), // 统一使用正斜杠
Name: info.Name(),
Size: info.Size(),
SizeStr: formatBytes(info.Size()),
@@ -297,7 +297,7 @@ func (s *FileSystemService) ListDir(path string) ([]map[string]interface{}, erro
fullPath := filepath.Join(path, entry.Name())
result = append(result, map[string]interface{}{
"name": entry.Name(),
- "path": fullPath,
+ "path": filepath.ToSlash(fullPath), // 统一使用正斜杠
"is_dir": entry.IsDir(),
"size": info.Size(),
"mod_time": info.ModTime().Format("2006-01-02 15:04:05"),
@@ -338,14 +338,14 @@ func (s *FileSystemService) CreateDir(path string) (*FileOperationResult, error)
if err != nil {
// 创建成功但获取信息失败,返回基本信息
return &FileOperationResult{
- Path: path,
+ Path: filepath.ToSlash(path), // 统一使用正斜杠
Name: filepath.Base(path),
IsDir: true,
}, nil
}
return &FileOperationResult{
- Path: path,
+ Path: filepath.ToSlash(path), // 统一使用正斜杠
Name: info.Name(),
Size: info.Size(),
SizeStr: formatBytes(info.Size()),
@@ -385,7 +385,7 @@ func (s *FileSystemService) CreateFile(path string) (*FileOperationResult, error
if err != nil {
// 创建成功但获取信息失败,返回基本信息
return &FileOperationResult{
- Path: path,
+ Path: filepath.ToSlash(path), // 统一使用正斜杠
Name: filepath.Base(path),
IsDir: false,
Size: 0,
@@ -393,7 +393,7 @@ func (s *FileSystemService) CreateFile(path string) (*FileOperationResult, error
}
return &FileOperationResult{
- Path: path,
+ Path: filepath.ToSlash(path), // 统一使用正斜杠
Name: info.Name(),
Size: info.Size(),
SizeStr: formatBytes(info.Size()),
@@ -424,7 +424,7 @@ func (s *FileSystemService) GetFileInfo(path string) (map[string]interface{}, er
return map[string]interface{}{
"name": info.Name(),
- "path": path,
+ "path": filepath.ToSlash(path), // 统一使用正斜杠
"size": info.Size(),
"size_str": formatBytes(info.Size()),
"is_dir": info.IsDir(),
@@ -472,21 +472,21 @@ func (s *FileSystemService) RenamePath(oldPath, newPath string) (*FileOperationR
if err != nil {
// 重命名成功但获取信息失败,返回基本信息
return &FileOperationResult{
- Path: newPath,
+ Path: filepath.ToSlash(newPath), // 统一使用正斜杠
Name: filepath.Base(newPath),
- OldPath: oldPath,
+ OldPath: filepath.ToSlash(oldPath),
}, nil
}
return &FileOperationResult{
- Path: newPath,
+ Path: filepath.ToSlash(newPath), // 统一使用正斜杠
Name: info.Name(),
Size: info.Size(),
SizeStr: formatBytes(info.Size()),
IsDir: info.IsDir(),
ModTime: info.ModTime().Format("2006-01-02 15:04:05"),
Mode: info.Mode().String(),
- OldPath: oldPath,
+ OldPath: filepath.ToSlash(oldPath),
}, nil
}
diff --git a/internal/filesystem/zip.go b/internal/filesystem/zip.go
index cbdbc40..7fe8368 100644
--- a/internal/filesystem/zip.go
+++ b/internal/filesystem/zip.go
@@ -181,7 +181,7 @@ func ListZipContents(zipPath string) ([]map[string]interface{}, error) {
entry := map[string]interface{}{
"name": name,
- "path": file.Name, // zip 中的完整路径
+ "path": file.Name, // zip 中的完整路径(已使用 /)
"is_dir": isDir,
"size": file.UncompressedSize64,
"compressed": file.CompressedSize64,
diff --git a/internal/filesystem/zip_helper.go b/internal/filesystem/zip_helper.go
index ebda4c7..a82e460 100644
--- a/internal/filesystem/zip_helper.go
+++ b/internal/filesystem/zip_helper.go
@@ -103,7 +103,7 @@ func getCompressionMethodString(method uint16) string {
func createFileInfoMap(file *zip.File, includeExtra ...bool) map[string]interface{} {
info := map[string]interface{}{
"name": filepath.Base(file.Name),
- "path": file.Name,
+ "path": file.Name, // zip 中的路径(已使用 /)
"is_dir": file.Mode().IsDir(),
"size": file.UncompressedSize64,
"compressed": file.CompressedSize64,
diff --git a/internal/service/update.go b/internal/service/update.go
index 5d3e7cd..6ecb75e 100644
--- a/internal/service/update.go
+++ b/internal/service/update.go
@@ -4,7 +4,6 @@ import (
"encoding/json"
"fmt"
"io"
- "log"
"net/http"
"os"
"os/exec"
@@ -62,20 +61,13 @@ func NewUpdateService(checkURL string) *UpdateService {
// CheckUpdate 检查更新
func (s *UpdateService) CheckUpdate() (*UpdateCheckResult, error) {
- log.Printf("[更新检查] 开始检查更新,检查地址: %s", s.checkURL)
-
config, err := LoadUpdateConfig()
if err != nil {
return nil, fmt.Errorf("加载配置失败: %v", err)
}
- // 同步版本号
- currentVersionStr, err := s.syncConfigVersion(config)
- if err != nil {
- return nil, err
- }
-
- currentVersion, err := ParseVersion(currentVersionStr)
+ // 获取当前版本(使用缓存)
+ currentVersion, err := ParseVersion(GetCurrentVersion())
if err != nil {
return nil, fmt.Errorf("解析当前版本失败: %v", err)
}
@@ -86,14 +78,6 @@ func (s *UpdateService) CheckUpdate() (*UpdateCheckResult, error) {
return nil, fmt.Errorf("获取远程版本信息失败: %v", err)
}
- log.Printf("[更新检查] 远程版本信息: 版本=%s, 下载地址=%s, 强制更新=%v, 更新日志长度=%d",
- remoteInfo.Version, remoteInfo.DownloadURL, remoteInfo.ForceUpdate, len(remoteInfo.Changelog))
- if remoteInfo.Changelog != "" {
- log.Printf("[更新检查] 更新日志内容: %s", remoteInfo.Changelog)
- } else {
- log.Printf("[更新检查] 警告: 远程接口未返回更新日志")
- }
-
// 解析远程版本号
remoteVersion, err := ParseVersion(remoteInfo.Version)
if err != nil {
@@ -102,55 +86,30 @@ func (s *UpdateService) CheckUpdate() (*UpdateCheckResult, error) {
// 比较版本
hasUpdate := remoteVersion.IsNewerThan(currentVersion)
- log.Printf("[更新检查] 版本比较: 当前=%s, 远程=%s, 有更新=%v",
- currentVersion.String(), remoteVersion.String(), hasUpdate)
// 更新最后检查时间
config.UpdateLastCheckTime()
- result := &UpdateCheckResult{
+ return &UpdateCheckResult{
HasUpdate: hasUpdate,
- CurrentVersion: currentVersionStr,
+ CurrentVersion: GetCurrentVersion(),
LatestVersion: remoteInfo.Version,
DownloadURL: remoteInfo.DownloadURL,
Changelog: remoteInfo.Changelog,
ForceUpdate: remoteInfo.ForceUpdate,
ReleaseDate: remoteInfo.ReleaseDate,
FileSize: remoteInfo.FileSize,
- }
-
- log.Printf("[更新检查] 检查完成: 有更新=%v", hasUpdate)
- return result, nil
-}
-
-// syncConfigVersion 同步配置中的版本号
-func (s *UpdateService) syncConfigVersion(config *UpdateConfig) (string, error) {
- currentVersionStr := GetCurrentVersion()
- if currentVersionStr == "" {
- currentVersionStr = config.CurrentVersion
- log.Printf("[更新检查] 使用配置中的版本号: %s", currentVersionStr)
- } else if config.CurrentVersion != currentVersionStr {
- log.Printf("[更新检查] 配置中的版本号 (%s) 与当前版本号 (%s) 不一致,更新配置",
- config.CurrentVersion, currentVersionStr)
- config.CurrentVersion = currentVersionStr
- if err := SaveUpdateConfig(config); err != nil {
- log.Printf("[更新检查] 更新配置失败: %v", err)
- }
- }
- return currentVersionStr, nil
+ }, nil
}
// fetchRemoteVersionInfo 获取远程版本信息
func (s *UpdateService) fetchRemoteVersionInfo() (*RemoteVersionInfo, error) {
if s.checkURL == "" {
- log.Printf("[远程版本] 版本检查 URL 未配置")
return nil, fmt.Errorf("版本检查 URL 未配置,请先设置检查地址")
}
- log.Printf("[远程版本] 请求远程版本信息: %s", s.checkURL)
-
// 添加时间戳参数防止缓存
- timestamp := time.Now().UnixMilli() // 使用毫秒级时间戳
+ timestamp := time.Now().UnixMilli()
var requestURL string
if strings.Contains(s.checkURL, "?") {
requestURL = fmt.Sprintf("%s&t=%d", s.checkURL, timestamp)
@@ -158,8 +117,6 @@ func (s *UpdateService) fetchRemoteVersionInfo() (*RemoteVersionInfo, error) {
requestURL = fmt.Sprintf("%s?t=%d", s.checkURL, timestamp)
}
- log.Printf("[远程版本] 实际请求URL: %s", requestURL)
-
// 创建 HTTP 客户端,设置超时
client := &http.Client{
Timeout: 10 * time.Second,
@@ -168,12 +125,10 @@ func (s *UpdateService) fetchRemoteVersionInfo() (*RemoteVersionInfo, error) {
// 发送请求
resp, err := client.Get(requestURL)
if err != nil {
- log.Printf("[远程版本] 网络请求失败: %v", err)
return nil, fmt.Errorf("网络请求失败: %v", err)
}
defer resp.Body.Close()
- log.Printf("[远程版本] HTTP 响应状态码: %d", resp.StatusCode)
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("服务器返回错误状态码: %d", resp.StatusCode)
}
@@ -181,25 +136,19 @@ func (s *UpdateService) fetchRemoteVersionInfo() (*RemoteVersionInfo, error) {
// 读取响应
body, err := io.ReadAll(resp.Body)
if err != nil {
- log.Printf("[远程版本] 读取响应失败: %v", err)
return nil, fmt.Errorf("读取响应失败: %v", err)
}
- log.Printf("[远程版本] 响应内容长度: %d 字节", len(body))
-
// 解析 JSON
var remoteInfo RemoteVersionInfo
if err := json.Unmarshal(body, &remoteInfo); err != nil {
- log.Printf("[远程版本] 解析 JSON 失败: %v, 响应内容: %s", err, string(body))
return nil, fmt.Errorf("解析响应失败: %v", err)
}
if remoteInfo.Version == "" {
- log.Printf("[远程版本] 远程版本信息不完整,响应内容: %s", string(body))
return nil, fmt.Errorf("远程版本信息不完整")
}
- log.Printf("[远程版本] 成功获取远程版本信息: %+v", remoteInfo)
return &remoteInfo, nil
}
diff --git a/internal/service/update_config.go b/internal/service/update_config.go
index c5405ea..5692172 100644
--- a/internal/service/update_config.go
+++ b/internal/service/update_config.go
@@ -3,7 +3,6 @@ package service
import (
"encoding/json"
"fmt"
- "log"
"os"
"path/filepath"
"time"
@@ -71,20 +70,16 @@ func LoadUpdateConfig() (*UpdateConfig, error) {
}
}
- // 同步最新版本号
- latestVersion := GetCurrentVersion()
- if config.CurrentVersion == "" || config.CurrentVersion != latestVersion {
- if config.CurrentVersion != "" {
- log.Printf("[配置] 配置中的版本号 (%s) 与最新版本号 (%s) 不一致", config.CurrentVersion, latestVersion)
- }
- config.CurrentVersion = latestVersion
- }
-
// 使用默认检查地址
if config.CheckURL == "" {
config.CheckURL = "https://img.1216.top/u-desk/last-version.json"
}
+ // 确保版本号不为空(使用缓存的版本号)
+ if config.CurrentVersion == "" {
+ config.CurrentVersion = GetCurrentVersion()
+ }
+
return &config, nil
}
diff --git a/internal/service/version.go b/internal/service/version.go
index 7b9af7e..370f3aa 100644
--- a/internal/service/version.go
+++ b/internal/service/version.go
@@ -8,6 +8,7 @@ import (
"path/filepath"
"strconv"
"strings"
+ "sync"
)
// ==================== 常量定义 ====================
@@ -15,6 +16,12 @@ import (
// AppVersion 应用版本号(发布时直接修改此处)
const AppVersion = "0.3.0"
+// 版本号缓存
+var (
+ cachedVersion string
+ versionOnce sync.Once
+)
+
// ==================== 类型定义 ====================
// Version 版本号结构
@@ -100,22 +107,25 @@ func (v *Version) IsOlderThan(other *Version) bool {
// ==================== 版本号获取 ====================
-// GetCurrentVersion 获取当前版本号
+// GetCurrentVersion 获取当前版本号(带缓存)
// 优先级:硬编码版本号 > wails.json(开发模式)> 默认值
func GetCurrentVersion() string {
- if AppVersion != "" {
- log.Printf("[版本] 使用硬编码版本号: %s", AppVersion)
- return AppVersion
- }
+ versionOnce.Do(func() {
+ if AppVersion != "" {
+ cachedVersion = AppVersion
+ return
+ }
- version := getVersionFromWailsJSON()
- if version != "" {
- log.Printf("[版本] 从 wails.json 获取版本号: %s", version)
- return version
- }
+ version := getVersionFromWailsJSON()
+ if version != "" {
+ cachedVersion = version
+ return
+ }
- log.Printf("[版本] 使用默认版本号: 0.0.1")
- return "0.0.1"
+ cachedVersion = "0.0.1"
+ })
+
+ return cachedVersion
}
// ==================== 配置文件读取 ====================
diff --git a/web/package-lock.json b/web/package-lock.json
index 70bfa8f..a4f8211 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -34,10 +34,13 @@
"highlight.js": "^11.11.1",
"marked": "^17.0.1",
"mermaid": "^11.12.2",
+ "pinia": "^3.0.4",
"vue": "^3.5.26"
},
"devDependencies": {
"@vitejs/plugin-vue": "^6.0.3",
+ "unplugin-auto-import": "^0.18.3",
+ "unplugin-vue-components": "^0.27.4",
"vite": "^7.3.0"
}
},
@@ -54,6 +57,16 @@
"url": "https://github.com/sponsors/antfu"
}
},
+ "node_modules/@antfu/utils": {
+ "version": "0.7.10",
+ "resolved": "https://registry.npmmirror.com/@antfu/utils/-/utils-0.7.10.tgz",
+ "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
"node_modules/@arco-design/color": {
"version": "0.4.0",
"license": "MIT",
@@ -1157,6 +1170,44 @@
"langium": "3.3.1"
}
},
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.53",
"resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz",
@@ -1164,6 +1215,29 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@rollup/pluginutils": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.3.0.tgz",
+ "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "estree-walker": "^2.0.2",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.54.0",
"cpu": [
@@ -1530,6 +1604,39 @@
"@vue/shared": "3.5.26"
}
},
+ "node_modules/@vue/devtools-api": {
+ "version": "7.7.9",
+ "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-7.7.9.tgz",
+ "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-kit": "^7.7.9"
+ }
+ },
+ "node_modules/@vue/devtools-kit": {
+ "version": "7.7.9",
+ "resolved": "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz",
+ "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-shared": "^7.7.9",
+ "birpc": "^2.3.0",
+ "hookable": "^5.5.3",
+ "mitt": "^3.0.1",
+ "perfect-debounce": "^1.0.0",
+ "speakingurl": "^14.0.1",
+ "superjson": "^2.2.2"
+ }
+ },
+ "node_modules/@vue/devtools-shared": {
+ "version": "7.7.9",
+ "resolved": "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz",
+ "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==",
+ "license": "MIT",
+ "dependencies": {
+ "rfdc": "^1.4.1"
+ }
+ },
"node_modules/@vue/reactivity": {
"version": "3.5.26",
"license": "MIT",
@@ -1582,6 +1689,33 @@
"node": ">=0.4.0"
}
},
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/anymatch/node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/b-tween": {
"version": "0.3.3",
"license": "MIT"
@@ -1590,6 +1724,58 @@
"version": "1.5.3",
"license": "MIT"
},
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/birpc": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmmirror.com/birpc/-/birpc-2.9.0.tgz",
+ "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/chevrotain": {
"version": "11.0.3",
"resolved": "https://registry.npmmirror.com/chevrotain/-/chevrotain-11.0.3.tgz",
@@ -1622,6 +1808,31 @@
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"license": "MIT"
},
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
"node_modules/color": {
"version": "3.2.1",
"license": "MIT",
@@ -1668,6 +1879,21 @@
"integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
"license": "MIT"
},
+ "node_modules/copy-anything": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-4.0.5.tgz",
+ "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==",
+ "license": "MIT",
+ "dependencies": {
+ "is-what": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mesqueeb"
+ }
+ },
"node_modules/cose-base": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/cose-base/-/cose-base-1.0.3.tgz",
@@ -2192,6 +2418,24 @@
"version": "1.11.19",
"license": "MIT"
},
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
"node_modules/delaunator": {
"version": "5.0.1",
"resolved": "https://registry.npmmirror.com/delaunator/-/delaunator-5.0.1.tgz",
@@ -2262,10 +2506,57 @@
"@esbuild/win32-x64": "0.27.2"
}
},
+ "node_modules/escape-string-regexp": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
+ "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/estree-walker": {
"version": "2.0.2",
"license": "MIT"
},
+ "node_modules/exsolve": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmmirror.com/exsolve/-/exsolve-1.0.8.tgz",
+ "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fastq": {
+ "version": "1.20.1",
+ "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.20.1.tgz",
+ "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz",
@@ -2284,6 +2575,19 @@
}
}
},
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
@@ -2299,6 +2603,19 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
+ "node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/hachure-fill": {
"version": "0.5.2",
"resolved": "https://registry.npmmirror.com/hachure-fill/-/hachure-fill-0.5.2.tgz",
@@ -2314,6 +2631,12 @@
"node": ">=12.0.0"
}
},
+ "node_modules/hookable": {
+ "version": "5.5.3",
+ "resolved": "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz",
+ "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
+ "license": "MIT"
+ },
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz",
@@ -2339,6 +2662,71 @@
"version": "0.3.4",
"license": "MIT"
},
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-what": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmmirror.com/is-what/-/is-what-5.5.0.tgz",
+ "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mesqueeb"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-9.0.1.tgz",
+ "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/katex": {
"version": "0.16.28",
"resolved": "https://registry.npmmirror.com/katex/-/katex-0.16.28.tgz",
@@ -2391,6 +2779,23 @@
"integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==",
"license": "MIT"
},
+ "node_modules/local-pkg": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-0.5.1.tgz",
+ "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mlly": "^1.7.3",
+ "pkg-types": "^1.2.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
"node_modules/lodash-es": {
"version": "4.17.23",
"resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.23.tgz",
@@ -2416,6 +2821,16 @@
"node": ">= 20"
}
},
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/mermaid": {
"version": "11.12.2",
"resolved": "https://registry.npmmirror.com/mermaid/-/mermaid-11.12.2.tgz",
@@ -2456,6 +2871,55 @@
"node": ">= 20"
}
},
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/micromatch/node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/mitt": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz",
+ "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
+ "license": "MIT"
+ },
"node_modules/mlly": {
"version": "1.8.0",
"resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.8.0.tgz",
@@ -2468,6 +2932,13 @@
"ufo": "^1.6.1"
}
},
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/nanoid": {
"version": "3.3.11",
"funding": [
@@ -2484,6 +2955,16 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/number-precision": {
"version": "1.6.0",
"license": "MIT"
@@ -2506,6 +2987,12 @@
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
"license": "MIT"
},
+ "node_modules/perfect-debounce": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
+ "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==",
+ "license": "MIT"
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"license": "ISC"
@@ -2516,7 +3003,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -2524,6 +3010,27 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/pinia": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmmirror.com/pinia/-/pinia-3.0.4.tgz",
+ "integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-api": "^7.7.7"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/posva"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.5.0",
+ "vue": "^3.5.11"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
"node_modules/pkg-types": {
"version": "1.3.1",
"resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz",
@@ -2577,10 +3084,91 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/quansync": {
+ "version": "0.2.11",
+ "resolved": "https://registry.npmmirror.com/quansync/-/quansync-0.2.11.tgz",
+ "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/antfu"
+ },
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/sxzz"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/readdirp/node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/resize-observer-polyfill": {
"version": "1.5.1",
"license": "MIT"
},
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rfdc": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz",
+ "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
+ "license": "MIT"
+ },
"node_modules/robust-predicates": {
"version": "3.0.2",
"resolved": "https://registry.npmmirror.com/robust-predicates/-/robust-predicates-3.0.2.tgz",
@@ -2591,6 +3179,7 @@
"version": "4.54.0",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/estree": "1.0.8"
},
@@ -2639,6 +3228,30 @@
"points-on-path": "^0.2.1"
}
},
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
"node_modules/rw": {
"version": "1.3.3",
"resolved": "https://registry.npmmirror.com/rw/-/rw-1.3.3.tgz",
@@ -2658,6 +3271,13 @@
"compute-scroll-into-view": "^1.0.20"
}
},
+ "node_modules/scule": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz",
+ "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/simple-swizzle": {
"version": "0.2.4",
"license": "MIT",
@@ -2672,6 +3292,28 @@
"node": ">=0.10.0"
}
},
+ "node_modules/speakingurl": {
+ "version": "14.0.1",
+ "resolved": "https://registry.npmmirror.com/speakingurl/-/speakingurl-14.0.1.tgz",
+ "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/strip-literal": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmmirror.com/strip-literal/-/strip-literal-2.1.1.tgz",
+ "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^9.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
"node_modules/style-mod": {
"version": "4.1.3",
"resolved": "https://registry.npmmirror.com/style-mod/-/style-mod-4.1.3.tgz",
@@ -2684,6 +3326,18 @@
"integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==",
"license": "MIT"
},
+ "node_modules/superjson": {
+ "version": "2.2.6",
+ "resolved": "https://registry.npmmirror.com/superjson/-/superjson-2.2.6.tgz",
+ "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==",
+ "license": "MIT",
+ "dependencies": {
+ "copy-anything": "^4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
"node_modules/tinyexec": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/tinyexec/-/tinyexec-1.0.2.tgz",
@@ -2710,6 +3364,19 @@
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
"node_modules/ts-dedent": {
"version": "2.2.0",
"resolved": "https://registry.npmmirror.com/ts-dedent/-/ts-dedent-2.2.0.tgz",
@@ -2725,6 +3392,163 @@
"integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==",
"license": "MIT"
},
+ "node_modules/unimport": {
+ "version": "3.14.6",
+ "resolved": "https://registry.npmmirror.com/unimport/-/unimport-3.14.6.tgz",
+ "integrity": "sha512-CYvbDaTT04Rh8bmD8jz3WPmHYZRG/NnvYVzwD6V1YAlvvKROlAeNDUBhkBGzNav2RKaeuXvlWYaa1V4Lfi/O0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rollup/pluginutils": "^5.1.4",
+ "acorn": "^8.14.0",
+ "escape-string-regexp": "^5.0.0",
+ "estree-walker": "^3.0.3",
+ "fast-glob": "^3.3.3",
+ "local-pkg": "^1.0.0",
+ "magic-string": "^0.30.17",
+ "mlly": "^1.7.4",
+ "pathe": "^2.0.1",
+ "picomatch": "^4.0.2",
+ "pkg-types": "^1.3.0",
+ "scule": "^1.3.0",
+ "strip-literal": "^2.1.1",
+ "unplugin": "^1.16.1"
+ }
+ },
+ "node_modules/unimport/node_modules/confbox": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.2.2.tgz",
+ "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/unimport/node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/unimport/node_modules/local-pkg": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-1.1.2.tgz",
+ "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mlly": "^1.7.4",
+ "pkg-types": "^2.3.0",
+ "quansync": "^0.2.11"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/unimport/node_modules/local-pkg/node_modules/pkg-types": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-2.3.0.tgz",
+ "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "confbox": "^0.2.2",
+ "exsolve": "^1.0.7",
+ "pathe": "^2.0.3"
+ }
+ },
+ "node_modules/unplugin": {
+ "version": "1.16.1",
+ "resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-1.16.1.tgz",
+ "integrity": "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.14.0",
+ "webpack-virtual-modules": "^0.6.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/unplugin-auto-import": {
+ "version": "0.18.6",
+ "resolved": "https://registry.npmmirror.com/unplugin-auto-import/-/unplugin-auto-import-0.18.6.tgz",
+ "integrity": "sha512-LMFzX5DtkTj/3wZuyG5bgKBoJ7WSgzqSGJ8ppDRdlvPh45mx6t6w3OcbExQi53n3xF5MYkNGPNR/HYOL95KL2A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@antfu/utils": "^0.7.10",
+ "@rollup/pluginutils": "^5.1.3",
+ "fast-glob": "^3.3.2",
+ "local-pkg": "^0.5.1",
+ "magic-string": "^0.30.14",
+ "minimatch": "^9.0.5",
+ "unimport": "^3.13.4",
+ "unplugin": "^1.16.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@nuxt/kit": "^3.2.2",
+ "@vueuse/core": "*"
+ },
+ "peerDependenciesMeta": {
+ "@nuxt/kit": {
+ "optional": true
+ },
+ "@vueuse/core": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/unplugin-vue-components": {
+ "version": "0.27.5",
+ "resolved": "https://registry.npmmirror.com/unplugin-vue-components/-/unplugin-vue-components-0.27.5.tgz",
+ "integrity": "sha512-m9j4goBeNwXyNN8oZHHxvIIYiG8FQ9UfmKWeNllpDvhU7btKNNELGPt+o3mckQKuPwrE7e0PvCsx+IWuDSD9Vg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@antfu/utils": "^0.7.10",
+ "@rollup/pluginutils": "^5.1.3",
+ "chokidar": "^3.6.0",
+ "debug": "^4.3.7",
+ "fast-glob": "^3.3.2",
+ "local-pkg": "^0.5.1",
+ "magic-string": "^0.30.14",
+ "minimatch": "^9.0.5",
+ "mlly": "^1.7.3",
+ "unplugin": "^1.16.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@babel/parser": "^7.15.8",
+ "@nuxt/kit": "^3.2.2",
+ "vue": "2 || 3"
+ },
+ "peerDependenciesMeta": {
+ "@babel/parser": {
+ "optional": true
+ },
+ "@nuxt/kit": {
+ "optional": true
+ }
+ }
+ },
"node_modules/uuid": {
"version": "11.1.0",
"resolved": "https://registry.npmmirror.com/uuid/-/uuid-11.1.0.tgz",
@@ -2888,6 +3712,13 @@
"resolved": "https://registry.npmmirror.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
"license": "MIT"
+ },
+ "node_modules/webpack-virtual-modules": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
+ "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
+ "dev": true,
+ "license": "MIT"
}
}
}
diff --git a/web/package.json b/web/package.json
index 881b550..820b9d1 100644
--- a/web/package.json
+++ b/web/package.json
@@ -34,10 +34,13 @@
"highlight.js": "^11.11.1",
"marked": "^17.0.1",
"mermaid": "^11.12.2",
+ "pinia": "^3.0.4",
"vue": "^3.5.26"
},
"devDependencies": {
"@vitejs/plugin-vue": "^6.0.3",
+ "unplugin-vue-components": "^0.27.4",
+ "unplugin-auto-import": "^0.18.3",
"vite": "^7.3.0"
}
}
diff --git a/web/src/App.vue b/web/src/App.vue
index 5ca6eca..1c676ec 100644
--- a/web/src/App.vue
+++ b/web/src/App.vue
@@ -58,29 +58,30 @@
diff --git a/web/src/components/DeviceTest.vue b/web/src/components/DeviceTest.vue
index 9df4dfb..253b410 100644
--- a/web/src/components/DeviceTest.vue
+++ b/web/src/components/DeviceTest.vue
@@ -235,7 +235,7 @@ const {
deleteFile,
} = useFileOperations({
onSuccess: (operation, data) => {
- console.log(`[DeviceTest] ${operation} 成功:`, data)
+ // 成功回调
},
onError: (operation, error) => {
console.error(`[DeviceTest] ${operation} 失败:`, error)
@@ -271,7 +271,6 @@ const { storedValue: pathHistory } = useLocalStorage(
try {
const oldContent = localStorage.getItem(STORAGE_KEYS.DEVICE_TEST.FILE_CONTENT)
if (oldContent) {
- console.log('[DeviceTest] 清理旧的文件内容缓存')
localStorage.removeItem(STORAGE_KEYS.DEVICE_TEST.FILE_CONTENT)
}
} catch (error) {
diff --git a/web/src/components/FileSystem/components/DropdownItem.vue b/web/src/components/FileSystem/components/DropdownItem.vue
new file mode 100644
index 0000000..be96892
--- /dev/null
+++ b/web/src/components/FileSystem/components/DropdownItem.vue
@@ -0,0 +1,272 @@
+
+
+
+
+
+ {{ item.name }}
+
+
+
+
+
+
+
+
+
+ {{ error }}
+
+
+
+ 空文件夹
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web/src/components/FileSystem/components/FileEditorPanel.vue b/web/src/components/FileSystem/components/FileEditorPanel.vue
index f1f5bc0..187000c 100644
--- a/web/src/components/FileSystem/components/FileEditorPanel.vue
+++ b/web/src/components/FileSystem/components/FileEditorPanel.vue
@@ -129,7 +129,7 @@
-
-
-
+
+
diff --git a/web/src/components/FileSystem/components/Sidebar.vue b/web/src/components/FileSystem/components/Sidebar.vue
index 1ac27f1..d74e070 100644
--- a/web/src/components/FileSystem/components/Sidebar.vue
+++ b/web/src/components/FileSystem/components/Sidebar.vue
@@ -27,7 +27,7 @@
@drop="handleDrop($event, index)"
@dragend="handleDragEnd"
>
-
+
-
-
-
-
-
-
-
-
-
-
+
+
@@ -119,6 +113,7 @@
import { computed } from 'vue'
import { IconForward, IconHistory, IconRefresh, IconMenu, IconClose, IconRight, IconCopy } from '@arco-design/web-vue/es/icon'
import type { ToolbarConfig } from '@/types/file-system'
+import PathBreadcrumb from './PathBreadcrumb.vue'
// Props
interface Props {
@@ -134,21 +129,13 @@ interface Emits {
(e: 'refresh'): void
(e: 'exitZip'): void
(e: 'goToPath', path: string): void
+ (e: 'openFile', path: string): void
(e: 'navigateToZipDirectory', path: string): void
(e: 'showMessage', message: string, type: 'success' | 'error' | 'warning' | 'info'): void
}
const emit = defineEmits()
-// 将反斜杠转换为正斜杠显示
-const normalizedPath = computed(() => {
- return props.config.filePath?.replace(/\\/g, '/') || ''
-})
-
-const normalizedPathHistory = computed(() => {
- return props.config.pathHistory.map(path => path.replace(/\\/g, '/'))
-})
-
// 事件处理
const handlePathUpdate = (path: string) => {
emit('update:filePath', path)
@@ -162,6 +149,10 @@ const handleGoToPath = (path: string) => {
emit('goToPath', path)
}
+const handleOpenFile = (path: string) => {
+ emit('openFile', path)
+}
+
const handleRefresh = () => {
emit('refresh')
}
@@ -235,22 +226,34 @@ const handleCopyPath = async () => {
width: 100%;
}
-/* 覆盖 Arco 输入框 append 的默认 padding */
-.path-input-wrapper :deep(.arco-input-append) {
- padding: 0 !important;
+.path-breadcrumb-wrapper {
+ display: flex;
+ align-items: center;
+ flex: 1;
+ min-width: 200px;
+ gap: 8px;
+ padding: 4px 8px;
+ background: var(--color-fill-1);
+ border-radius: 4px;
+ border: 1px solid var(--color-border);
+ transition: border-color 0.2s;
+}
+
+.path-breadcrumb-wrapper:hover {
+ border-color: var(--color-border-2);
}
.copy-icon-wrapper {
display: flex;
align-items: center;
justify-content: center;
- padding: 0 8px;
- width: 100%;
- height: 100%;
+ padding: 4px 8px;
cursor: pointer;
color: var(--color-text-3);
font-size: 14px;
transition: all 0.2s;
+ border-radius: 4px;
+ flex-shrink: 0;
}
.copy-icon-wrapper:hover {
diff --git a/web/src/components/FileSystem/composables/useFavorites.ts b/web/src/components/FileSystem/composables/useFavorites.ts
index 4a6d85b..53721b8 100644
--- a/web/src/components/FileSystem/composables/useFavorites.ts
+++ b/web/src/components/FileSystem/composables/useFavorites.ts
@@ -25,7 +25,13 @@ export function useFavorites() {
try {
const stored = localStorage.getItem(STORAGE_KEYS.FAVORITE_FILES)
if (stored) {
- favorites.value = JSON.parse(stored)
+ const loaded = JSON.parse(stored) as FavoriteFile[]
+
+ // 数据迁移:将旧字段 is_dir 转换为 isDir
+ favorites.value = loaded.map(fav => ({
+ ...fav,
+ isDir: fav.isDir ?? (fav as any).is_dir ?? false
+ }))
}
} catch (error) {
console.error('加载收藏列表失败:', error)
@@ -62,10 +68,10 @@ export function useFavorites() {
}
/**
- * 标准化路径用于比较(处理正斜杠/反斜杠不一致)
+ * 标准化路径用于比较(后端已统一为 /,直接转小写)
*/
const normalizePath = (path: string): string => {
- return path.replace(/\\/g, '/').toLowerCase()
+ return path.toLowerCase()
}
/**
diff --git a/web/src/components/FileSystem/composables/usePathNavigation.ts b/web/src/components/FileSystem/composables/usePathNavigation.ts
index ee14aee..5f64341 100644
--- a/web/src/components/FileSystem/composables/usePathNavigation.ts
+++ b/web/src/components/FileSystem/composables/usePathNavigation.ts
@@ -5,6 +5,7 @@
import { ref, watch, computed } from 'vue'
import { STORAGE_KEYS } from '@/utils/constants'
+import { normalizePathSeparators } from '@/utils/pathHelpers'
import type { PathHistory } from '@/types/file-system'
export interface UsePathNavigationOptions {
@@ -18,6 +19,10 @@ export interface UsePathNavigationOptions {
const restoreLastPath = (): string | null => {
try {
const lastPath = localStorage.getItem(STORAGE_KEYS.FILESYSTEM.FILE_PATH)
+ if (lastPath) {
+ // 规范化旧路径(可能包含反斜杠)
+ return normalizePathSeparators(lastPath)
+ }
return lastPath
} catch (error) {
console.error('恢复路径失败:', error)
@@ -56,8 +61,8 @@ export function usePathNavigation(options: UsePathNavigationOptions = {}) {
if (!path || path === filePath.value) return
try {
- // 路径规范化
- const normalizedPath = normalizePath(path)
+ // 路径规范化(处理反斜杠并统一为正斜杠)
+ const normalizedPath = normalizePathSeparators(path)
filePath.value = normalizedPath
// 添加到历史记录
@@ -177,11 +182,10 @@ export function usePathNavigation(options: UsePathNavigationOptions = {}) {
}
/**
- * 路径规范化(统一分隔符)
+ * 路径规范化(统一为正斜杠)
*/
const normalizePath = (path: string): string => {
- if (!path) return ''
- return path.replace(/\\/g, '/')
+ return normalizePathSeparators(path)
}
/**
diff --git a/web/src/components/FileSystem/index-simple.vue b/web/src/components/FileSystem/index-simple.vue
deleted file mode 100644
index 059992b..0000000
--- a/web/src/components/FileSystem/index-simple.vue
+++ /dev/null
@@ -1,200 +0,0 @@
-
-
-
-
FileSystem Debug Info
-
filePath: {{ filePath }}
-
fileList length: {{ fileList.length }}
-
showSidebar: {{ showSidebar }}
-
hasSelectedFile: {{ hasSelectedFile }}
-
-
-
-
-
-
-
-
-
-
-
diff --git a/web/src/components/FileSystem/index.vue b/web/src/components/FileSystem/index.vue
index 88a91e9..b93d425 100644
--- a/web/src/components/FileSystem/index.vue
+++ b/web/src/components/FileSystem/index.vue
@@ -8,6 +8,7 @@
@refresh="handleRefresh"
@exit-zip="handleExitZip"
@go-to-path="handleGoToPath"
+ @open-file="handleOpenFile"
@navigate-to-zip-directory="handleNavigateToZipDirectory"
@show-message="handleShowMessage"
/>
@@ -118,7 +119,9 @@ import { useCommonPaths } from './composables/useCommonPaths'
// 导入工具函数
import { getFileName, sortFileList } from '@/utils/fileUtils'
+import { getParentPath } from '@/utils/pathHelpers'
import { isImageFile, isVideoFile, isAudioFile, isPdfFile, isHtmlFile, isMarkdownFile } from '@/utils/fileTypeHelpers'
+import { listDir } from '@/api/system'
import { STORAGE_KEYS, DEFAULTS, UI_TEXT, VALIDATION_RULES, FILE_EXTENSIONS } from '@/utils/constants'
// 导入类型
@@ -345,6 +348,35 @@ const handleGoToPath = async (path: string) => {
await navigate(path)
}
+const handleOpenFile = async (path: string) => {
+ // 检查是文件还是目录
+ try {
+ const parentPath = getParentPath(path)
+ const files = await listDir(parentPath)
+
+ // 后端已统一返回 / 路径,直接比较
+ const targetFile = files.find(f => f.path === path)
+
+ if (targetFile) {
+ if (targetFile.isDir) {
+ // 是目录,导航进入
+ await navigate(path)
+ } else {
+ // 是文件,选中并加载
+ selectedFileItem.value = targetFile
+ await loadFileContent(path)
+ }
+ } else {
+ // 未找到,尝试直接导航(可能是目录)
+ await navigate(path)
+ }
+ } catch (error) {
+ console.error('打开文件失败:', error)
+ // 如果出错,尝试直接导航
+ await navigate(path)
+ }
+}
+
const handleNavigateToZipDirectory = async (path: string) => {
// 暂时不处理 ZIP
}
@@ -359,7 +391,7 @@ const handleShowMessage = (message: string, type: 'success' | 'error' | 'warning
// 侧边栏事件
const handleOpenFavorite = async (file: FavoriteFile) => {
- if (file.is_dir) {
+ if (file.isDir) {
await navigate(file.path)
} else {
await selectFile(file.path)
@@ -416,7 +448,7 @@ const handleFileClick = async (file: FileItem) => {
*/
// 正常文件系统浏览
- if (file.is_dir) {
+ if (file.isDir) {
// 目录:使用 navigate 函数,确保历史记录正确更新
await navigate(file.path)
} else {
@@ -427,7 +459,7 @@ const handleFileClick = async (file: FileItem) => {
}
const handleFileDoubleClick = async (file: FileItem) => {
- if (file.is_dir) {
+ if (file.isDir) {
await navigate(file.path)
} else {
// 检查是否为 ZIP 文件 - 暂时禁用
@@ -535,7 +567,7 @@ const handleSaveEditing = async (oldPath: string, newName: string) => {
// 如果重命名的是当前打开的文件,先关闭编辑器和预览
if (selectedFileItem.value?.path === oldPath) {
// 如果是文件(不是文件夹),才需要关闭编辑器
- if (!selectedFileItem.value.is_dir) {
+ if (!selectedFileItem.value.isDir) {
// 清空编辑器内容
await clearContent()
@@ -580,7 +612,7 @@ const handleSaveEditing = async (oldPath: string, newName: string) => {
errorMsg.includes('being used by another process') ||
errorMsg.includes('被另一个进程占用')) {
errorMsg = '文件正在被其他程序使用,请先关闭该文件后重试'
- if (selectedFileItem.value?.is_dir) {
+ if (selectedFileItem.value?.isDir) {
errorMsg = '文件夹正在被其他程序使用(如文件管理器、终端等),请先关闭后重试'
}
} else if (errorMsg.includes('access is denied') ||
@@ -728,7 +760,7 @@ const handleCreateFile = async () => {
}
// 检查是否已存在同名文件
- const existingFile = fileList.value.find(f => f.name === fileName && !f.is_dir)
+ const existingFile = fileList.value.find(f => f.name === fileName && !f.isDir)
if (existingFile) {
Message.error(`文件 "${fileName}" 已存在`)
// 重新显示对话框
@@ -784,7 +816,7 @@ const handleCreateDir = async () => {
}
// 检查是否已存在同名文件夹
- const existingFolder = fileList.value.find(f => f.name === folderName && f.is_dir)
+ const existingFolder = fileList.value.find(f => f.name === folderName && f.isDir)
if (existingFolder) {
Message.error(`文件夹 "${folderName}" 已存在`)
// 重新显示对话框
@@ -822,7 +854,7 @@ const validateFileName = (name: string): boolean => {
*/
const handleDeleteFile = async (file: FileItem) => {
const targetPath = file.path
- const isDirectory = file.is_dir
+ const isDirectory = file.isDir
const fileName = file.name || targetPath
// 根据类型显示不同的确认信息
@@ -919,12 +951,12 @@ const isMediaPreviewable = (filename: string): boolean => {
}
const selectFile = async (path: string) => {
- // 标准化路径进行比较(处理正斜杠/反斜杠不一致的问题)
- const normalizedPath = path.replace(/\\/g, '/').toLowerCase()
+ // 后端已统一返回 / 路径,直接比较
+ const normalizedPath = path.toLowerCase()
// 尝试在当前文件列表中查找
const file = fileList.value.find(f => {
- const normalizedFilePath = f.path.replace(/\\/g, '/').toLowerCase()
+ const normalizedFilePath = f.path.toLowerCase()
return normalizedFilePath === normalizedPath
})
@@ -938,7 +970,7 @@ const selectFile = async (path: string) => {
selectedFileItem.value = {
path,
name: fileName,
- is_dir: false,
+ isDir: false,
size: 0,
mod_time: '',
is_favorite: isFavorite(path)
@@ -1055,7 +1087,7 @@ const loadZipDirectoryContents = async (zipPath: string, currentDir: string): Pr
const result = filtered.map((f: any) => ({
name: f.name,
path: f.path,
- is_dir: f.is_dir,
+ isDir: f.isDir,
size: f.size || 0,
mod_time: f.mod_time || '',
is_favorite: false
diff --git a/web/src/components/ThemeToggle.vue b/web/src/components/ThemeToggle.vue
index 1933030..02a4734 100644
--- a/web/src/components/ThemeToggle.vue
+++ b/web/src/components/ThemeToggle.vue
@@ -1,26 +1,21 @@
-
+
- {{ isDark ? '🌙' : '☀️' }}
+ {{ themeStore.isDark ? '🌙' : '☀️' }}
diff --git a/web/src/components/UpdateNotification.vue b/web/src/components/UpdateNotification.vue
index 898cf54..516b5de 100644
--- a/web/src/components/UpdateNotification.vue
+++ b/web/src/components/UpdateNotification.vue
@@ -3,8 +3,9 @@
diff --git a/web/src/composables/useTheme.ts b/web/src/composables/useTheme.ts
deleted file mode 100644
index 2c41339..0000000
--- a/web/src/composables/useTheme.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-import { ref, computed } from 'vue'
-
-type Theme = 'light' | 'dark'
-
-const THEME_STORAGE_KEY = 'app-theme'
-
-// 单例模式:全局共享主题状态
-const theme = ref('light')
-let systemThemeListener: (() => void) | null = null
-
-// 应用主题到 DOM
-const applyTheme = (newTheme: Theme) => {
- theme.value = newTheme
- if (newTheme === 'dark') {
- document.body.setAttribute('arco-theme', 'dark')
- } else {
- document.body.removeAttribute('arco-theme')
- }
- localStorage.setItem(THEME_STORAGE_KEY, newTheme)
-}
-
-// 初始化主题(只调用一次)
-const initTheme = () => {
- const savedTheme = localStorage.getItem(THEME_STORAGE_KEY) as Theme
- if (savedTheme && (savedTheme === 'light' || savedTheme === 'dark')) {
- applyTheme(savedTheme)
- } else {
- // 检测系统偏好
- if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
- applyTheme('dark')
- } else {
- applyTheme('light')
- }
- }
-
- // 监听系统主题变化
- if (window.matchMedia) {
- const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
- const handleChange = (e: MediaQueryListEvent) => {
- // 如果用户没有手动设置过主题,则跟随系统
- if (!localStorage.getItem(THEME_STORAGE_KEY)) {
- applyTheme(e.matches ? 'dark' : 'light')
- }
- }
- mediaQuery.addEventListener('change', handleChange)
- systemThemeListener = () => mediaQuery.removeEventListener('change', handleChange)
- }
-}
-
-export function useTheme() {
- // 切换主题
- const toggleTheme = () => {
- const newTheme: Theme = theme.value === 'light' ? 'dark' : 'light'
- applyTheme(newTheme)
- }
-
- // 设置为亮色主题
- const setLightTheme = () => {
- applyTheme('light')
- }
-
- // 设置为暗色主题
- const setDarkTheme = () => {
- applyTheme('dark')
- }
-
- return {
- theme: computed(() => theme.value),
- isDark: computed(() => theme.value === 'dark'),
- toggleTheme,
- setLightTheme,
- setDarkTheme,
- initTheme
- }
-}
-
-// 导出初始化函数(在 main.js 中使用)
-export { initTheme }
diff --git a/web/src/composables/useTimeout.ts b/web/src/composables/useTimeout.ts
new file mode 100644
index 0000000..6ef902a
--- /dev/null
+++ b/web/src/composables/useTimeout.ts
@@ -0,0 +1,117 @@
+/**
+ * 定时器管理 Hook
+ * 自动管理定时器生命周期,防止内存泄漏
+ *
+ * @module composables/useTimeout
+ * @description 提供类型安全的定时器管理,组件卸载时自动清理所有定时器
+ */
+
+import { ref, onUnmounted } from 'vue'
+
+export interface TimeoutOptions {
+ /**
+ * 是否在组件卸载时自动清理所有定时器
+ * @default true
+ */
+ autoCleanup?: boolean
+}
+
+/**
+ * 定时器管理 Hook
+ *
+ * @param options - 配置选项
+ * @returns 定时器管理方法
+ *
+ * @example
+ * ```typescript
+ * const { setTimeout, clearTimeout, clearAll } = useTimeout()
+ *
+ * // 设置延迟执行
+ * const timer = setTimeout(() => {
+ * console.log('延迟执行')
+ * }, 1000)
+ *
+ * // 清除特定定时器
+ * clearTimeout(timer)
+ *
+ * // 清除所有定时器
+ * clearAll()
+ * ```
+ */
+export function useTimeout(options: TimeoutOptions = {}) {
+ const { autoCleanup = true } = options
+
+ // 使用 Set 存储所有定时器 ID
+ const timers = ref>(new Set())
+
+ /**
+ * 设置定时器(自动管理生命周期)
+ * @param callback - 要执行的回调函数
+ * @param delay - 延迟时间(毫秒)
+ * @returns 定时器 ID
+ */
+ const setTimeout = (
+ callback: () => T,
+ delay: number
+ ): NodeJS.Timeout => {
+ const timer = window.setTimeout(() => {
+ try {
+ callback()
+ } finally {
+ // 执行完成后自动从集合中移除
+ timers.value.delete(timer)
+ }
+ }, delay)
+
+ // 添加到集合中
+ timers.value.add(timer)
+
+ return timer
+ }
+
+ /**
+ * 清除特定定时器
+ * @param timer - 要清除的定时器 ID
+ */
+ const clearTimeout = (timer: NodeJS.Timeout) => {
+ window.clearTimeout(timer)
+ timers.value.delete(timer)
+ }
+
+ /**
+ * 清除所有定时器
+ */
+ const clearAll = () => {
+ timers.value.forEach((timer) => {
+ window.clearTimeout(timer)
+ })
+ timers.value.clear()
+ }
+
+ /**
+ * 获取当前活跃的定时器数量
+ */
+ const getActiveCount = () => timers.value.size
+
+ // 组件卸载时自动清理
+ if (autoCleanup) {
+ onUnmounted(() => {
+ clearAll()
+ })
+ }
+
+ return {
+ setTimeout,
+ clearTimeout,
+ clearAll,
+ getActiveCount
+ }
+}
+
+/**
+ * 定时器管理 Hook 的别名
+ * 便于语义化使用(如延迟执行、防抖等场景)
+ */
+export const useDelay = useTimeout
+
+export default useTimeout
diff --git a/web/src/main.js b/web/src/main.js
index 6e8e8df..c914ea6 100644
--- a/web/src/main.js
+++ b/web/src/main.js
@@ -1,15 +1,19 @@
-import {createApp} from 'vue'
-import ArcoVue from '@arco-design/web-vue'
+import { createApp } from 'vue'
+import { createPinia } from 'pinia'
+// Arco Design 样式(组件按需自动引入)
import '@arco-design/web-vue/dist/arco.css'
import './style.css'
import App from './App.vue'
-import {initTheme} from './composables/useTheme'
+import { useThemeStore } from './stores/theme'
const app = createApp(App)
-app.use(ArcoVue)
+const pinia = createPinia()
-// 在应用挂载前初始化主题
-initTheme()
+app.use(pinia)
+
+// 在应用挂载前初始化主题(需要先初始化 Pinia)
+const themeStore = useThemeStore()
+themeStore.initTheme()
app.mount('#app')
diff --git a/web/src/stores/config.ts b/web/src/stores/config.ts
new file mode 100644
index 0000000..5ce8613
--- /dev/null
+++ b/web/src/stores/config.ts
@@ -0,0 +1,187 @@
+import { defineStore } from 'pinia'
+import { ref, computed } from 'vue'
+import { Message } from '@arco-design/web-vue'
+
+/**
+ * Tab 配置类型
+ */
+interface TabConfig {
+ key: string
+ title: string
+ visible: boolean
+ enabled: boolean
+}
+
+/**
+ * 应用配置类型
+ */
+interface AppConfig {
+ tabs: TabConfig[]
+ visibleTabs: string[]
+ defaultTab: string
+}
+
+/**
+ * 应用配置管理 Store
+ * 统一管理应用配置(标签页、默认页等)
+ */
+export const useConfigStore = defineStore('config', () => {
+ // ==================== 状态 ====================
+ const appConfig = ref({
+ tabs: [],
+ visibleTabs: [],
+ defaultTab: 'file-system'
+ })
+
+ const loading = ref(false)
+
+ // ==================== 计算属性 ====================
+ /**
+ * 可见 Tabs(根据配置动态生成)
+ */
+ const visibleTabs = computed(() => {
+ const tabs = appConfig.value.tabs
+
+ if (!tabs?.length) {
+ return [
+ { key: 'file-system', title: '文件管理' },
+ { key: 'db-cli', title: '数据库' }
+ ]
+ }
+
+ const { visibleTabs: order } = appConfig.value
+ return tabs
+ .filter(tab => tab.visible)
+ .sort((a, b) => order.indexOf(a.key) - order.indexOf(b.key))
+ })
+
+ /**
+ * 所有可用 Tabs
+ */
+ const allTabs = computed(() => appConfig.value.tabs)
+
+ /**
+ * 默认 Tab
+ */
+ const defaultTab = computed(() => appConfig.value.defaultTab)
+
+ // ==================== 核心方法 ====================
+ /**
+ * 加载配置
+ */
+ const loadConfig = async () => {
+ if (!window.go?.main?.App) {
+ console.warn('Wails 绑定未准备好,1秒后重试')
+ setTimeout(loadConfig, 1000)
+ return
+ }
+
+ loading.value = true
+
+ try {
+ const result = await window.go.main.App.GetAppConfig()
+ if (!result.success) throw new Error(result.message)
+
+ const { tabs = [], visibleTabs = [], defaultTab = 'file-system' } = result.data
+
+ appConfig.value = {
+ tabs: tabs.map(tab => ({ ...tab, visible: visibleTabs.includes(tab.key) })),
+ visibleTabs,
+ defaultTab
+ }
+ } catch (error) {
+ console.error('加载配置失败:', error)
+ useDefaultConfig()
+ } finally {
+ loading.value = false
+ }
+ }
+
+ /**
+ * 使用默认配置
+ */
+ const useDefaultConfig = () => {
+ appConfig.value = {
+ tabs: [
+ { key: 'file-system', title: '文件管理', visible: true, enabled: true },
+ { key: 'db-cli', title: '数据库', visible: true, enabled: true }
+ ],
+ visibleTabs: ['file-system', 'db-cli'],
+ defaultTab: 'file-system'
+ }
+ }
+
+ /**
+ * 保存配置
+ */
+ const saveConfig = async (config: AppConfig) => {
+ if (!window.go?.main?.App) {
+ Message.error('Wails 绑定未准备好')
+ throw new Error('Wails binding not ready')
+ }
+
+ loading.value = true
+
+ try {
+ const result = await window.go.main.App.SaveAppConfig({
+ tabs: config.tabs,
+ visibleTabs: config.visibleTabs,
+ defaultTab: config.defaultTab
+ })
+
+ if (!result.success) {
+ Message.error(result.message || '保存配置失败')
+ throw new Error(result.message)
+ }
+
+ // 更新本地配置
+ appConfig.value = {
+ tabs: [...config.tabs],
+ visibleTabs: [...config.visibleTabs],
+ defaultTab: config.defaultTab
+ }
+
+ Message.success('配置保存成功')
+ return true
+ } catch (error) {
+ console.error('保存配置失败:', error)
+ const message = error instanceof Error ? error.message : '保存配置失败'
+ Message.error('保存配置失败:' + message)
+ throw error
+ } finally {
+ loading.value = false
+ }
+ }
+
+ /**
+ * 检查 Tab 是否可见
+ */
+ const isTabVisible = (tabKey: string) => {
+ return appConfig.value.visibleTabs.includes(tabKey)
+ }
+
+ /**
+ * 获取 Tab 配置
+ */
+ const getTab = (tabKey: string) => {
+ return appConfig.value.tabs.find(tab => tab.key === tabKey)
+ }
+
+ // ==================== 返回 ====================
+ return {
+ // 状态
+ appConfig,
+ loading,
+
+ // 计算属性
+ visibleTabs,
+ allTabs,
+ defaultTab,
+
+ // 方法
+ loadConfig,
+ saveConfig,
+ isTabVisible,
+ getTab
+ }
+})
diff --git a/web/src/stores/theme.ts b/web/src/stores/theme.ts
new file mode 100644
index 0000000..f11b9d2
--- /dev/null
+++ b/web/src/stores/theme.ts
@@ -0,0 +1,117 @@
+import { defineStore } from 'pinia'
+import { ref, computed } from 'vue'
+
+type Theme = 'light' | 'dark'
+
+const THEME_STORAGE_KEY = 'app-theme'
+
+/**
+ * 主题管理 Store
+ * 统一管理应用主题(亮色/暗色)及相关逻辑
+ */
+export const useThemeStore = defineStore('theme', () => {
+ // ==================== 状态 ====================
+ const theme = ref('light')
+ let systemThemeListener: (() => void) | null = null
+
+ // ==================== 计算属性 ====================
+ const isDark = computed(() => theme.value === 'dark')
+ const isLight = computed(() => theme.value === 'light')
+ const tooltipText = computed(() =>
+ isDark.value ? '切换到亮色主题' : '切换到夜间主题'
+ )
+
+ // ==================== 核心方法 ====================
+ /**
+ * 应用主题到 DOM
+ */
+ const applyTheme = (newTheme: Theme) => {
+ theme.value = newTheme
+
+ // 更新 DOM 属性
+ const method = newTheme === 'dark' ? 'setAttribute' : 'removeAttribute'
+ document.body[method]('arco-theme', 'dark')
+
+ // 持久化
+ localStorage.setItem(THEME_STORAGE_KEY, newTheme)
+ }
+
+ /**
+ * 切换主题
+ */
+ const toggleTheme = () => {
+ const newTheme: Theme = theme.value === 'light' ? 'dark' : 'light'
+ applyTheme(newTheme)
+ }
+
+ /**
+ * 设置为亮色主题
+ */
+ const setLightTheme = () => {
+ applyTheme('light')
+ }
+
+ /**
+ * 设置为暗色主题
+ */
+ const setDarkTheme = () => {
+ applyTheme('dark')
+ }
+
+ /**
+ * 初始化主题(应用启动时调用)
+ */
+ const initTheme = () => {
+ // 加载保存的主题或使用系统偏好
+ const savedTheme = localStorage.getItem(THEME_STORAGE_KEY) as Theme
+ const isValidTheme = savedTheme === 'light' || savedTheme === 'dark'
+
+ if (isValidTheme) {
+ applyTheme(savedTheme)
+ } else {
+ const prefersDark = window.matchMedia?.('(prefers-color-scheme: dark)').matches
+ applyTheme(prefersDark ? 'dark' : 'light')
+ }
+
+ // 监听系统主题变化(仅在未手动设置时)
+ if (!window.matchMedia) return
+
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
+ const handleChange = (e: MediaQueryListEvent) => {
+ if (!localStorage.getItem(THEME_STORAGE_KEY)) {
+ applyTheme(e.matches ? 'dark' : 'light')
+ }
+ }
+
+ mediaQuery.addEventListener('change', handleChange)
+ systemThemeListener = () => mediaQuery.removeEventListener('change', handleChange)
+ }
+
+ /**
+ * 清理系统主题监听器
+ */
+ const removeSystemThemeListener = () => {
+ if (systemThemeListener) {
+ systemThemeListener()
+ systemThemeListener = null
+ }
+ }
+
+ // ==================== 返回 ====================
+ return {
+ // 状态
+ theme,
+
+ // 计算属性
+ isDark,
+ isLight,
+ tooltipText,
+
+ // 方法
+ toggleTheme,
+ setLightTheme,
+ setDarkTheme,
+ initTheme,
+ removeSystemThemeListener
+ }
+})
diff --git a/web/src/stores/update.ts b/web/src/stores/update.ts
new file mode 100644
index 0000000..abcd006
--- /dev/null
+++ b/web/src/stores/update.ts
@@ -0,0 +1,288 @@
+import { defineStore } from 'pinia'
+import { ref, reactive } from 'vue'
+import { Message } from '@arco-design/web-vue'
+
+/**
+ * 更新管理 Store
+ * 统一管理版本检查、下载、安装等更新相关逻辑
+ */
+export const useUpdateStore = defineStore('update', () => {
+ // ==================== 状态 ====================
+ const updateInfo = ref(null)
+ const showUpdate = ref(false)
+ const checking = ref(false)
+ const downloading = ref(false)
+ const installing = ref(false)
+ const downloadProgress = ref(0)
+ const downloadStatus = ref<'active' | 'exception' | 'success'>('active')
+ const progressInfo = ref({
+ speed: 0,
+ downloaded: 0,
+ total: 0
+ })
+
+ // 节流:防止过度更新
+ let lastUpdateTime = 0
+ const UPDATE_THROTTLE = 100 // 100ms 最小更新间隔
+
+ // 最小显示时间:确保进度条至少显示 5 秒
+ let downloadStartTime = 0
+ const MIN_DISPLAY_TIME = 5000 // 5 秒最小显示时间
+
+ // ==================== 工具函数 ====================
+ const parseEventData = (event: unknown) => {
+ try {
+ return typeof event === 'string' ? JSON.parse(event) : (event as Record)
+ } catch {
+ return {}
+ }
+ }
+
+ const formatFileSize = (bytes: number): string => {
+ if (!bytes || bytes < 0) return '0 B'
+ const k = 1024
+ const sizes = ['B', 'KB', 'MB', 'GB']
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
+ return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]
+ }
+
+ const formatSpeed = (bytesPerSecond: number): string => {
+ return formatFileSize(bytesPerSecond) + '/s'
+ }
+
+ // ==================== 核心方法 ====================
+ /**
+ * 检查更新
+ * @param silent 是否静默模式(不显示消息)
+ */
+ const checkForUpdates = async (silent = false) => {
+ if (checking.value || !window.go?.main?.App) return
+
+ checking.value = true
+
+ try {
+ const configResult = await window.go.main.App.GetUpdateConfig()
+ if (!configResult.success) return
+
+ const { auto_check_enabled } = configResult.data || {}
+ if (!auto_check_enabled) return
+
+ const result = await window.go.main.App.CheckUpdate()
+ if (result.success && result.data?.has_update) {
+ updateInfo.value = result.data
+ showUpdate.value = true
+
+ if (!silent) {
+ Message.success('发现新版本!')
+ }
+ } else if (!silent) {
+ Message.success('已是最新版本')
+ }
+ } catch (error) {
+ if (!silent) {
+ console.error('检查更新失败:', error)
+ Message.error('检查更新失败:' + (error as Error).message)
+ }
+ } finally {
+ checking.value = false
+ }
+ }
+
+ /**
+ * 下载更新
+ */
+ const downloadUpdate = async () => {
+ const url = updateInfo.value?.download_url
+ if (!url) {
+ Message.warning('下载地址不存在')
+ return
+ }
+
+ // 记录开始时间
+ downloadStartTime = Date.now()
+
+ // 重置下载状态
+ downloading.value = true
+ downloadProgress.value = 1 // 设置为 1 而不是 0,确保进度条显示
+ downloadStatus.value = 'active'
+ progressInfo.value = { speed: 0, downloaded: 0, total: 0 }
+
+ try {
+ const result = await window.go.main.App.DownloadUpdate(url)
+ if (!result.success) {
+ downloadStatus.value = 'exception'
+ downloading.value = false
+ Message.error(result.message || '下载启动失败')
+ return
+ }
+ Message.success('下载请求已发送,等待后端发送进度事件...')
+ } catch (error) {
+ console.error('下载失败:', error)
+ downloadStatus.value = 'exception'
+ downloading.value = false
+ Message.error('下载失败:' + (error as Error).message)
+ }
+ }
+
+ /**
+ * 安装更新
+ */
+ const installUpdate = async (filePath: string) => {
+ if (!filePath) {
+ Message.warning('请先下载更新包')
+ return
+ }
+
+ installing.value = true
+
+ try {
+ const result = await window.go.main.App.InstallUpdate(filePath, true)
+ if (result.success) {
+ Message.success({
+ content: '安装成功!应用将在几秒后重启...',
+ duration: 3000
+ })
+ return
+ }
+ Message.error(result.message || '安装失败')
+ } catch (error) {
+ Message.error('安装失败:' + (error as Error).message)
+ } finally {
+ installing.value = false
+ }
+ }
+
+ /**
+ * 下载进度处理
+ */
+ const onDownloadProgress = (event: unknown) => {
+ const now = Date.now()
+ if (now - lastUpdateTime < UPDATE_THROTTLE) {
+ return
+ }
+
+ lastUpdateTime = now
+ const data = parseEventData(event)
+
+ progressInfo.value = {
+ speed: (data.speed as number) || 0,
+ downloaded: (data.downloaded as number) || 0,
+ total: (data.total as number) || 0
+ }
+
+ const rawProgress = Number(data.progress) || 0
+ const safeProgress = Math.min(100, Math.max(0, Math.round(rawProgress)))
+ downloadProgress.value = safeProgress
+ }
+
+ /**
+ * 下载完成处理
+ */
+ const onDownloadComplete = (event: unknown) => {
+ const data = parseEventData(event)
+
+ // 错误处理
+ if (data.error) {
+ console.error('下载失败:', data.error)
+ downloadStatus.value = 'exception'
+ downloading.value = false
+ Message.error('下载失败:' + data.error)
+ return
+ }
+
+ // 数据验证
+ if (!data.success || !data.file_path) {
+ console.error('下载数据不完整:', data)
+ downloadStatus.value = 'exception'
+ downloading.value = false
+ Message.error('下载完成但数据不完整')
+ return
+ }
+
+ // 完成下载
+ downloadProgress.value = 100
+ downloadStatus.value = 'success'
+ const fileSize = (data.file_size as number) || 0
+ progressInfo.value = {
+ speed: 0,
+ downloaded: fileSize,
+ total: fileSize
+ }
+
+ // 计算已经显示的时间
+ const elapsed = Date.now() - downloadStartTime
+ const remainingTime = Math.max(0, MIN_DISPLAY_TIME - elapsed)
+
+ // 确保进度条至少显示 3 秒
+ setTimeout(() => {
+ downloading.value = false // 安装前才关闭下载状态
+ installUpdate(data.file_path as string)
+ }, remainingTime)
+ }
+
+ /**
+ * 设置事件监听
+ */
+ const setupEventListeners = () => {
+ if (!window.runtime?.EventsOn) {
+ return
+ }
+
+ window.runtime.EventsOn('download-progress', onDownloadProgress)
+ window.runtime.EventsOn('download-complete', onDownloadComplete)
+ }
+
+ /**
+ * 移除事件监听
+ */
+ const removeEventListeners = () => {
+ if (!window.runtime?.EventsOff) {
+ return
+ }
+
+ window.runtime.EventsOff('download-progress')
+ window.runtime.EventsOff('download-complete')
+ }
+
+ /**
+ * 关闭更新提示
+ */
+ const closeUpdateNotification = () => {
+ showUpdate.value = false
+ }
+
+ // ==================== 返回 ====================
+ return {
+ // 状态
+ updateInfo,
+ showUpdate,
+ checking,
+ downloading,
+ installing,
+ downloadProgress,
+ downloadStatus,
+ progressInfo,
+
+ // 方法
+ checkForUpdates,
+ downloadUpdate,
+ installUpdate,
+ setupEventListeners,
+ removeEventListeners,
+ closeUpdateNotification,
+ formatFileSize,
+ formatSpeed
+ }
+})
+
+// ==================== 类型定义 ====================
+interface UpdateInfo {
+ has_update: boolean
+ current_version: string
+ latest_version: string
+ download_url: string
+ changelog: string
+ force_update: boolean
+ release_date: string
+ file_size: number
+}
diff --git a/web/src/style.css b/web/src/style.css
index 69b384a..834672e 100644
--- a/web/src/style.css
+++ b/web/src/style.css
@@ -48,4 +48,39 @@ body {
* {
scrollbar-width: thin;
scrollbar-color: var(--color-border-2, rgba(0, 0, 0, 0.1)) transparent;
+}
+
+/* Markdown 标题锚点链接样式 */
+.heading {
+ position: relative;
+ scroll-margin-top: 20px; /* 锚点跳转时的顶部偏移 */
+}
+
+.heading-anchor {
+ opacity: 0;
+ margin-left: 8px;
+ color: rgb(var(--primary-6));
+ text-decoration: none;
+ font-size: 0.8em;
+ font-weight: normal;
+ transition: opacity 0.2s ease;
+ cursor: pointer;
+}
+
+.heading:hover .heading-anchor {
+ opacity: 1;
+}
+
+.heading-anchor:hover {
+ text-decoration: none;
+}
+
+.heading-anchor:focus {
+ opacity: 1;
+ outline: none;
+}
+
+/* 平滑滚动 */
+html {
+ scroll-behavior: smooth;
}
\ No newline at end of file
diff --git a/web/src/types/file-system.ts b/web/src/types/file-system.ts
index e30efeb..c89b2e2 100644
--- a/web/src/types/file-system.ts
+++ b/web/src/types/file-system.ts
@@ -14,7 +14,7 @@ export interface FileItem {
/** 文件大小(字节) */
size: number
/** 是否为目录 */
- is_dir: boolean
+ isDir: boolean
/** 修改时间 */
modified_time?: string
/** 是否被收藏(运行时属性) */
diff --git a/web/src/utils/codeMirrorLoader.js b/web/src/utils/codeMirrorLoader.js
new file mode 100644
index 0000000..94121f5
--- /dev/null
+++ b/web/src/utils/codeMirrorLoader.js
@@ -0,0 +1,138 @@
+/**
+ * CodeMirror 语言包动态加载器
+ * 按需加载语言支持,减少初始包体积和构建时间
+ */
+
+const languageCache = new Map()
+
+/**
+ * 动态加载 CodeMirror 语言扩展
+ * @param {string} language - 语言名称
+ * @returns {Promise} CodeMirror 语言扩展
+ */
+export async function loadLanguageExtension(language) {
+ if (languageCache.has(language)) {
+ return languageCache.get(language)
+ }
+
+ try {
+ let extension
+
+ // 现代语言包(直接返回扩展)
+ const modernLangs = {
+ javascript: ['@codemirror/lang-javascript', 'javascript', { jsx: true }],
+ typescript: ['@codemirror/lang-javascript', 'javascript', { jsx: true }],
+ json: ['@codemirror/lang-json', 'json'],
+ yaml: ['@codemirror/lang-yaml', 'yaml'],
+ html: ['@codemirror/lang-html', 'html'],
+ css: ['@codemirror/lang-css', 'css'],
+ cpp: ['@codemirror/lang-cpp', 'cpp'],
+ c: ['@codemirror/lang-cpp', 'cpp'],
+ rust: ['@codemirror/lang-rust', 'rust'],
+ go: ['@codemirror/lang-go', 'go'],
+ python: ['@codemirror/lang-python', 'python'],
+ php: ['@codemirror/lang-php', 'php'],
+ sql: ['@codemirror/lang-sql', 'sql'],
+ markdown: ['@codemirror/lang-markdown', 'markdown'],
+ java: ['@codemirror/lang-java', 'java']
+ }
+
+ if (modernLangs[language]) {
+ const [path, method, ...args] = modernLangs[language]
+ const mod = await import(path)
+ extension = mod[method](...args)
+ } else {
+ // Legacy 语言包(需要 StreamLanguage 包装)
+ const legacyLangs = {
+ ruby: ['@codemirror/legacy-modes/mode/ruby', 'ruby'],
+ shell: ['@codemirror/legacy-modes/mode/shell', 'shell'],
+ bash: ['@codemirror/legacy-modes/mode/shell', 'shell'],
+ kotlin: ['@codemirror/legacy-modes/mode/clike', 'kotlin'],
+ csharp: ['@codemirror/legacy-modes/mode/clike', 'csharp'],
+ swift: ['@codemirror/legacy-modes/mode/swift', 'swift'],
+ r: ['@codemirror/legacy-modes/mode/r', 'r'],
+ perl: ['@codemirror/legacy-modes/mode/perl', 'perl'],
+ latex: ['@codemirror/legacy-modes/mode/stex', 'stex'],
+ tex: ['@codemirror/legacy-modes/mode/stex', 'stex'],
+ xml: ['@codemirror/legacy-modes/mode/xml', 'xml'],
+ svg: ['@codemirror/legacy-modes/mode/xml', 'xml'],
+ properties: ['@codemirror/legacy-modes/mode/properties', 'properties'],
+ ini: ['@codemirror/legacy-modes/mode/properties', 'properties'],
+ cfg: ['@codemirror/legacy-modes/mode/properties', 'properties'],
+ conf: ['@codemirror/legacy-modes/mode/properties', 'properties'],
+ dockerfile: ['@codemirror/legacy-modes/mode/dockerfile', 'dockerFile'],
+ matlab: ['@codemirror/legacy-modes/mode/octave', 'octave'],
+ octave: ['@codemirror/legacy-modes/mode/octave', 'octave']
+ }
+
+ if (legacyLangs[language]) {
+ const [path, method] = legacyLangs[language]
+ const [modeMod, { StreamLanguage }] = await Promise.all([
+ import(path),
+ import('@codemirror/language')
+ ])
+ extension = StreamLanguage.define(modeMod[method])
+ }
+ }
+
+ if (extension) {
+ languageCache.set(language, extension)
+ }
+ return extension
+ } catch (error) {
+ console.error(`[CodeMirror] 加载语言包失败: ${language}`, error)
+ return null
+ }
+}
+
+/**
+ * 根据文件扩展名获取语言名称
+ * @param {string} extension - 文件扩展名
+ * @returns {string} 语言名称
+ */
+export function getLanguageFromExtension(extension) {
+ const ext = extension.toLowerCase()
+
+ const langMap = {
+ js: 'javascript', jsx: 'javascript', mjs: 'javascript', cjs: 'javascript',
+ ts: 'typescript', tsx: 'typescript',
+ json: 'json',
+ yaml: 'yaml', yml: 'yaml',
+ xml: 'xml', xhtml: 'xml', svg: 'svg',
+ html: 'html', htm: 'html',
+ css: 'css', scss: 'css', sass: 'css', less: 'css',
+ cpp: 'cpp', c: 'c', cc: 'cpp', cxx: 'cpp', h: 'cpp', hpp: 'cpp', hxx: 'cpp',
+ rust: 'rust', rs: 'rust',
+ go: 'go',
+ python: 'python', py: 'python', pyw: 'python',
+ php: 'php',
+ ruby: 'ruby', rb: 'ruby',
+ perl: 'perl', pl: 'perl', pm: 'perl',
+ shell: 'shell', sh: 'shell', bash: 'shell', zsh: 'shell',
+ bat: 'shell', cmd: 'shell', ps1: 'shell',
+ sql: 'sql',
+ java: 'java',
+ kotlin: 'kotlin', kt: 'kotlin', kts: 'kotlin',
+ csharp: 'csharp', cs: 'csharp', csx: 'csharp',
+ swift: 'swift',
+ markdown: 'markdown', md: 'markdown',
+ r: 'r',
+ matlab: 'matlab', m: 'matlab',
+ latex: 'latex', tex: 'latex',
+ dockerfile: 'dockerfile',
+ makefile: 'makefile', mk: 'makefile', gnumakefile: 'makefile',
+ ini: 'ini', cfg: 'ini', conf: 'ini', properties: 'properties',
+ gitignore: 'gitignore',
+ txt: 'text', text: 'text', log: 'text', csv: 'text'
+ }
+
+ return langMap[ext] || 'text'
+}
+
+/**
+ * 预加载常用语言包
+ * 用于在应用启动时预热缓存
+ */
+export async function preloadCommonLanguages() {
+ await Promise.all(['javascript', 'json', 'markdown', 'python', 'sql'].map(loadLanguageExtension))
+}
diff --git a/web/src/utils/fileUtils.js b/web/src/utils/fileUtils.js
index f8b5379..ab5e2e2 100644
--- a/web/src/utils/fileUtils.js
+++ b/web/src/utils/fileUtils.js
@@ -5,6 +5,7 @@
* @description 提供文件相关的通用工具函数,避免代码重复
*/
+import { normalizePathSeparators } from './pathHelpers.js'
import { FILE_ICON_MAP, FILE_ICONS, BYTE_UNITS, FILE_SIZE_FORMAT, FILE_EXTENSIONS } from './constants'
/**
@@ -46,11 +47,8 @@ export function formatBytes(bytes) {
export function getFileName(path) {
if (!path) return ''
- // 统一分隔符为正斜杠
- const normalizedPath = path.replace(/\\/g, '/')
-
- // 分割路径并取最后一部分
- const parts = normalizedPath.split('/')
+ // 后端已统一返回 / 路径,直接分割
+ const parts = path.split('/')
return parts[parts.length - 1] || path
}
@@ -157,7 +155,7 @@ export function isPdfFile(path) {
*/
export function normalizeFilePath(path, encode = false) {
if (!path) return ''
- const normalized = path.replace(/\\/g, '/')
+ const normalized = normalizePathSeparators(path)
// 如果需要编码,则使用 encodeURIComponent
if (encode) {
@@ -327,18 +325,17 @@ export function sanitizeFileName(filename, replacement = '_') {
* @returns {Array} 排序后的文件列表
*
* @example
- * sortFileList([{name: 'b.txt', is_dir: false}, {name: 'a', is_dir: true}])
- * // [{name: 'a', is_dir: true}, {name: 'b.txt', is_dir: false}]
+ * sortFileList([{name: 'b.txt', isDir: false}, {name: 'a', isDir: true}])
+ * // [{name: 'a', isDir: true}, {name: 'b.txt', isDir: false}]
*/
export function sortFileList(fileList) {
if (!Array.isArray(fileList)) return fileList
return fileList.sort((a, b) => {
- // 如果都是目录或都是文件,按名称排序
- if (a.is_dir === b.is_dir) {
+ // API 层已转换,直接使用 isDir
+ if (a.isDir === b.isDir) {
return a.name.localeCompare(b.name)
}
- // 目录优先
- return a.is_dir ? -1 : 1
+ return a.isDir ? -1 : 1
})
}
diff --git a/web/src/utils/markedExtensions.ts b/web/src/utils/markedExtensions.ts
index 737c5b9..ae9473d 100644
--- a/web/src/utils/markedExtensions.ts
+++ b/web/src/utils/markedExtensions.ts
@@ -1,35 +1,67 @@
import { marked } from 'marked'
import hljs from 'highlight.js'
-import mermaid from 'mermaid'
-
-// 导入 highlight.js 核心和两种主题样式
import 'highlight.js/lib/common'
import 'highlight.js/styles/github-dark.css'
import 'highlight.js/styles/github.css'
-// Mermaid 初始化
-mermaid.initialize({ startOnLoad: false, theme: 'default', securityLevel: 'loose' })
+let mermaidInstance: typeof import('mermaid').default | null = null
+
+async function loadMermaid() {
+ if (mermaidInstance) return mermaidInstance
+
+ try {
+ const mermaid = await import('mermaid')
+ mermaid.default.initialize({
+ startOnLoad: false,
+ theme: 'default',
+ securityLevel: 'loose'
+ })
+ mermaidInstance = mermaid.default
+ return mermaidInstance
+ } catch {
+ return null
+ }
+}
-// 自定义 renderer
const renderer = new marked.Renderer()
renderer.code = function(token: any) {
- // Mermaid 代码块
if (token.lang === 'mermaid') {
return `${token.text}`
}
- // 普通代码块 - 使用 highlight.js 高亮
const lang = hljs.getLanguage(token.lang) ? token.lang : 'plaintext'
const highlighted = hljs.highlight(token.text, { language: lang }).value
return `${highlighted}
`
}
+renderer.heading = function(token: any) {
+ const raw = token.raw || ''
+ const depth = token.depth || 1
+ const text = token.text || ''
+
+ const id = raw
+ .toLowerCase()
+ .replace(/[^\u4e00-\u9fa5a-z0-9\s-]/g, '')
+ .trim()
+ .replace(/\s+/g, '-')
+ .replace(/-+/g, '-')
+ .replace(/^-+|-+$/g, '') || `heading-${Math.random().toString(36).slice(2, 11)}`
+
+ return `
+ ${text}#
+ `
+}
+
marked.use({ renderer, breaks: true, gfm: true })
export { marked }
+
export async function renderMermaidDiagrams() {
- await mermaid.run()
+ const mermaid = await loadMermaid()
+ if (mermaid) {
+ await mermaid.run()
+ }
}
diff --git a/web/src/utils/pathHelpers.js b/web/src/utils/pathHelpers.js
index e36d997..9e4f115 100644
--- a/web/src/utils/pathHelpers.js
+++ b/web/src/utils/pathHelpers.js
@@ -50,18 +50,33 @@ export const getFileName = (path) => {
* @example
* getParentPath('C:\\Users\\file.txt') // 'C:\\Users'
* getParentPath('/home/user/file.txt') // '/home/user'
+ * getParentPath('E:/file.txt') // 'E:/'
*/
export const getParentPath = (path) => {
if (!path) return ''
- // 查找最后一个分隔符的位置
- const lastSep = Math.max(
- path.lastIndexOf('/'),
- path.lastIndexOf('\\')
- )
+ // 规范化路径分隔符
+ const normalizedPath = path.replace(/\\/g, '/')
- if (lastSep <= 0) return path
- return path.substring(0, lastSep)
+ // 查找最后一个分隔符的位置
+ const lastSep = normalizedPath.lastIndexOf('/')
+
+ if (lastSep <= 0) {
+ // 没有分隔符或分隔符在开头,返回根目录(对于盘符情况)
+ if (/^[A-Za-z]:$/.test(normalizedPath)) {
+ return normalizedPath + '/' // E: 转换为 E:/
+ }
+ return normalizedPath
+ }
+
+ const parentPath = normalizedPath.substring(0, lastSep)
+
+ // 特殊处理:如果是盘符根目录下的文件(E:/file.txt -> E:/)
+ if (/^[A-Za-z]:$/.test(parentPath)) {
+ return parentPath + '/' // 确保根目录带斜杠
+ }
+
+ return parentPath || '/'
}
/**
diff --git a/web/src/views/db-cli/components/ResultPanel.vue b/web/src/views/db-cli/components/ResultPanel.vue
index fa92795..1f79451 100644
--- a/web/src/views/db-cli/components/ResultPanel.vue
+++ b/web/src/views/db-cli/components/ResultPanel.vue
@@ -747,14 +747,6 @@ const updateResultTableHeight = () => {
const maxHeight = availableHeight > 0 ? availableHeight : 400
tableScrollHeight.value = Math.max(minHeight, maxHeight)
-
- console.log('表格高度计算:', {
- containerHeight,
- paginationHeight,
- tableHeaderHeight,
- availableHeight,
- final: tableScrollHeight.value
- })
}, 150)
}
diff --git a/web/src/views/db-cli/composables/useStructureState.ts b/web/src/views/db-cli/composables/useStructureState.ts
index bd9963a..d9a6e01 100644
--- a/web/src/views/db-cli/composables/useStructureState.ts
+++ b/web/src/views/db-cli/composables/useStructureState.ts
@@ -34,11 +34,8 @@ export function useStructureState() {
dbType: 'mysql' | 'mongo' | 'redis',
nodeType: string
) => {
- console.log('🟢 loadStructure 开始:', { connectionId, database, tableName, dbType, nodeType })
-
// 对于连接和数据库节点,不需要加载结构
if (nodeType === 'connection' || nodeType === 'database') {
- console.log('🟡 跳过:节点类型为连接或数据库')
structureInfo.value = {
connectionId,
database,
@@ -52,7 +49,6 @@ export function useStructureState() {
// 如果没有表名,不加载(但保留 structureInfo 用于显示提示)
if (!tableName) {
- console.log('🟡 跳过:表名为空')
structureInfo.value = {
connectionId,
database,
@@ -78,14 +74,8 @@ export function useStructureState() {
tableName
)
- console.log('表结构加载成功:', { connectionId, database, tableName, result })
- console.log('返回数据类型:', typeof result)
- console.log('返回数据 keys:', result ? Object.keys(result) : 'null')
- console.log('返回数据 type 字段:', result?.type)
- console.log('返回数据 columns 字段:', result?.columns)
-
structureData.value = result
-
+
// 确保 structureInfo 也设置了
structureInfo.value = {
connectionId,
@@ -94,20 +84,6 @@ export function useStructureState() {
dbType,
nodeType
}
-
- // 确保 structureInfo 也设置了
- structureInfo.value = {
- connectionId,
- database,
- tableName,
- dbType,
- nodeType
- }
-
- console.log('✅ 设置完成 - structureData:', structureData.value)
- console.log('✅ 设置完成 - structureInfo:', structureInfo.value)
- console.log('✅ structureData 是否为 null:', structureData.value === null)
- console.log('✅ structureInfo 是否为 null:', structureInfo.value === null)
} catch (error: unknown) {
console.error('加载表结构失败:', error)
const errorMessage = error instanceof Error ? error.message : '加载表结构失败'
diff --git a/web/src/wailsjs/wailsjs/go/main/App.d.ts b/web/src/wailsjs/wailsjs/go/main/App.d.ts
new file mode 100644
index 0000000..1d8fdfa
--- /dev/null
+++ b/web/src/wailsjs/wailsjs/go/main/App.d.ts
@@ -0,0 +1,131 @@
+// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
+// This file is automatically generated. DO NOT EDIT
+import {filesystem} from '../models';
+import {main} from '../models';
+import {api} from '../models';
+
+export function CheckUpdate():Promise>;
+
+export function ClearCache():Promise;
+
+export function CreateDir(arg1:string):Promise;
+
+export function CreateFile(arg1:string):Promise;
+
+export function DeleteDbConnection(arg1:number):Promise;
+
+export function DeletePath(arg1:string):Promise;
+
+export function DeletePermanently(arg1:string):Promise;
+
+export function DeleteResultHistory(arg1:number):Promise;
+
+export function DetectFileTypeByContent(arg1:string):Promise>;
+
+export function DownloadUpdate(arg1:string):Promise>;
+
+export function EmptyRecycleBin():Promise;
+
+export function ExecuteSQL(arg1:number,arg2:string,arg3:string):Promise>;
+
+export function ExtractFileFromZip(arg1:string,arg2:string):Promise;
+
+export function ExtractFileFromZipToTemp(arg1:string,arg2:string):Promise;
+
+export function GetAppConfig():Promise>;
+
+export function GetAuditLogs(arg1:number):Promise>>;
+
+export function GetCPUInfo():Promise>;
+
+export function GetCommonPaths():Promise>;
+
+export function GetCurrentVersion():Promise>;
+
+export function GetDatabases(arg1:number):Promise>;
+
+export function GetDiskInfo():Promise>>;
+
+export function GetEnvVars():Promise>;
+
+export function GetFileInfo(arg1:string):Promise>;
+
+export function GetFileServerURL():Promise;
+
+export function GetIndexes(arg1:number,arg2:string,arg3:string):Promise>>;
+
+export function GetMemoryInfo():Promise>;
+
+export function GetRecycleBinEntries():Promise>>;
+
+export function GetResultHistory(arg1:any,arg2:string,arg3:number,arg4:number):Promise>;
+
+export function GetResultHistoryByID(arg1:number):Promise>;
+
+export function GetSystemInfo():Promise>;
+
+export function GetTableStructure(arg1:number,arg2:string,arg3:string):Promise>;
+
+export function GetTables(arg1:number,arg2:string):Promise>;
+
+export function GetUpdateConfig():Promise>;
+
+export function GetZipFileInfo(arg1:string,arg2:string):Promise>;
+
+export function Greet(arg1:string):Promise;
+
+export function InstallUpdate(arg1:string,arg2:boolean):Promise>;
+
+export function InstallUpdateWithHash(arg1:string,arg2:boolean,arg3:string,arg4:string):Promise>;
+
+export function ListDbConnections():Promise>>;
+
+export function ListDir(arg1:string):Promise>>;
+
+export function ListSqlTabs():Promise>>;
+
+export function ListZipContents(arg1:string):Promise>>;
+
+export function OpenPath(arg1:string):Promise;
+
+export function PreviewTableStructure(arg1:number,arg2:string,arg3:string,arg4:Record):Promise>;
+
+export function QueryUsers(arg1:string,arg2:number,arg3:number,arg4:number,arg5:number,arg6:number,arg7:string,arg8:string):Promise>;
+
+export function ReadFile(arg1:string):Promise;
+
+export function Reload():Promise;
+
+export function RenamePath(arg1:main.RenamePathRequest):Promise;
+
+export function ResolveShortcut(arg1:string):Promise>;
+
+export function RestoreFromRecycleBin(arg1:string):Promise;
+
+export function SaveAppConfig(arg1:main.SaveAppConfigRequest):Promise>;
+
+export function SaveDbConnection(arg1:api.SaveConnectionRequest):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;
+
+export function SetUpdateConfig(arg1:boolean,arg2:number,arg3:string):Promise>;
+
+export function TestDbConnection(arg1:number):Promise;
+
+export function TestDbConnectionWithParams(arg1:api.TestConnectionRequest):Promise;
+
+export function UpdateTableStructure(arg1:number,arg2:string,arg3:string,arg4:Record):Promise>;
+
+export function VerifyUpdateFile(arg1:string,arg2:string,arg3:string):Promise>;
+
+export function WindowClose():Promise;
+
+export function WindowIsMaximized():Promise;
+
+export function WindowMaximize():Promise;
+
+export function WindowMinimize():Promise;
+
+export function WriteFile(arg1:main.WriteFileRequest):Promise;
diff --git a/web/src/wailsjs/wailsjs/go/main/App.js b/web/src/wailsjs/wailsjs/go/main/App.js
new file mode 100644
index 0000000..bd6137b
--- /dev/null
+++ b/web/src/wailsjs/wailsjs/go/main/App.js
@@ -0,0 +1,255 @@
+// @ts-check
+// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
+// This file is automatically generated. DO NOT EDIT
+
+export function CheckUpdate() {
+ return window['go']['main']['App']['CheckUpdate']();
+}
+
+export function ClearCache() {
+ return window['go']['main']['App']['ClearCache']();
+}
+
+export function CreateDir(arg1) {
+ return window['go']['main']['App']['CreateDir'](arg1);
+}
+
+export function CreateFile(arg1) {
+ return window['go']['main']['App']['CreateFile'](arg1);
+}
+
+export function DeleteDbConnection(arg1) {
+ return window['go']['main']['App']['DeleteDbConnection'](arg1);
+}
+
+export function DeletePath(arg1) {
+ return window['go']['main']['App']['DeletePath'](arg1);
+}
+
+export function DeletePermanently(arg1) {
+ return window['go']['main']['App']['DeletePermanently'](arg1);
+}
+
+export function DeleteResultHistory(arg1) {
+ return window['go']['main']['App']['DeleteResultHistory'](arg1);
+}
+
+export function DetectFileTypeByContent(arg1) {
+ return window['go']['main']['App']['DetectFileTypeByContent'](arg1);
+}
+
+export function DownloadUpdate(arg1) {
+ return window['go']['main']['App']['DownloadUpdate'](arg1);
+}
+
+export function EmptyRecycleBin() {
+ return window['go']['main']['App']['EmptyRecycleBin']();
+}
+
+export function ExecuteSQL(arg1, arg2, arg3) {
+ return window['go']['main']['App']['ExecuteSQL'](arg1, arg2, arg3);
+}
+
+export function ExtractFileFromZip(arg1, arg2) {
+ return window['go']['main']['App']['ExtractFileFromZip'](arg1, arg2);
+}
+
+export function ExtractFileFromZipToTemp(arg1, arg2) {
+ return window['go']['main']['App']['ExtractFileFromZipToTemp'](arg1, arg2);
+}
+
+export function GetAppConfig() {
+ return window['go']['main']['App']['GetAppConfig']();
+}
+
+export function GetAuditLogs(arg1) {
+ return window['go']['main']['App']['GetAuditLogs'](arg1);
+}
+
+export function GetCPUInfo() {
+ return window['go']['main']['App']['GetCPUInfo']();
+}
+
+export function GetCommonPaths() {
+ return window['go']['main']['App']['GetCommonPaths']();
+}
+
+export function GetCurrentVersion() {
+ return window['go']['main']['App']['GetCurrentVersion']();
+}
+
+export function GetDatabases(arg1) {
+ return window['go']['main']['App']['GetDatabases'](arg1);
+}
+
+export function GetDiskInfo() {
+ return window['go']['main']['App']['GetDiskInfo']();
+}
+
+export function GetEnvVars() {
+ return window['go']['main']['App']['GetEnvVars']();
+}
+
+export function GetFileInfo(arg1) {
+ return window['go']['main']['App']['GetFileInfo'](arg1);
+}
+
+export function GetFileServerURL() {
+ return window['go']['main']['App']['GetFileServerURL']();
+}
+
+export function GetIndexes(arg1, arg2, arg3) {
+ return window['go']['main']['App']['GetIndexes'](arg1, arg2, arg3);
+}
+
+export function GetMemoryInfo() {
+ return window['go']['main']['App']['GetMemoryInfo']();
+}
+
+export function GetRecycleBinEntries() {
+ return window['go']['main']['App']['GetRecycleBinEntries']();
+}
+
+export function GetResultHistory(arg1, arg2, arg3, arg4) {
+ return window['go']['main']['App']['GetResultHistory'](arg1, arg2, arg3, arg4);
+}
+
+export function GetResultHistoryByID(arg1) {
+ return window['go']['main']['App']['GetResultHistoryByID'](arg1);
+}
+
+export function GetSystemInfo() {
+ return window['go']['main']['App']['GetSystemInfo']();
+}
+
+export function GetTableStructure(arg1, arg2, arg3) {
+ return window['go']['main']['App']['GetTableStructure'](arg1, arg2, arg3);
+}
+
+export function GetTables(arg1, arg2) {
+ return window['go']['main']['App']['GetTables'](arg1, arg2);
+}
+
+export function GetUpdateConfig() {
+ return window['go']['main']['App']['GetUpdateConfig']();
+}
+
+export function GetZipFileInfo(arg1, arg2) {
+ return window['go']['main']['App']['GetZipFileInfo'](arg1, arg2);
+}
+
+export function Greet(arg1) {
+ return window['go']['main']['App']['Greet'](arg1);
+}
+
+export function InstallUpdate(arg1, arg2) {
+ return window['go']['main']['App']['InstallUpdate'](arg1, arg2);
+}
+
+export function InstallUpdateWithHash(arg1, arg2, arg3, arg4) {
+ return window['go']['main']['App']['InstallUpdateWithHash'](arg1, arg2, arg3, arg4);
+}
+
+export function ListDbConnections() {
+ return window['go']['main']['App']['ListDbConnections']();
+}
+
+export function ListDir(arg1) {
+ return window['go']['main']['App']['ListDir'](arg1);
+}
+
+export function ListSqlTabs() {
+ return window['go']['main']['App']['ListSqlTabs']();
+}
+
+export function ListZipContents(arg1) {
+ return window['go']['main']['App']['ListZipContents'](arg1);
+}
+
+export function OpenPath(arg1) {
+ return window['go']['main']['App']['OpenPath'](arg1);
+}
+
+export function PreviewTableStructure(arg1, arg2, arg3, arg4) {
+ return window['go']['main']['App']['PreviewTableStructure'](arg1, arg2, arg3, arg4);
+}
+
+export function QueryUsers(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) {
+ return window['go']['main']['App']['QueryUsers'](arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
+}
+
+export function ReadFile(arg1) {
+ return window['go']['main']['App']['ReadFile'](arg1);
+}
+
+export function Reload() {
+ return window['go']['main']['App']['Reload']();
+}
+
+export function RenamePath(arg1) {
+ return window['go']['main']['App']['RenamePath'](arg1);
+}
+
+export function ResolveShortcut(arg1) {
+ return window['go']['main']['App']['ResolveShortcut'](arg1);
+}
+
+export function RestoreFromRecycleBin(arg1) {
+ return window['go']['main']['App']['RestoreFromRecycleBin'](arg1);
+}
+
+export function SaveAppConfig(arg1) {
+ return window['go']['main']['App']['SaveAppConfig'](arg1);
+}
+
+export function SaveDbConnection(arg1) {
+ return window['go']['main']['App']['SaveDbConnection'](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);
+}
+
+export function SaveSqlTabs(arg1) {
+ return window['go']['main']['App']['SaveSqlTabs'](arg1);
+}
+
+export function SetUpdateConfig(arg1, arg2, arg3) {
+ return window['go']['main']['App']['SetUpdateConfig'](arg1, arg2, arg3);
+}
+
+export function TestDbConnection(arg1) {
+ return window['go']['main']['App']['TestDbConnection'](arg1);
+}
+
+export function TestDbConnectionWithParams(arg1) {
+ return window['go']['main']['App']['TestDbConnectionWithParams'](arg1);
+}
+
+export function UpdateTableStructure(arg1, arg2, arg3, arg4) {
+ return window['go']['main']['App']['UpdateTableStructure'](arg1, arg2, arg3, arg4);
+}
+
+export function VerifyUpdateFile(arg1, arg2, arg3) {
+ return window['go']['main']['App']['VerifyUpdateFile'](arg1, arg2, arg3);
+}
+
+export function WindowClose() {
+ return window['go']['main']['App']['WindowClose']();
+}
+
+export function WindowIsMaximized() {
+ return window['go']['main']['App']['WindowIsMaximized']();
+}
+
+export function WindowMaximize() {
+ return window['go']['main']['App']['WindowMaximize']();
+}
+
+export function WindowMinimize() {
+ return window['go']['main']['App']['WindowMinimize']();
+}
+
+export function WriteFile(arg1) {
+ return window['go']['main']['App']['WriteFile'](arg1);
+}
diff --git a/web/src/wailsjs/wailsjs/go/models.ts b/web/src/wailsjs/wailsjs/go/models.ts
new file mode 100644
index 0000000..a136a0b
--- /dev/null
+++ b/web/src/wailsjs/wailsjs/go/models.ts
@@ -0,0 +1,177 @@
+export namespace api {
+
+ export class AppTabDefinition {
+ key: string;
+ title: string;
+ visible: boolean;
+ enabled: boolean;
+
+ static createFrom(source: any = {}) {
+ return new AppTabDefinition(source);
+ }
+
+ constructor(source: any = {}) {
+ if ('string' === typeof source) source = JSON.parse(source);
+ this.key = source["key"];
+ this.title = source["title"];
+ this.visible = source["visible"];
+ this.enabled = source["enabled"];
+ }
+ }
+ export class SaveConnectionRequest {
+ id: number;
+ name: string;
+ type: string;
+ host: string;
+ port: number;
+ username: string;
+ password: string;
+ database: string;
+ options: string;
+
+ static createFrom(source: any = {}) {
+ return new SaveConnectionRequest(source);
+ }
+
+ constructor(source: any = {}) {
+ if ('string' === typeof source) source = JSON.parse(source);
+ this.id = source["id"];
+ this.name = source["name"];
+ this.type = source["type"];
+ this.host = source["host"];
+ this.port = source["port"];
+ this.username = source["username"];
+ this.password = source["password"];
+ this.database = source["database"];
+ this.options = source["options"];
+ }
+ }
+ export class TestConnectionRequest {
+ id: number;
+ type: string;
+ host: string;
+ port: number;
+ username: string;
+ password: string;
+ database: string;
+ options: string;
+
+ static createFrom(source: any = {}) {
+ return new TestConnectionRequest(source);
+ }
+
+ constructor(source: any = {}) {
+ if ('string' === typeof source) source = JSON.parse(source);
+ this.id = source["id"];
+ this.type = source["type"];
+ this.host = source["host"];
+ this.port = source["port"];
+ this.username = source["username"];
+ this.password = source["password"];
+ this.database = source["database"];
+ this.options = source["options"];
+ }
+ }
+
+}
+
+export namespace filesystem {
+
+ export class FileOperationResult {
+ path: string;
+ name: string;
+ size: number;
+ size_str?: string;
+ is_dir: boolean;
+ mod_time?: string;
+ mode?: string;
+ old_path?: string;
+ deleted?: boolean;
+
+ static createFrom(source: any = {}) {
+ return new FileOperationResult(source);
+ }
+
+ constructor(source: any = {}) {
+ if ('string' === typeof source) source = JSON.parse(source);
+ this.path = source["path"];
+ this.name = source["name"];
+ this.size = source["size"];
+ this.size_str = source["size_str"];
+ this.is_dir = source["is_dir"];
+ this.mod_time = source["mod_time"];
+ this.mode = source["mode"];
+ this.old_path = source["old_path"];
+ this.deleted = source["deleted"];
+ }
+ }
+
+}
+
+export namespace main {
+
+ export class RenamePathRequest {
+ oldPath: string;
+ newPath: string;
+
+ static createFrom(source: any = {}) {
+ return new RenamePathRequest(source);
+ }
+
+ constructor(source: any = {}) {
+ if ('string' === typeof source) source = JSON.parse(source);
+ this.oldPath = source["oldPath"];
+ this.newPath = source["newPath"];
+ }
+ }
+ export class SaveAppConfigRequest {
+ tabs: api.AppTabDefinition[];
+ visibleTabs: string[];
+ defaultTab: string;
+
+ static createFrom(source: any = {}) {
+ return new SaveAppConfigRequest(source);
+ }
+
+ constructor(source: any = {}) {
+ if ('string' === typeof source) source = JSON.parse(source);
+ this.tabs = this.convertValues(source["tabs"], api.AppTabDefinition);
+ this.visibleTabs = source["visibleTabs"];
+ this.defaultTab = source["defaultTab"];
+ }
+
+ convertValues(a: any, classs: any, asMap: boolean = false): any {
+ if (!a) {
+ return a;
+ }
+ if (a.slice && a.map) {
+ return (a as any[]).map(elem => this.convertValues(elem, classs));
+ } else if ("object" === typeof a) {
+ if (asMap) {
+ for (const key of Object.keys(a)) {
+ a[key] = new classs(a[key]);
+ }
+ return a;
+ }
+ return new classs(a);
+ }
+ return a;
+ }
+ }
+ export class WriteFileRequest {
+ path: string;
+ content: string;
+
+ static createFrom(source: any = {}) {
+ return new WriteFileRequest(source);
+ }
+
+ constructor(source: any = {}) {
+ if ('string' === typeof source) source = JSON.parse(source);
+ this.path = source["path"];
+ this.content = source["content"];
+ }
+ }
+
+}
+
diff --git a/web/src/wailsjs/wailsjs/runtime/package.json b/web/src/wailsjs/wailsjs/runtime/package.json
new file mode 100644
index 0000000..1e7c8a5
--- /dev/null
+++ b/web/src/wailsjs/wailsjs/runtime/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "@wailsapp/runtime",
+ "version": "2.0.0",
+ "description": "Wails Javascript runtime library",
+ "main": "runtime.js",
+ "types": "runtime.d.ts",
+ "scripts": {
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/wailsapp/wails.git"
+ },
+ "keywords": [
+ "Wails",
+ "Javascript",
+ "Go"
+ ],
+ "author": "Lea Anthony ",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/wailsapp/wails/issues"
+ },
+ "homepage": "https://github.com/wailsapp/wails#readme"
+}
diff --git a/web/src/wailsjs/wailsjs/runtime/runtime.d.ts b/web/src/wailsjs/wailsjs/runtime/runtime.d.ts
new file mode 100644
index 0000000..4445dac
--- /dev/null
+++ b/web/src/wailsjs/wailsjs/runtime/runtime.d.ts
@@ -0,0 +1,249 @@
+/*
+ _ __ _ __
+| | / /___ _(_) /____
+| | /| / / __ `/ / / ___/
+| |/ |/ / /_/ / / (__ )
+|__/|__/\__,_/_/_/____/
+The electron alternative for Go
+(c) Lea Anthony 2019-present
+*/
+
+export interface Position {
+ x: number;
+ y: number;
+}
+
+export interface Size {
+ w: number;
+ h: number;
+}
+
+export interface Screen {
+ isCurrent: boolean;
+ isPrimary: boolean;
+ width : number
+ height : number
+}
+
+// Environment information such as platform, buildtype, ...
+export interface EnvironmentInfo {
+ buildType: string;
+ platform: string;
+ arch: string;
+}
+
+// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit)
+// emits the given event. Optional data may be passed with the event.
+// This will trigger any event listeners.
+export function EventsEmit(eventName: string, ...data: any): void;
+
+// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name.
+export function EventsOn(eventName: string, callback: (...data: any) => void): () => void;
+
+// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple)
+// sets up a listener for the given event name, but will only trigger a given number times.
+export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void;
+
+// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce)
+// sets up a listener for the given event name, but will only trigger once.
+export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void;
+
+// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff)
+// unregisters the listener for the given event name.
+export function EventsOff(eventName: string, ...additionalEventNames: string[]): void;
+
+// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall)
+// unregisters all listeners.
+export function EventsOffAll(): void;
+
+// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint)
+// logs the given message as a raw message
+export function LogPrint(message: string): void;
+
+// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace)
+// logs the given message at the `trace` log level.
+export function LogTrace(message: string): void;
+
+// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug)
+// logs the given message at the `debug` log level.
+export function LogDebug(message: string): void;
+
+// [LogError](https://wails.io/docs/reference/runtime/log#logerror)
+// logs the given message at the `error` log level.
+export function LogError(message: string): void;
+
+// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal)
+// logs the given message at the `fatal` log level.
+// The application will quit after calling this method.
+export function LogFatal(message: string): void;
+
+// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo)
+// logs the given message at the `info` log level.
+export function LogInfo(message: string): void;
+
+// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning)
+// logs the given message at the `warning` log level.
+export function LogWarning(message: string): void;
+
+// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload)
+// Forces a reload by the main application as well as connected browsers.
+export function WindowReload(): void;
+
+// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp)
+// Reloads the application frontend.
+export function WindowReloadApp(): void;
+
+// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop)
+// Sets the window AlwaysOnTop or not on top.
+export function WindowSetAlwaysOnTop(b: boolean): void;
+
+// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme)
+// *Windows only*
+// Sets window theme to system default (dark/light).
+export function WindowSetSystemDefaultTheme(): void;
+
+// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme)
+// *Windows only*
+// Sets window to light theme.
+export function WindowSetLightTheme(): void;
+
+// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme)
+// *Windows only*
+// Sets window to dark theme.
+export function WindowSetDarkTheme(): void;
+
+// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter)
+// Centers the window on the monitor the window is currently on.
+export function WindowCenter(): void;
+
+// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle)
+// Sets the text in the window title bar.
+export function WindowSetTitle(title: string): void;
+
+// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen)
+// Makes the window full screen.
+export function WindowFullscreen(): void;
+
+// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen)
+// Restores the previous window dimensions and position prior to full screen.
+export function WindowUnfullscreen(): void;
+
+// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen)
+// Returns the state of the window, i.e. whether the window is in full screen mode or not.
+export function WindowIsFullscreen(): Promise;
+
+// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize)
+// Sets the width and height of the window.
+export function WindowSetSize(width: number, height: number): void;
+
+// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize)
+// Gets the width and height of the window.
+export function WindowGetSize(): Promise;
+
+// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize)
+// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions.
+// Setting a size of 0,0 will disable this constraint.
+export function WindowSetMaxSize(width: number, height: number): void;
+
+// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize)
+// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions.
+// Setting a size of 0,0 will disable this constraint.
+export function WindowSetMinSize(width: number, height: number): void;
+
+// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition)
+// Sets the window position relative to the monitor the window is currently on.
+export function WindowSetPosition(x: number, y: number): void;
+
+// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition)
+// Gets the window position relative to the monitor the window is currently on.
+export function WindowGetPosition(): Promise;
+
+// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide)
+// Hides the window.
+export function WindowHide(): void;
+
+// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow)
+// Shows the window, if it is currently hidden.
+export function WindowShow(): void;
+
+// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise)
+// Maximises the window to fill the screen.
+export function WindowMaximise(): void;
+
+// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise)
+// Toggles between Maximised and UnMaximised.
+export function WindowToggleMaximise(): void;
+
+// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise)
+// Restores the window to the dimensions and position prior to maximising.
+export function WindowUnmaximise(): void;
+
+// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised)
+// Returns the state of the window, i.e. whether the window is maximised or not.
+export function WindowIsMaximised(): Promise;
+
+// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise)
+// Minimises the window.
+export function WindowMinimise(): void;
+
+// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise)
+// Restores the window to the dimensions and position prior to minimising.
+export function WindowUnminimise(): void;
+
+// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised)
+// Returns the state of the window, i.e. whether the window is minimised or not.
+export function WindowIsMinimised(): Promise;
+
+// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal)
+// Returns the state of the window, i.e. whether the window is normal or not.
+export function WindowIsNormal(): Promise;
+
+// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour)
+// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels.
+export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void;
+
+// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall)
+// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system.
+export function ScreenGetAll(): Promise;
+
+// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl)
+// Opens the given URL in the system browser.
+export function BrowserOpenURL(url: string): void;
+
+// [Environment](https://wails.io/docs/reference/runtime/intro#environment)
+// Returns information about the environment
+export function Environment(): Promise;
+
+// [Quit](https://wails.io/docs/reference/runtime/intro#quit)
+// Quits the application.
+export function Quit(): void;
+
+// [Hide](https://wails.io/docs/reference/runtime/intro#hide)
+// Hides the application.
+export function Hide(): void;
+
+// [Show](https://wails.io/docs/reference/runtime/intro#show)
+// Shows the application.
+export function Show(): void;
+
+// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext)
+// Returns the current text stored on clipboard
+export function ClipboardGetText(): Promise;
+
+// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext)
+// Sets a text on the clipboard
+export function ClipboardSetText(text: string): Promise;
+
+// [OnFileDrop](https://wails.io/docs/reference/runtime/draganddrop#onfiledrop)
+// OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
+export function OnFileDrop(callback: (x: number, y: number ,paths: string[]) => void, useDropTarget: boolean) :void
+
+// [OnFileDropOff](https://wails.io/docs/reference/runtime/draganddrop#dragandddropoff)
+// OnFileDropOff removes the drag and drop listeners and handlers.
+export function OnFileDropOff() :void
+
+// Check if the file path resolver is available
+export function CanResolveFilePaths(): boolean;
+
+// Resolves file paths for an array of files
+export function ResolveFilePaths(files: File[]): void
\ No newline at end of file
diff --git a/web/src/wailsjs/wailsjs/runtime/runtime.js b/web/src/wailsjs/wailsjs/runtime/runtime.js
new file mode 100644
index 0000000..7cb89d7
--- /dev/null
+++ b/web/src/wailsjs/wailsjs/runtime/runtime.js
@@ -0,0 +1,242 @@
+/*
+ _ __ _ __
+| | / /___ _(_) /____
+| | /| / / __ `/ / / ___/
+| |/ |/ / /_/ / / (__ )
+|__/|__/\__,_/_/_/____/
+The electron alternative for Go
+(c) Lea Anthony 2019-present
+*/
+
+export function LogPrint(message) {
+ window.runtime.LogPrint(message);
+}
+
+export function LogTrace(message) {
+ window.runtime.LogTrace(message);
+}
+
+export function LogDebug(message) {
+ window.runtime.LogDebug(message);
+}
+
+export function LogInfo(message) {
+ window.runtime.LogInfo(message);
+}
+
+export function LogWarning(message) {
+ window.runtime.LogWarning(message);
+}
+
+export function LogError(message) {
+ window.runtime.LogError(message);
+}
+
+export function LogFatal(message) {
+ window.runtime.LogFatal(message);
+}
+
+export function EventsOnMultiple(eventName, callback, maxCallbacks) {
+ return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks);
+}
+
+export function EventsOn(eventName, callback) {
+ return EventsOnMultiple(eventName, callback, -1);
+}
+
+export function EventsOff(eventName, ...additionalEventNames) {
+ return window.runtime.EventsOff(eventName, ...additionalEventNames);
+}
+
+export function EventsOffAll() {
+ return window.runtime.EventsOffAll();
+}
+
+export function EventsOnce(eventName, callback) {
+ return EventsOnMultiple(eventName, callback, 1);
+}
+
+export function EventsEmit(eventName) {
+ let args = [eventName].slice.call(arguments);
+ return window.runtime.EventsEmit.apply(null, args);
+}
+
+export function WindowReload() {
+ window.runtime.WindowReload();
+}
+
+export function WindowReloadApp() {
+ window.runtime.WindowReloadApp();
+}
+
+export function WindowSetAlwaysOnTop(b) {
+ window.runtime.WindowSetAlwaysOnTop(b);
+}
+
+export function WindowSetSystemDefaultTheme() {
+ window.runtime.WindowSetSystemDefaultTheme();
+}
+
+export function WindowSetLightTheme() {
+ window.runtime.WindowSetLightTheme();
+}
+
+export function WindowSetDarkTheme() {
+ window.runtime.WindowSetDarkTheme();
+}
+
+export function WindowCenter() {
+ window.runtime.WindowCenter();
+}
+
+export function WindowSetTitle(title) {
+ window.runtime.WindowSetTitle(title);
+}
+
+export function WindowFullscreen() {
+ window.runtime.WindowFullscreen();
+}
+
+export function WindowUnfullscreen() {
+ window.runtime.WindowUnfullscreen();
+}
+
+export function WindowIsFullscreen() {
+ return window.runtime.WindowIsFullscreen();
+}
+
+export function WindowGetSize() {
+ return window.runtime.WindowGetSize();
+}
+
+export function WindowSetSize(width, height) {
+ window.runtime.WindowSetSize(width, height);
+}
+
+export function WindowSetMaxSize(width, height) {
+ window.runtime.WindowSetMaxSize(width, height);
+}
+
+export function WindowSetMinSize(width, height) {
+ window.runtime.WindowSetMinSize(width, height);
+}
+
+export function WindowSetPosition(x, y) {
+ window.runtime.WindowSetPosition(x, y);
+}
+
+export function WindowGetPosition() {
+ return window.runtime.WindowGetPosition();
+}
+
+export function WindowHide() {
+ window.runtime.WindowHide();
+}
+
+export function WindowShow() {
+ window.runtime.WindowShow();
+}
+
+export function WindowMaximise() {
+ window.runtime.WindowMaximise();
+}
+
+export function WindowToggleMaximise() {
+ window.runtime.WindowToggleMaximise();
+}
+
+export function WindowUnmaximise() {
+ window.runtime.WindowUnmaximise();
+}
+
+export function WindowIsMaximised() {
+ return window.runtime.WindowIsMaximised();
+}
+
+export function WindowMinimise() {
+ window.runtime.WindowMinimise();
+}
+
+export function WindowUnminimise() {
+ window.runtime.WindowUnminimise();
+}
+
+export function WindowSetBackgroundColour(R, G, B, A) {
+ window.runtime.WindowSetBackgroundColour(R, G, B, A);
+}
+
+export function ScreenGetAll() {
+ return window.runtime.ScreenGetAll();
+}
+
+export function WindowIsMinimised() {
+ return window.runtime.WindowIsMinimised();
+}
+
+export function WindowIsNormal() {
+ return window.runtime.WindowIsNormal();
+}
+
+export function BrowserOpenURL(url) {
+ window.runtime.BrowserOpenURL(url);
+}
+
+export function Environment() {
+ return window.runtime.Environment();
+}
+
+export function Quit() {
+ window.runtime.Quit();
+}
+
+export function Hide() {
+ window.runtime.Hide();
+}
+
+export function Show() {
+ window.runtime.Show();
+}
+
+export function ClipboardGetText() {
+ return window.runtime.ClipboardGetText();
+}
+
+export function ClipboardSetText(text) {
+ return window.runtime.ClipboardSetText(text);
+}
+
+/**
+ * Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
+ *
+ * @export
+ * @callback OnFileDropCallback
+ * @param {number} x - x coordinate of the drop
+ * @param {number} y - y coordinate of the drop
+ * @param {string[]} paths - A list of file paths.
+ */
+
+/**
+ * OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
+ *
+ * @export
+ * @param {OnFileDropCallback} callback - Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
+ * @param {boolean} [useDropTarget=true] - Only call the callback when the drop finished on an element that has the drop target style. (--wails-drop-target)
+ */
+export function OnFileDrop(callback, useDropTarget) {
+ return window.runtime.OnFileDrop(callback, useDropTarget);
+}
+
+/**
+ * OnFileDropOff removes the drag and drop listeners and handlers.
+ */
+export function OnFileDropOff() {
+ return window.runtime.OnFileDropOff();
+}
+
+export function CanResolveFilePaths() {
+ return window.runtime.CanResolveFilePaths();
+}
+
+export function ResolveFilePaths(files) {
+ return window.runtime.ResolveFilePaths(files);
+}
\ No newline at end of file
diff --git a/web/vite.config.js b/web/vite.config.js
index 1c5a46c..b811c58 100644
--- a/web/vite.config.js
+++ b/web/vite.config.js
@@ -1,60 +1,74 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
+import AutoImport from 'unplugin-auto-import/vite'
+import Components from 'unplugin-vue-components/vite'
+import { ArcoResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
- plugins: [vue()],
+ plugins: [
+ vue(),
+ AutoImport({
+ imports: ['vue', 'vue-router'],
+ dts: 'src/auto-imports.d.ts',
+ }),
+ Components({
+ resolvers: [ArcoResolver({ sideEffect: true })],
+ dts: 'src/components.d.ts',
+ })
+ ],
resolve: {
- alias: {
- '@': resolve(__dirname, 'src')
- }
+ alias: { '@': resolve(__dirname, 'src') }
},
build: {
outDir: 'dist',
emptyOutDir: true,
- sourcemap: false, // 生产环境禁用 source map,减小打包体积
+ sourcemap: false,
+ minify: 'esbuild',
+ cssCodeSplit: true,
+ chunkSizeWarningLimit: 1000,
+ esbuild: {
+ target: 'es2020',
+ drop: ['console', 'debugger']
+ },
rollupOptions: {
output: {
- manualChunks: {
- 'codemirror': [
- '@codemirror/view',
- '@codemirror/state',
- '@codemirror/language',
- '@codemirror/commands',
- '@codemirror/lang-javascript',
- '@codemirror/lang-java',
- '@codemirror/lang-python',
- '@codemirror/lang-html',
- '@codemirror/lang-css',
- '@codemirror/lang-markdown',
- '@codemirror/lang-sql'
- ]
- }
+ manualChunks: (id) => {
+ if (!id.includes('node_modules')) return
+
+ if (id.includes('@codemirror')) {
+ if (id.includes('lang-') || id.includes('legacy-modes')) {
+ return 'vendor-codemirror-langs'
+ }
+ return 'vendor-codemirror-core'
+ }
+
+ if (id.includes('@arco-design')) return 'vendor-arco'
+ if (id.includes('mermaid')) return 'vendor-mermaid'
+ if (id.includes('marked') || id.includes('highlight.js')) return 'vendor-markdown'
+ if (id.includes('vue') || id.includes('pinia')) return 'vendor-vue'
+
+ return 'vendor'
+ },
+ chunkFileNames: 'assets/js/[name]-[hash].js',
+ entryFileNames: 'assets/js/[name]-[hash].js',
+ assetFileNames: 'assets/[ext]/[name]-[hash].[ext]'
}
}
},
optimizeDeps: {
include: [
- '@codemirror/view',
- '@codemirror/state',
- '@codemirror/language',
- '@codemirror/commands',
- '@codemirror/lang-javascript',
- '@codemirror/lang-java',
- '@codemirror/lang-python',
- '@codemirror/lang-html',
- '@codemirror/lang-css',
- '@codemirror/lang-markdown',
- '@codemirror/lang-sql',
- '@codemirror/legacy-modes/mode/go',
- '@codemirror/legacy-modes/mode/clike',
- '@codemirror/legacy-modes/mode/ruby',
- '@codemirror/legacy-modes/mode/rust',
- '@codemirror/legacy-modes/mode/shell',
- '@codemirror/legacy-modes/mode/yaml',
- '@codemirror/legacy-modes/mode/xml'
+ 'vue', 'pinia', '@arco-design/web-vue', 'marked', 'highlight.js',
+ '@codemirror/view', '@codemirror/state', '@codemirror/language', '@codemirror/commands',
+ '@codemirror/lang-javascript', '@codemirror/lang-json', '@codemirror/lang-yaml',
+ '@codemirror/lang-html', '@codemirror/lang-css', '@codemirror/lang-markdown',
+ '@codemirror/lang-sql', '@codemirror/lang-java', '@codemirror/lang-python',
+ '@codemirror/lang-php', '@codemirror/lang-rust', '@codemirror/lang-go', '@codemirror/lang-cpp',
+ '@codemirror/legacy-modes/mode/clike', '@codemirror/legacy-modes/mode/ruby',
+ '@codemirror/legacy-modes/mode/shell', '@codemirror/legacy-modes/mode/xml'
]
- }
+ },
+ cacheDir: 'node_modules/.vite'
})