From 50faf7ad4ec5af5f8ac2d7c3f0ca324117a7f218 Mon Sep 17 00:00:00 2001 From: xiaopeng <1509442308@qq.com> Date: Wed, 27 May 2026 10:02:30 +0800 Subject: [PATCH] feat: add forum seed categories, improve page layouts and navigation - Seed default forum categories (announcements, learning, feedback) on startup - Update forum pages with improved category/post/reply UI - Refine navbar, mobile nav, and home page content layout - Improve analytics, course-content, knowledge, profile, settings, spatial pages Co-Authored-By: Claude Opus 4.7 --- backend/main.py | 36 +++ web/src/app/(main)/analytics/page.tsx | 6 - web/src/app/(main)/course-content/page.tsx | 40 +-- web/src/app/(main)/forum/page.tsx | 209 +++++++++---- web/src/app/(main)/knowledge/page.tsx | 6 - web/src/app/(main)/profile/page.tsx | 6 - web/src/app/(main)/settings/page.tsx | 6 - web/src/app/(main)/spatial/page.tsx | 6 - web/src/app/forum/[categoryId]/page.tsx | 283 +++++++++++------- web/src/app/forum/post/[postId]/page.tsx | 243 +++++++++------ web/src/components/home/home-page-content.tsx | 32 +- web/src/components/home/navbar.tsx | 96 +++--- web/src/components/layout/mobile-nav.tsx | 12 +- 13 files changed, 592 insertions(+), 389 deletions(-) diff --git a/backend/main.py b/backend/main.py index eee242e..fce6903 100644 --- a/backend/main.py +++ b/backend/main.py @@ -56,6 +56,36 @@ def startup_knowledge_base(): except Exception as e: print(f"启动知识库文件监控服务失败: {str(e)}") + +def seed_forum_categories(): + """初始化论坛默认分类""" + from src.core.database import get_db + from src.models.forum import ForumCategory + + DEFAULT_CATEGORIES = [ + {"slug": "announcements", "name": "课程公告", "description": "课程通知、作业安排与重要信息"}, + {"slug": "course-learning", "name": "学习讨论", "description": "交流学习心得,讨论课程内容与难点"}, + {"slug": "system-feedback", "name": "使用反馈", "description": "分享系统使用经验,提出改进建议"}, + ] + + db = next(get_db()) + try: + existing_count = db.query(ForumCategory).count() + if existing_count > 0: + print(f"论坛分类已存在({existing_count} 个),跳过初始化") + return + + for cat_data in DEFAULT_CATEGORIES: + category = ForumCategory(**cat_data) + db.add(category) + db.commit() + print(f"已创建 {len(DEFAULT_CATEGORIES)} 个论坛分类") + except Exception as e: + db.rollback() + print(f"初始化论坛分类失败: {e}") + finally: + db.close() + # 应用启动事件 @app.on_event("startup") async def startup_event(): @@ -97,6 +127,12 @@ async def startup_event(): startup_knowledge_base() except Exception as e: print(f"启动知识库服务失败: {e}", file=sys.stderr) + + # 初始化论坛分类 + try: + seed_forum_categories() + except Exception as e: + print(f"初始化论坛分类失败: {e}", file=sys.stderr) sys.stderr.flush() print("应用启动事件完成", file=sys.stderr) diff --git a/web/src/app/(main)/analytics/page.tsx b/web/src/app/(main)/analytics/page.tsx index fe9eebf..ab51dc3 100644 --- a/web/src/app/(main)/analytics/page.tsx +++ b/web/src/app/(main)/analytics/page.tsx @@ -161,12 +161,6 @@ export default function AnalyticsPage() { return (
- {/* 页面头部 */} -
-

学习分析

-

了解您的学习进度和知识掌握情况

