fix: errors
This commit is contained in:
BIN
data/app.db
BIN
data/app.db
Binary file not shown.
11
errors.md
Normal file
11
errors.md
Normal 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. 文章表多写入些数据,方便测试分页功能。
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
17
scripts/insert_test_data.sql
Normal file
17
scripts/insert_test_data.sql
Normal 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语言的并发模型基于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'));
|
||||
38
scripts/test_fixes.sh
Executable file
38
scripts/test_fixes.sh
Executable 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"
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user