Module Update
Some checks failed
CI / build-and-test (push) Failing after 1m31s
CI / deploy (push) Has been skipped

This commit is contained in:
SpecialX
2025-12-30 14:42:30 +08:00
parent f1797265b2
commit e7c902e8e1
148 changed files with 19317 additions and 113 deletions

View File

@@ -0,0 +1,112 @@
# 架构决策记录 (ADR): 数据库 Schema 设计方案 v1.0
**状态**: 已实施 (IMPLEMENTED)
**日期**: 2025-12-23
**作者**: 首席系统架构师
**背景**: Next_Edu 平台 - K12 智慧教育管理系统
---
## 1. 概述 (Overview)
本文档详细记录了 Next_Edu 平台的数据库 Schema 架构设计。本设计优先考虑 **可扩展性 (Scalability)**、**灵活性 (Flexibility)**(针对复杂的嵌套内容)以及 **严格的类型安全 (Strict Type Safety)**,并完全符合 PRD 中规定的领域驱动设计 (DDD) 原则。
## 2. 技术栈决策 (Technology Stack Decisions)
| 组件 | 选择 | 理由 |
| :--- | :--- | :--- |
| **数据库** | MySQL 8.0+ | 强大的关系型支持,完善的 JSON 能力,行业标准。 |
| **ORM** | Drizzle ORM | 轻量级,零运行时开销 (Zero-runtime overhead),业界一流的 TypeScript 类型推断。 |
| **ID 策略** | CUID2 | 分布式友好 (k-sortable),安全(防连续猜测攻击),比 UUID 更短。 |
| **认证方案** | Auth.js v5 | 标准化的 OAuth 流程,支持自定义数据库适配器。 |
---
## 3. 核心 Schema 领域模型 (Core Schema Domains)
物理文件位于 `src/shared/db/schema.ts`,逻辑上分为三大领域。
### 3.1 身份与访问管理 (IAM)
我们采用了 **Auth.js 标准表****自定义 RBAC** 相结合的混合模式。
* **标准表**: `users`, `accounts` (OAuth), `sessions`, `verificationTokens`
* **RBAC 扩展**:
* `roles`: 定义系统角色(例如:`grade_head` 年级主任, `teacher` 老师)。
* `users_to_roles`: 多对多关联表。
* **设计目标**: 解决“一人多职”问题(例如:一个老师同时也是年级主任),避免在 `users` 表中堆砌字段。
### 3.2 智能题库中心 (Intelligent Question Bank) - 核心
这是最复杂的领域,需要支持无限层级嵌套和富文本内容。
#### 实体定义:
1. **`questions` (题目表)**:
* `id`: CUID2。
* `content`: **JSON 类型**。存储结构化内容(如 SlateJS 节点),支持富文本、图片和公式混排。
* `parentId`: **自引用 (Self-Reference)**
* *若为 NULL*: 独立题目 或 “大题干” (Parent)。
* *若有值*: 子题目 (Child)例如一篇阅读理解下的第1小题
* `type`: 枚举 (`single_choice`, `text`, `composite` 等)。
2. **`knowledge_points` (知识点表)**:
* 通过 `parentId` 实现树状结构。
* 支持无限层级 (学科 -> 章 -> 节 -> 知识点)。
3. **`questions_to_knowledge_points`**:
* 多对多关联。一道题可考察多个知识点;一个知识点可关联数千道题。
### 3.3 教务教学流 (Academic Teaching Flow)
将物理世界的教学过程映射为数字实体。
* **`textbooks` & `chapters`**: 标准的教材大纲映射。`chapters` 同样支持通过 `parentId` 进行嵌套。
* **`exams`**: 考试/作业的聚合实体。
* **`exam_submissions`**: 代表一名学生的单次答题记录。
* **`submission_answers`**: 细粒度的答题详情,记录每道题的答案,支持自动评分 (`score`) 和人工反馈 (`feedback`)。
---
## 4. 关键设计模式 (Key Design Patterns)
### 4.1 无限嵌套 ("Composite" Pattern)
我们没有为“题干”和“题目”创建单独的表,而是在 `questions` 表上使用 **自引用 (Self-Referencing)** 模式。
* **优点**:
* 统一的查询接口 (`db.query.questions.findFirst({ with: { children: true } })`)。
* 递归逻辑可统一应用。
* 当内容结构变化时,迁移更简单。
* **缺点**:
* 需要处理递归查询逻辑(已通过 Drizzle Relations 解决)。
### 4.2 CUID2 优于 自增 ID
* **安全性**: 防止 ID 枚举攻击(猜测下一个用户 ID
* **分布式**: 支持在客户端或多服务器节点生成,无碰撞风险。
* **性能**: `k-sortable` 特性保证了比随机 UUID v4 更好的索引局部性。
### 4.3 JSON 存储内容
* 教育内容不仅仅是“文本”。它包含格式、LaTeX 公式和图片引用。
* 使用 `JSON` 存储允许前端 (Next.js) 直接渲染富组件,无需解析复杂的 HTML 字符串。
---
## 5. 安全与索引策略 (Security & Indexing Strategy)
### 索引 (Indexes)
* **外键**: 所有外键列 (`author_id`, `parent_id` 等) 均显式建立索引。
* **性能**:
* `parent_id_idx`: 对树形结构的遍历性能至关重要。
* `email_idx`: 登录查询的核心索引。
### 类型安全 (Type Safety)
* 严格的 TypeScript 定义直接从 `src/shared/db/schema.ts` 导出。
* Zod Schema (待生成) 将与这些 Drizzle 定义保持 1:1 对齐。
---
## 6. 目录结构 (Directory Structure)
```bash
src/shared/db/
├── index.ts # 单例数据库连接池
├── schema.ts # 物理表结构定义
└── relations.ts # 逻辑 Drizzle 关系定义
```

View File

@@ -0,0 +1,52 @@
# Database Schema Change Request: Exam Structure Support
## 1. Table: `exams`
### Change
**Add Column**: `structure`
### Details
- **Type**: `JSON`
- **Nullable**: `TRUE` (Default: `NULL`)
### Reason
To support hierarchical exam structures (e.g., Sections/Groups containing Questions). The existing flat `exam_questions` table only supports a simple list of questions and is insufficient for complex exam layouts (e.g., "Part A: Reading", "Part B: Writing").
### Before vs After
**Before**:
`exams` table only stores metadata (`title`, `description`, etc.). Question ordering relies solely on `exam_questions.order`.
**After**:
`exams` table includes `structure` column to store the full tree representation:
```json
[
{ "id": "uuid-1", "type": "group", "title": "Section A", "children": [...] },
{ "id": "uuid-2", "type": "question", "questionId": "q1", "score": 10 }
]
```
*Note: `exam_questions` table is retained for relational integrity and efficient querying of question usage, but the presentation order/structure is now driven by this new JSON column.*
---
## 2. Table: `questions_to_knowledge_points`
### Change
**Rename Foreign Key Constraints**
### Details
- Rename constraint for `question_id` to `q_kp_qid_fk`
- Rename constraint for `knowledge_point_id` to `q_kp_kpid_fk`
### Reason
The default generated foreign key names (e.g., `questions_to_knowledge_points_knowledge_point_id_knowledge_points_id_fk`) exceed the MySQL identifier length limit (64 characters), causing migration failures.
### Before vs After
**Before**:
- FK Name: `questions_to_knowledge_points_question_id_questions_id_fk` (Too long)
- FK Name: `questions_to_knowledge_points_knowledge_point_id_knowledge_points_id_fk` (Too long)
**After**:
- FK Name: `q_kp_qid_fk`
- FK Name: `q_kp_kpid_fk`

View File

@@ -0,0 +1,115 @@
# Architecture RFC: Role-Based Routing & Directory Structure
**Status**: PROPOSED
**Date**: 2025-12-23
**Context**: Next_Edu supports multiple roles (Admin, Teacher, Student, Parent) with distinct UI/UX requirements.
## 1. 核心问题 (The Problem)
目前项目结构中,页面与角色耦合不清。
* `/dashboard` 目前硬编码为教师视图。
* 不同角色的功能模块(如“课程”)可能有完全不同的视图和逻辑(教师管理课程 vs 学生学习课程)。
* 缺乏统一的路由规范指导开发人员“把页面放哪里”。
## 2. 解决方案:基于角色的路由策略 (Role-Based Routing Strategy)
我们采用 **"Hybrid Routing" (混合路由)** 策略:
1. **Explicit Dashboard Routing**: Dashboards are separated by role in the file structure (e.g., `/teacher/dashboard`).
2. **Explicit Role Scopes (显式角色域)**: 具体的业务功能页面放入 `/teacher`, `/student`, `/admin` 专属路径下。
### 2.1 目录结构 (Directory Structure)
```
src/app/(dashboard)/
├── layout.tsx # Shared App Shell (Sidebar, Header)
├── dashboard/
│ └── page.tsx # [Redirector] Redirects to role-specific dashboard
├── teacher/ # 教师专属路由域
│ ├── dashboard/ # Teacher Dashboard
│ │ └── page.tsx
│ ├── classes/
│ │ └── page.tsx
│ ├── exams/
│ │ └── page.tsx
│ └── textbooks/
│ └── page.tsx
├── student/ # 学生专属路由域
│ ├── dashboard/ # Student Dashboard
│ │ └── page.tsx
│ ├── learning/
│ │ └── page.tsx
│ └── schedule/
│ └── page.tsx
└── admin/ # 管理员专属路由域
├── dashboard/ # Admin Dashboard
│ └── page.tsx
├── users/
└── school/
```
### 2.2 Dashboard Routing Logic
`/dashboard` 页面现在作为一个 **Portal (入口)****Redirector (重定向器)**,而不是直接渲染内容。
**Example: `src/app/(dashboard)/dashboard/page.tsx`**
```tsx
import { redirect } from "next/navigation";
import { auth } from "@/auth";
export default async function DashboardPage() {
const session = await auth();
const role = session?.user?.role;
switch (role) {
case "teacher":
redirect("/teacher/dashboard");
case "student":
redirect("/student/dashboard");
case "admin":
redirect("/admin/dashboard");
default:
redirect("/login");
}
}
```
## 3. 模块化组织 (Module Organization)
为了避免 `src/app` 变得臃肿,具体的**业务组件**必须存放在 `src/modules` 中。
```
src/modules/
├── teacher/ # 教师业务领域
│ ├── components/ # 仅教师使用的组件 (e.g., GradebookTable)
│ ├── hooks/
│ └── actions.ts
├── student/ # 学生业务领域
│ ├── components/ # (e.g., CourseProgressCard)
│ └── ...
├── shared/ # 跨角色共享
│ ├── components/ # (e.g., CourseCard - if generic)
```
## 4. 路由与导航配置 (Navigation Config)
`src/modules/layout/config/navigation.ts` 已经配置好了基于角色的菜单。
* 当用户访问 `/dashboard` 时,根据角色看到不同的 Dashboard 组件。
* 点击侧边栏菜单(如 "Exams")时,跳转到显式路径 `/teacher/exams`
## 5. 优势 (Benefits)
1. **Security**: 可以在 Middleware 或 Layout 层级轻松对 `/admin/*` 路径实施权限控制。
2. **Clarity**: 开发者清楚知道“教师的试卷列表页”应该放在 `src/app/(dashboard)/teacher/exams/page.tsx`
3. **Decoupling**: 教师端和学生端的逻辑完全解耦,互不影响。
---
## 6. Action Items (执行计划)
1. **Refactor Dashboard**: 将 `src/app/(dashboard)/dashboard/page.tsx` 重构为 Dispatcher。
2. **Create Role Directories**: 在 `src/app/(dashboard)` 下创建 `teacher`, `student`, `admin` 目录。
3. **Move Components**: 确保 `src/modules` 结构清晰。

View File

@@ -0,0 +1,39 @@
# Database Schema Changelog
## v1.1.0 - Exam Structure & Performance Optimization
**Date:** 2025-12-29
**Migration ID:** `0001_flawless_texas_twister`
**Author:** Principal Database Architect
### 1. Summary
This release introduces support for hierarchical exam structures (Sectioning/Grouping) and optimizes database constraint naming for better compatibility with MySQL environments.
### 2. Changes
#### 2.1 Table: `exams`
* **Action**: `ADD COLUMN`
* **Field**: `structure` (JSON)
* **Reason**: To support nested exam layouts (e.g., "Part I: Listening", "Section A").
* *Architecture Note*: This JSON field is strictly for **Presentation Layer** ordering and grouping. The `exam_questions` table remains the **Source of Truth** for relational integrity and scoring logic.
* **Schema Definition**:
```typescript
type ExamStructure = Array<
| { type: 'group', title: string, children: ExamStructure }
| { type: 'question', questionId: string, score: number }
>
```
#### 2.2 Table: `questions_to_knowledge_points`
* **Action**: `RENAME FOREIGN KEY`
* **Details**:
* Old: `questions_to_knowledge_points_question_id_questions_id_fk` -> New: `q_kp_qid_fk`
* Old: `questions_to_knowledge_points_knowledge_point_id_knowledge_points_id_fk` -> New: `q_kp_kpid_fk`
* **Reason**: Previous names exceeded MySQL's 64-character identifier limit, causing potential migration failures in production environments.
### 3. Migration Strategy
* **Up**: Run standard Drizzle migration. The script includes `ALTER TABLE ... DROP FOREIGN KEY` followed by `ADD CONSTRAINT`.
* **Down**: Revert `structure` column. Note that FK names can be kept short as they are implementation details.
### 4. Impact Analysis
* **Performance**: Negligible. JSON parsing is done client-side or at application layer.
* **Data Integrity**: High. Existing data is unaffected. New `structure` field defaults to `NULL`.

75
docs/db/seed-data.md Normal file
View File

@@ -0,0 +1,75 @@
# Database Seeding Strategy
**Status**: Implemented
**Script Location**: [`scripts/seed.ts`](../../scripts/seed.ts)
**Command**: `npm run db:seed`
---
## 1. Overview
The seed script is designed to populate the database with a **representative set of data** that covers all core business scenarios. It serves two purposes:
1. **Development**: Provides a consistent baseline for developers.
2. **Validation**: Verifies complex schema relationships (e.g., recursive trees, JSON structures).
## 2. Seed Data Topology
### 2.1 Identity & Access Management (IAM)
We strictly follow the RBAC model defined in `docs/architecture/001_database_schema_design.md`.
* **Roles**:
* `admin`: System Administrator.
* `teacher`: Academic Instructor.
* `student`: Learner.
* `grade_head`: Head of Grade Year (Demonstrates multi-role capability).
* **Users**:
* `admin@next-edu.com` (Role: Admin)
* `math@next-edu.com` (Role: Teacher + Grade Head)
* `alice@next-edu.com` (Role: Student)
### 2.2 Knowledge Graph
Generates a hierarchical structure to test recursive queries (`parentId`).
* **Math** (Level 0)
* └── **Algebra** (Level 1)
* └── **Linear Equations** (Level 2)
### 2.3 Question Bank
Includes rich content and nested structures.
1. **Simple Single Choice**: "What is 2 + 2?"
2. **Composite Question (Reading Comprehension)**:
* **Parent**: A reading passage.
* **Child 1**: Single Choice question about the passage.
* **Child 2**: Open-ended text question.
### 2.4 Exams
Demonstrates the new **JSON Structure** field (`exams.structure`).
* **Title**: "Algebra Mid-Term 2025"
* **Structure**:
```json
[
{
"type": "group",
"title": "Part 1: Basics",
"children": [{ "type": "question", "questionId": "...", "score": 10 }]
},
{
"type": "group",
"title": "Part 2: Reading",
"children": [{ "type": "question", "questionId": "...", "score": 20 }]
}
]
```
## 3. How to Run
### Prerequisites
Ensure your `.env` file contains a valid `DATABASE_URL`.
### Execution
Run the following command in the project root:
```bash
npm run db:seed
```
### Reset Behavior
**WARNING**: The script currently performs a **TRUNCATE** on all core tables before seeding. This ensures a clean state but will **WIPE EXISTING DATA**.

View File

@@ -0,0 +1,76 @@
# Auth UI Implementation Details
**Date**: 2025-12-23
**Author**: Senior Frontend Engineer
**Module**: Auth (`src/modules/auth`)
---
## 1. 概述 (Overview)
本文档记录了登录 (`/login`) 和注册 (`/register`) 页面的前端实现细节。遵循 Vertical Slice Architecture 和 Pixel-Perfect UI 规范。
## 2. 架构设计 (Architecture)
### 2.1 目录结构
所有认证相关的业务逻辑和组件均封装在 `src/modules/auth` 下,保持了高内聚。
```
src/
├── app/
│ └── (auth)/ # 路由层 (Server Components)
│ ├── layout.tsx # 统一的 AuthLayout 容器
│ ├── login/page.tsx
│ └── register/page.tsx
├── modules/
│ └── auth/ # 业务模块
│ └── components/ # 模块私有组件
│ ├── auth-layout.tsx # 左右分屏布局
│ ├── login-form.tsx # 登录表单 (Client Component)
│ └── register-form.tsx # 注册表单 (Client Component)
```
### 2.2 渲染策略
* **Server Components**: 页面入口 (`page.tsx`) 和布局 (`layout.tsx`) 默认为服务端组件,负责元数据 (`Metadata`) 和静态结构渲染。
* **Client Components**: 表单组件 (`*-form.tsx`) 标记为 `'use client'`处理交互逻辑状态管理、表单提交、Loading 状态)。
## 3. UI/UX 细节
### 3.1 布局 (Layout)
采用 **Split Screen (分屏)** 设计:
* **左侧 (Desktop Only)**:
* 深色背景 (`bg-zinc-900`),强调品牌沉浸感。
* 包含 Logo (`Next_Edu`) 和用户证言 (`Blockquote`)。
* 使用 `hidden lg:flex` 实现响应式显隐。
* **右侧**:
* 居中对齐的表单容器。
* 移动端优先 (`w-full`),桌面端限制最大宽度 (`sm:w-[350px]`)。
### 3.2 交互 (Interactions)
* **Loading State**: 表单提交时按钮进入 `disabled` 状态并显示 `Loader2` 旋转动画。
* **Micro-animations**:
* 按钮 Hover 效果。
* 链接 Hover 下划线 (`hover:underline`).
* **Feedback**: 模拟了 3 秒的异步请求延迟,用于演示加载状态。
## 4. 错误处理 (Error Handling)
### 4.1 模块级错误边界
* **Scoped Error Boundary**: `src/app/(auth)/error.tsx` 仅处理 Auth 模块内的运行时错误。
* 显示友好的 "Authentication Error" 提示。
* 提供 "Try again" 按钮重置状态。
### 4.2 模块级 404
* **Scoped Not Found**: `src/app/(auth)/not-found.tsx` 处理 Auth 模块内的无效路径。
* 引导用户返回 `/login` 页面,防止用户迷失。
## 5. 组件复用
* 使用了 `src/shared/components/ui` 中的标准 Shadcn 组件:
* `Button`, `Input`, `Label` (新增).
* 图标库统一使用 `lucide-react`.
## 5. 后续计划 (Next Steps)
* [ ] 集成 `next-auth` (Auth.js) 进行实际的身份验证。
* [ ] 添加 Zod Schema 进行前端表单验证。
* [ ] 对接后端 API (`src/modules/auth/actions.ts`).

