index.tsx 16 KB

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