|
@@ -11,6 +11,7 @@ import {
|
|
|
import styles from './index.module.less';
|
|
|
import 'plyr/dist/plyr.css';
|
|
|
import MusicScore from './component/musicScore';
|
|
|
+import iconChange from './image/icon-change.png';
|
|
|
import iconMenu from './image/icon-menu.png';
|
|
|
import iconUp from './image/icon-up.png';
|
|
|
import iconDown from './image/icon-down.png';
|
|
@@ -39,7 +40,12 @@ import Pen from './component/tools/pen';
|
|
|
import AudioPay from './component/audio-pay';
|
|
|
import TrainSettings from './model/train-settings';
|
|
|
import { useRoute } from 'vue-router';
|
|
|
-import { lessonPreTrainingPage, queryCourseware } from '../prepare-lessons/api';
|
|
|
+import {
|
|
|
+ courseScheduleUpdate,
|
|
|
+ lessonCoursewareDetail,
|
|
|
+ lessonPreTrainingPage,
|
|
|
+ queryCourseware
|
|
|
+} from '../prepare-lessons/api';
|
|
|
import Attentguide from '@/custom-plugins/guide-page/attent-guide';
|
|
|
import { vaildUrl } from '/src/utils/urlUtils';
|
|
|
import TimerMeter from '/src/components/timerMeter';
|
|
@@ -51,6 +57,7 @@ import toneIcon from '/src/components/layout/images/toneIcon.png';
|
|
|
import { px2vw } from '/src/utils';
|
|
|
import PlaceholderTone from '/src/components/layout/modals/placeholderTone';
|
|
|
import { state as globalState } from '/src/state';
|
|
|
+import Chapter from './model/chapter';
|
|
|
export type ToolType = 'init' | 'pen' | 'whiteboard';
|
|
|
export type ToolItem = {
|
|
|
type: ToolType;
|
|
@@ -69,13 +76,24 @@ export default defineComponent({
|
|
|
type: [String, Number],
|
|
|
default: ''
|
|
|
},
|
|
|
+ // 教材编号
|
|
|
+ lessonCourseId: {
|
|
|
+ type: [String, Number],
|
|
|
+ default: ''
|
|
|
+ },
|
|
|
detailId: {
|
|
|
type: String,
|
|
|
default: ''
|
|
|
},
|
|
|
+ // 班级编号
|
|
|
classGroupId: {
|
|
|
type: String,
|
|
|
default: ''
|
|
|
+ },
|
|
|
+ // 上课记录编号
|
|
|
+ classId: {
|
|
|
+ type: String,
|
|
|
+ defaault: ''
|
|
|
}
|
|
|
},
|
|
|
emits: ['close'],
|
|
@@ -120,8 +138,10 @@ export default defineComponent({
|
|
|
const data = reactive({
|
|
|
type: 'class' as '' | 'preview' | 'class', // 预览类型
|
|
|
subjectId: '' as any, // 声部编号
|
|
|
+ lessonCourseId: '' as any, // 教材编号
|
|
|
detailId: '' as any, // 编号 - 章节编号
|
|
|
classGroupId: '' as any, // 上课时需要 班级编号
|
|
|
+ classId: '' as any, // 上课编号
|
|
|
// detail: null,
|
|
|
knowledgePointList: [] as any,
|
|
|
itemList: [] as any,
|
|
@@ -360,7 +380,9 @@ export default defineComponent({
|
|
|
data.type = props.type || (query.type as any);
|
|
|
data.subjectId = props.subjectId || query.subjectId;
|
|
|
data.detailId = props.detailId || query.detailId;
|
|
|
+ data.lessonCourseId = props.lessonCourseId || query.lessonCourseId;
|
|
|
data.classGroupId = props.classGroupId || query.classGroupId;
|
|
|
+ data.classId = props.classId || query.classId;
|
|
|
|
|
|
const subdEl = document.getElementById(`moveNPopoverA`) as HTMLDivElement;
|
|
|
initBoundaryWrap(subdEl, boxBoundaryInfo);
|
|
@@ -368,6 +390,7 @@ export default defineComponent({
|
|
|
|
|
|
window.addEventListener('message', iframeHandle);
|
|
|
getDetail();
|
|
|
+ getLessonCoursewareDetail();
|
|
|
window.addEventListener('resize', resetSize);
|
|
|
});
|
|
|
|
|
@@ -523,9 +546,35 @@ export default defineComponent({
|
|
|
const popupData = reactive({
|
|
|
open: false,
|
|
|
activeIndex: 0,
|
|
|
- toolOpen: false // 工具弹窗控制
|
|
|
+ toolOpen: false, // 工具弹窗控制
|
|
|
+ chapterOpen: false, // 切换章节
|
|
|
+ chapterDetails: {} as any,
|
|
|
+ chapterLoading: false // 加载数据
|
|
|
});
|
|
|
|
|
|
+ /** 获取章节 */
|
|
|
+ const getLessonCoursewareDetail = async () => {
|
|
|
+ try {
|
|
|
+ const res = await lessonCoursewareDetail({
|
|
|
+ id: data.lessonCourseId
|
|
|
+ });
|
|
|
+
|
|
|
+ popupData.chapterDetails = res.data.lessonList || [];
|
|
|
+ } catch {
|
|
|
+ //
|
|
|
+ }
|
|
|
+ };
|
|
|
+ /** 更新上课记录 */
|
|
|
+ const classCourseScheduleUpdate = async () => {
|
|
|
+ try {
|
|
|
+ if (!data.classId) return;
|
|
|
+ await courseScheduleUpdate({
|
|
|
+ lessonCoursewareKnowledgeDetailId: data.detailId,
|
|
|
+ id: data.classId
|
|
|
+ });
|
|
|
+ } catch {}
|
|
|
+ };
|
|
|
+
|
|
|
const activeName = computed(() => {
|
|
|
let name = '';
|
|
|
data.knowledgePointList.forEach((item: any, index: number) => {
|
|
@@ -850,79 +899,117 @@ export default defineComponent({
|
|
|
|
|
|
return () => (
|
|
|
<div id="playContent" class={[styles.playContent, 'wrap']}>
|
|
|
- <div
|
|
|
- onClick={() => {
|
|
|
- clearTimeout(activeData.timer);
|
|
|
- activeData.model = !activeData.model;
|
|
|
- Object.values(data.videoRefs).map((n: any) =>
|
|
|
- n.toggleHideControl(activeData.model)
|
|
|
- );
|
|
|
- Object.values(data.audioRefs).map((n: any) =>
|
|
|
- n.toggleHideControl(activeData.model)
|
|
|
- );
|
|
|
- }}>
|
|
|
+ {!popupData.chapterLoading ? (
|
|
|
<div
|
|
|
- class={styles.coursewarePlay}
|
|
|
- style={{ width: parentContainer.width }}
|
|
|
- onClick={(e: Event) => {
|
|
|
- e.stopPropagation();
|
|
|
- setModelOpen();
|
|
|
+ onClick={() => {
|
|
|
+ clearTimeout(activeData.timer);
|
|
|
+ activeData.model = !activeData.model;
|
|
|
+ Object.values(data.videoRefs).map((n: any) =>
|
|
|
+ n.toggleHideControl(activeData.model)
|
|
|
+ );
|
|
|
+ Object.values(data.audioRefs).map((n: any) =>
|
|
|
+ n.toggleHideControl(activeData.model)
|
|
|
+ );
|
|
|
}}>
|
|
|
- <div class={styles.wraps}>
|
|
|
- {data.itemList.map((m: any, mIndex: number) => {
|
|
|
- const isRender =
|
|
|
- m.isRender || Math.abs(popupData.activeIndex - mIndex) < 2;
|
|
|
- const isEmtry = Math.abs(popupData.activeIndex - mIndex) > 4;
|
|
|
- if (isRender) {
|
|
|
- m.isRender = true;
|
|
|
- }
|
|
|
- return isRender ? (
|
|
|
- <div
|
|
|
- key={'index' + mIndex}
|
|
|
- class={[
|
|
|
- styles.itemDiv,
|
|
|
- popupData.activeIndex === mIndex && styles.itemActive,
|
|
|
- activeData.isAnimation && styles.acitveAnimation,
|
|
|
- Math.abs(popupData.activeIndex - mIndex) < 2
|
|
|
- ? styles.show
|
|
|
- : styles.hide
|
|
|
- ]}
|
|
|
- style={
|
|
|
- mIndex < popupData.activeIndex
|
|
|
- ? effects[effectIndex.value].prev
|
|
|
- : mIndex > popupData.activeIndex
|
|
|
- ? effects[effectIndex.value].next
|
|
|
- : {}
|
|
|
- }
|
|
|
- onClick={(e: Event) => {
|
|
|
- e.stopPropagation();
|
|
|
- clearTimeout(activeData.timer);
|
|
|
- if (Date.now() - activeData.nowTime < 300) {
|
|
|
- handleDbClick(m);
|
|
|
- return;
|
|
|
+ <div
|
|
|
+ class={styles.coursewarePlay}
|
|
|
+ style={{ width: parentContainer.width }}
|
|
|
+ onClick={(e: Event) => {
|
|
|
+ e.stopPropagation();
|
|
|
+ setModelOpen();
|
|
|
+ }}>
|
|
|
+ <div class={styles.wraps}>
|
|
|
+ {data.itemList.map((m: any, mIndex: number) => {
|
|
|
+ const isRender =
|
|
|
+ m.isRender || Math.abs(popupData.activeIndex - mIndex) < 2;
|
|
|
+ const isEmtry = Math.abs(popupData.activeIndex - mIndex) > 4;
|
|
|
+ if (isRender) {
|
|
|
+ m.isRender = true;
|
|
|
+ }
|
|
|
+ return isRender ? (
|
|
|
+ <div
|
|
|
+ key={'index' + mIndex}
|
|
|
+ class={[
|
|
|
+ styles.itemDiv,
|
|
|
+ popupData.activeIndex === mIndex && styles.itemActive,
|
|
|
+ activeData.isAnimation && styles.acitveAnimation,
|
|
|
+ Math.abs(popupData.activeIndex - mIndex) < 2
|
|
|
+ ? styles.show
|
|
|
+ : styles.hide
|
|
|
+ ]}
|
|
|
+ style={
|
|
|
+ mIndex < popupData.activeIndex
|
|
|
+ ? effects[effectIndex.value].prev
|
|
|
+ : mIndex > popupData.activeIndex
|
|
|
+ ? effects[effectIndex.value].next
|
|
|
+ : {}
|
|
|
}
|
|
|
- activeData.nowTime = Date.now();
|
|
|
- activeData.timer = setTimeout(() => {
|
|
|
- activeData.model = !activeData.model;
|
|
|
- Object.values(data.videoRefs).map((n: any) =>
|
|
|
- n.toggleHideControl(activeData.model)
|
|
|
- );
|
|
|
- Object.values(data.audioRefs).map((n: any) =>
|
|
|
- n.toggleHideControl(activeData.model)
|
|
|
- );
|
|
|
- if (activeData.model) {
|
|
|
- setModelOpen();
|
|
|
+ onClick={(e: Event) => {
|
|
|
+ e.stopPropagation();
|
|
|
+ clearTimeout(activeData.timer);
|
|
|
+ if (Date.now() - activeData.nowTime < 300) {
|
|
|
+ handleDbClick(m);
|
|
|
+ return;
|
|
|
}
|
|
|
- }, 300);
|
|
|
- }}>
|
|
|
- {m.type === 'VIDEO' ? (
|
|
|
- <>
|
|
|
- <VideoPlay
|
|
|
- ref={(v: any) => (data.videoRefs[mIndex] = v)}
|
|
|
+ activeData.nowTime = Date.now();
|
|
|
+ activeData.timer = setTimeout(() => {
|
|
|
+ activeData.model = !activeData.model;
|
|
|
+ Object.values(data.videoRefs).map((n: any) =>
|
|
|
+ n.toggleHideControl(activeData.model)
|
|
|
+ );
|
|
|
+ Object.values(data.audioRefs).map((n: any) =>
|
|
|
+ n.toggleHideControl(activeData.model)
|
|
|
+ );
|
|
|
+ if (activeData.model) {
|
|
|
+ setModelOpen();
|
|
|
+ }
|
|
|
+ }, 300);
|
|
|
+ }}>
|
|
|
+ {m.type === 'VIDEO' ? (
|
|
|
+ <>
|
|
|
+ <VideoPlay
|
|
|
+ ref={(v: any) => (data.videoRefs[mIndex] = v)}
|
|
|
+ item={m}
|
|
|
+ isEmtry={isEmtry}
|
|
|
+ onLoadedmetadata={(videoItem: any) => {
|
|
|
+ m.videoEle = videoItem;
|
|
|
+ m.isprepare = true;
|
|
|
+ }}
|
|
|
+ onTogglePlay={(paused: boolean) => {
|
|
|
+ m.autoPlay = false;
|
|
|
+ if (paused || popupData.open) {
|
|
|
+ clearTimeout(activeData.timer);
|
|
|
+ } else {
|
|
|
+ setModelOpen();
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ onReset={() => {
|
|
|
+ if (!m.videoEle?.paused) {
|
|
|
+ setModelOpen();
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ onError={() => {
|
|
|
+ console.log('video error');
|
|
|
+ m.error = true;
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ <Transition name="van-fade">
|
|
|
+ {!m.isprepare && (
|
|
|
+ <div class={styles.loadWrap}>
|
|
|
+ <Vue3Lottie
|
|
|
+ animationData={playLoadData}></Vue3Lottie>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </Transition>
|
|
|
+ </>
|
|
|
+ ) : m.type === 'IMG' ? (
|
|
|
+ <img src={m.content} />
|
|
|
+ ) : m.type === 'SONG' ? (
|
|
|
+ <AudioPay
|
|
|
item={m}
|
|
|
- isEmtry={isEmtry}
|
|
|
- onLoadedmetadata={(videoItem: any) => {
|
|
|
- m.videoEle = videoItem;
|
|
|
+ ref={(v: any) => (data.audioRefs[mIndex] = v)}
|
|
|
+ onLoadedmetadata={(audioItem: any) => {
|
|
|
+ m.audioEle = audioItem;
|
|
|
m.isprepare = true;
|
|
|
}}
|
|
|
onTogglePlay={(paused: boolean) => {
|
|
@@ -933,115 +1020,91 @@ export default defineComponent({
|
|
|
setModelOpen();
|
|
|
}
|
|
|
}}
|
|
|
+ onEnded={() => {
|
|
|
+ const _index = popupData.activeIndex + 1;
|
|
|
+ if (_index < data.itemList.length) {
|
|
|
+ handleSwipeChange(_index);
|
|
|
+ }
|
|
|
+ }}
|
|
|
onReset={() => {
|
|
|
- if (!m.videoEle?.paused) {
|
|
|
+ if (!m.audioEle?.paused) {
|
|
|
setModelOpen();
|
|
|
}
|
|
|
}}
|
|
|
- onError={() => {
|
|
|
- console.log('video error');
|
|
|
- m.error = true;
|
|
|
+ />
|
|
|
+ ) : (
|
|
|
+ <MusicScore
|
|
|
+ activeModel={activeData.model}
|
|
|
+ data-vid={m.id}
|
|
|
+ music={m}
|
|
|
+ onSetIframe={(el: any) => {
|
|
|
+ m.iframeRef = el;
|
|
|
}}
|
|
|
/>
|
|
|
- <Transition name="van-fade">
|
|
|
- {!m.isprepare && (
|
|
|
- <div class={styles.loadWrap}>
|
|
|
- <Vue3Lottie
|
|
|
- animationData={playLoadData}></Vue3Lottie>
|
|
|
- </div>
|
|
|
- )}
|
|
|
- </Transition>
|
|
|
- </>
|
|
|
- ) : m.type === 'IMG' ? (
|
|
|
- <img src={m.content} />
|
|
|
- ) : m.type === 'SONG' ? (
|
|
|
- <AudioPay
|
|
|
- item={m}
|
|
|
- ref={(v: any) => (data.audioRefs[mIndex] = v)}
|
|
|
- onLoadedmetadata={(audioItem: any) => {
|
|
|
- m.audioEle = audioItem;
|
|
|
- m.isprepare = true;
|
|
|
- }}
|
|
|
- onTogglePlay={(paused: boolean) => {
|
|
|
- m.autoPlay = false;
|
|
|
- if (paused || popupData.open) {
|
|
|
- clearTimeout(activeData.timer);
|
|
|
- } else {
|
|
|
- setModelOpen();
|
|
|
- }
|
|
|
- }}
|
|
|
- onEnded={() => {
|
|
|
- const _index = popupData.activeIndex + 1;
|
|
|
- if (_index < data.itemList.length) {
|
|
|
- handleSwipeChange(_index);
|
|
|
- }
|
|
|
- }}
|
|
|
- onReset={() => {
|
|
|
- if (!m.audioEle?.paused) {
|
|
|
- setModelOpen();
|
|
|
- }
|
|
|
- }}
|
|
|
- />
|
|
|
- ) : (
|
|
|
- <MusicScore
|
|
|
- activeModel={activeData.model}
|
|
|
- data-vid={m.id}
|
|
|
- music={m}
|
|
|
- onSetIframe={(el: any) => {
|
|
|
- m.iframeRef = el;
|
|
|
- }}
|
|
|
- />
|
|
|
- )}
|
|
|
- </div>
|
|
|
- ) : null;
|
|
|
- })}
|
|
|
- </div>
|
|
|
- <Transition name="right">
|
|
|
- {activeData.model && (
|
|
|
- <div
|
|
|
- class={styles.rightFixedBtns}
|
|
|
- onClick={(e: Event) => {
|
|
|
- e.stopPropagation();
|
|
|
- clearTimeout(activeData.timer);
|
|
|
- }}>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ ) : null;
|
|
|
+ })}
|
|
|
+ </div>
|
|
|
+ <Transition name="right">
|
|
|
+ {activeData.model && (
|
|
|
<div
|
|
|
- class={[
|
|
|
- styles.fullBtn,
|
|
|
- popupData.activeIndex === 0 ? styles.btnsDisabled : ''
|
|
|
- ]}
|
|
|
- onClick={() => {
|
|
|
- if (popupData.activeIndex === 0) return;
|
|
|
- handlePreAndNext('up');
|
|
|
+ class={styles.rightFixedBtns}
|
|
|
+ onClick={(e: Event) => {
|
|
|
+ e.stopPropagation();
|
|
|
+ clearTimeout(activeData.timer);
|
|
|
}}>
|
|
|
- <img src={iconUp} />
|
|
|
- </div>
|
|
|
- <div id="attent-0">
|
|
|
<div
|
|
|
- class={[styles.fullBtn, styles.point]}
|
|
|
- onClick={() => (popupData.open = true)}>
|
|
|
- <img src={iconMenu} />
|
|
|
+ class={[styles.fullBtn]}
|
|
|
+ onClick={() => (popupData.chapterOpen = true)}>
|
|
|
+ <img src={iconChange} />
|
|
|
</div>
|
|
|
-
|
|
|
<div
|
|
|
class={[
|
|
|
styles.fullBtn,
|
|
|
- popupData.activeIndex === data.itemList.length - 1
|
|
|
- ? styles.btnsDisabled
|
|
|
- : ''
|
|
|
+ styles.iconUp,
|
|
|
+ popupData.activeIndex === 0 ? styles.btnsDisabled : ''
|
|
|
]}
|
|
|
onClick={() => {
|
|
|
- if (popupData.activeIndex === data.itemList.length - 1)
|
|
|
- return;
|
|
|
- handlePreAndNext('down');
|
|
|
+ if (popupData.activeIndex === 0) return;
|
|
|
+ handlePreAndNext('up');
|
|
|
}}>
|
|
|
- <img src={iconDown} />
|
|
|
+ <img src={iconUp} />
|
|
|
+ </div>
|
|
|
+ <div id="attent-0">
|
|
|
+ <div
|
|
|
+ class={[styles.fullBtn, styles.point]}
|
|
|
+ onClick={() => (popupData.open = true)}>
|
|
|
+ <img src={iconMenu} />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div
|
|
|
+ class={[
|
|
|
+ styles.fullBtn,
|
|
|
+ styles.iconDown,
|
|
|
+ popupData.activeIndex === data.itemList.length - 1
|
|
|
+ ? styles.btnsDisabled
|
|
|
+ : ''
|
|
|
+ ]}
|
|
|
+ onClick={() => {
|
|
|
+ if (
|
|
|
+ popupData.activeIndex ===
|
|
|
+ data.itemList.length - 1
|
|
|
+ )
|
|
|
+ return;
|
|
|
+ handlePreAndNext('down');
|
|
|
+ }}>
|
|
|
+ <img src={iconDown} />
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
- )}
|
|
|
- </Transition>
|
|
|
+ )}
|
|
|
+ </Transition>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ ) : (
|
|
|
+ ''
|
|
|
+ )}
|
|
|
|
|
|
<div
|
|
|
style={{ transform: activeData.model ? '' : 'translateY(-100%)' }}
|
|
@@ -1161,20 +1224,53 @@ export default defineComponent({
|
|
|
showMask={false}>
|
|
|
<NDrawerContent title="资源列表" closable>
|
|
|
{data.knowledgePointList.map((item: any, index: number) => (
|
|
|
- <CardType
|
|
|
- item={item}
|
|
|
- isActive={popupData.activeIndex === index}
|
|
|
- isCollect={false}
|
|
|
- isShowCollect={false}
|
|
|
- onClick={(item: any) => {
|
|
|
- popupData.open = false;
|
|
|
- toggleMaterial(item.id);
|
|
|
- }}
|
|
|
- />
|
|
|
+ <div class={styles.cardContainer}>
|
|
|
+ <CardType
|
|
|
+ item={item}
|
|
|
+ isActive={popupData.activeIndex === index}
|
|
|
+ isCollect={false}
|
|
|
+ isShowCollect={false}
|
|
|
+ onClick={(item: any) => {
|
|
|
+ popupData.open = false;
|
|
|
+ toggleMaterial(item.id);
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
))}
|
|
|
</NDrawerContent>
|
|
|
</NDrawer>
|
|
|
|
|
|
+ {/* 显示列表 */}
|
|
|
+ <NDrawer
|
|
|
+ v-model:show={popupData.chapterOpen}
|
|
|
+ class={styles.drawerContainer}
|
|
|
+ onAfterLeave={handleClosePopup}
|
|
|
+ showMask={false}>
|
|
|
+ <NDrawerContent title="切换章节" closable>
|
|
|
+ <Chapter
|
|
|
+ treeList={popupData.chapterDetails}
|
|
|
+ itemActive={data.detailId as any}
|
|
|
+ onHandleSelect={async (val: any) => {
|
|
|
+ popupData.chapterLoading = true;
|
|
|
+
|
|
|
+ try {
|
|
|
+ data.detailId = val.itemActive;
|
|
|
+ // 更新上课记录 上课的时候才更新
|
|
|
+ if (data.type !== 'preview') {
|
|
|
+ await classCourseScheduleUpdate();
|
|
|
+ }
|
|
|
+ await getDetail();
|
|
|
+ popupData.activeIndex = 0;
|
|
|
+ popupData.chapterOpen = false;
|
|
|
+ } catch {
|
|
|
+ //
|
|
|
+ }
|
|
|
+ popupData.chapterLoading = false;
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </NDrawerContent>
|
|
|
+ </NDrawer>
|
|
|
+
|
|
|
{/* 批注 */}
|
|
|
{studyData.penShow && (
|
|
|
<Pen
|