payment.tsx 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  1. import OSticky from '@/components/o-sticky'
  2. import {
  3. Button,
  4. Cell,
  5. CellGroup,
  6. Checkbox,
  7. CheckboxGroup,
  8. Icon,
  9. Image,
  10. showConfirmDialog,
  11. showToast,
  12. Tag
  13. } from 'vant'
  14. import { defineComponent, nextTick, onMounted, reactive, ref } from 'vue'
  15. import styles from '../index.module.less'
  16. import radioCheck from '@/common/images/icon-radio-check.png'
  17. import radioDefault from '@/common/images/icon-radio-default.png'
  18. import iconGives from '../images/icon-gives.png'
  19. import { useRoute, useRouter } from 'vue-router'
  20. import request from '@/helpers/request'
  21. import { moneyFormat } from '@/helpers/utils'
  22. import { CountUp } from 'countup.js'
  23. import OPopup from '@/components/o-popup'
  24. import MemberBao from '../../member-bao'
  25. import GoodsDetail from '../../goods-detail'
  26. export default defineComponent({
  27. name: 'payment',
  28. emits: ['next'],
  29. setup() {
  30. const route = useRoute()
  31. const router = useRouter()
  32. const state = reactive({
  33. check: [] as any, // 选中的数据
  34. checkboxRefs: [] as any,
  35. details: [] as any, //
  36. goodsInfo: {} as any, // 商品
  37. textBookInfo: {} as any, // 教材
  38. repaireInfo: {} as any, // 乐器保养
  39. vipInfo: {} as any, // 团练宝
  40. paymentOrderDetails: [] as any, // 购买状态
  41. orderInfo: {
  42. needPrice: 0,
  43. originalPrice: 0
  44. },
  45. memberBaoStatus: false, // 团练宝详情状态
  46. goodsStatus: false, //
  47. selectGoodsId: null as any
  48. })
  49. // 查询未支付订单
  50. const paymentOrderUnpaid = async () => {
  51. try {
  52. const { data } = await request.get('/api-student/userPaymentOrder/unpaid')
  53. // 判断是否有待支付订单
  54. if (data.id) {
  55. showConfirmDialog({
  56. message: '您有待支付的订单,是否继续支付',
  57. cancelButtonText: '取消订单',
  58. confirmButtonText: '继续支付'
  59. })
  60. .then(() => {
  61. const paymentConfig = data.paymentConfig
  62. router.push({
  63. path: '/orderDetail',
  64. query: {
  65. pm: 1, // h5乐团报名
  66. config: JSON.stringify(paymentConfig.paymentConfig),
  67. orderNo: paymentConfig.orderNo
  68. }
  69. })
  70. })
  71. .catch(async () => {
  72. try {
  73. await request.post('/api-student/userPaymentOrder/cancelPayment/' + data.orderNo)
  74. } catch {
  75. //
  76. }
  77. })
  78. }
  79. } catch {
  80. //
  81. }
  82. }
  83. // 获取商品信息
  84. const registerGoods = async () => {
  85. try {
  86. const { data } = await request.get(
  87. '/api-student/orchestraRegister/registerGoods/' + route.query.id
  88. )
  89. // 获取已经购买商品信息
  90. const paymentOrderDetails = data.paymentOrderDetails || []
  91. paymentOrderDetails.forEach((item: any) => {
  92. state.paymentOrderDetails.push(item.goodsType)
  93. })
  94. // 初始化数据商品数据
  95. const details = data.details || []
  96. details.forEach((item: any) => {
  97. if (item.goodsType === 'INSTRUMENTS') {
  98. const img = item.goodsUrl ? item.goodsUrl.split(',')[0] : ''
  99. state.goodsInfo = { ...item, goodsUrl: img }
  100. } else if (item.goodsType === 'TEXTBOOK') {
  101. const img = item.goodsUrl ? item.goodsUrl.split(',')[0] : ''
  102. state.textBookInfo = { ...item, goodsUrl: img }
  103. } else if (item.goodsType === 'REPAIR') {
  104. state.repaireInfo = { ...item }
  105. } else if (item.goodsType === 'VIP') {
  106. state.vipInfo = { ...item }
  107. }
  108. state.details = details
  109. // 默认选中所有的
  110. if (!state.paymentOrderDetails.includes(item.goodsType) && item.goodsType !== 'REPAIR') {
  111. state.check.push(item.goodsId)
  112. }
  113. })
  114. calcPrice()
  115. } catch {
  116. //
  117. }
  118. }
  119. const onSelect = (type: string) => {
  120. state.checkboxRefs[type].toggle()
  121. calcPrice()
  122. }
  123. // 初始化金额
  124. const calcPrice = () => {
  125. const details = state.details
  126. const tempPrice = {
  127. needPrice: 0, //需要支付金额
  128. originalPrice: 0 // 原价
  129. }
  130. details.forEach((item: any) => {
  131. // 是否选中
  132. if (
  133. state.check.includes(item.goodsId) &&
  134. !state.paymentOrderDetails.includes(item.goodsType)
  135. ) {
  136. tempPrice.needPrice += parseFloat(item.currentPrice || 0)
  137. tempPrice.originalPrice += parseFloat(item.originalPrice || 0)
  138. }
  139. })
  140. state.orderInfo = tempPrice
  141. initNumCountUp()
  142. }
  143. const countUpRef = reactive({
  144. needPrice: null as any,
  145. originalPrice: null as any
  146. })
  147. const initNumCountUp = () => {
  148. nextTick(() => {
  149. // 在读学生
  150. if (countUpRef.needPrice) {
  151. countUpRef.needPrice.update(state.orderInfo.needPrice)
  152. } else {
  153. countUpRef.needPrice = new CountUp('needPrice', state.orderInfo.needPrice, {
  154. decimalPlaces: 2
  155. })
  156. if (!countUpRef.needPrice.error) {
  157. countUpRef.needPrice.start()
  158. } else {
  159. console.error(countUpRef.needPrice.error)
  160. }
  161. }
  162. // if (countUpRef.originalPrice) {
  163. // countUpRef.originalPrice.update(state.orderInfo.originalPrice)
  164. // } else {
  165. // countUpRef.originalPrice = new CountUp('originalPrice', state.orderInfo.originalPrice, {
  166. // decimalPlaces: 2
  167. // })
  168. // if (!countUpRef.originalPrice.error) {
  169. // countUpRef.originalPrice.start()
  170. // } else {
  171. // console.error(countUpRef.originalPrice.error)
  172. // }
  173. // }
  174. })
  175. }
  176. // 购买
  177. const onSubmit = async () => {
  178. try {
  179. // 判断订单号
  180. if (state.check.length <= 0) {
  181. showToast('请选择您要购买的商品')
  182. return
  183. }
  184. // 重新计算金额
  185. calcPrice()
  186. const params: any = [] // 支付参数
  187. const details = state.details
  188. let checkInstruments = false // 是否选择了乐器
  189. details.forEach((item: any) => {
  190. // 是否选中 并且没有购买过
  191. if (
  192. state.check.includes(item.goodsId) &&
  193. !state.paymentOrderDetails.includes(item.goodsType)
  194. ) {
  195. params.push({
  196. goodsId: item.goodsId,
  197. goodsNum: 1,
  198. goodsType: item.goodsType,
  199. paymentCashAmount: item.currentPrice, // 现金支付金额
  200. paymentCouponAmount: 0 // 优惠券金额
  201. })
  202. }
  203. // 判断是否是乐器
  204. if (
  205. item.goodsType === 'INSTRUMENTS' &&
  206. state.check.includes(item.goodsId) &&
  207. !state.paymentOrderDetails.includes(item.goodsType)
  208. ) {
  209. checkInstruments = true
  210. }
  211. })
  212. // 为了处理,商品和乐器保养做关联
  213. const repaire = state.repaireInfo
  214. if (checkInstruments && repaire.goodsId) {
  215. params.push({
  216. goodsId: repaire.goodsId,
  217. goodsNum: 1,
  218. goodsType: repaire.goodsType,
  219. paymentCashAmount: repaire.currentPrice, // 现金支付金额
  220. paymentCouponAmount: 0 // 优惠券金额
  221. })
  222. }
  223. console.log({
  224. bizId: route.query.id, // 乐团编号
  225. orderType: 'ORCHESTRA',
  226. paymentCashAmount: state.orderInfo.needPrice || 0,
  227. paymentCouponAmount: 0,
  228. goodsInfos: params
  229. })
  230. // 创建订单
  231. const { data } = await request.post('/api-student/userPaymentOrder/executeOrder', {
  232. data: {
  233. bizId: route.query.id, // 乐团编号
  234. orderType: 'ORCHESTRA',
  235. paymentCashAmount: state.orderInfo.needPrice || 0,
  236. paymentCouponAmount: 0,
  237. goodsInfos: params,
  238. orderName: '乐团报名缴费',
  239. orderDesc: '乐团报名缴费'
  240. }
  241. })
  242. console.log(data)
  243. router.push({
  244. path: '/orderDetail',
  245. query: {
  246. pm: 1, // h5乐团报名
  247. config: JSON.stringify(data.paymentConfig),
  248. orderNo: data.orderNo
  249. }
  250. })
  251. } catch (e: any) {
  252. //
  253. console.log(e)
  254. }
  255. }
  256. onMounted(() => {
  257. // 查询未支付订单
  258. registerGoods()
  259. paymentOrderUnpaid()
  260. })
  261. return () => (
  262. <>
  263. <div class={styles.applyTitle}>报名须知</div>
  264. <div class={[styles.paymentTips, styles.mlr13]}>
  265. <p>1、您注册时所选择的乐团声部,即为乐团录取最终确认的声部,请您务必仔细填写;</p>
  266. <p>
  267. 2、所有参与乐团的学生免费赠送选报声部教材,教材随乐器一同发放,若您自备乐器,则需承担教材运费。
  268. </p>
  269. </div>
  270. <CheckboxGroup
  271. v-model={state.check}
  272. style={{ paddingBottom: '20px' }}
  273. onChange={() => {
  274. calcPrice()
  275. }}
  276. >
  277. {/* 判断是否已经购买乐器 */}
  278. {!state.paymentOrderDetails.includes('INSTRUMENTS') && (
  279. <>
  280. <div class={styles.applyTitle}>乐器</div>
  281. <CellGroup
  282. inset
  283. class={[styles.mlr13, styles.sectionCell]}
  284. onClick={() => onSelect(state.goodsInfo.goodsId)}
  285. >
  286. <Cell border={false}>
  287. {{
  288. icon: () => (
  289. <Checkbox
  290. name={state.goodsInfo.goodsId}
  291. class={styles.checkbox}
  292. ref={(el: any) => (state.checkboxRefs[state.goodsInfo.goodsId] = el)}
  293. onClick={(e: Event) => {
  294. e.stopPropagation()
  295. }}
  296. v-slots={{
  297. icon: (props: any) => (
  298. <Icon
  299. class={styles.iconChecked}
  300. name={props.checked ? radioCheck : radioDefault}
  301. />
  302. )
  303. }}
  304. />
  305. ),
  306. title: () => (
  307. <div class={styles.section}>
  308. <Image
  309. class={styles.img}
  310. src={state.goodsInfo.goodsUrl}
  311. onClick={(e: any) => {
  312. e.stopPropagation()
  313. state.selectGoodsId = state.goodsInfo.goodsId
  314. state.goodsStatus = true
  315. }}
  316. />
  317. <div class={styles.sectionContent}>
  318. <h2>{state.goodsInfo.goodsName}</h2>
  319. <Tag
  320. color="linear-gradient(135deg, #FF8C4A 0%, #FF531C 100%)"
  321. textColor="#fff"
  322. class={styles.brandName}
  323. >
  324. {state.goodsInfo.brandName}
  325. </Tag>
  326. <p class={styles.model}>{state.goodsInfo.desciption}</p>
  327. </div>
  328. </div>
  329. )
  330. }}
  331. </Cell>
  332. <Cell border={false}>
  333. {{
  334. title: () => (
  335. <div class={styles.extra}>
  336. <div class={styles.sectionPrice}>
  337. <p class={styles.price}>
  338. 新团特惠:
  339. <span class={styles.numFont}>
  340. <span class={styles.numPrefix}>¥</span>
  341. {moneyFormat(state.goodsInfo.currentPrice)}
  342. </span>
  343. </p>
  344. <p class={styles.originPrice}>
  345. 原价:
  346. <del class={styles.numFont}>
  347. ¥{moneyFormat(state.goodsInfo.originalPrice)}
  348. </del>
  349. </p>
  350. </div>
  351. </div>
  352. )
  353. }}
  354. </Cell>
  355. <Cell center class={styles.gives}>
  356. {{
  357. title: () => (
  358. <div class={styles.sectionTips}>
  359. <Image src={iconGives} class={styles.iconGives} />
  360. 赠价值{state.repaireInfo.originalPrice}元乐器维保服务一年
  361. </div>
  362. )
  363. }}
  364. </Cell>
  365. </CellGroup>
  366. </>
  367. )}
  368. {/* 判断是否已经购买教材 */}
  369. {!state.paymentOrderDetails.includes('TEXTBOOK') && (
  370. <>
  371. <div class={styles.applyTitle}>教材</div>
  372. <CellGroup
  373. inset
  374. class={[styles.mlr13, styles.sectionCell]}
  375. onClick={() => {
  376. return
  377. // onSelect(state.textBookInfo.goodsId)
  378. }}
  379. >
  380. <Cell border={false}>
  381. {{
  382. icon: () => (
  383. <Checkbox
  384. name={state.textBookInfo.goodsId}
  385. disabled
  386. class={styles.checkbox}
  387. ref={(el: any) => (state.checkboxRefs[state.textBookInfo.goodsId] = el)}
  388. onClick={(e: Event) => {
  389. e.stopPropagation()
  390. }}
  391. v-slots={{
  392. icon: (props: any) => (
  393. <Icon
  394. class={styles.iconChecked}
  395. name={props.checked ? radioCheck : radioDefault}
  396. />
  397. )
  398. }}
  399. />
  400. ),
  401. title: () => (
  402. <div class={styles.section}>
  403. <Image
  404. class={styles.img}
  405. src={state.textBookInfo.goodsUrl}
  406. onClick={(e: any) => {
  407. e.stopPropagation()
  408. state.selectGoodsId = state.textBookInfo.goodsId
  409. state.goodsStatus = true
  410. }}
  411. />
  412. <div class={styles.sectionContent}>
  413. <h2>{state.textBookInfo.goodsName}</h2>
  414. <Tag
  415. color="linear-gradient(135deg, #FF8C4A 0%, #FF531C 100%)"
  416. textColor="#fff"
  417. class={styles.brandName}
  418. >
  419. {state.textBookInfo.brandName}
  420. </Tag>
  421. <p class={styles.model}>{state.textBookInfo.description}</p>
  422. </div>
  423. </div>
  424. )
  425. }}
  426. </Cell>
  427. <Cell>
  428. {{
  429. title: () => (
  430. <div class={styles.extra}>
  431. <div class={styles.sectionPrice}>
  432. <p class={styles.price}>
  433. 新团特惠:
  434. <span
  435. class={[
  436. state.textBookInfo.currentPrice > 0 ? styles.numFont : styles.free
  437. ]}
  438. >
  439. {state.textBookInfo.currentPrice > 0 ? (
  440. <>
  441. <span class={styles.numPrefix}>¥</span>
  442. {moneyFormat(state.textBookInfo.currentPrice)}
  443. </>
  444. ) : (
  445. '免费'
  446. )}
  447. </span>
  448. </p>
  449. <p class={styles.originPrice}>
  450. 原价:
  451. <del class={styles.numFont}>
  452. ¥{moneyFormat(state.textBookInfo.originalPrice)}
  453. </del>
  454. </p>
  455. </div>
  456. </div>
  457. )
  458. }}
  459. </Cell>
  460. </CellGroup>
  461. </>
  462. )}
  463. {!state.paymentOrderDetails.includes('VIP') && (
  464. <>
  465. <div class={styles.applyTitle}>乐团学习系统</div>
  466. <CellGroup
  467. inset
  468. class={[styles.mlr13, styles.sectionCell]}
  469. onClick={() => onSelect(state.vipInfo.goodsId)}
  470. >
  471. <Cell border={false}>
  472. {{
  473. icon: () => (
  474. <Checkbox
  475. name={state.vipInfo.goodsId}
  476. class={styles.checkbox}
  477. ref={(el: any) => (state.checkboxRefs[state.vipInfo.goodsId] = el)}
  478. onClick={(e: Event) => {
  479. e.stopPropagation()
  480. }}
  481. v-slots={{
  482. icon: (props: any) => (
  483. <Icon
  484. class={styles.iconChecked}
  485. name={props.checked ? radioCheck : radioDefault}
  486. />
  487. )
  488. }}
  489. />
  490. ),
  491. title: () => (
  492. <div class={styles.section}>
  493. <Image
  494. class={styles.img}
  495. src={state.vipInfo.goodsUrl}
  496. onClick={(e: any) => {
  497. e.stopPropagation()
  498. state.memberBaoStatus = true
  499. }}
  500. />
  501. <div class={styles.sectionContent}>
  502. <h2>{state.vipInfo.goodsName}</h2>
  503. <Tag
  504. color="linear-gradient(135deg, #FF8C4A 0%, #FF531C 100%)"
  505. textColor="#fff"
  506. class={styles.brandName}
  507. >
  508. 6个月
  509. </Tag>
  510. <p class={styles.model}>{state.vipInfo.description}</p>
  511. </div>
  512. </div>
  513. )
  514. }}
  515. </Cell>
  516. <Cell>
  517. {{
  518. title: () => (
  519. <div class={styles.extra}>
  520. <div class={styles.sectionPrice}>
  521. <p class={styles.price}>
  522. 新团特惠:
  523. <span class={styles.numFont}>
  524. <span class={styles.numPrefix}>¥</span>
  525. {moneyFormat(state.vipInfo.currentPrice)}
  526. </span>
  527. </p>
  528. <p class={styles.originPrice}>
  529. 原价:
  530. <del class={styles.numFont}>
  531. ¥{moneyFormat(state.vipInfo.originalPrice)}
  532. </del>
  533. </p>
  534. </div>
  535. </div>
  536. )
  537. }}
  538. </Cell>
  539. </CellGroup>
  540. </>
  541. )}
  542. </CheckboxGroup>
  543. <OSticky position="bottom" background="white">
  544. <div class={styles.paymentContainer}>
  545. <div class={styles.payemntPrice}>
  546. <p class={styles.needPrice}>
  547. 支付金额:
  548. <span class={styles.numFont}>
  549. <span>¥</span>
  550. <i style="font-style: normal" id="needPrice"></i>
  551. </span>
  552. </p>
  553. <p class={styles.allPrice}>
  554. 总原价:
  555. <del class={styles.numFont}>¥{moneyFormat(state.orderInfo.originalPrice)}</del>
  556. </p>
  557. </div>
  558. <div class={styles.paymentBtn}>
  559. <Button
  560. color="linear-gradient(135deg, #FF8C4A 0%, #FF531C 100%)"
  561. round
  562. onClick={onSubmit}
  563. >
  564. 立即购买
  565. </Button>
  566. </div>
  567. </div>
  568. </OSticky>
  569. <OPopup v-model:modelValue={state.memberBaoStatus} position="right">
  570. <MemberBao />
  571. </OPopup>
  572. <OPopup v-model:modelValue={state.goodsStatus} position="right" destroy>
  573. {state.goodsStatus && <GoodsDetail id={state.selectGoodsId} />}
  574. </OPopup>
  575. </>
  576. )
  577. }
  578. })