index.tsx 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  1. import {
  2. Image,
  3. Cell,
  4. CellGroup,
  5. Tag,
  6. Button,
  7. Stepper,
  8. Icon,
  9. Popup,
  10. showConfirmDialog,
  11. showToast
  12. } from 'vant';
  13. import { computed, defineComponent, onMounted, reactive } from 'vue';
  14. import styles from './index.module.less';
  15. import iconGift from './images/icon-gift.png';
  16. import shopEmpty from './images/shop-empty.png';
  17. import iconDateMember from './images/icon-date-member.png';
  18. import MSticky from '@/components/m-sticky';
  19. import MVideo from '@/components/m-video';
  20. import { useRoute, useRouter } from 'vue-router';
  21. import RegisterModal from './register-modal';
  22. import { useStudentRegisterStore } from '@/store/modules/student-register-store';
  23. import request from '@/helpers/request';
  24. import { moneyFormat } from '@/helpers/utils';
  25. import deepClone from '@/helpers/deep-clone';
  26. import { storage } from '@/helpers/storage';
  27. import { ACCESS_TOKEN } from '@/store/mutation-types';
  28. import OWxTip from '@/components/m-wx-tip';
  29. import MDialog from '@/components/m-dialog';
  30. import { CurrentTime, useCountDown } from '@vant/use';
  31. export default defineComponent({
  32. name: 'student-register',
  33. setup() {
  34. const route = useRoute();
  35. const studentRegisterStore = useStudentRegisterStore();
  36. const router = useRouter();
  37. // 初始化学校编号
  38. studentRegisterStore.setShoolId(route.query.sId as any);
  39. const forms = reactive({
  40. schoolId: route.query.sId as any,
  41. paymentType: '', // 支付类型
  42. popupShow: false,
  43. popupRegister: false,
  44. details: [] as any[],
  45. schoolType: '', // 学校类型
  46. gradeYear: '', // 学制
  47. bugGoods: false, // 是否购买AI
  48. submitLoading: false,
  49. dialogStatus: false,
  50. dialogMessage: '',
  51. countDownTime: 60 * 1000,
  52. dialogConfig: {} as any,
  53. showMore: true
  54. });
  55. const countDown = useCountDown({
  56. // 倒计时 60 秒
  57. time: forms.countDownTime,
  58. onChange(current: CurrentTime) {
  59. forms.dialogMessage = `有待支付订单,请在${Math.ceil(
  60. current.total / 1000
  61. )}s后重试`;
  62. },
  63. onFinish() {
  64. forms.dialogStatus = false;
  65. }
  66. });
  67. // 查询未支付订单
  68. const paymentOrderUnpaid = async () => {
  69. let result = false;
  70. try {
  71. const { data } = await request.get('/edu-app/userPaymentOrder/unpaid');
  72. // 判断是否有待支付订单
  73. if (!data.id) return false;
  74. // 判断是否可以取消订单
  75. if (data.cancelPayment) {
  76. await request.post(
  77. '/edu-app/userPaymentOrder/cancelPayment/' + data.orderNo
  78. );
  79. return false;
  80. } else {
  81. forms.countDownTime = data.cancelTimes;
  82. countDown.reset(Number(data.cancelTimes));
  83. countDown.start();
  84. forms.dialogMessage = `有待支付订单,请在${Math.ceil(
  85. countDown.current.value.total / 1000
  86. )}s后重试`;
  87. forms.dialogStatus = true;
  88. forms.dialogConfig = data;
  89. result = true;
  90. }
  91. } catch {
  92. //
  93. }
  94. return result;
  95. };
  96. const getRegisterGoods = async () => {
  97. try {
  98. const { data } = await request.get(
  99. '/edu-app/open/userOrder/registerGoods/' + forms.schoolId,
  100. {
  101. noAuthorization: true // 是否请求接口的时候添加toekn
  102. }
  103. );
  104. // 默认选中商品
  105. studentRegisterStore.setVip(data.details || []);
  106. forms.details = deepClone(data.details || []);
  107. forms.bugGoods = data.bugGoods;
  108. forms.schoolType = data.schoolType;
  109. forms.gradeYear = data.gradeYear;
  110. } catch {}
  111. };
  112. // 计算金额
  113. const calcPrice = computed(() => {
  114. let amount: number = 0; //现价
  115. let originAmount: number = 0; // 原价
  116. const vipList: any[] = studentRegisterStore.getVip;
  117. vipList.forEach((vip: any) => {
  118. amount += Number(vip.currentPrice);
  119. originAmount += Number(vip.originalPrice);
  120. });
  121. const goodsList: any[] = studentRegisterStore.getGoods;
  122. goodsList.forEach((good: any) => {
  123. amount += Number(good.price) * good.quantity;
  124. originAmount += Number(good.originalPrice) * good.quantity;
  125. });
  126. return {
  127. amount,
  128. originAmount
  129. };
  130. });
  131. // 删除商品
  132. const onGoodsRemove = (item: any) => {
  133. showConfirmDialog({
  134. message: '是否删除该商品',
  135. confirmButtonColor: '#FF8633'
  136. }).then(() => {
  137. studentRegisterStore.deleteGoods(item.productSkuId);
  138. });
  139. };
  140. // 登记成功之后购买
  141. const onRegisterSubmit = async () => {
  142. try {
  143. forms.submitLoading = true;
  144. // 检测token是否失效
  145. const Authorization = storage.get(ACCESS_TOKEN) || '';
  146. const authInfo = await request.post('/edu-app/open/user/verification', {
  147. noAuthorization: true,
  148. data: { token: Authorization }
  149. });
  150. // 判断当前token是否失效
  151. if (!authInfo.data) {
  152. storage.remove(ACCESS_TOKEN);
  153. studentRegisterStore.deleteToken();
  154. forms.popupRegister = true;
  155. return;
  156. }
  157. // 请求是否有待支付订单,如果有则自动关闭
  158. const status = await paymentOrderUnpaid();
  159. if (status) return;
  160. const schoolInfo = await request.get(
  161. '/edu-app/userPaymentOrder/registerStatus/' + forms.schoolId
  162. );
  163. const vipList = studentRegisterStore.getVip;
  164. const goodsList = studentRegisterStore.getGoods;
  165. if (schoolInfo.data.hasBuyCourse && vipList.length > 0) {
  166. setTimeout(() => {
  167. showToast('您已购买乐器AI学练工具,请勿重复购买');
  168. }, 100);
  169. return;
  170. }
  171. const params: any[] = [];
  172. vipList.forEach((vip: any) => {
  173. params.push({
  174. goodsId: vip.goodsId,
  175. goodsNum: 1,
  176. goodsType: vip.goodsType,
  177. paymentCashAmount: vip.currentPrice, // 现金支付金额
  178. paymentCouponAmount: 0 // 优惠券金额
  179. });
  180. });
  181. goodsList.forEach((goods: any) => {
  182. params.push({
  183. goodsId: goods.productId,
  184. goodsNum: goods.quantity,
  185. goodsType: 'INSTRUMENTS',
  186. paymentCashAmount: goods.price, // 现金支付金额
  187. paymentCouponAmount: 0, // 优惠券金额
  188. goodsSkuId: goods.productSkuId
  189. });
  190. });
  191. // 创建订单
  192. const { data } = await request.post(
  193. '/edu-app/userPaymentOrder/executeOrder',
  194. {
  195. hideLoading: false,
  196. data: {
  197. paymentType: forms.paymentType,
  198. bizId: forms.schoolId, // 乐团编号
  199. orderType: 'SCHOOL_REGISTER',
  200. paymentCashAmount: calcPrice.value.amount || 0,
  201. paymentCouponAmount: 0,
  202. goodsInfos: params,
  203. orderName: '学生登记',
  204. orderDesc: '学生登记'
  205. }
  206. }
  207. );
  208. router.push({
  209. path: '/order-detail',
  210. query: {
  211. pm: 1, // h5乐团报名
  212. config: JSON.stringify({
  213. ...data.paymentConfig,
  214. paymentType: data.paymentType
  215. }),
  216. orderNo: data.orderNo
  217. }
  218. });
  219. } finally {
  220. forms.submitLoading = false;
  221. }
  222. };
  223. onMounted(async () => {
  224. try {
  225. // 获取支付类型
  226. const { data } = await request.get(
  227. '/edu-app/open/paramConfig/queryByParamName',
  228. {
  229. requestType: 'form',
  230. params: {
  231. paramName: 'payment_service_provider'
  232. }
  233. }
  234. );
  235. if (data.id) {
  236. forms.paymentType = data.paramValue || '';
  237. }
  238. getRegisterGoods();
  239. } catch {}
  240. });
  241. return () => (
  242. <div class={styles['student-register']}>
  243. <div class={styles.studentSection} style={{ marginTop: '18px' }}>
  244. <div class={styles.titleTool}></div>
  245. {forms.details.map((item: any) => (
  246. <CellGroup
  247. class={styles.goodsSection}
  248. onClick={() => {
  249. if (studentRegisterStore.selectedVip(item.goodsId)) {
  250. studentRegisterStore.deleteVip(item.goodsId);
  251. } else {
  252. studentRegisterStore.setVip([item]);
  253. }
  254. }}>
  255. <Cell border={false} class={styles.goodsCell}>
  256. {{
  257. icon: () => <Image class={styles.img} src={item.goodsUrl} />,
  258. title: () => (
  259. <div class={styles.section}>
  260. <div class={styles.sectionContent}>
  261. <h2>
  262. {/* {item.goodsName} */}
  263. {/* <Tag class={styles.brandName}>12个月</Tag> */}
  264. <img
  265. src={iconDateMember}
  266. class={styles.iconDateMember}
  267. />
  268. </h2>
  269. <p
  270. class={[
  271. styles.model,
  272. forms.showMore ? styles.more : ''
  273. ]}>
  274. {item.description}
  275. </p>
  276. {item.description &&
  277. item.description.length >= 36 &&
  278. forms.showMore ? (
  279. <span
  280. class={styles.moreBtn}
  281. onClick={(e: MouseEvent) => {
  282. e.stopPropagation();
  283. forms.showMore = false;
  284. }}>
  285. 查看更多
  286. <Icon name="arrow-down" color="#aaa" />
  287. </span>
  288. ) : (
  289. ''
  290. )}
  291. {/* <div class={styles.sbtnGroup}>
  292. <span
  293. class={styles.btnDetail}
  294. onClick={(e: MouseEvent) => {
  295. e.stopPropagation();
  296. router.push('/student-digital-tools');
  297. }}>
  298. 查看详情
  299. </span>
  300. <span
  301. class={styles.btnVideo}
  302. onClick={(e: MouseEvent) => {
  303. e.stopPropagation();
  304. forms.popupShow = true;
  305. }}>
  306. 介绍视频
  307. </span>
  308. </div> */}
  309. </div>
  310. <i
  311. class={
  312. studentRegisterStore.selectedVip(item.goodsId)
  313. ? styles.selected
  314. : styles.noSelected
  315. }></i>
  316. </div>
  317. )
  318. }}
  319. </Cell>
  320. <Cell border={false} class={styles.priceCell}>
  321. {{
  322. title: () => (
  323. <div class={styles.sPriceGroup}>
  324. <div class={styles.tg}>
  325. 团购价:
  326. <span>
  327. <i>¥ </i>
  328. {moneyFormat(item.currentPrice)}
  329. </span>
  330. </div>
  331. {item.currentPrice < item.originalPrice && (
  332. <del>¥{moneyFormat(item.originalPrice)}</del>
  333. )}
  334. </div>
  335. )
  336. }}
  337. </Cell>
  338. {item.membershipDays > 0 && (
  339. <Cell border={false} class={styles.giftCell}>
  340. {{
  341. title: () => (
  342. <div class={styles.gift}>
  343. <img src={iconGift} class={styles.iconGift} />
  344. 现在购买赠送 <span>{item.membershipDays || 0}</span>
  345. 天有效期
  346. </div>
  347. )
  348. }}
  349. </Cell>
  350. )}
  351. </CellGroup>
  352. ))}
  353. </div>
  354. {forms.bugGoods && (
  355. <>
  356. <div class={styles.studentSection}>
  357. <div class={styles.titleBuy}></div>
  358. {studentRegisterStore.getGoods &&
  359. studentRegisterStore.getGoods.length <= 0 ? (
  360. <div class={styles.goodsEmpty}>
  361. <img src={shopEmpty} class={styles.shopImg} />
  362. <div class={styles.goodsContainer}>
  363. <h2>
  364. 为你的<span>音乐之旅</span>做好准备
  365. </h2>
  366. <p class={styles.tips}>快去选购乐器吧~</p>
  367. <Button
  368. class={styles.goSelect}
  369. type="primary"
  370. onClick={() => {
  371. router.push('/goods-list');
  372. }}>
  373. 进入商城选购
  374. <Icon name="arrow" />
  375. </Button>
  376. </div>
  377. </div>
  378. ) : (
  379. studentRegisterStore.getGoods.map((goods: any) => (
  380. <CellGroup class={styles.goodsSection}>
  381. <Cell border={false} class={styles.goodsCell}>
  382. {{
  383. icon: () => (
  384. <Image class={styles.img} src={goods.pic} />
  385. ),
  386. title: () => (
  387. <div class={styles.section}>
  388. <div class={styles.sectionContent}>
  389. <h2>
  390. {goods.name}
  391. <Tag class={styles.brandName}>
  392. {goods.brandName}
  393. </Tag>
  394. </h2>
  395. <p class={[styles.model]}>
  396. 规格:{goods.spDataJson}
  397. </p>
  398. <p class={[styles.model]}>{goods.productSn}</p>
  399. <Stepper
  400. min={1}
  401. max={99}
  402. v-model={goods.quantity}
  403. disableInput
  404. />
  405. </div>
  406. <i
  407. class={styles.delete}
  408. onClick={() => onGoodsRemove(goods)}></i>
  409. </div>
  410. )
  411. }}
  412. </Cell>
  413. <Cell border={false} class={styles.priceCell}>
  414. {{
  415. title: () => (
  416. <div class={styles.sPriceGroup}>
  417. <div class={styles.tg}>
  418. 团购价:
  419. <span>
  420. <i>¥ </i>
  421. {moneyFormat(goods.price)}
  422. </span>
  423. </div>
  424. {goods.price < goods.originalPrice && (
  425. <del>¥{moneyFormat(goods.originalPrice)}</del>
  426. )}
  427. </div>
  428. )
  429. }}
  430. </Cell>
  431. </CellGroup>
  432. ))
  433. )}
  434. </div>
  435. {studentRegisterStore.getGoods &&
  436. studentRegisterStore.getGoods.length > 0 && (
  437. <Button
  438. class={styles.addButton}
  439. block
  440. onClick={() => {
  441. router.push('/goods-list');
  442. }}>
  443. <Icon name="add-o" />
  444. 进入商城选购
  445. </Button>
  446. )}
  447. </>
  448. )}
  449. <MSticky position="bottom">
  450. <div class={styles.paymentContainer}>
  451. <div class={styles.payemntPrice}>
  452. <span class={styles.needPrice}>
  453. <i style="font-style: normal">¥ </i>
  454. <span>{moneyFormat(calcPrice.value.amount)}</span>
  455. </span>
  456. {calcPrice.value.originAmount > calcPrice.value.amount ? (
  457. <del class={styles.allPrice}>
  458. ¥ {moneyFormat(calcPrice.value.originAmount)}
  459. </del>
  460. ) : (
  461. ''
  462. )}
  463. </div>
  464. <div
  465. class={styles.paymentBtn}
  466. onClick={() => {
  467. const vipList = studentRegisterStore.getVip;
  468. const goodsList = studentRegisterStore.getGoods;
  469. if (vipList.length <= 0 && goodsList.length <= 0) {
  470. setTimeout(() => {
  471. showToast('请选择需要购买的商品');
  472. }, 100);
  473. return;
  474. }
  475. if (!studentRegisterStore.getToken) {
  476. forms.popupRegister = true;
  477. } else {
  478. onRegisterSubmit();
  479. }
  480. }}>
  481. <Button
  482. disabled={forms.submitLoading}
  483. loading={forms.submitLoading}>
  484. 确认购买
  485. </Button>
  486. </div>
  487. </div>
  488. </MSticky>
  489. <Popup v-model:show={forms.popupShow} class={styles.videoPopup}>
  490. {forms.popupShow && (
  491. <MVideo
  492. src={'https://daya.ks3-cn-beijing.ksyun.com/202105/SWmqmvW.mp4'}
  493. />
  494. )}
  495. </Popup>
  496. <Popup
  497. v-model:show={forms.popupRegister}
  498. class={styles.registerPopup}
  499. position="bottom"
  500. round>
  501. <RegisterModal
  502. schoolId={forms.schoolId}
  503. schoolType={forms.schoolType}
  504. gradeYear={forms.gradeYear}
  505. onClose={() => (forms.popupRegister = false)}
  506. onSubmit={onRegisterSubmit}
  507. />
  508. </Popup>
  509. <MDialog
  510. title="提示"
  511. v-model:show={forms.dialogStatus}
  512. message={forms.dialogMessage}
  513. allowHtml={true}
  514. confirmButtonText="继续支付"
  515. onConfirm={() => {
  516. const paymentConfig = forms.dialogConfig.paymentConfig;
  517. router.push({
  518. path: '/order-detail',
  519. query: {
  520. pm: 1, // h5乐团报名
  521. config: JSON.stringify(paymentConfig.paymentConfig),
  522. orderNo: paymentConfig.orderNo
  523. }
  524. });
  525. countDown.pause();
  526. }}
  527. onCancel={(val: any) => {
  528. countDown.pause();
  529. }}
  530. />
  531. {/* 是否在微信中打开 */}
  532. <OWxTip />
  533. </div>
  534. );
  535. }
  536. });