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/dataModel/CookieModel"
"main.go/dataModel/ShopModel"
"main.go/dataModel/SkuModel"
"main.go/dataModel/UserModel"
"main.go/res"
"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 // 新增:存储标签页与店铺的映射
SplitContainer *container.Split // 新增:保存分割布局引用
TopPanel *fyne.Container // 新增:保存顶部面板引用
ContentPanel *fyne.Container // 新增:保存内容面板引用
NeedsRefresh bool // 新增:状态变更标志
}
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), // 初始化映射
NeedsRefresh: false, // 初始化为false
}
// 启动状态监听器
startStateListener(appState)
// 尝试加载默认过滤文件
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 startStateListener(appState *AppState) {
go func() {
for {
time.Sleep(100 * time.Millisecond) // 每100ms检查一次
if appState.NeedsRefresh {
fyne.DoAndWait(func() {
refreshLeftPanel(appState)
appState.NeedsRefresh = false
})
}
}
}()
}
// 获取默认过滤文件路径
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)
})
}
// 修改 createMainUI 函数 - 保存分割布局引用
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)
appState.SplitContainer = split // 保存分割布局引用
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) fyne.CanvasObject {
return container.NewVBox(
// ... 其他代码保持不变 ...
container.NewCenter(
widget.NewButton("注销", func() {
// 重置状态
appState.CurrentUser = UserModel.UserInfo{}
appState.Shops = nil
appState.ProductTabs.Items = nil
appState.ProductTabs.Refresh()
appState.TabShopMap = make(map[string]ShopModel.Account)
// 设置状态变更标志
appState.NeedsRefresh = true
appState.StatusBar.SetText("已注销")
}),
),
)
}
// 重构创建顶部面板函数 - 确保状态正确反映
func createTopPanel(appState *AppState) *fyne.Container {
// 添加调试日志
fmt.Printf("创建顶部面板: 登录状态=%t, 用户名=%s\n",
appState.CurrentUser.LoginName != "",
appState.CurrentUser.LoginName)
var content fyne.CanvasObject
if appState.CurrentUser.LoginName != "" {
content = createLoggedInPanel(appState)
} else {
content = createLoginForm(appState)
}
return container.NewMax(content)
}
// 重构创建内容面板函数 - 确保状态正确反映
func createContentPanel(appState *AppState) *fyne.Container {
// 添加调试日志
fmt.Printf("创建内容面板: 登录状态=%t, 店铺数量=%d\n",
appState.CurrentUser.LoginName != "",
len(appState.Shops))
if appState.CurrentUser.LoginName != "" && len(appState.Shops) > 0 {
return createShopListPanel(appState)
} else if appState.CurrentUser.LoginName != "" {
return container.NewCenter(
widget.NewLabel("没有可用的店铺"),
)
}
return container.NewCenter(
widget.NewLabel("请先登录查看店铺列表"),
)
}
// 重构刷新函数 - 确保完全重建UI
func refreshLeftPanel(appState *AppState) {
if appState.SplitContainer == nil {
return
}
// 创建新的左侧面板
newLeftPanel := createLeftPanel(appState.Window, appState)
// 替换分割布局中的左侧面板
appState.SplitContainer.Leading = newLeftPanel
appState.LeftPanel = newLeftPanel
// 刷新分割布局
appState.SplitContainer.Refresh()
// 强制重绘整个窗口
appState.Window.Content().Refresh()
}
// 修改 createLeftPanel 函数 - 确保正确处理登录状态
func createLeftPanel(window fyne.Window, appState *AppState) *fyne.Container {
var topPanel fyne.CanvasObject
// 根据登录状态决定顶部面板
if appState.CurrentUser.LoginName != "" {
// 已登录状态
topPanel = createLoggedInPanel(appState)
} 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, // 中间 - 店铺列表
)
}
// 修改 createLoginForm 函数 - 简化结构
func createLoginForm(appState *AppState) fyne.CanvasObject {
usernameEntry := widget.NewEntry()
passwordEntry := widget.NewPasswordEntry()
usernameEntry.PlaceHolder = "输入邮箱地址"
passwordEntry.PlaceHolder = "输入密码"
// 登录按钮回调 - 直接调用刷新
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)
appState.StatusBar.SetText(fmt.Sprintf("登录成功! 共 %d 个店铺", len(shops)))
// 设置状态变更标志
appState.NeedsRefresh = true
})
}()
})
form := widget.NewForm(
widget.NewFormItem("邮箱:", usernameEntry),
widget.NewFormItem("密码:", passwordEntry),
)
appState.LoginForm = form
return container.NewVBox(
widget.NewLabelWithStyle("登录面板", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}),
form,
container.NewCenter(loginButton),
)
}
// 修改 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
appState.Shops = ShopModel.Api_select_struct(nil)
// 设置状态变更标志
appState.NeedsRefresh = true
})
}
// 修改后的异步加载店铺头像函数
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) {
// 模拟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
}
修改代码之后,点击登录按钮,任然没有切换到状态面板,而且createTopPanel、refreshShopListPanel、switchToLoggedInState、switchToLoginForm 和createContentPanel,等几个个函数没有调用。是否可以删除,还是需要调用即能解决面板切换的问题?
最新发布