Skip to content

在线课程

使用 VitePress 构建现代化的在线课程平台,为学习者提供完整的交互式学习体验。

项目概述

在线教育已成为现代学习的重要方式,一个好的在线课程平台需要提供丰富的学习内容、良好的用户体验和完善的学习跟踪功能。VitePress 凭借其强大的内容管理能力和灵活的扩展性,是构建在线课程平台的理想选择。

核心特性

  • 🎥 视频学习 - 高质量的视频播放和字幕支持
  • 📚 课程章节 - 结构化的课程内容组织
  • 练习题库 - 丰富的练习题和即时反馈
  • 📊 学习进度 - 详细的学习进度跟踪
  • 🏆 成就系统 - 学习徽章和证书颁发
  • 💬 讨论区 - 学习者交流和答疑
  • 📱 移动学习 - 完美的移动端学习体验
  • 🔐 用户管理 - 完整的用户注册和权限系统
  • 📈 数据分析 - 学习行为和效果分析
  • 🌍 多语言 - 国际化课程内容支持

技术架构

核心技术栈

json
{
  "frontend": "VitePress + Vue 3",
  "video": "Video.js / Plyr",
  "auth": "Firebase Auth / Auth0",
  "database": "Supabase / Firebase",
  "payment": "Stripe / PayPal",
  "analytics": "Google Analytics",
  "deployment": [
    "Vercel",
    "Netlify",
    "AWS Amplify"
  ]
}

项目结构

online-course/
├── docs/
│   ├── .vitepress/
│   │   ├── config.ts
│   │   ├── theme/
│   │   │   ├── index.ts
│   │   │   ├── Layout.vue
│   │   │   └── components/
│   │   │       ├── VideoPlayer.vue
│   │   │       ├── QuizComponent.vue
│   │   │       ├── ProgressTracker.vue
│   │   │       └── DiscussionBoard.vue
│   │   └── public/
│   ├── courses/
│   │   ├── web-development/
│   │   ├── data-science/
│   │   ├── mobile-development/
│   │   └── design/
│   ├── dashboard/
│   ├── profile/
│   └── index.md
├── api/
├── scripts/
├── package.json
└── README.md

实现步骤

1. 项目初始化

bash
# 创建项目
npm create vitepress@latest online-course
cd online-course

# 安装依赖
npm install
npm install -D video.js @supabase/supabase-js stripe

# 启动开发服务器
npm run docs:dev

2. 基础配置

typescript
// .vitepress/config.ts
import { defineConfig } from 'vitepress'

