index.tsx 19 KB

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