Commit 4e11ae05 by xiaowenfeng

1、微课制课工具1.0

parent ce6a7c38
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
env: {
development: {
plugins: ['dynamic-import-node']
}
},
plugins: [
[
'component', {
libraryName: 'mint-ui',
style: true
}
], ['import', {
libraryName: 'view-design',
libraryDirectory: 'src/components'
}]
]
}
......@@ -8,8 +8,24 @@
"lint": "vue-cli-service lint"
},
"dependencies": {
"ali-oss": "^6.11.2",
"axios": "^0.20.0",
"babel-plugin-import": "^1.13.0",
"core-js": "^3.6.5",
"vue": "^2.6.11"
"js-audio-recorder": "^1.0.6",
"kim-vue-touch": "^1.1.5",
"lamejs": "^1.2.0",
"mint-ui": "^2.2.13",
"reveal.js": "^4.0.2",
"spark-md5": "^3.0.1",
"swiper": "^6.2.0",
"vconsole": "^3.3.4",
"view-design": "^4.3.2",
"vue": "^2.6.11",
"vue-awesome-swiper": "^4.1.0",
"vue-cli": "^2.9.6",
"vue-progress-circle": "^1.1.0",
"vue-router": "^3.4.3"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
......@@ -17,6 +33,8 @@
"@vue/cli-service": "~4.5.0",
"@vue/eslint-config-standard": "^5.1.2",
"babel-eslint": "^10.1.0",
"babel-plugin-component": "^1.1.1",
"babel-plugin-dynamic-import-node": "^2.3.3",
"eslint": "^6.7.2",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0",
......
No preview for this file type
......@@ -3,7 +3,8 @@
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0">
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"> -->
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
......
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
<router-view></router-view>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
// import VConsole from 'vconsole/dist/vconsole.min.js'
// const vConsole = new VConsole()
// console.log('vConsole:', vConsole)
export default {
name: 'App',
components: {
HelloWorld
}
}
</script>
<style lang="scss">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
@import url("./css/common.css");
html {
font-size: 100px;
}
</style>
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
</ul>
<h3>Essential Links</h3>
<ul>
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
<template>
<div id="microBox">
<div class="micro-top">
<div class="micro-upload" v-if="pages.length === 0">
<div class="file-input-box" @click="uploadClick">
<div class="file-input-select">
<i class="iconfont">&#xe654;&nbsp;上传课件</i>
</div>
</div>
<input
type="file"
ref="filElem"
accept=".ppt, .pptx"
id="uploadFile"
@change="uploadFile"
v-show="false"
/>
</div>
<div class="micro-img-preview" ref="imgPreview">
<div class="reveal">
<div class="slides" ref="slides">
<section
v-for="(item, index) in pages"
:key="index"
:data-transition="item.transitions.in + ' ' + item.transitions.out"
:style="{ backgroundImage: 'url(' + baseSourceUrl + item.imgUri + ')' }"
@click="edit(index)">
<p v-show="!!item.text">{{item.text}}</p>
</section>
</div>
</div>
</div>
</div>
<transition name="slide-fade" mode="out-in">
<div class="micro-middle" v-show="!isEdit">
<swiper ref="swiperThumbs" class="gallery-thumbs" :options="swiperThumbs">
<swiper-slide
v-for="(item, index) in pages"
class="swiper-slide"
:style="{ backgroundImage: 'url(' + baseSourceUrl + item.imgUri + ')', opacity: activeIndex === index? '1':'0.4' }"
:key="index"
>
</swiper-slide>
</swiper>
<div class="swiper-button-next swiper-button-white" v-show="device.android ? false : device.ios ? false : true" @click="changPageIndex('down')"></div>
<div class="swiper-button-prev swiper-button-white" v-show="device.android ? false : device.ios ? false : true" @click="changPageIndex('up')"></div>
</div>
</transition>
<transition name="slide-fade" mode="out-in">
<div class="micro-bottom" v-show="!isEdit">
<button class="page-message">页码:{{activeIndex+1}}/{{pages.length}}</button>
<div class="page-button-wrap">
<!-- <button @click="publish" v-show="buildStatus.current === buildStatus.status.finish">发布</button>
<button @click="finish" v-show="buildStatus.current === buildStatus.status.parsingComplete">提交</button> -->
<button @click="preview" v-show="!token">预览</button>
<button @click="closeEdit"><span>退出</span></button>
</div>
</div>
</transition>
<transition name="slide-fade" mode="out-in">
<div class="edit" v-show="isEdit">
<div class="editBottom" :style="{width: editBottomWith + 'px'}">
<div class="editStatus">
<button @click="changeEditStatus(editStatus.playTime)" :class="currentEditStatus === editStatus.playTime ? 'button-active' : ''">时长</button>
<button @click="changeEditStatus(editStatus.editText)" :class="currentEditStatus === editStatus.editText ? 'button-active' : ''">字幕</button>
<button @click="changeEditStatus(editStatus.editToggle)" :class="currentEditStatus === editStatus.editToggle ? 'button-active' : ''">切换</button>
<button @click="changeEditStatus(editStatus.recorder)" :class="currentEditStatus === editStatus.recorder ? 'button-active' : ''">录音</button>
</div>
<input
type="text"
ref="inputText"
:class="isInputFocus && (device.android || device.ios) ? 'inputFixed' : 'inputStatic'"
@click="inputFocus"
@blur="inputBlur"
v-if="isEdit && currentEditStatus === editStatus.editText"
v-model="pages[editIndex].text"
maxlength="27"
placeholder="不多于27字" />
<div class="toggle" v-if="isEdit && currentEditStatus === editStatus.editToggle">
<div class="toggleSelect">
<span>进入:</span>
<select v-model="pages[editIndex].transitions.in" @blur="selectBlure" >
<option :value ="item.in.type" v-for="(item, index) in transitions" :key="index">{{item.in.name}}</option>
</select>
</div>
<div class="toggleSelect">
<span>离开:</span>
<select v-model="pages[editIndex].transitions.out" @blur="selectBlure" >
<option :value ="item.out.type" v-for="(item, index) in transitions" :key="index">{{item.out.name}}</option>
</select>
</div>
</div>
<div class="recorder" v-show="isEdit && currentEditStatus === editStatus.recorder">
<div class="recorderStatus">
<div class="timeRemaining">
<i v-show="!isRecording" class="iconfont">&#xe60e;</i>
<svg v-show="isRecording" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%" viewBox="0 0 24 30" style="enable-background:new 0 0 50 50" xml:space="preserve">
<rect x="0" y="9.22656" width="4" height="12.5469" fill="#909090">
<animate attributeName="height" attributeType="XML" values="5;21;5" begin="0s" dur="0.6s" repeatCount="indefinite"></animate>
<animate attributeName="y" attributeType="XML" values="13; 5; 13" begin="0s" dur="0.6s" repeatCount="indefinite"></animate>
</rect>
<rect x="10" y="5.22656" width="4" height="20.5469" fill="#909090">
<animate attributeName="height" attributeType="XML" values="5;21;5" begin="0.15s" dur="0.6s" repeatCount="indefinite"></animate>
<animate attributeName="y" attributeType="XML" values="13; 5; 13" begin="0.15s" dur="0.6s" repeatCount="indefinite"></animate>
</rect>
<rect x="20" y="8.77344" width="4" height="13.4531" fill="#909090">
<animate attributeName="height" attributeType="XML" values="5;21;5" begin="0.3s" dur="0.6s" repeatCount="indefinite"></animate>
<animate attributeName="y" attributeType="XML" values="13; 5; 13" begin="0.3s" dur="0.6s" repeatCount="indefinite"></animate>
</rect>
</svg>
</div>
<div class="timeCompute">{{timeRemaining}}</div>
</div>
<button class="trialSeeding" v-show="isEdit && pages[editIndex].audioSrc" @click="testAudio">
<i v-show="!isPlaying" class="iconfont">&#xe77e;</i>
<i v-show="isPlaying" class="iconfont">&#xe60e;</i>
</button>
<button class="ivu-btn ivu-btn-default" @click="recorderClick">
<i v-show="isRecording" class="iconfont">&#xe62e;</i>
<i v-show="!isRecording" class="iconfont">&#xe62f;</i>
</button>
<audio v-show="false" controls="controls" :src="isEdit ? baseSourceUrl + pages[editIndex].audioSrc : ''" ref="audioTest" @playing="playing" @ended="playEnded" @pause="playPause"></audio>
</div>
<div class="playTime" v-if="isEdit && currentEditStatus === editStatus.playTime">
<div class="playInput">
<input
type="text"
v-model="pages[editIndex].slideTime"
maxlength="3"
@click="inputFocus"
@blur="inputBlur"
@change="playinputChange"
:placeholder="'小于' + maxRecorderLength + '秒'"
:class="isInputFocus && (device.android || device.ios) ? 'playInputFixed' : 'playInputStatic'"
onkeyup="if(this.value.length==1){this.value=this.value.replace(/[^1-9]/g,'')}else{this.value=this.value.replace(/\D/g,'')}"
onafterpaste="if(this.value.length==1){this.value=this.value.replace(/[^1-9]/g,'')}else{this.value=this.value.replace(/\D/g,'')}" />
<button @mouseup="mouseupButton('add')" @mousedown="mousedownButton('add')" @mouseleave="mouseleaveButton">+</button>
<button @mouseup="mouseupButton('decrease')" @mousedown="mousedownButton('decrease')" @mouseleave="mouseleaveButton">-</button>
</div>
<div class="playTimeUnit">/ 秒</div>
</div>
</div>
</div>
</transition>
<div class="micro-shadow" @click="inputBlur" v-show="isInputFocus && (device.android || device.ios)"></div>
<div class="no-micro-lecture" v-if="pages.length === 0">
<div class="no-micro-lecture-tips" v-show="currentUploadStatus === uploadStatus.normal"><i class="iconfont">&#xe602;&nbsp;</i>请先上传课程</div>
<div class="no-micro-lecture-uploading" v-show="currentUploadStatus === uploadStatus.upload">
<div class="no-micro-lecture-uploading-trance" :style="{width: uploadProgress}"></div>
<br/>
上传中&nbsp;({{uploadProgress}})···
</div>
<div class="no-micro-lecture-tips" v-show="currentUploadStatus === uploadStatus.transcoding">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="30px" height="30px" viewBox="0 0 50 50" style="enable-background:new 0 0 50 50" xml:space="preserve">
<path fill="#909090" d="M43.935,25.145c0-10.318-8.364-18.683-18.683-18.683c-10.318,0-18.683,8.365-18.683,18.683h4.068c0-8.071,6.543-14.615,14.615-14.615c8.072,0,14.615,6.543,14.615,14.615H43.935z" transform="rotate(275.098 25 25)">
<animateTransform attributeType="xml" attributeName="transform" type="rotate" from="0 25 25" to="360 25 25" dur="0.6s" repeatCount="indefinite"></animateTransform>
</path>
</svg>
<br/>
<span v-show="buildStatus.current === buildStatus.status.initial">等待解析</span>
<span v-show="buildStatus.current === buildStatus.status.parsing">正在解析</span>
<span v-show="buildStatus.current === buildStatus.status.parsingComplete">解析完成</span>
<span v-show="buildStatus.current === buildStatus.status.fail">解析失败,请重新上传</span>
</div>
</div>
<div class="loadingImg" v-show="isSubmit">
<img src="../assets/loading-icon.gif" alt="loading">
</div>
<div class="initLoading" v-if="initStatus.isInit">
<div v-show="!initStatus.isError">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="30px" height="30px" viewBox="0 0 50 50" style="enable-background:new 0 0 50 50" xml:space="preserve">
<path fill="#909090" d="M43.935,25.145c0-10.318-8.364-18.683-18.683-18.683c-10.318,0-18.683,8.365-18.683,18.683h4.068c0-8.071,6.543-14.615,14.615-14.615c8.072,0,14.615,6.543,14.615,14.615H43.935z" transform="rotate(275.098 25 25)">
<animateTransform attributeType="xml" attributeName="transform" type="rotate" from="0 25 25" to="360 25 25" dur="0.6s" repeatCount="indefinite"></animateTransform>
</path>
</svg>
<br/>
<br/>
正在初始化···
</div>
<div v-show="initStatus.isError">
<i class="iconfont">&#xe6e7;&nbsp;课程获取失败</i>
</div>
</div>
<mt-popup
v-model="popupVisible"
position="bottom">
<mt-field label="课程名称" placeholder="请输入课程名称" :attr="{ maxlength: 100}" v-model="microLectureTitle"></mt-field>
<mt-field label="课程简介" placeholder="课程简介" type="textarea" rows="4" :attr="{ maxlength: 1000, style: 'resize: none' }" v-model="introduction"></mt-field>
<div class="hangdleBox" v-for="(item, index) in lectureClassify" :key="index">
<i-select style="width:200px" @on-change="selectClassify">
<i-option v-for="i in lectureClassify[index]" :value="i.value" :key="i.value" :label="i.label">{{ i.label }}</i-option>
</i-select>
</div>
<div class="hangdleBox">
<button v-show="!canPublish" class="ivu-btn ivu-btn-default" disabled><span>发布</span></button>
<button v-show="canPublish" class="ivu-btn ivu-btn-default" @click="lecturePublish"><span>发布</span></button>
</div>
</mt-popup>
</div>
</template>
<script>
import Vue from 'vue'
import { Swiper, SwiperSlide } from 'vue-awesome-swiper'
import Recorder from 'js-audio-recorder'
import * as revealjs from '../../node_modules/reveal.js/dist/reveal.js'
import 'swiper/swiper-bundle.css'
import '../../node_modules/reveal.js/dist/reveal.css'
import '../../node_modules/reveal.js/dist/theme/black.css'
import 'view-design/dist/styles/iview.css'
import convertToMp3 from '../lib/converToMp3'
import axios from 'axios'
import OSS from 'ali-oss'
import SparkMD5 from 'spark-md5'
import { Toast, MessageBox, Field, Popup } from 'mint-ui'
import { Select, Option, Button } from 'view-design'
// console.log(OSS)
// console.log(SparkMD5)
// console.log(Button.name)
Vue.component(Toast.name, Toast)
Vue.component(MessageBox.name, MessageBox)
Vue.component(Field.name, Field)
Vue.component(Popup.name, Popup)
Vue.component(Select.name, Select)
Vue.component(Option.name, Option)
Vue.component(Button.name, Button)
window.Reveal = revealjs
export default {
name: 'MicroBox',
data () {
return {
pages: [], // 课程数据结构,或者可以成为课程描述文件
baseSourceUrl: 'https://res.qida.com/', // 图片、语音的域名
currentUploadStatus: 'normal', // 上传的状态
uploadStatus: {
upload: 'upload',
transcoding: 'transcoding',
normal: 'normal'
},
buildStatus: { // 课程状态
current: 'I',
status: {
initial: 'I', // 上传成功
parsing: 'R', // 正在解析
parsingComplete: 'E', // 解析完成
fail: 'F', // 解析失败
finish: 'S', // 编辑完成
publish: 'P' // 已经发布
}
},
editStatus: { // 编辑状态
editText: 'editText',
recorder: 'recorder',
toggle: 'toggle',
playTime: 'playTime'
},
transitions: [ // 翻页特效
{ in: { type: 'node', name: '无' }, out: { type: 'node', name: '无' } },
{ in: { type: 'fade-in', name: '淡入' }, out: { type: 'fade-out', name: '淡出' } },
{ in: { type: 'slide-in', name: '滑入' }, out: { type: 'slide-out', name: '滑出' } },
{ in: { type: 'convex-in', name: '凸入' }, out: { type: 'convex-out', name: '凸出' } },
{ in: { type: 'concave-in', name: '凹入' }, out: { type: 'concave-out', name: '凹出' } },
{ in: { type: 'zoom-in', name: '缩放' }, out: { type: 'zoom-out', name: '缩放' } }
],
swiperThumbs: { // swiper配置项,详细查看swiper官网
spaceBetween: 10,
freeMode: true,
watchSlidesVisibility: true,
watchSlidesProgress: true,
slidesPerView: '5',
slideToClickedSlide: true,
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev'
},
on: {
click: (e) => {
// console.log(e)
window.Reveal.slide(this.swiperBottomThumbs.clickedIndex)
}
}
},
initStatus: { // 课程加载状态
isInit: true,
isError: false
},
device: { // 设备类型。这里需要注意的是,我把token把为空作为移动app的判断,
android: null,
ios: null
},
recorderConfig: {
sampleBits: 16, // 采样位数,支持 8 或 16,默认是16
sampleRate: 16000, // 采样率,支持 11025、16000、22050、24000、44100、48000,根据浏览器默认值,我的chrome是48000
numChannels: 1 // 声道,支持 1 或 2, 默认是1
},
lectureClassify: [], // 课程分类,注意与coursetype不同,这是课程内容的分类
classifySelect: {}, // 已经选择的课程分类
uploadProgress: '0%', // 上传进度
activeIndex: 0, // 当前活动的页码
maxRecorderLength: 600, // 最大录音时长
currentRecorderLength: 0, // 已经完成的录音时长
microLectureTitle: '', // 课程标题
introduction: '', // 课程简介
microId: null, // 课程ID,这个ID实际上的草稿的ID,就是没有发布前的ID
editBottomWith: null,
editIndex: null, // 当前编辑的页码
currentEditStatus: null, // 当前的编辑状态
recorder: null,
editCache: null,
isInputFocus: false,
isEdit: false, // 是否处于编辑状态
isSubmit: false,
isRecording: false, // 是否正在录音
isPlaying: false, // 是否正在播放录音
popupVisible: false,
token: null // token由移动app传过来
}
},
methods: {
getClassify (data) {
return axios({
url: '/crs/course/category/findByParentId.do',
method: 'get',
params: data
})
},
compatibleApi (data, method) {
const apis = {
pc: {
getLecture: '/res/make/get/mic/lecture.do',
updateCourse: '/res/make/update/slide/course.do',
audioUp: '/res/make/upload/audio.do',
ossToken: '/res/make/get/sts/token.do'
},
app: {
router: '/app/router.do',
methods: {
getLecture: 'get.mic.lecture',
updateCourse: 'update.slide.course',
audioUp: 'upload.audio'
}
}
}
let url = ''
// eslint-disable-next-line no-extra-boolean-cast
if (!!this.token) {
url = apis.app.router
return axios({
url: url,
method: 'post',
params: {
token: this.token,
method: apis.app.methods[method]
},
headers: { 'Content-type': 'application/x-www-form-urlencoded' },
data: data
})
} else {
url = apis.pc[method]
return axios({
url: url,
method: 'post',
headers: { 'Content-type': 'application/x-www-form-urlencoded' },
data: data
})
}
},
async setClassify (parentId) {
await this.getClassify({ parentId: parentId }).then((res) => {
const classify = res.data.values
const newlist = []
classify.forEach(item => {
newlist.push({ value: item.id, label: item.name, folderCount: item.folderCount })
})
this.lectureClassify.push(newlist)
this.classifySelect = {}
})
},
selectClassify (value) {
const list = this.lectureClassify
let v = null
let index = null
for (let i = 0; i < list.length; i++) {
for (let j = 0; j < list[i].length; j++) {
if (list[i][j].value === value) {
v = list[i][j]
index = i
break
}
}
}
const newList = this.lectureClassify.slice(0, index + 1)
this.lectureClassify = newList
this.classifySelect = v
if (v.folderCount !== 0) {
this.setClassify(v.value)
}
},
uploadClick () {
if (this.currentUploadStatus !== this.uploadStatus.normal) { return }
this.setClassify().then(() => {
MessageBox.prompt('请输入课程名称').then(({ value, action }) => {
// console.log('value:', value)
// console.log('action:', action)
this.microLectureTitle = value
this.$refs.filElem.dispatchEvent(new MouseEvent('click'))
})
})
},
uploadFile () {
const formData = new FormData()
formData.append('file', this.$refs.filElem.files[0])
formData.append('originType', 'C')
formData.append('courseTitle', this.microLectureTitle)
this.currentUploadStatus = this.uploadStatus.upload
this.buildStatus.current = this.buildStatus.status.initial
axios({
url: '/res/make/course.up',
method: 'post',
data: formData,
onUploadProgress: (e) => {
this.uploadProgress = parseInt((e.loaded / e.total) * 100) + '%'
}
}).then((res) => {
if (res.data.code === '00') {
this.currentUploadStatus = this.uploadStatus.transcoding
this.getTranscodingStatus(res.data.data)
} else {
MessageBox('提示', '上传失败!')
this.currentUploadStatus = this.uploadStatus.normal
}
}).catch(() => {
MessageBox('提示', '上传失败!')
this.currentUploadStatus = this.uploadStatus.normal
}).finally(() => {
this.uploadProgress = '0%'
this.$refs.filElem.value = null
})
},
getTranscodingStatus (id) {
setTimeout(() => {
this.getLectureJson(id)
}, 5000)
},
getLectureJson (id) {
const params = new URLSearchParams()
params.append('id', id)
this.compatibleApi(params, 'getLecture').then((res) => {
// console.log(res)
const { buildStatus, coursewareDir, id } = res.data.data.slideCourse
const { initial, parsing, parsingComplete, fail, finish, publish } = this.buildStatus.status
switch (buildStatus) {
case initial:
this.getTranscodingStatus(id)
break
case parsing:
this.buildStatus.current = parsing
this.getTranscodingStatus(id)
break
case parsingComplete:
this.buildStatus.current = parsingComplete
this.getPages(coursewareDir, id)
break
case finish:
this.buildStatus.current = finish
this.getPages(coursewareDir, id)
break
case publish:
this.buildStatus.current = publish
this.getPages(coursewareDir, id)
break
default:
MessageBox('提示', '课程获取失败!')
this.buildStatus.current = fail
this.currentUploadStatus = this.uploadStatus.normal
break
}
if (res.data.data.courseInfo) {
this.microLectureTitle = res.data.data.courseInfo.courseTitle
this.introduction = res.data.data.courseInfo.description
}
}).catch(() => {
MessageBox('提示', '课程获取失败!')
this.initStatus.isError = true
})
},
getPages (coursewareDir, microId) {
const url = 'https://qida-videos.oss-cn-shenzhen.aliyuncs.com/' + coursewareDir + '/content.json'
this.microId = microId
axios.get(url).then((res) => {
this.pageInit(res.data)
})
},
pageInit (data) {
/* 首张图片加载计数,如果重复3次加载失败,则课程加载失败 */
if (!!this.count === false) {
this.count = 0
}
const imgUri = data[0].imgUri
const img = new Image()
img.src = this.baseSourceUrl + imgUri + '?t=' + Math.random()
img.onload = () => {
this.pages = data
const imgPreviewWidth = this.$refs.imgPreview.clientWidth
const imgPreviewHeight = this.$refs.imgPreview.clientHeight
if (imgPreviewWidth > imgPreviewHeight) {
const slideWidth = img.width / img.height * imgPreviewHeight
this.revealInit(slideWidth, imgPreviewHeight)
} else {
const slideHeight = img.height / img.width * imgPreviewWidth
this.revealInit(imgPreviewWidth, slideHeight)
}
// eslint-disable-next-line no-extra-boolean-cast
if (!!this.token) {
this.bridgeRegist()
}
}
img.onerror = () => {
if (this.count < 3) {
setTimeout(() => {
this.count = this.count += 1
this.pageInit(data)
}, 2000)
} else {
this.initStatus.isError = true
this.showToast('课程初始化失败')
}
}
},
changPageIndex (param) {
if (param === 'down') {
window.Reveal.next()
} else {
window.Reveal.prev()
}
},
revealInit (initWidth, initHeight) {
window.Reveal.initialize({
hash: false,
controlsLayout: 'edges',
controls: false,
progress: false,
loop: true
// autoSlide: this.pages[this.activeIndex].slideTime * 1000
}).then(() => {
window.Reveal.configure({ width: initWidth, height: initHeight })
this.editBottomWith = window.Reveal.getConfig().width * window.Reveal.getScale()
this.initEvent()
this.initStatus.isInit = false
})
},
recorderInit () {
this.recorder = new Recorder(this.recorderConfig)
},
checkDevice () {
this.device.android = this.swiperBottomThumbs.device.android
this.device.ios = this.swiperBottomThumbs.device.ios
const microBox = document.getElementById('microBox')
microBox.style.height = this.device.android || this.device.ios ? window.innerHeight * 0.95 + 'px' : window.innerHeight + 'px'
},
initEvent () {
window.Reveal.on('slidechanged', event => {
this.swiperBottomThumbs.slideTo(event.indexh, 1000, false)
this.activeIndex = event.indexh
// console.log(this.activeIndex)
// if (this.activeIndex <= this.pages.length) {
// window.Reveal.configure({ autoSlide: this.pages[this.activeIndex].slideTime * 1000 })
// }
if (this.isEdit) {
this.editIndex = event.indexh
}
})
window.Reveal.on('resize', event => {
this.editBottomWith = window.Reveal.getConfig().width * window.Reveal.getScale()
})
},
edit (index) {
if (this.buildStatus.current === this.buildStatus.status.parsingComplete) {
if (this.isRecording) {
this.showToast('正在录音!')
return
}
this.isEdit = !this.isEdit
this.editIndex = index
this.currentEditStatus = this.editStatus.editText
window.Reveal.configure({ keyboard: !this.isEdit, touch: !this.isEdit })
} else {
this.showToast('当前课程无法编辑!')
}
},
showToast (message) {
return Toast({
message: message,
position: 'middle',
duration: 2000
})
},
changeEditStatus (status) {
this.currentEditStatus = status
},
inputFocus () {
this.isInputFocus = true
if (this.device.android || this.device.ios) {
document.body.scrollIntoViewIfNeeded(false)
}
},
inputBlur () {
this.isInputFocus = false
},
// 解决ios选择器弹起导致页面顶起的问题
selectBlure () {
if (this.device.ios) {
const selectBlureTimeout = setTimeout(() => {
window.scrollTo(0, document.body.scrollTop + 1)
document.body.scrollTop >= 1 && window.scrollTo(0, document.body.scrollTop - 1)
window.clearTimeout(selectBlureTimeout)
}, 10)
}
},
recorderClick () {
this.isRecording ? this.stopRecording() : this.startRecording()
},
computePlaytime (type) {
if (type === 'add') {
const slideTime = this.pages[this.editIndex].slideTime += 1
this.pages[this.editIndex].slideTime = slideTime <= this.maxRecorderLength ? slideTime : this.maxRecorderLength
} else {
const slideTime = this.pages[this.editIndex].slideTime -= 1
this.pages[this.editIndex].slideTime = slideTime >= 1 ? slideTime : 1
}
},
mousedownButton (type) {
this.interval = setInterval(() => {
this.computePlaytime(type)
}, 100)
},
mouseleaveButton () {
clearInterval(this.interval)
},
mouseupButton (type) {
this.mouseleaveButton()
this.computePlaytime(type)
},
playinputChange (e) {
const value = e.target.value === '' ? 1 : e.target.value
const num = parseInt(value)
this.pages[this.editIndex].slideTime = num >= this.maxRecorderLength ? this.maxRecorderLength : num
},
setupWebViewJavascriptBridge (callback) {
if (this.device.ios) {
if (window.WebViewJavascriptBridge) {
// eslint-disable-next-line no-undef
return callback(WebViewJavascriptBridge)
}
if (window.WVJBCallbacks) {
return window.WVJBCallbacks.push(callback)
}
window.WVJBCallbacks = [callback]
var WVJBIframe = document.createElement('iframe')
WVJBIframe.style.display = 'none'
WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__'
document.documentElement.appendChild(WVJBIframe)
setTimeout(function () {
document.documentElement.removeChild(WVJBIframe)
}, 0)
} else if (this.device.android) {
// require('../lib/WebViewJavascriptBridge')
console.log('window.WebViewJavascriptBridge:', window.WebViewJavascriptBridge)
if (window.WebViewJavascriptBridge) {
// eslint-disable-next-line no-undef
callback(WebViewJavascriptBridge)
} else {
document.addEventListener(
'WebViewJavascriptBridgeReady',
function () {
// eslint-disable-next-line no-undef
callback(WebViewJavascriptBridge)
},
false
)
}
}
},
bridgeRegist () {
this.setupWebViewJavascriptBridge((bridge) => {
if (this.device.android) {
bridge.init((message) => {
// console.log('message:', message)
// console.log('bridge:', '初始化完成!')
})
}
bridge.registerHandler('recorderSuccess', (data) => {
const dataJson = JSON.parse(data)
// console.log('录音成功:', dataJson)
if (dataJson.code === '0') {
this.isRecording = true
this.currentRecorderLength = parseInt(dataJson.message)
this.pages[this.editIndex].slideTime = this.currentRecorderLength
}
})
bridge.registerHandler('recorderFail', (data) => {
// console.log('录音失败:', JSON.parse(data))
if (JSON.parse(data).code === '0') {
MessageBox('提示', '用户未授权获取麦克风权限')
}
})
bridge.registerHandler('stopRecorderSuccess', (data) => {
var dataJson = JSON.parse(data)
// console.log('录音结束成功回调:', dataJson)
/*
code:
0、已经结束,开始上传
1、上传成功
*/
if (dataJson.code === '1') {
this.isSubmit = false
this.isRecording = false
this.currentRecorderLength = 0
this.pages[this.editIndex].audioSrc = dataJson.message + '?t=' + Math.random()
}
if (dataJson.code === '0') {
this.isSubmit = true
}
})
bridge.registerHandler('stopRecorderfail', (data) => {
// console.log('录音结束失败回调', data)
if (JSON.parse(data).code === '0') {
this.isSubmit = false
this.isRecording = false
this.currentRecorderLength = 0
MessageBox('提示', '保存失败!')
}
})
bridge.registerHandler('stopPlay', (data) => {
// console.log('结束播放录音', data)
if (JSON.parse(data).code === '0') {
if (this.isPlaying) {
this.testAudio()
}
}
})
})
},
appStartRecorder () {
this.setupWebViewJavascriptBridge((bridge) => {
// console.log('startRecorder:开始录音')
// console.log('当前编辑第几页:', this.pages[this.editIndex].pageNum)
bridge.callHandler('startRecorder', {
maxTime: 20,
id: this.microId,
pageNum: this.pages[this.editIndex].pageNum
}, null)
})
},
appStopRecorder () {
this.setupWebViewJavascriptBridge((bridge) => {
// console.log('startRecorder:结束录音')
bridge.callHandler('stopRecorder', {
maxTime: 20,
id: this.microId,
pageNum: this.pages[this.editIndex].pageNum
}, null)
})
},
startRecording () {
this.$refs.audioTest.pause()
this.playEnded()
// eslint-disable-next-line no-extra-boolean-cast
if (!this.token) {
this.recorder.start().then(() => {
this.isRecording = true
this.recorder.onprogress = (params) => {
this.currentRecorderLength = Math.ceil(params.duration)
this.pages[this.editIndex].slideTime = this.currentRecorderLength
if (this.currentRecorderLength >= this.maxRecorderLength) {
this.stopRecording()
}
}
}, (error) => {
console.log(`${error.name} : ${error.message}`)
MessageBox('提示', '无法获取麦克风权限!')
})
} else {
// console.log('this02:', this.device)
this.appStartRecorder()
}
},
stopRecording () {
this.isRecording = false
this.currentRecorderLength = 0
if (!this.token) {
const blob = convertToMp3(this.recorder, this.recorderConfig)
this.getMd5(blob)
// const formData = new FormData()
// formData.append('id', this.microId)
// formData.append('file', blob)
// formData.append('pageNum', this.pages[this.editIndex].pageNum)
// this.upLoadRecord(formData)
} else {
this.appStopRecorder()
}
},
getMd5 (blob) {
this.isSubmit = true
const file = blob
const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
const chunkSize = 1024 * 1024 * 2 // Read in chunks of 2MB
const chunks = Math.ceil(file.size / chunkSize)
let currentChunk = 0
const spark = new SparkMD5.ArrayBuffer()
const fileReader = new FileReader()
fileReader.onload = (e) => {
// console.log(e.target.result)
spark.append(e.target.result)
currentChunk += 1
if (currentChunk < chunks) {
loadNext()
} else {
const md5 = spark.end()
const params = new URLSearchParams()
params.append('fileMD5', md5)
params.append('fileSize', file.size)
params.append('fileOriginalName', new Date().getTime() + '.mp3')
params.append('fileStatus', 'D')
params.append('id', this.microId)
params.append('pageNum', this.pages[this.editIndex].pageNum)
this.getOssToken(params, file)
// console.log(md5)
}
}
fileReader.onerror = () => {
MessageBox('提示', '录音文件读取失败!')
}
const loadNext = () => {
var start = currentChunk * chunkSize
var end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize
fileReader.readAsArrayBuffer(blobSlice.call(file, start, end))
}
loadNext(file)
},
getOssToken (data, file) {
this.compatibleApi(data, 'ossToken').then((res) => {
const client = new OSS(res.data)
const objectKey = res.data.audioPath
const params = new URLSearchParams()
params.append('id', this.microId)
params.append('pageNum', this.pages[this.editIndex].pageNum)
var that = this
async function multipartUpload () {
try {
const result = await client.multipartUpload(objectKey, file, {
progress: (p, checkpoint) => {
// console.log(p)
// console.log(checkpoint)
}
})
// console.log('result:', result)
if (result.res.status === 200) {
params.append('fileStatus', 'S')
that.upLoadRecord(params)
} else {
params.append('fileStatus', 'F')
that.upLoadRecord(params)
MessageBox('提示', '保存失败!')
}
} catch (e) {
params.append('fileStatus', 'F')
that.upLoadRecord(params)
MessageBox('提示', '保存失败!')
// console.log(e)
}
}
multipartUpload()
})
},
upLoadRecord (data) {
// this.isSubmit = true
this.compatibleApi(data, 'audioUp').then((res) => {
if (res.data.code === '00') {
this.showToast('保存成功')
this.pages[this.editIndex].audioSrc = res.data.data + '?t=' + Math.random()
} else {
MessageBox('提示', '保存失败!')
}
this.isSubmit = false
}).catch(() => {
MessageBox('提示', '保存失败!')
this.isSubmit = false
})
},
testAudio () {
if (this.isRecording) {
this.showToast('正在录音')
return
}
this.isPlaying ? this.$refs.audioTest.pause() : this.$refs.audioTest.play()
},
playing () {
this.isPlaying = true
},
playEnded () {
this.isPlaying = false
},
playPause () {
this.isPlaying = false
},
publish () {
this.popupVisible = true
},
closeEdit () {
if (!this.token) {
window.close()
} else {
this.setupWebViewJavascriptBridge((bridge) => {
bridge.callHandler('closeEdit', null, null)
})
}
},
lecturePublish () {
this.popupVisible = false
this.isSubmit = true
let id = null
if (!!this.microLectureTitle === false) {
this.showToast('请设置课程名称')
return
}
if (!!this.introduction === false) {
this.showToast('请设置课程简介')
return
}
if (this.classifySelect.folderCount !== 0) {
this.showToast('请设置课程分类')
return
}
id = this.classifySelect.value
axios({
url: '/res/make/publish/micro/lecture.do',
method: 'post',
data: {
courseTitle: this.microLectureTitle,
description: this.introduction,
categoryId: id,
slideCourseId: this.microId
}
}).then((res) => {
if (res.data.executeStatus === 0) {
this.buildStatus.current = this.buildStatus.publish
this.isSubmit = false
MessageBox('提示', '课程发布成功!')
} else {
MessageBox('提示', '课程发布失败!')
}
}).catch(() => {
MessageBox('提示', '课程发布失败!')
})
},
preview () {
const { href } = this.$router.resolve({ path: '/microLecturePlay', query: { crsId: this.microId, isDevPreview: true } })
const ref = window.open(href, 'microLecturePlay')
ref && ref.focus && ref.focus()
ref.location.reload()
},
finish () {
MessageBox.confirm('提交后将无法进行修改!\n确定执行此操作?').then(action => {
this.isSubmit = true
axios({
url: '/res/make/finish/mic/lecture/edit.do',
method: 'post',
data: { id: this.microId }
}).then((res) => {
this.isSubmit = false
if (res.data.code === '00') {
this.buildStatus.current = this.buildStatus.status.finish
this.showToast('保存成功')
} else {
MessageBox('提示', '提交失败!')
}
}).catch(() => {
this.isSubmit = false
MessageBox('提示', '提交失败!')
})
}).catch((err) => {
console.log(err)
})
}
},
components: {
Swiper,
SwiperSlide
},
computed: {
swiperBottomThumbs () {
return this.$refs.swiperThumbs.$swiper
},
timeRemaining () {
const value = this.maxRecorderLength - this.currentRecorderLength
const result = parseInt(value)
const h = Math.floor(result / 3600) < 10 ? '0' + Math.floor(result / 3600) : Math.floor(result / 3600)
const m = Math.floor((result / 60 % 60)) < 10 ? '0' + Math.floor((result / 60 % 60)) : Math.floor((result / 60 % 60))
const s = Math.floor((result % 60)) < 10 ? '0' + Math.floor((result % 60)) : Math.floor((result % 60))
let res = ''
if (h !== '00') res += `${h}:`
res += `${m}:`
res += `${s}`
return res
},
canPublish () {
if (!!this.microLectureTitle === false) {
return false
}
if (!!this.introduction === false) {
return false
}
if (!!this.classifySelect.value === false) {
return false
} else if (this.classifySelect.folderCount !== 0) {
return false
}
return true
}
},
beforeMount () {
this.token = this.$route.query.token
},
mounted () {
this.checkDevice()
this.recorderInit()
if (this.$route.query.id) {
this.getLectureJson(this.$route.query.id)
} else {
this.initStatus.isInit = false
}
},
watch: {
isEdit (val, oldval) {
this.playEnded()
if (val === false && oldval === true) {
// console.log(this.editCache === JSON.stringify(this.pages[this.editIndex]))
/* editCache是用来临时保存该页编辑前的状态,
如果保存失败,则还原为编辑前的状态,
同时通过比较编辑前后的json字符串来判断是否有改动,减少无效提交
*/
if (this.editCache === JSON.stringify(this.pages[this.editIndex])) { return }
this.isSubmit = true
const page = { ...this.pages[this.editIndex] }
page.id = this.microId
const params = new URLSearchParams()
params.append('jsonPage', JSON.stringify(page))
this.compatibleApi(params, 'updateCourse').then((res) => {
if (res.data.code === '00') {
this.showToast('保存成功')
} else {
MessageBox('提示', '保存失败!')
this.pages[this.editIndex] = JSON.parse(this.editCache)
}
this.isSubmit = false
}).catch(() => {
MessageBox('提示', '保存失败!')
this.pages[this.editIndex] = JSON.parse(this.editCache)
this.isSubmit = false
})
} else {
this.editCache = JSON.stringify(this.pages[this.editIndex])
}
}
}
}
</script>
<style lang="scss">
// @import url("../css/common.css");
$backgroundColor: #000;
$fontColor: #909090;
$borderColor: #5a5a5a;
body {
padding: 0;
margin: 0;
position:relative;
background: $backgroundColor !important;
}
li {
list-style-type: none;
}
.mint-msgbox {
position: fixed;
top: 50%;
left: 50%;
transform: translate3d(-50%,-50%,0);
background-color: #fff;
width: 85%;
max-width: 500px;
border-radius: 3px;
font-size: 16px;
-webkit-user-select: none;
overflow: hidden;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
transition: .2s;
}
#microBox {
font-family: "Source Sans Pro", Helvetica, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: $fontColor;
width: 100vw;
background: $backgroundColor;
.initLoading {
position: fixed;
height: 100vh;
width: 100vw;
top: 0;
left: 0;
background: #000;
z-index: 1001;
div {
position: absolute;
text-align: center;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 0.12rem;
}
}
.loadingImg {
position: fixed;
height: 100vh;
width: 100vw;
background: rgba(0,0,0,0.3);
top: 0;
left: 0;
z-index: 1000;
img {
width: 32px;
height: 32px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
}
.no-micro-lecture{
width: 100%;
height: 93.6%;
background: #000;
position: fixed;
z-index: 999;
top: 6.4%;
text-align: center;
font-size: 0.16rem;
.no-micro-lecture-tips{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
}
.no-micro-lecture-uploading{
width: 80%;
max-width: 500px;
height: 10px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
}
.no-micro-lecture-uploading-trance {
height: 50%;
border-radius: 2px;
background: #fff;
transition-property: width;
transition-duration: 0.1s;
transition-timing-function: linear;
transition-delay: 0;
/* Safari */
-webkit-transition-property: width;
-webkit-transition-duration: 0.1s;
-webkit-transition-timing-function: linear;
-webkit-transition-delay: 0;
}
}
.pptImg {
height: 100%;
width: auto;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
/* 可以设置不同的进入和离开动画 */
/* 设置持续时间和动画函数 */
.slide-fade-enter-active {
transition: all .3s ease;
}
.slide-fade-leave-active {
transition: all .3s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-fade-enter, .slide-fade-leave-to {
transform: translateX(10px);
opacity: 0;
}
/* 顶部预览区域样式 */
.micro-top {
width: 100%;
height: 80%;
border-top: 0.5px solid $borderColor;
border-bottom: 0.5px solid $borderColor;
font-size: 0;
.micro-upload {
display: inline-block;
box-sizing: border-box;
width: 100%;
height: 8%;
border-bottom: 0.5px solid $borderColor;
.file-input-box {
width: 100%;
height: 100%;
background: #1f1f1f;
position: relative;
cursor: pointer;
.file-input-select {
i {
font-size: 16px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
&:hover{
color: #5f5f5f;
}
}
}
}
}
.micro-img-preview {
display: inline-block;
width: 100%;
height: 100%;
font-size: 16px;
position: relative;
}
}
/* 中部缩略图样式 */
.micro-middle {
width: 100%;
height: 15%;
position: relative;
@media screen and (max-width: 414px) {
max-height: 100px;
}
@media screen and (max-width: 375px) {
max-height: 80px;
}
@media screen and (max-width: 320px) {
max-height: 70px;
}
.swiper-button-white {
color: #ccc;
padding: 0 8px;
position: absolute;
margin: 0;
top: 50%;
transform: translateY(-50%);
z-index: 99;
border-radius: 5px;
&:hover{
color: #2b2b2b;
}
}
.swiper-container {
margin-left: auto;
margin-right: auto;
position: relative;
overflow: hidden;
list-style: none;
padding: 0;
z-index: 1;
height: 100%;
background: $backgroundColor;
.swiper-slide{
cursor: pointer;
flex-shrink: 0;
width: 20%;
height: 100%;
position: relative;
opacity: 0.4;
background-color: #1d1d1d;
transition-property: transform;
background-size: contain;
background-position: center;
background-repeat: no-repeat;
}
}
}
/* 顶部预览样式 */
.micro-bottom {
width: 100%;
height: 5%;
border-top: 0.5px solid $borderColor;
box-sizing: border-box;
background: $backgroundColor;
font-size: 0;
@media screen and (max-width: 414px) {
min-height: 45px;
}
@media screen and (max-width: 375px) {
min-height: 50px;
}
@media screen and (max-width: 320px) {
min-height: 40px;
}
position: relative;
.page-button-wrap {
position: absolute;
top: 50%;
right: 0;
transform: translateY(-50%);
}
button {
display: inline-block;
text-align: center;
background: #f6f6f6;
border-radius: 2px;
color: #444;
font-size: 0.12rem;
font-weight: bold;
padding: 0.05rem 0.1rem;
outline: none;
border: none;
margin: 0 5px;
cursor: pointer;
}
button:active{
background: rgb(78 110 242 / 0.5) !important;
color: #fff !important
}
.page-message {
position: absolute;
top: 50%;
left: 0;
margin: 0 5px;
transform: translateY(-50%);
}
}
/* revealjs样式修改 */
.reveal img {
margin: 0;
}
section {
top: 330px;
display: block;
height: 100%;
padding: 0;
cursor: pointer;
border: 1px solid $borderColor;
background-size: contain;
background-position: center;
background-repeat: no-repeat;
}
.reveal .slides>section, .reveal .slides>section>section {
box-sizing: border-box;
display: none;
position: absolute;
width: 100%;
// padding: 0;
pointer-events: auto;
z-index: 10;
transform-style: flat;
transition: transform-origin .8s cubic-bezier(.26,.86,.44,.985),transform .8s cubic-bezier(.26,.86,.44,.985),visibility .8s cubic-bezier(.26,.86,.44,.985),opacity .8s cubic-bezier(.26,.86,.44,.985);
}
.reveal p {
margin: 0;
line-height: 1.3;
position: absolute;
bottom: 0;
font-size: .12rem;
padding: 0.06rem .06rem;
width: 100%;
background: rgb(0 0 0 / 0.6);
box-sizing: border-box;
text-shadow: #000 1px 0 0, #000 0 1px 0, #000 -1px 0 0, #000 0 -1px 0;
}
/* 编辑样式 */
.edit {
position: relative;
background: $backgroundColor;
height: 100%;
width: 100%;
text-align: center;
font-size: 42px;
font-weight: normal;
color: #fff;
button:active{
background: rgb(78 110 242 / 0.5) !important;
color: #fff !important
}
.editTop {
height: 80%;
position: relative;
}
p {
margin: 20px 0;
line-height: 1.3;
}
.editBottom {
height: 20%;
background: #000;
font-size: 0;
line-height: 0.15;
box-sizing: border-box;
margin: auto;
.editStatus {
padding: 0.1rem 0;
display: flex;
flex-direction: row;
justify-content: space-between;
button {
font-size: 0.16rem;
@media screen and (max-width: 280px) {
font-size: 0.10rem;
}
line-height: initial;
background: #f6f6f6;
outline: none;
border: none;
padding: 0.05rem 0.15rem;
color: #444;
border-radius: 0.02rem;
cursor: pointer;
opacity: 0.4;
display: inline-block;
width: 21%;
}
.toggle {
font-size: 0.16rem;
}
.button-active {
opacity: 1;
}
}
.inputStatic {
position: static;
height: 32px;
line-height: 32px;
font-size: 0.16rem;
padding: 0.07rem 0.15rem;
width: 100%;
box-sizing: border-box;
border-radius: 2px;
border: none;
outline: none;
}
.inputFixed {
position: fixed;
width: 100%;
height: 32px;
line-height: 32px;
padding: 0 10px;
box-sizing: border-box;
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
font-size: 0.16rem;
top: 0;
z-index: 99;
left: 50%;
transform: translateX(-50%);
border: none;
outline: none;
}
.toggle {
font-size: 0.14rem;
width: 100%;
height: 32px;
.toggleSelect {
width: 100%;
height: 32px;
display: inline-block;
vertical-align: middle;
margin-bottom: 10px;
}
span {
background: #616161;
color: #000;
padding: 0.02rem 0.1rem;
border-radius: 2px;
display: inline-block;
height: 100%;
line-height: 32px;
width: 28%;
margin-right: 2%;
vertical-align: middle;
box-sizing: border-box;
}
select {
background: #fff;
color: #000;
padding: 0.02rem 0.1rem;
border-radius: 2px;
outline: none;
border: 0;
display: inline-block;
height: 100%;
width: 70%;
vertical-align: middle;
}
}
.recorder {
width: 100%;
text-align: right;
font-size: 0.14rem;
color: #000;
display: flex;
flex-direction: row;
justify-content: space-between;
button {
height: 32px;
border: none;
outline: none;
width: 15%;
border-radius: 2px;
cursor: pointer;
font-size: 0.14rem;
color: #000;
background: #fff;
}
.recorderStatus {
width: 35%;
height: 32px;
box-sizing: border-box;
border-radius: 2px;
.timeRemaining {
line-height: 30px;
height: 100%;
width: 50%;
background: #616161;;
border-top-left-radius: 2px;
border-bottom-left-radius: 2px;
text-align: center;
padding: 0;
display: inline-block;
vertical-align: middle;
}
.timeCompute{
width: 50%;
font-size: 0.14rem;
height: 100%;
line-height: 30px;
text-align: center;
display: inline-block;
vertical-align: middle;
color: #fff;
border: 1px solid #616161;
box-sizing: border-box;
}
}
.trialSeeding{
width: 15%;
height: 32px;
line-height: 32px;
box-sizing: border-box;
border-radius: 2px;
cursor: pointer;
font-size: .14rem;
color: #000;
background: #fff;
text-align: center;
}
}
.playTime {
width: 100%;
text-align: right;
font-size: 0.14rem;
color: #000;
display: flex;
flex-direction: row;
justify-content: space-between;
}
.playInput {
width: 60%;
height: 32px;
font-size: 0.16rem;
border-radius: 2px;
line-height: 32px;
color: #000;
text-align: center;
input {
display: inline-block;
border: none;
outline: none;
box-sizing: border-box;
vertical-align: middle;
background: #fff;
border-radius: 2px;
height: 100%;
padding: 0 15px;
}
.playInputFixed {
position: fixed;
top: 0;
z-index: 99;
left: 50%;
transform: translateX(-50%);
width:100% !important;
height: 32px;
padding: 0 10px;
box-sizing: border-box;
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
}
.playInputStatic {
position: static
}
button {
display: inline-block;
vertical-align: middle;
background: #fff;
border-radius: 2px;
outline: none;
border: none;
height: 100%;
}
input:nth-child(1) {
width: 70%;
// background: #616161;
}
button:nth-child(2) {
width: 15%;
box-sizing: border-box;
border-left: 1px solid #000;
cursor: pointer;
}
button:nth-child(3) {
width: 15%;
box-sizing: border-box;
border-left: 1px solid #000;
cursor: pointer;
}
}
.playTimeUnit{
display: inline-block;
vertical-align: middle;
width: 30%;
height: 32px;
line-height: 32px;
color: #000;
font-size: 0.16rem;
text-align: center;
background: #616161;
border-radius: 2px;
}
}
}
.micro-shadow {
width: 100%;
height: 100%;
background: rgba(134, 134, 134, 0.4);
position: fixed;
z-index: 98;
top: 0;
left: 0;
}
.mint-popup-bottom {
top: auto;
right: auto;
bottom: 0;
left: 50%;
transform: translate3d(-50%,0,0);
width: 100%;
.mint-cell-wrapper {
background-image: linear-gradient(180deg,#d9d9d9,#d9d9d9 50%,transparent 0);
background-size: 100% 1px;
background-repeat: no-repeat;
background-position: 0 0;
background-origin: content-box;
align-items: center;
box-sizing: border-box;
display: flex;
font-size: 16px;
line-height: 1;
min-height: inherit;
overflow: hidden;
padding: 0 10px;
width: 100%;
}
}
.hangdleBox {
background-image: linear-gradient(180deg,#d9d9d9,#d9d9d9 100%,transparent 0);
background-size: 100% 1px;
padding: 0 10px;
background-repeat: no-repeat;
background-position: 0 0;
background-origin: content-box;
align-items: center;
display: flex;
min-height: 48px;
justify-content: center;
button {
width: 100%;
max-width: 300px;
height: 41px;
}
.ivu-select-single{
width: 100% !important;
max-width: 300px;
}
.ivu-select-single .ivu-select-selection {
height: 41px;
position: relative;
}
.ivu-select-single .ivu-select-selection .ivu-select-placeholder, .ivu-select-single .ivu-select-selection .ivu-select-selected-value {
display: block;
height: 41px;
line-height: 41px;
font-size: 14px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding-left: 8px;
padding-right: 24px;
}
.ivu-select-item {
margin: 0;
line-height: normal;
padding: 10.5px 16px;
clear: both;
color: #515a6e;
font-size: 14px!important;
white-space: nowrap;
list-style: none;
cursor: pointer;
transition: background .2s ease-in-out;
}
}
}
</style>
<template>
<div id="mlplayBox">
<div class="starIcon" v-show="!isInit" @click="starPlay">
<div class="starTitle">
<i class="iconfont">&#xe653;</i>
<div class="loadingBox">
<div class="loadingTrace" :style="{width: initPercent}"></div>
</div>
<h2>{{!!microLectureTitle?microLectureTitle:'暂未设置标题'}}</h2>
<div>{{!!introduction?introduction:'暂未设置简介'}}</div>
</div>
</div>
<div class="pageNum goback" @click="goback" v-show="initPercent === '100%' && !!browserVersion && (browserVersion.ios || browserVersion.android)"><i class="iconfont">&#xe651;</i></div>
<div class="pageNum">{{activeIndex + 1}} / {{pages.length}}</div>
<div class="playControl" @click="toggle">
<progress-circle
:completed-steps="pc_options.completedSteps"
:total-steps="pc_options.totalSteps"
:start-color="pc_options.startColor"
:stop-color="pc_options.stopColor"
:inner-color="pc_options.innerColor"
:circle-width="pc_options.circleWidth"
:diameter="pc_options.diameter"
:circle-color="pc_options.circleColor"
:animation-duration="pc_options.animationDuration"
:key="activeIndex"
></progress-circle>
<i class="iconfont" v-show="microIsplaying">&#xe67d;</i>
<i class="iconfont" v-show="!microIsplaying">&#xe653;</i>
</div>
<!-- 因为app调入来时已经有了一个加入自学,这里就不设置了 -->
<!-- <div class="addStudy" @click="appFavorite" v-if="browserVersion.mobile" v-show="isParticipate === false || isParticipate === 'false'"><i class="iconfont">&#xe654;&nbsp;加入自学</i></div> -->
<div class="reveal" ref="reveal" :style="revealBoxStyle">
<div class="slides" ref="slides">
<section
v-for="(item, index) in pages"
:key="index"
:data-transition="item.transitions.in + ' ' + item.transitions.out"
:style="{ backgroundImage: 'url(' + baseSourceUrl + item.imgUri + ')' }"
v-swipeup="(e)=>vueTouch('up',e)"
v-swipedown="(e)=>vueTouch('down',e)"
>
<p v-show="!!item.text">{{item.text}}</p>
</section>
</div>
</div>
<audio v-show="false" ref="audio"></audio>
</div>
</template>
<script>
import * as revealjs from '../../node_modules/reveal.js/dist/reveal.js'
import '../../node_modules/reveal.js/dist/reveal.css'
import '../../node_modules/reveal.js/dist/theme/black.css'
import { ProgressCircle } from 'vue-progress-circle'
import Vue from 'vue'
import vueTouch from 'kim-vue-touch'
import axios from 'axios'
import { Toast, MessageBox } from 'mint-ui'
Vue.component(Toast.name, Toast)
Vue.component(MessageBox.name, MessageBox)
Vue.use(vueTouch)
window.Reveal = revealjs
export default {
name: 'MlplayBox',
data () {
return {
pages: [],
baseSourceUrl: 'https://res.qida.com/',
revealBoxStyle: {
width: 0,
height: 0,
position: 'absolute',
left: '50%',
top: '50%',
transform: ''
},
browserVersion: {},
buildStatus: {
status: {
initial: 'I',
parsing: 'R',
parsingComplete: 'E',
fail: 'F',
finish: 'S',
publish: 'P'
}
},
pc_options: {
totalSteps: 100,
completedSteps: 0,
diameter: 50,
circleWidth: 3,
animationDuration: 100,
circleColor: '#3a3a3a',
startColor: '#fff',
stopColor: '#fff',
innerColor: 'rgba(0,0,0,0.1)'
},
initPercent: '10%',
driverType: 'P', // 在哪一个端播放,P:pc端,A:全平台
activeIndex: 0,
sessiontTime: 0,
lastSessionTime: 0,
microLetureTitle: '',
describ: '',
microIsplaying: false,
isInit: false,
canplay: false,
isDevPreview: null,
lessonMode: null,
crsSource: null,
isParticipate: null,
crsId: null,
itemId: null,
token: null
}
},
components: {
ProgressCircle
},
methods: {
revealInit (initWidth, initHeight) {
window.Reveal.initialize({
hash: false,
controlsLayout: 'edges',
controls: true,
progress: true,
loop: false,
touch: false,
autoSlide: 0,
keyboard: false
}).then(() => {
this.initPercent = '100%'
this.canplay = true
this.revealBoxStyle.height = initHeight + 'px'
this.revealBoxStyle.width = initWidth + 'px'
window.Reveal.configure({ width: initWidth, height: initHeight, maxScale: 1, minScale: 1 })
this.initEvent()
})
},
compatibleApi (data, method, pub) {
const apis = {
pc: {
getLecture: this.isDevPreview ? '/res/make/get/mic/lecture' : '/mic/course/get/mic/lecture',
tracking: '/play/tracking/set',
initialize: '/play/initialize',
trackingInitialize: '/play/tracking/initialize',
setValue: '/play/tracking/element/set',
commitValue: '/play/tracking/commit',
newSetValue: '/play/tracking/set'
},
app: {
router: '/app/router.do',
pub: '/app/pub/router.do',
methods: {
getLecture: 'get.mic.course',
finish: 'clm.res.play.tracking.finish',
newSetValue: 'clm.res.play.tracking.set',
trackingInitialize: 'clm.res.play.tracking.initialize',
courseDetail: 'clm.res.course.detail.get',
favorite: 'clm.user.favorite.course.create',
initialize: 'clm.res.play.initialize'
}
}
}
let url = ''
// eslint-disable-next-line no-extra-boolean-cast
if (!!this.token) {
url = !pub ? apis.app.router : apis.app.pub
return axios({
url: url,
method: 'post',
params: {
token: this.token,
method: apis.app.methods[method]
},
// headers: { 'Content-type': 'application/x-www-form-urlencoded' },
data: data
})
} else {
url = apis.pc[method]
if (this.browserVersion.mobile) { url = '/api' + url } else {
url = url + '.do'
}
return axios({
url: url,
method: 'post',
// headers: { 'Content-type': 'application/x-www-form-urlencoded' },
data: data
})
}
},
screenInit (pages) {
/* 首张图片加载计数,如果重复3次加载失败,则课程加载失败 */
if (!!this.count === false) {
this.count = 0
}
const imgUri = pages[0].imgUri
const img = new Image()
img.src = this.baseSourceUrl + imgUri + '?t=' + Math.random()
this.initPercent = '85%'
img.onload = () => {
this.pages = pages
this.$nextTick(() => {
this.initPercent = '95%'
})
const innerWidth = window.innerWidth
const innerHeight = window.innerHeight
const translate = 'translate(-50%, -50%)'
const rotate = ' rotate(90deg) '
if (innerWidth > innerHeight) {
const slideWidth = img.width / img.height * innerHeight
const scale = 'scale(' + innerWidth / slideWidth + ')'
if (slideWidth > innerWidth) {
this.revealBoxStyle.transform = translate + scale
} else {
this.revealBoxStyle.transform = translate
}
this.setBodyStyle(translate, innerWidth, innerHeight)
this.revealInit(slideWidth, innerHeight)
} else {
const slideWidth = img.height / img.width * innerHeight
const scale = 'scale(' + innerWidth / slideWidth + ')'
if (slideWidth > innerWidth) {
this.revealBoxStyle.transform = translate + scale
} else {
this.revealBoxStyle.transform = translate
}
this.setBodyStyle(translate + rotate, innerHeight, innerWidth)
this.revealInit(innerHeight, slideWidth)
}
}
img.onerror = () => {
if (this.count < 3) {
setTimeout(() => {
this.count = this.count += 1
this.screenInit(pages)
}, 2000)
} else {
this.showToast('课程加载失败')
}
}
},
setBodyStyle (transform, width, height) {
document.body.style.width = width + 'px'
document.body.style.height = height + 'px'
document.body.style.position = this.revealBoxStyle.position
document.body.style.left = this.revealBoxStyle.left
document.body.style.top = this.revealBoxStyle.top
document.body.style.transform = transform
},
initEvent () {
window.Reveal.on('slidechanged', (event) => {
this.$refs.audio.pause()
// console.log('切换成功:', this.pc_options.completedSteps)
this.trackingApi()
this.activeIndex = event.indexh
// 是否播放还是手动切换
this.microIsplaying = true
this.pc_options.completedSteps = 0
this.pc_options.totalSteps = this.pages[this.activeIndex].slideTime * 10
this.intervalComputed()
this.audioPlay()
})
window.Reveal.on('autoslidepaused', (event) => {
// console.log(event)
})
window.Reveal.on('autoslidepaused', (event) => {
// console.log(event)
})
},
showToast (message) {
Toast({
message: message,
position: 'middle',
duration: 2000
})
},
getBrowserVersion () {
const u = navigator.userAgent
return {
// IE内核
trident: u.indexOf('Trident') > -1,
// opera内核
presto: u.indexOf('Presto') > -1,
// 苹果、谷歌内核
webKit: u.indexOf('AppleWebKit') > -1,
// 火狐内核
// eslint-disable-next-line eqeqeq
gecko: u.indexOf('Gecko') > -1 && u.indexOf('KHTML') == -1,
// 是否为移动终端
mobile: !!u.match(/AppleWebKit.*Mobile.*/),
// ios终端
ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/),
// android终端
android: u.indexOf('Android') > -1 || u.indexOf('Adr') > -1,
// 是否为iPhone或者QQHD浏览器
iPhone: u.indexOf('iPhone') > -1,
// 是否iPad
iPad: u.indexOf('iPad') > -1,
// 是否web应该程序,没有头部与底部
// eslint-disable-next-line eqeqeq
webApp: u.indexOf('Safari') == -1,
// 是否微信 (2015-01-22新增)
weixin: u.indexOf('MicroMessenger') > -1,
// 是否QQ
// eslint-disable-next-line eqeqeq
qq: u.match(/\sQQ/i) == ' qq'
}
},
vueTouch: function (txt, e) {
if (txt === 'up') {
window.Reveal.next()
} else {
window.Reveal.prev()
}
},
toggle () {
this.microIsplaying = !this.microIsplaying
},
starPlay () {
if (this.canplay === false) {
return
}
this.isInit = true
this.microIsplaying = true
window.Reveal.configure({
keyboard: true,
touch: !this.browserVersion.mobile
})
/* 这里特别说明 this.pages[this.activeIndex].slideTime * 10
因为圆环进度条的动画细腻轻度与组件“ProgressCircle”的”animationDuration“属性有关
这里期望动画执行间隔微100毫秒
所以定时器执行间隔也为100毫秒
*/
this.pc_options.totalSteps = this.pages[this.activeIndex].slideTime * 10
this.intervalComputed()
this.audioPlay()
},
intervalComputed () {
clearInterval(this.interval)
this.interval = setInterval(() => {
const sessiontTime = this.sessiontTime + 1
this.sessiontTime = sessiontTime
if (this.pc_options.totalSteps === this.pc_options.completedSteps) {
window.Reveal.next()
return
}
this.pc_options.completedSteps = this.pc_options.completedSteps += 1
if (this.pc_options.completedSteps % 300 === 0) {
// console.log(this.pc_options.completedSteps)
this.trackingApi()
}
if (this.pc_options.totalSteps < 300 && this.activeIndex + 1 === this.pages.length) {
if (this.pc_options.totalSteps === this.pc_options.completedSteps) {
this.trackingApi()
}
}
}, 100)
},
audioPlay () {
if (this.pages[this.activeIndex].audioSrc !== '') {
this.$refs.audio.src = this.baseSourceUrl + this.pages[this.activeIndex].audioSrc
this.$refs.audio.load()
this.$refs.audio.play()
}
},
getTranscodingStatus (id) {
setTimeout(() => {
this.getLectureJson(id)
}, 5000)
},
timeToFormat (times) {
let result = '00:00:00'
let hour, minute, second
if (times > 0) {
hour = Math.floor(times / 3600)
if (hour < 10) {
hour = '0' + hour
}
minute = Math.floor((times - 3600 * hour) / 60)
if (minute < 10) {
minute = '0' + minute
}
second = Math.floor((times - 3600 * hour - 60 * minute) % 60)
if (second < 10) {
second = '0' + second
}
result = hour + ':' + minute + ':' + second
}
return result
},
setSessionTime () {
const params = this.setBaseParams()
params.set('itemId', this.itemId)
params.set('element', 'cmi.core.session_time')
params.set('value', this.timeToFormat(this.sessiontTime / 10))
this.compatibleApi(params, 'setValue').then((res) => {
// console.log(res)
})
},
setLessLocationPoint () {
const params = this.setBaseParams()
params.set('itemId', this.itemId)
params.set('element', 'qida.core.lesson_location_point')
params.set('value', this.activeIndex + 1)
this.compatibleApi(params, 'setValue').then((res) => {
// console.log(res)
})
},
setLessProgress () {
const params = this.setBaseParams()
params.set('itemId', this.itemId)
params.set('element', 'cmi.core.lesson_progress')
const percent = parseInt((this.activeIndex + 1) / this.pages.length * 100)
params.set('value', percent)
this.compatibleApi(params, 'setValue').then((res) => {
// console.log(res)
})
},
setLessStatus () {
const params = this.setBaseParams()
params.set('itemId', this.itemId)
params.set('element', 'cmi.core.lesson_status')
params.set('value', 'completed')
this.compatibleApi(params, 'setValue').then((res) => {
// console.log(res)
})
},
commitValue () {
const params = this.setBaseParams()
params.set('itemId', this.itemId)
this.compatibleApi(params, 'commitValue').then((res) => {
// console.log(res)
})
},
newSetValueAPI () {
const params = this.setBaseParams()
let clientType = 'p'
if (this.browserVersion.ios) {
clientType = 'I'
}
if (this.browserVersion.android) {
clientType = 'A'
}
if (!this.token && (this.browserVersion.ios || this.browserVersion.android)) {
clientType = 'H'
}
params.set('clientType', clientType)
params.set('itemId', this.itemId)
params.set('endLocation', parseInt((this.activeIndex + 1) / this.pages.length * 100))
const value = this.sessiontTime - this.lastSessionTime
this.lastSessionTime = this.lastSessionTime + value
params.set('sessionTime', parseInt(value / 10))
this.compatibleApi(params, 'newSetValue').then((res) => {
// console.log(res)
})
},
setBaseParams () {
const params = new URLSearchParams()
params.set('crsId', this.crsId)
params.set('crsSource', this.crsSource)
params.set('isParticipate', this.isParticipate)
params.set('attempId', this.attempId)
params.set('lessonMode', this.lessonMode)
params.set('driverType', this.driverType)
params.set('taskId', this.taskId)
params.set('learnType', this.learnType)
return params
},
appFinish () {
const params = new URLSearchParams()
params.set('crsId', this.crsId)
params.set('crsSource', this.crsSource)
params.set('sessionTime', this.sessiontTime / 10)
params.set('lessonMode', this.lessonMode)
params.set('chapterId', this.itemId)
params.set('lessonLocation', this.activeIndex + 1)
params.set('attempId', this.attempId)
const percent = parseInt((this.activeIndex + 1) / this.pages.length * 100)
params.set('lessonProgress', percent)
const status = this.activeIndex + 1 >= this.pages.length ? 'completed' : 'incomplete'
params.set('lessonStatus', status)
this.compatibleApi(params, 'finish').then((res) => {
// console.log(res)
})
},
aapCourseDetail (initParams) {
const params = new URLSearchParams()
params.append('id', this.crsId)
params.append('originType', this.crsSource)
this.compatibleApi(params, 'courseDetail', 'pub').then((res) => {
// console.log(res)
const isParticipate = res.data.values.isParticipate
this.isParticipate = isParticipate
this.lessonMode = isParticipate ? null : 'B'
this.appInitializeApi(initParams)
})
},
setupWebViewJavascriptBridge (callback) {
if (this.browserVersion.ios) {
if (window.WebViewJavascriptBridge) {
// eslint-disable-next-line no-undef
return callback(WebViewJavascriptBridge)
}
if (window.WVJBCallbacks) {
return window.WVJBCallbacks.push(callback)
}
window.WVJBCallbacks = [callback]
var WVJBIframe = document.createElement('iframe')
WVJBIframe.style.display = 'none'
WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__'
document.documentElement.appendChild(WVJBIframe)
setTimeout(function () {
document.documentElement.removeChild(WVJBIframe)
}, 0)
} else if (this.browserVersion.android) {
// require('../lib/WebViewJavascriptBridge')
// console.log('window.WebViewJavascriptBridge:', window.WebViewJavascriptBridge)
if (window.WebViewJavascriptBridge) {
// eslint-disable-next-line no-undef
callback(WebViewJavascriptBridge)
} else {
document.addEventListener(
'WebViewJavascriptBridgeReady',
function () {
// eslint-disable-next-line no-undef
callback(WebViewJavascriptBridge)
},
false
)
}
}
},
bridgeRegist () {
this.setupWebViewJavascriptBridge((bridge) => {
if (this.browserVersion.android) {
bridge.init((message) => {
// console.log('message:', message)
// console.log('bridge:', '初始化完成!')
})
}
bridge.registerHandler('toggle', (data) => {
const dataJson = JSON.parse(data)
// console.log('切换播放状态', dataJson)
if (dataJson.code === '0') {
if (this.microIsplaying) {
this.toggle()
}
}
})
})
},
goback () {
if (!this.token && (this.browserVersion.ios || this.browserVersion.android)) {
window.history.back()
// eslint-disable-next-line no-extra-boolean-cast
} else if (!!this.token) {
this.setupWebViewJavascriptBridge((bridge) => {
// console.log('goback:返回')
bridge.callHandler('goback', null, null)
})
}
},
appFavorite () {
const params = new URLSearchParams()
params.append('courseId', this.crsId)
params.append('originType', this.crsSource)
this.compatibleApi(params, 'favorite').then((res) => {
// console.log(res)
if (res.data.executeStatus === 0) {
this.showToast('加入自学成功')
this.aapCourseDetail()
setTimeout(() => {
window.location.reload()
}, 1000)
} else {
MessageBox('提示', res.data.errorMsg)
}
}).catch((err) => {
MessageBox('提示', err)
})
},
trackingApi () {
this.newSetValueAPI()
if (!this.token) {
this.setSessionTime()
this.setLessLocationPoint()
this.setLessProgress()
this.commitValue()
if (this.activeIndex + 1 >= this.pages.length) {
this.setLessStatus()
}
} else {
this.appFinish()
}
},
getLectureJson (id) {
// const url = this.isDevPreview ? '/res/make/get/mic/lecture.do' : '/mic/course/get/mic/lecture.do'
// axios({
// url: url,
// method: 'post',
// data: { id: id }
// })
const params = new URLSearchParams()
params.append('id', id)
this.compatibleApi(params, 'getLecture').then((res) => {
const { buildStatus, coursewareDir, id } = res.data.data.slideCourse
const { initial, parsing, parsingComplete, fail, finish, publish } = this.buildStatus.status
switch (buildStatus) {
case initial:
this.getTranscodingStatus(id)
break
case parsing:
this.buildStatus.current = parsing
this.getTranscodingStatus(id)
break
case parsingComplete:
this.buildStatus.current = parsingComplete
this.getPages(coursewareDir, id)
break
case finish:
this.buildStatus.current = finish
this.getPages(coursewareDir, id)
break
case publish:
this.buildStatus.current = publish
this.getPages(coursewareDir, id)
break
default:
this.showToast('课程获取失败')
this.buildStatus.current = fail
this.currentUploadStatus = this.uploadStatus.normal
break
}
if (res.data.data.courseInfo) {
this.microLectureTitle = res.data.data.courseInfo.courseTitle
document.title = this.microLectureTitle + '_企大网'
this.introduction = res.data.data.courseInfo.description
} else if (res.data.data.slideCourse) {
this.microLectureTitle = res.data.data.slideCourse.name
document.title = this.microLectureTitle + '_企大网'
this.introduction = res.data.data.slideCourse.description
}
}).catch(() => {
this.showToast('课程获取失败')
})
},
getPages (coursewareDir, microId) {
const url = 'https://qida-videos.oss-cn-shenzhen.aliyuncs.com/' + coursewareDir + '/content.json'
this.microId = microId
axios({
url: url,
method: 'get',
responseType: 'json'
}).then((res) => {
// console.log('获取JSON:', res.data)
this.initPercent = '65%'
this.screenInit(res.data)
}).catch((err) => {
console.log(err)
})
},
setApi (params) {
this.compatibleApi(params, 'setValue').then((res) => {
// console.log(res)
})
},
initializeApi (params) {
this.compatibleApi(params, 'initialize').then((res) => {
// console.log(res)
if (res.data.executeStatus === 0) {
this.itemId = res.data.values.itemId
params.append('itemId', res.data.values.itemId)
// params.append('lessonMode', this.lessonMode)
this.trackingInitializeApi(params)
} else {
MessageBox('提示', res.data.errorMsg)
}
}).catch((err) => {
MessageBox('提示', err)
})
},
appInitializeApi (params) {
this.compatibleApi(params, 'initialize').then((res) => {
// console.log(res)
if (res.data.executeStatus === 0) {
this.itemId = res.data.values.chapterId
this.appTrackingInitializeApi(res.data.values)
} else {
MessageBox('提示', res.data.errorMsg)
}
}).catch((err) => {
MessageBox('提示', err)
})
},
trackingInitializeApi (params) {
this.compatibleApi(params, 'trackingInitialize').then((res) => {
// console.log(res)
if (res.data.executeStatus === 0) {
if (!this.lessonMode) {
this.attempId = res.data.values.attempId
}
this.getLectureJson(this.crsId)
} else {
MessageBox('提示', res.data.errorMsg)
}
}).catch((err) => {
MessageBox('提示', err)
})
},
appTrackingInitializeApi (data) {
const params = new URLSearchParams()
params.set('lessonMode', this.lessonMode)
params.set('driverType', this.driverType)
params.set('crsSource', this.crsSource)
params.set('resourceId', data.resourceId)
params.set('chapterId', data.chapterId)
params.set('crsType', data.crsType)
this.compatibleApi(params, 'trackingInitialize').then((res) => {
// console.log(res)
if (res.data.executeStatus === 0) {
if (!this.lessonMode) {
this.attempId = res.data.values.attempId
}
this.getLectureJson(this.crsId)
} else {
MessageBox('提示', res.data.errorMsg)
}
}).catch((err) => {
MessageBox('提示', err)
})
}
},
watch: {
microIsplaying (val, oldval) {
/* 暂停播放 */
if (val === false && oldval === true) {
clearInterval(this.interval)
if (this.pages[this.activeIndex].audioSrc !== '') {
this.$refs.audio.pause()
}
}
/* 开始播放 */
if (val === true && oldval === false) {
this.intervalComputed()
if (this.pages[this.activeIndex].audioSrc !== '') {
this.$refs.audio.play()
}
}
}
},
created () {
// console.log(this.$route.query)
this.initPercent = '5%'
const query = this.$route.query
const { isDevPreview, lessonMode, crsSource, isParticipate, crsId, token, taskId, learnType } = query
this.isDevPreview = isDevPreview
this.lessonMode = lessonMode
this.crsSource = crsSource
this.isParticipate = isParticipate
this.crsId = crsId
this.token = token
this.taskId = taskId || 0
this.learnType = learnType || 'S'
this.browserVersion = this.getBrowserVersion()
// this.getLectureJson(crsId)
// eslint-disable-next-line no-extra-boolean-cast
if (!this.isDevPreview) {
const params = this.setBaseParams()
if (!this.token) {
this.initializeApi(params)
} else {
const params = this.setBaseParams()
this.aapCourseDetail(params)
this.bridgeRegist()
}
} else {
this.getLectureJson(crsId)
}
},
mounted () {
// this.getLectureJson(this.$route.query.id)
}
}
</script>
<style lang="scss">
#mlplayBox {
width: 100%;
height: 100%;
.goback {
/* top: 20px; */
left: 20px;
/* right: unset!important; */
/* bottom: unset!important; */
/* background: rgba(0,0,0,.4)!important; */
/* color: hsla(0,0%,98.4%,.6)!important; */
/* border: 1px solid hsla(0,0%,98.4%,.6)!important; */
width: 60px !important;
}
// .goback i:after{
// content: "\21B5";
// font-size: .16rem;
// }
.addStudy {
position: fixed;
right: 0;
bottom: 0;
color: #FFF;
background-color: #59b8fb;
line-height: 1;
font-size: 1.07692308em;
padding: 0.61538462em 1.23076923em;
z-index: 1;
border-radius: 0.30769231em;
box-shadow: 0 0 0.61538462em rgba(0, 0, 0, 0.4);
border: none;
outline: 0;
}
.reveal img {
margin: 0;
}
.playControl {
position: absolute;
bottom: 20px;
left: 20px;
z-index: 12;
transform: translate3d(0, 0, 0);
cursor: pointer;
i {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #fff;
}
}
.pageNum {
width: 80px;
height: 35px;
background: rgb(0 0 0 / 40%);
z-index: 99;
position: absolute;
top: 20px;
right: 20px;
border-radius: 5px;
transform: translateZ(0);
text-align: center;
line-height: 35px;
font-size: .16rem;
color: rgb(251 251 251 / 60%);
border: 1px solid rgb(251 251 251 / 0.6);
}
.reveal .progress {
position: absolute;
display: none;
height: 3px;
width: 100%;
bottom: 0;
left: 0;
z-index: 10;
background-color: rgba(0,0,0,.2);
transform: translate3d(0, 0, 0);
}
section {
top: 330px;
display: block;
height: 100%;
padding: 0;
cursor: pointer;
background-size: contain;
background-position: center;
background-repeat: no-repeat;
box-sizing: border-box;
// border: 1px solid red;
top: 0 !important;
left: 0 !important;
}
// .reveal.slide section {
// -webkit-backface-visibility: visible;
// backface-visibility: visible;
// }
// .reveal .slides>section, .reveal .slides>section>section {
// box-sizing: border-box;
// border: 1px solid red;
// display: none;
// position: absolute;
// width: 100%;
// pointer-events: auto;
// z-index: 10;
// transform-style: flat;
// transition: transform-origin .8s cubic-bezier(.26,.86,.44,.985),transform .8s cubic-bezier(.26,.86,.44,.985),visibility .8s cubic-bezier(.26,.86,.44,.985),opacity .8s cubic-bezier(.26,.86,.44,.985);
// }
.reveal .slides>section[data-transition=convex].past, .reveal .slides>section[data-transition~=convex-out].past, .reveal.convex .slides>section:not([data-transition]).past {
transform: translate3d(-100%,0,0) rotateY(-90deg) translate3d(-100%,0,0);
}
.reveal p {
margin: 0;
line-height: 1.3;
position: absolute;
bottom: 0;
font-size: .12rem;
padding: 0.06rem .06rem;
width: 100%;
background: rgb(0 0 0 / 0.6);
box-sizing: border-box;
text-shadow: #000 1px 0 0, #000 0 1px 0, #000 -1px 0 0, #000 0 -1px 0;
}
.starIcon {
position: fixed;
z-index: 100;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.6);
text-align: center;
top: 50%;
left: 50%;
transform: translate3d(-50%, -50%, 0);
.starTitle {
width: 80%;
max-width: 500px;
position: absolute;
top: 40%;
left: 50%;
transform: translate(-50%, -60%);
color: #fff;
cursor: pointer;
.loadingBox {
width: 100%;
text-align: left;
font-size: 0.16rem;
color: #ccc;
margin: 20px 0;
background: #909090;
border-radius: 2px;
height: 2px;
}
.loadingTrace {
height: 2px;
background: #fff;
border-radius: 2px;
transition-property: width;
transition-duration: 0.1s;
transition-timing-function: linear;
transition-delay: 0;
/* Safari */
-webkit-transition-property: width;
-webkit-transition-duration: 0.1s;
-webkit-transition-timing-function: linear;
-webkit-transition-delay: 0;
}
i {
font-size: 0.32rem;
}
h2 {
font-size: 0.16rem;
margin: 20px 0 15px 0
}
div {
font-size: 0.12rem;
font-weight: 300;
}
}
}
}
</style>
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<!--
2013-9-30: Created.
-->
<svg>
<metadata>
Created by iconfont
</metadata>
<defs>
<font id="iconfont" horiz-adv-x="1024" >
<font-face
font-family="iconfont"
font-weight="500"
font-stretch="normal"
units-per-em="1024"
ascent="896"
descent="-128"
/>
<missing-glyph />
<glyph glyph-name="fanhui" unicode="&#58961;" d="M771.988-51.44l-403.54 405.135c-15.95 15.95-15.95 43.065 0 60.61l406.73 406.73c17.545 15.95 19.14 43.065 3.19 60.61C770.393 891.215 759.228 896 746.468 896s-23.926-4.785-31.901-14.355l-9.57-9.57-457.77-459.365c-15.95-15.95-15.95-43.065 0-60.61l467.34-467.34c7.975-7.975 17.545-12.76 28.71-12.76 23.925 0 43.066 19.14 43.066 41.47 1.595 14.356-4.785 27.116-14.355 35.09z" horiz-adv-x="1024" />
</font>
</defs></svg>
@font-face {
font-family: 'iconfont';
src: url('./fonts/iconfont.eot');
src: url('./fonts/iconfont.eot?#iefix') format('embedded-opentype'),
url('./fonts/iconfont.woff2') format('woff2'),
url('./fonts/iconfont.woff') format('woff'),
url('./fonts/iconfont.ttf') format('truetype'),
url('./fonts/iconfont.svg#iconfont') format('svg');
}
@font-face {
font-family: 'iconfont';
src: url('./backfont/iconfont.eot');
src: url('./backfont/iconfont.eot?#iefix') format('embedded-opentype'),
url('./backfont/iconfont.woff2') format('woff2'),
url('./backfont/iconfont.woff') format('woff'),
url('./backfont/iconfont.ttf') format('truetype'),
url('./backfont/iconfont.svg#iconfont') format('svg');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
\ No newline at end of file
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<!--
2013-9-30: Created.
-->
<svg>
<metadata>
Created by iconfont
</metadata>
<defs>
<font id="iconfont" horiz-adv-x="1024" >
<font-face
font-family="iconfont"
font-weight="500"
font-stretch="normal"
units-per-em="1024"
ascent="896"
descent="-128"
/>
<missing-glyph />
<glyph glyph-name="htmal5icon13" unicode="&#58926;" d="M703.109596 407.222911c0-112.022353-93.352131-190.437284-197.905372-190.437284s-197.906395 78.414931-197.906395 190.437284l-63.478753 0c0-126.95853 100.82022-231.512794 224.044706-250.183016l0-123.224486 74.680886 0 0 123.224486c123.224486 18.670222 224.044706 123.224486 224.044706 250.183016L703.109596 407.222911zM505.204224 295.201581c63.478753 0 112.022353 48.543599 112.022353 112.022353l0 224.044706c0 63.479777-48.543599 112.022353-112.022353 112.022353-63.479777 0-112.022353-48.543599-112.022353-112.022353l0-224.044706C393.181871 343.744157 441.724447 295.201581 505.204224 295.201581z" horiz-adv-x="1024" />
<glyph glyph-name="htmal5icon15" unicode="&#58927;" d="M766.589372 407.222911l-63.479777 0c0-26.13831-7.468089-52.27662-14.936177-74.680886l44.809555-44.808532C755.386216 321.339892 766.589372 362.414379 766.589372 407.222911zM617.225553 399.755846c0 3.734044 0 3.734044 0 7.468089l0 224.044706c0 63.479777-48.543599 112.022353-112.022353 112.022353-63.479777 0-112.022353-48.543599-112.022353-112.022353l0-7.468089L617.225553 399.755846zM217.679741 705.949526l-48.542576-48.542576 224.043682-224.044706 0-26.13831c0-63.478753 48.543599-112.022353 112.022353-112.022353 7.468089 0 14.936177 0 26.13831 3.734044l63.479777-63.478753c-26.13831-11.202133-56.010665-18.670222-85.884043-18.670222-104.554264 0-197.905372 78.414931-197.905372 190.437284l-67.213821 0c0-126.95853 100.82022-231.512794 224.044706-250.183016l0-123.224486 74.680886 0 0 123.224486c33.606399 3.734044 67.213821 18.670222 93.352131 33.606399l156.830885-156.830885 48.543599 48.543599L217.679741 705.949526z" horiz-adv-x="1024" />
<glyph glyph-name="bofang" unicode="&#58963;" d="M852.727563 503.552893C956.997809 437.526365 956.941389 330.440483 852.727563 264.44968L281.888889-97.019655C177.618644-163.046186 93.090909-120.054114 93.090909-1.137364L93.090909 769.139937C93.090909 888.120794 177.675064 931.013033 281.888889 865.022231L852.727563 503.552893 852.727563 503.552893Z" horiz-adv-x="1024" />
<glyph glyph-name="bofang1" unicode="&#59262;" d="M222.521003 731.670328l0-694.317349 543.891338 347.158674L222.521003 731.670328M178.355107 831.752744c5.207605 0 10.549264-1.178849 15.644305-3.729951l681.693844-416.160211c23.359011-11.698436 23.359011-43.00956 0-54.707996L193.999412-59.006649c-5.096065-2.543939-10.437723-3.715625-15.644305-3.715625-17.382902 0-33.235962 13.025665-33.235962 31.079856L145.119145 800.664701C145.118122 818.711729 160.956855 831.752744 178.355107 831.752744L178.355107 831.752744z" horiz-adv-x="1024" />
<glyph glyph-name="zanting" unicode="&#59005;" d="M379.337561 777.651568 145.347343 777.651568c-8.113795 0-16.247033-3.097548-22.444175-9.295714-6.197142-6.1777-9.295714-14.309914-9.295714-22.424732l0-729.759559c0-8.094352 3.098571-16.22759 9.295714-22.424732 6.197142-6.176676 14.33038-9.276271 22.444175-9.276271l233.990219 0c8.113795 0 16.247033 3.098571 22.444175 9.276271 6.1777 6.197142 9.275247 14.33038 9.275247 22.424732l0 729.760582c0 8.112772-3.098571 16.247033-9.275247 22.424732C395.584594 774.552997 387.451356 777.651568 379.337561 777.651568L379.337561 777.651568zM897.531634 768.356878c-6.197142 6.197142-14.33038 9.295714-22.444175 9.295714L641.09724 777.652592c-8.113795 0-16.247033-3.098571-22.444175-9.295714-6.196119-6.1777-9.295714-14.31196-9.295714-22.424732l0-729.759559c0-8.094352 3.099594-16.22759 9.295714-22.424732 6.197142-6.176676 14.33038-9.275247 22.444175-9.275247l233.990219 0c8.113795 0 16.247033 3.098571 22.444175 9.275247 6.197142 6.197142 9.275247 14.33038 9.275247 22.424732L906.806881 745.932146C906.806881 754.045941 903.727753 762.178155 897.531634 768.356878L897.531634 768.356878z" horiz-adv-x="1024" />
<glyph glyph-name="ico_meiyoushuju" unicode="&#58882;" d="M409.073-4.723l-263.213-0.137c-11.371 0-23.996 1.375-32.172 11.783-4.158 5.273-6.94 22.192-6.94 33.838V794.9c0.276 2.644 2.37 20.25 12.625 30.506 14.565 14.565 25.524 16.92 27.45 17.194h406.894v-108.85c-0.153-5.685 0.275-42.717 21.489-66.987 19.563-22.314 50.482-33.976 53.951-35.23l4.432-1.65 144.094-0.153v-129.94l45.484-35.076c2.644-2.644 5.273-5.41 7.764-8.055V690.757L598.238 896H145.844c-5.257 0-32.997-1.528-64.19-32.86C56.13 837.617 53.637 801.12 53.485 797.115v-755.65c-0.153-7.642 0.55-44.522 18.156-67.126a80.667 80.667 0 0 1 43.406-28.58c13.45-3.73 25.11-3.73 30.796-3.73l359.042 0.138a315.606 315.606 0 0 0-63.518 28.275l-32.325 24.835zM606.965 817.228L758.823 683.13l-115.513 0.138c-7.642 3.316-20.817 10.255-28.168 18.585-5.808 6.648-8.314 22.467-8.314 31.193l0.153 84.182z m351.156-871.882a42.55 42.55 0 0 0 0.153-59.912 42.672 42.672 0 0 0-29.956-12.334 42.443 42.443 0 0 0-29.956 12.35L767.305 16.49a277.596 277.596 0 0 0-165.17-54.226c-153.936 0-279.17 125.234-279.17 279.17 0 153.936 125.234 279.307 279.17 279.307 153.937 0 279.155-125.233 279.155-279.17 0-61.715-20.113-118.845-54.226-165.17l131.057-131.04z m-355.985 85.71c116.078 0 210.516 94.453 210.516 210.516 0 116.08-94.453 210.516-210.516 210.516-116.08 0-210.516-94.452-210.516-210.516 0-116.079 94.437-210.516 210.516-210.516z" horiz-adv-x="1024" />
<glyph glyph-name="gengxin" unicode="&#58884;" d="M887.4 485.4l-61.8-16.6c30.3-112.7-2.1-233.9-84.5-316.3-127.5-127.5-334.8-127.5-462.3 0-61.7 61.7-95.7 143.8-95.7 231.1 0 87.3 34 169.4 95.7 231.1 82.4 82.4 203.5 114.8 316.2 84.5L580.4 645l147.4 52.6L626.5 816.8l-14.9-55.7c-65.1 17.5-134.1 17.8-199.4 1-67.4-17.4-129.1-52.7-178.6-102.1-37.3-37.3-66.2-80.8-85.9-129.3-19-46.8-28.6-96.3-28.6-147.1s9.6-100.2 28.6-147.1c19.7-48.5 48.6-92 85.9-129.3 37.3-37.3 80.8-66.2 129.3-85.9 46.8-19 96.3-28.6 147.1-28.6s100.2 9.6 147.1 28.6c48.5 19.7 92 48.6 129.3 85.9 49.4 49.4 84.8 111.2 102.1 178.6 16.8 65.4 16.4 134.4-1.1 199.6z" horiz-adv-x="1024" />
<glyph glyph-name="Error" unicode="&#59111;" d="M853.333333 810.666667c56.8832 0 85.333333-28.450133 85.333334-85.333334v-85.333333H85.333333v85.333333c0 56.8832 28.450133 85.333333 85.333334 85.333334h682.666666z m-512-85.333334c0 11.946667-4.266667 22.186667-12.8 30.72-7.970133 7.970133-17.92 11.946667-29.866666 11.946667s-22.186667-3.976533-30.72-11.946667c-7.970133-8.533333-11.946667-18.773333-11.946667-30.72s3.976533-21.896533 11.946667-29.866666c8.533333-8.533333 18.773333-12.8 30.72-12.8s21.896533 4.266667 29.866666 12.8c8.533333 7.970133 12.8 17.92 12.8 29.866666z m115.2-29.866666c8.533333 7.970133 12.8 17.92 12.8 29.866666s-4.266667 22.186667-12.8 30.72c-7.970133 7.970133-17.92 11.946667-29.866666 11.946667s-22.186667-3.976533-30.72-11.946667c-7.970133-8.533333-11.946667-18.773333-11.946667-30.72s3.976533-21.896533 11.946667-29.866666c8.533333-8.533333 18.773333-12.8 30.72-12.8s21.896533 4.266667 29.866666 12.8z m86.186667-597.333334c-7.970133-8.533333-17.92-12.8-29.866667-12.8s-22.186667 4.266667-30.72 12.8c-7.970133 7.970133-11.946667 17.92-11.946666 29.866667s3.976533 22.186667 11.946666 30.72c8.533333 7.9872 18.773333 11.946667 30.72 11.946667s21.896533-3.959467 29.866667-11.946667c8.533333-8.533333 12.8-18.773333 12.8-30.72s-4.266667-21.896533-12.8-29.866667zM170.666667 768c-11.946667 0-22.186667-3.976533-30.72-11.946667-7.970133-8.533333-11.946667-18.773333-11.946667-30.72s3.976533-21.896533 11.946667-29.866666c8.533333-8.533333 18.773333-12.8 30.72-12.8s21.896533 4.266667 29.866666 12.8c8.533333 7.970133 12.8 17.92 12.8 29.866666s-4.266667 22.186667-12.8 30.72c-7.970133 7.970133-17.92 11.946667-29.866666 11.946667zM853.333333-59.733333H170.666667c-66.030933 0-102.4 36.369067-102.4 102.4V725.333333c0 66.030933 36.369067 102.4 102.4 102.4h682.666666c66.030933 0 102.4-36.369067 102.4-102.4v-682.666666c0-66.030933-36.369067-102.4-102.4-102.4zM102.4 622.933333v-580.266666c0-47.223467 21.0432-68.266667 68.266667-68.266667h682.666666c47.223467 0 68.266667 21.0432 68.266667 68.266667V622.933333H102.4z m0 34.133334h819.2v68.266666c0 47.223467-21.0432 68.266667-68.266667 68.266667H170.666667c-47.223467 0-68.266667-21.0432-68.266667-68.266667v-68.266666zM512.853333 232.106667a34.133333 34.133333 0 0 0-34.133333 34.133333V503.466667a34.133333 34.133333 0 0 0 68.266667 0v-237.226667a34.133333 34.133333 0 0 0-34.133334-34.133333z" horiz-adv-x="1024" />
<glyph glyph-name="zanting1" unicode="&#58894;" d="M304 720h80v-672h-80z m408 0h-64c-4.4 0-8-3.6-8-8v-656c0-4.4 3.6-8 8-8h64c4.4 0 8 3.6 8 8V712c0 4.4-3.6 8-8 8z" horiz-adv-x="1024" />
<glyph glyph-name="tianjia" unicode="&#58964;" d="M992 416H544V864a32 32 0 0 1-64 0v-448H32a32 32 0 0 1 0-64h448v-448a32 32 0 0 1 64 0V352h448a32 32 0 0 1 0 64z" horiz-adv-x="1024" />
</font>
</defs></svg>
/* eslint-disable */
//notation: js file can only use this kind of comments
//since comments will cause error when use in webview.loadurl,
//comments will be remove by java use regexp
(function() {
if (window.WebViewJavascriptBridge) {
return;
}
var receiveMessageQueue = [];
var messageHandlers = {};
var responseCallbacks = {};
var uniqueId = 1;
//set default messageHandler 初始化默认的消息线程
function init(messageHandler) {
if (WebViewJavascriptBridge._messageHandler) {
throw new Error('WebViewJavascriptBridge.init called twice');
}
WebViewJavascriptBridge._messageHandler = messageHandler;
var receivedMessages = receiveMessageQueue;
receiveMessageQueue = null;
for (var i = 0; i < receivedMessages.length; i++) {
_dispatchMessageFromNative(receivedMessages[i]);
}
}
// 发送
function send(data, responseCallback) {
_doSend('send', data, responseCallback);
}
// 注册线程 往数组里面添加值
function registerHandler(handlerName, handler) {
messageHandlers[handlerName] = handler;
}
// 调用线程
function callHandler(handlerName, data, responseCallback) {
_doSend(handlerName, data, responseCallback);
}
//sendMessage add message, 触发native处理 sendMessage
function _doSend(handlerName, message, responseCallback) {
var callbackId;
if(typeof responseCallback === 'string'){
callbackId = responseCallback;
} else if (responseCallback) {
callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();
responseCallbacks[callbackId] = responseCallback;
}else{
callbackId = '';
}
try {
var fn = eval('window.android.' + handlerName);
} catch(e) {
console.log(e);
}
if (typeof fn === 'function'){
var responseData = fn.call(this, JSON.stringify(message), callbackId);
if(responseData){
console.log('response message: '+ responseData);
responseCallback = responseCallbacks[callbackId];
if (!responseCallback) {
return;
}
responseCallback(responseData);
delete responseCallbacks[callbackId];
}
}
}
//提供给native使用,
function _dispatchMessageFromNative(messageJSON) {
setTimeout(function() {
var message = JSON.parse(messageJSON);
var responseCallback;
//java call finished, now need to call js callback function
if (message.responseId) {
responseCallback = responseCallbacks[message.responseId];
if (!responseCallback) {
return;
}
responseCallback(message.responseData);
delete responseCallbacks[message.responseId];
} else {
//直接发送
if (message.callbackId) {
var callbackResponseId = message.callbackId;
responseCallback = function(responseData) {
_doSend('response', responseData, callbackResponseId);
};
}
var handler = WebViewJavascriptBridge._messageHandler;
if (message.handlerName) {
handler = messageHandlers[message.handlerName];
}
//查找指定handler
try {
handler(message.data, responseCallback);
} catch (exception) {
if (typeof console != 'undefined') {
console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception);
}
}
}
});
}
//提供给native调用,receiveMessageQueue 在会在页面加载完后赋值为null,所以
function _handleMessageFromNative(messageJSON) {
console.log('handle message: '+ messageJSON);
if (receiveMessageQueue) {
receiveMessageQueue.push(messageJSON);
}
_dispatchMessageFromNative(messageJSON);
}
var WebViewJavascriptBridge = window.WebViewJavascriptBridge = {
init: init,
send: send,
registerHandler: registerHandler,
callHandler: callHandler,
_handleMessageFromNative: _handleMessageFromNative
};
var doc = document;
var readyEvent = doc.createEvent('Events');
readyEvent.initEvent('WebViewJavascriptBridgeReady');
readyEvent.bridge = WebViewJavascriptBridge;
doc.dispatchEvent(readyEvent);
})();
\ No newline at end of file
import lamejs from 'lamejs'
function convertToMp3 (recorder, recorderConfig) {
// 获取wav头信息
const { numChannels: channels, sampleRate } = recorderConfig
const mp3enc = new lamejs.Mp3Encoder(channels, sampleRate, 128)
// 获取左右通道数据
const result = recorder.getChannelData()
const buffer = []
const leftData = result.left && new Int16Array(result.left.buffer, 0, result.left.byteLength / 2)
const rightData = result.right && new Int16Array(result.right.buffer, 0, result.right.byteLength / 2)
const remaining = leftData.length + (rightData ? rightData.length : 0)
const maxSamples = 1152
for (let i = 0; i < remaining; i += maxSamples) {
const left = leftData.subarray(i, i + maxSamples)
let right = null
let mp3buf = null
if (channels === 2) {
right = rightData.subarray(i, i + maxSamples)
mp3buf = mp3enc.encodeBuffer(left, right)
} else {
mp3buf = mp3enc.encodeBuffer(left)
}
if (mp3buf.length > 0) {
buffer.push(mp3buf)
}
}
const enc = mp3enc.flush()
if (enc.length > 0) {
buffer.push(enc)
}
return new Blob(buffer, { type: 'audio/mp3' })
}
export default convertToMp3
import Vue from 'vue'
import App from './App.vue'
import axios from 'axios'
import router from './router'
axios.defaults.baseURL = window.location.origin
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
{
path: '/microEdit',
component: () => import(/* webpackChunkName:"name" */'@/components/MicroEdit.vue'),
meta: {
title: '微课编辑'
}
}, {
path: '/microLecturePlay',
component: () => import(/* webpackChunkName:"name" */'@/components/MicroLecturePlay.vue'),
meta: {
title: '微课'
}
}
]
const router = new VueRouter({
routes
})
export default router
module.exports = {
publicPath: process.env.NODE_ENV === 'production' ? './' : '/',
assetsDir: 'static',
outputDir: 'micro-lecture'
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment