index-share.tsx 26 KB

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