Преглед на файлове

职位发布套餐样式调整

Xiao_123 преди 5 месеца
родител
ревизия
45d97e734e

+ 11 - 0
src/styles/index.css

@@ -86,6 +86,10 @@
   color: #fb8c00;
 }
 
+.color-white {
+  color: #fff;
+}
+
 .font-size-12 {
   font-size: 12px;
 }
@@ -176,6 +180,13 @@
   margin: 0 10px;
 }
 
+.absolute-center {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+}
+
 .resume-box {
   border-radius: 5px;
   padding: 20px 30px;

Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
src/styles/index.min.css


+ 1 - 0
src/styles/index.scss

@@ -48,6 +48,7 @@
 .color-error { color: #fe574a; }
 .color-primary { color: #00897B; }
 .color-warning { color: #fb8c00; }
+.color-white { color: #fff; }
 
 .font-size-12 { font-size: 12px; }
 .font-size-13 { font-size: 13px; }

+ 355 - 0
src/views/recruit/enterprise/membershipPackage/dynamic/package-copy.vue

@@ -0,0 +1,355 @@
+<template>
+  <div class="d-flex list mb-3">
+    <div v-for="(val, index) in list" :key="index" :id="'positioning'+index" class="list-item text-center cursor-pointer" :class="{'active': index === current}" @click="handleClick(index, val)">
+      <template v-if="val.id === 'custom'">
+        <div class="d-flex flex-column algin-center justify-center" style="height: 100%">
+          <div>需要发布更多职位</div>
+          <div>请联系门墩儿购买企业套餐</div>
+          <div style="width: 100%; position: relative;">
+            <div style="width: 80px; height: 80px; margin: auto; opacity: 0.5;">
+              <v-img src="https://minio.menduner.com/dev/menduner/contact.png" width="80" height="80"></v-img>
+            </div>
+            <!-- <div style="opacity: 0.5;"><v-icon size="60">mdi-qrcode</v-icon></div> -->
+            <div class="absolute-center" style="color: #444; margin: 3px 0 0 2px;"><v-icon size="30">mdi-magnify-plus-outline</v-icon></div>
+          </div>
+        </div>
+      </template>
+      <template v-else>
+        <h4 class="mt-5">{{ val.name }}</h4>
+        <div class="color-primary">
+          <span>¥</span>
+          <span style="font-size: 35px;">{{ val.price / 100 }}</span>
+          <span> 元</span>
+        </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>
+      </template>
+    </div>
+  </div>
+  <div v-if="!Object.keys(select).length && !showCustom" class="color-warning text-center mt-15 font-size-20">请选择要购买的套餐</div>
+  <div v-if="payType && payQrCodeTxt" id="codeBox" class="code pa-5 resume-box">
+    <!-- <div class="resume-header">
+      <div class="resume-title">扫码支付</div>
+    </div> -->
+    <div class="d-flex" :style="{'margin-left': offsetCalc + 'px'}">
+      <div id="codeItem" class="d-flex flex-column align-center my-2">
+        <div class="d-flex align-center">
+          <span class="color-666 font-weight-bold">支付方式:</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 class="code-right">
+          <div class="price">
+            <span class="font-size-13">¥</span>
+            {{ FenYuanTransform(select?.price || 0) }}元
+          </div>
+        </div>
+        <div class="code-left">
+          <QrCode :text="payQrCodeTxt" :disabled="!remainderTimer" :width="170" @refresh="refreshQRCode" />
+        </div>
+        <div class="mt-52" style="color: var(--v-error-base);">
+          扫码支付时请勿离开
+          <span v-if="remainderZhShow">{{ remainderZhShow }}</span>
+        </div>
+      </div>
+    </div>
+  </div>
+  <div v-if="showCustom" id="codeBox" class="code pa-5 resume-box">
+    <div style="width: 100%;">
+      <div class="text-center">请扫码添加下方企业微信联系我们:</div>
+      <div class="my-3" style="width: 180px; height: 180px; margin: auto;">
+        <v-img src="https://minio.menduner.com/dev/menduner/contact.png" width="180" height="180"></v-img>
+      </div>
+      <div class="text-center mt-2">潘青海先生(Peter Pan)</div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+defineOptions({ name: 'membershipPackageDynamicPackage' })
+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'
+import { useRoute } from 'vue-router'; const route = useRoute()
+import { useRouter } from 'vue-router'; const router = useRouter()
+import Confirm from '@/plugins/confirm'
+import { useI18n } from '@/hooks/web/useI18n'; const { t } = useI18n()
+
+const current = ref()
+const select = ref({})
+
+// 套餐列表
+const list = ref([])
+const getPackageList = async () => {
+  const data = await getEnterprisePackageList()
+  list.value = data
+  list.value.push({id:'custom'})
+}
+
+const offsetCalc = ref(0)
+let codeItemOffsetWidth = null
+const codeBoxPx = 40 // codeBox左右内边距
+const calcStyle = (index) => {
+  nextTick(() => {
+    const codeBox = document.getElementById('codeBox')
+    const codeBoxOffsetWidth = codeBox.offsetWidth - codeBoxPx
+    if (!codeItemOffsetWidth) {
+      const codeItem = document.getElementById('codeItem')
+      codeItemOffsetWidth = codeItem.offsetWidth
+    }
+    //
+    const menu = document.getElementById(`positioning${index}`)
+    offsetCalc.value = 0
+    if (menu && codeBoxOffsetWidth && codeItemOffsetWidth) {
+      const menuHalfWidth= menu.offsetWidth/2
+      const codeItemHalfWidth = codeItemOffsetWidth/2
+      const calcNum = (menuHalfWidth+menuHalfWidth*2*index - codeItemHalfWidth) + (index ? 6 : 0)
+      //
+      if (calcNum+codeItemOffsetWidth > codeBoxOffsetWidth) { // 超出右侧,取最大
+        offsetCalc.value = codeBoxOffsetWidth-codeItemOffsetWidth
+      } else if (calcNum < 0) { // 超出左侧,取最小
+        offsetCalc.value = 0
+      } else {
+        offsetCalc.value = calcNum-20 > 0 ? calcNum-20 : 0
+      }
+    }
+  })
+}
+
+const showCustom = ref(false)
+const handleClick = (index, val) => {
+  if (val.id == 'custom') {
+    payQrCodeTxt.value = ''
+    showCustom.value = true
+    return
+  }
+  showCustom.value = false
+  payQrCodeTxt.value = ''
+  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 fromName = ref(route.query?.fromName || '')
+const callBackUrl = () => {
+  // if (!fromName.value) return
+  const urls = {
+    position: '/recruit/enterprise/position',
+    positionPay: '/recruit/enterprise/position?tab=0',
+  }
+  const texts = {
+    position: '职位管理页面',
+    positionPay: '职位管理待发布页面'
+  }
+  const url = fromName.value ? urls[fromName.value] : -1
+  const text = fromName.value ? texts[fromName.value] : '购买前页面'
+  //
+  Confirm(t('common.confirmTitle'), `支付成功!是否返回${text}?`).then(() => {
+    router.push(url)
+  })
+}
+// callBackUrl() 测试
+
+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()
+        // 支付成功
+        if (fromName.value) callBackUrl()
+        else 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 || '' // 生成二维码内容
+    calcStyle(current.value)
+    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">
+.list {
+  &-item {
+    width: 25%;
+    height: 172px;
+    background-color: #fcfcfd;
+    border: 1px solid #f3f3f3;
+    border-radius: 8px;
+    margin-right: 12px;
+    &:last-child {
+      margin-right: 0;
+    }
+    .periodValidity {
+      background-color: #f2f4f7;
+      border-radius: 0 0 8px 8px;
+    }
+  }
+  .active {
+    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>

+ 39 - 84
src/views/recruit/enterprise/membershipPackage/dynamic/package.vue

@@ -1,37 +1,33 @@
 <template>
-  <div class="d-flex list mb-3">
-    <div v-for="(val, index) in list" :key="index" :id="'positioning'+index" class="list-item text-center cursor-pointer" :class="{'active': index === current}" @click="handleClick(index, val)">
-      <template v-if="val.id === 'custom'">
-        <div class="d-flex flex-column algin-center justify-center" style="height: 100%">
-          <div>需要发布更多职位</div>
-          <div>请联系门墩儿购买企业套餐</div>
-          <div style="width: 100%; position: relative;">
-            <div style="width: 80px; height: 80px; margin: auto; opacity: 0.5;">
-              <v-img src="https://minio.menduner.com/dev/menduner/contact.png" width="80" height="80"></v-img>
-            </div>
-            <!-- <div style="opacity: 0.5;"><v-icon size="60">mdi-qrcode</v-icon></div> -->
-            <div class="absolute-center" style="color: #444; margin: 3px 0 0 2px;"><v-icon size="30">mdi-magnify-plus-outline</v-icon></div>
+  <v-slide-group v-model="model" class="pa-4" center-active show-arrows>
+    <v-slide-group-item v-for="(val, index) in list" :key="index" v-slot="{ isSelected }">
+      <v-card :class="{'active': isSelected}" class="ma-4 list-item elevation-2 text-center" height="175" @click="handleClick(index, val)">
+        <template v-if="val.id === 'custom'">
+          <div class="d-flex flex-column algin-center justify-center" style="height: 100%" :style="{'color': isSelected ? '#fff' : '#333'}">
+            <div>需要发布更多职位</div>
+            <div>请联系门墩儿购买企业套餐</div>
+            <div>点击查看联系方式</div>
           </div>
-        </div>
-      </template>
-      <template v-else>
-        <h4 class="mt-5">{{ val.name }}</h4>
-        <div class="color-primary">
-          <span>¥</span>
-          <span style="font-size: 35px;">{{ val.price / 100 }}</span>
-          <span> 元</span>
-        </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>
-      </template>
-    </div>
-  </div>
-  <div v-if="!Object.keys(select).length && !showCustom" class="color-warning text-center mt-15 font-size-20">请选择要购买的套餐</div>
+        </template>
+        <template v-else>
+          <h4 class="mt-5" :style="{'color': isSelected ? '#fff' : '#333'}">{{ val.name }}</h4>
+          <div :style="{'color': isSelected ? '#fff' : '#00897B'}">
+            <span>¥</span>
+            <span style="font-size: 35px;">{{ val.price / 100 }}</span>
+            <span> 元</span>
+          </div>
+          <div class="text-decoration-line-through" :style="{'color': isSelected ? '#fff' : '#666'}">原价:{{ val.originalPrice / 100 }}元</div>
+          <div class="font-size-14 mt-3 py-2" :style="{'color': isSelected ? '#fff' : '#999'}">有效期:{{ val.day }}天</div>
+        </template>
+      </v-card>
+    </v-slide-group-item>
+  </v-slide-group>
+
   <div v-if="payType && payQrCodeTxt" id="codeBox" class="code pa-5 resume-box">
-    <!-- <div class="resume-header">
+    <div class="resume-header">
       <div class="resume-title">扫码支付</div>
-    </div> -->
-    <div class="d-flex" :style="{'margin-left': offsetCalc + 'px'}">
+    </div>
+    <div class="d-flex justify-center">
       <div id="codeItem" class="d-flex flex-column align-center my-2">
         <div class="d-flex align-center">
           <span class="color-666 font-weight-bold">支付方式:</span>
@@ -86,50 +82,24 @@ import { useI18n } from '@/hooks/web/useI18n'; const { t } = useI18n()
 
 const current = ref()
 const select = ref({})
+const model = ref(0)
 
 // 套餐列表
 const list = ref([])
 const getPackageList = async () => {
   const data = await getEnterprisePackageList()
   list.value = data
-  list.value.push({id:'custom'})
-}
-
-const offsetCalc = ref(0)
-let codeItemOffsetWidth = null
-const codeBoxPx = 40 // codeBox左右内边距
-const calcStyle = (index) => {
-  nextTick(() => {
-    const codeBox = document.getElementById('codeBox')
-    const codeBoxOffsetWidth = codeBox.offsetWidth - codeBoxPx
-    if (!codeItemOffsetWidth) {
-      const codeItem = document.getElementById('codeItem')
-      codeItemOffsetWidth = codeItem.offsetWidth
-    }
-    //
-    const menu = document.getElementById(`positioning${index}`)
-    offsetCalc.value = 0
-    if (menu && codeBoxOffsetWidth && codeItemOffsetWidth) {
-      const menuHalfWidth= menu.offsetWidth/2
-      const codeItemHalfWidth = codeItemOffsetWidth/2
-      const calcNum = (menuHalfWidth+menuHalfWidth*2*index - codeItemHalfWidth) + (index ? 6 : 0)
-      //
-      if (calcNum+codeItemOffsetWidth > codeBoxOffsetWidth) { // 超出右侧,取最大
-        offsetCalc.value = codeBoxOffsetWidth-codeItemOffsetWidth
-      } else if (calcNum < 0) { // 超出左侧,取最小
-        offsetCalc.value = 0
-      } else {
-        offsetCalc.value = calcNum-20 > 0 ? calcNum-20 : 0
-      }
-    }
-  })
+  list.value.push({ id:'custom' })
+  select.value = data[0]
 }
 
 const showCustom = ref(false)
 const handleClick = (index, val) => {
+  model.value = index
   if (val.id == 'custom') {
     payQrCodeTxt.value = ''
     showCustom.value = true
+    clearTimer()
     return
   }
   showCustom.value = false
@@ -177,7 +147,6 @@ const updateAccountInfo = async (init = false) => {
 
 const fromName = ref(route.query?.fromName || '')
 const callBackUrl = () => {
-  // if (!fromName.value) return
   const urls = {
     position: '/recruit/enterprise/position',
     positionPay: '/recruit/enterprise/position?tab=0',
@@ -193,7 +162,6 @@ const callBackUrl = () => {
     router.push(url)
   })
 }
-// callBackUrl() 测试
 
 const payStatus = async () => {
   try {
@@ -226,7 +194,6 @@ const paySubmit = async () => {
     }
     const res = await payOrderSubmit(params)
     payQrCodeTxt.value = res?.displayContent || '' // 生成二维码内容
-    calcStyle(current.value)
     initIntervalFun()
     if (timer.value) clearInterval(timer.value); timer.value = null
     timer.value = setInterval(() => { payStatus() }, 1000) // 轮巡查询用户是否支付
@@ -253,7 +220,8 @@ const getCodeList = async () => {
           if (!payType.value) {
             // 默认值赋值(暂时只支持扫码)
             const bool = qrCodePay.includes(code)
-            if (bool) payType.value = code
+            // if (bool) payType.value = code
+            if (bool) payTypeChange(code)
           }
           payTypeList.value.push(item)
         }
@@ -315,25 +283,12 @@ const remainderCalc = () => {
 </script>
 
 <style scoped lang="scss">
-.list {
-  &-item {
-    width: 25%;
-    height: 172px;
-    background-color: #fcfcfd;
-    border: 1px solid #f3f3f3;
-    border-radius: 8px;
-    margin-right: 12px;
-    &:last-child {
-      margin-right: 0;
-    }
-    .periodValidity {
-      background-color: #f2f4f7;
-      border-radius: 0 0 8px 8px;
-    }
-  }
-  .active {
-    border: 1px solid #00897B;
-  }
+.list-item {
+  width: 250px;
+  background-color: #f7f8fa;
+}
+.active {
+  background-color: #00897B;
 }
 .code {
   background-color: #f7f8fa;

+ 1 - 1
src/views/recruit/enterprise/membershipPackage/index.vue

@@ -1,6 +1,6 @@
 <!-- 购买套餐 -->
 <template>
-  <v-card class="card-box pa-3" style="min-width: 1100px;">
+  <v-card class="card-box pa-3">
     <v-tabs v-model="tab" class="mb-3" align-tabs="center" color="primary" bg-color="#f7f8fa">
       <v-tab :value="0">职位发布套餐</v-tab>
       <v-tab :value="1">余额充值</v-tab>

Някои файлове не бяха показани, защото твърде много файлове са промени