export default defineConfig({
  title: '在线课程平台',
  description: '专业的在线学习平台',
  
  lang: 'zh-CN',
  base: '/',
  cleanUrls: true,
  
  head: [
    ['link', { rel: 'icon', href: '/favicon.ico' }],
    ['meta', { name: 'theme-color', content: '#f59e0b' }],
    ['meta', { name: 'og:type', content: 'website' }],
    ['meta', { name: 'og:locale', content: 'zh-CN' }],
    ['meta', { name: 'og:site_name', content: '在线课程平台' }],
    // Video.js CSS
    ['link', { rel: 'stylesheet', href: 'https://vjs.zencdn.net/8.0.4/video-js.css' }],
    // Video.js JavaScript
    ['script', { src: 'https://vjs.zencdn.net/8.0.4/video.min.js' }],
    // 支付系统
    ['script', { src: 'https://js.stripe.com/v3/' }],
    // 分析工具
    ['script', { async: '', src: 'https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID' }]
  ],
  
  themeConfig: {
    logo: '/logo.svg',
    siteTitle: '在线课程',
    
    nav: [
      { text: '首页', link: '/' },
      { text: '课程', link: '/courses/' },
      { text: '我的学习', link: '/dashboard/' },
      { text: '讨论区', link: '/discussions/' },
      {
        text: '更多',
        items: [
          { text: '关于我们', link: '/about' },
          { text: '帮助中心', link: '/help' },
          { text: '联系我们', link: '/contact' }
        ]
      }
    ],
    
    sidebar: {
      '/courses/': [
        {
          text: 'Web 开发',
          collapsed: false,
          items: [
            { text: 'HTML/CSS 基础', link: '/courses/web-development/html-css' },
            { text: 'JavaScript 入门', link: '/courses/web-development/javascript' },
            { text: 'Vue.js 实战', link: '/courses/web-development/vue' },
            { text: 'React 开发', link: '/courses/web-development/react' }
          ]
        },
        {
          text: '数据科学',
          collapsed: false,
          items: [
            { text: 'Python 基础', link: '/courses/data-science/python' },
            { text: '数据分析', link: '/courses/data-science/analysis' },
            { text: '机器学习', link: '/courses/data-science/ml' },
            { text: '深度学习', link: '/courses/data-science/dl' }
          ]
        },
        {
          text: '移动开发',
          collapsed: false,
          items: [
            { text: 'React Native', link: '/courses/mobile-development/react-native' },
            { text: 'Flutter', link: '/courses/mobile-development/flutter' },
            { text: 'iOS 开发', link: '/courses/mobile-development/ios' },
            { text: 'Android 开发', link: '/courses/mobile-development/android' }
          ]
        },
        {
          text: '设计课程',
          collapsed: false,
          items: [
            { text: 'UI/UX 设计', link: '/courses/design/ui-ux' },
            { text: '平面设计', link: '/courses/design/graphic' },
            { text: '交互设计', link: '/courses/design/interaction' }
          ]
        }
      ]
    },
    
    socialLinks: [
      { icon: 'github', link: 'https://github.com/your-org' },
      { icon: 'youtube', link: 'https://youtube.com/your-channel' }
    ],
    
    footer: {
      message: '让学习更简单,让成长更快速',
      copyright: 'Copyright © 2024 在线课程平台'
    },
    
    search: {
      provider: 'local',
      options: {
        locales: {
          zh: {
            translations: {
              button: {
                buttonText: '搜索课程',
                buttonAriaLabel: '搜索课程'
              },
              modal: {
                noResultsText: '无法找到相关课程',
                resetButtonTitle: '清除查询条件',
                footer: {
                  selectText: '选择',
                  navigateText: '切换'
                }
              }
            }
          }
        }
      }
    }
  },
  
  markdown: {
    theme: {
      light: 'github-light',
      dark: 'github-dark'
    },
    config: (md) => {
      // 自定义容器
      md.use(require('markdown-it-container'), 'lesson')
      md.use(require('markdown-it-container'), 'quiz')
      md.use(require('markdown-it-container'), 'exercise')
    }
  }
})

3. 首页设计

vue
<!-- docs/index.md -->
---
layout: home
title: 在线课程平台
titleTemplate: 专业的在线学习平台

hero:
  name: 在线课程
  text: 开启你的学习之旅
  tagline: 专业课程,实战项目,助你快速成长
  image:
    src: /hero-learning.svg
    alt: 在线学习
  actions:
    - theme: brand
      text: 开始学习
      link: /courses/
    - theme: alt
      text: 免费试听
      link: /free-trial

features:
  - icon: 🎥
    title: 高质量视频
    details: 专业制作的高清视频课程,支持多种播放速度
    link: /courses/
  - icon: 📚
    title: 结构化学习
    details: 系统化的课程设计,从基础到进阶循序渐进
    link: /courses/
  - icon: ✅
    title: 实战练习
    details: 丰富的练习题和项目实战,巩固学习效果
    link: /courses/
  - icon: 📊
    title: 学习跟踪
    details: 详细的学习进度和成绩统计,了解学习状况
    link: /dashboard/
  - icon: 🏆
    title: 认证证书
    details: 完成课程获得专业认证,提升职业竞争力
    link: /certificates/
  - icon: 💬
    title: 社区讨论
    details: 与同学和老师互动交流,解决学习疑问
    link: /discussions/
---

<script setup>
import { ref, onMounted } from 'vue'

const stats = ref({
  totalCourses: 0,
  totalStudents: 0,
  totalHours: 0,
  completionRate: 0
})

const featuredCourses = ref([])

onMounted(async () => {
  // 获取平台统计数据
  try {
    const [statsResponse, coursesResponse] = await Promise.all([
      fetch('/api/stats'),
      fetch('/api/featured-courses')
    ])
    
    stats.value = await statsResponse.json()
    featuredCourses.value = await coursesResponse.json()
  } catch (error) {
    console.error('Failed to load data:', error)
  }
})
</script>

