initPay.vue 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. <!-- 支付组件 -->
  2. <template>
  3. <div v-if="payType && payQrCodeTxt" class="code pa-5 resume-box">
  4. <div class="resume-header">
  5. <div class="resume-title">扫码支付</div>
  6. </div>
  7. <div class="d-flex align-end mt-3">
  8. <div class="code-left">
  9. <QrCode :text="payQrCodeTxt" :disabled="!remainderTimer" :width="170" @refresh="refreshQRCode" />
  10. </div>
  11. <div class="code-right ml-5">
  12. <div class="price">
  13. <span class="font-size-13">¥</span>
  14. {{ FenYuanTransform(props.info?.payPrice || 0) }}
  15. </div>
  16. <div class="mt-3 d-flex align-center">
  17. <span class="color-666 font-weight-bold mr-5">支付方式</span>
  18. <!-- <v-chip-group v-model="payment" selected-class="text-primary" mandatory>
  19. <v-chip filter v-for="k in paymentList" :key="k.value" :value="k.value" class="mr-3" label>
  20. {{ k.label }}
  21. <svg-icon class="ml-1" :name="k.icon" :size="k.size"></svg-icon>
  22. </v-chip>
  23. </v-chip-group> -->
  24. <v-chip-group v-model="payType" selected-class="text-primary" column mandatory @update:modelValue="payTypeChange">
  25. <v-chip filter v-for="k in payTypeList" :key="k.code" :value="k.code" class="mr-3" label>
  26. {{ k.name }}
  27. <svg-icon v-if="k.icon" class="ml-1" :name="k.icon" :size="k.size"></svg-icon>
  28. </v-chip>
  29. </v-chip-group>
  30. </div>
  31. <div class="font-size-14 color-666 mt-3 cursor-pointer">
  32. 服务协议
  33. <span class="septal-line"></span>
  34. 充值协议
  35. </div>
  36. </div>
  37. </div>
  38. <div
  39. class="mt-5 ml-2"
  40. style="color: var(--v-error-base);"
  41. >
  42. 扫码支付时请勿离开
  43. <span v-if="remainderZhShow">{{ remainderZhShow }}</span>
  44. </div>
  45. </div>
  46. </template>
  47. <script setup>
  48. defineOptions({name: 'personalRecharge-initPay'})
  49. import QrCode from '@/components/QrCode'
  50. import { FenYuanTransform } from '@/utils/position'
  51. import { definePayTypeList, qrCodePay, walletPay } from '@/utils/payType'
  52. import { getEnableCodeList, payOrderSubmit, getOrderPayStatus } from '@/api/common'
  53. import { setWalletRecharge } from '@/api/recruit/personal/myWallet.js'
  54. import { onUnmounted, ref, nextTick, watch } from 'vue'
  55. const emit = defineEmits(['payTypeChange', 'paySuccess', 'stopInterval'])
  56. const props = defineProps({
  57. info: {
  58. type: Object,
  59. default: () => ({ id: ''})
  60. },
  61. appId: {
  62. type: Number,
  63. default: 11 // 10为一般情况下支付,11为充值支付
  64. },
  65. })
  66. // 1.支付方式
  67. // 2.发起充值(创建钱包充值记录)
  68. // 2.发起充值(创建钱包充值记录)
  69. // 3.如果是二维码类型支付(isQrCodePay=true)生成二维码(需要绑定支付订单的订单号)
  70. // 4.轮询用户是否支付成功
  71. // 更新账户余额信息
  72. import { useUserStore } from '@/store/user'; const store = useUserStore()
  73. const updateAccountInfo = async () => {
  74. await store.getUserAccountBalance()
  75. loading.value = false
  76. }
  77. import Snackbar from '@/plugins/snackbar'
  78. import { useRoute } from 'vue-router'; const route = useRoute()
  79. import { useRouter } from 'vue-router'; const router = useRouter()
  80. const payStatusTimer = ref(null) // 支付状态轮询
  81. const payStatus = async () => {
  82. // if (payStatusTimer.value) clearInterval(payStatusTimer.value); payStatusTimer.value = null
  83. // setTimeout(() => { // 测试代码
  84. // }, 2000)
  85. try {
  86. const data = await getOrderPayStatus({ id: (props.appId - 0) === 11 ? payOrder.value.payOrderId : payOrder.value.id })
  87. if ((data?.status - 0) === 10) {
  88. // 支付成功
  89. if (payStatusTimer.value) clearInterval(payStatusTimer.value); payStatusTimer.value = null
  90. setTimeout(() => {
  91. // 更新点数(充值、发布职位)
  92. updateAccountInfo()
  93. // 清除定时器
  94. clearTimer()
  95. // 支付成功
  96. emit('paySuccess')
  97. // getUnpaidOrderList() // 重新创建新的支付订单
  98. // 返回指定页面
  99. if (route.fullPath === props.returnUrl) router.go(0)
  100. else if (props.returnUrl) router.push(props.returnUrl)
  101. Snackbar.success('支付成功')
  102. }, 2000);
  103. }
  104. } catch (error) {
  105. console.log(error)
  106. }
  107. }
  108. const payQrCodeTxt = ref('')
  109. // 如果是点数支付的话走完payOrderSubmit之后即扣点数,如果是二维码支付的话只是生成二维码,这一步以后是轮询是否支付成功
  110. const paySubmit = async () => {
  111. if (!payType.value) return
  112. if (!payOrder.value?.payOrderId) return
  113. try {
  114. if (payOrder.value) {
  115. // 提交支付订单
  116. const params = {
  117. id: payOrder.value.payOrderId, // 支付单编号
  118. channelCode: payType.value, // 支付渠道
  119. }
  120. const res = await payOrderSubmit(params)
  121. payQrCodeTxt.value = res?.displayContent || '二维码生成失败,请刷新再试。' // 生成二维码内容
  122. initIntervalFun()
  123. }
  124. } catch (error) {
  125. console.log(error)
  126. }
  127. }
  128. // 2.发起充值
  129. const loading = ref(true)
  130. const payOrder = ref({})
  131. const getUnpaidOrderList = async () => {
  132. try {
  133. //* 充值
  134. if (props.info.payPrice === undefined && props.info.packageId === undefined) return
  135. const params = {
  136. payPrice: (props.info.payPrice-0),
  137. packageId: props.info.id,
  138. }
  139. const data = await setWalletRecharge(params)
  140. payOrder.value = data || {}
  141. if (isQrCodePay.value) paySubmit()
  142. } catch (error) {
  143. console.log(error)
  144. } finally {
  145. nextTick(() => {
  146. loading.value = false
  147. })
  148. }
  149. }
  150. // 1.支付方式
  151. const isWalletPay = ref(false)
  152. const isQrCodePay = ref(false)
  153. const tip = ref('')
  154. const payTypeChange = (value) => {
  155. payType.value = value
  156. tip.value = payTypeList.value.find(e => e.code === payType.value)?.tip || ''
  157. isQrCodePay.value = qrCodePay.includes(payType.value)
  158. isWalletPay.value = walletPay.includes(payType.value)
  159. paySubmit()
  160. }
  161. // 1.支付方式
  162. const payType = ref('')
  163. const payTypeList = ref([])
  164. const codeList = ref([])
  165. const getCodeList = async () => {
  166. try {
  167. const list = await getEnableCodeList({appId: props.appId})
  168. codeList.value = list || []
  169. } catch (error) {
  170. console.log(error)
  171. } finally {
  172. if (definePayTypeList?.length && codeList.value?.length) {
  173. codeList.value.forEach(code => {
  174. const item = definePayTypeList.find(p => p.code === code)
  175. if (item) {
  176. if (!payType.value) {
  177. tip.value = item.tip || ''
  178. // payTypeChange(code) // 默认值赋值
  179. // 默认值赋值(暂时只支持扫码)
  180. const bool = qrCodePay.includes(code)
  181. if (bool) payTypeChange(code)
  182. }
  183. payTypeList.value.push(item)
  184. }
  185. })
  186. }
  187. getUnpaidOrderList()
  188. }
  189. }
  190. watch(
  191. () => props.info.id,
  192. (newVal, oldVal) => {
  193. if (!newVal) return
  194. if (!oldVal && newVal) getCodeList() // 初始化时要从获取支付方式列表开始
  195. if (oldVal && newVal) getUnpaidOrderList() // 切换充值金额,从创建新充值订单开始
  196. },
  197. { immediate: true },
  198. { deep: true }
  199. )
  200. const refreshQRCode =() => { // 刷新二维码
  201. getUnpaidOrderList()
  202. }
  203. const countdownTime = 60000*5 // 倒计时五分钟
  204. let remainder = 0 // number
  205. const remainderZhShow = ref('') // 倒计时展示
  206. const remainderTimer = ref(null)
  207. // 初始化倒计时
  208. const initIntervalFun = () => {
  209. remainder = countdownTime // 初始倒计时时间
  210. if (remainderTimer.value) clearInterval(remainderTimer.value); remainderTimer.value = null // 每一次点击都清除上一个轮询
  211. // 倒计时计算
  212. remainderCalc()
  213. remainderTimer.value = setInterval(() => { remainderCalc() }, 1000)
  214. if (payStatusTimer.value) clearInterval(payStatusTimer.value); payStatusTimer.value = null
  215. payStatusTimer.value = setInterval(() => { payStatus() }, 2000) // 轮巡查询用户是否支付
  216. }
  217. const remainderCalc = () => {
  218. remainder -= 1000
  219. remainderZhShow.value = formatDuration(remainder)
  220. if (remainder <= 0) clearTimer()
  221. }
  222. const formatDuration = (remainder) => {
  223. // 将毫秒转换为秒
  224. var seconds = Math.floor(remainder / 1000)
  225. // 计算分钟和剩余的秒数
  226. var minutes = Math.floor(seconds / 60)
  227. var remainingSeconds = seconds % 60
  228. // 格式化分钟和秒数,确保秒数为两位数(如果小于10,则前面补0)
  229. minutes = minutes.toString().padStart(2, '0')
  230. remainingSeconds = remainingSeconds.toString().padStart(2, '0')
  231. // 返回格式化的字符串
  232. return `${minutes}分${remainingSeconds}秒`
  233. }
  234. onUnmounted(() => {
  235. clearTimer()
  236. })
  237. const clearTimer = () => {
  238. if (payStatusTimer.value) clearInterval(payStatusTimer.value); payStatusTimer.value = null
  239. if (remainderTimer.value) clearInterval(remainderTimer.value); remainderTimer.value = null
  240. remainderZhShow.value = ''
  241. emit('stopInterval')
  242. }
  243. </script>
  244. <style lang="scss" scoped>
  245. .code {
  246. border-radius: 6px;
  247. background-color: #f7f8fa;
  248. &-left {
  249. border: 1px solid #00897B;
  250. border-radius: 6px;
  251. padding: 5px;
  252. }
  253. &-right {
  254. .price {
  255. font-size: 30px;
  256. font-weight: 700;
  257. color: var(--v-error-base);
  258. }
  259. }
  260. }
  261. </style>