index.tsx 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145
  1. import {
  2. computed,
  3. defineComponent,
  4. onMounted,
  5. reactive,
  6. ref,
  7. watch,
  8. nextTick
  9. } from 'vue'
  10. import {
  11. Image,
  12. Tabs,
  13. Tab,
  14. List,
  15. Button,
  16. Popup,
  17. Dialog,
  18. Sticky,
  19. Picker,
  20. DropdownMenu,
  21. DropdownItem,
  22. Tag
  23. } from 'vant'
  24. import styles from './index.module.less'
  25. import TheSticky from '@/components/the-sticky'
  26. import ColHeader from '@/components/col-header'
  27. // import { useWindowScroll, useEventListener } from '@vueuse/core'
  28. import request from '@/helpers/request'
  29. // import iconMenu from './images/icon-menu.png'
  30. // import iconRightTop from './images/icon-right-top.png'
  31. import Search from '@/components/col-search'
  32. import iconAlbumCover from '../../images/icon-album-cover.png'
  33. import iconTimer from './images/icon-timer.png'
  34. import iconArrow from './images/icon-arrow.png'
  35. import { state as baseState, setLogout } from '@/state'
  36. import Song from '../component/song'
  37. import { useRoute, useRouter } from 'vue-router'
  38. import ColResult from '@/components/col-result'
  39. import { moneyFormat } from '@/helpers/utils'
  40. import { orderStatus } from '@/views/order-detail/orderStatus'
  41. import { postMessage } from '@/helpers/native-message'
  42. import { browser } from '@/helpers/utils'
  43. // Import Swiper Vue.js components
  44. // import Swiper core and required modules
  45. import { Pagination } from 'swiper/modules'
  46. import { Swiper, SwiperSlide } from 'swiper/vue'
  47. // Import Swiper styles
  48. import 'swiper/css'
  49. import 'swiper/css/pagination'
  50. import CourseItem from '../lessonCourseware/component/CourseItem'
  51. export default defineComponent({
  52. name: 'train-tool',
  53. setup() {
  54. const sessionStorageToolSubject =
  55. sessionStorage.getItem('tool-subject-type')
  56. const toolSubject =
  57. sessionStorageToolSubject && JSON.parse(sessionStorageToolSubject)
  58. sessionStorage.removeItem('tool-subject-type')
  59. const route = useRoute()
  60. const router = useRouter()
  61. const tabsRef = ref()
  62. const background = ref<string>('rgba(55, 205, 177, 0)')
  63. // const color = ref<string>('#fff')
  64. const state = reactive({
  65. userId: '',
  66. details: {} as any,
  67. buy: route.query.buy as any,
  68. albumId: route.query.albumId || null,
  69. activeTab:
  70. toolSubject?.activeTab || route.query.subjectType || 'COURSEWARE', // 有缓存 默认用缓存,之后用请求头,最后默认
  71. initLoadPage: false,
  72. showLoading: false,
  73. loadingAlbum: false,
  74. loading: false,
  75. finished: false,
  76. isError: false,
  77. list: [] as any,
  78. popupStatus: false,
  79. // selectMember: {} as any, // 购买的月份
  80. ensembleCounts: false,
  81. musicCounts: false,
  82. subjectCounts: false,
  83. coursewareCounts: false,
  84. tenantAlbumStatus: 0 as any,
  85. ablumStatus: false,
  86. heightV: 0,
  87. hasBuyStatus: true, // 是否能继续购买
  88. albumList: [] as any, // 专辑列表
  89. initialSlide: 0,
  90. subjectStatus: false,
  91. openStatus: false,
  92. teacherSubjectId: null as any,
  93. teacherSubjectName: null as any,
  94. teacherSubjectIndex: 0,
  95. subjectList: [] // 声部列表
  96. })
  97. // const params = reactive({
  98. // keyword: (route.query.search as string) || '',
  99. // subjectType: subjectType,
  100. // page: 1,
  101. // subjectId: null,
  102. // albumId: route.query.albumId,
  103. // albumName: '',
  104. // level: '',
  105. // type: '',
  106. // title: title
  107. // })
  108. const searchObj = ref<any>({
  109. COURSEWARE: {},
  110. SUBJECT: {},
  111. MUSIC: {},
  112. ENSEMBLE: {}
  113. })
  114. const searchRef = ref()
  115. const params = reactive({
  116. keyword: toolSubject?.keyword || null as any,
  117. // subjectType: '',
  118. subjectId: toolSubject?.subjectId ||null,
  119. // albumId: route.query.albumId,
  120. // albumName: '',
  121. level: toolSubject?.level ||'',
  122. type: toolSubject?.type ||'',
  123. courseTypeCode: '',
  124. // title: '',
  125. page: 1,
  126. rows: 20
  127. })
  128. const apiSuffix = ref(
  129. baseState.platformType === 'STUDENT' ? '/api-student' : '/api-teacher'
  130. )
  131. const isSingleAlbum = computed(() => {
  132. const query = route.query
  133. if (query.taId || (query.albumId && state.buy === '1')) {
  134. return true
  135. } else {
  136. return false
  137. }
  138. })
  139. const isSearchStatus = computed(() => {
  140. const obj = searchObj.value[state.activeTab]
  141. let status = false
  142. if (state.activeTab === 'COURSEWARE') {
  143. if (obj.courseTypeList && obj.courseTypeList.length > 0) {
  144. status = true
  145. }
  146. } else {
  147. if (obj.subjects && obj.subjects.length > 0) {
  148. status = true
  149. }
  150. if (obj.levelList && obj.levelList.length > 0) {
  151. status = true
  152. }
  153. if (obj.typeList && obj.typeList.length > 0) {
  154. status = true
  155. }
  156. }
  157. return status
  158. })
  159. const getDetails = async () => {
  160. state.loadingAlbum = true
  161. try {
  162. // tenantGroupAlbum/buyAlbumInfo
  163. // 当我的曲目过来的时候才走单个查询
  164. if (state.albumId && state.buy === '1') {
  165. let url = apiSuffix.value + '/userTenantAlbumRecord/detail'
  166. if (state.albumId) {
  167. url = url + '?albumId=' + state.albumId
  168. }
  169. const { data } = await request.post(url)
  170. state.albumList = [data || {}]
  171. state.details = data || {}
  172. } else {
  173. const url =
  174. apiSuffix.value +
  175. `/tenantGroupAlbum/buyAlbumInfo?tenantGroupAlbumId=${
  176. route.query.taId || ''
  177. }`
  178. //&tenantAlbumId=${state.albumId || ''}
  179. // if (state.albumId) {
  180. // url = url + '?albumId=' + state.albumId
  181. // }
  182. const { data } = await request.get(url)
  183. state.albumList = data || []
  184. if (state.albumList.length > 0) {
  185. let index = 0
  186. // 以缓存为优先 其次 请求头 state.albumId
  187. if (toolSubject?.tenantGroupAlbumId || state.albumId) {
  188. index = state.albumList.findIndex(item => {
  189. return toolSubject?.tenantGroupAlbumId
  190. ? (baseState.platformType === 'STUDENT'
  191. ? item.tenantGroupAlbumId
  192. : item.id) === toolSubject?.tenantGroupAlbumId
  193. : item.id == state.albumId // 这里不全等 因为state.albumId为字符串 id为number
  194. })
  195. index < 0 && (index = 0)
  196. }
  197. state.initialSlide = index //默认展示第几个
  198. state.details = state.albumList[index] // 有缓存 就用缓存里面的数据
  199. } else {
  200. // state.albumList
  201. if (!browser().isApp) {
  202. Dialog.alert({
  203. title: '提示',
  204. message: '该教程不可购买',
  205. confirmButtonText: '确定',
  206. confirmButtonColor: '#2dc7aa'
  207. }).then(() => {
  208. if (browser().isApp) {
  209. postMessage({ api: 'back' })
  210. } else {
  211. setLogout()
  212. router.replace({
  213. path: '/login' as any,
  214. query: {
  215. returnUrl: '/train-tool',
  216. ...route.query
  217. }
  218. })
  219. }
  220. })
  221. }
  222. }
  223. }
  224. } catch {
  225. //
  226. }
  227. state.loadingAlbum = false
  228. }
  229. const getSelectCondition = async (type: string) => {
  230. // 判断是否已经查询过数据
  231. if (Object.keys(searchObj.value[type]).length > 0) {
  232. return
  233. }
  234. const { data } = await request.post(
  235. `${apiSuffix.value}/tenantAlbumMusic/selectCondition`,
  236. {
  237. data: {
  238. subjectId: state.teacherSubjectId,
  239. subjectType: type,
  240. tenantAlbumId: state.details.id
  241. }
  242. }
  243. )
  244. searchObj.value[type] = data || {}
  245. }
  246. watch(
  247. () => state.details,
  248. () => {
  249. state.ensembleCounts = state.details?.ensembleCounts ? true : false
  250. state.subjectCounts = state.details?.subjectCounts ? true : false
  251. state.musicCounts = state.details?.musicCounts ? true : false
  252. state.coursewareCounts = state.details?.coursewareCounts ? true : false
  253. if (state.details.buyTimesFlag) {
  254. if (state.details.buyedTimes >= state.details.buyTimes) {
  255. state.hasBuyStatus = false
  256. } else {
  257. state.hasBuyStatus = true
  258. }
  259. } else {
  260. state.hasBuyStatus = true
  261. }
  262. }
  263. )
  264. let listController
  265. const FetchList = async (hideLoading = false) => {
  266. if (!state.details.id) {
  267. return
  268. }
  269. if (listController) {
  270. listController.abort()
  271. }
  272. state.loading = true
  273. state.isError = false
  274. const tempParams = {
  275. ...params,
  276. // level: '',
  277. // type: '',
  278. // subjectId: params.subjectId
  279. albumId: state.details.id || null,
  280. subjectType: state.activeTab
  281. } as any
  282. // 老师端默认查询声部
  283. if (baseState.platformType === 'TEACHER') {
  284. // const users = baseState.user.data
  285. tempParams.subjectId = state.teacherSubjectId || null
  286. }
  287. try {
  288. listController = new AbortController()
  289. const { signal } = listController
  290. const { data } = await request.post(
  291. `${apiSuffix.value}/tenantAlbumMusic/page`,
  292. {
  293. hideLoading,
  294. data: tempParams,
  295. signal
  296. }
  297. )
  298. if (state.list.length > 0 && data.pageNo === 1) {
  299. return
  300. }
  301. state.list = state.list.concat(data.rows || [])
  302. params.page = data.pageNo + 1
  303. // showContact.value = state.list.length > 0
  304. state.loading = false
  305. state.finished = data.pageNo >= data.totalPage
  306. params.page = data.pageNo + 1
  307. } catch (error) {
  308. state.isError = true
  309. }
  310. state.loading = false
  311. }
  312. const getSubjectList = async () => {
  313. try {
  314. const res = await request.get('/api-tenant/open/subject/queryPage', {
  315. params: { page: 1, rows: 9999, queryType: 'list' }
  316. })
  317. const result = res.data.rows || []
  318. result.forEach((item: any) => {
  319. item.text = item.name
  320. })
  321. state.subjectList = result || []
  322. const index = state.subjectList.findIndex(
  323. (item: any) => item.id == state.teacherSubjectId
  324. )
  325. state.teacherSubjectIndex = index === -1 ? 0 : index
  326. } catch (e) {
  327. console.log(e)
  328. }
  329. }
  330. /** 设置声部 */
  331. const operatoinCatchSubjectInfo = (
  332. type: 'set' | 'get',
  333. params?: {
  334. defaultSubject: string
  335. defaultSubjectName: string
  336. userId: string | number
  337. }
  338. ) => {
  339. if (type === 'set') {
  340. localStorage.setItem(
  341. 'trainTool-teacherSubjectInfo',
  342. JSON.stringify(params)
  343. )
  344. } else if (type === 'get') {
  345. const result = localStorage.getItem('trainTool-teacherSubjectInfo')
  346. return result ? JSON.parse(result) : null
  347. }
  348. }
  349. // 获取信息
  350. const activeTypeParams = async () => {
  351. if (state.activeTab === 'COURSEWARE') {
  352. await getSelectCondition(state.activeTab)
  353. } else if (['SUBJECT', 'MUSIC', 'ENSEMBLE'].includes(state.activeTab)) {
  354. await getSelectCondition(state.activeTab)
  355. }
  356. }
  357. const onSearch = (value?: string) => {
  358. params.page = 1
  359. // state.finished = false
  360. params.keyword = value
  361. state.list = []
  362. FetchList()
  363. }
  364. onMounted(async () => {
  365. // useEventListener(document, 'scroll', evt => {
  366. // const { y } = useWindowScroll()
  367. // if (y.value > 20) {
  368. // background.value = `rgba(255, 255, 255)`
  369. // } else {
  370. // background.value = 'transparent'
  371. // }
  372. // })
  373. // 老师端默认查询声部
  374. if (baseState.platformType === 'TEACHER') {
  375. const users = baseState.user.data
  376. state.userId = users.userId
  377. const catchSubject = operatoinCatchSubjectInfo('get')
  378. if (catchSubject && users.userId === catchSubject.userId) {
  379. state.teacherSubjectId = catchSubject.defaultSubject || null
  380. state.teacherSubjectName = catchSubject.defaultSubjectName || null
  381. } else {
  382. state.teacherSubjectId = users.defaultSubject || null
  383. state.teacherSubjectName = users.defaultSubjectName || null
  384. }
  385. operatoinCatchSubjectInfo('set', {
  386. defaultSubject: state.teacherSubjectId,
  387. defaultSubjectName: state.teacherSubjectName,
  388. userId: state.userId
  389. })
  390. }
  391. state.loading = true
  392. state.loadingAlbum = true
  393. state.initLoadPage = true
  394. await getDetails()
  395. await FetchList()
  396. if (baseState.platformType === 'TEACHER') {
  397. getSubjectList()
  398. }
  399. activeTypeParams()
  400. state.initLoadPage = false
  401. state.loadingAlbum = false
  402. state.loading = false
  403. // 为了处理 swiper 会不显示的问题
  404. document.body.scrollIntoView()
  405. window.scrollTo(1, 0)
  406. })
  407. function handleChangeActiveTab() {
  408. state.showLoading = true
  409. state.activeTab = state.details?.coursewareCounts
  410. ? 'COURSEWARE'
  411. : state.details?.subjectCounts
  412. ? 'SUBJECT'
  413. : state.details?.musicCounts
  414. ? 'MUSIC'
  415. : 'ENSEMBLE'
  416. setTimeout(() => {
  417. state.showLoading = false
  418. }, 0);
  419. }
  420. const onSubmit = async () => {
  421. const album = state.details
  422. const details = state.details
  423. orderStatus.orderObject.orderType = 'TENANT_ALBUM'
  424. orderStatus.orderObject.orderName = details.name
  425. orderStatus.orderObject.orderDesc = details.name
  426. orderStatus.orderObject.actualPrice = album.actualPrice
  427. // orderStatus.orderObject.recomUserId = route.query.recomUserId || 0
  428. // orderStatus.orderObject.activityId = route.query.activityId || 0
  429. orderStatus.orderObject.orderNo = ''
  430. orderStatus.orderObject.orderList = [
  431. {
  432. orderType: 'TENANT_ALBUM',
  433. goodsName: details.name,
  434. actualPrice: album.actualPrice,
  435. price: album.actualPrice,
  436. ...details,
  437. ...album
  438. }
  439. ]
  440. const res = await request.post('/api-student/userOrder/getPendingOrder', {
  441. data: {
  442. goodType: 'TENANT_ALBUM',
  443. bizId: details.id
  444. }
  445. })
  446. const result = res.data
  447. if (result) {
  448. state.popupStatus = false
  449. Dialog.confirm({
  450. title: '提示',
  451. message: '您有一个未支付的订单,是否继续支付?',
  452. theme: 'round-button',
  453. className: 'confirm-button-group',
  454. cancelButtonText: '取消订单',
  455. confirmButtonText: '继续支付'
  456. })
  457. .then(async () => {
  458. orderStatus.orderObject.orderNo = result.orderNo
  459. orderStatus.orderObject.actualPrice = result.actualPrice
  460. orderStatus.orderObject.discountPrice = result.discountPrice
  461. orderStatus.orderObject.paymentConfig = {
  462. ...result.paymentConfig,
  463. paymentVendor: result.paymentVendor,
  464. paymentVersion: result.paymentVersion
  465. }
  466. routerTo()
  467. })
  468. .catch(() => {
  469. Dialog.close()
  470. // 只用取消订单,不用做其它处理
  471. cancelPayment(result.orderNo)
  472. })
  473. } else {
  474. routerTo()
  475. }
  476. }
  477. const routerTo = () => {
  478. const album = state.details
  479. sessionStorage.setItem(
  480. 'tool-subject-type',
  481. JSON.stringify({
  482. activeTab: state.activeTab,
  483. tenantGroupAlbumId:
  484. baseState.platformType === 'STUDENT'
  485. ? state.details.tenantGroupAlbumId
  486. : state.details.id, // 老师用专辑id当唯一值
  487. level: params.level,
  488. type: params.type,
  489. subjectId: params.type,
  490. keyword: params.keyword
  491. })
  492. )
  493. router.push({
  494. path: '/orderDetail',
  495. query: {
  496. orderType: 'TENANT_ALBUM',
  497. album: album.id
  498. }
  499. })
  500. }
  501. const cancelPayment = async (orderNo: string) => {
  502. try {
  503. await request.post('/api-student/userOrder/orderCancel/v2', {
  504. data: {
  505. orderNo
  506. }
  507. })
  508. } catch {
  509. //
  510. }
  511. }
  512. return () => (
  513. <div class={styles.trainTool}>
  514. {!state.loading && !state.details.id && state.buy != '1' ? (
  515. <>
  516. <TheSticky
  517. class={styles.theSticky}
  518. position="top"
  519. onBarHeight={(height: any) => {
  520. state.heightV = Math.floor(height)
  521. }}
  522. >
  523. <ColHeader border={false} isFixed={false} />
  524. </TheSticky>
  525. {!state.loading && (
  526. <div
  527. class={styles.colResultBox}
  528. style={{
  529. height: 'calc(100vh - var(--header-height))',
  530. display: 'flex',
  531. alignItems: 'center'
  532. }}
  533. >
  534. <ColResult
  535. tips="暂无教程"
  536. classImgSize="SMALL"
  537. btnStatus={false}
  538. />
  539. </div>
  540. )}
  541. </>
  542. ) : (
  543. !state.loadingAlbum && (
  544. <>
  545. <TheSticky
  546. class={styles.theSticky}
  547. position="top"
  548. onBarHeight={(height: any) => {
  549. state.heightV = Math.floor(height)
  550. }}
  551. >
  552. <ColHeader
  553. background={background.value}
  554. border={false}
  555. isFixed={false}
  556. hideHeader={route.query.taId ? true : false}
  557. // color={color.value}
  558. // backIconColor="white"
  559. >
  560. {{
  561. right: () =>
  562. baseState.platformType === 'TEACHER' && (
  563. <div
  564. class={styles.changeSubjectSection}
  565. onClick={() => {
  566. state.subjectStatus = true
  567. }}
  568. >
  569. <span class={styles.subjectName}>
  570. {state.teacherSubjectName}
  571. </span>
  572. <img
  573. class={state.subjectStatus && styles.active}
  574. src={iconArrow}
  575. />
  576. </div>
  577. )
  578. }}
  579. </ColHeader>
  580. </TheSticky>
  581. {/* <img class={styles.bgImg} src={state.details?.coverImg} /> */}
  582. <div class={styles.musicContent}></div>
  583. <div class={styles.bg}>
  584. <div class={styles.alumWrap}>
  585. {isSingleAlbum.value ? (
  586. <div class={styles.singleAlbum}>
  587. <div class={styles.img}>
  588. {state.details?.buyTimesFlag && (
  589. <span class={styles.quota}>
  590. 限购:{state.details?.buyedTimes}/
  591. {state.details?.buyTimes}次
  592. </span>
  593. )}
  594. <Image
  595. class={styles.image}
  596. width="100%"
  597. height="100%"
  598. fit="cover"
  599. src={state.details?.coverImg || iconAlbumCover}
  600. errorIcon={iconAlbumCover}
  601. />
  602. <div class={styles.iconPian}></div>
  603. </div>
  604. </div>
  605. ) : (
  606. state.albumList &&
  607. state.albumList.length > 0 && (
  608. <Swiper
  609. initialSlide={state.initialSlide}
  610. watchSlidesProgress={true}
  611. slidesPerView={'auto'}
  612. centeredSlides={true}
  613. modules={[Pagination]}
  614. pagination={{ clickable: true }}
  615. onSlideChange={(swiper: any) => {
  616. if(state.initLoadPage) return
  617. params.subjectId = null
  618. params.keyword = null
  619. params.level = ''
  620. params.type = ''
  621. params.courseTypeCode = ''
  622. searchObj.value = {
  623. COURSEWARE: {},
  624. SUBJECT: {},
  625. MUSIC: {},
  626. ENSEMBLE: {}
  627. }
  628. state.details = state.albumList[swiper.activeIndex]
  629. // 等tab渲染完了之后再切换 不然tab会自动重新赋值
  630. nextTick(async() => {
  631. // 当有初始值的时候不刷新
  632. // if (state.initialSlide) {
  633. // state.initialSlide = 0
  634. // return
  635. // }
  636. handleChangeActiveTab()
  637. // console.log(tabsRef.value, state.activeTab)
  638. tabsRef.value && tabsRef.value.resize()
  639. // console.log(state.activeTab, 'activeTab')
  640. activeTypeParams()
  641. params.page = 1
  642. state.list = []
  643. await FetchList(true)
  644. })
  645. }}
  646. >
  647. {state.albumList.map((album: any) => (
  648. <SwiperSlide>
  649. <div class={styles.img}>
  650. {album.buyTimesFlag && (
  651. <span class={styles.quota}>
  652. 限购{album.buyedTimes}/{album.buyTimes}次
  653. </span>
  654. )}
  655. <Image
  656. class={styles.image}
  657. width="100%"
  658. height="100%"
  659. fit="cover"
  660. src={album?.coverImg || iconAlbumCover}
  661. errorIcon={iconAlbumCover}
  662. />
  663. <div class={styles.iconPian}></div>
  664. </div>
  665. </SwiperSlide>
  666. ))}
  667. </Swiper>
  668. )
  669. )}
  670. <div class={styles.alumDes}>
  671. <div class={[styles.alumTitle, 'van-ellipsis']}>
  672. {state.details?.name}
  673. </div>
  674. <div
  675. class={[styles.des, 'van-multi-ellipsis--l2']}
  676. style={{
  677. height: '32px',
  678. lineHeight: '16px'
  679. }}
  680. >
  681. {state.details?.describe}
  682. </div>
  683. </div>
  684. {state.buy != '1' && baseState.platformType === 'STUDENT' && (
  685. <div class={styles.albumPriceGroup}>
  686. <div class={styles.albumTimer}>
  687. <img src={iconTimer} class={styles.iconTimer} />
  688. <span>有效期:{state.details?.purchaseNum || 0}天</span>
  689. </div>
  690. <div class={styles.albumPriceList}>
  691. {(state.details?.originalPrice || 0) >
  692. (state.details?.actualPrice || 0) && (
  693. <del class={styles.originPrice}>
  694. 原价:¥
  695. {moneyFormat(state.details?.originalPrice || 0)}
  696. </del>
  697. )}
  698. <span class={styles.currentPrice}>
  699. <span>
  700. ¥{moneyFormat(state.details?.actualPrice || 0)}
  701. </span>
  702. </span>
  703. </div>
  704. </div>
  705. )}
  706. </div>
  707. </div>
  708. <div class={[styles.musicList, 'musicList']}>
  709. <Sticky position="top" offsetTop={state.heightV}>
  710. <Tabs
  711. color="var(--van-primary)"
  712. background="transparent"
  713. lineWidth={20}
  714. shrink
  715. ref={tabsRef}
  716. v-model:active={state.activeTab}
  717. onClick-tab={val => {
  718. params.subjectId = null
  719. params.keyword = null
  720. params.level = ''
  721. params.type = ''
  722. params.courseTypeCode = ''
  723. state.activeTab = val.name
  724. activeTypeParams()
  725. params.page = 1
  726. state.list = []
  727. FetchList()
  728. }}
  729. >
  730. {!state.showLoading && <>
  731. {state.coursewareCounts && (
  732. <Tab title="云课堂" name="COURSEWARE"></Tab>
  733. )}
  734. {state.subjectCounts && (
  735. <Tab title="基础云练" name="SUBJECT"></Tab>
  736. )}
  737. {state.musicCounts && (
  738. <Tab title="独奏云练" name="MUSIC"></Tab>
  739. )}
  740. {state.ensembleCounts && (
  741. <Tab title="合奏云练" name="ENSEMBLE"></Tab>
  742. )}</>}
  743. </Tabs>
  744. <Search
  745. modelValue={params.keyword}
  746. onInput={(val: any) => {
  747. params.keyword = val
  748. }}
  749. placeholder={
  750. state.activeTab === 'COURSEWARE'
  751. ? '请输入教材关键词'
  752. : '请输入曲谱关键词'
  753. }
  754. class={styles.search}
  755. onSearch={onSearch}
  756. type="tenant"
  757. v-slots={{
  758. left: () =>
  759. isSearchStatus.value && state.activeTab !== 'COURSEWARE' && (
  760. <DropdownMenu zIndex={2999}>
  761. <DropdownItem
  762. onOpen={() => {
  763. const targetElement: any =
  764. document.querySelector('.musicList')
  765. const targetPosition =
  766. targetElement.getBoundingClientRect().top +
  767. window.scrollY
  768. window.scrollTo({
  769. top: targetPosition - state.heightV + 1
  770. })
  771. }}
  772. teleport="body"
  773. titleClass={
  774. params.subjectId ||
  775. params.type ||
  776. params.level
  777. ? styles.titleActive
  778. : ''
  779. }
  780. title="筛选"
  781. ref={searchRef}
  782. >
  783. <div
  784. class={styles.searchResult}
  785. style={{
  786. maxHeight: '45vh',
  787. overflowY: 'auto'
  788. }}
  789. >
  790. {searchObj.value[state.activeTab].subjects &&
  791. searchObj.value[state.activeTab].subjects
  792. .length > 0 && (
  793. <>
  794. <div class={styles.searchTitle}>
  795. 声部
  796. </div>
  797. <div
  798. class={[
  799. styles['radio-group'],
  800. styles.radio,
  801. styles['organ-radio']
  802. ]}
  803. >
  804. {searchObj.value[
  805. state.activeTab
  806. ].subjects.map((subject: any) => {
  807. const isActive =
  808. subject.id === params.subjectId
  809. const type = isActive
  810. ? 'primary'
  811. : 'default'
  812. return (
  813. <Tag
  814. size="large"
  815. plain={isActive}
  816. type={type}
  817. round
  818. onClick={() => {
  819. params.subjectId = subject.id
  820. }}
  821. >
  822. {subject.name}
  823. </Tag>
  824. )
  825. })}
  826. </div>
  827. </>
  828. )}
  829. {searchObj.value[state.activeTab].levelList &&
  830. searchObj.value[state.activeTab].levelList
  831. .length > 0 && (
  832. <>
  833. <div class={styles.searchTitle}>
  834. 级别
  835. </div>
  836. <div
  837. class={[
  838. styles['radio-group'],
  839. styles.radio,
  840. styles['organ-radio']
  841. ]}
  842. >
  843. {searchObj.value[
  844. state.activeTab
  845. ].levelList.map((subject: any) => {
  846. const isActive =
  847. subject.id === params.level
  848. const type = isActive
  849. ? 'primary'
  850. : 'default'
  851. return (
  852. <Tag
  853. size="large"
  854. plain={isActive}
  855. type={type}
  856. round
  857. onClick={() => {
  858. params.level = subject.id
  859. }}
  860. >
  861. {subject.value}
  862. </Tag>
  863. )
  864. })}
  865. </div>
  866. </>
  867. )}
  868. {searchObj.value[state.activeTab].typeList &&
  869. searchObj.value[state.activeTab].typeList
  870. .length > 0 && (
  871. <>
  872. <div class={styles.searchTitle}>
  873. 类型
  874. </div>
  875. <div
  876. class={[
  877. styles['radio-group'],
  878. styles.radio,
  879. styles['organ-radio']
  880. ]}
  881. >
  882. {searchObj.value[
  883. state.activeTab
  884. ].typeList.map((subject: any) => {
  885. const isActive =
  886. subject.id === params.type
  887. const type = isActive
  888. ? 'primary'
  889. : 'default'
  890. return (
  891. <Tag
  892. size="large"
  893. plain={isActive}
  894. type={type}
  895. round
  896. onClick={() => {
  897. params.type = subject.id
  898. }}
  899. >
  900. {subject.value}
  901. </Tag>
  902. )
  903. })}
  904. </div>
  905. </>
  906. )}
  907. </div>
  908. <div class={[styles.btnGroup2, 'btnMore']}>
  909. <Button
  910. class={styles.resetting}
  911. type="primary"
  912. plain
  913. round
  914. onClick={() => {
  915. params.subjectId = null
  916. params.level = ''
  917. params.type = ''
  918. }}
  919. >
  920. 重置
  921. </Button>
  922. <Button
  923. class={styles.confirm}
  924. type="primary"
  925. color="linear-gradient( 270deg, #FF204B 0%, #FE5B71 100%)"
  926. round
  927. block
  928. onClick={() => {
  929. onSearch(params.keyword)
  930. searchRef.value?.toggle()
  931. }}
  932. >
  933. 确定
  934. </Button>
  935. </div>
  936. </DropdownItem>
  937. </DropdownMenu>
  938. )
  939. }}
  940. />
  941. </Sticky>
  942. <div
  943. class={[
  944. styles.alumnList,
  945. state.activeTab === 'COURSEWARE'
  946. ? styles.alumnListCourseware
  947. : ''
  948. ]}
  949. >
  950. <List
  951. loading={state.loading}
  952. finished={state.finished}
  953. finished-text={' '}
  954. onLoad={FetchList}
  955. immediateCheck={false}
  956. error={state.isError}
  957. >
  958. {state.list && state.list.length ? (
  959. state.activeTab === 'COURSEWARE' ? (
  960. <CourseItem
  961. list={state.list.map(item => {
  962. return {
  963. name: item.musicSheetName,
  964. coverImg: item.titleImg,
  965. id: item.id
  966. }
  967. })}
  968. onItemClick={row => {
  969. sessionStorage.setItem(
  970. 'tool-subject-type',
  971. JSON.stringify({
  972. activeTab: state.activeTab,
  973. tenantGroupAlbumId:
  974. baseState.platformType === 'STUDENT'
  975. ? state.details.tenantGroupAlbumId
  976. : state.details.id, // 老师用专辑id当唯一值
  977. level: params.level,
  978. type: params.type,
  979. subjectId: params.type,
  980. keyword: params.keyword
  981. })
  982. )
  983. router.push({
  984. path: '/courseList',
  985. query: {
  986. id: row.id,
  987. albumId: state.details.id,
  988. taId: state.details.tenantGroupAlbumId, // 当通过我的曲目进来的时候 这个值为空
  989. buyStatus: state.hasBuyStatus ? '0' : '1' //默认能购买
  990. }
  991. })
  992. }}
  993. />
  994. ) : (
  995. <Song
  996. showNumber
  997. list={state.list}
  998. onDetail={(item: any) => {
  999. sessionStorage.setItem(
  1000. 'tool-subject-type',
  1001. JSON.stringify({
  1002. activeTab: state.activeTab,
  1003. tenantGroupAlbumId:
  1004. baseState.platformType === 'STUDENT'
  1005. ? state.details.tenantGroupAlbumId
  1006. : state.details.id, // 老师用专辑id当唯一值
  1007. level: params.level,
  1008. type: params.type,
  1009. subjectId: params.type,
  1010. keyword: params.keyword
  1011. })
  1012. )
  1013. router.push({
  1014. path: '/music-detail',
  1015. query: {
  1016. id: item.id,
  1017. tenantAlbumId: item.tenantAlbumId,
  1018. taId: state.details.tenantGroupAlbumId, // 当通过我的曲目进来的时候 这个值为空
  1019. buyStatus: state.hasBuyStatus ? '0' : '1' //默认能购买
  1020. }
  1021. })
  1022. }}
  1023. />
  1024. )
  1025. ) : (
  1026. !state.loading && (
  1027. <ColResult
  1028. tips={
  1029. state.activeTab === 'COURSEWARE'
  1030. ? '暂无教材'
  1031. : '暂无曲目'
  1032. }
  1033. classImgSize="SMALL"
  1034. btnStatus={false}
  1035. />
  1036. )
  1037. )}
  1038. </List>
  1039. </div>
  1040. </div>
  1041. {baseState.platformType === 'STUDENT' && state.buy != '1' && (
  1042. <TheSticky
  1043. position="bottom"
  1044. varName="--bottom-train-tool-height"
  1045. >
  1046. <div class={styles.btnGroup}>
  1047. <Button
  1048. round
  1049. block
  1050. disabled={!state.hasBuyStatus}
  1051. color="linear-gradient(270deg, #FF204B 0%, #FE5B71 100%)"
  1052. onClick={onSubmit}
  1053. >
  1054. 开通训练教程
  1055. </Button>
  1056. </div>
  1057. </TheSticky>
  1058. )}
  1059. </>
  1060. )
  1061. )}
  1062. {/* 选择声部 */}
  1063. <Popup
  1064. show={state.subjectStatus}
  1065. position="bottom"
  1066. round
  1067. safe-area-inset-bottom
  1068. onClose={() => (state.subjectStatus = false)}
  1069. onClosed={() => (state.openStatus = false)}
  1070. >
  1071. <Picker
  1072. defaultIndex={state.teacherSubjectIndex}
  1073. columns={state.subjectList}
  1074. confirmButtonText="确定"
  1075. onCancel={() => {
  1076. state.subjectStatus = false
  1077. }}
  1078. onConfirm={(val: any) => {params.keyword = null
  1079. params.level = ''
  1080. params.type = ''
  1081. params.courseTypeCode = ''
  1082. searchObj.value = {
  1083. COURSEWARE: {},
  1084. SUBJECT: {},
  1085. MUSIC: {},
  1086. ENSEMBLE: {}
  1087. }
  1088. state.teacherSubjectId = val.id
  1089. state.teacherSubjectName = val.name
  1090. params.page = 1
  1091. state.finished = false
  1092. state.list = []
  1093. activeTypeParams()
  1094. FetchList()
  1095. state.subjectStatus = false
  1096. operatoinCatchSubjectInfo('set', {
  1097. defaultSubject: state.teacherSubjectId,
  1098. defaultSubjectName: state.teacherSubjectName,
  1099. userId: state.userId
  1100. })
  1101. }}
  1102. ></Picker>
  1103. </Popup>
  1104. </div>
  1105. )
  1106. }
  1107. })