|
@@ -0,0 +1,464 @@
|
|
|
+import { defineComponent, nextTick, onMounted, onUnmounted, reactive, watch } from 'vue'
|
|
|
+// import WaveSurfer from 'wavesurfer.js';
|
|
|
+import styles from './index.module.less'
|
|
|
+import MSticky from '@/components/o-sticky'
|
|
|
+import MHeader from '@/components/o-header'
|
|
|
+import { Button, Cell, Image, List, Popup, Slider, showDialog, showToast } from 'vant'
|
|
|
+import iconDownload from './images/icon-download.png'
|
|
|
+import iconShare from './images/icon-share.png'
|
|
|
+import iconDelete from './images/icon-delete.png'
|
|
|
+import iconMember from './images/icon-member.png'
|
|
|
+import iconZan from './images/icon-zan.png'
|
|
|
+import iconZanActive from './images/icon-zan-active.png'
|
|
|
+import iconPlay from './images/icon-play.png'
|
|
|
+import iconPause from './images/icon-pause.png'
|
|
|
+import { postMessage, promisefiyPostMessage } from '@/helpers/native-message'
|
|
|
+import { browser, getGradeCh, getSecondRPM } from '@/helpers/utils'
|
|
|
+import { useRoute, useRouter } from 'vue-router'
|
|
|
+import { api_userMusicDetail, api_userMusicRemove, api_userMusicStarPage } from './api'
|
|
|
+import MEmpty from '@/components/o-empty'
|
|
|
+import dayjs from 'dayjs'
|
|
|
+import MVideo from '@/components/the-video-tcplayer'
|
|
|
+import ShareModel from './share-model'
|
|
|
+import { usePageVisibility } from '@vant/use'
|
|
|
+import videoBg from './images/video-bg.png'
|
|
|
+
|
|
|
+export default defineComponent({
|
|
|
+ name: 'creation-detail',
|
|
|
+ setup() {
|
|
|
+ const route = useRoute()
|
|
|
+ const router = useRouter()
|
|
|
+ const audioId = 'a' + +Date.now() + Math.floor(Math.random() * 100)
|
|
|
+
|
|
|
+ const state = reactive({
|
|
|
+ id: route.query.id,
|
|
|
+ deleteStatus: false,
|
|
|
+ shareStatus: false,
|
|
|
+ playType: '' as 'Audio' | 'Video' | '', // 播放类型
|
|
|
+ musicDetail: {} as any,
|
|
|
+ timer: null as any,
|
|
|
+ audioWidth: 0,
|
|
|
+ paused: true,
|
|
|
+ currentTime: 0,
|
|
|
+ duration: 0.1,
|
|
|
+ loop: false,
|
|
|
+ dragStatus: false, // 是否开始拖动
|
|
|
+ isClick: false,
|
|
|
+ list: [] as any,
|
|
|
+ listState: {
|
|
|
+ dataShow: true, // 判断是否有数据
|
|
|
+ loading: false,
|
|
|
+ finished: false
|
|
|
+ },
|
|
|
+ params: {
|
|
|
+ page: 1,
|
|
|
+ rows: 20
|
|
|
+ }
|
|
|
+ })
|
|
|
+ const audioDom = new Audio()
|
|
|
+ audioDom.controls = true
|
|
|
+ audioDom.style.width = '100%'
|
|
|
+ audioDom.className = styles.audio
|
|
|
+
|
|
|
+ /** 改变播放时间 */
|
|
|
+ const handleChangeTime = (val: number) => {
|
|
|
+ state.currentTime = val
|
|
|
+ clearTimeout(state.timer)
|
|
|
+ state.timer = setTimeout(() => {
|
|
|
+ // audioRef.value.currentTime = val;
|
|
|
+ audioDom.currentTime = val
|
|
|
+ state.timer = null
|
|
|
+ }, 60)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 切换音频播放
|
|
|
+ const onToggleAudio = (e: any) => {
|
|
|
+ e.stopPropagation()
|
|
|
+ if (audioDom.paused) {
|
|
|
+ audioDom.play()
|
|
|
+ } else {
|
|
|
+ audioDom.pause()
|
|
|
+ }
|
|
|
+
|
|
|
+ state.paused = audioDom.paused
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取列表
|
|
|
+ const getStarList = async () => {
|
|
|
+ try {
|
|
|
+ if (state.isClick) return
|
|
|
+ state.isClick = true
|
|
|
+ const res = await api_userMusicStarPage({
|
|
|
+ userMusicId: state.id,
|
|
|
+ ...state.params
|
|
|
+ })
|
|
|
+ state.listState.loading = false
|
|
|
+ const result = res.data || {}
|
|
|
+ // 处理重复请求数据
|
|
|
+ if (state.list.length > 0 && result.current === 1) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ state.list = state.list.concat(result.rows || [])
|
|
|
+ state.listState.finished = result.current >= result.pages
|
|
|
+ state.params.page = result.current + 1
|
|
|
+ state.listState.dataShow = state.list.length > 0
|
|
|
+ state.isClick = false
|
|
|
+ } catch {
|
|
|
+ state.listState.dataShow = false
|
|
|
+ state.listState.finished = true
|
|
|
+ state.isClick = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const initAudio = () => {
|
|
|
+ audioDom.src = state.musicDetail.videoUrl
|
|
|
+ audioDom.load()
|
|
|
+ audioDom.oncanplaythrough = () => {
|
|
|
+ state.paused = audioDom.paused
|
|
|
+ state.duration = audioDom.duration
|
|
|
+ }
|
|
|
+ // 播放时监听
|
|
|
+ audioDom.addEventListener('timeupdate', () => {
|
|
|
+ state.duration = audioDom.duration
|
|
|
+ state.currentTime = audioDom.currentTime
|
|
|
+ const rate = (state.currentTime / state.duration) * 100
|
|
|
+ state.audioWidth = rate > 100 ? 100 : rate
|
|
|
+ })
|
|
|
+ audioDom.addEventListener('ended', () => {
|
|
|
+ state.paused = audioDom.paused
|
|
|
+ })
|
|
|
+ // const wavesurfer = WaveSurfer.create({
|
|
|
+ // container: document.querySelector(`#${audioId}`) as HTMLElement,
|
|
|
+ // waveColor: '#fff',
|
|
|
+ // progressColor: '#2FA1FD',
|
|
|
+ // url: state.musicDetail.videoUrl,
|
|
|
+ // cursorWidth: 0,
|
|
|
+ // height: 35,
|
|
|
+ // width: 'auto',
|
|
|
+ // normalize: true,
|
|
|
+ // // Set a bar width
|
|
|
+ // barWidth: 2,
|
|
|
+ // // Optionally, specify the spacing between bars
|
|
|
+ // barGap: 2,
|
|
|
+ // // And the bar radius
|
|
|
+ // barRadius: 4,
|
|
|
+ // barHeight: 0.6,
|
|
|
+ // autoScroll: true,
|
|
|
+ // /** If autoScroll is enabled, keep the cursor in the center of the waveform during playback */
|
|
|
+ // autoCenter: true,
|
|
|
+ // hideScrollbar: false,
|
|
|
+ // media: audioDom
|
|
|
+ // });
|
|
|
+
|
|
|
+ // wavesurfer.once('interaction', () => {
|
|
|
+ // // wavesurfer.play();
|
|
|
+ // });
|
|
|
+ // wavesurfer.once('ready', () => {
|
|
|
+ // state.paused = audioDom.paused;
|
|
|
+ // state.duration = audioDom.duration;
|
|
|
+ // });
|
|
|
+
|
|
|
+ // wavesurfer.on('finish', () => {
|
|
|
+ // state.paused = true;
|
|
|
+ // });
|
|
|
+
|
|
|
+ // // 播放时监听
|
|
|
+ // audioDom.addEventListener('timeupdate', () => {
|
|
|
+ // state.currentTime = audioDom.currentTime;
|
|
|
+ // });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 删除作品
|
|
|
+ const onDelete = async () => {
|
|
|
+ try {
|
|
|
+ await api_userMusicRemove({ id: state.id })
|
|
|
+
|
|
|
+ setTimeout(() => {
|
|
|
+ state.deleteStatus = false
|
|
|
+ showToast('删除成功')
|
|
|
+ }, 100)
|
|
|
+
|
|
|
+ setTimeout(() => {
|
|
|
+ if (browser().isApp) {
|
|
|
+ postMessage({
|
|
|
+ api: 'goBack'
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ router.back()
|
|
|
+ }
|
|
|
+ }, 1200)
|
|
|
+ } catch {
|
|
|
+ //
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 下载
|
|
|
+ const onDownload = async () => {
|
|
|
+ await promisefiyPostMessage({
|
|
|
+ api: 'saveFile',
|
|
|
+ content: {
|
|
|
+ url: state.musicDetail.videoUrl
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ const pageVisibility = usePageVisibility()
|
|
|
+ watch(pageVisibility, (value) => {
|
|
|
+ if (value === 'hidden') {
|
|
|
+ if (audioDom) {
|
|
|
+ audioDom.pause()
|
|
|
+ state.paused = audioDom.paused
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ onMounted(async () => {
|
|
|
+ try {
|
|
|
+ const res = await api_userMusicDetail(state.id)
|
|
|
+ // console.log(res);
|
|
|
+ if (res.code === 999) {
|
|
|
+ showDialog({
|
|
|
+ message: res.message,
|
|
|
+ theme: 'round-button',
|
|
|
+ confirmButtonColor: 'linear-gradient(73deg, #5BECFF 0%, #259CFE 100%)'
|
|
|
+ }).then(() => {
|
|
|
+ if (browser().isApp) {
|
|
|
+ postMessage({
|
|
|
+ api: 'goBack'
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ router.back()
|
|
|
+ }
|
|
|
+ })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ state.musicDetail = res.data || {}
|
|
|
+ getStarList()
|
|
|
+ // 判断是视频还是音频
|
|
|
+ if (res.data.videoUrl.lastIndexOf('mp4') !== -1) {
|
|
|
+ state.playType = 'Video'
|
|
|
+ } else {
|
|
|
+ state.playType = 'Audio'
|
|
|
+ // 初始化
|
|
|
+ nextTick(() => {
|
|
|
+ initAudio()
|
|
|
+ })
|
|
|
+ }
|
|
|
+ } catch {
|
|
|
+ //
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ onUnmounted(() => {
|
|
|
+ if (audioDom) {
|
|
|
+ audioDom.pause()
|
|
|
+ state.paused = audioDom.paused
|
|
|
+ }
|
|
|
+ })
|
|
|
+ return () => (
|
|
|
+ <div class={styles.creation}>
|
|
|
+ <MSticky position="top">
|
|
|
+ <MHeader border={false} isBack={route.query.platformType != 'ANALYSIS'} />
|
|
|
+ </MSticky>
|
|
|
+ <div class={styles.playSection}>
|
|
|
+ {state.playType === 'Video' && (
|
|
|
+ <MVideo
|
|
|
+ src={state.musicDetail?.videoUrl}
|
|
|
+ poster={state.musicDetail?.videoImg || videoBg}
|
|
|
+ />
|
|
|
+ )}
|
|
|
+ {state.playType === 'Audio' && (
|
|
|
+ <div class={styles.audioSection}>
|
|
|
+ <div class={styles.audioContainer}>
|
|
|
+ {/* <div
|
|
|
+ id={audioId}
|
|
|
+ onClick={(e: MouseEvent) => {
|
|
|
+ e.stopPropagation();
|
|
|
+ }}></div> */}
|
|
|
+ <div
|
|
|
+ class={styles.waveActive}
|
|
|
+ style={{
|
|
|
+ width: state.audioWidth + '%'
|
|
|
+ }}
|
|
|
+ ></div>
|
|
|
+ <div class={styles.waveDefault}></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class={styles.audioBox}>
|
|
|
+ <div class={[styles.audioPan, state.paused && styles.imgRotate]}>
|
|
|
+ <Image class={styles.audioImg} src={state.musicDetail?.img} />
|
|
|
+ </div>
|
|
|
+ <i class={styles.audioPoint}></i>
|
|
|
+ <i class={[styles.audioZhen, state.paused && styles.active]}></i>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class={[styles.controls]}
|
|
|
+ onClick={(e: Event) => {
|
|
|
+ e.stopPropagation()
|
|
|
+ }}
|
|
|
+ onTouchmove={(e: TouchEvent) => {
|
|
|
+ // emit('close');
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <div class={styles.actions}>
|
|
|
+ <div class={styles.actionBtn} onClick={onToggleAudio}>
|
|
|
+ <img src={state.paused ? iconPlay : iconPause} />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class={[styles.slider]}>
|
|
|
+ <Slider
|
|
|
+ step={0.01}
|
|
|
+ class={styles.timeProgress}
|
|
|
+ v-model={state.currentTime}
|
|
|
+ max={state.duration}
|
|
|
+ onUpdate:modelValue={(val) => {
|
|
|
+ handleChangeTime(val)
|
|
|
+ }}
|
|
|
+ onDragStart={() => {
|
|
|
+ state.dragStatus = true
|
|
|
+ console.log('onDragStart')
|
|
|
+ }}
|
|
|
+ onDragEnd={() => {
|
|
|
+ state.dragStatus = false
|
|
|
+ console.log('onDragEnd')
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div class={styles.time}>
|
|
|
+ <div>{getSecondRPM(state.currentTime)}</div>
|
|
|
+ <span>/</span>
|
|
|
+ <div>{getSecondRPM(state.duration)}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <Cell class={styles.userSection} center border={false}>
|
|
|
+ {{
|
|
|
+ icon: () => <Image class={styles.userLogo} src={state.musicDetail.avatar} />,
|
|
|
+ title: () => (
|
|
|
+ <div class={styles.userInfo}>
|
|
|
+ <p class={styles.name}>
|
|
|
+ <span>{state.musicDetail?.username}</span>
|
|
|
+ {state.musicDetail.vipFlag && <img src={iconMember} class={styles.iconMember} />}
|
|
|
+ </p>
|
|
|
+ <p class={styles.sub}>
|
|
|
+ {state.musicDetail.subjectName}{' '}
|
|
|
+ {getGradeCh(state.musicDetail.currentGradeNum - 1)}
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ ),
|
|
|
+ value: () => (
|
|
|
+ <div class={[styles.zan, styles.zanActive]}>
|
|
|
+ <img src={iconZanActive} class={styles.iconZan} />
|
|
|
+ {state.musicDetail.likeNum}
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+ }}
|
|
|
+ </Cell>
|
|
|
+
|
|
|
+ <div class={styles.musicSection}>
|
|
|
+ <div class={styles.musicName}>
|
|
|
+ <span class={styles.musicTag}>曲目名称</span>
|
|
|
+ {state.musicDetail?.musicSheetName}
|
|
|
+ </div>
|
|
|
+ {state.musicDetail.desc && <div class={styles.musicDesc}>{state.musicDetail.desc}</div>}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class={styles.likeSection}>
|
|
|
+ <div class={styles.likeTitle}>点赞记录</div>
|
|
|
+
|
|
|
+ {state.listState.dataShow ? (
|
|
|
+ <List
|
|
|
+ finished={state.listState.finished}
|
|
|
+ finishedText=" "
|
|
|
+ class={[styles.container, styles.containerInformation]}
|
|
|
+ onLoad={getStarList}
|
|
|
+ immediateCheck={false}
|
|
|
+ >
|
|
|
+ {state.list.map((item: any, index: number) => (
|
|
|
+ <Cell
|
|
|
+ class={styles.likeItem}
|
|
|
+ border={state.list.length - 1 == index ? false : true}
|
|
|
+ >
|
|
|
+ {{
|
|
|
+ icon: () => <Image src={item.userAvatar} class={styles.userLogo} />,
|
|
|
+ title: () => (
|
|
|
+ <div class={styles.userInfo}>
|
|
|
+ <p class={styles.name}>{item.userName}</p>
|
|
|
+ <p class={styles.sub}>
|
|
|
+ {item.subjectName} {getGradeCh(item.currentGradeNum - 1)}
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ ),
|
|
|
+ value: () => (
|
|
|
+ <div class={styles.time}>
|
|
|
+ {dayjs(item.createTime).format('YYYY-MM-DD HH:mm')}
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+ }}
|
|
|
+ </Cell>
|
|
|
+ ))}
|
|
|
+ </List>
|
|
|
+ ) : (
|
|
|
+ <MEmpty description="暂无数据" />
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <MSticky position="bottom">
|
|
|
+ <div class={styles.bottomSection}>
|
|
|
+ <div class={styles.bottomShare}>
|
|
|
+ <p onClick={onDownload}>
|
|
|
+ <img src={iconDownload} />
|
|
|
+ <span>下载</span>
|
|
|
+ </p>
|
|
|
+ <p onClick={() => (state.shareStatus = true)}>
|
|
|
+ <img src={iconShare} />
|
|
|
+ <span>分享</span>
|
|
|
+ </p>
|
|
|
+ <p onClick={() => (state.deleteStatus = true)}>
|
|
|
+ <img src={iconDelete} />
|
|
|
+ <span>删除</span>
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ <Button
|
|
|
+ round
|
|
|
+ class={styles.btnEdit}
|
|
|
+ type="primary"
|
|
|
+ onClick={() => {
|
|
|
+ router.push({
|
|
|
+ path: '/creation-edit',
|
|
|
+ query: {
|
|
|
+ id: state.id
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ 编辑
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
+ </MSticky>
|
|
|
+
|
|
|
+ <Popup v-model:show={state.deleteStatus} round class={styles.popupContainer}>
|
|
|
+ <p class={styles.popupContent}>确定删除吗?</p>
|
|
|
+ <div class={styles.popupBtnGroup}>
|
|
|
+ <Button round onClick={() => (state.deleteStatus = false)}>
|
|
|
+ 取消
|
|
|
+ </Button>
|
|
|
+ <Button round type="primary" onClick={onDelete}>
|
|
|
+ 确定
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
+ </Popup>
|
|
|
+
|
|
|
+ <Popup
|
|
|
+ position="bottom"
|
|
|
+ v-model:show={state.shareStatus}
|
|
|
+ style={{ background: 'transparent' }}
|
|
|
+ >
|
|
|
+ <ShareModel musicDetail={state.musicDetail} onClose={() => (state.shareStatus = false)} />
|
|
|
+ </Popup>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+ }
|
|
|
+})
|