package main
import (
“fmt”
“image”
“log”
“net/http”
“os”
“path/filepath”
“runtime”
“strings”
“time”
"fyne.io/fyne/v2" "fyne.io/fyne/v2/app" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/data/binding" "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/widget" "main.go/res" "main.go/dataModel/CookieModel" "main.go/dataModel/ShopModel" "main.go/dataModel/SkuModel" "main.go/dataModel/UserModel" "main.go/tuuz/database"
)
// 全局状态
type AppState struct {
Window fyne.Window
CurrentUser UserModel.UserInfo
Shops []ShopModel.Account
ProductTabs *container.AppTabs
StatusBar *widget.Label
ShopListBinding binding.UntypedList
LoginForm *widget.Form
LeftPanel *fyne.Container // 改为存储整个左侧面板容器
FilterFilePath string
FilterKeywords []string
ShopListPanel *fyne.Container // 新增:存储店铺列表面板
FilterPanel *fyne.Container // 存储过滤面板引用
KeywordCount *widget.Label // 存储关键字计数标签
TabShopMap map[string]ShopModel.Account // 新增:存储标签页与店铺的映射
}
func main() {
os.Setenv(“PLAYWRIGHT_BROWSERS_PATH”, “./browsers”)
database.Init()
UserModel.UserInit()
ShopModel.ShopInit()
CookieModel.CreateCookieInfoTable()
SkuModel.ProductInit()
myApp := app.New() myWindow := myApp.NewWindow("店铺管理工具") myWindow.Resize(fyne.NewSize(1200, 800)) // 初始化应用状态 appState := &AppState{ FilterFilePath: getDefaultFilterPath(), TabShopMap: make(map[string]ShopModel.Account), // 初始化映射 } // 尝试加载默认过滤文件 go loadFilterFile(appState) // 创建状态栏 appState.StatusBar = widget.NewLabel("就绪") statusBar := container.NewHBox(layout.NewSpacer(), appState.StatusBar) // 创建主布局 mainContent := createMainUI(myWindow, appState) // 设置整体布局 content := container.NewBorder( nil, // 顶部 statusBar, // 底部 nil, // 左侧 nil, // 右侧 mainContent, ) myWindow.SetContent(content) // 启动时尝试自动登录 go tryAutoLogin(appState) myWindow.ShowAndRun()
}
// 获取默认过滤文件路径
func getDefaultFilterPath() string {
if runtime.GOOS == “windows” {
return filepath.Join(os.Getenv(“USERPROFILE”), “Documents”, “filter.txt”)
}
return filepath.Join(os.Getenv(“HOME”), “filter.txt”)
}
// 修改 refreshAllProductTabs 函数
func refreshAllProductTabs(appState *AppState) {
if appState.ProductTabs == nil || len(appState.ProductTabs.Items) == 0 {
return
}
// 遍历所有标签页并刷新 for _, tab := range appState.ProductTabs.Items { // 通过标签页标题获取店铺 shop, exists := appState.TabShopMap[tab.Text] if !exists { continue } // 重新加载商品 go func(shop ShopModel.Account) { products, err := loadProductsForShop(shop, appState) if err != nil { fyne.DoAndWait(func() { appState.StatusBar.SetText(fmt.Sprintf("刷新 %s 商品失败: %s", shop.AccountName, err.Error())) }) return } fyne.DoAndWait(func() { // 更新标签页内容 tab.Content = container.NewMax(createProductList(products)) appState.ProductTabs.Refresh() appState.StatusBar.SetText(fmt.Sprintf("已刷新 %s 的商品", shop.AccountName)) }) }(shop) }
}
// 加载过滤文件
func loadFilterFile(appState *AppState) {
if appState.FilterFilePath == “” {
return
}
if _, err := os.Stat(appState.FilterFilePath); os.IsNotExist(err) { err := os.WriteFile(appState.FilterFilePath, []byte{}, 0644) if err != nil { log.Printf("创建过滤文件失败: %v", err) } return } content, err := os.ReadFile(appState.FilterFilePath) if err != nil { log.Printf("读取过滤文件失败: %v", err) return } lines := strings.Split(string(content), "\n") appState.FilterKeywords = []string{} for _, line := range lines { trimmed := strings.TrimSpace(line) if trimmed != "" { appState.FilterKeywords = append(appState.FilterKeywords, trimmed) } } fyne.DoAndWait(func() { appState.StatusBar.SetText(fmt.Sprintf("已加载 %d 个过滤关键字", len(appState.FilterKeywords))) // 更新关键字数量标签 if appState.KeywordCount != nil { // 修正为 KeywordCount appState.KeywordCount.SetText(fmt.Sprintf("关键字数量: %d", len(appState.FilterKeywords))) } // 刷新所有已打开的商品标签页 refreshAllProductTabs(appState) })
}
// 修改主布局函数 - 确保右侧面板正确填充空间
func createMainUI(window fyne.Window, appState *AppState) fyne.CanvasObject {
appState.Window = window
// 创建整个左侧面板 leftPanel := createLeftPanel(window, appState) appState.LeftPanel = leftPanel // 保存左侧面板容器引用 // 右侧面板 appState.ProductTabs = container.NewAppTabs() appState.ProductTabs.SetTabLocation(container.TabLocationTop) rightPanel := container.NewBorder( widget.NewLabelWithStyle("商品信息", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), nil, nil, nil, container.NewMax(appState.ProductTabs), ) // 使用HSplit布局 split := container.NewHSplit(leftPanel, rightPanel) split.SetOffset(0.25) return split
}
// 修改createFilterPanel函数 - 返回容器并保存引用
func createFilterPanel(appState *AppState) *fyne.Container {
// 创建文件路径标签
pathLabel := widget.NewLabel("过滤文件: " + appState.FilterFilePath)
pathLabel.Wrapping = fyne.TextWrapWord
// 创建选择文件按钮 selectButton := widget.NewButton("选择过滤文件", func() { dialog.ShowFileOpen(func(reader fyne.URIReadCloser, err error) { if err != nil { dialog.ShowError(err, appState.Window) return } if reader == nil { return // 用户取消 } // 更新文件路径 appState.FilterFilePath = reader.URI().Path() pathLabel.SetText("过滤文件: " + appState.FilterFilePath) // 加载过滤文件 go func() { loadFilterFile(appState) // 刷新所有已打开的商品标签页 refreshAllProductTabs(appState) }() }, appState.Window) }) // 创建刷新按钮 refreshButton := widget.NewButton("刷新过滤", func() { if appState.FilterFilePath != "" { appState.StatusBar.SetText("刷新过滤关键字...") go func() { loadFilterFile(appState) // 刷新所有已打开的商品标签页 refreshAllProductTabs(appState) }() } else { appState.StatusBar.SetText("请先选择过滤文件") } }) // 创建按钮容器 buttonContainer := container.NewHBox( selectButton, refreshButton, ) // 创建关键字计数标签 - 保存引用 keywordCount := widget.NewLabel(fmt.Sprintf("关键字数量: %d", len(appState.FilterKeywords))) keywordCount.TextStyle = fyne.TextStyle{Bold: true} appState.KeywordCount = keywordCount // 创建面板容器 panel := container.NewVBox( widget.NewSeparator(), widget.NewLabelWithStyle("商品过滤", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), pathLabel, keywordCount, buttonContainer, ) return panel
}
// 添加 createLoggedInPanel 函数
func createLoggedInPanel(appState *AppState, username string) fyne.CanvasObject {
userInfo := container.NewVBox(
widget.NewLabelWithStyle(“登录状态”, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}),
widget.NewSeparator(),
container.NewHBox(
widget.NewLabel(“用户:”),
widget.NewLabel(username),
),
container.NewHBox(
widget.NewLabel(“店铺数量:”),
widget.NewLabel(fmt.Sprintf(“%d”, len(appState.Shops))),
),
widget.NewSeparator(),
)
logoutButton := widget.NewButton("注销", func() { // 重置状态 appState.CurrentUser = UserModel.UserInfo{} appState.Shops = []ShopModel.Account{} appState.ProductTabs.Items = []*container.TabItem{} appState.ProductTabs.Refresh() appState.TabShopMap = make(map[string]ShopModel.Account) // 更新UI appState.StatusBar.SetText("已注销") updateShopListBinding(appState) // 刷新左侧面板 refreshLeftPanel(appState) }) centeredLogoutButton := container.NewCenter(logoutButton) return container.NewVBox( userInfo, layout.NewSpacer(), centeredLogoutButton, layout.NewSpacer(), )
}
// 添加 refreshLeftPanel 函数
func refreshLeftPanel(appState *AppState) {
if appState.LeftPanel == nil {
return
}
// 重新创建整个左侧面板 newLeftPanel := createLeftPanel(appState.Window, appState) // 替换左侧面板内容 appState.LeftPanel.Objects = newLeftPanel.Objects appState.LeftPanel.Refresh()
}
// 修改 createLeftPanel 函数 - 确保正确处理登录状态
func createLeftPanel(window fyne.Window, appState *AppState) *fyne.Container {
var topPanel fyne.CanvasObject
// 根据登录状态决定顶部面板 if appState.CurrentUser.LoginName != "" { // 已登录状态 topPanel = createLoggedInPanel(appState, appState.CurrentUser.LoginName) } else { // 未登录状态 topPanel = createLoginForm(appState) } // 创建店铺列表面板 if appState.ShopListPanel == nil { appState.ShopListPanel = createShopListPanel(appState) } else { // 刷新现有店铺列表 appState.ShopListPanel = createShopListPanel(appState) } // 创建过滤面板 if appState.FilterPanel == nil { appState.FilterPanel = createFilterPanel(appState) } // 使用Border布局 return container.NewBorder( topPanel, // 顶部 - 登录状态或登录表单 appState.FilterPanel, // 底部 - 过滤面板 nil, nil, // 左侧和右侧 appState.ShopListPanel, // 中间 - 店铺列表 )
}
// 创建登录表单 - 优化布局版本
// 创建登录表单
func createLoginForm(appState *AppState) fyne.CanvasObject {
usernameEntry := widget.NewEntry()
passwordEntry := widget.NewPasswordEntry()
usernameEntry.PlaceHolder = “输入邮箱地址”
passwordEntry.PlaceHolder = “输入密码”
// 尝试加载保存的用户 user, err := UserModel.Api_find_by_username(usernameEntry.Text) if err == nil && user.LoginName != "" { usernameEntry.SetText(user.LoginName) } // 修改登录按钮回调 - 使用新的状态更新机制 loginButton := widget.NewButton("登录", func() { appState.StatusBar.SetText("登录中...") go func() { // 模拟网络请求 time.Sleep(500 * time.Millisecond) // 获取店铺信息 shops := ShopModel.Api_select_struct(nil) if len(shops) == 0 { fyne.DoAndWait(func() { appState.StatusBar.SetText("获取店铺信息为空") }) return } fyne.DoAndWait(func() { // 更新应用状态 appState.Shops = shops appState.CurrentUser, _ = UserModel.Api_find_by_username(usernameEntry.Text) // 更新UI appState.StatusBar.SetText(fmt.Sprintf("登录成功! 共 %d 个店铺", len(shops))) updateShopListBinding(appState) // 切换到登录状态 switchToLoggedInState(appState, usernameEntry.Text) }) }() }) form := widget.NewForm( widget.NewFormItem("邮箱:", usernameEntry), widget.NewFormItem("密码:", passwordEntry), ) appState.LoginForm = form formContainer := container.NewVBox( layout.NewSpacer(), form, layout.NewSpacer(), container.NewCenter(loginButton), layout.NewSpacer(), ) title := widget.NewLabelWithStyle("登录面板", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) return container.NewPadded( container.NewBorder( title, nil, nil, nil, formContainer, ), )
}
// 修改 refreshShopListPanel 函数
func refreshShopListPanel(appState *AppState) {
if appState.LeftPanel == nil {
return
}
// 创建新的店铺列表面板 appState.ShopListPanel = createShopListPanel(appState) // 重新创建整个左侧面板 newLeftPanel := createLeftPanel(appState.Window, appState) // 替换左侧面板内容 appState.LeftPanel.Objects = newLeftPanel.Objects appState.LeftPanel.Refresh()
}
// 修改 switchToLoggedInState 函数 - 移除未使用的变量
func switchToLoggedInState(appState *AppState, username string) {
// 不再声明 loggedInPanel 变量
// 直接重新创建整个左侧面板
refreshLeftPanel(appState)
appState.StatusBar.SetText(fmt.Sprintf(“登录成功! 共 %d 个店铺”, len(appState.Shops)))
}
// 修改 switchToLoginForm 函数
func switchToLoginForm(appState *AppState) {
// 重置状态
appState.CurrentUser = UserModel.UserInfo{}
appState.Shops = []ShopModel.Account{}
// 重新创建整个左侧面板 newLeftPanel := createLeftPanel(appState.Window, appState) // 替换左侧面板内容 if appState.LeftPanel != nil { appState.LeftPanel.Objects = newLeftPanel.Objects appState.LeftPanel.Refresh() }
}
// 尝试自动登录 - 添加主线程UI更新
func tryAutoLogin(appState *AppState) {
// 获取所有用户
users := UserModel.Api_select_struct(nil)
if len(users) == 0 {
fyne.DoAndWait(func() {
appState.StatusBar.SetText(“获取已经存在的账号为空”)
})
return
}
// 尝试使用第一个用户自动登录 user := users[0] fyne.DoAndWait(func() { appState.StatusBar.SetText(fmt.Sprintf("尝试自动登录: %s...", user.LoginName)) }) // 检查 LoginForm 是否已初始化 if appState.LoginForm == nil { fyne.DoAndWait(func() { appState.StatusBar.SetText("自动登录失败: 登录表单尚未初始化") }) return } // 获取用户名输入框 if len(appState.LoginForm.Items) < 1 { fyne.DoAndWait(func() { appState.StatusBar.SetText("自动登录失败: 登录表单项目不足") }) return } usernameItem := appState.LoginForm.Items[0] usernameEntry, ok := usernameItem.Widget.(*widget.Entry) if !ok { fyne.DoAndWait(func() { appState.StatusBar.SetText("自动登录失败: 用户名控件类型错误") }) return } // 获取密码输入框 if len(appState.LoginForm.Items) < 2 { fyne.DoAndWait(func() { appState.StatusBar.SetText("自动登录失败: 登录表单项目不足") }) return } passwordItem := appState.LoginForm.Items[1] passwordEntry, ok := passwordItem.Widget.(*widget.Entry) if !ok { fyne.DoAndWait(func() { appState.StatusBar.SetText("自动登录失败: 密码控件类型错误") }) return } // 触发登录 fyne.DoAndWait(func() { usernameEntry.SetText(user.LoginName) passwordEntry.SetText(user.LoginPass) appState.StatusBar.SetText("正在自动登录...") // 更新应用状态 appState.CurrentUser = user // 刷新UI refreshLeftPanel(appState) })
}
// 修改后的异步加载店铺头像函数
func loadShopAvatar(img *canvas.Image, url string) {
if url == “” {
// 使用默认头像
fyne.DoAndWait(func() {
img.Resource = fyne.Theme.Icon(fyne.CurrentApp().Settings().Theme(), “account”)
img.Refresh()
})
return
}
// 创建HTTP客户端(可设置超时) client := &http.Client{ Timeout: 10 * time.Second, } resp, err := client.Get(url) if err != nil { log.Printf("加载头像失败: %v", err) return } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { log.Printf("头像请求失败: %s", resp.Status) return } // 解码图片 imgData, _, err := image.Decode(resp.Body) if err != nil { log.Printf("解码头像失败: %v", err) return } // 在主线程更新UI fyne.DoAndWait(func() { img.Image = imgData img.Refresh() })
}
// 修改后的 createShopListPanel 函数
func createShopListPanel(appState *AppState) *fyne.Container {
// 创建绑定数据
if appState.ShopListBinding == nil {
appState.ShopListBinding = binding.NewUntypedList()
} else {
// 确保绑定数据是最新的
updateShopListBinding(appState)
}
// 创建列表控件 list := widget.NewListWithData( appState.ShopListBinding, func() fyne.CanvasObject { avatar := canvas.NewImageFromResource(nil) avatar.SetMinSize(fyne.NewSize(40, 40)) avatar.FillMode = canvas.ImageFillContain nameLabel := widget.NewLabel("") statusIcon := widget.NewIcon(nil) return container.NewHBox( avatar, container.NewVBox(nameLabel), layout.NewSpacer(), statusIcon, ) }, func(item binding.DataItem, obj fyne.CanvasObject) { hbox, ok := obj.(*fyne.Container) if !ok || len(hbox.Objects) < 4 { return } avatar, _ := hbox.Objects[0].(*canvas.Image) nameContainer, _ := hbox.Objects[1].(*fyne.Container) nameLabel, _ := nameContainer.Objects[0].(*widget.Label) statusIcon, _ := hbox.Objects[3].(*widget.Icon) val, err := item.(binding.Untyped).Get() if err != nil { return } shop, ok := val.(ShopModel.Account) if !ok { return } nameLabel.SetText(shop.AccountName) if shop.CanLogin { statusIcon.SetResource(res.ResShuffleSvg) } else { statusIcon.SetResource(fyne.Theme.Icon(fyne.CurrentApp().Settings().Theme(), "error")) } go loadShopAvatar(avatar, shop.AccountAvatar) }, ) list.OnSelected = func(id widget.ListItemID) { if id < 0 || id >= len(appState.Shops) { return } shop := appState.Shops[id] appState.StatusBar.SetText(fmt.Sprintf("加载 %s 的商品...", shop.AccountName)) go func() { products, err := loadProductsForShop(shop, appState) if err != nil { fyne.DoAndWait(func() { appState.StatusBar.SetText("加载商品失败: " + err.Error()) }) return } fyne.DoAndWait(func() { appState.StatusBar.SetText(fmt.Sprintf("已加载 %d 个商品", len(products))) addOrUpdateProductTab(appState, shop, products) }) }() } // 创建滚动容器 - 设置最小高度确保可滚动 scrollContainer := container.NewScroll(list) scrollContainer.SetMinSize(fyne.NewSize(280, 200)) // 最小高度200确保可滚动 // 使用Max容器确保填充空间 return container.NewMax( container.NewBorder( widget.NewLabel("店铺列表"), nil, nil, nil, scrollContainer, ), )
}
// 更新店铺列表绑定数据
func updateShopListBinding(appState *AppState) {
if appState.ShopListBinding == nil {
appState.ShopListBinding = binding.NewUntypedList()
}
values := make([]interface{}, len(appState.Shops)) for i, shop := range appState.Shops { values[i] = shop } appState.ShopListBinding.Set(values)
}
// 应用商品过滤
func applyProductFilter(products []SkuModel.DataItem, keywords []string) []SkuModel.DataItem {
if len(keywords) == 0 {
return products // 没有关键字,返回所有商品
}
filtered := []SkuModel.DataItem{} for _, product := range products { exclude := false for _, keyword := range keywords { if strings.Contains(strings.ToLower(product.Name), strings.ToLower(keyword)) { exclude = true break } } if !exclude { filtered = append(filtered, product) } } return filtered
}
// 为店铺加载商品数据
func loadProductsForShop(shop ShopModel.Account, appState *AppState) ([]SkuModel.DataItem, error) {
// 获取店铺的Cookie信息
// cookieInfo, found := CookieModel.Api_find_by_subject_id(shop.SubjectID)
// if !found {
// return nil, fmt.Errorf(“未找到店铺的Cookie信息”)
// }
// 模拟API调用获取商品数据 time.Sleep(500 * time.Millisecond) // 模拟网络延迟 // 模拟返回数据 products := []SkuModel.DataItem{ {ProductID: "1001", Name: "高端智能手机", MarketPrice: 99900, DiscountPrice: 100}, {ProductID: "1002", Name: "无线蓝牙耳机", MarketPrice: 199900, DiscountPrice: 50}, {ProductID: "1003", Name: "智能手表", MarketPrice: 299900, DiscountPrice: 30}, {ProductID: "1004", Name: "平板电脑", MarketPrice: 399900, DiscountPrice: 20}, {ProductID: "1005", Name: "笔记本电脑", MarketPrice: 499900, DiscountPrice: 10}, } // 应用过滤 filteredProducts := applyProductFilter(products, appState.FilterKeywords) return filteredProducts, nil
}
// 修改 addOrUpdateProductTab 函数 - 确保商品列表填充标签页空间
func addOrUpdateProductTab(appState *AppState, shop ShopModel.Account, products []SkuModel.DataItem) {
tabTitle := shop.AccountName
// 检查是否已存在该TAB for _, tab := range appState.ProductTabs.Items { if tab.Text == tabTitle { // 更新现有TAB tab.Content = container.NewMax(createProductList(products)) // 更新映射 appState.TabShopMap[tabTitle] = shop appState.ProductTabs.Refresh() return } } // 创建新TAB newTab := container.NewTabItem( tabTitle, container.NewMax(createProductList(products)), ) // 添加到映射 appState.TabShopMap[tabTitle] = shop appState.ProductTabs.Append(newTab) appState.ProductTabs.Select(newTab)
}
// 创建商品列表 - 修复表格填充问题
func createProductList(products []SkuModel.DataItem) fyne.CanvasObject {
// 创建表格
table := widget.NewTable(
func() (int, int) {
return len(products) + 1, 4 // 行数=商品数+表头,列数=4
},
func() fyne.CanvasObject {
return widget.NewLabel(“模板文本”)
},
func(id widget.TableCellID, cell fyne.CanvasObject) {
label := cell.(*widget.Label)
if id.Row == 0 { // 表头 switch id.Col { case 0: label.SetText("商品ID") case 1: label.SetText("商品名称") case 2: label.SetText("价格") case 3: label.SetText("库存") } label.TextStyle.Bold = true return } // 数据行 product := products[id.Row-1] switch id.Col { case 0: label.SetText(product.ProductID) case 1: label.SetText(product.Name) case 2: label.SetText(fmt.Sprintf("¥%.2f", float64(product.MarketPrice)/100)) case 3: label.SetText(fmt.Sprintf("%d", product.DiscountPrice)) } }, ) // 设置列宽 table.SetColumnWidth(0, 100) table.SetColumnWidth(1, 300) table.SetColumnWidth(2, 100) table.SetColumnWidth(3, 100) // 创建滚动容器 scrollContainer := container.NewScroll(table) scrollContainer.SetMinSize(fyne.NewSize(600, 400)) // 返回可滚动的表格容器 return scrollContainer
}
这是修改后的代码,是否是因为并没有完全修改完毕,现在的状态是,点击登陆按钮后不仅没有切换到状态面板,而且登陆面板和店铺列表重叠。这次修改后,请给出完整的代码。
最新发布