index.tsx 13 KB

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