Xiao_123 2 месяцев назад
Родитель
Сommit
1fc1592856

+ 2 - 1
src/components/FormUI/autocomplete/index.vue

@@ -20,6 +20,7 @@
       :hide-no-data="item.hideNoData"
       :no-data-text="item.noDataText || 'No data available'"
       :hide-selected="item.hideSelected"
+      :return-object="item.returnObject || false"
       :hide-details="item.hireDetails || false"
       @update:search="v => item.search ? debouncedCallbackSearch(v) : null"
       @update:modelValue="modelValueUpDate"
@@ -31,7 +32,7 @@ import { debounce } from 'lodash'
 import { ref, watch } from 'vue';
 defineOptions({ name:'FormUI-v-autocomplete'})
 
-const props = defineProps({item: Object, modelValue: [String, Array, Number, Boolean]})
+const props = defineProps({item: Object, modelValue: [String, Array, Number, Boolean, Object]})
 
 const value = ref()
 watch(

+ 7 - 1
src/permission.js

@@ -35,7 +35,13 @@ router.beforeEach(async (to, from, next) => {
     if (localStorage.getItem('entUpdatePassword') === 'needChange') fullScreen('entUpdatePassword')
     // 强制填写个人信息 fddeaddc47868b/ready
     else if (localStorage.getItem('necessaryInfoReady') === 'fddeaddc47868b' && tokenIndex === 2) dialogExtend('necessaryInfoDialog')
-    // 企业登录免费职位广告提示
+    
+    // 企业登录-招聘会广告
+    else if (!localStorage.getItem('jobFairAd') && tokenIndex === 1) {
+      localStorage.setItem('jobFairAd', 'hasBeenShow')
+      dialogExtend('jobFairAd')
+    }
+      // 企业登录免费职位广告提示
     else if (localStorage.getItem('positionAd')) {
       localStorage.setItem('positionAd', '')
       dialogExtend('positionAd')

+ 49 - 0
src/plugins/dialogExtend/components/jobFairAd.vue

@@ -0,0 +1,49 @@
+<!-- 招聘会广告 -->
+<template>
+  <v-app>
+    <v-dialog
+      v-model="dialog"
+      max-width="900"
+      :persistent="false"
+      @update:modelValue="handleChange"
+    >
+      <div style="cursor: pointer; margin: 0 auto; position: relative;">
+        <v-img src="https://minio.menduner.com/dev/fed8685fb4fec65347c2e3756db230ddd9c8f3538998c5678efe5acb51fb74e1.jpg" :width="adImgWidth" style="height: auto;border-radius: 4px;" @click="adClick"></v-img>
+        <span style="color: #fff; font-size: 32px; position: absolute; right: 0px; top: 0px;" class="mdi mdi-close-circle-outline cursor-pointer px-3" @click="dialog = false"></span>
+      </div>
+    </v-dialog>
+  </v-app>
+</template>
+
+<script setup>
+defineOptions({name: 'dialogExtend-positionAd'})
+import { onMounted, ref } from 'vue'
+
+const props = defineProps({
+  cancel: Function
+})
+
+const adImgWidth = ref(document?.documentElement?.clientWidth ?
+  Math.floor(document.documentElement.clientWidth/2.2) > 500 ?
+  Math.floor(document.documentElement.clientWidth/2.2) : 500
+  : 900
+)
+
+
+const dialog = ref(false)
+onMounted(() => {
+  dialog.value = true
+})
+
+const adClick =  () => {
+  dialog.value = false
+  window.location.href = '/recruit/enterprise/jobFair'
+  props.cancel()
+}
+
+const handleChange = (val) => {
+  props.cancel()
+}
+</script>
+<style lang="scss" scoped>
+</style>

+ 0 - 34
src/router/modules/components/headhunting.js

@@ -1,40 +1,6 @@
 import Layout from '@/layout'
 
 const headhunting = [
-  {
-    path: '/recruit/personal/jobFair',
-    component: Layout,
-    name: 'jobFair',
-    meta: {
-      title: '招聘会'
-    },
-    children: [
-      {
-        path: '/recruit/personal/jobFair',
-        component: () => import('@/views/recruit/personal/jobFair/index.vue')
-      },
-    ]
-  },
-  {
-    path: '/recruit/personal/jobFair/:id',
-    component: () => import('@/views/recruit/personal/jobFair/details/index.vue'),
-    name: 'jobFairDetails'
-  },
-  {
-    path: '/recruit/personal/jobFair/enterprises/:id',
-    component: () => import('@/views/recruit/personal/jobFair/details/enterprises.vue'),
-    name: 'jobFairEnterprises'
-  },
-  {
-    path: '/recruit/personal/jobFair/position/:id',
-    component: () => import('@/views/recruit/personal/jobFair/details/position.vue'),
-    name: 'jobFairPosition'
-  },
-  {
-    path: '/recruit/personal/jobFair/entPosition/:id',
-    component: () => import('@/views/recruit/personal/jobFair/details/entJobCard.vue'),
-    name: 'jobFairEntPosition'
-  },
   {
     path: '/headhunting',
     component: Layout,

+ 2 - 2
src/router/modules/components/recruit/enterprise.js

@@ -115,7 +115,7 @@ const enterprise = [
   {
     path: '/recruit/enterprise/jobFair',
     component: Layout,
-    name: 'jobFair',
+    name: 'EnterpriseJobFair',
     meta: {
       title: '招聘会',
       enName: 'Job Fair',
@@ -156,7 +156,7 @@ const enterprise = [
               title: '职位编辑',
               enName: 'Job Fair Edit'
             },
-            component: () => import('@/views/recruit/enterprise/jobFair/editJob.vue')
+            component: () => import('@/views/recruit/enterprise/jobFair/editJob/index.vue')
           }
         ]
       },

+ 34 - 0
src/router/modules/recruit.js

@@ -190,6 +190,40 @@ const recruit = [
       }
     ]
   },
+  {
+      path: '/recruit/personal/jobFair',
+      component: Layout,
+      name: 'personalJobFair',
+      meta: {
+        title: '招聘会'
+      },
+      children: [
+        {
+          path: '/recruit/personal/jobFair',
+          component: () => import('@/views/recruit/personal/jobFair/index.vue')
+        },
+      ]
+    },
+    {
+      path: '/recruit/personal/jobFair/:id',
+      component: () => import('@/views/recruit/personal/jobFair/details/index.vue'),
+      name: 'jobFairDetails'
+    },
+    {
+      path: '/recruit/personal/jobFair/enterprises/:id',
+      component: () => import('@/views/recruit/personal/jobFair/details/enterprises.vue'),
+      name: 'jobFairEnterprises'
+    },
+    {
+      path: '/recruit/personal/jobFair/position/:id',
+      component: () => import('@/views/recruit/personal/jobFair/details/position.vue'),
+      name: 'jobFairPosition'
+    },
+    {
+      path: '/recruit/personal/jobFair/entPosition/:id',
+      component: () => import('@/views/recruit/personal/jobFair/details/entJobCard.vue'),
+      name: 'jobFairEntPosition'
+    },
 ]
 
 setLoginType(recruit, 'personalCommon'),

+ 1 - 1
src/views/recruit/enterprise/hirePosition/components/add.vue

@@ -84,7 +84,7 @@ const handleSave = async () => {
   // if (!requirement?.areaId) return Snackbar.warning('请选择工作城市')
   if (!baseInfo || !requirement) return Snackbar.warning('请将信息填写完整')
   
-  submitParams = Object.assign(baseInfo, requirement, { currency_type: 0 }) // currency_type: 写死0(人民币)
+  submitParams = Object.assign(baseInfo, requirement, { currency_type: 0, source: '1', bizId: null }) // currency_type: 写死0(人民币)
   if (route.query && route.query.id) submitParams.id = route.query.id // 有id则为编辑
 
   saveEmit(submitParams) // 正常发布,到列表中发起支付(暂定解决方案)

+ 1 - 0
src/views/recruit/enterprise/interviewManagement/components/invite.vue

@@ -101,6 +101,7 @@ const getQuery = () => {
   obj.type = 1
   obj.id = props.itemData.id
   obj.userId = props.itemData.userId
+  if (props.itemData.jobFairId) obj.jobFairId = props.itemData.jobFairId
   return obj
 }
 

+ 5 - 2
src/views/recruit/enterprise/interviewManagement/components/item.vue

@@ -3,9 +3,12 @@
     <div class="d-flex align-center">
       <div class="mr-5 font-size-16" style="color: orange; width: 96px;">{{ timesTampChange(item.time, 'Y-M-D h:m') }}</div>
       <v-avatar class="mr-2" size=40 :image="getUserAvatar(item?.person?.avatar, item?.person?.sex)"></v-avatar>
-      <div class="d-flex flex-column mr-3" style="width: 120px;">
+      <div class="d-flex flex-column mr-3" style="width: 200px;">
         <span class="ellipsis mb-1">{{ item?.person?.name || item?.phone }}</span>
-        <span class="ellipsis" v-ellipse-tooltip style="color: var(--color-999);">{{ formatName(item?.job?.name) }}</span>
+        <span class="d-flex justify-center" style="max-width: 200px;">
+          <svg-icon v-if="item.jobFairId" class="mr-1" name="jobFair" size="20"></svg-icon>
+          <p v-ellipse-tooltip class="color-999">{{ formatName(item?.job?.name) }}</p>
+        </span>
       </div>
     </div>
     <div class="d-flex align-center right-item">

+ 1 - 1
src/views/recruit/enterprise/jobFair/components/job.vue

@@ -36,7 +36,7 @@
                 <!-- <span v-if="val.name.indexOf('style')" v-html="val.name" class="position-name"></span> -->
                 <span class="position-name">{{ formatName(val.name) }}</span>
                 <div>
-                  <v-btn size="small" color="primary" @click="handleTo(val)">添加至双选会</v-btn>
+                  <v-btn size="small" color="primary" @click="handleTo(val)">添加至招聘会</v-btn>
                 </div>
               </div>
               <div :class="['mt-3', 'other-info', 'ellipsis']">

+ 237 - 22
src/views/recruit/enterprise/jobFair/details.vue

@@ -1,36 +1,251 @@
 <template>
-  <v-card class="card-box pa-5">
-    <v-tabs v-model="tab" align-tabs="center" color="primary" bg-color="#f7f8fa" class="mb-3">
-      <v-tab v-for="(tab, index) in jobFairDetailsJob" :key="index" :value="index">{{ tab.title }}</v-tab>
-      <!-- <v-tab :value="1">投递简历</v-tab> -->
-    </v-tabs>
-    <component :is="jobFairDetailsJob[tab].component" :id="id"></component>
+  <v-card class="card-box pa-4">
+    <div class="position-relative">
+      <div class="text-end mb-3">
+        <v-btn color="primary" @click="handleAdd">新增职位</v-btn>
+        <v-btn color="primary" class="mx-3" variant="outlined" @click="handleJoin">选择已发布的职位加入招聘会</v-btn>
+        <v-btn color="primary" v-if="bgImg" variant="outlined" prepend-icon="mdi-share-all" @click="handleShare">我的分享海报</v-btn>
+      </div>
+      <JobItem :items="jobList" @refresh="getJobList"></JobItem>
+
+      <v-navigation-drawer v-model="showDrawer" location="right" temporary width="600">
+        <Loading :visible="positionLoading" :contained="true"></Loading>
+        <div class="resume-box">
+          <div class="resume-header">
+            <div class="resume-title mr-5">已发布职位</div>
+          </div>
+        </div>
+        <div class="px-3">
+          <v-text-field
+            v-model="positionSearch"
+            append-inner-icon="mdi-magnify"
+            density="compact"
+            :label="t('position.positionName')"
+            variant="outlined"
+            hide-details
+            color="primary"
+            single-line
+            @click:append-inner="getPositionList"
+            @keyup.enter="getPositionList"
+          ></v-text-field>
+        </div>
+        <div class="pa-3" v-if="positionItems.length">
+          <div v-for="val in positionItems" :key="val.id" class="itemBox mb-3" style="height: 80px;">
+            <div class="d-flex justify-space-between" style="padding: 10px 20px;">
+              <div class="position">
+                <div class="d-flex align-center justify-space-between">
+                  <span class="position-name">{{ formatName(val.name) }}</span>
+                  <div>
+                    <v-btn size="small" color="primary" @click="handleTo(val)">添加至招聘会</v-btn>
+                  </div>
+                </div>
+                <div :class="['mt-3', 'other-info', 'ellipsis']">
+                  <span>{{ val.areaName ? val.area?.str : '全国' }}</span>
+                  <span class="lines" v-if="val.eduName"></span>
+                  <span>{{ val.eduName }}</span>
+                  <span class="lines"></span>
+                  <span>{{ val.expName }}</span>
+                  <span class="lines"></span>
+                  <span v-if="!val.payFrom && !val.payTo">面议</span>
+                  <span v-else>{{ val.payFrom ? val.payFrom + '-' : '' }}{{ val.payTo }}{{ val.payName ? '/' + val.payName : '' }}</span>
+                  <span class="lines" v-if="val.positionName"></span>
+                  <span>{{ val.positionName }}</span>
+                </div>
+              </div>
+            </div>
+          </div>
+          <CtPagination
+            v-if="total"
+            :total="positionTotal"
+            :page="positionPageInfo.pageNo"
+            :limit="positionPageInfo.pageSize"
+            @handleChange="handleChangePage"
+          ></CtPagination>
+        </div>
+        <Empty v-else :elevation="false"></Empty>
+      </v-navigation-drawer>
+
+      <div class="hideCanvasView">
+        <JobFairEntShare 
+          :show="showShare" 
+          :enterpriseName="enterpriseName" 
+          :logoUrl="logoUrl" 
+          :positionList="positionList" 
+          :bgImg="bgImg" 
+          @success="handlePreview"
+        ></JobFairEntShare>
+      </div>
+    </div>
   </v-card>
+
+  <PreviewImage v-if="showPreview" :urlList="[previewSrc]" :fileName="enterpriseName" @close="showPreview = !showPreview, showShare = false" />
 </template>
 
 <script setup>
-defineOptions({ name: 'jobFairDetails' })
-import { shallowRef, ref } from 'vue'
-import { useRouter } from 'vue-router'
-import JobFairDetailsJob from './components/job.vue'
-import JobFairDetailsResume from './components/resume.vue'
+defineOptions({ name: 'jobFairJob'})
+import { ref } from 'vue'
+import { getJobFairPosition, getJobFair } from '@/api/recruit/enterprise/jobFair'
+import { dealDictArrayData } from '@/utils/position.js'
+import JobItem from './job/item.vue'
+import { useRouter, useRoute } from 'vue-router'
+import { useI18n } from '@/hooks/web/useI18n'
+import Snackbar from '@/plugins/snackbar'
+import { getEnterprisePubJobTypePermission } from '@/api/recruit/enterprise/position'
+import { getJobAdvertisedList } from '@/api/position'
+import { formatName } from '@/utils/getText'
+import JobFairEntShare from '@/views/recruit/components/jobFairEntShare'
+
 const router = useRouter()
+const route = useRoute()
+const { id } = route.params
+const { t } = useI18n()
+
+// 职位列表
+const jobList = ref([])
+
+const positionItems = ref([])
+const positionTotal = ref(0)
+const positionLoading = ref(false)
+const total = ref(0)
+const positionPageInfo = ref({
+  pageSize: 10,
+  pageNo: 1,
+})
+const positionSearch = ref('')
+
+// 分享海报
+const entBaseInfo = ref(localStorage.getItem('entBaseInfo') ? JSON.parse(localStorage.getItem('entBaseInfo')) : {})
+const showShare = ref(false)
+const showPreview = ref(false)
+const enterpriseName = ref(formatName(entBaseInfo.value.enterpriseAnotherName || entBaseInfo.value.enterpriseName))
+const logoUrl = ref(entBaseInfo.value.logoUrl)
+const previewSrc  = ref('')
+const positionList = ref([])
+const bgImg = ref('')
+
+// 职位列表
+const getJobList = async () => {
+  const data = await getJobFairPosition(id)
+  if (!data || !data.length) return jobList.value = []
+  jobList.value = dealDictArrayData([], data)
+}
+
+const handleAdd = async () => {
+  const data = await getEnterprisePubJobTypePermission()
+  if (!data || !data.length) return Snackbar.warning('没有该操作权限,请联系平台管理员升级后再试')
+  router.push(`/recruit/enterprise/jobFair/details/${id}/edit`)
+}
 
-const jobFairDetailsJob = shallowRef([
-  {
-    title: '职位',
-    component: JobFairDetailsJob
-  },
-  {
-    title: '投递简历',
-    component: JobFairDetailsResume
+const showDrawer = ref(false)
+const handleJoin = async () => {
+  getPositionList()
+  showDrawer.value = true
+}
+
+const handleChangePage = (index) => {
+  positionPageInfo.value.pageNo = index
+  getPositionList()
+}
+
+const handleTo = (val) => {
+  router.push(`/recruit/enterprise/jobFair/details/${id}/edit?id=${val.id}`)
+}
+
+// 获取职位列表
+const getPositionList = async () => {
+  positionLoading.value = true
+  const query = {
+    ...positionPageInfo.value,
+    status: 0,
+    hasExpiredData: false,
+    hire: false
+  }
+  if ( positionSearch.value) {
+    Object.assign(query, {
+      name: positionSearch.value,
+    })
   }
-])
+  try {
+    const { list, total } = await getJobAdvertisedList(query)
+    positionTotal.value = total
+    positionItems.value = list.length ? dealDictArrayData([], list) : []
+  } finally {
+    positionLoading.value = false
+  }
+}
+getJobList()
+
+const getJob = async () => {
+  const data = await getJobFair(id)
+  if (!data) return
+  bgImg.value = data.contentImg
+}
+getJob()
 
-const id = router.currentRoute.value.params.id
-const tab = ref(0)
+// 分享海报预览
+const handlePreview = (val) => {
+  if (!val) return
+  previewSrc.value = val
+  showPreview.value = true
+}
 
+const handleShare = () => {
+  positionList.value = jobList.value && jobList.value.length > 0 ? jobList.value.map(e => formatName(e.name)).slice(0, 2) : []
+  showShare.value = true
+}
 </script>
 
 <style scoped lang="scss">
+.hideCanvasView {
+	position: fixed;
+	top: -99999px;
+	left: -99999px;
+	z-index: -99999;
+}
+.resume-box {
+  padding-top: 120px;
+}
+
+.itemBox {
+  position: relative;
+  border: 1px solid #e5e6eb;
+}
+.position-name {
+  color: var(--color-333);
+  font-size: 19px;
+}
+.position {
+  width: 100%;
+  position: relative;
+  .item-select {
+    position: absolute;
+    left: -8px;
+    top: -13px;
+  }
+}
+.lines {
+  display: inline-block;
+  width: 1px;
+  height: 17px;
+  vertical-align: middle;
+  background-color: #e0e0e0;
+  margin: 0 10px;
+}
+.other-info {
+  font-size: 15px;
+  color: var(--color-666);
+}
+.bottom {
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  width: 100%;
+  height: 40px;
+  background-color: #f7f8fa;
+  font-size: 14px;
+  color: var(--color-888);
+}
+.actions:hover {
+  color: var(--v-primary-base);
+}
 </style>

+ 2 - 1
src/views/recruit/enterprise/jobFair/editJob.vue → src/views/recruit/enterprise/jobFair/editJob/index.vue

@@ -17,7 +17,7 @@
 <script setup>
 defineOptions({ name: 'editJob' })
 import { ref } from 'vue'
-import {  useRoute, useRouter } from 'vue-router'
+import { useRoute, useRouter } from 'vue-router'
 import CtForm from '@/components/CtForm'
 import Add from '@/views/recruit/enterprise/positionManagement/components/add.vue'
 import { useI18n } from '@/hooks/web/useI18n'
@@ -29,6 +29,7 @@ import {
 } from '@/api/recruit/enterprise/jobFair'
 import Snackbar from '@/plugins/snackbar'
 
+
 const { t } = useI18n()
 const route = useRoute()
 const router = useRouter()

+ 12 - 11
src/views/recruit/enterprise/jobFair/index.vue

@@ -1,17 +1,18 @@
 <template>
-  <v-card v-if="list.length" class="card-box pa-5 ">
-    <v-card v-for="(k, i) in list" :key="i" class="elevation-3">
-      <img :src="k.pcHeadImg" style="width: 100%; height: 300px;">
-      <div class="pa-5">
-        <div class="color-primary font-weight-bold font-size-18 mb-1" v-html="k.title"></div>
-        <div class="color-666 mt-3">活动时间:{{ timesTampChange(k.startTime, 'Y-M-D') }}至{{ timesTampChange(k.endTime, 'Y-M-D') }}</div>
-        <div class="text-end">
-          <v-btn color="primary" variant="outlined" @click.stop="handleBlockEnterprise(k.id)">立即加入</v-btn>
+  <v-card style="min-height: 70vh;">
+    <div v-if="list.length" class="card-box pa-5 ">
+      <v-card v-for="(k, i) in list" :key="i" class="elevation-3">
+        <img :src="k.pcPreviewImg" style="width: 100%; object-fit: contain;" />
+        <div class="px-5 py-3">
+          <div class="color-666">活动时间:{{ timesTampChange(k.startTime, 'Y-M-D') }}至{{ timesTampChange(k.endTime, 'Y-M-D') }}</div>
+          <div class="text-end">
+            <v-btn color="primary" variant="outlined" @click.stop="handleBlockEnterprise(k.id)">立即加入</v-btn>
+          </div>
         </div>
-      </div>
-    </v-card>
+      </v-card>
+    </div>
+    <Empty v-else message="暂无进行中的招聘会,去看看其他吧~" />
   </v-card>
-  <Empty v-else message="暂无进行中的招聘会,去看看其他吧~" />
 </template>
 
 <script setup>

+ 10 - 10
src/views/recruit/enterprise/jobFair/job/item.vue

@@ -7,7 +7,7 @@
             <div class="d-flex align-center">
               <span class="position-name">{{ formatName(val.name) }}</span>
             </div>
-            <div :class="['mt-3', 'other-info', 'ellipsis']">
+            <div class="mt-3 other-info">
               <span>{{ !val.areaId ? '全国' : val.area?.str }}</span>
               <span class="lines" v-if="!val.areaId || val.eduName"></span>
               <span>{{ val.eduName }}</span>
@@ -20,6 +20,10 @@
               <span>{{ val.positionName }}</span>
             </div>
           </div>
+          <div class="text-center color-primary d-flex flex-column justify-center cursor-pointer" @click="handleToResume(val)">
+            <div class="font-weight-bold font-size-18">{{ val.count || 0 }}</div>
+            <div class="font-size-14">已投递简历</div>
+          </div>
         </div>
         <div class="bottom pa-5 d-flex justify-space-between align-center">
           <div>到期时间:{{ val.expireTime ? timesTampChange(val.expireTime, 'Y-M-D') : '长期有效' }}</div>
@@ -76,6 +80,11 @@ const handleRemove = ({ id }) => {
   })
   
 }
+
+// 查看职位投递简历
+const handleToResume = (val) => {
+  router.push(`/recruit/enterprise/resume?id=${val.id}&jobFairId=${route.params.id}`)
+}
 </script>
 
 <style scoped lang="scss">
@@ -87,15 +96,6 @@ const handleRemove = ({ id }) => {
   color: var(--color-333);
   font-size: 19px;
 }
-.position {
-  max-width: 46%;
-  position: relative;
-  .item-select {
-    position: absolute;
-    left: -8px;
-    top: -13px;
-  }
-}
 .lines {
   display: inline-block;
   width: 1px;

+ 1 - 1
src/views/recruit/enterprise/positionManagement/components/add.vue

@@ -103,7 +103,7 @@ const handleSave = async () => {
   // if (!requirement?.areaId) return Snackbar.warning('请选择工作城市')
   if (!baseInfo || !requirement) return Snackbar.warning('请将信息填写完整')
   
-  submitParams = Object.assign(baseInfo, requirement, { currency_type: 0 }) // currency_type: 写死0(人民币)
+  submitParams = Object.assign(baseInfo, requirement, { currency_type: 0, source: props.isFair ? '2' : '0', bizId: props.isFair ? route.params.id : null }) // currency_type: 写死0(人民币)  source: 0职位管理|1招聘会
   console.log('submitParams', submitParams)
   if (route.query && route.query.id) submitParams.id = route.query.id // 有id则为编辑
   if (props.valid) {

+ 2 - 2
src/views/recruit/enterprise/positionManagement/components/item.vue

@@ -20,8 +20,8 @@
             <v-checkbox v-model="val.select" hide-details color="primary" @update:model-value="handleChangeSelect"></v-checkbox>
           </div>
           <div class="d-flex align-center" :class="{'cursor-pointer': tab === 1, 'ml-15': tab === 1}" @click="handleDetail(val)">
-            <span v-if="val.name.indexOf('style')" v-html="val.name" class="position-name"></span>
-            <span v-else class="position-name">{{ formatName(val.name) }}</span>
+            <span class="position-name">{{ formatName(val.name) }}</span>
+            <svg-icon v-if="val.bizId" name="jobFair" class="ml-1" size="25"></svg-icon>
           </div>
           <div :class="['mt-3', 'other-info', 'ellipsis', {'ml-10': tab === 1}]">
             <span>{{ !val.areaId ? '全国' : val.area?.str }}</span>

+ 189 - 0
src/views/recruit/enterprise/resume/components/filterPage.vue

@@ -0,0 +1,189 @@
+<template>
+	<div class="pa-5">
+		<h3 style="color: var(--v-primary-base);">条件筛选</h3>
+		<v-divider class="my-5"></v-divider>
+		<CtForm :items="formItems"></CtForm>
+		<div class="bottom">
+      <v-divider></v-divider>
+      <div class="d-flex justify-space-between mt-3">
+        <div class="ml-3">
+          <v-btn class="half-button mr-3" color="primary" @click="confirm">筛选</v-btn>
+          <v-btn class="half-button mr-3" variant="tonal" @click="emit('cancel')">取消</v-btn>
+        </div>
+        <v-btn class="half-button ml-3" variant="tonal" color="orange" @click="handleReset(true)">重置</v-btn>
+      </div>
+    </div>
+	</div>
+</template>
+
+<script setup>
+defineOptions({ name: 'filterPage'})
+import { ref, onMounted } from 'vue'
+import { getJobAdvertised } from '@/api/enterprise'
+import { getDict } from '@/hooks/web/useDictionaries'
+import { dealDictArrayData } from '@/utils/position'
+import { formatName } from '@/utils/getText'
+import Snackbar from '@/plugins/snackbar'
+import { getJobFairList } from '@/api/recruit/enterprise/jobFair'
+
+const emit = defineEmits(['confirm', 'cancel', 'reset'])
+const props = defineProps({ jobId: [String, Number], jobFairId: [String, Number] })
+
+const formItems = ref({
+  options: [
+    {
+      type: 'autocomplete',
+      key: 'jobId',
+      value: null,
+      label: '投递职位',
+      itemText: 'label',
+      itemValue: 'value',
+			clearable: true,
+			returnObject: true,
+      items: []
+    },
+		{
+      type: 'autocomplete',
+      key: 'jobFairId',
+      value: null,
+      label: '招聘会',
+      itemText: 'label',
+      itemValue: 'value',
+			clearable: true,
+			returnObject: true,
+      items: []
+    },
+		{
+			type: 'text',
+			key: 'name',
+			value: null,
+			col: 6,
+			label: '投递人姓名',
+			clearable: true
+		},
+		{
+      type: 'autocomplete',
+      key: 'eduType',
+      value: null,
+      label: '最高学历',
+      itemText: 'label',
+      itemValue: 'value',
+			clearable: true,
+			dictTypeName: 'menduner_education_type',
+			returnObject: true,
+			flexStyle: 'ml-3',
+			col: 6,
+      items: []
+    },
+		{
+      type: 'autocomplete',
+      key: 'expType',
+      value: null,
+      label: '工作经验',
+      itemText: 'label',
+      itemValue: 'value',
+			dictTypeName: 'menduner_exp_type',
+			returnObject: true,
+			col: 6,
+			clearable: true,
+      items: []
+    },
+		{
+      type: 'autocomplete',
+      key: 'jobStatus',
+      value: null,
+      label: '求职状态',
+      itemText: 'label',
+      itemValue: 'value',
+			dictTypeName: 'menduner_job_seek_status',
+			returnObject: true,
+			clearable: true,
+			flexStyle: 'ml-3',
+			col: 6,
+      items: []
+    }
+  ]
+})
+
+const confirm = () => {
+	const params = {}
+	const data = []
+	formItems.value.options.forEach(k => {
+		if (k.value) {
+			params[k.key] = k.type === 'text' ? k.value : k.value[k.itemValue]
+
+			const obj = k.type === 'text' ? { value: k.value, title: k.label, key: k.key } : { ...k.value, title: k.label, key: k.key }
+			data.push(obj)
+		}
+	})
+	if (!Object.keys(params).length) return Snackbar.warning('请选择筛选条件')
+
+	emit('confirm', params, data)
+}
+
+const handleReset = (close) => {
+	formItems.value.options.forEach(k => k.value = null)
+	emit('reset', close)
+}
+
+const handleClear = (item) => {
+	formItems.value.options.find(e => e.key === item.key).value = null
+}
+
+onMounted(() => {
+	formItems.value.options.forEach(async (k) => {
+		if (k.type === 'text') return
+		// 字典数据获取
+		if (k.dictTypeName) {
+			getDict(k.dictTypeName).then(({ data }) => {
+				data = data?.length && data || []
+				k.items = data
+			})
+			return
+		}
+		// 招聘会数据获取
+		if (k.key === 'jobFairId') {
+			const data = await getJobFairList()
+			k.items = data.map(e => {
+				return { label: e.title.replace(/<\/?p[^>]*>/gi, ''), value: e.id }
+			})
+			if (props.jobFairId) {
+				k.value = k.items.find(e => e.value.toString() === props.jobFairId)
+			}
+			return
+		}
+
+		// 投递职位
+		const data = await getJobAdvertised()
+		if (!data || !data.length) return
+		const list = dealDictArrayData([], data)
+		k.items = list.map(e => {
+			const salary = e.payFrom && e.payTo ? `${e.payFrom}-${e.payTo}${e.payName ? '/' + e.payName : ''}` : '面议'
+			return { label: `${formatName(e.name)}_${e.areaName ? e.area?.str : '全国'} ${salary}_${e.status === '0' ? '招聘中' : '已关闭'}`, value: e.id, isJobFair: e.source === '2' && e.bizId }
+		})
+
+		// 传入jobId,默认选中
+		if (props.jobId) {
+			k.value = k.items.find(e => e.value === props.jobId)
+			confirm()
+		}
+	})
+})
+
+defineExpose({
+	handleClear,
+	handleReset
+})
+</script>
+
+<style scoped lang="scss">
+.bottom {
+  position: fixed;
+  width: 96%;
+  margin: 0 12px;
+  bottom: 0;
+  left: 0;
+  padding-bottom: 14px;
+  background-color: #fff;
+}
+</style>

+ 1 - 0
src/views/recruit/enterprise/resume/components/invite.vue

@@ -108,6 +108,7 @@ const getQuery = () => {
     if (item.noParam) return
     obj[item.key] = item.value
   })
+  if (props.itemData?.jobFairId) obj.jobFairId = props.itemData.jobFairId
   return obj
 }
 

+ 7 - 1
src/views/recruit/enterprise/resume/components/table.vue

@@ -29,6 +29,10 @@
           <span class="defaultLink ml-3">{{ item?.person?.name || item?.phone }}</span>
         </div>
       </template>
+      <template #jobName="{item}">
+        <svg-icon v-if="item.jobFairId" name="jobFair" size="20"></svg-icon>
+        {{ formatName(item.job.name) }}
+      </template>
       <template #status="{ item }">
         <span v-if="tab === 0">{{ item.status && item.status === '0' ? '未查看' : '已查看' }}</span>
         <span v-else>{{ item.status ? props.statusList.find(i => i.value === item.status)?.label : '' }}</span>
@@ -111,7 +115,7 @@ const headers = ref([
   { title: '求职状态', key: 'person.jobStatusName', sortable: false },
   { title: '工作经验', key: 'person.expName', sortable: false },
   { title: '最高学历', key: 'person.eduName', sortable: false },
-  { title: '应聘职位', key: 'job.name', sortable: false, value: item => formatName(item.job.name) },
+  { title: '应聘职位', key: 'jobName', sortable: false },
   { title: '操作时间', key: 'createTime', sortable: false },
   { title: '状态', key: 'status', sortable: false },
   { title: '操作', value: 'actions', sortable: false }
@@ -177,6 +181,8 @@ const handleEliminate = async (item) => {
     userId: item.userId,
     type: props.tab === 0 ? '0' : '1' // 投递简历0 已邀约1
   }
+  // 招聘会职位则带id
+  if (item?.jobFairId) query.jobFairId = item.jobFairId
   await joinEliminate(query)
   Snackbar.success(t('common.operationSuccessful'))
   emit('refresh')

+ 73 - 24
src/views/recruit/enterprise/resume/index.vue

@@ -1,21 +1,38 @@
 <!-- 精英管理 -->
 <template>
   <v-card class="pa-3 card-box">
-    <div class="d-flex justify-space-between">
+    <div class="d-flex justify-space-between mb-3">
       <v-tabs v-model="tab" align-tabs="start" color="primary" bg-color="#f7f8fa" @update:model-value="handleChangeTab">
         <v-tab v-for="k in tabList" :value="k.value" :key="k.value">{{ k.label }}</v-tab>
       </v-tabs>
-      <TextInput v-model="textItems.value" :item="textItems" @appendInnerClick="handleSearch" @enter="handleSearch"></TextInput>
+      <!-- <div class="d-flex align-center">
+        <TextInput v-model="textItems.value" :item="textItems" @appendInnerClick="handleSearch" @enter="handleSearch"></TextInput>
+      </div> -->
+      <v-btn color="primary" prependIcon="mdi-filter-multiple-outline" class="ml-3" @click="showDrawer = true">筛选{{ rawData.length > 0 ? rawData.length : '' }}</v-btn>
     </div>
-    <Screen
-     :tab="tab" 
-     :jobId="router.currentRoute.value?.query?.id" 
-     :hire="router.currentRoute.value?.query?.hire"
-      @search="handleScreen"
-      @reset="handleScreenReset"
-      @select="handleSelect"
-      @change="handleChangeBounty"
-    ></Screen>
+
+    <div class="d-flex justify-space-between align-center mb-3" v-if="tab === 0">
+      <div class="mr-5 d-flex align-center">
+        <v-radio-group v-model="bounty" inline style="height: 28px;" @update:modelValue="handleChangeBounty">
+          <v-radio v-model="bounty" label="普通职位" :value="false" color="primary" hide-details density="compact" class="mr-3"></v-radio>
+          <v-radio v-model="bounty" label="赏金职位" :value="true" color="primary" hide-details density="compact"></v-radio>
+        </v-radio-group>
+
+        <v-radio-group class="ml-5" v-model="selected" inline style="height: 28px;" @update:modelValue="handleSelect">
+          <v-radio v-model="selected" label="新投递" value="0" color="primary" hide-details density="compact" class="mr-3"></v-radio>
+          <v-radio v-model="selected" label="已查看" value="1" color="primary" hide-details density="compact"></v-radio>
+        </v-radio-group>
+      </div>
+      <div class="color-primary font-size-14 cursor-pointer" @click="FilterPageRef.handleReset(false)">重置</div>
+    </div>
+    <div v-if="rawData && rawData.length > 0">
+      <v-chip v-for="item in rawData" :key="item.key" closable class="mr-2 mb-2" label @click:close="handleClose(item)">{{ item.title }}: {{ item.label || item.value }}</v-chip>
+    </div>
+
+    <!-- 筛选抽屉 -->
+    <v-navigation-drawer v-model="showDrawer" location="right" absolute temporary width="700">
+      <FilterPage ref="FilterPageRef" :jobId="route.query?.id" :jobFairId="route.query?.jobFairId" @confirm="handleConfirm" @cancel="showDrawer = false" @reset="handleScreenReset" />
+    </v-navigation-drawer>
 
     <v-window v-model="tab" class="mt-1">
       <v-window-item v-for="k in tabList" :value="k.value" :key="k.value">
@@ -27,18 +44,18 @@
 
 <script setup>
 defineOptions({ name: 'enterprise-elite-management'})
-import { ref } from 'vue'
+import { ref, onMounted } from 'vue'
 import { getPersonCvPage } from '@/api/enterprise'
 import { personCvUnfitPage } from '@/api/recruit/enterprise/personnel'
 import { dealDictObjData } from '@/utils/position'
 import { getDict } from '@/hooks/web/useDictionaries'
 import { getInterviewInvitePage } from '@/api/recruit/enterprise/interview'
 import TablePage from './components/table.vue'
-import Screen from './components/screen.vue'
 import { timesTampChange } from '@/utils/date'
-import { useRouter } from 'vue-router'
+import { useRoute } from 'vue-router'
+import FilterPage from './components/filterPage.vue'
 
-const router = useRouter()
+const route  = useRoute()
 const total = ref(0)
 const query = ref({
   pageNo: 1,
@@ -46,6 +63,10 @@ const query = ref({
   status: null,
   type: null
 })
+const selected = ref()
+const bounty = ref(null)
+const showDrawer = ref(false)
+const FilterPageRef = ref()
 const tab = ref(0)
 const tabList = ref([
   { label: '投递简历', value: 0, api: getPersonCvPage, status: null },
@@ -59,6 +80,7 @@ const textItems = ref({
   value: '',
   width: 250,
   label: '搜索姓名',
+  hideDetails: true,
   clearable: true,
   appendInnerIcon: 'mdi-magnify'
 })
@@ -96,7 +118,20 @@ const getList = async () => {
   })
 }
 // 没有带id时一进来才刷新,带id由组件传值刷新
-if (!router.currentRoute.value?.query?.id) getList()
+if (!route.query?.id) getList()
+
+onMounted(() => {
+  // 众聘职位
+  if (route.query?.hire) {
+    query.value.hire = true
+    bounty.value = true
+  }
+  // 招聘会职位
+  if (route.query?.jobFairId) {
+    if (tab.value !== 0) return delete query.value.jobFairId
+    query.value.jobFairId = route.query.jobFairId
+  }
+})
 
 // 分页
 const handleChangePage = (i) => {
@@ -112,16 +147,29 @@ const handleSearch = () => {
   getList()
 }
 
-// 下拉筛选
-const handleScreen = ({ value, key }) => {
-  if (value) query.value[key] = value
-  else delete query.value[key]
-  if (router.currentRoute.value?.query?.hire) query.value.hire = true
+// 筛选
+const rawData = ref([])
+const handleConfirm = (params, data) => {
+  rawData.value = data
+  query.value.pageNo = 1
+  query.value = Object.assign(query.value, params)
+  showDrawer.value = false
+  getList()
+}
+
+// 筛选条件单个清除
+const handleClose = (item) => {
+  FilterPageRef.value.handleClear(item)
+  delete query.value[item.key]
+  rawData.value = rawData.value.filter(e => e.key !== item.key)
   getList()
 }
 
-// 下拉筛选重置
-const handleScreenReset = () => {
+// 重置
+const handleScreenReset = (close) => {
+  rawData.value = []
+  bounty.value = ''
+  selected.value = ''
   query.value = {
     pageSize: 10,
     pageNo: 1,
@@ -131,10 +179,11 @@ const handleScreenReset = () => {
     query.value.status = null
     query.value.type = null
   }
-  textItems.value.value = ''
+  if (close) showDrawer.value = false
   getList()
 }
 
+// 职位类型选择
 const handleSelect = (e) => {
   query.value.pageNo = 1
   query.value.status = e