index.tsx 13 KB

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