index.tsx 16 KB

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