index.tsx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538
  1. import {
  2. PropType,
  3. defineComponent,
  4. nextTick,
  5. onMounted,
  6. reactive,
  7. watch,
  8. ref
  9. } from 'vue';
  10. import styles from './index.module.less';
  11. import icon_back from '../../image/icon_back.svg';
  12. import icon_play from '../../image/icon_class.png';
  13. import pre from '../../image/pre.png';
  14. import { useRouter } from 'vue-router';
  15. import { listenerMessage, postMessage } from '@/helpers/native-message';
  16. import { showToast } from 'vant';
  17. import queryString from 'query-string';
  18. import CoursewareDetail from '@/custom-plugins/guide-page/courseware-detail';
  19. import { usePageVisibility } from '@vant/use';
  20. import { setLogin, state } from '@/state';
  21. import TheNoticeBar from '@/components/the-noticeBar';
  22. import {
  23. api_lessonDetailCourseware,
  24. api_classDetailCourseware
  25. } from '../../api';
  26. import SelectCoursewarePop from '@/components/select-courseware-pop';
  27. import SelectCoursewareMember from '@/components/select-courseware-member';
  28. import request from '@/helpers/request';
  29. export default defineComponent({
  30. name: 'the-book',
  31. props: {
  32. bookData: {
  33. type: Object as PropType<any>,
  34. default: () => ({})
  35. },
  36. tab: {
  37. type: String,
  38. default: ''
  39. },
  40. show: {
  41. type: Boolean,
  42. default: false
  43. },
  44. rect: {
  45. type: Object as PropType<DOMRect>,
  46. default: () => ({})
  47. },
  48. subjectId: {
  49. type: [String, Number],
  50. default: ''
  51. }
  52. },
  53. emits: ['close'],
  54. setup(props, { emit }) {
  55. const pageVisibility = usePageVisibility();
  56. const router = useRouter();
  57. console.log(state.user.data.phone);
  58. const lastTimeKey = 'lastTime' + (state?.user?.data?.phone ?? '');
  59. const debounceSkip = ref(false);
  60. const data = reactive({
  61. show: false,
  62. width: 0,
  63. height: 0,
  64. transform: '',
  65. list: [] as any[][],
  66. lastTime: localStorage.getItem(lastTimeKey),
  67. isClick: false,
  68. coursewareList: [] as any
  69. });
  70. const showSelectCourseware = ref(false);
  71. const showMember = ref(false);
  72. const showGuide = ref(false);
  73. const isend = ref(false);
  74. const step = ref(0);
  75. const CoursewareDetailRef = ref();
  76. const handleCreate = (key: string, url: string) => {
  77. return new Promise((resolve, reject) => {
  78. const _s = document.head.querySelector(`script[data-key="${key}"]`);
  79. if (!_s) {
  80. const s = document.createElement('script');
  81. s.setAttribute('data-key', key);
  82. s.src = url;
  83. s.onload = async () => {
  84. console.log(key + ' 加载完成');
  85. resolve(1);
  86. };
  87. document.head.appendChild(s);
  88. }
  89. });
  90. };
  91. const init = async () => {
  92. // await handleCreate('jquery', '/book/jquery.min.1.7.js');
  93. // await handleCreate('turn', '/book/turn.js');
  94. console.log('初始化完成');
  95. };
  96. const isFirest = ref(true);
  97. let book: any = null;
  98. let timer: any = null;
  99. const handleBook = () => {
  100. book = (window as any).$('#flipbook');
  101. const height = document.body.clientHeight * 0.8;
  102. data.height = height;
  103. data.width = height * (210 / 297) * 2;
  104. book.turn({
  105. autoCenter: true,
  106. duration: 1000,
  107. disabled: true,
  108. acceleration: true, // 是否启动硬件加速 如果为触摸设备必须为true
  109. // pages: 11, // 页码总数
  110. elevation: 50, // 转换期间页面的高度
  111. width: data.width, // 宽度 单位 px
  112. height: data.height, // 高度 单位 px
  113. gradients: true // 是否显示翻页阴影效果
  114. // display: 'single', //设置单页还是双页
  115. });
  116. book.bind('start', (event: Event, pageObject: any, corner: any) => {
  117. // console.log(event, 'last', pageObject.next)
  118. // if (isFirest.value) {
  119. // console.log('第一次进来禁用', pageObject)
  120. // isFirest.value = false
  121. // book.turn('disabled', true);
  122. // }
  123. if (corner == 'tl' || corner == 'tr') {
  124. event.preventDefault();
  125. }
  126. if (data.isClick) {
  127. nextTick(() => {
  128. data.isClick = false;
  129. if (corner == 'tl' || corner == 'tr') {
  130. event.preventDefault();
  131. } else {
  132. book.turn('page', pageObject.next);
  133. }
  134. });
  135. }
  136. });
  137. book.bind('turned', (event: Event, page: any, corner: any) => {
  138. // setTimeout(() => {
  139. // }, 1000);
  140. // console.log(page - 1, 'page')
  141. // const index = (page - 1)
  142. // console.log(data.list[index * step.value], data.list)
  143. nextTick(() => {
  144. showGuide.value = true;
  145. });
  146. // if (page + 1 === book.turn('pages')) {
  147. // // noanimateClose()
  148. // handleClose()
  149. // // nextTick(() => {
  150. // // });
  151. // }
  152. });
  153. book.bind('turning', (event: Event, page: any, corner: any) => {
  154. // console.log(page, 'page', book.turn('pages'))
  155. if (page === book.turn('pages')) {
  156. handleClose(false);
  157. // handleClose()
  158. // nextTick(() => {
  159. // });
  160. }
  161. if (page === 1) {
  162. handleClose(false);
  163. }
  164. return;
  165. });
  166. // book.bind('turned', (e: any, page: any) => {
  167. };
  168. const getRect = () => {
  169. const bookWrap = document.querySelector(
  170. '.bookWrap'
  171. ) as unknown as HTMLElement;
  172. if (bookWrap) {
  173. const rect = bookWrap.getBoundingClientRect();
  174. const xScale = props.rect.width / (rect.width / 2);
  175. const yScale = props.rect.height / rect.height;
  176. const left =
  177. (((rect.width / 2) * (xScale - 1)) / 2 +
  178. props.rect.x -
  179. rect.x -
  180. rect.width / 4) /
  181. xScale;
  182. const top =
  183. ((rect.height * (yScale - 1)) / 2 + props.rect.y - rect.y) / yScale;
  184. const transform = `scale3d(${xScale}, ${yScale}, 1) translate(${left}px, ${top}px)`;
  185. bookWrap.style.transform = data.transform = transform;
  186. bookWrap.style.transition = 'transform 0s';
  187. nextTick(() => {
  188. requestAnimationFrame(() => {
  189. requestAnimationFrame(() => {
  190. bookWrap.style.transition = 'transform 1s';
  191. bookWrap.style.transform = '';
  192. data.show = true;
  193. timer = setTimeout(() => {
  194. book.turn('page', 2);
  195. }, 500);
  196. });
  197. });
  198. });
  199. }
  200. };
  201. const handleClose = (gotoOne = true) => {
  202. showGuide.value = false;
  203. // book.turn('disabled', false);
  204. if (isend.value) {
  205. return;
  206. }
  207. isend.value = true;
  208. clearTimeout(timer);
  209. const bookWrap = document.querySelector(
  210. '.bookWrap'
  211. ) as unknown as HTMLElement;
  212. if (gotoOne) {
  213. book.turn('page', 1);
  214. }
  215. if (bookWrap) {
  216. bookWrap.style.transform = data.transform;
  217. }
  218. emit('close');
  219. setTimeout(() => {
  220. bookWrap.style.transition = '';
  221. bookWrap.style.transform = '';
  222. data.show = false;
  223. data.list = [];
  224. isend.value = false;
  225. }, 1000);
  226. };
  227. onMounted(async () => {
  228. await init();
  229. listenerMessage('webViewOnResume', () => {
  230. data.lastTime = localStorage.getItem(lastTimeKey);
  231. });
  232. });
  233. const getList = () => {
  234. if (!props.bookData?.lessonList) return;
  235. step.value = Math.floor((document.body.clientHeight * 0.8 - 40) / 50);
  236. const list = [];
  237. let listItem = [] as any[];
  238. for (let i = 0; i < props.bookData.lessonList.length; i++) {
  239. const item = props.bookData.lessonList[i];
  240. if (listItem.length >= step.value) {
  241. list.push([...listItem]);
  242. listItem = [{ name: item.name }];
  243. } else {
  244. listItem.push({ name: item.name });
  245. }
  246. for (let j = 0; j < item.knowledgeList.length; j++) {
  247. if (listItem.length >= step.value) {
  248. list.push([...listItem]);
  249. listItem = [item.knowledgeList[j]];
  250. } else {
  251. listItem.push(item.knowledgeList[j]);
  252. }
  253. }
  254. }
  255. if (listItem.length) {
  256. list.push(listItem);
  257. }
  258. data.list = list;
  259. // console.log('🚀 ~ data.list:', data.list.length);
  260. // console.log(book.turn.pages, 'book.turn.pages')
  261. // console.log(book.turn('pages'), 'pages')
  262. };
  263. watch(
  264. () => props.show,
  265. () => {
  266. if (props.show) {
  267. getList();
  268. nextTick(() => {
  269. handleBook();
  270. requestAnimationFrame(() => {
  271. getRect();
  272. });
  273. });
  274. }
  275. }
  276. );
  277. watch(
  278. () => pageVisibility.value,
  279. async (val: any) => {
  280. if (val === 'visible') {
  281. const userCash = await request.get('/edu-app/user/getUserInfo', {
  282. initRequest: true // 初始化接口
  283. });
  284. setLogin(userCash.data);
  285. }
  286. }
  287. );
  288. // 检测有几个课件
  289. const checkCourseware = async (item: any) => {
  290. if (item.id) {
  291. if (!item.containMaterial) {
  292. showToast('暂无资源');
  293. return;
  294. }
  295. if (item.coursewareNum) {
  296. // needVipLock
  297. const schoolInfos = state.user.data?.schoolInfos;
  298. const schoolLock =
  299. schoolInfos && schoolInfos.length > 0
  300. ? schoolInfos[0].needVipLock
  301. : true;
  302. if (!state?.user?.data.vipMember && schoolLock) {
  303. showMember.value = true;
  304. return;
  305. } else {
  306. showMember.value = false;
  307. }
  308. try {
  309. const res =
  310. props.tab == 'all' || props.tab == 'favorite'
  311. ? await api_lessonDetailCourseware({
  312. lessonCoursewareKnowledgeDetailId: item.id
  313. })
  314. : await api_classDetailCourseware({
  315. lessonCoursewareKnowledgeDetailId: item.id
  316. });
  317. if (res?.code == 200 && res.data?.length) {
  318. // console.log(res.data)
  319. res.data.forEach((n: any) => {
  320. n.coursewareDetailKnowledgeId =
  321. n.coursewareDetailKnowledgeId || item.id;
  322. n.lessonCoursewareId = item.lessonCoursewareId;
  323. n.lessonCoursewareDetailId = item.lessonCoursewareDetailId;
  324. n.zjName = item.name; // 章节name
  325. });
  326. data.coursewareList = res.data;
  327. // 如果只有一个课件,直接进入该课件
  328. if (res.data.length == 1) {
  329. handleOpenPlay(res.data[0]);
  330. } else {
  331. // 如果有多个课件,需要选择一个课件进入上课页面
  332. showSelectCourseware.value = true;
  333. }
  334. }
  335. } catch {
  336. //
  337. }
  338. }
  339. }
  340. };
  341. const handleOpenPlay = async (item: any) => {
  342. if (item.id) {
  343. if (debounceSkip.value) return;
  344. debounceSkip.value = true;
  345. localStorage.setItem(lastTimeKey, item.id);
  346. const query = queryString.stringify({
  347. id: item.id, // 课件id
  348. lessonCoursewareId: item.lessonCoursewareId,
  349. courseId: props.bookData.id,
  350. lessonCoursewareDetailId: item.lessonCoursewareDetailId,
  351. name: item.zjName,
  352. subjectId: props.subjectId,
  353. tab: props.tab, // 当前切换的是哪个类型
  354. coursewareDetailKnowledgeId: item.coursewareDetailKnowledgeId // 章节id
  355. });
  356. const url =
  357. location.origin + location.pathname + '#/courseware-play?' + query;
  358. console.log('🚀 ~ url:', url);
  359. postMessage({
  360. api: 'openWebView',
  361. content: {
  362. url,
  363. orientation: 0,
  364. isHideTitle: false,
  365. c_orientation: 0 // 0 横屏 1 竖屏
  366. }
  367. });
  368. // router.push({
  369. // path: '/courseware-play',
  370. // query: {
  371. // id: item.id,
  372. // subjectId: props.subjectId,
  373. // lessonCoursewareId: item.lessonCoursewareId,
  374. // courseId: props.bookData.id,
  375. // lessonCoursewareDetailId: item.lessonCoursewareDetailId,
  376. // name: item.zjName,
  377. // tab: props.tab,
  378. // coursewareDetailKnowledgeId: item.coursewareDetailKnowledgeId
  379. // }
  380. // });
  381. debounceSkip.value = false;
  382. }
  383. };
  384. const isStartAnimate = (item: any) => {
  385. // console.log(item) item.name.length > 9 ? true :
  386. return false;
  387. };
  388. const changeShowGuide = (flag: boolean) => {
  389. showGuide.value = flag;
  390. if (flag) {
  391. console.log('changeShowGuide禁用');
  392. // book.turn('disabled', true);
  393. } else {
  394. console.log('changeShowGuide取消禁用');
  395. // book.turn('disabled', false);
  396. }
  397. };
  398. return () => (
  399. <div
  400. class={[styles.book, data.show ? '' : styles.bookHide]}
  401. onClick={() => handleClose()}
  402. onTouchmove={() => {
  403. console.log('sdfds');
  404. data.isClick = true;
  405. }}>
  406. <div class={styles.back}>
  407. <img src={icon_back} />
  408. </div>
  409. <div
  410. class="bookWrap"
  411. style={{ width: data.width + 'px' }}
  412. onClick={(e: Event) => {
  413. e.stopPropagation();
  414. }}>
  415. {!!data.list.length && (
  416. <div id="flipbook" class={[data.show && 'animated']}>
  417. <div class="page">
  418. <img
  419. style="width: 100%; height: 100%; object-fit: cover;"
  420. src={props.bookData.coverImg}
  421. />
  422. </div>
  423. {data.list.map((list: any) => {
  424. return (
  425. <div class="page">
  426. <div class={styles.wrap}>
  427. <div class={styles.wrapItem}>
  428. {list.map((item: any, index: number) => {
  429. return (
  430. <>
  431. <div
  432. class={[styles.item, item.id && styles.des]}
  433. onTouchstart={(e: TouchEvent) => {
  434. e.stopPropagation();
  435. }}
  436. onClick={(e: Event) => {
  437. e.stopPropagation();
  438. checkCourseware(item);
  439. }}
  440. onTouchend={(e: TouchEvent) => {
  441. console.log(e);
  442. }}>
  443. {item.id ? (
  444. <img
  445. id={index == 1 ? 'coursewareDetail-0' : ''}
  446. class={styles.icon}
  447. src={icon_play}
  448. />
  449. ) : null}
  450. <div
  451. class={styles.name}
  452. style={{ lineHeight: '20Px' }}>
  453. {data.lastTime === item.id && (
  454. <img
  455. src={pre}
  456. alt=""
  457. class={styles.preIcon}
  458. />
  459. )}
  460. <div class={styles.nameText}>
  461. {' '}
  462. {item.name}
  463. </div>
  464. {/* <TheNoticeBar text={item.name} isAnimation={isStartAnimate(item)}></TheNoticeBar> */}
  465. </div>
  466. </div>
  467. </>
  468. );
  469. })}
  470. </div>
  471. </div>
  472. </div>
  473. );
  474. })}
  475. {data.list.length % 2 === 1 && (
  476. <div class="page" style={{ pointerEvents: 'none' }}>
  477. <div class={styles.wrap}>
  478. <div class={styles.wrapItem}></div>
  479. </div>
  480. </div>
  481. )}
  482. <div class="page">
  483. <img
  484. style="width: 100%; height: 100%; object-fit: cover;"
  485. src={props.bookData.coverImg}
  486. />
  487. {/* <div class={styles.wrap}>
  488. <div
  489. class={styles.wrapItem}
  490. style={{ backgroundColor: '#fff' }}></div>
  491. </div> */}
  492. </div>
  493. </div>
  494. )}
  495. </div>
  496. {/* {showGuide.value ? <CoursewareDetail onChangeShowGuide={changeShowGuide} ref={CoursewareDetailRef}></CoursewareDetail> : null} */}
  497. {showSelectCourseware.value && (
  498. <SelectCoursewarePop
  499. list={data.coursewareList}
  500. onClose={() => {
  501. showSelectCourseware.value = false;
  502. }}
  503. onSelect={item => handleOpenPlay(item)}></SelectCoursewarePop>
  504. )}
  505. {showMember.value && (
  506. <SelectCoursewareMember
  507. onClose={() => {
  508. showMember.value = false;
  509. }}></SelectCoursewareMember>
  510. )}
  511. </div>
  512. );
  513. }
  514. });