index.vue 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. <template>
  2. <view class="pay-page">
  3. <view class="pay-card" v-if="orderNo">
  4. <text class="pay-title">确认支付</text>
  5. <text class="pay-amount">¥{{ fenToYuan(amount) }}</text>
  6. <text class="pay-order" v-if="orderNo">订单号:{{ orderNo }}</text>
  7. <view v-if="status === 'paid'" class="paid-box">
  8. <text class="paid-icon">✓</text>
  9. <text class="paid-text">支付成功</text>
  10. <button class="primary-btn" @click="goResult('success')">查看结果</button>
  11. </view>
  12. <view v-else>
  13. <button class="pay-btn" :loading="paying" :disabled="paying" @click="handlePay">立即支付</button>
  14. <view class="pay-tip">测试环境将模拟支付成功</view>
  15. </view>
  16. </view>
  17. <view v-if="!orderNo && !loading" class="empty-tip">
  18. <text>订单创建失败</text>
  19. </view>
  20. </view>
  21. </template>
  22. <script>
  23. import { createPayOrder, getPayStatus } from '@/utils/api.js'
  24. import { fenToYuan } from '@/utils/format.js'
  25. import { guardPage } from '@/utils/guard.js'
  26. export default {
  27. data() {
  28. return {
  29. enrollmentId: '',
  30. orderNo: '',
  31. amount: 0,
  32. status: '',
  33. prepayParams: null,
  34. loading: false,
  35. paying: false,
  36. pollTimer: null
  37. }
  38. },
  39. onLoad(options) {
  40. if (!guardPage()) return
  41. this.enrollmentId = options && options.enrollmentId ? options.enrollmentId : ''
  42. if (!this.enrollmentId) {
  43. uni.showToast({ title: '缺少报名信息', icon: 'none' })
  44. setTimeout(function() {
  45. uni.navigateBack({ delta: 1 })
  46. }, 1500)
  47. return
  48. }
  49. this.initOrder()
  50. },
  51. onUnload() {
  52. if (this.pollTimer) {
  53. clearInterval(this.pollTimer)
  54. this.pollTimer = null
  55. }
  56. },
  57. methods: {
  58. fenToYuan: fenToYuan,
  59. initOrder() {
  60. var self = this
  61. self.loading = true
  62. createPayOrder({ enrollmentId: self.enrollmentId }).then(function(resp) {
  63. var data = resp.data || {}
  64. self.orderNo = data.orderNo || ''
  65. self.amount = data.amount || 0
  66. self.status = data.status || ''
  67. self.prepayParams = data.prepayParams || null
  68. if (self.status === 'paid') {
  69. setTimeout(function() {
  70. self.goResult('success')
  71. }, 800)
  72. }
  73. }).catch(function(err) {
  74. uni.showToast({ title: (err && err.message) || '创建订单失败', icon: 'none' })
  75. }).finally(function() {
  76. self.loading = false
  77. })
  78. },
  79. handlePay() {
  80. var self = this
  81. if (this.paying) return
  82. // test-mode:后端 create 时已直接置 paid,直接查询确认
  83. var pp = this.prepayParams
  84. if (pp && pp.timeStamp) {
  85. // 生产模式:调起微信支付(预留)
  86. this.paying = true
  87. uni.requestPayment({
  88. provider: 'wxpay',
  89. timeStamp: pp.timeStamp,
  90. nonceStr: pp.nonceStr,
  91. package: pp.package,
  92. signType: pp.signType || 'MD5',
  93. paySign: pp.paySign,
  94. success: function() {
  95. self.startPoll()
  96. },
  97. fail: function() {
  98. self.paying = false
  99. uni.showToast({ title: '支付已取消', icon: 'none' })
  100. }
  101. })
  102. } else {
  103. // 测试模式:模拟支付成功
  104. this.paying = true
  105. uni.showLoading({ title: '模拟支付中…' })
  106. setTimeout(function() {
  107. self.startPoll()
  108. }, 800)
  109. }
  110. },
  111. startPoll() {
  112. var self = this
  113. var max = 6
  114. var count = 0
  115. if (this.pollTimer) clearInterval(this.pollTimer)
  116. this.pollTimer = setInterval(function() {
  117. count++
  118. getPayStatus({ orderNo: self.orderNo }).then(function(resp) {
  119. var data = resp.data || {}
  120. if (data.status === 'paid') {
  121. clearInterval(self.pollTimer)
  122. self.pollTimer = null
  123. self.paying = false
  124. uni.hideLoading()
  125. self.status = 'paid'
  126. self.goResult('success')
  127. } else if (count >= max) {
  128. clearInterval(self.pollTimer)
  129. self.pollTimer = null
  130. self.paying = false
  131. uni.hideLoading()
  132. uni.showToast({ title: '支付处理中,请稍后查看', icon: 'none' })
  133. }
  134. }).catch(function() {
  135. if (count >= max) {
  136. clearInterval(self.pollTimer)
  137. self.pollTimer = null
  138. self.paying = false
  139. uni.hideLoading()
  140. }
  141. })
  142. }, 1500)
  143. },
  144. goResult(status) {
  145. uni.redirectTo({
  146. url: '/pages/pay/result?status=' + status + '&amount=' + this.amount + '&orderNo=' + this.orderNo
  147. })
  148. }
  149. }
  150. }
  151. </script>
  152. <style scoped>
  153. .pay-page { min-height: 100vh; background: #F5F5F5; padding: 32rpx; }
  154. .pay-card { background: #FFF; border-radius: 16rpx; padding: 48rpx 32rpx; display: flex; flex-direction: column; align-items: center; }
  155. .pay-title { font-size: 28rpx; color: #5A4F4B; margin-bottom: 24rpx; }
  156. .pay-amount { font-size: 64rpx; font-weight: 700; color: #2A2326; margin-bottom: 16rpx; }
  157. .pay-order { font-size: 24rpx; color: #94877F; margin-bottom: 48rpx; }
  158. .paid-box { display: flex; flex-direction: column; align-items: center; width: 100%; }
  159. .paid-icon { width: 96rpx; height: 96rpx; border-radius: 50%; background: #22C55E; color: #FFF; font-size: 56rpx; display: flex; align-items: center; justify-content: center; margin-bottom: 24rpx; }
  160. .paid-text { font-size: 32rpx; font-weight: 600; color: #2A2326; margin-bottom: 48rpx; }
  161. .pay-btn { width: 100%; height: 88rpx; line-height: 88rpx; background: #FC7A57; color: #FFF; font-size: 30rpx; font-weight: 600; border-radius: 44rpx; border: none; }
  162. .pay-btn:active { opacity: 0.85; }
  163. .pay-tip { font-size: 24rpx; color: #94877F; margin-top: 16rpx; }
  164. .primary-btn { width: 400rpx; height: 80rpx; line-height: 80rpx; background: #FC7A57; color: #FFF; font-size: 28rpx; font-weight: 600; border-radius: 40rpx; border: none; }
  165. .primary-btn:active { opacity: 0.85; }
  166. .empty-tip { font-size: 26rpx; color: #94877F; padding: 64rpx 0; text-align: center; }
  167. </style>