瀏覽代碼

Merge branch 'dev' of https://git.citupro.com/zhengnaiwen_citu/menduner into dev

lifanagju_citu 6 月之前
父節點
當前提交
88d36f2cfc

+ 8 - 0
src/api/enterprise.js

@@ -225,4 +225,12 @@ export const updateGroupUserAccount = async (data) => {
     openEncryption: true,
     data
   })
+}
+
+// 企业-套餐列表
+export const getEnterprisePackageList = async () => {
+  return await request.get({
+    url: '/app-api/menduner/system/enterprise-package/list',
+    openEncryption: true
+  })
 }

+ 70 - 2
src/views/headhunting/components/nav.vue

@@ -8,22 +8,90 @@
         <div class="china cursor-pointer" @click="emit('click', '/headhunting')">中国</div>
       </div>
       <div class="d-flex align-center">
-        <div v-for="(k, index) in navList" :key="index" class="list-item cursor-pointer font-size-15" :class="{'mr-5': index !== navList.length - 1}" @click="emit('click', k.path)">
+        <div v-for="(k, index) in navList" :key="index" class="list-item cursor-pointer font-size-15" :class="{'mr-5': index !== navList.length - 1}" @click="handleClick(k)">
           {{ k.title }}
         </div>
       </div>
     </div>
   </div>
+
+  <!-- 联系我们 -->
+  <CtDialog :visible="showDialog" titleClass="text-h6" :footer="true" :widthType="2" title="联系我们" @submit="handleSubmit" @close="handleClose">
+    <CtForm ref="formPageRef" :items="formItems"></CtForm>
+  </CtDialog>
 </template>
 
 <script setup>
 defineOptions({ name: 'headhunting-nav'})
+import { ref } from 'vue'
+import { huntSubmit } from '@/api/headhunting'
+import Curtain from '@/plugins/curtain'
 
 const emit = defineEmits(['click'])
 const navList = [
   { title: '我们的服务', path: '/headhunting/service' },
-  { title: '候选人', path: '/login' }
+  { title: '候选人', path: '/login' },
+  { title: '联系我们', key: 'contact' }
 ]
+
+const formPageRef = ref()
+const formItems = ref({
+  options: [
+    {
+      type: 'text',
+      key: 'name',
+      value: '',
+      label: '姓名 *',
+      clearable: true,
+      outlined: true,
+      rules: [v => !!v || '请输入姓名']
+    },
+    {
+      type: 'phoneNumber',
+      key: 'phone',
+      value: '',
+      clearable: true,
+      label: '联系手机号 *',
+      rules: [v => !!v || '请填写联系手机号']
+    },
+    {
+      type: 'text',
+      key: 'enterpriseName',
+      value: '',
+      label: '企业名称 *',
+      clearable: true,
+      outlined: true,
+      rules: [v => !!v || '请输入企业名称']
+    },
+  ]
+})
+
+const showDialog = ref(false)
+const handleClick = (k) => {
+  if (k.path) emit('click', k.path)
+  if (k.key === 'contact') showDialog.value = true
+}
+
+// 联系我们
+const handleClose = () => {
+  showDialog.value = false
+  formItems.value.options.forEach(e => e.value = '')
+}
+
+const handleSubmit = async () => {
+  const { valid } = await formPageRef.value.formRef.validate()
+  if (!valid) return
+  const obj = {}
+  formItems.value.options.forEach(e => obj[e.key] = e.value)
+  await huntSubmit(obj)
+  handleClose()
+  Curtain('message', {
+    message: '提交成功,我们会尽快与您联系',
+    name: 'submit',
+    color: '#00897B',
+    iconFontSize: 300
+  })
+}
 </script>
 
 <style scoped lang="scss">

+ 8 - 3
src/views/login/index.vue

@@ -4,8 +4,8 @@
       <div class="login-content">
         <v-card height="392px" class="carousel mr-3" style="width: 792px; border-radius: 8px;">
           <v-carousel show-arrows="hover" cycle>
