index.tsx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. import { defineComponent, nextTick, onMounted, reactive, ref } from 'vue'
  2. import { useRoute, useRouter } from 'vue-router'
  3. import request from '@/helpers/request'
  4. import ColHeader from '@/components/col-header'
  5. import { postMessage } from '@/helpers/native-message'
  6. import { Button, Dialog, Icon, Image, List, NavBar, Popup, Sticky } from 'vant'
  7. // import classNames from 'classnames'
  8. // import Footer from '../album/footer'
  9. // import FavoriteIcon from '../album/favorite.svg'
  10. // import FavoritedIcon from '../album/favorited.svg'
  11. import styles from './index.module.less'
  12. // import Item from '../list/item'
  13. import { useRect } from '@vant/use'
  14. import { useEventListener, useWindowScroll } from '@vueuse/core'
  15. import { getRandomKey, musicBuy } from '../music'
  16. import { state } from '@/state'
  17. import IconPan from './pan.png'
  18. import oStart from './oStart.png'
  19. import iStart from './iStart.png'
  20. import Title from '../component/title'
  21. import Song from '../component/song'
  22. import ColResult from '@/components/col-result'
  23. import MusicGrid from '../component/music-grid'
  24. import { useEventTracking } from '@/helpers/hooks'
  25. import ColSticky from '@/components/col-sticky'
  26. import { moneyFormat } from '@/helpers/utils'
  27. import { orderStatus } from '@/views/order-detail/orderStatus'
  28. import iconShare from './icon_share.png'
  29. import ColShare from '@/components/col-share'
  30. const noop = () => {}
  31. export default defineComponent({
  32. name: 'AlbumDetail',
  33. props: {
  34. onItemClick: {
  35. type: Function,
  36. default: noop
  37. }
  38. },
  39. setup({ onItemClick }) {
  40. localStorage.setItem('behaviorId', getRandomKey())
  41. const router = useRouter()
  42. const route = useRoute()
  43. const params = reactive({
  44. search: '',
  45. relatedNum: 6, //相关专辑数
  46. page: 1,
  47. rows: 200
  48. })
  49. const albumDetail = ref<any>(null)
  50. // const data = ref<any>(null)
  51. const rows = ref<any[]>([])
  52. const loading = ref(false)
  53. const aId = route.query.activityId || ''
  54. const studentActivityId = ref(aId)
  55. // const finished = ref(false)
  56. const isError = ref(false)
  57. const favorited = ref(0)
  58. const albumFavoriteCount = ref(0)
  59. const headers = ref(null)
  60. const background = ref<string>('rgba(55, 205, 177, 0)')
  61. const color = ref<string>('#fff')
  62. const heightInfo = ref<any>('auto')
  63. const FetchList = async (id?: any) => {
  64. if (loading.value) {
  65. return
  66. }
  67. loading.value = true
  68. isError.value = false
  69. try {
  70. const res = await request.post('/music/album/detail', {
  71. prefix:
  72. state.platformType === 'TEACHER' ? '/api-teacher' : '/api-student',
  73. data: { id: id || route.params.id, ...params }
  74. })
  75. const { musicSheetList, ...rest } = res.data
  76. rows.value = [...musicSheetList.rows]
  77. const musicTagNames = rest?.musicTagNames?.split(',') || []
  78. albumDetail.value = {
  79. ...rest,
  80. musicTagNames
  81. }
  82. favorited.value = rest.favorite
  83. albumFavoriteCount.value = rest.albumFavoriteCount
  84. } catch (error) {
  85. isError.value = true
  86. }
  87. loading.value = false
  88. }
  89. const favoriteLoading = ref(false)
  90. onMounted(() => {
  91. FetchList()
  92. useEventListener(document, 'scroll', evt => {
  93. const { y } = useWindowScroll()
  94. if (y.value > 20) {
  95. background.value = `rgba(255, 255, 255)`
  96. color.value = 'black'
  97. postMessage({
  98. api: 'backIconChange',
  99. content: { iconStyle: 'black' }
  100. })
  101. } else {
  102. background.value = 'transparent'
  103. color.value = '#fff'
  104. postMessage({
  105. api: 'backIconChange',
  106. content: { iconStyle: 'white' }
  107. })
  108. }
  109. })
  110. useEventTracking('专辑')
  111. })
  112. const toggleFavorite = async (id: number) => {
  113. favoriteLoading.value = true
  114. try {
  115. await request.post('/music/album/favorite/' + id, {
  116. prefix:
  117. state.platformType === 'TEACHER' ? '/api-teacher' : '/api-student'
  118. })
  119. favorited.value = favorited.value === 1 ? 0 : 1
  120. albumFavoriteCount.value += favorited.value ? 1 : -1
  121. } catch (error) {}
  122. favoriteLoading.value = false
  123. }
  124. const onBuy = async () => {
  125. const album = albumDetail.value
  126. orderStatus.orderObject.orderType = 'ALBUM'
  127. orderStatus.orderObject.orderName = album.albumName
  128. orderStatus.orderObject.orderDesc = album.albumName
  129. orderStatus.orderObject.actualPrice = album.albumPrice
  130. orderStatus.orderObject.recomUserId = route.query.recomUserId || 0
  131. orderStatus.orderObject.activityId = route.query.activityId || 0
  132. orderStatus.orderObject.orderNo = ''
  133. orderStatus.orderObject.orderList = [
  134. {
  135. orderType: 'ALBUM',
  136. goodsName: album.albumName,
  137. recomUserId: route.query.recomUserId || 0,
  138. price: album.albumPrice,
  139. ...album
  140. }
  141. ]
  142. const res = await request.post('/api-student/userOrder/getPendingOrder', {
  143. data: {
  144. goodType: 'ALBUM',
  145. bizId: album.id
  146. }
  147. })
  148. const result = res.data
  149. if (result) {
  150. Dialog.confirm({
  151. title: '提示',
  152. message: '您有一个未支付的订单,是否继续支付?',
  153. confirmButtonColor: '#269a93',
  154. cancelButtonText: '取消订单',
  155. confirmButtonText: '继续支付'
  156. })
  157. .then(async () => {
  158. orderStatus.orderObject.orderNo = result.orderNo
  159. orderStatus.orderObject.actualPrice = result.actualPrice
  160. orderStatus.orderObject.discountPrice = result.discountPrice
  161. routerTo()
  162. })
  163. .catch(() => {
  164. Dialog.close()
  165. // 只用取消订单,不用做其它处理
  166. cancelPayment(result.orderNo)
  167. })
  168. } else {
  169. routerTo()
  170. }
  171. // this.$router.push({
  172. // path: '/orderDetail',
  173. // query: {
  174. // orderType: 'VIP'
  175. // }
  176. // })
  177. }
  178. const routerTo = () => {
  179. const album = albumDetail.value
  180. router.push({
  181. path: '/orderDetail',
  182. query: {
  183. orderType: 'ALBUM',
  184. album: album.id
  185. }
  186. })
  187. }
  188. const cancelPayment = async (orderNo: string) => {
  189. try {
  190. await request.post('/api-student/userOrder/orderCancel', {
  191. data: {
  192. orderNo
  193. }
  194. })
  195. // this.routerTo()
  196. } catch {}
  197. }
  198. const shareStatus = ref<boolean>(false)
  199. const shareUrl = ref<string>('')
  200. const shareDiscount = ref<number>(0)
  201. const onShare = async () => {
  202. const userId = state.user.data.userId
  203. const id = route.params.id
  204. let activityId = 0
  205. console.log(state.user, userId)
  206. if (state.platformType === 'TEACHER') {
  207. const res = await request.post('/api-teacher/open/vipProfit', {
  208. data: {
  209. bizId: id,
  210. userId
  211. }
  212. })
  213. // 如果有会员则显示
  214. if (
  215. albumDetail.value?.musicPaymentTypes &&
  216. albumDetail.value?.musicPaymentTypes.includes('VIP')
  217. ) {
  218. activityId = res.data.activityId || 0
  219. shareDiscount.value = res.data.discount || 0
  220. }
  221. }
  222. shareUrl.value = `${location.origin}/teacher#/shareAblum?id=${id}&recomUserId=${userId}&activityId=${activityId}&userType=${state.platformType}`
  223. console.log(shareUrl.value, 'shareUrl')
  224. shareStatus.value = true
  225. }
  226. return () => {
  227. return (
  228. <div class={styles.detail}>
  229. <div ref={headers}>
  230. <ColHeader
  231. background={background.value}
  232. border={false}
  233. color={color.value}
  234. backIconColor="white"
  235. onHeaderBack={() => {
  236. nextTick(() => {
  237. const { height } = useRect(headers as any)
  238. heightInfo.value = height
  239. })
  240. }}
  241. v-slots={{
  242. right: () => (
  243. <div class={styles.shareBtn} onClick={onShare}>
  244. <Image src={iconShare} />
  245. 分享
  246. </div>
  247. )
  248. }}
  249. />
  250. </div>
  251. <img class={styles.bgImg} src={albumDetail.value?.albumCoverUrl} />
  252. <div class={styles.musicContent}></div>
  253. <div class={styles.bg}>
  254. <div class={styles.alumWrap}>
  255. <div class={styles.img}>
  256. {albumDetail.value?.paymentType === 'CHARGE' && (
  257. <span class={styles.albumType}>付费</span>
  258. )}
  259. <Image
  260. class={styles.image}
  261. width="100%"
  262. height="100%"
  263. fit="cover"
  264. src={albumDetail.value?.albumCoverUrl}
  265. />
  266. </div>
  267. <div class={styles.alumDes}>
  268. <div class={[styles.alumTitle, 'van-ellipsis']}>
  269. {albumDetail.value?.albumName}
  270. </div>
  271. <div class={styles.tags}>
  272. {albumDetail.value?.musicTagNames?.map((tag: any) => (
  273. <span class={styles.tag}>{tag}</span>
  274. ))}
  275. </div>
  276. <div
  277. class={[styles.des, 'van-multi-ellipsis--l3']}
  278. style={{
  279. height: '48px',
  280. lineHeight: '16px'
  281. }}
  282. >
  283. {albumDetail.value?.albumDesc}
  284. </div>
  285. </div>
  286. </div>
  287. <div class={styles.alumCollect}>
  288. <img src={IconPan} />
  289. <span>共{albumDetail.value?.musicSheetCount}首曲目</span>
  290. <div
  291. class={styles.right}
  292. onClick={() => toggleFavorite(albumDetail.value?.id)}
  293. >
  294. <img src={favorited.value ? iStart : oStart} />
  295. <span>{albumFavoriteCount.value}人收藏</span>
  296. </div>
  297. </div>
  298. {albumDetail.value?.paymentType === 'CHARGE' &&
  299. albumDetail.value?.orderStatus !== 'PAID' && (
  300. <div class={styles.albumTips}>
  301. <span>此专辑为收费类型,购买即可自由练习该专辑</span>
  302. <span class={styles.albumPrice}>
  303. ¥{moneyFormat(albumDetail.value?.albumPrice)}
  304. </span>
  305. </div>
  306. )}
  307. </div>
  308. <div class={styles.alumnContainer}>
  309. <div class={styles.alumnList}>
  310. <Title title="曲目列表" isMore={false} />
  311. <Song
  312. list={rows.value}
  313. onDetail={(item: any) => {
  314. if (onItemClick === noop || !onItemClick) {
  315. musicBuy(item, () => {}, {
  316. albumId: route.params.id,
  317. albumName: albumDetail.value?.albumName
  318. })
  319. } else {
  320. onItemClick(item)
  321. }
  322. }}
  323. />
  324. {rows.value && rows.value.length <= 0 && (
  325. <ColResult btnStatus={false} tips="暂无曲目" />
  326. )}
  327. </div>
  328. {albumDetail.value?.relatedMusicAlbum &&
  329. albumDetail.value?.relatedMusicAlbum.length > 0 && (
  330. <>
  331. <Title
  332. title="相关专辑"
  333. onMore={() => {
  334. router.push({
  335. path: '/music-album'
  336. })
  337. }}
  338. />
  339. <MusicGrid
  340. list={albumDetail.value?.relatedMusicAlbum}
  341. onGoto={(n: any) => {
  342. router
  343. .push({
  344. name: 'music-album-detail',
  345. params: {
  346. id: n.id
  347. }
  348. })
  349. .then(() => {
  350. FetchList(n.id)
  351. window.scrollTo(0, 0)
  352. })
  353. }}
  354. />
  355. </>
  356. )}
  357. </div>
  358. {/* 判断是否是收费 是否是已经购买 */}
  359. {albumDetail.value?.paymentType === 'CHARGE' &&
  360. albumDetail.value?.orderStatus !== 'PAID' && (
  361. <ColSticky position="bottom" background="white">
  362. <div
  363. class={[
  364. 'btnGroup',
  365. albumDetail.value?.musicPaymentTypes &&
  366. albumDetail.value?.musicPaymentTypes.includes('VIP') &&
  367. 'btnMore'
  368. ]}
  369. style={{ paddingTop: '12px' }}
  370. >
  371. <Button
  372. block
  373. round
  374. type="primary"
  375. style={{ fontSize: '16px' }}
  376. onClick={onBuy}
  377. >
  378. 购买专辑
  379. </Button>
  380. {albumDetail.value?.musicPaymentTypes &&
  381. albumDetail.value?.musicPaymentTypes.includes('VIP') && (
  382. <>
  383. <Button
  384. block
  385. round
  386. type="primary"
  387. style={{ fontSize: '16px' }}
  388. onClick={() => {
  389. router.push({
  390. path: '/memberCenter',
  391. query: {
  392. ...route.query
  393. }
  394. })
  395. }}
  396. >
  397. {studentActivityId.value > 0 && (
  398. <div class={[styles.buttonDiscount]}>专属优惠</div>
  399. )}
  400. 开通会员
  401. </Button>
  402. </>
  403. )}
  404. </div>
  405. </ColSticky>
  406. )}
  407. <Popup
  408. v-model:show={shareStatus.value}
  409. style={{ background: 'transparent' }}
  410. >
  411. <ColShare
  412. teacherId={state.user.data.userId}
  413. shareUrl={shareUrl.value}
  414. shareType="album"
  415. shareLength={1}
  416. >
  417. <div class={styles.shareVip}>
  418. {shareDiscount.value === 1 && (
  419. <div class={styles.tagDiscount}>专属优惠</div>
  420. )}
  421. <img
  422. class={styles.icon}
  423. src={albumDetail.value?.albumCoverUrl}
  424. />
  425. <div class={styles.info}>
  426. <h4 class="van-multi-ellipsis--l2">
  427. {albumDetail.value?.albumName}
  428. </h4>
  429. <p
  430. class={['van-multi-ellipsis--l3']}
  431. style={{
  432. height: '48px',
  433. lineHeight: '16px'
  434. }}
  435. >
  436. {albumDetail.value?.albumDesc}
  437. </p>
  438. </div>
  439. </div>
  440. </ColShare>
  441. </Popup>
  442. </div>
  443. )
  444. }
  445. }
  446. })