<div class="platform-stats">
  <div class="stats-container">
    <div class="stat-item">
      <div class="stat-number">{{ stats.totalCourses }}+</div>
      <div class="stat-label">精品课程</div>
    </div>
    <div class="stat-item">
      <div class="stat-number">{{ stats.totalStudents }}+</div>
      <div class="stat-label">学习者</div>
    </div>
    <div class="stat-item">
      <div class="stat-number">{{ stats.totalHours }}+</div>
      <div class="stat-label">课程时长</div>
    </div>
    <div class="stat-item">
      <div class="stat-number">{{ stats.completionRate }}%</div>
      <div class="stat-label">完成率</div>
    </div>
  </div>
</div>

<div class="featured-courses">
  <h2>热门课程</h2>
  <div class="courses-grid">
    <div
      v-for="course in featuredCourses"
      :key="course.id"
      class="course-card"
    >
      <div class="course-image">
        <img :src="course.thumbnail" :alt="course.title">
        <div class="course-duration">{{ course.duration }}</div>
      </div>
      <div class="course-content">
        <h3>{{ course.title }}</h3>
        <p>{{ course.description }}</p>
        <div class="course-meta">
          <span class="instructor">{{ course.instructor }}</span>
          <span class="rating">⭐ {{ course.rating }}</span>
        </div>
        <div class="course-price">
          <span v-if="course.originalPrice" class="original-price">¥{{ course.originalPrice }}</span>
          <span class="current-price">¥{{ course.price }}</span>
        </div>
      </div>
    </div>
  </div>
</div>

<style>
:root {
  --vp-home-hero-name-color: transparent;
  --vp-home-hero-name-background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
  --vp-home-hero-image-background-image: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
  --vp-home-hero-image-filter: blur(44px);
}

.platform-stats {
  margin: 48px 0;
  padding: 40px 0;
  background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
  border-radius: 16px;
  color: white;
}

.stats-container {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 32px;
  max-width: 800px;
  margin: 0 auto;
  padding: 0 24px;
}

.stat-item {
  text-align: center;
}

.stat-number {
  font-size: 36px;
  font-weight: 700;
  margin-bottom: 8px;
}

.stat-label {
  font-size: 16px;
  opacity: 0.9;
}

.featured-courses {
  margin: 64px 0;
}

.featured-courses h2 {
  text-align: center;
  margin-bottom: 32px;
  font-size: 32px;
  color: var(--vp-c-text-1);
}

.courses-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 24px;
  max-width: 1200px;
  margin: 0 auto;
}

.course-card {
  background: var(--vp-c-bg);
  border: 1px solid var(--vp-c-border);
  border-radius: 12px;
  overflow: hidden;
  transition: all 0.2s ease;
  cursor: pointer;
}

.course-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 12px 32px rgba(245, 158, 11, 0.15);
  border-color: var(--vp-c-brand);
}

.course-image {
  position: relative;
  aspect-ratio: 16/9;
  overflow: hidden;
}

.course-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.course-duration {
  position: absolute;
  bottom: 8px;
  right: 8px;
  background: rgba(0, 0, 0, 0.8);
  color: white;
  padding: 4px 8px;
  border-radius: 4px;
  font-size: 12px;
}

.course-content {
  padding: 20px;
}

.course-content h3 {
  margin: 0 0 8px 0;
  font-size: 18px;
  color: var(--vp-c-text-1);
}

.course-content p {
  margin: 0 0 12px 0;
  color: var(--vp-c-text-2);
  font-size: 14px;
  line-height: 1.5;
}

.course-meta {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
  font-size: 12px;
}

.instructor {
  color: var(--vp-c-text-2);
}

.rating {
  color: var(--vp-c-brand);
  font-weight: 500;
}

.course-price {
  display: flex;
  align-items: center;
  gap: 8px;
}

.original-price {
  text-decoration: line-through;
  color: var(--vp-c-text-3);
  font-size: 14px;
}

.current-price {
  color: var(--vp-c-brand);
  font-size: 18px;
  font-weight: 600;
}

.VPFeature {
  transition: all 0.2s ease;
}

.VPFeature:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 25px rgba(245, 158, 11, 0.15);
}

@media (max-width: 768px) {
  .stats-container {
    grid-template-columns: repeat(2, 1fr);
    gap: 20px;
  }
  
  .stat-number {
    font-size: 28px;
  }
  
  .courses-grid {
    grid-template-columns: 1fr;
  }
}
</style>

4. 视频播放器组件

