如何实现仅首章节可访问,完成全部视频后解锁后续章节?
解决方案:章节式课程动态解锁功能
核心逻辑
实现这个功能的核心是用户进度持久化存储+后端权限校验+前端交互控制:
- 数据库记录用户每节课程的完成状态
- 后端根据用户进度返回对应章节的解锁状态
- 前端渲染时对未解锁章节的课程添加禁用逻辑,视频完成后触发进度更新并自动检查解锁条件
一、后端实现(PHP+数据库)
1. 数据库表设计
至少需要4张核心表(可根据现有结构调整):
-- 用户表(复用现有表即可) CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL ); -- 章节表 CREATE TABLE chapters ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL, order_num INT NOT NULL -- 章节排序,用于判断解锁顺序 ); -- 课程表(关联章节) CREATE TABLE courses ( id INT PRIMARY KEY AUTO_INCREMENT, chapter_id INT NOT NULL, title VARCHAR(100) NOT NULL, video_url VARCHAR(255) NOT NULL, FOREIGN KEY (chapter_id) REFERENCES chapters(id) ); -- 用户进度表(核心) CREATE TABLE user_progress ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, course_id INT NOT NULL, is_completed TINYINT(1) DEFAULT 0, -- 0未完成,1已完成 completed_at DATETIME NULL, FOREIGN KEY (user_id) REFERENCES users(id), FOREIGN KEY (course_id) REFERENCES courses(id), UNIQUE KEY user_course_unique (user_id, course_id) );
2. PHP核心接口
获取用户章节解锁状态
<?php // 从session获取当前用户ID(需确保已完成用户登录校验) $user_id = $_SESSION['user_id']; // 1. 获取所有章节并按顺序排序 $chapters = $pdo->query("SELECT * FROM chapters ORDER BY order_num ASC")->fetchAll(PDO::FETCH_ASSOC); // 2. 计算每个章节的完成情况 $chapter_status = []; foreach ($chapters as $chapter) { // 获取该章节下总课程数 $total_courses = $pdo->query("SELECT COUNT(*) FROM courses WHERE chapter_id = {$chapter['id']}")->fetchColumn(); // 获取用户已完成的课程数 $completed_courses = $pdo->query("SELECT COUNT(*) FROM user_progress WHERE user_id = {$user_id} AND course_id IN (SELECT id FROM courses WHERE chapter_id = {$chapter['id']}) AND is_completed = 1")->fetchColumn(); $chapter_status[$chapter['id']] = [ 'is_completed' => $completed_courses == $total_courses, 'is_unlocked' => false // 默认锁定 ]; } // 3. 解锁逻辑:从第一个章节开始,前一个章节完成则解锁当前章节 $last_completed = false; foreach ($chapters as $index => $chapter) { if ($index == 0) { // 首章节默认解锁 $chapter_status[$chapter['id']]['is_unlocked'] = true; $last_completed = $chapter_status[$chapter['id']]['is_completed']; } else { // 当前章节解锁条件:前序章节已完成 $chapter_status[$chapter['id']]['is_unlocked'] = $last_completed; // 更新前置状态(仅当当前章节已解锁时) if ($chapter_status[$chapter['id']]['is_unlocked']) { $last_completed = $chapter_status[$chapter['id']]['is_completed']; } } } // 返回JSON格式数据给前端 echo json_encode(['chapters' => $chapters, 'chapter_status' => $chapter_status]); ?>
更新课程完成状态
<?php $user_id = $_SESSION['user_id']; $course_id = $_POST['course_id']; // 插入或更新用户进度(已完成) $stmt = $pdo->prepare("INSERT INTO user_progress (user_id, course_id, is_completed, completed_at) VALUES (:user_id, :course_id, 1, NOW()) ON DUPLICATE KEY UPDATE is_completed = 1, completed_at = NOW()"); $stmt->execute(['user_id' => $user_id, 'course_id' => $course_id]); // 返回更新后的章节状态(可封装成公共函数复用之前的状态计算逻辑) // ...(此处省略重复代码,建议将章节状态计算封装为独立函数) ?>
二、前端实现(HTML+JavaScript)
1. 动态渲染章节与课程
<div id="chapter-container"> <!-- 动态生成的章节内容 --> </div> <script> // 从后端获取章节数据 fetch('get_chapter_status.php') .then(res => res.json()) .then(data => { const container = document.getElementById('chapter-container'); data.chapters.forEach(chapter => { const status = data.chapter_status[chapter.id]; // 创建章节容器 const chapterDiv = document.createElement('div'); chapterDiv.className = 'chapter'; chapterDiv.innerHTML = `<h3>章节${chapter.order_num}: ${chapter.title}</h3>`; // 获取当前章节下的课程列表 fetch(`get_courses.php?chapter_id=${chapter.id}`) .then(res => res.json()) .then(courses => { const courseList = document.createElement('ul'); courses.forEach(course => { const courseItem = document.createElement('li'); // 判断课程是否可访问:章节已解锁,或课程已完成(允许复习) const isClickable = status.is_unlocked || course.is_completed; courseItem.className = isClickable ? 'course' : 'course locked'; courseItem.innerHTML = ` <span>${course.title}</span> ${isClickable ? `<video data-course-id="${course.id}" src="${course.video_url}" controls></video>` : '<span class="lock-icon">🔒</span>'} `; courseList.appendChild(courseItem); }); chapterDiv.appendChild(courseList); container.appendChild(chapterDiv); }); // 监听视频完成事件 chapterDiv.addEventListener('ended', (e) => { if (e.target.tagName === 'VIDEO') { const courseId = e.target.dataset.courseId; // 调用后端接口更新进度 fetch('update_progress.php', { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: `course_id=${courseId}` }) .then(res => res.json()) .then(() => { // 刷新页面或局部更新章节状态 location.reload(); // 简单实现,也可做局部DOM更新优化 }); } }); }); }); </script>
2. 前端样式控制
.course.locked { opacity: 0.5; pointer-events: none; } .lock-icon { color: #999; margin-left: 10px; }
三、可用工具与优化建议
- 前端:无需额外库,原生JS即可实现;若使用jQuery可简化DOM操作和AJAX请求,但非必需。
- 后端:如果用Laravel、ThinkPHP等框架,可利用ORM简化数据库关联查询,减少重复代码。
- 优化点:
- 前端可缓存章节状态,避免重复请求
- 视频完成判断可增加防作弊逻辑(比如监听播放时长达到90%以上才标记完成)
- 解锁下一章节时添加动画提示,提升用户体验
内容的提问来源于stack exchange,提问作者M.A.Asardeen
相关产品推荐
相关产品推荐

