index-share.tsx 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864
  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. if (window.location.pathname.includes('teacher')) {
  307. window.location.href = location.origin + '/student' + '/#/transfer'
  308. } else {
  309. window.location.href = location.origin + '/student' + '/#/download'
  310. }
  311. }
  312. }, 3000)
  313. }
  314. }
  315. const shareCall = (str: string, params?: any) => {
  316. const query = {
  317. url: str,
  318. action: params?.action || 'h5', // app, h5
  319. pageTag: params?.pageTag || 1 // 页面标识
  320. }
  321. const iosStr = encodeURIComponent(JSON.stringify(query))
  322. const userAgent = navigator.userAgent || navigator.vendor;
  323. const platform = navigator.platform || 'unknown';
  324. if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent) || (platform === 'MacIntel')) {
  325. window.location.href = `ColexiuStudent://linkUrl=${iosStr}`
  326. } else if (/(Android)/i.test(userAgent)) {
  327. window.location.href = `colexiustudent://html:8888/SplashActivity?url=${iosStr}`
  328. } else {
  329. Toast('请用手机或移动设备打开')
  330. }
  331. }
  332. const checkLogin = async () => {
  333. try {
  334. // 判断是否登录
  335. const Authorization = getAuth() // storage.get(ACCESS_TOKEN) || ''
  336. if (Authorization) {
  337. await api_verification({
  338. token: Authorization
  339. })
  340. state.loginTag = true
  341. // if (!res.data) {
  342. // removeAuth()
  343. // setLogout()
  344. // }
  345. }
  346. } catch (e: any) {
  347. // 登录是否有效
  348. state.loginTag = false
  349. removeAuth()
  350. setLogout()
  351. }
  352. };
  353. const __init = async () => {
  354. await checkLogin();
  355. try {
  356. const res = await api_openUserMusicDetail(state.id)
  357. if (res.code === 999) {
  358. // 没有的时候显示缺省页
  359. state.isEmpty = true
  360. staffState.isShow = true
  361. return
  362. } else {
  363. state.musicDetail = res.data
  364. try{
  365. const jsonConfig = JSON.parse(res.data.jsonConfig)
  366. jsonConfig.speedRate && (staffState.speedRate = jsonConfig.speedRate)
  367. jsonConfig.musicRenderType && (staffState.musicRenderType = jsonConfig.musicRenderType)
  368. jsonConfig["part-index"] && (staffState.partIndex = jsonConfig["part-index"])
  369. }catch{
  370. }
  371. // 五线谱
  372. initStaff()
  373. getList()
  374. // 判断是视频还是音频
  375. if (res.data.videoUrl.lastIndexOf('mp4') !== -1) {
  376. state.playType = 'Video'
  377. } else {
  378. state.playType = 'Audio'
  379. }
  380. // 初始化
  381. nextTick(() => {
  382. initMediaPlay();
  383. });
  384. }
  385. } catch (err) {
  386. state.listState.dataShow = false
  387. // 没有的时候显示缺省页
  388. state.message = err;
  389. state.messageStatus = true;
  390. }
  391. }
  392. // 滚动事件
  393. const handleScroll = () => {
  394. // 作品已删除不让滚动变色
  395. if(state.isEmpty) return
  396. const height =
  397. window.scrollY ||
  398. document.documentElement.scrollTop
  399. // 防止多次调用
  400. if(height > 0 && isScreenScroll.value === false){
  401. isScreenScroll.value = true
  402. if(isApp){
  403. setStatusBarTextColor(false)
  404. }
  405. }
  406. if(height <= 0){
  407. isScreenScroll.value = false
  408. if(isApp){
  409. setStatusBarTextColor(true)
  410. }
  411. }
  412. }
  413. // 跳转下载页
  414. function handlerDownLoad(){
  415. if(weixin){
  416. wxStatus.value = true
  417. }else{
  418. // 如果是老师端
  419. if (window.location.pathname.includes('teacher')) {
  420. window.location.href = location.origin + '/student' + '/#/transfer'
  421. } else {
  422. router.push({
  423. path:"/download"
  424. })
  425. }
  426. }
  427. }
  428. const pageVisibility = usePageVisibility()
  429. watch(pageVisibility, value => {
  430. if (value === 'hidden') {
  431. state._plrl?.pause();
  432. }
  433. });
  434. // 初始化五线谱
  435. function initStaff(){
  436. // const src = `/klx-music-score/#/simple-detail?id=${state.musicDetail.musicSheetId}&musicRenderType=${staffState.musicRenderType}&part-index=${staffState.partIndex}&userMusicId=${state.id}`;
  437. 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}`;
  438. staffState.staffSrc = src
  439. window.addEventListener('message', (event) => {
  440. const { api, height } = event.data;
  441. if (api === 'api_musicPage') {
  442. staffState.isShow = true
  443. staffState.height = height + "px"
  444. // 如果是播放中自动开始播放 不是播放 自动跳转到当前位置
  445. // if(playProgressData.playState){
  446. // handlerClickPlay()
  447. // }else{
  448. // updateProgressStaff(state._plrl.currentTime)
  449. // }
  450. }
  451. });
  452. }
  453. function staffMoveInstance(){
  454. let isPause = true
  455. const requestAnimationFrameFun = () => {
  456. requestAnimationFrame(() => {
  457. staffDom.value?.contentWindow?.postMessage(
  458. {
  459. api: 'api_playProgress',
  460. content: {
  461. currentTime: state._plrl.currentTime * staffState.speedRate
  462. }
  463. },
  464. "*"
  465. )
  466. if (!isPause) {
  467. requestAnimationFrameFun()
  468. }
  469. })
  470. }
  471. const playStaff = () => {
  472. // 没渲染不执行
  473. if(!staffState.isShow) return
  474. isPause = false
  475. staffDom.value?.contentWindow?.postMessage(
  476. {
  477. api: 'api_play'
  478. },
  479. "*"
  480. )
  481. requestAnimationFrameFun()
  482. }
  483. const pauseStaff = () => {
  484. // 没渲染不执行
  485. if(!staffState.isShow) return
  486. isPause = true
  487. staffDom.value?.contentWindow?.postMessage(
  488. {
  489. api: 'api_paused'
  490. },
  491. "*"
  492. )
  493. }
  494. const updateProgressStaff = (currentTime: number) => {
  495. // 没渲染不执行
  496. if(!staffState.isShow) return
  497. staffDom.value?.contentWindow?.postMessage(
  498. {
  499. api: 'api_updateProgress',
  500. content: {
  501. currentTime: currentTime * staffState.speedRate
  502. }
  503. },
  504. "*"
  505. )
  506. }
  507. return {
  508. playStaff,
  509. pauseStaff,
  510. updateProgressStaff
  511. }
  512. }
  513. // 设置导航栏颜色
  514. function setStatusBarTextColor(isWhite:boolean){
  515. postMessage({
  516. api: 'setStatusBarTextColor',
  517. content: { statusBarTextColor: isWhite }
  518. })
  519. }
  520. function setFullHeight(){
  521. creationHeight.value = window.innerHeight
  522. }
  523. onBeforeMount(() => {
  524. if(isApp) {
  525. postMessage({
  526. api: "setRequestedOrientation",
  527. content: {
  528. orientation: 1,
  529. },
  530. });
  531. setStatusBarTextColor(true)
  532. }
  533. })
  534. onMounted(async () => {
  535. window.addEventListener("scroll", handleScroll)
  536. __init()
  537. setFullHeight()
  538. window.addEventListener('resize', setFullHeight)
  539. })
  540. onUnmounted(() => {
  541. window.removeEventListener("scroll", handleScroll)
  542. window.removeEventListener('resize', setFullHeight)
  543. state._plrl?.destroy()
  544. })
  545. onBeforeRouteUpdate((to: any) => {
  546. state.id = to.query.id;
  547. state.playType = '';
  548. state.params.page = 1;
  549. state.list = [];
  550. if(state._plrl){
  551. state._plrl.destroy()
  552. }
  553. plyrState.playIngShow = true
  554. staffState.staffSrc = ""
  555. staffState.isShow = false
  556. staffState.height = "initial"
  557. __init();
  558. })
  559. return () => (
  560. <div
  561. style={
  562. {
  563. '--barheight':state.heightV + "px",
  564. "--creationHeight":creationHeight.value ? creationHeight.value+"px" : "100vh"
  565. }
  566. }
  567. class={[
  568. styles.creation,
  569. isTablet ? styles.creationTablet : '',
  570. isScreenScroll.value && styles.isShareScreenScroll
  571. ]}>
  572. <div class={styles.creationBg}></div>
  573. <MSticky position="top"
  574. onGetHeight={(height: any) => {
  575. console.log(height, 'height', height)
  576. state.heightV = height
  577. }}
  578. >
  579. {
  580. isApp ? <MHeader
  581. leftClickDefault={false}
  582. color={isScreenScroll.value ? "#333333" : "#ffffff"}
  583. background={isScreenScroll.value ? `rgb(255,255,255` : "transparent"}
  584. border={false}
  585. isBack={route.query.platformType != 'ANALYSIS'}
  586. title={"作品详情"}
  587. onLeftClick={()=>{
  588. setStatusBarTextColor(false)
  589. postMessage({
  590. api: 'back'
  591. });
  592. }}
  593. />
  594. : <div class={styles.logoDownload}>
  595. <img src={isScreenScroll.value ? logo1Img : logoImg} class={styles.logoImg}></img>
  596. <div class={styles.logTit} onClick={handlerDownLoad}>下载App</div>
  597. </div>
  598. }
  599. </MSticky>
  600. {
  601. state.isEmpty ?
  602. <div class={styles.isEmpty}>
  603. <MEmpty tips="作品已删除~" btnStatus={false} />
  604. </div> :
  605. <>
  606. <div class={styles.singerBox}>
  607. <div class={styles.musicSheetName}>
  608. <NoticeBar
  609. text={state.musicDetail?.musicSheetName}
  610. background="none"
  611. />
  612. </div>
  613. <div class={styles.singerName}>
  614. 演奏:{state.musicDetail?.username}
  615. </div>
  616. </div>
  617. <Sticky zIndex={1000} offsetTop={state.heightV - 1 + "px"}>
  618. <div class={[styles.playSection, plyrState.mediaTimeShow && styles.mediaTimeShow,!plyrState.loaded && styles.notLoaded,isLandscapeScreen.value&&styles.isLandscapeScreen, state.playType === 'Audio' && styles.isLandscapeScreen2]} id="playMediaSection" onClick={handlerClickPlay}>
  619. {
  620. isLandscapeScreen.value &&
  621. <div class={styles.backBox}>
  622. <img class={styles.backImg} src={backImg} onClick={handlerBack}/>
  623. <div class={[styles.musicDetail, state.playType === 'Audio' && styles.adMusicDetail]}>
  624. <div class={styles.musicSheetName}>
  625. <NoticeBar
  626. text={state.musicDetail?.musicSheetName}
  627. background="none"
  628. />
  629. </div>
  630. <div class={styles.username}>演奏:{state.musicDetail?.username}</div>
  631. </div>
  632. </div>
  633. }
  634. {
  635. state.playType &&
  636. <>
  637. {
  638. state.playType === 'Audio' &&
  639. <div class={styles.audioBox}>
  640. <canvas class={styles.audioVisualizer} id="audioVisualizer"></canvas>
  641. <audio
  642. crossorigin="anonymous"
  643. id="audioMediaSrc"
  644. src={state.musicDetail?.videoUrl}
  645. controls="false"
  646. preload="metadata"
  647. playsinline
  648. webkit-playsinline
  649. />
  650. <img src={tyBg} class={styles.tyBg} />
  651. <div class={styles.audioBoxBg}>
  652. <div class={[styles.audioPan, plyrState.playIngShow && styles.imgRotate]}>
  653. <img class={styles.audioImg} src={state.musicDetail.img || musicBg} />
  654. </div>
  655. <i class={styles.audioPoint}></i>
  656. <i class={[styles.audioZhen, plyrState.playIngShow && styles.active]}></i>
  657. </div>
  658. </div>
  659. }
  660. {
  661. state.playType === 'Video' &&
  662. <video
  663. id="videoMediaSrc"
  664. class={styles.videoBox}
  665. src={state.musicDetail?.videoUrl}
  666. data-poster={ state.musicDetail?.videoImg || videobg}
  667. poster={ state.musicDetail?.videoImg || videobg}
  668. preload="metadata"
  669. playsinline
  670. webkit-playsinline
  671. x5-playsinline
  672. />
  673. }
  674. <div class={[styles.playLarge, !plyrState.mediaTimeShow && plyrState.playIngShow && styles.playIngShow]}></div>
  675. <div class={styles.mediaTimeCon}>
  676. <div class={styles.mediaTime}>
  677. <div>
  678. {getSecondRPM(plyrState.currentTime)}
  679. </div>
  680. <div class={styles.note}>/</div>
  681. <div class={styles.duration}>
  682. {getSecondRPM(plyrState.duration)}
  683. </div>
  684. </div>
  685. </div>
  686. <div class={styles.landscapeScreen} onClick={handlerLandscapeScreen}></div>
  687. {/* 谱面 */}
  688. {
  689. staffState.staffSrc &&
  690. <div class={[styles.staffBoxCon, staffState.isShow && styles.staffBoxShow]}>
  691. <div
  692. class={[styles.staffBox, state.playType === 'Video' && styles.staffBoxBg]}
  693. style={
  694. {
  695. '--staffBoxHeight':staffState.height
  696. }
  697. }
  698. >
  699. <div class={styles.mask}></div>
  700. <iframe
  701. ref={staffDom}
  702. class={styles.staff}
  703. frameborder="0"
  704. src={staffState.staffSrc}>
  705. </iframe>
  706. </div>
  707. </div>
  708. }
  709. </>
  710. }
  711. </div>
  712. </Sticky>
  713. <div class={[styles.musicSection, styles.musicShareSection]}>
  714. <div class={styles.avatarInfoBox}>
  715. <div class={styles.avatar}>
  716. <div class={styles.avatarImg}>
  717. <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} />
  718. {
  719. (state.musicDetail.vipType === 'VIP' || state.musicDetail.vipType === 'PERMANENT_SVIP' || state.musicDetail.vipType === 'SVIP') &&
  720. <img class={styles.vipIcon} src={state.musicDetail.vipType === 'VIP' ? vipIcon : svipIcon} />
  721. }
  722. </div>
  723. <div class={styles.infoCon}>
  724. <div class={styles.info}>
  725. <span class={styles.userName}>{state.musicDetail?.username}</span>
  726. {state.musicDetail.vipFlag && (
  727. <img src={iconMember} class={styles.iconMember} />
  728. )}
  729. </div>
  730. <div class={styles.sub}>
  731. {state.musicDetail.subjectName}{' '}
  732. {getGradeCh(state.musicDetail.currentGradeNum - 1)}
  733. </div>
  734. </div>
  735. </div>
  736. <div class={styles.linkes} onClick={onStarChange}>
  737. <img src={state.musicDetail.starFlag ? iconZanActive : iconZan} class={styles.iconZan} />
  738. <span>{state.musicDetail.likeNum}</span>
  739. </div>
  740. </div>
  741. <TextEllipsis class={styles.textEllipsis} text={state.musicDetail?.desc || ''} />
  742. </div>
  743. <div class={styles.likeSection}>
  744. <div class={styles.likeTitle}>推荐作品</div>
  745. {state.listState.dataShow ? (
  746. <>
  747. <List
  748. finished={true}
  749. finishedText=" "
  750. class={[styles.container, styles.containerInformation]}
  751. //onLoad={getList}
  752. immediateCheck={false}>
  753. {state.list.map((item: any, index:number) => (
  754. <Cell
  755. class={[styles.likeShareItem, index===state.list.length-1&&styles.likeShareItemLast]}
  756. border={false}
  757. onClick={() => onDetail(item)}
  758. >
  759. {{
  760. icon: () => (
  761. <div class={styles.audioImgBox}>
  762. <img
  763. src={audioPan}
  764. class={styles.audioPan}
  765. crossorigin="anonymous"
  766. />
  767. <img
  768. src={
  769. item.img || musicBg
  770. }
  771. class={styles.muploader}
  772. crossorigin="anonymous"
  773. />
  774. <img class={styles.imgLabel} src={item.videoUrl?.lastIndexOf('mp4') !== -1 ? videoLabel : audioLabel} />
  775. </div>
  776. ),
  777. title: () => (
  778. <div class={styles.userInfo}>
  779. <div class={[styles.musicSheetName,'van-ellipsis']}>{item.musicSheetName}</div>
  780. <div class={styles.usernameCon}>
  781. <div class={styles.likeNum}>
  782. <img src={iconZanActive} />
  783. <span>{item.likeNum}</span>
  784. </div>
  785. <div class={[styles.username, 'van-ellipsis']}>{item.username}</div>
  786. </div>
  787. </div>
  788. ),
  789. value: () => (
  790. <img src={playImg} class={styles.playImg} />
  791. )
  792. }}
  793. </Cell>
  794. ))}
  795. </List>
  796. {
  797. (!state.listState.finished || state.params.page>2) &&
  798. <div class={styles.btnImg}>
  799. <img onClick={handleChangeList} onTouchstart={()=>{}} src={btnImg} />
  800. </div>
  801. }
  802. </>
  803. ) : (
  804. <MEmpty tips="暂无作品~" btnStatus={false} />
  805. )}
  806. </div>
  807. {
  808. !isScreenScroll.value &&
  809. <MSticky position="bottom" offsetBottom={state.heightB - 1 + "px"} >
  810. <div class={styles.upward}>
  811. <img src={iconUpward} />
  812. </div>
  813. </MSticky>
  814. }
  815. </>
  816. }
  817. <Popup
  818. v-model:show={state.loginStatus}
  819. style={{ background: 'transparent', overflow: 'inherit' }}
  820. >
  821. <LoginModel
  822. onClose={() => (state.loginStatus = false)}
  823. onConfirm={async (val: boolean) => {
  824. state.loginTag = val
  825. state.loginStatus = false
  826. const { data } = await api_openUserMusicDetail(state.id)
  827. state.musicDetail = data
  828. }}
  829. />
  830. </Popup>
  831. <MWxTip
  832. v-model:show={state.messageStatus}
  833. message={state.message}
  834. showButton={false}
  835. />
  836. {
  837. !staffState.isShow && <Loading></Loading>
  838. }
  839. {wxStatus.value && (
  840. <div
  841. class={styles.wxpopup}
  842. onClick={() => {
  843. wxStatus.value = false;
  844. }}>
  845. <img src={wxBg} alt="" />
  846. </div>
  847. )}
  848. </div>
  849. )
  850. }
  851. })