1058 lines
28 KiB
Markdown
1058 lines
28 KiB
Markdown
# 技术选型与项目初始化
|
||
|
||
> 创建时间:2025-02-04
|
||
> 基于之前的设计文档,确定技术栈并搭建项目框架
|
||
|
||
---
|
||
|
||
## 一、技术选型确认
|
||
|
||
### 1.1 前端技术栈
|
||
|
||
| 技术 | 选择 | 版本 | 说明 |
|
||
|-----|------|------|------|
|
||
| 框架 | Vue 3 | ^3.4.0 | 渐进式框架,生态完善 |
|
||
| 构建工具 | Vite | ^5.0.0 | 快速的开发服务器 |
|
||
| UI组件库 | Ant Design Vue | ^4.0.0 | 企业级UI组件 |
|
||
| 状态管理 | Pinia | ^2.1.0 | Vue官方推荐 |
|
||
| 路由 | Vue Router | ^4.2.0 | 官方路由管理 |
|
||
| HTTP客户端 | Axios | ^1.6.0 | Promise-based HTTP |
|
||
| 工具库 | Lodash-es | ^4.17.0 | 实用工具函数 |
|
||
| 日期处理 | Day.js | ^1.11.0 | 轻量级日期库 |
|
||
|
||
### 1.2 后端技术栈
|
||
|
||
| 技术 | 选择 | 版本 | 说明 |
|
||
|-----|------|------|------|
|
||
| 框架 | NestJS | ^10.0.0 | Node.js企业级框架 |
|
||
| 语言 | TypeScript | ^5.0.0 | 类型安全 |
|
||
| ORM | Prisma | ^5.0.0 | 现代化ORM |
|
||
| 数据库 | PostgreSQL | ^15.0.0 | 开源关系数据库 |
|
||
| 缓存 | Redis | ^7.0.0 | 内存数据库 |
|
||
| 认证 | JWT + Passport | - | Token认证 |
|
||
| 文件存储 | 阿里云OSS / MinIO | - | 对象存储 |
|
||
| 验证 | class-validator | - | 数据验证 |
|
||
|
||
### 1.3 开发工具
|
||
|
||
| 工具 | 用途 |
|
||
|-----|------|
|
||
| VS Code | 代码编辑器 |
|
||
| Git | 版本控制 |
|
||
| Docker | 容器化部署 |
|
||
| Postman | API测试 |
|
||
|
||
---
|
||
|
||
## 二、项目目录结构
|
||
|
||
### 2.1 整体结构
|
||
|
||
```
|
||
reading-platform/
|
||
├── frontend/ # 前端项目(三端统一)
|
||
│ ├── src/
|
||
│ │ ├── views/
|
||
│ │ │ ├── admin/ # 超管端页面
|
||
│ │ │ ├── school/ # 学校端页面
|
||
│ │ │ ├── teacher/ # 教师端页面
|
||
│ │ │ └── auth/ # 认证页面
|
||
│ │ ├── components/ # 公共组件
|
||
│ │ ├── stores/ # Pinia状态管理
|
||
│ │ ├── api/ # API请求
|
||
│ │ ├── router/ # 路由配置
|
||
│ │ ├── utils/ # 工具函数
|
||
│ │ └── types/ # TypeScript类型
|
||
│ ├── package.json
|
||
│ └── vite.config.ts
|
||
│
|
||
├── backend/ # 后端项目
|
||
│ ├── src/
|
||
│ │ ├── modules/
|
||
│ │ │ ├── auth/ # 认证模块
|
||
│ │ │ ├── admin/ # 超管端模块
|
||
│ │ │ ├── school/ # 学校端模块
|
||
│ │ │ ├── teacher/ # 教师端模块
|
||
│ │ │ ├── course/ # 课程包模块
|
||
│ │ │ ├── tenant/ # 租户模块
|
||
│ │ │ ├── common/ # 公共模块
|
||
│ │ │ └── upload/ # 文件上传模块
|
||
│ │ ├── common/
|
||
│ │ │ ├── decorators/ # 装饰器
|
||
│ │ │ ├── guards/ # 守卫
|
||
│ │ │ ├── filters/ # 过滤器
|
||
│ │ │ └── interceptors/ # 拦截器
|
||
│ │ ├── config/ # 配置
|
||
│ │ └── database/ # 数据库相关
|
||
│ ├── prisma/
|
||
│ │ └── schema.prisma # Prisma模型
|
||
│ ├── package.json
|
||
│ └── nest-cli.json
|
||
│
|
||
├── docs/ # 文档
|
||
│ └── ... # 已有的设计文档
|
||
│
|
||
├── docker-compose.yml # Docker编排
|
||
└── README.md
|
||
```
|
||
|
||
---
|
||
|
||
## 三、数据库模型(Prisma Schema)
|
||
|
||
### 3.1 核心模型定义
|
||
|
||
```prisma
|
||
// prisma/schema.prisma
|
||
|
||
generator client {
|
||
provider = "prisma-client-js"
|
||
}
|
||
|
||
datasource db {
|
||
provider = "postgresql"
|
||
url = env("DATABASE_URL")
|
||
}
|
||
|
||
// ==================== 租户相关 ====================
|
||
|
||
model Tenant {
|
||
id BigInt @id @default(autoincrement())
|
||
name String
|
||
address String?
|
||
contactPerson String? @map("contact_person")
|
||
contactPhone String? @map("contact_phone")
|
||
logoUrl String? @map("logo_url")
|
||
|
||
packageType PackageType @map("package_type")
|
||
teacherQuota Int @default(20) @map("teacher_quota")
|
||
studentQuota Int @default(200) @map("student_quota")
|
||
storageQuota BigInt @default(5368709120) @map("storage_quota") // 5GB
|
||
|
||
startDate DateTime @map("start_date") @db.Date
|
||
expireDate DateTime @map("expire_date") @db.Date
|
||
|
||
teacherCount Int @default(0) @map("teacher_count")
|
||
studentCount Int @default(0) @map("student_count")
|
||
storageUsed BigInt @default(0) @map("storage_used")
|
||
|
||
status TenantStatus @default(ACTIVE)
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
teachers Teacher[]
|
||
students Student[]
|
||
classes Class[]
|
||
lessons Lesson[]
|
||
tenantCourses TenantCourse[]
|
||
|
||
@@map("tenants")
|
||
}
|
||
|
||
enum PackageType {
|
||
BASIC
|
||
STANDARD
|
||
ADVANCED
|
||
}
|
||
|
||
enum TenantStatus {
|
||
ACTIVE
|
||
SUSPENDED
|
||
EXPIRED
|
||
}
|
||
|
||
// ==================== 教师相关 ====================
|
||
|
||
model Teacher {
|
||
id BigInt @id @default(autoincrement())
|
||
tenantId BigInt @map("tenant_id")
|
||
name String
|
||
phone String
|
||
email String?
|
||
|
||
loginAccount String @unique @map("login_account")
|
||
passwordHash String @map("password_hash")
|
||
classIds Json? @map("class_ids") // 存储负责的班级ID列表
|
||
|
||
status TeacherStatus @default(ACTIVE)
|
||
|
||
lessonCount Int @default(0) @map("lesson_count")
|
||
feedbackCount Int @default(0) @map("feedback_count")
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
lastLoginAt DateTime? @map("last_login_at")
|
||
|
||
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
||
lessons Lesson[]
|
||
feedbacks LessonFeedback[]
|
||
|
||
@@map("teachers")
|
||
}
|
||
|
||
enum TeacherStatus {
|
||
ACTIVE
|
||
SUSPENDED
|
||
}
|
||
|
||
// ==================== 班级相关 ====================
|
||
|
||
model Class {
|
||
id BigInt @id @default(autoincrement())
|
||
tenantId BigInt @map("tenant_id")
|
||
name String
|
||
grade Grade
|
||
|
||
teacherId BigInt? @map("teacher_id")
|
||
|
||
studentCount Int @default(0) @map("student_count")
|
||
lessonCount Int @default(0) @map("lesson_count")
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
||
teacher Teacher? @relation(fields: [teacherId], references: [id])
|
||
students Student[]
|
||
lessons Lesson[]
|
||
|
||
@@map("classes")
|
||
}
|
||
|
||
enum Grade {
|
||
SMALL // 小班
|
||
MIDDLE // 中班
|
||
BIG // 大班
|
||
}
|
||
|
||
// ==================== 学生相关 ====================
|
||
|
||
model Student {
|
||
id BigInt @id @default(autoincrement())
|
||
tenantId BigInt @map("tenant_id")
|
||
classId BigInt @map("class_id")
|
||
name String
|
||
gender Gender?
|
||
birthDate DateTime? @map("birth_date") @db.Date
|
||
parentPhone String? @map("parent_phone")
|
||
parentName String? @map("parent_name")
|
||
|
||
readingCount Int @default(0) @map("reading_count")
|
||
lessonCount Int @default(0) @map("lesson_count")
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
||
class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
|
||
records StudentRecord[]
|
||
|
||
@@map("students")
|
||
}
|
||
|
||
enum Gender {
|
||
MALE
|
||
FEMALE
|
||
}
|
||
|
||
// ==================== 课程包相关 ====================
|
||
|
||
model Course {
|
||
id BigInt @id @default(autoincrement())
|
||
name String
|
||
description String? @db.Text
|
||
|
||
pictureBookId BigInt? @map("picture_book_id")
|
||
pictureBookName String? @map("picture_book_name")
|
||
|
||
gradeTags Json @map("grade_tags") // ["small", "middle"]
|
||
domainTags Json @map("domain_tags") // [{"level1":"语言", "level2":"阅读与书写准备"}]
|
||
|
||
duration Int @default(25) // 分钟
|
||
|
||
status CourseStatus @default(DRAFT)
|
||
version String @default("1.0")
|
||
|
||
usageCount Int @default(0) @map("usage_count")
|
||
teacherCount Int @default(0) @map("teacher_count")
|
||
avgRating Decimal @default(0) @map("avg_rating") @db.Decimal(3, 2)
|
||
|
||
createdBy BigInt? @map("created_by")
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
publishedAt DateTime? @map("published_at")
|
||
|
||
resources CourseResource[]
|
||
scripts CourseScript[]
|
||
activities CourseActivity[]
|
||
lessons Lesson[]
|
||
tenantCourses TenantCourse[]
|
||
|
||
@@map("courses")
|
||
}
|
||
|
||
enum CourseStatus {
|
||
DRAFT
|
||
REVIEWING
|
||
PUBLISHED
|
||
ARCHIVED
|
||
}
|
||
|
||
// ==================== 课程资源 ====================
|
||
|
||
model CourseResource {
|
||
id BigInt @id @default(autoincrement())
|
||
courseId BigInt @map("course_id")
|
||
resourceType ResourceType @map("resource_type")
|
||
resourceName String @map("resource_name")
|
||
fileUrl String @map("file_url")
|
||
fileSize BigInt? @map("file_size")
|
||
mimeType String? @map("mime_type")
|
||
metadata Json? // 其他元数据(时长、分辨率等)
|
||
|
||
sortOrder Int @default(0) @map("sort_order")
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
|
||
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
|
||
|
||
@@map("course_resources")
|
||
}
|
||
|
||
enum ResourceType {
|
||
EBOOK
|
||
AUDIO
|
||
VIDEO
|
||
PPT
|
||
ANIMATION
|
||
INTERACTIVE
|
||
}
|
||
|
||
// ==================== 课程脚本 ====================
|
||
|
||
model CourseScript {
|
||
id BigInt @id @default(autoincrement())
|
||
courseId BigInt @map("course_id")
|
||
stepIndex Int @map("step_index")
|
||
stepName String @map("step_name")
|
||
stepType StepType @map("step_type")
|
||
|
||
duration Int // 分钟
|
||
objective String? @db.Text
|
||
teacherScript String? @map("teacher_script") @db.Text
|
||
interactionPoints Json? @map("interaction_points")
|
||
|
||
resourceIds Json? @map("resource_ids") // 关联的资源ID列表
|
||
|
||
sortOrder Int @default(0) @map("sort_order")
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
|
||
pages CourseScriptPage[]
|
||
|
||
@@unique([courseId, stepIndex])
|
||
@@map("course_scripts")
|
||
}
|
||
|
||
enum StepType {
|
||
INTRO // 导入
|
||
GUIDE // 引导观察
|
||
READING // 师幼共读
|
||
ACTIVITY // 延伸活动
|
||
ENDING // 活动结束
|
||
}
|
||
|
||
// ==================== 逐页配置 ====================
|
||
|
||
model CourseScriptPage {
|
||
id BigInt @id @default(autoincrement())
|
||
scriptId BigInt @map("script_id")
|
||
pageNumber Int @map("page_number")
|
||
questions Json? // 提问列表
|
||
interactionComponent Json? @map("interaction_component")
|
||
teacherNotes String? @map("teacher_notes") @db.Text
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
script CourseScript @relation(fields: [scriptId], references: [id], onDelete: Cascade)
|
||
|
||
@@unique([scriptId, pageNumber])
|
||
@@map("course_script_pages")
|
||
}
|
||
|
||
// ==================== 延伸活动 ====================
|
||
|
||
model CourseActivity {
|
||
id BigInt @id @default(autoincrement())
|
||
courseId BigInt @map("course_id")
|
||
name String
|
||
|
||
domain String? // 所属领域
|
||
domainTagId BigInt? @map("domain_tag_id")
|
||
activityType ActivityType @map("activity_type")
|
||
duration Int? // 分钟
|
||
|
||
onlineMaterials Json? @map("online_materials")
|
||
offlineMaterials String? @map("offline_materials") @db.Text
|
||
activityGuide String? @map("activity_guide") @db.Text
|
||
objectives String? @db.Text
|
||
|
||
sortOrder Int @default(0) @map("sort_order")
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
|
||
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
|
||
|
||
@@map("course_activities")
|
||
}
|
||
|
||
enum ActivityType {
|
||
HANDS_ON // 动手操作
|
||
COMMUNICATION // 交流讨论
|
||
GAME // 游戏互动
|
||
OTHER
|
||
}
|
||
|
||
// ==================== 授课记录 ====================
|
||
|
||
model Lesson {
|
||
id BigInt @id @default(autoincrement())
|
||
tenantId BigInt @map("tenant_id")
|
||
teacherId BigInt @map("teacher_id")
|
||
classId BigInt @map("class_id")
|
||
courseId BigInt @map("course_id")
|
||
|
||
plannedDatetime DateTime? @map("planned_datetime")
|
||
startDatetime DateTime? @map("start_datetime")
|
||
endDatetime DateTime? @map("end_datetime")
|
||
actualDuration Int? @map("actual_duration") // 分钟
|
||
|
||
status LessonStatus @default(PLANNED)
|
||
|
||
overallRating OverallRating? @map("overall_rating")
|
||
participationRating ParticipationRating? @map("participation_rating")
|
||
completionNote CompletionNote? @map("completion_note")
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
tenant Tenant @relation(fields: [tenantId], references: [id])
|
||
teacher Teacher @relation(fields: [teacherId], references: [id])
|
||
class Class @relation(fields: [classId], references: [id])
|
||
course Course @relation(fields: [courseId], references: [id])
|
||
feedbacks LessonFeedback[]
|
||
records StudentRecord[]
|
||
|
||
@@map("lessons")
|
||
}
|
||
|
||
enum LessonStatus {
|
||
PLANNED
|
||
IN_PROGRESS
|
||
COMPLETED
|
||
CANCELLED
|
||
}
|
||
|
||
enum OverallRating {
|
||
EXCELLENT
|
||
GOOD
|
||
AVERAGE
|
||
NEEDS_IMPROVEMENT
|
||
}
|
||
|
||
enum ParticipationRating {
|
||
VERY_HIGH
|
||
HIGH
|
||
MEDIUM
|
||
LOW
|
||
}
|
||
|
||
enum CompletionNote {
|
||
ALL_COMPLETED
|
||
ADJUSTED
|
||
PARTIAL
|
||
}
|
||
|
||
// ==================== 课程反馈 ====================
|
||
|
||
model LessonFeedback {
|
||
id BigInt @id @default(autoincrement())
|
||
lessonId BigInt @map("lesson_id")
|
||
teacherId BigInt @map("teacher_id")
|
||
|
||
designQuality Int? @map("design_quality") // 1-5
|
||
participation Int? // 1-5
|
||
goalAchievement Int? @map("goal_achievement") // 1-5
|
||
|
||
stepFeedbacks Json? @map("step_feedbacks")
|
||
|
||
pros String? @db.Text
|
||
suggestions String? @db.Text
|
||
|
||
activitiesDone Json? @map("activities_done")
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
lesson Lesson @relation(fields: [lessonId], references: [id], onDelete: Cascade)
|
||
teacher Teacher @relation(fields: [teacherId], references: [id])
|
||
|
||
@@unique([lessonId, teacherId])
|
||
@@map("lesson_feedbacks")
|
||
}
|
||
|
||
// ==================== 学生测评记录 ====================
|
||
|
||
model StudentRecord {
|
||
id BigInt @id @default(autoincrement())
|
||
lessonId BigInt @map("lesson_id")
|
||
studentId BigInt @map("student_id")
|
||
|
||
focus Int? // 1-5
|
||
participation Int?
|
||
interest Int?
|
||
understanding Int?
|
||
|
||
domainAchievements Json? @map("domain_achievements")
|
||
|
||
notes String? @db.Text
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
lesson Lesson @relation(fields: [lessonId], references: [id], onDelete: Cascade)
|
||
student Student @relation(fields: [studentId], references: [id])
|
||
|
||
@@unique([lessonId, studentId])
|
||
@@map("student_records")
|
||
}
|
||
|
||
// ==================== 标签体系 ====================
|
||
|
||
model Tag {
|
||
id BigInt @id @default(autoincrement())
|
||
level Int // 1-4
|
||
code String @unique
|
||
name String
|
||
parentId BigInt? @map("parent_id")
|
||
|
||
description String? @db.Text
|
||
metadata Json?
|
||
|
||
sortOrder Int @default(0) @map("sort_order")
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
|
||
@@index([parentId])
|
||
@@index([level])
|
||
@@map("tags")
|
||
}
|
||
|
||
// ==================== 租户课程授权 ====================
|
||
|
||
model TenantCourse {
|
||
id BigInt @id @default(autoincrement())
|
||
tenantId BigInt @map("tenant_id")
|
||
courseId BigInt @map("course_id")
|
||
|
||
authorized Boolean @default(true)
|
||
authorizedAt DateTime? @map("authorized_at")
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
|
||
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
||
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
|
||
|
||
@@unique([tenantId, courseId])
|
||
@@map("tenant_courses")
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 四、后端核心模块
|
||
|
||
### 4.1 项目初始化
|
||
|
||
```bash
|
||
# 创建后端项目
|
||
mkdir reading-platform-backend
|
||
cd reading-platform-backend
|
||
npm init -y
|
||
|
||
# 安装依赖
|
||
npm install @nestjs/common@^10.0.0 @nestjs/core@^10.0.0 @nestjs/platform-express@^10.0.0
|
||
npm install @nestjs/config@^3.0.0 @nestjs/jwt@^10.0.0 @nestjs/passport@^10.0.0
|
||
npm install @prisma/client@^5.0.0 passport@^0.6.0 passport-jwt@^4.0.0
|
||
npm install class-validator@^0.14.0 class-transformer@^0.5.1
|
||
npm install bcrypt@^5.1.0
|
||
npm install @nestjs/throttler@^5.0.0 # 限流
|
||
npm install -D @nestjs/cli@^10.0.0
|
||
npm install -D prisma@^5.0.0
|
||
npm install -D typescript@^5.0.0 @types/node@^20.0.0 @types/passport-jwt@^4.0.0 @types/bcrypt@^5.0.0
|
||
|
||
# 安装额外依赖
|
||
npm install @nestjs/swagger@^7.0.0 # API文档
|
||
npm install rxjs@^7.8.0
|
||
npm install reflect-metadata@^0.1.0
|
||
```
|
||
|
||
### 4.2 NestJS主模块
|
||
|
||
```typescript
|
||
// src/main.ts
|
||
import { NestFactory } from '@nestjs/core';
|
||
import { ValidationPipe } from '@nestjs/common';
|
||
import { AppModule } from './app.module';
|
||
|
||
async function bootstrap() {
|
||
const app = await NestFactory.create(AppModule);
|
||
|
||
// 全局验证管道
|
||
app.useGlobalPipes(
|
||
new ValidationPipe({
|
||
whitelist: true,
|
||
forbidNonWhitelisted: true,
|
||
transform: true,
|
||
}),
|
||
);
|
||
|
||
// CORS
|
||
app.enableCors({
|
||
origin: process.env.FRONTEND_URL || 'http://localhost:5173',
|
||
credentials: true,
|
||
});
|
||
|
||
// API前缀
|
||
app.setGlobalPrefix('api/v1');
|
||
|
||
const port = process.env.PORT || 3000;
|
||
await app.listen(port);
|
||
|
||
console.log(`🚀 Application is running on: http://localhost:${port}`);
|
||
console.log(`📚 API documentation: http://localhost:${port}/api/docs`);
|
||
}
|
||
|
||
bootstrap();
|
||
```
|
||
|
||
```typescript
|
||
// src/app.module.ts
|
||
import { Module } from '@nestjs/common';
|
||
import { ConfigModule } from '@nestjs/config';
|
||
import { ThrottlerModule } from '@nestjs/throttler';
|
||
import { PrismaModule } from './database/prisma.module';
|
||
import { AuthModule } from './modules/auth/auth.module';
|
||
import { AdminModule } from './modules/admin/admin.module';
|
||
import { SchoolModule } from './modules/school/school.module';
|
||
import { TeacherModule } from './modules/teacher/teacher.module';
|
||
import { CourseModule } from './modules/course/course.module';
|
||
import { TenantModule } from './modules/tenant/tenant.module';
|
||
import { CommonModule } from './modules/common/common.module';
|
||
import { UploadModule } from './modules/upload/upload.module';
|
||
|
||
@Module({
|
||
imports: [
|
||
ConfigModule.forRoot({
|
||
isGlobal: true,
|
||
envFilePath: `.env.${process.env.NODE_ENV || 'development'}`,
|
||
}),
|
||
ThrottlerModule.forRoot([{
|
||
ttl: 60000, // 60秒
|
||
limit: 100, // 最多100个请求
|
||
}]),
|
||
PrismaModule,
|
||
AuthModule,
|
||
AdminModule,
|
||
SchoolModule,
|
||
TeacherModule,
|
||
CourseModule,
|
||
TenantModule,
|
||
CommonModule,
|
||
UploadModule,
|
||
],
|
||
})
|
||
export class AppModule {}
|
||
```
|
||
|
||
---
|
||
|
||
## 五、前端项目初始化
|
||
|
||
### 5.1 项目创建
|
||
|
||
```bash
|
||
# 使用Vite创建Vue3项目
|
||
npm create vite@latest reading-platform-frontend -- --template vue-ts
|
||
cd reading-platform-frontend
|
||
|
||
# 安装依赖
|
||
npm install
|
||
npm install vue-router@^4.2.0 pinia@^2.1.0
|
||
npm install ant-design-vue@^4.0.0
|
||
npm install axios@^1.6.0 dayjs@^1.11.0 lodash-es@^4.17.0
|
||
npm install @ant-design/icons-vue@^7.0.0
|
||
|
||
# 开发依赖
|
||
npm install -D @types/lodash-es@^4.17.0 @types/node@^20.0.0
|
||
npm install -D unplugin-vue-components@^0.26.0 # 组件自动导入
|
||
npm install -D unplugin-auto-import@^0.17.0 # API自动导入
|
||
```
|
||
|
||
### 5.2 Vite配置
|
||
|
||
```typescript
|
||
// vite.config.ts
|
||
import { defineConfig } from 'vite';
|
||
import vue from '@vitejs/plugin-vue';
|
||
import { resolve } from 'path';
|
||
import AutoImport from 'unplugin-auto-import/vite';
|
||
import Components from 'unplugin-vue-components/vite';
|
||
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers';
|
||
|
||
export default defineConfig({
|
||
plugins: [
|
||
vue(),
|
||
AutoImport({
|
||
imports: [
|
||
'vue',
|
||
'vue-router',
|
||
'pinia',
|
||
{
|
||
'ant-design-vue': [
|
||
'message',
|
||
'notification',
|
||
'Modal',
|
||
],
|
||
},
|
||
],
|
||
dts: 'src/auto-imports.d.ts',
|
||
}),
|
||
Components({
|
||
resolvers: [
|
||
AntDesignVueResolver({
|
||
importStyle: false,
|
||
}),
|
||
],
|
||
dts: 'src/components.d.ts',
|
||
}),
|
||
],
|
||
resolve: {
|
||
alias: {
|
||
'@': resolve(__dirname, 'src'),
|
||
},
|
||
},
|
||
server: {
|
||
port: 5173,
|
||
proxy: {
|
||
'/api': {
|
||
target: 'http://localhost:3000',
|
||
changeOrigin: true,
|
||
},
|
||
},
|
||
},
|
||
});
|
||
```
|
||
|
||
### 5.3 路由配置
|
||
|
||
```typescript
|
||
// src/router/index.ts
|
||
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
|
||
|
||
const routes: RouteRecordRaw[] = [
|
||
{
|
||
path: '/login',
|
||
name: 'Login',
|
||
component: () => import('@/views/auth/LoginView.vue'),
|
||
meta: { requiresAuth: false },
|
||
},
|
||
{
|
||
path: '/admin',
|
||
name: 'Admin',
|
||
component: () => import('@/views/admin/LayoutView.vue'),
|
||
meta: { requiresAuth: true, role: 'admin' },
|
||
children: [
|
||
{
|
||
path: '',
|
||
redirect: '/admin/dashboard',
|
||
},
|
||
{
|
||
path: 'dashboard',
|
||
name: 'AdminDashboard',
|
||
component: () => import('@/views/admin/DashboardView.vue'),
|
||
},
|
||
{
|
||
path: 'courses',
|
||
name: 'AdminCourses',
|
||
component: () => import('@/views/admin/courses/CourseListView.vue'),
|
||
},
|
||
// ... 其他超管端路由
|
||
],
|
||
},
|
||
{
|
||
path: '/school',
|
||
name: 'School',
|
||
component: () => import('@/views/school/LayoutView.vue'),
|
||
meta: { requiresAuth: true, role: 'school' },
|
||
children: [
|
||
{
|
||
path: '',
|
||
redirect: '/school/dashboard',
|
||
},
|
||
{
|
||
path: 'dashboard',
|
||
name: 'SchoolDashboard',
|
||
component: () => import('@/views/school/DashboardView.vue'),
|
||
},
|
||
// ... 其他学校端路由
|
||
],
|
||
},
|
||
{
|
||
path: '/teacher',
|
||
name: 'Teacher',
|
||
component: () => import('@/views/teacher/LayoutView.vue'),
|
||
meta: { requiresAuth: true, role: 'teacher' },
|
||
children: [
|
||
{
|
||
path: '',
|
||
redirect: '/teacher/dashboard',
|
||
},
|
||
{
|
||
path: 'dashboard',
|
||
name: 'TeacherDashboard',
|
||
component: () => import('@/views/teacher/DashboardView.vue'),
|
||
},
|
||
{
|
||
path: 'courses',
|
||
name: 'TeacherCourses',
|
||
component: () => import('@/views/teacher/courses/CourseListView.vue'),
|
||
},
|
||
{
|
||
path: 'courses/:id',
|
||
name: 'TeacherCourseDetail',
|
||
component: () => import('@/views/teacher/courses/CourseDetailView.vue'),
|
||
},
|
||
{
|
||
path: 'courses/:id/prepare',
|
||
name: 'TeacherCoursePrepare',
|
||
component: () => import('@/views/teacher/courses/PrepareModeView.vue'),
|
||
},
|
||
{
|
||
path: 'lessons/:id',
|
||
name: 'TeacherLesson',
|
||
component: () => import('@/views/teacher/lessons/LessonView.vue'),
|
||
},
|
||
// ... 其他教师端路由
|
||
],
|
||
},
|
||
{
|
||
path: '/:pathMatch(.*)*',
|
||
name: 'NotFound',
|
||
component: () => import('@/views/NotFoundView.vue'),
|
||
},
|
||
];
|
||
|
||
const router = createRouter({
|
||
history: createWebHistory(),
|
||
routes,
|
||
});
|
||
|
||
// 路由守卫
|
||
router.beforeEach((to, from, next) => {
|
||
const token = localStorage.getItem('token');
|
||
|
||
if (to.meta.requiresAuth && !token) {
|
||
next('/login');
|
||
} else if (to.path === '/login' && token) {
|
||
// 根据用户角色跳转
|
||
const role = localStorage.getItem('role');
|
||
next(`/${role}/dashboard`);
|
||
} else {
|
||
next();
|
||
}
|
||
});
|
||
|
||
export default router;
|
||
```
|
||
|
||
---
|
||
|
||
## 六、Docker配置
|
||
|
||
### 6.1 docker-compose.yml
|
||
|
||
```yaml
|
||
version: '3.8'
|
||
|
||
services:
|
||
postgres:
|
||
image: postgres:15-alpine
|
||
container_name: reading-platform-db
|
||
environment:
|
||
POSTGRES_USER: admin
|
||
POSTGRES_PASSWORD: password
|
||
POSTGRES_DB: reading_platform
|
||
ports:
|
||
- "5432:5432"
|
||
volumes:
|
||
- postgres_data:/var/lib/postgresql/data
|
||
|
||
redis:
|
||
image: redis:7-alpine
|
||
container_name: reading-platform-redis
|
||
ports:
|
||
- "6379:6379"
|
||
volumes:
|
||
- redis_data:/data
|
||
|
||
backend:
|
||
build:
|
||
context: ./backend
|
||
dockerfile: Dockerfile
|
||
container_name: reading-platform-backend
|
||
environment:
|
||
NODE_ENV: development
|
||
DATABASE_URL: postgresql://admin:password@postgres:5432/reading_platform
|
||
REDIS_URL: redis://redis:6379
|
||
JWT_SECRET: your-secret-key
|
||
ports:
|
||
- "3000:3000"
|
||
depends_on:
|
||
- postgres
|
||
- redis
|
||
volumes:
|
||
- ./backend:/app
|
||
- /app/node_modules
|
||
command: npm run start:dev
|
||
|
||
frontend:
|
||
build:
|
||
context: ./frontend
|
||
dockerfile: Dockerfile
|
||
container_name: reading-platform-frontend
|
||
ports:
|
||
- "5173:5173"
|
||
volumes:
|
||
- ./frontend:/app
|
||
- /app/node_modules
|
||
command: npm run dev
|
||
|
||
volumes:
|
||
postgres_data:
|
||
redis_data:
|
||
```
|
||
|
||
---
|
||
|
||
## 七、环境变量配置
|
||
|
||
### 7.1 后端环境变量
|
||
|
||
```bash
|
||
# backend/.env.development
|
||
NODE_ENV=development
|
||
PORT=3000
|
||
|
||
# 数据库
|
||
DATABASE_URL="postgresql://admin:password@localhost:5432/reading_platform"
|
||
|
||
# Redis
|
||
REDIS_URL="redis://localhost:6379"
|
||
|
||
# JWT
|
||
JWT_SECRET="your-super-secret-jwt-key-change-in-production"
|
||
JWT_EXPIRES_IN="7d"
|
||
|
||
# 文件上传
|
||
UPLOAD_DIR="./uploads"
|
||
MAX_FILE_SIZE=104857600 # 100MB
|
||
|
||
# OSS (阿里云)
|
||
OSS_REGION="oss-cn-hangzhou"
|
||
OSS_ACCESS_KEY_ID="your-access-key"
|
||
OSS_ACCESS_KEY_SECRET="your-secret"
|
||
OSS_BUCKET="reading-platform"
|
||
|
||
# 前端URL
|
||
FRONTEND_URL="http://localhost:5173"
|
||
```
|
||
|
||
### 7.2 前端环境变量
|
||
|
||
```bash
|
||
# frontend/.env.development
|
||
VITE_API_BASE_URL=http://localhost:3000/api/v1
|
||
VITE_APP_TITLE=幼儿阅读教学服务平台
|
||
```
|
||
|
||
---
|
||
|
||
## 八、下一步开发计划
|
||
|
||
> **更新时间**: 2026-02-13
|
||
> **项目状态**: 开发中 - 超管端课程包核心功能开发中
|
||
|
||
### Phase 1: 基础框架(第1-2周)✅ 已完成
|
||
|
||
- [x] 技术选型确认
|
||
- [x] 项目初始化
|
||
- [x] 数据库设计与迁移
|
||
- [x] 认证系统实现
|
||
- [x] 权限管理实现
|
||
- [x] 标签体系初始化
|
||
|
||
### Phase 2: 超管端核心(第3-6周)🔄 进行中
|
||
|
||
- [x] 课程包制作工作台
|
||
- [x] 课程包管理(草稿/发布/审核)
|
||
- [x] 课程包审核流程
|
||
- [x] 租户管理(基础功能)
|
||
- [ ] 资源库管理(第二阶段开发)
|
||
- [ ] 数据统计看板
|
||
|
||
### Phase 3: 教师端核心(第7-10周)🔄 进行中
|
||
|
||
- [x] 课程中心
|
||
- [x] 备课模式
|
||
- [x] 上课模式
|
||
- [ ] 班级管理
|
||
- [ ] 反馈记录
|
||
|
||
### Phase 4: 学校端(第11-12周)🔄 框架完成
|
||
|
||
- [ ] 教师管理
|
||
- [ ] 学生管理
|
||
- [ ] 课程授权查看
|
||
- [ ] 数据报表
|
||
|
||
### Phase 5: 反馈与数据(第13-14周)⬜ 待开发
|
||
|
||
- [ ] 教师反馈收集
|
||
- [ ] 测评记录
|
||
- [ ] 数据统计优化
|
||
- [ ] 性能优化
|
||
|
||
---
|
||
|
||
## 九、资源库开发策略(2026-02-13 确认)
|
||
|
||
根据MVP优先级,资源库功能采用三阶段开发策略:
|
||
|
||
| 阶段 | 内容 | 状态 |
|
||
|-----|------|------|
|
||
| **第一阶段** | 完成课程包核心功能,关联绘本使用文本输入 | ✅ 进行中 |
|
||
| **第二阶段** | 开发资源库完整管理功能 | ⬜ 待开发 |
|
||
| **第三阶段** | 重构资源选择逻辑,整合课程包与资源库 | ⬜ 待开发 |
|
||
|
||
详见 `开发进展记录.md` 资源库开发策略部分。
|
||
|
||
---
|
||
|
||
现在让我开始创建具体的项目文件...
|