index.tsx 51 KB

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