index.tsx 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820
  1. import { defineComponent, onMounted, onUnmounted, reactive, watch, ref, onBeforeMount } from 'vue'
  2. // import WaveSurfer from 'wavesurfer.js';
  3. import styles from './index.module.less'
  4. import MSticky from '@/components/col-sticky'
  5. import MHeader from '@/components/col-header'
  6. import { Button, Cell, Dialog, Image, List, Popup, Slider, Toast, Sticky, NoticeBar } from 'vant'
  7. import iconDownload from './images/icon-download.png'
  8. import iconShare from './images/icon-share.png'
  9. import iconDelete from './images/icon-delete.png'
  10. import iconVip from './images/icon-vip.png'
  11. import iconSVip from './images/icon-svip.png'
  12. import iconZan from './images/icon-zan.png'
  13. import { postMessage, promisefiyPostMessage } from '@/helpers/native-message'
  14. import { browser, getGradeCh, getSecondRPM } from '@/helpers/utils'
  15. import { useRoute, useRouter } from 'vue-router'
  16. import {
  17. api_userMusicDetail,
  18. api_userMusicRemove,
  19. api_userMusicStarPage
  20. } from './api'
  21. import MEmpty from '@/components/col-result'
  22. import dayjs from 'dayjs'
  23. import { nextTick } from 'process'
  24. import ShareModel from './share-model'
  25. import { usePageVisibility } from '@vant/use'
  26. import "plyr/dist/plyr.css";
  27. import Plyr from "plyr";
  28. import { generateMixedData } from "./audioVisualDraw"
  29. import backImg from "./images/back.png";
  30. import back1Img from "./images/back1.png";
  31. import musicBg from "./images/music_bg.png";
  32. import videobg from "./images/videobg.png";
  33. import iconUpward from './images/upward.png';
  34. import iconEdit from './images/edit.png';
  35. import iconMember from './images/icon-member.png';
  36. import vipIcon from './images/vip_icon.png';
  37. import svipIcon from './images/svip_icon.png';
  38. import tyBg from './images/ty.png';
  39. import TextEllipsis from './text-ellipsis/index';
  40. import Loading from './loading';
  41. import { state as originState } from '@/state'
  42. export default defineComponent({
  43. name: 'creation-detail',
  44. setup() {
  45. const {isApp, isTablet, isTeacher} = browser()
  46. const route = useRoute()
  47. const router = useRouter()
  48. const isScreenScroll = ref(false)
  49. const mStickyBottom = ref()
  50. const mStickyUpward = ref()
  51. const state = reactive({
  52. id: route.query.id,
  53. deleteStatus: false,
  54. shareStatus: false,
  55. playType: '' as 'Audio' | 'Video' | '', // 播放类型
  56. musicDetail: {} as any,
  57. isClick: false,
  58. list: [] as any,
  59. listState: {
  60. dataShow: true, // 判断是否有数据
  61. loading: false,
  62. finished: false
  63. },
  64. params: {
  65. page: 1,
  66. rows: 20
  67. },
  68. _plrl: null as any,
  69. heightV:0,
  70. heightB:0
  71. });
  72. const plyrState = reactive({
  73. duration: 0,
  74. currentTime: 0,
  75. mediaTimeShow: false,
  76. playIngShow: true
  77. })
  78. // 谱面
  79. const staffState = reactive({
  80. staffSrc: "",
  81. isShow: false,
  82. height:"initial",
  83. speedRate:1,
  84. musicRenderType:"staff",
  85. partIndex: 0
  86. })
  87. const isLandscapeScreen = ref(false)
  88. const staffDom= ref<HTMLIFrameElement>()
  89. const {playStaff, pauseStaff, updateProgressStaff} = staffMoveInstance()
  90. // 获取列表
  91. const getStarList = async () => {
  92. try {
  93. if (state.isClick) return
  94. state.isClick = true
  95. const res = await api_userMusicStarPage({
  96. userMusicId: state.id,
  97. ...state.params
  98. })
  99. state.listState.loading = false
  100. const result = res.data || {}
  101. // 处理重复请求数据
  102. if (state.list.length > 0 && result.current === 1) {
  103. return
  104. }
  105. state.list = state.list.concat(result.rows || [])
  106. state.listState.finished = result.current >= result.pages
  107. state.params.page = result.current + 1
  108. state.listState.dataShow = state.list.length > 0
  109. state.isClick = false
  110. } catch {
  111. state.listState.dataShow = false
  112. state.listState.finished = true
  113. state.isClick = false
  114. }
  115. }
  116. // 删除作品
  117. const onDelete = async () => {
  118. try {
  119. await api_userMusicRemove({ id: state.id });
  120. setTimeout(() => {
  121. state.deleteStatus = false;
  122. Toast('删除成功');
  123. }, 100);
  124. setTimeout(() => {
  125. if (isApp) {
  126. postMessage({
  127. api: 'goBack'
  128. });
  129. } else {
  130. router.back();
  131. }
  132. }, 1200);
  133. } catch {
  134. //
  135. }
  136. };
  137. // 下载
  138. const onDownload = async () => {
  139. await promisefiyPostMessage({
  140. api: 'saveFile',
  141. content: {
  142. url: state.musicDetail.videoUrl
  143. }
  144. });
  145. };
  146. // 滚动事件
  147. const handleScroll = () => {
  148. console.log('滚动',123)
  149. const height =
  150. window.scrollY ||
  151. document.documentElement.scrollTop
  152. // 防止多次调用
  153. if(height > 0 && isScreenScroll.value === false){
  154. isScreenScroll.value = true
  155. setStatusBarTextColor(false)
  156. }
  157. if(height <= 0){
  158. isScreenScroll.value = false
  159. setStatusBarTextColor(true)
  160. }
  161. }
  162. // 设置导航栏颜色
  163. function setStatusBarTextColor(isWhite:boolean){
  164. postMessage({
  165. api: 'setStatusBarTextColor',
  166. content: { statusBarTextColor: isWhite }
  167. })
  168. }
  169. const pageVisibility = usePageVisibility();
  170. watch(pageVisibility, value => {
  171. if (value === 'hidden') {
  172. state._plrl?.pause();
  173. }
  174. });
  175. // 初始化 媒体播放
  176. function initMediaPlay(){
  177. const id = state.playType === "Audio" ? "#audioMediaSrc" : "#videoMediaSrc";
  178. state._plrl = new Plyr(id, {
  179. controls: ["play", "progress", "current-time", "duration"],
  180. fullscreen: {
  181. enabled: false,
  182. fallback: false
  183. }
  184. });
  185. const player = state._plrl
  186. // 创建音波数据
  187. if(state.playType === "Audio"){
  188. const audioDom = document.querySelector("#audioMediaSrc") as HTMLAudioElement
  189. const canvasDom = document.querySelector("#audioVisualizer") as HTMLCanvasElement
  190. const { pauseVisualDraw, playVisualDraw } = audioVisualDraw(audioDom, canvasDom)
  191. player.on('play', () => {
  192. playVisualDraw()
  193. });
  194. player.on('pause', () => {
  195. pauseVisualDraw()
  196. });
  197. }
  198. // player.on('loadedmetadata', () => {
  199. // player.currentTime = playProgressData.playProgress
  200. // });
  201. player.on("timeupdate", ()=>{
  202. plyrState.currentTime = player.currentTime
  203. })
  204. player.on('play', () => {
  205. plyrState.playIngShow = false
  206. playStaff()
  207. });
  208. player.on('pause', () => {
  209. plyrState.playIngShow = true
  210. pauseStaff()
  211. });
  212. player.on('ended', () => {
  213. player.currentTime = 0
  214. if(!player.playing){
  215. setTimeout(() => {
  216. updateProgressStaff(player.currentTime)
  217. }, 100);
  218. }
  219. });
  220. // 处理按压事件
  221. const handleStart = () => {
  222. if(isLandscapeScreen.value){
  223. return
  224. }
  225. plyrState.duration = player.duration
  226. plyrState.mediaTimeShow = true
  227. };
  228. // 处理松开事件
  229. const handleEnd = () => {
  230. plyrState.mediaTimeShow = false
  231. // 暂停的时候调用
  232. if(!player.playing){
  233. updateProgressStaff(player.currentTime)
  234. }
  235. };
  236. const progressDom = document.querySelector("#playMediaSection .plyr__controls .plyr__progress__container") as HTMLElement
  237. progressDom.addEventListener('mousedown', handleStart);
  238. progressDom.addEventListener('touchstart', handleStart);
  239. progressDom.addEventListener('mouseup', handleEnd);
  240. progressDom.addEventListener('touchend', handleEnd);
  241. }
  242. //点击改变播放状态
  243. function handlerClickPlay(event?:MouseEvent){
  244. // 原生 播放暂停按钮 点击的时候 不触发
  245. // @ts-ignore
  246. if(event?.target?.matches('button.plyr__control')){
  247. return
  248. }
  249. if (state._plrl.playing) {
  250. state._plrl.pause();
  251. } else {
  252. state._plrl.play();
  253. }
  254. }
  255. /**
  256. * 音频可视化
  257. * @param audioDom
  258. * @param canvasDom
  259. * @param fftSize 2的幂数,最小为32
  260. */
  261. function audioVisualDraw(audioDom: HTMLAudioElement, canvasDom: HTMLCanvasElement, fftSize = 128) {
  262. type propsType = { canvWidth: number; canvHeight: number; canvFillColor: string; lineColor: string; lineGap: number }
  263. // canvas
  264. const canvasCtx = canvasDom.getContext("2d")!
  265. let { width, height } = canvasDom.getBoundingClientRect()
  266. width = Math.ceil(width)
  267. height = Math.ceil(height)
  268. canvasDom.width = width
  269. canvasDom.height = height
  270. // audio
  271. // let audioCtx : AudioContext | null = null
  272. // let analyser : AnalyserNode | null = null
  273. // let source : MediaElementAudioSourceNode | null = null
  274. // const dataArray = new Uint8Array(fftSize / 2)
  275. const draw = (data: Uint8Array, ctx: CanvasRenderingContext2D, { lineGap, canvWidth, canvHeight, canvFillColor, lineColor }: propsType) => {
  276. if (!ctx) return
  277. const w = canvWidth
  278. const h = canvHeight
  279. fillCanvasBackground(ctx, w, h, canvFillColor)
  280. // 可视化
  281. const dataLen = data.length
  282. let step = (w / 2 - lineGap * dataLen) / dataLen
  283. step < 1 && (step = 1)
  284. const midX = w / 2
  285. const midY = h / 2
  286. let xLeft = midX
  287. for (let i = 0; i < dataLen; i++) {
  288. const value = data[i]
  289. const percent = value / 255 // 最大值为255
  290. const barHeight = percent * midY
  291. canvasCtx.fillStyle = lineColor
  292. // 中间加间隙
  293. if (i === 0) {
  294. xLeft -= lineGap / 2
  295. }
  296. canvasCtx.fillRect(xLeft - step, midY - barHeight, step, barHeight)
  297. canvasCtx.fillRect(xLeft - step, midY, step, barHeight)
  298. xLeft -= step + lineGap
  299. }
  300. let xRight = midX
  301. for (let i = 0; i < dataLen; i++) {
  302. const value = data[i]
  303. const percent = value / 255 // 最大值为255
  304. const barHeight = percent * midY
  305. canvasCtx.fillStyle = lineColor
  306. if (i === 0) {
  307. xRight += lineGap / 2
  308. }
  309. canvasCtx.fillRect(xRight, midY - barHeight, step, barHeight)
  310. canvasCtx.fillRect(xRight, midY, step, barHeight)
  311. xRight += step + lineGap
  312. }
  313. }
  314. const fillCanvasBackground = (ctx: CanvasRenderingContext2D, w: number, h: number, colors: string) => {
  315. ctx.clearRect(0, 0, w, h)
  316. ctx.fillStyle = colors
  317. ctx.fillRect(0, 0, w, h)
  318. }
  319. const requestAnimationFrameFun = () => {
  320. // requestAnimationFrame(() => {
  321. // //analyser?.getByteFrequencyData(dataArray)
  322. // draw(generateMixedData(48), canvasCtx, {
  323. // lineGap: 2,
  324. // canvWidth: width,
  325. // canvHeight: height,
  326. // canvFillColor: "transparent",
  327. // lineColor: "rgba(255, 255, 255, 0.7)"
  328. // })
  329. // if (!isPause) {
  330. // requestAnimationFrameFun()
  331. // }
  332. // })
  333. const _time = setInterval(() => {
  334. if (isPause) {
  335. clearInterval(_time)
  336. return
  337. }
  338. //analyser?.getByteFrequencyData(dataArray)
  339. draw(generateMixedData(48), canvasCtx, {
  340. lineGap: 2,
  341. canvWidth: width,
  342. canvHeight: height,
  343. canvFillColor: "transparent",
  344. lineColor: "rgba(255, 255, 255, 0.7)"
  345. })
  346. }, 300);
  347. }
  348. let isPause = true
  349. const playVisualDraw = () => {
  350. // if (!audioCtx) {
  351. // audioCtx = new AudioContext()
  352. // source = audioCtx.createMediaElementSource(audioDom)
  353. // analyser = audioCtx.createAnalyser()
  354. // analyser.fftSize = fftSize
  355. // source?.connect(analyser)
  356. // analyser.connect(audioCtx.destination)
  357. // }
  358. //audioCtx.resume() // 重新更新状态 加了暂停和恢复音频音质发生了变化 所以这里取消了
  359. isPause = false
  360. requestAnimationFrameFun()
  361. }
  362. const pauseVisualDraw = () => {
  363. isPause = true
  364. requestAnimationFrame(()=>{
  365. canvasCtx.clearRect(0, 0, width, height);
  366. })
  367. //audioCtx?.suspend() // 暂停 加了暂停和恢复音频音质发生了变化 所以这里取消了
  368. // source?.disconnect()
  369. // analyser?.disconnect()
  370. }
  371. return {
  372. playVisualDraw,
  373. pauseVisualDraw
  374. }
  375. }
  376. function handlerBack(event:any){
  377. event.stopPropagation()
  378. verticalScreen()
  379. }
  380. function landscapeScreen(){
  381. postMessage({
  382. api: "setRequestedOrientation",
  383. content: {
  384. orientation: 0,
  385. },
  386. });
  387. isLandscapeScreen.value = true
  388. }
  389. function verticalScreen(){
  390. postMessage({
  391. api: "setRequestedOrientation",
  392. content: {
  393. orientation: 1,
  394. },
  395. });
  396. isLandscapeScreen.value = false
  397. }
  398. function handlerLandscapeScreen(event:any){
  399. event.stopPropagation()
  400. if(!isLandscapeScreen.value){
  401. landscapeScreen()
  402. }
  403. // playProgressData.playState = !!state._plrl?.playing
  404. // playProgressData.playProgress = state._plrl?.currentTime || 0
  405. // router.push({
  406. // path:"/playCreation",
  407. // query:{
  408. // resourceUrl:encodeURIComponent(state.musicDetail?.videoUrl),
  409. // videoBgUrl:encodeURIComponent(state.musicDetail?.videoImg || ""),
  410. // musicSheetName:encodeURIComponent(state.musicDetail?.musicSheetName),
  411. // username:encodeURIComponent(state.musicDetail?.username),
  412. // musicSheetId:encodeURIComponent(state.musicDetail?.musicSheetId),
  413. // speedRate:encodeURIComponent(staffState.speedRate),
  414. // musicRenderType:encodeURIComponent(staffState.musicRenderType),
  415. // partIndex:encodeURIComponent(staffState.partIndex),
  416. // }
  417. // })
  418. }
  419. // 初始化五线谱
  420. function initStaff(){
  421. const systemType = originState.platformType === 'TEACHER' || isTeacher ? 'teacher' : 'student'
  422. const src = `/klx-music-score/#/simple-detail?id=${state.musicDetail.musicSheetId}&musicRenderType=${staffState.musicRenderType}&part-index=${staffState.partIndex}&userMusicId=${state.id}&systemType=${systemType}`;
  423. // 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}`;
  424. staffState.staffSrc = src
  425. window.addEventListener('message', (event) => {
  426. const { api, height } = event.data;
  427. if (api === 'api_musicPage') {
  428. staffState.isShow = true
  429. staffState.height = height + "px"
  430. }
  431. });
  432. }
  433. function staffMoveInstance(){
  434. let isPause = true
  435. const requestAnimationFrameFun = () => {
  436. requestAnimationFrame(() => {
  437. staffDom.value?.contentWindow?.postMessage(
  438. {
  439. api: 'api_playProgress',
  440. content: {
  441. currentTime: state._plrl.currentTime * staffState.speedRate
  442. }
  443. },
  444. "*"
  445. )
  446. if (!isPause) {
  447. requestAnimationFrameFun()
  448. }
  449. })
  450. }
  451. const playStaff = () => {
  452. // 没渲染不执行
  453. if(!staffState.isShow) return
  454. isPause = false
  455. staffDom.value?.contentWindow?.postMessage(
  456. {
  457. api: 'api_play'
  458. },
  459. "*"
  460. )
  461. requestAnimationFrameFun()
  462. }
  463. const pauseStaff = () => {
  464. // 没渲染不执行
  465. if(!staffState.isShow) return
  466. isPause = true
  467. staffDom.value?.contentWindow?.postMessage(
  468. {
  469. api: 'api_paused'
  470. },
  471. "*"
  472. )
  473. }
  474. const updateProgressStaff = (currentTime: number) => {
  475. // 没渲染不执行
  476. if(!staffState.isShow) return
  477. staffDom.value?.contentWindow?.postMessage(
  478. {
  479. api: 'api_updateProgress',
  480. content: {
  481. currentTime: currentTime * staffState.speedRate
  482. }
  483. },
  484. "*"
  485. )
  486. }
  487. return {
  488. playStaff,
  489. pauseStaff,
  490. updateProgressStaff
  491. }
  492. }
  493. onMounted(async () => {
  494. document.addEventListener("scroll", handleScroll)
  495. setStatusBarTextColor(true)
  496. try {
  497. const res = await api_userMusicDetail(state.id)
  498. // console.log(res);
  499. if (res.code === 999) {
  500. Dialog.alert({
  501. message: res.msg,
  502. theme: 'round-button',
  503. confirmButtonColor: '#2DC7AA'
  504. }).then(() => {
  505. if (isApp) {
  506. postMessage({
  507. api: 'goBack'
  508. })
  509. } else {
  510. router.back()
  511. }
  512. })
  513. return
  514. }
  515. state.musicDetail = res.data || {}
  516. try{
  517. const jsonConfig = JSON.parse(res.data.jsonConfig)
  518. jsonConfig.speedRate && (staffState.speedRate = jsonConfig.speedRate)
  519. jsonConfig.musicRenderType && (staffState.musicRenderType = jsonConfig.musicRenderType)
  520. jsonConfig["part-index"] && (staffState.partIndex = jsonConfig["part-index"])
  521. }catch{
  522. }
  523. // 五线谱
  524. initStaff()
  525. getStarList()
  526. // 判断是视频还是音频
  527. if (res.data.videoUrl.lastIndexOf('mp4') !== -1) {
  528. state.playType = 'Video'
  529. } else {
  530. state.playType = 'Audio'
  531. }
  532. // 初始化
  533. nextTick(() => {
  534. initMediaPlay()
  535. })
  536. } catch {
  537. //
  538. }
  539. })
  540. onUnmounted(() => {
  541. document.removeEventListener("scroll", handleScroll)
  542. state._plrl?.destroy()
  543. })
  544. return () => (
  545. <div
  546. style={
  547. {
  548. '--barheight':state.heightV + "px"
  549. }
  550. }
  551. class={[
  552. styles.creation,
  553. isTablet && styles.creationTablet,
  554. isScreenScroll.value && styles.isScreenScroll
  555. ]}>
  556. <div class={styles.creationBg}></div>
  557. <MSticky position="top"
  558. onGetHeight={(height: any) => {
  559. console.log(height, 'height', height)
  560. state.heightV = height
  561. }}
  562. >
  563. <MHeader
  564. color={isScreenScroll.value ? "#333333" : "#ffffff"}
  565. background={isScreenScroll.value ? `rgb(255,255,255` : "transparent"}
  566. title={state.musicDetail?.musicSheetName}
  567. border={false}
  568. isBack={route.query.platformType != 'ANALYSIS'}
  569. onLeftClick={()=>{ setStatusBarTextColor(false) }}
  570. />
  571. </MSticky>
  572. <div class={styles.singer}>
  573. 演奏:{state.musicDetail?.username}
  574. </div>
  575. <Sticky zIndex={1000} offsetTop={state.heightV - 1 + "px"}>
  576. <div class={[styles.playSection, plyrState.mediaTimeShow && styles.mediaTimeShow, isLandscapeScreen.value&&styles.isLandscapeScreen, state.playType === 'Audio' && styles.isLandscapeScreen2]} id="playMediaSection" onClick={handlerClickPlay}>
  577. {
  578. isLandscapeScreen.value &&
  579. <div class={styles.backBox}>
  580. <img class={[styles.backImg, state.playType === 'Video' && styles.back1Img]} src={state.playType === 'Video' ? back1Img : backImg} onClick={handlerBack}/>
  581. <div class={[styles.musicDetail, state.playType === 'Audio' && styles.adMusicDetail]}>
  582. <div class={styles.musicSheetName}>
  583. <NoticeBar
  584. text={state.musicDetail?.musicSheetName}
  585. background="none"
  586. />
  587. </div>
  588. <div class={styles.username}>演奏:{state.musicDetail?.username}</div>
  589. </div>
  590. </div>
  591. }
  592. {
  593. state.playType === 'Audio' &&
  594. <div class={styles.audioBox}>
  595. <canvas class={styles.audioVisualizer} id="audioVisualizer"></canvas>
  596. <audio
  597. crossorigin="anonymous"
  598. id="audioMediaSrc"
  599. src={state.musicDetail?.videoUrl}
  600. controls="false"
  601. preload="metadata"
  602. playsinline
  603. webkit-playsinline
  604. />
  605. <img src={tyBg} class={styles.tyBg} />
  606. <div class={styles.audioBoxBg}>
  607. <div class={[styles.audioPan, plyrState.playIngShow && styles.imgRotate]}>
  608. <img class={styles.audioImg} src={state.musicDetail.img || musicBg} />
  609. </div>
  610. <i class={styles.audioPoint}></i>
  611. <i class={[styles.audioZhen, plyrState.playIngShow && styles.active]}></i>
  612. </div>
  613. </div>
  614. }
  615. {
  616. state.playType === 'Video' &&
  617. <video
  618. id="videoMediaSrc"
  619. class={styles.videoBox}
  620. src={state.musicDetail?.videoUrl}
  621. data-poster={ state.musicDetail?.videoImg || videobg}
  622. poster={ state.musicDetail?.videoImg || videobg}
  623. preload="metadata"
  624. playsinline
  625. webkit-playsinline
  626. x5-playsinline
  627. />
  628. }
  629. <div class={[styles.playLarge, !plyrState.mediaTimeShow && plyrState.playIngShow && styles.playIngShow]}></div>
  630. <div class={styles.mediaTimeCon}>
  631. <div class={styles.mediaTime}>
  632. <div>
  633. {getSecondRPM(plyrState.currentTime)}
  634. </div>
  635. <div class={styles.note}>/</div>
  636. <div class={styles.duration}>
  637. {getSecondRPM(plyrState.duration)}
  638. </div>
  639. </div>
  640. </div>
  641. <div class={styles.landscapeScreen} onClick={handlerLandscapeScreen}></div>
  642. {/* 谱面 */}
  643. {
  644. staffState.staffSrc &&
  645. <div class={[styles.staffBoxCon, staffState.isShow && styles.staffBoxShow]}>
  646. <div
  647. class={[styles.staffBox, state.playType === 'Video' && styles.staffBoxBg]}
  648. style={
  649. {
  650. '--staffBoxHeight':staffState.height
  651. }
  652. }
  653. >
  654. <div class={styles.mask}></div>
  655. <iframe
  656. ref={staffDom}
  657. class={styles.staff}
  658. frameborder="0"
  659. src={staffState.staffSrc}>
  660. </iframe>
  661. </div>
  662. </div>
  663. }
  664. </div>
  665. </Sticky>
  666. <div class={styles.musicSection}>
  667. <div class={styles.avatarInfoBox}>
  668. <div class={styles.avatar}>
  669. <div class={styles.avatarImg}>
  670. <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} />
  671. {
  672. (state.musicDetail.vipType === 'VIP' || state.musicDetail.vipType === 'PERMANENT_SVIP' || state.musicDetail.vipType === 'SVIP') &&
  673. <img class={styles.vipIcon} src={state.musicDetail.vipType === 'VIP' ? vipIcon : svipIcon} />
  674. }
  675. </div>
  676. <div class={styles.infoCon}>
  677. <div class={styles.info}>
  678. <span class={styles.userName}>{state.musicDetail?.username}</span>
  679. {state.musicDetail.vipFlag && (
  680. <img src={iconMember} class={styles.iconMember} />
  681. )}
  682. </div>
  683. <div class={styles.sub}>
  684. {state.musicDetail.subjectName}{' '}
  685. {getGradeCh(state.musicDetail.currentGradeNum - 1)}
  686. </div>
  687. </div>
  688. </div>
  689. <div class={styles.linkes}>
  690. <img src={iconZan} class={styles.iconZan} />
  691. <span>{state.musicDetail.likeNum}</span>
  692. </div>
  693. </div>
  694. <TextEllipsis class={styles.textEllipsis} text={state.musicDetail.desc || ''} />
  695. </div>
  696. <div class={styles.likeSection}>
  697. <div class={styles.likeTitle}>点赞记录</div>
  698. {state.listState.dataShow ? (
  699. <List
  700. finished={state.listState.finished}
  701. finishedText=" "
  702. onLoad={getStarList}
  703. immediateCheck={false}>
  704. {state.list.map((item: any, index: number) => (
  705. <Cell
  706. class={[styles.likeItem, index===state.list.length-1&&styles.likeItemLast]}
  707. border={false}
  708. >
  709. {{
  710. icon: () => (
  711. <Image src={item.userAvatar} class={styles.userLogo} />
  712. ),
  713. title: () => (
  714. <div class={styles.userInfo}>
  715. <p class={styles.name}>{item.userName}</p>
  716. <p class={styles.sub}>
  717. {item.subjectName}{' '}
  718. {getGradeCh(item.currentGradeNum - 1)}
  719. </p>
  720. </div>
  721. ),
  722. value: () => (
  723. <div class={styles.time}>
  724. {dayjs(item.createTime).format('YYYY-MM-DD HH:mm')}
  725. </div>
  726. )
  727. }}
  728. </Cell>
  729. ))}
  730. </List>
  731. ) : (
  732. <MEmpty class={styles.mEmpty} tips="暂无内容" btnStatus={false} />
  733. )}
  734. </div>
  735. {
  736. !isScreenScroll.value &&
  737. <MSticky ref={mStickyUpward} position="bottom" offsetBottom={state.heightB - 1 + "px"} >
  738. <div class={styles.upward}>
  739. <img src={iconUpward} />
  740. </div>
  741. </MSticky>
  742. }
  743. <MSticky ref={mStickyBottom} position="bottom" onGetHeight={(height: any) => {
  744. console.log(height, 'height', height)
  745. state.heightB = height
  746. }}>
  747. <div class={styles.bottomSection}>
  748. <div class={styles.bottomShare}>
  749. <p onClick={onDownload}>
  750. <img src={iconDownload} />
  751. <span>下载</span>
  752. </p>
  753. <p onClick={() => (state.shareStatus = true)}>
  754. <img src={iconShare} />
  755. <span>分享</span>
  756. </p>
  757. <p onClick={() => (state.deleteStatus = true)}>
  758. <img src={iconDelete} />
  759. <span>删除</span>
  760. </p>
  761. </div>
  762. <img src={iconEdit}
  763. class={styles.btnEdit}
  764. onClick={() => {
  765. router.push({
  766. path: '/creation-edit',
  767. query: {
  768. id: state.id
  769. }
  770. });
  771. }}
  772. />
  773. </div>
  774. </MSticky>
  775. <Popup
  776. v-model:show={state.deleteStatus}
  777. round
  778. class={styles.popupContainer}
  779. >
  780. <p class={styles.popupTit}>温馨提示</p>
  781. <p class={styles.popupContent}>确认删除作品吗?</p>
  782. <div class={styles.popupBtnGroup}>
  783. <Button round onClick={() => (state.deleteStatus = false)}>
  784. 取消
  785. </Button>
  786. <Button round type="primary" onClick={onDelete}>
  787. 确认
  788. </Button>
  789. </div>
  790. </Popup>
  791. <Popup
  792. position="bottom"
  793. v-model:show={state.shareStatus}
  794. style={{ background: 'transparent' }}
  795. >
  796. <ShareModel
  797. playType={state.playType}
  798. musicDetail={state.musicDetail}
  799. onClose={() => (state.shareStatus = false)}
  800. />
  801. </Popup>
  802. {
  803. !staffState.isShow && <Loading></Loading>
  804. }
  805. </div>
  806. )
  807. }
  808. })