View File

@@ -0,0 +1,100 @@
# 教师仪表盘实现与 Hydration 修复记录
**日期**: 2025-12-23
**作者**: 资深前端工程师 (Senior Frontend Engineer)
**状态**: 已实现
## 1. 概述
本文档详细说明了教师仪表盘 (Teacher Dashboard) 的实现细节,该实现严格遵循 Next_Edu 设计系统 v1.3.0。文档还记录了开发过程中遇到的 Hydration 错误及其解决方案。
## 2. 组件架构
仪表盘采用垂直切片架构 (Vertical Slice Architecture),代码位于 `src/modules/dashboard`
### 2.1 文件结构
```
src/modules/dashboard/
└── components/
├── teacher-stats.tsx # 核心指标 (学生数, 课程数, 待批改作业)
├── teacher-schedule.tsx # 今日日程列表
├── recent-submissions.tsx # 最近的学生提交记录
└── teacher-quick-actions.tsx # 常用操作 (创建作业等)
```
### 2.2 设计系统集成
所有组件严格遵循 v1.3.0 规范:
- **排版 (Typography)**: 使用 `Geist Sans`,数据展示开启 `tabular-nums`
- **色彩 (Colors)**: 使用语义化 HSL 变量 (`muted`, `primary`, `destructive`)。
- **图标 (Icons)**: 使用 `lucide-react` (如 `Users`, `BookOpen`, `Inbox`)。
- **状态 (States)**:
- **Loading**: 使用自定义骨架屏 (Skeleton),拒绝全屏 Spinner。
- **Empty**: 使用 `EmptyState` 组件处理无数据场景。
## 3. 组件详情
### 3.1 TeacherStats (教师统计)
- **用途**: 展示教师当前状态的高层概览。
- **特性**:
- 在响应式网格中展示 4 个关键指标。
- 支持 `isLoading` 属性以渲染骨架屏。
- 使用 `Card` 组件作为容器。
### 3.2 TeacherSchedule (教师日程)
- **用途**: 展示今日课程安排。
- **特性**:
- 列出课程时间及地点。
- 使用 Badge 区分 "Lecture" (讲座) 和 "Workshop" (研讨会)。
- **空状态**: 当无日程时显示 "No Classes Today"。
### 3.3 RecentSubmissions (最近提交)
- **用途**: 追踪最新的学生活动。
- **特性**:
- 展示学生头像、姓名、作业名称及时间。
- "Late" (迟交) 状态指示器。
- **空状态**: 当列表为空时显示 "No New Submissions"。
### 3.4 EmptyState Component (空状态组件)
- **位置**: `src/shared/components/ui/empty-state.tsx`
- **规范**:
- 虚线边框容器。
- 居中图标 (Muted 背景)。
- 清晰的标题和描述。
- 可选的操作按钮插槽。
## 4. Hydration 错误修复
### 4.1 问题描述
开发过程中观察到 "Hydration failed" 错误,原因是 HTML 嵌套无效。具体来说,是 `p` 标签内包含了块级元素(或 React 在 hydration 检查期间视为块级的元素)。
### 4.2 根本原因分析
React 的 hydration 过程对 HTML 有效性要求极高。将 `div` 放入 `p` 标签中违反了 HTML5 标准,但浏览器通常会自动修正 DOM 结构,导致实际 DOM 与 React 基于虚拟 DOM 预期的结构不一致。
### 4.3 实施的修复
将所有仪表盘组件中存在风险的 `p` 标签替换为 `div` 标签,以确保嵌套结构的健壮性。
**示例 (RecentSubmissions):**
*修改前 (有风险):*
```tsx
<p className="text-sm font-medium leading-none">
{item.studentName}
</p>
```
*修改后 (安全):*
```tsx
<div className="text-sm font-medium leading-none">
{item.studentName}
</div>
```
**受影响的组件:**
1. `recent-submissions.tsx`
2. `teacher-stats.tsx`
3. `teacher-schedule.tsx`
## 5. 下一步计划
- 将 Mock Data 对接到真实的 API 端点 (React Server Actions)。
- 实现 "Quick Actions" (快捷操作) 的具体功能。
- 为 Submissions 和 Schedule 添加 "View All" (查看全部) 跳转导航。

