package.vue 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. <template>
  2. <v-slide-group v-model="model" center-active show-arrows>
  3. <v-slide-group-item v-for="(val, index) in list" :key="index" v-slot="{ isSelected }">
  4. <v-card :class="{'active': isSelected}" class="ma-4 list-item elevation-2 text-center" height="175" @click="handleClick(index, val)">
  5. <template v-if="val.id === 'custom'">
  6. <div class="d-flex flex-column algin-center justify-center" style="height: 100%" :style="{'color': isSelected ? '#fff' : '#333'}">
  7. <div>需要发布更多职位</div>
  8. <div>请联系门墩儿购买企业套餐</div>
  9. <div>点击查看联系方式</div>
  10. </div>
  11. </template>
  12. <template v-else>
  13. <h4 class="mt-5" :style="{'color': isSelected ? '#fff' : '#333'}">{{ val.name }}</h4>
  14. <div :style="{'color': isSelected ? '#fff' : '#00897B'}">
  15. <span>¥</span>
  16. <span style="font-size: 35px;">{{ val.price / 100 }}</span>
  17. <span> 元</span>
  18. </div>
  19. <div class="text-decoration-line-through" :style="{'color': isSelected ? '#fff' : '#666'}">原价:{{ val.originalPrice / 100 }}元</div>
  20. <div class="font-size-14 mt-3 py-2" :style="{'color': isSelected ? '#fff' : '#999'}">有效期:{{ val.day }}天</div>
  21. </template>
  22. </v-card>
  23. </v-slide-group-item>
  24. </v-slide-group>
  25. <div v-if="payType && payQrCodeTxt" id="codeBox" class="code pa-5 resume-box">
  26. <div class="resume-header">
  27. <div class="resume-title">扫码支付</div>
  28. </div>
  29. <div class="d-flex justify-center">
  30. <div id="codeItem" class="d-flex flex-column align-center my-2">
  31. <div class="d-flex align-center">
  32. <span class="color-666 font-weight-bold">支付方式:</span>
  33. <v-chip-group v-model="payType" selected-class="text-primary" column mandatory @update:modelValue="payTypeChange">
  34. <v-chip filter v-for="k in payTypeList" :key="k.code" :value="k.code" class="mr-3" label>
  35. {{ k.name }}
  36. <svg-icon v-if="k.icon" class="ml-1" :name="k.icon" :size="k.size"></svg-icon>
  37. </v-chip>
  38. </v-chip-group>
  39. </div>
  40. <div class="code-right">
  41. <div class="price">
  42. <span class="font-size-13">¥</span>
  43. {{ FenYuanTransform(select?.price || 0) }}元
  44. </div>
  45. </div>
  46. <div class="code-left">
  47. <QrCode :text="payQrCodeTxt" :disabled="!remainderTimer" :width="170" @refresh="refreshQRCode" />
  48. </div>
  49. <div class="mt-52" style="color: var(--v-error-base);">
  50. 扫码支付时请勿离开
  51. <span v-if="remainderZhShow">{{ remainderZhShow }}</span>
  52. </div>
  53. </div>
  54. </div>
  55. </div>
  56. <div v-if="showCustom" id="codeBox" class="code pa-5 resume-box">
  57. <div style="width: 100%;">
  58. <div class="text-center">请扫码添加下方企业微信联系我们:</div>
  59. <div class="my-3" style="width: 180px; height: 180px; margin: auto;">
  60. <v-img src="https://minio.menduner.com/dev/menduner/contact.png" width="180" height="180"></v-img>
  61. </div>
  62. <div class="text-center mt-2">潘青海先生(Peter Pan)</div>
  63. </div>
  64. </div>
  65. </template>
  66. <script setup>
  67. defineOptions({ name: 'membershipPackageDynamicPackage' })
  68. import { ref, onUnmounted, nextTick } from 'vue'
  69. import { getEnterprisePackageList } from '@/api/enterprise'
  70. import { FenYuanTransform } from '@/utils/position'
  71. import { getEnableCodeList, payOrderSubmit, getOrderPayStatus, getUnpaidOrder } from '@/api/common'
  72. import { definePayTypeList, qrCodePay } from '@/utils/payType'
  73. import { useUserStore } from '@/store/user'; const store = useUserStore()
  74. import Snackbar from '@/plugins/snackbar'
  75. import { createTradeOrder } from '@/api/position'
  76. import { useRoute } from 'vue-router'; const route = useRoute()
  77. import { useRouter } from 'vue-router'; const router = useRouter()
  78. import Confirm from '@/plugins/confirm'
  79. import { useI18n } from '@/hooks/web/useI18n'; const { t } = useI18n()
  80. const current = ref()
  81. const select = ref({})
  82. const model = ref(0)
  83. // 套餐列表
  84. const list = ref([])
  85. const getPackageList = async () => {
  86. const data = await getEnterprisePackageList()
  87. list.value = data
  88. list.value.push({ id:'custom' })
  89. select.value = data[0]
  90. }
  91. const showCustom = ref(false)
  92. const handleClick = (index, val) => {
  93. model.value = index
  94. if (val.id == 'custom') {
  95. payQrCodeTxt.value = ''
  96. showCustom.value = true
  97. clearTimer()
  98. return
  99. }
  100. showCustom.value = false
  101. payQrCodeTxt.value = ''
  102. current.value = index
  103. select.value = val
  104. getUnpaidOrderList()
  105. }
  106. const payQrCodeTxt = ref('')
  107. // 2.发起充值
  108. const loading = ref(true)
  109. const payOrder = ref({})
  110. let maxCount = 0
  111. const getUnpaidOrderList = async () => {
  112. const data = await getUnpaidOrder({ spuId: select.value.id, type: 4 })
  113. if (!data) {
  114. await createTradeOrder({ price: (select.value.price-0), spuId: select.value.id, spuName: select.value.name, type: 4 })
  115. if (maxCount > 3) return // 避免死循环
  116. maxCount++
  117. setTimeout(() => {
  118. getUnpaidOrderList()
  119. }, 1000)
  120. }
  121. payOrder.value = data?.payOrder || null
  122. paySubmit()
  123. }
  124. const payTypeChange = (val) => {
  125. payType.value = val
  126. getUnpaidOrderList()
  127. }
  128. const timer = ref(null)
  129. onUnmounted(() => {
  130. if (timer.value) clearInterval(timer.value); timer.value = null
  131. })
  132. // 更新职位可发布数量
  133. const updateAccountInfo = async (init = false) => {
  134. await store.getEnterpriseInfo(true)
  135. if (init) return
  136. loading.value = false
  137. }
  138. const fromName = ref(route.query?.fromName || '')
  139. const callBackUrl = () => {
  140. const urls = {
  141. position: '/recruit/enterprise/position',
  142. positionPay: '/recruit/enterprise/position?tab=0',
  143. }
  144. const texts = {
  145. position: '职位管理页面',
  146. positionPay: '职位管理待发布页面'
  147. }
  148. const url = fromName.value ? urls[fromName.value] : -1
  149. const text = fromName.value ? texts[fromName.value] : '购买前页面'
  150. //
  151. Confirm(t('common.confirmTitle'), `支付成功!是否返回${text}?`).then(() => {
  152. router.push(url)
  153. })
  154. }
  155. const payStatus = async () => {
  156. try {
  157. const data = await getOrderPayStatus({ id: payOrder.value.id })
  158. if ((data?.status - 0) === 10) {
  159. // 支付成功
  160. if (timer.value) clearInterval(timer.value); timer.value = null
  161. setTimeout(() => {
  162. // 更新点数(充值、发布职位)
  163. updateAccountInfo()
  164. // 清除定时器
  165. clearTimer()
  166. // 支付成功
  167. if (fromName.value) callBackUrl()
  168. else Snackbar.success('支付成功')
  169. }, 2000);
  170. }
  171. } catch (error) {
  172. console.log(error)
  173. }
  174. }
  175. const paySubmit = async () => {
  176. if (!payType.value) return
  177. try {
  178. // 提交支付订单
  179. const params = {
  180. channelCode: payType.value, // 支付渠道
  181. id: payOrder.value.id
  182. }
  183. const res = await payOrderSubmit(params)
  184. payQrCodeTxt.value = res?.displayContent || '' // 生成二维码内容
  185. initIntervalFun()
  186. if (timer.value) clearInterval(timer.value); timer.value = null
  187. timer.value = setInterval(() => { payStatus() }, 1000) // 轮巡查询用户是否支付
  188. } catch (error) {
  189. console.log(error)
  190. }
  191. }
  192. // 1.支付方式
  193. const payType = ref('')
  194. const payTypeList = ref([])
  195. const codeList = ref([])
  196. const getCodeList = async () => {
  197. try {
  198. const list = await getEnableCodeList({ appId: 11 })
  199. codeList.value = list || []
  200. } catch (error) {
  201. console.log(error)
  202. } finally {
  203. if (definePayTypeList?.length && codeList.value?.length) {
  204. codeList.value.forEach(code => {
  205. const item = definePayTypeList.find(p => p.code === code)
  206. if (item) {
  207. if (!payType.value) {
  208. // 默认值赋值(暂时只支持扫码)
  209. const bool = qrCodePay.includes(code)
  210. // if (bool) payType.value = code
  211. if (bool) payTypeChange(code)
  212. }
  213. payTypeList.value.push(item)
  214. }
  215. })
  216. }
  217. }
  218. }
  219. nextTick(async () => {
  220. await getPackageList()
  221. await getCodeList()
  222. })
  223. const refreshQRCode =() => { // 刷新二维码
  224. getUnpaidOrderList()
  225. }
  226. const remainderTimer = ref(null)
  227. const countdownTime = 60000 * 3 // 倒计时三分钟
  228. let remainder = 0 // number
  229. // 初始化倒计时
  230. const initIntervalFun = () => {
  231. remainder = countdownTime // 初始倒计时时间
  232. if (remainderTimer.value) clearInterval(remainderTimer.value); remainderTimer.value = null // 每一次点击都清除上一个轮询
  233. // 倒计时计算
  234. remainderCalc()
  235. remainderTimer.value = setInterval(() => { remainderCalc() }, 1000)
  236. if (timer.value) clearInterval(timer.value); timer.value = null
  237. timer.value = setInterval(() => { payStatus() }, 2000) // 轮巡查询用户是否支付
  238. }
  239. const formatDuration = (remainder) => {
  240. // 将毫秒转换为秒
  241. var seconds = Math.floor(remainder / 1000)
  242. // 计算分钟和剩余的秒数
  243. var minutes = Math.floor(seconds / 60)
  244. var remainingSeconds = seconds % 60
  245. // 格式化分钟和秒数,确保秒数为两位数(如果小于10,则前面补0)
  246. minutes = minutes.toString().padStart(2, '0')
  247. remainingSeconds = remainingSeconds.toString().padStart(2, '0')
  248. // 返回格式化的字符串
  249. return `${minutes}分${remainingSeconds}秒`
  250. }
  251. const remainderZhShow = ref('')
  252. const clearTimer = () => {
  253. if (timer.value) clearInterval(timer.value); timer.value = null
  254. if (remainderTimer.value) clearInterval(remainderTimer.value); remainderTimer.value = null
  255. remainderZhShow.value = ''
  256. }
  257. const remainderCalc = () => {
  258. remainder -= 1000
  259. remainderZhShow.value = formatDuration(remainder)
  260. if (remainder <= 0) clearTimer()
  261. }
  262. </script>
  263. <style scoped lang="scss">
  264. .list-item {
  265. width: 250px;
  266. background-color: #f7f8fa;
  267. }
  268. .active {
  269. background-color: #00897B;
  270. }
  271. .code {
  272. background-color: #f7f8fa;
  273. border-radius: 6px;
  274. margin: 0 auto;
  275. &-left {
  276. border: 1px solid #00897B;
  277. border-radius: 6px;
  278. padding: 5px;
  279. }
  280. &-right {
  281. .price {
  282. font-size: 30px;
  283. font-weight: 700;
  284. color: var(--v-error-base);
  285. }
  286. }
  287. }
  288. </style>