index.tsx 13 KB

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