index.tsx 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720
  1. import {
  2. computed,
  3. defineComponent,
  4. onMounted,
  5. reactive,
  6. ref,
  7. toRef,
  8. TransitionGroup,
  9. watch
  10. } from 'vue';
  11. import styles from './index.module.less';
  12. import iconChange from '../images/icon-change.png';
  13. import iconDownload from '../images/icon-download.png';
  14. import iconStaff from '../images/icon-staff.png';
  15. import staff from '../images/staff/staff.png'
  16. import staffActive from '../images/staff/staff-active.png'
  17. import first from '../images/staff/first.png'
  18. import firstActive from '../images/staff/first-active.png'
  19. import fixed from '../images/staff/fixed.png'
  20. import fixedActive from '../images/staff/fixed-active.png'
  21. import { Button, NoticeBar, Popover, showLoadingToast, showToast } from 'vant';
  22. import { state } from '@/state';
  23. import {
  24. getInstrumentName,
  25. sortMusical,
  26. vaildMusicScoreUrl
  27. } from '@/helpers/utils';
  28. import { storage } from '@/helpers/storage';
  29. import { ACCESS_TOKEN } from '@/store/mutation-types';
  30. import { promisefiyPostMessage } from '@/helpers/native-message';
  31. import html2canvas from 'html2canvas';
  32. import { addWatermark, convasToImg } from '@/views/co-ai/imageFunction';
  33. export default defineComponent({
  34. name: 'music-detail',
  35. props: {
  36. item: {
  37. type: Object,
  38. default: () => ({})
  39. }
  40. },
  41. emits: ['handleGoto'],
  42. setup(props, { emit }) {
  43. const item = toRef(props.item);
  44. const data = reactive({
  45. musicPdfUrl: '',
  46. iframeSrc: '',
  47. selectMusicInstrumentIndex: 0,
  48. popoverShow: false,
  49. showChangeVoice: false,
  50. /** 显示哪种曲谱 */
  51. showMusicImg: 'staff' as 'staff' | 'first' | 'fixed',
  52. trackList: [] as any, // 可筛选的分轨信息
  53. showTransBtn: true // 是否显示转谱按钮
  54. });
  55. const downRef = ref();
  56. watch(
  57. () => props.item,
  58. () => {
  59. item.value = props.item;
  60. data.musicPdfUrl = ''
  61. data.iframeSrc = ''
  62. console.log(props.item, 'item')
  63. __init();
  64. }
  65. );
  66. const _actions = computed(() => {
  67. const details = item.value;
  68. let { scoreType, isConvertibleScore } = details || {};
  69. let action: any[] = [
  70. {
  71. value: 'first',
  72. text: '首调',
  73. icon: first
  74. },
  75. {
  76. value: 'fixed',
  77. text: '固定调',
  78. icon: fixed
  79. }
  80. ];
  81. if (
  82. !(
  83. ['JIAN', 'FIRST'].includes(scoreType) && isConvertibleScore === false
  84. ) &&
  85. !(isConvertibleScore === undefined || isConvertibleScore === null)
  86. ) {
  87. action.unshift({
  88. value: 'staff',
  89. text: '五线谱',
  90. icon: staff
  91. });
  92. }
  93. action.forEach((item: any) => {
  94. if(item.value === data.showMusicImg) {
  95. if(item.value === 'first') {
  96. item.icon = firstActive
  97. } else if(item.value == 'fixed') {
  98. item.icon = fixedActive
  99. } else if(item.value === 'staff') {
  100. item.icon = staffActive
  101. }
  102. }
  103. })
  104. return action.map((item, index) => {
  105. return {
  106. ...item,
  107. color:
  108. data.showMusicImg === item.value ? 'var(--van-primary-color)' : '',
  109. className: data.showMusicImg === item.value ? 'fontBlod' : ''
  110. };
  111. });
  112. });
  113. const trackList = computed(() => {
  114. return data.trackList.map((item: any) => {
  115. return {
  116. ...item,
  117. color:
  118. data.selectMusicInstrumentIndex === item.value ? 'var(--van-primary-color)' : '',
  119. className: data.selectMusicInstrumentIndex === item.value ? 'fontBlod' : ''
  120. };
  121. });
  122. })
  123. /** 保存图片 */
  124. const handleSave = async () => {
  125. if (data.musicPdfUrl) {
  126. const songName = item.value?.musicSheetName;
  127. promisefiyPostMessage({
  128. api: 'downloadFile',
  129. content: {
  130. downloadUrl: data.musicPdfUrl,
  131. fileName: songName
  132. }
  133. });
  134. return;
  135. }
  136. showLoadingToast({ message: '正在保存', duration: 0 });
  137. try {
  138. html2canvas(downRef.value, {
  139. backgroundColor: '#fff',
  140. allowTaint: true,
  141. useCORS: true
  142. })
  143. .then(async canvas => {
  144. // 添加水印
  145. const waterCanvasImg = await addWatermark(canvas);
  146. // canvas转图片
  147. const dataURL = await convasToImg(waterCanvasImg);
  148. console.log(dataURL, 'dataURL');
  149. setTimeout(() => {
  150. showToast('已保存到相册');
  151. }, 500);
  152. await promisefiyPostMessage({
  153. api: 'savePicture',
  154. content: {
  155. base64: dataURL
  156. }
  157. });
  158. })
  159. .catch(() => {
  160. setTimeout(() => {
  161. showToast('保存失败');
  162. }, 500);
  163. });
  164. } catch (error) {
  165. setTimeout(() => {
  166. showToast('保存失败');
  167. }, 500);
  168. }
  169. };
  170. // 根据musicSheetType返回的值,判断是否显示切换声轨按钮
  171. const isEnsemble = computed(() => {
  172. if (item.value) {
  173. const musicSheetType = item.value?.musicSheetType;
  174. if (musicSheetType === 'SINGLE') {
  175. return false;
  176. } else {
  177. return true;
  178. }
  179. } else {
  180. return false;
  181. }
  182. });
  183. // 判断 值当前有没有图片
  184. const isMusicImg = computed(() => {
  185. const musicsData = item.value;
  186. if (data.showMusicImg === 'first' && musicsData?.musicFirstImg) {
  187. return true;
  188. }
  189. if (data.showMusicImg === 'fixed' && musicsData?.musicJianImg) {
  190. return true;
  191. }
  192. if (musicsData?.musicImg) {
  193. return true;
  194. }
  195. return false;
  196. });
  197. // 判断是否可转谱 - 为空也可以转谱
  198. const checkConverTible = (isConvertibleScore: any, scoreType: string) => {
  199. if (
  200. isConvertibleScore ||
  201. isConvertibleScore === '' ||
  202. isConvertibleScore === undefined ||
  203. isConvertibleScore === null ||
  204. (['JIAN', 'FIRST'].includes(scoreType) && !isConvertibleScore)
  205. ) {
  206. return true;
  207. } else {
  208. return false;
  209. }
  210. };
  211. // 解析xml,获取分轨信息
  212. const analyzeXml = async () => {
  213. const details = item.value;
  214. // console.log(details?.musicSheetType, 'details?.musicSheetType');
  215. if (details?.musicSheetType === 'CONCERT') {
  216. if (details.xmlFileUrl) {
  217. const res = await fetch(details.xmlFileUrl).then(response =>
  218. response.text()
  219. );
  220. filterTracks(res);
  221. }
  222. } else {
  223. // showMusicImg: 'first' as 'staff' | 'first' | 'fixed',
  224. const { scoreType, isConvertibleScore } = details || {};
  225. let musicImgType: 'staff' | 'first' | 'fixed' = 'first';
  226. musicImgType =
  227. scoreType === 'STAVE'
  228. ? 'staff'
  229. : scoreType === 'JIAN'
  230. ? 'fixed'
  231. : scoreType === 'FIRST'
  232. ? 'first'
  233. : 'first';
  234. data.showMusicImg = musicImgType;
  235. data.showTransBtn = checkConverTible(isConvertibleScore, scoreType);
  236. }
  237. };
  238. /** 获取分轨名称 */
  239. const getInstrumentNameCode = (instruments: any, name = '') => {
  240. name = name.toLocaleLowerCase().replace(/ /g, '').replace(/\d*/gi, '');
  241. if (!name) return '';
  242. for (let key of instruments) {
  243. const _key = key.toLocaleLowerCase().replace(/ /g, '');
  244. // if (_key.includes(name)) {
  245. // return key
  246. // }
  247. if (_key === name) {
  248. return _key;
  249. }
  250. }
  251. // for (let key of instruments) {
  252. // const _key = key.toLocaleLowerCase().replace(/ /g, '')
  253. // if (name.includes(_key)) {
  254. // return key
  255. // }
  256. // }
  257. return '';
  258. };
  259. // 通过乐器编码返回乐器编号
  260. const instrumentCodeToInstrumentId = (
  261. subjectList: Array<any>,
  262. code: string
  263. ) => {
  264. const codeIdMap = new Map<string, []>() as any;
  265. const codeMapKeys: string[] = [];
  266. subjectList.forEach((data: any) => {
  267. if (data.enableFlag) {
  268. const codes = data.code?.split(/[,,]/);
  269. codes.forEach((code: string) => {
  270. let codeTemp = code?.replace(/ /g, '').toLowerCase();
  271. codeMapKeys.push(codeTemp);
  272. if (codeIdMap.has(codeTemp)) {
  273. codeIdMap.get(codeTemp).push(data.id + '');
  274. } else {
  275. const arr = [] as any;
  276. arr.push(data.id + '');
  277. codeIdMap.set(codeTemp, arr);
  278. }
  279. });
  280. }
  281. });
  282. if (!code) {
  283. return '';
  284. }
  285. code = code && code?.replace(/ /g, '').toLowerCase();
  286. const tempCode = getInstrumentNameCode(codeMapKeys, code);
  287. if (codeIdMap.has(tempCode)) {
  288. const result = codeIdMap.get(tempCode);
  289. // console.log('result:', result);
  290. return result[0] || '';
  291. }
  292. return '';
  293. };
  294. // 初始化编号
  295. const initUserDefaultInstrument = () => {
  296. const userInstrumentId = state.user.data.instrumentId;
  297. const item = data.trackList.find(
  298. (track: any) => track.instrumentId === userInstrumentId + ''
  299. );
  300. data.selectMusicInstrumentIndex = item ? item.value : 0;
  301. };
  302. // 过滤出能切换的分轨
  303. const filterTracks = (xml: any) => {
  304. const xmlParse = new DOMParser().parseFromString(xml, 'text/xml');
  305. const partList: any =
  306. xmlParse
  307. .getElementsByTagName('part-list')?.[0]
  308. ?.getElementsByTagName('score-part') || [];
  309. const partListNames = Array.from(partList).map(
  310. (item: any) =>
  311. item.getElementsByTagName('part-name')?.[0]?.textContent?.trim() ||
  312. item.getAttribute('id') ||
  313. ''
  314. );
  315. const parts: any = xmlParse.getElementsByTagName('part');
  316. /** 第一分谱如果是约定的配置分谱则跳过 */
  317. if (partListNames[0]?.toLocaleUpperCase?.() === 'COMMON') {
  318. partListNames.shift();
  319. }
  320. // 根据后台已选择的分轨筛选出能切换的声轨
  321. const multiTracksSelection = item.value?.multiTracksSelection;
  322. const canSelectTracks = multiTracksSelection
  323. ? multiTracksSelection?.split(',')
  324. : [];
  325. const musicalInstruments = item.value?.musicalInstruments || [];
  326. const arr = partListNames
  327. .map((item: any, index: number) => {
  328. // 该声轨能否被选
  329. const canselect =
  330. canSelectTracks.length == 0 || canSelectTracks.includes(item)
  331. ? true
  332. : false;
  333. const instrumentName = getInstrumentName(item);
  334. const instrumentId = instrumentCodeToInstrumentId(
  335. musicalInstruments,
  336. item
  337. );
  338. const sortId = sortMusical(instrumentName, index);
  339. return {
  340. text: item + (instrumentName ? `(${instrumentName})` : ''),
  341. value: index,
  342. instrumentId,
  343. sortId,
  344. canselect,
  345. track: item
  346. };
  347. })
  348. .filter((item: any) => item.canselect);
  349. //.sort((a: any, b: any) => a.sortId - b.sortId);
  350. data.trackList = arr;
  351. // 是否显示总谱
  352. const selectMusic = item.value;
  353. if (selectMusic) {
  354. const musicalInstruments = selectMusic.musicalInstruments || [];
  355. if (selectMusic.isScoreRender) {
  356. data.trackList.unshift({
  357. text: '总谱',
  358. value: 999,
  359. sortId: 0,
  360. canselect: true,
  361. track: 999
  362. });
  363. if (selectMusic.defaultScoreRender) {
  364. data.selectMusicInstrumentIndex = 999;
  365. } else {
  366. initUserDefaultInstrument();
  367. }
  368. } else {
  369. initUserDefaultInstrument();
  370. }
  371. }
  372. const details = item.value;
  373. const { scoreType, isConvertibleScore } = details || {};
  374. let musicImgType: 'staff' | 'first' | 'fixed' = 'first';
  375. musicImgType =
  376. scoreType === 'STAVE'
  377. ? 'staff'
  378. : scoreType === 'JIAN'
  379. ? 'fixed'
  380. : scoreType === 'FIRST'
  381. ? 'first'
  382. : 'first';
  383. data.showMusicImg = musicImgType;
  384. data.showTransBtn = checkConverTible(isConvertibleScore, scoreType);
  385. };
  386. const musicIframeLoad = () => {
  387. const token = storage.get(ACCESS_TOKEN);
  388. const details = item.value;
  389. if (!details?.id) {
  390. data.iframeSrc = '';
  391. return;
  392. }
  393. // 如果在配置里面匹配不到,则默认显示五线谱
  394. const musicRenderType =
  395. data.showMusicImg === 'first'
  396. ? 'firstTone'
  397. : data.showMusicImg === 'fixed'
  398. ? 'fixedTone'
  399. : data.showMusicImg === 'staff'
  400. ? 'staff'
  401. : 'staff';
  402. // pdf
  403. const musicSheetType = details?.musicSheetType;
  404. let musicPdfUrl = '';
  405. if (
  406. musicSheetType === 'SINGLE' ||
  407. data.selectMusicInstrumentIndex === 999
  408. ) {
  409. if (data.showMusicImg === 'first') {
  410. musicPdfUrl = details.firstPdfUrl;
  411. } else if (data.showMusicImg === 'fixed') {
  412. musicPdfUrl = details.jianPdfUrl;
  413. } else {
  414. musicPdfUrl = details.musicPdfUrl;
  415. }
  416. } else {
  417. const trackList = data.trackList || [];
  418. const selectTrack = trackList.find(
  419. (item: any) => item.value === data.selectMusicInstrumentIndex
  420. );
  421. const background = details.background || [];
  422. const selectItem = background.find(
  423. (item: any) =>
  424. item.track === selectTrack?.track && item.audioPlayType === 'PLAY'
  425. );
  426. if (selectItem) {
  427. if (data.showMusicImg === 'first') {
  428. musicPdfUrl = selectItem.firstPdfUrl;
  429. } else if (data.showMusicImg === 'fixed') {
  430. musicPdfUrl = selectItem.jianPdfUrl;
  431. } else {
  432. musicPdfUrl = selectItem.musicPdfUrl;
  433. }
  434. }
  435. }
  436. data.musicPdfUrl = musicPdfUrl;
  437. if (musicPdfUrl) {
  438. // data.iframeSrc = `/pdf/web/viewer.html?file=${encodeURIComponent(data.musicPdfUrl)}&t=${Date.now()}`;
  439. data.iframeSrc = `${location.origin}${
  440. location.pathname
  441. }pdf/web/viewer.html?file=${encodeURIComponent(
  442. data.musicPdfUrl
  443. )}&t=${Date.now()}`;
  444. } else {
  445. // const origin = /(localhost|192)/.test(location.host)
  446. // ? 'https://test.lexiaoya.cn'
  447. // : location.origin;
  448. // data.iframeSrc = `${origin}/instrument/?id=${details.id}&modelType=practise&modeType=json&Authorization=${token}&isPreView=true&part-index=${data.selectMusicInstrumentIndex}&musicRenderType=${musicRenderType}`;
  449. data.iframeSrc = `${vaildMusicScoreUrl()}/instrument/?id=${
  450. details?.id
  451. }&modelType=practise&modeType=json&Authorization=${token}&isPreView=true&part-index=${
  452. data.selectMusicInstrumentIndex
  453. }&musicRenderType=${musicRenderType}&zoom=0.6`;
  454. }
  455. // console.log('地址', data.iframeSrc, isEnsemble.value , data.musicPdfUrl , !isMusicImg.value);
  456. };
  457. const __init = async () => {
  458. await analyzeXml();
  459. musicIframeLoad();
  460. };
  461. /** 去云练习 */
  462. const handleGoto = () => {
  463. emit('handleGoto', item.value, data.showMusicImg, data.selectMusicInstrumentIndex)
  464. };
  465. onMounted(() => {
  466. __init();
  467. });
  468. return () => (
  469. <div class={styles.musicDetail}>
  470. <div class={styles.musicContainer}>
  471. <div class={styles.container}>
  472. <div
  473. class={styles['right-musicName']}
  474. style={{
  475. opacity: !data.musicPdfUrl ? '1' : '0',
  476. height: !data.musicPdfUrl ? 'auto' : '0'
  477. }}>
  478. <NoticeBar
  479. text={item.value?.musicSheetName}
  480. class={styles.noticeBar}
  481. background="none"
  482. />
  483. </div>
  484. {data.iframeSrc &&
  485. (isEnsemble.value || data.musicPdfUrl || !isMusicImg.value) ? (
  486. <div class={styles.iframeSection}>
  487. <>
  488. {item.value?.id ? (
  489. data.musicPdfUrl ? (
  490. <iframe
  491. id="staffIframeRef"
  492. style={{
  493. width: '100%',
  494. paddingTop: '4px'
  495. // opacity: loading.value ? 0 : 1
  496. }}
  497. src={data.iframeSrc}></iframe>
  498. ) : (
  499. <iframe
  500. id="staffIframeRef"
  501. style={{
  502. width: '100%'
  503. // opacity: loading.value ? 0 : 1
  504. }}
  505. src={data.iframeSrc}></iframe>
  506. )
  507. ) : (
  508. ''
  509. )}
  510. </>
  511. </div>
  512. ) : (
  513. <div class={styles.imgSection}>
  514. {data.showMusicImg === 'first' ? (
  515. <>
  516. {item.value?.musicFirstImg
  517. ?.split(',')
  518. .map((item: any, index: number) => {
  519. return (
  520. <img
  521. class={styles.staff}
  522. src={item}
  523. key={item}
  524. crossorigin="anonymous"
  525. />
  526. );
  527. })}
  528. </>
  529. ) : data.showMusicImg === 'fixed' ? (
  530. <>
  531. <TransitionGroup name="van-fade">
  532. {item.value?.musicJianImg
  533. ?.split(',')
  534. .map((item: any, index: number) => {
  535. return (
  536. <img
  537. class={styles.staff}
  538. src={item}
  539. key={item}
  540. crossorigin="anonymous"
  541. />
  542. );
  543. })}
  544. </TransitionGroup>
  545. </>
  546. ) : (
  547. <>
  548. {item.value?.musicImg
  549. ?.split(',')
  550. .map((item: any, index: number) => {
  551. return (
  552. <img
  553. class={styles.staff}
  554. src={item + '?v=' + Date.now()}
  555. key={item}
  556. crossorigin="anonymous"
  557. />
  558. );
  559. })}
  560. </>
  561. )}
  562. </div>
  563. )}
  564. </div>
  565. <div class={styles.container} ref={downRef} style={{
  566. display: 'block',
  567. height: 'auto',
  568. }}>
  569. <div
  570. class={styles['right-musicName']}
  571. style={{
  572. opacity: !data.musicPdfUrl ? '1' : '0',
  573. height: !data.musicPdfUrl ? 'auto' : '0'
  574. }}>
  575. <div class={styles.name}>{item.value?.musicSheetName}</div>
  576. </div>
  577. <div>
  578. {data.showMusicImg === 'first' ? (
  579. <>
  580. {item.value?.musicFirstImg
  581. ?.split(',')
  582. .map((item: any, index: number) => {
  583. return (
  584. <img
  585. class={styles.staff}
  586. src={item}
  587. key={item}
  588. crossorigin="anonymous"
  589. />
  590. );
  591. })}
  592. </>
  593. ) : data.showMusicImg === 'fixed' ? (
  594. <>
  595. <TransitionGroup name="van-fade">
  596. {item.value?.musicJianImg
  597. ?.split(',')
  598. .map((item: any, index: number) => {
  599. return (
  600. <img
  601. class={styles.staff}
  602. src={item}
  603. key={item}
  604. crossorigin="anonymous"
  605. />
  606. );
  607. })}
  608. </TransitionGroup>
  609. </>
  610. ) : (
  611. <>
  612. {item.value?.musicImg
  613. ?.split(',')
  614. .map((item: any, index: number) => {
  615. return (
  616. <img
  617. class={styles.staff}
  618. src={item + '?v=' + Date.now()}
  619. key={item}
  620. crossorigin="anonymous"
  621. />
  622. );
  623. })}
  624. </>
  625. )}
  626. </div>
  627. </div>
  628. </div>
  629. <div class={styles.btnGroup}>
  630. <div class={styles.operation}>
  631. {isEnsemble.value && (
  632. <Popover
  633. v-model:show={data.showChangeVoice}
  634. class={'transferStaff transferStaffSection'}
  635. actions={trackList.value}
  636. placement="top-start"
  637. onSelect={(item: any) => {
  638. // console.log(item, 'item')
  639. data.selectMusicInstrumentIndex = item.value
  640. data.showChangeVoice = false;
  641. musicIframeLoad();
  642. }}>
  643. {{
  644. reference: () => (
  645. <div class={[styles.item, styles.itemPopover]}>
  646. <img src={iconChange} class={styles.icon} />
  647. <span>声部</span>
  648. </div>
  649. )
  650. }}
  651. </Popover>
  652. )}
  653. {data.showTransBtn && (
  654. <Popover
  655. v-model:show={data.popoverShow}
  656. class={'transferStaff'}
  657. actions={_actions.value}
  658. placement="top-start"
  659. onSelect={(item: any) => {
  660. data.showMusicImg = item.value;
  661. data.popoverShow = false;
  662. musicIframeLoad();
  663. }}>
  664. {{
  665. reference: () => (
  666. <div class={[styles.item, styles.itemPopover]}>
  667. <img src={iconStaff} class={styles.icon} />
  668. <span>转谱</span>
  669. </div>
  670. )
  671. }}
  672. </Popover>
  673. )}
  674. {!isEnsemble.value && (isMusicImg.value || data.musicPdfUrl) && (
  675. <div class={styles.item} onClick={handleSave}>
  676. <img src={iconDownload} class={styles.icon} />
  677. <span>下载</span>
  678. </div>
  679. )}
  680. </div>
  681. <Button round class={styles.goBtn} onClick={handleGoto}>
  682. 立即练习
  683. </Button>
  684. </div>
  685. </div>
  686. );
  687. }
  688. });