index.tsx 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407
  1. import {
  2. computed,
  3. defineComponent,
  4. nextTick,
  5. onBeforeUnmount,
  6. onMounted,
  7. onUnmounted,
  8. reactive,
  9. ref
  10. } from 'vue'
  11. import { useRoute, useRouter } from 'vue-router'
  12. import request from '@/helpers/request'
  13. import ColHeader from '@/components/col-header'
  14. import { postMessage, promisefiyPostMessage } from '@/helpers/native-message'
  15. import {
  16. Button,
  17. Cell,
  18. CellGroup,
  19. Dialog,
  20. Icon,
  21. Image,
  22. Popup,
  23. RadioGroup,
  24. Tag,
  25. Radio,
  26. Toast,
  27. Picker
  28. } from 'vant'
  29. import styles from './index.module.less'
  30. // import Item from '../list/item'
  31. import { useRect } from '@vant/use'
  32. import { getRandomKey, musicBuy } from '../music'
  33. import { state } from '@/state'
  34. // import { useEventTracking } from '@/helpers/hooks'
  35. import ColSticky from '@/components/col-sticky'
  36. import { browser, getHttpOrigin, moneyFormat } from '@/helpers/utils'
  37. import { orderStatus } from '@/views/order-detail/orderStatus'
  38. import iconShare from '@/views/music/album/icon_share.svg'
  39. import iconAlbum from './images/icon_album.png'
  40. // import iconAlbum2 from './images/icon_album2.png'
  41. import iconDownload from './images/icon_download.png'
  42. import iconChange from './images/icon-change.png'
  43. import iconAddCourse from './images/icon-add-course.png'
  44. import iconRemoveCourse from './images/icon-remove-course.png'
  45. import ColShare from '@/components/col-share'
  46. import iconCollect from './images/icon_collect.png'
  47. import iconCollectActive from './images/icon_collect_active.png'
  48. import iconListen from './images/icon_listen.png'
  49. import activeButtonIcon from '@common/images/icon_checkbox.png'
  50. import inactiveButtonIcon from '@common/images/icon_checkbox_default.png'
  51. import staffDetafult from './images/staff-default.png'
  52. import firstDefault from './images/first-default.png'
  53. import fixedDefault from './images/fixed-default.png'
  54. import Plyr from 'plyr'
  55. import 'plyr/dist/plyr.css'
  56. import Download from './download'
  57. import { getInstrumentName } from '@/constant/instruments'
  58. import { svgtopng } from '@/tenant/music/music-detail/formatSvgToImg'
  59. import { useThrottleFn } from '@vueuse/core'
  60. // import {
  61. // formatXML,
  62. // getCustomInfo,
  63. // onlyVisible
  64. // } from '@/tenant/music/music-detail/instrument'
  65. export const getAssetsHomeFile = (fileName: string) => {
  66. const path = `../component/images/${fileName}`
  67. const modules = import.meta.globEager('../component/images/*')
  68. return modules[path].default
  69. }
  70. export default defineComponent({
  71. name: 'MusicDetail',
  72. setup() {
  73. const behaviorId = ref(getRandomKey())
  74. localStorage.setItem('behaviorId', behaviorId.value)
  75. const router = useRouter()
  76. const route = useRoute()
  77. const loading = ref(false)
  78. const aId = Number(route.query.activityId) || 0
  79. const studentActivityId = ref(aId)
  80. const isError = ref(false)
  81. const headers = ref(null)
  82. const footers = ref(null)
  83. const heightInfo = ref<any>('0')
  84. const audioFileUrl = ref('')
  85. const staffData = reactive({
  86. musicId: route.query.id as any,
  87. instrumentId: '', // 声部编号
  88. subjectId: '',
  89. isConcert: false, // 是否合奏
  90. details: {} as any,
  91. list: [], // 列表数据
  92. open: false,
  93. closed: true,
  94. audioReady: false,
  95. iframeSrc: "",
  96. musicXml: "",
  97. instrumentName: "",
  98. iframeRef: null as any,
  99. imgs: [] as any,
  100. musicPdfUrl: "", // 当前声轨PDF
  101. partList: [] as any[],
  102. partNames: [] as string[],
  103. selectedPartName: "" as any,
  104. selectedPartIndex: 0,
  105. isComberRender: false, // 是否合并谱显示
  106. metronomeUrl: "", // 合奏使用链接
  107. metronomeMp3Url: "", // 独奏使用链接
  108. })
  109. // 当前先中的数据
  110. const currentColumn = computed(() => {
  111. return partColumns.value.find((item: any) => item.value === staffData.selectedPartIndex)
  112. })
  113. // 是否转谱不能转,默认谱面不是五线谱时
  114. const defaultConvertible = computed(() => {
  115. const details = staffData.details
  116. let isConvertibleScore = details.isConvertibleScore
  117. let isHiddenStaff = false // 是否隐藏五线谱转谱
  118. if(!isConvertibleScore && ["FIRST", "JIAN"].includes(details.scoreType)) {
  119. isConvertibleScore = true
  120. isHiddenStaff = true
  121. }
  122. return {
  123. isConvertibleScore,
  124. isHiddenStaff
  125. }
  126. })
  127. const downloadStatus = ref<boolean>(false)
  128. const staff = reactive({
  129. status: false,
  130. radio: 'STAVE' // STAVE: 五线谱; JIAN: 固定调; FIRST: 首调
  131. })
  132. const colors: any = {
  133. FREE: {
  134. color: '#01B84F',
  135. text: '免费'
  136. },
  137. VIP: {
  138. color: '#CD863E',
  139. text: '会员'
  140. },
  141. CHARGE: {
  142. color: '#3591CE',
  143. text: '点播'
  144. }
  145. }
  146. // 更改预览状态
  147. const onChangeStaff = (type: string) => {
  148. staff.radio = type
  149. staff.status = false
  150. staffData.imgs = []
  151. if (type == 'FIRST') {
  152. loading.value = false
  153. const tempPdf = staffData.details?.firstPdfUrl
  154. initIframe(tempPdf, 'FIRST')
  155. } else if (type == 'JIAN') {
  156. loading.value = false
  157. const tempPdf = staffData.details?.jianPdfUrl
  158. initIframe(tempPdf, 'JIAN')
  159. } else {
  160. loading.value = false
  161. const tempPdf = staffData.details?.musicPdfUrl
  162. initIframe(tempPdf, 'STAVE')
  163. }
  164. }
  165. const initIframe = (tempPdf: string, staff: string) => {
  166. if (tempPdf) {
  167. staffData.musicPdfUrl = tempPdf
  168. } else {
  169. staffData.musicPdfUrl = ''
  170. }
  171. renderStaff()
  172. }
  173. const FetchList = async (id?: any) => {
  174. if (loading.value) {
  175. return
  176. }
  177. loading.value = true
  178. isError.value = false
  179. try {
  180. const {data} = await request.post('/musicSheet/detailSmall', {
  181. prefix:
  182. state.platformType === 'TEACHER' ? '/api-teacher' : '/api-student',
  183. data: {
  184. musicSheetId: route.query.id,
  185. tenantAlbumId: route.query.tenantAlbumId,
  186. providerType: route.query.providerType,
  187. instrumentId: staffData.instrumentId,
  188. subjectId: staffData.subjectId
  189. }
  190. })
  191. staffData.details = data
  192. await toDetail(data)
  193. if (data.auditStatus === 'DOING') {
  194. Dialog.confirm({
  195. message: '曲目审核中',
  196. showConfirmButton: true,
  197. showCancelButton: false,
  198. confirmButtonColor: 'var(--van-primary)'
  199. }).then(() => {
  200. if (browser().isApp) {
  201. postMessage({ api: 'goBack' })
  202. } else {
  203. router.back()
  204. }
  205. })
  206. }
  207. } catch (error) {
  208. isError.value = true
  209. }
  210. loading.value = false
  211. }
  212. const player = ref<any>(null)
  213. const audio = ref<any>(null)
  214. const freeRate = ref<any>(0)
  215. const initAudio = async () => {
  216. const controls = [
  217. 'play-large',
  218. 'play',
  219. 'progress',
  220. 'captions',
  221. // 'fullscreen',
  222. 'duration'
  223. ]
  224. player.value = new Plyr(audio.value, {
  225. controls: controls
  226. })
  227. const config = await request.get(
  228. '/sysConfig/queryByParamNameList',
  229. {
  230. prefix: state.platformType === 'TEACHER' ? '/api-teacher' : '/api-student',
  231. params: {
  232. paramNames: 'music_sheet_free_rate'
  233. }
  234. }
  235. )
  236. freeRate.value = config.data[0]?.paramValue || 0
  237. // player.value.on("ready", () => {
  238. // staffData.audioReady = true;
  239. // nextTick(async () => {
  240. // console.log('1111')
  241. // renderStaff();
  242. // });
  243. // });
  244. nextTick(async () => {
  245. renderStaff();
  246. });
  247. player.value.on('timeupdate', () => {
  248. // 允许播放时间
  249. const players = player.value
  250. const playTime = (players.duration * freeRate.value) / 100 || 0
  251. // 时间,不能播放
  252. if (players.currentTime >= playTime && !buyState.value.play) {
  253. players.stop()
  254. // players.pause()
  255. }
  256. })
  257. }
  258. const showLoading = async (e: any) => {
  259. if (e.data?.api === 'musicStaffRender') {
  260. const osmdImg = e.data.osmdImg
  261. // showImg.value = []
  262. const imgs: any = []
  263. for (let i = 0; i < osmdImg.length; i++) {
  264. const img = await svgtopng(
  265. osmdImg[i].img,
  266. osmdImg[i].width,
  267. osmdImg[i].height
  268. )
  269. imgs.push(img)
  270. }
  271. staffData.imgs = imgs
  272. loading.value = e.data.loading
  273. }
  274. }
  275. onMounted(async () => {
  276. if(route.query.instrumentId) {
  277. staffData.instrumentId = route.query.instrumentId as any
  278. } else {
  279. const instrumentIds = state.user.data?.instrumentId
  280. if(instrumentIds) {
  281. const instrumentId = instrumentIds.split(',')[0]
  282. staffData.instrumentId = instrumentId
  283. }
  284. }
  285. const subjectIds = state.user.data?.subjectId
  286. if(subjectIds) {
  287. const subjectId = subjectIds.split(',')[0]
  288. staffData.subjectId = subjectId
  289. }
  290. postMessage({
  291. api: 'setStatusBarTextColor',
  292. content: { statusBarTextColor: true }
  293. })
  294. await FetchList()
  295. const { height } = useRect(headers as any)
  296. const footer = useRect(footers as any)
  297. heightInfo.value = height + footer.height
  298. // // 初始化音频
  299. // if (audioFileUrl.value) {
  300. // initAudio()
  301. // }
  302. window.addEventListener('message', showLoading)
  303. })
  304. onBeforeUnmount(() => {
  305. postMessage({
  306. api: 'setStatusBarTextColor',
  307. content: { statusBarTextColor: false }
  308. })
  309. })
  310. onUnmounted(() => {
  311. window.removeEventListener('message', showLoading)
  312. })
  313. const toggleFavorite = async () => {
  314. /**
  315. * 酷乐秀老师端 收藏曲目 music/sheet/favorite/id?providerType=?? 添加参数
  316. * @ApiModelProperty("曲目评测来源 TENANT 机构 PLATFORM 平台")
  317. * private String providerType;
  318. * 表示收藏的是机构曲目,还是平台曲目,
  319. */
  320. let apiUrl = '', providerType = ''
  321. if (browser().isTeacher) {
  322. providerType = route.query.tenantAlbumId || route.query.providerType == 'TENANT' ? 'TENANT' : 'PLATFORM'
  323. }
  324. apiUrl = `/music/sheet/favorite/${staffData.details?.id}`
  325. try {
  326. await request.post(apiUrl, {
  327. requestType: 'form',
  328. data: {
  329. providerType
  330. },
  331. prefix:
  332. state.platformType === 'TEACHER' ? '/api-teacher' : '/api-student'
  333. })
  334. staffData.details.favorite = staffData.details?.favorite ? 0 : 1
  335. staffData.details.favoriteCount = staffData.details?.favorite
  336. ? staffData.details.favoriteCount + 1
  337. : staffData.details.favoriteCount - 1 < 0
  338. ? 0
  339. : staffData.details.favoriteCount - 1
  340. setTimeout(() => {
  341. Toast(staffData.details?.favorite ? '收藏成功' : '取消收藏成功')
  342. }, 100)
  343. } catch (error) {
  344. //
  345. }
  346. }
  347. const onAddCourse = async () => {
  348. try {
  349. const res = await request.post('/api-teacher/courseCourseware/submit', {
  350. data: {
  351. musicSheetId: staffData.details.id,
  352. clientType: 'TEACHER',
  353. userId: state.user.data?.userId
  354. }
  355. })
  356. // console.log(res)
  357. setTimeout(() => {
  358. staffData.details.coursewareId = res.data.id || ''
  359. Toast('已将曲目添加到课件')
  360. staffData.details.coursewareStatus = 1
  361. }, 100)
  362. } catch {
  363. //
  364. }
  365. }
  366. const removeCourse = async () => {
  367. Dialog.confirm({
  368. title: '提示',
  369. message: '您是否确定移出课件',
  370. confirmButtonColor: '#269a93',
  371. cancelButtonText: '取消',
  372. confirmButtonText: '确定'
  373. }).then(async () => {
  374. try {
  375. await request.post(
  376. '/api-teacher/courseCourseware/remove/' +
  377. staffData.details.coursewareId,
  378. {
  379. data: {}
  380. }
  381. )
  382. setTimeout(() => {
  383. Toast('移出成功')
  384. staffData.details.coursewareStatus = 0
  385. }, 100)
  386. } catch {
  387. //
  388. }
  389. })
  390. }
  391. const onBuy = async () => {
  392. const music = staffData.details
  393. orderStatus.orderObject.orderType = 'MUSIC'
  394. orderStatus.orderObject.orderName = music.name
  395. orderStatus.orderObject.orderDesc = music.name
  396. orderStatus.orderObject.actualPrice = music.musicPrice
  397. orderStatus.orderObject.recomUserId = route.query.recomUserId || 0
  398. orderStatus.orderObject.activityId = route.query.activityId || 0
  399. orderStatus.orderObject.orderNo = ''
  400. orderStatus.orderObject.orderList = [
  401. {
  402. orderType: 'MUSIC',
  403. goodsName: music.name,
  404. actualPrice: music.musicPrice,
  405. price: music.musicPrice,
  406. ...music
  407. }
  408. ]
  409. const res = await request.post('/userOrder/getPendingOrder', {
  410. prefix: state.platformType === 'TEACHER' ? '/api-teacher' : '/api-student',
  411. data: {
  412. goodType: 'MUSIC',
  413. bizId: music.id
  414. }
  415. })
  416. const result = res.data
  417. if (result) {
  418. Dialog.confirm({
  419. title: '提示',
  420. message: '您有一个未支付的订单,是否继续支付?',
  421. confirmButtonColor: '#269a93',
  422. cancelButtonText: '取消订单',
  423. confirmButtonText: '继续支付'
  424. })
  425. .then(async () => {
  426. orderStatus.orderObject.orderNo = result.orderNo
  427. orderStatus.orderObject.actualPrice = result.actualPrice
  428. orderStatus.orderObject.discountPrice = result.discountPrice
  429. orderStatus.orderObject.paymentConfig = {
  430. ...result.paymentConfig,
  431. paymentVendor: result.paymentVendor,
  432. paymentVersion: result.paymentVersion
  433. }
  434. routerTo()
  435. })
  436. .catch(() => {
  437. Dialog.close()
  438. // 只用取消订单,不用做其它处理
  439. cancelPayment(result.orderNo)
  440. })
  441. } else {
  442. routerTo()
  443. }
  444. }
  445. const routerTo = () => {
  446. const music = staffData.details
  447. router.push({
  448. path: '/orderDetail',
  449. query: {
  450. orderType: 'MUSIC',
  451. musicId: music.id
  452. }
  453. })
  454. }
  455. const cancelPayment = async (orderNo: string) => {
  456. try {
  457. await request.post('/userOrder/orderCancel', {
  458. prefix: state.platformType === 'TEACHER' ? '/api-teacher' : '/api-student',
  459. data: {
  460. orderNo
  461. }
  462. })
  463. } catch {}
  464. }
  465. const paymentType = computed(() => {
  466. let paymentType = staffData.details?.paymentType
  467. if (typeof paymentType === 'string') {
  468. paymentType = paymentType.split(',')
  469. return paymentType
  470. }
  471. return []
  472. })
  473. const buyState = computed(() => {
  474. const music = staffData.details
  475. return {
  476. hasTenantAlbum: route.query?.tenantAlbumId ? true : false, // 是否从专辑来的
  477. play: music.play ? true : false, // 是否可以播放
  478. free: music?.paymentType.includes('FREE'),
  479. charge: music?.paymentType.includes('CHARGE'),
  480. vip: music?.paymentType.includes('VIP'),
  481. buy: music?.orderStatus === 'PAID' // 是否已买
  482. }
  483. })
  484. const shareStatus = ref(false)
  485. const shareUrl = ref('')
  486. const shareDiscount = ref(0)
  487. const partColumns = ref<any>([]);
  488. // console.log(data)
  489. const onShare = async () => {
  490. try {
  491. const res = await request.post('/api-teacher/open/musicShareProfit', {
  492. data: {
  493. bizId: staffData.details?.id,
  494. userId: state.user.data?.userId
  495. }
  496. })
  497. let url =
  498. location.origin +
  499. `/teacher/#/shareMusic?id=${staffData.details?.id}&recomUserId=${state.user.data?.userId}&userType=${state.platformType}`
  500. // 判断是否有活动
  501. if (res.data.discount === 1) {
  502. url += `&activityId=${res.data.activityId}`
  503. }
  504. shareDiscount.value = res.data.discount || 0
  505. shareUrl.value = url
  506. shareStatus.value = true
  507. return
  508. } catch {}
  509. }
  510. /** 获取分轨信息 */
  511. const getInstrumentItem = (instruments: any, name = "") => {
  512. name = name.toLocaleLowerCase().replace(/ /g, ""); //.replace(/\d*/gi, '')
  513. if (!name) return "";
  514. for (let key in instruments) {
  515. const item = instruments[key];
  516. const _key = item.track?.toLocaleLowerCase().replace(/ /g, ""); //.replace(/\d*/gi, '')
  517. if (_key === name) {
  518. return item;
  519. }
  520. }
  521. return "";
  522. };
  523. // 根据当前选中的声部和曲目筛选出对应的声轨
  524. function filterSoundCodes(musicalInstruments: any) {
  525. // 老师端,加上乐器id
  526. const instrumentIds = staffData.instrumentId // appState.instrumentId || appState.user?.instrumentId || route.query.instrumentId
  527. if (instrumentIds) {
  528. const { track } = musicalInstruments.find((item: any) => {
  529. return instrumentIds == item.musicalInstrumentId
  530. }) || {}
  531. return track
  532. }
  533. return null
  534. }
  535. // 根据当前选中的声部和曲目筛选出对应的声轨
  536. // function filterSoundInfo(musicalInstruments: any) {
  537. // if(musicalInstruments.length <= 0) return null
  538. // // 老师端,加上乐器id
  539. // const instrumentIds = staffData.instrumentId //appState.instrumentId || appState.user?.instrumentId || route.query.instrumentId
  540. // if (instrumentIds) {
  541. // const item = musicalInstruments.find((item: any) => {
  542. // return instrumentIds == item.musicalInstrumentId && item.audioPlayType == 'PLAY'
  543. // }) || null
  544. // return item
  545. // }
  546. // return null
  547. // }
  548. // 获取PDF
  549. const getCurrentPdf = (item: any, scoreType: string) => {
  550. let pdfUrl = ''
  551. if(scoreType === 'FIRST') {
  552. pdfUrl = item.firstPdfUrl
  553. } else if(scoreType === 'JIAN') {
  554. pdfUrl = item.jianPdfUrl
  555. } else if(scoreType === 'STAVE') {
  556. pdfUrl = item.musicPdfUrl
  557. }
  558. return pdfUrl || ''
  559. }
  560. const toDetail = async (row: any) => {
  561. staff.radio = row.scoreType // 默认是什么谱页
  562. const tempSongList = row.musicSheetSoundList || []
  563. let partList = tempSongList
  564. staffData.list = tempSongList
  565. partList = partList.filter(
  566. (item: any) => !item.track?.toLocaleUpperCase()?.includes("COMMON")
  567. );
  568. partColumns.value = partList.map((item: any, index: number) => {
  569. // 因为合奏的,显示总谱会加一个声轨
  570. const defaultIndex = row.musicSheetType !== "SINGLE" && row.isScoreRender ? index + 1 : index
  571. const cnName = getInstrumentName(item.track)
  572. return {
  573. text: item.track + (cnName ? `(${cnName})` : ''),
  574. name: item.track + (cnName ? `(${cnName})` : ''), // true
  575. track: item.track,
  576. musicPdfUrl: item.musicPdfUrl,
  577. firstPdfUrl: item.firstPdfUrl,
  578. jianPdfUrl: item.jianPdfUrl,
  579. // xmlIndex,
  580. value: defaultIndex,
  581. };
  582. });
  583. staffData.details = row || {};
  584. // staffData.musicXml = staffData.details?.xmlFileUrl;
  585. staffData.isComberRender = staffData.details?.isScoreRender;
  586. let defaultShowStaff: any
  587. if(staffData.details?.musicSheetSoundList) {
  588. const soundCodes = filterSoundCodes(staffData.details?.musicSheetSoundList)
  589. if (soundCodes) {
  590. const soundCodesArr = soundCodes.split(",").map((code: string) => {
  591. return code
  592. .toLowerCase()
  593. .replace(/^\d+|\d+$/g, "")
  594. .trim()
  595. })
  596. defaultShowStaff = partColumns.value.find((item: any) =>
  597. soundCodesArr.includes(
  598. item.track &&
  599. item.track
  600. .toLowerCase()
  601. .replace(/^\d+|\d+$/g, "")
  602. .trim()
  603. )
  604. )
  605. if(defaultShowStaff) {
  606. staffData.selectedPartIndex = defaultShowStaff.value
  607. }
  608. }
  609. }
  610. if (row.musicSheetType === "SINGLE") {
  611. staffData.musicPdfUrl = getCurrentPdf(row, row.scoreType)
  612. // 生成的图片
  613. // staffData.imgs = row.musicImg ? row.musicImg.split(',') : [];
  614. } else {
  615. // 初始化数据
  616. // 是否显示总谱
  617. if (staffData.isComberRender) {
  618. partColumns.value.unshift({
  619. text: "总谱",
  620. value: 0,
  621. xmlIndex: 999,
  622. musicPdfUrl: row.musicPdfUrl,
  623. firstPdfUrl: row.firstPdfUrl,
  624. jianPdfUrl: row.jianPdfUrl,
  625. track: "",
  626. name: "总谱",
  627. });
  628. // 如果是总谱则默认选中,否则选择
  629. staffData.selectedPartIndex = row.defaultScoreRender ? 0 : defaultShowStaff ? defaultShowStaff.value : 1
  630. // staffData.selectedPartIndex = defaultShowStaff ? defaultShowStaff.value : 1
  631. let pdfUrl = ''
  632. if(row.defaultScoreRender) {
  633. pdfUrl = getCurrentPdf(row, row.scoreType)
  634. } else {
  635. pdfUrl = getCurrentPdf(defaultShowStaff, row.scoreType)
  636. }
  637. staffData.musicPdfUrl = pdfUrl
  638. } else {
  639. const item = partColumns.value.find((item: any) => item.value === staffData.selectedPartIndex)
  640. if (item) {
  641. staffData.musicPdfUrl = getCurrentPdf(item, row.scoreType) //item.musicPdfUrl;
  642. } else {
  643. staffData.musicPdfUrl = "";
  644. }
  645. }
  646. }
  647. // 通过isScoreRender判断是否合并渲染
  648. audioFileUrl.value = row.mp3Url
  649. if (
  650. audioFileUrl.value
  651. ) {
  652. // audioFileUrl.value = staffData.isConcert ? staffData.metronomeUrl : staffData.metronomeMp3Url
  653. nextTick(async () => {
  654. initAudio();
  655. });
  656. } else {
  657. renderStaff();
  658. }
  659. }
  660. const getPreViewCloud = (musicId: string, partIndex: number, track: string) => {
  661. const Authorization = sessionStorage.getItem("Authorization") || "";
  662. const musicScorePath = "/klx-music-score/";
  663. const musicScoreUrl = getHttpOrigin() + musicScorePath;
  664. // const musicScoreUrl = "https://test.colexiu.com" + musicScorePath;
  665. // const musicScoreUrl = 'http://192.168.3.68:3000/instrument.html';
  666. let href = `${musicScoreUrl}?t=${Date.now()}#/?id=${musicId}&Authorization=${Authorization}&isPreView=true&zoom=0.5&downPng=A4`;
  667. // // 老师端加上systemType=teacher
  668. href += ('&systemType=' +( state.platformType === "STUDENT" ? `student` : 'teacher'));
  669. // 总谱还是什么
  670. if(partIndex === 999) {
  671. href += `&part-index=${partIndex}`
  672. } else {
  673. href += `&part-name=${encodeURIComponent(track || '')}`
  674. }
  675. // musicRenderType
  676. // /** 五线谱 */
  677. // staff = "staff",
  678. // /** 简谱(首调) */
  679. // firstTone = "firstTone",
  680. // /** 固定音高 */
  681. // fixedTone = "fixedTone",
  682. if(staff.radio === "STAVE") {
  683. href += '&musicRenderType=staff'
  684. } else if(staff.radio === 'JIAN') {
  685. href += '&musicRenderType=fixedTone'
  686. } else if(staff.radio === 'FIRST') {
  687. href += '&musicRenderType=firstTone'
  688. }
  689. return href
  690. };
  691. const renderStaff = async () => {
  692. try {
  693. nextTick(() => {
  694. // console.log(staffData.musicPdfUrl, 'staffData.musicPdfUrl')
  695. if (staffData.musicPdfUrl) {
  696. const url = `${location.origin}${
  697. location.pathname
  698. }pdf/web/viewer-pdf.html?file=${encodeURIComponent(
  699. staffData.musicPdfUrl
  700. )}&t=${Date.now()}`
  701. const iframeRef = document.querySelector('#staffIframeRef') as any
  702. iframeRef.contentWindow.location.replace(url)
  703. // staffData.iframeSrc = url
  704. } else {
  705. const url = getPreViewCloud(staffData.musicId, currentColumn.value?.xmlIndex || 0, currentColumn.value?.track || '')
  706. const iframeRef = document.querySelector('#staffIframeRef') as any
  707. iframeRef.contentWindow.location.replace(url)
  708. // staffData.iframeSrc = url
  709. }
  710. })
  711. } catch (error) {
  712. //
  713. console.log(error, 'error');
  714. }
  715. }
  716. const resetRender = () => {
  717. const iframeRef: any = document.getElementById("staffIframeRef")
  718. iframeRef.contentWindow.location.replace(getPreViewCloud(staffData.musicId, currentColumn.value?.xmlIndex || 0, currentColumn.value.track))
  719. }
  720. return () => {
  721. return (
  722. <div class={styles.detail}>
  723. <ColSticky position="top">
  724. <div ref={headers}>
  725. <ColHeader
  726. background="transparent"
  727. hideHeader={false}
  728. border={false}
  729. isFixed={false}
  730. color="#fff"
  731. title={staffData.details?.name}
  732. backIconColor="white"
  733. v-slots={{
  734. right: () => (
  735. <div
  736. class={styles.shareBtn}
  737. style={{
  738. color: '#fff'
  739. }}
  740. onClick={onShare}
  741. >
  742. <Image src={iconShare} />
  743. 分享
  744. </div>
  745. )
  746. }}
  747. />
  748. </div>
  749. </ColSticky>
  750. <img class={styles.bgImg} src={staffData.details?.musicCover} />
  751. <div class={styles.bgContent}></div>
  752. <div
  753. class={styles.musicContainer}
  754. style={{
  755. marginTop: '16px',
  756. height: `calc(100vh - var(--header-height) - var(--bottom-height) - 16px)`
  757. }}
  758. >
  759. <div>
  760. <Cell
  761. border={false}
  762. center
  763. class={styles.musicInfo}
  764. v-slots={{
  765. icon: () => (
  766. <Image
  767. class={styles.pImg}
  768. src={ staffData.details?.musicCover}
  769. />
  770. ),
  771. title: () => (
  772. <div class={styles.info}>
  773. <h4
  774. class="van-ellipsis"
  775. // onClick={() => handleGotoMusicScore( staffData.details)}
  776. >
  777. { staffData.details?.name}
  778. </h4>
  779. <p
  780. style={{
  781. display: 'flex',
  782. alignItems: 'center'
  783. }}
  784. >
  785. {paymentType.value.map(
  786. tag =>
  787. tag && (
  788. <Tag
  789. style={{ color: colors[tag]?.color }}
  790. class={styles.tag}
  791. type="success"
  792. plain
  793. >
  794. {colors[tag].text}
  795. </Tag>
  796. )
  797. )}
  798. { staffData.details?.exquisiteFlag === 1 && (
  799. <Image
  800. class={styles.exquisiteFlag}
  801. src={getAssetsHomeFile('icon_exquisite.png')}
  802. />
  803. )}
  804. { staffData.details?.albumNums > 0 && (
  805. <Image
  806. class={styles.songAlbum}
  807. src={getAssetsHomeFile('icon_album_active.png')}
  808. />
  809. )}
  810. <span
  811. class={[
  812. styles.coomposer,
  813. browser().isApp &&
  814. staffData.details?.sourceType === 'TEACHER' &&
  815. state.platformType === 'STUDENT' &&
  816. styles.links
  817. ]}
  818. onClick={() => {
  819. if (
  820. browser().isApp &&
  821. staffData.details?.sourceType === 'TEACHER' &&
  822. state.platformType === 'STUDENT'
  823. ) {
  824. router.push({
  825. path: '/teacherHome',
  826. query: {
  827. teacherId: staffData.details?.userId,
  828. tabs: 'music'
  829. }
  830. })
  831. }
  832. }}
  833. >
  834. {/* <img class={styles.iconAlbum2} src={iconAlbum2} /> */}
  835. <span>
  836. { staffData.details?.userName ||
  837. '游客' + ( staffData.details?.userId || '')}
  838. </span>
  839. </span>
  840. </p>
  841. </div>
  842. ),
  843. value: () => (
  844. <>
  845. <span
  846. style={{
  847. visibility:
  848. state.platformType === 'TEACHER' && !route.query.tenantAlbumId
  849. ? 'visible'
  850. : 'hidden'
  851. }}
  852. class={styles.download}
  853. onClick={() => {
  854. if ( staffData.details?.coursewareStatus) {
  855. removeCourse()
  856. } else {
  857. onAddCourse()
  858. }
  859. }}
  860. >
  861. <img
  862. src={
  863. staffData.details?.coursewareStatus
  864. ? iconRemoveCourse
  865. : iconAddCourse
  866. }
  867. />
  868. <span>
  869. { staffData.details?.coursewareStatus
  870. ? '移出课件'
  871. : '添加课件'}
  872. </span>
  873. </span>
  874. <span
  875. class={styles.download}
  876. onClick={() => toggleFavorite()}
  877. >
  878. <img
  879. src={
  880. staffData.details?.favorite
  881. ? iconCollectActive
  882. : iconCollect
  883. }
  884. />
  885. <span>收藏</span>
  886. </span>
  887. </>
  888. )
  889. }}
  890. />
  891. <div class={styles.functionSection}>
  892. <div
  893. class={styles.functionItem}
  894. onClick={() => {
  895. router.push({
  896. path: '/look-album-list',
  897. query: {
  898. id: staffData.details?.id,
  899. musicSubject: staffData.details?.musicSubject
  900. }
  901. })
  902. }}
  903. >
  904. <img src={iconAlbum} />
  905. <span>专辑</span>
  906. </div>
  907. <div
  908. class={styles.functionItem}
  909. style={{
  910. display:
  911. staffData.details?.musicSheetType === 'CONCERT'
  912. ? ''
  913. : 'none'
  914. }}
  915. onClick={() => {
  916. if ( staffData.details?.musicSheetType === 'CONCERT') {
  917. staffData.open = true
  918. staffData.closed = false;
  919. }
  920. }}
  921. >
  922. <img src={iconChange} />
  923. <span>切换乐器</span>
  924. </div>
  925. {/* 独奏的才有转谱功能 */}
  926. {defaultConvertible.value.isConvertibleScore && staffData.details?.musicSheetType === 'SINGLE' ? (
  927. <div
  928. class={styles.functionItem}
  929. style={{
  930. display:
  931. staffData.details?.musicSheetType === 'SINGLE'
  932. ? ''
  933. : 'none'
  934. }}
  935. onClick={() => {
  936. staff.status = true
  937. }}
  938. >
  939. <img src={iconChange} />
  940. <span>转谱</span>
  941. </div>
  942. ) : null}
  943. <div
  944. class={[styles.functionItem, (loading.value ||
  945. (!staffData.musicPdfUrl && staffData.imgs.length <= 0)) ? styles.disabled : '']}
  946. onClick={() => {
  947. // console.log(loading.value, staffData.musicPdfUrl, staffData.imgs, '---------------')
  948. if((loading.value ||
  949. (!staffData.musicPdfUrl && staffData.imgs.length <= 0))) return
  950. if (staffData.musicPdfUrl) {
  951. const songName =
  952. staffData.details?.name +
  953. (staffData.isConcert &&
  954. currentColumn.value?.name
  955. ? `(${
  956. currentColumn.value
  957. ?.name || ""
  958. })`
  959. : "");
  960. promisefiyPostMessage({
  961. api: "downloadFile",
  962. content: {
  963. downloadUrl: staffData.musicPdfUrl,
  964. fileName: songName,
  965. },
  966. });
  967. } else {
  968. downloadStatus.value = true;
  969. }
  970. }}
  971. >
  972. <img src={iconDownload} />
  973. <span>下载</span>
  974. </div>
  975. </div>
  976. </div>
  977. <div class={styles.musicContent}>
  978. {staffData.details.id ? (
  979. staffData.musicPdfUrl ? (
  980. <iframe
  981. style={{
  982. opacity: loading.value ? 0 : 1,
  983. }}
  984. id="staffIframeRef"
  985. src={staffData.iframeSrc}
  986. onLoad={() => {
  987. // 判断是用哪个渲染的
  988. loading.value = false;
  989. }}
  990. ></iframe>
  991. ) : (
  992. <>
  993. <p class={styles.musicTitle}>
  994. {staffData.details?.name && (
  995. <>
  996. {staffData.details?.name}
  997. {staffData.isConcert &&
  998. currentColumn.value?.name
  999. ? `(${
  1000. currentColumn.value
  1001. ?.name || ""
  1002. })`
  1003. : ""}
  1004. </>
  1005. )}
  1006. </p>
  1007. {/* {loading.value && (
  1008. <>
  1009. <Vue3Lottie
  1010. animationData={AstronautJSON}
  1011. class={styles.finch}
  1012. ></Vue3Lottie>
  1013. <p class={styles.finchLoad}>加载中...</p>
  1014. </>
  1015. )} */}
  1016. <iframe
  1017. id="staffIframeRef"
  1018. style={{
  1019. opacity: loading.value ? 0 : 1,
  1020. }}
  1021. src={staffData.iframeSrc}
  1022. onLoad={() => {
  1023. loading.value = false;
  1024. // musicIframeLoad();
  1025. }}
  1026. ></iframe>
  1027. </>
  1028. )
  1029. ) : null}
  1030. </div>
  1031. </div>
  1032. {staffData.details?.id && (
  1033. <ColSticky
  1034. position="bottom"
  1035. background="white"
  1036. varName="--bottom-height"
  1037. >
  1038. <div ref={footers}>
  1039. <div class={styles.videoOperation}>
  1040. {audioFileUrl.value && (
  1041. <>
  1042. {!buyState.value.play &&
  1043. freeRate.value != 100 &&
  1044. freeRate.value != 0 && (
  1045. <div class={[styles.audition]}>
  1046. <img src={iconListen} />
  1047. <span>每首曲目可试听{freeRate.value}%</span>
  1048. </div>
  1049. )}
  1050. <div class={[styles.audio, styles.collectCell]}>
  1051. <audio id="player" controls ref={audio}>
  1052. <source src={audioFileUrl.value} type="audio/mp3" />
  1053. </audio>
  1054. </div>
  1055. </>
  1056. )}
  1057. </div>
  1058. {/* 判断是否是免费的,或者已经购买过,是否从专辑过来的 */}
  1059. {buyState.value.play ||
  1060. (state.platformType === 'TEACHER' &&
  1061. buyState.value.hasTenantAlbum) ? (
  1062. <Button
  1063. round
  1064. block
  1065. type="primary"
  1066. color="linear-gradient(180deg, #59E5D5 0%, #2DC7AA 100%)"
  1067. onClick={() => {
  1068. const throttleFn = useThrottleFn(() => {
  1069. player.value && player.value.stop()
  1070. // 新版云教练的谱面类型使用musicRenderType字段
  1071. let musicRenderType = ''
  1072. if(staff.radio === "STAVE") {
  1073. musicRenderType = 'staff'
  1074. } else if(staff.radio === 'JIAN') {
  1075. musicRenderType = 'fixedTone'
  1076. } else if(staff.radio === 'FIRST') {
  1077. musicRenderType = 'firstTone'
  1078. }
  1079. let extraParam: any = {
  1080. // 'part-index': currentColumn.value.track || 0,
  1081. 'part-name': encodeURIComponent(currentColumn.value?.track?.trim() || ''),
  1082. musicRenderType,
  1083. instrumentId: staffData.instrumentId
  1084. }
  1085. // 有专辑id
  1086. if (route.query.tenantAlbumId) {
  1087. extraParam.albumId = route.query.tenantAlbumId
  1088. } else if (browser().isTeacher && route.query.providerType == 'TENANT') {
  1089. // 机构老师端
  1090. extraParam.albumId = 1
  1091. }
  1092. musicBuy({
  1093. id: staffData.details.id
  1094. }, () => {}, extraParam)
  1095. }, 300)
  1096. throttleFn()
  1097. }}
  1098. >
  1099. 立即练习
  1100. </Button>
  1101. ) : (
  1102. <div class={styles.colSticky}>
  1103. {/* 只有,有点播类型的才显示价格 */}
  1104. {buyState.value.charge && (
  1105. <div class={styles.priceSection}>
  1106. <span>点播价:</span>
  1107. <span class={styles.price}>
  1108. <i>¥</i>
  1109. {moneyFormat(staffData.details?.musicPrice)}
  1110. </span>
  1111. </div>
  1112. )}
  1113. <div class={[styles.buyBtn]}>
  1114. {/* 判断是否是需要收费的 */}
  1115. {buyState.value.charge && (
  1116. <Button
  1117. round
  1118. type="primary"
  1119. color="linear-gradient(180deg, #59E5D5 0%, #2DC7AA 100%)"
  1120. class={styles.primary}
  1121. onClick={onBuy}
  1122. >
  1123. 立即点播
  1124. </Button>
  1125. )}
  1126. {/* 判断是否有会员的 */}
  1127. {buyState.value.vip && (
  1128. <Button
  1129. round
  1130. block={!buyState.value.charge ? true : false}
  1131. type="primary"
  1132. color="linear-gradient(180deg, #F7BD8D 0%, #CD8806 100%)"
  1133. class={styles.memeber}
  1134. onClick={() => {
  1135. router.push({
  1136. path: '/memberCenter',
  1137. query: {
  1138. ...route.query
  1139. }
  1140. })
  1141. }}
  1142. >
  1143. {studentActivityId.value > 0 && (
  1144. <div class={[styles.buttonDiscount]}>专属优惠</div>
  1145. )}
  1146. 开通会员
  1147. </Button>
  1148. )}
  1149. </div>
  1150. </div>
  1151. )}
  1152. </div>
  1153. </ColSticky>
  1154. )}
  1155. <Popup
  1156. v-model:show={shareStatus.value}
  1157. style={{ background: 'transparent' }}
  1158. teleport="body"
  1159. >
  1160. <ColShare
  1161. teacherId={state.user.data?.userId}
  1162. shareUrl={shareUrl.value}
  1163. shareType="music"
  1164. >
  1165. <div class={styles.shareMate}>
  1166. {shareDiscount.value === 1 && (
  1167. <div class={styles.tagDiscount}>专属优惠</div>
  1168. )}
  1169. <img
  1170. class={styles.icon}
  1171. crossorigin="anonymous"
  1172. src={staffData.details?.musicCover}
  1173. />
  1174. <div class={styles.info}>
  1175. <h4 class="van-multi-ellipsis--l2">
  1176. {staffData.details?.name}
  1177. </h4>
  1178. <p>作曲人:{staffData.details?.composer}</p>
  1179. </div>
  1180. </div>
  1181. </ColShare>
  1182. </Popup>
  1183. <Popup v-model:show={downloadStatus.value} position="bottom" round>
  1184. {downloadStatus.value && (
  1185. <Download
  1186. imgList={JSON.parse(JSON.stringify(staffData.imgs))}
  1187. musicSheetName={
  1188. staffData.details.name +
  1189. (staffData.isConcert &&
  1190. currentColumn.value?.name
  1191. ? `(${
  1192. currentColumn.value?.name || ""
  1193. })`
  1194. : "")
  1195. }
  1196. />
  1197. )}
  1198. </Popup>
  1199. <Popup
  1200. v-model:show={staff.status}
  1201. teleport="body"
  1202. closeable
  1203. style={{ width: '80%' }}
  1204. round
  1205. >
  1206. <div class={styles.staffContainer}>
  1207. <div class={styles.staffTitle}>选择转换曲谱</div>
  1208. <RadioGroup v-model={staff.radio}>
  1209. <CellGroup border={false}>
  1210. {!defaultConvertible.value.isHiddenStaff ? <Cell
  1211. center
  1212. border={false}
  1213. class={staff.radio === 'STAVE' ? styles.active : ''}
  1214. onClick={() => onChangeStaff('STAVE')}
  1215. >
  1216. {{
  1217. icon: () => (
  1218. <Image src={staffDetafult} class={styles.staffImg} />
  1219. ),
  1220. title: () => <span class={styles.name}>五线谱</span>,
  1221. value: () => (
  1222. <Radio name="STAVE">
  1223. {{
  1224. icon: (props: any) => (
  1225. <Icon
  1226. class={styles.boxStyle}
  1227. size={16}
  1228. name={
  1229. props.checked
  1230. ? activeButtonIcon
  1231. : inactiveButtonIcon
  1232. }
  1233. />
  1234. )
  1235. }}
  1236. </Radio>
  1237. )
  1238. }}
  1239. </Cell> : ""}
  1240. <Cell
  1241. center
  1242. border={false}
  1243. class={staff.radio === 'FIRST' ? styles.active : ''}
  1244. onClick={() => onChangeStaff('FIRST')}
  1245. >
  1246. {{
  1247. icon: () => (
  1248. <Image src={firstDefault} class={styles.staffImg} />
  1249. ),
  1250. title: () => <span class={styles.name}>简谱-首调</span>,
  1251. value: () => (
  1252. <Radio name="FIRST">
  1253. {{
  1254. icon: (props: any) => (
  1255. <Icon
  1256. class={styles.boxStyle}
  1257. size={16}
  1258. name={
  1259. props.checked
  1260. ? activeButtonIcon
  1261. : inactiveButtonIcon
  1262. }
  1263. />
  1264. )
  1265. }}
  1266. </Radio>
  1267. )
  1268. }}
  1269. </Cell>
  1270. <Cell
  1271. center
  1272. border={false}
  1273. class={staff.radio === 'JIAN' ? styles.active : ''}
  1274. onClick={() => onChangeStaff('JIAN')}
  1275. >
  1276. {{
  1277. icon: () => (
  1278. <Image src={fixedDefault} class={styles.staffImg} />
  1279. ),
  1280. title: () => <span class={styles.name}>简谱-固定调</span>,
  1281. value: () => (
  1282. <Radio name="JIAN">
  1283. {{
  1284. icon: (props: any) => (
  1285. <Icon
  1286. class={styles.boxStyle}
  1287. size={16}
  1288. name={
  1289. props.checked
  1290. ? activeButtonIcon
  1291. : inactiveButtonIcon
  1292. }
  1293. />
  1294. )
  1295. }}
  1296. </Radio>
  1297. )
  1298. }}
  1299. </Cell>
  1300. </CellGroup>
  1301. </RadioGroup>
  1302. </div>
  1303. </Popup>
  1304. <Popup
  1305. teleport="body"
  1306. position="bottom"
  1307. round
  1308. v-model:show={staffData.open}
  1309. onClosed={() => {
  1310. staffData.closed = true;
  1311. }}
  1312. >
  1313. {!staffData.closed && (
  1314. <Picker
  1315. columns={partColumns.value}
  1316. defaultIndex={staffData.selectedPartIndex}
  1317. onConfirm={value => {
  1318. staffData.open = false;
  1319. staffData.selectedPartIndex = value.value;
  1320. staffData.imgs = [];
  1321. nextTick(() => {
  1322. const item = partColumns.value.find((child: any) => child.value === value.value)
  1323. const tempPdf = getCurrentPdf(item, staff.radio)
  1324. staffData.musicPdfUrl = tempPdf;
  1325. if (tempPdf) {
  1326. renderStaff();
  1327. } else {
  1328. loading.value = true;
  1329. if (staffData.musicPdfUrl) {
  1330. resetRender();
  1331. } else {
  1332. renderStaff();
  1333. }
  1334. }
  1335. });
  1336. }}
  1337. onCancel={() => (staffData.open = false)}
  1338. />
  1339. )}
  1340. </Popup>
  1341. </div>
  1342. )
  1343. }
  1344. }
  1345. })