index.tsx 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742
  1. import {
  2. closeToast,
  3. Icon,
  4. Loading,
  5. Popup,
  6. showToast,
  7. Slider,
  8. Swipe,
  9. SwipeInstance,
  10. SwipeItem
  11. } from 'vant'
  12. import {
  13. defineComponent,
  14. onMounted,
  15. reactive,
  16. nextTick,
  17. onUnmounted,
  18. ref,
  19. watch,
  20. Transition,
  21. TransitionGroup
  22. } from 'vue'
  23. import iconBack from './image/back.svg'
  24. import styles from './index.module.less'
  25. import 'plyr/dist/plyr.css'
  26. import request from '@/helpers/request'
  27. import { state } from '@/state'
  28. import { useRoute, useRouter } from 'vue-router'
  29. import { listenerMessage, postMessage, promisefiyPostMessage } from '@/helpers/native-message'
  30. import MusicScore from './component/musicScore'
  31. import iconMenu from './image/icon-menu.svg'
  32. import iconDian from './image/icon-dian.svg'
  33. import iconTouping from './image/icon-touping.svg'
  34. import iconPoint from './image/icon-point.svg'
  35. import iconLoop from './image/icon-loop.svg'
  36. import iconLoopActive from './image/icon-loop-active.svg'
  37. import iconplay from './image/icon-play.svg'
  38. import iconpause from './image/icon-pause.svg'
  39. import iconUp from './image/icon-up.svg'
  40. import iconDown from './image/icon-down.svg'
  41. import Points from './component/points'
  42. import { browser, getSecondRPM } from '@/helpers/utils'
  43. import { Vue3Lottie } from 'vue3-lottie'
  44. import playLoadData from './datas/data.json'
  45. import { usePageVisibility, useRect } from '@vant/use'
  46. import PlayRecordTime from './playRecordTime'
  47. import VideoPlay from './component/video-play'
  48. import {
  49. Pagination,
  50. Navigation,
  51. Virtual,
  52. EffectFade,
  53. EffectFlip,
  54. EffectCreative,
  55. Lazy
  56. } from 'swiper'
  57. import { Swiper, SwiperSlide } from 'swiper/vue'
  58. import 'swiper/less'
  59. import 'swiper/less/effect-fade'
  60. import 'swiper/less/effect-flip'
  61. import 'swiper/less/effect-creative'
  62. import { handleCheckVip } from '../hook/useFee'
  63. import OGuide from '@/components/o-guide'
  64. export default defineComponent({
  65. name: 'CoursewarePlay',
  66. setup() {
  67. const pageVisibility = usePageVisibility()
  68. const isPlay = ref(false)
  69. /** 页面显示和隐藏 */
  70. watch(pageVisibility, (value) => {
  71. const activeItem = data.itemList[popupData.activeIndex]
  72. if (activeItem.type != 'VIDEO') return
  73. if (value == 'hidden') {
  74. isPlay.value = !activeItem.videoEle.paused
  75. togglePlay(activeItem, false)
  76. } else {
  77. // 页面显示,并且
  78. if (isPlay.value) togglePlay(activeItem, true)
  79. }
  80. })
  81. /** 设置播放容器 16:9 */
  82. const parentContainer = reactive({
  83. width: '100vw'
  84. })
  85. const setContainer = () => {
  86. let min = Math.min(screen.width, screen.height)
  87. let max = Math.max(screen.width, screen.height)
  88. let width = min * (16 / 9)
  89. if (width > max) {
  90. parentContainer.width = '100vw'
  91. return
  92. } else {
  93. parentContainer.width = width + 'px'
  94. }
  95. }
  96. const handleInit = (type = 0) => {
  97. //设置容器16:9
  98. setContainer()
  99. // 横屏
  100. postMessage(
  101. {
  102. api: 'setRequestedOrientation',
  103. content: {
  104. orientation: type
  105. }
  106. },
  107. () => {
  108. console.log(234)
  109. }
  110. )
  111. // 头,包括返回箭头
  112. postMessage({
  113. api: 'setTitleBarVisibility',
  114. content: {
  115. status: type
  116. }
  117. })
  118. // 安卓的状态栏
  119. postMessage({
  120. api: 'setStatusBarVisibility',
  121. content: {
  122. isVisibility: type
  123. }
  124. })
  125. // 进入页面设置常量
  126. postMessage({
  127. api: 'keepScreenLongLight',
  128. content: {
  129. isOpenLight: type ? true : false
  130. }
  131. })
  132. }
  133. handleInit()
  134. onUnmounted(() => {
  135. handleInit(1)
  136. window.removeEventListener('message', iframeHandle)
  137. })
  138. const route = useRoute()
  139. const router = useRouter()
  140. const headeRef = ref()
  141. const data = reactive({
  142. detail: null,
  143. knowledgePointList: [] as any,
  144. itemList: [] as any,
  145. showHead: true,
  146. isCourse: false,
  147. isRecordPlay: false,
  148. videoRefs: {}
  149. })
  150. const activeData = reactive({
  151. nowTime: 0,
  152. model: true, // 遮罩
  153. videoBtns: true, // 视频
  154. currentTime: 0,
  155. duration: 0,
  156. timer: null as any,
  157. item: null as any
  158. })
  159. // 获取缓存路径
  160. const getCacheFilePath = async (material: any) => {
  161. const res = await promisefiyPostMessage({
  162. api: 'getCourseFilePath',
  163. content: {
  164. url: material.content,
  165. localPath: '',
  166. materialId: material.materialId,
  167. updateTime: material.updateTime,
  168. type: material.type // SONG VIDEO IMAGE
  169. }
  170. })
  171. // console.log('缓存路径返回', res)
  172. return res
  173. }
  174. // 获取当前课程是否签退
  175. const getCourseSchedule = async () => {
  176. if (!route.query.courseId) return
  177. try {
  178. const res = await request.get(
  179. `${state.platformApi}/courseSchedule/detail/${route.query.courseId}`,
  180. {
  181. hideLoading: true
  182. }
  183. )
  184. if (res?.data) {
  185. data.isCourse =
  186. res.data.status === 'ING' && state.platformType == 'TEACHER' ? true : false
  187. // data.isRecordPlay = Date.now() > dayjs(res.data.startTime).valueOf()
  188. }
  189. } catch (e) {
  190. console.log(e)
  191. }
  192. }
  193. const getItemList = async () => {
  194. const list: any = []
  195. const browserInfo = browser()
  196. for (let i = 0; i < data.knowledgePointList.length; i++) {
  197. const item = data.knowledgePointList[i]
  198. const itemLength = item.materialList.length - 1
  199. for (let j = 0; j < item.materialList.length; j++) {
  200. const material = item.materialList[j]
  201. //请求本地缓存
  202. if (browserInfo.isApp && ['VIDEO', 'IMG'].includes(material.type)) {
  203. const localData = await getCacheFilePath(material)
  204. if (localData?.content?.localPath) {
  205. material.url = material.content
  206. material.content = localData.content.localPath
  207. }
  208. }
  209. list.push({
  210. ...material,
  211. iframeRef: null,
  212. videoEle: null,
  213. tabName: item.name,
  214. isLast: j === itemLength, // 当前知识点
  215. autoPlay: false, //加载完成是否自动播放
  216. isprepare: false, // 视频是否加载完成
  217. isRender: false // 是否渲染了
  218. })
  219. }
  220. }
  221. let _firstIndex = list.findIndex((n: any) => n.materialId == route.query.kId)
  222. _firstIndex = _firstIndex > -1 ? _firstIndex : 0
  223. const item = list[_firstIndex]
  224. item.autoPlay = true
  225. popupData.activeIndex = _firstIndex
  226. popupData.tabName = item.tabName
  227. popupData.tabActive = item.knowledgePointId
  228. popupData.itemActive = item.id
  229. popupData.itemName = item.name
  230. // console.log('🚀 ~ list', list)
  231. nextTick(() => {
  232. data.itemList = list
  233. postMessage({
  234. api: 'courseLoading',
  235. content: {
  236. show: false,
  237. type: 'fullscreen'
  238. }
  239. })
  240. })
  241. }
  242. const getDetail = async () => {
  243. try {
  244. const res: any = await request.get(
  245. state.platformApi + `/lessonCoursewareDetail/detail/${route.query.id}`,
  246. {
  247. hideLoading: true
  248. }
  249. )
  250. if (Array.isArray(res?.data)) {
  251. data.detail = res.data
  252. }
  253. if (Array.isArray(res?.data?.knowledgePointList)) {
  254. let index = 0
  255. data.knowledgePointList = res.data.knowledgePointList.map((n: any) => {
  256. if (Array.isArray(n.materialList)) {
  257. n.materialList = n.materialList.map((item: any) => {
  258. index++
  259. return {
  260. ...item,
  261. materialId: item.id,
  262. id: index + ''
  263. }
  264. })
  265. }
  266. return n
  267. })
  268. getItemList()
  269. }
  270. } catch (error) {}
  271. }
  272. const iframTime = ref()
  273. // ifram事件处理
  274. const iframeHandle = (ev: MessageEvent) => {
  275. if (ev.data?.api === 'headerTogge') {
  276. clearTimeout(iframTime.value)
  277. iframTime.value = setTimeout(() => {
  278. activeData.model = ev.data.show || (ev.data.playState == 'play' ? false : true)
  279. }, 500)
  280. }
  281. }
  282. onMounted(() => {
  283. const hasVip = handleCheckVip()
  284. if (!hasVip) {
  285. nextTick(() => {
  286. postMessage({
  287. api: 'courseLoading',
  288. content: {
  289. show: false,
  290. type: 'fullscreen'
  291. }
  292. })
  293. })
  294. return
  295. }
  296. getDetail()
  297. getCourseSchedule()
  298. window.addEventListener('message', iframeHandle)
  299. })
  300. const playRef = ref()
  301. // 返回
  302. const goback = () => {
  303. try {
  304. playRef.value?.handleOut()
  305. } catch (error) {}
  306. if (route.query.source == 'my-course') {
  307. router.back()
  308. return
  309. }
  310. postMessage({ api: 'goBack' })
  311. }
  312. const popupData = reactive({
  313. open: false,
  314. activeIndex: 0,
  315. tabActive: '',
  316. tabName: '',
  317. itemActive: '',
  318. itemName: '',
  319. guideOpen: false
  320. })
  321. /**停止所有的播放 */
  322. const handleStop = () => {
  323. for (let i = 0; i < data.itemList.length; i++) {
  324. const activeItem = data.itemList[i]
  325. if (popupData.activeIndex !== i) {
  326. if (activeItem.type === 'VIDEO' && activeItem.videoEle) {
  327. activeItem.videoEle.stop()
  328. }
  329. // console.log('🚀 ~ activeItem:', activeItem)
  330. // 停止曲谱的播放
  331. if (activeItem.type === 'SONG') {
  332. activeItem.iframeRef?.contentWindow?.postMessage({ api: 'setPlayState' }, '*')
  333. }
  334. }
  335. }
  336. }
  337. // 切换素材
  338. const toggleMaterial = () => {
  339. const index = data.itemList.findIndex((n: any) => n.id == popupData.itemActive)
  340. if (index > -1) {
  341. handleSwipeChange(index)
  342. }
  343. }
  344. /** 延迟收起模态框 */
  345. const setModelOpen = () => {
  346. clearTimeout(activeData.timer)
  347. closeToast()
  348. activeData.timer = setTimeout(() => {
  349. activeData.model = false
  350. Object.values(data.videoRefs).map((n: any) => n.toggleHideControl(false))
  351. }, 4000)
  352. }
  353. // 去点名,签退
  354. const gotoRollCall = (pageTag: string) => {
  355. postMessage({
  356. api: 'open_app_page',
  357. content: {
  358. action: 'app',
  359. pageTag: pageTag,
  360. url: '',
  361. params: JSON.stringify({ courseId: route.query.courseId })
  362. }
  363. })
  364. }
  365. // 双击
  366. const handleDbClick = (item: any) => {
  367. if (item && item.type === 'VIDEO') {
  368. const videoEle: HTMLVideoElement = item.videoEle
  369. if (videoEle) {
  370. if (videoEle.paused) {
  371. closeToast()
  372. videoEle.play()
  373. } else {
  374. showToast('已暂停')
  375. videoEle.pause()
  376. }
  377. }
  378. }
  379. }
  380. // 切换播放
  381. const togglePlay = (m: any, isPlay: boolean) => {
  382. if (isPlay) {
  383. m.videoEle?.play()
  384. } else {
  385. m.videoEle?.pause()
  386. }
  387. }
  388. const showIndex = ref(-4)
  389. const effectIndex = ref(0)
  390. const effects = [
  391. {
  392. prev: {
  393. transform: 'translate3d(0, 0, -800px) rotateX(180deg)'
  394. },
  395. next: {
  396. transform: 'translate3d(0, 0, -800px) rotateX(-180deg)'
  397. }
  398. },
  399. {
  400. prev: {
  401. transform: 'translate3d(-100%, 0, -800px)'
  402. },
  403. next: {
  404. transform: 'translate3d(100%, 0, -800px)'
  405. }
  406. },
  407. {
  408. prev: {
  409. transform: 'translate3d(-50%, 0, -800px) rotateY(80deg)'
  410. },
  411. next: {
  412. transform: 'translate3d(50%, 0, -800px) rotateY(-80deg)'
  413. }
  414. },
  415. {
  416. prev: {
  417. transform: 'translate3d(-100%, 0, -800px) rotateY(-120deg)'
  418. },
  419. next: {
  420. transform: 'translate3d(100%, 0, -800px) rotateY(120deg)'
  421. }
  422. },
  423. //风车4
  424. {
  425. prev: {
  426. transform: 'translate3d(-50%, 50%, -800px) rotateZ(-14deg)',
  427. opacity: 0
  428. },
  429. next: {
  430. transform: 'translate3d(50%, 50%, -800px) rotateZ(14deg)',
  431. opacity: 0
  432. }
  433. },
  434. //翻页5
  435. {
  436. prev: {
  437. transform: 'translateZ(-800px) rotate3d(0, -1, 0, 90deg)',
  438. opacity: 0
  439. },
  440. next: {
  441. transform: 'translateZ(-800px) rotate3d(0, 1, 0, 90deg)',
  442. opacity: 0
  443. },
  444. current: { transitionDelay: '700ms' }
  445. }
  446. ]
  447. // 轮播切换
  448. const handleSwipeChange = (index: number) => {
  449. if (popupData.activeIndex == index) return
  450. // console.log('轮播切换')
  451. popupData.activeIndex = index
  452. setTimeout(() => {
  453. handleStop()
  454. const item = data.itemList[index]
  455. if (item) {
  456. popupData.tabActive = item.knowledgePointId
  457. popupData.itemActive = item.id
  458. popupData.itemName = item.name
  459. popupData.tabName = item.tabName
  460. if (item.type == 'SONG') {
  461. activeData.model = true
  462. }
  463. if (item.type === 'VIDEO') {
  464. // 自动播放下一个视频
  465. clearTimeout(activeData.timer)
  466. closeToast()
  467. item.autoPlay = true
  468. nextTick(() => {
  469. item.videoEle?.play()
  470. })
  471. }
  472. }
  473. }, 800)
  474. }
  475. // 上一个知识点, 下一个知识点
  476. const handlePreAndNext = (type: string) => {
  477. if (type === 'up') {
  478. handleSwipeChange(popupData.activeIndex - 1)
  479. } else {
  480. handleSwipeChange(popupData.activeIndex + 1)
  481. }
  482. }
  483. return () => (
  484. <div class={styles.playContent}>
  485. <div
  486. onClick={() => {
  487. clearTimeout(activeData.timer)
  488. activeData.model = !activeData.model
  489. Object.values(data.videoRefs).map((n: any) => n.toggleHideControl(activeData.model))
  490. }}
  491. >
  492. <div
  493. class={styles.coursewarePlay}
  494. style={{ width: parentContainer.width }}
  495. onClick={(e: Event) => {
  496. e.stopPropagation()
  497. setModelOpen()
  498. }}
  499. >
  500. <div class={styles.wraps}>
  501. {data.itemList.map((m: any, mIndex: number) => {
  502. const isRender = m.isRender || Math.abs(popupData.activeIndex - mIndex) < 2
  503. if (isRender) {
  504. m.isRender = true
  505. }
  506. return isRender ? (
  507. <div
  508. key={'index' + mIndex}
  509. class={[styles.itemDiv, popupData.activeIndex === mIndex && styles.itemActive]}
  510. style={
  511. mIndex < popupData.activeIndex
  512. ? effects[effectIndex.value].prev
  513. : mIndex > popupData.activeIndex
  514. ? effects[effectIndex.value].next
  515. : effects[effectIndex.value].current
  516. }
  517. onClick={(e: Event) => {
  518. e.stopPropagation()
  519. clearTimeout(activeData.timer)
  520. if (Date.now() - activeData.nowTime < 300) {
  521. handleDbClick(m)
  522. return
  523. }
  524. activeData.nowTime = Date.now()
  525. activeData.timer = setTimeout(() => {
  526. activeData.model = !activeData.model
  527. Object.values(data.videoRefs).map((n: any) =>
  528. n.toggleHideControl(activeData.model)
  529. )
  530. if (activeData.model) {
  531. setModelOpen()
  532. }
  533. }, 300)
  534. }}
  535. >
  536. {m.type === 'VIDEO' ? (
  537. <>
  538. <VideoPlay
  539. ref={(v: any) => (data.videoRefs[mIndex] = v)}
  540. item={m}
  541. onLoadedmetadata={(videoItem: any) => {
  542. m.videoEle = videoItem
  543. }}
  544. onTogglePlay={(paused: boolean) => {
  545. // console.log('播放切换', paused)
  546. // 首次播放完成
  547. if (!m.isprepare) {
  548. m.isprepare = true
  549. }
  550. m.autoPlay = false
  551. if (paused || popupData.open || popupData.guideOpen) {
  552. clearTimeout(activeData.timer)
  553. } else {
  554. setModelOpen()
  555. }
  556. }}
  557. onEnded={() => handleSwipeChange(popupData.activeIndex + 1)}
  558. onReset={() => {
  559. if (!m.videoEle?.paused) {
  560. setModelOpen()
  561. }
  562. }}
  563. />
  564. <Transition name="van-fade">
  565. {!m.isprepare && (
  566. <div class={styles.loadWrap}>
  567. <Vue3Lottie animationData={playLoadData}></Vue3Lottie>
  568. </div>
  569. )}
  570. </Transition>
  571. </>
  572. ) : m.type === 'IMG' ? (
  573. <img src={m.content} />
  574. ) : (
  575. <MusicScore
  576. data-vid={m.id}
  577. music={m}
  578. onSetIframe={(el: any) => {
  579. m.iframeRef = el
  580. }}
  581. />
  582. )}
  583. </div>
  584. ) : null
  585. })}
  586. </div>
  587. <Transition name="right">
  588. {activeData.model && (
  589. <div
  590. class={styles.rightFixedBtns}
  591. onClick={(e: Event) => {
  592. e.stopPropagation()
  593. clearTimeout(activeData.timer)
  594. }}
  595. >
  596. <div class={styles.btnsWrap}>
  597. <div
  598. class={[styles.fullBtn, styles.point]}
  599. onClick={() => (popupData.open = true)}
  600. >
  601. <img src={iconMenu} />
  602. <span>知识点</span>
  603. </div>
  604. </div>
  605. <div class={[styles.btnsWrap, styles.btnsBottom]}>
  606. <div class={styles.fullBtn} onClick={() => (popupData.guideOpen = true)}>
  607. <img src={iconTouping} />
  608. <span>投屏</span>
  609. </div>
  610. {data.isCourse && (
  611. <>
  612. <div
  613. class={styles.fullBtn}
  614. onClick={() => gotoRollCall('student_roll_call')}
  615. >
  616. <img src={iconDian} />
  617. <span>点名</span>
  618. </div>
  619. <div class={styles.fullBtn} onClick={() => gotoRollCall('sign_out')}>
  620. <img src={iconPoint} />
  621. <span>签退</span>
  622. </div>
  623. </>
  624. )}
  625. </div>
  626. </div>
  627. )}
  628. </Transition>
  629. <Transition name="left">
  630. {activeData.model && (
  631. <div class={styles.leftFixedBtns} onClick={(e: Event) => e.stopPropagation()}>
  632. {popupData.activeIndex != 0 && (
  633. <div class={[styles.btnsWrap, styles.prePoint]}>
  634. <div class={styles.fullBtn} onClick={() => handlePreAndNext('up')}>
  635. <img src={iconUp} />
  636. <span style={{ textAlign: 'center' }}>上一个</span>
  637. </div>
  638. </div>
  639. )}
  640. {popupData.activeIndex != data.itemList.length - 1 && (
  641. <div class={styles.btnsWrap}>
  642. <div class={styles.fullBtn} onClick={() => handlePreAndNext('down')}>
  643. <span style={{ textAlign: 'center' }}>下一个</span>
  644. <img src={iconDown} />
  645. </div>
  646. </div>
  647. )}
  648. </div>
  649. )}
  650. </Transition>
  651. </div>
  652. </div>
  653. <div
  654. style={{ transform: activeData.model ? '' : 'translateY(-100%)' }}
  655. id="coursePlayHeader"
  656. class={styles.headerContainer}
  657. ref={headeRef}
  658. >
  659. <div class={styles.backBtn} onClick={() => goback()}>
  660. <Icon name={iconBack} />
  661. 返回
  662. </div>
  663. <div
  664. class={styles.menu}
  665. onClick={() => {
  666. showIndex.value += 1
  667. if (showIndex.value > 0) {
  668. effectIndex.value = effectIndex.value >= effects.length ? 0 : effectIndex.value + 1
  669. }
  670. setModelOpen()
  671. }}
  672. >
  673. {popupData.tabName} {showIndex.value > 0 ? `动画${effectIndex.value}` : ''}
  674. </div>
  675. {data.isCourse && <PlayRecordTime ref={playRef} list={data.itemList} />}
  676. </div>
  677. <Popup
  678. class={styles.popup}
  679. overlayClass={styles.overlayClass}
  680. position="right"
  681. round
  682. v-model:show={popupData.open}
  683. onClose={() => {
  684. const item = data.itemList[popupData.activeIndex]
  685. if (item?.type == 'VIDEO' && !item.videoEle?.paused) {
  686. setModelOpen()
  687. }
  688. }}
  689. >
  690. <Points
  691. data={data.knowledgePointList}
  692. tabActive={popupData.tabActive}
  693. itemActive={popupData.itemActive}
  694. onHandleSelect={(res: any) => {
  695. // console.log(res)
  696. popupData.tabActive = res.tabActive
  697. popupData.itemActive = res.itemActive
  698. popupData.tabName = res.tabName
  699. popupData.open = false
  700. toggleMaterial()
  701. }}
  702. />
  703. </Popup>
  704. <Popup
  705. class={styles.popup}
  706. overlayClass={styles.overlayClass}
  707. position="right"
  708. round
  709. v-model:show={popupData.guideOpen}
  710. onClose={() => {
  711. const item = data.itemList[popupData.activeIndex]
  712. if (item?.type == 'VIDEO' && !item.videoEle?.paused) {
  713. setModelOpen()
  714. }
  715. }}
  716. >
  717. <OGuide />
  718. </Popup>
  719. </div>
  720. )
  721. }
  722. })