index-share.tsx 14 KB

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