View File

@@ -0,0 +1,98 @@
# Textbooks Module Implementation Details
**Date**: 2025-12-23
**Author**: DevOps Architect
**Module**: Textbooks (`src/modules/textbooks`)
---
## 1. 概述 (Overview)
本文档记录了教材模块 (`Textbooks Module`) 的全栈实现细节。该模块负责教材、章节结构及知识点映射的数字化管理,采用了 **Vertical Slice Architecture****Immersive Workbench** 交互设计。
## 2. 架构设计 (Architecture)
### 2.1 目录结构
所有教材相关的业务逻辑、数据访问和组件均封装在 `src/modules/textbooks` 下,实现了高度的模块化和隔离。
```
src/
├── app/
│ └── (dashboard)/
│ └── teacher/
│ └── textbooks/ # 路由层 (Server Components)
│ ├── page.tsx # 列表页
│ ├── loading.tsx # 列表骨架屏
│ └── [id]/ # 详情页
│ ├── page.tsx
│ └── loading.tsx
├── modules/
│ └── textbooks/ # 业务模块
│ ├── actions.ts # Server Actions (增删改)
│ ├── data-access.ts # 数据访问层 (Mock/DB)
│ ├── types.ts # 类型定义 (Schema-aligned)
│ └── components/ # 模块私有组件
│ ├── textbook-content-layout.tsx # [核心] 三栏布局工作台
│ ├── chapter-sidebar-list.tsx # 递归章节树
│ ├── knowledge-point-panel.tsx # 知识点管理面板
│ ├── create-chapter-dialog.tsx # 章节创建弹窗
│ └── ... (其他交互组件)
```
### 2.2 渲染策略
* **Server Components**: 页面入口 (`page.tsx`) 负责初始数据获取 (Data Fetching),利用 `Promise.all` 并行拉取教材、章节和知识点数据,实现 "Render-as-you-fetch"。
* **Client Components**: 工作台布局 (`textbook-content-layout.tsx`) 标记为 `'use client'`,接管后续的所有交互逻辑(状态管理、局部更新、弹窗控制),提供类似 SPA 的流畅体验。
## 3. UI/UX 细节
### 3.1 布局 (Layout)
采用 **Immersive Workbench (沉浸式工作台)** 三栏设计:
* **左侧 (Navigation)**:
* 展示递归的章节树 (`Recursive Tree`)。
* 支持折叠/展开,清晰展示教材结构。
* 顶部提供 "+" 按钮快速创建新章节。
* **中间 (Content)**:
* **阅读模式**: 渲染 Markdown 格式的章节正文。
* **编辑模式**: 提供 `Textarea` 进行内容创作,支持实时保存。
* **右侧 (Context)**:
* **上下文感知**: 仅显示当前选中章节的关联知识点。
* 提供知识点的快速添加 (`Dialog`) 和删除操作。
### 3.2 交互 (Interactions)
* **Selection State**: 点击左侧章节,中间和右侧区域即时更新,无需页面跳转。
* **Optimistic UI**: 虽然使用 Server Actions但通过本地状态 (`useState`) 实现了操作的即时反馈(如保存正文后立即退出编辑模式)。
* **Feedback**: 使用 `sonner` (`toast`) 提供操作成功或失败的提示。
## 4. 数据流与逻辑 (Data Flow)
### 4.1 Server Actions
所有数据变更操作均通过 `src/modules/textbooks/actions.ts` 定义的 Server Actions 处理:
* `createChapterAction`: 创建章节(支持嵌套)。
* `updateChapterContentAction`: 更新正文内容。
* `createKnowledgePointAction`: 创建知识点并自动关联当前章节。
* `updateTextbookAction`: 更新教材元数据Title, Subject, Grade, Publisher
* `deleteTextbookAction`: 删除教材及其关联数据。
* `delete...Action`: 处理删除逻辑。
### 4.2 数据访问层 (Data Access)
* **Mock Implementation**: 目前在 `data-access.ts` 中使用内存数组模拟数据库操作,并人为增加了延迟 (`setTimeout`) 以测试 Loading 状态。
* **Type Safety**: 定义了严格的 TypeScript 类型 (`Chapter`, `KnowledgePoint`, `UpdateTextbookInput`),确保前后端数据契约一致。
## 5. 组件复用
* 使用了 `src/shared/components/ui` 中的 Shadcn 组件:
* `Dialog`, `ScrollArea`, `Card`, `Button`, `Input`, `Textarea`, `Select`.
* `Collapsible` 用于实现递归章节树。
* 图标库统一使用 `lucide-react`.
## 6. Settings 功能实现 (New)
* **入口**: 详情页右上角的 "Settings" 按钮。
* **组件**: `TextbookSettingsDialog`
* **功能**:
* **Edit**: 修改教材的基本信息。
* **Delete**: 提供红色删除按钮,二次确认后执行删除并跳转回列表页。
## 7. 后续计划 (Next Steps)
* [ ] **富文本编辑器**: 集成 Tiptap 替换现有的 Markdown Textarea支持更丰富的格式。
* [ ] **拖拽排序**: 实现章节树的拖拽排序 (`dnd-kit`)。
* [ ] **数据库对接**: 将 `data-access.ts` 中的 Mock 逻辑替换为真实的 `drizzle-orm` 数据库调用。

