detail.tsx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. import {
  2. computed,
  3. defineComponent,
  4. nextTick,
  5. onMounted,
  6. onUnmounted,
  7. reactive,
  8. ref
  9. } from 'vue';
  10. import styles from './detail.module.less';
  11. import CBreadcrumb from '/src/components/CBreadcrumb';
  12. import { useRoute } from 'vue-router';
  13. import { NScrollbar, NSpin, NTabPane, NTabs } from 'naive-ui';
  14. import SearchGroupResources from './search-group-resources';
  15. import TheSearch from '/src/components/TheSearch';
  16. import TheEmpty from '/src/components/TheEmpty';
  17. import { api_musicSheetPage } from '../xiaoku-ai/api';
  18. import { formatUsedNum } from '.';
  19. import { vaildMusicScoreUrl } from '/src/utils/urlUtils';
  20. import { exitFullscreen, fscreen } from '/src/utils';
  21. import { state as baseState } from '/src/state';
  22. import { useUserStore } from '/src/store/modules/users';
  23. import PreviewWindow from '../preview-window';
  24. export default defineComponent({
  25. name: 'xiaoku-detail',
  26. setup() {
  27. const route = useRoute();
  28. const userStore = useUserStore();
  29. const routerList = ref([
  30. { name: 'AI学练', path: '/xiaoku-list' },
  31. { name: '曲目列表', path: '' }
  32. ]);
  33. const forms = reactive({
  34. page: 1,
  35. rows: 32,
  36. status: true,
  37. searchType: ''
  38. });
  39. const state = reactive({
  40. countPage: 1,
  41. loading: true,
  42. finshed: false,
  43. reshing: false,
  44. tabName: '' as '' | 'RECOMMEND' | 'HOT' | 'NEW',
  45. list: [] as any,
  46. allSearch: {
  47. name: '',
  48. musicTagIds: '',
  49. audioPlayTypes: null as any,
  50. bookVersionId: null as any,
  51. musicalInstrumentId: null as any,
  52. subjectId: null
  53. },
  54. hotSearch: {
  55. name: '',
  56. musicalInstrumentId: null as any
  57. },
  58. newSearch: {
  59. name: '',
  60. musicalInstrumentId: null as any
  61. },
  62. recommendSearch: {
  63. name: '',
  64. musicalInstrumentId: null as any
  65. },
  66. previewModal: false,
  67. previewParams: {
  68. type: '',
  69. src: ''
  70. } as any
  71. });
  72. const searchValue = computed(() => {
  73. if (state.tabName === 'RECOMMEND') {
  74. return state.recommendSearch.name;
  75. } else if (state.tabName === 'HOT') {
  76. return state.hotSearch.name;
  77. } else if (state.tabName === 'NEW') {
  78. return state.newSearch.name;
  79. } else {
  80. return state.allSearch.name;
  81. }
  82. });
  83. const musicalInstrumentId = computed(() => {
  84. let id = state.allSearch.musicalInstrumentId;
  85. if (state.tabName === 'RECOMMEND') {
  86. id = state.recommendSearch.musicalInstrumentId;
  87. } else if (state.tabName === 'HOT') {
  88. id = state.hotSearch.musicalInstrumentId;
  89. } else if (state.tabName === 'NEW') {
  90. id = state.newSearch.musicalInstrumentId;
  91. }
  92. return id;
  93. });
  94. const onSearch = async (item: any) => {
  95. forms.page = 1;
  96. state.reshing = true;
  97. state.finshed = false;
  98. state.list = [];
  99. const { subjectId, ...res } = item;
  100. if (state.tabName === 'HOT') {
  101. state.hotSearch = Object.assign(state.hotSearch, {
  102. musicalInstrumentId: subjectId
  103. });
  104. } else if (state.tabName == 'NEW') {
  105. state.newSearch = Object.assign(state.newSearch, {
  106. musicalInstrumentId: subjectId
  107. });
  108. } else if (state.tabName === 'RECOMMEND') {
  109. state.recommendSearch = Object.assign(state.recommendSearch, {
  110. musicalInstrumentId: subjectId
  111. });
  112. } else {
  113. state.allSearch = Object.assign(state.allSearch, {
  114. ...res,
  115. musicalInstrumentId: subjectId,
  116. subjectId: null
  117. });
  118. }
  119. getList();
  120. nextTick(() => {
  121. __initSpin();
  122. });
  123. };
  124. const spinRef = ref();
  125. const handleResh = () => {
  126. if (state.loading || state.finshed) return;
  127. forms.page = forms.page + 1;
  128. getList();
  129. };
  130. const getList = async () => {
  131. if (forms.page == 1) {
  132. state.loading = true;
  133. }
  134. let res = {} as any;
  135. const { ...result } = forms;
  136. let params = {
  137. ...result,
  138. searchType: state.tabName
  139. } as any;
  140. if (state.tabName === 'RECOMMEND') {
  141. params = Object.assign(params, state.recommendSearch);
  142. params.rows = 60;
  143. params.page = 1;
  144. } else if (state.tabName === 'HOT') {
  145. params = Object.assign(params, state.hotSearch);
  146. params.rows = 60;
  147. params.page = 1;
  148. } else if (state.tabName === 'NEW') {
  149. params = Object.assign(params, state.newSearch);
  150. params.rows = 60;
  151. params.page = 1;
  152. } else {
  153. params.name = state.allSearch.name;
  154. const { ...more } = state.allSearch;
  155. params = Object.assign(params, { ...more });
  156. }
  157. try {
  158. res = await api_musicSheetPage(params);
  159. } catch (error) {
  160. console.log(error);
  161. }
  162. if (state.reshing) {
  163. state.list = [];
  164. state.reshing = false;
  165. }
  166. if (res?.code === 200 && Array.isArray(res?.data?.rows)) {
  167. const tempResult = res?.data?.rows || [];
  168. tempResult.forEach((item: any) => {
  169. item.audioPlayTypeArray = item.audioPlayTypes
  170. ? item.audioPlayTypes.split(',')
  171. : [];
  172. if (item.musicSheetName) {
  173. const regex = new RegExp(params.name, 'gi');
  174. const highlightedText = item.musicSheetName.replace(
  175. regex,
  176. `<span>$&</span>`
  177. );
  178. item.musicNameReg = highlightedText;
  179. }
  180. });
  181. state.list = [...state.list, ...res.data.rows];
  182. state.finshed = forms.page >= res.data.pages;
  183. state.countPage = res.data.pages;
  184. } else {
  185. state.finshed = true;
  186. }
  187. state.loading = false;
  188. };
  189. const __initSpin = () => {
  190. // const obv = new IntersectionObserver(entries => {
  191. // if (entries[0].intersectionRatio > 0) {
  192. // handleResh();
  193. // }
  194. // });
  195. // obv.observe(spinRef.value);
  196. };
  197. // 查看详情
  198. const onDetail = (item: any) => {
  199. // 默认进页面显示对应的曲谱
  200. // let lineType = item.scoreType || 'FIRST';
  201. const lineType =
  202. item.scoreType === 'FIRST'
  203. ? 'firstTone'
  204. : item.scoreType === 'JIAN'
  205. ? 'fixedTone'
  206. : item.scoreType === 'STAVE'
  207. ? 'staff'
  208. : 'firstTone';
  209. let src = `${vaildMusicScoreUrl()}/instrument?v=${+new Date()}&platform=pc&id=${
  210. item.id
  211. }&Authorization=${
  212. userStore.getToken
  213. }&musicRenderType=${lineType}&showGuide=true&part-index=${0}`;
  214. let musicalInstrumentId = '';
  215. if (state.tabName === 'RECOMMEND') {
  216. musicalInstrumentId = state.recommendSearch.musicalInstrumentId;
  217. } else if (state.tabName === 'HOT') {
  218. musicalInstrumentId = state.hotSearch.musicalInstrumentId;
  219. } else if (state.tabName === 'NEW') {
  220. musicalInstrumentId = state.newSearch.musicalInstrumentId;
  221. } else {
  222. musicalInstrumentId = state.allSearch.musicalInstrumentId;
  223. }
  224. if (musicalInstrumentId) {
  225. src += '&instrumentId=' + musicalInstrumentId;
  226. }
  227. if (window.matchMedia('(display-mode: standalone)').matches) {
  228. baseState.application = window.matchMedia(
  229. '(display-mode: standalone)'
  230. ).matches;
  231. state.previewModal = true;
  232. fscreen();
  233. state.previewParams = {
  234. type: 'music',
  235. src
  236. };
  237. } else {
  238. window.open(src, +new Date() + '');
  239. }
  240. };
  241. const iframeHandle = (ev: MessageEvent) => {
  242. if (ev.data?.api === 'back') {
  243. exitFullscreen();
  244. state.previewModal = !state.previewModal;
  245. }
  246. };
  247. onMounted(async () => {
  248. if (route.query.type) {
  249. state.tabName = route.query.type as any;
  250. }
  251. // getList();
  252. __initSpin();
  253. window.addEventListener('message', iframeHandle);
  254. });
  255. onUnmounted(() => {
  256. window.removeEventListener('message', iframeHandle);
  257. });
  258. return () => (
  259. <div class={styles.xiaokuDetail}>
  260. <CBreadcrumb list={routerList.value}></CBreadcrumb>
  261. <div class={styles.detailContainer}>
  262. <NTabs
  263. paneClass={styles.paneTitle}
  264. justifyContent="start"
  265. // animated
  266. paneWrapperClass={styles.paneWrapperContainer}
  267. v-model:value={state.tabName}
  268. onUpdate:value={(val: any) => {
  269. forms.page = 1;
  270. state.finshed = false;
  271. state.reshing = true;
  272. state.list = [];
  273. if (musicalInstrumentId.value) {
  274. getList();
  275. __initSpin();
  276. }
  277. }}
  278. v-slots={{
  279. suffix: () => (
  280. <TheSearch
  281. placeholder="请输入曲目名称"
  282. round
  283. value={searchValue.value}
  284. onUpdate:value={(val: string) => {
  285. // 重置搜索条件
  286. if (state.tabName === 'RECOMMEND') {
  287. state.recommendSearch.name = val;
  288. } else if (state.tabName === 'HOT') {
  289. state.hotSearch.name = val;
  290. } else if (state.tabName === 'NEW') {
  291. state.newSearch.name = val;
  292. } else {
  293. state.allSearch.name = val;
  294. }
  295. }}
  296. class={styles.inputSearch}
  297. onSearch={val => {
  298. if (state.tabName === 'RECOMMEND') {
  299. state.recommendSearch.name = val;
  300. } else if (state.tabName === 'HOT') {
  301. state.hotSearch.name = val;
  302. } else if (state.tabName === 'NEW') {
  303. state.newSearch.name = val;
  304. } else {
  305. state.allSearch.name = val;
  306. }
  307. forms.page = 1;
  308. state.finshed = false;
  309. state.list = [];
  310. getList();
  311. }}
  312. />
  313. )
  314. }}>
  315. <NTabPane name={``} tab={'全部曲目'}></NTabPane>
  316. <NTabPane name={`RECOMMEND`} tab={'推荐曲目'}></NTabPane>
  317. <NTabPane name={`HOT`} tab={'热门曲目'}></NTabPane>
  318. <NTabPane name={`NEW`} tab={'最新曲目'}></NTabPane>
  319. </NTabs>
  320. <NScrollbar
  321. class={[
  322. [
  323. styles.wrapList,
  324. !state.loading &&
  325. state.list.length === 0 &&
  326. styles.wrapListEmpty
  327. ]
  328. ]}
  329. onScroll={async (e: any) => {
  330. if (state.tabName) {
  331. return;
  332. }
  333. const clientHeight = e.target?.clientHeight;
  334. const scrollTop = e.target?.scrollTop;
  335. const scrollHeight = e.target?.scrollHeight;
  336. // 是否到底,是否加载完
  337. if (
  338. clientHeight + scrollTop + 20 >= scrollHeight &&
  339. !state.finshed &&
  340. !state.loading
  341. ) {
  342. if (forms.page >= state.countPage) return;
  343. forms.page = forms.page + 1;
  344. await getList();
  345. }
  346. }}>
  347. {/* , state.tabName ? styles.searchSectionHide : '' */}
  348. <NSpin show={state.loading}>
  349. <div
  350. class={[
  351. styles.loadingSection,
  352. !state.loading &&
  353. state.list.length === 0 &&
  354. styles.loadingSectionEmpty
  355. ]}>
  356. <div class={[styles.searchSection]}>
  357. <SearchGroupResources
  358. type={state.tabName}
  359. musicalInstrumentId={musicalInstrumentId.value}
  360. onSearch={(val: any) => {
  361. onSearch(val);
  362. }}
  363. />
  364. </div>
  365. {state.list.length > 0 && (
  366. <div
  367. class={[
  368. styles.sectionContainer
  369. // state.tabName && styles.noSearchContainer
  370. ]}>
  371. {state.list.map((item: any) => (
  372. <div
  373. class={styles.sectionItem}
  374. onClick={() => onDetail(item)}>
  375. <div class={styles.img}>
  376. <img
  377. referrerpolicy="no-referrer"
  378. src={item.titleImg}
  379. />
  380. </div>
  381. <div class={styles.infos}>
  382. <div
  383. class={styles.topName}
  384. v-html={item.musicNameReg}></div>
  385. <div class={styles.types}>
  386. <div class={styles.hot}>
  387. <span>{formatUsedNum(item.usedNum)}</span>
  388. </div>
  389. {item.audioPlayTypes?.includes('SING') && (
  390. <div class={styles.sing}>演唱</div>
  391. )}
  392. {item.audioPlayTypes?.includes('PLAY') && (
  393. <div class={styles.song}>演奏</div>
  394. )}
  395. <div class={styles.author}>{item.composer}</div>
  396. </div>
  397. </div>
  398. </div>
  399. ))}
  400. </div>
  401. )}
  402. {/* <div
  403. // ref={spinRef}
  404. class={[styles.loadingWrap, (state.finshed || !state.loading) && styles.showLoading]}>
  405. <NSpin show={true}></NSpin>
  406. </div> */}
  407. {!state.loading && state.list.length === 0 && (
  408. <div class={styles.empty}>
  409. <TheEmpty></TheEmpty>
  410. </div>
  411. )}
  412. </div>
  413. </NSpin>
  414. </NScrollbar>
  415. </div>
  416. {/* 应用内预览或上课 */}
  417. <PreviewWindow
  418. v-model:show={state.previewModal}
  419. type="music"
  420. params={state.previewParams}
  421. />
  422. </div>
  423. );
  424. }
  425. });