index-share.tsx 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882
  1. import {
  2. defineComponent,
  3. onMounted,
  4. onUnmounted,
  5. reactive,
  6. ref,
  7. watch,
  8. nextTick,
  9. onBeforeMount
  10. } from 'vue';
  11. // import WaveSurfer from 'wavesurfer.js';
  12. // import Regions from 'wavesurfer.js/dist/plugins/regions.js';
  13. import styles from './index.module.less';
  14. import MHeader from '@/components/m-header';
  15. import { Cell, Image, List, Popup, Sticky, TextEllipsis, NoticeBar, showConfirmDialog, showToast } from 'vant';
  16. import iconMember from './images/icon-member.png';
  17. import iconZan from './images/icon-zan.png';
  18. import iconZanActive from './images/icon-zan-active.png';
  19. import logoImg from './images/logo.png';
  20. import logo1Img from './images/logo1.png';
  21. import iconUpward from './images/upward.png';
  22. import iconPlay from './images/icon-play.png';
  23. import iconPause from './images/icon-pause.png';
  24. import audioPan from './images/audio-pan.png';
  25. import audioLabel from './share-model/images/audioLabel.png';
  26. import videoLabel from './share-model/images/videoLabel.png';
  27. import musicBg from './share-model/images/music-bg.png';
  28. import playImg from './images/play.png';
  29. import { browser, getGradeCh, getSecondRPM, vaildMusicScoreUrl } from '@/helpers/utils';
  30. import { onBeforeRouteUpdate, useRoute, useRouter, onBeforeRouteLeave } from 'vue-router';
  31. import {
  32. postMessage
  33. } from '@/helpers/native-message';
  34. import {
  35. api_openUserMusicDetail,
  36. api_openUserMusicPage,
  37. api_userMusicStar,
  38. api_verification
  39. } from './api';
  40. import MEmpty from '@/components/m-empty';
  41. import MVideo from '@/components/m-video';
  42. import LoginModel from './login-model';
  43. import { removeAuth } from '../student-register/layout/utils';
  44. import { setLogout } from '@/state';
  45. import { storage } from '@/helpers/storage';
  46. import { ACCESS_TOKEN } from '@/store/mutation-types';
  47. import MWxTip from '@/components/m-wx-tip';
  48. import { usePageVisibility, useEventListener, useWindowSize } from '@vant/use';
  49. import LoginChangeModel from './login-change-model';
  50. import MSticky from '@/components/m-sticky';
  51. import "plyr/dist/plyr.css";
  52. import Plyr from "plyr";
  53. import { Vue3Lottie } from "vue3-lottie";
  54. import audioBga from "./images/audioBga.json";
  55. import audioBga1 from "./images/leftCloud.json";
  56. import audioBga2 from "./images/rightCloud.json";
  57. import videobg from "./images/videobg.png";
  58. import btnImg from './images/btn.png';
  59. import audioVisualDraw from "./audioVisualDraw"
  60. //import playProgressData from "./playCreation/playProgress"
  61. import Loading from './loading';
  62. import backImg from "./images/back.png";
  63. import wxBg from '../download/images/wx_bg.png';
  64. export default defineComponent({
  65. name: 'creation-detail',
  66. setup() {
  67. const {isApp, isTablet, weixin} = browser()
  68. const route = useRoute();
  69. const router = useRouter();
  70. const isScreenScroll = ref(false)
  71. const creationHeight = ref(0)
  72. const state = reactive({
  73. id: route.query.id,
  74. isEmpty:false,
  75. loginTag: false, // 是否登录标识
  76. loginStatus: false,
  77. loginChangeState: false, // 切换账号
  78. credential: {} as any,
  79. playType: '' as 'Audio' | 'Video' | '', // 播放类型
  80. musicDetail: {} as any,
  81. isClick: false,
  82. list: [] as any,
  83. listState: {
  84. dataShow: true, // 判断是否有数据
  85. loading: false,
  86. finished: false
  87. },
  88. params: {
  89. page: 1,
  90. rows: 4
  91. },
  92. messageStatus: false,
  93. message: '',
  94. _plrl: null as any,
  95. heightV:0,
  96. heightB:0
  97. });
  98. const plyrState = reactive({
  99. duration: 0,
  100. currentTime: 0,
  101. mediaTimeShow: false,
  102. playIngShow: true,
  103. loaded:false
  104. })
  105. // 谱面
  106. const staffState = reactive({
  107. staffSrc: "",
  108. isShow: false,
  109. height:"initial",
  110. speedRate:1,
  111. musicRenderType:"staff",
  112. partIndex:0
  113. })
  114. const isLandscapeScreen = ref(false)
  115. const wxStatus = ref(false)
  116. const staffDom= ref<HTMLIFrameElement>()
  117. const {playStaff, pauseStaff, updateProgressStaff} = staffMoveInstance()
  118. // 点赞
  119. const onStarChange = async () => {
  120. await checkLogin();
  121. // 是否登录
  122. if (!state.loginTag) {
  123. state.loginStatus = true;
  124. return;
  125. }
  126. try {
  127. await api_userMusicStar({
  128. userMusicId: state.id,
  129. star: !state.musicDetail.starFlag
  130. });
  131. state.musicDetail.starFlag = !state.musicDetail.starFlag;
  132. if (state.musicDetail.starFlag) {
  133. state.musicDetail.likeNum += 1;
  134. } else {
  135. state.musicDetail.likeNum -= 1;
  136. }
  137. } catch {
  138. //
  139. }
  140. };
  141. // 获取列表
  142. const getList = async () => {
  143. try {
  144. if (state.isClick) return;
  145. state.isClick = true;
  146. const res = await api_openUserMusicPage({
  147. type: 'FORMAL',
  148. exclusionId: state.id,
  149. sort: 1,
  150. ...state.params
  151. });
  152. state.listState.loading = false;
  153. const result = res.data || {};
  154. // 处理重复请求数据
  155. // if (state.list.length > 0 && result.current === 1) {
  156. // return;
  157. // }
  158. state.list = result.rows || [];
  159. state.listState.finished = result.current >= result.pages;
  160. state.params.page = result.current + 1;
  161. state.listState.dataShow = state.list.length > 0;
  162. state.isClick = false;
  163. } catch {
  164. state.listState.dataShow = false;
  165. state.listState.finished = true;
  166. state.isClick = false;
  167. }
  168. };
  169. function handleChangeList() {
  170. if(state.listState.finished){
  171. state.listState.finished = false
  172. state.params.page = 1;
  173. getList()
  174. }else{
  175. getList()
  176. }
  177. }
  178. const onDetail = (item: any) => {
  179. // playProgressData.playProgress = 0
  180. // playProgressData.playState = false
  181. router.push({
  182. path: '/shareCreation',
  183. query: {
  184. id: item.id
  185. }
  186. });
  187. };
  188. // 初始化 媒体播放
  189. function initMediaPlay(){
  190. const id = state.playType === "Audio" ? "#audioMediaSrc" : "#videoMediaSrc";
  191. state._plrl = new Plyr(id, {
  192. controls: ["play", "progress", "current-time", "duration"],
  193. fullscreen: {
  194. enabled: false,
  195. fallback: false
  196. }
  197. });
  198. const player = state._plrl
  199. // 创建音波数据
  200. if(state.playType === "Audio"){
  201. const audioDom = document.querySelector("#audioMediaSrc") as HTMLAudioElement
  202. const canvasDom = document.querySelector("#audioVisualizer") as HTMLCanvasElement
  203. const { pauseVisualDraw, playVisualDraw } = audioVisualDraw(audioDom, canvasDom)
  204. player.on('play', () => {
  205. playVisualDraw()
  206. });
  207. player.on('pause', () => {
  208. pauseVisualDraw()
  209. });
  210. }
  211. // 在微信中运行的时候,微信没有开放自动加载资源的权限,所以要等播放之后才显示播放控制器
  212. player.on('loadedmetadata', () => {
  213. plyrState.loaded = true
  214. //player.currentTime = playProgressData.playProgress
  215. });
  216. player.on("timeupdate", ()=>{
  217. plyrState.currentTime = player.currentTime
  218. })
  219. player.on('play', () => {
  220. plyrState.playIngShow = false
  221. playStaff()
  222. });
  223. player.on('pause', () => {
  224. plyrState.playIngShow = true
  225. pauseStaff()
  226. });
  227. player.on('ended', () => {
  228. player.currentTime = 0
  229. if(!player.playing){
  230. setTimeout(() => {
  231. updateProgressStaff(player.currentTime)
  232. }, 100);
  233. }
  234. });
  235. // 处理按压事件
  236. const handleStart = () => {
  237. if(isLandscapeScreen.value){
  238. return
  239. }
  240. plyrState.duration = player.duration
  241. plyrState.mediaTimeShow = true
  242. };
  243. // 处理松开事件
  244. const handleEnd = () => {
  245. plyrState.mediaTimeShow = false
  246. // 暂停的时候调用
  247. if(!player.playing){
  248. updateProgressStaff(player.currentTime)
  249. }
  250. };
  251. const progressDom = document.querySelector("#playMediaSection .plyr__controls .plyr__progress__container") as HTMLElement
  252. progressDom.addEventListener('mousedown', handleStart);
  253. progressDom.addEventListener('touchstart', handleStart);
  254. progressDom.addEventListener('mouseup', handleEnd);
  255. progressDom.addEventListener('touchend', handleEnd);
  256. }
  257. //点击改变播放状态
  258. function handlerClickPlay(event?:MouseEvent){
  259. // 原生 播放暂停按钮 点击的时候 不触发
  260. // @ts-ignore
  261. if(event?.target?.matches('button.plyr__control')){
  262. return
  263. }
  264. const player = state._plrl;
  265. if (player.playing) {
  266. player.pause();
  267. } else {
  268. player.play();
  269. }
  270. }
  271. function handlerBack(event:any){
  272. event.stopPropagation()
  273. verticalScreen()
  274. }
  275. function landscapeScreen(){
  276. postMessage({
  277. api: "setRequestedOrientation",
  278. content: {
  279. orientation: 0,
  280. },
  281. });
  282. isLandscapeScreen.value = true
  283. }
  284. function verticalScreen(){
  285. postMessage({
  286. api: "setRequestedOrientation",
  287. content: {
  288. orientation: 1,
  289. },
  290. });
  291. isLandscapeScreen.value = false
  292. }
  293. function handlerLandscapeScreen(event:any){
  294. event.stopPropagation()
  295. if(isApp){
  296. landscapeScreen()
  297. return
  298. }
  299. if(weixin){
  300. wxStatus.value = true
  301. }else{
  302. const t = Date.now()
  303. const str = location.href
  304. shareCall(str)
  305. setTimeout(() => {
  306. if(Date.now() - t < 3500){
  307. window.location.href = location.origin + '/classroom-app/#/transfer'
  308. }
  309. }, 3000)
  310. }
  311. // showConfirmDialog({
  312. // className: "dialogMusicClass",
  313. // message: '即将离开\n打开“音乐数字课堂”',
  314. // confirmButtonText: "允许",
  315. // cancelButtonText: "取消"
  316. // }).then(() => {
  317. // const str = location.href
  318. // shareCall(str)
  319. // });
  320. // playProgressData.playState = !!state._plrl?.playing
  321. // playProgressData.playProgress = state._plrl?.currentTime || 0
  322. // router.push({
  323. // path:"/playCreation",
  324. // query:{
  325. // resourceUrl:encodeURIComponent(state.musicDetail?.videoUrl),
  326. // videoBgUrl:encodeURIComponent(state.musicDetail?.videoImg || ""),
  327. // musicSheetName:encodeURIComponent(state.musicDetail?.musicSheetName),
  328. // username:encodeURIComponent(state.musicDetail?.username),
  329. // musicSheetId:encodeURIComponent(state.musicDetail?.musicSheetId),
  330. // speedRate:encodeURIComponent(staffState.speedRate),
  331. // musicRenderType:encodeURIComponent(staffState.musicRenderType),
  332. // partIndex:encodeURIComponent(staffState.partIndex),
  333. // }
  334. // })
  335. }
  336. const shareCall = (str: string, params?: any) => {
  337. const query = {
  338. url: str,
  339. action: params?.action || 'h5', // app, h5
  340. pageTag: params?.pageTag || 1 // 页面标识
  341. }
  342. const iosStr = encodeURIComponent(JSON.stringify(query))
  343. const userAgent = navigator.userAgent || navigator.vendor;
  344. const platform = navigator.platform || 'unknown';
  345. if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent) || (platform === 'MacIntel')) {
  346. window.location.href = `BandInstrumentTeam://linkUrl=${iosStr}`
  347. } else if (/(Android)/i.test(userAgent)) {
  348. window.location.href = `colexiukt://html:8888/SplashActivity?url=${iosStr}`
  349. } else {
  350. showToast('请用手机或移动设备打开')
  351. }
  352. }
  353. const checkLogin = async () => {
  354. try {
  355. // 判断是否登录
  356. const Authorization = storage.get(ACCESS_TOKEN) || '';
  357. if (Authorization) {
  358. const res = await api_verification({
  359. token: Authorization
  360. });
  361. console.log(res.data, 'res.data');
  362. state.loginTag = res.data;
  363. if (!res.data) {
  364. removeAuth();
  365. setLogout();
  366. }
  367. }
  368. } catch (err) {
  369. //
  370. storage.remove(ACCESS_TOKEN);
  371. removeAuth();
  372. setLogout();
  373. state.loginTag = false;
  374. }
  375. };
  376. const __init = async () => {
  377. await checkLogin();
  378. try {
  379. const res = await api_openUserMusicDetail(state.id);
  380. if (res.code === 999) {
  381. // 没有的时候显示缺省页
  382. state.isEmpty = true
  383. staffState.isShow = true
  384. return;
  385. } else {
  386. state.musicDetail = res.data;
  387. try{
  388. const jsonConfig = JSON.parse(res.data.jsonConfig)
  389. jsonConfig.speedRate && (staffState.speedRate = jsonConfig.speedRate)
  390. jsonConfig.musicRenderType && (staffState.musicRenderType = jsonConfig.musicRenderType)
  391. jsonConfig.partIndex && (staffState.partIndex = jsonConfig.partIndex)
  392. }catch{
  393. }
  394. // 五线谱
  395. initStaff()
  396. getList();
  397. // 判断是视频还是音频
  398. if (res.data.videoUrl.lastIndexOf('mp4') !== -1) {
  399. state.playType = 'Video';
  400. } else {
  401. state.playType = 'Audio';
  402. }
  403. // 初始化
  404. nextTick(() => {
  405. initMediaPlay();
  406. });
  407. }
  408. } catch (err:any) {
  409. // 没有的时候显示缺省页
  410. state.message = err;
  411. state.messageStatus = true;
  412. }
  413. };
  414. // 滚动事件
  415. const cleanScrollEvent = useEventListener('scroll', () => {
  416. // 作品已删除不让滚动变色
  417. if(state.isEmpty) return
  418. const height =
  419. window.scrollY ||
  420. document.documentElement.scrollTop
  421. // 防止多次调用
  422. if(height > 0 && isScreenScroll.value === false){
  423. isScreenScroll.value = true
  424. if(isApp){
  425. setStatusBarTextColor(false)
  426. }
  427. }
  428. if(height <= 0){
  429. isScreenScroll.value = false
  430. if(isApp){
  431. setStatusBarTextColor(true)
  432. }
  433. }
  434. })
  435. function handlerDownLoad(){
  436. if(weixin){
  437. wxStatus.value = true
  438. }else{
  439. router.push({
  440. path:"/transfer"
  441. })
  442. }
  443. }
  444. const pageVisibility = usePageVisibility();
  445. watch(pageVisibility, value => {
  446. if (value === 'hidden') {
  447. state._plrl?.pause();
  448. }
  449. });
  450. // 初始化五线谱
  451. function initStaff(){
  452. const src = `${vaildMusicScoreUrl()}/instrument/#/simple-detail?id=${state.musicDetail.musicSheetId}&musicRenderType=${staffState.musicRenderType}&part-index=${staffState.partIndex}`;
  453. //const src = `http://192.168.3.122:3000/instrument.html#/simple-detail?id=${state.musicDetail.musicSheetId}&musicRenderType=${staffState.musicRenderType}&part-index=${staffState.partIndex}`;
  454. staffState.staffSrc = src
  455. window.addEventListener('message', (event) => {
  456. const { api, height } = event.data;
  457. if (api === 'api_musicPage') {
  458. staffState.isShow = true
  459. staffState.height = height + "px"
  460. // 如果是播放中自动开始播放 不是播放 自动跳转到当前位置
  461. // if(playProgressData.playState){
  462. // handlerClickPlay()
  463. // }else{
  464. // updateProgressStaff(state._plrl.currentTime)
  465. // }
  466. }
  467. });
  468. }
  469. function staffMoveInstance(){
  470. let isPause = true
  471. const requestAnimationFrameFun = () => {
  472. requestAnimationFrame(() => {
  473. staffDom.value?.contentWindow?.postMessage(
  474. {
  475. api: 'api_playProgress',
  476. content: {
  477. currentTime: state._plrl.currentTime * staffState.speedRate
  478. }
  479. },
  480. "*"
  481. )
  482. if (!isPause) {
  483. requestAnimationFrameFun()
  484. }
  485. })
  486. }
  487. const playStaff = () => {
  488. // 没渲染不执行
  489. if(!staffState.isShow) return
  490. isPause = false
  491. staffDom.value?.contentWindow?.postMessage(
  492. {
  493. api: 'api_play'
  494. },
  495. "*"
  496. )
  497. requestAnimationFrameFun()
  498. }
  499. const pauseStaff = () => {
  500. // 没渲染不执行
  501. if(!staffState.isShow) return
  502. isPause = true
  503. staffDom.value?.contentWindow?.postMessage(
  504. {
  505. api: 'api_paused'
  506. },
  507. "*"
  508. )
  509. }
  510. const updateProgressStaff = (currentTime: number) => {
  511. // 没渲染不执行
  512. if(!staffState.isShow) return
  513. staffDom.value?.contentWindow?.postMessage(
  514. {
  515. api: 'api_updateProgress',
  516. content: {
  517. currentTime: currentTime * staffState.speedRate
  518. }
  519. },
  520. "*"
  521. )
  522. }
  523. return {
  524. playStaff,
  525. pauseStaff,
  526. updateProgressStaff
  527. }
  528. }
  529. // 设置导航栏颜色
  530. function setStatusBarTextColor(isWhite:boolean){
  531. postMessage({
  532. api: 'setStatusBarTextColor',
  533. content: { statusBarTextColor: isWhite }
  534. })
  535. }
  536. function setFullHeight(){
  537. creationHeight.value = window.innerHeight
  538. }
  539. onBeforeMount(() => {
  540. if(isApp) {
  541. postMessage({
  542. api: "setRequestedOrientation",
  543. content: {
  544. orientation: 1,
  545. },
  546. });
  547. setStatusBarTextColor(true)
  548. }
  549. })
  550. onMounted(async () => {
  551. __init();
  552. setFullHeight()
  553. window.addEventListener('resize', setFullHeight)
  554. });
  555. onUnmounted(() => {
  556. cleanScrollEvent()
  557. window.removeEventListener('resize', setFullHeight)
  558. state._plrl?.destroy()
  559. });
  560. onBeforeRouteUpdate((to: any) => {
  561. state.id = to.query.id;
  562. state.playType = '';
  563. state.params.page = 1;
  564. state.list = [];
  565. if(state._plrl){
  566. state._plrl.destroy()
  567. }
  568. plyrState.playIngShow = true
  569. staffState.staffSrc = ""
  570. staffState.isShow = false
  571. staffState.height = "initial"
  572. __init();
  573. });
  574. // onBeforeRouteLeave((to, from, next)=>{
  575. // if(to.path !== "/playCreation"){
  576. // playProgressData.playProgress = 0
  577. // playProgressData.playState = false
  578. // }
  579. // next()
  580. // })
  581. return () => (
  582. <div
  583. style={
  584. {
  585. '--barheight':state.heightV + "px",
  586. "--creationHeight":creationHeight.value ? creationHeight.value+"px" : "100vh"
  587. }
  588. }
  589. class={[
  590. styles.creation,
  591. isTablet ? styles.creationTablet : '',
  592. isScreenScroll.value && styles.isShareScreenScroll
  593. ]}>
  594. <div class={styles.creationBg}></div>
  595. <MSticky position="top"
  596. onBarHeight={(height: any) => {
  597. console.log(height, 'height', height)
  598. state.heightV = height
  599. }}
  600. >
  601. {
  602. isApp ? <MHeader
  603. color={isScreenScroll.value ? "#333333" : "#ffffff"}
  604. background={isScreenScroll.value ? `rgb(255,255,255` : "transparent"}
  605. border={false}
  606. isBack={route.query.platformType != 'ANALYSIS'}
  607. title={"作品详情"}
  608. onLeftClick={()=>{ setStatusBarTextColor(false) }}
  609. />
  610. : <div class={styles.logoDownload}>
  611. <img src={isScreenScroll.value ? logo1Img : logoImg} class={styles.logoImg}></img>
  612. <div class={styles.logTit} onClick={handlerDownLoad}>下载App</div>
  613. </div>
  614. }
  615. </MSticky>
  616. {
  617. state.isEmpty ?
  618. <div class={styles.isEmpty}>
  619. <MEmpty image={"empty2"} description="作品已删除~" />
  620. </div> :
  621. <>
  622. <div class={styles.singerBox}>
  623. <div class={styles.musicSheetName}>
  624. <NoticeBar
  625. text={state.musicDetail?.musicSheetName}
  626. background="none"
  627. />
  628. </div>
  629. <div class={styles.singerName}>
  630. 演奏:{state.musicDetail?.username}
  631. </div>
  632. </div>
  633. <Sticky zIndex={1000} offsetTop={state.heightV - 1 + "px"}>
  634. <div class={[styles.playSection, plyrState.mediaTimeShow && styles.mediaTimeShow,!plyrState.loaded && styles.notLoaded,isLandscapeScreen.value&&styles.isLandscapeScreen]} id="playMediaSection" onClick={handlerClickPlay}>
  635. {
  636. isLandscapeScreen.value &&
  637. <div class={styles.backBox}>
  638. <img class={styles.backImg} src={backImg} onClick={handlerBack}/>
  639. <div class={styles.musicDetail}>
  640. <div class={styles.musicSheetName}>
  641. <NoticeBar
  642. text={state.musicDetail?.musicSheetName}
  643. background="none"
  644. />
  645. </div>
  646. <div class={styles.username}>演奏:{state.musicDetail?.username}</div>
  647. </div>
  648. </div>
  649. }
  650. {
  651. state.playType &&
  652. <>
  653. {
  654. state.playType === 'Audio' &&
  655. <div class={styles.audioBox}>
  656. <canvas class={styles.audioVisualizer} id="audioVisualizer"></canvas>
  657. <Vue3Lottie class={styles.audioBga} animationData={audioBga} autoPlay={true} loop={true}></Vue3Lottie>
  658. <Vue3Lottie class={styles.audioBga1} animationData={audioBga1} autoPlay={true} loop={true}></Vue3Lottie>
  659. <Vue3Lottie class={styles.audioBga2} animationData={audioBga2} autoPlay={true} loop={true}></Vue3Lottie>
  660. <audio
  661. crossorigin="anonymous"
  662. id="audioMediaSrc"
  663. src={state.musicDetail?.videoUrl}
  664. controls="false"
  665. preload="metadata"
  666. playsinline
  667. />
  668. </div>
  669. }
  670. {
  671. state.playType === 'Video' &&
  672. <video
  673. id="videoMediaSrc"
  674. class={styles.videoBox}
  675. src={state.musicDetail?.videoUrl}
  676. data-poster={ state.musicDetail?.videoImg || videobg}
  677. poster={ state.musicDetail?.videoImg || videobg}
  678. preload="metadata"
  679. playsinline
  680. webkit-playsinline
  681. x5-playsinline
  682. />
  683. }
  684. <div class={[styles.playLarge, !plyrState.mediaTimeShow && plyrState.playIngShow && styles.playIngShow]}></div>
  685. <div class={styles.mediaTimeCon}>
  686. <div class={styles.mediaTime}>
  687. <div>
  688. {getSecondRPM(plyrState.currentTime)}
  689. </div>
  690. <div class={styles.note}>/</div>
  691. <div class={styles.duration}>
  692. {getSecondRPM(plyrState.duration)}
  693. </div>
  694. </div>
  695. </div>
  696. <div class={styles.landscapeScreen} onClick={handlerLandscapeScreen}></div>
  697. {/* 谱面 */}
  698. {
  699. staffState.staffSrc &&
  700. <div class={[styles.staffBoxCon, staffState.isShow && styles.staffBoxShow]}>
  701. <div
  702. class={styles.staffBox}
  703. style={
  704. {
  705. '--staffBoxHeight':staffState.height
  706. }
  707. }
  708. >
  709. <div class={styles.mask}></div>
  710. <iframe
  711. ref={staffDom}
  712. class={styles.staff}
  713. frameborder="0"
  714. src={staffState.staffSrc}>
  715. </iframe>
  716. </div>
  717. </div>
  718. }
  719. </>
  720. }
  721. </div>
  722. </Sticky>
  723. <div class={[styles.musicSection, styles.musicShareSection]}>
  724. <div class={styles.avatarInfoBox}>
  725. <div class={styles.avatar}>
  726. <Image class={styles.userLogo} src={state.musicDetail.avatar} />
  727. <div class={styles.infoCon}>
  728. <div class={styles.info}>
  729. <span class={styles.userName}>{state.musicDetail?.username}</span>
  730. {state.musicDetail.vipFlag && (
  731. <img src={iconMember} class={styles.iconMember} />
  732. )}
  733. </div>
  734. <div class={styles.sub}>
  735. {state.musicDetail.subjectName}{' '}
  736. {getGradeCh(state.musicDetail.currentGradeNum - 1)}
  737. </div>
  738. </div>
  739. </div>
  740. <div class={styles.linkes} onClick={onStarChange}>
  741. <img src={state.musicDetail.starFlag ? iconZanActive : iconZan} class={styles.iconZan} />
  742. <span>{state.musicDetail.likeNum}</span>
  743. </div>
  744. </div>
  745. <TextEllipsis class={styles.textEllipsis} rows={2} content={state.musicDetail?.desc} expand-text="展开" collapse-text="收起" />
  746. </div>
  747. <div class={styles.likeSection}>
  748. <div class={styles.likeTitle}>推荐作品</div>
  749. {state.listState.dataShow ? (
  750. <>
  751. <List
  752. finished={true}
  753. finishedText=" "
  754. class={[styles.container, styles.containerInformation]}
  755. //onLoad={getList}
  756. immediateCheck={false}>
  757. {state.list.map((item: any, index:number) => (
  758. <Cell
  759. class={[styles.likeShareItem, index===state.list.length-1&&styles.likeShareItemLast]}
  760. border={false}
  761. onClick={() => onDetail(item)}
  762. >
  763. {{
  764. icon: () => (
  765. <div class={styles.audioImgBox}>
  766. <img
  767. src={audioPan}
  768. class={styles.audioPan}
  769. crossorigin="anonymous"
  770. />
  771. <img
  772. src={
  773. item.img || musicBg
  774. }
  775. class={styles.muploader}
  776. crossorigin="anonymous"
  777. />
  778. <img class={styles.imgLabel} src={item.videoUrl?.lastIndexOf('mp4') !== -1 ? videoLabel : audioLabel} />
  779. </div>
  780. ),
  781. title: () => (
  782. <div class={styles.userInfo}>
  783. <div class={[styles.musicSheetName,'van-ellipsis']}>{item.musicSheetName}</div>
  784. <div class={styles.usernameCon}>
  785. <div class={styles.likeNum}>
  786. <img src={iconZanActive} />
  787. <span>{item.likeNum}</span>
  788. </div>
  789. <div class={[styles.username, 'van-ellipsis']}>{item.username}</div>
  790. </div>
  791. </div>
  792. ),
  793. value: () => (
  794. <img src={playImg} class={styles.playImg} />
  795. )
  796. }}
  797. </Cell>
  798. ))}
  799. </List>
  800. {
  801. (!state.listState.finished || state.params.page>2) &&
  802. <div class={styles.btnImg}>
  803. <img onClick={handleChangeList} onTouchstart={()=>{}} src={btnImg} />
  804. </div>
  805. }
  806. </>
  807. ) : (
  808. <MEmpty image={"empty2"} description="暂无作品" />
  809. )}
  810. </div>
  811. {
  812. !isScreenScroll.value &&
  813. <MSticky position="bottom" offsetBottom={state.heightB - 1 + "px"} >
  814. <div class={styles.upward}>
  815. <img src={iconUpward} />
  816. </div>
  817. </MSticky>
  818. }
  819. </>
  820. }
  821. <Popup
  822. v-model:show={state.loginStatus}
  823. style={{ background: 'transparent', overflow: 'inherit' }}>
  824. <LoginModel
  825. onClose={() => (state.loginStatus = false)}
  826. onConfirm={async (val: any) => {
  827. if (val.loginTag) {
  828. state.loginTag = val.loginTag;
  829. state.loginStatus = false;
  830. const { data } = await api_openUserMusicDetail(state.id);
  831. state.musicDetail = data;
  832. } else {
  833. state.credential = val.data;
  834. state.loginChangeState = true;
  835. state.loginStatus = false;
  836. }
  837. }}
  838. />
  839. </Popup>
  840. <Popup
  841. v-model:show={state.loginChangeState}
  842. style={{ background: 'transparent', overflow: 'inherit' }}>
  843. <LoginChangeModel
  844. credential={state.credential}
  845. onClose={() => {
  846. state.credential = {};
  847. state.loginChangeState = false;
  848. }}
  849. onConfirm={async (val: any) => {
  850. state.loginTag = val.loginTag;
  851. state.loginChangeState = false;
  852. const { data } = await api_openUserMusicDetail(state.id);
  853. state.musicDetail = data;
  854. }}
  855. />
  856. </Popup>
  857. <MWxTip
  858. v-model:show={state.messageStatus}
  859. message={state.message}
  860. showButton={false}
  861. />
  862. {
  863. !staffState.isShow && <Loading></Loading>
  864. }
  865. {wxStatus.value && (
  866. <div
  867. class={styles.wxpopup}
  868. onClick={() => {
  869. wxStatus.value = false;
  870. }}>
  871. <img src={wxBg} alt="" />
  872. </div>
  873. )}
  874. </div>
  875. );
  876. }
  877. });