Files
tech-forum/frontend/index.html
hubian 4c697d9829 fix: 修复前端页面问题
1. 下拉菜单使用display:none隐藏,hover时显示
2. 清理文件末尾重复代码
3. 用户头像添加边框美化
2026-04-12 18:29:17 +08:00

345 lines
16 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<title>技术论坛 - 首页</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">
<style>
.gradient-bg { background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%); }
.dropdown-menu {
position: absolute;
right: 0;
top: 100%;
width: 160px;
background: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
border: 1px solid #e5e7eb;
display: none;
z-index: 100;
}
.dropdown:hover .dropdown-menu {
display: block;
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<!-- 导航栏 -->
<nav class="bg-white border-b border-gray-100 sticky top-0 z-50">
<div class="max-w-7xl mx-auto px-4">
<div class="flex items-center justify-between h-16">
<div class="flex items-center gap-8">
<a href="/" class="text-xl font-bold text-gray-800 flex items-center gap-2">
<span class="text-2xl">👨‍💻</span> 技术论坛
</a>
<div class="hidden md:flex items-center gap-6">
<a href="/" class="text-blue-600 font-medium">首页</a>
<a href="#topics" class="text-gray-600 hover:text-blue-600">工具分享</a>
<a href="#tags" class="text-gray-600 hover:text-blue-600">标签</a>
</div>
</div>
<div class="flex items-center gap-4">
<!-- 搜索框 -->
<div class="relative hidden sm:block">
<i class="ri-search-line absolute left-3 top-2.5 text-gray-400"></i>
<input type="text" id="searchInput" placeholder="搜索帖子..."
class="pl-9 pr-4 py-2 border border-gray-200 rounded-lg w-48 focus:w-64 transition-all focus:ring-2 focus:ring-blue-500">
</div>
<!-- 用户状态 -->
<div id="userArea"></div>
</div>
</div>
</div>
</nav>
<!-- 主内容 -->
<main class="max-w-7xl mx-auto px-4 py-8">
<div class="flex gap-8">
<!-- 左侧内容 -->
<div class="flex-1">
<!-- Tab切换 -->
<div class="bg-white rounded-lg border border-gray-100 mb-6">
<div class="flex border-b border-gray-100">
<button onclick="loadPosts('all')" id="tab-all" class="px-6 py-4 text-blue-600 border-b-2 border-blue-600 font-medium">
全部
</button>
<button onclick="loadPosts('discussion')" id="tab-discussion" class="px-6 py-4 text-gray-500 hover:text-gray-700">
技术交流
</button>
<button onclick="loadPosts('share')" id="tab-share" class="px-6 py-4 text-gray-500 hover:text-gray-700">
工具分享
</button>
</div>
</div>
<!-- 帖子列表 -->
<div id="postList" class="space-y-4">
<div class="text-center py-12 text-gray-500">加载中...</div>
</div>
<!-- 加载更多 -->
<div id="loadMore" class="text-center py-8 hidden">
<button onclick="loadMorePosts()" class="px-6 py-2 border border-gray-300 rounded-lg text-gray-600 hover:bg-gray-50">
加载更多
</button>
</div>
</div>
<!-- 右侧边栏 -->
<div class="hidden lg:block w-80">
<!-- 发帖按钮 -->
<a href="/create" class="block w-full py-3 gradient-bg text-white text-center rounded-lg font-medium hover:opacity-90 mb-6">
<i class="ri-add-line mr-1"></i> 发布帖子
</a>
<!-- 热门标签 -->
<div id="tags" class="bg-white rounded-lg border border-gray-100 p-4 mb-6">
<h3 class="font-medium text-gray-800 mb-3 flex items-center gap-2">
<i class="ri-price-tag-3-line text-blue-500"></i> 热门标签
</h3>
<div id="tagList" class="flex flex-wrap gap-2">
<span class="text-gray-400 text-sm">加载中...</span>
</div>
</div>
<!-- 热门工具分享主题 -->
<div id="topics" class="bg-white rounded-lg border border-gray-100 p-4">
<h3 class="font-medium text-gray-800 mb-3 flex items-center gap-2">
<i class="ri-tools-line text-purple-500"></i> 工具分享
</h3>
<div id="topicList" class="space-y-3">
<span class="text-gray-400 text-sm">加载中...</span>
</div>
</div>
</div>
</div>
</main>
<!-- 搜索结果弹窗 -->
<div id="searchModal" class="fixed inset-0 bg-black/50 z-50 hidden items-center justify-center p-4">
<div class="bg-white rounded-xl w-full max-w-2xl max-h-[80vh] overflow-hidden">
<div class="p-4 border-b flex justify-between items-center">
<h3 class="font-bold">搜索结果</h3>
<button onclick="closeSearchModal()" class="text-gray-400"><i class="ri-close-line text-xl"></i></button>
</div>
<div id="searchResults" class="p-4 overflow-auto max-h-[60vh]"></div>
</div>
</div>
<script>
let currentUser = null;
let currentType = 'all';
let currentPage = 1;
// 初始化
document.addEventListener('DOMContentLoaded', () => {
checkLogin();
loadPosts('all');
loadTags();
loadTopics();
});
// 检查登录状态
async function checkLogin() {
const token = localStorage.getItem('token');
const user = localStorage.getItem('user');
if (token && user) {
currentUser = JSON.parse(user);
renderUserArea();
} else {
renderLoginButton();
}
}
function renderUserArea() {
document.getElementById('userArea').innerHTML = `
<div class="flex items-center gap-3">
<a href="/create" class="px-4 py-2 gradient-bg text-white rounded-lg text-sm hidden sm:block">
发布帖子
</a>
<div class="dropdown relative">
<button class="flex items-center gap-2 cursor-pointer p-1">
<img src="${currentUser.avatar}" class="w-8 h-8 rounded-full border-2 border-gray-200">
<span class="hidden sm:inline text-gray-700">${currentUser.username}</span>
<i class="ri-arrow-down-s-line text-gray-400"></i>
</button>
<div class="dropdown-menu py-1">
<a href="/user/${currentUser.id}" class="flex items-center gap-2 px-4 py-2 text-gray-700 hover:bg-gray-50 transition">
<i class="ri-user-line"></i> 个人主页
</a>
<hr class="my-1 border-gray-100">
<button onclick="logout()" class="flex items-center gap-2 w-full text-left px-4 py-2 text-red-500 hover:bg-gray-50 transition">
<i class="ri-logout-box-line"></i> 退出登录
</button>
</div>
</div>
</div>
`;
}
function renderLoginButton() {
document.getElementById('userArea').innerHTML = `
<a href="/login" class="px-4 py-2 text-gray-600 hover:text-blue-600">登录</a>
<a href="/register" class="px-4 py-2 gradient-bg text-white rounded-lg">注册</a>
`;
}
function logout() {
localStorage.removeItem('token');
localStorage.removeItem('user');
window.location.reload();
}
// 加载帖子
async function loadPosts(type) {
currentType = type;
currentPage = 1;
// 更新Tab样式
document.querySelectorAll('[id^="tab-"]').forEach(tab => {
tab.classList.remove('text-blue-600', 'border-b-2', 'border-blue-600', 'font-medium');
tab.classList.add('text-gray-500');
});
document.getElementById('tab-' + type).classList.remove('text-gray-500');
document.getElementById('tab-' + type).classList.add('text-blue-600', 'border-b-2', 'border-blue-600', 'font-medium');
const url = type === 'all' ? '/api/posts' : '/api/posts?type=' + type;
const res = await fetch(url);
const data = await res.json();
renderPosts(data.posts);
if (data.posts.length >= 20) {
document.getElementById('loadMore').classList.remove('hidden');
} else {
document.getElementById('loadMore').classList.add('hidden');
}
}
function renderPosts(posts) {
const container = document.getElementById('postList');
if (posts.length === 0) {
container.innerHTML = '<div class="text-center py-12 text-gray-500">暂无帖子</div>';
return;
}
container.innerHTML = posts.map(post => `
<a href="/post/${post.id}" class="block bg-white rounded-lg border border-gray-100 p-5 hover:shadow-md transition-shadow">
<div class="flex items-start gap-4">
<img src="${post.author.avatar}" class="w-10 h-10 rounded-full">
<div class="flex-1">
<div class="flex items-center gap-2 mb-1">
<span class="text-xs px-2 py-0.5 rounded ${post.type === 'discussion' ? 'bg-blue-100 text-blue-600' : 'bg-purple-100 text-purple-600'}">
${post.type === 'discussion' ? '技术交流' : '工具分享'}
</span>
${post.is_pinned ? '<span class="text-xs px-2 py-0.5 rounded bg-red-100 text-red-600">置顶</span>' : ''}
</div>
<h3 class="font-medium text-gray-900 hover:text-blue-600 mb-1">${post.title}</h3>
<p class="text-sm text-gray-500 line-clamp-2">${post.content_preview}</p>
<div class="flex items-center gap-4 mt-3 text-xs text-gray-400">
<span>${post.author.username}</span>
<span>${new Date(post.created_at).toLocaleDateString()}</span>
<span><i class="ri-eye-line"></i> ${post.views}</span>
<span><i class="ri-heart-line"></i> ${post.likes}</span>
<span><i class="ri-chat-3-line"></i> ${post.replies}</span>
</div>
${post.tags && post.tags.length > 0 ? `
<div class="flex gap-2 mt-2">
${post.tags.slice(0, 3).map(tag => `
<span class="text-xs px-2 py-0.5 bg-gray-100 text-gray-600 rounded">${tag}</span>
`).join('')}
</div>
` : ''}
</div>
</div>
</a>
`).join('');
}
// 加载标签
async function loadTags() {
const res = await fetch('/api/tags');
const tags = await res.json();
const container = document.getElementById('tagList');
if (tags.length === 0) {
container.innerHTML = '<span class="text-gray-400 text-sm">暂无标签</span>';
return;
}
container.innerHTML = tags.slice(0, 10).map(tag => `
<a href="?tag=${tag.name}" class="px-3 py-1 bg-gray-100 hover:bg-blue-100 text-gray-700 hover:text-blue-700 rounded-full text-sm">
${tag.name} (${tag.count})
</a>
`).join('');
}
// 加载工具分享主题
async function loadTopics() {
const res = await fetch('/api/topics');
const topics = await res.json();
const container = document.getElementById('topicList');
if (topics.length === 0) {
container.innerHTML = '<span class="text-gray-400 text-sm">暂无主题</span>';
return;
}
container.innerHTML = topics.slice(0, 5).map(topic => `
<a href="/topic/${topic.id}" class="flex items-center gap-3 p-2 hover:bg-gray-50 rounded-lg">
<span class="text-2xl">${topic.icon || '🔧'}</span>
<div class="flex-1 min-w-0">
<p class="font-medium text-gray-800 truncate">${topic.name}</p>
<p class="text-xs text-gray-500">${topic.questions_count || 0} 问题 · ${topic.followers || 0} 关注</p>
</div>
</a>
`).join('');
}
// 搜索功能
document.getElementById('searchInput').addEventListener('keyup', async (e) => {
if (e.key === 'Enter') {
const query = e.target.value.trim();
if (!query) return;
const res = await fetch('/api/search?q=' + encodeURIComponent(query));
const data = await res.json();
const container = document.getElementById('searchResults');
if (data.posts.length === 0 && data.topics.length === 0) {
container.innerHTML = '<p class="text-center text-gray-500 py-8">未找到相关内容</p>';
} else {
let html = '';
if (data.posts.length > 0) {
html += '<div class="mb-6"><h4 class="font-medium text-gray-700 mb-3">帖子 (' + data.posts.length + ')</h4><div class="space-y-2">';
data.posts.forEach(p => {
html += '<a href="/post/' + p.id + '" class="block p-3 hover:bg-gray-50 rounded-lg"><p class="font-medium text-gray-800">' + p.title + '</p><p class="text-sm text-gray-500">' + p.author + ' · ' + new Date(p.created_at).toLocaleDateString() + '</p></a>';
});
html += '</div></div>';
}
if (data.topics.length > 0) {
html += '<div><h4 class="font-medium text-gray-700 mb-3">工具分享 (' + data.topics.length + ')</h4><div class="space-y-2">';
data.topics.forEach(t => {
html += '<a href="/topic/' + t.id + '" class="flex items-center gap-2 p-3 hover:bg-gray-50 rounded-lg"><span class="text-xl">' + (t.icon || '🔧') + '</span><span class="font-medium text-gray-800">' + t.name + '</span></a>';
});
html += '</div></div>';
}
container.innerHTML = html;
}
document.getElementById('searchModal').classList.remove('hidden');
document.getElementById('searchModal').classList.add('flex');
}
});
function closeSearchModal() {
document.getElementById('searchModal').classList.add('hidden');
document.getElementById('searchModal').classList.remove('flex');
}
</script>
</body>
</html>