Files
file-tool-wails/handlers.go

863 lines
26 KiB
Go
Raw Normal View History

2026-06-17 08:05:20 +08:00
package main
import (
"context"
2026-06-30 12:30:49 +08:00
"encoding/base64"
2026-06-17 08:05:20 +08:00
"fmt"
"os"
"os/exec"
"path/filepath"
stdruntime "runtime"
"strings"
2026-06-23 15:33:22 +08:00
"time"
2026-06-17 08:05:20 +08:00
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"`
2026-06-23 15:33:22 +08:00
InputPaths []string `json:"inputPaths,omitempty"`
2026-06-17 08:05:20 +08:00
OutputPath string `json:"outputPath,omitempty"`
Format string `json:"format,omitempty"`
2026-06-23 15:33:22 +08:00
OutputFormat string `json:"outputFormat,omitempty"`
2026-06-17 08:05:20 +08:00
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"`
2026-06-23 15:33:22 +08:00
PageRanges string `json:"pageRanges,omitempty"`
DPI float64 `json:"dpi,omitempty"`
ImageFormat string `json:"imageFormat,omitempty"`
OptimizeLevel string `json:"optimizeLevel,omitempty"`
2026-06-17 08:05:20 +08:00
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
}
2026-06-30 12:30:49 +08:00
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
}
2026-06-17 08:05:20 +08:00
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()
}
2026-06-30 12:30:49 +08:00
// 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))}
}
2026-06-17 08:05:20 +08:00
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)
2026-06-23 15:33:22 +08:00
case ".txt", ".md", ".html":
return h.processTextToPDF(req)
2026-06-17 08:05:20 +08:00
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":
2026-06-23 15:33:22 +08:00
err = h.pdfService.OptimizePDF(req.InputPath, outputPath, req.OptimizeLevel)
2026-06-17 08:05:20 +08:00
case "split":
2026-06-23 15:33:22 +08:00
outputDir := filepath.Dir(outputPath)
_, err = h.pdfService.SplitPDF(req.InputPath, outputDir, req.PageRanges)
2026-06-17 08:05:20 +08:00
case "rotate":
err = h.pdfService.RotatePDF(req.InputPath, outputPath, int(req.Angle))
case "watermark":
err = h.pdfService.AddWatermark(req.InputPath, outputPath, req.Text)
2026-06-23 15:33:22 +08:00
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("<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"utf-8\">\n<title>PDF Text</title>\n<style>body{font-family:sans-serif;max-width:800px;margin:0 auto;padding:20px;line-height:1.6}</style>\n</head>\n<body>\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if line != "" {
html.WriteString("<p>" + line + "</p>\n")
}
}
html.WriteString("</body>\n</html>")
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)
}
}
2026-06-17 08:05:20 +08:00
default:
2026-06-23 15:33:22 +08:00
err = h.pdfService.OptimizePDF(req.InputPath, outputPath, "medium")
2026-06-17 08:05:20 +08:00
}
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)
}
2026-06-23 15:33:22 +08:00
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)
}
2026-06-17 08:05:20 +08:00
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,
})
}
2026-06-23 15:33:22 +08:00
case "toPdf":
if outputPath == "" {
outputPath = services.ChangeExtension(req.InputPath, ".pdf")
}
err = h.imageService.ImageToPDF(req.InputPath, outputPath)
2026-06-17 08:05:20 +08:00
default:
quality := 85
if req.QualityInt > 0 {
quality = req.QualityInt
}
2026-06-23 15:33:22 +08:00
err = h.imageService.ConvertFormat(req.InputPath, outputPath, h.getConvertFormat(req), quality)
2026-06-17 08:05:20 +08:00
}
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 {
2026-06-23 15:33:22 +08:00
if req.OutputFormat != "" {
return req.OutputFormat
}
if req.Format == "convert" {
2026-06-17 08:05:20 +08:00
return "png"
}
2026-06-23 15:33:22 +08:00
return req.Format
2026-06-17 08:05:20 +08:00
}
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
}
2026-06-23 15:33:22 +08:00
func (h *FileHandler) ExtractPDFText(path string) (string, error) {
return h.pdfService.ExtractText(path)
}
2026-06-30 12:30:49 +08:00
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(),
}
}
2026-06-23 15:33:22 +08:00
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)
}
2026-06-17 08:05:20 +08:00
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"},
}
}