payment.vue 17 KB

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