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

如何用Laravel、Bootstrap和JS实现课程场景支持自动切换的单开手风琴?

手风琴交互效果图

实现步骤

依赖确认

先确保项目已引入Bootstrap 5,Laravel项目可以直接用npm安装,或者在Blade模板引入官方CDN即可,不用额外安装其他UI组件。

1. 后端逻辑准备

控制器中取出当前用户的课程列表,按学习顺序排序,带上完成状态:

// CourseController.php
public function index()
{
    $courses = auth()->user()->courses()
        ->orderBy('sort', 'asc')
        ->get(['id', 'title', 'study_content', 'is_completed']);
    return view('courses.index', compact('courses'));
}

新增标记课程完成的接口:

// routes/web.php
Route::post('/courses/{course}/finish', [CourseController::class, 'finish'])->name('courses.finish');

// CourseController.php
public function finish(Course $course)
{
    // 可自行补充权限校验逻辑,确保是当前用户的课程
    $course->update(['is_completed' => 1]);
    return response()->json(['success' => 1]);
}

2. Blade模板渲染手风琴

直接用Bootstrap原生手风琴结构,自带data-bs-parent属性就可以实现同一时间只展开一个面板的需求,不需要额外写JS控制:

<!-- 记得在head标签中加csrf meta标签 -->
<meta name="csrf-token" content="{{ csrf_token() }}">

<div class="accordion" id="courseAccordion">
  @foreach($courses as $k => $course)
  <div class="accordion-item course-card" 
       data-id="{{ $course->id }}" 
       data-is-completed="{{ $course->is_completed ? 1 : 0 }}">
    <h2 class="accordion-header" id="heading{{ $course->id }}">
      <button class="accordion-button {{ $course->is_completed ? 'collapsed' : ($k > 0 ? 'collapsed' : '') }}" 
              type="button" 
              data-bs-toggle="collapse" 
              data-bs-target="#collapse{{ $course->id }}" 
              aria-expanded="{{ $course->is_completed ? 'false' : ($k == 0 ? 'true' : 'false') }}" 
              aria-controls="collapse{{ $course->id }}">
        {{ $course->title }}
        @if($course->is_completed)
          <span class="ms-2 badge bg-success">已完成</span>
        @endif
      </button>
    </h2>
    <div id="collapse{{ $course->id }}" 
         class="accordion-collapse collapse {{ $course->is_completed ? '' : ($k == 0 ? 'show' : '') }}" 
         aria-labelledby="heading{{ $course->id }}" 
         data-bs-parent="#courseAccordion">
      <div class="accordion-body">
        {{ $course->study_content }}
        <button class="btn btn-primary mt-3 finish-btn" data-id="{{ $course->id }}">标记完成</button>
      </div>
    </div>
  </div>
  @endforeach
</div>

3. JS交互逻辑

监听完成按钮点击,请求接口成功后自动折叠当前面板,展开下一节未完成的课程:

document.addEventListener('DOMContentLoaded', () => {
  // 绑定所有完成按钮的点击事件
  document.querySelectorAll('.finish-btn').forEach(btn => {
    btn.addEventListener('click', e => {
      const courseId = e.target.dataset.id;
      const currentCard = document.querySelector(`.course-card[data-id="${courseId}"]`);
      const currentCollapse = bootstrap.Collapse.getInstance(document.getElementById(`collapse${courseId}`));

      // 请求后端标记课程完成
      fetch("{{ route('courses.finish', '') }}/" + courseId, {
        method: 'POST',
        headers: {
          'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
          'Content-Type': 'application/json'
        }
      }).then(res => res.json()).then(res => {
        if (!res.success) return;
        // 折叠当前已完成面板
        currentCollapse.hide();
        // 新增已完成标记
        currentCard.dataset.isCompleted = '1';
        currentCard.querySelector('.accordion-button').insertAdjacentHTML('beforeend', '<span class="ms-2 badge bg-success">已完成</span>');
        // 查找下一个未完成的课程并展开
        const nextCard = currentCard.nextElementSibling;
        if (nextCard && nextCard.dataset.isCompleted == '0') {
          new bootstrap.Collapse(document.getElementById(`collapse${nextCard.dataset.id}`), { toggle: true });
        }
      })
    })
  })
})

内容的提问来源于stack exchange,提问作者Hasib Shanto

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.09 01:51:01