index.tsx 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020
  1. import { closeToast, Icon, Popup, showDialog, showToast } from "vant"
  2. import { defineComponent, onMounted, reactive, nextTick, onUnmounted, ref, watch, Transition, computed } from "vue"
  3. import iconBack from "./image/back.svg"
  4. import styles from "./index.module.scss"
  5. import "plyr/dist/plyr.css"
  6. import "vant/lib/index.css"
  7. // import request from "@/helpers/request"
  8. // import { state } from "@/state"
  9. const state = {
  10. platformApi: "/api",
  11. platformType: "TEACHER"
  12. }
  13. import { useRoute } from "vue-router"
  14. import { postMessage, promisefiyPostMessage } from "./helpers/native-message"
  15. import MusicScore from "./component/musicScore"
  16. import iconDian from "./image/icon-dian.svg"
  17. import iconPoint from "./image/icon-point.svg"
  18. import icons from "./image/icons.json"
  19. const { iconUp, iconDown, iconPen, iconTouping } = icons
  20. import Points from "./component/points"
  21. import { browser } from "./helpers/utils"
  22. import { Vue3Lottie } from "vue3-lottie"
  23. import playLoadData from "./datas/data.json"
  24. import { usePageVisibility } from "@vant/use"
  25. import PlayRecordTime from "./playRecordTime"
  26. //import { handleCheckVip } from "../hook/useFee"
  27. function handleCheckVip() {
  28. return true
  29. }
  30. //import OGuide from "./component/o-guide"
  31. import Tool, { ToolItem, ToolType } from "./component/tool"
  32. import Pen from "./component/tools/pen"
  33. import VideoItem from "./component/video-item"
  34. import { getLessonCourseDetail_gym, getLessonCoursewareDetail_gyt } from "@/api/cloudTextbooks.api"
  35. import { httpAjaxErrMsg } from "@/plugin/httpAjax"
  36. import userStore from "@/store/modules/user"
  37. export default defineComponent({
  38. name: "CoursewarePlay",
  39. setup() {
  40. const pageVisibility = usePageVisibility()
  41. const userStoreHook = userStore()
  42. /** 页面显示和隐藏 */
  43. watch(
  44. () => pageVisibility.value,
  45. value => {
  46. if (value == "hidden") {
  47. handleStop()
  48. }
  49. }
  50. )
  51. /** 设置播放容器 16:9 */
  52. const parentContainer = reactive({
  53. width: "100vw"
  54. })
  55. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  56. const setContainer = () => {
  57. const min = Math.min(screen.width, screen.height)
  58. const max = Math.max(screen.width, screen.height)
  59. const width = min * (16 / 9)
  60. if (width > max) {
  61. parentContainer.width = "100vw"
  62. return
  63. } else {
  64. parentContainer.width = width + "px"
  65. }
  66. }
  67. const handleInit = (type = 0) => {
  68. //设置容器16:9
  69. //setContainer()
  70. // 横屏
  71. postMessage(
  72. {
  73. api: "setRequestedOrientation",
  74. content: {
  75. orientation: type
  76. }
  77. },
  78. () => {
  79. console.log(234)
  80. }
  81. )
  82. // 头,包括返回箭头
  83. // postMessage({
  84. // api: 'setTitleBarVisibility',
  85. // content: {
  86. // status: type
  87. // }
  88. // })
  89. // 安卓的状态栏
  90. postMessage({
  91. api: "setStatusBarVisibility",
  92. content: {
  93. isVisibility: type
  94. }
  95. })
  96. // 进入页面设置常量
  97. postMessage({
  98. api: "keepScreenLongLight",
  99. content: {
  100. isOpenLight: type ? true : false
  101. }
  102. })
  103. }
  104. handleInit()
  105. onUnmounted(() => {
  106. handleInit(1)
  107. window.removeEventListener("message", iframeHandle)
  108. window.removeEventListener("keyup", handleEventKeyup)
  109. })
  110. const route = useRoute()
  111. const headeRef = ref()
  112. const data = reactive({
  113. detail: null as any,
  114. knowledgePointList: [] as any,
  115. itemList: [] as any,
  116. showHead: true,
  117. isCourse: false,
  118. isRecordPlay: false,
  119. videoRefs: {},
  120. videoState: "init" as "init" | "play",
  121. videoItemRef: null as any,
  122. animationState: "start" as "start" | "end"
  123. })
  124. const activeData = reactive({
  125. isAutoPlay: true, // 是否自动播放
  126. nowTime: 0,
  127. model: true, // 遮罩
  128. isAnimation: true, // 是否动画
  129. videoBtns: true, // 视频
  130. currentTime: 0,
  131. duration: 0,
  132. timer: null as any,
  133. item: null as any
  134. })
  135. // 获取缓存路径
  136. const getCacheFilePath = async (material: any) => {
  137. const res = await promisefiyPostMessage({
  138. api: "getCourseFilePath",
  139. content: {
  140. url: material.content,
  141. localPath: "",
  142. materialId: material.materialId,
  143. updateTime: material.updateTime,
  144. type: material.typeCode // SONG VIDEO IMAGE
  145. }
  146. })
  147. // console.log('缓存路径返回', res)
  148. return res
  149. }
  150. // 获取当前课程是否签退
  151. // const getCourseSchedule = async () => {
  152. // if (!route.query.courseId) return;
  153. // try {
  154. // const res = await request.get(
  155. // `${state.platformApi}/courseSchedule/detail/${route.query.courseId}`,
  156. // {
  157. // hideLoading: true
  158. // }
  159. // );
  160. // if (res?.data) {
  161. // data.isCourse =
  162. // res.data.status === 'ING' && state.platformType == 'TEACHER'
  163. // ? true
  164. // : false;
  165. // // data.isRecordPlay = Date.now() > dayjs(res.data.startTime).valueOf()
  166. // }
  167. // } catch (e) {
  168. // console.log(e);
  169. // }
  170. // };
  171. const getTempList = async (materialList: any, name: any) => {
  172. const list: any = []
  173. const browserInfo = browser()
  174. for (let j = 0; j < materialList.length; j++) {
  175. const material = materialList[j]
  176. //请求本地缓存
  177. if (browserInfo.isApp && ["VIDEO", "IMG"].includes(material.typeCode)) {
  178. const localData = await getCacheFilePath(material)
  179. if (localData?.content?.localPath) {
  180. material.url = material.content
  181. material.content = localData.content.localPath
  182. }
  183. }
  184. list.push({
  185. ...material,
  186. iframeRef: null,
  187. videoEle: null,
  188. tabName: name,
  189. autoPlay: false, //加载完成是否自动播放
  190. isprepare: false, // 视频是否加载完成
  191. isRender: false // 是否渲染了
  192. })
  193. }
  194. return list
  195. }
  196. const getItemList = async () => {
  197. const list: any = []
  198. for (let i = 0; i < data.knowledgePointList.length; i++) {
  199. const item = data.knowledgePointList[i]
  200. if (item.materialList && item.materialList.length > 0) {
  201. const tempList = await getTempList(item.materialList, item.name)
  202. list.push(...tempList)
  203. }
  204. // 第二层级
  205. if (item.children && item.children.length > 0) {
  206. const childrenList = item.children || []
  207. for (let j = 0; j < childrenList.length; j++) {
  208. const childItem = childrenList[j]
  209. const tempList = await getTempList(childItem.materialList, childItem.name)
  210. list.push(...tempList)
  211. }
  212. }
  213. }
  214. // console.log(list, 'list')
  215. let _firstIndex = list.findIndex((n: any) => n.knowledgePointMaterialRelationId == route.query.kId || n.materialId == route.query.kId)
  216. _firstIndex = _firstIndex > -1 ? _firstIndex : 0
  217. const item = list[_firstIndex]
  218. // console.log(_firstIndex, '_firstIndex', route.query.kId, 'route.query.kId', item)
  219. // 是否自动播放
  220. if (activeData.isAutoPlay) {
  221. item.autoPlay = true
  222. }
  223. popupData.activeIndex = _firstIndex
  224. popupData.playIndex = _firstIndex
  225. popupData.tabName = item.tabName
  226. popupData.tabActive = item.knowledgePointId
  227. popupData.itemActive = item.id
  228. popupData.itemName = item.name
  229. nextTick(() => {
  230. data.itemList = list
  231. checkedAnimation(popupData.activeIndex)
  232. postMessage({
  233. api: "courseLoading",
  234. content: {
  235. show: false,
  236. type: "fullscreen"
  237. }
  238. })
  239. setTimeout(() => {
  240. data.animationState = "end"
  241. }, 500)
  242. })
  243. }
  244. const getDetail = async () => {
  245. try {
  246. //const res: any = await request.get(state.platformApi + `/lessonCourseware/getLessonCourseDetail/${route.query.id}`)
  247. const res: any = await httpAjaxErrMsg(
  248. userStoreHook.roles === "GYM" ? getLessonCourseDetail_gym : getLessonCoursewareDetail_gyt,
  249. route.params.id as string
  250. )
  251. data.detail = res.data
  252. if (res?.data?.lockFlag) {
  253. postMessage({
  254. api: "courseLoading",
  255. content: {
  256. show: false,
  257. type: "fullscreen"
  258. }
  259. })
  260. showDialog({
  261. title: "温馨提示",
  262. message: "课件已锁定"
  263. }).then(() => {
  264. goback()
  265. })
  266. return
  267. }
  268. if (Array.isArray(res?.data?.knowledgePointList)) {
  269. let index = 0
  270. data.knowledgePointList = res.data.knowledgePointList.map((n: any) => {
  271. if (Array.isArray(n.materialList)) {
  272. n.materialList = n.materialList.map((item: any) => {
  273. index++
  274. return {
  275. ...item,
  276. knowledgePointId: [item.knowledgePointId],
  277. materialId: item.id,
  278. id: index + "",
  279. typeCode: item.type || item.typeCode // GYM和GYT type字段不一样 这里统一
  280. }
  281. })
  282. }
  283. if (Array.isArray(n.children)) {
  284. n.children = n.children.map((cn: any) => {
  285. cn.materialList = cn.materialList.map((item: any) => {
  286. index++
  287. return {
  288. ...item,
  289. knowledgePointId: [n.id, item.knowledgePointId],
  290. materialId: item.id,
  291. id: index + "",
  292. typeCode: item.type || item.typeCode // GYM和GYT type字段不一样 这里统一
  293. }
  294. })
  295. return cn
  296. })
  297. }
  298. return n
  299. })
  300. getItemList()
  301. }
  302. } catch (error) {
  303. console.log(error)
  304. }
  305. }
  306. // ifram事件处理
  307. const iframeHandle = (ev: MessageEvent) => {
  308. if (ev.data?.api === "headerTogge") {
  309. activeData.model = ev.data.show || (ev.data.playState == "play" ? false : true)
  310. }
  311. }
  312. onMounted(async () => {
  313. await getDetail()
  314. const hasFree = String(data.detail?.accessScope) === "0"
  315. if (!hasFree) {
  316. const hasVip = handleCheckVip()
  317. if (!hasVip) {
  318. nextTick(() => {
  319. postMessage({
  320. api: "courseLoading",
  321. content: {
  322. show: false,
  323. type: "fullscreen"
  324. }
  325. })
  326. })
  327. return
  328. }
  329. }
  330. // getCourseSchedule();
  331. window.addEventListener("message", iframeHandle)
  332. window.addEventListener("keyup", handleEventKeyup)
  333. })
  334. function handleEventKeyup(event: any) {
  335. const key = event.key
  336. if (key === "ArrowDown") {
  337. handlePreAndNext("down")
  338. } else if (key === "ArrowUp") {
  339. handlePreAndNext("up")
  340. }
  341. }
  342. const playRef = ref()
  343. // 返回
  344. const goback = () => {
  345. try {
  346. playRef.value?.handleOut()
  347. } catch (error) {
  348. console.log(error)
  349. }
  350. postMessage({ api: "goBack" })
  351. window.close()
  352. }
  353. const popupData = reactive({
  354. open: false,
  355. activeIndex: 0,
  356. playIndex: 0,
  357. tabActive: "",
  358. tabName: "",
  359. itemActive: "",
  360. itemName: "",
  361. guideOpen: false,
  362. toolOpen: false // 工具弹窗控制
  363. })
  364. const stopVideo = (el: HTMLVideoElement) => {
  365. return new Promise(resolve => {
  366. if (el.paused) return resolve(true)
  367. el.onpause = () => {
  368. console.log("暂停")
  369. resolve(true)
  370. }
  371. el.pause()
  372. })
  373. }
  374. /**停止所有的播放 */
  375. const handleStop = async () => {
  376. const videos = document.querySelectorAll("video")
  377. for (let i = 0; i < videos.length; i++) {
  378. const videoEle = videos[i] as HTMLVideoElement
  379. await stopVideo(videoEle)
  380. }
  381. console.log("视频暂停完成")
  382. data.itemList.forEach((item: any) => {
  383. if (item.typeCode === "SONG") {
  384. item.iframeRef?.contentWindow?.postMessage({ api: "setPlayState" }, "*")
  385. }
  386. })
  387. }
  388. // 切换素材
  389. const toggleMaterial = (itemActive: any) => {
  390. const index = data.itemList.findIndex((n: any) => n.id == itemActive)
  391. if (index > -1) {
  392. handleSwipeChange(index)
  393. }
  394. }
  395. /** 延迟收起模态框 */
  396. const setModelOpen = () => {
  397. clearTimeout(activeData.timer)
  398. closeToast()
  399. activeData.timer = setTimeout(() => {
  400. activeData.model = false
  401. }, 4000)
  402. }
  403. /** 立即收起所有的模态框 */
  404. const clearModel = () => {
  405. clearTimeout(activeData.timer)
  406. closeToast()
  407. activeData.model = false
  408. }
  409. const toggleModel = (type = true) => {
  410. activeData.model = type
  411. }
  412. // 去点名,签退
  413. const gotoRollCall = (pageTag: string) => {
  414. postMessage({
  415. api: "open_app_page",
  416. content: {
  417. action: "app",
  418. pageTag: pageTag,
  419. url: "",
  420. params: JSON.stringify({ courseId: route.query.courseId })
  421. }
  422. })
  423. }
  424. // 双击
  425. const handleDbClick = () => {
  426. if (activeVideoItem.value.typeCode === "VIDEO") {
  427. const activeVideoRef = data.videoItemRef?.getVideoRef()
  428. if (activeVideoRef) {
  429. if (activeVideoRef.paused) {
  430. activeVideoRef.play()
  431. } else {
  432. activeVideoRef.pause()
  433. showToast("已暂停")
  434. }
  435. }
  436. }
  437. }
  438. const effectIndex = ref(0)
  439. const effects = [
  440. {
  441. prev: {
  442. transform: "translate3d(0, 0, -800px) rotateX(180deg)"
  443. },
  444. next: {
  445. transform: "translate3d(0, 0, -800px) rotateX(-180deg)"
  446. }
  447. },
  448. {
  449. prev: {
  450. transform: "translate3d(-100%, 0, -800px)"
  451. },
  452. next: {
  453. transform: "translate3d(100%, 0, -800px)"
  454. }
  455. },
  456. {
  457. prev: {
  458. transform: "translate3d(-50%, 0, -800px) rotateY(80deg)"
  459. },
  460. next: {
  461. transform: "translate3d(50%, 0, -800px) rotateY(-80deg)"
  462. }
  463. },
  464. {
  465. prev: {
  466. transform: "translate3d(-100%, 0, -800px) rotateY(-120deg)"
  467. },
  468. next: {
  469. transform: "translate3d(100%, 0, -800px) rotateY(120deg)"
  470. }
  471. },
  472. // 风车4
  473. {
  474. prev: {
  475. transform: "translate3d(-50%, 50%, -800px) rotateZ(-14deg)",
  476. opacity: 0
  477. },
  478. next: {
  479. transform: "translate3d(50%, 50%, -800px) rotateZ(14deg)",
  480. opacity: 0
  481. }
  482. },
  483. // 翻页5
  484. {
  485. prev: {
  486. transform: "translateZ(-800px) rotate3d(0, -1, 0, 90deg)",
  487. opacity: 0
  488. },
  489. next: {
  490. transform: "translateZ(-800px) rotate3d(0, 1, 0, 90deg)",
  491. opacity: 0
  492. },
  493. current: { transitionDelay: "700ms" }
  494. }
  495. ]
  496. const acitveTimer = ref()
  497. // 轮播切换
  498. const handleSwipeChange = async (index: number) => {
  499. // 如果是当前正在播放 或者是视频最后一个
  500. if (popupData.activeIndex == index) return
  501. await handleStop()
  502. data.animationState = "start"
  503. data.videoState = "init"
  504. clearTimeout(acitveTimer.value)
  505. checkedAnimation(popupData.activeIndex, index)
  506. nextTick(() => {
  507. popupData.activeIndex = index
  508. acitveTimer.value = setTimeout(
  509. () => {
  510. popupData.playIndex = index
  511. const item = data.itemList[index]
  512. if (item) {
  513. popupData.tabActive = item.knowledgePointId
  514. popupData.itemActive = item.id
  515. popupData.itemName = item.name
  516. popupData.tabName = item.tabName
  517. if (item.typeCode == "SONG") {
  518. activeData.model = true
  519. }
  520. }
  521. requestAnimationFrame(() => {
  522. const _effectIndex = effectIndex.value + 1
  523. effectIndex.value = _effectIndex >= effects.length - 1 ? 0 : _effectIndex
  524. if (item && item.typeCode === "VIDEO") {
  525. // 自动播放下一个视频
  526. clearTimeout(activeData.timer)
  527. closeToast()
  528. item.autoPlay = true
  529. data.animationState = "end"
  530. }
  531. })
  532. },
  533. activeData.isAnimation ? 850 : 0
  534. )
  535. })
  536. }
  537. /** 是否有转场动画 */
  538. const checkedAnimation = (index: number, nextIndex?: number) => {
  539. nextIndex = nextIndex ? nextIndex : index + 1
  540. const item = data.itemList[index]
  541. const nextItem = data.itemList[nextIndex]
  542. if (nextItem) {
  543. if (nextItem.knowledgePointId != item.knowledgePointId) {
  544. activeData.isAnimation = true
  545. return
  546. }
  547. const videoEle = item.videoEle
  548. const nextVideo = nextItem.videoEle
  549. if (videoEle && videoEle.duration < 8 && index < nextIndex) {
  550. activeData.isAnimation = false
  551. } else if (nextVideo && nextVideo.duration < 8 && index > nextIndex) {
  552. activeData.isAnimation = false
  553. } else {
  554. activeData.isAnimation = true
  555. }
  556. } else {
  557. activeData.isAnimation = item?.adviseStudyTimeSecond < 8 ? false : true
  558. }
  559. }
  560. // 上一个知识点, 下一个知识点
  561. const handlePreAndNext = (type: string) => {
  562. if (type === "up") {
  563. if (!popupData.activeIndex) {
  564. return
  565. }
  566. handleSwipeChange(popupData.activeIndex - 1)
  567. } else {
  568. if (popupData.activeIndex === data.itemList.length - 1) {
  569. return
  570. }
  571. handleSwipeChange(popupData.activeIndex + 1)
  572. }
  573. }
  574. /** 弹窗关闭 */
  575. const handleClosePopup = () => {
  576. const item = data.itemList[popupData.activeIndex]
  577. if (item?.typeCode == "VIDEO" && !item.videoEle?.paused) {
  578. setModelOpen()
  579. }
  580. }
  581. /** 教学数据 */
  582. const studyData = reactive({
  583. type: "" as ToolType,
  584. penShow: false
  585. })
  586. const whiteShow = ref(false)
  587. /** 打开教学工具 */
  588. const openStudyTool = (item: ToolItem) => {
  589. const activeItem = data.itemList[popupData.activeIndex]
  590. // 暂停视频和曲谱的播放
  591. if (activeItem.typeCode === "VIDEO" && data.videoItemRef?.getVideoItem()) {
  592. data.videoItemRef?.getVideoItem().pause()
  593. }
  594. if (activeItem.typeCode === "SONG") {
  595. activeItem.iframeRef?.contentWindow?.postMessage({ api: "setPlayState" }, "*")
  596. }
  597. clearModel()
  598. popupData.toolOpen = false
  599. studyData.type = item.type
  600. switch (item.type) {
  601. case "pen":
  602. studyData.penShow = true
  603. break
  604. case "white":
  605. whiteShow.value = true
  606. break
  607. }
  608. }
  609. /** 关闭教学工具 */
  610. const closeStudyTool = () => {
  611. studyData.type = "init"
  612. toggleModel()
  613. }
  614. const activeVideoItem = computed(() => {
  615. console.log(data.itemList, " data.itemList")
  616. const item = data.itemList[popupData.activeIndex]
  617. if (item && item.typeCode && item.typeCode.toLocaleUpperCase() === "VIDEO") {
  618. return item
  619. }
  620. return {}
  621. })
  622. let closeModelTimer: any = null
  623. return () => (
  624. <div id="playContent" class={styles.playContent}>
  625. <div
  626. class={styles.coursewarePlay}
  627. style={{ width: parentContainer.width }}
  628. onClick={() => {
  629. clearTimeout(closeModelTimer)
  630. clearTimeout(activeData.timer)
  631. closeToast()
  632. if (Date.now() - activeData.nowTime < 300) {
  633. handleDbClick()
  634. return
  635. }
  636. activeData.nowTime = Date.now()
  637. closeModelTimer = setTimeout(() => {
  638. activeData.model = !activeData.model
  639. }, 300)
  640. }}
  641. >
  642. <div class={styles.wraps}>
  643. <div
  644. style={
  645. activeVideoItem.value.typeCode && data.animationState === "end" && data.videoState === "play"
  646. ? {
  647. zIndex: 15,
  648. opacity: 1
  649. }
  650. : { opacity: 0, zIndex: -1 }
  651. }
  652. class={styles.itemDiv}
  653. >
  654. <VideoItem
  655. ref={(el: any) => (data.videoItemRef = el)}
  656. item={activeVideoItem.value}
  657. activeModel={activeData.model}
  658. onClose={setModelOpen}
  659. onPlay={() => {
  660. data.videoState = "play"
  661. }}
  662. onPause={() => {
  663. clearTimeout(activeData.timer)
  664. activeData.model = true
  665. }}
  666. onEnded={() => {
  667. const _index = popupData.activeIndex + 1
  668. if (_index < data.itemList.length) {
  669. handleSwipeChange(_index)
  670. }
  671. }}
  672. />
  673. </div>
  674. {data.itemList.map((m: any, mIndex: number) => {
  675. const isRenderItem = Math.abs(popupData.activeIndex - mIndex) < 2
  676. const isRender = Math.abs(popupData.playIndex - mIndex) < 2
  677. // 判断是否是当前选中的元素
  678. const activeEle = popupData.playIndex === mIndex ? true : false
  679. return isRenderItem ? (
  680. <div
  681. key={"index" + mIndex}
  682. data-id={"data" + mIndex}
  683. class={[
  684. styles.itemDiv,
  685. activeEle && styles.itemActive,
  686. activeData.isAnimation && styles.acitveAnimation,
  687. isRenderItem ? styles.show : styles.hide
  688. ]}
  689. style={
  690. mIndex < popupData.activeIndex
  691. ? effects[effectIndex.value].prev
  692. : mIndex > popupData.activeIndex
  693. ? effects[effectIndex.value].next
  694. : {}
  695. }
  696. >
  697. {/* {m.type === 'VIDEO' && (
  698. <>
  699. <VideoPlay
  700. ref={(v: any) => (data.videoRefs[mIndex] = v)}
  701. item={m}
  702. isActive={activeEle}
  703. isEmtry={isEmtry}
  704. onPrepare={(val) => {
  705. m.isprepare = val
  706. }}
  707. onLoadedmetadata={(videoItem: any) => {
  708. m.videoEle = videoItem
  709. }}
  710. onTogglePlay={(paused: boolean) => {
  711. // console.log('播放切换', paused)
  712. if (!m.isprepare) {
  713. m.isprepare = true
  714. }
  715. m.autoPlay = false
  716. if (paused || popupData.open || popupData.guideOpen) {
  717. clearTimeout(activeData.timer)
  718. } else {
  719. setModelOpen()
  720. }
  721. }}
  722. onEnded={() => {
  723. const _index = popupData.activeIndex + 1
  724. if (_index < data.itemList.length) {
  725. handleSwipeChange(_index)
  726. }
  727. }}
  728. onReset={() => {
  729. if (!m.videoEle?.paused) {
  730. setModelOpen()
  731. }
  732. }}
  733. />
  734. <Transition name="van-fade">
  735. {!m.isprepare && (
  736. <div class={styles.loadWrap}>
  737. <Vue3Lottie animationData={playLoadData}></Vue3Lottie>
  738. </div>
  739. )}
  740. </Transition>
  741. </>
  742. )} */}
  743. <Transition name="van-fade">
  744. {m.typeCode === "VIDEO" && data.animationState !== "end" && data.videoState != "play" && (
  745. <div class={styles.loadWrap}>
  746. <Vue3Lottie animationData={playLoadData}></Vue3Lottie>
  747. </div>
  748. )}
  749. </Transition>
  750. {isRender && m.typeCode === "IMG" && <img src={m.content} />}
  751. {isRender && m.typeCode === "SONG" && (
  752. <MusicScore
  753. activeModel={activeData.model}
  754. data-vid={m.id}
  755. music={m}
  756. onSetIframe={(el: any) => {
  757. m.iframeRef = el
  758. }}
  759. />
  760. )}
  761. </div>
  762. ) : (
  763. ""
  764. )
  765. })}
  766. </div>
  767. <Transition name="right">
  768. {activeData.model && (
  769. <div
  770. class={styles.rightFixedBtns}
  771. onClick={(e: Event) => {
  772. e.stopPropagation()
  773. clearTimeout(activeData.timer)
  774. }}
  775. >
  776. <div class={styles.btnsWrap}>
  777. <div
  778. class={[styles.fullBtn, styles.point]}
  779. onClick={() =>
  780. openStudyTool({
  781. type: "white",
  782. icon: iconPen,
  783. name: "白板"
  784. })
  785. }
  786. >
  787. <img src={require("./image/bb.png")} />
  788. <span>白板</span>
  789. </div>
  790. </div>
  791. <div class={styles.btnsWrap}>
  792. <div
  793. class={[styles.fullBtn, styles.point]}
  794. onClick={() =>
  795. openStudyTool({
  796. type: "pen",
  797. icon: iconPen,
  798. name: "批注"
  799. })
  800. }
  801. >
  802. <img src={require("./image/pz.png")} />
  803. <span>批注</span>
  804. </div>
  805. </div>
  806. <div class={styles.btnsWrap}>
  807. <div class={[styles.fullBtn, styles.point]} onClick={() => (console.log(popupData.open), (popupData.open = true))}>
  808. <img src={require("./image/zzd.png")} />
  809. <span>知识点</span>
  810. </div>
  811. </div>
  812. <div class={styles.btnsWrap}>
  813. <div
  814. class={[styles.fullBtn, styles.point]}
  815. onClick={() => {
  816. goback()
  817. }}
  818. >
  819. <img src={require("./image/js.png")} />
  820. <span>结束</span>
  821. </div>
  822. </div>
  823. <div class={[styles.btnsWrap, styles.btnsBottom]}>
  824. {/* <div class={styles.fullBtn} onClick={() => (popupData.guideOpen = true)}>
  825. <img src={iconTouping} />
  826. <span>投屏</span>
  827. </div> */}
  828. {data.isCourse && (
  829. <>
  830. <div class={styles.fullBtn} onClick={() => gotoRollCall("student_roll_call")}>
  831. <img src={iconDian} />
  832. <span>点名</span>
  833. </div>
  834. <div class={styles.fullBtn} onClick={() => gotoRollCall("sign_out")}>
  835. <img src={iconPoint} />
  836. <span>签退</span>
  837. </div>
  838. </>
  839. )}
  840. </div>
  841. </div>
  842. )}
  843. </Transition>
  844. <Transition name="left">
  845. {activeData.model && (
  846. <div class={styles.leftFixedBtns} onClick={(e: Event) => e.stopPropagation()}>
  847. {popupData.activeIndex != 0 && (
  848. <div class={[styles.btnsWrap, styles.prePoint]}>
  849. <div
  850. class={styles.fullBtn}
  851. onClick={() => {
  852. // useThrottleFn(() => {
  853. // handlePreAndNext('up')
  854. // }, 300)
  855. // onChangeSwiper('up')
  856. handlePreAndNext("up")
  857. }}
  858. >
  859. <img src={iconUp} />
  860. <span style={{ textAlign: "center" }}>上一个</span>
  861. </div>
  862. </div>
  863. )}
  864. {popupData.activeIndex != data.itemList.length - 1 && (
  865. <div class={styles.btnsWrap}>
  866. <div
  867. class={styles.fullBtn}
  868. onClick={() => {
  869. // console.log('click down')
  870. // useThrottleFn(() => {
  871. // console.log('click down pass')
  872. // handlePreAndNext('down')
  873. // }, 300)
  874. // onChangeSwiper('down')
  875. handlePreAndNext("down")
  876. }}
  877. >
  878. <span style={{ textAlign: "center" }}>下一个</span>
  879. <img src={iconDown} />
  880. </div>
  881. </div>
  882. )}
  883. </div>
  884. )}
  885. </Transition>
  886. </div>
  887. <div
  888. style={{ transform: activeData.model ? "" : "translateY(-100%)" }}
  889. id="coursePlayHeader"
  890. class={styles.headerContainer}
  891. ref={headeRef}
  892. >
  893. <div class={styles.backBtn} onClick={() => goback()}>
  894. <Icon name={iconBack} />
  895. 返回
  896. </div>
  897. {data.isCourse && <PlayRecordTime ref={playRef} list={data.knowledgePointList} />}
  898. <div
  899. class={styles.menu}
  900. onClick={() => {
  901. const _effectIndex = effectIndex.value + 1
  902. effectIndex.value = _effectIndex >= effects.length - 1 ? 0 : _effectIndex
  903. setModelOpen()
  904. }}
  905. >
  906. {popupData.tabName}
  907. </div>
  908. {/* 移除标注 */}
  909. {state.platformType == "TEACHER1" && (
  910. <div
  911. class={styles.headRight}
  912. onClick={(e: Event) => {
  913. e.stopPropagation()
  914. clearTimeout(activeData.timer)
  915. }}
  916. >
  917. <div class={styles.rightBtn} onClick={() => (popupData.guideOpen = true)}>
  918. <img src={iconTouping} />
  919. </div>
  920. <div
  921. class={styles.rightBtn}
  922. onClick={() => {
  923. openStudyTool({
  924. type: "pen",
  925. icon: iconPen,
  926. name: "批注"
  927. })
  928. }}
  929. >
  930. <img src={iconPen} />
  931. </div>
  932. {/* <div class={styles.rightBtn} onClick={() => (popupData.toolOpen = true)}>
  933. <img src={iconMore} />
  934. </div> */}
  935. </div>
  936. )}
  937. </div>
  938. {/* 更多弹窗 */}
  939. <Popup
  940. class={styles.popupMore}
  941. overlayClass={styles.overlayClass}
  942. position="right"
  943. round
  944. v-model:show={popupData.toolOpen}
  945. onClose={handleClosePopup}
  946. >
  947. <Tool onHandleTool={openStudyTool} />
  948. </Popup>
  949. <Popup
  950. class={styles.popup}
  951. style={{ background: "rgba(0,0,0, 0.75)" }}
  952. overlayClass={styles.overlayClass}
  953. position="right"
  954. round
  955. v-model:show={popupData.open}
  956. onClose={handleClosePopup}
  957. >
  958. <Points
  959. data={data.knowledgePointList}
  960. tabActive={popupData.tabActive}
  961. itemActive={popupData.itemActive}
  962. onHandleSelect={(res: any) => {
  963. // onChangeSwiper('change', res.itemActive)
  964. popupData.open = false
  965. toggleMaterial(res.itemActive)
  966. }}
  967. />
  968. </Popup>
  969. <Popup
  970. class={styles.popup}
  971. overlayClass={styles.overlayClass}
  972. position="right"
  973. round
  974. v-model:show={popupData.guideOpen}
  975. onClose={handleClosePopup}
  976. >
  977. {/* <OGuide /> */}
  978. </Popup>
  979. {studyData.penShow && <Pen show={studyData.type === "pen"} close={() => closeStudyTool()} />}
  980. {whiteShow.value && <Pen isWhite={whiteShow.value} show={studyData.type === "white"} close={() => closeStudyTool()} />}
  981. </div>
  982. )
  983. }
  984. })