You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现仅首章节可访问,完成全部视频后解锁后续章节?

解决方案:章节式课程动态解锁功能

核心逻辑

实现这个功能的核心是用户进度持久化存储+后端权限校验+前端交互控制:

  1. 数据库记录用户每节课程的完成状态
  2. 后端根据用户进度返回对应章节的解锁状态
  3. 前端渲染时对未解锁章节的课程添加禁用逻辑,视频完成后触发进度更新并自动检查解锁条件

一、后端实现(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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 13:27:02