Jelajahi Sumber

职位管理

lifanagju_citu 2 bulan lalu
induk
melakukan
f4968a9a99

+ 92 - 0
api/new/position.js

@@ -12,3 +12,95 @@ export const getJobAdvertisedList = (params) => {
     }
   })
 }
+
+// 获取待支付的订单
+export const getUnpaidOrder = (params) => {
+  return request({
+    url: '/app-api/menduner/system/recruit/trade/order/get/unpaid',
+    method: 'GET',
+    params,
+    custom: {
+      showLoading: true,
+      auth: true
+    }
+  })
+}
+
+// 创建订单
+export const createTradeOrder = (data) => {
+  return request({
+    url: '/app-api/menduner/system/recruit/trade/order/create',
+    method: "POST",
+    data,
+    custom: {
+      openEncryption: true,
+      showLoading: true,
+      auth: true
+    }
+  })
+}
+
+// 招聘端-开启职位
+export const enableJobAdvertised = (ids) => {
+  return request({
+    url: `/app-api/menduner/system/recruit/job-advertised/enable?ids=${ids}`,
+    method: "POST",
+    custom: {
+      openEncryption: true,
+      showLoading: true,
+      auth: true
+    }
+  })
+}
+
+// 招聘端-关闭职位
+export const closeJobAdvertised = (ids) => {
+  return request({
+    url: `/app-api/menduner/system/recruit/job-advertised/disable?ids=${ids}`,
+    method: "POST",
+    custom: {
+      openEncryption: true,
+      showLoading: true,
+      auth: true
+    }
+  })
+}
+
+// 招聘端-置顶职位
+export const topJobAdvertised = (ids) => {
+  return request({
+    url: `/app-api/menduner/system/recruit/job-advertised/top?ids=${ids}`,
+    method: "POST",
+    custom: {
+      openEncryption: true,
+      showLoading: true,
+      auth: true
+    }
+  })
+}
+
+// 招聘端-取消置顶职位
+export const topJobAdvertisedCancel = (ids) => {
+  return request({
+    url: `/app-api/menduner/system/recruit/job-advertised/un-top?ids=${ids}`,
+    method: "POST",
+    custom: {
+      openEncryption: true,
+      showLoading: true,
+      auth: true
+    }
+  })
+}
+
+// 招聘端-刷新职位
+export const refreshJobAdvertised = (ids) => {
+  return request({
+    url: `/app-api/menduner/system/recruit/job-advertised/refresh?ids=${ids}`,
+    method: "POST",
+    custom: {
+      openEncryption: true,
+      showLoading: true,
+      auth: true
+    }
+  })
+}

+ 120 - 37
components/PositionList/index.vue

@@ -1,8 +1,8 @@
 <template>
   <view class="ss-m-x-20">
     <!-- 岗位列表 -->
-    <view v-if="data?.length" class="ss-p-b-30 ss-p-t-20">
-      <view v-for="(item, index) in data" :key="index" class="mList">
+    <view v-if="list?.length" class="ss-p-b-30 ss-p-t-20">
+      <view v-for="(item, index) in list" :key="index" class="mList">
         <!-- 职位信息 -->
         <view class="list-shape" style="border-radius: 12px;">
           <!-- 职位 -->
@@ -32,7 +32,7 @@
               </view>
             </view>
             <view class="d-flex flex-column align-center justify-center resumeCount" @tap="handleToResume(item)">
-              <view style="font-size: 14px;">{{ item.payFrom || 0 }}</view>
+              <view style="font-size: 14px;">{{ item.count || 0 }}</view>
               <view>已投递简历</view>
             </view>
           </view>
@@ -40,19 +40,15 @@
             <view class="ss-m-t-10">到期时间:{{ item.expireTime ? timesTampChange(item.expireTime, 'Y-M-D') : '长期有效' }}</view>
           </view>
           <view class="sub-li-bottom">
