index.tsx 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627
  1. import { computed, defineComponent, onMounted, reactive, ref, watch } from 'vue'
  2. import {
  3. Image,
  4. Tabs,
  5. Tab,
  6. List,
  7. Button,
  8. Popup,
  9. Dialog,
  10. Sticky,
  11. Swipe,
  12. SwipeItem
  13. } from 'vant'
  14. import styles from './index.module.less'
  15. import TheSticky from '@/components/the-sticky'
  16. import ColHeader from '@/components/col-header'
  17. import { useWindowScroll, useEventListener } from '@vueuse/core'
  18. import request from '@/helpers/request'
  19. import iconMenu from './images/icon-menu.png'
  20. import iconRightTop from './images/icon-right-top.png'
  21. import iconAlbumCover from '../../images/icon-album-cover.png'
  22. import iconTimer from './images/icon-timer.png'
  23. import { state as baseState, setLogout } from '@/state'
  24. import Song from '../component/song'
  25. import { useRoute, useRouter } from 'vue-router'
  26. import ColResult from '@/components/col-result'
  27. import { moneyFormat } from '@/helpers/utils'
  28. import { orderStatus } from '@/views/order-detail/orderStatus'
  29. import { postMessage } from '@/helpers/native-message'
  30. import { browser } from '@/helpers/utils'
  31. // Import Swiper Vue.js components
  32. // import Swiper core and required modules
  33. import { Pagination } from 'swiper/modules'
  34. import { Swiper, SwiperSlide } from 'swiper/vue'
  35. // Import Swiper styles
  36. import 'swiper/css'
  37. import 'swiper/css/pagination'
  38. import CourseItem from '../lessonCourseware/component/CourseItem'
  39. export default defineComponent({
  40. name: 'train-tool',
  41. setup() {
  42. const subjectType = sessionStorage.getItem('tool-subject-type')
  43. sessionStorage.removeItem('tool-subject-type')
  44. const route = useRoute()
  45. const router = useRouter()
  46. const background = ref<string>('rgba(55, 205, 177, 0)')
  47. const color = ref<string>('#fff')
  48. const state = reactive({
  49. details: {} as any,
  50. buy: route.query.buy as any,
  51. albumId: route.query.albumId || null,
  52. activeTab: route.query.subjectType || 'SUBJECT',
  53. loadingAlbum: false,
  54. loading: false,
  55. finished: false,
  56. isError: false,
  57. list: [] as any,
  58. popupStatus: false,
  59. selectMember: {} as any, // 购买的月份
  60. ensembleCounts: false,
  61. musicCounts: false,
  62. subjectCounts: false,
  63. coursewareCounts: false,
  64. tenantAlbumStatus: 0 as any,
  65. ablumStatus: false,
  66. heightV: 0,
  67. hasBuyStatus: true, // 是否能继续购买
  68. albumList: [] as any // 专辑列表
  69. })
  70. const params = reactive({
  71. page: 1,
  72. rows: 20
  73. })
  74. const apiSuffix = ref(
  75. baseState.platformType === 'STUDENT' ? '/api-student' : '/api-teacher'
  76. )
  77. const isSingleAlbum = computed(() => {
  78. const query = route.query
  79. if (query.taId || (query.albumId && state.buy === '1')) {
  80. return true
  81. } else {
  82. return false
  83. }
  84. })
  85. const getDetails = async () => {
  86. state.loadingAlbum = true
  87. try {
  88. // tenantGroupAlbum/buyAlbumInfo
  89. // 当我的曲目过来的时候才走单个查询
  90. if (state.albumId && state.buy === '1') {
  91. let url = apiSuffix.value + '/userTenantAlbumRecord/detail'
  92. if (state.albumId) {
  93. url = url + '?albumId=' + state.albumId
  94. }
  95. const { data } = await request.post(url)
  96. state.albumList = [data || {}]
  97. state.details = data || {}
  98. } else {
  99. const url =
  100. apiSuffix.value +
  101. `/tenantGroupAlbum/buyAlbumInfo?tenantGroupAlbumId=${
  102. route.query.taId || ''
  103. }&tenantAlbumId=${state.albumId || ''}`
  104. // if (state.albumId) {
  105. // url = url + '?albumId=' + state.albumId
  106. // }
  107. const { data } = await request.get(url)
  108. state.albumList = data || []
  109. if (state.albumList.length > 0) {
  110. state.details = state.albumList[0]
  111. } else {
  112. // state.albumList
  113. if (!browser().isApp) {
  114. Dialog.alert({
  115. title: '提示',
  116. message: '该专辑不可购买',
  117. confirmButtonText: '确定',
  118. confirmButtonColor: '#2dc7aa'
  119. }).then(() => {
  120. if (browser().isApp) {
  121. postMessage({ api: 'back' })
  122. } else {
  123. setLogout()
  124. router.replace({
  125. path: '/login' as any,
  126. query: {
  127. returnUrl: '/train-tool',
  128. ...route.query
  129. }
  130. })
  131. }
  132. })
  133. }
  134. }
  135. }
  136. } catch {
  137. //
  138. }
  139. state.loadingAlbum = false
  140. }
  141. watch(
  142. () => state.details,
  143. () => {
  144. state.ensembleCounts = state.details?.ensembleCounts <= 0 ? false : true
  145. state.subjectCounts = state.details?.subjectCounts <= 0 ? false : true
  146. state.musicCounts = state.details?.musicCounts <= 0 ? false : true
  147. state.coursewareCounts =
  148. state.details?.coursewareCounts <= 0 ? false : true
  149. if (state.subjectCounts) {
  150. state.activeTab = 'SUBJECT'
  151. } else if (state.musicCounts) {
  152. state.activeTab = 'MUSIC'
  153. } else if (state.ensembleCounts) {
  154. state.activeTab = 'ENSEMBLE'
  155. } else if (state.coursewareCounts) {
  156. state.activeTab = 'COURSEWARE'
  157. }
  158. // 带的参数
  159. if (route.query.subjectType == 'SUBJECT' && state.subjectCounts) {
  160. state.activeTab = 'SUBJECT'
  161. } else if (route.query.subjectType == 'MUSIC' && state.musicCounts) {
  162. state.activeTab = 'MUSIC'
  163. } else if (
  164. route.query.subjectType == 'ENSEMBLE' &&
  165. state.ensembleCounts
  166. ) {
  167. state.activeTab = 'ENSEMBLE'
  168. } else if (
  169. route.query.subjectType == 'COURSEWARE' &&
  170. state.coursewareCounts
  171. ) {
  172. state.activeTab = 'COURSEWARE'
  173. }
  174. // subjectType 缓存
  175. if (subjectType == 'SUBJECT' && state.subjectCounts) {
  176. state.activeTab = 'SUBJECT'
  177. } else if (subjectType == 'MUSIC' && state.musicCounts) {
  178. state.activeTab = 'MUSIC'
  179. } else if (subjectType == 'ENSEMBLE' && state.ensembleCounts) {
  180. state.activeTab = 'ENSEMBLE'
  181. } else if (subjectType == 'COURSEWARE' && state.coursewareCounts) {
  182. state.activeTab = 'COURSEWARE'
  183. }
  184. if (state.details.buyTimesFlag) {
  185. if (state.details.buyedTimes >= state.details.buyTimes) {
  186. state.hasBuyStatus = false
  187. } else {
  188. state.hasBuyStatus = true
  189. }
  190. } else {
  191. state.hasBuyStatus = true
  192. }
  193. }
  194. )
  195. const FetchList = async (hideLoading = false) => {
  196. if (state.loading) {
  197. return
  198. }
  199. state.loading = true
  200. state.isError = false
  201. const tempParams = {
  202. albumId: state.details.id || null,
  203. subjectType: state.activeTab,
  204. ...params
  205. }
  206. try {
  207. const { data } = await request.post(
  208. `${apiSuffix.value}/tenantAlbumMusic/page`,
  209. {
  210. hideLoading,
  211. data: tempParams
  212. }
  213. )
  214. if (state.list.length > 0 && data.pageNo === 1) {
  215. return
  216. }
  217. state.list = state.list.concat(data.rows || [])
  218. params.page = data.pageNo + 1
  219. // showContact.value = state.list.length > 0
  220. state.loading = false
  221. state.finished = data.pageNo >= data.totalPage
  222. params.page = data.pageNo + 1
  223. } catch (error) {
  224. state.isError = true
  225. }
  226. state.loading = false
  227. }
  228. onMounted(async () => {
  229. useEventListener(document, 'scroll', evt => {
  230. const { y } = useWindowScroll()
  231. if (y.value > 20) {
  232. background.value = `rgba(255, 255, 255)`
  233. } else {
  234. background.value = 'transparent'
  235. }
  236. })
  237. state.loading = true
  238. state.loadingAlbum = true
  239. await getDetails()
  240. await FetchList()
  241. state.loadingAlbum = false
  242. state.loading = false
  243. // 为了处理 swiper 会不显示的问题
  244. document.body.scrollIntoView()
  245. window.scrollTo(1, 0)
  246. })
  247. const onSubmit = async () => {
  248. const album = state.details
  249. const details = state.details
  250. orderStatus.orderObject.orderType = 'TENANT_ALBUM'
  251. orderStatus.orderObject.orderName = details.name
  252. orderStatus.orderObject.orderDesc = details.name
  253. orderStatus.orderObject.actualPrice = album.actualPrice
  254. // orderStatus.orderObject.recomUserId = route.query.recomUserId || 0
  255. // orderStatus.orderObject.activityId = route.query.activityId || 0
  256. orderStatus.orderObject.orderNo = ''
  257. orderStatus.orderObject.orderList = [
  258. {
  259. orderType: 'TENANT_ALBUM',
  260. goodsName: details.name,
  261. actualPrice: album.actualPrice,
  262. price: album.actualPrice,
  263. ...details,
  264. ...album
  265. }
  266. ]
  267. const res = await request.post('/api-student/userOrder/getPendingOrder', {
  268. data: {
  269. goodType: 'TENANT_ALBUM',
  270. bizId: details.id
  271. }
  272. })
  273. const result = res.data
  274. if (result) {
  275. state.popupStatus = false
  276. Dialog.confirm({
  277. title: '提示',
  278. message: '您有一个未支付的订单,是否继续支付?',
  279. theme: 'round-button',
  280. className: 'confirm-button-group',
  281. cancelButtonText: '取消订单',
  282. confirmButtonText: '继续支付'
  283. })
  284. .then(async () => {
  285. orderStatus.orderObject.orderNo = result.orderNo
  286. orderStatus.orderObject.actualPrice = result.actualPrice
  287. orderStatus.orderObject.discountPrice = result.discountPrice
  288. orderStatus.orderObject.paymentConfig = {
  289. ...result.paymentConfig,
  290. paymentVendor: result.paymentVendor,
  291. paymentVersion: result.paymentVersion
  292. }
  293. routerTo()
  294. })
  295. .catch(() => {
  296. Dialog.close()
  297. // 只用取消订单,不用做其它处理
  298. cancelPayment(result.orderNo)
  299. })
  300. } else {
  301. routerTo()
  302. }
  303. }
  304. const routerTo = () => {
  305. const album = state.details
  306. router.push({
  307. path: '/orderDetail',
  308. query: {
  309. orderType: 'ALBUM',
  310. album: album.id
  311. }
  312. })
  313. }
  314. const cancelPayment = async (orderNo: string) => {
  315. try {
  316. await request.post('/api-student/userOrder/orderCancel/v2', {
  317. data: {
  318. orderNo
  319. }
  320. })
  321. } catch {
  322. //
  323. }
  324. }
  325. return () => (
  326. <div class={styles.trainTool}>
  327. {!state.loading && !state.details.id && state.buy != '1' ? (
  328. <>
  329. <TheSticky
  330. position="top"
  331. onBarHeight={(height: any) => {
  332. console.log(height, 'height', height)
  333. state.heightV = height
  334. }}
  335. >
  336. <ColHeader border={false} isFixed={false} />
  337. </TheSticky>
  338. {!state.loading && (
  339. <div
  340. style={{
  341. height: 'calc(100vh - var(--header-height))',
  342. display: 'flex',
  343. alignItems: 'center'
  344. }}
  345. >
  346. <ColResult
  347. tips="暂无专辑"
  348. classImgSize="SMALL"
  349. btnStatus={false}
  350. />
  351. </div>
  352. )}
  353. </>
  354. ) : (
  355. !state.loadingAlbum && (
  356. <>
  357. <TheSticky
  358. position="top"
  359. onBarHeight={(height: any) => {
  360. state.heightV = height
  361. }}
  362. >
  363. <ColHeader
  364. background={background.value}
  365. border={false}
  366. isFixed={false}
  367. hideHeader={route.query.taId ? true : false}
  368. // color={color.value}
  369. // backIconColor="white"
  370. />
  371. </TheSticky>
  372. {/* <img class={styles.bgImg} src={state.details?.coverImg} /> */}
  373. <div class={styles.musicContent}></div>
  374. <div class={styles.bg}>
  375. <div class={styles.alumWrap}>
  376. {isSingleAlbum.value ? (
  377. <div class={styles.singleAlbum}>
  378. <div class={styles.img}>
  379. {state.details?.buyTimesFlag && (
  380. <span class={styles.quota}>
  381. 限购:{state.details?.buyedTimes}/
  382. {state.details?.buyTimes}次
  383. </span>
  384. )}
  385. <Image
  386. class={styles.image}
  387. width="100%"
  388. height="100%"
  389. fit="cover"
  390. src={state.details?.coverImg || iconAlbumCover}
  391. errorIcon={iconAlbumCover}
  392. />
  393. <div class={styles.iconPian}></div>
  394. </div>
  395. </div>
  396. ) : (
  397. state.albumList &&
  398. state.albumList.length > 0 && (
  399. <Swiper
  400. watchSlidesProgress={true}
  401. slidesPerView={'auto'}
  402. centeredSlides={true}
  403. modules={[Pagination]}
  404. pagination={{ clickable: true }}
  405. // onSlideChange={(swiper: any) => {}}
  406. onTransitionEnd={(swiper: any) => {
  407. state.details = state.albumList[swiper.activeIndex]
  408. state.ensembleCounts =
  409. state.details?.ensembleCounts <= 0 ? false : true
  410. state.subjectCounts =
  411. state.details?.subjectCounts <= 0 ? false : true
  412. state.musicCounts =
  413. state.details?.musicCounts <= 0 ? false : true
  414. state.coursewareCounts =
  415. state.details?.coursewareCounts <= 0 ? false : true
  416. if (state.subjectCounts) {
  417. state.activeTab = 'SUBJECT'
  418. } else if (state.musicCounts) {
  419. state.activeTab = 'MUSIC'
  420. } else if (state.ensembleCounts) {
  421. state.activeTab = 'ENSEMBLE'
  422. } else if (state.coursewareCounts) {
  423. state.activeTab = 'COURSEWARE'
  424. }
  425. params.page = 1
  426. state.list = []
  427. FetchList(true)
  428. }}
  429. >
  430. {state.albumList.map((album: any) => (
  431. <SwiperSlide>
  432. <div class={styles.img}>
  433. {album.buyTimesFlag && (
  434. <span class={styles.quota}>
  435. 限购{album.buyedTimes}/{album.buyTimes}次
  436. </span>
  437. )}
  438. <Image
  439. class={styles.image}
  440. width="100%"
  441. height="100%"
  442. fit="cover"
  443. src={album?.coverImg || iconAlbumCover}
  444. errorIcon={iconAlbumCover}
  445. />
  446. <div class={styles.iconPian}></div>
  447. </div>
  448. </SwiperSlide>
  449. ))}
  450. </Swiper>
  451. )
  452. )}
  453. <div class={styles.alumDes}>
  454. <div class={[styles.alumTitle, 'van-ellipsis']}>
  455. {state.details?.name}
  456. </div>
  457. <div
  458. class={[styles.des, 'van-multi-ellipsis--l2']}
  459. style={{
  460. height: '32px',
  461. lineHeight: '16px'
  462. }}
  463. >
  464. {state.details?.describe}
  465. </div>
  466. </div>
  467. {state.buy != '1' && baseState.platformType === 'STUDENT' && (
  468. <div class={styles.albumPriceGroup}>
  469. <div class={styles.albumTimer}>
  470. <img src={iconTimer} class={styles.iconTimer} />
  471. <span>有效期:{state.details?.purchaseNum || 0}天</span>
  472. </div>
  473. <div class={styles.albumPriceList}>
  474. {(state.details?.originalPrice || 0) >
  475. (state.details?.actualPrice || 0) && (
  476. <del class={styles.originPrice}>
  477. 原价:¥
  478. {moneyFormat(state.details?.originalPrice || 0)}
  479. </del>
  480. )}
  481. <span class={styles.currentPrice}>
  482. <span>
  483. ¥{moneyFormat(state.details?.actualPrice || 0)}
  484. </span>
  485. </span>
  486. </div>
  487. </div>
  488. )}
  489. </div>
  490. </div>
  491. <div class={styles.musicList}>
  492. <Sticky position="top" offsetTop={state.heightV}>
  493. <Tabs
  494. color="var(--van-primary)"
  495. background="transparent"
  496. lineWidth={20}
  497. shrink
  498. v-model:active={state.activeTab}
  499. onChange={val => {
  500. state.activeTab = val
  501. params.page = 1
  502. state.list = []
  503. FetchList()
  504. }}
  505. >
  506. {state.subjectCounts && (
  507. <Tab title="声部练习" name="SUBJECT"></Tab>
  508. )}
  509. {state.musicCounts && (
  510. <Tab title="独奏曲目" name="MUSIC"></Tab>
  511. )}
  512. {state.ensembleCounts && (
  513. <Tab title="合奏练习" name="ENSEMBLE"></Tab>
  514. )}
  515. {state.coursewareCounts && (
  516. <Tab title="云教程" name="COURSEWARE"></Tab>
  517. )}
  518. </Tabs>
  519. </Sticky>
  520. <div class={styles.alumnList}>
  521. <List
  522. loading={state.loading}
  523. finished={state.finished}
  524. finished-text={' '}
  525. onLoad={FetchList}
  526. immediateCheck={false}
  527. error={state.isError}
  528. >
  529. {state.list && state.list.length ? (
  530. state.activeTab === 'COURSEWARE' ? (
  531. <CourseItem
  532. list={state.list.map(item => {
  533. return {
  534. name: item.musicSheetName,
  535. coverImg: item.titleImg,
  536. id: item.id
  537. }
  538. })}
  539. onItemClick={row => {
  540. sessionStorage.setItem(
  541. 'tool-subject-type',
  542. state.activeTab as any
  543. )
  544. router.push({
  545. path: '/courseList',
  546. query: {
  547. id: row.id,
  548. albumId: state.details.id,
  549. taId: state.details.tenantGroupAlbumId // 当通过我的曲目进来的时候 这个值为空
  550. }
  551. })
  552. }}
  553. />
  554. ) : (
  555. <Song
  556. showNumber
  557. list={state.list}
  558. onDetail={(item: any) => {
  559. sessionStorage.setItem(
  560. 'tool-subject-type',
  561. state.activeTab as any
  562. )
  563. router.push({
  564. path: '/music-detail',
  565. query: {
  566. id: item.id,
  567. tenantAlbumId: item.tenantAlbumId,
  568. taId: state.details.tenantGroupAlbumId // 当通过我的曲目进来的时候 这个值为空
  569. }
  570. })
  571. }}
  572. />
  573. )
  574. ) : (
  575. !state.loading && (
  576. <ColResult
  577. tips="暂无曲目" //暂无教程
  578. classImgSize="SMALL"
  579. btnStatus={false}
  580. />
  581. )
  582. )}
  583. </List>
  584. </div>
  585. </div>
  586. {baseState.platformType === 'STUDENT' && state.buy != '1' && (
  587. <TheSticky position="bottom">
  588. <div class={styles.btnGroup}>
  589. <Button
  590. round
  591. block
  592. disabled={!state.hasBuyStatus}
  593. color="linear-gradient(270deg, #FF204B 0%, #FE5B71 100%)"
  594. onClick={onSubmit}
  595. >
  596. 开通训练教程
  597. </Button>
  598. </div>
  599. </TheSticky>
  600. )}
  601. </>
  602. )
  603. )}
  604. </div>
  605. )
  606. }
  607. })