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