vip.tsx 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. import Calendar from '@/business-components/calendar'
  2. import request from '@/helpers/request'
  3. import { state } from '@/state'
  4. import dayjs from 'dayjs'
  5. import {
  6. ActionSheet,
  7. Button,
  8. Cell,
  9. CellGroup,
  10. Dialog,
  11. Stepper,
  12. Sticky,
  13. Tag,
  14. Popup,
  15. Toast,
  16. Icon
  17. } from 'vant'
  18. import { defineComponent } from 'vue'
  19. import { getWeekCh } from '@/helpers/utils'
  20. import styles from './practice.module.less'
  21. import { orderStatus } from '@/views/order-detail/orderStatus'
  22. import ColResult from '@/components/col-result'
  23. import { tradeOrder } from '@/student/trade/tradeOrder'
  24. import icon3 from '../images/icon3.png'
  25. import Tips from './tips'
  26. export default defineComponent({
  27. name: 'VIP_COURSE',
  28. props: {
  29. userInfo: {
  30. type: Object,
  31. default: {}
  32. }
  33. },
  34. data() {
  35. const query = this.$route.query
  36. return {
  37. teacherId: query.teacherId,
  38. subjectId: query.subjectId,
  39. teacherSubjectList: [],
  40. subjectStatus: false,
  41. subjectInfo: {
  42. freeMinutes: 0,
  43. subjectPrice: 0,
  44. courseMinutes: 0,
  45. id: null,
  46. subjectName: '',
  47. subjectId: 0
  48. },
  49. courseNum: 4,
  50. calendarStatus: false,
  51. calendarList: [] as any,
  52. selectCourseList: [] as any,
  53. coursePlanStatus: false,
  54. selectStatus: false,
  55. coursePlanList: [] as any,
  56. calendarDate: dayjs().add(1, 'day').toDate() as Date, // 日历当前时间
  57. settingStatus: true, // 是否设置VIP定制课
  58. loadDataStatus: true // 是否加载数据
  59. }
  60. },
  61. async mounted() {
  62. try {
  63. this.loadDataStatus = true
  64. const res = await request.get(
  65. '/api-student/courseSchedule/getTeacherSubjectPrice',
  66. {
  67. params: {
  68. teacherId: this.teacherId,
  69. groupType: 'VIP'
  70. }
  71. }
  72. )
  73. const result = res.data || []
  74. if (result.length > 0) {
  75. const userSubjectId = this.subjectId || state.user.data?.subjectId
  76. const findItem = result.find((item: any) => {
  77. return item.subjectId === Number(userSubjectId)
  78. })
  79. // 判断是否有跟学生相同的科目,如果没有则默认取第一个
  80. const tempRes = findItem || result[0]
  81. const { subjectName, subjectPrice, courseMinutes, subjectId, id, freeMinutes } = tempRes
  82. this.subjectInfo = {
  83. subjectPrice,
  84. freeMinutes,
  85. id,
  86. courseMinutes,
  87. subjectName,
  88. subjectId
  89. }
  90. result.forEach((item: any) => {
  91. item.name = item.subjectName
  92. })
  93. this.teacherSubjectList = result
  94. this.getList()
  95. this.onBuy(true)
  96. this.settingStatus = true
  97. // 判断如果是审核的则不显示
  98. const resVersion = await request.post('/api-teacher/open/appVersion', {
  99. data: {
  100. platform:
  101. state.platformType === 'STUDENT' ? 'ios-student' : 'ios-teacher',
  102. version: state.version
  103. }
  104. })
  105. this.settingStatus = resVersion.data.check ? false : true
  106. } else {
  107. this.settingStatus = false
  108. }
  109. this.loadDataStatus = false
  110. } catch {
  111. this.loadDataStatus = false
  112. }
  113. },
  114. computed: {
  115. showSelectList() {
  116. const arr: any = this.selectCourseList
  117. let list = [...arr]
  118. list.forEach((item: any) => {
  119. item.title =
  120. dayjs(item.startTime).format('YYYY-MM-DD') +
  121. ' ' +
  122. getWeekCh(dayjs(item.startTime).day()) +
  123. ' ' +
  124. item.start +
  125. '~' +
  126. item.end
  127. })
  128. return list
  129. },
  130. selectType() {
  131. // 循环次数是否足够
  132. return this.selectCourseList.length < this.courseNum
  133. ? 'noEnough'
  134. : 'enough'
  135. }
  136. },
  137. methods: {
  138. async onSubmit() {
  139. if (this.selectCourseList.length <= 0) {
  140. Toast('请选择课程时间')
  141. return
  142. }
  143. if (this.selectCourseList.length < this.courseNum) {
  144. this.selectStatus = true
  145. return
  146. }
  147. await this._lookCourse()
  148. },
  149. async getList(date?: Date) {
  150. try {
  151. const tempDate = date || dayjs().add(1, 'day').toDate()
  152. let params = {
  153. day: dayjs(tempDate).format('DD'),
  154. month: dayjs(tempDate).format('MM'),
  155. year: dayjs(tempDate).format('YYYY')
  156. }
  157. let res = await request.post(
  158. '/api-student/courseSchedule/createPracticeCourseCalendar',
  159. {
  160. data: {
  161. ...params,
  162. teacherSubjectPriceId: this.subjectInfo.id,
  163. studentId: state.user.data?.userId,
  164. teacherId: this.teacherId
  165. }
  166. }
  167. )
  168. const result = res.data || []
  169. let tempObj = {}
  170. result.forEach((item: any) => {
  171. tempObj[item.date] = item
  172. })
  173. this.calendarList = tempObj
  174. this.calendarStatus = result.length > 0
  175. } catch {}
  176. },
  177. onSelectDay(obj: any) {
  178. const result = obj || []
  179. let list = [...this.selectCourseList] as any
  180. result.forEach((item: any) => {
  181. const isExist = list.some(
  182. (course: any) => course.startTime === item.startTime
  183. )
  184. !isExist && list.push({ ...item })
  185. })
  186. // 去掉不在
  187. let tempList: any[] = []
  188. list.forEach((item: any) => {
  189. const isExist = result.some(
  190. (course: any) => course.startTime === item.startTime
  191. )
  192. isExist && tempList.push(item)
  193. })
  194. // 对数组进行排序
  195. tempList.sort((first: any, second: any) => {
  196. if (first.startTime > second.startTime) return 1
  197. if (first.startTime < second.startTime) return -1
  198. return 0
  199. })
  200. console.log(tempList, 'list')
  201. this.selectCourseList = [...tempList] as any
  202. },
  203. onCloseTag(item: any) {
  204. Dialog.confirm({
  205. title: '提示',
  206. message: '您是否要删除该选择的课程?',
  207. confirmButtonColor: 'var(--van-primary)'
  208. }).then(() => {
  209. const index = this.selectCourseList.findIndex(
  210. (course: any) => course.startTime === item.startTime
  211. )
  212. this.selectCourseList.splice(index, 1)
  213. })
  214. },
  215. async _lookCourse(callBack?: Function) {
  216. try {
  217. let times = [] as any
  218. this.selectCourseList.forEach((item: any) => {
  219. times.push({
  220. startTime: item.startTime,
  221. endTime: item.endTime
  222. })
  223. })
  224. const res = await request.post(
  225. '/api-student/courseGroup/lockCourseToCache',
  226. {
  227. data: {
  228. courseFreeMinutes: this.subjectInfo.freeMinutes,
  229. courseNum: this.courseNum,
  230. courseType: 'VIP',
  231. loop: this.selectType === 'noEnough' ? 1 : 0,
  232. teacherId: this.teacherId,
  233. timeList: [...times]
  234. }
  235. }
  236. )
  237. const result = res.data || []
  238. result.forEach((item: any, index: number) => {
  239. this.coursePlanList[index] = {
  240. ...this.coursePlanList[index],
  241. startTime: item.startTime,
  242. endTime: item.endTime,
  243. classNum: index + 1
  244. }
  245. })
  246. this.coursePlanStatus = true
  247. this.selectStatus = true
  248. callBack && callBack()
  249. } catch (e: any) {
  250. // 报错时需要重置日历表的数据
  251. const message = e.message
  252. Dialog.alert({
  253. title: '提示',
  254. confirmButtonColor: 'var(--van-primary)',
  255. message
  256. }).then(() => {
  257. this.getList(this.calendarDate || new Date())
  258. this.selectCourseList = []
  259. this.selectStatus = false
  260. })
  261. }
  262. },
  263. async onReset() {
  264. // 是否有锁课状态 或 是锁课类型的
  265. if (this.coursePlanStatus || this.selectType === 'enough') {
  266. this.selectStatus = false
  267. setTimeout(() => {
  268. this.coursePlanList = []
  269. }, 500)
  270. } else if (this.selectType === 'noEnough') {
  271. this.selectStatus = false
  272. }
  273. setTimeout(() => {
  274. this.coursePlanStatus = false
  275. }, 500)
  276. },
  277. async onSure() {
  278. const status = this.coursePlanStatus
  279. await this._lookCourse(() => {
  280. if (status) {
  281. this.selectStatus = false
  282. this.onBuy()
  283. }
  284. })
  285. },
  286. async onBuy(goTo?: boolean) {
  287. try {
  288. const res = await request.post(
  289. '/api-student/userOrder/getPendingOrder',
  290. {
  291. data: {
  292. goodType: 'VIP_COURSE',
  293. bizId: this.teacherId
  294. }
  295. }
  296. )
  297. const subjectInfo = this.subjectInfo
  298. const tempCourseList = [...this.coursePlanList]
  299. // console.log(this.coursePlanList)
  300. tempCourseList.forEach((item: any) => {
  301. item.classDate = dayjs(item.startTime).format('YYYY-MM-DD')
  302. item.title = `${dayjs(item.startTime).format(
  303. 'YYYY-MM-DD'
  304. )} ${getWeekCh(dayjs(item.startTime).day())} ${dayjs(
  305. item.startTime
  306. ).format('HH:mm')}~${dayjs(item.endTime).format('HH:mm')}`
  307. })
  308. orderStatus.orderObject.orderType = 'VIP_COURSE'
  309. orderStatus.orderObject.orderName = subjectInfo.subjectName + 'VIP定制课'
  310. orderStatus.orderObject.orderDesc = subjectInfo.subjectName + 'VIP定制课'
  311. orderStatus.orderObject.actualPrice = Number(
  312. (this.courseNum * subjectInfo.subjectPrice).toFixed(2)
  313. )
  314. orderStatus.orderObject.orderNo = ''
  315. orderStatus.orderObject.orderList = [
  316. {
  317. orderType: 'VIP_COURSE',
  318. goodsName: subjectInfo.subjectName + 'VIP定制课',
  319. courseGroupName: subjectInfo.subjectName + 'VIP定制课',
  320. courseIntroduce: subjectInfo.subjectName + 'VIP定制课',
  321. subjectId: subjectInfo.subjectId,
  322. singleCourseMinutes: subjectInfo.courseMinutes,
  323. teacherSubjectPriceId: subjectInfo.id,
  324. courseNum: this.courseNum,
  325. coursePrice: (this.courseNum * subjectInfo.subjectPrice).toFixed(2),
  326. price: (this.courseNum * subjectInfo.subjectPrice).toFixed(2),
  327. teacherName:
  328. this.userInfo.username || `游客${this.userInfo.userId || ''}`,
  329. teacherId: this.userInfo.userId,
  330. starGrade: this.userInfo.starGrade,
  331. avatar: this.userInfo.heardUrl,
  332. classTime: tempCourseList
  333. }
  334. ]
  335. const result = res.data
  336. if (result) {
  337. Dialog.confirm({
  338. title: '提示',
  339. message: '您有一个未支付的订单,是否继续支付?',
  340. confirmButtonColor: '#269a93',
  341. cancelButtonText: '取消订单',
  342. confirmButtonText: '继续支付'
  343. })
  344. .then(async () => {
  345. tradeOrder(result, this.routerTo)
  346. // this.routerTo()
  347. })
  348. .catch(() => {
  349. Dialog.close()
  350. // 只用取消订单,不用做其它处理
  351. this.cancelPayment(result.orderNo)
  352. })
  353. } else {
  354. !goTo && this.routerTo()
  355. }
  356. } catch {}
  357. },
  358. routerTo() {
  359. this.$router.push({
  360. path: '/orderDetail',
  361. query: {
  362. orderType: 'VIP_COURSE'
  363. }
  364. })
  365. },
  366. async cancelPayment(orderNo: string) {
  367. try {
  368. await request.post('/api-student/userOrder/orderCancel', {
  369. data: {
  370. orderNo
  371. }
  372. })
  373. // this.routerTo()
  374. } catch {}
  375. }
  376. },
  377. render() {
  378. return (
  379. <>
  380. <div class={styles.tipSection}><Tips type="LIVE_COURSE" title='什么是VIP定制课?' content='VIP定制课程采用一对一专属授课模式,每节课时长为45分钟。课程内容根据学生的具体需求量身打造,旨在全面提升学生的个人技能与表现。不论是希望在乐器演奏技巧上取得突破,如提高指法精准度、气息控制能力或节奏掌握等;还是为即将到来的重要活动、比赛或考级做充分准备,我们都能提供高度匹配的教学方案。此外,教学进度将根据每位学员的学习吸收情况灵活调整,确保每个人都能在最适合自己的节奏中稳步前进,扎实提升个人能力。' /></div>
  381. {!this.loadDataStatus &&
  382. (this.settingStatus ? (
  383. <>
  384. <div class={styles.practice}>
  385. <CellGroup class={styles.group} border={false}>
  386. <Cell
  387. title="选择专业"
  388. isLink
  389. value={this.subjectInfo.subjectName}
  390. onClick={() => (this.subjectStatus = true)}
  391. />
  392. {this.subjectInfo.subjectPrice > 0 && (
  393. <Cell
  394. title="VIP定制课收费"
  395. v-slots={{
  396. default: () => (
  397. <div class={styles.price}>
  398. <span>
  399. ¥
  400. {(this as any).$filters.moneyFormat(
  401. this.subjectInfo.subjectPrice
  402. )}
  403. </span>
  404. /{this.subjectInfo.courseMinutes}分钟
  405. </div>
  406. )
  407. }}
  408. />
  409. )}
  410. <Cell
  411. title="课时数"
  412. v-slots={{
  413. default: () => (
  414. <Stepper
  415. v-model={this.courseNum}
  416. theme="round"
  417. longPress={false}
  418. max={12}
  419. min={4}
  420. buttonSize={22}
  421. onChange={() => {
  422. this.selectCourseList = []
  423. }}
  424. />
  425. )
  426. }}
  427. />
  428. </CellGroup>
  429. {this.calendarStatus && (
  430. <div class={styles.group}>
  431. <Calendar
  432. selectList={this.selectCourseList}
  433. list={this.calendarList}
  434. maxDays={this.courseNum}
  435. nextMonth={(date: Date) => this.getList(date)}
  436. prevMonth={(date: Date) => this.getList(date)}
  437. selectDay={this.onSelectDay}
  438. v-model:calendarDate={this.calendarDate}
  439. />
  440. </div>
  441. )}
  442. {this.showSelectList.length > 0 && <Cell
  443. class={[styles.arrangeCell]}
  444. v-slots={{
  445. title: () => (
  446. <div class={styles.rTitle}>
  447. <span>已选择课程时间</span>
  448. </div>
  449. ),
  450. label: () => (
  451. <div class={styles.rTag}>
  452. {this.showSelectList.map((item: any) => (
  453. <>
  454. <Tag
  455. plain
  456. round
  457. closeable
  458. size="large"
  459. type="primary"
  460. class={styles.tag}
  461. onClose={() => this.onCloseTag(item)}
  462. >
  463. {item.title}
  464. </Tag>
  465. <br />
  466. </>
  467. ))}
  468. </div>
  469. )
  470. }}
  471. ></Cell>}
  472. <Popup show={this.selectStatus} class={styles.selectPopup}>
  473. <div class={styles.selectContainer}>
  474. <div class={styles.rTitle}>
  475. <span>提示</span>
  476. </div>
  477. <div class={styles.selectPopupContent}>
  478. <p class={styles.desc}>
  479. {this.selectType === 'noEnough' &&
  480. !this.coursePlanStatus
  481. ? '您所选择的上课时间未达到您输入的课时数,系统根据已选时间将自动按周顺延排课。'
  482. : '您已选择以下上课时间段,时间段会暂时锁定,锁定期间学员不可购买该时间段课程。'}
  483. </p>
  484. {this.coursePlanList &&
  485. this.coursePlanList.length > 0 &&
  486. this.coursePlanStatus && (
  487. <p class={styles.times}>
  488. {this.coursePlanList.map((item: any) => (
  489. <span>
  490. {dayjs(item.startTime || new Date()).format(
  491. 'YYYY-MM-DD'
  492. )}{' '}
  493. {dayjs(item.startTime || new Date()).format(
  494. 'HH:mm'
  495. )}
  496. ~
  497. {dayjs(item.endTime || new Date()).format(
  498. 'HH:mm'
  499. )}
  500. </span>
  501. ))}
  502. </p>
  503. )}
  504. </div>
  505. <div class={styles.selectBtn}>
  506. <Button
  507. class={styles.btn}
  508. type="primary"
  509. round
  510. block
  511. plain
  512. onClick={this.onReset}
  513. >
  514. {this.selectType === 'noEnough'
  515. ? '继续选择'
  516. : '重新选择'}
  517. </Button>
  518. <Button
  519. class={styles.btn}
  520. type="primary"
  521. round
  522. block
  523. onClick={this.onSure}
  524. >
  525. 确认
  526. </Button>
  527. </div>
  528. </div>
  529. </Popup>
  530. <ActionSheet
  531. show={this.subjectStatus}
  532. actions={this.teacherSubjectList}
  533. cancelText="取消"
  534. closeOnClickAction
  535. onCancel={() => (this.subjectStatus = false)}
  536. onSelect={(item: any) => {
  537. const {
  538. subjectName,
  539. subjectPrice,
  540. courseMinutes,
  541. id,
  542. subjectId,
  543. freeMinutes
  544. } = item
  545. this.subjectInfo = {
  546. subjectPrice,
  547. freeMinutes,
  548. id,
  549. courseMinutes,
  550. subjectName,
  551. subjectId
  552. }
  553. this.subjectStatus = false
  554. }}
  555. />
  556. </div>
  557. <div
  558. class={['btnGroup', styles.fixedBtn]}
  559. style={{ background: '#fff', paddingTop: '10px' }}
  560. >
  561. <Button block round type="primary" onClick={this.onSubmit}>
  562. 确认约课
  563. </Button>
  564. </div>
  565. </>
  566. ) : (
  567. <ColResult
  568. btnStatus={false}
  569. // type="emptyBook"
  570. classImgSize="SMALL"
  571. tips="老师暂未开放VIP定制课"
  572. />
  573. ))}
  574. </>
  575. )
  576. }
  577. })