practice.tsx 20 KB

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