index.tsx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. import {
  2. TransitionGroup,
  3. computed,
  4. defineComponent,
  5. nextTick,
  6. onMounted,
  7. reactive,
  8. ref,
  9. watch
  10. } from 'vue';
  11. import styles from './index.module.less';
  12. import MSearch from '@/components/m-search';
  13. import icon_play from '@/common/images/icon_play.svg';
  14. import {
  15. Empty,
  16. Icon,
  17. List,
  18. Loading,
  19. NoticeBar,
  20. Popover,
  21. Popup,
  22. showLoadingToast,
  23. showToast
  24. } from 'vant';
  25. import icon_back from './image/icon_back.svg';
  26. import icon_down from '@/common/images/icon_down.svg';
  27. import icon_jianpu from '@/common/images/icon_jianpu.svg';
  28. import icon_jianpuActive from '@/common/images/icon_jianpuActive.svg';
  29. import icons from '@/common/images/index.json';
  30. import {
  31. listenerMessage,
  32. postMessage,
  33. promisefiyPostMessage
  34. } from '@/helpers/native-message';
  35. import html2canvas from 'html2canvas';
  36. import { api_musicSheetCategoriesPage, api_musicSheetPage } from './api';
  37. import { state } from '@/state';
  38. import MEmpty from '@/components/m-empty';
  39. import Coaiguide from '@/custom-plugins/guide-page/coai-guide';
  40. import { usePageVisibility } from '@vant/use';
  41. import TheVip from '@/components/the-vip';
  42. import request from '@/helpers/request';
  43. import { useRoute } from 'vue-router';
  44. export default defineComponent({
  45. name: 'co-ai',
  46. setup() {
  47. const route = useRoute();
  48. const categorForms = reactive({
  49. page: 1,
  50. rows: 999,
  51. subjectId: state.user.data?.subjectId || '',
  52. musicTagIds: route.query.musicTagId ? [route.query.musicTagId] : []
  53. });
  54. const musicForms = reactive({
  55. page: 1,
  56. rows: 20,
  57. status: 1,
  58. keyword: '', // 关键词
  59. musicSheetCategoriesId: route.query.id as any
  60. });
  61. const data = reactive({
  62. musicSheetCategoriesName: route.query.name as any,
  63. /** 教材Index */
  64. typeIndex: 0,
  65. /** 音乐Index */
  66. musicIndex: 0,
  67. /** 显示哪种曲谱 */
  68. showMusicImg: 'first' as 'staff' | 'first' | 'fixed',
  69. popoverShow: false,
  70. popoverMusicShow: false,
  71. /** 教材列表 */
  72. types: [] as any[],
  73. /** 音乐列表 */
  74. musics: [] as any[],
  75. loading: true,
  76. finshed: false,
  77. searchNoticeShow: false,
  78. searchNotice: {
  79. left: '',
  80. top: '',
  81. width: '',
  82. height: ''
  83. },
  84. showVip: false,
  85. vipMember: state.user.data?.vipMember
  86. });
  87. const downRef = ref();
  88. const showGuide = ref(false);
  89. const _actions = computed(() => {
  90. return [
  91. {
  92. value: 'staff',
  93. text: '五线谱'
  94. },
  95. {
  96. value: 'first',
  97. text: '首调'
  98. },
  99. {
  100. value: 'fixed',
  101. text: '固定调'
  102. }
  103. ].map((item, index) => {
  104. return {
  105. ...item,
  106. color:
  107. data.showMusicImg === item.value ? 'var(--van-primary-color)' : '',
  108. className: data.showMusicImg === item.value ? 'fontBlod' : ''
  109. };
  110. });
  111. });
  112. const _types = computed(() => {
  113. return data.types.map((item: any) => {
  114. return {
  115. ...item,
  116. color:
  117. musicForms.musicSheetCategoriesId == item.value
  118. ? 'var(--van-primary-color)'
  119. : '',
  120. className:
  121. musicForms.musicSheetCategoriesId == item.value ? 'fontBlod' : ''
  122. };
  123. });
  124. });
  125. // 返回
  126. const goback = () => {
  127. postMessage({ api: 'goBack' });
  128. };
  129. /** 去云教练 */
  130. const handleGoto = () => {
  131. if (!data.vipMember) {
  132. data.showVip = true;
  133. return;
  134. }
  135. // 默认进页面显示对应的曲谱
  136. let lineType = 'staff';
  137. if (data.showMusicImg === 'first') {
  138. lineType = 'firstTone';
  139. } else if (data.showMusicImg === 'fixed') {
  140. lineType = 'fixedTone';
  141. } else if (data.showMusicImg === 'staff') {
  142. lineType = 'staff';
  143. }
  144. let src = `${location.origin}/instrument?id=${
  145. data.musics[data.musicIndex]?.id
  146. }&musicRenderType=${lineType}&showGuide=true`;
  147. postMessage({
  148. api: 'openAccompanyWebView',
  149. content: {
  150. url: src,
  151. orientation: 0,
  152. isHideTitle: true,
  153. statusBarTextColor: false,
  154. isOpenLight: true,
  155. c_orientation: 0 // 0 横屏 1 竖屏
  156. }
  157. });
  158. };
  159. /** 保存图片 */
  160. const handleSave = async () => {
  161. showLoadingToast({ message: '正在保存', duration: 0 });
  162. try {
  163. html2canvas(downRef.value, {
  164. backgroundColor: '#fff',
  165. allowTaint: true,
  166. useCORS: true
  167. })
  168. .then(async canvas => {
  169. var dataURL = canvas.toDataURL('image/png', 1); //可选取多种模式
  170. setTimeout(() => {
  171. showToast('已保存到相册');
  172. }, 500);
  173. const res = await promisefiyPostMessage({
  174. api: 'savePicture',
  175. content: {
  176. base64: dataURL
  177. }
  178. });
  179. })
  180. .catch(() => {
  181. setTimeout(() => {
  182. showToast('保存失败');
  183. }, 500);
  184. });
  185. } catch (error) {
  186. setTimeout(() => {
  187. showToast('保存失败');
  188. }, 500);
  189. }
  190. };
  191. /** 获取音乐教材列表 */
  192. const getMusicSheetCategories = async () => {
  193. try {
  194. const res = await api_musicSheetCategoriesPage({
  195. ...categorForms
  196. });
  197. if (res.code === 200 && Array.isArray(res?.data?.rows)) {
  198. const temp: any = [];
  199. res.data.rows.forEach((item: any) => {
  200. temp.push({
  201. value: item.id,
  202. text: item.name
  203. });
  204. });
  205. data.types = temp;
  206. }
  207. } catch (error) {
  208. console.log('🚀 ~ error:', error);
  209. }
  210. };
  211. /** 获取曲谱列表 */
  212. const getMusicList = async () => {
  213. data.loading = true;
  214. try {
  215. const res = await api_musicSheetPage({
  216. ...musicForms
  217. });
  218. if (res.code === 200 && Array.isArray(res?.data?.rows)) {
  219. data.musics = [...data.musics, ...res.data.rows];
  220. data.finshed = !res.data.next;
  221. }
  222. showGuide.value = true;
  223. } catch (error) {
  224. console.log('🚀 ~ error:', error);
  225. }
  226. data.loading = false;
  227. };
  228. const handleReset = () => {
  229. musicForms.page = 1;
  230. data.musics = [];
  231. getMusicList();
  232. };
  233. const spinRef = ref();
  234. const handleResh = () => {
  235. if (data.loading || data.finshed) return;
  236. musicForms.page = musicForms.page + 1;
  237. getMusicList();
  238. };
  239. const setSearchBox = () => {
  240. const el = document.querySelector('.searchNotice .van-field__control');
  241. if (el) {
  242. const rect = el.getBoundingClientRect();
  243. data.searchNotice.left = rect.x + 'px';
  244. data.searchNotice.top = rect.y + 'px';
  245. data.searchNotice.width = rect.width + 'px';
  246. data.searchNotice.height = rect.height + 'px';
  247. }
  248. };
  249. onMounted(async () => {
  250. // 安卓的状态栏
  251. postMessage({
  252. api: 'setStatusBarVisibility',
  253. content: {
  254. isVisibility: 0
  255. }
  256. });
  257. await getMusicSheetCategories();
  258. getMusicList();
  259. const obv = new IntersectionObserver(entries => {
  260. if (entries[0].intersectionRatio > 0) {
  261. handleResh();
  262. }
  263. });
  264. nextTick(() => {
  265. obv.observe(spinRef.value);
  266. });
  267. const getUserInfo = async () => {
  268. const res = await request.get('/edu-app/user/getUserInfo', {
  269. initRequest: true, // 初始化接口
  270. requestType: 'form',
  271. hideLoading: true
  272. });
  273. if (res?.code === 200) {
  274. data.vipMember = res.data.vipMember;
  275. }
  276. };
  277. listenerMessage('webViewOnResume', () => {
  278. console.log('页面显示');
  279. getUserInfo();
  280. data.typeIndex = 0;
  281. data.musicIndex = 0;
  282. handleReset();
  283. });
  284. setSearchBox();
  285. });
  286. return () => (
  287. <div class={styles.container}>
  288. <div class={styles.back} onClick={goback}>
  289. <img src={icon_back} />
  290. </div>
  291. <div class={styles.musicCFixed}>
  292. <Popover
  293. v-model:show={data.popoverMusicShow}
  294. class={styles.popoverMusic}
  295. actions={_types.value}
  296. placement="bottom"
  297. showArrow={false}
  298. onSelect={(item: any) => {
  299. // data.showMusicImg = item.value;
  300. if (item.value == musicForms.musicSheetCategoriesId) {
  301. return;
  302. }
  303. data.musics = [];
  304. musicForms.musicSheetCategoriesId = item.value;
  305. data.musicSheetCategoriesName = item.text;
  306. data.popoverMusicShow = false;
  307. getMusicList();
  308. }}>
  309. {{
  310. reference: () => (
  311. <span class={styles.musicName}>
  312. {data.musicSheetCategoriesName}
  313. <Icon name="arrow-down"></Icon>
  314. </span>
  315. )
  316. }}
  317. </Popover>
  318. </div>
  319. <div class={styles.content}>
  320. <div class={[styles.leftContent]}>
  321. <div class={styles.leftBg2}></div>
  322. {/* <div class={styles.leftBg}></div> */}
  323. {/* <div class={styles.types}>
  324. {data.types.map((item, index) => {
  325. return (
  326. <div
  327. class={[
  328. styles.type,
  329. musicForms.musicSheetCategoriesId === item.id &&
  330. styles.typeActive
  331. ]}
  332. onClick={() => {
  333. musicForms.musicSheetCategoriesId = item.id;
  334. handleReset();
  335. }}>
  336. <div class={styles.typeImg}>
  337. <img
  338. class={styles.typeIcon}
  339. src={item.coverImg}
  340. onLoad={(e: Event) => {
  341. const el = e.target as HTMLImageElement;
  342. el.setAttribute('loaded', 'true');
  343. }}
  344. />
  345. </div>
  346. </div>
  347. );
  348. })}
  349. </div> */}
  350. <div class={styles.center}>
  351. <div class={styles.centerSearch}>
  352. <div id="coai-0">
  353. <MSearch
  354. class={[
  355. 'searchNotice',
  356. data.searchNoticeShow ? styles.searchNoticeShow : ''
  357. ]}
  358. shape="round"
  359. background="transparent"
  360. clearable={false}
  361. placeholder="请输入关键字"
  362. modelValue={musicForms.keyword}
  363. onFocus={() => (data.searchNoticeShow = false)}
  364. onBlur={val => {
  365. musicForms.keyword = val?.trim() || '';
  366. requestAnimationFrame(() => {
  367. requestAnimationFrame(() => {
  368. if (musicForms.keyword) {
  369. data.searchNoticeShow = true;
  370. }
  371. });
  372. });
  373. }}
  374. onSearch={val => {
  375. musicForms.keyword = val;
  376. handleReset();
  377. }}
  378. />
  379. </div>
  380. </div>
  381. <div class={styles.musicContent}>
  382. {data.musics.map((item: any, index: number) => {
  383. return (
  384. <div
  385. class={[
  386. styles.musicItem,
  387. data.musicIndex === index
  388. ? styles.musicActive
  389. : styles.disableNotic
  390. ]}
  391. onClick={() => (data.musicIndex = index)}>
  392. <img
  393. class={styles.musicAvtor}
  394. src={item.titleImg}
  395. onLoad={(e: Event) => {
  396. const el = e.target as HTMLImageElement;
  397. el.setAttribute('loaded', 'true');
  398. }}
  399. />
  400. <div class={styles.musicInfo}>
  401. <div class={styles.musicName}>
  402. <NoticeBar
  403. text={item.musicSheetName}
  404. class={styles.noticeBar}
  405. background="none"
  406. />
  407. </div>
  408. <div class={styles.musicDes}>
  409. <div class={styles.musicFavitor}>{item.usedNum}</div>
  410. <div class={[styles.musicAuthor, 'van-ellipsis']}>
  411. {item.composer || '佚名'}
  412. </div>
  413. </div>
  414. </div>
  415. {/* <img class={[styles.musicIcon]} src={icon_play} /> */}
  416. </div>
  417. );
  418. })}
  419. {!data.finshed && (
  420. <div ref={spinRef} class={styles.loadingWrap}>
  421. <Loading color="#259CFE" />
  422. </div>
  423. )}
  424. {!data.loading && data.musics.length === 0 && (
  425. <div class={styles.empty}>
  426. <MEmpty description="暂无曲谱" />
  427. </div>
  428. )}
  429. </div>
  430. </div>
  431. </div>
  432. <div class={[styles.opacityBg, styles.right]}>
  433. <div class={styles.rightBox}>
  434. <div ref={downRef}>
  435. <div class={styles['right-musicName']}>
  436. {data.musics[data.musicIndex]?.musicSheetName}
  437. </div>
  438. {data.showMusicImg === 'first' ? (
  439. <>
  440. {data.musics[data.musicIndex]?.musicSvg
  441. ?.split(',')
  442. .map((item: any, index: number) => {
  443. return (
  444. <img
  445. class={styles.staff}
  446. src={item + '?v=' + Date.now()}
  447. key={item}
  448. crossorigin="anonymous"
  449. />
  450. );
  451. })}
  452. </>
  453. ) : data.showMusicImg === 'fixed' ? (
  454. <>
  455. <TransitionGroup name="van-fade">
  456. {data.musics[data.musicIndex]?.musicJianSvg
  457. ?.split(',')
  458. .map((item: any, index: number) => {
  459. return (
  460. <img
  461. class={styles.staff}
  462. src={item + '?v=' + Date.now()}
  463. key={item}
  464. crossorigin="anonymous"
  465. />
  466. );
  467. })}
  468. </TransitionGroup>
  469. </>
  470. ) : (
  471. <>
  472. {data.musics[data.musicIndex]?.musicImg
  473. ?.split(',')
  474. .map((item: any, index: number) => {
  475. return (
  476. <img
  477. class={styles.staff}
  478. src={item + '?v=' + Date.now()}
  479. key={item}
  480. crossorigin="anonymous"
  481. />
  482. );
  483. })}
  484. </>
  485. )}
  486. </div>
  487. </div>
  488. <div class={styles.rightBtns}>
  489. <Popover
  490. v-model:show={data.popoverShow}
  491. class={styles.popover}
  492. actions={_actions.value}
  493. placement="top-start"
  494. onSelect={(item: any) => {
  495. data.showMusicImg = item.value;
  496. data.popoverShow = false;
  497. }}
  498. // onSelect={onSelect}
  499. >
  500. {{
  501. reference: () => <img id="coai-1" src={icon_jianpuActive} />
  502. }}
  503. </Popover>
  504. <img id="coai-2" src={icon_down} onClick={handleSave} />
  505. <div class={styles.rightBtnsRight} id="coai-3">
  506. <img src={icons.icon_start} onClick={() => handleGoto()} />
  507. </div>
  508. </div>
  509. </div>
  510. </div>
  511. {data.searchNotice.width && data.searchNoticeShow && (
  512. <div class={styles.searchNotice} style={{ ...data.searchNotice }}>
  513. <NoticeBar
  514. text={musicForms.keyword}
  515. color="#333"
  516. background="none"
  517. />
  518. </div>
  519. )}
  520. {showGuide.value && <Coaiguide></Coaiguide>}
  521. <Popup
  522. class="popup-custom van-scale"
  523. transition="van-scale"
  524. closeOnClickOverlay={false}
  525. v-model:show={data.showVip}>
  526. <TheVip
  527. onClose={val => {
  528. if (val) {
  529. postMessage({
  530. api: 'openWebView',
  531. content: {
  532. url: `${location.origin}${location.pathname}#/member-center`,
  533. orientation: 1
  534. }
  535. });
  536. }
  537. data.showVip = false;
  538. }}
  539. />
  540. </Popup>
  541. </div>
  542. );
  543. }
  544. });