diff --git a/.env.example b/.env.example index 5a33af4..7fb9245 100644 --- a/.env.example +++ b/.env.example @@ -12,6 +12,12 @@ JWT_SECRET=p1-dev-secret-change-in-production JWT_ISSUER=next-edu-cloud JWT_AUDIENCE=next-edu-cloud +# 开发模式旁路(仅本地联调) +# DEV_MODE=true 时接受 "Authorization: Bearer dev-token" 旁路 JWT 校验, +# 注入固定身份 x-user-id=dev-user, x-user-roles=teacher,admin +# 生产环境必须设为 false 或不设此变量 +DEV_MODE=false + # 服务端口 API_GATEWAY_PORT=8080 CLASSES_SERVICE_PORT=3001 diff --git a/apps/teacher-portal/src/app/page.tsx b/apps/teacher-portal/src/app/page.tsx index a1abe7b..4b804ed 100644 --- a/apps/teacher-portal/src/app/page.tsx +++ b/apps/teacher-portal/src/app/page.tsx @@ -1,6 +1,6 @@ -'use client'; +"use client"; -import { useState, useEffect, useCallback } from 'react'; +import { useState, useEffect, useCallback } from "react"; interface ClassItem { id: string; @@ -20,24 +20,28 @@ interface ApiResponse { export default function HomePage() { 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 [name, setName] = useState(""); + const [gradeId, setGradeId] = useState( + "550e8400-e29b-41d4-a716-446655440000", + ); + const [description, setDescription] = useState(""); const [error, setError] = useState(null); const fetchClasses = useCallback(async () => { setLoading(true); setError(null); try { - const res = await fetch('/api/v1/classes'); + const res = await fetch("/api/v1/classes", { + headers: { Authorization: "Bearer dev-token" }, + }); const json: ApiResponse = await res.json(); if (json.success && json.data) { setClasses(json.data); } else { - setError(json.error?.message || 'Failed to load'); + setError(json.error?.message || "Failed to load"); } } catch (e) { - setError(e instanceof Error ? e.message : 'Network error'); + setError(e instanceof Error ? e.message : "Network error"); } finally { setLoading(false); } @@ -51,49 +55,61 @@ export default function HomePage() { e.preventDefault(); if (!name.trim()) return; try { - const res = await fetch('/api/v1/classes', { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer dev-token' }, + const res = await fetch("/api/v1/classes", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer dev-token", + }, body: JSON.stringify({ name, gradeId, description }), }); const json = await res.json(); if (!json.success) { - setError(json.error?.message || 'Create failed'); + setError(json.error?.message || "Create failed"); return; } - setName(''); - setDescription(''); + setName(""); + setDescription(""); await fetchClasses(); } catch (e) { - setError(e instanceof Error ? e.message : 'Network error'); + 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: { 'Authorization': 'Bearer dev-token' }, + method: "DELETE", + headers: { Authorization: "Bearer dev-token" }, }); const json = await res.json(); if (!json.success) { - setError(json.error?.message || 'Delete failed'); + setError(json.error?.message || "Delete failed"); return; } await fetchClasses(); } catch (e) { - setError(e instanceof Error ? e.message : 'Network error'); + setError(e instanceof Error ? e.message : "Network error"); } }; return ( -
-
+
+
-

+

班级管理

-

+

P1 黄金模板验证 - classes 域 CRUD

@@ -102,11 +118,19 @@ export default function HomePage() {
{/* 左侧:创建表单 */}