-
- {/* 标签页 */}
{[ diff --git a/web/src/app/(main)/course-content/page.tsx b/web/src/app/(main)/course-content/page.tsx index 1d40173..97c3da2 100644 --- a/web/src/app/(main)/course-content/page.tsx +++ b/web/src/app/(main)/course-content/page.tsx @@ -93,32 +93,20 @@ export default function CourseContentPage() { return (
- {/* 页面头部 */} -
-
-
-
- -
-
-

课程内容

-

探索国土空间规划课程的核心知识点

-
-
- {/* 视图切换 */} - setViewMode(v as 'graph' | 'list')}> - - - - 知识图谱 - - - - 列表视图 - - - -
+ {/* 视图切换 */} +
+ setViewMode(v as 'graph' | 'list')}> + + + + 知识图谱 + + + + 列表视图 + + +
{/* 错误提示 */} diff --git a/web/src/app/(main)/forum/page.tsx b/web/src/app/(main)/forum/page.tsx index b6522ba..33b64f6 100644 --- a/web/src/app/(main)/forum/page.tsx +++ b/web/src/app/(main)/forum/page.tsx @@ -2,17 +2,76 @@ import { useEffect, useState } from "react"; import Link from "next/link"; -import { MessageSquare, Loader2, MessageCircle } from "lucide-react"; +import { + MessageSquare, + Loader2, + MessageCircle, + BookOpen, + Lightbulb, + Megaphone, + ChevronRight, + Clock, + User, +} from "lucide-react"; import { forumAPI } from "@/lib/api"; import type { ForumCategory, ForumPostSummary } from "@/types"; -import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; -import { Alert, AlertDescription } from "@/components/ui/alert"; + +const CATEGORY_ICONS: Record = { + default: , +}; + +const CATEGORY_ORDER = ["公告", "通知", "学习", "讨论", "课程", "反馈", "使用"]; + +function getCategoryIcon(name: string) { + if (name.includes("公告") || name.includes("通知")) + return ; + if (name.includes("学习") || name.includes("讨论") || name.includes("课程")) + return ; + if (name.includes("反馈") || name.includes("使用")) + return ; + return CATEGORY_ICONS.default; +} + +function getCategoryGradient(name: string) { + if (name.includes("公告") || name.includes("通知")) + return "from-rose-500/10 to-rose-50"; + if (name.includes("学习") || name.includes("讨论") || name.includes("课程")) + return "from-blue-500/10 to-blue-50"; + if (name.includes("反馈") || name.includes("使用")) + return "from-amber-500/10 to-amber-50"; + return "from-slate-500/10 to-slate-50"; +} + +function getCategoryAccent(name: string) { + if (name.includes("公告") || name.includes("通知")) return "text-rose-600"; + if (name.includes("学习") || name.includes("讨论") || name.includes("课程")) return "text-blue-600"; + if (name.includes("反馈") || name.includes("使用")) return "text-amber-600"; + return "text-slate-600"; +} + +function formatRelativeTime(dateStr: string) { + const date = new Date( + new Date(dateStr).getTime() + 8 * 60 * 60 * 1000 + ); + const now = new Date(); + const diffMs = now.getTime() - date.getTime(); + const diffMinutes = Math.floor(diffMs / (1000 * 60)); + const diffHours = Math.floor(diffMs / (1000 * 60 * 60)); + const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); + + if (diffMinutes < 1) return "刚刚"; + if (diffMinutes < 60) return `${diffMinutes} 分钟前`; + if (diffHours < 24) return `${diffHours} 小时前`; + if (diffDays < 7) return `${diffDays} 天前`; + return date.toLocaleDateString("zh-CN", { month: "short", day: "numeric" }); +} export default function ForumHomePage() { const [categories, setCategories] = useState([]); - const [categoryPosts, setCategoryPosts] = useState>({}); + const [categoryPosts, setCategoryPosts] = useState< + Record + >({}); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); @@ -25,10 +84,9 @@ export default function ForumHomePage() { const postsEntries = await Promise.all( data.map(async (category) => { try { - const posts = await forumAPI.getPosts(category.id, 10); + const posts = await forumAPI.getPosts(category.id, 3); return [category.id, posts] as const; - } catch (err) { - console.warn(`加载分类 ${category.name} 的帖子失败:`, err); + } catch { return [category.id, []] as const; } }) @@ -36,7 +94,8 @@ export default function ForumHomePage() { setCategoryPosts(Object.fromEntries(postsEntries)); setError(null); } catch (err) { - const message = err instanceof Error ? err.message : "加载论坛数据失败"; + const message = + err instanceof Error ? err.message : "加载论坛数据失败"; setError(message); } finally { setIsLoading(false); @@ -47,78 +106,110 @@ export default function ForumHomePage() { }, []); return ( -
-
-

- - 课程社区 -

-

- 分享课程学习心得,交流系统使用体验,欢迎参与讨论。 -

-
- +
{isLoading ? ( -
+
- 正在加载社区分类... + 正在加载社区...
) : error ? ( - - {error} - +
+

{error}

+
) : ( -
- {categories.map((category) => ( - - - {category.name} - {category.description && ( - {category.description} - )} - - -
- {category.post_count} 条讨论 - +
+ {[...categories] + .sort((a, b) => { + const getOrder = (name: string) => { + for (let i = 0; i < CATEGORY_ORDER.length; i++) { + if (name.includes(CATEGORY_ORDER[i])) return i; + } + return CATEGORY_ORDER.length; + }; + return getOrder(a.name) - getOrder(b.name); + }) + .map((category) => { + const posts = categoryPosts[category.id] || []; + return ( +
+ {/* Category header */} +
+
+ + {getCategoryIcon(category.name)} + +
+

+ {category.name} +

+ {category.description && ( +

+ {category.description} +

+ )} +
+
+
+ + {category.post_count} 条讨论 + + + 查看全部 + + +
-
- {categoryPosts[category.id] && categoryPosts[category.id].length > 0 ? ( - categoryPosts[category.id].map((post) => ( + + {/* Posts list */} +
+ {posts.length > 0 ? ( + posts.map((post) => ( -
-
+
+ +
+
+

{post.title} -

-
+

+
{post.author_name} - · - {new Date(new Date(post.created_at).getTime() + 8 * 60 * 60 * 1000).toLocaleString()} - · - {post.reply_count} 条回复 + + + {formatRelativeTime(post.created_at)} + + + + {post.reply_count} +
+ )) ) : ( -
- - 暂无讨论,成为第一个发帖的人吧! +
+ 暂无讨论,成为第一个发帖的人
)}
- - - ))} +
+ ); + })}
)}
); } - diff --git a/web/src/app/(main)/knowledge/page.tsx b/web/src/app/(main)/knowledge/page.tsx index 8e8a207..7a9e16d 100644 --- a/web/src/app/(main)/knowledge/page.tsx +++ b/web/src/app/(main)/knowledge/page.tsx @@ -129,12 +129,6 @@ export default function KnowledgePage() { return (
- {/* 页面头部 */} -
-

知识库管理

-

创建和管理您的知识库,组织文档内容

-
- {/* 错误提示 */} {error && ( diff --git a/web/src/app/(main)/profile/page.tsx b/web/src/app/(main)/profile/page.tsx index c121a3e..0adc599 100644 --- a/web/src/app/(main)/profile/page.tsx +++ b/web/src/app/(main)/profile/page.tsx @@ -164,12 +164,6 @@ export default function ProfilePage() { return (
- {/* 页面头部 */} -
-

个人资料

-

管理您的个人信息和账户设置

-
- {/* 错误提示 */} {error && ( diff --git a/web/src/app/(main)/settings/page.tsx b/web/src/app/(main)/settings/page.tsx index 364cc57..1687e22 100644 --- a/web/src/app/(main)/settings/page.tsx +++ b/web/src/app/(main)/settings/page.tsx @@ -130,12 +130,6 @@ export default function SettingsPage() { return (
- {/* 页面头部 */} -
-

设置

-

管理您的账户设置和偏好

-
- {/* 错误提示 */} {error && ( diff --git a/web/src/app/(main)/spatial/page.tsx b/web/src/app/(main)/spatial/page.tsx index 2cb76fc..eefdcb6 100644 --- a/web/src/app/(main)/spatial/page.tsx +++ b/web/src/app/(main)/spatial/page.tsx @@ -375,12 +375,6 @@ export default function SpatialPage() { return (
- {/* 页面头部 */} -
-

空间出图

-

使用AI生成国土空间规划相关的设计图和示意图

-
- {/* Tab导航 */} diff --git a/web/src/app/forum/[categoryId]/page.tsx b/web/src/app/forum/[categoryId]/page.tsx index 17065c4..cd6668e 100644 --- a/web/src/app/forum/[categoryId]/page.tsx +++ b/web/src/app/forum/[categoryId]/page.tsx @@ -5,14 +5,37 @@ import Link from "next/link"; import { useParams, useRouter } from "next/navigation"; import { forumAPI } from "@/lib/api"; import type { ForumCategory, ForumPostSummary } from "@/types"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; -import { Alert, AlertDescription } from "@/components/ui/alert"; -import { Loader2, MessageCircle } from "lucide-react"; +import { + Loader2, + MessageCircle, + Clock, + User, + PenLine, + Send, + ArrowLeft, +} from "lucide-react"; import { useAuthStore } from "@/store/auth"; +function formatRelativeTime(dateStr: string) { + const date = new Date( + new Date(dateStr).getTime() + 8 * 60 * 60 * 1000 + ); + const now = new Date(); + const diffMs = now.getTime() - date.getTime(); + const diffMinutes = Math.floor(diffMs / (1000 * 60)); + const diffHours = Math.floor(diffMs / (1000 * 60 * 60)); + const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); + + if (diffMinutes < 1) return "刚刚"; + if (diffMinutes < 60) return `${diffMinutes} 分钟前`; + if (diffHours < 24) return `${diffHours} 小时前`; + if (diffDays < 7) return `${diffDays} 天前`; + return date.toLocaleDateString("zh-CN", { month: "short", day: "numeric" }); +} + export default function ForumCategoryPage() { const params = useParams(); const router = useRouter(); @@ -27,6 +50,7 @@ export default function ForumCategoryPage() { const [content, setContent] = useState(""); const [submitError, setSubmitError] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); + const [showForm, setShowForm] = useState(false); const { isAuthenticated } = useAuthStore(); @@ -53,7 +77,8 @@ export default function ForumCategoryPage() { setPosts(postList); setError(null); } catch (err) { - const message = err instanceof Error ? err.message : "加载帖子失败"; + const message = + err instanceof Error ? err.message : "加载帖子失败"; setError(message); } finally { setIsLoading(false); @@ -79,10 +104,12 @@ export default function ForumCategoryPage() { }); setTitle(""); setContent(""); + setShowForm(false); const updatedPosts = await forumAPI.getPosts(categoryId); setPosts(updatedPosts); } catch (err) { - const message = err instanceof Error ? err.message : "发布帖子失败"; + const message = + err instanceof Error ? err.message : "发布帖子失败"; setSubmitError(message); } finally { setIsSubmitting(false); @@ -90,129 +117,155 @@ export default function ForumCategoryPage() { }; return ( -
-
- +
+
+ {/* Back nav */} + -
-

- - {currentCategory ? currentCategory.name : "论坛分类"} -

- {currentCategory?.description && ( -

{currentCategory.description}

+ {/* Category header */} +
+
+

+ {currentCategory ? currentCategory.name : "论坛分类"} +

+ {currentCategory?.description && ( +

+ {currentCategory.description} +

+ )} +
+ {isAuthenticated && ( + )}
+ {/* New post form (collapsible) */} + {showForm && ( +
+
+ {submitError && ( +

{submitError}

+ )} + setTitle(e.target.value)} + placeholder="帖子标题" + maxLength={200} + disabled={isSubmitting} + className="bg-background" + /> +