Просмотр исходного кода

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

lifanagju_citu 7 месяцев назад
Родитель
Сommit
e577db45fd

+ 1 - 0
components.d.ts

@@ -63,6 +63,7 @@ declare module 'vue' {
     SvgIcon: typeof import('./src/components/SvgIcon/index.vue')['default']
     TextArea: typeof import('./src/components/FormUI/textArea/index.vue')['default']
     TextInput: typeof import('./src/components/FormUI/TextInput/index.vue')['default']
+    TipDialog: typeof import('./src/components/CtDialog/tipDialog.vue')['default']
     VerificationCode: typeof import('./src/components/VerificationCode/index.vue')['default']
     WangEditor: typeof import('./src/components/FormUI/wangEditor/index.vue')['default']
   }

+ 73 - 0
src/components/CtDialog/tipDialog.vue

@@ -0,0 +1,73 @@
+<template>
+  <div>
+    <v-dialog
+      v-model="show"
+      :max-width="maxWidth"
+      :persistent="persistent"
+    >
+      <div class="white-bgc pa-5" style="border-radius: 2px; max-height: 600px; overflow-y: auto;">
+        <div class="d-flex align-center">
+          <span style="font-size: 44px;" :class="icon" class="mdi ml-2 mr-2" :style="{'color': iconColor}"></span>
+          <div :style="{'color': color}">
+            {{ message }}
+            <span v-if="showSymbol">,</span>
+          </div>
+          <slot></slot>
+        </div>
+      </div>
+      <div class="text-center mt-3">
+        <span style="color: white; font-size: 28px;" class="mdi mdi-close-circle-outline cursor-pointer" @click="show = false; emit('close')"></span>
+      </div>
+    </v-dialog>
+  </div>
+</template>
+
+<script setup>
+defineOptions({ name: 'tipDialog'})
+import { ref, watch } from 'vue'
+
+const emit = defineEmits(['close'])
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    default: false
+  },
+  message: {
+    type: String,
+    default: ''
+  },
+  maxWidth: {
+    type: [String, Number],
+    default: '400'
+  },
+  persistent: {
+    type: Boolean,
+    default: false
+  },
+  icon: {
+    type: String,
+    default: ''
+  },
+  color: {
+    type: String,
+    default: '#666'
+  },
+  iconColor: {
+    type: String,
+    default: 'var(--v-primary-base)'
+  },
+  showSymbol: {
+    type: Boolean,
+    default: true
+  }
+})
+
+const show = ref(false)
+watch(() => props.visible, (newVal) => {
+  show.value = newVal
+})
+</script>
+
+<style scoped lang="scss">
+
+</style>

+ 9 - 5
src/components/CtTable/index.vue

@@ -18,6 +18,7 @@
       :select-strategy="selectStrategy"
       color="primary"
       hover
+      :height="height"
       hide-default-footer
       loading-text="Loading... Please wait"
       fixed-header
@@ -163,10 +164,13 @@ const handleSelect = (e) => {
 </script>
 
 <style scoped lang="scss">
-:deep(.v-table > .v-table__wrapper > table > thead) {
-  background-color: #f7f8fa !important;
-}
-:deep(.v-selection-control__input) {
-  color: var(--v-primary-base) !important;
+:deep {
+  .v-table.v-table--fixed-header > .v-table__wrapper > table > thead > tr > th {
+    text-wrap: nowrap !important;
+    background-color: #f7f8fa !important;
+  }
+  .v-selection-control__input {
+    color: var(--v-primary-base) !important;
+  }
 }
 </style>

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

@@ -76,6 +76,14 @@ const enterprise = [
         },
         component: () => import('@/views/recruit/enterprise/search/index.vue')
       },
+      {
+        path: '/recruit/enterprise/interviewManagement',
+        meta: {
+          title: '面试',
+          enName: 'Interview'
+        },
+        component: () => import('@/views/recruit/enterprise/interviewManagement/index.vue')
+      },
       {
         path: '/recruit/enterprise/chatTools',
         meta: {
@@ -91,14 +99,6 @@ const enterprise = [
           enName: 'Resume'
         },
         component: () => import('@/views/recruit/enterprise/resume/index.vue')
-      },
-      {
-        path: '/recruit/enterprise/interviewManagement',
-        meta: {
-          title: '面试',
-          enName: 'Interview'
-        },
-        component: () => import('@/views/recruit/enterprise/interviewManagement/index.vue')
       }
     ]
   },

