diff --git a/data/app.db b/data/app.db index 9b45822..55ea547 100644 Binary files a/data/app.db and b/data/app.db differ diff --git a/errors.md b/errors.md new file mode 100644 index 0000000..fc51f60 --- /dev/null +++ b/errors.md @@ -0,0 +1,11 @@ +## 待修复问题 + +1. 左侧表展示排序会随机变化,需要固定排序 +2. 左侧表搜索无法支持原始表名搜索,需要同时展示原始表名并支持搜索 +3. 详情弹出层内容无 CSS 样式,展示混乱。且弹出层关闭按钮 icon 肉眼不可见 +4. toggleTheme 报错 + ``` + 应用错误: ReferenceError: toggleTheme is not defined + at HTMLButtonElement.onclick (?table=posts:32:40) + ``` +5. 文章表多写入些数据,方便测试分页功能。 diff --git a/internal/repository/data_repository.go b/internal/repository/data_repository.go index 5601bd7..47209ae 100644 --- a/internal/repository/data_repository.go +++ b/internal/repository/data_repository.go @@ -3,6 +3,7 @@ package repository import ( "fmt" "log/slog" + "sort" "github.com/rogeecn/database_render/internal/config" "github.com/rogeecn/database_render/internal/database" @@ -53,6 +54,11 @@ func (r *DataRepository) GetTables() ([]model.TableInfo, error) { }) } + // Sort tables by name to ensure consistent ordering + sort.Slice(tables, func(i, j int) bool { + return tables[i].Name < tables[j].Name + }) + return tables, nil } diff --git a/internal/template/renderer.go b/internal/template/renderer.go index 5467deb..9b21e08 100644 --- a/internal/template/renderer.go +++ b/internal/template/renderer.go @@ -123,6 +123,16 @@ func (r *Renderer) loadTemplates() error { "eq": func(a, b interface{}) bool { return fmt.Sprintf("%v", a) == fmt.Sprintf("%v", b) }, + "until": func(n int) []int { + result := make([]int, n) + for i := 0; i < n; i++ { + result[i] = i + 1 + } + return result + }, + "ge": func(a, b int) bool { + return a >= b + }, } // Load templates diff --git a/scripts/insert_test_data.sql b/scripts/insert_test_data.sql new file mode 100644 index 0000000..7527faf --- /dev/null +++ b/scripts/insert_test_data.sql @@ -0,0 +1,17 @@ +-- 为posts表插入更多测试数据 +INSERT INTO posts (title, content, category, tags, author_id, status, view_count, created_at, updated_at) VALUES +('深入理解Go语言并发编程', 'Go语言的并发模型基于CSP(Communicating Sequential Processes)理论,通过goroutine和channel实现高效的并发编程...', '技术', 'go,并发,编程', 1, 'published', 1250, datetime('now', '-1 day'), datetime('now')), +('微服务架构设计实践', '微服务架构通过将单体应用拆分为一组小型服务,每个服务运行在自己的进程中,服务间通过轻量级通信机制进行交互...', '架构', '微服务,架构,设计', 2, 'published', 890, datetime('now', '-2 days'), datetime('now')), +('Docker容器化技术详解', 'Docker是一种开源的容器化平台,它允许开发者将应用程序及其依赖打包到一个可移植的容器中...', '容器', 'docker,容器,kubernetes', 3, 'published', 2100, datetime('now', '-3 days'), datetime('now')), +('Kubernetes集群管理入门', 'Kubernetes是一个开源的容器编排平台,用于自动化容器化应用程序的部署、扩展和管理...', '容器', 'kubernetes,容器,编排', 4, 'published', 1560, datetime('now', '-4 days'), datetime('now')), +('现代前端框架对比分析', 'React、Vue和Angular是目前最流行的三大前端框架,本文将从性能、学习曲线、生态系统等方面进行对比...', '前端', 'react,vue,angular,前端', 1, 'published', 2340, datetime('now', '-5 days'), datetime('now')), +('数据库性能优化策略', '数据库性能优化是系统性能调优的重要组成部分,本文将介绍索引优化、查询优化、缓存策略等多个方面...', '数据库', '数据库,性能优化,索引', 2, 'published', 1780, datetime('now', '-6 days'), datetime('now')), +('RESTful API设计最佳实践', 'RESTful API是目前最流行的API设计风格,本文将介绍RESTful API的设计原则、最佳实践和常见误区...', 'API', 'restful,api,设计', 3, 'published', 3200, datetime('now', '-7 days'), datetime('now')), +('CI/CD流水线搭建指南', '持续集成和持续部署是现代软件开发的重要实践,本文将详细介绍如何搭建高效的CI/CD流水线...', 'DevOps', 'ci/cd,devops,自动化', 4, 'published', 980, datetime('now', '-8 days'), datetime('now')), +('云原生应用开发实战', '云原生是一种构建和运行应用程序的方法,充分利用云计算的弹性、可扩展性和分布式特性...', '云原生', '云原生,kubernetes,docker', 1, 'published', 1450, datetime('now', '-9 days'), datetime('now')), +('分布式系统架构设计', '分布式系统是现代互联网应用的基础,本文将探讨分布式系统的架构设计原则、CAP理论、一致性算法等...', '架构', '分布式,架构,cap理论', 2, 'published', 2670, datetime('now', '-10 days'), datetime('now')), +('DevOps文化与实践', 'DevOps是一种文化和实践,强调开发团队和运维团队之间的协作,以实现更快、更可靠的软件交付...', 'DevOps', 'devops,文化,协作', 3, 'published', 1120, datetime('now', '-11 days'), datetime('now')), +('大数据处理技术综述', '大数据技术栈包括数据采集、存储、处理、分析和可视化等多个环节,本文将全面介绍当前主流的大数据技术...', '大数据', '大数据,hadoop,spark', 4, 'published', 1890, datetime('now', '-12 days'), datetime('now')), +('人工智能与机器学习入门', '人工智能和机器学习正在改变我们的生活和工作方式,本文将介绍AI/ML的基本概念、算法和应用场景...', 'AI/ML', '人工智能,机器学习,深度学习', 1, 'published', 3560, datetime('now', '-13 days'), datetime('now')), +('网络安全防护策略', '网络安全是信息技术发展的重要保障,本文将介绍常见的网络攻击类型、防护策略和安全最佳实践...', '安全', '网络安全,安全防护,最佳实践', 2, 'published', 2340, datetime('now', '-14 days'), datetime('now')), +('移动应用开发趋势', '移动应用开发技术日新月异,本文将分析2024年移动应用开发的最新趋势和技术栈选择...', '移动开发', '移动开发,react native,flutter', 3, 'published', 1670, datetime('now', '-15 days'), datetime('now')); \ No newline at end of file diff --git a/scripts/test_fixes.sh b/scripts/test_fixes.sh new file mode 100755 index 0000000..2521cb4 --- /dev/null +++ b/scripts/test_fixes.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +echo "🧪 测试修复结果" +echo "==================" + +# 1. 测试排序稳定性 +echo "1. 检查表格排序稳定性..." +sqlite3 data/app.db "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;" > /tmp/table_order1.txt +sqlite3 data/app.db "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;" > /tmp/table_order2.txt +if diff /tmp/table_order1.txt /tmp/table_order2.txt > /dev/null; then + echo " ✅ 表格排序稳定" +else + echo " ❌ 表格排序不稳定" +fi + +# 2. 测试数据量 +echo "2. 检查posts表数据量..." +count=$(sqlite3 data/app.db "SELECT COUNT(*) FROM posts;") +echo " 📊 posts表共有 $count 条记录" + +if [ "$count" -gt 20 ]; then + echo " ✅ 数据量充足,支持分页测试" +else + echo " ❌ 数据量不足" +fi + +# 3. 检查数据库连接 +echo "3. 检查数据库连接..." +if sqlite3 data/app.db ".tables" > /dev/null 2>&1; then + echo " ✅ 数据库连接正常" +else + echo " ❌ 数据库连接失败" +fi + +echo "" +echo "🎉 所有修复测试完成!" +echo "请启动应用验证:" +echo " make dev" 或者 "go run cmd/server/main.go" \ No newline at end of file diff --git a/web/static/css/app.css b/web/static/css/app.css index cb7319e..23fc7f5 100644 --- a/web/static/css/app.css +++ b/web/static/css/app.css @@ -384,6 +384,12 @@ input[type="search"]::-webkit-search-results-decoration { justify-content: center; z-index: 1000; padding: 16px; + animation: fadeIn 0.2s ease; +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } } .modal.hidden { @@ -398,6 +404,18 @@ input[type="search"]::-webkit-search-results-decoration { width: 100%; max-height: 80vh; overflow-y: auto; + animation: slideUp 0.2s ease; +} + +@keyframes slideUp { + from { + transform: translateY(20px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } } .modal-header { @@ -423,6 +441,9 @@ input[type="search"]::-webkit-search-results-decoration { cursor: pointer; border-radius: var(--radius-sm); transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; } .modal-close:hover { @@ -430,10 +451,54 @@ input[type="search"]::-webkit-search-results-decoration { color: var(--text-primary); } +.modal-close svg { + width: 24px; + height: 24px; + stroke: currentColor; + stroke-width: 2; +} + .modal-body { padding: 24px; } +/* 详情内容样式 */ +.detail-content { + display: flex; + flex-direction: column; + gap: 16px; +} + +.detail-item { + display: flex; + flex-direction: column; + gap: 4px; + padding: 12px; + background: var(--bg-secondary); + border-radius: var(--radius-md); + border: 1px solid var(--border-light); +} + +.detail-label { + font-weight: 600; + font-size: 14px; + color: var(--text-secondary); + text-transform: capitalize; +} + +.detail-value { + font-size: 16px; + color: var(--text-primary); + line-height: 1.5; + word-break: break-word; +} + +.detail-value:empty::before { + content: '- -'; + color: var(--text-muted); + font-style: italic; +} + /* 通知样式 */ .notification { position: fixed; diff --git a/web/static/css/components/sidebar.css b/web/static/css/components/sidebar.css index 4d64735..5d36b81 100644 --- a/web/static/css/components/sidebar.css +++ b/web/static/css/components/sidebar.css @@ -205,23 +205,34 @@ flex: 1; min-width: 0; overflow: hidden; + display: flex; + flex-direction: column; + gap: 1px; } .nav-title { - display: block; font-size: 14px; line-height: 1.3; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + color: var(--text-primary); +} + +.nav-subtitle { + font-size: 11px; + color: var(--text-muted); + font-family: 'Monaco', 'Consolas', 'Courier New', monospace; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + opacity: 0.8; } .nav-description { - display: block; font-size: 12px; color: var(--text-muted); - margin-top: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; diff --git a/web/static/js/app.js b/web/static/js/app.js index c3d7f1b..ea1553a 100644 --- a/web/static/js/app.js +++ b/web/static/js/app.js @@ -216,9 +216,12 @@ class AppState { navLinks.forEach(link => { const title = link.querySelector('.nav-title').textContent.toLowerCase(); + const subtitle = link.querySelector('.nav-subtitle').textContent.toLowerCase(); const description = link.querySelector('.nav-description').textContent.toLowerCase(); - const matches = title.includes(searchTerm) || description.includes(searchTerm); + const matches = title.includes(searchTerm) || + subtitle.includes(searchTerm) || + description.includes(searchTerm); link.style.display = matches ? 'flex' : 'none'; link.parentElement.style.display = matches ? 'block' : 'none'; @@ -517,7 +520,7 @@ class AppState { for (const [key, value] of Object.entries(data.data || {})) { html += `
- +
${key}
${value || ''}
`; @@ -609,6 +612,7 @@ class AppState { // 全局函数 window.toggleSidebar = () => app.toggleSidebar(); window.closeSidebar = () => app.closeSidebar(); +window.toggleTheme = () => app.toggleTheme(); window.debounceSearch = (query) => app.handleSearchInput(query); window.performSearch = () => { const searchInput = document.getElementById('searchInput'); diff --git a/web/templates/list.html b/web/templates/list.html index cceb866..846787e 100644 --- a/web/templates/list.html +++ b/web/templates/list.html @@ -63,7 +63,7 @@ class="nav-link {{if eq .Name $.Table}}active{{end}}" role="menuitem" aria-current="{{if eq .Name $.Table}}page{{end}}" - title="{{.Alias}} - {{.Description}}"> + title="{{.Alias}} ({{.Name}}) - {{.Description}}"> -