package main import ( "context" "fmt" "net/http" "os" "path/filepath" stdruntime "runtime" "strings" "time" "github.com/jung-kurt/gofpdf" "u-desk/internal/api" "u-desk/internal/common" "u-desk/internal/database" "u-desk/internal/filesystem" "u-desk/internal/service" "u-desk/internal/storage" "u-desk/internal/system" "github.com/wailsapp/wails/v2/pkg/runtime" ) // App 应用结构体 type App struct { ctx context.Context db *database.DB connectionAPI *api.ConnectionAPI sqlAPI *api.SqlAPI tabAPI *api.TabAPI updateAPI *api.UpdateAPI configAPI *api.ConfigAPI pdfAPI *api.PdfAPI fileServer *http.Server filesystem *filesystem.FileSystemService isAlwaysOnTop bool } // NewApp 创建新的应用实例 func NewApp() *App { return &App{} } // Startup 应用启动时调用 func (a *App) Startup(ctx context.Context) { a.ctx = ctx // 1. 核心初始化:SQLite(必须同步,很快) sqliteDB, err := storage.InitFast() if err != nil { panic(fmt.Sprintf("SQLite 初始化失败,应用无法启动: %v", err)) } _ = sqliteDB // 全局 DB 已由 InitFast() 设置 // 2. 初始化配置服务 configService, err := api.NewConfigAPI() if err != nil { panic(fmt.Sprintf("配置服务初始化失败: %v", err)) } a.configAPI = configService // 2.5. 迁移旧配置 _ = a.configAPI.MigrateTabConfig() // 2.6. 初始化PDF导出API fmt.Println("[启动] 初始化PDF导出模块...") pdfAPI, err := api.NewPdfAPI() if err != nil { fmt.Printf("[启动] PDF导出API初始化失败: %v\n", err) // PDF导出失败不应影响应用启动,所以只警告不panic } else { a.pdfAPI = pdfAPI fmt.Println("[启动] PDF导出模块初始化完成") } // 3. 初始化版本号(提前触发缓存,避免后续重复计算) version := service.GetCurrentVersion() fmt.Printf("[启动] 当前版本: %s\n", version) // 4. 读取配置,获取可见的 Tabs visibleTabs := a.getVisibleTabs() fmt.Printf("[启动] 可用的模块: %v\n", visibleTabs) // 4. 根据配置初始化模块(条件初始化) if err := a.initModulesByConfig(visibleTabs); err != nil { panic(fmt.Sprintf("模块初始化失败: %v", err)) } // 5. 异步初始化:UpdateAPI(涉及网络请求,完全异步) go func() { if updateAPI, err := api.NewUpdateAPI("https://img.1216.top/u-desk/last-version.json"); err == nil { a.updateAPI = updateAPI a.updateAPI.SetContext(ctx) a.startAutoUpdateCheck() } }() } // getVisibleTabs 获取配置中的可见 Tabs func (a *App) getVisibleTabs() []string { config, err := a.configAPI.GetAppConfig() if err != nil { fmt.Printf("[启动] 读取配置失败,使用默认配置: %v\n", err) return common.DefaultVisibleTabs } // 快速检查成功标识 success, ok := config["success"].(bool) if !ok || !success { fmt.Printf("[启动] 配置读取失败,使用默认配置\n") return common.DefaultVisibleTabs } // 提取 data data, ok := config["data"].(map[string]interface{}) if !ok { return common.DefaultVisibleTabs } // 提取 visibleTabs visibleTabsInterface, ok := data["visibleTabs"].([]interface{}) if !ok { return common.DefaultVisibleTabs } visibleTabs := common.InterfaceSliceToStringSlice(visibleTabsInterface) if len(visibleTabs) == 0 { return common.DefaultVisibleTabs } return visibleTabs } // initModulesByConfig 根据配置初始化模块 func (a *App) initModulesByConfig(visibleTabs []string) error { // 检查是否启用数据库模块 if common.Contains(visibleTabs, common.TabDatabase) { fmt.Println("[启动] 初始化数据库模块...") var err error // 初始化 ConnectionAPI if a.connectionAPI, err = api.NewConnectionAPI(); err != nil { return err } // 初始化 SqlAPI if a.sqlAPI, err = api.NewSqlAPI(); err != nil { return err } // 初始化 TabAPI if a.tabAPI, err = api.NewTabAPI(); err != nil { return err } fmt.Println("[启动] 数据库模块初始化完成") } else { fmt.Println("[启动] 跳过数据库模块(未启用)") } // 检查是否启用文件系统模块 if common.Contains(visibleTabs, common.TabFileSystem) { fmt.Println("[启动] 初始化文件系统模块...") // 初始化文件系统服务 fsConfig := filesystem.DefaultConfig() var err error a.filesystem, err = filesystem.NewFileSystemService(fsConfig) if err != nil { return fmt.Errorf("文件系统服务初始化失败: %w", err) } // 异步启动文件服务器 go a.startFileServer() fmt.Println("[启动] 文件系统模块初始化完成") } else { fmt.Println("[启动] 跳过文件系统模块(未启用)") } return nil } // startFileServer 启动文件服务器 func (a *App) startFileServer() { // 启动独立的本地文件服务器(使用 filesystem 包中的实现) if _, err := filesystem.StartLocalFileServer(); err != nil { fmt.Printf("[文件服务器] 启动失败: %v\n", err) return } 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(shutdownCtx); err != nil { fmt.Printf("[文件系统服务] 关闭失败: %v\n", err) } else { fmt.Println("[文件系统服务] 已关闭") } } // 2. 停止文件服务器(使用全局服务器的关闭方法) fmt.Println("[文件服务器] 正在关闭...") if err := filesystem.ShutdownLocalFileServer(); err != nil { fmt.Printf("[文件服务器] 关闭失败: %v\n", err) } else { fmt.Println("[文件服务器] 已关闭") } } // QueryUsers 查询用户列表 func (a *App) QueryUsers(keyword string, status int, role int, organid int, page int, pageSize int, sortField string, sortOrder string) (map[string]interface{}, error) { db, err := a.getDB() if err != nil { return nil, err } return db.QueryUsers(keyword, status, role, organid, page, pageSize, sortField, sortOrder) } // getDB 获取数据库连接(延迟加载,按需初始化) func (a *App) getDB() (*database.DB, error) { if a.db != nil { return a.db, nil } // 首次调用时才连接数据库 db, err := database.Init() if err != nil { return nil, fmt.Errorf("数据库连接失败: %v", err) } a.db = db return db, nil } // Greet 测试方法 func (a *App) Greet(name string) string { return "Hello " + name + ", It's show time!" } // GetSystemInfo 获取系统信息 func (a *App) GetSystemInfo() (map[string]interface{}, error) { return system.GetSystemInfo() } // GetCPUInfo 获取 CPU 信息 func (a *App) GetCPUInfo() (map[string]interface{}, error) { return system.GetCPUInfo() } // GetMemoryInfo 获取内存信息 func (a *App) GetMemoryInfo() (map[string]interface{}, error) { return system.GetMemoryInfo() } // GetDiskInfo 获取磁盘信息 func (a *App) GetDiskInfo() ([]map[string]interface{}, error) { return system.GetDiskInfo() } // ReadFile 读取文件 func (a *App) ReadFile(path string) (string, error) { return a.filesystem.ReadFile(path) } // WriteFileRequest 写入文件请求结构体 type WriteFileRequest struct { Path string `json:"path"` Content string `json:"content"` } // WriteFile 写入文件 func (a *App) WriteFile(req WriteFileRequest) error { return a.filesystem.WriteFile(req.Path, req.Content) } // ListDir 列出目录 func (a *App) ListDir(path string) ([]map[string]interface{}, error) { return a.filesystem.ListDir(path) } // CreateDir 创建目录 func (a *App) CreateDir(path string) (*filesystem.FileOperationResult, error) { return a.filesystem.CreateDir(path) } // CreateFile 创建文件 func (a *App) CreateFile(path string) (*filesystem.FileOperationResult, error) { return a.filesystem.CreateFile(path) } // DeletePath 删除文件或目录 func (a *App) DeletePath(path string) (*filesystem.FileOperationResult, error) { return a.filesystem.DeletePath(path) } // RenamePathRequest 重命名文件或目录请求结构体 type RenamePathRequest struct { OldPath string `json:"oldPath"` NewPath string `json:"newPath"` } // RenamePath 重命名文件或目录 func (a *App) RenamePath(req RenamePathRequest) (*filesystem.FileOperationResult, error) { return a.filesystem.RenamePath(req.OldPath, req.NewPath) } // GetFileInfo 获取文件信息 func (a *App) GetFileInfo(path string) (map[string]interface{}, error) { return a.filesystem.GetFileInfo(path) } // GetEnvVars 获取环境变量 func (a *App) GetEnvVars() (map[string]string, error) { envVars := make(map[string]string) for _, env := range os.Environ() { if key, value, found := strings.Cut(env, "="); found { envVars[key] = value } } return envVars, nil } // OpenPath 使用系统默认程序打开文件或目录 func (a *App) OpenPath(path string) error { return a.filesystem.OpenPath(path) } // ========== Zip 文件操作接口 ========== // ListZipContents 列出 zip 文件内容 func (a *App) ListZipContents(zipPath string) ([]map[string]interface{}, error) { return a.filesystem.ListZipContents(zipPath) } // ExtractFileFromZip 从 zip 文件中提取单个文件内容 func (a *App) ExtractFileFromZip(zipPath, filePath string) (string, error) { return a.filesystem.ExtractFileFromZip(zipPath, filePath) } // ExtractFileFromZipToTemp 从 zip 文件中提取单个文件到临时目录 // 返回临时文件的完整路径,适用于图片等二进制文件 func (a *App) ExtractFileFromZipToTemp(zipPath, filePath string) (string, error) { return a.filesystem.ExtractFileFromZipToTemp(zipPath, filePath) } // GetZipFileInfo 获取 zip 文件中特定文件的信息 func (a *App) GetZipFileInfo(zipPath, filePath string) (map[string]interface{}, error) { return a.filesystem.GetZipFileInfo(zipPath, filePath) } // ResolveShortcut 解析快捷方式文件,返回目标路径信息 func (a *App) ResolveShortcut(lnkPath string) (map[string]interface{}, error) { targetPath, err := a.filesystem.ResolveShortcut(lnkPath) if err != nil { return map[string]interface{}{ "success": false, "message": err.Error(), }, err } // 获取目标文件信息 fileInfo, err := a.filesystem.GetFileInfo(targetPath) if err != nil { // 目标文件不存在或无法访问 return map[string]interface{}{ "success": true, "targetPath": targetPath, "targetExists": false, "targetAccessible": false, }, nil } // 返回完整的目标信息 return map[string]interface{}{ "success": true, "targetPath": targetPath, "targetExists": true, "targetAccessible": true, "targetInfo": fileInfo, }, nil } // GetCommonPaths 获取常用系统路径 func (a *App) GetCommonPaths() (map[string]string, error) { homeDir, err := os.UserHomeDir() if err != nil { return nil, err } paths := map[string]string{ "home": homeDir, "desktop": filepath.Join(homeDir, "Desktop"), "documents": filepath.Join(homeDir, "Documents"), "downloads": filepath.Join(homeDir, "Downloads"), } // Windows: 动态添加所有盘符 if stdruntime.GOOS == "windows" { for _, drive := range "ABCDEFGHIJKLMNOPQRSTUVWXYZ" { path := string(drive) + ":\\" if _, err := os.Stat(path); err == nil { key := fmt.Sprintf("root_%c", drive) paths[key] = path } } } return paths, nil } // ========== 数据库连接管理接口 ========== // SaveDbConnection 保存数据库连接配置 func (a *App) SaveDbConnection(req api.SaveConnectionRequest) error { return a.connectionAPI.SaveDbConnection(req) } // ListDbConnections 获取连接列表 func (a *App) ListDbConnections() ([]map[string]interface{}, error) { return a.connectionAPI.ListDbConnections() } // DeleteDbConnection 删除连接配置 func (a *App) DeleteDbConnection(id uint) error { return a.connectionAPI.DeleteDbConnection(id) } // TestDbConnection 测试连接(通过已保存的连接ID) func (a *App) TestDbConnection(id uint) error { return a.connectionAPI.TestDbConnection(id) } // TestDbConnectionWithParams 测试数据库连接(直接传入参数,不保存数据) func (a *App) TestDbConnectionWithParams(req api.TestConnectionRequest) error { return a.connectionAPI.TestDbConnectionWithParams(req) } // LoadAllDatabases 加载全部数据库列表 func (a *App) LoadAllDatabases(req api.LoadAllDatabasesRequest) ([]string, error) { return a.connectionAPI.LoadAllDatabases(req) } // ExecuteSQL 执行 SQL 语句 // 注意:SQL 语句应该已经包含分页信息(LIMIT 和 OFFSET),由客户端添加 func (a *App) ExecuteSQL(connectionId uint, sqlStr string, database string) (map[string]interface{}, error) { return a.sqlAPI.ExecuteSQL(connectionId, sqlStr, database) } // GetDatabases 获取数据库列表 func (a *App) GetDatabases(connectionId uint) ([]string, error) { return a.sqlAPI.GetDatabases(connectionId) } // GetTables 获取表列表 func (a *App) GetTables(connectionId uint, database string) ([]string, error) { return a.sqlAPI.GetTables(connectionId, database) } // GetTableStructure 获取表结构 func (a *App) GetTableStructure(connectionId uint, database, tableName string) (map[string]interface{}, error) { return a.sqlAPI.GetTableStructure(connectionId, database, tableName) } // GetIndexes 获取索引列表 func (a *App) GetIndexes(connectionId uint, database, tableName string) ([]map[string]interface{}, error) { return a.sqlAPI.GetIndexes(connectionId, database, tableName) } // PreviewTableStructure 预览表结构变更 func (a *App) PreviewTableStructure(connectionId uint, database, tableName string, structure map[string]interface{}) ([]string, error) { return a.sqlAPI.PreviewTableStructure(connectionId, database, tableName, structure) } // UpdateTableStructure 更新表结构 func (a *App) UpdateTableStructure(connectionId uint, database, tableName string, structure map[string]interface{}) ([]string, error) { return a.sqlAPI.UpdateTableStructure(connectionId, database, tableName, structure) } // SaveResult 手动保存执行结果 func (a *App) SaveResult(connectionId uint, database, sql string, resultType string, data interface{}, columns []string, rowsAffected int, executionTime int64) (map[string]interface{}, error) { return a.sqlAPI.SaveResult(connectionId, database, sql, resultType, data, columns, rowsAffected, executionTime) } // GetResultHistory 获取结果历史 func (a *App) GetResultHistory(connectionId *uint, keyword string, limit, offset int) (map[string]interface{}, error) { return a.sqlAPI.GetResultHistory(connectionId, keyword, limit, offset) } // GetResultHistoryByID 根据ID获取结果历史 func (a *App) GetResultHistoryByID(id uint) (map[string]interface{}, error) { return a.sqlAPI.GetResultHistoryByID(id) } // DeleteResultHistory 删除结果历史 func (a *App) DeleteResultHistory(id uint) error { return a.sqlAPI.DeleteResultHistory(id) } // Reload 重新加载窗口(用于菜单项) func (a *App) Reload() { if a.ctx != nil { runtime.WindowReload(a.ctx) } } // ClearCache 清理本地缓存(用于菜单项) func (a *App) ClearCache() { if a.ctx != nil { // 发送事件到前端,让前端清理 localStorage runtime.EventsEmit(a.ctx, "clear-cache") } } // ========== 窗口控制方法 ========== // WindowMinimize 最小化窗口 func (a *App) WindowMinimize() { if a.ctx != nil { runtime.WindowMinimise(a.ctx) } } // WindowMaximize 最大化/还原窗口 func (a *App) WindowMaximize() { if a.ctx != nil { if runtime.WindowIsMaximised(a.ctx) { runtime.WindowUnmaximise(a.ctx) } else { runtime.WindowMaximise(a.ctx) } } } // WindowClose 关闭窗口 func (a *App) WindowClose() { if a.ctx != nil { runtime.Quit(a.ctx) } } // WindowIsMaximized 检查窗口是否最大化 func (a *App) WindowIsMaximized() bool { if a.ctx != nil { return runtime.WindowIsMaximised(a.ctx) } return false } // WindowToggleAlwaysOnTop 切换窗口置顶 func (a *App) WindowToggleAlwaysOnTop() bool { if a.ctx == nil { return false } a.isAlwaysOnTop = !a.isAlwaysOnTop runtime.WindowSetAlwaysOnTop(a.ctx, a.isAlwaysOnTop) return a.isAlwaysOnTop } // ========== SQL 标签页管理接口 ========== // SaveSqlTabs 保存 SQL 标签页列表 func (a *App) SaveSqlTabs(tabs []map[string]interface{}) error { return a.tabAPI.SaveSqlTabs(tabs) } // ListSqlTabs 获取 SQL 标签页列表 func (a *App) ListSqlTabs() ([]map[string]interface{}, error) { return a.tabAPI.ListSqlTabs() } // ========== 版本更新管理接口 ========== // CheckUpdate 检查更新(UpdateAPI 可能尚未初始化完成) func (a *App) CheckUpdate() (map[string]interface{}, error) { if a.updateAPI == nil { return nil, fmt.Errorf("更新功能正在初始化中") } return a.updateAPI.CheckUpdate() } // GetCurrentVersion 获取当前版本号 func (a *App) GetCurrentVersion() (map[string]interface{}, error) { if a.updateAPI == nil { return nil, fmt.Errorf("更新功能正在初始化中") } return a.updateAPI.GetCurrentVersion() } // GetUpdateConfig 获取更新配置 func (a *App) GetUpdateConfig() (map[string]interface{}, error) { if a.updateAPI == nil { return nil, fmt.Errorf("更新功能正在初始化中") } return a.updateAPI.GetUpdateConfig() } // SetUpdateConfig 设置更新配置 func (a *App) SetUpdateConfig(autoCheckEnabled bool, checkIntervalMinutes int, checkURL string) (map[string]interface{}, error) { if a.updateAPI == nil { return nil, fmt.Errorf("更新功能正在初始化中") } return a.updateAPI.SetUpdateConfig(autoCheckEnabled, checkIntervalMinutes, checkURL) } // DownloadUpdate 下载更新包 func (a *App) DownloadUpdate(downloadURL string) (map[string]interface{}, error) { if a.updateAPI == nil { return nil, fmt.Errorf("更新功能正在初始化中") } return a.updateAPI.DownloadUpdate(downloadURL) } // InstallUpdate 安装更新包 func (a *App) InstallUpdate(installerPath string, autoRestart bool) (map[string]interface{}, error) { if a.updateAPI == nil { return nil, fmt.Errorf("更新功能正在初始化中") } return a.updateAPI.InstallUpdate(installerPath, autoRestart) } // InstallUpdateWithHash 安装更新包(带哈希验证) func (a *App) InstallUpdateWithHash(installerPath string, autoRestart bool, expectedHash string, hashType string) (map[string]interface{}, error) { if a.updateAPI == nil { return nil, fmt.Errorf("更新功能正在初始化中") } return a.updateAPI.InstallUpdateWithHash(installerPath, autoRestart, expectedHash, hashType) } // VerifyUpdateFile 验证更新文件哈希值 func (a *App) VerifyUpdateFile(filePath string, expectedHash string, hashType string) (map[string]interface{}, error) { if a.updateAPI == nil { return nil, fmt.Errorf("更新功能正在初始化中") } return a.updateAPI.VerifyUpdateFile(filePath, expectedHash, hashType) } // startAutoUpdateCheck 启动自动更新检查 func (a *App) startAutoUpdateCheck() { if a.updateAPI == nil { return } config, err := a.updateAPI.GetUpdateConfig() if err != nil { return } success, ok := config["success"].(bool) if !ok || !success { return } configData, ok := config["data"].(map[string]interface{}) if !ok { return } autoCheckEnabled, ok := configData["auto_check_enabled"].(bool) if !ok || !autoCheckEnabled { return } interval, ok := configData["check_interval_minutes"].(int) if !ok || interval <= 0 { interval = 5 } // 立即检查一次 go a.checkUpdate() // 启动定时器 ticker := time.NewTicker(time.Duration(interval) * time.Minute) go func() { for range ticker.C { a.checkUpdate() } }() } // checkUpdate 执行更新检查 func (a *App) checkUpdate() { defer func() { if r := recover(); r != nil { fmt.Printf("[自动检查更新] 发生错误: %v\n", r) } }() if a.updateAPI == nil { return } result, err := a.updateAPI.CheckUpdate() if err != nil { return } success, ok := result["success"].(bool) if !ok || !success { return } data, ok := result["data"].(map[string]interface{}) if !ok { return } hasUpdate, ok := data["has_update"].(bool) if ok && hasUpdate && a.ctx != nil { runtime.EventsEmit(a.ctx, "update-available", data) } } // ========== 审计日志接口 ========== // GetAuditLogs 获取审计日志 func (a *App) GetAuditLogs(limit int) ([]map[string]interface{}, error) { return a.filesystem.GetAuditLogs(limit) } // ========== 文件服务器接口 ========== // GetFileServerURL 获取本地文件服务器的URL func (a *App) GetFileServerURL() string { return "http://localhost:18765" } // DetectFileTypeByContent 通过文件内容检测文件类型(用于小文件) func (a *App) DetectFileTypeByContent(path string) (map[string]interface{}, error) { return filesystem.DetectFileTypeByContentSimple(path) } // ========== 回收站接口 ========== // GetRecycleBinEntries 获取回收站条目 func (a *App) GetRecycleBinEntries() ([]map[string]interface{}, error) { return a.filesystem.GetRecycleBinEntries() } // RestoreFromRecycleBin 从回收站恢复文件 func (a *App) RestoreFromRecycleBin(recyclePath string) error { return a.filesystem.RestoreFromRecycleBin(recyclePath) } // DeletePermanently 永久删除回收站中的文件 func (a *App) DeletePermanently(recyclePath string) error { return a.filesystem.DeletePermanently(recyclePath) } // EmptyRecycleBin 清空回收站 func (a *App) EmptyRecycleBin() error { return a.filesystem.EmptyRecycleBin() } // ========== 应用配置接口 ========== // GetAppConfig 获取应用配置 func (a *App) GetAppConfig() (map[string]interface{}, error) { if a.configAPI == nil { return nil, fmt.Errorf("配置服务正在初始化中") } return a.configAPI.GetAppConfig() } // SaveAppConfigRequest 保存应用配置请求 type SaveAppConfigRequest struct { Tabs []api.AppTabDefinition `json:"tabs"` VisibleTabs []string `json:"visibleTabs"` DefaultTab string `json:"defaultTab"` } // SaveAppConfig 保存应用配置 func (a *App) SaveAppConfig(req SaveAppConfigRequest) (map[string]interface{}, error) { if a.configAPI == nil { return nil, fmt.Errorf("配置服务正在初始化中") } // 保存前检查是否有新启用的模块,需要动态初始化 oldConfig, _ := a.configAPI.GetAppConfig() var oldVisibleTabs []string if success, ok := oldConfig["success"].(bool); ok && success { if data, ok := oldConfig["data"].(map[string]interface{}); ok { if vtInterface, ok := data["visibleTabs"].([]interface{}); ok { oldVisibleTabs = common.InterfaceSliceToStringSlice(vtInterface) } } } apiReq := api.SaveAppConfigRequest{ Tabs: req.Tabs, VisibleTabs: req.VisibleTabs, DefaultTab: req.DefaultTab, } result, err := a.configAPI.SaveAppConfig(apiReq) if err != nil { return result, err } // 保存成功后,检查是否有新启用的模块需要初始化 if success, ok := result["success"].(bool); ok && success { a.handleNewlyEnabledModules(oldVisibleTabs, req.VisibleTabs) } return result, nil } // handleNewlyEnabledModules 处理新启用的模块 func (a *App) handleNewlyEnabledModules(oldTabs, newTabs []string) { newlyEnabled := common.Difference(newTabs, oldTabs) if len(newlyEnabled) == 0 { return } fmt.Printf("[模块] 检测到新启用的模块: %v\n", newlyEnabled) for _, tab := range newlyEnabled { switch tab { case common.TabDatabase: a.initDatabaseModule() case common.TabFileSystem: a.initFilesystemModule() case common.TabDevice: fmt.Println("[模块] 设备测试模块已启用") } } } // initDatabaseModule 延迟初始化数据库模块 func (a *App) initDatabaseModule() { if a.connectionAPI != nil { fmt.Println("[模块] 数据库模块已初始化,跳过") return } fmt.Println("[模块] 延迟初始化数据库模块...") var err error // 初始化 ConnectionAPI if a.connectionAPI, err = api.NewConnectionAPI(); err != nil { fmt.Printf("[模块] 数据库模块初始化失败: %v\n", err) return } // 初始化 SqlAPI if a.sqlAPI, err = api.NewSqlAPI(); err != nil { fmt.Printf("[模块] SqlAPI 初始化失败: %v\n", err) return } // 初始化 TabAPI if a.tabAPI, err = api.NewTabAPI(); err != nil { fmt.Printf("[模块] TabAPI 初始化失败: %v\n", err) return } fmt.Println("[模块] 数据库模块初始化完成") } // initFilesystemModule 延迟初始化文件系统模块 func (a *App) initFilesystemModule() { if a.filesystem != nil { fmt.Println("[模块] 文件系统模块已初始化,跳过") return } fmt.Println("[模块] 延迟初始化文件系统模块...") fsConfig := filesystem.DefaultConfig() var err error a.filesystem, err = filesystem.NewFileSystemService(fsConfig) if err != nil { fmt.Printf("[模块] 文件系统模块初始化失败: %v\n", err) return } // 启动文件服务器 go a.startFileServer() fmt.Println("[模块] 文件系统模块初始化完成") } // ExportPDF 导出PDF文件 func (a *App) ExportPDF(content string, title string, fileName string, fontSize int, pageWidth int, pageHeight int) (map[string]interface{}, error) { if a.pdfAPI == nil { return map[string]interface{}{ "success": false, "message": "PDF导出功能未初始化", }, fmt.Errorf("PDF导出功能未初始化") } req := api.PdfExportRequest{ Content: content, Title: title, FileName: fileName, FontSize: fontSize, PageWidth: pageWidth, PageHeight: pageHeight, } result, err := a.pdfAPI.ExportMarkdownToPDF(req) if err != nil { return map[string]interface{}{ "success": false, "message": err.Error(), }, err } return map[string]interface{}{ "success": result.Success, "message": result.Message, "path": result.Path, "size": result.Size, }, nil } // SelectPDFSaveDirectory 选择PDF保存目录 func (a *App) SelectPDFSaveDirectory() (string, error) { if a.pdfAPI == nil { return "", fmt.Errorf("PDF导出功能未初始化") } return a.pdfAPI.SelectDirectory() } // ExportMarkdownToPDF 使用gofpdf导出Markdown为PDF func (a *App) ExportMarkdownToPDF(markdownContent string) (string, error) { // 1. 弹出保存对话框 savePath, err := runtime.SaveFileDialog(a.ctx, runtime.SaveDialogOptions{ Title: "保存 PDF", DefaultFilename: "document.pdf", Filters: []runtime.FileFilter{ {DisplayName: "PDF 文件", Pattern: "*.pdf"}, }, }) if err != nil || savePath == "" { return "", err } // 2. 创建PDF pdf := gofpdf.New("P", "mm", "A4", "") pdf.AddPage() pdf.SetAutoPageBreak(true, 15) // 3. 解析Markdown并写入PDF lines := strings.Split(markdownContent, "\n") for _, line := range lines { if strings.HasPrefix(line, "# ") { // H1 标题 pdf.SetFont("Arial", "B", 24) pdf.Cell(40, 10, strings.TrimPrefix(line, "# ")) pdf.Ln(12) } else if strings.HasPrefix(line, "## ") { // H2 标题 pdf.SetFont("Arial", "B", 18) pdf.Cell(40, 10, strings.TrimPrefix(line, "## ")) pdf.Ln(10) } else if strings.HasPrefix(line, "### ") { // H3 标题 pdf.SetFont("Arial", "B", 14) pdf.Cell(40, 10, strings.TrimPrefix(line, "### ")) pdf.Ln(8) } else if strings.HasPrefix(line, "- ") || strings.HasPrefix(line, "* ") { // 无序列表 pdf.SetFont("Arial", "", 12) pdf.Cell(10, 7, "•") pdf.Cell(0, 7, strings.TrimPrefix(line, "- ")) pdf.Ln(7) } else if strings.HasPrefix(line, "1. ") || strings.HasPrefix(line, "2. ") || strings.HasPrefix(line, "3. ") { // 有序列表 pdf.SetFont("Arial", "", 12) pdf.Cell(10, 7, strings.TrimSpace(strings.SplitN(line, ".", 2)[0]) + ".") pdf.Cell(0, 7, strings.TrimSpace(strings.SplitN(line, ".", 2)[1])) pdf.Ln(7) } else if line == "" { // 空行 pdf.Ln(7) } else { // 普通文本 pdf.SetFont("Arial", "", 12) pdf.MultiCell(190, 7, line, "", "", false) } } // 4. 保存文件 err = pdf.OutputFileAndClose(savePath) if err != nil { return "", fmt.Errorf("保存PDF文件失败: %v", err) } return savePath, nil }