+ 28 - 18
src/views/recruit/components/message/index.vue

@@ -147,6 +147,9 @@
   <CtDialog :visible="showInvite" :widthType="4" titleClass="text-h6" title="邀请面试" @close="showInvite = false" @submit="handleSubmit">
     <InvitePage v-if="showInvite" ref="inviteRef" :item-data="itemData" :position="positionList"></InvitePage>
   </CtDialog>
+  <TipDialog :visible="showTip" icon="mdi-check-circle-outline" message="面试邀请发送成功" @close="showTip = false">
+    <div class="color-primary text-decoration-underline cursor-pointer" @click="handleToInterviewManagement">点击到面试中查看。</div>
+  </TipDialog>
 
   <!-- 求简历-选择求简历的职位 -->
   <CtDialog :visible="showSelectPosition" :widthType="2" titleClass="text-h6" title="选择要求简历的职位" @close="showSelectPosition = false" @submit="handleRequestResumeSubmit">
@@ -195,6 +198,7 @@ import { getUserAvatar } from '@/utils/avatar'
 import { dealDictArrayData } from '@/utils/position'
 import { previewFile } from '@/utils'
 import { timesTampChange } from '@/utils/date'
+import { useRouter } from 'vue-router'
 
 const { t } = useI18n()
 const chatRef = ref()
@@ -213,6 +217,7 @@ const pageSize = ref(1)
 const hasMore = ref(false)
 
 const positionList = ref([])
+const showTip = ref(false)
 const showInvite = ref(false)
 // 企业-求简历
 const showSelectPosition = ref(false)
