index.tsx 55 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657
  1. import { closeToast, Icon, Popup, showDialog, showToast } from 'vant'
  2. import {
  3. defineComponent,
  4. onMounted,
  5. reactive,
  6. nextTick,
  7. onUnmounted,
  8. ref,
  9. watch,
  10. Transition,
  11. computed,
  12. onBeforeUnmount,
  13. shallowRef
  14. } from 'vue'
  15. import iconBack from './image/back.png'
  16. import styles from './index.module.less'
  17. import 'plyr/dist/plyr.css'
  18. import request from '@/helpers/request'
  19. import { state } from '@/state'
  20. import { useRoute } from 'vue-router'
  21. import { listenerMessage, postMessage, promisefiyPostMessage } from '@/helpers/native-message'
  22. import qs from 'query-string'
  23. import MusicScore from './component/musicScore'
  24. // import iconDian from './image/icon-dian.svg'
  25. // import iconPoint from './image/icon-point.svg'
  26. import { iconUp, iconDown, iconPen, iconTouping, iconMenu, iconCourseType, iconSearch } from './image/icons.json'
  27. import Points from './component/points'
  28. import { browser } from '@/helpers/utils'
  29. import { Vue3Lottie } from 'vue3-lottie'
  30. import playLoadData from './datas/data.json'
  31. import { usePageVisibility } from '@vant/use'
  32. import { useInterval, useIntervalFn, useNetwork } from '@vueuse/core'
  33. import PlayRecordTime from './playRecordTime'
  34. import { handleCheckVip } from '../hook/useFee'
  35. import OGuide from '@/components/o-guide'
  36. import Tool, { ToolItem, ToolType } from './component/tool'
  37. import Pen from './component/tools/pen'
  38. import VideoItem from './component/video-item'
  39. import deepClone from '@/helpers/deep-clone'
  40. import VideoPlay from './component/video-play'
  41. import CoursewareType from './component/courseware-type'
  42. import CoursewareTips from './component/courseware-tips'
  43. import GlobalTools from '@/components/globalTools'
  44. import { isPlay, penShow, toolOpen, whitePenShow } from '@/components/globalTools/globalTools'
  45. import PointsSearch from './component/points-search'
  46. export default defineComponent({
  47. name: 'CoursewarePlay',
  48. setup() {
  49. const pageVisibility = usePageVisibility()
  50. const { isOnline } = useNetwork()
  51. /** 页面显示和隐藏 */
  52. watch(
  53. () => pageVisibility.value,
  54. (value) => {
  55. if (value == 'hidden') {
  56. handleStop()
  57. }
  58. }
  59. )
  60. /** 设置播放容器 16:9 */
  61. const parentContainer = reactive({
  62. width: '100vw'
  63. })
  64. const setContainer = () => {
  65. const min = Math.min(screen.width, screen.height)
  66. const max = Math.max(screen.width, screen.height)
  67. const width = min * (16 / 9)
  68. if (width > max) {
  69. parentContainer.width = '100vw'
  70. return
  71. } else {
  72. parentContainer.width = width + 'px'
  73. }
  74. }
  75. const handleInit = (type = 0) => {
  76. //设置容器16:9
  77. // setContainer()
  78. // 横屏
  79. postMessage(
  80. {
  81. api: 'setRequestedOrientation',
  82. content: {
  83. orientation: type
  84. }
  85. },
  86. () => {
  87. console.log(234)
  88. }
  89. )
  90. // 头,包括返回箭头
  91. // postMessage({
  92. // api: 'setTitleBarVisibility',
  93. // content: {
  94. // status: type
  95. // }
  96. // })
  97. // 安卓的状态栏
  98. postMessage({
  99. api: 'setStatusBarVisibility',
  100. content: {
  101. isVisibility: type
  102. }
  103. })
  104. // 进入页面设置常量
  105. postMessage({
  106. api: 'keepScreenLongLight',
  107. content: {
  108. isOpenLight: type ? true : false
  109. }
  110. })
  111. }
  112. handleInit()
  113. onUnmounted(() => {
  114. handleInit(1)
  115. window.removeEventListener('message', iframeHandle)
  116. })
  117. const route = useRoute()
  118. const headeRef = ref()
  119. const isCurrentCoursewareMenu = shallowRef(true) // 是否为当前选的课程类型
  120. const detailTempSearchList = shallowRef<any[]>()
  121. const detailList = shallowRef<any[]>()// 搜索来的所有数据
  122. const data = reactive({
  123. source: route.query.source as any, // 来源 search 搜索
  124. searchLoading: false, // 搜索加载状态
  125. search: route.query.search as any, // 默认的搜索条件 -
  126. searchTemp: route.query.search as any, // 默认的搜索条件 -
  127. currentId: route.query.id as any,
  128. detail: null as any,
  129. knowledgePointList: [] as any,
  130. itemList: [] as any,
  131. lookVideoDataList: [] as any, // 观看视频统计数据
  132. showHead: true,
  133. isCourse: false,
  134. isRecordPlay: false,
  135. videoRefs: {},
  136. refLevelList: [] as any,
  137. videoState: 'init' as 'init' | 'play',
  138. videoItemRef: null as any,
  139. animationState: 'start' as 'start' | 'end',
  140. disableScreenRecordingFlag: '0' // 是否禁止录屏
  141. })
  142. const activeData = reactive({
  143. isAutoPlay: true, // 是否自动播放
  144. nowTime: 0,
  145. model: true, // 遮罩
  146. isAnimation: true, // 是否动画
  147. videoBtns: true, // 视频
  148. currentTime: 0,
  149. duration: 0,
  150. timer: null as any,
  151. item: null as any
  152. })
  153. // 获取缓存路径
  154. const getCacheFilePath = async (material: any) => {
  155. const res = await promisefiyPostMessage({
  156. api: 'getCourseFilePath',
  157. content: {
  158. url: material.content,
  159. localPath: '',
  160. materialId: material.materialId,
  161. updateTime: material.updateTime,
  162. type: material.type // SONG VIDEO IMAGE
  163. }
  164. })
  165. // console.log('缓存路径返回', res)
  166. return res
  167. }
  168. // 获取当前课程是否签退
  169. const getCourseSchedule = async () => {
  170. if (!route.query.courseId) return
  171. try {
  172. const res = await request.get(
  173. `${state.platformApi}/courseSchedule/detail/${route.query.courseId}`,
  174. {
  175. hideLoading: true
  176. }
  177. )
  178. if (res?.data) {
  179. data.isCourse =
  180. res.data.status === 'ING' && state.platformType == 'TEACHER' ? true : false
  181. // data.isRecordPlay = Date.now() > dayjs(res.data.startTime).valueOf()
  182. }
  183. } catch (e) {
  184. console.log(e)
  185. }
  186. }
  187. const getTempList = async (materialList: any, name: any) => {
  188. const list: any = []
  189. const browserInfo = browser()
  190. for (let j = 0; j < materialList.length; j++) {
  191. const material = materialList[j]
  192. //请求本地缓存
  193. if (browserInfo.isApp && ['VIDEO', 'IMG'].includes(material.type)) {
  194. const localData = await getCacheFilePath(material)
  195. if (localData?.content?.localPath) {
  196. material.url = material.content
  197. material.content = localData.content.localPath
  198. }
  199. }
  200. const videoData = data.lookVideoDataList.find(
  201. (i: any) => i.materialId === material.materialId
  202. )
  203. material.moreTime = videoData?.videoBrowseData ? JSON.parse(videoData.videoBrowseData) : []
  204. material.videoTime = videoData?.videoTime || 0 // 视频时长
  205. material.iframeRef = null
  206. material.videoEle = null
  207. material.tabName = name
  208. material.autoPlay = false //加载完成是否自动播放
  209. material.isprepare = false // 视频是否加载完成
  210. material.isRender = false // 是否渲染了
  211. list.push(material)
  212. // list.push({
  213. // ...material,
  214. // moreTime: videoData?.videoBrowseData ? JSON.parse(videoData.videoBrowseData) : [],
  215. // videoTime: videoData?.videoTime || 0, // 视频时长
  216. // iframeRef: null,
  217. // videoEle: null,
  218. // tabName: name,
  219. // autoPlay: false, //加载完成是否自动播放
  220. // isprepare: false, // 视频是否加载完成
  221. // isRender: false // 是否渲染了
  222. // })
  223. }
  224. return list
  225. }
  226. const getItemList = async () => {
  227. const list: any = []
  228. for (let i = 0; i < data.knowledgePointList.length; i++) {
  229. const item = data.knowledgePointList[i]
  230. if (item.materialList && item.materialList.length > 0) {
  231. const tempList = await getTempList(item.materialList, item.name)
  232. list.push(...tempList)
  233. }
  234. // 第二层级
  235. if (item.children && item.children.length > 0) {
  236. const childrenList = item.children || []
  237. for (let j = 0; j < childrenList.length; j++) {
  238. const childItem = childrenList[j]
  239. const tempList = await getTempList(childItem.materialList, childItem.name)
  240. list.push(...tempList)
  241. }
  242. }
  243. }
  244. // console.log(list, 'list')
  245. let _firstIndex = list.findIndex(
  246. (n: any) =>
  247. n.knowledgePointMaterialRelationId == route.query.kId || n.materialId == route.query.kId
  248. )
  249. _firstIndex = _firstIndex > -1 ? _firstIndex : 0
  250. const item = list[_firstIndex]
  251. // console.log(_firstIndex, '_firstIndex', route.query.kId, 'route.query.kId', item)
  252. // 是否自动播放
  253. if (activeData.isAutoPlay) {
  254. item.autoPlay = true
  255. }
  256. popupData.activeIndex = _firstIndex
  257. popupData.playIndex = _firstIndex
  258. popupData.tabName = item.tabName
  259. popupData.tabActive = item.knowledgePointId
  260. popupData.itemActive = item.id
  261. popupData.itemName = item.name
  262. nextTick(() => {
  263. data.itemList = list
  264. checkedAnimation(popupData.activeIndex)
  265. postMessage({
  266. api: 'courseLoading',
  267. content: {
  268. show: false,
  269. type: 'fullscreen'
  270. }
  271. })
  272. //检测是否录屏
  273. if (data.disableScreenRecordingFlag === '1') {
  274. handleLimitScreenRecord()
  275. }
  276. setTimeout(() => {
  277. data.animationState = 'end'
  278. }, 500)
  279. })
  280. }
  281. const getDetail = async (id?: any) => {
  282. try {
  283. const res: any = await request.get(
  284. state.platformApi + `/lessonCoursewareDetail/detail/${id || route.query.id}`,
  285. {
  286. hideLoading: true
  287. }
  288. )
  289. const result = res.data || {}
  290. result.lessonTargetDesc = result.lessonTargetDesc ? result.lessonTargetDesc.replace(/\n/g, "<br />") : ""
  291. data.detail = result;
  292. if (res?.data?.lockFlag) {
  293. postMessage({
  294. api: 'courseLoading',
  295. content: {
  296. show: false,
  297. type: 'fullscreen'
  298. }
  299. })
  300. showDialog({
  301. title: '温馨提示',
  302. message: '课件已锁定'
  303. }).then(() => {
  304. goback()
  305. })
  306. return
  307. }
  308. if (Array.isArray(res?.data?.knowledgePointList)) {
  309. let index = 0
  310. data.knowledgePointList = res.data.knowledgePointList.map((n: any) => {
  311. if (Array.isArray(n.materialList)) {
  312. n.materialList = n.materialList.map((item: any) => {
  313. index++
  314. const materialRefs = item.materialRefs ? item.materialRefs : []
  315. const materialMusicId = materialRefs.length > 0 ? materialRefs[0].resourceId : null
  316. return {
  317. ...item,
  318. materialMusicId,
  319. knowledgePointId: [item.knowledgePointId],
  320. materialId: item.id,
  321. id: index + ''
  322. }
  323. })
  324. }
  325. if (Array.isArray(n.children)) {
  326. n.children = n.children.map((cn: any) => {
  327. cn.materialList = cn.materialList.map((item: any) => {
  328. index++
  329. const materialRefs = item.materialRefs ? item.materialRefs : []
  330. const materialMusicId =
  331. materialRefs.length > 0 ? materialRefs[0].resourceId : null
  332. return {
  333. ...item,
  334. materialMusicId,
  335. knowledgePointId: [n.id, item.knowledgePointId],
  336. materialId: item.id,
  337. id: index + ''
  338. }
  339. })
  340. return cn
  341. })
  342. }
  343. return n
  344. })
  345. getItemList()
  346. }
  347. return true
  348. } catch (error) {
  349. console.log(error)
  350. }
  351. }
  352. const getSearchItemList = async (knowledgePointList: any[]) => {
  353. const list: any = [];
  354. for (let i = 0; i < knowledgePointList.length; i++) {
  355. const item = knowledgePointList[i];
  356. if (item.materialList && item.materialList.length > 0) {
  357. const tempList = await getTempList(item.materialList, item.name);
  358. list.push(...tempList);
  359. }
  360. // 第二层级
  361. if (item.children && item.children.length > 0) {
  362. const childrenList = item.children || [];
  363. for (let j = 0; j < childrenList.length; j++) {
  364. const childItem = childrenList[j];
  365. const tempList = await getTempList(
  366. childItem.materialList,
  367. childItem.name
  368. );
  369. list.push(...tempList);
  370. }
  371. }
  372. }
  373. return list
  374. };
  375. /** 从搜索页面来的 */
  376. const getSearchDetail = async (params: { type?: string, id?: any, search?: string }) => {
  377. try {
  378. const res = await request.post(
  379. state.platformApi +
  380. `/courseSchedule/myCoursewareDetail/${params.id || route.query.lessonId}`,
  381. {
  382. hideLoading: true,
  383. requestType: "form",
  384. data: {
  385. detailFlag: "1",
  386. searchFlag: true,
  387. search: params.search
  388. }
  389. }
  390. );
  391. const result = res.data || []
  392. const allList: any[] = []
  393. for(let i = 0; i < result.length; i++) {
  394. const itemResult = result[i];
  395. itemResult.name = itemResult.coursewareDetailName;
  396. itemResult.id = itemResult.lessonCoursewareDetailId;
  397. itemResult.lessonTargetDesc = itemResult.lessonTargetDesc ? itemResult.lessonTargetDesc.replace(/\n/g, "<br />") : ""
  398. if (Array.isArray(itemResult?.knowledgePointList)) {
  399. let index = 0;
  400. itemResult.children = itemResult.knowledgePointList.map(
  401. (n: any) => {
  402. if (Array.isArray(n.materialList)) {
  403. n.materialList = n.materialList.map((item: any) => {
  404. index++;
  405. const materialRefs = item.materialRefs
  406. ? item.materialRefs
  407. : [];
  408. const materialMusicId =
  409. materialRefs.length > 0
  410. ? materialRefs[0].resourceId
  411. : null;
  412. const useStatus = materialRefs.length > 0
  413. ? materialRefs[0]?.extend?.useStatus : null
  414. const isLock = useStatus === 'LOCK' && state.platformType === "STUDENT" ? true : false
  415. return {
  416. ...item,
  417. isLock,
  418. materialMusicId,
  419. content: item.content,
  420. coursewareDetailId: itemResult.lessonCoursewareDetailId,
  421. knowledgePointId: [itemResult.lessonCoursewareDetailId, item.knowledgePointId],
  422. materialId: item.id,
  423. id: (i * 1000 + '') + index + ''
  424. };
  425. });
  426. }
  427. if (Array.isArray(n.children)) {
  428. n.children = n.children.map((cn: any) => {
  429. cn.materialList = cn.materialList.map((item: any) => {
  430. index++;
  431. const materialRefs = item.materialRefs
  432. ? item.materialRefs
  433. : [];
  434. const materialMusicId =
  435. materialRefs.length > 0
  436. ? materialRefs[0].resourceId
  437. : null;
  438. const useStatus = materialRefs.length > 0
  439. ? materialRefs[0]?.extend?.useStatus : null
  440. const isLock = useStatus === 'LOCK' && state.platformType === "STUDENT" ? true : false
  441. return {
  442. ...item,
  443. isLock,
  444. materialMusicId,
  445. coursewareDetailId: itemResult.lessonCoursewareDetailId,
  446. content: item.content,
  447. knowledgePointId: [itemResult.lessonCoursewareDetailId, n.id, item.knowledgePointId],
  448. materialId: item.id,
  449. id: (i * 1000 + '') + index + ''
  450. };
  451. });
  452. return cn;
  453. });
  454. }
  455. return n;
  456. }
  457. );
  458. itemResult.knowledgePointList = null // 去掉不要的
  459. itemResult.list = await getSearchItemList(itemResult.children);
  460. allList.push(...itemResult.list)
  461. }
  462. }
  463. if(params.type === 'pointSearch') {
  464. detailTempSearchList.value = result
  465. // 初始化选中的数据 临时
  466. popupData.tempTabActive = allList.length > 0 ? allList[0].knowledgePointId : []
  467. popupData.tempItemActive = "-1"
  468. data.searchTemp = params.search
  469. return
  470. }
  471. detailList.value = result
  472. detailTempSearchList.value = result
  473. if(!params.type) {
  474. let _firstIndex = allList.findIndex(
  475. (n: any) =>
  476. n.knowledgePointMaterialRelationId == route.query.kId ||
  477. n.materialId == route.query.kId
  478. );
  479. _firstIndex = _firstIndex > -1 ? _firstIndex : 0;
  480. const item = allList[_firstIndex];
  481. // console.log(item, 'item')
  482. // console.log(_firstIndex, '_firstIndex', route.query.kId, 'route.query.kId', item)
  483. // 是否自动播放
  484. if (activeData.isAutoPlay) {
  485. item.autoPlay = true;
  486. }
  487. popupData.activeIndex = _firstIndex;
  488. popupData.playIndex = _firstIndex;
  489. popupData.tabName = item.tabName;
  490. popupData.tabActive = item.knowledgePointId;
  491. popupData.itemActive = item.id;
  492. popupData.itemName = item.name;
  493. data.detail = detailList.value?.find((child: any) => child.lessonCoursewareDetailId === item.lessonCoursewareDetailId)
  494. }
  495. nextTick(() => {
  496. data.itemList = allList;
  497. checkedAnimation(popupData.activeIndex);
  498. postMessage({
  499. api: 'courseLoading',
  500. content: {
  501. show: false,
  502. type: 'fullscreen'
  503. }
  504. });
  505. if (data.disableScreenRecordingFlag === '1') {
  506. // 检测是否录屏
  507. handleLimitScreenRecord();
  508. }
  509. setTimeout(() => {
  510. data.animationState = 'end';
  511. }, 500);
  512. });
  513. return true
  514. } catch (error) {
  515. console.log(error);
  516. }
  517. }
  518. const onTitleTip = (type: "phaseGoals" | "checkItem", text: string) => {
  519. handleStop()
  520. popupData.pointOpen = true
  521. popupData.pointContent = text
  522. if(type === "checkItem") {
  523. popupData.pointTitle = '检查事项'
  524. } else if(type === "phaseGoals") {
  525. popupData.pointTitle = '阶段目标'
  526. }
  527. }
  528. // ifram事件处理
  529. const iframeHandle = (ev: MessageEvent) => {
  530. if (ev.data?.api === 'headerTogge') {
  531. activeData.model = ev.data.show || (ev.data.playState == 'play' ? false : true)
  532. }
  533. }
  534. // 获取学生观看数据
  535. const getLookVideoData = async () => {
  536. try {
  537. const res = await request.get(
  538. state.platformApi + `/studentCoursewareMaterialRelation/findByDetailId`,
  539. {
  540. hideLoading: true,
  541. params: {
  542. lessonCoursewareDetailId: route.query.id
  543. }
  544. }
  545. )
  546. data.lookVideoDataList = res.data || [] // 视频播放数据
  547. } catch {
  548. //
  549. }
  550. }
  551. // 切换播放
  552. const togglePlay = (m: any, isPlay: boolean) => {
  553. if (isPlay) {
  554. m.videoEle?.play()
  555. } else {
  556. m.videoEle?.pause()
  557. }
  558. }
  559. let timers: any = null
  560. const checkVideoPlay = () => {
  561. const activeVideoRef = data.videoItemRef?.getPlyrRef()
  562. if (activeVideoRef) {
  563. timers = setInterval(() => {
  564. if (!activeVideoRef.paused()) {
  565. activeVideoRef.pause()
  566. clearInterval(timers)
  567. }
  568. activeVideoRef.pause()
  569. }, 100)
  570. }
  571. setTimeout(() => {
  572. clearInterval(timers)
  573. }, 3000)
  574. }
  575. //录屏时间触发
  576. const handleLimitScreenRecord = async () => {
  577. const result = await promisefiyPostMessage({
  578. api: 'getDeviceStatus',
  579. content: { type: 'video' }
  580. })
  581. const { status } = result?.content || {}
  582. if (status == '1') {
  583. data.itemList.forEach((item: any) => (item.autoPlay = false))
  584. handleStop()
  585. // 处理事件 - 事件事件后加载的
  586. checkVideoPlay()
  587. showDialog({
  588. title: '温馨提示',
  589. message: '课件内容请勿录屏',
  590. beforeClose: () => {
  591. return new Promise((resolve) => {
  592. promisefiyPostMessage({
  593. api: 'getDeviceStatus',
  594. content: { type: 'video' }
  595. }).then((res: any) => {
  596. const content = res.content
  597. if (content?.status == '1') {
  598. const activeItem = data.itemList[popupData.activeIndex]
  599. togglePlay(activeItem, false)
  600. resolve(false)
  601. } else {
  602. const activeItem = data.itemList[popupData.activeIndex]
  603. togglePlay(activeItem, true)
  604. resolve(true)
  605. }
  606. })
  607. })
  608. }
  609. })
  610. }
  611. }
  612. // 获取禁止录屏
  613. const sysParamConfig = async () => {
  614. try {
  615. const res = await request.get(`${state.platformApi}/sysParamConfig/queryByParamName`, {
  616. params: {
  617. paramName: 'disable_screen_recording_flag'
  618. }
  619. })
  620. data.disableScreenRecordingFlag = res.data.paramValue || ''
  621. } catch {
  622. //
  623. }
  624. }
  625. const getRefLevel = async (id?: any) => {
  626. try {
  627. const res = await request.post(state.platformApi + '/lessonCoursewareDetail/refLevel', {
  628. data: {
  629. lessonCoursewareDetailId: id || route.query.id,
  630. courseScheduleId: route.query.courseId as any
  631. }
  632. })
  633. data.refLevelList = res.data || []
  634. return true
  635. } catch {
  636. //
  637. }
  638. }
  639. onMounted(async () => {
  640. await sysParamConfig()
  641. if (state.platformType === 'STUDENT') {
  642. await getLookVideoData()
  643. }
  644. if(data.source === 'search') {
  645. await getSearchDetail({search: data.search})
  646. } else {
  647. // 只有老师有 课程类型 切换
  648. if(state.platformType === "TEACHER") {
  649. await getRefLevel()
  650. }
  651. await getDetail()
  652. }
  653. // console.log(data.detail, "data.detail");
  654. // const hasFree = String(data.detail?.accessScope) === '0'
  655. // if (!hasFree) {
  656. // const hasVip = handleCheckVip()
  657. // if (!hasVip) {
  658. // nextTick(() => {
  659. // postMessage({
  660. // api: 'courseLoading',
  661. // content: {
  662. // show: false,
  663. // type: 'fullscreen'
  664. // }
  665. // })
  666. // })
  667. // return
  668. // }
  669. // }
  670. getCourseSchedule()
  671. window.addEventListener('message', iframeHandle)
  672. if (data.disableScreenRecordingFlag === '1') {
  673. //禁止录屏 ios
  674. listenerMessage('setVideoPlayer', (result) => {
  675. if (result?.content?.status == 'pause') {
  676. handleLimitScreenRecord()
  677. }
  678. })
  679. // 安卓
  680. postMessage({
  681. api: 'limitScreenRecord',
  682. content: {
  683. type: 1
  684. }
  685. })
  686. }
  687. })
  688. onBeforeUnmount(() => {
  689. postMessage({
  690. api: 'limitScreenRecord',
  691. content: {
  692. type: 0
  693. }
  694. })
  695. })
  696. const playRef = ref()
  697. // 返回
  698. const goback = () => {
  699. try {
  700. playRef.value?.handleOut()
  701. } catch (error) {
  702. console.log(error)
  703. }
  704. postMessage({ api: 'goBack' })
  705. }
  706. const popupData = reactive({
  707. pointOpen: false,
  708. pointContent: "",
  709. pointTitle: "",
  710. coursewareOpen: false,
  711. open: false,
  712. activeIndex: 0,
  713. playIndex: 0,
  714. tempTabActive: '', // 临时选中
  715. tempItemActive: "", // 临时编号
  716. tabActive: '',
  717. tabName: '',
  718. itemActive: '',
  719. itemName: '',
  720. guideOpen: false,
  721. toolOpen: false // 工具弹窗控制
  722. })
  723. const stopVideo = (el: HTMLVideoElement) => {
  724. return new Promise((resolve) => {
  725. if (el.paused) return resolve(true)
  726. el.onpause = () => {
  727. console.log('暂停')
  728. resolve(true)
  729. }
  730. el.pause()
  731. })
  732. }
  733. /**停止所有的播放 */
  734. const handleStop = () => {
  735. for (let i = 0; i < data.itemList.length; i++) {
  736. const activeItem = data.itemList[i]
  737. if (activeItem.type === 'VIDEO') {
  738. // activeItem.videoEle?.currentTime(0)
  739. activeItem.videoEle?.pause()
  740. // activeItem.videoEle?.stop()
  741. }
  742. // 停止曲谱的播放
  743. if (activeItem.type === 'SONG') {
  744. activeItem.iframeRef?.contentWindow?.postMessage({ api: 'setPlayState' }, '*')
  745. }
  746. }
  747. console.log('视频暂停完成')
  748. data.itemList.forEach((item: any) => {
  749. if (item.type === 'SONG') {
  750. item.iframeRef?.contentWindow?.postMessage({ api: 'setPlayState' }, '*')
  751. }
  752. })
  753. }
  754. // 切换素材
  755. const toggleMaterial = (itemActive: any) => {
  756. const index = data.itemList.findIndex((n: any) => n.id == itemActive)
  757. if (index > -1) {
  758. handleSwipeChange(index)
  759. }
  760. }
  761. /** 延迟收起模态框 */
  762. const setModelOpen = () => {
  763. clearTimeout(activeData.timer)
  764. closeToast()
  765. activeData.timer = setTimeout(() => {
  766. activeData.model = false
  767. }, 4000)
  768. }
  769. /** 立即收起所有的模态框 */
  770. const clearModel = () => {
  771. clearTimeout(activeData.timer)
  772. closeToast()
  773. activeData.model = false
  774. }
  775. const toggleModel = (type = true) => {
  776. activeData.model = type
  777. }
  778. // 去点名,签退
  779. const gotoRollCall = (pageTag: string) => {
  780. postMessage({
  781. api: 'open_app_page',
  782. content: {
  783. action: 'app',
  784. pageTag: pageTag,
  785. url: '',
  786. params: JSON.stringify({ courseId: route.query.courseId })
  787. }
  788. })
  789. }
  790. // 双击
  791. const handleDbClick = () => {
  792. if (activeVideoItem.value.type === 'VIDEO') {
  793. const activeVideoRef = data.videoItemRef?.getPlyrRef()
  794. if (activeVideoRef) {
  795. if (activeVideoRef.paused()) {
  796. activeVideoRef.play()
  797. } else {
  798. activeVideoRef.pause()
  799. showToast('已暂停')
  800. }
  801. }
  802. }
  803. }
  804. const effectIndex = ref(0)
  805. const effects = [
  806. {
  807. prev: {
  808. transform: 'translate3d(0, 0, -800px) rotateX(180deg)'
  809. },
  810. next: {
  811. transform: 'translate3d(0, 0, -800px) rotateX(-180deg)'
  812. }
  813. },
  814. {
  815. prev: {
  816. transform: 'translate3d(-100%, 0, -800px)'
  817. },
  818. next: {
  819. transform: 'translate3d(100%, 0, -800px)'
  820. }
  821. },
  822. {
  823. prev: {
  824. transform: 'translate3d(-50%, 0, -800px) rotateY(80deg)'
  825. },
  826. next: {
  827. transform: 'translate3d(50%, 0, -800px) rotateY(-80deg)'
  828. }
  829. },
  830. {
  831. prev: {
  832. transform: 'translate3d(-100%, 0, -800px) rotateY(-120deg)'
  833. },
  834. next: {
  835. transform: 'translate3d(100%, 0, -800px) rotateY(120deg)'
  836. }
  837. },
  838. // 风车4
  839. {
  840. prev: {
  841. transform: 'translate3d(-50%, 50%, -800px) rotateZ(-14deg)',
  842. opacity: 0
  843. },
  844. next: {
  845. transform: 'translate3d(50%, 50%, -800px) rotateZ(14deg)',
  846. opacity: 0
  847. }
  848. },
  849. // 翻页5
  850. {
  851. prev: {
  852. transform: 'translateZ(-800px) rotate3d(0, -1, 0, 90deg)',
  853. opacity: 0
  854. },
  855. next: {
  856. transform: 'translateZ(-800px) rotate3d(0, 1, 0, 90deg)',
  857. opacity: 0
  858. },
  859. current: { transitionDelay: '700ms' }
  860. }
  861. ]
  862. const acitveTimer = ref()
  863. // 轮播切换
  864. const handleSwipeChange = async (index: number) => {
  865. if(data.source === 'search') {
  866. const item = data.itemList[index];
  867. data.detail = detailList.value?.find((child: any) => child.coursewareDetailId === item.coursewareDetailId)
  868. popupData.tabActive = item.knowledgePointId;
  869. popupData.itemActive = item.id;
  870. popupData.itemName = item.name;
  871. popupData.tabName = item.tabName;
  872. if (item.typeCode == 'SONG') {
  873. activeData.model = true;
  874. }
  875. }
  876. // 如果是当前正在播放 或者是视频最后一个
  877. if (popupData.activeIndex == index) return
  878. await handleStop()
  879. data.animationState = 'start'
  880. data.videoState = 'init'
  881. clearTimeout(acitveTimer.value)
  882. checkedAnimation(popupData.activeIndex, index)
  883. nextTick(() => {
  884. popupData.activeIndex = index
  885. acitveTimer.value = setTimeout(
  886. () => {
  887. popupData.playIndex = index
  888. const item = data.itemList[index]
  889. if (item) {
  890. popupData.tabActive = item.knowledgePointId
  891. popupData.itemActive = item.id
  892. popupData.itemName = item.name
  893. popupData.tabName = item.tabName
  894. if (item.type == 'SONG') {
  895. activeData.model = true
  896. }
  897. }
  898. if (item.type === 'VIDEO') {
  899. // 自动播放下一个视频
  900. clearTimeout(activeData.timer)
  901. closeToast()
  902. item.autoPlay = true
  903. // console.log(item, 'item')
  904. // 当视屏异常时重置链接
  905. if (item.error) {
  906. item.videoEle?.src(item.content)
  907. item.error = false
  908. }
  909. nextTick(() => {
  910. item.videoEle?.play()
  911. })
  912. }
  913. requestAnimationFrame(() => {
  914. const _effectIndex = effectIndex.value + 1
  915. effectIndex.value = _effectIndex >= effects.length - 1 ? 0 : _effectIndex
  916. })
  917. },
  918. activeData.isAnimation ? 800 : 0
  919. )
  920. })
  921. }
  922. /** 是否有转场动画 */
  923. const checkedAnimation = (index: number, nextIndex?: number) => {
  924. nextIndex = nextIndex ? nextIndex : index + 1
  925. const item = data.itemList[index]
  926. const nextItem = data.itemList[nextIndex]
  927. if (nextItem) {
  928. if (nextItem.knowledgePointId != item.knowledgePointId) {
  929. activeData.isAnimation = true
  930. return
  931. }
  932. const videoEle = item.videoEle
  933. const nextVideo = nextItem.videoEle
  934. if (videoEle && videoEle.duration < 8 && index < nextIndex) {
  935. activeData.isAnimation = false
  936. } else if (nextVideo && nextVideo.duration < 8 && index > nextIndex) {
  937. activeData.isAnimation = false
  938. } else {
  939. activeData.isAnimation = true
  940. }
  941. } else {
  942. activeData.isAnimation = item?.adviseStudyTimeSecond < 8 ? false : true
  943. }
  944. }
  945. // 上一个知识点, 下一个知识点
  946. const handlePreAndNext = (type: string) => {
  947. if (type === 'up') {
  948. handleSwipeChange(popupData.activeIndex - 1)
  949. } else {
  950. handleSwipeChange(popupData.activeIndex + 1)
  951. }
  952. }
  953. /** 弹窗关闭 */
  954. const handleClosePopup = () => {
  955. const item = data.itemList[popupData.activeIndex]
  956. if (item?.type == 'VIDEO' && !item.videoEle?.paused) {
  957. setModelOpen()
  958. }
  959. }
  960. /** 教学数据 */
  961. const studyData = reactive({
  962. type: '' as ToolType,
  963. penShow: false
  964. })
  965. /** 打开教学工具 */
  966. const openStudyTool = (item: ToolItem) => {
  967. const activeItem = data.itemList[popupData.activeIndex]
  968. // 暂停视频和曲谱的播放
  969. if (activeItem.type === 'VIDEO' && activeItem.videoEle) {
  970. activeItem.videoEle.pause()
  971. }
  972. if (activeItem.type === 'SONG') {
  973. activeItem.iframeRef?.contentWindow?.postMessage({ api: 'setPlayState' }, '*')
  974. }
  975. clearModel()
  976. popupData.toolOpen = false
  977. studyData.type = item.type
  978. switch (item.type) {
  979. case 'pen':
  980. studyData.penShow = true
  981. break
  982. }
  983. }
  984. /** 关闭教学工具 */
  985. const closeStudyTool = () => {
  986. studyData.type = 'init'
  987. toggleModel()
  988. }
  989. const activeVideoItem = computed(() => {
  990. const item = data.itemList[popupData.activeIndex]
  991. if (item && item.type && item.type.toLocaleUpperCase() === 'VIDEO') {
  992. return item
  993. }
  994. return {}
  995. })
  996. let closeModelTimer: any = null
  997. /**
  998. * 统计视频播放时间段
  999. */
  1000. const intervalFnRef = ref() // 定时任务
  1001. // 播放视频总时长
  1002. const videoIntervalRef = useInterval(1000, { controls: true })
  1003. videoIntervalRef.pause()
  1004. /**
  1005. * 格式化视屏播放有效时间 - 合并区间
  1006. * @param intervals [[], []]
  1007. * @example [[4, 8],[0, 4],[10, 30]]
  1008. * @returns [[0, 8], [10, 30]]
  1009. */
  1010. const formatEffectiveTime = (intervals: any[]) => {
  1011. const res: any = []
  1012. intervals.sort((a, b) => a[0] - b[0])
  1013. let prev = intervals[0]
  1014. for (let i = 1; i < intervals.length; i++) {
  1015. const cur = intervals[i]
  1016. if (prev[1] >= cur[0]) {
  1017. // 有重合
  1018. prev[1] = Math.max(cur[1], prev[1])
  1019. } else {
  1020. // 不重合,prev推入res数组
  1021. res.push(prev)
  1022. prev = cur // 更新 prev
  1023. }
  1024. }
  1025. res.push(prev)
  1026. // console.log(res, 'formatEffectiveTime')
  1027. return res
  1028. }
  1029. /**
  1030. * 获取数据有效期
  1031. * @param intervals [[], []]
  1032. * @returns 0s
  1033. */
  1034. const formatTimer = (intervals: any[]) => {
  1035. const afterIntervals = formatEffectiveTime(intervals)
  1036. let time = 0
  1037. afterIntervals.forEach((t: any) => {
  1038. time += t[1] - t[0]
  1039. })
  1040. return time
  1041. }
  1042. // 保存零时时间
  1043. // const moreTime: any = ref([]) // 多个观看时间段 已经放到列表里面了
  1044. let tempTime: any = [] // 临时存储时间
  1045. const currentTimer = useInterval(1000, { controls: true })
  1046. // 监听播放状态,
  1047. watch(
  1048. () => videoIntervalRef.isActive.value,
  1049. (newVal: boolean) => {
  1050. initVideoCount(newVal)
  1051. }
  1052. )
  1053. // 白板的批注打开时暂停播放
  1054. watch(
  1055. () => [whitePenShow.value, penShow.value],
  1056. () => {
  1057. if (whitePenShow.value || penShow.value) {
  1058. handleStop()
  1059. }
  1060. }
  1061. )
  1062. // 是否收起
  1063. watch(
  1064. () => activeData.model,
  1065. () => {
  1066. if (activeData.model) {
  1067. isPlay.value = false
  1068. } else {
  1069. isPlay.value = true
  1070. toolOpen.value = false
  1071. }
  1072. }
  1073. )
  1074. /**
  1075. * 初始化视频时长
  1076. * @param newVal 播放状态
  1077. * @param repeat 是否为定时发送的
  1078. */
  1079. const initVideoCount = (newVal: any, repeat = false) => {
  1080. // console.log('watch', forms.player.currentTime)
  1081. const activeVideoRef = data.videoItemRef?.getPlyrRef()
  1082. const initTime = deepClone(tempTime)
  1083. if (repeat) {
  1084. if (tempTime.length > 0) {
  1085. // console.log('join video', tempTime, 'initTime', initTime)
  1086. tempTime[1] = Math.floor(activeVideoRef.currentTime())
  1087. }
  1088. } else {
  1089. if (newVal) {
  1090. tempTime[0] = Math.floor(activeVideoRef.currentTime())
  1091. } else {
  1092. tempTime[1] = Math.floor(activeVideoRef.currentTime())
  1093. }
  1094. }
  1095. if (tempTime.length >= 2) {
  1096. // console.log(tempTime, 'tempTime', moreTime.value)
  1097. // 处理在短时间内的时间差 【视屏拖动,点击可能会导致时间差太大】
  1098. const diffTime = tempTime[1] - tempTime[0] - currentTimer.counter.value > 2
  1099. // 结束时间,如果 大于开始时间则清除
  1100. if (tempTime[1] >= tempTime[0] && !diffTime) {
  1101. data.itemList[popupData.activeIndex].moreTime.push(tempTime)
  1102. // moreTime.value.push(tempTime)
  1103. }
  1104. if (repeat) {
  1105. tempTime = deepClone(initTime)
  1106. } else {
  1107. tempTime = []
  1108. currentTimer.counter.value = 0
  1109. }
  1110. }
  1111. }
  1112. // 更新时间
  1113. const updateStat = async () => {
  1114. try {
  1115. const itemList = data.itemList
  1116. const params: any = []
  1117. itemList.forEach((item: any) => {
  1118. if (item.moreTime.length > 0) {
  1119. const videoBrowseData = formatEffectiveTime(item.moreTime)
  1120. const time = videoBrowseData.length > 0 ? formatTimer(videoBrowseData) : 0
  1121. const temp = {
  1122. lessonCoursewareDetailId: route.query.id,
  1123. browseTime: time, // 播放时长
  1124. videoBrowseData: JSON.stringify(videoBrowseData), // 播放的数据
  1125. videoTime: item.videoTime, // 视频时长
  1126. materialId: item.materialId
  1127. }
  1128. params.push(temp)
  1129. }
  1130. })
  1131. // 只有学生才统计数据
  1132. if (params.length > 0 && state.platformType === 'STUDENT') {
  1133. await request.post(`${state.platformApi}/studentCoursewareMaterialRelation/save`, {
  1134. data: params
  1135. })
  1136. }
  1137. } catch {
  1138. //
  1139. }
  1140. }
  1141. onMounted(() => {
  1142. // 间隔多少时间同步数据
  1143. intervalFnRef.value = useIntervalFn(async () => {
  1144. // 同步数据时先进行有效时间进行保存
  1145. initVideoCount(false, true)
  1146. await updateStat()
  1147. videoIntervalRef.counter.value = 0
  1148. }, 10000)
  1149. })
  1150. /** 统计视频播放时间段 */
  1151. return () => (
  1152. <div id="playContent" class={styles.playContent}>
  1153. <div
  1154. class={styles.coursewarePlay}
  1155. style={{ width: parentContainer.width }}
  1156. onClick={() => {
  1157. clearTimeout(closeModelTimer)
  1158. clearTimeout(activeData.timer)
  1159. closeToast()
  1160. if (Date.now() - activeData.nowTime < 300) {
  1161. handleDbClick()
  1162. return
  1163. }
  1164. activeData.nowTime = Date.now()
  1165. closeModelTimer = setTimeout(() => {
  1166. activeData.model = !activeData.model
  1167. }, 300)
  1168. }}
  1169. >
  1170. <div class={styles.wraps}>
  1171. <div
  1172. style={
  1173. activeVideoItem.value.type &&
  1174. data.animationState === 'end' &&
  1175. data.videoState === 'play'
  1176. ? {
  1177. zIndex: 15,
  1178. opacity: 1
  1179. }
  1180. : { opacity: 0, zIndex: -1, pointerEvents: 'none' }
  1181. }
  1182. class={styles.itemDiv}
  1183. >
  1184. <VideoPlay
  1185. ref={(el: any) => (data.videoItemRef = el)}
  1186. item={activeVideoItem.value}
  1187. activeModel={activeData.model}
  1188. // isEmtry={isEmtry}
  1189. onPlay={() => {
  1190. data.videoState = 'play'
  1191. data.animationState = 'end'
  1192. if(whitePenShow.value || penShow.value || popupData.coursewareOpen || popupData.open || popupData.guideOpen || popupData.pointOpen) {
  1193. handleStop()
  1194. }
  1195. }}
  1196. onLoadedmetadata={(videoItem: any) => {
  1197. data.videoState = 'play'
  1198. activeVideoItem.value.videoEle = videoItem
  1199. if (!activeVideoItem.value.isprepare) {
  1200. activeVideoItem.value.isprepare = true
  1201. }
  1202. }}
  1203. onPause={() => {
  1204. clearTimeout(activeData.timer)
  1205. // activeData.model = true
  1206. videoIntervalRef.pause()
  1207. }}
  1208. onSeeked={() => {
  1209. videoIntervalRef.isActive.value && videoIntervalRef.pause()
  1210. }}
  1211. onSeeking={() => {
  1212. videoIntervalRef.isActive.value && videoIntervalRef.pause()
  1213. }}
  1214. onWaiting={() => {
  1215. videoIntervalRef.isActive.value && videoIntervalRef.pause()
  1216. }}
  1217. onTimeupdate={() => {
  1218. const activeVideoRef = data.videoItemRef?.getPlyrRef()
  1219. if (
  1220. !videoIntervalRef.isActive.value &&
  1221. activeVideoRef?.currentTime() > 0 &&
  1222. !activeVideoRef?.paused()
  1223. ) {
  1224. videoIntervalRef.resume()
  1225. }
  1226. }}
  1227. onTogglePlay={(paused: boolean) => {
  1228. // console.log('播放切换', paused)
  1229. // 首次播放完成
  1230. if (!activeVideoItem.value.isprepare) {
  1231. activeVideoItem.value.isprepare = true
  1232. }
  1233. activeVideoItem.value.autoPlay = false
  1234. if (paused || popupData.open || popupData.guideOpen) {
  1235. clearTimeout(activeData.timer)
  1236. } else {
  1237. setModelOpen()
  1238. }
  1239. }}
  1240. onEnded={async () => {
  1241. const _index = popupData.activeIndex + 1
  1242. if (_index < data.itemList.length) {
  1243. handleSwipeChange(_index)
  1244. } else {
  1245. // 说明是最后一个
  1246. intervalFnRef.value.pause()
  1247. // 同步数据时先进行有效时间进行保存
  1248. initVideoCount(false, true)
  1249. await updateStat()
  1250. }
  1251. }}
  1252. onReset={() => {
  1253. if (!activeVideoItem.value.videoEle?.paused) {
  1254. setModelOpen()
  1255. }
  1256. }}
  1257. onError={() => {
  1258. // 视屏异常
  1259. activeVideoItem.value.error = true
  1260. }}
  1261. />
  1262. </div>
  1263. {data.itemList.map((m: any, mIndex: number) => {
  1264. const isRenderItem = Math.abs(popupData.activeIndex - mIndex) < 2
  1265. const isRender = Math.abs(popupData.playIndex - mIndex) < 2
  1266. // 判断是否是当前选中的元素
  1267. const activeEle = popupData.playIndex === mIndex ? true : false
  1268. return isRenderItem ? (
  1269. <div
  1270. key={'index' + mIndex}
  1271. data-id={'data' + mIndex}
  1272. class={[
  1273. styles.itemDiv,
  1274. activeEle && styles.itemActive,
  1275. activeData.isAnimation && styles.acitveAnimation,
  1276. isRenderItem ? styles.show : styles.hide
  1277. ]}
  1278. style={
  1279. mIndex < popupData.activeIndex
  1280. ? effects[effectIndex.value].prev
  1281. : mIndex > popupData.activeIndex
  1282. ? effects[effectIndex.value].next
  1283. : {}
  1284. }
  1285. >
  1286. <Transition name="van-fade">
  1287. {m.type === 'VIDEO' &&
  1288. data.animationState !== 'end' &&
  1289. data.videoState != 'play' &&
  1290. !m.isprepare && (
  1291. <div class={styles.loadWrap}>
  1292. <Vue3Lottie animationData={playLoadData}></Vue3Lottie>
  1293. </div>
  1294. )}
  1295. </Transition>
  1296. {isRender && m.type === 'IMG' && (
  1297. <>
  1298. <img src={m.content} />
  1299. {m.materialMusicId && state.platformType !== 'SCHOOL' && (
  1300. <div
  1301. class={[styles.goPractice, activeData.model ? '' : styles.hide]}
  1302. onClick={(e: any) => {
  1303. // 去云练习完整版
  1304. e.stopPropagation()
  1305. const parmas = qs.stringify({
  1306. id: m.materialMusicId
  1307. })
  1308. const src = `${location.origin}/orchestra-music-score/?` + parmas
  1309. postMessage({
  1310. api: 'openAccompanyWebView',
  1311. content: {
  1312. url: src,
  1313. orientation: 0,
  1314. c_orientation: 0,
  1315. isHideTitle: true,
  1316. statusBarTextColor: false,
  1317. isOpenLight: true
  1318. }
  1319. })
  1320. }}
  1321. ></div>
  1322. )}
  1323. </>
  1324. )}
  1325. {isRender && m.type === 'SONG' && (
  1326. <MusicScore
  1327. activeModel={activeData.model}
  1328. data-vid={m.id}
  1329. music={m}
  1330. onSetIframe={(el: any) => {
  1331. m.iframeRef = el
  1332. }}
  1333. />
  1334. )}
  1335. </div>
  1336. ) : (
  1337. ''
  1338. )
  1339. })}
  1340. </div>
  1341. <Transition name="left">
  1342. {activeData.model && (
  1343. <div class={styles.leftFixedBtns} onClick={(e: Event) => e.stopPropagation()}>
  1344. <div class={[styles.btnsWrap, styles.prePoint]}>
  1345. {data.source === 'search' ?
  1346. <div class={styles.fullBtn} onClick={() => {
  1347. handleStop()
  1348. detailTempSearchList.value = detailList.value
  1349. popupData.tempItemActive = ""
  1350. popupData.tempTabActive = ""
  1351. data.searchTemp = ""
  1352. // data.searchTemp = JSON.parse(JSON.stringify(data.search))
  1353. popupData.open = true
  1354. }}>
  1355. <img src={iconSearch} />
  1356. </div> : <>
  1357. {state.platformType === 'TEACHER' && <div class={styles.fullBtn} onClick={() => {
  1358. popupData.coursewareOpen = true
  1359. handleStop()
  1360. }}>
  1361. <img src={iconCourseType} />
  1362. </div>}
  1363. <div class={styles.fullBtn} onClick={() => {
  1364. popupData.open = true
  1365. handleStop()
  1366. }}>
  1367. <img src={iconMenu} />
  1368. {/* <span>知识点</span> */}
  1369. </div>
  1370. </>}
  1371. <div
  1372. class={[styles.fullBtn, !(popupData.activeIndex != 0) && styles.disabled]}
  1373. onClick={() => {
  1374. if(popupData.activeIndex != 0) handlePreAndNext('up')
  1375. }}
  1376. >
  1377. <img src={iconUp} />
  1378. {/* <span style={{ textAlign: 'center' }}>上一个</span> */}
  1379. </div>
  1380. <div
  1381. class={[styles.fullBtn, !(popupData.activeIndex != data.itemList.length - 1) && styles.disabled]}
  1382. onClick={() => {
  1383. if(popupData.activeIndex != data.itemList.length - 1) handlePreAndNext('down')
  1384. }}
  1385. >
  1386. {/* <span style={{ textAlign: 'center' }}>下一个</span> */}
  1387. <img src={iconDown} />
  1388. </div>
  1389. </div>
  1390. </div>
  1391. )}
  1392. </Transition>
  1393. </div>
  1394. <div
  1395. style={{ transform: activeData.model ? '' : 'translateY(-100%)' }}
  1396. id="coursePlayHeader"
  1397. class={styles.headerContainer}
  1398. ref={headeRef}
  1399. >
  1400. <div class={styles.backBtn}>
  1401. <Icon name={iconBack} onClick={() => goback()} />
  1402. <div class={styles.titleSection}>
  1403. <div class={styles.title}>{popupData.tabName}</div>
  1404. <div class={styles.titleContent}>
  1405. <p>{data.itemList[popupData.activeIndex]?.name}</p>
  1406. {data.detail?.lessonTargetDesc ? <span onClick={() => onTitleTip('phaseGoals', data.detail?.lessonTargetDesc)}>阶段目标</span>: ""}
  1407. {data.itemList[popupData.activeIndex]?.checkItem ? <span onClick={() => onTitleTip('checkItem', data.itemList[popupData.activeIndex]?.checkItem)}>检查事项</span> : ""}
  1408. </div>
  1409. </div>
  1410. </div>
  1411. {data.isCourse && isCurrentCoursewareMenu.value && <PlayRecordTime ref={playRef} isCurrentCoursewareMenu={isCurrentCoursewareMenu.value} list={data.knowledgePointList} />}
  1412. {/* <div
  1413. class={styles.menu}
  1414. onClick={() => {
  1415. const _effectIndex = effectIndex.value + 1
  1416. effectIndex.value = _effectIndex >= effects.length - 1 ? 0 : _effectIndex
  1417. setModelOpen()
  1418. }}
  1419. >
  1420. {popupData.tabName}
  1421. </div> */}
  1422. {state.platformType === 'TEACHER' && (
  1423. <div
  1424. class={styles.headRight}
  1425. onClick={(e: Event) => {
  1426. e.stopPropagation()
  1427. clearTimeout(activeData.timer)
  1428. }}
  1429. >
  1430. {data.isCourse && (
  1431. <>
  1432. <div class={styles.pointBtn} onClick={() => gotoRollCall('student_roll_call')}>
  1433. {/* <img src={iconDian} /> */}
  1434. <span>点名</span>
  1435. </div>
  1436. <div class={styles.pointBtn} onClick={() => gotoRollCall('sign_out')}>
  1437. {/* <img src={iconPoint} /> */}
  1438. <span>签退</span>
  1439. </div>
  1440. </>
  1441. )}
  1442. <div class={styles.rightBtn} onClick={() => (popupData.guideOpen = true)}>
  1443. <img src={iconTouping} />
  1444. </div>
  1445. {/* <div
  1446. class={styles.rightBtn}
  1447. onClick={() => {
  1448. openStudyTool({
  1449. type: 'pen',
  1450. icon: iconPen,
  1451. name: '批注'
  1452. })
  1453. }}
  1454. >
  1455. <img src={iconPen} />
  1456. </div> */}
  1457. {/* <div class={styles.rightBtn} onClick={() => (popupData.toolOpen = true)}>
  1458. <img src={iconMore} />
  1459. </div> */}
  1460. </div>
  1461. )}
  1462. </div>
  1463. {/* 更多弹窗 */}
  1464. <Popup
  1465. class={[styles.popupMore, styles.popupCoursewarePlay]}
  1466. overlayClass={styles.overlayClass}
  1467. position="right"
  1468. round
  1469. v-model:show={popupData.toolOpen}
  1470. onClose={handleClosePopup}
  1471. >
  1472. <Tool onHandleTool={openStudyTool} />
  1473. </Popup>
  1474. <Popup
  1475. class={[styles.popup, styles.popupCoursewarePlay]}
  1476. overlayClass={styles.overlayClass}
  1477. position="right"
  1478. round
  1479. v-model:show={popupData.open}
  1480. onClose={handleClosePopup}
  1481. >
  1482. {data.source === 'search' ?
  1483. <PointsSearch
  1484. data={detailTempSearchList.value}
  1485. search={data.searchTemp || data.search}
  1486. loading={data.searchLoading}
  1487. tabActive={popupData.tempTabActive || popupData.tabActive}
  1488. itemActive={popupData.tempItemActive || popupData.itemActive}
  1489. open={popupData.open}
  1490. onHandleSelect={(res: any) => {
  1491. popupData.open = false;
  1492. if(res.isSearch) {
  1493. detailList.value = detailTempSearchList.value
  1494. const tempList: any[] = []
  1495. detailTempSearchList.value?.forEach((item: any) => {
  1496. if(Array.isArray(item.list)) {
  1497. tempList.push(...item.list)
  1498. }
  1499. })
  1500. data.itemList = tempList || []
  1501. data.search = data.searchTemp ? JSON.parse(JSON.stringify(data.searchTemp)) : ''
  1502. }
  1503. toggleMaterial(res.itemActive);
  1504. }}
  1505. onHandleSearch={async (val: any) => {
  1506. data.searchLoading = true
  1507. detailTempSearchList.value = []
  1508. await getSearchDetail({
  1509. type: 'pointSearch',
  1510. search: val.search
  1511. })
  1512. data.searchTemp = val.search;
  1513. data.searchLoading = false
  1514. }} /> :
  1515. <Points
  1516. data={data.knowledgePointList}
  1517. tabActive={popupData.tabActive}
  1518. itemActive={popupData.itemActive}
  1519. onHandleSelect={(res: any) => {
  1520. // onChangeSwiper('change', res.itemActive)
  1521. popupData.open = false
  1522. toggleMaterial(res.itemActive)
  1523. }}
  1524. />}
  1525. </Popup>
  1526. <Popup
  1527. class={[styles.popup, styles.popupCoursewarePlay]}
  1528. overlayClass={styles.overlayClass}
  1529. position="right"
  1530. round
  1531. v-model:show={popupData.coursewareOpen}
  1532. onClose={handleClosePopup}>
  1533. {/* 课件类型 */}
  1534. <CoursewareType list={data.refLevelList} onConfirm={async (item: any) => {
  1535. // 判断是否为当前课程类型
  1536. if(data.currentId === item.id) {
  1537. return
  1538. }
  1539. //
  1540. const n = await getDetail(item.id);
  1541. const s = await getRefLevel(item.id);
  1542. if(n && s) {
  1543. data.currentId = item.id;
  1544. isCurrentCoursewareMenu.value = item.id === route.query.id ? true : false
  1545. popupData.coursewareOpen = false;
  1546. popupData.activeIndex = 0;
  1547. nextTick(() => {
  1548. popupData.open = true
  1549. })
  1550. } else {
  1551. if(!isOnline.value) {
  1552. showToast('网络异常')
  1553. }
  1554. }
  1555. }} />
  1556. </Popup>
  1557. <Popup
  1558. class={[styles.popup, styles.popupCoursewarePlay]}
  1559. overlayClass={styles.overlayClass}
  1560. position="right"
  1561. round
  1562. v-model:show={popupData.guideOpen}
  1563. onClose={handleClosePopup}
  1564. >
  1565. <OGuide />
  1566. </Popup>
  1567. <Popup
  1568. class={[styles.popup, styles.popupPoint]}
  1569. round
  1570. style={{ background: 'transparent !important' }}
  1571. v-model:show={popupData.pointOpen}
  1572. onClose={handleClosePopup}>
  1573. <CoursewareTips onClose={() => {
  1574. popupData.pointOpen = false
  1575. }} show={popupData.pointOpen} content={popupData.pointContent} titleName={popupData.pointTitle} />
  1576. </Popup>
  1577. <GlobalTools />
  1578. </div>
  1579. )
  1580. }
  1581. })