在当前数字化教育浪潮中,构建一个多分校网校平台不仅能有效整合教学资源,还能提升教学效率,实现教育资源的共享与优化。本文将深入探讨视频课程在网校平台中的技术实现,帮助教育机构构建高效、稳定的多分校网校平台。
1. 平台架构设计
一个高效的多分校网校平台需要合理的架构设计,通常包括以下几个部分:
- 前端:负责用户交互,包括视频播放、互动功能等。
- 后端:负责业务逻辑处理,包括视频管理、用户管理等。
- 数据库:存储用户信息、课程信息、学习记录等。
- 云服务:提供视频存储和分发服务。
2. 前端开发:
使用Vue.js构建现代化的前端应用。创建一个简单的视频信息展示页面:
<template>
<div>
<a-card :bordered="false" v-show="showList">
<a-row>
<a-col :span="12">
<span style="font-weight: 600; font-style: normal; font-size: 20px; color: #000000D8">
视频课程
</span>
</a-col>
<a-col :span="12" >
<a-button @click="addCourse" type="primary" style="float: right">新建课程</a-button>
</a-col>
</a-row>
<div >
<!-- 专业科目 -->
<MajorList @changeMajor="changeMajor"></MajorList>
<a-divider orientation="left">
</a-divider>
<!-- 查询区域 -->
<div class="table-page-search-wrapper" >
<a-form layout="inline" @keyup.enter.native="searchQuery">
<a-row :gutter="24">
<a-col :xl="6" :lg="7" :md="8" :sm="24">
<a-form-item label="课程名称">
<j-input placeholder="请输入课程名称" v-model="queryParam.courseVideoName"></j-input>
</a-form-item>
</a-col>
<!-- <a-col :xl="6" :lg="7" :md="8" :sm="24">-->
<!-- <a-form-item label="学年">-->
<!-- <a-input placeholder="请输入学年" v-model="queryParam.schoolYear"></a-input>-->
<!-- </a-form-item>-->
<!-- </a-col>-->
<a-col :xl="4" :lg="7" :md="8" :sm="24">
<a-form-item label="学年">
<a-date-picker
mode="year"
placeholder="请选择年份"
format='YYYY'
v-model="queryParam.yearValue"
:open='yearShowOne'
@openChange="openChangeOne"
@panelChange="panelChangeOne"
@change="change"
/>
</a-form-item>
</a-col>
<template>
<a-col :xl="6" :lg="7" :md="8" :sm="24">
<a-form-item label="发布状态">
<j-dict-select-tag placeholder="请选择发布状态" v-model="queryParam.postStatus" dictCode="post_status"/>
</a-form-item>
</a-col>
</template>
<a-col :xl="6" :lg="7" :md="8" :sm="24">
<span style="overflow: hidden;" class="table-page-search-submitButtons">
<a-button type="primary" @click="searchQuery1" icon="search">查询</a-button>
<a-button @click="searchReset" icon="reload" style="margin-left: 8px">重置</a-button>
<a-button @click="checkVideoAll" icon="reload" style="float: right">一键共用</a-button>
</span>
</a-col>
</a-row>
</a-form>
</div>
<!-- 查询区域-END -->
<!-- table区域-begin -->
<div>
<!-- <div class="ant-alert ant-alert-info" style="margin-bottom: 16px;">-->
<!-- <i class="anticon anticon-info-circle ant-alert-icon"></i> 已选择 <a style="font-weight: 600">{{ selectedRowKeys.length }}</a>项-->
<!-- <a style="margin-left: 24px" @click="onClearSelected">清空</a>-->
<!-- </div>-->
<a-table
ref="table"
size="middle"
:scroll="{x:true}"
bordered
rowKey="id"
:columns="columns"
:dataSource="dataSource"
:pagination="pagination"
:loading="loading"
class="j-table-force-nowrap"
@change="handleTableChange">
<template slot="htmlSlot" slot-scope="text">
<div v-html="text"></div>
</template>
<template slot="courseType" slot-scope="text">
<div v-if="text == 1">平台</div>
<div v-if="text == 2">普为</div>
</template>
<template slot="imgSlot" slot-scope="text">
<span v-if="!text" style="font-size: 12px;font-style: italic;">无图片</span>
<img v-else :src="getImgView(text)" height="25px" alt="" style="max-width:80px;font-size: 12px;font-style: italic;"/>
</template>
<template slot=learningPhaseSlot slot-scope="text">
<a-tag v-if = "text=='导学'" color="cyan" > {{text}}</a-tag>
<a-tag v-else-if="text=='基础'" color="green" > {{text}}</a-tag>
<a-tag v-else color="blue">
{{text}}
</a-tag>
</template>
<template slot=postStatusSlot slot-scope="text">
<a-tag v-if = "text=='已发布'" color="blue" > {{text}}</a-tag>
<a-tag v-else = "text=='未发布'" color="gray" > {{text}}</a-tag>
</template>
<template slot="fileSlot" slot-scope="text">
<span v-if="!text" style="font-size: 12px;font-style: italic;">无文件</span>
<a-button
v-else
:ghost="true"
type="primary"
icon="download"
size="small"
@click="downloadFile(text)">
下载
</a-button>
</template>
<span slot="action" slot-scope="text,record">
<a @click="editCourse(record)" >管理</a>
<a-divider type="vertical" />
<a @click="updateCourse(record)" v-if="record.postStatus==0">发布</a>
<a @click="updateCourse2(record)" v-if="record.postStatus==1">取消发布</a>
<a-divider type="vertical" />
<a-popconfirm title="确定删除吗?" @confirm="() => handleDelete(record.id)">
<a>删除</a>
</a-popconfirm>
<!-- <a-divider type="vertical" />-->
<!-- <a-dropdown>-->
<!-- <a class="ant-dropdown-link">更多 <a-icon type="down" /></a>-->
<!-- <a-menu slot="overlay">-->
<!-- <a-menu-item>-->
<!-- <a @click="handleDetail(record)">详情</a>-->
<!-- </a-menu-item>-->
<!-- <a-menu-item>-->
<!-- <a-popconfirm title="确定删除吗?" @confirm="() => handleDelete(record.id)">-->
<!-- <a>删除</a>-->
<!-- </a-popconfirm>-->
<!-- </a-menu-item>-->
<!-- </a-menu>-->
<!-- </a-dropdown>-->
</span>
</a-table>
</div>
</div>
</a-card>
<wd-course-form ref="WdCourseFormRef" v-show="wdCourseFormVisible" :courseId="courseId" :majorId="majorId" :templateId="templateId" @goback="goback"></wd-course-form>
<a-modal v-model="visibleVideo"
cancelText="关闭"
:closable="!copySpin"
:keyboard="!copySpin"
:maskClosable="!copySpin"
width="800px"
>
<template solt="title">
<p style="font-size: x-large">共用内容</p>
<p style="color: red">点击复制后程序会在后台进行运行,可自行刷新查看!</p>
<a-divider />
</template>
<a-spin :spinning="copySpin">
<div>
<a-row>
<a-col :span="24">
<a-cascader @change="majorChange" style="width: 100%" v-model="sourceMajorId" :display-render="displayRender" :options="options" :show-search="{ filter }" placeholder="请选择" />
</a-col>
<a-col :span="24">
<div style="height: 20px; width: 100%; float: left;">
<div slot="header" >
<div style="align-items: center;width: 10%; float: left;">
<a-checkbox :checked="checkAll" @change="onCheckAllChange"></a-checkbox>
</div>
<div style="width: 70%; text-align: center; float: left;">
课程名称
</div>
<div style="width: 20%; text-align: center; float: left;">
年份
</div>
</div>
</div>
<div style="height: 400px; width: 100%;overflow: auto;float: left;">
<a-list :data-source="videoListAll">
<a-list-item slot="renderItem" slot-scope="item, index">
<div style="align-items: center;width: 10%;">
<a-checkbox :value="item.id" :checked="checkAllList.indexOf(item.id) > -1" @change="listCheckbox"></a-checkbox>
</div>
<div style="text-align: center;width: 70%;">{{item.courseVideoName}}</div>
<div style="text-align: center;width: 20%;">{{item.schoolYear}}</div>
</a-list-item>
<div v-if="false" class="demo-loading-container">
<a-spin />
</div>
</a-list>
</div>
</a-col>
</a-row>
</div>
</a-spin>
<template slot="footer">
<a-button :disabled="copySpin" @click="copyEntity">复制</a-button>
<a-button :disabled="copySpin" @click="sharCancel">关闭</a-button>
</template>
</a-modal>
</div>
</template>
<script>
import moment from 'moment'
import '@/assets/less/TableExpand.less'
import { mixinDevice } from '@/utils/mixin'
import { JeecgListMixin } from '@/mixins/JeecgListMixin'
import MajorList from '@/components/MajorList'
import WdCourseForm from './modules/WdCourseForm'
import { httpAction, getAction, postAction } from '@/api/manage'
export default {
name: 'WdCourseVideoList',
mixins:[JeecgListMixin, mixinDevice],
components: {
WdCourseForm,
MajorList,
},
data () {
let ellipsis = (v, l = 20) => (<j-ellipsis value={v} length={l}/>)
return {
checkAll: false,
videoListAll: [],
checkAllList: [],
options: [],
sourceMajorId: [],
visibleVideo: false,
copySpin: false,
//重构
showList:true,
courseId: '',
majorId:'',
templateId:'',
wdCourseFormVisible: false,
//重构
editabletabsValue:'',
description: '视频课程管理页面',
// 表头
columns: [
{
title:'课程编码',
align:"center",
dataIndex: 'id'
},
{
title:'课程名称',
align:"center",
dataIndex: 'courseVideoName',
customRender: (t) => ellipsis(t)
},
{
title:'课程类型',
align:"center",
dataIndex: 'courseType',
scopedSlots: {customRender: 'courseType'}
},
{
title:'课程图片',
align:"center",
dataIndex: 'courseVideoImgPath',
scopedSlots: {customRender: 'imgSlot'}
},
{
title:'开课时间',
align:"center",
dataIndex: 'startTime',
customRender:function (text) {
return !text?"":(text.length>10?text.substr(0,10):text)
}
},
{
title:'课时',
align:"center",
dataIndex: 'classHour'
},
{
title:'学年',
align:"center",
dataIndex: 'schoolYear'
},
{
title: '排序',
align: "center",
dataIndex: 'sort'
},
{
title:'学习阶段',
align:"center",
dataIndex: 'learningPhase_dictText',
scopedSlots: { customRender: 'learningPhaseSlot' },
},
{
title: '标签',
align: "center",
dataIndex: 'labelNames',
},
{
title:'发布状态',
align:"center",
dataIndex: 'postStatus_dictText',
scopedSlots: { customRender: 'postStatusSlot' },
},
{
title: '是否参与售卖',
align: "center",
dataIndex: 'isSale',
customRender: function (text) {
if (text==0){
return '是'
}else {
return '否'
}
}
},
{
title: '操作',
dataIndex: 'action',
align:"center",
fixed:"right",
width:147,
scopedSlots: { customRender: 'action' }
}
],
url: {
list: "/course/WdCourseVideo/wdCourseVideo/list",
delete: "/course/WdCourseVideo/wdCourseVideo/delete",
deleteBatch: "/course/WdCourseVideo/wdCourseVideo/deleteBatch",
exportXlsUrl: "/course/WdCourseVideo/wdCourseVideo/exportXls",
importExcelUrl: "course/WdCourseVideo/wdCourseVideo/importExcel",
majorList: "/major/list",
edit: "/course/WdCourseVideo/wdCourseVideo/edit",
majorLists: '/major/listByParentId',
findByCourseVideo: '/course/WdCourseVideo/wdCourseVideo/findByCourseVideo',
copyCourseVideo: '/course/WdCourseVideo/wdCourseVideo/copyCourseVideo',
},
dictOptions:{},
superFieldList:[],
majorParentList:[],
model:{},
dataSource:[],
yearShowOne: false,
queryParam: {
majorIds: '',
courseVideoName: '',
yearValue: null,// 设置值: moment(value, 'YYYY')
schoolYear: '',
postStatus: ''
},
pagination: {
current: 1,
pageSize: 10,
total: 0,
showTotal: (total, range) => range[0] + "-" + range[1] + " 共 " + total+" 条",
showSizeChanger: true,
pageSizeOptions: ['10', '20', '30'],
showQuickJumper: true,
onChange: (page, pageSize) => {
this.onChange(page, pageSize);
},
onShowSizeChange: (current, size) => {
this.onShowSizeChange(current, size);
},
},
}
},
created() {
this.searchQuery1();
},
computed: {
importExcelUrl: function(){
return `${window._CONFIG['domianURL']}/${this.url.importExcelUrl}`;
},
},
methods: {
copyEntity(){
this.copySpin = true;
if(this.sourceMajorId <= 0){
this.$message.error('请选择专业')
this.copySpin = false;
return;
}
if(this.checkAllList.length <= 0){
this.$message.error('请选择课程')
this.copySpin = false;
return;
}
// copy数据
console.log(this.sourceMajorId[1]);
console.log(this.checkAllList);
console.log(this.majorId);
const data = {
sourceMajorId: this.sourceMajorId[1],
courseVideoList: this.checkAllList,
targetMajorId: this.majorId
}
postAction(this.url.copyCourseVideo, data).then(res => {
if (res.success) {
this.copySpin = false;
this.$message.success(res.message);
this.visibleVideo = false;
this.changeMajor(this.majorId);
} else {
this.copySpin = false;
this.$message.warning(res.message);
}
})
},
sharCancel(){
this.visibleVideo = false;
},
checkVideoAll(){
// 一键共用课程
this.loadingMajor();
this.visibleVideo = true;
this.videoListAll = [];
this.sourceMajorId = [];
this.checkAll = false;
this.checkAllList = [];
},
displayRender({ labels }) {
return labels.join('-');
},
majorChange(e){
console.log(this.sourceMajorId[1]);
this.findByCourseVideo(this.sourceMajorId[1]);
},
findByCourseVideo(majorId){
getAction(this.url.findByCourseVideo+'?majorId='+majorId).then(res => {
if(res.success) {
this.videoListAll = res.result || [];
console.log(this.videoListAll);
} else {
this.$message.error(res.message);
}
})
},
listCheckbox(e) {
if(!e.target.checked) {
if(this.checkAllList.indexOf(e.target.value) > -1) {
this.checkAllList.splice(this.checkAllList.indexOf(e.target.value), 1)
}
} else {
this.checkAllList.push(e.target.value)
}
if(this.checkAllList.length === this.videoListAll.length) {
this.checkAll = true
} else {
this.checkAll = false
}
},
onCheckAllChange(e) {
this.checkAll = e.target.checked
if(!this.checkAll) {
this.checkAllList = []
} else {
let arr = []
this.videoListAll.forEach(item => {
arr.push(item.id)
})
this.checkAllList = arr
}
},
loadingMajor() {
getAction(this.url.majorLists).then(res => {
if(res.success) {
let options = [];
res.result.forEach(e => {
let parent = {
value: e.id,
label: e.majorName,
};
let children = [];
e.children.forEach(e2 => {
let major = {
value: e2.id,
label: e2.majorName,
};
children.push(major);
});
parent.children = children;
options.push(parent);
});
this.options = options;
} else {
this.$message.error(res.message);
}
})
},
filter(inputValue, path) {
return path.some(option => option.label.toLowerCase().indexOf(inputValue.toLowerCase()) > -1);
},
getDataList(params) {
this.tableLoading = true;
getAction(this.url.list, Object.assign({
pageNo: this.pagination.current,
pageSize: this.pagination.pageSize
}, params)).then((res) => {
if(res.success) {
this.dataSource = res.result.records;
this.pagination.total = res.result.total;
} else {
this.$message.error(res.message);
}
}).finally(() => {
this.tableLoading = false;
});
},
searchQuery1() {
if(this.queryParam.yearValue != null && this.queryParam.yearValue != '') {
this.queryParam.schoolYear = moment(this.queryParam.yearValue).format('YYYY');
}
this.dataSource = [];
this.getDataList(this.queryParam);
},
onChange(page, pageSize) {
this.pagination.current = page;
this.pagination.pageSize = pageSize;
if(this.queryParam.yearValue != null && this.queryParam.yearValue != '') {
this.queryParam.schoolYear = moment(this.queryParam.yearValue).format('YYYY');
}
this.getDataList(this.queryParam);
},
onShowSizeChange(current, size) {
this.pagination.current = current;
this.pagination.pageSize = size;
if(this.queryParam.yearValue != null && this.queryParam.yearValue != '') {
this.queryParam.schoolYear = moment(this.queryParam.yearValue).format('YYYY');
}
this.getDataList(this.queryParam);
},
initDictConfig(){
},
getSuperFieldList(){
let fieldList=[];
fieldList.push({type:'string',value:'courseVideoCode',text:'课程编码',dictCode:''})
fieldList.push({type:'string',value:'courseVideoName',text:'课程名称',dictCode:''})
fieldList.push({type:'string',value:'courseVideoDescription',text:'课程描述',dictCode:''})
fieldList.push({type:'string',value:'courseVideoImgPath',text:'课程图片',dictCode:''})
fieldList.push({type:'date',value:'startTime',text:'开课时间'})
fieldList.push({type:'BigDecimal',value:'classHour',text:'课时',dictCode:''})
fieldList.push({type:'string',value:'schoolYear'
3. 后端开发:
使用springcloud构建后端API。创建一个简单的视频模型和相应的API视图:
@AutoLog(value = "视频课程-分页列表查询")
@ApiOperation(value="视频课程-分页列表查询", notes="视频课程-分页列表查询")
@GetMapping(value = "/list")
public Result<?> queryPageList(WdCourseVideo wdCourseVideo,
@RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
@RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
HttpServletRequest req) {
QueryWrapper<WdCourseVideo> queryWrapper = QueryGenerator.initQueryWrapper(wdCourseVideo, req.getParameterMap());
queryWrapper.orderByDesc("sort").orderByDesc("create_time").orderByDesc("id");
Page<WdCourseVideo> page = new Page<WdCourseVideo>(pageNo, pageSize);
IPage<WdCourseVideo> pageList = wdCourseVideoService.page(page, queryWrapper);
for (WdCourseVideo record : page.getRecords()) {
if(record.getCourseType() == 1){
try{
record.setClassHour(""+setClassHour(record.getId(), record.getTemplateId(), 1));
wdCourseVideoService.updateById(record);
}catch (Exception e){
System.out.println("计算课时有误");
}
}
}
for (WdCourseVideo record : pageList.getRecords()) {
//查询绑定的标签id
record.setLabelIds(wdCourseLabelRelationService.selectCourseLabelIds(record.getId()));
record.setLabelNames(wdCourseLabelRelationService.selectCourseLabelNames(record.getId()));
//查询是否售卖
record.setIsSale(1);
WdCourseSale courseSale = wdCourseSaleService.getOne(new LambdaQueryWrapper<WdCourseSale>().eq(WdCourseSale::getCourseId, record.getId()));
if (courseSale!=null){
record.setIsSale(courseSale.getIsSale());
}
}
return Result.OK(pageList);
}
4. 启动项目:
启动后端服务:
java -jar /opt/myapp/educatuion.jar
启动前端服务:
cd education
npm install
npm run serve
构建多分校网校平台是未来教育发展的重要方向。通过视频课程技术的应用,可以有效解决传统教学中的诸多问题,实现教育资源的优化配置和高效利用。希望本文的技术详解能为教育机构提供参考,助力打造高效、稳定的多分校网校平台,实现教育事业的进一步发展。