From 2c7afe59ef9be68b0dacf7b95b874403725a2e65 Mon Sep 17 00:00:00 2001 From: SpecialX <47072643+wangxiner55@users.noreply.github.com> Date: Thu, 9 Jul 2026 00:58:42 +0800 Subject: [PATCH] =?UTF-8?q?feat(teacher-portal):=20=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E9=A1=B5=E4=BE=A7=E8=BE=B9=E6=A0=8F=E8=B7=AF?= =?UTF-8?q?=E7=94=B1=E7=BB=84=E4=B8=8E=E7=9C=9F=E5=AE=9EJWT=E9=9B=86?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - lib/auth.ts: localStorage token 存储 + login/logout - app/login: 登录表单页 - components/AppShell: 视口驱动侧边栏 + 路由保护 - app/(app)/layout: 路由组布局套 AppShell - app/(app)/dashboard: 聚合统计卡片页 - app/(app)/classes: 迁移 CRUD 改用真实 JWT - app/page.tsx: 根路径重定向 - known-issues.md: 记录 P2 经验 --- .../src/app/(app)/classes/page.tsx | 294 ++++++++++++++++++ .../src/app/(app)/dashboard/page.tsx | 139 +++++++++ apps/teacher-portal/src/app/(app)/layout.tsx | 5 + apps/teacher-portal/src/app/login/page.tsx | 133 ++++++++ apps/teacher-portal/src/app/page.tsx | 292 +---------------- .../src/components/AppShell.tsx | 161 ++++++++++ apps/teacher-portal/src/lib/auth.ts | 77 +++++ docs/troubleshooting/known-issues.md | 105 ++++--- 8 files changed, 875 insertions(+), 331 deletions(-) create mode 100644 apps/teacher-portal/src/app/(app)/classes/page.tsx create mode 100644 apps/teacher-portal/src/app/(app)/dashboard/page.tsx create mode 100644 apps/teacher-portal/src/app/(app)/layout.tsx create mode 100644 apps/teacher-portal/src/app/login/page.tsx create mode 100644 apps/teacher-portal/src/components/AppShell.tsx create mode 100644 apps/teacher-portal/src/lib/auth.ts diff --git a/apps/teacher-portal/src/app/(app)/classes/page.tsx b/apps/teacher-portal/src/app/(app)/classes/page.tsx new file mode 100644 index 0000000..3b9d025 --- /dev/null +++ b/apps/teacher-portal/src/app/(app)/classes/page.tsx @@ -0,0 +1,294 @@ +"use client"; + +import { useState, useEffect, useCallback } from "react"; +import { getToken } from "@/lib/auth"; + +interface ClassItem { + id: string; + name: string; + gradeId: string; + description?: string; + createdAt: number; + updatedAt: number; +} + +interface ApiResponse { + success: boolean; + data?: T; + error?: { code: string; message: string }; +} + +export default function ClassesPage() { + const [classes, setClasses] = useState([]); + const [loading, setLoading] = useState(false); + const [name, setName] = useState(""); + const [gradeId, setGradeId] = useState( + "550e8400-e29b-41d4-a716-446655440000", + ); + const [description, setDescription] = useState(""); + const [error, setError] = useState(null); + + const authHeaders = (): Record => { + const token = getToken(); + return token ? { Authorization: `Bearer ${token}` } : {}; + }; + + const fetchClasses = useCallback(async () => { + setLoading(true); + setError(null); + try { + const res = await fetch("/api/v1/classes", { + headers: authHeaders(), + }); + const json: ApiResponse = await res.json(); + if (json.success && json.data) { + setClasses(json.data); + } else { + setError(json.error?.message || "Failed to load"); + } + } catch (e) { + setError(e instanceof Error ? e.message : "Network error"); + } finally { + setLoading(false); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + fetchClasses(); + }, [fetchClasses]); + + const handleCreate = async (e: React.FormEvent) => { + e.preventDefault(); + if (!name.trim()) return; + try { + const res = await fetch("/api/v1/classes", { + method: "POST", + headers: { + "Content-Type": "application/json", + ...authHeaders(), + }, + body: JSON.stringify({ name, gradeId, description }), + }); + const json = await res.json(); + if (!json.success) { + setError(json.error?.message || "Create failed"); + return; + } + setName(""); + setDescription(""); + await fetchClasses(); + } catch (e) { + setError(e instanceof Error ? e.message : "Network error"); + } + }; + + const handleDelete = async (id: string) => { + try { + const res = await fetch(`/api/v1/classes/${id}`, { + method: "DELETE", + headers: authHeaders(), + }); + const json = await res.json(); + if (!json.success) { + setError(json.error?.message || "Delete failed"); + return; + } + await fetchClasses(); + } catch (e) { + setError(e instanceof Error ? e.message : "Network error"); + } + }; + + return ( +
+
+

+ 班级管理 +

+

+ classes 域 CRUD · JWT 鉴权 +

+
+ +
+ +
+