kindergarten_java/docs/design/14-技术选型与项目初始化.md
2026-02-28 16:41:39 +08:00

1058 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 技术选型与项目初始化
> 创建时间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` 资源库开发策略部分。
---
现在让我开始创建具体的项目文件...