|
@@ -1,10 +1,4 @@
|
|
|
-import {
|
|
|
- Icon,
|
|
|
- showConfirmDialog,
|
|
|
- Slider,
|
|
|
- Swipe,
|
|
|
- SwipeItem
|
|
|
-} from 'vant'
|
|
|
+import { Icon, showConfirmDialog, Slider, Swipe, SwipeItem } from 'vant'
|
|
|
import {
|
|
|
defineComponent,
|
|
|
onMounted,
|
|
@@ -13,6 +7,8 @@ import {
|
|
|
ref,
|
|
|
watch,
|
|
|
Transition,
|
|
|
+ nextTick,
|
|
|
+ computed
|
|
|
} from 'vue'
|
|
|
import styles from './index.module.less'
|
|
|
import 'plyr/dist/plyr.css'
|
|
@@ -28,6 +24,8 @@ import iconpause from '../coursewarePlay/image/icon-pause.svg'
|
|
|
import iconVideobg from '../coursewarePlay/image/icon-videobg.png'
|
|
|
import { browser, getSecondRPM } from '@/helpers/utils'
|
|
|
import qs from 'query-string'
|
|
|
+import { Vue3Lottie } from 'vue3-lottie'
|
|
|
+import playLoadData from '../coursewarePlay/datas/data.json'
|
|
|
|
|
|
const materialType = {
|
|
|
视频: 'VIDEO',
|
|
@@ -38,7 +36,23 @@ const materialType = {
|
|
|
export default defineComponent({
|
|
|
name: 'exercise-after-class',
|
|
|
setup() {
|
|
|
+ /** 设置播放容器 16:9 */
|
|
|
+ const parentContainer = reactive({
|
|
|
+ width: '100vw'
|
|
|
+ })
|
|
|
+ const setContainer = () => {
|
|
|
+ let min = Math.min(screen.width, screen.height)
|
|
|
+ let max = Math.max(screen.width, screen.height)
|
|
|
+ let width = min * (16 / 9)
|
|
|
+ if (width > max) {
|
|
|
+ parentContainer.width = '100vw'
|
|
|
+ return
|
|
|
+ } else {
|
|
|
+ parentContainer.width = width + 'px'
|
|
|
+ }
|
|
|
+ }
|
|
|
const handleInit = (type = 0) => {
|
|
|
+ setContainer()
|
|
|
// 横屏
|
|
|
postMessage({
|
|
|
api: 'setRequestedOrientation',
|
|
@@ -67,20 +81,13 @@ export default defineComponent({
|
|
|
})
|
|
|
|
|
|
const route = useRoute()
|
|
|
- watch(
|
|
|
- () => route.query,
|
|
|
- () => {
|
|
|
- getDetail()
|
|
|
- trainingRecord()
|
|
|
- }
|
|
|
- )
|
|
|
const router = useRouter()
|
|
|
const query = route.query
|
|
|
const browserInfo = browser()
|
|
|
const headeRef = ref()
|
|
|
const data = reactive({
|
|
|
videoData: null as any,
|
|
|
- details: [] as any,
|
|
|
+ trainings: [] as any[],
|
|
|
trainingTimes: 0,
|
|
|
itemList: [] as any,
|
|
|
showHead: true,
|
|
@@ -93,71 +100,50 @@ export default defineComponent({
|
|
|
timer: null as any,
|
|
|
item: null as any
|
|
|
})
|
|
|
- const getDetail = async () => {
|
|
|
- data.itemList = []
|
|
|
- let details = []
|
|
|
- try {
|
|
|
- const res: any = await request.get(
|
|
|
- state.platformApi + `/lessonTraining/courseSchedule/${route.query.courseScheduleId}`
|
|
|
- )
|
|
|
- if (Array.isArray(res?.data)) {
|
|
|
- const studentLevel = state.user?.data?.studentLevel || 1
|
|
|
- details = res.data.find((n: any) => n.studentLevel === studentLevel)?.details || []
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- console.log('error')
|
|
|
- }
|
|
|
- if (details.length) {
|
|
|
- data.details = details
|
|
|
- const videoData: any =
|
|
|
- details.find((n: any) => n.materialId == route.query.materialId) || {}
|
|
|
- try {
|
|
|
- videoData.training = JSON.parse(videoData?.lessonTrainingTemp?.trainingConfigJson)
|
|
|
- } catch (error) {}
|
|
|
- data.itemList.push({
|
|
|
- ...videoData,
|
|
|
- id: videoData.materialId,
|
|
|
- currentTime: 0,
|
|
|
- duration: 100,
|
|
|
- paused: true,
|
|
|
- loop: false,
|
|
|
- videoEle: null,
|
|
|
- timer: null,
|
|
|
- muted: true, // 静音
|
|
|
- autoplay: true, //自动播放
|
|
|
- })
|
|
|
- popupData.itemActive = videoData.id
|
|
|
- popupData.tabName = videoData.materialName
|
|
|
- data.videoData = videoData
|
|
|
- handleExerciseCompleted()
|
|
|
- }
|
|
|
- }
|
|
|
- const getTrainingTimes = (res: any) => {
|
|
|
- let trainingTimes = 0
|
|
|
- if (Array.isArray(res?.trainings)) {
|
|
|
- const train = res.trainings.find((n: any) => n.materialId === route.query?.materialId)
|
|
|
- if (train) {
|
|
|
- trainingTimes = train.trainingTimes
|
|
|
- }
|
|
|
- }
|
|
|
- data.trainingTimes = trainingTimes
|
|
|
- }
|
|
|
// 获取课后练习记录
|
|
|
- const trainingRecord = async () => {
|
|
|
+ const getTrainingRecord = async () => {
|
|
|
try {
|
|
|
const res: any = await request.post(
|
|
|
state.platformApi +
|
|
|
- `/studentLessonTraining/trainingRecord/${query.courseScheduleId}?userId=${state.user?.data?.id}`
|
|
|
+ `/studentLessonTraining/trainingRecord/${query.courseScheduleId}?userId=${state.user?.data?.id}`,
|
|
|
+ {
|
|
|
+ hideLoading: true
|
|
|
+ }
|
|
|
)
|
|
|
- if (res?.data) {
|
|
|
- getTrainingTimes(res.data)
|
|
|
- handleExerciseCompleted()
|
|
|
+ if (Array.isArray(res?.data?.trainings)) {
|
|
|
+ return res.data.trainings
|
|
|
}
|
|
|
} catch (error) {}
|
|
|
+ return []
|
|
|
}
|
|
|
- onMounted(() => {
|
|
|
- getDetail()
|
|
|
- trainingRecord()
|
|
|
+ const setRecord = async (trainings: any[]) => {
|
|
|
+ if (Array.isArray(trainings)) {
|
|
|
+ data.trainings = trainings.map((n: any) => {
|
|
|
+ try {
|
|
|
+ n.trainingContent = JSON.parse(n.trainingContent)
|
|
|
+ } catch (error) {
|
|
|
+ n.trainingContent = ''
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ ...n,
|
|
|
+ currentTime: 0,
|
|
|
+ duration: 100,
|
|
|
+ paused: true,
|
|
|
+ loop: false,
|
|
|
+ videoEle: null,
|
|
|
+ timer: null,
|
|
|
+ muted: true, // 静音
|
|
|
+ autoplay: true //自动播放
|
|
|
+ }
|
|
|
+ })
|
|
|
+ data.itemList = data.trainings.filter((n: any) => n.materialId == route.query.materialId)
|
|
|
+ data.videoData = data.itemList[0]
|
|
|
+ handleExerciseCompleted()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ onMounted(async () => {
|
|
|
+ const trainings = await getTrainingRecord()
|
|
|
+ setRecord(trainings)
|
|
|
})
|
|
|
// 返回
|
|
|
const goback = () => {
|
|
@@ -197,12 +183,24 @@ export default defineComponent({
|
|
|
const res: any = await request.post(
|
|
|
state.platformApi + '/studentLessonTraining/lessonTrainingRecord',
|
|
|
{
|
|
|
- data: body
|
|
|
+ data: body,
|
|
|
+ hideLoading: true
|
|
|
}
|
|
|
)
|
|
|
- trainingRecord()
|
|
|
+
|
|
|
} catch (error) {}
|
|
|
data.recordLoading = false
|
|
|
+
|
|
|
+ try {
|
|
|
+ const _res: any = await getTrainingRecord()
|
|
|
+ if (Array.isArray(_res?.data.trainings)){
|
|
|
+ const item = _res.data.trainings.filter((n: any) => n.materialId == route.query.materialId)
|
|
|
+ data.videoData.trainingTimes = item.trainingTimes
|
|
|
+ handleExerciseCompleted()
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+
|
|
|
+ }
|
|
|
}
|
|
|
// 停止所有的播放
|
|
|
const handleStopVideo = () => {
|
|
@@ -214,14 +212,15 @@ export default defineComponent({
|
|
|
// 判断练习是否完成
|
|
|
const handleExerciseCompleted = () => {
|
|
|
if (
|
|
|
- data.trainingTimes != 0 &&
|
|
|
- data.trainingTimes == (data.videoData as any)?.training?.practiceTimes
|
|
|
+ data?.videoData.trainingTimes != 0 &&
|
|
|
+ data?.videoData.trainingTimes + '' === data.videoData?.trainingContent?.practiceTimes
|
|
|
) {
|
|
|
- handleStopVideo()
|
|
|
- const itemIndex = data.details.findIndex(
|
|
|
+ // handleStopVideo()
|
|
|
+ const itemIndex = data.trainings.findIndex(
|
|
|
(n: any) => n.materialId == data.videoData?.materialId
|
|
|
- )
|
|
|
- const isLastIndex = itemIndex === data.details.length - 1
|
|
|
+ )
|
|
|
+ // console.log(itemIndex ,data.trainings, data.videoData?.materialId)
|
|
|
+ const isLastIndex = itemIndex === data.trainings.length - 1
|
|
|
showConfirmDialog({
|
|
|
title: '课后训练',
|
|
|
message: '你已完成该练习~',
|
|
@@ -231,16 +230,11 @@ export default defineComponent({
|
|
|
})
|
|
|
.then(() => {
|
|
|
if (!isLastIndex) {
|
|
|
- const nextItem = data.details[itemIndex + 1]
|
|
|
+ const nextItem = data.trainings[itemIndex + 1]
|
|
|
if (nextItem?.type === materialType.视频) {
|
|
|
- // console.log('下一题视频', data.details[itemIndex].materialId, nextItem.materialId)
|
|
|
- router.replace({
|
|
|
- path: '/exerciseAfterClass',
|
|
|
- query: {
|
|
|
- ...query,
|
|
|
- materialId: nextItem.materialId
|
|
|
- }
|
|
|
- })
|
|
|
+ data.itemList = [nextItem]
|
|
|
+ data.videoData = nextItem
|
|
|
+ handleExerciseCompleted()
|
|
|
}
|
|
|
if (nextItem?.type === materialType.曲目) {
|
|
|
handleInit(1)
|
|
@@ -252,7 +246,6 @@ export default defineComponent({
|
|
|
materialId: nextItem.materialId
|
|
|
})
|
|
|
let src = `${location.origin}/orchestra-music-score/?` + parmas
|
|
|
- // console.log("🚀 ~ src", src)
|
|
|
postMessage({
|
|
|
api: 'openAccompanyWebView',
|
|
|
content: {
|
|
@@ -264,140 +257,154 @@ export default defineComponent({
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
+ } else {
|
|
|
+ postMessage({ api: 'goBack' })
|
|
|
}
|
|
|
})
|
|
|
.catch(() => {
|
|
|
- data.details[itemIndex].currentTime = 0
|
|
|
+ data.trainings[itemIndex].currentTime = 0
|
|
|
})
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return () => (
|
|
|
- <div class={styles.coursewarePlay}>
|
|
|
- <Swipe
|
|
|
- style={{ height: '100vh' }}
|
|
|
- ref={swipeRef}
|
|
|
- showIndicators={false}
|
|
|
- loop={false}
|
|
|
- vertical
|
|
|
- lazyRender={true}
|
|
|
- touchable={false}
|
|
|
- >
|
|
|
- {data.itemList.map((m: any, mIndex: number) => {
|
|
|
- return (
|
|
|
- <SwipeItem>
|
|
|
- <>
|
|
|
- <div
|
|
|
- class={styles.itemDiv}
|
|
|
- onClick={() => {
|
|
|
- clearTimeout(m.timer)
|
|
|
- activeData.model = !activeData.model
|
|
|
- }}
|
|
|
- >
|
|
|
- <video
|
|
|
- playsinline="false"
|
|
|
- preload="auto"
|
|
|
- class="player"
|
|
|
- poster={iconVideobg}
|
|
|
- data-vid={m.id}
|
|
|
- src={m.content}
|
|
|
- loop={m.loop}
|
|
|
- autoplay={m.autoplay}
|
|
|
- muted={m.muted}
|
|
|
- onLoadedmetadata={async (e: Event) => {
|
|
|
- const videoEle = e.target as unknown as HTMLVideoElement
|
|
|
- m.duration = videoEle.duration
|
|
|
- m.videoEle = videoEle
|
|
|
+ <div class={styles.playContent}>
|
|
|
+ <div class={styles.coursewarePlay} style={{ width: parentContainer.width }}>
|
|
|
+ <Swipe
|
|
|
+ style={{ height: '100%' }}
|
|
|
+ ref={swipeRef}
|
|
|
+ showIndicators={false}
|
|
|
+ loop={false}
|
|
|
+ vertical
|
|
|
+ lazyRender={true}
|
|
|
+ touchable={false}
|
|
|
+ duration={0}
|
|
|
+ >
|
|
|
+ {data.itemList.map((m: any, mIndex: number) => {
|
|
|
+ return (
|
|
|
+ <SwipeItem>
|
|
|
+ <>
|
|
|
+ <div
|
|
|
+ class={styles.itemDiv}
|
|
|
+ onClick={() => {
|
|
|
+ clearTimeout(m.timer)
|
|
|
+ activeData.model = !activeData.model
|
|
|
}}
|
|
|
- onTimeupdate={(e: Event) => {
|
|
|
- const videoEle = e.target as unknown as HTMLVideoElement
|
|
|
- m.currentTime = videoEle.currentTime
|
|
|
- }}
|
|
|
- onPlay={() => {
|
|
|
- // 播放
|
|
|
- m.paused = false
|
|
|
- if (m.muted){
|
|
|
- m.muted = false
|
|
|
- m.videoEle.pause()
|
|
|
- }
|
|
|
- }}
|
|
|
- onPause={() => {
|
|
|
- //暂停
|
|
|
- m.paused = true
|
|
|
- }}
|
|
|
- onEnded={() => addTrainingRecord(m)}
|
|
|
>
|
|
|
- <source src={m.content} type="video/mp4" />
|
|
|
- </video>
|
|
|
- </div>
|
|
|
- <Transition name="bottom">
|
|
|
- {activeData.model && (
|
|
|
- <div class={styles.bottomFixedContainer}>
|
|
|
- <div class={styles.time}>
|
|
|
- <span>{getSecondRPM(m.currentTime)}</span>
|
|
|
- <span>{getSecondRPM(m.duration)}</span>
|
|
|
- </div>
|
|
|
- <div class={styles.slider}>
|
|
|
- {m.duration && (
|
|
|
- <Slider
|
|
|
- buttonSize={16}
|
|
|
- modelValue={m.currentTime}
|
|
|
- min={0}
|
|
|
- max={m.duration}
|
|
|
- />
|
|
|
- )}
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class={styles.actions}>
|
|
|
- <div class={styles.actionBtn}>
|
|
|
- {m.paused ? (
|
|
|
- <img
|
|
|
- src={iconplay}
|
|
|
- onClick={(e: Event) => {
|
|
|
- clearTimeout(m.timer)
|
|
|
- m.videoEle?.play()
|
|
|
- m.paused = false
|
|
|
- m.timer = setTimeout(() => {
|
|
|
- activeData.model = false
|
|
|
- }, 4000)
|
|
|
- }}
|
|
|
- />
|
|
|
- ) : (
|
|
|
- <img
|
|
|
- src={iconpause}
|
|
|
- onClick={(e: Event) => {
|
|
|
- clearTimeout(m.timer)
|
|
|
- m.videoEle?.pause()
|
|
|
- m.paused = true
|
|
|
- }}
|
|
|
+ <video
|
|
|
+ playsinline="false"
|
|
|
+ preload="auto"
|
|
|
+ class="player"
|
|
|
+ poster={iconVideobg}
|
|
|
+ data-vid={m.id}
|
|
|
+ src={m.content}
|
|
|
+ loop={m.loop}
|
|
|
+ autoplay={m.autoplay}
|
|
|
+ muted={m.muted}
|
|
|
+ onLoadedmetadata={async (e: Event) => {
|
|
|
+ const videoEle = e.target as unknown as HTMLVideoElement
|
|
|
+ m.duration = videoEle.duration
|
|
|
+ m.videoEle = videoEle
|
|
|
+ m.loaded = true
|
|
|
+ }}
|
|
|
+ onTimeupdate={(e: Event) => {
|
|
|
+ if (!m.loaded) return
|
|
|
+ const videoEle = e.target as unknown as HTMLVideoElement
|
|
|
+ m.currentTime = videoEle.currentTime
|
|
|
+ }}
|
|
|
+ onPlay={() => {
|
|
|
+ console.log('播放')
|
|
|
+ // 播放
|
|
|
+ m.paused = false
|
|
|
+ if (m.muted) {
|
|
|
+ m.muted = false
|
|
|
+ m.videoEle.pause()
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ onPause={() => {
|
|
|
+ console.log('暂停')
|
|
|
+ //暂停
|
|
|
+ m.paused = true
|
|
|
+ }}
|
|
|
+ onEnded={() => addTrainingRecord(m)}
|
|
|
+ >
|
|
|
+ <source src={m.content} type="video/mp4" />
|
|
|
+ </video>
|
|
|
+ </div>
|
|
|
+ <Transition name="bottom">
|
|
|
+ {activeData.model && !m.muted && (
|
|
|
+ <div class={styles.bottomFixedContainer}>
|
|
|
+ <div class={styles.time}>
|
|
|
+ <span>{getSecondRPM(m.currentTime)}</span>
|
|
|
+ <span>{getSecondRPM(m.duration)}</span>
|
|
|
+ </div>
|
|
|
+ <div class={styles.slider}>
|
|
|
+ {m.duration && (
|
|
|
+ <Slider
|
|
|
+ buttonSize={16}
|
|
|
+ modelValue={m.currentTime}
|
|
|
+ min={0}
|
|
|
+ max={m.duration}
|
|
|
/>
|
|
|
)}
|
|
|
</div>
|
|
|
+
|
|
|
+ <div class={styles.actions}>
|
|
|
+ <div class={styles.actionBtn}>
|
|
|
+ {m.paused ? (
|
|
|
+ <img
|
|
|
+ src={iconplay}
|
|
|
+ onClick={(e: Event) => {
|
|
|
+ clearTimeout(m.timer)
|
|
|
+ m.videoEle?.play()
|
|
|
+ m.paused = false
|
|
|
+ m.timer = setTimeout(() => {
|
|
|
+ activeData.model = false
|
|
|
+ }, 4000)
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ ) : (
|
|
|
+ <img
|
|
|
+ src={iconpause}
|
|
|
+ onClick={(e: Event) => {
|
|
|
+ clearTimeout(m.timer)
|
|
|
+ m.videoEle?.pause()
|
|
|
+ m.paused = true
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
+ )}
|
|
|
+ </Transition>
|
|
|
+ {m.muted && (
|
|
|
+ <div class={styles.loadWrap}>
|
|
|
+ <Vue3Lottie animationData={playLoadData}></Vue3Lottie>
|
|
|
</div>
|
|
|
)}
|
|
|
- </Transition>
|
|
|
- </>
|
|
|
- </SwipeItem>
|
|
|
- )
|
|
|
- })}
|
|
|
- </Swipe>
|
|
|
+ </>
|
|
|
+ </SwipeItem>
|
|
|
+ )
|
|
|
+ })}
|
|
|
+ </Swipe>
|
|
|
|
|
|
- <Transition name="top">
|
|
|
- {activeData.model && (
|
|
|
- <div class={styles.headerContainer} ref={headeRef}>
|
|
|
- <div class={styles.backBtn} onClick={() => goback()}>
|
|
|
- <Icon name={iconBack} />
|
|
|
- 返回
|
|
|
- </div>
|
|
|
- <div class={styles.menu}>{popupData.tabName}</div>
|
|
|
- <div class={styles.nums}>
|
|
|
- 练习次数:{data.trainingTimes}/
|
|
|
- {(data.videoData as any)?.training?.practiceTimes || 0}
|
|
|
+ <Transition name="top">
|
|
|
+ {activeData.model && (
|
|
|
+ <div class={styles.headerContainer} ref={headeRef}>
|
|
|
+ <div class={styles.backBtn} onClick={() => goback()}>
|
|
|
+ <Icon name={iconBack} />
|
|
|
+ 返回
|
|
|
+ </div>
|
|
|
+ <div class={styles.menu}>{popupData.tabName}</div>
|
|
|
+ <div class={styles.nums}>
|
|
|
+ 练习次数:{data.videoData?.trainingTimes || 0}/
|
|
|
+ {data.videoData?.trainingContent?.practiceTimes || 0}
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
- )}
|
|
|
- </Transition>
|
|
|
+ )}
|
|
|
+ </Transition>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
)
|
|
|
}
|