index.tsx 25 KB

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