-            <v-carousel-item v-for="(item, i) in carouselList" :key="i">
-              <div style="height: 392px; overflow: hidden;">
+            <v-carousel-item v-for="(item, i) in carouselList" :key="i" @click="handleClick(item)">
+              <div style="height: 392px; overflow: hidden;" :class="{'cursor-pointer': item.link}">
                 <v-img :src="item.src" :lazy-src="item.src" cover style="height: 100%; overflow: hidden;">
                   <template v-slot:placeholder>
                     <v-row align="center" class="fill-height ma-0" justify="center">
@@ -225,9 +225,14 @@ const carouselList = ref([
   { src: 'https://minio.citupro.com/dev/menduner/preferredGroup/SWISS-HOTEL-MANAGEMENT-SCHOOL-MBA.jpg'},
   { src: 'https://minio.citupro.com/dev/menduner/preferredGroup/Hong-Kong-Polytechnic-University-banner.jpg' },
   { src: 'https://minio.menduner.com/dev/menduner/Grand-Mercure.jpg' },
-  { src: 'https://minio.menduner.com/dev/5cca497a87187f541359c6b327a217b963e397dc39042327be5cc1cbc9447818.jpg' }
+  { src: 'https://minio.menduner.com/dev/5cca497a87187f541359c6b327a217b963e397dc39042327be5cc1cbc9447818.jpg' },
+  { src: 'https://minio.menduner.com/dev/fbff08c5608d746380f859a97241109fe4726f776a25301d7d0fb43d9ba7df67.png', link: 'https://mp.weixin.qq.com/s/CBv7xhWopp8uby4yS5ojsA' }
 ])
 
