package app import ( "context" "encoding/json" "fmt" "image/color" "io" "os" "strings" "time" "fyne.io/fyne/v2" fyneapp "fyne.io/fyne/v2/app" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" "updata_hfb/internal/hfb" ) const sampleText = `上号方式:QQ扫码 哈夫币纯币:156 段位:铂金 等级:60 保险格数:9 每日消耗:10 体力:7 负重:7 AWM子弹数量:80 6头数量:13 6甲数量:12 皮肤:暗星 绝密KD:0.2 押金:350 号主在线时间:11:00 至 00:00 封禁记录:无 常用地区:海南 回收比例:1:47.0 回收租金:332 联系电话:15203616450` func Run() { a := fyneapp.NewWithID("com.hfb.updata") w := a.NewWindow("哈夫币外部上传") w.Resize(fyne.NewSize(980, 880)) prefs := a.Preferences() serverEntry := widget.NewEntry() serverEntry.SetText(prefStringWithFallback(prefs, "server", envDefault("HFB_SERVER", "http://127.0.0.1:8080"))) serverEntry.SetPlaceHolder("例如:http://127.0.0.1:8080") uploaderEntry := widget.NewEntry() uploaderEntry.SetText(prefStringWithFallback(prefs, "uploader", strings.TrimSpace(os.Getenv("HFB_UPLOADER_NAME")))) uploaderEntry.SetPlaceHolder("后台上传管理员名称") secretEntry := widget.NewEntry() secretEntry.Password = true secretEntry.SetText(prefStringWithFallback(prefs, "secret", strings.TrimSpace(os.Getenv("HFB_UPLOAD_SECRET")))) secretEntry.SetPlaceHolder("未配置密钥可留空") timeoutEntry := widget.NewEntry() timeoutEntry.SetText(prefStringWithFallback(prefs, "timeout", "15s")) timeoutEntry.SetPlaceHolder("例如:15s") dataEntry := widget.NewMultiLineEntry() dataEntry.Wrapping = fyne.TextWrapWord dataEntry.SetMinRowsVisible(14) dataEntry.SetPlaceHolder(sampleText) resultView := newResultPanel(420) resultView.SetText("```text\n暂无结果\n点击“预览 JSON”或“上传”后,这里会显示多行结果。\n```") statusLabel := widget.NewLabel("准备就绪") setResult := func(text string) { resultView.SetText(formatResultText(text)) } setBusy := func(uploadBtn *widget.Button, previewBtn *widget.Button, busy bool) { if busy { uploadBtn.Disable() previewBtn.Disable() statusLabel.SetText("正在上传,请稍等...") return } uploadBtn.Enable() previewBtn.Enable() } var uploadBtn *widget.Button var previewBtn *widget.Button loadBtn := widget.NewButton("打开文件", func() { dialog.ShowFileOpen(func(reader fyne.URIReadCloser, err error) { if err != nil { dialog.ShowError(fmt.Errorf("打开文件失败:%w", err), w) return } if reader == nil { return } defer reader.Close() content, err := io.ReadAll(reader) if err != nil { dialog.ShowError(fmt.Errorf("读取文件失败:%w", err), w) return } dataEntry.SetText(string(content)) statusLabel.SetText("已载入文件") }, w) }) previewBtn = widget.NewButton("预览 JSON", func() { req, err := buildRequest(uploaderEntry.Text, dataEntry.Text) if err != nil { statusLabel.SetText("解析失败") dialog.ShowError(err, w) return } out, err := hfb.MarshalPretty(req) if err != nil { statusLabel.SetText("生成 JSON 失败") dialog.ShowError(err, w) return } setResult(string(out)) statusLabel.SetText("预览已生成") }) uploadBtn = widget.NewButton("上传", func() { timeout, err := time.ParseDuration(strings.TrimSpace(timeoutEntry.Text)) if err != nil || timeout <= 0 { dialog.ShowError(fmt.Errorf("请求超时时间不正确:%s", timeoutEntry.Text), w) return } req, err := buildRequest(uploaderEntry.Text, dataEntry.Text) if err != nil { statusLabel.SetText("解析失败") dialog.ShowError(err, w) return } server := strings.TrimSpace(serverEntry.Text) secret := strings.TrimSpace(secretEntry.Text) setBusy(uploadBtn, previewBtn, true) setResult("") go func() { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() resp, err := hfb.Client{ Server: server, Secret: secret, Timeout: timeout, }.Upload(ctx, req) fyne.Do(func() { setBusy(uploadBtn, previewBtn, false) if err != nil { statusLabel.SetText("上传失败") setResult(err.Error()) dialog.ShowError(err, w) return } statusLabel.SetText("上传成功") setResult(resp) dialog.ShowInformation("上传成功", "数据已经提交到服务器。", w) }) }() }) uploadBtn.Importance = widget.HighImportance serverEntry.OnChanged = func(s string) { prefs.SetString("server", strings.TrimSpace(s)) } uploaderEntry.OnChanged = func(s string) { prefs.SetString("uploader", strings.TrimSpace(s)) } secretEntry.OnChanged = func(s string) { prefs.SetString("secret", strings.TrimSpace(s)) } timeoutEntry.OnChanged = func(s string) { prefs.SetString("timeout", strings.TrimSpace(s)) } configForm := widget.NewForm( widget.NewFormItem("服务器", serverEntry), widget.NewFormItem("上传人", uploaderEntry), widget.NewFormItem("上传密钥", secretEntry), widget.NewFormItem("超时时间", timeoutEntry), ) configAccordion := widget.NewAccordion( widget.NewAccordionItem("连接配置", configForm), ) configAccordion.Close(0) toolbar := container.NewHBox(loadBtn, previewBtn, uploadBtn) content := container.NewBorder( nil, container.NewVBox(statusLabel), nil, nil, container.NewVScroll(container.NewVBox( configAccordion, toolbar, widget.NewLabel("账号数据"), dataEntry, widget.NewLabel("结果"), resultView, )), ) w.SetContent(content) w.ShowAndRun() } func buildRequest(uploaderName string, rawText string) (hfb.ExternalUploadRequest, error) { if strings.TrimSpace(rawText) == "" { return hfb.ExternalUploadRequest{}, fmt.Errorf("账号数据不能为空") } parsed, err := hfb.ParseUploadText(rawText) if err != nil { return hfb.ExternalUploadRequest{}, err } name := strings.TrimSpace(uploaderName) if name == "" { name = parsed.UploaderName } if name == "" { return hfb.ExternalUploadRequest{}, fmt.Errorf("缺少上传人名称:请在“上传人”里填写,或在文本中增加“上传人:姓名”") } return hfb.NewExternalUploadRequest(name, parsed.Account, time.Now()), nil } func envDefault(key string, fallback string) string { value := strings.TrimSpace(os.Getenv(key)) if value == "" { return fallback } return value } func prefStringWithFallback(prefs fyne.Preferences, key, fallback string) string { if prefs == nil { return fallback } value := strings.TrimSpace(prefs.String(key)) if value != "" { return value } return fallback } func formatResultText(text string) string { text = strings.TrimSpace(text) if text == "" { return "暂无结果" } var payload any if err := json.Unmarshal([]byte(text), &payload); err == nil { if pretty, err := json.MarshalIndent(payload, "", " "); err == nil { return "```json\n" + string(pretty) + "\n```" } } return "```text\n" + text + "\n```" } type resultPanel struct { widget.BaseWidget scroll *container.Scroll content *widget.RichText minHeight float32 } func newResultPanel(minHeight float32) *resultPanel { panel := &resultPanel{ content: widget.NewRichTextFromMarkdown(""), minHeight: minHeight, } panel.scroll = container.NewVScroll(panel.content) panel.ExtendBaseWidget(panel) return panel } func (p *resultPanel) SetText(text string) { p.content.ParseMarkdown(text) p.Refresh() } func (p *resultPanel) CreateRenderer() fyne.WidgetRenderer { return &resultPanelRenderer{panel: p} } type resultPanelRenderer struct { panel *resultPanel } func (r *resultPanelRenderer) Layout(size fyne.Size) { r.panel.scroll.Resize(size) r.panel.scroll.Move(fyne.NewPos(0, 0)) } func (r *resultPanelRenderer) MinSize() fyne.Size { size := r.panel.scroll.MinSize() if size.Height < r.panel.minHeight { size.Height = r.panel.minHeight } if size.Width < 1 { size.Width = 1 } return size } func (r *resultPanelRenderer) Refresh() { r.panel.scroll.Refresh() } func (r *resultPanelRenderer) BackgroundColor() color.Color { return theme.Color(theme.ColorNameBackground) } func (r *resultPanelRenderer) Objects() []fyne.CanvasObject { return []fyne.CanvasObject{r.panel.scroll} } func (r *resultPanelRenderer) Destroy() {}