diff --git a/web/src/app/(main)/forum/page.tsx b/web/src/app/(main)/forum/page.tsx index a67e972..d2885fe 100644 --- a/web/src/app/(main)/forum/page.tsx +++ b/web/src/app/(main)/forum/page.tsx @@ -33,40 +33,22 @@ function getCategoryIcon(name: string) { 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"; + if (name.includes("公告") || name.includes("通知")) return "text-rose-600 dark:text-rose-400"; + if (name.includes("学习") || name.includes("讨论") || name.includes("课程")) return "text-blue-600 dark:text-blue-400"; + if (name.includes("反馈") || name.includes("使用")) return "text-amber-600 dark:text-amber-400"; + return "text-slate-600 dark:text-slate-400"; } -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" }); +function getCategoryBorder(name: string) { + if (name.includes("公告") || name.includes("通知")) return "border-l-rose-500"; + if (name.includes("学习") || name.includes("讨论") || name.includes("课程")) return "border-l-blue-500"; + if (name.includes("反馈") || name.includes("使用")) return "border-l-amber-500"; + return "border-l-slate-500"; } +import { format } from "date-fns"; + export default function ForumHomePage() { const [categories, setCategories] = useState([]); const [categoryPosts, setCategoryPosts] = useState< @@ -134,12 +116,10 @@ export default function ForumHomePage() { return (
{/* Category header */} -
+
{getCategoryIcon(category.name)} @@ -170,15 +150,15 @@ export default function ForumHomePage() {
{/* Posts list */} -
+
{posts.length > 0 ? ( posts.map((post) => ( -
+
@@ -189,7 +169,7 @@ export default function ForumHomePage() { {post.author_name} - {formatRelativeTime(post.created_at)} + {format(new Date(post.created_at), "yyyy/M/d HH:mm")} diff --git a/web/src/app/(main)/profile/page.tsx b/web/src/app/(main)/profile/page.tsx index 0adc599..dff9e89 100644 --- a/web/src/app/(main)/profile/page.tsx +++ b/web/src/app/(main)/profile/page.tsx @@ -7,16 +7,10 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { useAuthStore } from "@/store/auth"; import MobileNav from "@/components/layout/mobile-nav"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; -import { Alert, AlertDescription } from "@/components/ui/alert"; -import { +import { Loader2, - User as UserIcon, - Mail, - Calendar, CheckCircle, Edit2, Save, @@ -69,10 +63,7 @@ export default function ProfilePage() { router.push("/login"); return; } - - if (isAuthenticated) { - loadUserData(); - } + if (isAuthenticated) loadUserData(); }, [isAuthenticated, authLoading, router]); useEffect(() => { @@ -86,12 +77,8 @@ export default function ProfilePage() { try { setIsLoading(true); setError(null); - - // 加载用户信息 const userData = await authAPI.getCurrentUser(); setUser(userData); - - // 加载统计数据 try { const stats = await analyticsAPI.getStatistics(); setStatistics({ @@ -102,7 +89,6 @@ export default function ProfilePage() { }); } catch (err) { console.warn("加载统计数据失败:", err); - // 统计数据加载失败不影响页面显示 } } catch (err) { console.error("加载用户信息失败:", err); @@ -117,17 +103,13 @@ export default function ProfilePage() { setIsSaving(true); setError(null); setSuccess(null); - const updatedUser = await authAPI.updateUserInfo({ email: data.email, full_name: data.full_name || undefined, }); - setUser(updatedUser); setIsEditing(false); setSuccess("个人信息更新成功"); - - // 3秒后清除成功消息 setTimeout(() => setSuccess(null), 3000); } catch (err: any) { console.error("更新用户信息失败:", err); @@ -155,235 +137,147 @@ export default function ProfilePage() { ); } - if (!isAuthenticated || !user) { - return null; - } + if (!isAuthenticated || !user) return null; const initials = (user.full_name || user.username || "U").charAt(0).toUpperCase(); return (
-
- {/* 错误提示 */} +
+ + {/* 状态提示 */} {error && ( - - {error} - +
{error}
)} - - {/* 成功提示 */} {success && ( - - - - {success} - - +
+ {success} +
)} -
- {/* 左侧:用户头像和基本信息 */} -
- {/* 用户头像卡片 */} - - - 头像 - 您的账户头像 - - -
-
- {initials} -
-
-

- 当前使用用户名首字母作为头像 -

-

- 未来版本将支持上传自定义头像 -

-
+
+ {/* 头像与基本信息 */} +
+
+

个人信息

+ {!isEditing && ( + + )} +
+ + {/* 头像 */} +
+
+ {initials} +
+
+

{user.full_name || user.username}

+

{user.email}

+
+
+ + {/* 表单 */} +
+
+
+ + +

用户名创建后无法修改

- - - - {/* 基本信息卡片 */} - - -
-
- 基本信息 - 您的账户基本信息 -
- {!isEditing && ( - - )} +
+ +
- - - - {/* 用户名(只读) */} -
- - -

- 用户名创建后无法修改 -

-
+
- {/* 邮箱(可编辑) */} -
- - - {errors.email && ( -

- {errors.email.message} -

- )} -
+
+ + + {errors.email &&

{errors.email.message}

} +
- {/* 真实姓名(可编辑) */} -
- - -
+
+ + +
- {/* 注册时间(只读) */} -
- - -
+ {isEditing && ( +
+ + +
+ )} + +
- {/* 账户状态(只读) */} -
- -
- - {user.is_active && ( - - )} -
-
- - {/* 编辑模式下的按钮 */} - {isEditing && ( -
- - -
- )} - - - -
- - {/* 右侧:学习统计 */} -
- - - 学习统计 - 您的学习数据概览 - - - {statistics ? ( -
-
-
- - 总对话数 -
- {statistics.total_sessions} -
-
-
- - 总消息数 -
- {statistics.total_messages} -
-
-
- - 知识库 -
- - -
-
-
- - 文档数 -
- {statistics.total_documents} -
-
- ) : ( -
- -

加载统计数据中...

-
- )} -
-
-
+ {/* 学习统计 */} +
+
+

学习统计

+
+ {statistics ? ( +
+
+ +

{statistics.total_sessions}

+

对话数

+
+
+ +

{statistics.total_messages}

+

消息数

+
+
+ +

{statistics.total_documents}

+

文档数

+
+
+ +

{statistics.active_days}

+

活跃天数

+
+
+ ) : ( +
+ +

加载中...

+
+ )} +
- - {/* 移动端导航 */}
); } - diff --git a/web/src/app/(main)/settings/page.tsx b/web/src/app/(main)/settings/page.tsx index 1687e22..018e7dd 100644 --- a/web/src/app/(main)/settings/page.tsx +++ b/web/src/app/(main)/settings/page.tsx @@ -2,7 +2,6 @@ import { useState } from "react"; -// 禁用静态生成 export const dynamic = 'force-dynamic'; import { useRouter } from "next/navigation"; import { useForm } from "react-hook-form"; @@ -10,15 +9,11 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { useAuthStore } from "@/store/auth"; import MobileNav from "@/components/layout/mobile-nav"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; -import { Alert, AlertDescription } from "@/components/ui/alert"; import { ThemeToggle } from "@/components/ui/theme-toggle"; -import { +import { Loader2, - Lock, Eye, EyeOff, CheckCircle, @@ -39,17 +34,8 @@ const passwordSchema = z.object({ type PasswordForm = z.infer; -// 密码强度检查函数 -function getPasswordStrength(password: string): { - strength: "weak" | "medium" | "strong"; - label: string; - color: string; - score: number; -} { - if (!password) { - return { strength: "weak", label: "", color: "", score: 0 }; - } - +function getPasswordStrength(password: string) { + if (!password) return { label: "", color: "", score: 0 }; let score = 0; if (password.length >= 6) score++; if (password.length >= 8) score++; @@ -58,13 +44,9 @@ function getPasswordStrength(password: string): { if (/[0-9]/.test(password)) score++; if (/[^a-zA-Z0-9]/.test(password)) score++; - if (score <= 2) { - return { strength: "weak", label: "弱", color: "text-red-600", score }; - } else if (score <= 4) { - return { strength: "medium", label: "中", color: "text-yellow-600", score }; - } else { - return { strength: "strong", label: "强", color: "text-green-600", score }; - } + if (score <= 2) return { label: "弱", color: "text-red-600", score }; + if (score <= 4) return { label: "中", color: "text-yellow-600", score }; + return { label: "强", color: "text-green-600", score }; } export default function SettingsPage() { @@ -95,16 +77,12 @@ export default function SettingsPage() { setIsSubmitting(true); setError(null); setSuccess(null); - await authAPI.changePassword({ old_password: data.old_password, new_password: data.new_password, }); - setSuccess("密码修改成功"); reset(); - - // 3秒后清除成功消息 setTimeout(() => setSuccess(null), 3000); } catch (err: any) { console.error("修改密码失败:", err); @@ -129,223 +107,142 @@ export default function SettingsPage() { return (
-
- {/* 错误提示 */} +
+ + {/* 状态提示 */} {error && ( - - {error} - +
+ {error} +
)} - - {/* 成功提示 */} {success && ( - - - - {success} - - +
+ + {success} +
)} -
+
{/* 账户安全 */} - - -
- - 账户安全 +
+
+ +

账户安全

+
+
+
+ +
+ + +
+ {errors.old_password &&

{errors.old_password.message}

}
- 修改您的登录密码 - - - - {/* 当前密码 */} -
- -
- - -
- {errors.old_password && ( -

- {errors.old_password.message} -

- )} -
- {/* 新密码 */} -
- -
- - -
- {newPassword && ( -
-
- 密码强度: - - {passwordStrength.label} - -
-
-
-
+
+ +
+ + +
+ {newPassword && ( +
+
+ 密码强度: + {passwordStrength.label} +
+
+
- )} - {errors.new_password && ( -

- {errors.new_password.message} -

- )} -
- - {/* 确认新密码 */} -
- -
- -
- {errors.confirm_password && ( -

- {errors.confirm_password.message} -

+ )} + {errors.new_password &&

{errors.new_password.message}

} +
+ +
+ +
+ + +
+ {errors.confirm_password &&

{errors.confirm_password.message}

} +
+ +
+
- - {/* 密码要求提示 */} -
-

密码要求:

-
    -
  • • 至少6个字符
  • -
  • • 建议包含大小写字母、数字和特殊字符
  • -
  • • 避免使用常用密码或个人信息
  • -
-
- - {/* 提交按钮 */} -
- -
- - - + +
+ +
{/* 偏好设置 */} - - -
- - 偏好设置 +
+
+ +

偏好设置

+
+
+
+

外观主题

+

切换浅色或深色模式

- 自定义您的使用偏好 - - -
- {/* 主题设置 */} -
-
-
- - -
-

- 切换浅色/深色主题模式 -

-
- -
- - {/* 未来可添加其他偏好设置 */} -
-

- 更多偏好设置功能即将推出 -

-
-
-
- + +
+
- - {/* 移动端导航 */}
); } - diff --git a/web/src/app/forum/[categoryId]/page.tsx b/web/src/app/forum/[categoryId]/page.tsx index cd6668e..d559fae 100644 --- a/web/src/app/forum/[categoryId]/page.tsx +++ b/web/src/app/forum/[categoryId]/page.tsx @@ -5,7 +5,6 @@ import Link from "next/link"; import { useParams, useRouter } from "next/navigation"; import { forumAPI } from "@/lib/api"; import type { ForumCategory, ForumPostSummary } from "@/types"; -import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { @@ -19,22 +18,7 @@ import { } 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" }); -} +import { format } from "date-fns"; export default function ForumCategoryPage() { const params = useParams(); @@ -118,7 +102,7 @@ export default function ForumCategoryPage() { return (
-
+
{/* Back nav */}
{isAuthenticated && ( - + )}
{/* New post form (collapsible) */} {showForm && ( -
+
{submitError && (

{submitError}

@@ -179,11 +162,10 @@ export default function ForumCategoryPage() { disabled={isSubmitting} />
- +
@@ -204,7 +186,7 @@ export default function ForumCategoryPage() { {/* Login prompt */} {!isAuthenticated && ( -
+
需要登录后才能发帖。请先 登录 @@ -235,14 +217,14 @@ export default function ForumCategoryPage() {

暂无帖子,来发第一个帖吧

) : ( -
+
{posts.map((post) => ( -
+
@@ -253,7 +235,7 @@ export default function ForumCategoryPage() { {post.author_name} - {formatRelativeTime(post.created_at)} + {format(new Date(post.created_at), "yyyy/M/d HH:mm")} diff --git a/web/src/app/forum/post/[postId]/page.tsx b/web/src/app/forum/post/[postId]/page.tsx index ca1e27e..5300551 100644 --- a/web/src/app/forum/post/[postId]/page.tsx +++ b/web/src/app/forum/post/[postId]/page.tsx @@ -9,22 +9,7 @@ import { Textarea } from "@/components/ui/textarea"; import { Loader2, MessageCircle, Clock, User, 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" }); -} +import { format } from "date-fns"; export default function ForumPostPage() { const params = useParams(); @@ -90,7 +75,8 @@ export default function ForumPostPage() { return (
-
+
+
{/* Back nav */}