new-index.tsx 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273
  1. import TheSticky from '@/components/the-sticky'
  2. import styles from './new-index.module.less'
  3. import { useEventListener, useThrottleFn, useWindowScroll } from '@vueuse/core'
  4. import { postMessage, promisefiyPostMessage } from '@/helpers/native-message'
  5. // import iconShare from '../../images/icon-share.png'
  6. import oStart from '../album-detail/icon-hart.png'
  7. import iStart from '../album-detail/icon-hart-active.png'
  8. import iconDownload from './images/icon-download.png'
  9. import iconMemberSmall from './images/icon-member-small.png'
  10. import {
  11. computed,
  12. defineComponent,
  13. nextTick,
  14. onMounted,
  15. onUnmounted,
  16. reactive,
  17. ref,
  18. toRaw,
  19. watch
  20. } from 'vue'
  21. import umiRequest from 'umi-request'
  22. import { useRoute, useRouter } from 'vue-router'
  23. import request from '@/helpers/request'
  24. import ColHeader from '@/components/col-header'
  25. import {
  26. Button,
  27. Cell,
  28. CellGroup,
  29. Dialog,
  30. Icon,
  31. Image,
  32. Popup,
  33. RadioGroup,
  34. Radio,
  35. Toast,
  36. Picker
  37. } from 'vant'
  38. import { useRect } from '@vant/use'
  39. import { Vue3Lottie } from 'vue3-lottie'
  40. import { getRandomKey, musicBuy } from '../music'
  41. import { getOssUploadUrl, state } from '@/state'
  42. import { browser, moneyFormat } from '@/helpers/utils'
  43. import { orderStatus } from '@/views/order-detail/orderStatus'
  44. import AstronautJSON from './animate/refresh_anim.json'
  45. // import ColShare from '@/components/col-share'
  46. import iconListen from './images/icon_listen.png'
  47. import iconTeacher from '@common/images/icon_teacher.png'
  48. import emtpy from './images/emtpy.png'
  49. import { state as baseState } from '@/state'
  50. import activeButtonIcon from './images/icon_checkbox.png'
  51. import inactiveButtonIcon from './images/icon_checkbox_default.png'
  52. import staffDetafult from './images/staff-default.png'
  53. import firstDefault from './images/first-default.png'
  54. import fixedDefault from './images/fixed-default.png'
  55. import Plyr from 'plyr'
  56. import 'plyr/dist/plyr.css'
  57. import Download from './download'
  58. import { getInstrumentName } from '@/constant/instruments'
  59. // import { getUploadSign, onOnlyFileUpload } from '@/helpers/oss-file-upload'
  60. import { svgtopng } from './formatSvgToImg'
  61. // import { shareCall } from '@/teacher/share-page/share'
  62. import deepClone from '@/helpers/deep-clone'
  63. import { formatXML, getCustomInfo, onlyVisible } from './instrument'
  64. import { data } from 'browserslist'
  65. export default defineComponent({
  66. name: 'new-index',
  67. setup() {
  68. localStorage.setItem('behaviorId', getRandomKey())
  69. const router = useRouter()
  70. const route = useRoute()
  71. const loading = ref(false)
  72. const background = ref<string>('rgba(55, 205, 177, 0)')
  73. const color = ref<string>('#fff')
  74. const aId = Number(route.query.activityId) || 0
  75. const studentActivityId = ref(aId)
  76. const isError = ref(false)
  77. const headers = ref(null)
  78. const footers = ref(null)
  79. const heightInfo = ref<any>('0')
  80. const musicDetail = ref<any>(null)
  81. const audioFileUrl = ref('')
  82. const showImg = ref([] as any)
  83. const firstList = ref<Array<any>>([])
  84. const fixedList = ref<Array<any>>([])
  85. const staffList = ref<Array<any>>([])
  86. const musicPdfUrl = ref('')
  87. const uploadImgs = ref<boolean>(false)
  88. const defaultImgs = ref({
  89. first: false,
  90. fixed: false,
  91. staff: false
  92. })
  93. const downloadStatus = ref<boolean>(false)
  94. const staff = reactive({
  95. status: false,
  96. radio: 'staff' // staff first fixed
  97. })
  98. const apiSuffix = ref(
  99. baseState.platformType === 'STUDENT' ? '/api-student' : '/api-teacher'
  100. )
  101. // 更改预览状态
  102. const onChangeStaff = (type: string) => {
  103. staff.radio = type
  104. staff.status = false
  105. if (type == 'first' && (!defaultImgs.value.first || musicDetail.value?.firstPdfUrl)) {
  106. loading.value = false
  107. const tempPdf = musicDetail.value?.firstPdfUrl
  108. initIframe(tempPdf, 'first', staffData.musicXml)
  109. } else if (type == 'fixed' && (!defaultImgs.value.fixed || musicDetail.value?.jianPdfUrl)) {
  110. loading.value = false
  111. const tempPdf = musicDetail.value?.jianPdfUrl
  112. initIframe(tempPdf, 'first', staffData.musicXml)
  113. } else {
  114. if (!defaultImgs.value.staff) {
  115. loading.value = false
  116. const tempPdf = musicDetail.value?.musicPdfUrl
  117. initIframe(tempPdf, 'first', staffData.musicXml)
  118. }
  119. }
  120. }
  121. watch(
  122. () => staff.radio,
  123. (val: string) => {
  124. if (val == 'first') {
  125. showImg.value = deepClone(firstList.value)
  126. } else if (val == 'fixed') {
  127. showImg.value = deepClone(fixedList.value)
  128. } else {
  129. showImg.value = deepClone(staffList.value)
  130. }
  131. }
  132. )
  133. const FetchList = async (id?: any) => {
  134. if (loading.value) {
  135. return
  136. }
  137. loading.value = true
  138. isError.value = false
  139. try {
  140. const res = await request.get(`/music/sheet/detail/${route.query.id}`, {
  141. prefix:
  142. state.platformType === 'TEACHER' ? '/api-teacher' : '/api-student',
  143. params: {
  144. tenantAlbumId: route.query.tenantAlbumId || null
  145. }
  146. })
  147. musicDetail.value = res.data
  148. // 取原音,如果有多个则默认第一个
  149. const background = res.data.background
  150. audioFileUrl.value =
  151. background && background.length > 0 ? background[0].audioFileUrl : ''
  152. showImg.value = res.data.musicImg ? res.data.musicImg.split(',') : []
  153. firstList.value = res.data.firstTone
  154. ? res.data.firstTone.split(',')
  155. : []
  156. fixedList.value = res.data.fixedTone
  157. ? res.data.fixedTone.split(',')
  158. : []
  159. staffList.value = res.data.musicImg ? res.data.musicImg.split(',') : []
  160. // 初始化默认数据是否有值
  161. if (firstList.value.length > 0) {
  162. defaultImgs.value.first = true
  163. }
  164. if (fixedList.value.length > 0) {
  165. defaultImgs.value.fixed = true
  166. }
  167. if (staffList.value.length > 0) {
  168. defaultImgs.value.staff = true
  169. }
  170. nextTick(async () => {
  171. await toDetail()
  172. renderStaff()
  173. })
  174. } catch (error) {
  175. isError.value = true
  176. }
  177. if (musicDetail.value?.musicSheetType !== 'CONCERT') {
  178. loading.value = false
  179. }
  180. }
  181. const player = ref<any>(null)
  182. const audio = ref<any>(null)
  183. const freeRate = ref<any>(0)
  184. const initAudio = async () => {
  185. const controls = [
  186. // 'play-large',
  187. 'play',
  188. 'progress',
  189. 'captions',
  190. // 'fullscreen',
  191. 'duration'
  192. ]
  193. player.value = new Plyr(audio.value, {
  194. controls: controls
  195. })
  196. const config = await request.get(
  197. '/api-student/sysConfig/queryByParamNameList',
  198. {
  199. params: {
  200. paramNames: 'music_sheet_free_rate'
  201. }
  202. }
  203. )
  204. freeRate.value = config.data[0]?.paramValue || 0
  205. player.value.on('timeupdate', () => {
  206. // 允许播放时间
  207. const players = player.value
  208. const playTime = (players.duration * freeRate.value) / 100 || 0
  209. // 时间,不能播放
  210. if (players.currentTime >= playTime && !buyState.value.play) {
  211. players.stop()
  212. // players.pause()
  213. }
  214. })
  215. }
  216. const showLoading = async (e: any) => {
  217. // console.log(e, 'showLoading')
  218. if (e.data?.api === 'musicStaffRender') {
  219. const osmdImg = e.data.osmdImg
  220. showImg.value = []
  221. const imgs: any = []
  222. // 独奏并且的图片则不用生成
  223. if (
  224. musicDetail.value?.musicSheetType === 'SINGLE' &&
  225. musicDetail.value?.notation
  226. ) {
  227. if (
  228. (staff.radio === 'first' && firstList.value.length > 0) ||
  229. (staff.radio === 'fixed' && fixedList.value.length > 0) ||
  230. (staff.radio === 'staff' && staffList.value.length > 0)
  231. ) {
  232. if (staff.radio === 'first') {
  233. showImg.value = firstList.value
  234. } else if (staff.radio === 'fixed') {
  235. showImg.value = fixedList.value
  236. }
  237. if (staff.radio === 'staff') {
  238. showImg.value = staffList.value
  239. }
  240. loading.value = e.data.loading
  241. return
  242. }
  243. }
  244. uploadImgs.value = true
  245. if (osmdImg) {
  246. for (let i = 0; i < osmdImg.length; i++) {
  247. const img = await svgtopng(
  248. osmdImg[i].img,
  249. osmdImg[i].width,
  250. osmdImg[i].height
  251. )
  252. imgs.push(img)
  253. }
  254. }
  255. uploadImgs.value = false
  256. // 判断是否为独奏
  257. if (
  258. musicDetail.value?.musicSheetType === 'SINGLE' &&
  259. musicDetail.value?.notation
  260. ) {
  261. if (staff.radio === 'first') {
  262. firstList.value = imgs
  263. } else if (staff.radio === 'fixed') {
  264. fixedList.value = imgs
  265. } else if (staff.radio === 'staff') {
  266. staffList.value = imgs
  267. }
  268. showImg.value = imgs
  269. } else {
  270. showImg.value = imgs
  271. }
  272. loading.value = e.data.loading
  273. }
  274. }
  275. onMounted(async () => {
  276. postMessage({
  277. api: 'setStatusBarTextColor',
  278. content: { statusBarTextColor: true }
  279. })
  280. await FetchList()
  281. const { height } = useRect(headers as any)
  282. const footer = useRect(footers as any)
  283. heightInfo.value = height + footer.height
  284. // 初始化音频
  285. if (audioFileUrl.value) {
  286. initAudio()
  287. }
  288. window.addEventListener('message', showLoading)
  289. })
  290. onUnmounted(() => {
  291. postMessage({
  292. api: 'setStatusBarTextColor',
  293. content: { statusBarTextColor: false }
  294. })
  295. window.removeEventListener('message', showLoading)
  296. })
  297. const toggleFavorite = async () => {
  298. try {
  299. await request.post('/music/sheet/favorite/' + musicDetail.value?.id, {
  300. prefix:
  301. state.platformType === 'TEACHER' ? '/api-teacher' : '/api-student'
  302. })
  303. musicDetail.value.favorite = musicDetail.value?.favorite ? 0 : 1
  304. musicDetail.value.favoriteCount = musicDetail.value?.favorite
  305. ? musicDetail.value.favoriteCount + 1
  306. : musicDetail.value.favoriteCount - 1 < 0
  307. ? 0
  308. : musicDetail.value.favoriteCount - 1
  309. setTimeout(() => {
  310. Toast(musicDetail.value?.favorite ? '收藏成功' : '取消收藏成功')
  311. }, 100)
  312. } catch (error) {
  313. //
  314. }
  315. }
  316. const onBuy = async () => {
  317. const music = musicDetail.value
  318. orderStatus.orderObject.orderType = 'MUSIC'
  319. orderStatus.orderObject.orderName = music.musicSheetName
  320. orderStatus.orderObject.orderDesc = music.musicSheetName
  321. orderStatus.orderObject.actualPrice = music.musicPrice
  322. orderStatus.orderObject.recomUserId = route.query.recomUserId || 0
  323. orderStatus.orderObject.activityId = route.query.activityId || 0
  324. orderStatus.orderObject.orderNo = ''
  325. orderStatus.orderObject.orderList = [
  326. {
  327. orderType: 'MUSIC',
  328. goodsName: music.musicSheetName,
  329. actualPrice: music.musicPrice,
  330. ...music
  331. }
  332. ]
  333. const res = await request.post('/api-student/userOrder/getPendingOrder', {
  334. data: {
  335. goodType: 'MUSIC',
  336. bizId: music.id
  337. }
  338. })
  339. const result = res.data
  340. if (result) {
  341. Dialog.confirm({
  342. title: '提示',
  343. message: '您有一个未支付的订单,是否继续支付?',
  344. theme: 'round-button',
  345. className: 'confirm-button-group',
  346. cancelButtonText: '取消订单',
  347. confirmButtonText: '继续支付'
  348. })
  349. .then(async () => {
  350. orderStatus.orderObject.orderNo = result.orderNo
  351. orderStatus.orderObject.actualPrice = result.actualPrice
  352. orderStatus.orderObject.discountPrice = result.discountPrice
  353. orderStatus.orderObject.paymentConfig = {
  354. ...result.paymentConfig,
  355. paymentVendor: result.paymentVendor,
  356. paymentVersion: result.paymentVersion
  357. }
  358. routerTo()
  359. })
  360. .catch(() => {
  361. Dialog.close()
  362. // 只用取消订单,不用做其它处理
  363. cancelPayment(result.orderNo)
  364. })
  365. } else {
  366. routerTo()
  367. }
  368. }
  369. const routerTo = () => {
  370. const music = musicDetail.value
  371. router.push({
  372. path: '/orderDetail',
  373. query: {
  374. orderType: 'MUSIC',
  375. musicId: music.id
  376. }
  377. })
  378. }
  379. const cancelPayment = async (orderNo: string) => {
  380. try {
  381. await request.post('/api-student/userOrder/orderCancel', {
  382. data: {
  383. orderNo
  384. }
  385. })
  386. } catch {
  387. //
  388. }
  389. }
  390. const buyState = computed(() => {
  391. const music = musicDetail.value
  392. return {
  393. hasTenantAlbum: route.query?.tenantAlbumId ? true : false, // 是否从专辑来的
  394. play: music.play ? true : false, // 是否可以播放
  395. free: music?.paymentType.includes('FREE'),
  396. charge: music?.paymentType.includes('CHARGE'),
  397. vip: music?.paymentType.includes('VIP'),
  398. buy: music?.orderStatus === 'PAID' // 是否已买
  399. }
  400. })
  401. const staffData = reactive({
  402. open: false,
  403. iframeSrc: '',
  404. musicXml: '',
  405. instrumentName: '',
  406. iframeRef: null as any,
  407. partIndex: 0,
  408. partXmlIndex: 0,
  409. partList: [] as any[],
  410. tempPartList: [] as any[],
  411. xmlPartList: [] as any[]
  412. })
  413. /** 渲染五线谱 */
  414. // 长笛、单簧管、萨克斯、小号、长号、圆号、大号、上低音号
  415. const sortList = {
  416. 长笛: 1,
  417. 单簧管: 2,
  418. 中音单簧管: 3,
  419. 低音单簧管: 4,
  420. 高音萨克斯风: 5,
  421. 中音萨克斯风: 6,
  422. 次中音萨克斯风: 7,
  423. 低音萨克斯风: 8,
  424. 小号: 9,
  425. 长号: 10,
  426. 圆号: 11,
  427. 大号: 12,
  428. 上低音号: 13
  429. }
  430. const instrumentSort = (list: Array<any>) => {
  431. list.sort((a, b) => {
  432. return (
  433. (sortList[getInstrumentName(a.track)] || 20) -
  434. (sortList[getInstrumentName(b.track)] || 20)
  435. )
  436. })
  437. return list
  438. }
  439. const toDetail = async () => {
  440. try {
  441. if (musicDetail.value?.xmlFileUrl) {
  442. // 获取文件
  443. const res = await umiRequest.get(musicDetail.value?.xmlFileUrl, {
  444. mode: 'cors'
  445. })
  446. let partNames: string[] = []
  447. const xml: any = new DOMParser().parseFromString(res, 'text/xml')
  448. for (const item of xml.getElementsByTagName('part-name')) {
  449. if (item.textContent) {
  450. partNames.push(item.textContent)
  451. }
  452. }
  453. partNames = partNames.filter(
  454. (item: any) => !item?.toLocaleUpperCase()?.includes('COMMON')
  455. )
  456. const partList: any = []
  457. for (let j = 0; j < partNames.length; j++) {
  458. partList.push({
  459. name: partNames[j],
  460. value: j
  461. })
  462. }
  463. staffData.xmlPartList = partList
  464. }
  465. staffData.musicXml = musicDetail.value?.xmlFileUrl || ''
  466. const tempList = musicDetail.value?.background || []
  467. const tempPartList = [] as any
  468. staffData.xmlPartList.forEach((part: any) => {
  469. const item = tempList.find((item: any) => item.track === part.name)
  470. if (item) {
  471. tempPartList.push({
  472. ...item,
  473. index: part.value
  474. })
  475. }
  476. })
  477. staffData.partList = tempPartList
  478. staffData.tempPartList = JSON.parse(JSON.stringify(staffData.partList))
  479. staffData.partList = instrumentSort(staffData.partList)
  480. // console.log(staffData.partList, ' staffData.partList')
  481. staffData.partXmlIndex = staffData.partList[0]?.index || 0
  482. staffData.instrumentName =
  483. musicDetail.value?.musicSheetType === 'CONCERT'
  484. ? getInstrumentName(staffData.partList[staffData.partIndex]?.track)
  485. : ''
  486. if (musicDetail.value?.musicSheetType === 'SINGLE') {
  487. musicPdfUrl.value = musicDetail.value?.musicPdfUrl
  488. } else {
  489. musicPdfUrl.value = staffData.partList[0]?.musicPdfUrl
  490. }
  491. } catch (error) {
  492. //
  493. }
  494. }
  495. const renderStaff = async () => {
  496. try {
  497. nextTick(() => {
  498. if (musicPdfUrl.value) {
  499. const url = `${location.origin}${
  500. location.pathname
  501. }pdf/web/viewer-pdf.html?file=${encodeURIComponent(
  502. musicPdfUrl.value
  503. )}&t=${Date.now()}`
  504. // const url = '/pdf/web/viewer-pdf.html?file=' +
  505. // encodeURIComponent(musicPdfUrl.value) +
  506. // '&t=' +
  507. // Date.now()
  508. const iframeRef = document.querySelector('#staffIframeRef') as any
  509. iframeRef.contentWindow.location.replace(
  510. url
  511. )
  512. staffData.iframeSrc = url
  513. } else {
  514. const url = `${location.origin}${location.pathname}osmd/index.html`
  515. // const url = `${location.origin}/osmd/index.html`
  516. const iframeRef = document.querySelector('#staffIframeRef') as any
  517. iframeRef.contentWindow.location.replace(url)
  518. staffData.iframeSrc = url
  519. }
  520. })
  521. } catch (error) {
  522. //
  523. }
  524. }
  525. const musicIframeLoad = async () => {
  526. const iframeRef: any = document.getElementById('staffIframeRef')
  527. if (iframeRef && iframeRef.contentWindow.renderXml) {
  528. const res = await umiRequest.get(staffData.musicXml, {
  529. mode: 'cors'
  530. })
  531. const parseXmlInfo = getCustomInfo(res)
  532. const xml = formatXML(parseXmlInfo.parsedXML)
  533. const currentXml = onlyVisible(xml, staffData.partXmlIndex)
  534. iframeRef.contentWindow.renderXml(currentXml, 0)
  535. // iframeRef.contentWindow.renderXml(
  536. // staffData.musicXml,
  537. // staffData.partXmlIndex
  538. // )
  539. }
  540. }
  541. const resetRender = async () => {
  542. const iframeRef: any = document.getElementById('staffIframeRef')
  543. if (iframeRef && iframeRef.contentWindow.renderXml) {
  544. loading.value = true
  545. // iframeRef.contentWindow.resetRender(staffData.partXmlIndex)
  546. const res = await umiRequest.get(staffData.musicXml, {
  547. mode: 'cors'
  548. })
  549. const parseXmlInfo = getCustomInfo(res)
  550. const xml = formatXML(parseXmlInfo.parsedXML)
  551. const currentXml = onlyVisible(xml, staffData.partXmlIndex)
  552. iframeRef.contentWindow.renderXml(currentXml, 0)
  553. staffData.instrumentName = getInstrumentName(
  554. staffData.partList[staffData.partIndex]?.track
  555. )
  556. }
  557. }
  558. const resetRenderPage = async (type: string, xmlUrl: string) => {
  559. const iframeRef: any = document.getElementById('staffIframeRef')
  560. if (iframeRef && iframeRef.contentWindow.renderXml) {
  561. // console.log('resetRenderPage')
  562. // iframeRef.contentWindow.resetRenderPage(type, xmlUrl)
  563. const res = await umiRequest.get(staffData.musicXml, {
  564. mode: 'cors'
  565. })
  566. const parseXmlInfo = getCustomInfo(res)
  567. const xml = formatXML(parseXmlInfo.parsedXML)
  568. const currentXml = onlyVisible(xml, staffData.partXmlIndex)
  569. iframeRef.contentWindow.resetRenderPage(type, currentXml)
  570. }
  571. }
  572. const partColumns = computed(() => {
  573. return staffData.partList.map((item: any, index: number) => {
  574. const instrumentName =
  575. musicDetail.value?.musicSheetType === 'CONCERT'
  576. ? getInstrumentName(item.track)
  577. : ''
  578. return {
  579. text: item.track + (instrumentName ? `(${instrumentName})` : ''),
  580. value: index,
  581. instrumentName,
  582. musicPdfUrl: item.musicPdfUrl,
  583. firstPdfUrl: item.firstPdfUrl,
  584. jianPdfUrl: item.jianPdfUrl,
  585. xmlValue: item.index,
  586. track: item.track
  587. }
  588. })
  589. })
  590. const onDownloadApp = () => {
  591. Dialog.alert({
  592. title: '提示',
  593. message: '请在酷乐秀APP中使用',
  594. confirmButtonColor: '#2dc7aa'
  595. }).then(() => {
  596. window.location.href = location.origin + '/student/#/download'
  597. })
  598. }
  599. // 购买
  600. const onSubmit = async () => {
  601. const url =
  602. apiSuffix.value +
  603. '/tenantGroupAlbum/buyAlbumInfo?tenantGroupAlbumId=' +
  604. (route.query.taId || '')
  605. // if (state.albumId) {
  606. // url = url + '?albumId=' + state.albumId
  607. // }
  608. const { data } = await request.get(url)
  609. const details = data[0]
  610. orderStatus.orderObject.orderType = 'TENANT_ALBUM'
  611. orderStatus.orderObject.orderName = details.name
  612. orderStatus.orderObject.orderDesc = details.name
  613. orderStatus.orderObject.actualPrice = details.actualPrice
  614. // orderStatus.orderObject.recomUserId = route.query.recomUserId || 0
  615. // orderStatus.orderObject.activityId = route.query.activityId || 0
  616. orderStatus.orderObject.orderNo = ''
  617. orderStatus.orderObject.orderList = [
  618. {
  619. orderType: 'TENANT_ALBUM',
  620. goodsName: details.name,
  621. actualPrice: details.actualPrice,
  622. price: details.actualPrice,
  623. ...details
  624. }
  625. ]
  626. const res = await request.post('/api-student/userOrder/getPendingOrder', {
  627. data: {
  628. goodType: 'TENANT_ALBUM',
  629. bizId: details.id
  630. }
  631. })
  632. const result = res.data
  633. if (result) {
  634. Dialog.confirm({
  635. title: '提示',
  636. message: '您有一个未支付的订单,是否继续支付?',
  637. theme: 'round-button',
  638. className: 'confirm-button-group',
  639. cancelButtonText: '取消订单',
  640. confirmButtonText: '继续支付'
  641. })
  642. .then(async () => {
  643. orderStatus.orderObject.orderNo = result.orderNo
  644. orderStatus.orderObject.actualPrice = result.actualPrice
  645. orderStatus.orderObject.discountPrice = result.discountPrice
  646. orderStatus.orderObject.paymentConfig = {
  647. ...result.paymentConfig,
  648. paymentVendor: result.paymentVendor,
  649. paymentVersion: result.paymentVersion
  650. }
  651. routerToALBUM(details.id)
  652. })
  653. .catch(() => {
  654. Dialog.close()
  655. // 只用取消订单,不用做其它处理
  656. cancelPaymentALBUM(result.orderNo)
  657. })
  658. } else {
  659. routerToALBUM(details.id)
  660. }
  661. }
  662. const cancelPaymentALBUM = async (orderNo: string) => {
  663. try {
  664. await request.post('/api-student/userOrder/orderCancel/v2', {
  665. data: {
  666. orderNo
  667. }
  668. })
  669. } catch {
  670. //
  671. }
  672. }
  673. const routerToALBUM = (id: string) => {
  674. router.push({
  675. path: '/orderDetail',
  676. query: {
  677. orderType: 'ALBUM',
  678. album: id
  679. }
  680. })
  681. }
  682. const initIframe = (tempPdf: string, staff: string, xml: string) => {
  683. if (tempPdf) {
  684. musicPdfUrl.value = tempPdf
  685. renderStaff()
  686. } else {
  687. musicPdfUrl.value = ''
  688. // 为了处理,之前是使用pdf渲染,现在又用osmd,iframe没有重新加载
  689. if (staffData.iframeSrc.indexOf('pdf/web') !== -1) {
  690. renderStaff()
  691. } else {
  692. resetRenderPage(staff, xml)
  693. }
  694. }
  695. }
  696. return () => (
  697. <div class={styles.detail}>
  698. <TheSticky position="top">
  699. <ColHeader
  700. background={background.value}
  701. hideHeader={false}
  702. border={false}
  703. isFixed={false}
  704. color={color.value}
  705. backIconColor="white"
  706. />
  707. </TheSticky>
  708. <img class={styles.bgImg} src={musicDetail.value?.titleImg} />
  709. <div class={styles.musicContentBg}></div>
  710. <div class={styles.bg}>
  711. <div class={styles.alumWrap}>
  712. <div class={styles.img}>
  713. {/* {albumDetail.value?.paymentType === 'CHARGE' && (
  714. <span class={styles.albumType}>付费</span>
  715. )} */}
  716. <Image
  717. class={styles.image}
  718. width="100%"
  719. height="100%"
  720. fit="cover"
  721. src={musicDetail.value?.titleImg}
  722. />
  723. </div>
  724. <div class={styles.alumDes}>
  725. <div class={[styles.alumTitle, 'van-ellipsis']}>
  726. {musicDetail.value?.musicSheetName}
  727. </div>
  728. <div class={[styles.des, 'van-multi-ellipsis--l2']}>
  729. {!musicDetail.value?.composer
  730. ? `上传者:${musicDetail.value?.addName || ''}`
  731. : `作曲:${musicDetail.value?.composer || ''}`}
  732. </div>
  733. </div>
  734. </div>
  735. <div class={[styles.alumCollect]}>
  736. <div
  737. class={[
  738. styles.alumCollectItem,
  739. showImg.value.length <= 0 && !musicPdfUrl.value
  740. ? styles.alumCollectItemActive
  741. : ''
  742. ]}
  743. onClick={() => {
  744. if (browser().isApp) {
  745. if (musicPdfUrl.value) {
  746. const songName =
  747. musicDetail.value?.musicSheetName +
  748. (staffData.instrumentName
  749. ? `(${staffData.instrumentName})`
  750. : '')
  751. promisefiyPostMessage({
  752. api: 'downloadFile',
  753. content: {
  754. downloadUrl: musicPdfUrl.value,
  755. fileName: songName
  756. }
  757. })
  758. return
  759. }
  760. if (showImg.value.length > 0) {
  761. downloadStatus.value = true
  762. }
  763. } else {
  764. onDownloadApp()
  765. }
  766. }}
  767. >
  768. <img src={iconDownload} />
  769. <span>下载曲谱</span>
  770. </div>
  771. <div
  772. class={styles.alumCollectItem}
  773. onClick={() => toggleFavorite()}
  774. >
  775. <img src={musicDetail.value?.favorite ? iStart : oStart} />
  776. <span>{musicDetail.value?.favoriteCount}人收藏</span>
  777. </div>
  778. </div>
  779. {/* {buyState.value.hasTenantAlbum ? 'true' : 'false'} */}
  780. {musicDetail.value?.id &&
  781. !buyState.value.play &&
  782. !buyState.value.hasTenantAlbum && (
  783. <div class={styles.albumTips}>
  784. {buyState.value.charge && buyState.value.vip ? (
  785. <>
  786. <span>开通会员或点播单曲,即可自由练习该曲谱</span>
  787. <span class={styles.albumPrice}>
  788. ¥{moneyFormat(musicDetail.value?.musicPrice)}
  789. </span>
  790. </>
  791. ) : buyState.value.vip ? (
  792. <span>
  793. <img src={iconMemberSmall} class={styles.iconMemberSmall} />
  794. 此曲谱为会员专享,开通会员即可自由练习该曲谱
  795. </span>
  796. ) : buyState.value.charge ? (
  797. <>
  798. <span>此曲谱为点播曲谱,点播即可自由练习该曲谱</span>
  799. <span class={styles.albumPrice}>
  800. ¥{moneyFormat(musicDetail.value?.musicPrice)}
  801. </span>
  802. </>
  803. ) : (
  804. ''
  805. )}
  806. </div>
  807. )}
  808. </div>
  809. <div class={styles.musicContent}>
  810. {musicDetail.value?.notation ? (
  811. <span
  812. class={styles.iconTransfer}
  813. style={{
  814. display:
  815. musicDetail.value?.musicSheetType === 'SINGLE' ? '' : 'none',
  816. // opacity: showImg.value.length <= 0 || data.musicPdfUrl ? 0.6 : 1
  817. }}
  818. onClick={() => {
  819. // if (showImg.value.length <= 0 || data.musicPdfUrl) return
  820. staff.status = true
  821. }}
  822. >
  823. 转谱
  824. </span>
  825. ) : null}
  826. <span
  827. class={styles.iconTransfer}
  828. style={{
  829. display:
  830. musicDetail.value?.musicSheetType === 'CONCERT' ? '' : 'none'
  831. }}
  832. onClick={() => {
  833. staffData.open = true
  834. }}
  835. >
  836. 切换声轨
  837. </span>
  838. {musicDetail.value?.musicSheetType === 'CONCERT' || musicPdfUrl.value ||
  839. !defaultImgs.value[staff.radio] ? (
  840. <>
  841. {musicPdfUrl.value ? (
  842. <iframe
  843. id="staffIframeRef"
  844. style={{
  845. opacity: loading.value ? 0 : 1
  846. }}
  847. // src={staffData.iframeSrc}
  848. onLoad={() => {
  849. // 判断是用哪个渲染的
  850. loading.value = false
  851. }}
  852. ></iframe>
  853. ) : (
  854. <>
  855. <p class={styles.musicTitle}>
  856. {(musicDetail.value?.musicSheetName
  857. ? musicDetail.value?.musicSheetName
  858. : '') +
  859. (staffData.instrumentName
  860. ? `(${staffData.instrumentName})`
  861. : '')}
  862. </p>
  863. {loading.value && (
  864. <div>
  865. <Vue3Lottie
  866. animationData={AstronautJSON}
  867. class={styles.finch}
  868. ></Vue3Lottie>
  869. {/* <p class={styles.finchLoad}>加载中...</p> */}
  870. </div>
  871. )}
  872. <iframe
  873. id="staffIframeRef"
  874. style={{
  875. opacity: loading.value ? 0 : 1
  876. }}
  877. // src={staffData.iframeSrc}
  878. onLoad={() => {
  879. musicIframeLoad()
  880. }}
  881. ></iframe>
  882. </>
  883. )}
  884. </>
  885. ) : (
  886. <div class={styles.imgSection}>
  887. <p class={styles.musicTitle}>
  888. {(musicDetail.value?.musicSheetName
  889. ? musicDetail.value?.musicSheetName
  890. : '') +
  891. (staffData.instrumentName
  892. ? `(${staffData.instrumentName})`
  893. : '')}
  894. </p>
  895. {showImg.value.length > 0 ? (
  896. <div class={styles.musicImg}>
  897. <div>
  898. <img src={showImg.value[0]} alt="" />
  899. </div>
  900. </div>
  901. ) : loading.value ? (
  902. <>
  903. <Vue3Lottie
  904. animationData={AstronautJSON}
  905. class={styles.finch}
  906. ></Vue3Lottie>
  907. {/* <p class={styles.finchLoad}>加载中...</p> */}
  908. </>
  909. ) : (
  910. <div class={styles.empty}>
  911. <Image src={emtpy} class={styles.emptyImg} />
  912. <p class={styles.emptyTip}>暂无乐谱预览图</p>
  913. </div>
  914. )}
  915. </div>
  916. )}
  917. </div>
  918. {musicDetail.value?.id && (
  919. <TheSticky position="bottom" varName="--bottom-height">
  920. <div style={{ backgroundColor: '#fff' }}>
  921. <div class={styles.videoOperation}>
  922. {audioFileUrl.value && (
  923. <>
  924. {!buyState.value.play &&
  925. freeRate.value != 100 &&
  926. freeRate.value != 0 && (
  927. <div class={[styles.audition]}>
  928. <img src={iconListen} />
  929. <span>每首曲目可试听{freeRate.value}%</span>
  930. </div>
  931. )}
  932. <div class={[styles.audio, styles.collectCell]}>
  933. <audio id="player" controls ref={audio}>
  934. <source src={audioFileUrl.value} type="audio/mp3" />
  935. </audio>
  936. </div>
  937. </>
  938. )}
  939. </div>
  940. <div ref={footers} class={styles.footers}>
  941. {/* 判断是否是免费的,或者已经购买过 */}
  942. {buyState.value.play ? (
  943. <Button
  944. round
  945. block
  946. type="primary"
  947. color="linear-gradient( 270deg, #FF204B 0%, #FE5B71 100%)"
  948. onClick={() => {
  949. if (!browser().isApp) {
  950. onDownloadApp()
  951. return
  952. }
  953. const throttleFn = useThrottleFn(() => {
  954. player.value && player.value.stop()
  955. const item: any = partColumns.value.find(
  956. (c: any) => c.value === staffData.partIndex
  957. )
  958. // const index = staffData.tempPartList.findIndex(
  959. // (i: any) => i.track === item?.track
  960. // )
  961. musicBuy(musicDetail.value, () => {}, {
  962. 'part-index': item?.xmlValue || 0,
  963. sett: staff.radio,
  964. albumId: route.query.tenantAlbumId || '', // 专辑编号
  965. // 1:忽略系统节拍器
  966. ignoreSysMetronome:
  967. route.query.subjectType === 'MUSIC' ? 1 : 0
  968. })
  969. }, 500)
  970. throttleFn()
  971. }}
  972. >
  973. 立即练习
  974. </Button>
  975. ) : buyState.value.hasTenantAlbum && !buyState.value.play ? (
  976. <Button
  977. disabled={route.query.buyStatus === '1'}
  978. round
  979. block
  980. type="primary"
  981. color="linear-gradient( 270deg, #FF204B 0%, #FE5B71 100%)"
  982. onClick={() => {
  983. if (!browser().isApp) {
  984. onDownloadApp()
  985. return
  986. }
  987. if (route.query.type === 'search') {
  988. router.push('train-tool')
  989. } else {
  990. onSubmit()
  991. }
  992. }}
  993. >
  994. 开通训练教程
  995. </Button>
  996. ) : (
  997. <div class={[styles.buyBtn]}>
  998. {/* 判断是否是需要收费的 */}
  999. {buyState.value.charge && (
  1000. <Button
  1001. round
  1002. type="primary"
  1003. color="linear-gradient(270deg, #FF204B 0%, #FE5B71 100%)"
  1004. class={styles.primary}
  1005. onClick={() => {
  1006. if (!browser().isApp) {
  1007. onDownloadApp()
  1008. return
  1009. }
  1010. onBuy()
  1011. }}
  1012. >
  1013. 立即点播
  1014. </Button>
  1015. )}
  1016. {/* 判断是否有会员的 */}
  1017. {buyState.value.vip && (
  1018. <Button
  1019. round
  1020. block={!buyState.value.charge ? true : false}
  1021. type="primary"
  1022. color="linear-gradient(270deg, #FF204B 0%, #FE5B71 100%)"
  1023. class={styles.memeber}
  1024. onClick={() => {
  1025. if (!browser().isApp) {
  1026. onDownloadApp()
  1027. return
  1028. }
  1029. router.push({
  1030. path: '/memberCenter',
  1031. query: {
  1032. ...route.query
  1033. }
  1034. })
  1035. }}
  1036. >
  1037. {studentActivityId.value > 0 && (
  1038. <div class={[styles.buttonDiscount]}>专属优惠</div>
  1039. )}
  1040. 开通会员
  1041. </Button>
  1042. )}
  1043. </div>
  1044. )}
  1045. </div>
  1046. </div>
  1047. </TheSticky>
  1048. )}
  1049. <Popup v-model:show={downloadStatus.value} position="bottom" round>
  1050. {downloadStatus.value && (
  1051. <Download
  1052. imgList={JSON.parse(JSON.stringify(showImg.value))}
  1053. musicSheetName={musicDetail.value?.musicSheetName +
  1054. (staffData.instrumentName
  1055. ? `(${staffData.instrumentName})`
  1056. : '')}
  1057. />
  1058. )}
  1059. </Popup>
  1060. <Popup
  1061. v-model:show={staff.status}
  1062. teleport="body"
  1063. closeable
  1064. style={{ width: '80%' }}
  1065. class={styles.staffChange}
  1066. round
  1067. >
  1068. <div class={styles.staffContainer}>
  1069. <div class={styles.staffTitle}>转换曲谱</div>
  1070. <RadioGroup v-model={staff.radio}>
  1071. <CellGroup border={false}>
  1072. <Cell
  1073. center
  1074. border={false}
  1075. class={staff.radio === 'staff' ? styles.active : ''}
  1076. onClick={() => onChangeStaff('staff')}
  1077. >
  1078. {{
  1079. icon: () => (
  1080. <Image src={staffDetafult} class={styles.staffImg} />
  1081. ),
  1082. title: () => <span class={styles.name}>五线谱</span>,
  1083. value: () => (
  1084. <Radio name="staff">
  1085. {{
  1086. icon: (props: any) => (
  1087. <Icon
  1088. class={styles.boxStyle}
  1089. name={
  1090. props.checked
  1091. ? activeButtonIcon
  1092. : inactiveButtonIcon
  1093. }
  1094. />
  1095. )
  1096. }}
  1097. </Radio>
  1098. )
  1099. }}
  1100. </Cell>
  1101. <Cell
  1102. center
  1103. border={false}
  1104. class={staff.radio === 'first' ? styles.active : ''}
  1105. onClick={() => onChangeStaff('first')}
  1106. >
  1107. {{
  1108. icon: () => (
  1109. <Image src={firstDefault} class={styles.staffImg} />
  1110. ),
  1111. title: () => <span class={styles.name}>简谱-首调</span>,
  1112. value: () => (
  1113. <Radio name="first">
  1114. {{
  1115. icon: (props: any) => (
  1116. <Icon
  1117. class={styles.boxStyle}
  1118. name={
  1119. props.checked
  1120. ? activeButtonIcon
  1121. : inactiveButtonIcon
  1122. }
  1123. />
  1124. )
  1125. }}
  1126. </Radio>
  1127. )
  1128. }}
  1129. </Cell>
  1130. <Cell
  1131. center
  1132. border={false}
  1133. class={staff.radio === 'fixed' ? styles.active : ''}
  1134. onClick={() => onChangeStaff('fixed')}
  1135. >
  1136. {{
  1137. icon: () => (
  1138. <Image src={fixedDefault} class={styles.staffImg} />
  1139. ),
  1140. title: () => <span class={styles.name}>简谱-固定调</span>,
  1141. value: () => (
  1142. <Radio name="fixed">
  1143. {{
  1144. icon: (props: any) => (
  1145. <Icon
  1146. class={styles.boxStyle}
  1147. name={
  1148. props.checked
  1149. ? activeButtonIcon
  1150. : inactiveButtonIcon
  1151. }
  1152. />
  1153. )
  1154. }}
  1155. </Radio>
  1156. )
  1157. }}
  1158. </Cell>
  1159. </CellGroup>
  1160. </RadioGroup>
  1161. </div>
  1162. </Popup>
  1163. <Popup
  1164. teleport="body"
  1165. position="bottom"
  1166. round
  1167. v-model:show={staffData.open}
  1168. >
  1169. <Picker
  1170. columns={partColumns.value}
  1171. onConfirm={value => {
  1172. staffData.open = false
  1173. staffData.partIndex = value.value
  1174. staffData.partXmlIndex = value.xmlValue
  1175. staffData.instrumentName = value.instrumentName
  1176. showImg.value = []
  1177. nextTick(() => {
  1178. // const item = getInstrumentItem(musicDetail.value?.background || [], value.selectedOptions[0].track);
  1179. let tempPdf = value?.musicPdfUrl
  1180. if (musicDetail.value?.musicSheetType !== 'CONCERT') {
  1181. // tempPdf = ''
  1182. if (staff.radio === 'first') {
  1183. tempPdf = value?.firstPdfUrl
  1184. } else if (staff.radio === 'fixed') {
  1185. tempPdf = value?.jianPdfUrl
  1186. } else if (staff.radio === 'staff') {
  1187. tempPdf = value?.musicPdfUrl
  1188. }
  1189. }
  1190. if (tempPdf) {
  1191. musicPdfUrl.value = tempPdf
  1192. renderStaff()
  1193. } else {
  1194. musicPdfUrl.value = ''
  1195. loading.value = true
  1196. // 为了处理,之前是使用pdf渲染,现在又用osmd,iframe没有重新加载
  1197. if (staffData.iframeSrc.indexOf('pdf/web') !== -1) {
  1198. renderStaff()
  1199. } else {
  1200. resetRender()
  1201. }
  1202. }
  1203. // resetRender()
  1204. })
  1205. }}
  1206. onCancel={() => (staffData.open = false)}
  1207. />
  1208. </Popup>
  1209. </div>
  1210. )
  1211. }
  1212. })