payment.tsx 17 KB

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