index.tsx 15 KB

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