package main import ( "fmt" "image" "log" "net/http" "os" "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/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 *container.Split // 存储左侧面板引用 } 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{} // 创建状态栏 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() } // 自定义布局:固定宽度布局 type fixedWidthLayout struct { width float32 } func (f *fixedWidthLayout) MinSize(objects []fyne.CanvasObject) fyne.Size { if len(objects) == 0 { return fyne.NewSize(f.width, 0) } min := objects[0].MinSize() return fyne.NewSize(f.width, min.Height) } func (f *fixedWidthLayout) Layout(objects []fyne.CanvasObject, size fyne.Size) { // 布局所有子元素(只有一个),宽度固定,高度使用容器的高度 for _, child := range objects { child.Resize(fyne.NewSize(f.width, size.Height)) } } // 创建主UI布局 func createMainUI(window fyne.Window, appState *AppState) fyne.CanvasObject { appState.Window = window // 保存窗口引用 // 创建左侧面板(登录 + 店铺列表) leftPanel := createLeftPanel(window, appState) // 使用自定义布局固定左侧宽度为300像素 fixedLeft := container.New(&fixedWidthLayout{width: 300}, leftPanel) // 右侧面板(商品TAB展示) appState.ProductTabs = container.NewAppTabs() rightPanel := container.NewBorder( widget.NewLabel("商品信息"), nil, nil, nil, appState.ProductTabs, ) // 主布局(左右固定宽度布局) return container.NewHBox( fixedLeft, widget.NewSeparator(), // 添加分隔线 container.NewMax(rightPanel), // 右侧填充剩余空间 ) } // 创建左侧面板 func createLeftPanel(window fyne.Window, appState *AppState) fyne.CanvasObject { // 登录表单 loginPanel := createLoginForm(appState) // 店铺列表 shopListPanel := createShopListPanel(appState) // 左侧布局(上下分割) split := container.NewVSplit(loginPanel, shopListPanel) split.SetOffset(0.3) // 登录区域占30% appState.LeftPanel = split // 保存左侧面板引用 return split } // 创建登录表单 - 修复后的版本 func createLoginForm(appState *AppState) fyne.CanvasObject { usernameEntry := widget.NewEntry() passwordEntry := widget.NewPasswordEntry() // 尝试从数据库加载用户 user, err := UserModel.Api_find_by_username(usernameEntry.Text) if err == nil && user.LoginName != "" { usernameEntry.SetText(user.LoginName) } // 创建表单字段 emailLabel := widget.NewLabel("邮箱") passwordLabel := widget.NewLabel("密码") // 创建登录按钮 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 } appState.Shops = shops appState.CurrentUser, _ = UserModel.Api_find_by_username(usernameEntry.Text) // 更新UI fyne.DoAndWait(func() { appState.StatusBar.SetText(fmt.Sprintf("登录成功! 共 %d 个店铺", len(shops))) updateShopListBinding(appState) // 切换登录状态显示 switchToLoggedInState(appState, usernameEntry.Text) }) }() }) // 将登录按钮放在居中容器中 centeredButton := container.NewCenter(loginButton) // 创建表单布局 formGrid := container.NewGridWithColumns(2, emailLabel, usernameEntry, passwordLabel, passwordEntry, ) // 添加间距 paddedForm := container.NewVBox( widget.NewLabelWithStyle("登录面板", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), layout.NewSpacer(), formGrid, layout.NewSpacer(), centeredButton, layout.NewSpacer(), ) return container.NewPadded(paddedForm) } // 切换到登录状态显示 func switchToLoggedInState(appState *AppState, username string) { // 创建用户信息显示 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{} // 清空标签页 // 更新UI fyne.DoAndWait(func() { appState.StatusBar.SetText("已注销") updateShopListBinding(appState) switchToLoginForm(appState) }) }) // 将注销按钮居中 centeredLogoutButton := container.NewCenter(logoutButton) // 组合所有组件 loggedInPanel := container.NewVBox( userInfo, layout.NewSpacer(), centeredLogoutButton, layout.NewSpacer(), ) // 替换左侧面板的顶部内容 appState.LeftPanel.Leading = container.NewPadded(loggedInPanel) } // 切换回登录表单 func switchToLoginForm(appState *AppState) { // 重新创建登录表单 loginForm := createLoginForm(appState) // 替换左侧面板的顶部内容 appState.LeftPanel.Leading = loginForm } // 尝试自动登录 - 添加主线程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)) }) // 更新登录表单 if appState.LoginForm != nil { // 获取用户名输入框 usernameItem := appState.LoginForm.Items[0] usernameEntry, ok := usernameItem.Widget.(*widget.Entry) if !ok { fyne.DoAndWait(func() { appState.StatusBar.SetText("自动登录失败: 用户名控件类型错误") }) return } // 获取密码输入框 - 这里使用 *widget.Entry 类型 passwordItem := appState.LoginForm.Items[1] passwordEntry, ok := passwordItem.Widget.(*widget.Entry) if !ok { fyne.DoAndWait(func() { appState.StatusBar.SetText("自动登录失败: 密码控件类型错误") }) return } // 在主线程更新UI fyne.DoAndWait(func() { usernameEntry.SetText(user.LoginName) passwordEntry.SetText(user.LoginPass) appState.StatusBar.SetText("正在自动登录...") }) // 触发登录 appState.LoginForm.OnSubmit() } } // 修改后的异步加载店铺头像函数 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.CanvasObject { // 创建绑定数据 appState.ShopListBinding = binding.NewUntypedList() // 创建列表控件 - 使用自定义模板 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 { log.Println("错误:传入对象不是容器") return } // 检查容器结构是否如预期 if len(hbox.Objects) < 4 { log.Println("错误:容器子元素数量不足") return } // 获取头像组件(直接类型断言) avatar, ok := hbox.Objects[0].(*canvas.Image) if !ok { log.Println("错误:第一个子元素不是图像") return } // 获取名称标签(通过嵌套容器) nameContainer, ok := hbox.Objects[1].(*fyne.Container) if !ok || len(nameContainer.Objects) == 0 { log.Println("错误:名称容器无效") return } nameLabel, ok := nameContainer.Objects[0].(*widget.Label) if !ok { log.Println("错误:名称标签无效") return } // 获取状态图标 statusIcon, ok := hbox.Objects[3].(*widget.Icon) if !ok { log.Println("错误:状态图标无效") return } // 获取店铺数据 val, err := item.(binding.Untyped).Get() if err != nil { log.Printf("获取数据失败: %v", err) return } shop, ok := val.(ShopModel.Account) if !ok { log.Println("错误:数据类型不匹配") return } // 设置店铺名称 nameLabel.SetText(shop.AccountName) // 设置状态图标 if shop.CanLogin { statusIcon.SetResource(res.ResShuffleSvg) } else { statusIcon.SetResource(fyne.Theme.Icon(fyne.CurrentApp().Settings().Theme(), "error")) } // 异步加载头像(使用原有loadShopAvatar函数) go loadShopAvatar(avatar, shop.AccountAvatar) }, ) // 添加点击事件 - 使用主线程更新UI list.OnSelected = func(id widget.ListItemID) { shop := appState.Shops[id] // 在主线程更新状态栏 appState.StatusBar.SetText(fmt.Sprintf("加载 %s 的商品...", shop.AccountName)) go func() { // 加载商品数据 products, err := loadProductsForShop(shop) if err != nil { fyne.DoAndWait(func() { appState.StatusBar.SetText("加载商品失败: " + err.Error()) }) return } // 在主线程更新UI fyne.DoAndWait(func() { appState.StatusBar.SetText(fmt.Sprintf("已加载 %d 个商品", len(products))) addOrUpdateProductTab(appState, shop, products) }) }() } return container.NewBorder( widget.NewLabel("店铺列表"), nil, nil, nil, list, ) } // 更新店铺列表绑定数据 func updateShopListBinding(appState *AppState) { // 清空绑定数据 appState.ShopListBinding.Set(make([]interface{}, 0)) // 添加新数据 values := make([]interface{}, len(appState.Shops)) for i, shop := range appState.Shops { values[i] = shop } appState.ShopListBinding.Set(values) } // 为店铺加载商品数据 func loadProductsForShop(shop ShopModel.Account) ([]SkuModel.DataItem, error) { // 获取店铺的Cookie信息 // cookieInfo, found := CookieModel.Api_find_by_subject_id(shop.SubjectID) // if !found { // return nil, fmt.Errorf("未找到店铺的Cookie信息") // } // 模拟API调用获取商品数据 time.Sleep(1 * time.Second) // 模拟网络延迟 // 这里应该是实际的API调用 // products, err := SkuModel.GetSkuList(shop.SubjectID, cookieInfo.Token, cookieInfo.VerifyFp) // 模拟返回数据 return []SkuModel.DataItem{ {ProductID: "1001", Name: "商品A", MarketPrice: 999, DiscountPrice: 100}, {ProductID: "1002", Name: "商品B", MarketPrice: 1999, DiscountPrice: 50}, {ProductID: "1003", Name: "商品C", MarketPrice: 2999, DiscountPrice: 30}, }, nil } // 添加或更新商品TAB - 确保在主线程调用 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 = createProductList(products) appState.ProductTabs.Refresh() return } } // 创建新TAB newTab := container.NewTabItem(tabTitle, createProductList(products)) 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", product.MarketPrice/100)) case 3: label.SetText(fmt.Sprintf("%d", product.MarketPrice)) } }, ) // 设置列宽 table.SetColumnWidth(0, 100) table.SetColumnWidth(1, 300) table.SetColumnWidth(2, 100) table.SetColumnWidth(3, 100) return table } 这是修改后的完整代码,导致商品列表只显示一部分,并不是完全占据右侧面板
时间: 2025-07-22 09:26:34 浏览: 7
<think>我们注意到在`createMainUI`函数中,右侧面板使用了`container.NewBorder`,其中顶部放置了一个标签,而中间区域是`appState.ProductTabs`(一个`*container.AppTabs`)。但是,在`addOrUpdateProductTab`中,我们为每个店铺创建了一个商品列表,这个列表是一个表格(`widget.NewTable`),然后放在一个标签页中。
问题描述:商品列表只显示一部分,没有完全占据右侧面板。
可能的原因:
1. 表格没有正确设置大小策略,导致无法扩展。
2. 包含表格的容器没有设置扩展属性。
3. 标签页容器(`container.AppTabs`)没有设置填充整个可用空间。
解决方案:
在Fyne中,容器默认不会扩展其内容,除非我们明确设置。我们可以通过将表格放入一个具有合适布局的容器中来解决,例如使用`container.NewMax`或`container.NewBorder`来填充可用空间。
具体修改:
在`createProductList`函数中,我们返回的表格应该被包裹在一个可以扩展的容器中,比如`container.NewMax`,这样表格就会填充整个标签页的空间。
修改后的`createProductList`函数:</think># 修复商品列表显示问题并优化布局
您的商品列表只显示一部分是因为表格没有正确填充可用空间。我将修复这个问题,并提供完整的解决方案来确保商品列表完全占据右侧面板。
```go
// ... 其他代码保持不变 ...
// 创建商品列表 - 修复表格填充问题
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", product.MarketPrice/100))
case 3:
label.SetText(fmt.Sprintf("%d", product.MarketPrice))
}
},
)
// 设置列宽
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
}
// 修改主布局函数 - 确保右侧面板正确填充空间
func createMainUI(window fyne.Window, appState *AppState) fyne.CanvasObject {
appState.Window = window // 保存窗口引用
// 创建左侧面板(登录 + 店铺列表)
leftPanel := createLeftPanel(window, appState)
// 使用自定义布局固定左侧宽度为300像素
fixedLeft := container.New(&fixedWidthLayout{width: 300}, leftPanel)
// 右侧面板(商品TAB展示)
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), // 使用Max确保标签页填充空间
)
// 主布局(左右固定宽度布局)
return container.NewBorder(
nil, nil,
fixedLeft, // 左侧固定宽度面板
nil,
rightPanel, // 右侧主内容区
)
}
// 修改 createShopListPanel 函数 - 确保店铺列表可以滚动
func createShopListPanel(appState *AppState) fyne.CanvasObject {
// ... 创建列表控件代码保持不变 ...
// 创建滚动容器
scrollContainer := container.NewScroll(list)
scrollContainer.SetMinSize(fyne.NewSize(280, 200)) // 设置最小尺寸
return container.NewBorder(
widget.NewLabel("店铺列表"),
nil, nil, nil,
scrollContainer, // 使用可滚动的列表
)
}
// 修改 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 = createProductList(products)
appState.ProductTabs.Refresh()
return
}
}
// 创建新TAB - 使用容器确保填充
newTab := container.NewTabItem(tabTitle,
container.NewMax(createProductList(products)), // 确保商品列表填充整个标签页
)
appState.ProductTabs.Append(newTab)
appState.ProductTabs.Select(newTab)
}
// ... 其他代码保持不变 ...
```
## 问题分析与解决方案
### 问题原因分析
商品列表只显示一部分的主要原因是:
1. **表格容器没有正确设置尺寸**:表格没有设置最小尺寸或扩展策略
2. **缺少滚动容器**:当内容超出可视区域时无法滚动查看
3. **布局容器配置不当**:右侧面板没有正确填充剩余空间
### 解决方案
#### 1. 为商品表格添加滚动容器
```go
func createProductList(products []SkuModel.DataItem) fyne.CanvasObject {
// 创建表格...
// 创建滚动容器
scrollContainer := container.NewScroll(table)
scrollContainer.SetMinSize(fyne.NewSize(600, 400)) // 设置最小尺寸
return scrollContainer
}
```
#### 2. 确保标签页填充整个右侧面板
```go
// 右侧面板容器
rightPanel := container.NewBorder(
widget.NewLabelWithStyle("商品信息", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}),
nil, nil, nil,
container.NewMax(appState.ProductTabs), // 使用Max确保标签页填充空间
)
```
#### 3. 修改主布局使用Border布局
```go
// 主布局(左右固定宽度布局)
return container.NewBorder(
nil, nil,
fixedLeft, // 左侧固定宽度面板
nil,
rightPanel, // 右侧主内容区
)
```
#### 4. 确保店铺列表可滚动
```go
func createShopListPanel(appState *AppState) fyne.CanvasObject {
// ... 创建列表控件代码 ...
// 创建滚动容器
scrollContainer := container.NewScroll(list)
scrollContainer.SetMinSize(fyne.NewSize(280, 200))
return container.NewBorder(
widget.NewLabel("店铺列表"),
nil, nil, nil,
scrollContainer, // 使用可滚动的列表
)
}
```
#### 5. 确保商品列表填充标签页
```go
func addOrUpdateProductTab(appState *AppState, shop ShopModel.Account, products []SkuModel.DataItem) {
// ...
// 创建新TAB - 使用容器确保填充
newTab := container.NewTabItem(tabTitle,
container.NewMax(createProductList(products)), // 确保商品列表填充整个标签页
)
// ...
}
```
## 完整优化后的代码
```go
package main
import (
"fmt"
"image"
"log"
"net/http"
"os"
"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/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 *container.Split // 存储左侧面板引用
}
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{}
// 创建状态栏
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()
}
// 自定义布局:固定宽度布局
type fixedWidthLayout struct {
width float32
}
func (f *fixedWidthLayout) MinSize(objects []fyne.CanvasObject) fyne.Size {
if len(objects) == 0 {
return fyne.NewSize(f.width, 0)
}
min := objects[0].MinSize()
return fyne.NewSize(f.width, min.Height)
}
func (f *fixedWidthLayout) Layout(objects []fyne.CanvasObject, size fyne.Size) {
// 布局所有子元素(只有一个),宽度固定,高度使用容器的高度
for _, child := range objects {
child.Resize(fyne.NewSize(f.width, size.Height))
}
}
// 创建主UI布局 - 优化后的版本
func createMainUI(window fyne.Window, appState *AppState) fyne.CanvasObject {
appState.Window = window // 保存窗口引用
// 创建左侧面板(登录 + 店铺列表)
leftPanel := createLeftPanel(window, appState)
// 使用自定义布局固定左侧宽度为300像素
fixedLeft := container.New(&fixedWidthLayout{width: 300}, leftPanel)
// 右侧面板(商品TAB展示)
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), // 使用Max确保标签页填充空间
)
// 主布局(左右固定宽度布局)
return container.NewBorder(
nil, nil,
fixedLeft, // 左侧固定宽度面板
nil,
rightPanel, // 右侧主内容区
)
}
// 创建左侧面板
func createLeftPanel(window fyne.Window, appState *AppState) fyne.CanvasObject {
// 登录表单
loginPanel := createLoginForm(appState)
// 店铺列表
shopListPanel := createShopListPanel(appState)
// 左侧布局(上下分割)
split := container.NewVSplit(loginPanel, shopListPanel)
split.SetOffset(0.3) // 登录区域占30%
appState.LeftPanel = split // 保存左侧面板引用
return split
}
// 创建登录表单
func createLoginForm(appState *AppState) fyne.CanvasObject {
// ... 保持不变 ...
}
// 切换到登录状态显示
func switchToLoggedInState(appState *AppState, username string) {
// ... 保持不变 ...
}
// 切换回登录表单
func switchToLoginForm(appState *AppState) {
// ... 保持不变 ...
}
// 尝试自动登录
func tryAutoLogin(appState *AppState) {
// ... 保持不变 ...
}
// 加载店铺头像
func loadShopAvatar(img *canvas.Image, url string) {
// ... 保持不变 ...
}
// 创建店铺列表面板 - 优化滚动功能
func createShopListPanel(appState *AppState) fyne.CanvasObject {
// 创建绑定数据
appState.ShopListBinding = binding.NewUntypedList()
// 创建列表控件 - 使用自定义模板
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) {
// ... 保持不变 ...
},
)
// 添加点击事件
list.OnSelected = func(id widget.ListItemID) {
// ... 保持不变 ...
}
// 创建滚动容器
scrollContainer := container.NewScroll(list)
scrollContainer.SetMinSize(fyne.NewSize(280, 200)) // 设置最小尺寸
return container.NewBorder(
widget.NewLabel("店铺列表"),
nil, nil, nil,
scrollContainer, // 使用可滚动的列表
)
}
// 更新店铺列表绑定数据
func updateShopListBinding(appState *AppState) {
// ... 保持不变 ...
}
// 为店铺加载商品数据
func loadProductsForShop(shop ShopModel.Account) ([]SkuModel.DataItem, error) {
// ... 保持不变 ...
}
// 添加或更新商品TAB - 确保商品列表填充标签页
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 - 使用Max容器确保填充
tab.Content = container.NewMax(createProductList(products))
appState.ProductTabs.Refresh()
return
}
}
// 创建新TAB - 使用Max容器确保填充
newTab := container.NewTabItem(tabTitle,
container.NewMax(createProductList(products)),
)
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", product.MarketPrice/100))
case 3:
label.SetText(fmt.Sprintf("%d", product.MarketPrice))
}
},
)
// 设置列宽
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
}
```
## 关键优化点说明
### 1. 添加滚动容器
```go
// 创建滚动容器
scrollContainer := container.NewScroll(table)
scrollContainer.SetMinSize(fyne.NewSize(600, 400)) // 设置最小尺寸
```
### 2. 确保标签页填充空间
```go
// 右侧面板容器
rightPanel := container.NewBorder(
widget.NewLabelWithStyle("商品信息", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}),
nil, nil, nil,
container.NewMax(appState.ProductTabs), // 使用Max确保标签页填充空间
)
```
### 3. 优化主布局结构
```go
// 主布局(左右固定宽度布局)
return container.NewBorder(
nil, nil,
fixedLeft, // 左侧固定宽度面板
nil,
rightPanel, // 右侧主内容区
)
```
### 4. 确保商品列表填充标签页
```go
// 创建新TAB - 使用Max容器确保填充
newTab := container.NewTabItem(tabTitle,
container.NewMax(createProductList(products)),
)
```
### 5. 优化店铺列表滚动
```go
// 创建滚动容器
scrollContainer := container.NewScroll(list)
scrollContainer.SetMinSize(fyne.NewSize(280, 200))
return container.NewBorder(
widget.NewLabel("店铺列表"),
nil, nil, nil,
scrollContainer, // 使用可滚动的列表
)
```
## 布局结构说明
### 优化后的主布局
```
+---------------------------------+-------------------------------+
| 左侧固定区域 | 右侧主内容区 |
| 宽度=300px | 自动填充剩余空间 |
| +----------------------------+ | +-------------------------+ |
| | 登录面板 | | | 商品信息 | |
| | | | +-------------------------+ |
| +----------------------------+ | | | |
| | 店铺列表 | | | [商品标签页] | |
| | (带滚动功能) | | | | |
| | | | | +-------------------+ | |
| | | | | | 商品表格 | | |
| | | | | | (带滚动功能) | | |
| +----------------------------+ | | | | | |
| | | +-------------------+ | |
| | | | |
+---------------------------------+--+-------------------------+--+
```
### 滚动容器的作用
1. **商品表格**:当商品数量较多时,可以通过滚动查看所有内容
2. **店铺列表**:当店铺数量较多时,可以通过滚动查看所有店铺
3. **最小尺寸设置**:确保即使内容较少时,容器也有合理的大小
## 其他优化
1. **标签页位置**:
```go
appState.ProductTabs.SetTabLocation(container.TabLocationTop) // 标签在顶部
```
2. **标题样式**:
```go
widget.NewLabelWithStyle("商品信息", fyne.TextAlignCenter, fyne.TextStyle{Bold: true})
```
3. **表格列宽**:
```go
table.SetColumnWidth(0, 100) // 商品ID列宽
table.SetColumnWidth(1, 300) // 商品名称列宽
```
这些修改解决了商品列表只显示一部分的问题,确保商品列表完全占据右侧面板,并提供了更好的用户体验。
阅读全文
相关推荐


















