Browse Source

精英管理

Xiao_123 10 months ago
parent
commit
b0e08adc74

+ 2 - 2
src/api/recruit/enterprise/personnel/index.js

@@ -16,9 +16,9 @@ export const joinEliminate = async (data) => {
 }
 
 // 招聘端-牛人管理-入职
-export const personEntryByEnterprise = async (ids) => {
+export const personEntryByEnterprise = async (id) => {
   return await request.post({
-    url: `/app-admin-api/menduner/system/person-cv/entry?ids=${ids}`
+    url: `/app-admin-api/menduner/system/person-cv/entry?id=${id}`
   })
 }
 

+ 2 - 2
src/api/recruit/public/delivery/index.js

@@ -23,9 +23,9 @@ export const hireJobCvRelSettlement = async (id) => {
 }
 
 // 入职
-export const hireJobCvRelEntry = async (ids) => {
+export const hireJobCvRelEntry = async (id) => {
   return await request.post({
-    url: `/app-admin-api/menduner/system/person-cv/entry?ids=${ids}`
+    url: `/app-admin-api/menduner/system/person-cv/entry?id=${id}`
   })
 }
 

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

@@ -12,7 +12,7 @@
 </template>
 
 <script setup>
-defineOptions({ name: 'formPage'})
+defineOptions({ name: 'interview-invite-form'})
 import { ref } from 'vue'
 
 const props = defineProps({

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

@@ -86,7 +86,8 @@ import Confirm from '@/plugins/confirm'
 
 defineProps({
   items: Array,
-  statusList: Array
+  statusList: Array,
+  positionItems: Array
 })
 const emit = defineEmits(['refresh', 'action'])
 

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

@@ -58,7 +58,7 @@
       <v-divider style="height: auto;" class="mr-5" vertical></v-divider>
       <div style="flex: 1;overflow: hidden;">
         <div v-if="items.length">
-          <itemPage :items="items" :statusList="statusList" @refresh="handleRefresh"></itemPage>
+          <itemPage :items="items" :statusList="statusList" :positionItems="positionItems" @refresh="handleRefresh"></itemPage>
           <CtPagination
             v-if="total > 0"
             :total="total"

+ 99 - 0
src/views/recruit/enterprise/personnelManagement/components/invite.vue

@@ -0,0 +1,99 @@
+<template>
+  <CtForm ref="CtFormRef" :items="formItems" style="height: 420px;">
+    <template #time="{ item }">
+      <VueDatePicker 
+        v-model="item.value"
+        placeholder="面试时间 *"
+        class="mb-4"
+        model-type="timestamp"
+        :text-input="{ format: 'MM.dd.yyyy HH:mm' }" />
+    </template>
+  </CtForm>
+</template>
+
+<script setup>
+defineOptions({ name: 'formPage'})
+import { ref } from 'vue'
+
+const props = defineProps({
+  itemData: {
+    type: Object,
+    default: () => {}
+  }
+})
+
+const CtFormRef = ref()
+const formItems = ref({
+  options: [
+    {
+      slotName: 'time',
+      key: 'time',
+      value: null,
+      rules: [v => !!v || '请选择面试时间'],
+    },
+    {
+      type: 'text',
+      key: 'position',
+      value: '',
+      noParam: true,
+      disabled: true,
+      label: '面试岗位'
+    },
+    {
+      type: 'text',
+      key: 'address',
+      value: '',
+      label: '面试地点 *',
+      rules: [v => !!v || '请输入面试地点'],
+    },
+    {
+      type: 'text',
+      key: 'invitePhone',
+      value: null,
+      label: '联系电话 *',
+      outlined: true,
+      rules: [v => !!v || '请填写联系电话']
+    },
+    {
+      type: 'textarea',
+      key: 'remark',
+      value: '',
+      label: '备注事项',
+      counter: 140,
+      rules: [
+        value => {
+          if (value?.length <= 140) return true
+          return '请输入备注事项,最多140字'
+        }
+      ]
+    }
+  ]
+})
+
+if (Object.keys(props.itemData).length) {
+  const obj = formItems.value.options.find(e => e.key === 'position')
+  obj.value = `${props.itemData?.job?.name}${props.itemData?.job?.areaName ? '_' + props.itemData?.job?.areaName : ''} ${props.itemData?.job?.payFrom}-${props.itemData?.job?.payTo}/${props.itemData?.job?.payName}`
+}
+
+const getQuery = () => {
+  const obj = {
+    type: 1,
+    jobId: props.itemData.job.id,
+    userId: props.itemData.userId
+  }
+  formItems.value.options.forEach(item => {
+    if (item.noParam) return
+    obj[item.key] = item.value
+  })
+  return obj
+}
+
+defineExpose({
+  CtFormRef,
+  getQuery
+})
+</script>
+
+<style scoped lang="scss">
+
+</style>

+ 44 - 23
src/views/recruit/enterprise/personnelManagement/components/table.vue

@@ -1,8 +1,5 @@
 <template>
   <div>
-    <div class="text-end">
-      <v-btn v-if="tab === 1" color="primary" :disabled="selected.length ? false : true" variant="tonal" @click="handleAction('all', 0)">入职</v-btn>
-    </div>
     <v-data-table
       class="mt-3"
       v-model="selected"
@@ -30,15 +27,20 @@
       <template v-slot:item.actions="{ item }">
         <div v-if="tab === 0">
           <v-btn color="primary" variant="text" @click="handlePreviewResume(item)">查看附件</v-btn>
+          <v-btn color="primary" variant="text" @click="handleInterviewInvite(item)">邀请面试</v-btn>
         </div>
         <v-btn v-if="tab === 0 || tab === 1" color="primary" variant="text" @click="handleEliminate(item)">不合适</v-btn>
         <div v-if="tab === 1">
-          <!-- <v-btn color="primary" variant="text" @click="handleInterviewInvite(item)">邀请面试</v-btn> -->
-          <v-btn color="primary" variant="text" @click="handleAction('', 0, item)">入职</v-btn>
+          <v-btn color="primary" variant="text" @click="handleEnterByEnterprise(item)">入职</v-btn>
         </div>
-        <v-btn v-if="tab === 4" color="primary" variant="text" @click="handleCancelEliminate(item)">取消不合适</v-btn>
+        <v-btn v-if="tab === 3" color="primary" variant="text" @click="handleCancelEliminate(item)">取消不合适</v-btn>
       </template>
     </v-data-table>
+
+    <!-- 邀请面试 -->
+    <CtDialog :visible="showInvite" :widthType="2" titleClass="text-h6" title="面试信息" @close="handleEditClose" @submit="handleEditSubmit">
+      <InvitePage v-if="showInvite" ref="inviteRef" :itemData="itemData"></InvitePage>
+    </CtDialog>
   </div>
 </template>
 
@@ -47,14 +49,17 @@ defineOptions({ name: 'table-page'})
 import { ref, computed, watch } from 'vue'
 import { previewFile } from '@/utils'
 import { personJobCvLook, joinEliminate, personEntryByEnterprise, personCvUnfitCancel } from '@/api/recruit/enterprise/personnel'
+import { saveInterviewInvite } from '@/api/recruit/enterprise/interview'
 import { useI18n } from '@/hooks/web/useI18n'
 import Snackbar from '@/plugins/snackbar'
+import InvitePage from './invite.vue'
 
 const { t } = useI18n()
 const emit = defineEmits(['refresh'])
 const props = defineProps({
   tab: Number,
-  items: Array
+  items: Array,
+  statusList: Array
 })
 const badgeColor = computed(() => (item) => {
   return (item.person && item.person.sex) ? (item.person.sex === '1' ? '#1867c0' : 'error') : 'error'
@@ -64,6 +69,8 @@ const badgeIcon = computed(() => (item) => {
   return (item.person && item.person.sex) ? (item.person.sex === '1' ? 'mdi-gender-male' : 'mdi-gender-female') : 'mdi-gender-female'
 })
 
+const inviteRef = ref()
+const showInvite = ref(false)
 const selected = ref([])
 const headers = ref([
   { title: '姓名', value: 'name', sortable: false },
@@ -72,20 +79,21 @@ const headers = ref([
   { title: '工作经验', key: 'person.expName', sortable: false },
   { title: '最高学历', key: 'person.eduName', sortable: false },
   { title: '岗位薪资', key: 'job', value: item => `${item.job.payFrom}-${item.job.payTo}/${item.job.payName}`, sortable: false },
+  { title: '状态', key: 'status', sortable: false, value: item => item.status ? props.statusList.find(i => i.value === item.status).label : '' },
   { title: '操作', value: 'actions', sortable: false }
 ])
 const unfit = { title: '类型', key: 'unfitType', sortable: false, value: item => item.type === '0' ? '简历不合适' : '面试不合适' }
 const delivery = { title: '类型', key: 'deliveryType', sortable: false, value: item => item.status === '0' ? '新投递' : '已查看' }
 
-const list = [0, 4]
+const list = [0, 3]
 watch(
   () => props.tab,
   (val) => {
     if (list.indexOf(val) !== -1) {
       headers.value.splice(-1, 0, val === 0 ? delivery : unfit)
     } else {
-      const index = headers.value.findIndex(item => item.key === val === 0 ? 'deliveryType' : 'unfitType')
-      if (index > -1) headers.value.splice(index, 1)
+      const index = headers.value.indexOf(item => item.key === val === 0 ? 'deliveryType' : 'unfitType')
+      if (index !== -1) headers.value.splice(index, 1)
     }
   },
   { immediate: true }
@@ -97,15 +105,10 @@ const handleToPersonDetail = ({ userId, id }) => {
   window.open(`/recruit/enterprise/talentPool/details/${userId}?id=${id}`)
 }
 
-const apiList = [
-  personEntryByEnterprise // 入职
-]
-
 // 入职
-const handleAction = async (type, index, item) => {
-  const ids = type ? selected.value : [item?.id]
-  if (!ids) return
-  await apiList[index](ids)
+const handleEnterByEnterprise = async (item) => {
+  if (!item.id) return
+  await personEntryByEnterprise(item.id)
   Snackbar.success(t('common.operationSuccessful'))
   emit('refresh')
 }
@@ -139,9 +142,28 @@ const handlePreviewResume = async ({ url, id }) => {
   previewFile(url)
 }
 
-// const handleInterviewInvite = (item) => {
-//   console.log(item, 'item')
-// }
+// 邀请面试
+const itemData = ref({})
+const handleInterviewInvite = (item) => {
+  itemData.value = item
+  showInvite.value = true
+}
+
+const handleEditClose = () => {
+  showInvite.value = false
+  itemData.value = {}
+}
+
+const handleEditSubmit = async () => {
+  const { valid } = await inviteRef.value.CtFormRef.formRef.validate()
+  if (!valid) return
+  const query = inviteRef.value.getQuery()
+  if (!query?.time) return Snackbar.warning('请选择面试时间')
+  await saveInterviewInvite(query)
+  Snackbar.success(t('common.operationSuccessful'))
+  handleEditClose()
+  emit('refresh')
+}
 </script>
 
 <style scoped lang="scss">
@@ -149,7 +171,6 @@ const handlePreviewResume = async ({ url, id }) => {
   background-color: #f7f8fa !important;
 }
 :deep(.v-selection-control__input) {
-  // color: var(--v-primary-base) !important;
-  color: #767778;
+  color: var(--v-primary-base) !important;
 }
 </style>

+ 10 - 4
src/views/recruit/enterprise/personnelManagement/index.vue

@@ -11,7 +11,7 @@
 
     <v-window v-model="tab" class="mt-1">
       <v-window-item v-for="k in tabList" :value="k.value" :key="k.value">
-        <TablePage :items="items" :tab="k.value" @refresh="getList"></TablePage>
+        <TablePage :items="items" :tab="k.value" :statusList="statusList" @refresh="getList"></TablePage>
         <CtPagination
           v-if="total > 0"
           :total="total"
@@ -30,6 +30,7 @@ import { ref } 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'
@@ -45,9 +46,8 @@ const tab = ref(0)
 const tabList = ref([
   { label: '投递简历', value: 0, api: getPersonCvPage, status: null },
   { label: '已邀约', value: 1, api: getInterviewInvitePage, status: '0' },
-  // { label: '已发offer', value: 2, api: getInterviewInvitePage, status: '1' },
-  { label: '已入职', value: 3, api: getInterviewInvitePage, status: '2' },
-  { label: '不合适', value: 4, api: personCvUnfitPage },
+  { label: '已入职', value: 2, api: getInterviewInvitePage, status: '2' },
+  { label: '不合适', value: 3, api: personCvUnfitPage },
 ])
 const textItems = ref({
   type: 'text',
@@ -58,6 +58,12 @@ const textItems = ref({
   appendInnerIcon: 'mdi-magnify'
 })
 
+// 状态字典
+const statusList = ref([])
+getDict('menduner_interview_invite_status').then(({data}) => {
+  if (data && data.length) statusList.value = data
+})
+
 // 获取牛人列表
 const items = ref([])
 const getList = async () => {

+ 21 - 16
src/views/recruit/enterprise/publicRecruitmentManagement/deliver/components/table.vue

@@ -1,9 +1,5 @@
 <template>
   <div>
-    <div class="text-end">
-      <v-btn v-if="tab === 1" color="primary" :disabled="selected.length ? false : true" variant="tonal" @click="handleAction('all', 0, {})">不合适</v-btn>
-      <v-btn v-if="tab === 2" color="primary" :disabled="selected.length ? false : true" variant="tonal" @click="handleAction('all', 1, {})">入职</v-btn>
-    </div>
     <div class="mr-5 d-flex align-center" v-if="tab === 0">
       <div style="width: 200px;">
         <v-radio-group v-model="radio" inline style="height: 28px;" @update:modelValue="handleChangeSelected">
@@ -39,8 +35,8 @@
       </template>
       <template v-slot:item.actions="{ item }">
         <v-btn v-if="tab === 0 && item.url" color="primary" variant="text" @click="handlePreviewResume(item)">查看附件</v-btn>
-        <v-btn v-if="tab === 1" color="primary" variant="text" @click="handleAction('', 0, item)">不合适</v-btn>
-        <v-btn v-if="tab === 2" color="primary" variant="text" @click="handleAction('', 1, item)">入职</v-btn>
+        <v-btn v-if="tab === 1 || tab === 0" color="primary" variant="text" @click="handleEliminate(item)">不合适</v-btn>
+        <v-btn v-if="tab === 2" color="primary" variant="text" @click="handleEnterByEnterprise(item)">入职</v-btn>
         <v-btn v-if="tab === 3" color="primary" variant="text" @click="handleSettlement(item)">结算</v-btn>
         <v-btn v-if="radio === '1'" color="primary" variant="text">邀请面试</v-btn>
       </template>
@@ -51,7 +47,8 @@
 <script setup>
 defineOptions({ name: 'table-page'})
 import { ref, computed } from 'vue'
-import { hireJobCvRelEntry, hireJobCvRelEliminate, hireJobCvRelSettlement, hireJobCvRelLook } from '@/api/recruit/public/delivery'
+import { hireJobCvRelSettlement, hireJobCvRelLook } from '@/api/recruit/public/delivery'
+import { joinEliminate, personEntryByEnterprise } from '@/api/recruit/enterprise/personnel'
 import { previewFile } from '@/utils'
 import { useI18n } from '@/hooks/web/useI18n'
 import Snackbar from '@/plugins/snackbar'
@@ -94,16 +91,24 @@ const handleToPersonDetail = ({ userId, id }) => {
   window.open(`/recruit/enterprise/talentPool/details/${userId}?id=${id}`)
 }
 
-const apiList = [
-  hireJobCvRelEliminate, // 不合适
-  hireJobCvRelEntry // 入职
-]
+// 入职
+const handleEnterByEnterprise = async (item) => {
+  if (!item.id) return
+  await personEntryByEnterprise(item.id)
+  Snackbar.success(t('common.operationSuccessful'))
+  emit('refresh')
+}
 
-// 不合适、入职
-const handleAction = async (type, index, item) => {
-  const ids = type ? selected.value : [item?.id]
-  if (!ids) return
-  await apiList[index](ids)
+// 不合适
+const handleEliminate = async (item) => {
+  if (!item.id || !item?.job?.id) return
+  const query = {
+    bizId: item.id,
+    jobId: item.job.id,
+    userId: item.userId,
+    type: props.tab === 0 ? '0' : '1' // 投递简历0 已邀约1
+  }
+  await joinEliminate(query)
   Snackbar.success(t('common.operationSuccessful'))
   emit('refresh')
 }