payment.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  1. <template>
  2. <div class="payment">
  3. <header>课程缴费</header>
  4. <div class="section">
  5. <h2 class="title">课程</h2>
  6. <div class="options">
  7. <div v-for="s in classInfo" :key="s.courseId">
  8. <div class="item" v-if="s.classType == 2">
  9. <div class="option" @click="onSelect(s)">
  10. <div class="0_hd"><i class="check_default" :class="[s.status ? 'check_active' : '']"></i></div>
  11. <div class="o_bd">乐团课</div>
  12. <span class="o_ft">价格:¥{{ s.buyCount * s.price }}</span>
  13. </div>
  14. <p class="timer">开课周期:{{ s.startCycel }}</p>
  15. <!-- <input class="inputAmount" type="number" @keyup="calcAmount" v-model="s.inputNumber" pattern="[0-9]" placeholder="输入金额"> -->
  16. </div>
  17. </div>
  18. </div>
  19. </div>
  20. <div v-for="c in classInfo" :key="c.courseId">
  21. <div class="section" v-if="c.classType != 2">
  22. <div class="options">
  23. <div class="option" @click="onSelect(c)">
  24. <div class="0_hd"><i class="check_default" :class="[c.status ? 'check_active' : '']"></i></div>
  25. <div class="o_bd">个别提高课</div>
  26. <span class="o_ft">
  27. 现价:¥{{ c.smallAmount }}
  28. </span>
  29. </div>
  30. </div>
  31. <div class="classInfo">
  32. <div class="class" v-if="c.memo">
  33. <p style="color: #FA101D;">{{ c.memo }}</p>
  34. </div>
  35. </div>
  36. <div class="classInfo" :class="[c.memo? 'classStatus' : '']">
  37. <div class="class">
  38. <p>学习科目:{{ c.subNames }}</p>
  39. <p>指导老师:{{ c.teaNames }}</p>
  40. </div>
  41. <div class="class">
  42. <p>开课周期:{{ c.startCycel }}</p>
  43. <!-- <p>计划开课周期:{{ c.planCycle }}</p> -->
  44. <p>每课时长:{{ c.duration }}分钟</p>
  45. </div>
  46. </div>
  47. <div class="classTime">
  48. <p class="title">购买次数:</p>
  49. <div class="ct_button_group">
  50. <span @click="fixationTimer(t, c)" v-for="t in classTimer" :key="t.timer" :class="[t.status?'active':'']">{{ t.timer }}次</span>
  51. </div>
  52. <input type="number" placeholder="输入次数" min="1" max="20" @keyup="onInputCheck(c)" v-model="c.inputNumber" class="inputTime" pattern="[0-9]">
  53. </div>
  54. </div>
  55. </div>
  56. <div class="buy">
  57. <div class="price">
  58. <!-- <p class="oldprice">
  59. <del class="text">原价</del>
  60. <del>¥{{ orderInfo.marketPrice }}</del>
  61. </p> -->
  62. <p class="now_price">
  63. <span class="text">仅需支付</span>
  64. <span>¥{{ orderInfo.referencePrice }}</span>
  65. </p>
  66. </div>
  67. <a @click="onCheckSubmit">购买</a>
  68. <!-- <a @click="buy">购买</a> -->
  69. </div>
  70. <van-popup id="protocolPopup" v-model="popupStatus" position="bottom">
  71. <small-protocol :proto="protocolData" @popupClose="onPopupClose"></small-protocol>
  72. </van-popup>
  73. </div>
  74. </template>
  75. <script>
  76. import smallProtocol from './smallProtocol'
  77. import qs from 'qs'
  78. import { Dialog } from 'vant'
  79. export default {
  80. name: 'payment',
  81. components: { smallProtocol, Dialog },
  82. data() {
  83. return {
  84. popupStatus: false, // 协议弹窗样式
  85. protocolData: {}, // 协议参数
  86. type: true,
  87. classInfo: [],
  88. memo: null,
  89. inputTimes: 0, // 输入次数
  90. // mainSubject: [], // 主课程
  91. // 金额列表,金额计算
  92. orderInfo: {
  93. marketPrice: 0, // 原价总金额
  94. referencePrice: 0, // 现价总金额
  95. }, // 信息列表
  96. classTimer: [{
  97. status: true,
  98. timer: 10
  99. },{
  100. status: false,
  101. timer: 20
  102. }]
  103. }
  104. },
  105. mounted() {
  106. let userId = this.$route.query.userId
  107. axios.post('/user/queryUserCourse', qs.stringify({ userId: userId })).then((res) => {
  108. let data = res.data.data.courses
  109. // let data = res.data.data
  110. if(!data) {
  111. throw '数据为空'
  112. }
  113. this.memo = res.data.data.memo // 特殊描述
  114. let tempClassForm = {}
  115. if(this.memo) {
  116. let tempMemo = this.memo.split('&')
  117. tempMemo.forEach(me => {
  118. var memoItem = me.split('^')
  119. tempClassForm[memoItem[0]] = memoItem[1]
  120. })
  121. }
  122. /**
  123. * 过滤数据
  124. * 乐团课同一个乐团只需要显示一个乐团(价格高的)
  125. * 如果有多个乐团课则显示多个
  126. */
  127. let subjectStatus = {}
  128. let paymentConfig = sessionStorage.getItem('paymentConfig')
  129. if(paymentConfig) {
  130. paymentConfig = JSON.parse(paymentConfig)
  131. sessionStorage.removeItem('homeConfig') // 用完就不要了
  132. }
  133. data.forEach(el => {
  134. el.status = false // 状态
  135. el.inputNumber = null // 输入的内容
  136. el.smallAmount = 0 // 小课金额
  137. if(tempClassForm[this.classFormToCN(el.classForm)]) {
  138. el.memo = tempClassForm[this.classFormToCN(el.classForm)]
  139. } else if(tempClassForm['default']) {
  140. el.memo = tempClassForm['default']
  141. } else {
  142. el.memo = ''
  143. }
  144. // 开课周期
  145. if(el.planCycle) {
  146. el.startCycel = this.weekSelect(el.planCycle) + ' ' + el.planBegin.split(' ')[1]
  147. } else {
  148. el.startCycel = '不上课'
  149. }
  150. // 课程
  151. let searchClassId = subjectStatus[el.classId], // 查询Id是否存在
  152. countAmount = el.buyCount * el.price // 总价
  153. if(!searchClassId && el.classType == 2) {
  154. this.classInfo.push(el)
  155. subjectStatus[el.classId] = {
  156. price: countAmount
  157. }
  158. } else if(searchClassId && el.classType == 2 && searchClassId.price < countAmount) {
  159. for(let i = 0; i < this.classInfo.length; i++) {
  160. if(this.classInfo[i].classId == el.classId) {
  161. this.classInfo[i] = el
  162. }
  163. }
  164. subjectStatus[el.classId] = {
  165. price: countAmount
  166. }
  167. }
  168. // 小课
  169. if(el.classType == 1) {
  170. this.classInfo.push(el)
  171. }
  172. //
  173. if(paymentConfig && paymentConfig.config == el.courseId) {
  174. el.status = true
  175. }
  176. })
  177. this.calcAmount() // 计算金额
  178. })
  179. },
  180. methods: {
  181. buy() {
  182. let [record, timer] = [0, 0] // 购买课次数据
  183. let params = {
  184. userId: this.$route.query.userId
  185. }
  186. this.classTimer.forEach(ct => {
  187. if(ct.status) {
  188. timer = ct.timer
  189. }
  190. })
  191. let cour = {}
  192. let configIndex, inputCount // 课程编号(唯一) 输入的次数
  193. // 拼接参数
  194. this.classInfo.forEach(c => {
  195. if(c.classType == 2 && c.status) {
  196. cour = {
  197. courseId: c.courseId,
  198. buyCount: c.buyCount,
  199. price: c.price
  200. }
  201. }
  202. if(c.classType == 1 && c.status) {
  203. cour = {
  204. courseId: c.courseId,
  205. buyCount: parseInt(this.inputTimes),
  206. price: c.price
  207. }
  208. if(timer) {
  209. cour.buyCount = timer
  210. }
  211. }
  212. if(c.status) {
  213. record++
  214. configIndex = c.courseId
  215. }
  216. })
  217. params.courses = JSON.stringify(cour)
  218. if(!record) {
  219. Dialog.alert({
  220. title: '提示',
  221. message: '请选择续费课程',
  222. confirmButtonColor: '#269a93'
  223. })
  224. return false
  225. }
  226. // 保存用户选择信息保存本地
  227. sessionStorage.setItem('paymentConfig', JSON.stringify({
  228. config: configIndex
  229. }))
  230. axios.post('/yqpay/renewalsPay', qs.stringify(params)).then(res => {
  231. let result = res.data
  232. if(result.status) {
  233. document.querySelector('#onSubmit').action = result.data.host
  234. document.querySelector('#apiContent').value = result.data.apiContent
  235. document.querySelector('#merNo').value = result.data.merNo
  236. document.querySelector('#notifyUrl').value = result.data.notifyUrl
  237. document.querySelector('#sign').value = result.data.sign
  238. document.querySelector('#signType').value = result.data.signType
  239. document.querySelector('#timestamp').value = result.data.timestamp
  240. document.querySelector('#version').value = result.data.version
  241. document.querySelector('#onSubmit').submit()
  242. } else {
  243. Dialog.alert({
  244. title: '提示',
  245. message: result.msg,
  246. confirmButtonColor: '#269a93'
  247. })
  248. }
  249. })
  250. },
  251. onSelect(item) { // 选中哪个课程(大课还是小课)
  252. this.classInfo.forEach(e => {
  253. e.status = false
  254. })
  255. item.status = true
  256. this.calcAmount()
  257. },
  258. // 固定次数计算
  259. fixationTimer(t, item) {
  260. item.inputNumber = null
  261. this.classTimer.forEach(e => {
  262. e.status = false
  263. })
  264. t.status = true
  265. let amount = t.timer * item.price
  266. item.smallAmount = amount
  267. // 如果当前课程选中则需要重新计算金额
  268. if(item.status) {
  269. this.orderInfo.referencePrice = amount
  270. }
  271. },
  272. onInputCheck(item) {
  273. if(item.inputNumber <= 1 ) {
  274. item.inputNumber = 1
  275. }
  276. if(item.inputNumber > 20) {
  277. item.inputNumber = 20
  278. }
  279. this.inputTimes = item.inputNumber
  280. this.calcAmount(item)
  281. },
  282. // 计算总金额
  283. calcAmount(item) {
  284. let timer, c = this.classInfo
  285. this.classTimer.forEach(ct => {
  286. // 判断是否从 Input 里面输入的
  287. if(item && item.inputNumber) {
  288. ct.status = false
  289. }
  290. if(ct.status) {
  291. timer = ct.timer
  292. }
  293. })
  294. c.forEach(e => {
  295. // 小课现价
  296. let amount = timer ? (timer * e.price) : e.inputNumber * e.price
  297. e.smallAmount = amount
  298. if(e.status) {
  299. if(e.classType == 2) {
  300. this.orderInfo.referencePrice = e.price * e.buyCount
  301. } else {
  302. // 判断是否有选中固定次数
  303. this.orderInfo.referencePrice = amount
  304. }
  305. }
  306. })
  307. },
  308. weekSelect(num) {
  309. // 计算是周几
  310. let res = num.toString(2).split('').reverse() // 转换成二进制 并分割成数组 反转
  311. let strArr = []
  312. parseInt(res[0]) ? strArr.push('周一') : ''
  313. parseInt(res[1]) ? strArr.push('周二') : ''
  314. parseInt(res[2]) ? strArr.push('周三') : ''
  315. parseInt(res[3]) ? strArr.push('周四') : ''
  316. parseInt(res[4]) ? strArr.push('周五') : ''
  317. parseInt(res[5]) ? strArr.push('周六') : ''
  318. parseInt(res[6]) ? strArr.push('周七') : ''
  319. strArr = strArr.join('和')
  320. return strArr
  321. },
  322. onCheckSubmit() {
  323. // 小课需要弹出协议
  324. let buyTimer
  325. this.classTimer.forEach(e => {
  326. if(e.status) {
  327. buyTimer = e.timer
  328. }
  329. })
  330. let item
  331. this.classInfo.forEach(c => {
  332. if(c.status) {
  333. item = c
  334. }
  335. })
  336. let tempBuyCount
  337. if(item.classType == 2) {
  338. tempBuyCount = item.buyCount
  339. } else {
  340. tempBuyCount = buyTimer ? buyTimer : parseInt(this.inputTimes)
  341. }
  342. this.protocolData = {
  343. userId: this.$route.query.userId,
  344. price: item.price,
  345. buyCount: tempBuyCount,
  346. classForm: item.classForm,
  347. className: item.className,
  348. subNames: item.subNames,
  349. classType: item.classType
  350. }
  351. this.popupStatus = true
  352. },
  353. onPopupClose(status) {
  354. // document.querySelector('#protocolPopup').scroll(0, 0)
  355. // this.popupStatus = false
  356. this.buy()
  357. },
  358. classFormToCN(number) { // 小课课程类型
  359. let resultStr
  360. switch(number) {
  361. case 1001:
  362. resultStr = '1V1'
  363. break;
  364. case 2002:
  365. resultStr = '1V2'
  366. break;
  367. case 3003:
  368. resultStr = '1V3'
  369. break;
  370. case 4004:
  371. resultStr = '1V4'
  372. break;
  373. case 5005:
  374. resultStr = '1V5'
  375. break;
  376. case 6020:
  377. resultStr = '小组课'
  378. break;
  379. case 21099:
  380. resultStr = '大课'
  381. break;
  382. case 999999:
  383. resultStr = '团体课'
  384. break;
  385. }
  386. return resultStr
  387. }
  388. }
  389. }
  390. </script>
  391. <style lang="less" scoped>
  392. .payment {
  393. margin-bottom: .7rem;
  394. }
  395. .classStatus {
  396. border-top:0 !important;
  397. margin-top: 0 !important;
  398. padding-top: 0 !important;
  399. }
  400. header {
  401. height: .40rem;
  402. line-height: .40rem;
  403. color: #000;
  404. font-size: .17rem;
  405. background: #fff;
  406. box-shadow: 0px 1px 8px 0px rgba(0,0,0,0.07);
  407. text-align: center;
  408. margin-bottom: .06rem;
  409. }
  410. .section {
  411. padding: .16rem .22rem .1rem;
  412. background: #fff;
  413. margin-bottom: .1rem;
  414. .line_bottom {
  415. border-bottom: 1px solid #ededed;
  416. }
  417. > .title {
  418. font-size: .2rem;
  419. line-height: .28rem;
  420. font-weight: bold;
  421. &::before {
  422. content: ' ';
  423. width: .04rem;
  424. height: 0.15rem;
  425. background: #14928a;
  426. display: inline-block;
  427. margin-right: .07rem;
  428. border-radius: 8px;
  429. }
  430. }
  431. }
  432. .options {
  433. // padding-top: .08rem;
  434. .option {
  435. line-height: .26rem;
  436. font-size: .15rem;
  437. display: flex;
  438. align-items: center;
  439. position: relative;
  440. .o_bd {
  441. flex: 1;
  442. .protocol {
  443. font-size: .1rem;
  444. line-height: .14rem;
  445. }
  446. }
  447. .o_ft {
  448. font-size: .12rem;
  449. color: #FA101D;
  450. del {
  451. color: #AAAAAA;
  452. font-size: .11rem;
  453. }
  454. }
  455. .check_default {
  456. position: relative;
  457. margin-right: .08rem;
  458. display: block;
  459. width: .14rem;
  460. height: .14rem;
  461. border-radius: 50%;
  462. border: .01rem solid #D0CFCF;
  463. &::before {
  464. position: absolute;
  465. top: .01rem;
  466. left: .01rem;
  467. content: ' ';
  468. display: inline-block;
  469. width: .12rem;
  470. height: .12rem;
  471. background: #D0CFCF;
  472. border-radius: 50%;
  473. }
  474. &.check_active {
  475. border: .01rem solid #F1111B;
  476. &::before {
  477. background: #F1111B;
  478. border-radius: 50%;
  479. }
  480. }
  481. }
  482. }
  483. }
  484. .item {
  485. position: relative;
  486. border-top: 1px solid #ededed;
  487. padding: .2rem 0;
  488. // &:last-child {
  489. // border-bottom: 0;
  490. // }
  491. .timer {
  492. font-size: .14rem;
  493. color: #ACACAC;
  494. }
  495. .inputAmount {
  496. position: absolute;
  497. top: .2rem;
  498. right: 0;
  499. width: 1.08rem;
  500. height: .4rem;
  501. border-radius: 5px;
  502. border: 1px solid rgba(238,238,238,1);
  503. padding-left: .08rem;
  504. }
  505. }
  506. .classInfo {
  507. color: #ACACAC;
  508. font-size: .12rem;
  509. display: flex;
  510. justify-content: space-between;
  511. border-top: 1px solid #ededed;
  512. margin-top: .08rem;
  513. padding-top: .08rem;
  514. line-height: .22rem;
  515. }
  516. .classTime {
  517. color: #ACACAC;
  518. font-size: .12rem;
  519. .ct_button_group {
  520. padding-top: .08rem;
  521. padding-bottom: .15rem;
  522. display: flex;
  523. justify-content: space-between;
  524. span {
  525. width: 1.6rem;
  526. line-height: .36rem;
  527. text-align: center;
  528. background:rgba(255,255,255,1);
  529. border-radius:5px;
  530. font-size: .14rem;
  531. color: #444444;
  532. border:1px solid rgba(226,224,224,1);
  533. &.active {
  534. background: #14928A;
  535. color: #fff;
  536. }
  537. }
  538. }
  539. .inputTime {
  540. width: calc(100% - .08rem);
  541. height: .4rem;
  542. border-radius:5px;
  543. border:1px solid rgba(238,238,238,1);
  544. padding-left: .08rem;
  545. font-size: .14rem;
  546. color: #444;
  547. }
  548. }
  549. .buy {
  550. position: fixed;
  551. bottom: 0;
  552. left: 0;
  553. right: 0;
  554. height: .6rem;
  555. display: flex;
  556. align-items: center;
  557. padding: 0 .2rem;
  558. border-top: 1px solid #FFE9E9E9;
  559. color: #000000;
  560. font-size: .12rem;
  561. background: #fff;
  562. .price {
  563. flex: 1;
  564. font-size: .16rem;
  565. }
  566. font-size: .16rem;
  567. span {
  568. color: #FA101D;
  569. }
  570. .text {
  571. font-size: .12rem;
  572. width: .55rem;
  573. display: inline-block;
  574. color: #000;
  575. }
  576. del {
  577. color: #B5B5B5;
  578. &.text {
  579. color: #B5B5B5;
  580. }
  581. }
  582. a {
  583. display: inline-block;
  584. font-size: .18rem;
  585. color: #fff;
  586. background: #F1111B;
  587. border-radius: .04rem;
  588. box-shadow:0px 2px 4px 0px rgba(0,0,0,0.19);
  589. padding: .08rem .28rem;
  590. }
  591. }
  592. </style>