index-share.tsx 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734
  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 } 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. {state.musicDetail?.musicSheetName}
  508. </div>
  509. <div class={styles.singerName}>
  510. 演奏:{state.musicDetail?.username}
  511. </div>
  512. </div>
  513. <Sticky offsetTop={state.heightV - 1 + "px"}>
  514. <div class={[styles.playSection, plyrState.mediaTimeShow && styles.mediaTimeShow,!plyrState.loaded && styles.notLoaded]} id="playMediaSection" onClick={handlerClickPlay}>
  515. {
  516. state.playType &&
  517. <>
  518. {
  519. state.playType === 'Audio' &&
  520. <div class={styles.audioBox}>
  521. <canvas class={styles.audioVisualizer} id="audioVisualizer"></canvas>
  522. <Vue3Lottie class={styles.audioBga} animationData={audioBga} autoPlay={true} loop={true}></Vue3Lottie>
  523. <Vue3Lottie class={styles.audioBga1} animationData={audioBga1} autoPlay={true} loop={true}></Vue3Lottie>
  524. <Vue3Lottie class={styles.audioBga2} animationData={audioBga2} autoPlay={true} loop={true}></Vue3Lottie>
  525. <audio
  526. crossorigin="anonymous"
  527. id="audioMediaSrc"
  528. src={state.musicDetail?.videoUrl}
  529. controls="false"
  530. preload="metadata"
  531. playsinline
  532. />
  533. </div>
  534. }
  535. {
  536. state.playType === 'Video' &&
  537. <video
  538. id="videoMediaSrc"
  539. class={styles.videoBox}
  540. src={state.musicDetail?.videoUrl}
  541. data-poster={ state.musicDetail?.videoImg || videobg}
  542. preload="metadata"
  543. playsinline
  544. webkit-playsinline
  545. />
  546. }
  547. <div class={[styles.playLarge, !plyrState.mediaTimeShow && plyrState.playIngShow && styles.playIngShow]}></div>
  548. <div class={styles.mediaTimeCon}>
  549. <div class={styles.mediaTime}>
  550. <div>
  551. {getSecondRPM(plyrState.currentTime)}
  552. </div>
  553. <div class={styles.note}>/</div>
  554. <div class={styles.duration}>
  555. {getSecondRPM(plyrState.duration)}
  556. </div>
  557. </div>
  558. </div>
  559. <div class={styles.landscapeScreen} onClick={handlerLandscapeScreen}></div>
  560. {/* 谱面 */}
  561. {
  562. staffState.staffSrc &&
  563. <div
  564. class={[styles.staffBox, staffState.isShow && styles.staffBoxShow]}
  565. style={
  566. {
  567. '--staffBoxHeight':staffState.height
  568. }
  569. }
  570. >
  571. <div class={styles.mask}></div>
  572. <iframe
  573. ref={staffDom}
  574. class={styles.staff}
  575. frameborder="0"
  576. src={staffState.staffSrc}>
  577. </iframe>
  578. </div>
  579. }
  580. </>
  581. }
  582. </div>
  583. </Sticky>
  584. <div class={[styles.musicSection, styles.musicShareSection]}>
  585. <div class={styles.avatarInfoBox}>
  586. <div class={styles.avatar}>
  587. <Image class={styles.userLogo} src={state.musicDetail.avatar} />
  588. <div class={styles.infoCon}>
  589. <div class={styles.info}>
  590. <span class={styles.userName}>{state.musicDetail?.username}</span>
  591. {state.musicDetail.vipFlag && (
  592. <img src={iconMember} class={styles.iconMember} />
  593. )}
  594. </div>
  595. <div class={styles.sub}>
  596. {state.musicDetail.subjectName}{' '}
  597. {getGradeCh(state.musicDetail.currentGradeNum - 1)}
  598. </div>
  599. </div>
  600. </div>
  601. <div class={styles.linkes} onClick={onStarChange}>
  602. <img src={state.musicDetail.starFlag ? iconZanActive : iconZan} class={styles.iconZan} />
  603. <span>{state.musicDetail.likeNum}</span>
  604. </div>
  605. </div>
  606. <TextEllipsis class={styles.textEllipsis} rows={2} content={state.musicDetail?.desc} expand-text="展开" collapse-text="收起" />
  607. </div>
  608. <div class={styles.likeSection}>
  609. <div class={styles.likeTitle}>推荐作品</div>
  610. {state.listState.dataShow ? (
  611. <>
  612. <List
  613. finished={true}
  614. finishedText=" "
  615. class={[styles.container, styles.containerInformation]}
  616. //onLoad={getList}
  617. immediateCheck={false}>
  618. {state.list.map((item: any, index:number) => (
  619. <Cell
  620. class={[styles.likeShareItem, index===state.list.length-1&&styles.likeShareItemLast]}
  621. border={false}
  622. onClick={() => onDetail(item)}
  623. >
  624. {{
  625. icon: () => (
  626. <div class={styles.audioImgBox}>
  627. <img
  628. src={audioPan}
  629. class={styles.audioPan}
  630. crossorigin="anonymous"
  631. />
  632. <img
  633. src={
  634. item.img || musicBg
  635. }
  636. class={styles.muploader}
  637. crossorigin="anonymous"
  638. />
  639. <img class={styles.imgLabel} src={item.videoUrl?.lastIndexOf('mp4') !== -1 ? videoLabel : audioLabel} />
  640. </div>
  641. ),
  642. title: () => (
  643. <div class={styles.userInfo}>
  644. <div class={[styles.musicSheetName,'van-ellipsis']}>{item.musicSheetName}</div>
  645. <div class={styles.usernameCon}>
  646. <div class={styles.likeNum}>
  647. <img src={iconZanActive} />
  648. <span>{item.likeNum}</span>
  649. </div>
  650. <div class={[styles.username, 'van-ellipsis']}>{item.username}</div>
  651. </div>
  652. </div>
  653. ),
  654. value: () => (
  655. <img src={playImg} class={styles.playImg} />
  656. )
  657. }}
  658. </Cell>
  659. ))}
  660. </List>
  661. {
  662. (!state.listState.finished || state.params.page>2) &&
  663. <div class={styles.btnImg}>
  664. <img onClick={handleChangeList} onTouchstart={()=>{}} src={btnImg} />
  665. </div>
  666. }
  667. </>
  668. ) : (
  669. <MEmpty image={"empty2"} description="暂无作品" />
  670. )}
  671. </div>
  672. {
  673. !isScreenScroll.value &&
  674. <MSticky position="bottom" offsetBottom={state.heightB - 1 + "px"} >
  675. <div class={styles.upward}>
  676. <img src={iconUpward} />
  677. </div>
  678. </MSticky>
  679. }
  680. </>
  681. }
  682. <Popup
  683. v-model:show={state.loginStatus}
  684. style={{ background: 'transparent', overflow: 'inherit' }}>
  685. <LoginModel
  686. onClose={() => (state.loginStatus = false)}
  687. onConfirm={async (val: any) => {
  688. if (val.loginTag) {
  689. state.loginTag = val.loginTag;
  690. state.loginStatus = false;
  691. const { data } = await api_openUserMusicDetail(state.id);
  692. state.musicDetail = data;
  693. } else {
  694. state.credential = val.data;
  695. state.loginChangeState = true;
  696. state.loginStatus = false;
  697. }
  698. }}
  699. />
  700. </Popup>
  701. <Popup
  702. v-model:show={state.loginChangeState}
  703. style={{ background: 'transparent', overflow: 'inherit' }}>
  704. <LoginChangeModel
  705. credential={state.credential}
  706. onClose={() => {
  707. state.credential = {};
  708. state.loginChangeState = false;
  709. }}
  710. onConfirm={async (val: any) => {
  711. state.loginTag = val.loginTag;
  712. state.loginChangeState = false;
  713. const { data } = await api_openUserMusicDetail(state.id);
  714. state.musicDetail = data;
  715. }}
  716. />
  717. </Popup>
  718. <MWxTip
  719. v-model:show={state.messageStatus}
  720. message={state.message}
  721. showButton={false}
  722. />
  723. {
  724. !staffState.isShow && <Loading></Loading>
  725. }
  726. </div>
  727. );
  728. }
  729. });