initPay.vue 9.1 KB

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