vue
<!-- .vitepress/theme/components/VideoPlayer.vue -->
<template>
  <div class="video-player-container">
    <div class="video-header">
      <h3>{{ lesson.title }}</h3>
      <div class="video-controls">
        <button @click="togglePlaybackSpeed" class="speed-btn">
          {{ playbackSpeed }}x
        </button>
        <button @click="toggleFullscreen" class="fullscreen-btn">
          <FullscreenIcon />
        </button>
      </div>
    </div>
    
    <div class="video-wrapper">
      <video
        ref="videoRef"
        class="video-js vjs-default-skin"
        controls
        preload="auto"
        :poster="lesson.poster"
        data-setup="{}"
      >
        <source :src="lesson.videoUrl" type="video/mp4">
        <track
          v-if="lesson.subtitles"
          kind="subtitles"
          :src="lesson.subtitles"
          srclang="zh"
          label="中文"
          default
        >
        <p class="vjs-no-js">
          要观看此视频,请启用 JavaScript,并考虑升级到
          <a href="https://videojs.com/html5-video-support/" target="_blank">
            支持HTML5视频的网络浏览器
          </a>。
        </p>
      </video>
      
      <div v-if="showNotes" class="video-notes">
        <div class="notes-header">
          <h4>课程笔记</h4>
          <button @click="showNotes = false" class="close-btn">×</button>
        </div>
        <div class="notes-content">
          <textarea
            v-model="userNotes"
            placeholder="在这里记录你的学习笔记..."
            @input="saveNotes"
          ></textarea>
        </div>
      </div>
    </div>
    
    <div class="video-footer">
      <div class="progress-info">
        <span>进度: {{ Math.round(watchProgress) }}%</span>
        <div class="progress-bar">
          <div class="progress-fill" :style="{ width: watchProgress + '%' }"></div>
        </div>
      </div>
      
      <div class="video-actions">
        <button @click="showNotes = !showNotes" class="notes-btn">
          <NotesIcon />
          笔记
        </button>
        <button @click="markAsCompleted" class="complete-btn" :disabled="!canMarkComplete">
          <CheckIcon />
          标记完成
        </button>
      </div>
    </div>
    
    <div class="lesson-navigation">
      <button
        v-if="previousLesson"
        @click="navigateToLesson(previousLesson)"
        class="nav-btn prev-btn"
      >
        ← 上一课: {{ previousLesson.title }}
      </button>
      
      <button
        v-if="nextLesson"
        @click="navigateToLesson(nextLesson)"
        class="nav-btn next-btn"
      >
        下一课: {{ nextLesson.title }} →
      </button>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted, watch } from 'vue'
import { useRouter } from 'vitepress'
import videojs from 'video.js'
import FullscreenIcon from './FullscreenIcon.vue'
import NotesIcon from './NotesIcon.vue'
import CheckIcon from './CheckIcon.vue'

const props = defineProps({
  lesson: {
    type: Object,
    required: true
  },
  previousLesson: Object,
  nextLesson: Object
})

const router = useRouter()
const videoRef = ref()
const showNotes = ref(false)
const userNotes = ref('')
const watchProgress = ref(0)
const playbackSpeed = ref(1)
const canMarkComplete = ref(false)

let player = null
let progressInterval = null

onMounted(() => {
  initializePlayer()
  loadUserNotes()
  loadWatchProgress()
})

onUnmounted(() => {
  if (player) {
    player.dispose()
  }
  if (progressInterval) {
    clearInterval(progressInterval)
  }
})

watch(() => props.lesson, (newLesson) => {
  if (player && newLesson) {
    player.src({ src: newLesson.videoUrl, type: 'video/mp4' })
    player.poster(newLesson.poster)
    loadUserNotes()
    loadWatchProgress()
  }
})

function initializePlayer() {
  player = videojs(videoRef.value, {
    fluid: true,
    responsive: true,
    playbackRates: [0.5, 1, 1.25, 1.5, 2],
    plugins: {
      hotkeys: {
        volumeStep: 0.1,
        seekStep: 5,
        enableModifiersForNumbers: false
      }
    }
  })
  
  player.ready(() => {
    // 监听播放进度
    progressInterval = setInterval(() => {
      if (player && !player.paused()) {
        const currentTime = player.currentTime()
        const duration = player.duration()
        
        if (duration > 0) {
          const progress = (currentTime / duration) * 100
          watchProgress.value = progress
          
          // 观看超过80%可以标记完成
          canMarkComplete.value = progress >= 80
          
          // 保存观看进度
          saveWatchProgress(progress)
        }
      }
    }, 1000)
  })
  
  // 监听播放速度变化
  player.on('ratechange', () => {
    playbackSpeed.value = player.playbackRate()
  })
  
  // 监听全屏变化
  player.on('fullscreenchange', () => {
    // 处理全屏状态变化
  })
}

