balance.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. <template>
  2. <div>
  3. <div class="text-end color-primary my-3 text-decoration-underline cursor-pointer" @click="handleToOrder">充值记录<v-icon>mdi-chevron-double-right</v-icon></div>
  4. <div class="d-flex align-center justify-center mt-5">
  5. <div
  6. v-for="(item, index) in list"
  7. :key="index"
  8. class="packagesItem cursor-pointer mx-3"
  9. :class="{'active': current === (index+1)}"
  10. style="width: 200px;"
  11. @click="handleClick(index, item)"
  12. >
  13. <div class="d-flex flex-column align-center pb-5" style="position: relative;">
  14. <div class="my-5 font-size-16 font-weight-bold" style="z-index: 2;">{{ item.name }}</div>
  15. <div class="priceBox mt-3" style="position: relative;">
  16. <span v-if="item.custom">
  17. <div v-if="inputValue" class="custom-point-show" style="position: absolute; top: -24px;">{{ inputValue }}M豆</div>
  18. <input v-model="inputValue" @blur="inputChange" type="text" class="custom-input-num mr-1" :placeholder="item.placeholder">元
  19. <div class="color-warning font-size-12 text-center mt-1">只能输入整数</div>
  20. </span>
  21. <span class="color-primary" v-else>
  22. <span style="font-size: 25px;">{{ FenYuanTransform(item.payPrice) }}</span>
  23. </span>
  24. </div>
  25. <div class="vip">
  26. <svg-icon name="diamond" size="50"></svg-icon>
  27. </div>
  28. </div>
  29. </div>
  30. </div>
  31. <div v-if="!Object.keys(select).length" class="color-warning text-center mt-10 font-size-14">请选择要充值的金额</div>
  32. <div v-if="payType && payQrCodeTxt" class="code pa-5 resume-box">
  33. <div class="resume-header">
  34. <div class="resume-title">扫码支付</div>
  35. </div>
  36. <div class="d-flex align-end mt-3">
  37. <div class="code-left">
  38. <QrCode :text="payQrCodeTxt" :disabled="!remainderTimer" :width="170" @refresh="refreshQRCode" />
  39. </div>
  40. <div class="code-right ml-5">
  41. <div class="price">
  42. <span class="font-size-13">¥</span>
  43. {{ FenYuanTransform(select?.payPrice || 0) }}元
  44. </div>
  45. <div class="mt-3 d-flex align-center">
  46. <span class="color-666 font-weight-bold mr-5">支付方式</span>
  47. <v-chip-group v-model="payType" selected-class="text-primary" column mandatory @update:modelValue="payTypeChange">
  48. <v-chip filter v-for="k in payTypeList" :key="k.code" :value="k.code" class="mr-3" label>
  49. {{ k.name }}
  50. <svg-icon v-if="k.icon" class="ml-1" :name="k.icon" :size="k.size"></svg-icon>
  51. </v-chip>
  52. </v-chip-group>
  53. </div>
  54. </div>
  55. </div>
  56. <div class="mt-5 ml-2" style="color: var(--v-error-base);">
  57. 扫码支付时请勿离开
  58. <span v-if="remainderZhShow">{{ remainderZhShow }}</span>
  59. </div>
  60. </div>
  61. </div>
  62. </template>
  63. <script setup>
  64. defineOptions({ name: 'membershipPackageBalance' })
  65. import { ref, onUnmounted, nextTick, watch } from 'vue'
  66. import { FenYuanTransform } from '@/utils/position'
  67. import { getEnableCodeList, payOrderSubmit, getOrderPayStatus } from '@/api/common'
  68. import { definePayTypeList, qrCodePay } from '@/utils/payType'
  69. import { rechargeOrderCreate } from '@/api/recruit/enterprise/member/points'
  70. import { getEnterpriseRechargePackageList } from '@/api/recruit/enterprise/member/points'
  71. import { useUserStore } from '@/store/user'; const store = useUserStore()
  72. import Snackbar from '@/plugins/snackbar'
  73. import { useRouter } from 'vue-router'
  74. const router = useRouter()
  75. const current = ref()
  76. const inputValue = ref('')
  77. const payQrCodeTxt = ref('')
  78. const remainderZhShow = ref('') // 倒计时展示
  79. const select = ref({})
  80. const list = ref([])
  81. const getData = async () => {
  82. const data = await getEnterpriseRechargePackageList()
  83. const end = { name: '自定义金额', id: 'custom', placeholder: '请输入', custom: true }
  84. list.value = data ? [...data, end] : [end]
  85. }
  86. const handleClick = (index, item) => {
  87. current.value = index + 1
  88. select.value = item
  89. getUnpaidOrderList()
  90. }
  91. const inputChange = () => {
  92. if (!inputValue.value) return
  93. current.value = list.value.length
  94. const item = list.value[list.value.length-1]
  95. item.payPrice = FenYuanTransform(inputValue.value, 'toCent')
  96. item.id = 'custom' + inputValue.value
  97. select.value = item
  98. getUnpaidOrderList()
  99. }
  100. const timeout = ref(null)
  101. watch(
  102. () => inputValue.value,
  103. (val) => {
  104. let num = val && val !=='0' ? (val.match(/\d+/g)?.join('') || null) : null
  105. if (num > 100000000) {
  106. num = '100000000'
  107. Snackbar.warning('最多不可超过一亿元')
  108. }
  109. inputValue.value = num
  110. clearTimeout(timeout.value)
  111. timeout.value = setTimeout(() => inputChange(), 500) // 防抖
  112. }
  113. )
  114. // 2.发起充值
  115. const loading = ref(true)
  116. const payOrder = ref({})
  117. const getUnpaidOrderList = async () => {
  118. if (select.value.payPrice === undefined) return payQrCodeTxt.value = ''
  119. const params = {
  120. payPrice: (select.value.payPrice-0),
  121. }
  122. if (typeof select.value.id === 'string' && !select.value.id.includes('custom')) params.packageId = select.value.id
  123. const data = await rechargeOrderCreate(params)
  124. payOrder.value = data || {}
  125. paySubmit()
  126. }
  127. const payTypeChange = (val) => {
  128. payType.value = val
  129. paySubmit()
  130. }
  131. const timer = ref(null)
  132. onUnmounted(() => {
  133. if (timer.value) clearInterval(timer.value); timer.value = null
  134. })
  135. // 更新账户余额
  136. const updateAccountInfo = async (init = false) => {
  137. await store.getEnterpriseUserAccountInfo()
  138. if (init) return
  139. loading.value = false
  140. }
  141. const payStatus = async () => {
  142. try {
  143. const data = await getOrderPayStatus({ id: payOrder.value.payOrderId })
  144. if ((data?.status - 0) === 10) {
  145. // 支付成功
  146. if (timer.value) clearInterval(timer.value); timer.value = null
  147. setTimeout(() => {
  148. // 更新点数(充值、发布职位)
  149. updateAccountInfo()
  150. // 清除定时器
  151. clearTimer()
  152. // 支付成功
  153. Snackbar.success('支付成功')
  154. }, 2000);
  155. }
  156. } catch (error) {
  157. console.log(error)
  158. }
  159. }
  160. const paySubmit = async () => {
  161. if (!payType.value) return
  162. try {
  163. if (payOrder.value) {
  164. if (!payOrder.value?.payOrderId) return
  165. // 提交支付订单
  166. const params = {
  167. channelCode: payType.value, // 支付渠道
  168. id: payOrder.value.payOrderId // 支付单编号
  169. }
  170. const res = await payOrderSubmit(params)
  171. payQrCodeTxt.value = res?.displayContent || '' // 生成二维码内容
  172. initIntervalFun()
  173. if (timer.value) clearInterval(timer.value); timer.value = null
  174. timer.value = setInterval(() => { payStatus() }, 1000) // 轮巡查询用户是否支付
  175. }
  176. } catch (error) {
  177. console.log(error)
  178. }
  179. }
  180. // 1.支付方式
  181. const payType = ref('')
  182. const payTypeList = ref([])
  183. const codeList = ref([])
  184. const getCodeList = async () => {
  185. try {
  186. const list = await getEnableCodeList({ appId: 11 })
  187. codeList.value = list || []
  188. } catch (error) {
  189. console.log(error)
  190. } finally {
  191. if (definePayTypeList?.length && codeList.value?.length) {
  192. codeList.value.forEach(code => {
  193. const item = definePayTypeList.find(p => p.code === code)
  194. if (item) {
  195. if (!payType.value) {
  196. // 默认值赋值(暂时只支持扫码)
  197. const bool = qrCodePay.includes(code)
  198. if (bool) payType.value = code
  199. }
  200. payTypeList.value.push(item)
  201. }
  202. })
  203. }
  204. }
  205. }
  206. nextTick(async () => {
  207. await getData()
  208. await getCodeList()
  209. })
  210. const refreshQRCode =() => { // 刷新二维码
  211. getUnpaidOrderList()
  212. }
  213. const remainderTimer = ref(null)
  214. const countdownTime = 60000 * 3 // 倒计时三分钟
  215. let remainder = 0 // number
  216. // 初始化倒计时
  217. const initIntervalFun = () => {
  218. remainder = countdownTime // 初始倒计时时间
  219. if (remainderTimer.value) clearInterval(remainderTimer.value); remainderTimer.value = null // 每一次点击都清除上一个轮询
  220. // 倒计时计算
  221. remainderCalc()
  222. remainderTimer.value = setInterval(() => { remainderCalc() }, 1000)
  223. if (timer.value) clearInterval(timer.value); timer.value = null
  224. timer.value = setInterval(() => { payStatus() }, 2000) // 轮巡查询用户是否支付
  225. }
  226. const formatDuration = (remainder) => {
  227. // 将毫秒转换为秒
  228. var seconds = Math.floor(remainder / 1000)
  229. // 计算分钟和剩余的秒数
  230. var minutes = Math.floor(seconds / 60)
  231. var remainingSeconds = seconds % 60
  232. // 格式化分钟和秒数,确保秒数为两位数(如果小于10,则前面补0)
  233. minutes = minutes.toString().padStart(2, '0')
  234. remainingSeconds = remainingSeconds.toString().padStart(2, '0')
  235. // 返回格式化的字符串
  236. return `${minutes}分${remainingSeconds}秒`
  237. }
  238. const clearTimer = () => {
  239. if (timer.value) clearInterval(timer.value); timer.value = null
  240. if (remainderTimer.value) clearInterval(remainderTimer.value); remainderTimer.value = null
  241. remainderZhShow.value = ''
  242. }
  243. const remainderCalc = () => {
  244. remainder -= 1000
  245. remainderZhShow.value = formatDuration(remainder)
  246. if (remainder <= 0) clearTimer()
  247. }
  248. const handleToOrder = () => {
  249. router.push('/recruit/enterprise/tradingOrder?key=tab_recharge')
  250. }
  251. </script>
  252. <style scoped lang="scss">
  253. .packagesItem {
  254. border: 1px solid var(--color-f3);
  255. border-radius: 8px;
  256. background-color: var(--color-f2f4f742);
  257. }
  258. .dailyPrice {
  259. border-radius: 14px;
  260. background-color: #dde3e94f;
  261. padding: 2px 18px;
  262. color: var(--color-666);
  263. }
  264. .active {
  265. border: 2px solid #00897B;
  266. .priceBox {
  267. color: var(--v-primary-base);
  268. }
  269. .dailyPrice {
  270. color: var(--v-error-base);
  271. background-color: #fff4e7;
  272. }
  273. }
  274. .custom-input-num {
  275. border: none;
  276. outline: none;
  277. background-color: transparent;
  278. width: 120px;
  279. max-width: 120px;
  280. text-align: center;
  281. background-color: #d9d9d98c;
  282. border-radius: 20px;
  283. font-size: 20px;
  284. color: var(--v-primary-base);
  285. }
  286. .code {
  287. max-width: 1100px;
  288. background-color: #f7f8fa;
  289. border-radius: 6px;
  290. margin: 0 auto;
  291. &-left {
  292. border: 1px solid #00897B;
  293. border-radius: 6px;
  294. padding: 5px;
  295. }
  296. &-right {
  297. .price {
  298. font-size: 30px;
  299. font-weight: 700;
  300. color: var(--v-error-base);
  301. }
  302. }
  303. }
  304. .vip {
  305. width: 50px;
  306. height: 50px;
  307. position: absolute;
  308. top: 0;
  309. right: 0;
  310. overflow: hidden;
  311. border-radius: 8px
  312. }
  313. .custom-point-show {
  314. width: 100%;
  315. text-align: center;
  316. font-weight: normal;
  317. color: gray;
  318. font-size: 13px;
  319. }
  320. :deep(.v-slide-group__content) {
  321. background: none !important;
  322. }
  323. </style>