-            <!-- <view v-if="tab === 1">
-              <span class="cursor-pointer" @tap="handleAction(item.top ? 4 : 3, '', item)">{{ item.top ? '取消置顶' : $t('common.topping') }}</span>
-              <span class="divider-mx">|</span>
-              <span class="cursor-pointer" @tap="handleAction(0, '', item)">{{ $t('common.close') }}</span>
-            </view> -->
+            <view v-if="tab === 1">
+              <span class="cursor-pointer divider-mx" @tap="handleAction(item.top ? 4 : 3, '', item)">{{ item.top ? '取消置顶' : '置顶' }}</span>
+              <span class="cursor-pointer divider-mx" @tap="handleAction(0, '', item, item)">关闭</span>
+            </view>
             <!-- <span v-if="(item.status-0) === 99 && tab === 0" class="cursor-pointer color-primary" @tap="toPay(item)">发布</span> -->
-            <!-- <span class="divider-mx" v-if="tab !== 2 && tab !== 3">|</span> -->
-            <span v-if="tab === 2" class="cursor-pointer" @tap="handleAction(1, '', item, item)">激活</span>
-            <span class="divider-mx" v-if="tab === 2"></span>
-            <span class="cursor-pointer" @tap="handleDetail(item)">详情</span>  
+            <span v-if="tab === 2" class="cursor-pointer divider-mx" @tap="handleAction(1, '', item, item)">激活</span>
+            <span class="cursor-pointer divider-mx" @tap="handleDetail(item)">详情</span>
             <!-- <view v-if="tab !== 3">
-              <span class="divider-mx">|</span>
-              <span class="cursor-pointer" :style="{'color': item.edit ? '#333' : '#999'}" @tap="handleEdit(item)">{{ $t('common.edit') }}</span>
+              <span class="cursor-pointer divider-mx" :style="{'color': item.edit ? '#333' : '#999'}" @tap="handleEdit(item)">{{ '编辑' }}</span>
             </view> -->
           </view>
         </view>
@@ -63,6 +59,19 @@
       <image src="https://minio.citupro.com/dev/static/nodata.png" mode="widthFix" style="width: 100vw;height: 100vh;"></image>
       <view style="color: gray; text-align: center;">暂无数据</view>
     </view>
+    <!-- 确认框 -->
+    <uni-popup ref="confirm" type="dialog">
+      <uni-popup-dialog
+        type="warn"
+        cancelText="取消"
+        confirmText="确认" 
+        title="系统提示"
+        :content="dialogContent"
+        @confirm="handleConfirm"
+        @close="handleClose"
+      ></uni-popup-dialog>
+    </uni-popup>
+    <payPopup v-if="props.payable" ref="payRef" amount="123"></payPopup>
   </view>
 </template>
 
@@ -70,46 +79,119 @@
 // import { commissionCalculation } from '@/utils/position'
 import { timesTampChange } from '@/utils/date'
 import { formatName } from '@/utils/getText'
-import { ref, watch } from 'vue'
-const emit = defineEmits(['entClick'])
-
+import { ref } from 'vue'
+import payPopup from '@/components/payPopup'
+import {
+  topJobAdvertisedCancel,
+  topJobAdvertised,
+  refreshJobAdvertised,
+  closeJobAdvertised,
+  enableJobAdvertised,
+  getUnpaidOrder,
+  createTradeOrder
+} from '@/api/new/position'
+import { userStore } from '@/store/user'; const useUserStore = userStore()
+const emit = defineEmits(['entClick', 'refresh'])
 const props = defineProps({
   tab: { type: Number, default: 0 },
   list: { type: Array, default: () => [] },
   noMore: { type: Boolean, default: false },
-  showWelfareTag: { type: Boolean, default: true }
+  showWelfareTag: { type: Boolean, default: true },
+  payable: { type: Boolean, default: false },
 })
 
-const data = ref()
-watch(
-  () => props.list, 
-  (newVal) => {
-    data.value = newVal?.length ? newVal : []
-  },
-  { immediate: true },
-  { deep: true }
-)
+const userInfo = ref(useUserStore?.userInfo || {})
+
+const spuId = ref('')
+const spuName = ref('')
+const operateObj = ref({})
+const payRef = ref()
+// 支付
+const toPay = async (val) => {
+  // // 待发布且有额度的激活职位即可
+  // if (userInfo.value.entitlement?.publishJobCount > 0) {
+  //   await enableJobAdvertised([val.id])
+  //   emit('refresh', true)
+  //   setTimeout(() => { uni.showToast({ title: '发布成功', icon: 'success' }) }, 1000)
+  //   return
+  // }
+
+  operateObj.value = val
+  spuId.value = val.id || ''
+  spuName.value = val.name || ''
+  payRef.value && payRef.value.handleOpen()
+}
 
