index-share.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. import { defineComponent, onMounted, reactive, ref } from 'vue';
  2. import WaveSurfer from 'wavesurfer.js';
  3. import styles from './index.module.less';
  4. import MSticky from '@/components/m-sticky';
  5. import MHeader from '@/components/m-header';
  6. import { Cell, Image, List, Popup, Slider } from 'vant';
  7. import iconMember from './images/icon-member.png';
  8. import iconZan from './images/icon-zan.png';
  9. import iconZanActive from './images/icon-zan-active.png';
  10. import iconZ from './images/icon-z.png';
  11. import iconPlay from './images/icon-play.png';
  12. import iconPause from './images/icon-pause.png';
  13. import { browser, getGradeCh, getSecondRPM } from '@/helpers/utils';
  14. import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router';
  15. import {
  16. api_openUserMusicDetail,
  17. api_openUserMusicPage,
  18. api_userMusicStar,
  19. api_verification
  20. } from './api';
  21. import MEmpty from '@/components/m-empty';
  22. import { nextTick } from 'process';
  23. import MVideo from '@/components/m-video';
  24. import LoginModel from './login-model';
  25. import { removeAuth } from '../student-register/layout/utils';
  26. import { setLogout } from '@/state';
  27. import { storage } from '@/helpers/storage';
  28. import { ACCESS_TOKEN } from '@/store/mutation-types';
  29. export default defineComponent({
  30. name: 'creation-detail',
  31. setup() {
  32. const route = useRoute();
  33. const router = useRouter();
  34. const audioId = 'a' + +Date.now() + Math.floor(Math.random() * 100);
  35. const state = reactive({
  36. id: route.query.id,
  37. loginTag: false, // 是否登录标识
  38. loginStatus: false,
  39. playType: '' as 'Audio' | 'Video' | '', // 播放类型
  40. musicDetail: {} as any,
  41. timer: null as any,
  42. paused: true,
  43. currentTime: 0,
  44. duration: 0.1,
  45. loop: false,
  46. dragStatus: false, // 是否开始拖动
  47. isClick: false,
  48. list: [] as any,
  49. listState: {
  50. dataShow: true, // 判断是否有数据
  51. loading: false,
  52. finished: false
  53. },
  54. params: {
  55. page: 1,
  56. rows: 20
  57. }
  58. });
  59. const wavesurfer = ref();
  60. const audioDom = new Audio();
  61. audioDom.controls = true;
  62. audioDom.style.width = '100%';
  63. audioDom.className = styles.audio;
  64. /** 改变播放时间 */
  65. const handleChangeTime = (val: number) => {
  66. state.currentTime = val;
  67. clearTimeout(state.timer);
  68. state.timer = setTimeout(() => {
  69. audioDom.currentTime = val;
  70. state.timer = null;
  71. }, 60);
  72. };
  73. // 切换音频播放
  74. const onToggleAudio = (e: any) => {
  75. e.stopPropagation();
  76. if (audioDom.paused) {
  77. audioDom.play();
  78. } else {
  79. audioDom.pause();
  80. }
  81. state.paused = audioDom.paused;
  82. };
  83. // 点赞
  84. const onStarChange = async () => {
  85. // 是否登录
  86. if (!state.loginTag) {
  87. state.loginStatus = true;
  88. return;
  89. }
  90. try {
  91. await api_userMusicStar({
  92. userMusicId: state.id,
  93. star: !state.musicDetail.starFlag
  94. });
  95. state.musicDetail.starFlag = !state.musicDetail.starFlag;
  96. if (state.musicDetail.starFlag) {
  97. state.musicDetail.likeNum += 1;
  98. } else {
  99. state.musicDetail.likeNum -= 1;
  100. }
  101. } catch {
  102. //
  103. }
  104. };
  105. // 获取列表
  106. const getList = async () => {
  107. try {
  108. if (state.isClick) return;
  109. state.isClick = true;
  110. const res = await api_openUserMusicPage({
  111. type: 'FORMAL',
  112. exclusionId: state.id,
  113. ...state.params
  114. });
  115. state.listState.loading = false;
  116. const result = res.data || {};
  117. // 处理重复请求数据
  118. if (state.list.length > 0 && result.current === 1) {
  119. return;
  120. }
  121. state.list = state.list.concat(result.rows || []);
  122. state.listState.finished = result.current >= result.pages;
  123. state.params.page = result.current + 1;
  124. state.listState.dataShow = state.list.length > 0;
  125. state.isClick = false;
  126. } catch {
  127. state.listState.dataShow = false;
  128. state.listState.finished = true;
  129. state.isClick = false;
  130. }
  131. };
  132. const onDetail = (item: any) => {
  133. console.log(item, 'item');
  134. router.push({
  135. path: '/shareCreation',
  136. query: {
  137. id: item.id
  138. }
  139. });
  140. };
  141. const initAudio = () => {
  142. wavesurfer.value = WaveSurfer.create({
  143. container: document.querySelector(`#${audioId}`) as HTMLElement,
  144. waveColor: '#fff',
  145. progressColor: '#2FA1FD',
  146. url: state.musicDetail.videoUrl,
  147. cursorWidth: 0,
  148. height: 35,
  149. width: 'auto',
  150. normalize: true,
  151. // Set a bar width
  152. barWidth: 2,
  153. // Optionally, specify the spacing between bars
  154. barGap: 2,
  155. // And the bar radius
  156. barRadius: 4,
  157. barHeight: 0.6,
  158. autoScroll: true,
  159. /** If autoScroll is enabled, keep the cursor in the center of the waveform during playback */
  160. autoCenter: true,
  161. hideScrollbar: false,
  162. media: audioDom
  163. });
  164. wavesurfer.value.once('interaction', () => {
  165. // wavesurfer.value.play();
  166. });
  167. wavesurfer.value.once('ready', () => {
  168. state.paused = audioDom.paused;
  169. state.duration = audioDom.duration;
  170. });
  171. wavesurfer.value.on('finish', () => {
  172. state.paused = true;
  173. });
  174. // 播放时监听
  175. audioDom.addEventListener('timeupdate', () => {
  176. state.currentTime = audioDom.currentTime;
  177. });
  178. };
  179. const __init = async () => {
  180. try {
  181. // 判断是否登录
  182. const Authorization = storage.get(ACCESS_TOKEN) || '';
  183. if (Authorization) {
  184. const res = await api_verification({
  185. token: Authorization
  186. });
  187. console.log(res, 'res');
  188. state.loginTag = res.data;
  189. if (!res.data) {
  190. removeAuth();
  191. setLogout();
  192. }
  193. }
  194. const { data } = await api_openUserMusicDetail(state.id);
  195. state.musicDetail = data;
  196. getList();
  197. // 判断是视频还是音频
  198. if (data.videoFilePath) {
  199. state.playType = 'Video';
  200. } else {
  201. state.playType = 'Audio';
  202. // 初始化
  203. nextTick(() => {
  204. initAudio();
  205. });
  206. }
  207. } catch {
  208. //
  209. }
  210. };
  211. onMounted(async () => {
  212. __init();
  213. });
  214. onBeforeRouteUpdate((to: any) => {
  215. state.id = to.query.id;
  216. state.playType = '';
  217. state.params.page = 1;
  218. __init();
  219. });
  220. return () => (
  221. <div class={styles.creation}>
  222. <MSticky position="top">
  223. <MHeader
  224. border={false}
  225. isBack={route.query.platformType != 'ANALYSIS'}
  226. />
  227. </MSticky>
  228. <div class={styles.playSection}>
  229. {state.playType === 'Video' && (
  230. <MVideo
  231. src={state.musicDetail.videoUrl}
  232. poster={state.musicDetail.img}
  233. />
  234. )}
  235. {state.playType === 'Audio' && (
  236. <div class={styles.audioSection}>
  237. <div class={styles.audioContainer}>
  238. <div
  239. id={audioId}
  240. onClick={(e: MouseEvent) => {
  241. e.stopPropagation();
  242. }}></div>
  243. </div>
  244. <div class={styles.audioBox}>
  245. <div class={styles.audioPan}>
  246. <Image class={styles.audioImg} src={state.musicDetail.img} />
  247. </div>
  248. <i class={styles.audioPoint}></i>
  249. <i class={styles.audioZhen}></i>
  250. </div>
  251. <div
  252. class={[styles.controls]}
  253. onClick={(e: Event) => {
  254. e.stopPropagation();
  255. }}
  256. onTouchmove={(e: TouchEvent) => {
  257. // emit('close');
  258. }}>
  259. <div class={styles.actions}>
  260. <div class={styles.actionBtn} onClick={onToggleAudio}>
  261. <img src={state.paused ? iconPlay : iconPause} />
  262. </div>
  263. </div>
  264. <div class={[styles.slider]}>
  265. <Slider
  266. step={0.01}
  267. class={styles.timeProgress}
  268. v-model={state.currentTime}
  269. max={state.duration}
  270. onUpdate:modelValue={val => {
  271. handleChangeTime(val);
  272. }}
  273. onDragStart={() => {
  274. state.dragStatus = true;
  275. console.log('onDragStart');
  276. }}
  277. onDragEnd={() => {
  278. state.dragStatus = false;
  279. console.log('onDragEnd');
  280. }}
  281. />
  282. </div>
  283. <div class={styles.time}>
  284. <div>{getSecondRPM(state.currentTime)}</div>
  285. <span>/</span>
  286. <div>{getSecondRPM(state.duration)}</div>
  287. </div>
  288. </div>
  289. </div>
  290. )}
  291. </div>
  292. <Cell class={styles.userSection} center>
  293. {{
  294. icon: () => (
  295. <Image class={styles.userLogo} src={state.musicDetail.avatar} />
  296. ),
  297. title: () => (
  298. <div class={styles.userInfo}>
  299. <p class={styles.name}>
  300. {state.musicDetail.username}
  301. {state.musicDetail.vipFlag && (
  302. <img src={iconMember} class={styles.iconMember} />
  303. )}
  304. </p>
  305. <p class={styles.sub}>
  306. {state.musicDetail.subjectName}{' '}
  307. {getGradeCh(state.musicDetail.currentGradeNum)}
  308. </p>
  309. </div>
  310. ),
  311. value: () => (
  312. <div class={styles.zan} onClick={onStarChange}>
  313. <img
  314. src={state.musicDetail.starFlag ? iconZanActive : iconZan}
  315. class={styles.iconZan}
  316. />
  317. {state.musicDetail.likeNum}
  318. </div>
  319. )
  320. }}
  321. </Cell>
  322. <div class={styles.musicSection}>
  323. <div class={styles.musicName}>
  324. <span class={styles.musicTag}>曲目名称</span>
  325. {state.musicDetail.musicSheetName}
  326. </div>
  327. {state.musicDetail.desc && (
  328. <div class={styles.musicDesc}>{state.musicDetail.desc}</div>
  329. )}
  330. </div>
  331. <div class={styles.likeSection}>
  332. <div class={styles.likeTitle}>推荐作品</div>
  333. {state.listState.dataShow ? (
  334. <List
  335. finished={state.listState.finished}
  336. finishedText=" "
  337. class={[styles.container, styles.containerInformation]}
  338. onLoad={getList}
  339. immediateCheck={false}>
  340. <div class={styles.cellGroup}>
  341. {state.list.map((item: any) => (
  342. <div class={styles.cell} onClick={() => onDetail(item)}>
  343. <div class={styles.cellImg}>
  344. <Image
  345. class={styles.cellImage}
  346. src={item.img}
  347. fit="cover"
  348. />
  349. <div class={styles.iconZan}>{item.likeNum}</div>
  350. </div>
  351. <div class={[styles.cellTitle, 'van-ellipsis']}>
  352. {item.musicSheetName}
  353. </div>
  354. <div class={styles.users}>
  355. <Image src={item.avatar} class={styles.userImg} />
  356. <span class={styles.name}>{item.username}</span>
  357. </div>
  358. </div>
  359. ))}
  360. </div>
  361. </List>
  362. ) : (
  363. <MEmpty description="暂无数据" />
  364. )}
  365. </div>
  366. <Popup
  367. v-model:show={state.loginStatus}
  368. style={{ background: 'transparent', overflow: 'inherit' }}>
  369. <LoginModel
  370. onClose={() => (state.loginStatus = false)}
  371. onConfirm={async (val: boolean) => {
  372. state.loginStatus = val;
  373. state.loginStatus = false;
  374. const { data } = await api_openUserMusicDetail(state.id);
  375. state.musicDetail = data;
  376. }}
  377. />
  378. </Popup>
  379. </div>
  380. );
  381. }
  382. });