@@ -610,29 +615,29 @@ const handleDelete = async ({ channel }) => {
 // 没有企业ID则enterpriseId为undefined
 // 发送消息体 { text, type: 2 }
 // 面试邀约
+
+const getPositionList = async () => {
+  const data = await getJobAdvertised({})
+  if (!data.length) return
+  const list = dealDictArrayData([], data)
+  positionList.value = list.map(e => {
+    const salary = e.payFrom && e.payTo ? `${e.payFrom ? e.payFrom + '-' : ''}${e.payTo}${e.payName ? '/' + e.payName : ''}` : '面议'
+    return {
+      label: `${e.name}${e.areaName ? '_' + e.areaName : ''} ${salary}`,
+      value: e.id,
+      data: e
+    }
+  })
+}
+
 async function handleInvite (item) {
   item.loading = true
   positionList.value = []
   try {
-    const data = await getJobAdvertised({ hire: false })
-    if (!data.length) return
-    const list = dealDictArrayData([], data)
-    positionList.value = list.map(e => {
-      const salary = e.payFrom && e.payTo ? `${e.payFrom ? e.payFrom + '-' : ''}${e.payTo}${e.payName ? '/' + e.payName : ''}` : '面议'
-      return {
-        label: `${e.name}${e.areaName ? '_' + e.areaName : ''} ${salary}`,
-        value: e.id,
-        data: e
-      }
-    })
-    // itemData.value = {
-    //   userId: '',
-    //   jobId: ''
-    // }
+    await getPositionList()
+    if (!positionList.value.length) return Snackbar.warning('请先发布职位')
     if (item.key === 'requestResume') return showSelectPosition.value = true
     showInvite.value = true
-    // send(JSON.stringify(msg), channelItem.value, 101)
-    // console.log(query)
   } catch (error) {
     console.log(error)
   } finally {
@@ -657,11 +662,16 @@ const handleSubmit = async () => {
   const data = await saveInterviewInvite(query)
   // 保留邀请id
   query.id = data.id
-  Snackbar.success(t('common.operationSuccessful'))
+  showTip.value = true
   send(JSON.stringify(query), channelItem.value, 101)
   showInvite.value = false
 }
 
+const router = useRouter()
+const handleToInterviewManagement = () => {
+  router.push('/recruit/enterprise/interviewManagement')
+}
+
 // 企业-求简历
 const handleRequestResumeSubmit = async () => {
   const { valid } = await requestFromRef.value.formRef.validate()

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

@@ -84,7 +84,7 @@ const formItems = ref({
 
 if (Object.keys(props.itemData).length) {
   formItems.value.options.forEach(item => {
-    item.value = props.itemData[item.key]
+    if (props.itemData[item.key]) item.value = props.itemData[item.key]
   })
 }
 

+ 27 - 14
src/views/recruit/enterprise/resume/components/table.vue

@@ -1,17 +1,21 @@
 <template>
   <div>
-    <v-data-table
+    <CtTable
       class="mt-3"
       :items="items"
       :headers="headers"
-      hover
-      :disable-sort="true"
+      :loading="false"
+      :elevation="0"
       height="60vh"
-      item-value="id"
-      no-data-text="暂无数据"
+      :disableSort="true"
+      :isTools="false"
+      :showPage="true"
+      :total="total"
+      :page-info="pageInfo"
+      itemKey="id"
+      @pageHandleChange="e => emit('page', e)"
     >
-      <template #bottom></template>
-      <template v-slot:[`item.name`]="{ item }">
+      <template #name="{ item }">
         <div class="d-flex align-center cursor-pointer" @click="handleToPersonDetail(item)">
           <v-badge
             v-if="item?.person?.sex === '1' || item?.person?.sex === '2'"
@@ -25,11 +29,11 @@
           <span class="defaultLink ml-3">{{ item?.person?.name }}</span>
         </div>
       </template>
-      <template v-slot:[`item.status`]="{ item }">
+      <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>
       </template>
-      <template v-slot:[`item.actions`]="{ item }">
+      <template #actions="{ item }">
         <v-btn v-if="tab === 0" color="primary" variant="text" @click="handlePreviewResume(item)">查看附件</v-btn>
         <v-btn v-if="tab === 0" :color="item.jobClosed ? 'grey' : 'primary'" variant="text" @click="handleInterviewInvite(item)">邀请面试<v-tooltip v-if="item.jobClosed" activator="parent" location="top">职位已关闭</v-tooltip></v-btn>
         <v-btn v-if="tab === 0" :color="item.jobClosed ? 'grey' : 'primary'" variant="text" @click="handleToCommunicate(item)">立即沟通<v-tooltip v-if="item.jobClosed" activator="parent" location="top">职位已关闭</v-tooltip></v-btn>
@@ -39,12 +43,16 @@
         <v-btn v-if="tab === 4" color="primary" variant="text" @click="handleCancelEliminate(item)">取消不合适</v-btn>
         <v-btn v-if="tab === 2 && item?.job?.hire" color="primary" variant="text" @click="handleSettlement(item)">结算</v-btn>
       </template>
-    </v-data-table>
+    </CtTable>
 
     <!-- 邀请面试 -->
     <CtDialog :visible="showInvite" :widthType="4" titleClass="text-h6" title="面试信息" @close="handleEditClose" @submit="handleEditSubmit">
       <InvitePage v-if="showInvite" ref="inviteRef" :itemData="itemData"></InvitePage>
     </CtDialog>
+
+    <TipDialog :visible="showTip" icon="mdi-check-circle-outline" message="面试邀请发送成功" @close="showTip = false">
+      <div class="color-primary text-decoration-underline cursor-pointer" @click="handleToInterviewManagement">点击到面试中查看。</div>
+    </TipDialog>
   </div>
 </template>
 
@@ -63,12 +71,15 @@ import { getUserAvatar } from '@/utils/avatar'
 import { talkToUser, defaultTextEnt } from '@/hooks/web/useIM'
 import { useRouter } from 'vue-router'; const router = useRouter()
 
+const showTip = ref(false)
 const { t } = useI18n()
 const emit = defineEmits(['refresh'])
 const props = defineProps({
   tab: Number,
   items: Array,
-  statusList: Array
+  statusList: Array,
+  pageInfo: Object,
+  total: Number
 })
 const badgeColor = computed(() => (item) => {
   return (item.person && item.person.sex) ? (item.person.sex === '1' ? '#1867c0' : 'error') : 'error'
@@ -80,7 +91,6 @@ const badgeIcon = computed(() => (item) => {
 
 const userStore = useUserStore()
 const inviteRef = ref()
-// const publicRef = ref()
 const showInvite = ref(false)
 const headers = ref([
   { title: '姓名', value: 'name', sortable: false },
@@ -192,13 +202,12 @@ const handleEditClose = () => {
 }
 
 const handleEditSubmit = async () => {
-  // if (inviteType.value) return
   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'))
+  showTip.value = true
   handleEditClose()
   emit('refresh')
 }
@@ -214,6 +223,10 @@ const handleSettlement = async (item) => {
     await userStore.getEnterpriseUserAccountInfo()
   }, 2000)
 }
+
+const handleToInterviewManagement = () => {
+  router.push('/recruit/enterprise/interviewManagement')
+}
 </script>
 
 <style scoped lang="scss">

+ 1 - 8
src/views/recruit/enterprise/resume/index.vue

@@ -11,14 +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" :statusList="statusList" @refresh="getList"></TablePage>
-        <CtPagination
-          v-if="total > 0"
-          :total="total"
-          :page="query.pageNo"
-          :limit="query.pageSize"
-          @handleChange="handleChangePage"
-        ></CtPagination>
+        <TablePage :items="items" :total="total"  :pageInfo="query" :tab="k.value" :statusList="statusList" @refresh="getList" @page="handleChangePage"></TablePage>
       </v-window-item>
     </v-window>
   </v-card>

+ 24 - 1
src/views/recruit/enterprise/search/recommend/index.vue

@@ -32,6 +32,7 @@
       </template>
       <template #actions="{ item }">
         <v-btn color="primary" variant="text" @click="handleInvite(item)">邀请面试</v-btn>
+        <v-btn color="primary" variant="text" @click="handleCommunicate(item)">立即沟通</v-btn>
       </template>
     </CtTable>
   </div>
@@ -40,6 +41,9 @@
   <CtDialog :visible="showInvite" :widthType="4" titleClass="text-h6" title="邀请面试" @close="showInvite = false; itemData = {}" @submit="handleSubmit">
     <InvitePage v-if="showInvite" ref="inviteRef" :itemData="itemData"></InvitePage>
   </CtDialog>
+  <TipDialog :visible="showTip" icon="mdi-check-circle-outline" message="面试邀请发送成功" @close="showTip = false">
+    <div class="color-primary text-decoration-underline cursor-pointer" @click="handleToInterviewManagement">点击到面试中查看。</div>
+  </TipDialog>
 </template>
 
 <script setup>
@@ -52,6 +56,8 @@ import { getUserAvatar } from '@/utils/avatar'
 import InvitePage from '@/views/recruit/enterprise/resume/components/invite'
 import Snackbar from '@/plugins/snackbar'
 import { saveInterviewInvite } from '@/api/recruit/enterprise/interview'
+import { useRouter } from 'vue-router'; const router = useRouter()
+import { talkToUser, defaultTextEnt } from '@/hooks/web/useIM'
 
 const query = ref({
   pageNo: 1,
@@ -80,6 +86,7 @@ const headers = ref([
   { title: '操作', key: 'actions', sortable: false }
 ])
 const inviteRef = ref()
+const showTip = ref(false)
 const showInvite = ref(false)
 
 // 职位列表
@@ -146,17 +153,33 @@ const handleSubmit = async () => {
     return
   }
   await saveInterviewInvite(query)
-  Snackbar.success('操作成功')
   showInvite.value = false
+  showTip.value = true
+  getData()
   itemData .value = {}
 }
 
+// 立即沟通
+const handleCommunicate = async (item) => {
+  // // 企业必须有招聘中的职位才能发起沟通
+  if (!selectItems.value.items.length) return Snackbar.warning('请先发布职位')
+  const userId = item.userId
+  if (!userId) return
+  await talkToUser({userId, text: defaultTextEnt})
+  let url = `/recruit/enterprise/chatTools?id=${userId}`
+  router.push(url)
+}
+
 // 人才详情
 const handleToPersonDetail = ({ userId, id }) => {
   if (!userId || !id) return
   window.open(`/recruit/enterprise/talentPool/details/${userId}?id=${id}`)
 }
 
+const handleToInterviewManagement = () => {
+  router.push('/recruit/enterprise/interviewManagement')
+}
+
 const badgeColor = computed(() => (item) => {
   return (item && item.sex) ? (item.sex === '1' ? '#1867c0' : 'error') : 'error'
 })

+ 81 - 1
src/views/recruit/enterprise/search/retrieval/index.vue

@@ -49,8 +49,19 @@
           <span class="defaultLink ml-3 mt-2">{{ item?.name }}</span>
         </div>
       </template>
+      <template #actions="{ item }">
+        <v-btn color="primary" variant="text" @click="handleInvite(item)">邀请面试</v-btn>
+        <v-btn color="primary" variant="text" @click="handleCommunicate(item)">立即沟通</v-btn>
+      </template>
     </CtTable>
   </div>
+
+  <CtDialog :visible="showInvite" :widthType="4" titleClass="text-h6" title="邀请面试" @close="showInvite = false" @submit="handleSubmit">
+    <InvitePage v-if="showInvite" ref="inviteRef" :item-data="itemData" :position="positionList"></InvitePage>
+  </CtDialog>
+  <TipDialog :visible="showTip" icon="mdi-check-circle-outline" message="面试邀请发送成功" @close="showTip = false">
+    <div class="color-primary text-decoration-underline cursor-pointer" @click="handleToInterviewManagement">点击到面试中查看。</div>
+  </TipDialog>
 </template>
 
 <script setup>
@@ -62,8 +73,13 @@ import Area from './components/area.vue'
 import { getPersonSearchPage } from '@/api/enterprise.js'
 import { dealDictArrayData } from '@/utils/position'
 import { timesTampChange } from '@/utils/date'
+import { talkToUser, defaultTextEnt } from '@/hooks/web/useIM'
 import { getUserAvatar } from '@/utils/avatar'
+import { getJobAdvertisedList } from '@/api/position'
 import Snackbar from '@/plugins/snackbar'
+import { saveInterviewInvite } from '@/api/recruit/enterprise/interview'
+import { useRouter } from 'vue-router'; const router = useRouter()
+import InvitePage from '@/views/recruit/enterprise/interviewManagement/components/invite'
 
 const textItem = ref({
   type: 'text',
@@ -96,7 +112,8 @@ const headers = ref([
   { title: '所在城市', key: 'areaName', sortable: false },
   { title: '户籍地', key: 'regName', sortable: false },
   { title: '婚姻状况', key: 'maritalStatusName', sortable: false },
-  { title: '首次工作时间', key: 'firstWorkTime', sortable: false, value: item => timesTampChange(item.firstWorkTime, 'Y-M-D') }
+  { title: '首次工作时间', key: 'firstWorkTime', sortable: false, value: item => timesTampChange(item.firstWorkTime, 'Y-M-D') },
+  { title: '操作', key: 'actions', sortable: false }
 ])
 
 const getData = async () => {
@@ -159,6 +176,69 @@ const handleClose = (item) => {
   position.value = position.value.filter(k => k.id !== item.id)
 }
 
+// 职位列表
+const jobNum = ref(0)
+const positionList = ref([])
+const getJobList = async () => {
+  const { total, list } = await getJobAdvertisedList({ pageNo: 1, pageSize: 10, hasExpiredData: false, status: 0 })
+  jobNum.value = total
+  if (!list.length) {
+    positionList.value = []
+    jobNum.value = 0
+    return
+  }
+  positionList.value = list.map(e => {
+    const salary = e.payFrom && e.payTo ? `${e.payFrom ? e.payFrom + '-' : ''}${e.payTo}${e.payName ? '/' + e.payName : ''}` : '面议'
+    return {
+      label: `${e.name}${e.areaName ? '_' + e.areaName : ''} ${salary}`,
+      value: e.id,
+      data: e
+    }
+  })
+}
+getJobList()
+
+// 立即沟通
+const handleCommunicate = async (item) => {
+  // // 企业必须有招聘中的职位才能发起沟通
+  if (jobNum.value === 0) return Snackbar.warning('请先发布职位')
+  const userId = item.userId
+  if (!userId) return
+  await talkToUser({userId, text: defaultTextEnt})
+  let url = `/recruit/enterprise/chatTools?id=${userId}`
+  router.push(url)
+}
+
+const showInvite = ref(false)
+const showTip = ref(false)
+const inviteRef = ref()
+const itemData = ref({})
+const handleInvite = (item) => {
+  if (jobNum.value === 0) return Snackbar.warning('请先发布职位')
+  itemData.value = item
+  showInvite.value = true
+}
+// 发送面试邀请
+const handleSubmit = async () => {
+  const { valid } = await inviteRef.value.CtFormRef.formRef.validate()
+  if (!valid) {
+    return
+  }
+  const query = inviteRef.value.getQuery()
+  if (!query.time) {
+    Snackbar.warning('时间不能为空')
+    return
+  }
+  delete query.id
+  await saveInterviewInvite(query)
+  showInvite.value = false
+  showTip.value = true
+}
+
+const handleToInterviewManagement = () => {
+  router.push('/recruit/enterprise/interviewManagement')
+}
+
 // 人才详情
 const handleToPersonDetail = ({ userId, id }) => {
   if (!userId || !id) return

+ 16 - 16
src/views/recruit/enterprise/staffInfoSetting/index.vue

@@ -32,7 +32,7 @@ import { ref } from 'vue'
 import { saveUserInfo } from '@/api/enterprise'
 import { uploadFile } from '@/api/common'
 import { useI18n } from '@/hooks/web/useI18n'
-import { getDict } from '@/hooks/web/useDictionaries'
+// import { getDict } from '@/hooks/web/useDictionaries'
 import { useUserStore } from '@/store/user'
 import Snackbar from '@/plugins/snackbar'
 import { getUserAvatar } from '@/utils/avatar'
@@ -56,15 +56,15 @@ const formItems = ref({
       value: '',
       flexStyle: 'align-center'
     },
-    {
-      type: 'ifRadio',
-      key: 'sex',
-      value: '2',
-      label: '性别',
-      width: 90,
-      dictTypeName: 'menduner_sex',
-      items: []
-    },
+    // {
+    //   type: 'ifRadio',
+    //   key: 'sex',
+    //   value: '2',
+    //   label: '性别',
+    //   width: 90,
+    //   dictTypeName: 'menduner_sex',
+    //   items: []
+    // },
     {
       type: 'text',
       key: 'name',
@@ -111,12 +111,12 @@ const baseInfo = ref(JSON.parse(localStorage.getItem('entBaseInfo')) || {})
 const query = ref({})
 // 获取字典数据以及字段回显
 formItems.value.options.forEach(item => {
-  if (item.dictTypeName) {
-    getDict(item.dictTypeName).then(({ data }) => {
-      data = data?.length && data || []
-      item.items = data
-    })
-  }
+  // if (item.dictTypeName) {
+  //   getDict(item.dictTypeName).then(({ data }) => {
+  //     data = data?.length && data || []
+  //     item.items = data
+  //   })
+  // }
   if (Object.keys(baseInfo).length) {
     item.value = baseInfo.value[item.key]
     query.value.id = baseInfo.value.id