index.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. import {
  2. Transition,
  3. TransitionGroup,
  4. computed,
  5. defineComponent,
  6. nextTick,
  7. onMounted,
  8. reactive,
  9. ref
  10. } from 'vue';
  11. import styles from './index.module.less';
  12. import icon_back from './images/icon_back.svg';
  13. import icon_separator from './images/icon_separator.svg';
  14. import {
  15. NBreadcrumb,
  16. NBreadcrumbItem,
  17. NButton,
  18. NEmpty,
  19. NImage,
  20. NSpace,
  21. NSpin
  22. } from 'naive-ui';
  23. import TheSearch from '/src/components/TheSearch';
  24. import { IMusicItem } from './type';
  25. import icon_arrow from './images/icon_arrow.svg';
  26. import icon_play from './images/icon_play.svg';
  27. import icon_pause from './images/icon_pause.svg';
  28. import icon_goXiaoku from './images/icon_goXiaoku.svg';
  29. import icon_favitor from '/src/common/images/icon-collect-default.png';
  30. import icon_favitorActive from '/src/common/images/icon-collect-active.png';
  31. import { useRoute, useRouter } from 'vue-router';
  32. import PlayItem from './component/play-item';
  33. import PlayLoading from './component/play-loading';
  34. import TheNoticeBar from '/src/components/TheNoticeBar';
  35. import {
  36. api_materialFavorite,
  37. api_materialFavoriteStatus,
  38. api_musicSheetPage,
  39. api_subjectList
  40. } from '../xiaoku-ai/api';
  41. import { useUserStore } from '/src/store/modules/users';
  42. import Musicguide from '@/custom-plugins/guide-page/music-guide'
  43. import TheEmpty from '/src/components/TheEmpty';
  44. export default defineComponent({
  45. name: 'XiaokuMusic',
  46. setup() {
  47. const user = useUserStore();
  48. const route = useRoute();
  49. const router = useRouter();
  50. const forms = reactive({
  51. page: 1,
  52. rows: 20,
  53. status: true,
  54. keyword: '', // 关键词
  55. musicSheetCategoriesId: route.query.id || ''
  56. });
  57. const data = reactive({
  58. loading: false,
  59. finshed: false,
  60. reshing: false,
  61. tags: [] as any[],
  62. tagIndex: 0,
  63. list: [] as unknown as IMusicItem[],
  64. listActive: 0,
  65. playState: 'pause' as 'play' | 'pause',
  66. showPlayer: false
  67. });
  68. const showGuide = ref(false)
  69. const getSubjects = async () => {
  70. const res = await api_subjectList();
  71. if (Array.isArray(res?.data)) {
  72. data.tags = [{ name: '全部', id: 0 }, ...res.data];
  73. }
  74. };
  75. const getList = async () => {
  76. data.loading = true;
  77. let res = {} as any;
  78. try {
  79. res = await api_musicSheetPage({
  80. ...forms,
  81. musicSubject: data.tagIndex ? data.tagIndex : ''
  82. });
  83. } catch (error) {
  84. console.log(error);
  85. }
  86. if (data.reshing) {
  87. data.list = [];
  88. data.reshing = false;
  89. }
  90. if (res?.code === 200 && Array.isArray(res?.data?.rows)) {
  91. data.list = [...data.list, ...res.data.rows];
  92. data.finshed = res.data.rows.length < forms.rows;
  93. console.log('🚀 ~ data.finshed:', data.finshed);
  94. }
  95. if (data.list[data.listActive]) {
  96. getFavitor(data.list[data.listActive]);
  97. }
  98. data.loading = false;
  99. setTimeout(() => {
  100. showGuide.value = true
  101. }, 500)
  102. };
  103. const handleGetList = () => {
  104. forms.page = 1;
  105. data.finshed = false;
  106. getList();
  107. };
  108. const spinRef = ref();
  109. const handleResh = () => {
  110. console.log(data.finshed);
  111. if (data.loading || data.finshed) return;
  112. forms.page = forms.page + 1;
  113. getList();
  114. };
  115. onMounted(async () => {
  116. getSubjects();
  117. await getList();
  118. const obv = new IntersectionObserver(entries => {
  119. if (entries[0].intersectionRatio > 0) {
  120. handleResh();
  121. }
  122. });
  123. nextTick(() => {
  124. obv.observe(spinRef.value);
  125. });
  126. });
  127. /** 查看收藏状态 */
  128. const getFavitor = async (item: any) => {
  129. const res = await api_materialFavoriteStatus({
  130. type: 'MUSIC',
  131. materialId: item.id
  132. });
  133. if (res?.code === 200) {
  134. item.favitor = res.data;
  135. }
  136. };
  137. /** 改变模仿的曲谱 */
  138. const handleChange = (item: IMusicItem) => {
  139. const index = data.list.findIndex(_item => _item.id === item.id);
  140. if (index > -1) {
  141. data.listActive = index;
  142. }
  143. getFavitor(item);
  144. };
  145. /** 选中的item */
  146. const activeItem = computed(() => {
  147. return data.list[data.listActive] || {};
  148. });
  149. /** 收藏曲谱 */
  150. const handleFavitor = () => {
  151. data.list[data.listActive].favitor = !data.list[data.listActive].favitor;
  152. api_materialFavorite({
  153. favoriteFlag: data.list[data.listActive].favitor,
  154. type: 'MUSIC',
  155. materialId: data.list[data.listActive].id
  156. });
  157. };
  158. /** 播放曲目 */
  159. const handlePlay = (item: IMusicItem) => {
  160. const index = data.list.findIndex(_item => _item.id === item.id);
  161. if (index > -1) {
  162. if (data.listActive === index) {
  163. data.playState = data.playState === 'play' ? 'pause' : 'play';
  164. } else {
  165. data.playState = 'play';
  166. }
  167. data.showPlayer = true;
  168. data.listActive = index;
  169. }
  170. };
  171. /** 音频控制 */
  172. const handleChangeAudio = (
  173. type: 'play' | 'pause' | 'pre' | 'next' | 'favitor'
  174. ) => {
  175. if (type === 'play') {
  176. data.playState = 'play';
  177. } else if (type === 'pause') {
  178. data.playState = 'pause';
  179. } else if (type === 'pre') {
  180. if (data.list[data.listActive - 1]) {
  181. handlePlay(data.list[data.listActive - 1]);
  182. }
  183. } else if (type === 'next') {
  184. if (data.list[data.listActive + 1]) {
  185. handlePlay(data.list[data.listActive + 1]);
  186. }
  187. } else if (type === 'favitor') {
  188. handleFavitor();
  189. }
  190. };
  191. return () => (
  192. <div class={styles.container}>
  193. <NSpace align="center" wrapItem={false} size={16}>
  194. <img
  195. style={{ cursor: 'pointer' }}
  196. src={icon_back}
  197. onClick={() => router.push({ path: '/xiaoku-ai' })}
  198. />
  199. <NBreadcrumb separator="">
  200. <NBreadcrumbItem
  201. onClick={() => router.push({ path: '/xiaoku-ai' })}>
  202. 曲谱列表
  203. </NBreadcrumbItem>
  204. <img class={styles.separator} src={icon_separator} />
  205. <NBreadcrumbItem>{route.query.name}</NBreadcrumbItem>
  206. </NBreadcrumb>
  207. </NSpace>
  208. <div
  209. class={styles.wrap}
  210. style={{ paddingBottom: data.showPlayer ? '108Px' : '' }}>
  211. <div class={styles.content}>
  212. <div class={styles.tools}>
  213. <NSpace
  214. style={{ width: '100%' }}
  215. size={[24, 12]}
  216. wrapItem={false}>
  217. <div {...{
  218. id
  219. : 'music-0'
  220. }}>
  221. <NSpace
  222. style={{ width: '100%' }}
  223. size={[24, 12]}
  224. wrapItem={false}>
  225. {data.tags.map(item => (
  226. <NButton
  227. round
  228. textColor={data.tagIndex === item.id ? '#fff' : '#000'}
  229. color={data.tagIndex === item.id ? '#198CFE' : '#fff'}
  230. onClick={() => {
  231. data.tagIndex = item.id;
  232. data.reshing = true;
  233. handleGetList();
  234. }}>
  235. {item.name}
  236. </NButton>
  237. ))}
  238. </NSpace>
  239. </div>
  240. </NSpace>
  241. <TheSearch
  242. style={{ marginLeft: 'auto' }}
  243. round
  244. border={false}
  245. onSearch={val => {
  246. forms.keyword = val;
  247. data.reshing = true;
  248. handleGetList();
  249. }}
  250. />
  251. </div>
  252. <div class={styles.contentWrap}>
  253. <div class={styles.musicList}>
  254. <div class={styles.wrapList}>
  255. {data.list.map((item: IMusicItem, index) => {
  256. return (
  257. <div class={styles.itemContainer}>
  258. <div
  259. class={[
  260. styles.item,
  261. data.listActive === index && styles.active
  262. ]}
  263. onClick={() => handleChange(item)}>
  264. <div class={styles.img}>
  265. <NImage
  266. lazy
  267. objectFit="cover"
  268. previewDisabled={true}
  269. src={item.titleImg}
  270. onLoad={e => {
  271. (e.target as any).dataset.loaded = 'true';
  272. }}
  273. />
  274. <PlayLoading
  275. class={[
  276. data.listActive === index &&
  277. data.playState === 'play'
  278. ? ''
  279. : styles.showPlayLoading
  280. ]}
  281. />
  282. </div>
  283. <div class={styles.title}>
  284. <div class={styles.titleName}>
  285. <TheNoticeBar text={item.musicSheetName} />
  286. </div>
  287. <div class={styles.titleDes}>{item.composer}</div>
  288. </div>
  289. {index == 0 ? <NButton
  290. color="#259CFE"
  291. textColor="#fff"
  292. {...{ id: 'music-1' }}
  293. round
  294. class={styles.btn}
  295. type="primary"
  296. onClick={(e: Event) => {
  297. e.stopPropagation();
  298. handlePlay(item);
  299. }}>
  300. 试听
  301. <img
  302. src={
  303. data.listActive === index &&
  304. data.playState === 'play'
  305. ? icon_pause
  306. : icon_play
  307. }
  308. />
  309. </NButton> : <NButton
  310. color="#259CFE"
  311. textColor="#fff"
  312. round
  313. class={styles.btn}
  314. type="primary"
  315. onClick={(e: Event) => {
  316. e.stopPropagation();
  317. handlePlay(item);
  318. }}>
  319. 试听
  320. <img
  321. src={
  322. data.listActive === index &&
  323. data.playState === 'play'
  324. ? icon_pause
  325. : icon_play
  326. }
  327. />
  328. </NButton>}
  329. <img class={styles.arrow} src={icon_arrow} />
  330. </div>
  331. </div>
  332. );
  333. })}
  334. {!data.finshed && (
  335. <div ref={spinRef} class={styles.loadingWrap}>
  336. <NSpin show={true}></NSpin>
  337. </div>
  338. )}
  339. {!data.loading && data.list.length === 0 && (
  340. <div class={styles.empty}>
  341. <TheEmpty></TheEmpty>
  342. </div>
  343. )}
  344. </div>
  345. </div>
  346. <div class={styles.musicStaff}>
  347. <div class={styles.musicName}>
  348. {activeItem.value.musicSheetName}
  349. </div>
  350. <img
  351. id='music-2'
  352. style={{ display: activeItem.value.id ? '' : 'none' }}
  353. class={styles.goBtn}
  354. src={icon_goXiaoku}
  355. onClick={() => {
  356. handleChangeAudio('pause');
  357. const origin = /(localhost|192)/.test(location.host)
  358. ? 'https://test.lexiaoya.cn'
  359. : location.origin;
  360. const src = `${origin}/instrument?platform=pc&showGuide=true&id=${activeItem.value.id}&Authorization=${user.getToken}`;
  361. window.open(src);
  362. }}
  363. />
  364. <div
  365. style={{ display: activeItem.value.id ? '' : 'none' }}
  366. class={styles.favitor}
  367. onClick={() => handleFavitor()}>
  368. <Transition name="favitor" mode="out-in">
  369. {activeItem.value.favitor ? (
  370. <img src={icon_favitorActive} key="1" />
  371. ) : (
  372. <img src={icon_favitor} key="2" />
  373. )}
  374. </Transition>
  375. </div>
  376. <div class={styles.staffImgs}>
  377. <TransitionGroup name="van-fade">
  378. {(activeItem.value?.musicSvg || activeItem.value?.musicImg) ? (activeItem.value?.musicSvg || activeItem.value?.musicImg)
  379. ?.split(',')
  380. .map((item, index) => {
  381. return <img src={item} key={item} />;
  382. }) : <TheEmpty></TheEmpty>}
  383. </TransitionGroup>
  384. </div>
  385. </div>
  386. </div>
  387. </div>
  388. </div>
  389. {data.list.length !== 0 && (
  390. <PlayItem
  391. show={data.showPlayer}
  392. playState={data.playState}
  393. item={activeItem.value}
  394. onChange={value => handleChangeAudio(value)}
  395. />
  396. )}
  397. {showGuide.value ? <Musicguide ></Musicguide> : null}
  398. </div>
  399. );
  400. }
  401. });