+setTimeout(() => { toPay(props.list[0]) }, 2000)
+
+const confirm = ref()
+const dialogContent = ref('')
+let handleActionInfo = {}
+const apiList = [closeJobAdvertised, enableJobAdvertised, refreshJobAdvertised, topJobAdvertised, topJobAdvertisedCancel]
 // 职位关闭、激活、刷新、置顶
 const handleAction = async (index, type, { id }, item) => {
+  const ids = type ? [] : [id]
+  if (!ids?.length && !index) return
+  try {
+    handleActionInfo.ids = ids
+    handleActionInfo.index = index
+    uni.showLoading({ title: '操作中...', mask: true })
+
+    // 关闭职位提醒
+    if (index === 0) {
+      const text = userInfo.value?.entitlement?.publishJobCount && userInfo.value?.entitlement?.publishJobCount > 0 ? '将消耗一个发布点数' : '将重新收取费用'
+      const positionName = item?.name ? '【' + formatName(item?.name) + '】' : '所选'
+      dialogContent.value = `是否确认关闭${positionName}职位?关闭后再激活,${text}!`
+      confirm.value.open()
+      return
+    }
+
+    // 没有可发布额度激活职位需重新付款
+    userInfo.value = await useUserStore.getUserInfos()
+    if (index === 1 && userInfo.value?.entitlement.publishJobCount <= 3990039900) {
+      // 判断是否已有创建好的订单,有就直接跳转支付,没有就创建订单
+      const data = await getUnpaidOrder({ spuId: id, type: 1 })
+      if (!data) await createTradeOrder({ spuId: id, spuName: item.name, price: 39900, type: 1 })
+      toPay(item)
+      return
+    }
+    await apiList[index](ids)
+    emit('refresh', true)
+    setTimeout(() => { uni.showToast({ title: '操作成功', icon: 'success' }) }, 1000)
+    // 
+  } catch (error) {
+    uni.showToast({ title: '操作失败', icon: 'error', duration: 2000 })
+    console.log(error)
+  } finally {
+    uni.hideLoading()
+  }
+}
+
+const handleClose = () => {
+  confirm.value.close()
+}
+const handleConfirm = async () => {
+  try {
+    uni.showLoading({ title: '关闭中...', mask: true })
+    await apiList[handleActionInfo?.index](handleActionInfo?.ids)
+    emit('refresh', true)
+    setTimeout(() => { uni.showToast({ title: '关闭成功', icon: 'success' }) }, 1000)
+  } catch (error) {
+    uni.showToast({ title: '关闭失败', icon: 'error' })
+    console.log(error)
+  } finally {
+    uni.hideLoading()
+  }
 }
 
 // 职位关闭、激活、刷新、置顶
 const handleEdit = async (val) => {
   if (!val.id || !val.edit) {
-    uni.showToast({
-      title: '职位发布时间超过24小时的不支持编辑',
-      icon: 'none',
-      duration: 2000
-    })
+    uni.showToast({ title: '职位发布时间超过24小时的不支持编辑', icon: 'none', duration: 2000 })
     return
   } 
 }
 
