index.tsx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  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. // 如果有多个课件,需要选择一个课件进入上课页面
  277. if (item.coursewareNum && item.coursewareNum > 1) {
  278. try {
  279. const res = props.tab == 'all' ? await api_lessonDetailCourseware({
  280. lessonCoursewareKnowledgeDetailId: item.id,
  281. }): await api_classDetailCourseware({
  282. lessonCoursewareKnowledgeDetailId: item.id,
  283. })
  284. if (res?.code == 200 ) {
  285. console.log(res.data)
  286. data.coursewareList = res.data;
  287. showSelectCourseware.value = true;
  288. }
  289. } catch {
  290. //
  291. }
  292. } else {
  293. handleOpenPlay(item);
  294. }
  295. }
  296. }
  297. const handleOpenPlay = async (item: any) => {
  298. if (item.id) {
  299. localStorage.setItem(lastTimeKey, item.id);
  300. const query = queryString.stringify({
  301. id: item.id, // 课件id
  302. lessonCoursewareId: item.lessonCoursewareId,
  303. courseId: props.bookData.id,
  304. lessonCoursewareDetailId: item.lessonCoursewareDetailId,
  305. name: item.name,
  306. subjectId: props.subjectId,
  307. tab: props.tab, // 当前切换的是哪个类型
  308. coursewareDetailKnowledgeId: item.coursewareDetailKnowledgeId, // 章节id
  309. });
  310. const url =
  311. location.origin + location.pathname + '#/courseware-play?' + query;
  312. console.log('🚀 ~ url:', url);
  313. postMessage({
  314. api: 'openWebView',
  315. content: {
  316. url,
  317. orientation: 0,
  318. isHideTitle: false,
  319. c_orientation: 0 // 0 横屏 1 竖屏
  320. }
  321. });
  322. router.push({
  323. path: '/courseware-play',
  324. query: {
  325. id: item.id,
  326. subjectId: props.subjectId,
  327. lessonCoursewareId: item.lessonCoursewareId,
  328. courseId: props.bookData.id,
  329. lessonCoursewareDetailId: item.lessonCoursewareDetailId,
  330. name: item.name,
  331. tab: props.tab,
  332. coursewareDetailKnowledgeId: item.coursewareDetailKnowledgeId
  333. }
  334. });
  335. }
  336. };
  337. const isStartAnimate = (item: any) => {
  338. // console.log(item) item.name.length > 9 ? true :
  339. return false;
  340. };
  341. const changeShowGuide = (flag: boolean) => {
  342. showGuide.value = flag;
  343. if (flag) {
  344. console.log('changeShowGuide禁用');
  345. // book.turn('disabled', true);
  346. } else {
  347. console.log('changeShowGuide取消禁用');
  348. // book.turn('disabled', false);
  349. }
  350. };
  351. return () => (
  352. <div
  353. class={[styles.book, data.show ? '' : styles.bookHide]}
  354. onClick={() => handleClose()}
  355. onTouchmove={() => {
  356. console.log('sdfds');
  357. data.isClick = true;
  358. }}>
  359. <div class={styles.back}>
  360. <img src={icon_back} />
  361. </div>
  362. <div
  363. class="bookWrap"
  364. style={{ width: data.width + 'px' }}
  365. onClick={(e: Event) => {
  366. e.stopPropagation();
  367. }}>
  368. {!!data.list.length && (
  369. <div id="flipbook" class={[data.show && 'animated']}>
  370. <div class="page">
  371. <img
  372. style="width: 100%; height: 100%; object-fit: cover;"
  373. src={props.bookData.coverImg}
  374. />
  375. </div>
  376. {data.list.map((list: any) => {
  377. return (
  378. <div class="page">
  379. <div class={styles.wrap}>
  380. <div class={styles.wrapItem}>
  381. {list.map((item: any, index: number) => {
  382. return (
  383. <>
  384. <div
  385. class={[styles.item, item.id && styles.des]}
  386. onTouchstart={(e: TouchEvent) => {
  387. e.stopPropagation();
  388. }}
  389. onClick={(e: Event) => {
  390. e.stopPropagation();
  391. checkCourseware(item);
  392. }}
  393. onTouchend={(e: TouchEvent) => {
  394. console.log(e);
  395. }}>
  396. {item.id ? (
  397. <img
  398. id={index == 1 ? 'coursewareDetail-0' : ''}
  399. class={styles.icon}
  400. src={icon_play}
  401. />
  402. ) : null}
  403. <div
  404. class={styles.name}
  405. style={{ lineHeight: '20Px' }}>
  406. {data.lastTime === item.id && (
  407. <img
  408. src={pre}
  409. alt=""
  410. class={styles.preIcon}
  411. />
  412. )}
  413. <div class={styles.nameText}>
  414. {' '}
  415. {item.name}
  416. </div>
  417. {/* <TheNoticeBar text={item.name} isAnimation={isStartAnimate(item)}></TheNoticeBar> */}
  418. </div>
  419. </div>
  420. </>
  421. );
  422. })}
  423. </div>
  424. </div>
  425. </div>
  426. );
  427. })}
  428. {data.list.length % 2 === 1 && (
  429. <div class="page" style={{ pointerEvents: 'none' }}>
  430. <div class={styles.wrap}>
  431. <div class={styles.wrapItem}></div>
  432. </div>
  433. </div>
  434. )}
  435. <div class="page">
  436. <img
  437. style="width: 100%; height: 100%; object-fit: cover;"
  438. src={props.bookData.coverImg}
  439. />
  440. {/* <div class={styles.wrap}>
  441. <div
  442. class={styles.wrapItem}
  443. style={{ backgroundColor: '#fff' }}></div>
  444. </div> */}
  445. </div>
  446. </div>
  447. )}
  448. </div>
  449. {/* {showGuide.value ? <CoursewareDetail onChangeShowGuide={changeShowGuide} ref={CoursewareDetailRef}></CoursewareDetail> : null} */}
  450. {
  451. showSelectCourseware.value &&
  452. <SelectCoursewarePop
  453. list={data.coursewareList}
  454. onClose={() => {
  455. showSelectCourseware.value = false;
  456. }}
  457. onSelect={(item) => handleOpenPlay(item)}
  458. ></SelectCoursewarePop>
  459. }
  460. </div>
  461. );
  462. }
  463. });