index.tsx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. import request from '@/helpers/request'
  2. import { state } from '@/state'
  3. import {
  4. Button,
  5. Cell,
  6. CellGroup,
  7. Dialog,
  8. Empty,
  9. Grid,
  10. GridItem,
  11. Icon,
  12. Image,
  13. Loading,
  14. Popup,
  15. showConfirmDialog,
  16. showLoadingToast,
  17. showToast,
  18. Skeleton,
  19. SkeletonImage,
  20. Space
  21. } from 'vant'
  22. import {
  23. defineComponent,
  24. onMounted,
  25. reactive,
  26. onUnmounted,
  27. nextTick,
  28. Transition,
  29. TransitionGroup
  30. } from 'vue'
  31. import styles from './index.module.less'
  32. import { useRoute, useRouter } from 'vue-router'
  33. import {
  34. listenerMessage,
  35. postMessage,
  36. promisefiyPostMessage,
  37. removeListenerMessage
  38. } from '@/helpers/native-message'
  39. import iconLook from './image/look.svg'
  40. import iconCachePoint from './image/icon-cache-point.png'
  41. import iconCourse from './image/icon-course.png'
  42. import iconCourseLock from './image/icon-course-lock.png'
  43. import iconTip from './image/iconTip.png'
  44. import { browser } from '@/helpers/utils'
  45. import OEmpty from '@/components/o-empty'
  46. import { handleCheckVip } from '../hook/useFee'
  47. import iconList from './image/icon-list.png'
  48. import OSticky from '@/components/o-sticky'
  49. import OHeader from '@/components/o-header'
  50. import { useEventListener } from '@vant/use'
  51. import OLoading from '@/components/o-loading'
  52. export default defineComponent({
  53. name: 'courseList',
  54. setup() {
  55. const route = useRoute()
  56. const router = useRouter()
  57. const browserInfo = browser()
  58. // const catchList = store
  59. const data = reactive({
  60. titleOpacity: 0,
  61. catchStatus: false,
  62. catchItem: {} as any,
  63. loading: true,
  64. detail: {
  65. cover: '',
  66. name: '',
  67. des: ''
  68. },
  69. list: [] as any,
  70. isDownloading: false // 是否在下载资源
  71. })
  72. /** 获取课件详情 */
  73. const getDetail = async () => {
  74. const res: any = await request.get(
  75. `${state.platformApi}/lessonCourseware/detail/${route.query.id}`
  76. )
  77. if (res?.data) {
  78. data.detail.cover = res.data.coverImg
  79. data.detail.name = res.data.name
  80. data.detail.des = res.data.lessonTargetDesc
  81. }
  82. }
  83. const getList = async () => {
  84. data.loading = true
  85. if (route.query.courseScheduleId) {
  86. try {
  87. const res: any = await request.post(
  88. state.platformApi + '/courseSchedule/getCoursewareDetail',
  89. {
  90. params: {
  91. courseScheduleId: route.query.courseScheduleId,
  92. coursewareId: route.query.id
  93. }
  94. }
  95. )
  96. if (Array.isArray(res?.data)) {
  97. data.list = res.data
  98. }
  99. } catch (error) {}
  100. } else {
  101. try {
  102. const res: any = await request.post(
  103. state.platformApi + '/courseSchedule/myCoursewareDetail/' + route.query.id
  104. )
  105. if (Array.isArray(res?.data)) {
  106. res.data.forEach((item: any) => {
  107. const { knowledgePointList, ...res } = item
  108. const tempK = knowledgePointList || []
  109. tempK.forEach((child: any) => {
  110. child.materialList = [
  111. ...(child.materialList || []),
  112. ...getKnowledgeMaterials(child.children || [])
  113. ]
  114. child.children = null
  115. })
  116. })
  117. const _list = await checkCoursewareCache(res.data)
  118. data.list = browserInfo.isApp
  119. ? res.data.map((item: any) => {
  120. const _item = _list.find(
  121. (n: any) => n.lessonCoursewareDetailId == item.lessonCoursewareDetailId
  122. )
  123. const n = {
  124. ...item
  125. }
  126. if (_item) {
  127. n.hasCache = _item.hasCache
  128. }
  129. return n
  130. })
  131. : res.data
  132. }
  133. } catch (error) {}
  134. }
  135. data.loading = false
  136. }
  137. // 获取子节点数据
  138. const getKnowledgeMaterials = (list: any = []) => {
  139. const tempList: any = []
  140. list.forEach((item: any) => {
  141. if (item.materialList && item.materialList.length > 0) {
  142. tempList.push(...(item.materialList || []))
  143. }
  144. if (item.children && item.children.length > 0) {
  145. tempList.push(...getKnowledgeMaterials(item.children || []))
  146. }
  147. })
  148. return tempList
  149. }
  150. onMounted(() => {
  151. getDetail()
  152. getList()
  153. listenerMessage('downloadCoursewareToCache', getProgress)
  154. })
  155. onUnmounted(() => {
  156. removeListenerMessage('downloadCoursewareToCache', getProgress)
  157. })
  158. const handleClick = async (item: any) => {
  159. if (!item.knowledgePointList) {
  160. showConfirmDialog({
  161. message: '该课件暂无知识点'
  162. })
  163. return
  164. }
  165. if (route.query.code === 'select') {
  166. console.log('选择课时')
  167. setCoursewareDetail(item)
  168. return
  169. }
  170. // 有正在上传中的
  171. if (item.downloadStatus === 1) return
  172. if (!item.hasCache) {
  173. const hasFree = String(item.accessScope) === '0'
  174. if (!hasFree) {
  175. const hasVip = handleCheckVip()
  176. if (!hasVip) return
  177. }
  178. // 下载中不提示
  179. if (item.downloadStatus == 1) {
  180. // 取消下载
  181. postMessage({ api: 'cancelDownloadCourseware' })
  182. setTimeout(() => {
  183. postMessage({ api: 'cancelDownloadCourseware' })
  184. item.downloadStatus = 0
  185. data.isDownloading = false
  186. }, 1000)
  187. showLoadingToast({
  188. message: '取消中...',
  189. forbidClick: false,
  190. loadingType: 'spinner',
  191. duration: 1000
  192. })
  193. return
  194. }
  195. // 重新下载
  196. if (item.downloadStatus == 3) {
  197. downCatch(item)
  198. return
  199. }
  200. data.catchStatus = true
  201. data.catchItem = item
  202. // 下载中不提示
  203. // if (item.downloadStatus == 1) {
  204. // return
  205. // }
  206. // // 重新下载
  207. // if (item.downloadStatus == 3) {
  208. // downCatch(item)
  209. // return
  210. // }
  211. // data.catchStatus = true
  212. // data.catchItem = item
  213. // try {
  214. // await showConfirmDialog({
  215. // message: '当前课程没有缓存,是否缓存?',
  216. // })
  217. // } catch (error) {
  218. // gotoPlay(item)
  219. // return
  220. // }
  221. // downCatch(item)
  222. return
  223. }
  224. gotoPlay(item)
  225. }
  226. // 去课件播放
  227. const gotoPlay = (item: any) => {
  228. data.catchStatus = false
  229. postMessage({
  230. api: 'openWebView',
  231. content: {
  232. url: `${location.origin}${location.pathname}#/coursewarePlay?id=${item.lessonCoursewareDetailId}&source=my-course`,
  233. orientation: 0,
  234. isHideTitle: true,
  235. statusBarTextColor: false,
  236. isOpenLight: true,
  237. showLoadingAnim: true
  238. }
  239. })
  240. }
  241. // 检查数据的缓存状态
  242. const checkCoursewareCache = (list: []): Promise<any[]> => {
  243. if (!browser().isApp) {
  244. return Promise.resolve(list)
  245. }
  246. return new Promise((resolve) => {
  247. postMessage(
  248. {
  249. api: 'checkCoursewareCache',
  250. content: {
  251. data: list
  252. }
  253. },
  254. (res) => {
  255. if (res?.content?.data) {
  256. resolve(res.content.data)
  257. return
  258. }
  259. return []
  260. }
  261. )
  262. })
  263. }
  264. // 下载缓存
  265. const downCatch = async (item: any) => {
  266. if (browserInfo.isApp) {
  267. data.catchStatus = false
  268. data.isDownloading = true
  269. const res = await postMessage({
  270. api: 'downloadCoursewareToCache',
  271. content: {
  272. data: item
  273. }
  274. })
  275. return res
  276. }
  277. return true
  278. }
  279. // 下载缓存进度
  280. const getProgress = (res: any) => {
  281. // console.log('🚀 ~ res', res)
  282. if (!data.isDownloading) {
  283. return
  284. }
  285. if (res?.content?.lessonCoursewareDetailId) {
  286. const { lessonCoursewareDetailId, downloadStatus, progress } = res.content
  287. const course = data.list.find(
  288. (n: any) => n.lessonCoursewareDetailId == lessonCoursewareDetailId
  289. )
  290. if (course) {
  291. course.downloadStatus = downloadStatus
  292. course.progress = progress
  293. if (downloadStatus == 2) {
  294. course.hasCache = 1
  295. course.progress = 100
  296. // 下载完成
  297. data.isDownloading = false
  298. }
  299. }
  300. }
  301. }
  302. // 绑定课时
  303. const setCoursewareDetail = async (item: any) => {
  304. try {
  305. const res: any = await request.post(
  306. state.platformApi + '/courseSchedule/setCoursewareDetail',
  307. {
  308. params: {
  309. courseScheduleId: route.query.courseScheduleId,
  310. coursewareDetailId: item.lessonCoursewareDetailId
  311. }
  312. }
  313. )
  314. if (res.code === 200) {
  315. postMessage({ api: 'back' })
  316. }
  317. } catch (error) {}
  318. }
  319. useEventListener('scroll', (e: Event) => {
  320. const height = window.scrollY || window.pageYOffset || document.documentElement.scrollTop
  321. data.titleOpacity = height > 100 ? 1 : height / 100
  322. })
  323. return () => (
  324. <div class={styles.courseList}>
  325. <OHeader
  326. border={false}
  327. background={`rgba(255,255,255, ${data.titleOpacity})`}
  328. color="rgba(124, 61, 18, 1)"
  329. title="教材详情"
  330. />
  331. <div class={styles.periodContent}>
  332. <div class={styles.cover}>
  333. <img
  334. src={data.detail.cover}
  335. onLoad={(e: Event) => {
  336. if (e.target) {
  337. ;(e.target as any).style.opacity = 1
  338. }
  339. }}
  340. />
  341. </div>
  342. <div>
  343. <div class={styles.contentTitle}>{data.detail.name}</div>
  344. <div class={styles.contentLabel}>教学目标:{data.detail.des}</div>
  345. </div>
  346. </div>
  347. <TransitionGroup name="van-fade">
  348. {!data.loading && (
  349. <>
  350. <div key="periodTitle" class={styles.periodTitle}>
  351. <img class={styles.pIcon} src={iconList} />
  352. <div class={styles.pTitle}>课程列表</div>
  353. <div class={styles.pNum}>共{data.list.length}课</div>
  354. </div>
  355. <div key="list" class={styles.periodList}>
  356. <CellGroup inset>
  357. {data.list.map((item: any) => {
  358. const isLock =
  359. item.lockFlag ||
  360. ((route.query.code == 'select' || state.platformType == 'STUDENT') &&
  361. !item.unlock)
  362. const isSelect = route.query.code === 'select'
  363. return (
  364. <Cell
  365. border
  366. center
  367. title={item.coursewareDetailName}
  368. label={!browserInfo.isStudent ? `已使用${item.useNum || 0}次` : ''}
  369. onClick={() => !isLock && handleClick(item)}
  370. >
  371. {{
  372. icon: () => (
  373. <div class={styles.periodItem}>
  374. <div class={styles.periodItemModel}>
  375. <img src={isLock ? iconCourseLock : iconCourse} />
  376. {!isLock && String(item.accessScope) === '0' && (
  377. <img class={styles.periodTip} src={iconTip} />
  378. )}
  379. {item.hasCache ? (
  380. <img class={styles.iconCachePoint} src={iconCachePoint} />
  381. ) : (
  382. ''
  383. )}
  384. {item.downloadStatus === 1 && (
  385. <div class={styles.downloading}>{`${item.progress || 0}%`}</div>
  386. )}
  387. </div>
  388. </div>
  389. ),
  390. value: () => (
  391. <>
  392. {isSelect ? (
  393. <Button
  394. disabled={isLock}
  395. class={[styles.baseBtn, isLock ? styles.disable : styles.look]}
  396. >
  397. 选择
  398. </Button>
  399. ) : item.knowledgePointList ? (
  400. <>
  401. {item.hasCache ? (
  402. <Button
  403. class={[
  404. styles.baseBtn,
  405. isLock ? styles.disable : styles.look
  406. ]}
  407. >
  408. 查看
  409. </Button>
  410. ) : (
  411. <Button
  412. disabled={item.downloadStatus === 1 ? true : false}
  413. class={[
  414. styles.baseBtn,
  415. isLock ? styles.disable : styles.down,
  416. item.downloadStatus ? styles.downing : ''
  417. ]}
  418. >
  419. {item.downloadStatus === 1 ? `取消下载` : '查看'}
  420. </Button>
  421. )}
  422. </>
  423. ) : (
  424. ''
  425. )}
  426. </>
  427. )
  428. }}
  429. </Cell>
  430. )
  431. })}
  432. </CellGroup>
  433. </div>
  434. </>
  435. )}
  436. </TransitionGroup>
  437. {data.loading && <OLoading />}
  438. {!data.loading && !data.list.length && <OEmpty tips="暂无内容" />}
  439. <Popup v-model:show={data.catchStatus} round class={styles.courseDialog}>
  440. <i class={styles.iconClose} onClick={() => (data.catchStatus = false)}></i>
  441. <div class={styles.title}>下载提醒</div>
  442. <div class={styles.content}>
  443. 您尚未下载课件内容,为了更加流畅的学习体验,推荐您下载后观看课件。
  444. </div>
  445. <div class={styles.popupBtnGroup}>
  446. <Button round onClick={() => gotoPlay(data.catchItem)}>
  447. 直接观看
  448. </Button>
  449. <Button round type="primary" onClick={() => downCatch(data.catchItem)}>
  450. 下载课件
  451. </Button>
  452. </div>
  453. </Popup>
  454. </div>
  455. )
  456. }
  457. })