发布日期:2024年9月10日
一、平台架构设计
本教程将构建一个完整的在线教育平台,包含以下核心模块:
- 直播系统:WebRTC实时互动
- 课程管理:多媒体课程展示
- 学习系统:进度跟踪与笔记
- 用户中心:多角色权限控制
- 支付系统:课程购买与订阅
技术栈:Vue2 + Vuex + ElementUI + WebRTC + WebSocket
二、项目初始化与工程化
1. 项目创建与配置
# 使用Vue CLI创建项目
vue create edu-platform
# 安装核心依赖
cd edu-platform
npm install vuex element-ui socket.io-client webrtc-adapter
npm install -D sass sass-loader
2. 目录结构规划
src/
├── api/ # 接口服务
├── components/ # 公共组件
│ ├── live/ # 直播组件
│ └── course/ # 课程组件
├── directives/ # 自定义指令
├── filters/ # 全局过滤器
├── mixins/ # 混入对象
├── router/ # 路由配置
├── store/ # Vuex状态
│ ├── modules/ # 模块化状态
│ └── index.js # 主入口
├── styles/ # 全局样式
├── utils/ # 工具函数
├── views/ # 页面组件
│ ├── live/ # 直播页面
│ └── course/ # 课程页面
├── App.vue
└── main.js
三、WebRTC直播系统
1. 直播房间组件
<template>
<div class="live-room">
<div class="video-container">
<video ref="localVideo" autoplay muted></video>
<video ref="remoteVideo" autoplay></video>
</div>
<div class="controls">
<el-button @click="toggleCamera">{{ cameraOn ? '关闭' : '开启' }}摄像头</el-button>
<el-button @click="toggleMic">{{ micOn ? '静音' : '取消静音' }}</el-button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
cameraOn: false,
micOn: false,
peerConnection: null,
localStream: null
}
},
methods: {
async initStream() {
try {
this.localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
this.$refs.localVideo.srcObject = this.localStream;
this.cameraOn = true;
this.micOn = true;
} catch (err) {
console.error('获取媒体设备失败:', err);
}
},
toggleCamera() {
if (this.localStream) {
this.localStream.getVideoTracks()[0].enabled = !this.cameraOn;
this.cameraOn = !this.cameraOn;
}
}
},
mounted() {
this.initStream();
this.initWebSocket();
}
}
</script>
2. 信令服务交互
// utils/socket.js
import io from 'socket.io-client';
class SocketService {
constructor() {
this.socket = null;
}
connect(token) {
this.socket = io(process.env.VUE_APP_SOCKET_URL, {
auth: { token }
});
this.socket.on('connect', () => {
console.log('Socket connected');
});
this.socket.on('disconnect', () => {
console.log('Socket disconnected');
});
}
joinRoom(roomId) {
this.socket.emit('join', { roomId });
}
on(event, callback) {
this.socket.on(event, callback);
}
}
export const socket = new SocketService();
四、课程管理系统
1. 课程树形组件
<template>
<div class="course-tree">
<el-tree
:data="chapters"
node-key="id"
:props="defaultProps"
:expand-on-click-node="false"
@node-click="handleNodeClick"
>
<span slot-scope="{ node, data }" class="tree-node">
<span>{{ node.label }}</span>
<span v-if="data.duration" class="duration">{{ formatDuration(data.duration) }}</span>
</span>
</el-tree>
</div>
</template>
<script>
export default {
props: {
chapters: {
type: Array,
required: true
}
},
data() {
return {
defaultProps: {
children: 'sections',
label: 'title'
}
}
},
methods: {
handleNodeClick(data) {
if (data.videoUrl) {
this.$emit('play', data);
}
}
}
}
</script>
2. 视频播放器封装
<template>
<div class="video-player">
<video
ref="videoPlayer"
:src="videoUrl"
@timeupdate="handleTimeUpdate"
@ended="handleVideoEnd"
></video>
<div class="controls">
<el-slider
v-model="currentTime"
:max="duration"
@change="seekTo"
></el-slider>
<div class="actions">
<el-button @click="togglePlay">{{ playing ? '暂停' : '播放' }}</el-button>
<span class="time">{{ formatTime(currentTime) }} / {{ formatTime(duration) }}</span>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
videoUrl: String
},
data() {
return {
playing: false,
currentTime: 0,
duration: 0
}
},
methods: {
togglePlay() {
if (this.playing) {
this.$refs.videoPlayer.pause();
} else {
this.$refs.videoPlayer.play();
}
this.playing = !this.playing;
},
seekTo(time) {
this.$refs.videoPlayer.currentTime = time;
}
}
}
</script>
五、学习进度跟踪
1. 进度状态管理
// store/modules/progress.js
const state = {
courseProgress: {}
};
const mutations = {
UPDATE_PROGRESS(state, { courseId, sectionId, progress }) {
if (!state.courseProgress[courseId]) {
state.courseProgress[courseId] = {};
}
state.courseProgress[courseId][sectionId] = progress;
}
};
const actions = {
saveProgress({ commit }, payload) {
return api.saveProgress(payload).then(() => {
commit('UPDATE_PROGRESS', payload);
});
}
};
export default {
namespaced: true,
state,
mutations,
actions
};
2. 进度可视化组件
<template>
<div class="progress-tracker">
<div class="progress-bar">
<div
class="progress"
:style="{ width: `${percentage}%` }"
></div>
</div>
<div class="stats">
<span>已完成 {{ completedCount }} / {{ totalCount }} 节</span>
<span>{{ percentage }}%</span>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
props: {
courseId: {
type: String,
required: true
}
},
computed: {
...mapGetters('progress', ['getCourseProgress']),
progressData() {
return this.getCourseProgress(this.courseId) || {};
},
completedCount() {
return Object.values(this.progressData).filter(p => p >= 0.9).length;
},
totalCount() {
return Object.keys(this.progressData).length;
},
percentage() {
return this.totalCount > 0
? Math.round((this.completedCount / this.totalCount) * 100)
: 0;
}
}
}
</script>
六、多角色权限控制
1. 动态路由配置
// router/index.js
import Vue from 'vue';
import Router from 'vue-router';
import { getToken } from '@/utils/auth';
Vue.use(Router);
const createRouter = () => new Router({
mode: 'history',
routes: [
{
path: '/login',
component: () => import('@/views/Login.vue'),
meta: { public: true }
},
{
path: '/',
component: () => import('@/layouts/MainLayout.vue'),
children: [
// 公共路由
{
path: '',
component: () => import('@/views/Home.vue')
}
]
}
]
});
const router = createRouter();
// 动态添加路由
export function addRoutes(routes) {
const mainRoute = router.options.routes.find(r => r.path === '/');
routes.forEach(route => {
mainRoute.children.push(route);
});
router.addRoutes([mainRoute]);
}
// 路由守卫
router.beforeEach((to, from, next) => {
const isPublic = to.matched.some(record => record.meta.public);
const isAuthenticated = getToken();
if (!isPublic && !isAuthenticated) {
return next('/login');
}
if (to.path === '/login' && isAuthenticated) {
return next('/');
}
next();
});
export default router;
2. 权限指令实现
// directives/permission.js
import store from '@/store';
function checkPermission(el, binding) {
const { value } = binding;
const roles = store.getters && store.getters.roles;
if (value && value instanceof Array) {
if (value.length > 0) {
const hasPermission = roles.some(role => {
return value.includes(role);
});
if (!hasPermission) {
el.parentNode && el.parentNode.removeChild(el);
}
}
} else {
throw new Error(`需要指定权限数组,如 v-permission="['admin']"`);
}
}
export default {
inserted(el, binding) {
checkPermission(el, binding);
},
update(el, binding) {
checkPermission(el, binding);
}
};
七、支付系统集成
1. 支付组件实现
<template>
<div class="payment-container">
<el-radio-group v-model="paymentMethod">
<el-radio label="wechat">微信支付</el-radio>
<el-radio label="alipay">支付宝</el-radio>
</el-radio-group>
<div v-if="paymentMethod === 'wechat'" class="qrcode-wrapper">
<div ref="qrcode"></div>
<p>请使用微信扫码支付</p>
</div>
<el-button type="primary" @click="handleSubmit">确认支付</el-button>
</div>
</template>
<script>
import QRCode from 'qrcodejs2';
export default {
props: {
orderId: {
type: String,
required: true
},
amount: {
type: Number,
required: true
}
},
data() {
return {
paymentMethod: 'wechat',
qrcode: null
}
},
methods: {
initQRCode(url) {
this.$refs.qrcode.innerHTML = '';
this.qrcode = new QRCode(this.$refs.qrcode, {
text: url,
width: 200,
height: 200
});
},
async handleSubmit() {
try {
const res = await this.$api.createPayment({
orderId: this.orderId,
amount: this.amount,
method: this.paymentMethod
});
if (this.paymentMethod === 'wechat') {
this.initQRCode(res.qrcodeUrl);
this.checkPaymentStatus();
} else {
window.location.href = res.paymentUrl;
}
} catch (err) {
this.$message.error(err.message);
}
}
}
}
</script>
八、总结与扩展
通过本教程,您已经掌握了:
- WebRTC直播系统开发
- 课程管理系统实现
- 学习进度跟踪技术
- 多角色权限控制方案
- 支付系统集成方法
扩展学习方向:
- 移动端适配优化
- SSR服务端渲染
- 微前端架构改造
- 大数据可视化分析