View File

@@ -0,0 +1,87 @@
# Question Bank Module Implementation
## 1. Overview
The Question Bank module (`src/modules/questions`) is a core component for teachers to manage their examination resources. It implements a comprehensive CRUD interface with advanced filtering and batch operations.
**Status**: IMPLEMENTED
**Date**: 2025-12-23
**Author**: Senior Frontend Engineer
---
## 2. Architecture & Tech Stack
### 2.1 Vertical Slice Architecture
Following the project's architectural guidelines, all question-related logic is encapsulated within `src/modules/questions`:
- `components/`: UI components (Data Table, Dialogs, Filters)
- `actions.ts`: Server Actions for data mutation
- `data-access.ts`: Database query logic
- `schema.ts`: Zod schemas for validation
- `types.ts`: TypeScript interfaces
### 2.2 Key Technologies
- **Data Grid**: `@tanstack/react-table` for high-performance rendering.
- **State Management**: `nuqs` for URL-based state (filters, search).
- **Forms**: `react-hook-form` + `zod` + `shadcn/ui` form components.
- **Validation**: Strict server-side and client-side validation using Zod schemas.
---
## 3. Component Design
### 3.1 QuestionDataTable (`question-data-table.tsx`)
- **Features**: Pagination, Sorting, Row Selection.
- **Performance**: Uses `React.memo` compatible patterns where possible (though `useReactTable` itself is not memoized).
- **Responsiveness**: Mobile-first design with horizontal scroll for complex columns.
### 3.2 QuestionColumns (`question-columns.tsx`)
Custom cell renderers for rich data display:
- **Type Badge**: Color-coded badges for different question types (Single Choice, Multiple Choice, etc.).
- **Difficulty**: Visual indicator with color (Green -> Red) and numerical value.
- **Actions**: Dropdown menu for Edit, Delete, View Details, and Copy ID.
### 3.3 Create/Edit Dialog (`create-question-dialog.tsx`)
A unified dialog component for both creating and editing questions.
- **Dynamic Fields**: Shows/hides "Options" field based on question type.
- **Interactive Options**: Allows adding/removing/reordering options for choice questions.
- **Optimistic UI**: Shows loading states during submission.
### 3.4 Filters (`question-filters.tsx`)
- **URL Sync**: All filter states (Search, Type, Difficulty) are synced to URL parameters.
- **Debounce**: Search input uses debounce to prevent excessive requests.
- **Server Filtering**: Filtering logic is executed on the server side (currently simulated in `page.tsx`, ready for DB integration).
---
## 4. Implementation Details
### 4.1 Data Flow
1. **Read**: `page.tsx` (Server Component) fetches data based on `searchParams`.
2. **Write**: Client components invoke Server Actions (simulated) -> Revalidate Path -> UI Updates.
3. **Filter**: User interaction -> Update URL -> Server Component Re-render -> New Data.
### 4.2 Type Safety
A shared `Question` interface ensures consistency across the stack:
```typescript
export interface Question {
id: string;
content: any; // Rich text structure
type: QuestionType;
difficulty: number;
// ... relations
}
```
### 4.3 UI/UX Standards
- **Empty States**: Custom `EmptyState` component when no data matches.
- **Loading States**: Skeleton screens for table loading.
- **Feedback**: `Sonner` toasts for success/error notifications.
- **Confirmation**: `AlertDialog` for destructive actions (Delete).
---
## 5. Next Steps
- [ ] Integrate with real Database (replace Mock Data).
- [ ] Implement Rich Text Editor (Slate.js / Tiptap) for question content.
- [ ] Add "Batch Import" functionality.
- [ ] Implement "Tags" management for Knowledge Points.

View File

@@ -0,0 +1,90 @@
# 考试模块实现设计文档
## 1. 概述
考试模块提供了一个完整的评估管理生命周期,使教师能够创建考试、组卷(支持嵌套分组)、发布评估以及对学生的提交进行评分。
## 2. 数据架构
### 2.1 核心实体
- **Exams**: 根实体,包含元数据(标题、时间安排)和结构信息。
- **ExamQuestions**: 关系链接,用于查询题目的使用情况(扁平化表示)。
- **ExamSubmissions**: 学生的考试尝试记录。
- **SubmissionAnswers**: 链接到特定题目的单个答案。
### 2.2 `structure` 字段
为了支持层级布局(如章节/分组),我们在 `exams` 表中引入了一个 JSON 列 `structure`。这作为考试布局的“单一事实来源”Source of Truth
**JSON Schema:**
```typescript
type ExamNode = {
id: string; // 节点的唯一 UUID
type: 'group' | 'question';
title?: string; // 'group' 类型必填
questionId?: string; // 'question' 类型必填
score?: number; // 在此考试上下文中的分值
children?: ExamNode[]; // 'group' 类型的递归子节点
}
```
## 3. 组件架构
### 3.1 组卷(构建器)
位于 `/teacher/exams/[id]/build`
- **`ExamAssembly` (客户端组件)**
- 管理 `structure` 状态树。
- 处理“添加题目”、“添加章节”、“移除”和“重新排序”操作。
- 实时计算总分和进度。
- **`StructureEditor` (客户端组件)**
- 基于 `@dnd-kit` 构建。
- 提供嵌套的可排序Sortable界面。
- 支持在组内/组间拖拽题目(当前优化为 2 层深度)。
- **`QuestionBankList`**
- 可搜索/筛选的可用题目列表。
- “添加”操作将节点追加到结构树中。
### 3.2 阅卷界面
位于 `/teacher/exams/grading/[submissionId]`
- **`GradingView` (客户端组件)**
- **左侧面板**: 只读视图,显示学生的答案与题目内容。
- **右侧面板**: 评分和反馈的输入字段。
- **状态**: 在提交前管理本地更改。
- **Actions**: `gradeSubmissionAction` 更新 `submissionAnswers` 并将总分聚合到 `examSubmissions`
## 4. 关键工作流
### 4.1 创建与构建考试
1. **创建**: 教师输入基本信息(标题、科目)。数据库创建记录(草稿状态)。
2. **构建**:
- 教师打开“构建”页面。
- 服务器从数据库 Hydrate注水`initialStructure`
- 教师从题库拖拽题目到结构树。
- 教师创建章节(分组)。
- **保存**: 同时提交 `questionsJson`(扁平化,用于索引)和 `structureJson`(树状,用于布局)到 `updateExamAction`
3. **发布**: 状态变更为 `published`
### 4.2 阅卷流程
1. **列表**: 教师查看 `submission-data-table`
2. **评分**: 打开特定提交。
3. **审查**: 遍历题目。
- 系统显示学生答案。
- 教师输入分数(上限为满分)和反馈。
4. **提交**: 服务器更新单个答案记录并重新计算提交总分。
## 5. 技术决策
### 5.1 混合存储策略
我们在存储考试题目时采用了 **混合方法**
- **关系型 (`exam_questions`)**: 用于“查找所有使用题目 X 的考试”查询和外键约束。
- **文档型 (`exams.structure`)**: 用于渲染嵌套 UI 和保留任意排序/分组。
*理由*: 这结合了 SQL 的完整性和 NoSQL 在 UI 布局上的灵活性。
### 5.2 拖拽功能
使用 `@dnd-kit` 代替旧库,因为:
- 更好的无障碍支持(键盘支持)。
- 模块化架构Sensors, Modifiers
- 面向未来(现代 React Hooks 模式)。
### 5.3 Server Actions
所有变更操作(保存草稿、发布、评分)均使用 Next.js Server Actions以确保类型安全并自动重新验证缓存。

