package main import ( "context" "encoding/base64" "fmt" "os" "os/exec" "path/filepath" stdruntime "runtime" "strings" "time" wailsRuntime "github.com/wailsapp/wails/v2/pkg/runtime" "xk/services" ) type FileHandler struct { ctx context.Context pdfService *services.PDFService wordService *services.WordService excelService *services.ExcelService imageService *services.ImageService } func NewFileHandler() *FileHandler { return &FileHandler{ pdfService: services.NewPDFService(), wordService: services.NewWordService(), excelService: services.NewExcelService(), imageService: services.NewImageService(), } } func (h *FileHandler) startup(ctx context.Context) { h.ctx = ctx } type FileResult struct { Success bool `json:"success"` Message string `json:"message"` Path string `json:"path,omitempty"` Size int64 `json:"size,omitempty"` OriginalSize int64 `json:"originalSize,omitempty"` } type ProcessRequest struct { InputPath string `json:"inputPath"` InputPaths []string `json:"inputPaths,omitempty"` OutputPath string `json:"outputPath,omitempty"` Format string `json:"format,omitempty"` OutputFormat string `json:"outputFormat,omitempty"` Quality string `json:"quality,omitempty"` QualityInt int `json:"qualityInt,omitempty"` Width int `json:"width,omitempty"` Height int `json:"height,omitempty"` Angle float64 `json:"angle,omitempty"` BgColor string `json:"bgColor,omitempty"` BgRemoveColor *services.ColorRGB `json:"bgRemoveColor,omitempty"` Text string `json:"text,omitempty"` Threshold int `json:"threshold,omitempty"` Shape *services.CropShape `json:"shape,omitempty"` Brightness int `json:"brightness,omitempty"` Contrast int `json:"contrast,omitempty"` Saturation int `json:"saturation,omitempty"` GrayscaleIntensity int `json:"grayscaleIntensity,omitempty"` SharpenAmount float64 `json:"sharpenAmount,omitempty"` BlurRadius float64 `json:"blurRadius,omitempty"` FlipH bool `json:"flipH,omitempty"` FlipV bool `json:"flipV,omitempty"` MaintainRatio *bool `json:"maintainRatio,omitempty"` PageRanges string `json:"pageRanges,omitempty"` DPI float64 `json:"dpi,omitempty"` ImageFormat string `json:"imageFormat,omitempty"` OptimizeLevel string `json:"optimizeLevel,omitempty"` CropX int `json:"cropX,omitempty"` CropY int `json:"cropY,omitempty"` } type AppConfig struct { DefaultOutputDir string `json:"defaultOutputDir"` } type Shortcut struct { ID string `json:"id"` Name string `json:"name"` Category string `json:"category"` Icon string `json:"icon"` } type RecentUse struct { ID string `json:"id"` Name string `json:"name"` Category string `json:"category"` UsedAt int64 `json:"usedAt"` } func (h *FileHandler) OpenFileDialog(title string, filters []string) (string, error) { var fileFilters []wailsRuntime.FileFilter if title == "" { title = "选择文件" } if len(filters) == 0 { filters = []string{"*"} } for _, f := range filters { displayName := "所有文件 (*.*)" switch f { case "*.pdf": displayName = "PDF 文件 (*.pdf)" case "*.doc;*.docx": displayName = "Word 文档 (*.doc;*.docx)" case "*.xls;*.xlsx": displayName = "Excel 文件 (*.xls;*.xlsx)" case "*.jpg;*.jpeg;*.png;*.gif;*.bmp;*.tiff;*.webp;*.ico": displayName = "图片文件 (*.jpg;*.png;*.gif;*.bmp;*.tiff;*.webp;*.ico)" } fileFilters = append(fileFilters, wailsRuntime.FileFilter{DisplayName: displayName, Pattern: f}) } path, err := wailsRuntime.OpenFileDialog(h.ctx, wailsRuntime.OpenDialogOptions{Title: title, Filters: fileFilters}) return path, err } func (h *FileHandler) OpenFilesDialog(title string, filters []string) ([]string, error) { var fileFilters []wailsRuntime.FileFilter if title == "" { title = "选择文件" } if len(filters) == 0 { filters = []string{"*"} } for _, f := range filters { displayName := "所有文件 (*.*)" switch f { case "*.jpg;*.jpeg;*.png;*.gif;*.bmp;*.tiff;*.webp;*.ico": displayName = "图片文件 (*.jpg;*.png;*.gif;*.bmp;*.tiff;*.webp;*.ico)" case "*.pdf": displayName = "PDF 文件 (*.pdf)" } fileFilters = append(fileFilters, wailsRuntime.FileFilter{DisplayName: displayName, Pattern: f}) } paths, err := wailsRuntime.OpenMultipleFilesDialog(h.ctx, wailsRuntime.OpenDialogOptions{Title: title, Filters: fileFilters}) return paths, err } func (h *FileHandler) OpenSaveDialog(title string, defaultFilename string, filters []string, defaultDir string) (string, error) { if title == "" { title = "保存文件" } var fileFilters []wailsRuntime.FileFilter for _, f := range filters { displayName := "所有文件 (*.*)" switch f { case "*.pdf": displayName = "PDF 文件 (*.pdf)" case "*.csv": displayName = "CSV 文件 (*.csv)" case "*.json": displayName = "JSON 文件 (*.json)" case "*.jpg;*.jpeg": displayName = "JPEG 图片 (*.jpg;*.jpeg)" case "*.png": displayName = "PNG 图片 (*.png)" case "*.ico": displayName = "ICO 图标 (*.ico)" } fileFilters = append(fileFilters, wailsRuntime.FileFilter{DisplayName: displayName, Pattern: f}) } path, err := wailsRuntime.SaveFileDialog(h.ctx, wailsRuntime.SaveDialogOptions{Title: title, DefaultFilename: defaultFilename, DefaultDirectory: defaultDir, Filters: fileFilters}) return path, err } func (h *FileHandler) OpenFolder(path string) error { if path == "" { return fmt.Errorf("路径不能为空") } var cmd *exec.Cmd switch stdruntime.GOOS { case "windows": cmd = exec.Command("explorer", path) case "darwin": cmd = exec.Command("open", path) default: cmd = exec.Command("xdg-open", path) } return cmd.Start() } // SaveBase64ToFile 将 base64 数据写入指定路径 func (h *FileHandler) SaveBase64ToFile(base64Data string, outputPath string) FileResult { if base64Data == "" { return FileResult{Success: false, Message: "数据为空"} } if outputPath == "" { return FileResult{Success: false, Message: "输出路径为空"} } data, err := base64.StdEncoding.DecodeString(base64Data) if err != nil { return FileResult{Success: false, Message: "解码失败: " + err.Error()} } if err := os.WriteFile(outputPath, data, 0644); err != nil { return FileResult{Success: false, Message: "写入文件失败: " + err.Error()} } return FileResult{Success: true, Path: outputPath, Size: int64(len(data))} } func (h *FileHandler) GetConfig() AppConfig { config := AppConfig{DefaultOutputDir: getDefaultOutputDir()} data, err := os.ReadFile(getConfigPath()) if err != nil { return config } _ = services.ParseJSON(data, &config) return config } func (h *FileHandler) SaveConfig(config AppConfig) error { data, err := services.ToJSON(config) if err != nil { return err } return os.WriteFile(getConfigPath(), data, 0644) } func (h *FileHandler) GetShortcuts() []Shortcut { var shortcuts []Shortcut data, err := os.ReadFile(getShortcutsPath()) if err != nil { return getDefaultShortcuts() } _ = services.ParseJSON(data, &shortcuts) if len(shortcuts) == 0 { return getDefaultShortcuts() } return shortcuts } func (h *FileHandler) SaveShortcuts(shortcuts []Shortcut) error { data, err := services.ToJSON(shortcuts) if err != nil { return err } return os.WriteFile(getShortcutsPath(), data, 0644) } func (h *FileHandler) GetRecentUses() []RecentUse { var recent []RecentUse data, err := os.ReadFile(getRecentPath()) if err != nil { return []RecentUse{} } _ = services.ParseJSON(data, &recent) return recent } func (h *FileHandler) AddRecentUse(tool RecentUse) error { var recent []RecentUse data, err := os.ReadFile(getRecentPath()) if err == nil { _ = services.ParseJSON(data, &recent) } for i, r := range recent { if r.ID == tool.ID { recent = append(recent[:i], recent[i+1:]...) break } } recent = append([]RecentUse{tool}, recent...) if len(recent) > 5 { recent = recent[:5] } data, err = services.ToJSON(recent) if err != nil { return err } return os.WriteFile(getRecentPath(), data, 0644) } func (h *FileHandler) GetImageBase64(path string) (string, error) { return h.imageService.GetImageBase64(path) } func (h *FileHandler) GetCompareImages(originalPath, processedPath string) (map[string]string, error) { result := make(map[string]string) if originalPath != "" { b64, err := h.imageService.GetImageBase64(originalPath) if err == nil { result["original"] = b64 } } if processedPath != "" { b64, err := h.imageService.GetImageBase64(processedPath) if err == nil { result["processed"] = b64 } } return result, nil } func (h *FileHandler) SaveResult(tempPath, outputPath string) (FileResult, error) { if outputPath == "" { return FileResult{}, fmt.Errorf("未指定输出路径") } data, err := os.ReadFile(tempPath) if err != nil { return FileResult{}, fmt.Errorf("读取临时文件失败: %v", err) } dir := filepath.Dir(outputPath) if err := os.MkdirAll(dir, 0755); err != nil { return FileResult{}, fmt.Errorf("创建目录失败: %v", err) } if err := os.WriteFile(outputPath, data, 0644); err != nil { return FileResult{}, fmt.Errorf("保存文件失败: %v", err) } info, err := os.Stat(outputPath) if err != nil { return FileResult{}, fmt.Errorf("获取文件信息失败: %v", err) } return FileResult{ Success: true, Message: "保存成功", Path: outputPath, Size: info.Size(), }, nil } func (h *FileHandler) ProcessFile(req ProcessRequest) FileResult { if req.InputPath == "" { return FileResult{Success: false, Message: "请选择文件"} } ext := strings.ToLower(filepath.Ext(req.InputPath)) switch ext { case ".pdf": return h.processPDF(req) case ".doc", ".docx": return h.processWord(req) case ".xls", ".xlsx": return h.processExcel(req) case ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".webp", ".ico": return h.processImage(req) case ".txt", ".md", ".html": return h.processTextToPDF(req) default: return FileResult{Success: false, Message: fmt.Sprintf("不支持的文件格式: %s", ext)} } } func (h *FileHandler) resolveOutputPath(inputPath, outputPath, suffix, ext string) string { if outputPath != "" { return outputPath } config := h.GetConfig() outDir := config.DefaultOutputDir if outDir == "" { outDir = filepath.Dir(inputPath) } baseName := services.GetBaseName(inputPath) return filepath.Join(outDir, baseName+suffix+ext) } func (h *FileHandler) getTempPath(inputPath, suffix string) string { tempDir := os.TempDir() baseName := services.GetBaseName(inputPath) return filepath.Join(tempDir, "xk_"+baseName+suffix+".png") } func (h *FileHandler) processPDF(req ProcessRequest) FileResult { var err error outputPath := h.resolveOutputPath(req.InputPath, req.OutputPath, "_processed", ".pdf") switch req.Format { case "compress": err = h.pdfService.OptimizePDF(req.InputPath, outputPath, req.OptimizeLevel) case "split": outputDir := filepath.Dir(outputPath) _, err = h.pdfService.SplitPDF(req.InputPath, outputDir, req.PageRanges) case "rotate": err = h.pdfService.RotatePDF(req.InputPath, outputPath, int(req.Angle)) case "watermark": err = h.pdfService.AddWatermark(req.InputPath, outputPath, req.Text) case "merge": err = h.pdfService.MergePDFs(req.InputPaths, outputPath) case "toImage": outputDir := filepath.Dir(outputPath) results, imgErr := h.pdfService.PDFToImages(req.InputPath, outputDir, req.ImageFormat, req.DPI) if imgErr != nil { err = imgErr } else if len(results) > 0 { return FileResult{Success: true, Message: fmt.Sprintf("已生成 %d 张图片", len(results)), Path: results[0], Size: 0} } case "toText": text, txtErr := h.pdfService.ExtractText(req.InputPath) if txtErr != nil { err = txtErr } else { txtPath := services.ChangeExtension(outputPath, ".txt") if writeErr := os.WriteFile(txtPath, []byte(text), 0644); writeErr != nil { err = writeErr } else { return h.getFileResult(req.InputPath, txtPath) } } case "toWord": text, txtErr := h.pdfService.ExtractText(req.InputPath) if txtErr != nil { err = txtErr } else { txtPath := services.ChangeExtension(outputPath, ".txt") if writeErr := os.WriteFile(txtPath, []byte(text), 0644); writeErr != nil { err = writeErr } else { return h.getFileResult(req.InputPath, txtPath) } } case "toExcel": text, txtErr := h.pdfService.ExtractText(req.InputPath) if txtErr != nil { err = txtErr } else { csvPath := services.ChangeExtension(outputPath, ".csv") lines := strings.Split(text, "\n") var csvContent strings.Builder for _, line := range lines { line = strings.TrimSpace(line) if line != "" { if strings.ContainsAny(line, ",\"") { csvContent.WriteString("\"" + strings.ReplaceAll(line, "\"", "\"\"") + "\"\n") } else { csvContent.WriteString(line + "\n") } } } if writeErr := os.WriteFile(csvPath, []byte(csvContent.String()), 0644); writeErr != nil { err = writeErr } else { return h.getFileResult(req.InputPath, csvPath) } } case "toHtml": text, txtErr := h.pdfService.ExtractText(req.InputPath) if txtErr != nil { err = txtErr } else { htmlPath := services.ChangeExtension(outputPath, ".html") lines := strings.Split(text, "\n") var html strings.Builder html.WriteString("\n\n\n\nPDF Text\n\n\n\n") for _, line := range lines { line = strings.TrimSpace(line) if line != "" { html.WriteString("