-// 支付
-const toPay = async (val) => {
-}
-
 // 职位详情
 const handleDetail = (item) => {
   if (!item.id) return
@@ -161,7 +243,8 @@ const handleToResume = (val) => {
 }
 
 .sub-li-bottom {
-  text-align: right;
+  display: flex;
+  justify-content: flex-end;
   border-top: 1px dashed #eee;
   margin-top: 10px;
   padding-top: 10px;

+ 264 - 0
components/payPopup/index.vue

@@ -0,0 +1,264 @@
+<template>
+  <view style="z-index: 999;">
+    <uni-popup ref="popup" :is-mask-click="false" borderRadius="10px 10px 0 0" background-color="#eee">
+      <view class="popup-content">
+        <view class="popup-content-close">
+          <view class="icon" @tap="handleClose">
+            <uni-icons
+              type="closeempty"
+              color="#999"
+              size="24"
+            />
+          </view>
+        </view>
+        <view class="popup-content-main">
+          <view class="popup-content-main-count">
+            <view class="title">{{ title }}</view>
+            <view class="pay">
+              <uni-icons color="#000" type="icon-renminbi1688" size="16" custom-prefix="iconfont"></uni-icons>
+              <view>{{ amount }}</view>
+            </view>
+          </view>
+          <view class="popup-content-main-type">
+            <view v-if="payTypeList?.length" class="card">
+              <radio-group @change="radioChange">
+                <label class="card-label" v-for="item in payTypeList" :key="item.value">
+                  <view class="name">
+                    <uni-icons :color="item.color" class="mr-1" :type="item.icon" size="24" custom-prefix="iconfont"></uni-icons>
+                    {{item.name}}
+                  </view>
+                  <view>
+                    <radio :value="item.value" :disabled="item.disabled" :checked="item.value === channelValue" />
+                  </view>
+                </label>
+              </radio-group>
+            </view>
+          </view>
+        </view>
+        <view class="popup-content-btn">
+          <button class="popup-content-btn-s" @tap="handlePay">
+            确认支付 
+            <uni-icons color="#FFF" type="icon-renminbi1688" size="16" custom-prefix="iconfont"></uni-icons>
+            {{ amount }}
+          </button>
+        </view>
+      </view>
+    </uni-popup>
+  </view>
+</template>
+<script setup>
+import { ref } from 'vue'
+import { getEnableCodeList } from '@/api/common'
+
+const props = defineProps({
+  title: { // 标题
+    type: String,
+    default: '支付'
+  },
+  amount: { // 支付金额
+    type: [String, Number],
+    default: '金额获取失败'
+  },
+})
+
+const payType = [
+  {
+    name: '微信支付',
+    value: 'wx_lite',
+    icon: 'icon-weixinzhifu',
+    color: '#1AAD19'
+  },
+  {
+    name: '钱包支付',
+    value: 'wallet',
+    disabled: true,
+    icon: 'icon-qianbao1',
+    color: '#00B760'
+  }
+]
+
+// 余额和其他还没有接暂时只支持微信支付
+const payTypeList = ref([])
+// 获取支付方式
+const getPayMethodsList = async () => {
+  payTypeList.value = []
+  try {
+    const res = await getEnableCodeList({appId: 14})
+    if (!res?.data?.length) {
+      return
+    }
+    payTypeList.value.push(...payType.filter(e => res.data.includes(e.value)))
+    const result = payType.find(item => !item.disabled && item.value)
+    if (result) channelValue.value = result.value
+  } catch (error) {
+    console.log(error)
+  }
+}
+getPayMethodsList()
+
+const channelValue = ref('')
+const radioChange = (e) => {
+  channelValue.value = e?.detail?.value || ''
+}
+
+const tabBarControl = (show = false) => { // 显示/隐藏TabBar
+  const currentPage = getCurrentPages()
+  if (!currentPage) return
+  const currentTabBar = currentPage[0]?.getTabBar?.()
+  currentTabBar?.setData({ show })
+}
+
+const popup = ref()
+const handleClose = () => {
+  tabBarControl(true)
+  popup.value.close()
+}
+const handleOpen = () => {
+  tabBarControl(false)
+  popup.value.open('bottom')
+}
+
+// 支付
+const handlePay = async () => {
+}
+
+defineExpose({
+	handleOpen
+})
+</script>
+<style lang="scss" scoped>
+.pay {
+  position: sticky;
+  bottom: 0;
+  padding: 0 40rpx 50rpx 40rpx;
+  box-sizing: border-box;
+  &-box {
+    width: 100%;
+    background: linear-gradient(121deg,#fde2c2 29.02%,#c19164 104.03%);
+    border-radius: 180rpx 0 180rpx 0;
+    box-shadow: 3rpx 6rpx 10rpx 0rpx rgb(216 160 82);
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    height: 100rpx;
+    .price {
+      padding: 0 40rpx;
+      font-size: 40rpx;
+      font-weight: 600;
+      color: #e68735;
+    }
+    .btn {
+      height: 100%;
+      display: flex;
+      align-items: center;
+      padding: 0 40rpx;
+      border: 2rpx solid #00B760;
+      background: #00B760;
+      color: #FFF;
+      border-radius: 180rpx 0 180rpx 0;
+      position: relative;
+      // &::after {
+      //   content: '';
+      //   position: absolute;
+      //   width: 50rpx;
+      //   height: 50rpx;
+      //   background: radial-gradient(top right, transparent 50%, #00B760 50%);
+      //   left: 0;
+      //   top: 0;
+      //   margin-left: -25rpx;
+      //   border-radius: 180rpx;
+      // }
+    }
+  }
+}
+
+.popup-content {
+  z-index: 999;
+  max-height: 500px;
+  display: flex;
+  flex-direction: column;
+  &-close {
+    display: flex;
+    padding: 10px;
+    justify-content: flex-end;
+    .icon {
+      width: 30px;
+      height: 30px;
+      background: #ccc;
+      border-radius: 30px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+    }
+  }
+  &-main {
+    flex: 1;
+    height: 0;
+    overflow-y: auto;
+    &-count {
+      margin-bottom: 20px;
+      text-align: center;
+      .title {
+        font-size: 28rpx;
+        color: #666
+      }
+      .pay {
+        font-size: 52rpx;
+        color: #000;
+        font-weight: 600;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        padding: 10px 0;
+      }
+    }
+    &-type {
+      width: 100%;
+      padding: 0 20px;
+      box-sizing: border-box;
+      .card {
+        border-radius: 10px;
+        margin: 0 auto;
+        background: #FFF;
+        padding: 10px;
+        &-label {
+          padding: 15px 0;
+          box-sizing: border-box;
+          display: flex;
+          justify-content: space-between;
+          border-bottom: 1px solid #eee;
+          &:last-of-type {
+            border-bottom: none;
+          }
+          .name {
+            display: flex;
+            align-items: center;
+            color: #333;
+          }
+        }
+      }
+    }
+  }
+  &-btn {
+    height: 70px;
+    width: 100%;
+    margin-top: 10px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    &-s {
+      height: 40px;
+      width: 75%;
+      line-height: 40px;
+      color: #FFF;
+      // color: #724d2b;
+      background: #00B760;
+      // background: linear-gradient(121deg,#fde2c2 29.02%,#c19164 104.03%);
+      border-radius: 90px;
+    }
+  }
+}
+.mr-1 {
+  margin-right: 10px;
+}
+</style>

+ 10 - 4
pages/index/position.vue

@@ -29,8 +29,9 @@
           </view>
         </view>
         <view v-else>
-          <PositionList v-if="positionListData?.length" :tab="tab" :list="positionListData" :noMore="false"></PositionList>
+          <PositionList v-if="positionListData?.length" :tab="tab" :payable="true" :list="positionListData" :noMore="false" @refresh="refresh"></PositionList>
           <uni-load-more :status="more" />
+          <view style="padding-bottom: 20vh;"></view>
         </view>
       </view>
     </scroll-view>
@@ -86,9 +87,8 @@ const getData = async () => {
       more.value = 'noMore'
       return
     }
-    // positionListData.value.concat(dealDictArrayData([], list))
     positionListData.value.push(...dealDictArrayData([], list))
-    console.log('positionListData.value:', positionListData.value)
+    // console.log('positionListData:', positionListData.value)
     more.value = 'more'
     if (positionListData.value.length === total.value) {
       more.value = 'noMore'
@@ -117,11 +117,18 @@ const onSearch = () => {
 
 // 加载更多
 const loadingMore = () => {
+  if (more.value === 'noMore') return
+
   more.value = 'loading'
   query.value.pageNo++
   getData()
 }
 
+const refresh = (reset = false) => {
+  if (reset) query.value.pageNo = 1
+  getData()
+}
+
 const handleClick = () => {
   let url = ''
   if (url) {
@@ -156,7 +163,6 @@ const handleClick = () => {
 .box {
   height: 100vh;
   overflow: hidden;
-  padding-bottom: 120rpx;
   box-sizing: border-box;
   display: flex;
   flex-direction: column;

+ 5 - 0
pages/index/resume.vue

@@ -8,6 +8,11 @@
 	import layoutPage from '@/layout'
 	import { onShow } from '@dcloudio/uni-app'
 	
+	// // 测试数据
+	// uni.switchTab({
+  //   url: '/pages/index/position'
+  // })
+	
 	// 设置自定义tabbar选中值
 	onShow(() => {
 	    const currentPage = getCurrentPages()[0];  // 获取当前页面实例