Browse Source

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

lifanagju_citu 7 months ago
parent
commit
eafa47800b

+ 15 - 0
src/api/position.js

@@ -185,6 +185,21 @@ export const getJobAdvertisedExport = async (params) => {
   })
 }
 
+// 招聘端-导出批量上传职位模版
+export const jobAdvertisedTemplateDownload = async () => {
+  return await request.download({
+    url: '/app-api/menduner/system/recruit/job-advertised/import-template'
+  })
+}
+
+// 招聘端-批量上传职位
+export const jobAdvertisedUpload = async (data) => {
+  return await request.upload({
+    url: '/app-api/menduner/system/recruit/job-advertised/import',
+    data
+  })
+}
+
 // 招聘端-发布职位详情
 export const getJobDetails = async (params) => {
   return await request.get({

+ 11 - 2
src/components/Upload/file.vue

@@ -10,10 +10,18 @@ import { useI18n } from '@/hooks/web/useI18n'
 import { uploadFile } from '@/api/common'
 
 const emits = defineEmits(['success'])
-defineProps({
+const props = defineProps({
   accept: {
     type: String,
     default: '.pdf, .doc, .docx'
+  },
+  custom: {
+    type: Boolean,
+    default: false
+  },
+  customName: {
+    type: String,
+    default: ''
   }
 })
 
@@ -40,7 +48,8 @@ const handleUploadFile = async (e) => {
   }
   const arr = file.name.split('.')
   const formData = new FormData()
-  formData.append('file', file)
+  formData.append(props.customName || 'file', file)
+  if (props.custom) return emits('success', formData)
   const { data } = await uploadFile(formData)
   if (!data) return
   emits('success', data, arr[0], file.name)

+ 84 - 24
src/views/recruit/components/message/index.vue

@@ -39,7 +39,7 @@
               color="primary"
               class="mb-2"
               :active="val.channel.channelID === info?.channel?.channelID"
-              :title="val.userInfoVo ? (val.userInfoVo.userInfoResp?.name ? val.userInfoVo.userInfoResp.name : '游客') : '系统消息'"
+              :title="val.userInfoVo ? (val.userInfoVo.userInfoResp?.name ? val.userInfoVo.userInfoResp.name : val.userInfoVo.userInfoResp?.phone) : '系统消息'"
               :subtitle="timesTampChange(+val.timestamp.padEnd(13, '0'))"
             >
               <template v-slot:title="{ title }">
@@ -148,8 +148,13 @@
     <InvitePage v-if="showInvite" ref="inviteRef" :item-data="itemData" :position="positionList"></InvitePage>
   </CtDialog>
 
+  <!-- 求简历-选择求简历的职位 -->
+  <CtDialog :visible="showSelectPosition" :widthType="2" titleClass="text-h6" title="选择要求简历的职位" @close="showSelectPosition = false" @submit="handleRequestResumeSubmit">
+    <CtForm v-if="showSelectPosition" ref="requestFromRef" :items="requestFormItems"></CtForm>
+  </CtDialog>
+
   <!-- 选择附件简历投递 -->
-  <CtDialog :visible="showResume" :widthType="2" titleClass="text-h6" title="发送简历" @close="showResume = false; selectResume = null " @submit="handleSubmitResume">
+  <CtDialog :visible="showResume" :widthType="2" titleClass="text-h6" title="发送简历" @close="showResume = false; selectResume = null; enRequestPositionInfo = {}" @submit="handleSubmitResume">
     <div style="position: relative; min-height: 200px">
       <v-radio-group v-model="selectResume">
         <v-radio v-for="val in resumeList" :key="val.id" :value="val.id" :label="val.title" color="primary"></v-radio>
@@ -204,9 +209,28 @@ const messageItems = ref([])
 const pageSize = ref(1)
 const hasMore = ref(false)
 
+const positionList = ref([])
 const showInvite = ref(false)
+// 企业-求简历
+const showSelectPosition = ref(false)
+const requestFromRef = ref()
+const requestFormItems = ref({
+  options: [
+    {
+      type: 'autocomplete',
+      key: 'jobId',
+      value: null,
+      label: '招聘职位 *',
+      outlined: true,
+      clearable: false,
+      itemText: 'label',
+      itemValue: 'value',
+      rules: [v => !!v || '请选择招聘职位'],
+      items: positionList
+    }
+  ]
+})
 
-const positionList = ref([])
 
 const showDelete = ref(false)
 const itemData = ref({})
@@ -252,8 +276,8 @@ const showRightNoData = ref(false)
 const info = ref({})
 
 const enterpriseTools = ref([
-  { name: '求简历', icon: 'mdi-email', color:"primary", loading: false, handle: handleRequest, disabled: false, disabledText: '简历已接收' },
-  { name: '面试邀约', icon: 'mdi-email', color:"primary", loading: false, handle: handleInvite, disabled: false, disabledText: '面试邀约' }
+  { name: '求简历', key: 'requestResume', icon: 'mdi-email', color:"primary", loading: false, handle: handleInvite, disabled: false, disabledText: '简历已接收' },
+  { name: '面试邀约', key: 'interviewInvite', icon: 'mdi-email', color:"primary", loading: false, handle: handleInvite, disabled: false, disabledText: '面试邀约' }
 ])
 
 const userTools = ref([
@@ -385,9 +409,9 @@ async function getMessageTypeSync () {
 
 function handleChangeSendResumeStatus (status) {
   if (!isEnterprise) {
-      const item = userTools.value.find(e => e.key === 'sendResume')
-      item.disabled = status
-    }
+    const item = userTools.value.find(e => e.key === 'sendResume')
+    item.disabled = status
+  }
 }
 
 async function handleChange (items) {
@@ -442,6 +466,7 @@ const handleUploadResume = async (url, title, filename) => {
 
 // 获取简历
 const showUploadDialog = ref(false)
+const enRequestPositionInfo = ref({}) // 企业求简历时选中的职位信息
 async function handleSendResume (item) {
   try {
     item.loading = true
@@ -453,6 +478,7 @@ async function handleSendResume (item) {
       return
     }
     resumeList.value = result
+    if (item?.content?.query?.positionInfo?.data && Object.keys(item?.content?.query?.positionInfo?.data).length) enRequestPositionInfo.value = item?.content?.query?.positionInfo?.data
     showResume.value = true
   } finally {
     item.loading = false
@@ -481,7 +507,6 @@ const handleSubmitAttachment = async () => {
   })
   if (!obj.title || !obj.url) return
   await savePersonResumeCv(obj)
-  showUploadDialog.value = false
   const text = {
     remark: '发送简历',
     query: {
@@ -490,7 +515,29 @@ const handleSubmitAttachment = async () => {
     },
     type: 1
   }
+  if (enRequestPositionInfo.value) text.query.positionInfo = enRequestPositionInfo.value
   send (JSON.stringify(text), channelItem.value, 105)
+
+  // 简历投递至简历库
+  if (isEmployment.value !== '-1') {
+    await jobCvRelHireSend({
+      jobId: positionInfo.value.id,
+      url: obj.url,
+      recommendUserId: isEmployment.value
+    })
+  } else {
+    const jobId = enRequestPositionInfo.value && enRequestPositionInfo.value?.id ? enRequestPositionInfo.value?.id : positionInfo.value.id
+    const type = (enRequestPositionInfo.value && Object.keys(enRequestPositionInfo.value).length ? enRequestPositionInfo.value.hire : positionInfo.value.hire) ? 1 : 0
+    await jobCvRelSend({
+      jobId,
+      title: obj.title,
+      url: obj.url,
+      type
+    })
+  }
+  handleChangeSendResumeStatus(true)
+  showUploadDialog.value = false
+  enRequestPositionInfo.value = {}
 }
 
 async function handleSubmitResume () {
@@ -508,6 +555,7 @@ async function handleSubmitResume () {
     },
     type: 1
   }
+  if (enRequestPositionInfo.value) text.query.positionInfo = enRequestPositionInfo.value
   send (JSON.stringify(text), channelItem.value, 105)
 
   // 简历投递至简历库
@@ -518,28 +566,18 @@ async function handleSubmitResume () {
       recommendUserId: isEmployment.value
     })
   } else {
+    const jobId = enRequestPositionInfo.value && enRequestPositionInfo.value?.id ? enRequestPositionInfo.value?.id : positionInfo.value.id
+    const type = (enRequestPositionInfo.value && Object.keys(enRequestPositionInfo.value).length ? enRequestPositionInfo.value.hire : positionInfo.value.hire) ? 1 : 0
     await jobCvRelSend({
-      jobId: positionInfo.value.id,
+      jobId,
       title: _info.title,
       url: _info.url,
-      type: positionInfo.value.hire ? 1 : 0
+      type
     })
   }
   handleChangeSendResumeStatus(true)
   showResume.value = false
-}
-// 求简历
-function handleRequest () {
-  const text = {
-    remark: '求简历',
-    query: {
-      src: '',
-      title: '',
-      id: '',
-    },
-    type: 2
-  }
-  send (JSON.stringify(text), channelItem.value, 105)
+  enRequestPositionInfo.value = {}
 }
 
 // 简历预览
@@ -588,6 +626,7 @@ async function handleInvite (item) {
     //   userId: '',
     //   jobId: ''
     // }
+    if (item.key === 'requestResume') return showSelectPosition.value = true
     showInvite.value = true
     // send(JSON.stringify(msg), channelItem.value, 101)
     // console.log(query)
@@ -598,6 +637,7 @@ async function handleInvite (item) {
   }
 }
 
+// 企业-发送面试邀请
 const handleSubmit = async () => {
   const { valid } = await inviteRef.value.CtFormRef.formRef.validate()
   if (!valid) {
@@ -617,7 +657,27 @@ const handleSubmit = async () => {
   Snackbar.success(t('common.operationSuccessful'))
   send(JSON.stringify(query), channelItem.value, 101)
   showInvite.value = false
+}
 
+// 企业-求简历
+const handleRequestResumeSubmit = async () => {
+  const { valid } = await requestFromRef.value.formRef.validate()
+  if (!valid) return
+  const jobId = requestFormItems.value.options.find(e => e.key === 'jobId').value
+  const positionInfo = positionList.value.find(e => e.value === jobId)
+
+  const text = {
+    remark: '求简历',
+    query: {
+      src: '',
+      title: '',
+      id: '',
+      positionInfo
+    },
+    type: 2
+  }
+  send (JSON.stringify(text), channelItem.value, 105)
+  showSelectPosition.value = false
 }
 
 const handleAgree = (val) => {

+ 49 - 14
src/views/recruit/enterprise/positionManagement/index.vue

@@ -6,6 +6,11 @@
       </div>
       <div class="text-end">
         <v-btn prepend-icon="mdi-plus" color="primary" @click="handleAdd">{{ $t('position.newPositionsAdded') }}</v-btn>
+        <v-btn :loading="uploadLoading" prepend-icon="mdi-download-box-outline" color="primary" variant="tonal" class="ml-3" @click="handleUpload">
+          批量上传职位
+          <File ref="uploadFile" :custom="true" customName="multipartFile" accept=".xlsx, .xls" @success="handleUploadPosition"></File>
+        </v-btn>
+        <v-btn :loading="templateLoading" prepend-icon="mdi-export-variant" color="primary" variant="tonal" class="ml-3" @click="handleDownloadTemplate">批量上传模版下载</v-btn>
         <v-btn :loading="exportLoading" prepend-icon="mdi-export-variant" color="primary" variant="tonal" class="ml-3" @click="handleExport">职位列表下载</v-btn>
       </div>
       
@@ -37,11 +42,12 @@ import { ref } from 'vue'
 import TextUI from '@/components/FormUI/TextInput'
 import PositionItem from './components/item.vue'
 import { useRouter } from 'vue-router'; const router = useRouter()
-import { getJobAdvertisedList, getJobAdvertisedExport } from '@/api/position'
+import { getJobAdvertisedList, getJobAdvertisedExport, jobAdvertisedTemplateDownload, jobAdvertisedUpload } from '@/api/position'
 import { dealDictArrayData } from '@/utils/position'
 import { useI18n } from '@/hooks/web/useI18n'
 import { useUserStore } from '@/store/user'
 import download from '@/utils/download'
+import Snackbar from '@/plugins/snackbar'
 
 const store = useUserStore()
 
@@ -55,7 +61,10 @@ const query = ref({
   hasExpiredData: false, // true 到期职位
   hire: false // true 众聘岗位
 })
+const templateLoading = ref(false)
+const uploadLoading = ref(false)
 const exportLoading = ref(false)
+const uploadFile = ref()
 
 const tab = ref(1)
 
@@ -80,19 +89,6 @@ const handleAdd = async () => {
   await store.getEnterpriseUserAccountInfo()
 }
 
-const handleExport = async () => {
-  exportLoading.value = true
-  try {
-    const data = await getJobAdvertisedExport(query.value)
-    const label = tabList.find(e => e.value === tab.value)?.label || ''
-    const txt = `职位列表${label? '(' + label + ')' : ''}`
-    download.excel(data, txt)
-  } finally {
-    exportLoading.value = false
-  }
-}
-
-
 const loading = ref(false)
 // 获取职位列表
 const getPositionList = async () => {
@@ -115,6 +111,45 @@ const getPositionList = async () => {
 }
 getPositionList()
 
+// 职位列表导出
+const handleExport = async () => {
+  exportLoading.value = true
+  try {
+    const data = await getJobAdvertisedExport(query.value)
+    const label = tabList.find(e => e.value === tab.value)?.label || ''
+    const txt = `职位列表${label? '(' + label + ')' : ''}`
+    download.excel(data, txt)
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+// 批量上传模版导出
+const handleDownloadTemplate = async () => {
+  templateLoading.value = true
+  try {
+    const data = await jobAdvertisedTemplateDownload()
+    download.excel(data, '批量上传模版下载')
+  } finally {
+    templateLoading.value = false
+  }
+}
+
+// 批量上传
+const handleUpload = () => {
+  uploadFile.value.trigger()
+}
+const handleUploadPosition = async (formData) => {
+  uploadLoading.value = true
+  try {
+    await jobAdvertisedUpload(formData)
+    Snackbar.success('上传成功')
+    getPositionList()
+  } finally {
+    uploadLoading.value = false
+  }
+}
+
 const handleChangeTab = () => {
   query.value.pageNo = 1
   getPositionList()

+ 6 - 0
src/views/recruit/enterprise/staffInfoSetting/index.vue

@@ -95,6 +95,12 @@ const formItems = ref({
       //     return '请输入正确的电子邮箱'
       //   }
       // ]
+    },
+    {
+      type: 'text',
+      key: 'postName',
+      value: '',
+      label: '所属岗位'
     }
   ]
 })

+ 2 - 62
src/views/recruit/enterprise/systemManagement/groupAccount/index.vue

@@ -43,7 +43,6 @@
           @pageHandleChange="handleChangePage"
         >
           <template #actions="{ item }">
-            <!-- <v-btn color="primary" variant="text" @click="handleBinding(item)">{{ $t('enterprise.userManagement.jobBinding') }}</v-btn> -->
             <v-btn v-if="item.status === '1' && item.userType !== '1'" color="primary" variant="text" @click="handleAction('', 0, item)">{{ $t('enterprise.userManagement.enable') }}</v-btn>
             <v-btn v-if="item.status === '0' && item.userType !== '1'" color="primary" variant="text" @click="handleAction('', 1, item)">{{ $t('enterprise.userManagement.disable') }}</v-btn>
             <v-btn color="primary" variant="text" @click="handleChangeEmail(item)">修改员工邮箱</v-btn>
@@ -53,9 +52,6 @@
     </v-row>
   </v-card>
 
-  <CtDialog :visible="show" :widthType="2" titleClass="text-h6" :title="$t('enterprise.userManagement.selectBinding')" @close="handleClose" @submit="handleSubmit">
-    <CtForm ref="formPageRef" :items="formItems"></CtForm>
-  </CtDialog>
   <CtDialog :visible="showEditEmail" :widthType="2" titleClass="text-h6" title="修改员工邮箱" @close="showEditEmail = false" @submit="handleEditEmailSubmit">
     <CtForm ref="editEmailFormRef" class="mt-3" :items="emailFormItems"></CtForm>
   </CtDialog>
@@ -66,14 +62,12 @@ defineOptions({ name: 'group-account'})
 import { ref } from 'vue'
 import { useI18n } from '@/hooks/web/useI18n'
 import { timesTampChange } from '@/utils/date'
-import { getEnterprisePostPage } from '@/api/recruit/enterprise/system/post'
 import { getEnterpriseTree } from '@/api/recruit/enterprise/system/group'
-import { getEnterpriseUserList, systemUserEnable, systemUserDisable, systemUserBindingPost } from '@/api/recruit/enterprise/system/user'
+import { getEnterpriseUserList, systemUserEnable, systemUserDisable } from '@/api/recruit/enterprise/system/user'
 import Confirm from '@/plugins/confirm'
 import Snackbar from '@/plugins/snackbar'
 import { checkEmail } from '@/utils/validate'
 import { entUpdateEmail } from '@/api/enterprise'
-// import { useRouter } from 'vue-router'; const router = useRouter()
 
 const { t } = useI18n()
 const total = ref(0)
@@ -89,7 +83,7 @@ const treeData = ref([])
 const headers = [
   { title: t('login.username'), key: 'name', sortable: false },
   { title: t('enterprise.userManagement.affiliatedEnterprise'), key: 'enterpriseAnotherName', sortable: false },
-  { title: t('enterprise.userManagement.post'), key: 'post.nameCn', sortable: false },
+  { title: t('enterprise.userManagement.post'), key: 'postName', sortable: false },
   { title: t('enterprise.userManagement.phone'), key: 'phone', sortable: false },
   { title: t('enterprise.userManagement.email'), key: 'email', sortable: false },
   { title: t('enterprise.userManagement.accountType'), key: 'userType', value: item => item.userType === '1' ? t('enterprise.userManagement.administrators') : t('enterprise.userManagement.regularUser'), sortable: false },
@@ -104,26 +98,6 @@ const textItem = ref({
   label: '请输入用户名称搜索'
 })
 
-const show = ref(false)
-const formPageRef = ref()
-const bindQuery = ref({})
-const postList = ref([])
-const formItems = ref({
-  options: [
-    {
-      type: 'autocomplete',
-      key: 'postId',
-      value: null,
-      label: '岗位 *',
-      noAttach: false,
-      itemText: 'nameCn',
-      itemValue: 'id',
-      rules: [v => !!v || '请选择要绑定的岗位'],
-      items: []
-    }
-  ]
-})
-
 // 获取用户列表
 const getUserList = async () => {
   loading.value = true
@@ -160,12 +134,6 @@ const handleClick = (e) => {
   getUserList()
 }
 
-const getPostList = async () => {
-  const res = await getEnterprisePostPage({ pageNo: 1, pageSize: 100 })
-  postList.value = res.list
-}
-getPostList()
-
 const apiList = [
   { api: systemUserEnable, desc: t('enterprise.userManagement.enableAccount') }, 
   { api: systemUserDisable, desc: t('enterprise.userManagement.disableAccount') }
@@ -182,39 +150,11 @@ const handleAction = (type, index, item) => {
   })
 }
 
-// 绑定岗位
-// const handleBinding = async (item) => {
-//   if (!postList.value.length) {
-//     Snackbar.warning(t('enterprise.userManagement.postNodataToAdd'))
-//     return
-//   }
-//   bindQuery.value.id = item.id
-//   const obj = formItems.value.options.find(e => e.key === 'postId')
-//   obj.items = postList.value
-//   obj.value = item.postId
-//   show.value = true
-// }
-
 const handleAdd = (type) => {
   // type: 类型(0 邀请同事 | 1 邀请子公司)
   window.open(`/recruit/enterprise/systemManagement/groupAccount/invite/${type}`)
 }
 
-const handleClose = () => {
-  show.value = false
-  query.value = {}
-}
-
-const handleSubmit = async () => {
-  const { valid } = await formPageRef.value.formRef.validate()
-  if (!valid) return
-  const postId = formItems.value.options.find(e => e.key === 'postId').value
-  await systemUserBindingPost(bindQuery.value.id, postId)
-  Snackbar.success(t('common.operationSuccessful'))
-  handleClose()
-  getUserList()
-}
-
 const emailFormItems = ref({
   options: [
     {

+ 2 - 2
src/views/recruit/personal/PersonalCenter/resume/online/index.vue

@@ -43,7 +43,7 @@ import vocationalSkills from './components/vocationalSkills.vue'
 const { t } = useI18n()
 const scrollBox = ref()
 const tab = ref(0)
-const paths = [basicInfo, selfEvaluation, jobIntention, educationExp, workExperience, trainingExperience, vocationalSkills]
+const paths = [basicInfo, selfEvaluation, jobIntention, educationExp, workExperience, vocationalSkills, trainingExperience]
 const items = ref([
   { text: t('resume.basicInfo'), id: 'basicInfo', status: false },
   { text: t('resume.personalAdvantages'), id: 'selfEvaluation', status: false },
@@ -51,8 +51,8 @@ const items = ref([
   { text: t('resume.educationExp'), id: 'educationExp', status: false },
   { text: t('resume.workExperience'), id: 'workExperience', status: false },
   // { text: t('resume.projectExperience'), id: 'projectExperience', status: false },
+  { text: t('resume.vocationalSkills'), id: 'vocationalSkills', status: false },
   { text: t('resume.trainingExperience'), id: 'trainingExperience', status: false },
-  { text: t('resume.vocationalSkills'), id: 'vocationalSkills', status: false }
 ])
 
 onMounted(() => {