" + line + "

\n") } } html.WriteString("\n") if writeErr := os.WriteFile(htmlPath, []byte(html.String()), 0644); writeErr != nil { err = writeErr } else { return h.getFileResult(req.InputPath, htmlPath) } } case "toMarkdown": text, txtErr := h.pdfService.ExtractText(req.InputPath) if txtErr != nil { err = txtErr } else { mdPath := services.ChangeExtension(outputPath, ".md") if writeErr := os.WriteFile(mdPath, []byte(text), 0644); writeErr != nil { err = writeErr } else { return h.getFileResult(req.InputPath, mdPath) } } default: err = h.pdfService.OptimizePDF(req.InputPath, outputPath, "medium") } if err != nil { return FileResult{Success: false, Message: err.Error()} } return h.getFileResult(req.InputPath, outputPath) } func (h *FileHandler) processWord(req ProcessRequest) FileResult { outputPath := h.resolveOutputPath(req.InputPath, req.OutputPath, "", ".pdf") err := h.wordService.ConvertToPDF(req.InputPath, outputPath) if err != nil { return FileResult{Success: false, Message: err.Error()} } return h.getFileResult(req.InputPath, outputPath) } func (h *FileHandler) processExcel(req ProcessRequest) FileResult { outputExt := ".csv" if req.Format == "json" { outputExt = ".json" } outputPath := h.resolveOutputPath(req.InputPath, req.OutputPath, "", outputExt) var err error switch req.Format { case "csv": err = h.excelService.ConvertToCSV(req.InputPath, outputPath) case "json": err = h.excelService.ConvertToJSON(req.InputPath, outputPath) default: err = h.excelService.ConvertToCSV(req.InputPath, outputPath) } if err != nil { return FileResult{Success: false, Message: err.Error()} } return h.getFileResult(req.InputPath, outputPath) } func (h *FileHandler) processTextToPDF(req ProcessRequest) FileResult { outputPath := h.resolveOutputPath(req.InputPath, req.OutputPath, "", ".pdf") ext := strings.ToLower(filepath.Ext(req.InputPath)) var err error switch ext { case ".md": err = h.pdfService.MarkdownToPDF(req.InputPath, outputPath) case ".html": err = h.pdfService.HTMLToPDF(req.InputPath, outputPath) default: err = h.pdfService.TextToPDF(req.InputPath, outputPath) } if err != nil { return FileResult{Success: false, Message: err.Error()} } return h.getFileResult(req.InputPath, outputPath) } func (h *FileHandler) processImage(req ProcessRequest) FileResult { isImageOp := req.Format == "compress" || req.Format == "resize" || req.Format == "rotate" || req.Format == "grayscale" || req.Format == "brightness" || req.Format == "removebg" || req.Format == "crop" || req.Format == "flipH" || req.Format == "flipV" || req.Format == "sharpen" || req.Format == "blur" || req.Format == "invert" var outputPath string if isImageOp { outputPath = h.getTempPath(req.InputPath, "_processed") } else if req.Format == "convert" { outputPath = h.resolveOutputPath(req.InputPath, req.OutputPath, "", "."+h.getConvertFormat(req)) } else { outputPath = h.resolveOutputPath(req.InputPath, req.OutputPath, "", "."+req.Format) } var err error switch req.Format { case "compress": quality := 85 if req.QualityInt > 0 { quality = req.QualityInt } else { switch req.Quality { case "high": quality = 95 case "low": quality = 60 } } err = h.imageService.CompressImage(req.InputPath, outputPath, quality, req.Width, req.Height) case "resize": maintainRatio := true if req.MaintainRatio != nil { maintainRatio = *req.MaintainRatio } err = h.imageService.ResizeImage(req.InputPath, outputPath, req.Width, req.Height, maintainRatio) case "rotate": err = h.imageService.RotateImage(req.InputPath, outputPath, req.Angle, req.BgColor) if err == nil && req.FlipH { err = h.imageService.FlipH(outputPath, outputPath) } if err == nil && req.FlipV { err = h.imageService.FlipV(outputPath, outputPath) } case "grayscale": intensity := req.GrayscaleIntensity if intensity <= 0 { intensity = 100 } err = h.imageService.AdjustGrayscale(req.InputPath, outputPath, intensity) case "brightness": brightness := float64(req.Brightness) err = h.imageService.AdjustBrightness(req.InputPath, outputPath, brightness) if err == nil && req.Contrast != 0 { contrastPath := h.getTempPath(req.InputPath, "_contrast") if err2 := h.imageService.AdjustContrast(outputPath, contrastPath, float64(req.Contrast)); err2 != nil { err = err2 } else { os.Remove(outputPath) os.Rename(contrastPath, outputPath) } } if err == nil && req.Saturation != 0 { satPath := h.getTempPath(req.InputPath, "_saturation") if err2 := h.imageService.AdjustSaturation(outputPath, satPath, float64(req.Saturation)); err2 != nil { err = err2 } else { os.Remove(outputPath) os.Rename(satPath, outputPath) } } case "flipH": err = h.imageService.FlipH(req.InputPath, outputPath) case "flipV": err = h.imageService.FlipV(req.InputPath, outputPath) case "sharpen": amount := req.SharpenAmount if amount <= 0 { amount = 1.0 } err = h.imageService.SharpenImage(req.InputPath, outputPath, amount) case "blur": radius := req.BlurRadius if radius <= 0 { radius = 3.0 } err = h.imageService.BlurImage(req.InputPath, outputPath, radius) case "invert": err = h.imageService.InvertImage(req.InputPath, outputPath) case "removebg": err = h.imageService.RemoveBackground(req.InputPath, outputPath, req.Threshold, req.BgRemoveColor) case "crop": if req.Shape != nil { shape := *req.Shape if shape.X == 0 && shape.Y == 0 && (req.CropX != 0 || req.CropY != 0) { shape.X = req.CropX shape.Y = req.CropY } err = h.imageService.CropImageShape(req.InputPath, outputPath, shape) } else { err = h.imageService.CropImageShape(req.InputPath, outputPath, services.CropShape{ Type: "rectangle", X: req.CropX, Y: req.CropY, Width: req.Width, Height: req.Height, }) } case "toPdf": if outputPath == "" { outputPath = services.ChangeExtension(req.InputPath, ".pdf") } err = h.imageService.ImageToPDF(req.InputPath, outputPath) default: quality := 85 if req.QualityInt > 0 { quality = req.QualityInt } err = h.imageService.ConvertFormat(req.InputPath, outputPath, h.getConvertFormat(req), quality) } if err != nil { return FileResult{Success: false, Message: err.Error()} } result := h.getFileResult(req.InputPath, outputPath) if result.Success && isImageOp { result.Path = outputPath } return result } func (h *FileHandler) getConvertFormat(req ProcessRequest) string { if req.OutputFormat != "" { return req.OutputFormat } if req.Format == "convert" { return "png" } return req.Format } func (h *FileHandler) getFileResult(inputPath, outputPath string) FileResult { info, err := os.Stat(outputPath) if err != nil { return FileResult{Success: false, Message: fmt.Sprintf("获取文件信息失败: %v", err)} } result := FileResult{Success: true, Message: "处理完成", Path: outputPath, Size: info.Size()} if inputPath != "" { if inputInfo, err := os.Stat(inputPath); err == nil { result.OriginalSize = inputInfo.Size() } } return result } func (h *FileHandler) GetFileInfo(path string) map[string]interface{} { ext := strings.ToLower(filepath.Ext(path)) result := make(map[string]interface{}) switch ext { case ".pdf": info, err := h.pdfService.GetPDFInfo(path) if err == nil { result["type"] = "pdf" result["pages"] = info.Pages result["size"] = info.Size } case ".doc", ".docx": info, err := h.wordService.GetWordInfo(path) if err == nil { result["type"] = "word" result["title"] = info.Title result["words"] = info.Words result["size"] = info.Size } case ".xls", ".xlsx": info, err := h.excelService.GetExcelInfo(path) if err == nil { result["type"] = "excel" result["sheets"] = info.Sheets result["size"] = info.Size } case ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".webp", ".ico": info, err := h.imageService.GetImageInfo(path) if err == nil { result["type"] = "image" result["width"] = info.Width result["height"] = info.Height result["format"] = info.Format result["size"] = info.Size } } return result } func (h *FileHandler) ExtractPDFText(path string) (string, error) { return h.pdfService.ExtractText(path) } func (h *FileHandler) StitchImages(inputPaths []string, direction string, gap int, bgColor string, outputPath string) FileResult { if len(inputPaths) < 2 { return FileResult{Success: false, Message: "至少需要 2 张图片才能拼接"} } // 确定输出路径 if outputPath == "" { config := h.GetConfig() outDir := config.DefaultOutputDir if outDir == "" { outDir = filepath.Dir(inputPaths[0]) } outputPath = filepath.Join(outDir, "拼接长图.png") } resultPath, err := h.imageService.StitchImages(services.StitchRequest{ InputPaths: inputPaths, Direction: direction, Gap: gap, BgColor: bgColor, OutputPath: outputPath, }) if err != nil { return FileResult{Success: false, Message: err.Error()} } info, statErr := os.Stat(resultPath) if statErr != nil { return FileResult{Success: false, Message: fmt.Sprintf("获取文件信息失败: %v", statErr)} } return FileResult{ Success: true, Message: fmt.Sprintf("拼接完成,共 %d 张图片", len(inputPaths)), Path: resultPath, Size: info.Size(), } } func (h *FileHandler) LogError(msg string) { h.writeLog("ERROR", msg) } func (h *FileHandler) LogInfo(msg string) { h.writeLog("INFO", msg) } func (h *FileHandler) writeLog(level, msg string) { exe, _ := os.Executable() logDir := filepath.Join(filepath.Dir(exe), "logs") if err := os.MkdirAll(logDir, 0755); err != nil { return } logFile := filepath.Join(logDir, time.Now().Format("2006-01-02")+".log") f, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return } defer f.Close() fmt.Fprintf(f, "[%s] [%s] %s\n", time.Now().Format("15:04:05"), level, msg) } func (h *FileHandler) GetLogDates() []string { exe, _ := os.Executable() logDir := filepath.Join(filepath.Dir(exe), "logs") entries, err := os.ReadDir(logDir) if err != nil { return []string{} } var dates []string for _, e := range entries { if !e.IsDir() && strings.HasSuffix(e.Name(), ".log") { dates = append(dates, strings.TrimSuffix(e.Name(), ".log")) } } return dates } func (h *FileHandler) GetLogs(date string) string { exe, _ := os.Executable() logDir := filepath.Join(filepath.Dir(exe), "logs") logFile := filepath.Join(logDir, date+".log") data, err := os.ReadFile(logFile) if err != nil { return "" } return string(data) } func (h *FileHandler) GetExeDir() string { exe, _ := os.Executable() return filepath.Dir(exe) } func getConfigPath() string { home, _ := os.UserHomeDir() return filepath.Join(home, ".xk-config.json") } func getShortcutsPath() string { home, _ := os.UserHomeDir() return filepath.Join(home, ".xk-shortcuts.json") } func getRecentPath() string { home, _ := os.UserHomeDir() return filepath.Join(home, ".xk-recent.json") } func getDefaultOutputDir() string { home, _ := os.UserHomeDir() return filepath.Join(home, "Desktop") } func getDefaultShortcuts() []Shortcut { return []Shortcut{ {ID: "pdf-compress", Name: "压缩PDF", Category: "pdf", Icon: "FolderChecked"}, {ID: "image-compress", Name: "压缩图片", Category: "image", Icon: "FolderChecked"}, {ID: "image-convert", Name: "图片格式转换", Category: "image", Icon: "Switch"}, {ID: "excel-csv", Name: "Excel转CSV", Category: "excel", Icon: "Document"}, {ID: "word-pdf", Name: "Word转PDF", Category: "word", Icon: "Document"}, } }