index.tsx 20 KB

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