index.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. import {
  2. TransitionGroup,
  3. defineComponent,
  4. nextTick,
  5. onMounted,
  6. reactive,
  7. ref,
  8. watch
  9. } from 'vue';
  10. import styles from './index.module.less';
  11. import MSearch from '@/components/m-search';
  12. import icon_play from '@/common/images/icon_play.svg';
  13. import {
  14. Empty,
  15. List,
  16. Loading,
  17. NoticeBar,
  18. Popup,
  19. showLoadingToast,
  20. showToast
  21. } from 'vant';
  22. import icon_back from './image/icon_back.svg';
  23. import icon_down from '@/common/images/icon_down.svg';
  24. import icon_jianpu from '@/common/images/icon_jianpu.svg';
  25. import icon_jianpuActive from '@/common/images/icon_jianpuActive.svg';
  26. import icons from '@/common/images/index.json';
  27. import {
  28. listenerMessage,
  29. postMessage,
  30. promisefiyPostMessage
  31. } from '@/helpers/native-message';
  32. import { rows } from './data.json';
  33. import html2canvas from 'html2canvas';
  34. import { api_musicSheetCategoriesPage, api_musicSheetPage } from './api';
  35. import { state } from '@/state';
  36. import MEmpty from '@/components/m-empty';
  37. import Coaiguide from '@/custom-plugins/guide-page/coai-guide'
  38. import { usePageVisibility } from '@vant/use';
  39. import TheVip from '@/components/the-vip';
  40. import request from '@/helpers/request';
  41. export default defineComponent({
  42. name: 'co-ai',
  43. setup() {
  44. const categorForms = reactive({
  45. page: 1,
  46. rows: 999,
  47. subjectId: state.user.data?.subjectId || ''
  48. });
  49. const musicForms = reactive({
  50. page: 1,
  51. rows: 20,
  52. status: 1,
  53. keyword: '', // 关键词
  54. musicSheetCategoriesId: ''
  55. });
  56. const titles = rows;
  57. const data = reactive({
  58. /** 教材Index */
  59. typeIndex: 0,
  60. /** 音乐Index */
  61. musicIndex: 0,
  62. /** 显示简谱 */
  63. isShowJianpu: true,
  64. /** 教材列表 */
  65. types: [] as any[],
  66. /** 音乐列表 */
  67. musics: [] as any[],
  68. loading: true,
  69. finshed: false,
  70. searchNoticeShow: false,
  71. searchNotice: {
  72. left: '',
  73. top: '',
  74. width: '',
  75. height: ''
  76. },
  77. showVip: false,
  78. vipMember: state.user.data?.vipMember
  79. });
  80. const downRef = ref();
  81. const showGuide = ref(false)
  82. // 返回
  83. const goback = () => {
  84. postMessage({ api: 'goBack' });
  85. };
  86. /** 去云教练 */
  87. const handleGoto = () => {
  88. if (!data.vipMember) {
  89. data.showVip = true;
  90. return;
  91. }
  92. let src = `${location.origin}/instrument?id=${
  93. data.musics[data.musicIndex]?.id
  94. }&showGuide=true`;
  95. console.log(src);
  96. postMessage({
  97. api: 'openAccompanyWebView',
  98. content: {
  99. url: src,
  100. orientation: 0,
  101. isHideTitle: true,
  102. statusBarTextColor: false,
  103. isOpenLight: true,
  104. c_orientation: 0 // 0 横屏 1 竖屏
  105. }
  106. });
  107. };
  108. /** 保存图片 */
  109. const handleSave = async () => {
  110. showLoadingToast({ message: '正在保存', duration: 0 });
  111. try {
  112. html2canvas(downRef.value, {
  113. backgroundColor: '#fff',
  114. allowTaint: true,
  115. useCORS: true
  116. })
  117. .then(async canvas => {
  118. var dataURL = canvas.toDataURL('image/png', 1); //可选取多种模式
  119. setTimeout(() => {
  120. showToast('保存成功');
  121. }, 500);
  122. const res = await promisefiyPostMessage({
  123. api: 'savePicture',
  124. content: {
  125. base64: dataURL
  126. }
  127. });
  128. })
  129. .catch(() => {
  130. setTimeout(() => {
  131. showToast('保存失败');
  132. }, 500);
  133. });
  134. } catch (error) {
  135. setTimeout(() => {
  136. showToast('保存失败');
  137. }, 500);
  138. }
  139. };
  140. /** 获取音乐教材列表 */
  141. const getMusicSheetCategories = async () => {
  142. try {
  143. const res = await api_musicSheetCategoriesPage({
  144. ...categorForms
  145. });
  146. if (res.code === 200 && Array.isArray(res?.data?.rows)) {
  147. data.types = res.data.rows;
  148. if (!musicForms.musicSheetCategoriesId && data.types.length > 0) {
  149. musicForms.musicSheetCategoriesId = data.types[0].id;
  150. }
  151. }
  152. } catch (error) {
  153. console.log('🚀 ~ error:', error);
  154. }
  155. };
  156. /** 获取曲谱列表 */
  157. const getMusicList = async () => {
  158. data.loading = true;
  159. try {
  160. const res = await api_musicSheetPage({
  161. ...musicForms
  162. });
  163. if (res.code === 200 && Array.isArray(res?.data?.rows)) {
  164. data.musics = [...data.musics, ...res.data.rows];
  165. data.finshed = !res.data.next;
  166. }
  167. showGuide.value = true
  168. } catch (error) {
  169. console.log('🚀 ~ error:', error);
  170. }
  171. data.loading = false;
  172. };
  173. const handleReset = () => {
  174. musicForms.page = 1;
  175. data.musics = [];
  176. getMusicList();
  177. };
  178. const spinRef = ref();
  179. const handleResh = () => {
  180. if (data.loading || data.finshed) return;
  181. musicForms.page = musicForms.page + 1;
  182. getMusicList();
  183. };
  184. const setSearchBox = () => {
  185. const el = document.querySelector('.searchNotice .van-field__control');
  186. if (el) {
  187. const rect = el.getBoundingClientRect();
  188. data.searchNotice.left = rect.x + 'px';
  189. data.searchNotice.top = rect.y + 'px';
  190. data.searchNotice.width = rect.width + 'px';
  191. data.searchNotice.height = rect.height + 'px';
  192. }
  193. };
  194. onMounted(async () => {
  195. await getMusicSheetCategories();
  196. getMusicList();
  197. const obv = new IntersectionObserver(entries => {
  198. if (entries[0].intersectionRatio > 0) {
  199. handleResh();
  200. }
  201. });
  202. nextTick(() => {
  203. obv.observe(spinRef.value);
  204. });
  205. const getUserInfo = async () => {
  206. const res = await request.get('/edu-app/user/getUserInfo', {
  207. initRequest: true, // 初始化接口
  208. requestType: 'form',
  209. hideLoading: true
  210. });
  211. if (res?.code === 200) {
  212. data.vipMember = res.data.vipMember;
  213. }
  214. }
  215. listenerMessage('webViewOnResume', () => {
  216. console.log('页面显示')
  217. getUserInfo()
  218. data.typeIndex = 0;
  219. data.musicIndex = 0;
  220. handleReset();
  221. });
  222. setSearchBox();
  223. });
  224. return () => (
  225. <div class={styles.container}>
  226. <div class={styles.back} onClick={goback}>
  227. <img src={icon_back} />
  228. </div>
  229. <div class={styles.content}>
  230. <div class={[styles.leftContent]}>
  231. <div class={styles.leftBg2}></div>
  232. <div class={styles.leftBg}></div>
  233. <div class={styles.types}>
  234. {data.types.map((item, index) => {
  235. return (
  236. <div
  237. class={[
  238. styles.type,
  239. musicForms.musicSheetCategoriesId === item.id &&
  240. styles.typeActive
  241. ]}
  242. onClick={() => {
  243. musicForms.musicSheetCategoriesId = item.id;
  244. handleReset();
  245. }}>
  246. <div class={styles.typeImg}>
  247. <img
  248. class={styles.typeIcon}
  249. src={item.coverImg}
  250. onLoad={(e: Event) => {
  251. const el = e.target as HTMLImageElement;
  252. el.setAttribute('loaded', 'true');
  253. }}
  254. />
  255. </div>
  256. </div>
  257. );
  258. })}
  259. </div>
  260. <div class={styles.center}>
  261. <div class={styles.centerSearch}>
  262. <div id="coai-0">
  263. <MSearch
  264. class={["searchNotice", data.searchNoticeShow ? styles.searchNoticeShow : '']}
  265. shape="round"
  266. background="transparent"
  267. clearable={false}
  268. placeholder="请输入关键字"
  269. onFocus={() => (data.searchNoticeShow = false)}
  270. onBlur={(val) => {
  271. musicForms.keyword = val;
  272. requestAnimationFrame(() => {
  273. requestAnimationFrame(() => {
  274. data.searchNoticeShow = true;
  275. });
  276. });
  277. }}
  278. onSearch={val => {
  279. musicForms.keyword = val;
  280. handleReset();
  281. }}
  282. />
  283. </div></div>
  284. <div class={styles.musicContent}>
  285. {data.musics.map((item: any, index: number) => {
  286. return (
  287. <div
  288. class={[
  289. styles.musicItem,
  290. data.musicIndex === index
  291. ? styles.musicActive
  292. : styles.disableNotic
  293. ]}
  294. onClick={() => (data.musicIndex = index)}>
  295. <img
  296. class={styles.musicAvtor}
  297. src={item.titleImg}
  298. onLoad={(e: Event) => {
  299. const el = e.target as HTMLImageElement;
  300. el.setAttribute('loaded', 'true');
  301. }}
  302. />
  303. <div class={styles.musicInfo}>
  304. <div class={styles.musicName}>
  305. <NoticeBar
  306. text={item.musicSheetName}
  307. class={styles.noticeBar}
  308. background="none"
  309. />
  310. </div>
  311. <div class={styles.musicDes}>
  312. <div class={styles.musicFavitor}>{item.usedNum}</div>
  313. <div class={[styles.musicAuthor, 'van-ellipsis']}>
  314. {item.composer || '佚名'}
  315. </div>
  316. </div>
  317. </div>
  318. {/* <img class={[styles.musicIcon]} src={icon_play} /> */}
  319. </div>
  320. );
  321. })}
  322. {!data.finshed && (
  323. <div ref={spinRef} class={styles.loadingWrap}>
  324. <Loading color="#259CFE" />
  325. </div>
  326. )}
  327. {!data.loading && data.musics.length === 0 && (
  328. <div class={styles.empty}>
  329. <MEmpty description="暂无曲谱" />
  330. </div>
  331. )}
  332. </div>
  333. </div>
  334. </div>
  335. <div class={[styles.opacityBg, styles.right]}>
  336. <div ref={downRef}>
  337. <div class={styles['right-musicName']}>
  338. {data.musics[data.musicIndex]?.musicSheetName}
  339. </div>
  340. {data.isShowJianpu ? (
  341. <>
  342. <TransitionGroup name="van-fade">
  343. {data.musics[data.musicIndex]?.musicSvg
  344. ?.split(',')
  345. .map((item: any, index: number) => {
  346. return (
  347. <img
  348. class={styles.staff}
  349. src={item + '?v=' + Date.now()}
  350. key={item}
  351. />
  352. );
  353. })}
  354. </TransitionGroup>
  355. </>
  356. ) : (
  357. <>
  358. <TransitionGroup name="van-fade">
  359. {data.musics[data.musicIndex]?.musicImg
  360. ?.split(',')
  361. .map((item: any, index: number) => {
  362. return (
  363. <img
  364. class={styles.staff}
  365. src={item + '?v=' + Date.now()}
  366. key={item}
  367. crossorigin="anonymous"
  368. />
  369. );
  370. })}
  371. </TransitionGroup>
  372. </>
  373. )}
  374. </div>
  375. <div class={styles.rightBtns}>
  376. <img
  377. id="coai-1"
  378. src={data.isShowJianpu ? icon_jianpuActive : icon_jianpu}
  379. onClick={() => (data.isShowJianpu = !data.isShowJianpu)}
  380. />
  381. <img id="coai-2" src={icon_down} onClick={handleSave} />
  382. <div class={styles.rightBtnsRight} id="coai-3">
  383. <img src={icons.icon_start} onClick={() => handleGoto()} />
  384. </div>
  385. </div>
  386. </div>
  387. </div>
  388. {data.searchNotice.width && data.searchNoticeShow && (
  389. <div class={styles.searchNotice} style={{ ...data.searchNotice }}>
  390. <NoticeBar
  391. text={musicForms.keyword}
  392. color="#333"
  393. background="none"
  394. />
  395. </div>
  396. )}
  397. { showGuide.value&& <Coaiguide ></Coaiguide>}
  398. <Popup
  399. class="popup-custom van-scale"
  400. transition="van-scale"
  401. closeOnClickOverlay={false}
  402. v-model:show={data.showVip}>
  403. <TheVip
  404. onClose={val => {
  405. if (val) {
  406. postMessage({
  407. api: 'openWebView',
  408. content: {
  409. url: `${location.origin}${location.pathname}#/member-center`,
  410. orientation: 1,
  411. }
  412. });
  413. }
  414. data.showVip = false;
  415. }}
  416. />
  417. </Popup>
  418. </div>
  419. );
  420. }
  421. });