文章目录
AI书签管理工具开发全记录(十五):TUI基本逻辑实现与数据展示
1.前言 📝
在上一篇文章中,我们完善了TUI的基本功能框架,增加了焦点切换功能。并且增加了日志显示功能支持。本文将聚焦于书签数据的页面展示。
2.核心要点分析
2.1 核心分析
categoryList *tview.List
bookmarkList *tview.List
categoryList和bookmarkList都是tview.List。
AddItem定义如下:
func (l *List) AddItem(mainText, secondaryText string, shortcut rune, selected func()) *List {
l.InsertItem(-1, mainText, secondaryText, shortcut, selected)
return l
}
这意味着我们只能存储一级分类标题,和二级分类标题。无法存放id之类的数据,更别说更多的自定义数据。
2.2 解决方案
将当前分类数据完整信息进行存储,后续通过选中的index判断当前选中的数据项。
// 存储分类信息 按index存储
categoryCache map[int]models.Category
// 存储书签信息 按index存储
bookmarkCache map[int]models.Bookmark
每次进行渲染分类时,除了需要清空列表数据,还需要重建分类信息缓存。
// 清空分类列表
t.categoryList.Clear()
t.categoryCache = make(map[int]models.Category)
3. 程序流程
3.1 用户交互流程
3.功能实现
3.1 书签分类渲染
func (t *TuiView) RenderCategoryList() {
// 清空分类列表
t.categoryList.Clear()
// 从数据库查询分类列表
var categories []models.Category
if err := t.db.Find(&categories).Error; err != nil {
// 处理错误,例如记录日志或显示错误信息
return
}
// 清空分类列表
t.categoryList.Clear()
t.categoryCache = make(map[int]models.Category)
// 渲染分类列表到tui界面
for _, category := range categories {
t.categoryList.AddItem(category.Name, category.Description, 0, nil)
// 存储分类信息
t.categoryCache[t.categoryList.GetItemCount()-1] = category
}
// 如果没有分类,显示提示信息
if t.categoryList.GetItemCount() == 0 {
t.categoryList.AddItem("无分类", "先去添加分类吧", 0, nil)
} else {
t.categoryList.SetCurrentItem(0)
t.RenderBookmarksView(categories[0].ID)
}
}
3.2 书签分类渲染
// 添加RenderBookmarksView函数
func (t *TuiView) RenderBookmarksView(categoryID uint) {
t.LogInfo("RenderBookmarksView: " + strconv.FormatUint(uint64(categoryID), 10))
// 清空书签列表
t.bookmarkList.Clear()
t.bookmarkCache = make(map[int]models.Bookmark)
// 从数据库查询指定分类的书签列表
var bookmarks []models.Bookmark
if err := t.db.Where("category_id = ?", categoryID).Find(&bookmarks).Error; err != nil {
// 处理错误,例如记录日志或显示错误信息
return
}
// 渲染书签列表到tui界面
for _, bookmark := range bookmarks {
t.bookmarkList.AddItem(bookmark.Title, bookmark.URL, 0, nil)
// 存储书签信息
t.bookmarkCache[t.bookmarkList.GetItemCount()-1] = bookmark
}
if t.bookmarkList.GetItemCount() > 0 {
t.bookmarkList.SetCurrentItem(0)
t.RenderBookmarkDetail(bookmarks[0].ID)
}
}
3.3 书签描述渲染
书签描述支持markdown,所以我们需要对markdown语法进行支持,更好的在终端中进行渲染。
我们需要使用到glamour库
安装
go get -u "github.com/charmbracelet/glamour"
编写工具函数
package utils
import (
"fmt"
"github.com/charmbracelet/glamour"
)
var MarkdownInstance = Markdown{}
type Markdown struct {
renderer *glamour.TermRenderer
}
func init() {
renderer, err := glamour.NewTermRenderer(
glamour.WithStandardStyle("dark"),
glamour.WithWordWrap(120),
glamour.WithEmoji(),
)
if err != nil {
fmt.Println(err)
panic(err)
}
MarkdownInstance.renderer = renderer
}
func (c *Markdown) MarkdownRender(content string) (string, error) {
out, err := c.renderer.Render(content)
if err != nil {
return "", err
}
return out, nil
}
书签描述渲染
// 完善RenderBookmarkDetail函数,调用markdown渲染
func (t *TuiView) RenderBookmarkDetail(bookmarkID uint) {
// 从数据库查询书签详情
var bookmark models.Bookmark
if err := t.db.First(&bookmark, bookmarkID).Error; err != nil {
// 处理错误,例如记录日志或显示错误信息
return
}
// 调用markdown渲染
renderedContent, err := utils.MarkdownInstance.MarkdownRender(bookmark.Description)
if err != nil {
// 处理错误,例如记录日志或显示错误信息
return
}
// 渲染书签详情到tui界面
t.descriptionView.SetText(tview.TranslateANSI(renderedContent))
}
注意必须调用tview.TranslateANSI
方法,否则终端会展示一堆乱码。
3.3 搜索功能实现
func (t *TuiView) SearchBookmarks(keyword string) {
t.isSearching = true
t.searchBox.SetText(keyword)
t.bookmarkList.Clear()
t.categoryList.Clear()
t.categoryCache = make(map[int]models.Category)
t.bookmarkCache = make(map[int]models.Bookmark)
// 从数据库查询匹配的书签
var bookmarks []models.Bookmark
if err := t.db.Where("title LIKE ? OR category_id IN (SELECT id FROM categories WHERE name LIKE ?)", "%"+keyword+"%", "%"+keyword+"%").Find(&bookmarks).Error; err != nil {
// 处理错误,例如记录日志或显示错误信息
return
}
// 将书签按分类分组
categoryBookmarks := make(map[uint][]models.Bookmark)
for _, bookmark := range bookmarks {
categoryBookmarks[bookmark.CategoryID] = append(categoryBookmarks[bookmark.CategoryID], bookmark)
}
// 查询所有包含匹配书签的分类
var categories []models.Category
if err := t.db.Where("id IN (?)", t.db.Table("bookmarks").Select("category_id").Where("title LIKE ? OR category_id IN (SELECT id FROM categories WHERE name LIKE ?)", "%"+keyword+"%", "%"+keyword+"%")).Find(&categories).Error; err != nil {
// 处理错误,例如记录日志或显示错误信息
return
}
// 渲染分类列表到tui界面
for _, category := range categories {
t.categoryList.AddItem(category.Name, category.Description, 0, nil)
// 存储分类信息
t.categoryCache[t.categoryList.GetItemCount()-1] = category
}
// 如果没有匹配的分类,显示提示信息
if t.categoryList.GetItemCount() == 0 {
t.categoryList.AddItem("无匹配分类", "请尝试其他关键词", 0, nil)
} else {
// 渲染第一个分类下的书签
if bookmarks, ok := categoryBookmarks[categories[0].ID]; ok {
for _, bookmark := range bookmarks {
t.bookmarkList.AddItem(bookmark.Title, bookmark.URL, 0, nil)
// 存储书签信息
t.bookmarkCache[t.bookmarkList.GetItemCount()-1] = bookmark
}
if t.bookmarkList.GetItemCount() > 0 {
t.bookmarkList.SetCurrentItem(0)
t.RenderBookmarkDetail(bookmarks[0].ID)
}
}
t.categoryList.SetCurrentItem(0)
}
}
需要注意,一定要屏蔽掉原有的书签查询逻辑,否则会造成数据异常。
3.4 事件绑定
绑定事件后,切换分类时,数据才会实时查询渲染。当搜索时,我们交由搜索函数自生处理搜索逻辑。
func (t *TuiView) BindChangeFunc() {
t.categoryList.SetChangedFunc(func(index int, mainText, secondaryText string, shortcut rune) {
if t.isSearching {
return
}
// 获取当前分类列表
category := t.categoryCache[index]
t.LogInfo(fmt.Sprintf("category: %v", category))
t.RenderBookmarksView(category.ID)
})
t.bookmarkList.SetChangedFunc(func(index int, mainText, secondaryText string, shortcut rune) {
bookmark := t.bookmarkCache[index]
t.LogInfo(fmt.Sprintf("bookmark: %v", bookmark))
t.RenderBookmarkDetail(bookmark.ID)
})
t.searchBox.SetDoneFunc(func(key tcell.Key) {
if key == tcell.KeyEnter {
t.SearchBookmarks(t.searchBox.GetText())
// 设置焦点到书签列表
t.Focus(t.bookmarkList)
} else if key == tcell.KeyF2 {
t.ResetSearch()
t.searchBox.SetText("")
}
})
}
3.5 启动时渲染分类
根据是否传入了搜索关键词来决定渲染逻辑。
// 如果搜索词不为空,直接加载搜索结果
if searchKeyword != "" {
c.searchBox.SetText(searchKeyword)
c.Search(searchKeyword)
c.focusIndex = 1
} else {
c.RenderCategoryView()
}
4.演示
可以快速进行书签的浏览和查询
往期系列
- Ai书签管理工具开发全记录(一):项目总览与技术蓝图
- Ai书签管理工具开发全记录(二):项目基础框架搭建
- AI书签管理工具开发全记录(三):配置及数据系统设计
- AI书签管理工具开发全记录(四):日志系统设计与实现
- AI书签管理工具开发全记录(五):后端服务搭建与API实现
- AI书签管理工具开发全记录(六):前端管理基础框框搭建 Vue3+Element Plus
- AI书签管理工具开发全记录(七):页面编写与接口对接
- AI书签管理工具开发全记录(八):Ai创建书签功能实现
- AI书签管理工具开发全记录(九):用户端页面集成与展示
- AI书签管理工具开发全记录(十):命令行中结合ai高效添加书签
- AI书签管理工具开发全记录(十一):MCP集成
- AI书签管理工具开发全记录(十二):MCP集成查询
- AI书签管理工具开发全记录(十三):TUI基本框架搭建
- AI书签管理工具开发全记录(十四):TUI基本界面完善