+const handleClick = (item) => {
+  if (item.link) window.open(item.link)
+}
+
 // 获取验证码
 const verify = ref()
 const getCode = async () => {

+ 10 - 8
src/views/mall/purchasePackage/components/packageList.vue

@@ -1,14 +1,14 @@
 <!--  -->
 <template>
   <div class="d-flex mt-5 list">
-    <div v-for="(val, i) in packDataList" :key="i" class="list-item cursor-pointer" :class="{'active': active === i }" @click="handleClickItem(val, i)">
+    <div v-for="(val, i) in packDataList" :key="i" class="list-item cursor-pointer elevation-2" :class="{'active': active === i }" @click="handleClickItem(val, i)">
       <div v-if="val.id === userStore.userInfo?.vipFlag && userStore.userInfo?.vipExpireDate && userStore.userInfo?.vipExpireDate > Date.now()" class="recommend long">我的套餐</div>
       <div v-if="val.recommend" class="recommend">推荐</div>
       <div class="text-center font-weight-bold">{{ val.name }}</div>
       <div class="text-center my-5">
         <div v-if="val.price && !val.cycle">
-          <span class="font-weight-bold font-size-20">{{ val.price }}</span>
+          <span class="font-weight-bold font-size-20">{{ val.price / 100 }}</span>
           <!-- /年 -->
         </div>
         <div v-if="val.cycle">¥<span class="font-weight-bold font-size-18 font-size-20">{{ val.price }}</span><span class="font-size-14 mr-3">起</span>  {{ val.cycle }}</div>
@@ -47,7 +47,8 @@
         <v-btn
           color="error"
           variant="outlined"
-          rounded 
+          rounded
+          :disabled="Number(val.id) < Number(userStore.userInfo?.vipFlag)"
           :loading="val.loading"
           @click="createOrder(val)"
         >开通会员</v-btn>
@@ -57,7 +58,7 @@
   
   <CtDialog :visible="open" :widthType="3" :footer="false" titleClass="text-h6" title="开通会员" @close="open = false">
     <m-pay
-      :payPrice="payPrice"
+      :payPrice="payPrice / 100"
       :qrCode="qrCode"
       :disabled="disabled"
       :expirationTime="expirationTime"
@@ -106,10 +107,10 @@ const packDataList = ref([])
 const getData = async () => {
   const data = await getMembershipPackageList()
   if (!data?.length) return
-  let vipFlagIndex = null
+  // let vipFlagIndex = null
   const list = data.map((item, index) => {
     item.id = item.id?.toString()
-    if (item.id === userStore.userInfo?.vipFlag) vipFlagIndex = index // 低于当前套餐的(套餐)不展示
+    // if (item.id === userStore.userInfo?.vipFlag) vipFlagIndex = index // 低于当前套餐的(套餐)不展示
     if (item.recommend) active.value = index // 推荐套餐
     return {
       ...item,
@@ -119,7 +120,8 @@ const getData = async () => {
     }
   })
   // 低于当前套餐的(套餐)不展示
-  packDataList.value = vipFlagIndex ? list.slice(vipFlagIndex) : list
+  // packDataList.value = vipFlagIndex ? list.slice(vipFlagIndex) : list
+  packDataList.value = list
 }
 getData()
 
@@ -148,7 +150,7 @@ async function createOrder (val) {
     await orderCreated({
       spuId: val.id, // 商品编号
       spuName: val.name, // 商品名称
-      price: val.price * 100, // 价格
+      price: val.price, // 价格
       type: val.type // 订单类型 0平台订单|1求职端订单|2招聘端订单|3会员套餐
     })
 

+ 4 - 5
src/views/recruit/enterprise/membershipPackage/dynamic/balance.vue

@@ -1,5 +1,6 @@
 <template>
   <div>
+    <div class="text-end color-primary my-3 text-decoration-underline cursor-pointer" @click="handleToOrder">充值记录<v-icon>mdi-chevron-double-right</v-icon></div>
     <div class="d-flex align-center justify-center mt-5">
       <div
         v-for="(item, index) in list"
@@ -29,7 +30,7 @@
         </div> 
       </div>
     </div>
-    <div class="text-end color-primary my-3 text-decoration-underline cursor-pointer" @click="handleToOrder">充值记录<v-icon>mdi-chevron-double-right</v-icon></div>
+    <div v-if="!Object.keys(select).length" class="color-warning text-center mt-10 font-size-14">请选择要充值的金额</div>
     <div v-if="payType && payQrCodeTxt" class="code pa-5 resume-box">
       <div class="resume-header">
         <div class="resume-title">扫码支付</div>
@@ -75,7 +76,7 @@ import Snackbar from '@/plugins/snackbar'
 import { useRouter } from 'vue-router'
 
 const router = useRouter()
-const current = ref(1)
+const current = ref()
 const inputValue = ref('')
 const payQrCodeTxt = ref('')
 const remainderZhShow = ref('') // 倒计时展示
@@ -85,7 +86,6 @@ const getData = async () => {
   const data = await getEnterpriseRechargePackageList()
   const end = { name: '自定义金额', id: 'custom', placeholder: '请输入', custom: true }
   list.value = data ? [...data, end] : [end]
-  select.value = list.value[0]
 }
 
 const handleClick = (index, item) => {
@@ -208,13 +208,12 @@ const getCodeList = async () => {
           if (!payType.value) {
             // 默认值赋值(暂时只支持扫码)
             const bool = qrCodePay.includes(code)
-            if (bool) payTypeChange(code)
+            if (bool) payType.value = code
           }
           payTypeList.value.push(item)
         }
       })
     }
-    getUnpaidOrderList()
   }
 }
 nextTick(async () => {

+ 229 - 15
src/views/recruit/enterprise/membershipPackage/dynamic/package.vue

@@ -1,29 +1,226 @@
 <template>
-  <div class="d-flex list">
-    <div v-for="(val, index) in list" :key="index" class="list-item text-center cursor-pointer" :class="{'active': index === active}" @click="active = index">
-      <h4 class="mt-5">{{ val.title }}</h4>
+  <div class="d-flex list mb-3">
+    <div v-for="(val, index) in list" :key="index" class="list-item text-center cursor-pointer" :class="{'active': index === current}" @click="handleClick(index, val)">
+      <h4 class="mt-5">{{ val.name }}</h4>
       <div class="color-primary">
         <span>¥</span>
-        <span style="font-size: 35px;">{{ val.price }}</span>
+        <span style="font-size: 35px;">{{ val.price / 100 }}</span>
         <span> 元</span>
       </div>
-      <div class="text-decoration-line-through color-666">原价:{{ val.originalPrice }}元</div>
-      <div class="font-size-14 color-999 mt-3 periodValidity py-2">有效期:120天</div>
+      <div class="text-decoration-line-through color-666">原价:{{ val.originalPrice / 100 }}元</div>
+      <div class="font-size-14 color-999 mt-3 periodValidity py-2">有效期:{{ val.day }}天</div>
+    </div>
+  </div>
+  <div v-if="!Object.keys(select).length" class="color-warning text-center mt-10 font-size-14">请选择要购买的套餐</div>
+  <div v-if="payType && payQrCodeTxt" class="code pa-5 resume-box">
+    <div class="resume-header">
+      <div class="resume-title">扫码支付</div>
+    </div>
+    <div class="d-flex align-end mt-3">
+      <div class="code-left">
+        <QrCode :text="payQrCodeTxt" :disabled="!remainderTimer" :width="170" @refresh="refreshQRCode" />
+      </div>
+      <div class="code-right ml-5">
+        <div class="price">
+          <span class="font-size-13">¥</span>
+          {{ FenYuanTransform(select?.price || 0) }}元
+        </div>
+        <div class="mt-3 d-flex align-center">
+          <span class="color-666 font-weight-bold mr-5">支付方式</span>
+          <v-chip-group v-model="payType" selected-class="text-primary" column mandatory @update:modelValue="payTypeChange">
+            <v-chip filter v-for="k in payTypeList" :key="k.code" :value="k.code" class="mr-3" label>
+              {{ k.name }}
+              <svg-icon v-if="k.icon" class="ml-1" :name="k.icon" :size="k.size"></svg-icon>
+            </v-chip>
+          </v-chip-group>
+        </div>
+      </div>
+    </div>
+    <div class="mt-5 ml-2" style="color: var(--v-error-base);">
+      扫码支付时请勿离开
+      <span v-if="remainderZhShow">{{ remainderZhShow }}</span>
     </div>
   </div>
 </template>
 
 <script setup>
 defineOptions({ name: 'membershipPackageDynamicPackage' })
-import { ref } from 'vue'
-
-const active = ref(0)
-const list = [
-  { title: '1个职位', price: 399, originalPrice: 599 },
-  { title: '2个职位', price: 599, originalPrice: 799 },
-  { title: '3个职位', price: 799, originalPrice: 999 },
-  { title: '5个职位', price: 999, originalPrice: 1299 }
-]
+import { ref, onUnmounted, nextTick } from 'vue'
+import { getEnterprisePackageList } from '@/api/enterprise'
+import { FenYuanTransform } from '@/utils/position'
+import { getEnableCodeList, payOrderSubmit, getOrderPayStatus, getUnpaidOrder } from '@/api/common'
+import { definePayTypeList, qrCodePay } from '@/utils/payType'
+import { useUserStore } from '@/store/user'; const store = useUserStore()
+import Snackbar from '@/plugins/snackbar'
+import { createTradeOrder } from '@/api/position'
+
+const current = ref()
+const select = ref({})
+
+// 套餐列表
+const list = ref([])
+const getPackageList = async () => {
+  const data = await getEnterprisePackageList()
+  list.value = data
+}
+
+const handleClick = (index, val) => {
+  current.value = index
+  select.value = val
+  getUnpaidOrderList()
+}
+
+const payQrCodeTxt = ref('')
+
+// 2.发起充值
+const loading = ref(true)
+const payOrder = ref({})
+let maxCount = 0
+const getUnpaidOrderList = async () => {
+  const data = await getUnpaidOrder({ spuId: select.value.id, type: 4 })
+  if (!data) {
+    await createTradeOrder({ price: (select.value.price-0), spuId: select.value.id, spuName: select.value.name, type: 4 })
+    if (maxCount > 3) return // 避免死循环
+    maxCount++
+    setTimeout(() => {
+      getUnpaidOrderList()
+    }, 1000)
+  }
+  payOrder.value = data?.payOrder || null
+  paySubmit()
+}
+
+const payTypeChange = (val) => {
+  payType.value = val
+  getUnpaidOrderList()
+}
+const timer = ref(null)
+onUnmounted(() => {
+  if (timer.value) clearInterval(timer.value); timer.value = null
+})
+
+// 更新职位可发布数量
+const updateAccountInfo = async (init = false) => {
+  await store.getEnterpriseInfo(true)
+  if (init) return
+  loading.value = false
+}
+
+const payStatus = async () => {
+  try {
+    const data = await getOrderPayStatus({ id: payOrder.value.id })
+    if ((data?.status - 0) === 10) {
+      // 支付成功
+      if (timer.value) clearInterval(timer.value); timer.value = null
+      setTimeout(() => {
+        // 更新点数(充值、发布职位)
+        updateAccountInfo()
+        // 清除定时器
+        clearTimer()
+        // 支付成功
+        Snackbar.success('支付成功')
+      }, 2000);
+    }
+  } catch (error) {
+    console.log(error)
+  }
+}
+
+const paySubmit = async () => {
+  if (!payType.value) return
+  try {
+    // 提交支付订单
+    const params = {
+      channelCode: payType.value, // 支付渠道
+      id: payOrder.value.id
+    }
+    const res = await payOrderSubmit(params)
+    payQrCodeTxt.value = res?.displayContent || '' // 生成二维码内容
+    
+    initIntervalFun()
+    if (timer.value) clearInterval(timer.value); timer.value = null
+    timer.value = setInterval(() => { payStatus() }, 1000) // 轮巡查询用户是否支付
+  } catch (error) {
+    console.log(error)
+  }
+}
+
+// 1.支付方式
+const payType = ref('')
+const payTypeList = ref([])
+const codeList = ref([])
+const getCodeList = async () => {
+  try {
+    const list = await getEnableCodeList({ appId: 11 })
+    codeList.value = list || []
+  } catch (error) {
+    console.log(error)
+  } finally {
+    if (definePayTypeList?.length && codeList.value?.length) {
+      codeList.value.forEach(code => {
+        const item = definePayTypeList.find(p => p.code === code)
+        if (item) {
+          if (!payType.value) {
+            // 默认值赋值(暂时只支持扫码)
+            const bool = qrCodePay.includes(code)
+            if (bool) payType.value = code
+          }
+          payTypeList.value.push(item)
+        }
+      })
+    }
+  }
+}
+
+nextTick(async () => {
+  await getPackageList()
+  await getCodeList()
+})
+
+const refreshQRCode =() => { // 刷新二维码
+  getUnpaidOrderList()
+}
+
+const remainderTimer = ref(null)
+const countdownTime = 60000 * 3 // 倒计时三分钟
+let remainder = 0 // number
+// 初始化倒计时
+const initIntervalFun = () => {
+  remainder = countdownTime // 初始倒计时时间
+  if (remainderTimer.value) clearInterval(remainderTimer.value); remainderTimer.value = null // 每一次点击都清除上一个轮询
+  // 倒计时计算
+  remainderCalc()
+  remainderTimer.value = setInterval(() => { remainderCalc() }, 1000)
+
+  if (timer.value) clearInterval(timer.value); timer.value = null
+  timer.value = setInterval(() => { payStatus() }, 2000) // 轮巡查询用户是否支付
+}
+
+const formatDuration = (remainder) => {
+  // 将毫秒转换为秒
+  var seconds = Math.floor(remainder / 1000)
+  // 计算分钟和剩余的秒数
+  var minutes = Math.floor(seconds / 60)
+  var remainingSeconds = seconds % 60
+  // 格式化分钟和秒数,确保秒数为两位数(如果小于10,则前面补0)
+  minutes = minutes.toString().padStart(2, '0')
+  remainingSeconds = remainingSeconds.toString().padStart(2, '0')
+  // 返回格式化的字符串
+  return `${minutes}分${remainingSeconds}秒`
+}
+
+const remainderZhShow = ref('')
+const clearTimer = () => {
+  if (timer.value) clearInterval(timer.value); timer.value = null
+  if (remainderTimer.value) clearInterval(remainderTimer.value); remainderTimer.value = null
+  remainderZhShow.value = ''
+}
+
+const remainderCalc = () => {
+  remainder -= 1000
+  remainderZhShow.value = formatDuration(remainder)
+  if (remainder <= 0) clearTimer()
+}
 </script>
 
 <style scoped lang="scss">
@@ -47,4 +244,21 @@ const list = [
     border: 1px solid #00897B;
   }
 }
+.code {
+  background-color: #f7f8fa;
+  border-radius: 6px;
+  margin: 0 auto;
+  &-left {
+    border: 1px solid #00897B;
+    border-radius: 6px;
+    padding: 5px;
+  }
+  &-right {
+    .price {
+      font-size: 30px;
+      font-weight: 700;
+      color: var(--v-error-base);
+    }
+  }
+}
 </style>

+ 8 - 3
src/views/recruit/personal/home/components/homeJobTypeCard/index.vue

@@ -55,8 +55,8 @@
     </v-card>
     <v-card height="392px" class="card rightCardBox">
       <v-carousel show-arrows="hover" cycle>
-        <v-carousel-item v-for="(item, i) in carouselList" :key="i">
-          <div style="height: 392px; overflow: hidden;">
+        <v-carousel-item v-for="(item, i) in carouselList" :key="i" @click="handleClick(item)">
+          <div style="height: 392px; overflow: hidden;" :class="{'cursor-pointer': item.link}">
             <v-img :src="item.src" :lazy-src="item.src" cover style="height: 100%; overflow: hidden;">
               <template v-slot:placeholder>
                 <v-row align="center" class="fill-height ma-0" justify="center">
@@ -150,8 +150,13 @@ const carouselList = ref([
   { src: 'https://minio.citupro.com/dev/menduner/preferredGroup/SWISS-HOTEL-MANAGEMENT-SCHOOL-MBA.jpg'},
   { src: 'https://minio.citupro.com/dev/menduner/preferredGroup/Hong-Kong-Polytechnic-University-banner.jpg' },
   { src: 'https://minio.menduner.com/dev/menduner/Grand-Mercure.jpg' },
-  { src: 'https://minio.menduner.com/dev/5cca497a87187f541359c6b327a217b963e397dc39042327be5cc1cbc9447818.jpg' }
+  { src: 'https://minio.menduner.com/dev/5cca497a87187f541359c6b327a217b963e397dc39042327be5cc1cbc9447818.jpg' },
+  { src: 'https://minio.menduner.com/dev/fbff08c5608d746380f859a97241109fe4726f776a25301d7d0fb43d9ba7df67.png', link: 'https://mp.weixin.qq.com/s/CBv7xhWopp8uby4yS5ojsA' }
 ])
+
+const handleClick = (item) => {
+  if (item.link) window.open(item.link)
+}
 </script>
 
 <style lang="scss" scoped>