Files
updata_hfb/internal/app/gui.go
T
2026-07-23 13:28:14 +08:00

319 lines
8.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 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
皮肤:暗星
绝密KD0.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() {}