Files
sundynix-agentix/sundynix-desktop/app.go
T
Blizzard b4012dbbba fix(desktop): 报告 PDF 导出在壳内改走原生桥 —— WKWebView 拦死 window.open
实机验证抓到的:桌面壳内点 PDF 报「打印窗口被拦截」——Wails v3 的 WKWebView
把 window.open 拦成 null,前端弹打印窗那条路在壳内根本走不通(浏览器预览没事)。

- app.go 加 PrintReportPage(filename, html):打印视图 HTML 落临时文件(文件名
  过滤路径字符),openInSystem 交系统默认浏览器打开,页面 onload 自动唤起打印框,
  用户直接「存储为 PDF」。CJK 零字体依赖的原有优势不变。
- desktop.ts printReportHtml 改 async:inWails 走原生桥,浏览器维持 window.open;
  RunsView 调用点随之 async + 错误透 toast。
- 绑定重新生成(注意要 `wails3 generate bindings -ts`,裸跑默认吐 JS 且要
  GOWORK=off,否则 go.work 干扰找不到 Service)。

验证:go test/tsc/68 例 vitest 全绿;壳内被拦是用户实机复现的。
⚠️ 原生桥新路径(临时文件→浏览器→打印框)用户尚未实机点验,重新打的包已就位,
下次跑报告顺手验。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 09:40:15 +08:00

138 lines
4.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"fmt"
"io"
"net/http"
"os"
"os/exec"
"path/filepath"
goruntime "runtime"
"strings"
"time"
"github.com/wailsapp/wails/v3/pkg/application"
)
// App 经 Wails v3 的 Service 绑定暴露给前端,承载只有桌面端能做的原生能力:
// 文件读写、系统"另存为"框、用系统默认应用打开、原生通知。
// v3 中不再需要注入 ctx——对话框经 application.Get().Dialog 获取。
type App struct{}
// Ping 供前端探活 Go 桥是否就绪。
func (a *App) Ping() string { return "sundynix-desktop ok" }
// ReadLocalFile 读取本地文件内容(本地文件系统 I/O)。
func (a *App) ReadLocalFile(path string) (string, error) {
b, err := os.ReadFile(path)
if err != nil {
return "", err
}
return string(b), nil
}
// SaveReportAs 弹原生"另存为"对话框,把 url 指向的报告(.docx)下载到用户选定路径。
// 返回保存路径;用户取消则返回空串。
func (a *App) SaveReportAs(url, filename string) (string, error) {
if filename == "" {
filename = "report.docx"
}
path, err := application.Get().Dialog.SaveFile().
SetFilename(filename).
AddFilter("Word 文档 (*.docx)", "*.docx").
PromptForSingleSelection()
if err != nil || path == "" {
return "", err
}
if err := download(url, path); err != nil {
return "", err
}
return path, nil
}
// OpenReport 把报告下载到临时目录,并用系统默认应用(Word/Pages/WPS)打开。
func (a *App) OpenReport(url, filename string) error {
if filename == "" {
filename = "report.docx"
}
dst := filepath.Join(os.TempDir(), "sundynix-open-"+filename)
if err := download(url, dst); err != nil {
return err
}
return openInSystem(dst)
}
// PrintReportPage 把报告打印视图 HTML 落到临时文件,交系统默认浏览器打开(在那里 ⌘P →
// 存储为 PDF)。Wails 的 WKWebView 会把 window.open 拦成 null,前端弹打印窗那条路在壳内
// 走不通;转交系统浏览器后「前端打印出 PDF、CJK 零字体依赖」的原有优势不变。返回落盘路径。
func (a *App) PrintReportPage(filename, html string) (string, error) {
if filename == "" {
filename = "report"
}
// 文件名进过滤:主题可能含 / 之类的路径字符。
safe := strings.Map(func(r rune) rune {
if strings.ContainsRune(`/\:*?"<>|`, r) {
return '_'
}
return r
}, filename)
dst := filepath.Join(os.TempDir(), "sundynix-print-"+safe+".html")
if err := os.WriteFile(dst, []byte(html), 0o600); err != nil {
return "", err
}
return dst, openInSystem(dst)
}
// Notify 弹一条系统通知(best-effortmacOS 用 osascript,其它平台暂静默)。
func (a *App) Notify(title, body string) {
if goruntime.GOOS == "darwin" {
script := fmt.Sprintf("display notification %q with title %q", body, title)
_ = exec.Command("osascript", "-e", script).Start()
}
}
// downloadClient 给下载加超时:默认 http.Get 用的 client 没有超时,
// 上游卡住的话「另存为」会永远转下去,用户只能强杀 app。
var downloadClient = &http.Client{Timeout: 3 * time.Minute}
// download 把 url 下载到 dst。失败一律不留残file:
// 中途断开会在用户选定的路径上留个半截 .docx,带着用户起的名字,以后双击打不开。
func download(url, dst string) (err error) {
resp, err := downloadClient.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return fmt.Errorf("下载失败 HTTP %d", resp.StatusCode)
}
f, err := os.Create(dst)
if err != nil {
return err
}
defer func() {
// Close 的错误不能吞:写文件时 io.Copy 成功不代表数据落了盘,
// flush 失败只会在 Close 上报出来——吞掉就是静默截断。
cerr := f.Close()
if err == nil {
err = cerr
}
if err != nil {
_ = os.Remove(dst) // 失败不留残file
}
}()
_, err = io.Copy(f, resp.Body)
return err
}
func openInSystem(path string) error {
switch goruntime.GOOS {
case "darwin":
return exec.Command("open", path).Start()
case "windows":
return exec.Command("rundll32", "url.dll,FileProtocolHandler", path).Start()
default:
return exec.Command("xdg-open", path).Start()
}
}