AI书签管理工具开发全记录(十五):TUI基本逻辑实现与数据展示
文章目录
- AI书签管理工具开发全记录(十五):TUI基本逻辑实现与数据展示
- 1.前言 📝
- 2.核心要点分析
- 2.1 核心分析
- 2.2 解决方案
- 3. 程序流程
- 3.1 用户交互流程
- 3.功能实现
- 3.1 书签分类渲染
- 3.2 书签分类渲染
- 3.3 书签描述渲染
- 3.3 搜索功能实现
- 3.4 事件绑定
- 3.5 启动时渲染分类
- 4.演示
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.Categoryif 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.Bookmarkif 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 utilsimport ("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.Bookmarkif 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 = truet.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.Bookmarkif 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.Categoryif 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基本界面完善