function togglePlaybackSpeed() {
  const speeds = [0.5, 1, 1.25, 1.5, 2]
  const currentIndex = speeds.indexOf(playbackSpeed.value)
  const nextIndex = (currentIndex + 1) % speeds.length
  const newSpeed = speeds[nextIndex]
  
  player.playbackRate(newSpeed)
  playbackSpeed.value = newSpeed
}

function toggleFullscreen() {
  if (player.isFullscreen()) {
    player.exitFullscreen()
  } else {
    player.requestFullscreen()
  }
}

async function loadUserNotes() {
  try {
    const response = await fetch(`/api/lessons/${props.lesson.id}/notes`)
    const data = await response.json()
    userNotes.value = data.notes || ''
  } catch (error) {
    console.error('Failed to load notes:', error)
  }
}

async function saveNotes() {
  try {
    await fetch(`/api/lessons/${props.lesson.id}/notes`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        notes: userNotes.value
      })
    })
  } catch (error) {
    console.error('Failed to save notes:', error)
  }
}

async function loadWatchProgress() {
  try {
    const response = await fetch(`/api/lessons/${props.lesson.id}/progress`)
    const data = await response.json()
    watchProgress.value = data.progress || 0
    canMarkComplete.value = watchProgress.value >= 80
  } catch (error) {
    console.error('Failed to load progress:', error)
  }
}

async function saveWatchProgress(progress) {
  try {
    await fetch(`/api/lessons/${props.lesson.id}/progress`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        progress: progress,
        timestamp: new Date().toISOString()
      })
    })
  } catch (error) {
    console.error('Failed to save progress:', error)
  }
}

async function markAsCompleted() {
  if (!canMarkComplete.value) return
  
  try {
    await fetch(`/api/lessons/${props.lesson.id}/complete`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        completedAt: new Date().toISOString()
      })
    })
    
    // 显示完成提示
    alert('恭喜!你已完成本课学习')
    
    // 记录学习事件
    if (typeof gtag !== 'undefined') {
      gtag('event', 'lesson_completed', {
        event_category: 'learning',
        event_label: props.lesson.id,
        value: 1
      })
    }
  } catch (error) {
    console.error('Failed to mark as completed:', error)
  }
}

function navigateToLesson(lesson) {
  router.go(lesson.url)
}
</script>

<style scoped>
.video-player-container {
  background: var(--vp-c-bg);
  border: 1px solid var(--vp-c-border);
  border-radius: 12px;
  overflow: hidden;
  margin: 24px 0;
}

.video-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 16px 20px;
  background: var(--vp-c-bg-soft);
  border-bottom: 1px solid var(--vp-c-border);
}

.video-header h3 {
  margin: 0;
  font-size: 18px;
  color: var(--vp-c-text-1);
}

.video-controls {
  display: flex;
  gap: 8px;
}

.speed-btn,
.fullscreen-btn {
  padding: 6px 12px;
  background: var(--vp-c-bg);
  border: 1px solid var(--vp-c-border);
  border-radius: 6px;
  color: var(--vp-c-text-2);
  cursor: pointer;
  transition: all 0.2s;
  font-size: 12px;
}

.speed-btn:hover,
.fullscreen-btn:hover {
  background: var(--vp-c-bg-mute);
  color: var(--vp-c-text-1);
}

.video-wrapper {
  position: relative;
}

.video-js {
  width: 100%;
  height: auto;
}

.video-notes {
  position: absolute;
  top: 20px;
  right: 20px;
  width: 300px;
  background: var(--vp-c-bg);
  border: 1px solid var(--vp-c-border);
  border-radius: 8px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
  z-index: 10;
}

.notes-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 12px 16px;
  backgroun
# Online Course

本文档正在建设中,敬请期待。

## 概述

这里将提供关于 Online Course 的详细信息和指导。

## 主要内容

- 基础概念介绍
- 使用方法说明
- 最佳实践建议
- 常见问题解答

## 相关资源

- [VitePress 官方文档](https://vitepress.dev/)
- [Vue.js 官方文档](https://vuejs.org/)
- [更多教程](../tutorials/index)

---

*本文档将持续更新,如有问题请通过 [GitHub Issues](https://github.com/shingle666) 反馈。*

vitepress开发指南