fix: errors

This commit is contained in:
2025-08-06 14:59:39 +08:00
parent c4ad0c1dc9
commit aa20b6d7e6
10 changed files with 188 additions and 23 deletions

Binary file not shown.

11
errors.md Normal file
View File

@@ -0,0 +1,11 @@
## 待修复问题
1. 左侧表展示排序会随机变化,需要固定排序
2. 左侧表搜索无法支持原始表名搜索,需要同时展示原始表名并支持搜索
3. 详情弹出层内容无 CSS 样式,展示混乱。且弹出层关闭按钮 icon 肉眼不可见
4. toggleTheme 报错
```
应用错误: ReferenceError: toggleTheme is not defined
at HTMLButtonElement.onclick (?table=posts:32:40)
```
5. 文章表多写入些数据,方便测试分页功能。

View File

@@ -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
}

View File

@@ -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

View File

@@ -0,0 +1,17 @@
-- 为posts表插入更多测试数据
INSERT INTO posts (title, content, category, tags, author_id, status, view_count, created_at, updated_at) VALUES
('深入理解Go语言并发编程', 'Go语言的并发模型基于CSPCommunicating 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'));

38
scripts/test_fixes.sh Executable file
View File

@@ -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"

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 += `
<div class="detail-item">
<label class="detail-label">${key}</label>
<div class="detail-label">${key}</div>
<div class="detail-value">${value || ''}</div>
</div>
`;
@@ -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');

View File

@@ -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}}">
<div class="nav-link-content">
<svg class="nav-icon" width="18" height="18" fill="none" viewBox="0 0 24 24">
<path d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2z" stroke="currentColor" stroke-width="2"/>
@@ -71,6 +71,7 @@
</svg>
<div class="nav-text-content">
<span class="nav-title">{{.Alias}}</span>
<span class="nav-subtitle">{{.Name}}</span>
<span class="nav-description">{{.RowCount}} 条记录</span>
</div>
{{if eq .Name $.Table}}
@@ -357,12 +358,14 @@
{{$start = max 1 (sub .Pages 4)}}
{{end}}
{{range $i := $start | $end}}
{{if eq $i .Page}}
<span class="pagination-button active">{{$i}}</span>
{{else}}
<a href="?table={{.Table}}&page={{$i}}&per_page={{.PerPage}}&search={{.Search}}&sort={{.SortField}}&order={{.SortOrder}}"
class="pagination-button">{{$i}}</a>
{{range $i := until $end}}
{{if ge $i $start}}
{{if eq $i $.Page}}
<span class="pagination-button active">{{$i}}</span>
{{else}}
<a href="?table={{$.Table}}&page={{$i}}&per_page={{$.PerPage}}&search={{$.Search}}&sort={{$.SortField}}&order={{$.SortOrder}}"
class="pagination-button">{{$i}}</a>
{{end}}
{{end}}
{{end}}
@@ -379,18 +382,18 @@
</div>
<!-- Detail Modal -->
<div id="detailModal" class="modal">
<div id="detailModal" class="modal hidden">
<div class="modal-content">
<div class="p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-medium">详情信息</h3>
<button onclick="closeModal()" class="text-gray-400 hover:text-gray-600">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
<div id="detailContent" class="space-y-4">
<div class="modal-header">
<h3 class="modal-title">详情信息</h3>
<button onclick="closeModal()" class="modal-close" aria-label="关闭">
<svg width="24" height="24" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
<div class="modal-body">
<div id="detailContent" class="detail-content">
<!-- Content will be loaded here -->
</div>
</div>