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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"github.com/rogeecn/database_render/internal/config"
|
"github.com/rogeecn/database_render/internal/config"
|
||||||
"github.com/rogeecn/database_render/internal/database"
|
"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
|
return tables, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -123,6 +123,16 @@ func (r *Renderer) loadTemplates() error {
|
|||||||
"eq": func(a, b interface{}) bool {
|
"eq": func(a, b interface{}) bool {
|
||||||
return fmt.Sprintf("%v", a) == fmt.Sprintf("%v", b)
|
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
|
// 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;
|
justify-content: center;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
animation: fadeIn 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal.hidden {
|
.modal.hidden {
|
||||||
@@ -398,6 +404,18 @@ input[type="search"]::-webkit-search-results-decoration {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
max-height: 80vh;
|
max-height: 80vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
animation: slideUp 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideUp {
|
||||||
|
from {
|
||||||
|
transform: translateY(20px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-header {
|
.modal-header {
|
||||||
@@ -423,6 +441,9 @@ input[type="search"]::-webkit-search-results-decoration {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-close:hover {
|
.modal-close:hover {
|
||||||
@@ -430,10 +451,54 @@ input[type="search"]::-webkit-search-results-decoration {
|
|||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-close svg {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
stroke: currentColor;
|
||||||
|
stroke-width: 2;
|
||||||
|
}
|
||||||
|
|
||||||
.modal-body {
|
.modal-body {
|
||||||
padding: 24px;
|
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 {
|
.notification {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|||||||
@@ -205,23 +205,34 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-title {
|
.nav-title {
|
||||||
display: block;
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
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 {
|
.nav-description {
|
||||||
display: block;
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
margin-top: 1px;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|||||||
@@ -216,9 +216,12 @@ class AppState {
|
|||||||
|
|
||||||
navLinks.forEach(link => {
|
navLinks.forEach(link => {
|
||||||
const title = link.querySelector('.nav-title').textContent.toLowerCase();
|
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 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.style.display = matches ? 'flex' : 'none';
|
||||||
link.parentElement.style.display = matches ? 'block' : 'none';
|
link.parentElement.style.display = matches ? 'block' : 'none';
|
||||||
@@ -517,7 +520,7 @@ class AppState {
|
|||||||
for (const [key, value] of Object.entries(data.data || {})) {
|
for (const [key, value] of Object.entries(data.data || {})) {
|
||||||
html += `
|
html += `
|
||||||
<div class="detail-item">
|
<div class="detail-item">
|
||||||
<label class="detail-label">${key}</label>
|
<div class="detail-label">${key}</div>
|
||||||
<div class="detail-value">${value || ''}</div>
|
<div class="detail-value">${value || ''}</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -609,6 +612,7 @@ class AppState {
|
|||||||
// 全局函数
|
// 全局函数
|
||||||
window.toggleSidebar = () => app.toggleSidebar();
|
window.toggleSidebar = () => app.toggleSidebar();
|
||||||
window.closeSidebar = () => app.closeSidebar();
|
window.closeSidebar = () => app.closeSidebar();
|
||||||
|
window.toggleTheme = () => app.toggleTheme();
|
||||||
window.debounceSearch = (query) => app.handleSearchInput(query);
|
window.debounceSearch = (query) => app.handleSearchInput(query);
|
||||||
window.performSearch = () => {
|
window.performSearch = () => {
|
||||||
const searchInput = document.getElementById('searchInput');
|
const searchInput = document.getElementById('searchInput');
|
||||||
|
|||||||
@@ -63,7 +63,7 @@
|
|||||||
class="nav-link {{if eq .Name $.Table}}active{{end}}"
|
class="nav-link {{if eq .Name $.Table}}active{{end}}"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
aria-current="{{if eq .Name $.Table}}page{{end}}"
|
aria-current="{{if eq .Name $.Table}}page{{end}}"
|
||||||
title="{{.Alias}} - {{.Description}}">
|
title="{{.Alias}} ({{.Name}}) - {{.Description}}">
|
||||||
<div class="nav-link-content">
|
<div class="nav-link-content">
|
||||||
<svg class="nav-icon" width="18" height="18" fill="none" viewBox="0 0 24 24">
|
<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"/>
|
<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>
|
</svg>
|
||||||
<div class="nav-text-content">
|
<div class="nav-text-content">
|
||||||
<span class="nav-title">{{.Alias}}</span>
|
<span class="nav-title">{{.Alias}}</span>
|
||||||
|
<span class="nav-subtitle">{{.Name}}</span>
|
||||||
<span class="nav-description">{{.RowCount}} 条记录</span>
|
<span class="nav-description">{{.RowCount}} 条记录</span>
|
||||||
</div>
|
</div>
|
||||||
{{if eq .Name $.Table}}
|
{{if eq .Name $.Table}}
|
||||||
@@ -357,14 +358,16 @@
|
|||||||
{{$start = max 1 (sub .Pages 4)}}
|
{{$start = max 1 (sub .Pages 4)}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{range $i := $start | $end}}
|
{{range $i := until $end}}
|
||||||
{{if eq $i .Page}}
|
{{if ge $i $start}}
|
||||||
|
{{if eq $i $.Page}}
|
||||||
<span class="pagination-button active">{{$i}}</span>
|
<span class="pagination-button active">{{$i}}</span>
|
||||||
{{else}}
|
{{else}}
|
||||||
<a href="?table={{.Table}}&page={{$i}}&per_page={{.PerPage}}&search={{.Search}}&sort={{.SortField}}&order={{.SortOrder}}"
|
<a href="?table={{$.Table}}&page={{$i}}&per_page={{$.PerPage}}&search={{$.Search}}&sort={{$.SortField}}&order={{$.SortOrder}}"
|
||||||
class="pagination-button">{{$i}}</a>
|
class="pagination-button">{{$i}}</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
{{if lt .Page .Pages}}
|
{{if lt .Page .Pages}}
|
||||||
<a href="?table={{.Table}}&page={{add .Page 1}}&per_page={{.PerPage}}&search={{.Search}}&sort={{.SortField}}&order={{.SortOrder}}"
|
<a href="?table={{.Table}}&page={{add .Page 1}}&per_page={{.PerPage}}&search={{.Search}}&sort={{.SortField}}&order={{.SortOrder}}"
|
||||||
@@ -379,18 +382,18 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Detail Modal -->
|
<!-- Detail Modal -->
|
||||||
<div id="detailModal" class="modal">
|
<div id="detailModal" class="modal hidden">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="p-6">
|
<div class="modal-header">
|
||||||
<div class="flex justify-between items-center mb-4">
|
<h3 class="modal-title">详情信息</h3>
|
||||||
<h3 class="text-lg font-medium">详情信息</h3>
|
<button onclick="closeModal()" class="modal-close" aria-label="关闭">
|
||||||
<button onclick="closeModal()" class="text-gray-400 hover:text-gray-600">
|
<svg width="24" height="24" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<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 stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="detailContent" class="space-y-4">
|
<div class="modal-body">
|
||||||
|
<div id="detailContent" class="detail-content">
|
||||||
<!-- Content will be loaded here -->
|
<!-- Content will be loaded here -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user