diff --git a/frontend/bindings/u-desk/app.ts b/frontend/bindings/u-desk/app.ts index ad0a410..d85f291 100644 --- a/frontend/bindings/u-desk/app.ts +++ b/frontend/bindings/u-desk/app.ts @@ -559,13 +559,6 @@ export function SaveConnectionProfile(req: $models.SaveProfileRequest): $Cancell }); } -/** - * SelectPDFSaveDirectory 选择PDF保存目录 - */ -export function SelectPDFSaveDirectory(): $CancellablePromise { - return $Call.ByID(1403263131); -} - /** * SetMainWindow 设置主窗口引用(由 main.go 在创建窗口后调用) */ diff --git a/internal/api/pdf_api.go b/internal/api/pdf_api.go index 811c6ab..201c50d 100644 --- a/internal/api/pdf_api.go +++ b/internal/api/pdf_api.go @@ -308,10 +308,11 @@ func (api *PdfAPI) generatePDFFromHTML(htmlContent string, pageWidth, pageHeight var buf []byte - // 使用 file URL 加载本地HTML文件 + // file URL 需整体转义,与 generatePDFFromLocalFile 同姿势,中文/空格临时路径才能正确导航 + fileURL := (&url.URL{Scheme: "file", Path: filepath.ToSlash(htmlFile)}).String() + err = chromedp.Run(chromeCtx, - // 导航到HTML文件 - chromedp.Navigate("file://"+htmlFile), + chromedp.Navigate(fileURL), // 等待页面加载完成 chromedp.WaitReady("body"), // 打印到PDF @@ -377,13 +378,42 @@ func findChromeExecutable() (string, error) { return "", fmt.Errorf("未找到 Chrome 或 Edge 浏览器,导出 PDF 需要安装其一") } +// appendExportLog 追加一行PDF导出诊断日志到 %USERPROFILE%\.u-desk\logs\pdf-export.log +// 临时诊断插桩:用户反馈导出转圈即停且错误无处可查,用于收口记录导出链路每个返回点,定位后整体移除 +// 写日志自身失败静默,不向导出主链路传播错误 +func appendExportLog(msg string) { + home := os.Getenv("USERPROFILE") + if home == "" { + return + } + f, err := os.OpenFile(filepath.Join(home, ".u-desk", "logs", "pdf-export.log"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return + } + defer f.Close() + fmt.Fprintf(f, "%s %s\n", time.Now().Format("2006-01-02 15:04:05"), msg) +} + // ExportHtmlFileToPDF 将本地HTML文件加载渲染后导出为PDF -func (api *PdfAPI) ExportHtmlFileToPDF(srcPath string, saveName string) (*PdfExportResponse, error) { +func (api *PdfAPI) ExportHtmlFileToPDF(srcPath string, saveName string) (resp *PdfExportResponse, err error) { + // 临时诊断插桩:defer 收口记录每个 error 返回点与最终成功结果 + defer func() { + if err != nil { + appendExportLog(fmt.Sprintf("ExportHtmlFileToPDF error=%s", err.Error())) + return + } + if resp != nil { + appendExportLog(fmt.Sprintf("ExportHtmlFileToPDF ok path=%s size=%d", resp.Path, resp.Size)) + } + }() + appendExportLog(fmt.Sprintf("ExportHtmlFileToPDF entry srcPath=%q saveName=%q", srcPath, saveName)) + // 只接受实际存在的 html 文件,避免把任意路径交给 Chrome 渲染 info, err := os.Stat(srcPath) if err != nil { return nil, fmt.Errorf("HTML文件不存在: %s", srcPath) } + appendExportLog(fmt.Sprintf("ExportHtmlFileToPDF stat ok size=%d isDir=%v", info.Size(), info.IsDir())) if info.IsDir() { return nil, fmt.Errorf("目标路径是目录,不是HTML文件: %s", srcPath) } @@ -430,11 +460,24 @@ func (api *PdfAPI) ExportHtmlFileToPDF(srcPath string, saveName string) (*PdfExp } // generatePDFFromLocalFile 用 headless Chrome 加载本地HTML文件并打印为PDF -func (api *PdfAPI) generatePDFFromLocalFile(srcPath string) ([]byte, error) { +func (api *PdfAPI) generatePDFFromLocalFile(srcPath string) (buf []byte, err error) { + start := time.Now() + appendExportLog(fmt.Sprintf("generatePDFFromLocalFile start srcPath=%q", srcPath)) + // 临时诊断插桩:defer 收口记录结束耗时与结果 + defer func() { + if err != nil { + appendExportLog(fmt.Sprintf("generatePDFFromLocalFile end error=%s elapsed=%s", err.Error(), time.Since(start))) + return + } + appendExportLog(fmt.Sprintf("generatePDFFromLocalFile end ok bytes=%d elapsed=%s", len(buf), time.Since(start))) + }() + chromePath, err := findChromeExecutable() if err != nil { + appendExportLog(fmt.Sprintf("generatePDFFromLocalFile findChromeExecutable error=%s", err.Error())) return nil, err } + appendExportLog(fmt.Sprintf("generatePDFFromLocalFile browser hit=%q", chromePath)) absPath, err := filepath.Abs(srcPath) if err != nil { @@ -464,8 +507,6 @@ func (api *PdfAPI) generatePDFFromLocalFile(srcPath string) ([]byte, error) { chromeCtx, chromeCancel := chromedp.NewContext(allocCtx) defer chromeCancel() - var buf []byte - err = chromedp.Run(chromeCtx, chromedp.Navigate(fileURL), // 先确保新文档的 body 可查询,后续 JS 才会落在正确文档上