=test_update_homework_tests_and_work_log
Some checks failed
CI / build-deploy (push) Has been cancelled
Some checks failed
CI / build-deploy (push) Has been cancelled
This commit is contained in:
@@ -1,360 +0,0 @@
|
||||
# Next_Edu Architecture RFC
|
||||
|
||||
**Status**: PROPOSED
|
||||
**Date**: 2025-12-22
|
||||
**Author**: Principal Software Architect
|
||||
**Version**: 1.0.0
|
||||
|
||||
---
|
||||
|
||||
## 1. 核心原则 (Core Principles)
|
||||
|
||||
本架构设计遵循以下核心原则,旨在构建一个高性能、可扩展、易维护的企业级在线教育平台。
|
||||
|
||||
1. **Vertical Slice Architecture (垂直切片)**: 拒绝传统的按技术分层(Layered Architecture),采用按业务功能分层。代码应根据“它属于哪个功能”而不是“它是什么文件”来组织。
|
||||
2. **Type Safety First (类型安全优先)**: 全链路 TypeScript (Strict Mode)。从数据库 Schema 到 API 再到 UI 组件,必须保持类型一致性。
|
||||
3. **Server-First (服务端优先)**: 充分利用 Next.js 15 App Router 的 RSC (React Server Components) 能力,减少客户端 Bundle 体积。
|
||||
4. **Performance by Default (默认高性能)**: 严禁引入重型动画库,动效优先使用 CSS Native 实现。Web Vitals 指标作为 CI 阻断标准。
|
||||
5. **Strict Engineering (严格工程化)**: CI/CD 流程标准化,代码风格统一,自动化测试覆盖。
|
||||
|
||||
---
|
||||
|
||||
## 2. 技术栈全景图 (Technology Panorama)
|
||||
|
||||
### 核心框架
|
||||
* **Framework**: Next.js 15 (App Router)
|
||||
* **Language**: TypeScript 5.x (Strict Mode enabled)
|
||||
* *Correction*: 鉴于 Next.js App Router 的特性,`.tsx` 仅用于包含 JSX 的组件文件。所有的业务逻辑、Hooks、API 路由、Lib 工具函数必须使用 `.ts` 后缀,以明确区分“渲染层”与“逻辑层”。
|
||||
* **Runtime**: Node.js 20.x (LTS)
|
||||
|
||||
### 数据层
|
||||
* **Database**: MySQL 8.0+
|
||||
* **ORM**: Drizzle ORM (轻量、无运行时开销、类型安全)
|
||||
* **Driver**: `mysql2` (配合连接池) 或 `serverless-mysql` (针对特定 Serverless 环境)
|
||||
* **Validation**: Zod (Schema 定义与运行时验证)
|
||||
|
||||
### UI/UX 层
|
||||
* **Styling**: Tailwind CSS v3.4+
|
||||
* **Components**: Shadcn/UI (基于 Radix UI 的 Headless 组件拷贝)
|
||||
* **Icons**: Lucide React
|
||||
* **Animations**: CSS Transitions / Tailwind `animate-*` / `tailwindcss-animate`
|
||||
* *Complex Interactions*: Framer Motion (仅限按需加载 `LazyMotion`)
|
||||
|
||||
### 身份验证与授权
|
||||
* **Auth**: Auth.js v5 (NextAuth)
|
||||
* *Decision Driver*: 相比 Clerk,Auth.js 提供了完全的**数据所有权**和**无 Vendor Lock-in**。
|
||||
* *Enterprise Needs*: 允许自定义 Session 结构(如注入 `Role` 字段)并直接对接现有 MySQL 数据库,满足复杂的企业级权限管理需求。
|
||||
|
||||
### 状态管理
|
||||
* **Server State**: TanStack Query v5 (仅用于复杂客户端轮询/无限加载)
|
||||
* **URL State (Primary)**: Nuqs (Type-safe search params state manager)
|
||||
* *Principle*: 绝大多数状态(筛选、分页、Tab)应存在 URL 中,以支持分享和书签。
|
||||
* **Global Client State (Secondary)**: Zustand
|
||||
* *Usage*: 仅限极少数全局交互状态(如播放器悬浮窗、全局 Modal)。
|
||||
* *Anti-pattern*: **严禁使用 Redux**。避免不必要的样板代码和 Bundle 体积。
|
||||
|
||||
### 基础设施 & DevOps
|
||||
* **CI/CD**: GitHub Actions (Strictly v3)
|
||||
* **Linting**: ESLint (Next.js config), Prettier
|
||||
* **Package Manager**: pnpm (推荐) 或 npm
|
||||
|
||||
---
|
||||
|
||||
## 3. 项目目录结构规范 (Project Structure)
|
||||
|
||||
采用 **Feature-based / Vertical Slice** 架构。所有业务逻辑应封装在 `src/modules` 中。
|
||||
|
||||
文档存放位置:
|
||||
* 架构设计文档: `docs/architecture/`
|
||||
* API 规范文档: `docs/api/`
|
||||
|
||||
### 目录树 (Directory Tree)
|
||||
|
||||
```
|
||||
Next_Edu/
|
||||
├── .github/
|
||||
│ └── workflows/
|
||||
│ └── ci.yml # GitHub Actions (v3 strict)
|
||||
├── docs/
|
||||
│ └── architecture/ # 架构决策记录 (ADR)
|
||||
├── drizzle/ # 数据库迁移文件 (Generated)
|
||||
├── public/ # 静态资源
|
||||
├── src/
|
||||
│ ├── app/ # [路由层] 极薄,仅负责路由分发和布局
|
||||
│ │ ├── (auth)/ # 路由组
|
||||
│ │ ├── (dashboard)/
|
||||
│ │ ├── api/ # Webhooks / External APIs
|
||||
│ │ ├── layout.tsx
|
||||
│ │ └── page.tsx
|
||||
│ │
|
||||
│ ├── modules/ # [核心业务层] 垂直切片
|
||||
│ │ ├── courses/ # 课程模块
|
||||
│ │ │ ├── components/ # 模块私有组件 (CourseCard, Player)
|
||||
│ │ │ ├── actions.ts # Server Actions (业务逻辑入口)
|
||||
│ │ │ ├── service.ts # 领域服务 (可选,复杂逻辑拆分)
|
||||
│ │ │ ├── data-access.ts # 数据库查询 (DTOs)
|
||||
│ │ │ └── types.ts # 模块私有类型
|
||||
│ │ │
|
||||
│ │ ├── users/ # 用户模块
|
||||
│ │ ├── payments/ # 支付模块
|
||||
│ │ └── community/ # 社区模块
|
||||
│ │
|
||||
│ ├── shared/ # [共享层] 仅存放真正通用的代码
|
||||
│ │ ├── components/ # 通用 UI (Button, Dialog - Shadcn)
|
||||
│ │ ├── lib/ # 通用工具 (utils, date formatting)
|
||||
│ │ ├── db/ # Drizzle Client & Schema
|
||||
│ │ │ ├── index.ts # DB 连接实例
|
||||
│ │ │ └── schema.ts # 全局 Schema 定义 (或按模块拆分导出)
|
||||
│ │ └── hooks/ # 通用 Hooks
|
||||
│ │
|
||||
│ ├── env.mjs # 环境变量类型检查
|
||||
│ └── middleware.ts # 边缘中间件 (Auth check)
|
||||
├── drizzle.config.ts # Drizzle 配置文件
|
||||
├── next.config.mjs
|
||||
└── package.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 数据库层设计 (Database Strategy)
|
||||
|
||||
### 连接配置 (Connection Pooling)
|
||||
在 Next.js 的 Serverless/Edge 环境中,直接连接 MySQL 可能导致连接数耗尽。我们采取以下策略:
|
||||
|
||||
1. **开发环境**: 使用 Global Singleton 模式防止 Hot Reload 导致连接泄露。
|
||||
2. **生产环境**:
|
||||
* 推荐使用支持 HTTP 连接或内置连接池的 Serverless MySQL 方案 (如 PlanetScale)。
|
||||
* 若使用标准 MySQL,必须配置连接池 (`connectionLimit`) 并合理设置空闲超时。
|
||||
|
||||
**代码示例 (`src/shared/db/index.ts`)**:
|
||||
|
||||
```typescript
|
||||
import { drizzle } from "drizzle-orm/mysql2";
|
||||
import mysql from "mysql2/promise";
|
||||
import * as schema from "./schema";
|
||||
|
||||
// Global cache to prevent connection exhaustion in development
|
||||
const globalForDb = globalThis as unknown as {
|
||||
conn: mysql.Pool | undefined;
|
||||
};
|
||||
|
||||
const poolConnection = globalForDb.conn ?? mysql.createPool({
|
||||
uri: process.env.DATABASE_URL,
|
||||
waitForConnections: true,
|
||||
connectionLimit: 10, // 根据数据库规格调整
|
||||
queueLimit: 0
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV !== "production") globalForDb.conn = poolConnection;
|
||||
|
||||
export const db = drizzle(poolConnection, { schema, mode: "default" });
|
||||
```
|
||||
|
||||
### Migration 策略
|
||||
* 使用 `drizzle-kit` 进行迁移管理。
|
||||
* 严禁在生产环境运行时自动执行 Migration。
|
||||
* **流程**:
|
||||
1. 修改 Schema (`schema.ts`).
|
||||
2. 运行 `pnpm drizzle-kit generate` 生成 SQL 文件。
|
||||
3. Review SQL 文件。
|
||||
4. 在 CI/CD 部署前置步骤或手动运行 `pnpm drizzle-kit migrate`。
|
||||
|
||||
### Server Components 中的数据查询
|
||||
* **Colocation**: 查询逻辑应尽量靠近使用它的组件,或者封装在 `data-access.ts` 中。
|
||||
* **Request Memoization**: 即使在一个请求中多次调用相同的查询函数,Next.js 的 `cache` (或 React `cache`) 也会自动去重。
|
||||
|
||||
```typescript
|
||||
// src/modules/courses/data-access.ts
|
||||
import { cache } from 'react';
|
||||
import { db } from '@/shared/db';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { courses } from '@/shared/db/schema';
|
||||
|
||||
// 使用 React cache 确保单次请求内的去重
|
||||
export const getCourseById = cache(async (id: string) => {
|
||||
return await db.query.courses.findFirst({
|
||||
where: eq(courses.id, id),
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. UI/UX 动效规范 (Animation Guidelines)
|
||||
|
||||
### 核心策略
|
||||
* **CSS Native First**: 90% 的交互通过 CSS `transition` 和 `animation` 实现。
|
||||
* **Hardware Acceleration**: 确保动画属性触发 GPU 加速 (`transform`, `opacity`)。
|
||||
* **Micro-interactions**: 关注 `:hover`, `:active`, `:focus-visible` 状态。
|
||||
|
||||
### 高性能通用组件示例 (Interactive Card)
|
||||
|
||||
这是一个符合规范的卡片组件,使用了 Tailwind 的 `group` 和 `transform` 属性实现丝滑的微交互,且没有 JS 运行时开销。
|
||||
|
||||
```tsx
|
||||
// src/shared/components/ui/interactive-card.tsx
|
||||
import { cn } from "@/shared/lib/utils";
|
||||
|
||||
interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function InteractiveCard({ className, children, ...props }: CardProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"group relative overflow-hidden rounded-xl border border-border bg-card text-card-foreground shadow-sm",
|
||||
// 核心动效:
|
||||
// 1. duration-300 ease-out: 丝滑的时间函数
|
||||
// 2. hover:shadow-md: 悬浮提升感
|
||||
// 3. hover:-translate-y-1: 物理反馈
|
||||
"transition-all duration-300 ease-out hover:-translate-y-1 hover:shadow-md",
|
||||
// 消除 Safari 上的闪烁
|
||||
"transform-gpu backface-hidden",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{/* 光泽效果 (Shimmer Effect) - 仅 CSS */}
|
||||
<div
|
||||
className="absolute inset-0 -translate-x-full bg-gradient-to-r from-transparent via-white/5 to-transparent transition-transform duration-700 group-hover:translate-x-full"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
<div className="relative p-6">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. CI/CD 配置文件模板 (GitHub Actions)
|
||||
|
||||
**警告**: 必须严格遵守 `v3` 版本限制。严禁使用 `v4`。
|
||||
|
||||
文件路径: `.github/workflows/ci.yml`
|
||||
|
||||
```yaml
|
||||
name: CI/CD Pipeline
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main", "develop" ]
|
||||
pull_request:
|
||||
branches: [ "main", "develop" ]
|
||||
|
||||
env:
|
||||
NODE_VERSION: '20.x'
|
||||
|
||||
jobs:
|
||||
quality-check:
|
||||
name: Quality & Type Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# 强制使用 v3
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm' # 或 'pnpm'
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Linting (ESLint)
|
||||
run: npm run lint
|
||||
|
||||
- name: Type Checking (TSC)
|
||||
# 确保没有 TS 错误
|
||||
run: npx tsc --noEmit
|
||||
|
||||
test:
|
||||
name: Unit Tests
|
||||
needs: quality-check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run Tests
|
||||
run: npm run test
|
||||
|
||||
build-check:
|
||||
name: Production Build Check
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Cache Next.js build
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.npm
|
||||
${{ github.workspace }}/.next/cache
|
||||
# Generate a new cache whenever packages or source files change.
|
||||
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
|
||||
|
||||
- name: Build Application
|
||||
run: npm run build
|
||||
env:
|
||||
# 构建时跳过 ESLint/TS 检查 (因为已经在 quality-check job 做过了,加速构建)
|
||||
NEXT_TELEMETRY_DISABLED: 1
|
||||
```
|
||||
|
||||
## 工作记录(2026-01-12)
|
||||
|
||||
### 注册与首次登录引导
|
||||
- 注册流程调整为“仅创建账户并跳转登录”,首次登录后通过全局弹窗分步骤完成资料配置
|
||||
- 全局引导弹窗包含:选择角色 → 通用信息(姓名/电话/住址)→ 角色信息(可跳过,后续在设置中补全)→ 完成
|
||||
- 新增/补齐用户扩展字段与迁移:phone、address、gender、age、gradeId、departmentId、onboardedAt
|
||||
- 新增引导状态与提交接口:`/api/onboarding/status`、`/api/onboarding/complete`
|
||||
|
||||
相关文件:
|
||||
- src/shared/components/onboarding-gate.tsx
|
||||
- src/app/api/onboarding/status/route.ts
|
||||
- src/app/api/onboarding/complete/route.ts
|
||||
- src/shared/db/schema.ts
|
||||
- drizzle/0008_add_user_profile_fields.sql
|
||||
|
||||
### 注册失败排查与错误提示
|
||||
- 注册 server action 增强错误信息(可识别重复邮箱、未迁移、权限错误、连接失败等),开发环境可返回更具体的底层错误消息
|
||||
- 本地排查曾出现 `ECONNREFUSED`,属于数据库连接不可达问题(需检查 MySQL 服务状态与 DATABASE_URL 配置)
|
||||
|
||||
相关文件:
|
||||
- src/app/(auth)/register/page.tsx
|
||||
|
||||
### 顶部头像信息修复
|
||||
- 修复右上角头像/下拉信息写死为 admin 的问题,改为从 NextAuth session 动态读取当前用户 name/email 并生成头像 fallback
|
||||
|
||||
相关文件:
|
||||
- src/modules/layout/components/site-header.tsx
|
||||
@@ -1,112 +0,0 @@
|
||||
# 架构决策记录 (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 关系定义
|
||||
```
|
||||
@@ -1,52 +0,0 @@
|
||||
# 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`
|
||||
@@ -1,438 +0,0 @@
|
||||
# Frontend Engineering Standards (Next_Edu)
|
||||
|
||||
**Status**: ACTIVE
|
||||
**Owner**: Frontend Team
|
||||
**Scope**: Next.js App Router 前端工程规范(编码、目录、交互、样式、数据流、质量门禁)
|
||||
**Applies To**: `src/app/*`, `src/modules/*`, `src/shared/*`, `docs/design/*`
|
||||
|
||||
---
|
||||
|
||||
## 0. 目标与非目标
|
||||
|
||||
### 0.1 目标
|
||||
|
||||
- 让新加入的前端工程师在 30 分钟内完成对齐并开始稳定迭代
|
||||
- 保证 UI 一致性(Design Token + Shadcn/UI 复用)
|
||||
- 充分利用 App Router + RSC(Server-First)降低 bundle、提升性能
|
||||
- 保证类型安全与可维护性(Vertical Slice、数据访问边界清晰)
|
||||
- 形成可执行的质量门禁(lint/typecheck/build 与评审清单)
|
||||
|
||||
### 0.2 非目标
|
||||
|
||||
- 不规定具体业务模块的需求细节(业务规则以 `docs/design/*` 与 PRD 为准)
|
||||
- 不引入与当前仓库技术栈不一致的新框架/库(新增依赖需明确收益与替代方案)
|
||||
|
||||
---
|
||||
|
||||
## 1. 接手流程(Onboarding Checklist)
|
||||
|
||||
### 1.1 先读什么(按顺序)
|
||||
|
||||
- 设计系统与 UI 规范:[docs/design/design_system.md](file:///c:/Users/xiner/Desktop/CICD/docs/design/design_system.md)
|
||||
- 角色路由与目录规范:[docs/architecture/002_role_based_routing.md](file:///c:/Users/xiner/Desktop/CICD/docs/architecture/002_role_based_routing.md)
|
||||
- 项目架构总览:[ARCHITECTURE.md](file:///c:/Users/xiner/Desktop/CICD/ARCHITECTURE.md)
|
||||
- 你将要改动的模块实现文档:`docs/design/00*_*.md`
|
||||
|
||||
### 1.2 开发前对齐(必须)
|
||||
|
||||
- 核对 Design Tokens 与暗色模式变量:
|
||||
- Tailwind 语义色映射:[tailwind.config.ts](file:///c:/Users/xiner/Desktop/CICD/tailwind.config.ts)
|
||||
- CSS 变量定义:[src/app/globals.css](file:///c:/Users/xiner/Desktop/CICD/src/app/globals.css)
|
||||
- 盘点可复用 UI 组件:`src/shared/components/ui/*`
|
||||
- 盘点通用工具(`cn` 等):[src/shared/lib/utils.ts](file:///c:/Users/xiner/Desktop/CICD/src/shared/lib/utils.ts)
|
||||
|
||||
### 1.3 环境变量与配置校验(必须)
|
||||
|
||||
- 统一使用 `@t3-oss/env-nextjs` 的 `env` 入口读取环境变量,禁止在业务代码中散落 `process.env.*`
|
||||
- Schema 定义与校验入口:[src/env.mjs](file:///c:/Users/xiner/Desktop/CICD/src/env.mjs)
|
||||
- 任何新增环境变量:
|
||||
- 必须先在 `src/env.mjs` 增加 schema
|
||||
- 必须在 docs 中更新部署/运行说明(就近更新对应模块文档或全局架构文档)
|
||||
|
||||
### 1.3 本地跑通(推荐顺序)
|
||||
|
||||
- 安装依赖:`npm install`
|
||||
- 启动开发:`npm run dev`
|
||||
- 质量检查:
|
||||
- `npm run lint`
|
||||
- `npm run typecheck`
|
||||
- `npm run build`
|
||||
|
||||
---
|
||||
|
||||
## 2. 核心工程原则(必须遵守)
|
||||
|
||||
### 2.1 Vertical Slice(按业务功能组织)
|
||||
|
||||
- 业务必须放在 `src/modules/<feature>/*`
|
||||
- `src/app/*` 是路由层,只负责:
|
||||
- 布局组合(layout)
|
||||
- 读取 `searchParams` / `params`
|
||||
- 调用模块的数据访问函数(`data-access.ts`)
|
||||
- 组合模块组件渲染
|
||||
- 通用能力放在 `src/shared/*`:
|
||||
- 通用 UI:`src/shared/components/ui/*`
|
||||
- 通用工具:`src/shared/lib/*`
|
||||
- DB 与 schema:`src/shared/db/*`
|
||||
|
||||
### 2.2 Server-First(默认 Server Component)
|
||||
|
||||
- 默认写 Server Component
|
||||
- 只有在需要以下能力时,才把“最小子组件”标记为 Client Component:
|
||||
- `useState/useEffect/useMemo`(与交互/浏览器相关)
|
||||
- DOM 事件(`onClick/onChange` 等)
|
||||
- `useRouter/usePathname` 等客户端导航 hooks
|
||||
- Radix/Portal 类组件需要客户端(Dialog/Dropdown 等通常在 client 内组合使用)
|
||||
|
||||
### 2.3 不重复造轮子(Shadcn/UI 优先)
|
||||
|
||||
- 禁止手写 Modal/Dropdown/Tooltip 等基础交互容器
|
||||
- 优先组合 `src/shared/components/ui/*`(Button/Card/Dialog/DropdownMenu/AlertDialog/Skeleton/EmptyState 等)
|
||||
- 若现有基础组件无法满足需求:
|
||||
1. 优先通过 Composition 在业务模块里封装“业务组件”
|
||||
2. 仅在存在 bug 或需要全局一致性调整时,才考虑改动 `src/shared/components/ui/*`(并在 PR 中明确影响面)
|
||||
- **图表库**:统一使用 `Recharts`,禁止引入其他图表库(Chart.js / ECharts 等)。
|
||||
- 使用 `src/shared/components/ui/chart.tsx` 进行封装。
|
||||
- 遵循 Shadcn/UI Chart 规范。
|
||||
|
||||
### 2.4 Client Component 引用边界(强制)
|
||||
|
||||
- 禁止在 Client Component 中导入任何“服务端实现”代码(例如 DB 实例、data-access、server-only 模块)
|
||||
- Client Component 允许导入:
|
||||
- `src/shared/components/ui/*`(基础 UI)
|
||||
- `src/shared/lib/*`(纯前端工具函数)
|
||||
- Server Actions(`"use server"` 导出的 action 函数)
|
||||
- 类型定义必须使用 `import type`(避免把服务端依赖带入 client bundle)
|
||||
- 所有 `data-access.ts` 必须包含 `import "server-only"`,并将其视为强制安全边界(不是可选优化)
|
||||
|
||||
---
|
||||
|
||||
## 3. 目录与路由规范
|
||||
|
||||
### 3.1 路由目录(App Router)
|
||||
|
||||
- 认证域:`src/app/(auth)/*`
|
||||
- 控制台域(共享 App Shell):`src/app/(dashboard)/*`
|
||||
- 角色域:`src/app/(dashboard)/teacher|student|admin/*`
|
||||
- `/dashboard` 作为入口页(重定向/分发到具体角色 dashboard):参考 [002_role_based_routing.md](file:///c:/Users/xiner/Desktop/CICD/docs/architecture/002_role_based_routing.md)
|
||||
|
||||
### 3.2 页面文件职责
|
||||
|
||||
- `page.tsx`:页面组装(RSC),不承载复杂交互
|
||||
- `loading.tsx`:路由级加载态(Skeleton)
|
||||
- `error.tsx`:路由级错误边界(友好 UI)
|
||||
- `not-found.tsx`:路由级 404
|
||||
|
||||
### 3.3 错误处理与用户反馈(Error Handling & Feedback)
|
||||
|
||||
- 路由级错误(404/500/未捕获异常):
|
||||
- 交由 `not-found.tsx` / `error.tsx` 处理
|
||||
- 禁止在 `error.tsx` 里弹 Toast 堆栈刷屏,错误页输出必须友好且可恢复(例如提供 retry / 返回入口)
|
||||
- 业务操作反馈(表单提交/按钮操作/行级动作):
|
||||
- 统一由 Client Component 在调用 Server Action 后触发 `sonner` toast
|
||||
- 只在“成功”或“明确失败(业务/校验错误)”时触发 toast;未知异常由 action 归一为失败 message
|
||||
|
||||
### 3.4 异步组件与 Suspense(Streaming)
|
||||
|
||||
- 对于数据加载超过 300ms 的非核心 UI 区块(例如:仪表盘某张统计卡片/图表/第三方数据块):
|
||||
- 必须用 `<Suspense fallback={<Skeleton />}>` 包裹,以避免全页阻塞
|
||||
- 禁止在 `page.tsx` 顶层用多个串行 `await` 造成瀑布请求:
|
||||
- 多个独立请求必须使用 `Promise.all`
|
||||
- 或拆分为多个 async 子组件并行流式渲染(用 `Suspense` 分段展示)
|
||||
|
||||
### 3.3 动态渲染策略(避免 build 阶段查库)
|
||||
|
||||
当页面在渲染时会查询数据库或依赖 request-time 数据,且无法安全静态化时:
|
||||
|
||||
- 在页面入口显式声明:
|
||||
- `export const dynamic = "force-dynamic"`
|
||||
- 该策略已用于教师端班级与作业相关页面,见相应 design 文档(例如教师班级模块更新记录)
|
||||
|
||||
---
|
||||
|
||||
## 4. 模块内文件结构(强制)
|
||||
|
||||
每个业务模块使用统一结构(可按复杂度增减,但命名必须一致):
|
||||
|
||||
```
|
||||
src/modules/<feature>/
|
||||
├── components/ # 仅该模块使用的 UI 组件(可含 client 组件)
|
||||
├── actions.ts # Server Actions(写入/变更 + revalidatePath)
|
||||
├── data-access.ts # 数据查询与聚合(server-only + cache)
|
||||
├── schema.ts # Zod schema(若需要)
|
||||
└── types.ts # 类型定义(与 DB/DTO 对齐)
|
||||
```
|
||||
|
||||
约束:
|
||||
|
||||
- `actions.ts` 必须包含 `"use server"`
|
||||
- `data-access.ts` 必须包含 `import "server-only"`(防止误导入到 client bundle)
|
||||
- 复杂页面组件必须下沉到 `src/modules/<feature>/components/*`,路由层只做组装
|
||||
|
||||
---
|
||||
|
||||
## 5. Server / Client 边界与拆分策略
|
||||
|
||||
### 5.1 最小化 Client Component 的落地方式
|
||||
|
||||
- 页面保持 RSC
|
||||
- 把需要交互的部分抽成独立 `components/*` 子组件并标记 `"use client"`
|
||||
- Client 组件向上暴露“数据变化事件”,由 Server Action 完成写入并 `revalidatePath`
|
||||
|
||||
### 5.4 Hydration 一致性(必须)
|
||||
|
||||
- 所有 Client Component 的首屏渲染必须保证与 SSR 产出的 HTML 一致
|
||||
- 禁止在 render 分支中使用:
|
||||
- `typeof window !== "undefined"` 之类的 server/client 分支
|
||||
- `Date.now()` / `Math.random()` 等不稳定输入
|
||||
- 依赖用户 locale 的时间格式化(除非服务端与客户端完全一致并带 snapshot)
|
||||
- 对于 Radix 等组件生成的动态 aria/id 导致的属性差异:
|
||||
- 优先通过组件封装确保首屏稳定
|
||||
- 若确认差异不可避免且不影响交互,可在最小范围使用 `suppressHydrationWarning`
|
||||
|
||||
### 5.2 页面必须只做“拼装”,功能模块必须独立
|
||||
|
||||
- 任何功能模块都必须在 `src/modules/<feature>/components/*` 内独立实现
|
||||
- `page.tsx` 只负责:
|
||||
- 读取 `params/searchParams`
|
||||
- 调用 `data-access.ts` 获取数据
|
||||
- 以组合方式拼装模块组件(不在 page 内实现具体交互与复杂 UI)
|
||||
- 行数不是拆分依据,只是“路由层变厚”的信号;一旦出现成块的功能 UI,应立即下沉到模块组件
|
||||
|
||||
### 5.3 什么时候允许在 Client 中做“局部工作台”
|
||||
|
||||
当交互复杂到“页面需要类似 SPA 的局部体验”,允许将工作台容器作为 Client:
|
||||
|
||||
- 典型场景:三栏工作台、拖拽排序编辑器、复杂筛选器组合、富交互表格
|
||||
- 但仍要求:
|
||||
- 初始数据由 RSC 获取并传入 Client
|
||||
- 写操作通过 Server Actions
|
||||
- UI 状态尽量 URL 化(能分享/回溯)
|
||||
|
||||
---
|
||||
|
||||
## 6. 样式与 UI 一致性(Design System 强制项)
|
||||
|
||||
### 6.1 Token 优先(语义化颜色/圆角)
|
||||
|
||||
- 颜色必须使用语义 token:
|
||||
- `bg-background`, `bg-card`, `bg-muted`, `text-foreground`, `text-muted-foreground`, `border-border` 等
|
||||
- 禁止硬编码颜色值(`#fff`/`rgb()`)与随意引入灰度(如 `bg-gray-100`)
|
||||
- 圆角、边框、阴影遵循设计系统:
|
||||
- 常规组件使用 `rounded-md` 等语义半径(由 `--radius` 映射)
|
||||
|
||||
### 6.2 className 规范
|
||||
|
||||
- 所有条件样式必须使用 `className={cn(...)}`
|
||||
- `cn` 入口为 `@/shared/lib/utils`
|
||||
|
||||
### 6.3 禁止 Arbitrary Values(默认)
|
||||
|
||||
- 默认禁止 `w-[123px]` 等任意值
|
||||
- 只有在设计系统或现有实现明确允许、并且无法用 token/栅格解决时,才可使用,并在 PR 描述说明原因
|
||||
|
||||
### 6.4 微交互与状态(必须有)
|
||||
|
||||
- 按钮 hover:必须有 transition(现有 Button 组件已内置)
|
||||
- 列表项 hover:使用 `hover:bg-muted/50` 等轻量反馈
|
||||
- Loading:必须使用 `Skeleton`(路由级 `loading.tsx` 或组件内 skeleton)
|
||||
- Empty:必须使用 `EmptyState`
|
||||
- Toast:统一使用 `sonner`
|
||||
|
||||
---
|
||||
|
||||
## 7. 图标规范(lucide-react)
|
||||
|
||||
- 统一使用 `lucide-react`
|
||||
- 图标尺寸统一:默认 `h-4 w-4`,需要强调时 `h-5 w-5`
|
||||
- 颜色使用语义化:例如 `text-muted-foreground`
|
||||
|
||||
---
|
||||
|
||||
## 8. 数据流规范(查询、写入、状态)
|
||||
|
||||
### 8.1 查询(data-access.ts)
|
||||
|
||||
- 所有查询放在 `src/modules/<feature>/data-access.ts`
|
||||
- 需要复用/去重的查询优先用 `cache` 包裹(React cache)
|
||||
- 查询函数返回“UI 直接可消费的 DTO”,避免页面层再做复杂映射
|
||||
|
||||
### 8.2 写入(actions.ts)
|
||||
|
||||
- 所有写操作必须通过 Server Actions
|
||||
- 每个 action:
|
||||
- 校验输入(Zod 或手写 guard)
|
||||
- 执行 DB 写入
|
||||
- 必须 `revalidatePath`(以页面为单位)
|
||||
|
||||
### 8.3 Server Action 返回结构(统一反馈协议)
|
||||
|
||||
- 所有 Server Action 必须返回统一结构,用于前端统一处理 toast 与表单错误
|
||||
- 统一使用类型:[src/shared/types/action-state.ts](file:///c:/Users/xiner/Desktop/CICD/src/shared/types/action-state.ts)
|
||||
|
||||
```ts
|
||||
export type ActionState<T = void> = {
|
||||
success: boolean
|
||||
message?: string
|
||||
errors?: Record<string, string[]>
|
||||
data?: T
|
||||
}
|
||||
```
|
||||
|
||||
约束:
|
||||
|
||||
- `errors` 必须对齐 `zod` 的 `error.flatten().fieldErrors` 结构
|
||||
- 禁止在各模块内重复定义自有的 ActionState 类型
|
||||
|
||||
### 8.4 Toast 触发时机(强制)
|
||||
|
||||
- Client Component 在调用 Server Action 后:
|
||||
- `success: true`:触发 `toast.success(message)`(或使用模块内约定的成功文案)
|
||||
- `success: false`:
|
||||
- 存在 `errors`:优先渲染表单字段错误;可选触发 `toast.error(message)`
|
||||
- 不存在 `errors`:触发 `toast.error(message || "Action failed")`
|
||||
- 对于路由级异常与边界错误,禁止用 toast 替代 `error.tsx`
|
||||
|
||||
### 8.5 URL State(nuqs 优先)
|
||||
|
||||
- 列表页筛选/分页/Tab/排序等“可分享状态”必须放 URL
|
||||
- 使用 `nuqs` 做类型安全的 query state 管理
|
||||
|
||||
### 8.6 Data Access 权限边界(Security / IDOR 防护)
|
||||
|
||||
- `data-access.ts` 不是纯 DTO 映射层,必须承担数据归属权校验
|
||||
- 允许两种合规方式(二选一,但模块内必须统一):
|
||||
- **方式 A(强制传参)**:所有 data-access 函数显式接收 `actor`(userId/role)并在查询条件中约束归属(例如 teacherId)
|
||||
- **方式 B(函数内获取)**:data-access 函数首行获取 session/user 并校验 role/归属,再执行查询
|
||||
- 禁止把权限校验放在 page.tsx 或 client 组件中作为唯一屏障
|
||||
|
||||
---
|
||||
|
||||
## 9. 数据完整性与 Seed 规则(禁止 Mock)
|
||||
|
||||
项目默认不使用 Mock 数据。
|
||||
|
||||
当某功能缺失实际数据,开发者必须把数据补齐到数据库与种子数据中,而不是在前端临时模拟。
|
||||
|
||||
执行规范:
|
||||
|
||||
- 若缺失的是“表结构/字段/关系”:
|
||||
- 修改 `src/shared/db/schema.ts` 与 `src/shared/db/relations.ts`(按既有模式)
|
||||
- 生成并提交 Drizzle migration(`drizzle/*.sql`)
|
||||
- 若缺失的是“可演示的业务数据”:
|
||||
- 更新 `scripts/seed.ts`,确保 `npm run db:seed` 可一键生成可用数据
|
||||
- 文档同步(必须):
|
||||
- 在 [schema-changelog.md](file:///c:/Users/xiner/Desktop/CICD/docs/db/schema-changelog.md) 记录本次新增/变更的数据表、字段、索引与外键
|
||||
- 在对应模块的 `docs/design/00*_*.md` 中补充“新增了哪些数据/为什么需要/如何验证(db:seed + 页面路径)”
|
||||
|
||||
### 9.1 Seed 分层(降低阻塞)
|
||||
|
||||
- Seed 分为两类:
|
||||
- **Baseline Seed**:全项目必备的最小集合(核心用户/角色/基础字典数据等),保证任何页面都不因“数据空”而无法进入流程
|
||||
- **Scenario Seed(按模块)**:面向具体模块的可演示数据包(例如:班级/题库/试卷/作业),用于复现与验证该模块交互
|
||||
- 任何模块新增数据依赖,必须以 “Scenario Seed” 的形式落到 `scripts/seed.ts`,而不是把数据要求隐含在前端逻辑里
|
||||
|
||||
### 9.2 Seed 可复现与数据锚点(保证跨模块联动)
|
||||
|
||||
- Seed 必须可重复执行(idempotent),避免开发环境多次执行后产生脏数据与重复数据
|
||||
- 对跨模块联动依赖的关键实体,必须提供可稳定引用的数据锚点:
|
||||
- 固定标识(如固定 email/slug/title 组合)或可预测 ID(按现有 seed 约定)
|
||||
- 文档必须写明锚点是什么、依赖它的模块有哪些、如何验证
|
||||
- 禁止在 UI 里依赖“随机生成数据顺序”来定位实体(例如 “取第一条记录作为 demo 用户” 这类逻辑应退化为明确锚点)
|
||||
|
||||
### 9.3 外部服务的例外(仅限 Adapter Mock)
|
||||
|
||||
- 内部业务数据严格遵守“DB + Migration + Seed”,不允许 Mock
|
||||
- 仅当对接外部不可控服务(支付/短信/第三方 AI 流式等)且无法用本地 seed 复现时:
|
||||
- 允许在 `src/shared/lib/mock-adapters/*` 建立 mock 适配器
|
||||
- 必须先定义 Adapter 接口,再提供真实实现与 mock 实现(业务模块只能依赖接口,不可直接依赖某个具体实现)
|
||||
- 该 mock 仅用于外部服务交互层,禁止承载内部业务数据
|
||||
|
||||
---
|
||||
|
||||
## 10. 表单规范(react-hook-form + zod)
|
||||
|
||||
- 表单统一使用 `react-hook-form` + `@hookform/resolvers` + `zod`
|
||||
- 错误提示放在输入框下方:
|
||||
- 字号 `text-xs`
|
||||
- 颜色 `text-destructive`
|
||||
- 破坏性操作必须二次确认(`AlertDialog`)
|
||||
- 提交中按钮禁用并展示 loading(可使用 `useFormStatus` 或本地 state)
|
||||
|
||||
---
|
||||
|
||||
## 11. 质量门禁与评审清单(PR 必须过)
|
||||
|
||||
### 11.1 本地必须通过
|
||||
|
||||
- `npm run lint`
|
||||
- `npm run typecheck`
|
||||
- `npm run build`
|
||||
|
||||
### 11.2 代码评审清单(Reviewer 逐项检查)
|
||||
|
||||
- 目录结构是否符合 Vertical Slice(路由层是否保持“薄”)
|
||||
- 页面是否只做拼装(功能 UI 是否全部下沉到模块组件)
|
||||
- Server/Client 边界是否最小化(是否把整页误标 client)
|
||||
- 是否复用 `src/shared/components/ui/*`,是否重复实现基础交互
|
||||
- 是否使用语义化 token(颜色/圆角/间距),是否引入硬编码颜色与大量 arbitrary values
|
||||
- Loading/Empty/Error 是否齐全(Skeleton/EmptyState/error.tsx)
|
||||
- 列表页筛选是否 URL 化(nuqs),是否支持刷新/分享
|
||||
- 写操作是否通过 Server Action 且正确 `revalidatePath`
|
||||
- 是否避免 Mock(数据是否通过迁移 + seed 补齐,且 docs/db 与模块文档已同步)
|
||||
- 是否引入不必要的依赖与重型客户端逻辑
|
||||
|
||||
### 11.3 Commit 规范(Git History)
|
||||
|
||||
- 推荐遵循 Conventional Commits:
|
||||
- `feat:` 新功能
|
||||
- `fix:` 修复 bug
|
||||
- `docs:` 文档更新
|
||||
- `refactor:` 重构(无功能变化)
|
||||
- `chore:` 工程杂项
|
||||
- 约束:
|
||||
- 单次提交必须聚焦一个意图,避免把大范围格式化与功能修改混在一起
|
||||
- 涉及 DB 迁移与 seed 变更时,commit message 必须包含模块/领域关键词,便于追溯
|
||||
|
||||
---
|
||||
|
||||
## 12. 文档同步规则(Docs Sync)
|
||||
|
||||
以下情况必须同步更新文档(就近放在 `docs/design/*` 或 `docs/architecture/*`):
|
||||
|
||||
- 新增“全局交互模式”(例如:新的工作台/拖拽范式/跨模块复用交互)
|
||||
- 新增“全局组件”或改变基础 UI 行为(影响 `src/shared/components/ui/*`)
|
||||
- 新增关键路由结构或权限/角色策略
|
||||
|
||||
### 12.1 业务组件可发现性(可选但推荐)
|
||||
|
||||
- 对 `src/modules/<feature>/components` 内的复杂业务组件(例如:试卷编辑器、排课表、工作台):
|
||||
- 推荐在对应的 `docs/design/00*_*.md` 增加“用法示例 + 关键 props + 截图”
|
||||
- 若团队资源允许,可引入 Storybook 作为可视化组件目录(不作为硬性门禁)
|
||||
|
||||
---
|
||||
|
||||
## 13. Performance Essentials(必须遵守)
|
||||
|
||||
- 图片:
|
||||
- 强制使用 `next/image` 替代 `<img>`(SVG 或已明确无需优化的极小图标除外)
|
||||
- 头像等外部域名资源必须配置并明确缓存策略
|
||||
- 字体:
|
||||
- 强制使用 `next/font` 管理字体加载
|
||||
- 禁止在 CSS 中 `@import` 外部字体 URL(避免 CLS 与阻塞渲染)
|
||||
- 依赖:
|
||||
- 禁止引入重型动画库作为默认方案;复杂动效需按需加载并解释收益
|
||||
- **图表**:标准图表库统一使用 `recharts`(通过 `src/shared/components/ui/chart.tsx` 封装),禁止引入其他图表库(如 Chart.js / Highcharts)。
|
||||
- 大体积 Client 组件必须拆分与动态加载,并通过 `Suspense` 提供 skeleton fallback
|
||||
|
||||
---
|
||||
|
||||
## 14. 参考实现(从现有代码学习的路径)
|
||||
|
||||
- 设计系统与 UI 组件清单:[docs/design/design_system.md](file:///c:/Users/xiner/Desktop/CICD/docs/design/design_system.md)
|
||||
- Auth:路由层 RSC + 表单 client 拆分模式:[docs/design/001_auth_ui_implementation.md](file:///c:/Users/xiner/Desktop/CICD/docs/design/001_auth_ui_implementation.md)
|
||||
- 教师端班级模块:URL state + client 交互组件 + server actions 的组合:[docs/design/002_teacher_dashboard_implementation.md](file:///c:/Users/xiner/Desktop/CICD/docs/design/002_teacher_dashboard_implementation.md)
|
||||
- 教材工作台:RSC 拉初始数据 + client 工作台容器接管交互:[docs/design/003_textbooks_module_implementation.md](file:///c:/Users/xiner/Desktop/CICD/docs/design/003_textbooks_module_implementation.md)
|
||||
- 题库:nuqs 驱动筛选 + TanStack Table + CRUD actions:[docs/design/004_question_bank_implementation.md](file:///c:/Users/xiner/Desktop/CICD/docs/design/004_question_bank_implementation.md)
|
||||
- 考试组卷:拖拽编辑器(@dnd-kit)+ structure JSON 模型:[docs/design/005_exam_module_implementation.md](file:///c:/Users/xiner/Desktop/CICD/docs/design/005_exam_module_implementation.md)
|
||||
- 作业:冻结 exam → assignment 的域模型 + 学生作答/教师批改闭环:[docs/design/006_homework_module_implementation.md](file:///c:/Users/xiner/Desktop/CICD/docs/design/006_homework_module_implementation.md)
|
||||
@@ -1,81 +0,0 @@
|
||||
# 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 }]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### 2.5 Classes / Enrollment / Schedule
|
||||
Seeds the teacher class management domain.
|
||||
* **Classes**: Creates at least one class owned by a teacher user.
|
||||
* **Enrollments**: Links students to classes via `class_enrollments` (default status: `active`).
|
||||
* **Schedule**: Populates `class_schedule` with weekday + start/end times for timetable validation.
|
||||
|
||||
## 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**.
|
||||
@@ -1,76 +0,0 @@
|
||||
# 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`).
|
||||
@@ -1,164 +0,0 @@
|
||||
# 学校基础数据模块(School)实现文档与更新记录
|
||||
|
||||
**日期**: 2026-01-07
|
||||
**作者**: Frontend Team
|
||||
**状态**: 已实现
|
||||
|
||||
## 1. 范围
|
||||
|
||||
本文档覆盖管理端「School」域的基础数据维护页面(Schools / Departments / Academic Year / Grades),并记录相关实现约束与关键更新,遵循 [003_frontend_engineering_standards.md](file:///c:/Users/xiner/Desktop/CICD/docs/architecture/003_frontend_engineering_standards.md) 的工程规范(Vertical Slice、Server/Client 边界、质量门禁)。
|
||||
|
||||
## 2. 路由入口(Admin)
|
||||
|
||||
School 域路由位于 `src/app/(dashboard)/admin/school/*`,均显式声明 `export const dynamic = "force-dynamic"` 以避免构建期预渲染触发数据库访问。
|
||||
|
||||
- `/admin/school`:入口重定向到 Classes(当前落点不在 `school` 模块内)
|
||||
实现:[page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/admin/school/page.tsx)
|
||||
- `/admin/school/schools`:学校维护(增删改)
|
||||
实现:[page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/admin/school/schools/page.tsx)
|
||||
- `/admin/school/departments`:部门维护(增删改)
|
||||
实现:[page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/admin/school/departments/page.tsx)
|
||||
- `/admin/school/academic-year`:学年维护(增删改 + 设为当前学年)
|
||||
实现:[page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/admin/school/academic-year/page.tsx)
|
||||
- `/admin/school/grades`:年级维护(增删改 + 指派年级组长/教研组长)
|
||||
实现:[page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/admin/school/grades/page.tsx)
|
||||
- `/admin/school/grades/insights`:年级维度作业统计(跨班级聚合)
|
||||
实现:[page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/admin/school/grades/insights/page.tsx)
|
||||
|
||||
## 3. 模块结构(Vertical Slice)
|
||||
|
||||
School 模块位于 `src/modules/school`:
|
||||
|
||||
```
|
||||
src/modules/school/
|
||||
├── components/
|
||||
│ ├── schools-view.tsx
|
||||
│ ├── departments-view.tsx
|
||||
│ ├── academic-year-view.tsx
|
||||
│ └── grades-view.tsx
|
||||
├── actions.ts
|
||||
├── data-access.ts
|
||||
├── schema.ts
|
||||
└── types.ts
|
||||
```
|
||||
|
||||
边界约束:
|
||||
- `data-access.ts` 包含 `import "server-only"`,仅用于服务端查询与 DTO 组装。
|
||||
- `actions.ts` 包含 `"use server"`,写操作统一通过 Server Actions 并 `revalidatePath`。
|
||||
- `components/*` 为 Client 交互层(表单、Dialog、筛选、行级操作),调用 Server Actions 并用 `sonner` toast 反馈。
|
||||
|
||||
## 4. 数据访问(data-access.ts)
|
||||
|
||||
实现:[data-access.ts](file:///c:/Users/xiner/Desktop/CICD/src/modules/school/data-access.ts)
|
||||
|
||||
- `getSchools(): Promise<SchoolListItem[]>`
|
||||
- `getDepartments(): Promise<DepartmentListItem[]>`
|
||||
- `getAcademicYears(): Promise<AcademicYearListItem[]>`
|
||||
- `getGrades(): Promise<GradeListItem[]>`
|
||||
- join `schools` 获取 `school.name`
|
||||
- 收集 `gradeHeadId/teachingHeadId` 并批量查询 `users` 以组装 `StaffOption`
|
||||
- `getStaffOptions(): Promise<StaffOption[]>`
|
||||
- 角色过滤 `teacher/admin`
|
||||
- 排序 `name/email`,用于 Select 列表可用性
|
||||
- `getGradesForStaff(staffId: string): Promise<GradeListItem[]>`
|
||||
- 用于按负责人(年级组长/教研组长)反查关联年级
|
||||
|
||||
返回 DTO 类型定义位于:[types.ts](file:///c:/Users/xiner/Desktop/CICD/src/modules/school/types.ts)
|
||||
|
||||
## 5. 写操作(actions.ts)
|
||||
|
||||
实现:[actions.ts](file:///c:/Users/xiner/Desktop/CICD/src/modules/school/actions.ts)
|
||||
|
||||
通用约束:
|
||||
- 输入校验:统一使用 [schema.ts](file:///c:/Users/xiner/Desktop/CICD/src/modules/school/schema.ts) 的 Zod schema
|
||||
- 返回结构:统一使用 [ActionState](file:///c:/Users/xiner/Desktop/CICD/src/shared/types/action-state.ts)
|
||||
- 刷新策略:对目标页面路径执行 `revalidatePath`
|
||||
|
||||
Departments:
|
||||
- `createDepartmentAction(formData)`
|
||||
- `updateDepartmentAction(departmentId, formData)`
|
||||
- `deleteDepartmentAction(departmentId)`
|
||||
|
||||
Academic Year:
|
||||
- `createAcademicYearAction(formData)`
|
||||
- `updateAcademicYearAction(academicYearId, formData)`
|
||||
- `deleteAcademicYearAction(academicYearId)`
|
||||
- 当 `isActive=true` 时,通过事务把其它学年置为非激活,保证唯一激活学年
|
||||
|
||||
Schools:
|
||||
- `createSchoolAction(formData)`
|
||||
- `updateSchoolAction(schoolId, formData)`
|
||||
- `deleteSchoolAction(schoolId)`
|
||||
- 删除后会同时刷新 `/admin/school/schools` 与 `/admin/school/grades`
|
||||
|
||||
Grades:
|
||||
- `createGradeAction(formData)`
|
||||
- `updateGradeAction(gradeId, formData)`
|
||||
- `deleteGradeAction(gradeId)`
|
||||
|
||||
## 6. UI 组件(components/*)
|
||||
|
||||
Schools:
|
||||
- 实现:[schools-view.tsx](file:///c:/Users/xiner/Desktop/CICD/src/modules/school/components/schools-view.tsx)
|
||||
- 交互:列表 + Dialog 表单(新增/编辑)+ 删除确认(AlertDialog)
|
||||
|
||||
Departments:
|
||||
- 实现:[departments-view.tsx](file:///c:/Users/xiner/Desktop/CICD/src/modules/school/components/departments-view.tsx)
|
||||
- 交互:列表 + Dialog 表单(新增/编辑)+ 删除确认(AlertDialog)
|
||||
|
||||
Academic Year:
|
||||
- 实现:[academic-year-view.tsx](file:///c:/Users/xiner/Desktop/CICD/src/modules/school/components/academic-year-view.tsx)
|
||||
- 交互:列表 + Dialog 表单(新增/编辑)+ 删除确认(AlertDialog)+ 设为当前学年(isActive)
|
||||
|
||||
Grades:
|
||||
- 实现:[grades-view.tsx](file:///c:/Users/xiner/Desktop/CICD/src/modules/school/components/grades-view.tsx)
|
||||
- 交互:列表展示 + URL 驱动筛选(搜索/学校/负责人/排序)+ Dialog 表单(新增/编辑)+ 删除确认(AlertDialog)
|
||||
- 负责人指派:
|
||||
- 年级组长(gradeHeadId)
|
||||
- 教研组长(teachingHeadId)
|
||||
|
||||
## 7. 关键交互与规则(Grades)
|
||||
|
||||
页面入口(RSC 组装)在服务端并发拉取三类数据:
|
||||
- 年级列表:`getGrades()`
|
||||
- 学校选项:`getSchools()`
|
||||
- 负责人候选:`getStaffOptions()`
|
||||
|
||||
实现:[page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/admin/school/grades/page.tsx)
|
||||
|
||||
### 7.1 URL State(筛选/排序)
|
||||
|
||||
Grades 列表页的筛选状态 URL 化(`nuqs`):
|
||||
- `q`:关键字(匹配 grade/school)
|
||||
- `school`:学校过滤(`all` 或具体 schoolId)
|
||||
- `head`:负责人过滤(全部 / 两者缺失 / 缺年级组长 / 缺教研组长)
|
||||
- `sort`:排序(默认/名称/更新时间等)
|
||||
|
||||
实现:[grades-view.tsx](file:///c:/Users/xiner/Desktop/CICD/src/modules/school/components/grades-view.tsx)
|
||||
|
||||
### 7.2 表单校验
|
||||
|
||||
Grades 的新增/编辑表单在客户端做轻量校验:
|
||||
- 必填:`schoolId`、`name`
|
||||
- `order`:非负整数
|
||||
- 去重:同一学校下年级名称不允许重复(忽略大小写 + 规范化空格)
|
||||
|
||||
说明:
|
||||
- 服务端写入前仍会经过 `UpsertGradeSchema` 校验(schema.ts),避免仅依赖客户端校验。
|
||||
|
||||
### 7.3 负责人选择(Radix Select)
|
||||
|
||||
Radix Select 约束:`SelectItem` 的 `value` 不能为 `""`(空字符串),否则会触发运行时错误。
|
||||
|
||||
当前实现策略:
|
||||
- UI 中 “未设置” 选项使用占位值 `__none__`
|
||||
- 在 `onValueChange` 中将 `__none__` 映射回 `""` 存入本地表单 state
|
||||
- 提交时依旧传递空字符串,由 `UpsertGradeSchema` 将其归一为 `null`
|
||||
|
||||
实现:[grades-view.tsx](file:///c:/Users/xiner/Desktop/CICD/src/modules/school/components/grades-view.tsx)
|
||||
|
||||
## 2. 更新记录(2026-01-07)
|
||||
|
||||
- 修复 Add Grades 弹窗报错:将 4 处 `<SelectItem value=\"\">` 替换为占位值 `__none__`,并在 `onValueChange` 中映射回 `\"\"`,保持“可清空选择/显示 placeholder”的行为不变。
|
||||
- 修复新建年级按钮不可用:创建/编辑表单在状态变化时触发实时校验更新,避免校验状态滞后导致提交被禁用。
|
||||
- 质量门禁:本地通过 `npm run lint` 与 `npm run typecheck`。
|
||||
@@ -242,6 +242,35 @@
|
||||
|
||||
- **目的**: 创建考试基础信息。
|
||||
- **关键组件**: `ExamForm`
|
||||
- **AI 生成执行逻辑**:
|
||||
- **入口**:
|
||||
- 选择 `Assembly Mode` 为 `AI Generation`
|
||||
- 表单增加 `aiQuestionCount` 与 `aiPrompt`,提交时走 `createAiExamAction`
|
||||
- **请求数据组装**:
|
||||
- 复用基础字段:`title`、`subject`、`grade`、`difficulty`、`totalScore`、`durationMin`、`scheduledAt`
|
||||
- 可选字段:`aiQuestionCount`、`aiPrompt`
|
||||
- **服务端校验**:
|
||||
- `AiExamCreateSchema` 校验基础字段与 AI 字段
|
||||
- 解析失败直接返回 `Invalid form data`
|
||||
- **AI 调用**:
|
||||
- 通过 `createAiChatCompletion` 发送系统提示与用户输入
|
||||
- 使用 `env.AI_MODEL`,默认 `gpt-4o-mini`
|
||||
- 期望输出 JSON,仅包含 `sections` 或 `questions`
|
||||
- **响应解析**:
|
||||
- `extractJson` 支持从纯 JSON 或代码块中提取
|
||||
- `AiExamResponseSchema` 校验题型与字段结构
|
||||
- 无题目返回 `AI returned no questions`
|
||||
- **题目裁剪与分值归一化**:
|
||||
- 若设置题量,按顺序裁剪题目或分组
|
||||
- `normalizeScores` 按 `totalScore` 归一化各题分值
|
||||
- **题目落库**:
|
||||
- 将 AI 题目转换为题库格式 `{ text, options }`
|
||||
- 写入 `questions` 表,并记录 `authorId`
|
||||
- **结构与关联写入**:
|
||||
- 生成 `structure`(按分组或平铺题目)
|
||||
- 写入 `exams` 表,同时写入 `exam_questions`
|
||||
- **后续跳转**:
|
||||
- 创建成功后跳转 `/teacher/exams/[id]/build` 继续编辑
|
||||
|
||||
### 3.17 组卷 `/teacher/exams/[id]/build`
|
||||
实现:[exams/[id]/build/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/exams/%5Bid%5D/build/page.tsx)
|
||||
|
||||
154
docs/design/010_qa_test_plan_and_feedback.md
Normal file
154
docs/design/010_qa_test_plan_and_feedback.md
Normal file
@@ -0,0 +1,154 @@
|
||||
# 项目全量测试方案与执行反馈
|
||||
|
||||
**日期**: 2026-03-18
|
||||
**角色**: 首席测试师
|
||||
**范围**: `src/app` 页面路由、`src/modules` 业务模块、`src/app/api` 接口路由、工程质量门禁
|
||||
|
||||
---
|
||||
|
||||
## 1. 测试目标
|
||||
|
||||
- 建立一份可复用的全量测试方案,覆盖核心业务与工程质量门禁。
|
||||
- 对当前代码库执行可落地的全量检查,输出可追溯反馈。
|
||||
- 明确已验证范围、未验证范围、风险与后续建议。
|
||||
|
||||
---
|
||||
|
||||
## 2. 测试对象与覆盖边界
|
||||
|
||||
### 2.1 业务域
|
||||
|
||||
- 认证与会话:登录/注册、会话注入、路由守卫
|
||||
- 教师端:工作台、班级、作业、考试、题库、教材
|
||||
- 学生端:仪表盘、课程、作业、教材、课表
|
||||
- 管理端:学校、年级、班级、部门、学年、洞察
|
||||
- 家长端:仪表盘
|
||||
- 设置与个人资料
|
||||
|
||||
### 2.2 技术域
|
||||
|
||||
- 页面路由编译与渲染入口(`src/app/**/page.tsx`)
|
||||
- API 路由编译与入口(`src/app/api/**/route.ts`)
|
||||
- 业务模块(`src/modules/**`)
|
||||
- 类型系统与静态检查(TypeScript + ESLint)
|
||||
- 生产构建可通过性(Next.js build)
|
||||
|
||||
### 2.3 本次可执行边界说明
|
||||
|
||||
- 已执行:静态与构建级全量验证(lint、typecheck、build)。
|
||||
- 已执行:页面路由与 API 路由清单级覆盖核对(基于代码库现状)。
|
||||
- 未执行:依赖真实数据库与外部 AI 服务的端到端交互验证(当前环境未提供专用测试库与固定测试账号)。
|
||||
|
||||
---
|
||||
|
||||
## 3. 测试策略
|
||||
|
||||
### 3.1 质量门禁(必须通过)
|
||||
|
||||
1. ESLint 静态规范检查
|
||||
2. TypeScript 类型检查
|
||||
3. Next.js 生产构建
|
||||
|
||||
### 3.2 全量覆盖策略(按层)
|
||||
|
||||
1. 路由层:统计并核对全部页面与 API 路由入口文件是否可参与编译
|
||||
2. 业务层:通过构建期依赖解析与类型系统覆盖模块间调用链
|
||||
3. 集成层:以 `next build` 验证服务端组件、路由结构、导入关系、打包一致性
|
||||
4. 回归层:输出缺口与风险,给出后续补测清单
|
||||
|
||||
### 3.3 通过标准
|
||||
|
||||
- `npm run lint` 退出码为 0
|
||||
- `npm run typecheck` 退出码为 0
|
||||
- `npm run build` 退出码为 0
|
||||
- 无新增阻断级缺陷(Blocker/Critical)
|
||||
|
||||
---
|
||||
|
||||
## 4. 测试用例总表(主干)
|
||||
|
||||
| 用例ID | 级别 | 用例名称 | 执行方式 | 通过标准 |
|
||||
|---|---|---|---|---|
|
||||
| QA-GATE-001 | P0 | 代码规范检查 | `npm run lint` | 命令成功退出 |
|
||||
| QA-GATE-002 | P0 | 类型系统检查 | `npm run typecheck` | 命令成功退出 |
|
||||
| QA-GATE-003 | P0 | 生产构建检查 | `npm run build` | 构建成功 |
|
||||
| QA-ROUTE-001 | P0 | 页面路由入口覆盖 | 路由文件清单核对 | 页面入口均存在 |
|
||||
| QA-API-001 | P0 | API 路由入口覆盖 | 路由文件清单核对 | API 入口均存在 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 路由覆盖清单(本次统计)
|
||||
|
||||
### 5.1 页面路由入口
|
||||
|
||||
- 共统计 `46` 个页面入口文件(`src/app/**/page.tsx`)。
|
||||
- 覆盖角色:认证、教师、学生、管理端、家长、通用页面。
|
||||
|
||||
### 5.2 API 路由入口
|
||||
|
||||
- 共统计 `4` 个 API 入口文件(`src/app/api/**/route.ts`)。
|
||||
- 覆盖接口:`auth`、`ai/chat`、`onboarding/status`、`onboarding/complete`。
|
||||
|
||||
---
|
||||
|
||||
## 6. 执行记录与反馈
|
||||
|
||||
### 6.1 执行环境
|
||||
|
||||
- 操作系统:Windows
|
||||
- 项目目录:`E:\Desktop\CICD`
|
||||
- 包管理器:npm
|
||||
|
||||
### 6.2 结果总览
|
||||
|
||||
| 检查项 | 命令 | 结果 | 备注 |
|
||||
|---|---|---|---|
|
||||
| 代码规范 | `npm run lint` | 通过 | 退出码 0 |
|
||||
| 类型检查 | `npm run typecheck` | 通过 | 退出码 0 |
|
||||
| 生产构建 | `npm run build` | 通过 | 退出码 0,构建期间出现 baseline-browser-mapping 数据过期提示 |
|
||||
| 接口集成测试 | `npm run test:integration` | 通过 | 3 个测试文件,10 条用例通过 |
|
||||
| E2E 冒烟测试 | `npm run test:e2e:smoke` | 通过 | 本地优先使用系统 Chrome,2 条用例通过 |
|
||||
| E2E 全路由回归 | `npm run test:e2e:full-routes` | 通过 | 38 条用例通过,覆盖静态页面路由守卫与可达性 |
|
||||
|
||||
### 6.3 缺陷与风险
|
||||
|
||||
- 阻断级缺陷:0
|
||||
- 严重缺陷:0
|
||||
- 主要风险:跨角色真实业务数据写入链路仍需测试库与固定账号支撑,当前已完成路由级全覆盖回归。
|
||||
- 次要风险:构建日志出现 `baseline-browser-mapping` 数据过期提示,建议在依赖维护窗口升级该依赖数据包。
|
||||
|
||||
### 6.4 结论
|
||||
|
||||
- 当前代码库在静态检查、类型系统与生产构建三道门禁均通过,可进入下一阶段联调或发布候选流程。
|
||||
- 本次已完成企业级测试基线的第一阶段落地:集成测试框架、E2E 框架、CI 质量门禁已接入。
|
||||
- 本次已完成“代码级全量可构建性 + API 集成验证”并通过,未发现新增回归错误。
|
||||
- 若要达到“发布级全链路验收”,仍需补充测试库与固定账号下的完整业务 E2E 场景。
|
||||
|
||||
---
|
||||
|
||||
## 7. 后续补测计划
|
||||
|
||||
- 增加端到端测试(Playwright)覆盖关键教师流:创建考试 → 派发作业 → 提交批改。
|
||||
- 增加 API 集成测试(鉴权、参数校验、错误码)。
|
||||
- 增加数据层测试(测试库 + 回滚策略)验证查询过滤与 RBAC 边界。
|
||||
|
||||
---
|
||||
|
||||
## 8. 企业级实现落地清单(本次新增)
|
||||
|
||||
- 测试基础设施:
|
||||
- 新增 Vitest 配置:`vitest.config.ts`
|
||||
- 新增 Playwright 配置:`playwright.config.ts`
|
||||
- 新增集成测试初始化:`tests/setup/integration.setup.ts`
|
||||
- 集成测试用例:
|
||||
- `tests/integration/api-ai-chat.route.test.ts`
|
||||
- `tests/integration/api-onboarding-status.route.test.ts`
|
||||
- `tests/integration/api-onboarding-complete.route.test.ts`
|
||||
- E2E 冒烟用例:
|
||||
- `tests/e2e/smoke-auth.spec.ts`
|
||||
- E2E 全路由回归用例:
|
||||
- `tests/e2e/full-route-regression.spec.ts`
|
||||
- 工程脚本:
|
||||
- `package.json` 新增 `test`、`test:ci`、`test:integration`、`test:e2e`、`test:e2e:smoke`、`test:e2e:full-routes`
|
||||
- CI 门禁:
|
||||
- `.gitea/workflows/ci.yml` 新增 Playwright Chromium 安装、集成测试、E2E 全量回归测试步骤
|
||||
@@ -1,261 +0,0 @@
|
||||
# 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**: 动态路径导航 (Dynamic Breadcrumb).
|
||||
* **Implementation**: 基于 `usePathname()` 自动解析路由段。
|
||||
* **Mapping**: 通过 `NAV_CONFIG` 或 `BREADCRUMB_MAP` 映射路径到友好标题 (e.g., `/teacher/textbooks` -> "Textbooks").
|
||||
* **Filtering**: 自动过滤根角色路径 (e.g., `/teacher`) 以保持简洁。
|
||||
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)
|
||||
@@ -1,180 +0,0 @@
|
||||
# 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` 表为每个班级/学生生成状态记录。
|
||||
@@ -1,90 +0,0 @@
|
||||
require("dotenv/config");
|
||||
|
||||
const fs = require("node:fs");
|
||||
const crypto = require("node:crypto");
|
||||
const path = require("node:path");
|
||||
const mysql = require("mysql2/promise");
|
||||
|
||||
const JOURNAL = {
|
||||
"0000_aberrant_cobalt_man": 1766460456274,
|
||||
"0001_flawless_texas_twister": 1767004087964,
|
||||
"0002_equal_wolfpack": 1767145757594,
|
||||
};
|
||||
|
||||
function sha256Hex(input) {
|
||||
return crypto.createHash("sha256").update(input).digest("hex");
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const url = process.env.DATABASE_URL;
|
||||
if (!url) {
|
||||
throw new Error("DATABASE_URL is not set");
|
||||
}
|
||||
|
||||
const conn = await mysql.createConnection(url);
|
||||
|
||||
await conn.query(
|
||||
"CREATE TABLE IF NOT EXISTS `__drizzle_migrations` (id serial primary key, hash text not null, created_at bigint)"
|
||||
);
|
||||
|
||||
const [existing] = await conn.query(
|
||||
"SELECT id, hash, created_at FROM `__drizzle_migrations` ORDER BY created_at DESC LIMIT 1"
|
||||
);
|
||||
if (Array.isArray(existing) && existing.length > 0) {
|
||||
console.log("✅ __drizzle_migrations already has entries. Skip baselining.");
|
||||
await conn.end();
|
||||
return;
|
||||
}
|
||||
|
||||
const [[accountsRow]] = await conn.query(
|
||||
"SELECT COUNT(*) AS cnt FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='accounts'"
|
||||
);
|
||||
const accountsExists = Number(accountsRow?.cnt ?? 0) > 0;
|
||||
if (!accountsExists) {
|
||||
console.log("ℹ️ No existing tables detected (accounts missing). Skip baselining.");
|
||||
await conn.end();
|
||||
return;
|
||||
}
|
||||
|
||||
const [[structureRow]] = await conn.query(
|
||||
"SELECT COUNT(*) AS cnt FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='exams' AND column_name='structure'"
|
||||
);
|
||||
const examsStructureExists = Number(structureRow?.cnt ?? 0) > 0;
|
||||
|
||||
const [[homeworkRow]] = await conn.query(
|
||||
"SELECT COUNT(*) AS cnt FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='homework_assignments'"
|
||||
);
|
||||
const homeworkExists = Number(homeworkRow?.cnt ?? 0) > 0;
|
||||
|
||||
const baselineTags = [];
|
||||
baselineTags.push("0000_aberrant_cobalt_man");
|
||||
if (examsStructureExists) baselineTags.push("0001_flawless_texas_twister");
|
||||
if (homeworkExists) baselineTags.push("0002_equal_wolfpack");
|
||||
|
||||
const drizzleDir = path.resolve(__dirname, "..", "..", "drizzle");
|
||||
for (const tag of baselineTags) {
|
||||
const sqlPath = path.join(drizzleDir, `${tag}.sql`);
|
||||
if (!fs.existsSync(sqlPath)) {
|
||||
throw new Error(`Missing migration file: ${sqlPath}`);
|
||||
}
|
||||
const sqlText = fs.readFileSync(sqlPath).toString();
|
||||
const hash = sha256Hex(sqlText);
|
||||
const createdAt = JOURNAL[tag];
|
||||
if (typeof createdAt !== "number") {
|
||||
throw new Error(`Missing journal timestamp for: ${tag}`);
|
||||
}
|
||||
await conn.query(
|
||||
"INSERT INTO `__drizzle_migrations` (`hash`, `created_at`) VALUES (?, ?)",
|
||||
[hash, createdAt]
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`✅ Baselined __drizzle_migrations: ${baselineTags.join(", ")}`);
|
||||
await conn.end();
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error("❌ Baseline failed:", err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
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
|
||||
const rows = (tables as unknown as [unknown])[0]
|
||||
if (!Array.isArray(rows)) return
|
||||
|
||||
for (const row of rows) {
|
||||
const record = row as Record<string, unknown>
|
||||
const tableName =
|
||||
typeof record.TABLE_NAME === "string"
|
||||
? record.TABLE_NAME
|
||||
: typeof record.table_name === "string"
|
||||
? record.table_name
|
||||
: null
|
||||
if (!tableName) continue
|
||||
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)
|
||||
})
|
||||
@@ -1,5 +1,59 @@
|
||||
# Work Log
|
||||
|
||||
## 2026-03-19
|
||||
|
||||
### 1. 作业与权限测试覆盖补齐(第二阶段)
|
||||
- 新增角色路由与代理守卫集成测试:
|
||||
- `tests/integration/dashboard-routing.test.ts`
|
||||
- `tests/integration/proxy-guard.test.ts`
|
||||
- 扩展 onboarding 完成接口集成测试,覆盖班级-学科映射与教师分配逻辑:
|
||||
- `tests/integration/api-onboarding-complete.route.test.ts`
|
||||
- 新增认证业务流 E2E(注册 -> 登录 -> 受保护区域):
|
||||
- `tests/e2e/auth-business-flow.spec.ts`
|
||||
- 新增并补齐作业流程集成测试,覆盖创建、开始作答、提交、批改、保存答案:
|
||||
- `tests/integration/homework-create-assignment.test.ts`
|
||||
- `tests/integration/homework-actions.test.ts`
|
||||
- `saveHomeworkAnswerAction` 增加关键分支用例:
|
||||
- started 状态首次保存答案(insert)
|
||||
- started 状态更新已有答案(update)
|
||||
|
||||
### 2. 验证
|
||||
- `npm run test:integration`:通过(7 文件,38 用例)
|
||||
- `npm run lint`:通过
|
||||
- `npm run typecheck`:通过
|
||||
- `npm run test:e2e`:通过(40 通过,1 跳过)
|
||||
- 语言诊断:无错误
|
||||
|
||||
## 2026-03-18
|
||||
|
||||
### 1. 企业级测试体系落地(第一阶段)
|
||||
- 新增集成测试与 E2E 基础设施:
|
||||
- `vitest.config.ts`
|
||||
- `playwright.config.ts`
|
||||
- `tests/setup/integration.setup.ts`
|
||||
- 新增接口集成测试:
|
||||
- `tests/integration/api-ai-chat.route.test.ts`
|
||||
- `tests/integration/api-onboarding-status.route.test.ts`
|
||||
- `tests/integration/api-onboarding-complete.route.test.ts`
|
||||
- 新增认证冒烟 E2E:
|
||||
- `tests/e2e/smoke-auth.spec.ts`
|
||||
- 新增全路由回归 E2E:
|
||||
- `tests/e2e/full-route-regression.spec.ts`
|
||||
- 新增工程脚本:
|
||||
- `test`、`test:ci`、`test:integration`、`test:e2e`、`test:e2e:smoke`、`test:e2e:full-routes`
|
||||
- 更新 CI 质量门禁:
|
||||
- 增加 Playwright Chromium 安装
|
||||
- 增加集成测试执行
|
||||
- 增加 E2E 全量回归测试执行
|
||||
|
||||
### 2. 验证
|
||||
- `npm run lint`:通过
|
||||
- `npm run typecheck`:通过
|
||||
- `npm run test:integration`:通过(3 文件,10 用例)
|
||||
- `npm run build`:通过
|
||||
- `npm run test:e2e:smoke`:通过(本地使用系统 Chrome 通道,2 用例通过)
|
||||
- `npm run test:e2e:full-routes`:通过(38 用例通过)
|
||||
|
||||
## 2026-03-03
|
||||
|
||||
### 1. 教师加入班级学科分配逻辑修复
|
||||
|
||||
Reference in New Issue
Block a user