index-share.tsx 29 KB

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