index.tsx 18 KB

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