View File

@@ -0,0 +1,258 @@
# Next_Edu Design System Specs
**Version**: 1.4.0 (Updated)
**Status**: ACTIVE
**Role**: Chief Creative Director
**Philosophy**: "Data as Art" - Clean, Minimalist, Information-Dense.
---
## 1. 核心理念 (Core Philosophy)
Next_Edu 旨在对抗教育系统常见的信息过载。我们的设计风格深受 **International Typographic Style (国际主义设计风格)** 影响。
* **Precision (精准)**: 每一个像素的留白都有其目的。
* **Clarity (清晰)**: 通过排版和对比度区分层级,而非装饰性的色块。
* **Efficiency (效率)**: 专为高密度数据操作优化,减少视觉噪音。
---
## 2. 视觉基础 (Visual Foundation)
### 2.1 色彩系统 (Color System)
我们放弃 Hex 直选,全面采用 **HSL 语义化变量** 以支持完美的多主题适配。
#### **Base (基调)**
* **Neutral**: Zinc (锌色). 冷峻、纯净,无偏色。
* **Brand**: Deep Indigo (深靛蓝).
* `Primary`: 专业、权威,避免幼稚的高饱和蓝。
* 语义: `hsl(var(--primary))`
#### **Functional (功能色)**
| 语义 | 色系 | 用途 |
| :--- | :--- | :--- |
| **Destructive** | Red | 删除、危险操作、系统错误 |
| **Warning** | Amber | 需注意的状态、非阻断性警告 |
| **Success** | Emerald | 操作成功、状态正常 |
| **Info** | Blue | 一般性提示、帮助信息 |
#### **Surface Hierarchy (层级)**
1. **Background**: 应用底层背景。
2. **Card**: 内容承载容器,轻微提升层级。
3. **Popover**: 悬浮层、下拉菜单,最高层级。
4. **Muted**: 用于次级信息或禁用状态背景。
### 2.2 排版 (Typography)
* **Font Family**: `Geist Sans` > `Inter` > `System UI`.
* *Requirement*: 必须开启 `tabular-nums` (等宽数字) 特性,确保表格数据对齐。
* **Scale**:
* **Base Body**: 14px (0.875rem) - 提升信息密度。
* **H1 (Page Title)**: 24px, Tracking -0.02em, Weight 600.
* **H2 (Section Title)**: 20px, Tracking -0.01em, Weight 600.
* **H3 (Card Title)**: 16px, Weight 600.
* **Tiny/Caption**: 12px, Text-Muted-Foreground.
### 2.3 质感 (Look & Feel)
* **Borders**: `1px solid var(--border)`. 界面骨架,以此分割区域而非背景色块。
* **Radius**:
* `sm` (4px): Badges, Checkboxes.
* `md` (8px): **Default**. Buttons, Inputs, Cards.
* `lg` (12px): Modals, Dialogs.
* **Shadows**:
* Default: None (Flat).
* Hover: `shadow-sm` (仅用于可交互元素).
* Dropdown/Popover: `shadow-md`.
* *Ban*: 禁止使用大面积弥散阴影。
---
## 3. 核心布局 (App Shell)
### 3.1 架构 (Architecture)
我们采用了 `SidebarProvider` + `AppSidebar` 的组合模式,确保了布局的灵活性和移动端的完美适配。
* **Provider**: `SidebarProvider` (src/modules/layout/components/sidebar-provider.tsx)
* 管理侧边栏状态 (`expanded`, `isMobile`).
* 负责在移动端渲染 Sheet (Drawer)。
* 负责在桌面端渲染 Sticky Sidebar。
* **Key Prop**: `sidebar` (显式传递侧边栏组件)。
### 3.2 布局结构
```
+-------------------------------------------------------+
| Sidebar | Header (Sticky) |
| |-------------------------------------------|
| (Collap- | Main Content |
| sible) | |
| | +-------------------------------------+ |
| | | Card | |
| | | +---------------------------------+ | |
| | | | Data Table | | |
| | | +---------------------------------+ | |
| | +-------------------------------------+ |
| | |
+-------------------------------------------------------+
```
### 3.3 详细规范
#### **Sidebar (侧边栏)**
* **Width**: Expanded `260px` | Collapsed `64px` | Mobile `Sheet (Drawer)`.
* **Behavior**:
* Desktop: 固定左侧,支持折叠。
* Mobile: 默认隐藏,点击汉堡菜单从左侧滑出。
* **Navigation Item**:
* Height: `36px` (Compact).
* State:
* `Inactive`: `text-muted-foreground hover:text-foreground`.
* `Active`: `bg-sidebar-accent text-sidebar-accent-foreground font-medium`.
#### **Header (顶栏)**
* **Height**: `64px` (h-16).
* **Layout**: `flex items-center justify-between px-6 border-b`.
* **Components**:
1. **Breadcrumb**: 显示当前路径,层级清晰。
2. **Global Search**: `Cmd+K` 触发,居中或靠右。
3. **User Nav**: 头像 + 下拉菜单。
#### **Main Content (内容区)**
* **Padding**: `p-6` (Desktop) / `p-4` (Mobile).
* **Max Width**: `max-w-[1600px]` (默认) 或 `w-full` (针对超宽报表)。
---
## 4. 导航与角色系统 (Navigation & Roles)
Next_Edu 支持多角色Multi-Tenant / Role-Based导航系统。
### 4.1 配置文件
导航结构已从 UI 组件中解耦,统一配置在:
`src/modules/layout/config/navigation.ts`
### 4.2 支持的角色 (Roles)
系统内置支持以下角色,每个角色拥有独立的侧边栏菜单结构:
* **Admin**: 系统管理员,拥有所有管理权限 (School Management, User Management, Finance)。
* **Teacher**: 教师,关注班级管理 (My Classes)、成绩录入 (Gradebook) 和日程。
* **Student**: 学生,关注课程学习 (My Learning) 和作业提交。
* **Parent**: 家长,关注子女动态和学费缴纳。
### 4.3 开发与调试
* **View As (Dev Mode)**: 在开发环境下,侧边栏顶部提供 "View As" 下拉菜单,允许开发者实时切换角色视角,预览不同角色的导航结构。
* **Implementation**: `AppSidebar` 组件通过读取 `NAV_CONFIG[currentRole]` 动态渲染菜单项。
---
## 5. 错误处理与边界情况 (Error Handling & Boundaries)
系统必须优雅地处理错误和边缘情况,避免白屏或无反馈。
### 5.1 全局错误边界 (Global Error Boundary)
* **Scope**: 捕获渲染期间的未处理异常。
* **UI**: 显示友好的错误页面(非技术堆栈信息),提供 "Try Again" 按钮重置状态。
* **Implementation**: 使用 React `ErrorBoundary` 或 Next.js `error.tsx`
### 5.2 404 Not Found
* **Design**: 必须保留 App Shell (Sidebar + Header),仅在 Main Content 区域显示 404 提示。
* **Content**: "Page not found" 文案 + 返回 Dashboard 的主操作按钮。
### 5.3 空状态 (Empty States)
当列表或表格无数据时,**严禁**只显示空白。
* **Component**: `EmptyState`
* **Composition**:
1. **Icon**: 线性风格图标 (muted foreground).
2. **Title**: 简短说明 (e.g., "No students found").
3. **Description**: 解释原因或下一步操作 (e.g., "Add a student to get started").
4. **Action**: (可选) "Create New" 按钮。
### 5.4 加载状态 (Loading States)
* **Initial Load**: 使用 `Skeleton` 骨架屏,模拟内容布局,避免 CLS (Content Layout Shift)。禁止使用全屏 Spinner。
* **Action Loading**: 按钮点击后进入 `disabled` + `spinner` 状态。
* **Table Loading**: 表格内容区域显示 3-5 行 Skeleton Rows。
### 5.5 表单验证 (Form Validation)
* **Style**: 错误信息显示在输入框下方,字号 `text-xs`,颜色 `text-destructive`
* **Input**: 边框变红 (`border-destructive`)。
---
## 6. 职责边界与协作 (Responsibility Boundaries)
**[IMPORTANT] 严禁越界修改 (Strict No-Modification Policy)**
为了维护大型项目的可维护性UI 工程师和开发人员必须遵守以下边界规则:
### 6.1 模块化原则 (Modularity)
* **Scope**: 开发者仅应对分配给自己的模块负责。例如,负责 "Dashboard" 的开发者**不应**修改 "Sidebar" 或 "Auth" 模块的代码。
* **Dependencies**: 如果你的模块依赖于其他模块的变更,**必须**先与该模块的负责人沟通,或在 PR 中明确标注。
### 6.2 共享组件 (Shared Components)
* **Immutable Core**: `src/shared/components/ui` 下的基础组件(如 Button, Card视为**核心库**。
* **Extension**: 如果基础组件不能满足需求优先考虑组合Composition或创建新的业务组件而不是修改核心组件的源码。
* **Modification Request**: 只有在发现严重 Bug 或需要全局样式调整时,才允许修改核心组件,且必须经过 Design Lead 审批。
### 6.3 样式一致性 (Consistency)
* **Global CSS**: `globals.css` 定义了系统的物理法则。严禁在局部组件中随意覆盖全局 CSS 变量。
* **Tailwind Config**: 禁止随意在组件中添加任意值Arbitrary Values, e.g., `w-[123px]`),必须使用 Design Token。
---
## 7. 组件设计规范 (Component Specs)
### 7.1 Card (卡片)
卡片是信息组织的基本单元。
* **Class**: `bg-card text-card-foreground border rounded-lg shadow-none`.
* **Header**: `p-6 pb-2`. Title (`font-semibold leading-none tracking-tight`).
* **Content**: `p-6 pt-0`.
### 7.2 Data Table (数据表格)
教务系统的核心组件。
* **Density**:
* `Default`: Row Height `48px` (h-12).
* `Compact`: Row Height `36px` (h-9).
* **Header**: `bg-muted/50 text-muted-foreground text-xs uppercase font-medium`.
* **Stripes**: 默认关闭。仅在列数 > 8 时开启 `even:bg-muted/50`
* **Actions**: 行操作按钮应默认隐形 (`opacity-0`)Hover 时显示 (`group-hover:opacity-100`),减少视觉干扰。
### 7.3 Feedback (反馈与通知)
* **Toast**: 使用 `Sonner` 组件。
* 位置: 默认右下角 (Bottom Right).
* 样式: 极简黑白风格 (跟随主题),支持撤销操作。
* 调用: `toast("Event has been created", { description: "Sunday, December 03, 2023 at 9:00 AM" })`.
* **Skeleton**: 加载状态必须使用 Skeleton 骨架屏,禁止使用全屏 Spinner。
* **Badge**: 状态指示器。
* `default`: 主要状态 (Primary).
* `secondary`: 次要状态 (Neutral).
* `destructive`: 错误/警告状态 (Error).
* `outline`: 描边风格 (Subtle).
---
## 8. 开发指南 (Developer Guide)
### 8.1 CSS Variables
所有颜色和圆角均通过 CSS 变量控制,定义在 `globals.css` 中。禁止在代码中 Hardcode 颜色值 (如 `#FFFFFF`, `rgb(0,0,0)` )。
### 8.2 Tailwind Utility 优先
优先使用 Tailwind Utility Classes。
*`text-sm text-muted-foreground`
*`.custom-text-class { font-size: 14px; color: #666; }`
### 8.3 Dark Mode
设计系统原生支持深色模式。只要正确使用语义化颜色变量(如 `bg-background`, `text-foreground`Dark Mode 将自动完美适配,无需额外编写 `dark:` 修饰符(除非为了特殊调整)。
### 8.4 组件库引用
所有 UI 组件位于 `src/shared/components/ui`
* `Button`: 基础按钮
* `Input`: 输入框
* `Select`: 下拉选择器 (New)
* `Sheet`: 侧边栏/抽屉
* `Sonner`: Toast 通知
* `Badge`: 徽章/标签
* `Skeleton`: 加载占位符
* `DropdownMenu`: 下拉菜单
* `Avatar`: 头像
* `Label`: 表单标签
* `EmptyState`: 空状态占位 (New)

View File

@@ -0,0 +1,180 @@
# Next_Edu 产品需求文档 (PRD) - K12 智慧教学管理系统
**版本**: 2.0.0 (K12 Enterprise Edition)
**状态**: 规划中
**最后更新**: 2025-12-22
**作者**: Senior EdTech Product Manager
**适用范围**: 全校级教学管理 (教-考-练-评)
---
## 1. 角色与权限矩阵 (Complex Role Matrix)
本系统采用基于 RBAC (Role-Based Access Control) 的多维权限设计,并结合 **行级安全 (Row-Level Security, RLS)** 策略,确保数据隔离与行政管理的精确匹配。
### 1.1 角色定义与核心职责
| 角色 | 核心职责 | 权限特征 (Scope) |
| :--- | :--- | :--- |
| **系统管理员 (Admin)** | 基础数据维护、账号管理、学期设置 | 全局系统配置,不可触碰教学业务数据内容(隐私保护)。 |
| **校长 (Principal)** | 全校教学概况监控、宏观统计报表 | **全校可见**。查看所有年级、学科的统计数据(平均分、作业完成率),无修改具体的题目/作业权限。 |
| **年级主任 (Grade Head)** | 本年级行政管理、班级均衡度分析 | **年级可见**。管理本年级所有行政班级;查看本年级跨学科对比;无权干涉其他年级。 |
| **教研组长 (Subject Head)** | 学科资源建设、命题质量把控 | **学科可见**。管理本学科公共题库、教案模板;查看全校该学科教学质量;无权查看其他学科详情。 |
| **班主任 (Class Teacher)** | 班级学生管理、家校通知、综合评价 | **行政班可见**。查看本班所有学生的跨学科成绩、考勤;发布班级公告。 |
| **任课老师 (Teacher)** | 备课、出卷、批改、个别辅导 | **教学班可见**。仅能操作自己所教班级的该学科作业/考试;私有题库管理。 |
| **学生 (Student)** | 完成作业、参加考试、查看错题本 | **个人可见**。仅能访问分配给自己的任务;查看个人成长档案。 |
### 1.2 关键权限辨析:年级主任 vs 教研组长
* **维度差异**:
* **年级主任 (横向管理)**: 关注的是 **"人" (People & Administration)**。例如:高一(3)班的整体纪律如何?高一年级整体是否在期中考试中达标?他们需要跨学科的数据视图(如:某学生是否偏科)。
* **教研组长 (纵向管理)**: 关注的是 **"内容" (Content & Pedagogy)**。例如:英语科目的“阅读理解”题型得分率全校是否偏低?公共题库的题目质量如何?他们需要跨年级但单学科的深度视图。
* **数据可见性 (RLS 策略)**:
* `GradeHead_View`: `WHERE class.grade_id = :current_user_grade_id`
* `SubjectHead_View`: `WHERE course.subject_id = :current_user_subject_id` (可能跨所有年级)
---
## 2. 核心功能模块深度拆解
### 2.1 智能题库中心 (Smart Question Bank)
这是系统的核心资产库,必须支持高复杂度的题目结构。
* **多层嵌套题目结构 (Nested Questions)**:
* **场景**: 英语完形填空、语文现代文阅读、理综大题。
* **逻辑**: 引入 **"题干 (Stem)"** 与 **"子题 (Sub-question)"** 的概念。
* **父题 (Parent)**: 承载公共题干(如一篇 500 字的文章、一张物理实验图表)。通常不直接设分,或者设总分。
* **子题 (Child)**: 依附于父题,是具体的答题点(选择、填空、简答)。每个子题有独立的分值、答案和解析。
* **交互**: 组卷时,拖动“父题”,所有“子题”必须作为一个原子整体跟随移动,不可拆分。
* **知识点图谱 (Knowledge Graph)**:
* **结构**: 树状结构 (Tree)。例如:`数学 -> 代数 -> 函数 -> 二次函数 -> 二次函数的图像`
* **关联**:
* **多对多 (Many-to-Many)**: 一道题可能考察多个知识点(综合题)。
* **权重**: (可选高级需求) 标记主要考点与次要考点。
### 2.2 课本与大纲映射 (Textbook & Curriculum)
* **课本数字化**:
* 系统预置主流教材版本 (如人教版、北师大版)。
* **核心映射**: `Textbook Chapter` (课本章节) <--> `Knowledge Point` (知识点)。
* **价值**: 老师备课时,只需选择“必修一 第一章”,系统自动推荐关联的“集合”相关题目,无需手动去海量题库搜索。
### 2.3 试卷/作业组装引擎 (Assembly Engine)
* **智能筛选**: 支持交集筛选(同时包含“力学”和“三角函数”的题目)。
* **AB 卷生成**: 针对防作弊场景支持题目乱序或选项乱序Shuffle
* **作业分层**: 支持“必做题”与“选做题”设置,满足分层教学需求。
### 2.4 消息通知中心 (Notification System)
分级分策略的消息分发:
* **强提醒 (High Priority)**: 系统公告、考试开始提醒。通过站内信 + 弹窗 + (集成)短信/微信模板消息。
* **业务流 (Medium Priority)**: 作业发布、成绩出炉。站内红点 + 列表推送。
* **弱提醒 (Low Priority)**: 错题本更新、周报生成。仅在进入相关模块时提示。
---
## 3. 数据实体关系推演 (Data Entity Relationships)
基于 MySQL 关系型数据库的设计方案。
### 3.1 核心实体模型 (ER Draft)
1. **SysUser**: `id`, `username`, `role`, `school_id`
2. **TeacherProfile**: `user_id`, `is_grade_head`, `is_subject_head`
3. **Class**: `id`, `grade_level` (e.g., 10), `class_name` (e.g., "3班"), `homeroom_teacher_id`
4. **Subject**: `id`, `name` (e.g., "Math")
5. **Course**: `id`, `class_id`, `subject_id`, `teacher_id` (核心教学关系表: 谁教哪个班的哪门课)
### 3.2 题库与知识点设计 (关键难点)
#### Table: `knowledge_points` (知识点树)
* `id`: UUID
* `subject_id`: FK
* `name`: VARCHAR
* `parent_id`: UUID (Self-reference, Root is NULL)
* `level`: INT (1, 2, 3...)
* `code`: VARCHAR (e.g., "M-ALG-01-02" 用于快速检索)
#### Table: `questions` (支持嵌套)
* `id`: UUID
* `content`: TEXT (HTML/Markdown, store images as URLs)
* `type`: ENUM ('SINGLE', 'MULTI', 'FILL', 'ESSAY', 'COMPOSITE')
* `parent_id`: UUID (Self-reference, **核心设计**)
* If `NULL`: 这是一道独立题目 OR 复合题的大题干。
* If `NOT NULL`: 这是一个子题目,属于 `parent_id` 对应的题干。
* `difficulty`: INT (1-5)
* `answer`: TEXT (JSON structure for structured answers)
* `analysis`: TEXT (解析)
* `created_by`: FK (Teacher)
* `scope`: ENUM ('PUBLIC', 'PRIVATE')
#### Table: `question_knowledge` (题目-知识点关联)
* `question_id`: FK
* `knowledge_point_id`: FK
* **Primary Key**: (`question_id`, `knowledge_point_id`)
### 3.3 课本映射设计
#### Table: `textbooks`
* `id`: UUID
* `name`: VARCHAR
* `grade_level`: INT
* `subject_id`: FK
#### Table: `textbook_chapters`
* `id`: UUID
* `textbook_id`: FK
* `name`: VARCHAR
* `parent_id`: UUID (Sections within Chapters)
* `content`: TEXT (Rich text content of the chapter/section) -- [ADDED for Content Viewing]
* `order`: INT
#### Table: `chapter_knowledge_mapping`
* `chapter_id`: FK
* `knowledge_point_id`: FK
* *解释*: 这张表是连接“教学进度”与“底层知识”的桥梁。
---
## 4. 关键业务流程 (User Flows)
### 4.1 智能组卷与发布流程 (Exam Creation Flow)
这是一个高频且复杂的路径,需要极高的流畅度。
1. **启动组卷**:
* 老师进入 [教学工作台] -> 点击 [新建试卷/作业]。
* 输入基本信息(名称、考试时长、总分)。
2. **设定范围 (锚定课本)**:
* 老师选择教材版本:`人教版高中数学必修一`
* 选择章节:勾选 `第一章 集合``第二章 函数概念`
* *系统动作*: 后台查询 `chapter_knowledge_mapping`,提取出这几章对应的所有 `knowledge_points`
3. **筛选题目**:
* 系统展示题目列表,默认过滤条件为上述提取的知识点。
* 老师增加筛选:`难度: 中等`, `题型: 选择题`
* **处理嵌套题**: 如果筛选结果包含一个“完形填空”的子题,系统在 UI 上必须**强制展示**其对应的父题干,并提示老师“需整体添加”。
4. **加入试题篮 (Cart)**:
* 老师点击“+”号。
* 试题篮动态更新:`当前题目数: 15, 预计总分: 85`
5. **试卷精修 (Refine)**:
* 进入“试卷预览”模式。
* 调整题目顺序 (Drag & drop)。
* 修改某道题的分值(覆盖默认分值)。
6. **发布设置**:
* 选择发布对象:`高一(3)班`, `高一(5)班` (基于 `Course` 表权限)。
* 设置时间:`开始时间`, `截止时间`
* 发布模式:`在线作答``线下答题卡` (若线下,系统生成 PDF 和答题卡样式)。
7. **完成**:
* 学生端收到 `Notifications` 推送。
* `Exams` 表生成记录,`ExamAllocations` 表为每个班级/学生生成状态记录。

35
docs/scripts/reset-db.ts Normal file
View File

@@ -0,0 +1,35 @@
import "dotenv/config"
import { db } from "@/shared/db"
import { sql } from "drizzle-orm"
async function reset() {
console.log("🔥 Resetting database...")
// Disable foreign key checks
await db.execute(sql`SET FOREIGN_KEY_CHECKS = 0;`)
// Get all table names
const tables = await db.execute(sql`
SELECT table_name
FROM information_schema.tables
WHERE table_schema = DATABASE();
`)
// Drop each table
for (const row of (tables[0] as unknown as any[])) {
const tableName = row.TABLE_NAME || row.table_name
console.log(`Dropping table: ${tableName}`)
await db.execute(sql.raw(`DROP TABLE IF EXISTS \`${tableName}\`;`))
}
// Re-enable foreign key checks
await db.execute(sql`SET FOREIGN_KEY_CHECKS = 1;`)
console.log("✅ Database reset complete.")
process.exit(0)
}
reset().catch((err) => {
console.error("❌ Reset failed:", err)
process.exit(1)
})

346
docs/scripts/seed-exams.ts Normal file
View File

@@ -0,0 +1,346 @@
import "dotenv/config"
import { db } from "@/shared/db"
import { users, exams, questions, knowledgePoints, examSubmissions, examQuestions, submissionAnswers } from "@/shared/db/schema"
import { createId } from "@paralleldrive/cuid2"
import { faker } from "@faker-js/faker"
import { eq } from "drizzle-orm"
/**
* Seed Script for Next_Edu
*
* Usage:
* 1. Ensure DATABASE_URL is set in .env
* 2. Run with tsx: npx tsx docs/scripts/seed-exams.ts
*/
const SUBJECTS = ["Mathematics", "Physics", "English", "Chemistry", "Biology"]
const GRADES = ["Grade 10", "Grade 11", "Grade 12"]
const DIFFICULTY = [1, 2, 3, 4, 5]
async function seed() {
console.log("🌱 Starting seed process...")
// 1. Create a Teacher User if not exists
const teacherEmail = "teacher@example.com"
let teacherId = "user_teacher_123"
const existingTeacher = await db.query.users.findFirst({
where: eq(users.email, teacherEmail)
})
if (!existingTeacher) {
console.log("Creating teacher user...")
await db.insert(users).values({
id: teacherId,
name: "Senior Teacher",
email: teacherEmail,
role: "teacher",
image: "https://api.dicebear.com/7.x/avataaars/svg?seed=Teacher",
})
} else {
teacherId = existingTeacher.id
console.log("Teacher user exists:", teacherId)
}
// 1b. Create Students
console.log("Creating students...")
const studentIds: string[] = []
for (let i = 0; i < 5; i++) {
const sId = createId()
studentIds.push(sId)
await db.insert(users).values({
id: sId,
name: faker.person.fullName(),
email: faker.internet.email(),
role: "student",
image: `https://api.dicebear.com/7.x/avataaars/svg?seed=${sId}`,
})
}
// 2. Create Knowledge Points
console.log("Creating knowledge points...")
const kpIds: string[] = []
for (const subject of SUBJECTS) {
for (let i = 0; i < 3; i++) {
const kpId = createId()
kpIds.push(kpId)
await db.insert(knowledgePoints).values({
id: kpId,
name: `${subject} - ${faker.science.unit()}`,
description: faker.lorem.sentence(),
level: 1,
})
}
}
// 3. Create Questions
console.log("Creating questions...")
const questionIds: string[] = []
for (let i = 0; i < 50; i++) {
const qId = createId()
questionIds.push(qId)
const type = faker.helpers.arrayElement(["single_choice", "multiple_choice", "text", "judgment"])
await db.insert(questions).values({
id: qId,
content: {
text: faker.lorem.paragraph(),
options: type.includes("choice") ? [
{ id: "A", text: faker.lorem.sentence(), isCorrect: true },
{ id: "B", text: faker.lorem.sentence(), isCorrect: false },
{ id: "C", text: faker.lorem.sentence(), isCorrect: false },
{ id: "D", text: faker.lorem.sentence(), isCorrect: false },
] : undefined
},
type: type as any,
difficulty: faker.helpers.arrayElement(DIFFICULTY),
authorId: teacherId,
})
}
// 4. Create Exams & Submissions
console.log("Creating exams and submissions...")
for (let i = 0; i < 15; i++) {
const examId = createId()
const subject = faker.helpers.arrayElement(SUBJECTS)
const grade = faker.helpers.arrayElement(GRADES)
const status = faker.helpers.arrayElement(["draft", "published", "archived"])
const scheduledAt = faker.date.soon({ days: 30 })
const meta = {
subject,
grade,
difficulty: faker.helpers.arrayElement(DIFFICULTY),
totalScore: 100,
durationMin: faker.helpers.arrayElement([45, 60, 90, 120]),
questionCount: faker.number.int({ min: 10, max: 30 }),
tags: [faker.word.sample(), faker.word.sample()],
scheduledAt: scheduledAt.toISOString()
}
await db.insert(exams).values({
id: examId,
title: `${subject} ${faker.helpers.arrayElement(["Midterm", "Final", "Quiz", "Unit Test"])}`,
description: JSON.stringify(meta),
creatorId: teacherId,
startTime: scheduledAt,
status: status as any,
})
// Link some questions to this exam (random 5 questions)
const selectedQuestions = faker.helpers.arrayElements(questionIds, 5)
await db.insert(examQuestions).values(
selectedQuestions.map((qId, idx) => ({
examId,
questionId: qId,
score: 20, // 5 * 20 = 100
order: idx
}))
)
// Create submissions for published exams
if (status === "published") {
const submittingStudents = faker.helpers.arrayElements(studentIds, faker.number.int({ min: 1, max: 3 }))
for (const studentId of submittingStudents) {
const submissionId = createId()
const submissionStatus = faker.helpers.arrayElement(["submitted", "graded"])
await db.insert(examSubmissions).values({
id: submissionId,
examId,
studentId,
score: submissionStatus === "graded" ? faker.number.int({ min: 60, max: 100 }) : null,
status: submissionStatus,
submittedAt: faker.date.recent(),
})
// Generate answers for this submission
for (const qId of selectedQuestions) {
await db.insert(submissionAnswers).values({
id: createId(),
submissionId: submissionId,
questionId: qId,
answerContent: { answer: faker.lorem.sentence() }, // Mock answer
score: submissionStatus === "graded" ? faker.number.int({ min: 0, max: 20 }) : null,
feedback: submissionStatus === "graded" ? faker.lorem.sentence() : null,
})
}
}
}
}
// 5. Create a specific Primary School Chinese Exam (小学语文)
console.log("Creating Primary School Chinese Exam...")
const chineseExamId = createId()
const chineseQuestions = []
// 5a. Pinyin Questions
const pinyinQ1 = createId()
const pinyinQ2 = createId()
chineseQuestions.push({ id: pinyinQ1, score: 5 }, { id: pinyinQ2, score: 5 })
await db.insert(questions).values([
{
id: pinyinQ1,
content: { text: "看拼音写词语chūn tiān ( )" },
type: "text",
difficulty: 1,
authorId: teacherId,
},
{
id: pinyinQ2,
content: { text: "看拼音写词语huā duǒ ( )" },
type: "text",
difficulty: 1,
authorId: teacherId,
}
])
// 5b. Vocabulary Questions
const vocabQ1 = createId()
const vocabQ2 = createId()
chineseQuestions.push({ id: vocabQ1, score: 5 }, { id: vocabQ2, score: 5 })
await db.insert(questions).values([
{
id: vocabQ1,
content: {
text: "选词填空:今天天气真( )。",
options: [
{ id: "A", text: "美好", isCorrect: false },
{ id: "B", text: "晴朗", isCorrect: true },
{ id: "C", text: "快乐", isCorrect: false }
]
},
type: "single_choice",
difficulty: 2,
authorId: teacherId,
},
{
id: vocabQ2,
content: {
text: "下列词语中,书写正确的是( )。",
options: [
{ id: "A", text: "漂扬", isCorrect: false },
{ id: "B", text: "飘扬", isCorrect: true },
{ id: "C", text: "票扬", isCorrect: false }
]
},
type: "single_choice",
difficulty: 2,
authorId: teacherId,
}
])
// 5c. Reading Comprehension Questions
const readingQ1 = createId()
const readingQ2 = createId()
chineseQuestions.push({ id: readingQ1, score: 10 }, { id: readingQ2, score: 10 })
await db.insert(questions).values([
{
id: readingQ1,
content: {
text: "阅读短文《小兔子乖乖》,回答问题:\n\n小兔子乖乖把门儿开开...\n\n文中提到的动物是",
options: [
{ id: "A", text: "大灰狼", isCorrect: false },
{ id: "B", text: "小兔子", isCorrect: true },
{ id: "C", text: "小花猫", isCorrect: false }
]
},
type: "single_choice",
difficulty: 3,
authorId: teacherId,
},
{
id: readingQ2,
content: { text: "请用一句话形容小兔子。" },
type: "text",
difficulty: 3,
authorId: teacherId,
}
])
// 5d. Construct Exam Structure
const chineseExamStructure = [
{
id: createId(),
type: "group",
title: "第一部分:基础知识",
children: [
{
id: createId(),
type: "group",
title: "一、看拼音写词语",
children: [
{ id: createId(), type: "question", questionId: pinyinQ1, score: 5 },
{ id: createId(), type: "question", questionId: pinyinQ2, score: 5 }
]
},
{
id: createId(),
type: "group",
title: "二、词语积累",
children: [
{ id: createId(), type: "question", questionId: vocabQ1, score: 5 },
{ id: createId(), type: "question", questionId: vocabQ2, score: 5 }
]
}
]
},
{
id: createId(),
type: "group",
title: "第二部分:阅读理解",
children: [
{
id: createId(),
type: "group",
title: "三、短文阅读",
children: [
{ id: createId(), type: "question", questionId: readingQ1, score: 10 },
{ id: createId(), type: "question", questionId: readingQ2, score: 10 }
]
}
]
}
]
await db.insert(exams).values({
id: chineseExamId,
title: "小学语文三年级上册期末考试",
description: JSON.stringify({
subject: "Chinese",
grade: "Grade 3",
difficulty: 3,
totalScore: 40,
durationMin: 90,
questionCount: 6,
tags: ["期末", "语文", "三年级"]
}),
structure: chineseExamStructure,
creatorId: teacherId,
status: "published",
startTime: new Date(),
})
// Link questions to exam
await db.insert(examQuestions).values(
chineseQuestions.map((q, idx) => ({
examId: chineseExamId,
questionId: q.id,
score: q.score,
order: idx
}))
)
console.log("✅ Seed completed successfully!")
process.exit(0)
}
seed().catch((err) => {
console.error("❌ Seed failed:", err)
process.exit(1)
})