index.tsx 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738
  1. import ColSticky from '@/components/col-sticky'
  2. import request from '@/helpers/request'
  3. import { browser, removeAuth } from '@/helpers/utils'
  4. import { postMessage } from '@/helpers/native-message'
  5. import {
  6. Button,
  7. Cell,
  8. CellGroup,
  9. Dialog,
  10. Image,
  11. Notify,
  12. Popup,
  13. Toast
  14. } from 'vant'
  15. import { defineComponent } from 'vue'
  16. import styles from './index.module.less'
  17. import logo from '@/common/images/logo.png'
  18. import { shareCall } from '@/teacher/share-page/share'
  19. import { getRandomKey } from '@/views/music/music'
  20. import qs from 'query-string'
  21. import dayjs from 'dayjs'
  22. import { orderStatus } from '@/views/order-detail/orderStatus'
  23. import { difficulty } from '@/constant'
  24. import { state } from '@/state'
  25. import iconTeacher from '@/common/images/icon_teacher.png'
  26. export const getAssetsHomeFile = (fileName: string) => {
  27. const path = `./images/${fileName}`
  28. const modules = import.meta.globEager('./images/*')
  29. return modules[path].default
  30. }
  31. export default defineComponent({
  32. name: 'track-review-activity',
  33. data() {
  34. const query = this.$route.query
  35. return {
  36. id: query.id,
  37. recomUserId: query.recomUserId || '', // 推荐人id
  38. activeInfo: {} as any,
  39. popupStatus: false,
  40. wxStatus: false,
  41. behaviorId: getRandomKey(),
  42. selectMusic: {} as any,
  43. hiddenProperty: null as any
  44. }
  45. },
  46. computed: {
  47. rankingMethod() {
  48. const activeInfo: any = this.activeInfo
  49. return activeInfo.rankingMethod || ''
  50. },
  51. activityMusic() {
  52. const activeInfo: any = this.activeInfo
  53. const list =
  54. activeInfo.rankingMethod === 'TOTAL_SCORE'
  55. ? activeInfo.subjectInfos
  56. : activeInfo.activityMusicVoList
  57. return list || []
  58. },
  59. // 用户是否有中选的曲子
  60. userSelectMusic() {
  61. let status = false
  62. this.activityMusic.forEach((item: any) => {
  63. if (item.join === 1) {
  64. status = true
  65. }
  66. })
  67. return status
  68. },
  69. // this.activeInfo.activityRewardList
  70. rewardList() {
  71. const activeInfo: any = this.activeInfo
  72. let list = activeInfo.activityRewardList || []
  73. if (list && list.length > 0) {
  74. if (activeInfo.rankingMethod === 'TOTAL_SCORE') {
  75. list = [list]
  76. } else {
  77. const last = list.sort((a: any, b: any) => {
  78. return Number(a.group) < Number(b.group) ? -1 : 1
  79. })
  80. list = this.groupBy(last, (item: any) => {
  81. return [item.group]
  82. })
  83. }
  84. }
  85. // console.log(list)
  86. return list || []
  87. }
  88. },
  89. async mounted() {
  90. await this.getMusicInfo()
  91. // 判断是否在内容打开页面
  92. if (!browser().isApp) {
  93. // this.popupStatus = true
  94. // removeAuth()
  95. // return
  96. } else if (state.platformType === 'TEACHER') {
  97. this.onBackDialog('请使用酷乐秀学生端扫码打开')
  98. return
  99. } else {
  100. console.log(this.hiddenProperty, 'hidden property')
  101. this.hiddenProperty =
  102. 'hidden' in document
  103. ? 'hidden'
  104. : 'webkitHidden' in document
  105. ? 'webkitHidden'
  106. : 'mozHidden' in document
  107. ? 'mozHidden'
  108. : null
  109. const visibilityChangeEvent = this.hiddenProperty.replace(
  110. /hidden/i,
  111. 'visibilitychange'
  112. )
  113. document.addEventListener(visibilityChangeEvent, this.onVisibilityChange)
  114. }
  115. // 判断活动状态的活动时间
  116. this.checkActivityTime()
  117. },
  118. unmounted() {
  119. if (this.hiddenProperty) {
  120. const visibilityChangeEvent = this.hiddenProperty.replace(
  121. /hidden/i,
  122. 'visibilitychange'
  123. )
  124. document.removeEventListener(
  125. visibilityChangeEvent,
  126. this.onVisibilityChange
  127. )
  128. }
  129. },
  130. methods: {
  131. groupBy(array: any, f: any) {
  132. const groups = {}
  133. array.forEach(o => {
  134. //注意这里必须是forEach 大写
  135. const group = JSON.stringify(f(o))
  136. groups[group] = groups[group] || []
  137. groups[group].push(o)
  138. })
  139. return Object.keys(groups).map(function (group) {
  140. return groups[group]
  141. })
  142. },
  143. onVisibilityChange() {
  144. if (!document[this.hiddenProperty]) {
  145. this.getMusicInfo()
  146. }
  147. },
  148. async getMusicInfo() {
  149. try {
  150. const res = await request.post(
  151. '/api-student/open/activity/info/' + this.id
  152. )
  153. this.activeInfo = res.data
  154. document.title = this.activeInfo.activityName
  155. } catch {
  156. //
  157. }
  158. },
  159. checkActivityTime() {
  160. // 判断活动状态的活动时间
  161. try {
  162. const activeInfo = this.activeInfo
  163. if (activeInfo.activityState !== 1) {
  164. Dialog.alert({
  165. message: '活动已结束,感谢你的关注!',
  166. theme: 'round-button',
  167. confirmButtonColor: '#2dc7aa'
  168. })
  169. this.onBackDialog('活动已结束,感谢你的关注!')
  170. return false
  171. }
  172. const nowTime = dayjs()
  173. const startTime = dayjs(activeInfo.activityStart)
  174. const endTime = dayjs(activeInfo.activityEnd)
  175. if (dayjs(nowTime).isBefore(dayjs(startTime))) {
  176. this.onBackDialog('活动尚未开始,请您耐心等待!')
  177. return false
  178. } else if (!dayjs(nowTime).isBefore(dayjs(endTime))) {
  179. this.onBackDialog('活动已结束,感谢你的关注!')
  180. return false
  181. }
  182. return true
  183. } catch {
  184. //
  185. }
  186. },
  187. onBackDialog(str: string) {
  188. Dialog.alert({
  189. message: str,
  190. theme: 'round-button',
  191. confirmButtonColor: '#2dc7aa'
  192. }).then(() => {
  193. postMessage({ api: 'back' })
  194. })
  195. },
  196. async onJoinActve() {
  197. if (!browser().isApp) {
  198. this.popupStatus = true
  199. removeAuth()
  200. return
  201. }
  202. const activeInfo = this.activeInfo
  203. try {
  204. if (!this.checkActivityTime()) {
  205. return
  206. }
  207. if (activeInfo.registrationMethod === 'CHARGE') {
  208. // 判断是否有待支付订单
  209. const res = await request.post(
  210. '/api-student/userOrder/getPendingOrder',
  211. {
  212. data: {
  213. goodType: 'ACTI_REGIST',
  214. bizId: activeInfo.id
  215. }
  216. }
  217. )
  218. // 判断是否是收费活动
  219. orderStatus.orderObject.orderType = 'ACTI_REGIST'
  220. orderStatus.orderObject.orderName = activeInfo.activityName
  221. orderStatus.orderObject.orderDesc = activeInfo.activityName
  222. orderStatus.orderObject.actualPrice = activeInfo.registrationPrice
  223. orderStatus.orderObject.orderNo = res.data?.orderNo || ''
  224. orderStatus.orderObject.recomUserId = this.recomUserId
  225. orderStatus.orderObject.orderList = [
  226. {
  227. orderType: 'ACTI_REGIST',
  228. goodsName: activeInfo.activityName,
  229. activityId: activeInfo.id,
  230. actualPrice: activeInfo.registrationPrice,
  231. recomUserId: this.recomUserId
  232. }
  233. ]
  234. this.$router.push({
  235. path: '/orderDetail',
  236. query: {
  237. orderType: 'ACTI_REGIST',
  238. activityId: activeInfo.id
  239. }
  240. })
  241. } else {
  242. await request.post(
  243. `/api-student/activity/joinActivity/${activeInfo.id}`
  244. )
  245. // 成功通知
  246. setTimeout(() => {
  247. Toast('报名成功')
  248. }, 300)
  249. // Notify({ type: 'success', message: '报名成功' })
  250. this.getMusicInfo()
  251. }
  252. } catch {
  253. //
  254. }
  255. },
  256. async onOpenMusic() {
  257. try {
  258. const selectMusic = this.selectMusic
  259. let evaluationId = ''
  260. // 判断是否是总分评测
  261. if (this.rankingMethod === 'TOTAL_SCORE') {
  262. // 直接取第一首曲目的评测编号
  263. const activityMusicVoList = this.activeInfo.activityMusicVoList || []
  264. activityMusicVoList.forEach(item => {
  265. console.log(item, selectMusic)
  266. if (item.subjectId == selectMusic.subjectId && !evaluationId) {
  267. evaluationId = item.evaluationId
  268. }
  269. })
  270. } else {
  271. evaluationId = selectMusic.evaluationId
  272. }
  273. if (selectMusic.join !== 1) {
  274. await request.post(`/api-student/activity/evaluation/${evaluationId}`)
  275. this.selectMusic.join = 1
  276. }
  277. this.popupStatus = false
  278. if (this.rankingMethod === 'TOTAL_SCORE') {
  279. this.$router.push({
  280. path: '/track-song',
  281. query: {
  282. subjectId: selectMusic.subjectId,
  283. id: this.id
  284. }
  285. })
  286. return
  287. }
  288. const browserInfo = browser()
  289. const url = qs.stringifyUrl({
  290. url: location.origin + '/accompany',
  291. query: {
  292. id: selectMusic.musicSheetId,
  293. behaviorId: this.behaviorId,
  294. client: browserInfo.isTeacher ? 'teacher' : 'student',
  295. setting: JSON.stringify({
  296. mode: 'EVALUATING',
  297. resets: ['SPEED'],
  298. difficulty: this.activeInfo.evaluationDifficulty,
  299. feeType: 'FREE',
  300. submitData: { evaluationId: this.selectMusic.evaluationId }
  301. })
  302. }
  303. })
  304. postMessage({
  305. api: 'openAccompanyWebView',
  306. content: {
  307. url,
  308. orientation: 0,
  309. isHideTitle: true,
  310. statusBarTextColor: false,
  311. isOpenLight: true
  312. }
  313. })
  314. } catch {
  315. //
  316. }
  317. },
  318. onOpenApp() {
  319. //
  320. if (!browser().isApp) {
  321. if (browser().weixin) {
  322. this.wxStatus = true
  323. return
  324. }
  325. // 如果不在app里面则不需要唤起操作
  326. const { origin } = location
  327. const str =
  328. origin +
  329. `/student/#/track-review-activity?id=${this.id}&recomUserId=${this.recomUserId}`
  330. shareCall(str, {})
  331. // 不管有没有唤起,则跳转到下载页面
  332. setTimeout(() => {
  333. window.location.href = location.origin + '/student/#/download'
  334. }, 3000)
  335. } else {
  336. this.popupStatus = false
  337. }
  338. }
  339. },
  340. render() {
  341. return (
  342. <div
  343. class={styles.review}
  344. style={{
  345. background: `url(${this.activeInfo.subjectUrl}) no-repeat top center ${this.activeInfo.backgroundUrl}`,
  346. backgroundSize: 'contain'
  347. }}
  348. >
  349. <div class={styles.reviewContainer}>
  350. <div class={[styles.section, styles.activeTime]}>
  351. <div class={styles.activeBg}>
  352. <img src={getAssetsHomeFile('icon_time.png')} />
  353. <p>
  354. <span>活动时间:</span>
  355. {dayjs(this.activeInfo.activityStart).format(
  356. 'YYYY-MM-DD'
  357. )} ~ {dayjs(this.activeInfo.activityEnd).format('YYYY-MM-DD')}
  358. </p>
  359. </div>
  360. </div>
  361. <div class={[styles.section]}>
  362. <div class={styles.title}>
  363. <img src={getAssetsHomeFile('icon_arrow_left.png')} />
  364. <span>活动介绍</span>
  365. <img src={getAssetsHomeFile('icon_arrow_right.png')} />
  366. </div>
  367. <div class={styles.tips}>{this.activeInfo.describe}</div>
  368. </div>
  369. <div class={[styles.section]}>
  370. <h2 class={styles.title2}>
  371. <span>
  372. <i class={styles.titlePrefix}></i>
  373. 活动奖品
  374. </span>
  375. <span
  376. class={styles.titleTips}
  377. onClick={() =>
  378. this.$router.push({
  379. path: '/leaderboard',
  380. query: { id: this.id }
  381. })
  382. }
  383. >
  384. 查看挑战排行榜
  385. <img
  386. style={{ width: '16px', marginLeft: '4px' }}
  387. src={getAssetsHomeFile('icon-lv.png')}
  388. />
  389. </span>
  390. </h2>
  391. {this.rewardList.map((item: any, index: number) => (
  392. <>
  393. <div class={styles.prizeTitle}>
  394. {this.rankingMethod === 'TOTAL_SCORE' ? (
  395. <div class={styles.prizeTitle_score}>
  396. 总评测分数达到 {this.activeInfo.rankingScore || 0}
  397. 分即可获奖
  398. </div>
  399. ) : (
  400. <>
  401. {index === 0 && (
  402. <Image
  403. class={styles.prizeLevel}
  404. src={getAssetsHomeFile('icon_level.png')}
  405. />
  406. )}
  407. {index === 1 && (
  408. <Image
  409. class={styles.prizeLevel}
  410. src={getAssetsHomeFile('icon_level2.png')}
  411. />
  412. )}
  413. {index === 2 && (
  414. <Image
  415. class={styles.prizeLevel}
  416. src={getAssetsHomeFile('icon_level3.png')}
  417. />
  418. )}
  419. 第{++index}名奖品
  420. </>
  421. )}
  422. </div>
  423. <div class={styles.prizeSection}>
  424. {item.map((child: any) => (
  425. <div class={[styles.prize]}>
  426. <Image src={child.imgUrl} />
  427. <div class={styles.prizeContainer}>
  428. <div class={styles.prizeName}>{child.rewardName}</div>
  429. <div class={styles.prizeDesc}>
  430. {child.rewardDescribe}
  431. </div>
  432. </div>
  433. </div>
  434. ))}
  435. </div>
  436. </>
  437. ))}
  438. </div>
  439. <div class={[styles.section]} style={{ backgroundColor: '#fff' }}>
  440. <h2 class={styles.title2}>
  441. <span>
  442. <i class={styles.titlePrefix}></i>
  443. 活动{this.rankingMethod === 'TOTAL_SCORE' ? '声部' : '曲目'}
  444. </span>
  445. {this.rankingMethod === 'TOTAL_SCORE' ? (
  446. <span
  447. class={styles.titleTips}
  448. onClick={() => {
  449. this.$router.push({
  450. path: '/track-subject-song',
  451. query: {
  452. id: this.id
  453. }
  454. })
  455. }}
  456. >
  457. 查看曲目
  458. </span>
  459. ) : (
  460. <span class={styles.titleTips}>
  461. 共{this.activityMusic.length || 0}首曲目
  462. </span>
  463. )}
  464. </h2>
  465. {this.activityMusic.map((item: any) => (
  466. <CellGroup class={styles.musicItem} border={false}>
  467. <Cell
  468. center
  469. titleClass={styles.musicTitle}
  470. isLink
  471. v-slots={{
  472. icon: () => (
  473. <Image
  474. src={getAssetsHomeFile(
  475. this.rankingMethod !== 'TOTAL_SCORE'
  476. ? 'icon_music.png'
  477. : 'icon_subject.png'
  478. )}
  479. class={styles.iconMusic}
  480. />
  481. ),
  482. title: () => (
  483. <span class={styles.musicName}>
  484. {this.rankingMethod === 'TOTAL_SCORE'
  485. ? item.subjectName
  486. : item.musicSheetName}
  487. </span>
  488. ),
  489. value: () => (
  490. <span
  491. onClick={() => {
  492. this.$router.push({
  493. path: '/track-subject-song',
  494. query: {
  495. id: this.id,
  496. subjectId: item.subjectId
  497. }
  498. })
  499. }}
  500. >
  501. {this.rankingMethod === 'TOTAL_SCORE'
  502. ? `共${item.musicNums}首活动曲目`
  503. : item.musicSubject}
  504. </span>
  505. )
  506. }}
  507. />
  508. <Cell
  509. center
  510. class={styles.cellLevel}
  511. v-slots={{
  512. icon: () => (
  513. <div class={styles.kingSection}>
  514. <Image
  515. src={getAssetsHomeFile('icon_king.png')}
  516. class={styles.iconKing}
  517. />
  518. <p class={styles.score}>
  519. {item.userId ? item.score : '--'}
  520. <span>分</span>
  521. </p>
  522. </div>
  523. ),
  524. title: () => (
  525. <div class={styles.users}>
  526. <div class={styles.userInfo}>
  527. <div class={styles.userImg}>
  528. {item.userId ? (
  529. <>
  530. <Image
  531. src={item.userAvatar || iconTeacher}
  532. class={styles.userLogo}
  533. fit="cover"
  534. />
  535. <img
  536. src={getAssetsHomeFile('icon_level.png')}
  537. class={styles.iconLevel}
  538. />
  539. </>
  540. ) : (
  541. <div class={styles.userLogo}>
  542. <img
  543. class={styles.img}
  544. src={getAssetsHomeFile('icon_no_level.png')}
  545. />
  546. </div>
  547. )}
  548. </div>
  549. <div class={styles.userName}>
  550. {item.userId ? (
  551. <>
  552. <p class={styles.name}>{item.username}</p>
  553. {this.rankingMethod !== 'TOTAL_SCORE' && (
  554. <p>
  555. <span class={styles.subjectName}>
  556. {item.userSubject}
  557. </span>
  558. </p>
  559. )}
  560. </>
  561. ) : (
  562. <span class={styles.noText}>虚位以待</span>
  563. )}
  564. </div>
  565. </div>
  566. <div class={styles.userBtn}>
  567. <Button
  568. round
  569. style={{
  570. padding: '0 8px',
  571. height: '32px'
  572. }}
  573. color="linear-gradient(180deg, #FFA200 0%, #FF6900 100%)"
  574. disabled={
  575. this.activeInfo.join === 0 ||
  576. (this.userSelectMusic && item.join != 1)
  577. }
  578. onClick={() => {
  579. if (!this.checkActivityTime()) {
  580. return
  581. }
  582. this.selectMusic = item
  583. if (item.join === 1) {
  584. if (this.rankingMethod === 'TOTAL_SCORE') {
  585. this.$router.push({
  586. path: '/track-song',
  587. query: {
  588. id: this.id,
  589. subjectId: item.subjectId,
  590. subjectName: item.subjectName
  591. }
  592. })
  593. } else {
  594. this.onOpenMusic()
  595. }
  596. } else {
  597. this.popupStatus = true
  598. }
  599. }}
  600. >
  601. 立刻挑战
  602. </Button>
  603. </div>
  604. </div>
  605. )
  606. }}
  607. ></Cell>
  608. </CellGroup>
  609. ))}
  610. </div>
  611. <div class={[styles.section]}>
  612. <div class={styles.title}>
  613. <img src={getAssetsHomeFile('icon_arrow_left.png')} />
  614. <span>活动规则</span>
  615. <img src={getAssetsHomeFile('icon_arrow_right.png')} />
  616. </div>
  617. <div class={styles.tips}>{this.activeInfo.ruleDescribe}</div>
  618. </div>
  619. </div>
  620. {this.activeInfo.join !== 1 && (
  621. <ColSticky position="bottom">
  622. <div class={styles.btnGroup}>
  623. <Button
  624. round
  625. class={styles.submit}
  626. block
  627. onClick={this.onJoinActve}
  628. >
  629. 报名参与
  630. </Button>
  631. </div>
  632. </ColSticky>
  633. )}
  634. <Popup
  635. v-model:show={this.popupStatus}
  636. round
  637. style={{ width: '90%' }}
  638. closeOnClickOverlay={false}
  639. >
  640. <div class={styles.popupContainer}>
  641. <div class={[styles.popupTitle, 'van-hairline--bottom']}>
  642. <i class={styles.line}></i>提示
  643. <img
  644. src={getAssetsHomeFile('icon_close.png')}
  645. class={styles.popupClose}
  646. onClick={() => (this.popupStatus = false)}
  647. />
  648. </div>
  649. <div class={styles.popupContent}>
  650. {browser().isApp ? (
  651. <>
  652. {this.rankingMethod === 'TOTAL_SCORE' ? (
  653. <>
  654. <p>
  655. 确定要参加 <span>{this.selectMusic.subjectName}</span>
  656. (声部)的比拼吗?
  657. </p>
  658. <p class={styles.popupTips}>
  659. 每位用户仅可选择一个声部参与挑战哦!
  660. </p>
  661. </>
  662. ) : (
  663. <>
  664. <p>
  665. 确定要参加<span>{this.selectMusic.musicSheetName}</span>
  666. 评测
  667. <span>
  668. {difficulty[this.activeInfo.evaluationDifficulty]}
  669. </span>
  670. 的比拼吗?
  671. </p>
  672. <p class={styles.popupTips}>
  673. 每位用户仅可选择一首曲目的一个难度哦!
  674. </p>
  675. </>
  676. )}
  677. </>
  678. ) : (
  679. <div class={styles.appOut}>
  680. <img src={logo} />
  681. <p>请在酷乐秀APP内打开活动链接!</p>
  682. </div>
  683. )}
  684. </div>
  685. {browser().isApp ? (
  686. <div class={['btnGroup, btnMore', styles.popupBtn]}>
  687. <Button
  688. type="primary"
  689. round
  690. plain
  691. onClick={() => (this.popupStatus = false)}
  692. >
  693. 再想想
  694. </Button>
  695. <Button type="primary" round onClick={this.onOpenMusic}>
  696. 就是它了
  697. </Button>
  698. </div>
  699. ) : (
  700. <div class={['btnGroup, btnMore', styles.popupBtn]}>
  701. <Button type="primary" round onClick={this.onOpenApp}>
  702. 确定
  703. </Button>
  704. </div>
  705. )}
  706. </div>
  707. </Popup>
  708. {this.wxStatus && (
  709. <div
  710. class={styles.wxpopup}
  711. onClick={() => {
  712. this.wxStatus = false
  713. }}
  714. >
  715. <img src={getAssetsHomeFile('wx_bg.png')} alt="" />
  716. </div>
  717. )}
  718. </div>
  719. )
  720. }
  721. })