Browse Source

邀请面试

Xiao_123 9 months ago
parent
commit
661e80a919

+ 7 - 0
src/api/common/index.js

@@ -249,4 +249,11 @@ export const deleteConversation = async (data) => {
     url: '/app-api/im/conversation/delete',
     data
   })
+}
+
+// 求职端-根据邀请人id获取面试邀约列表
+export const getInterviewInviteListByInviteUserId = async (inviteUserId) => {
+  return await request.get({
+    url: `/app-api/menduner/system/interview-invite/get/list/by/${inviteUserId}`
+  })
 }

+ 1 - 1
src/api/position.js

@@ -107,7 +107,7 @@ export const jobCvRelSend = async (data) => {
 // 获取已投递的职位列表
 export const getJobDeliveryList = async (params) => {
   return await request.get({
-    url: '/app-api/menduner/system/job-cv-rel/get/job/cv/page',
+    url: '/app-api/menduner/system/job-cv-rel/page',
     params
   })
 }

+ 2 - 2
src/api/publicRecruitment/index.js

@@ -3,14 +3,14 @@ import request from '@/config/axios'
 // 获取推荐职位投递状态数量
 export const getHireJobCvRelCount = async () => {
   return await request.get({
-    url: '/app-api/menduner/system/job-cv-rel/get/commend/count'
+    url: '/app-api/menduner/system/job-cv-rel/hire/get/recommend/count'
   })
 }
 
 // 获取推荐邀请投递的职位信息
 export const getHireJobCvRelPage = async (params) => {
   return await request.get({
-    url: '/app-api/menduner/system/job-cv-rel/page',
+    url: '/app-api/menduner/system/job-cv-rel/hire/page',
     params
   })
 }

+ 1 - 1
src/api/recruit/personal/personalCenter/index.js

@@ -4,7 +4,7 @@ import request from '@/config/axios'
 // 看过我的企业用户列表
 export const getInterestedMePage = async (params) => {
   return await request.get({
-    url: '/app-api/menduner/system/job-cv-rel/get/job/cv/look/page',
+    url: '/app-api/menduner/system/job-cv-rel/look/page',
     params
   })
 }

+ 24 - 10
src/hooks/web/useIM.js

@@ -22,10 +22,12 @@ import {
 } from "wukongimjssdk"
 
 
-const ObjType = [ 101, 102 ]
+const ObjType = [ 101, 102, 103, 104 ]
 
 const { ObjectContent } = initRegister(101)
 const { ObjectContent: ObjectContent2 } = initRegister(102)
+const { ObjectContent: ObjectContent3 } = initRegister(103)
+const { ObjectContent: ObjectContent4 } = initRegister(104)
 // 注册消息体
 function initRegister (contentType) {
   class ObjectContent extends MessageContent {
@@ -121,15 +123,15 @@ export function useDataSource () {
     const resp = await getMessageSync(query)
     const messageList = resp && resp["messages"]
     if (messageList) {
-        messageList.forEach((msg) => {
-            // const message = Convert.toMessage(msg);
-            // msg.channel = new Channel(msg.channel_id, msg.channel_type)
-            msg.payload = JSON.parse(Base64.decode(msg.payload))
-            if (ObjType.includes(msg.payload.type)) {
-              msg.payload.content = JSON.parse(msg.payload.content ?? '{}')
-            }
-            resultMessages.push(msg);
-        });
+      messageList.forEach((msg) => {
+        // const message = Convert.toMessage(msg);
+        // msg.channel = new Channel(msg.channel_id, msg.channel_type)
+        msg.payload = JSON.parse(Base64.decode(msg.payload))
+        if (ObjType.includes(msg.payload.type)) {
+          msg.payload.content = JSON.parse(msg.payload.content ?? '{}')
+        }
+        resultMessages.push(msg)
+      })
     }
     // console.log(resultMessages)
     const more = resp.more === 1
@@ -365,6 +367,18 @@ export function send (text, _channel, type) {
     // console.log(_text)
     return
   }
+  // 求职者拒绝面试邀请
+  if (type === 103) {
+    _text = new ObjectContent3(text)
+    WKSDK.shared().chatManager.send(_text, _channel)
+    return
+  }
+  // 求职者接受面试邀请
+  if (type === 104) {
+    _text = new ObjectContent4(text)
+    WKSDK.shared().chatManager.send(_text, _channel)
+    return
+  }
   _text = new MessageText(text)
   // console.log(_text)
   WKSDK.shared().chatManager.send(_text, _channel)

+ 86 - 119
src/views/recruit/components/message/components/chatting.vue

@@ -22,26 +22,36 @@
           </template>
         </p>
       </div>
-      <!-- <div class="position-content" v-if="!isEnterprise">
-        <div class="d-flex mb-2">
-          <div class="font-weight-black">{{ info.name }}</div>
-          <div class="position-content-emolument">{{ info.payFrom }} - {{ info.payFrom }}</div>
+    </div>
+    <v-divider></v-divider>
+    <div class="pa-3" v-if="interview.length">
+      <div v-for="val in interview" :key="val.id" class="color-666">
+        <div class="d-flex justify-space-between">
+          <div class="font-weight-bold">
+            <span>{{ val.job.name }}</span>
+            <span class="ml-3">{{ val.job.payFrom }}-{{ val.job.payTo }}/{{ val.job.payUnit }}</span>
+          </div>
+          <div>{{ statusList.find(e => e.value === val.status).label }}</div>
         </div>
-        <div class="text-subtitle-2">{{ info?.enterprise?.name }}</div>
-        <div class="pt-3">
-          <v-chip
-            v-for="(item, index) in info?.enterprise?.welfareList"
-            :key="item + index"
-            color="green"
-            label
-            class="mr-3 mb-3"
-          >
-            {{ item }}
-          </v-chip>
+        <div class="d-flex align-center mt-1 font-size-14 justify-space-between">
+          <div>
+            <span>面试时间:{{ timesTampChange(val.time, 'Y-M-D h:m') }}</span>
+            <span class="septal-line"></span>
+            <span>面试地点:{{ val.address }}</span>
+            <span class="septal-line"></span>
+            <span>联系电话:{{ val.invitePhone }}</span>
+          </div>
+        </div>
+        <div class="mt-2 d-flex justify-space-between align-center">
+          <div class="tipsText" @click="handleToCenter">在“个人中心-面试”中管理我的面试</div>
+          <div v-if="val.status === '0'">
+            <v-btn class="mr-3" variant="outlined" color="error" size="small" @click="handleRefuse(val)">拒绝邀请</v-btn>
+            <v-btn variant="outlined" color="primary" size="small" @click="handleAgree(val)">接受邀请</v-btn>
+          </div>
         </div>
-      </div> -->
+      </div>
     </div>
-    <v-divider></v-divider>
+    <v-divider v-if="interview.length"></v-divider>
     <div class="my-3 message-box" @scroll="handleScroll" ref="chatRef">
       <div>
         <div class="d-flex justify-center" v-if="hasMore">
@@ -86,8 +96,6 @@
                 <div class="text-subtitle-2 text-right">
                   地址:{{ val.payload.content.positionInfo.address }}
                 </div>
-                
-                
               </div>
             </v-card>
           </template>
@@ -108,23 +116,31 @@
                 {{ val.payload.content.text }}
               </div>
             </template>
-            <!-- 发起职位 -->
+            <!-- 发起面试邀请 -->
             <div v-else-if="val.payload.type === 101">
-              <v-chip
-                class="ma-2"
-                color="teal"
-                label
-              >
-              发起了面试邀请
-              <v-icon icon="mdi-email-newsletter" end></v-icon>
+              <v-chip class="ma-2" color="teal" label>
+                <v-icon icon="mdi-email-newsletter" start></v-icon>
+                发起了面试邀请
+              </v-chip>
+            </div>
+            <div v-else-if="val.payload.type === 103">
+              <v-chip class="ma-2" label color="error">
+                <v-icon icon="mdi-close" start></v-icon>
+                拒绝了面试邀请
+              </v-chip>
+            </div>
+            <div v-else-if="val.payload.type === 104">
+              <v-chip class="ma-2" label color="primary">
+                <v-icon icon="mdi-check" start></v-icon>
+                接受了面试邀请
               </v-chip>
             </div>
             <div v-else class="message-text" :class="{ active: val.from_uid === IM.uid}">
               {{ val.payload.content }}
             </div>
           </div>
-          <!-- 插入面试职位邀请 -->
-          <div v-if="val.payload.type === 101" class="d-flex justify-center">
+          <!-- 插入个人-面试职位邀请:同意、拒绝 -->
+          <div v-if="isEnterprise && val.payload.type === 101" class="d-flex justify-center">
             <v-card
               color="teal"
               variant="tonal"
@@ -135,7 +151,6 @@
             >
               <v-card-item>
                 <div>
-                  <!-- {{val.payload.content}} -->
                   <div class="text-overline mb-1">
                     面试邀请
                   </div>
@@ -149,36 +164,18 @@
                     </div>
                   </div>
 
-                  <!-- <div class="text-caption"></div> -->
                   <div class="text-caption">面试时间: {{ timesTampChange(val.payload.content?.time) }}</div>
                   <div class="text-caption">面试地点: {{ val.payload.content.address }}</div>
                   <div class="text-caption">联系电话: {{ val.payload.content.invitePhone }}</div>
                 </div>
               </v-card-item>
-
-              <v-card-actions class="justify-end" v-if="val.from_uid !== IM.uid">
-                <v-btn  @click="handleAgree(val.payload.content)">
-                  接受邀请
-                </v-btn>
-              </v-card-actions>
             </v-card>
           </div>
         </div>
       </div>
     </div>
-    <!-- <v-divider></v-divider> -->
     <div class="tools pa-3" v-if="Object.keys(info).length > 0">
       <slot name="tools"></slot>
-      <!-- <v-btn
-        v-for="tool in tools"
-        :key="tool.name"
-        size="small"
-        :prepend-icon="tool.icon"
-        class="mr-3"
-        @click="tool.handle"
-      >
-        {{ tool.name }}
-      </v-btn> -->
     </div>
     <div class="bottom-info">
       <v-divider></v-divider>
@@ -189,21 +186,16 @@
           placeholder="请输入消息 按Ctrl+Enter换行"
           hide-details
           no-resize
+          color="primary"
           bg-color="white"
           variant="plain"
           :disabled="Object.keys(info).length === 0"
           @keydown="handleKeyDown"
         >
-          <!-- @keydown.stop.prevent="handleKeyDown" -->
           <template #append-inner>
             <v-btn color="primary" :disabled="!inputVal" style="align-self: center;" @click="handleSend">发送</v-btn>
           </template>
         </v-textarea>
-        <!-- <v-text-field  v-model="inputVal" label="请输入消息" color="primary" density="comfortable" variant="plain" hide-details></v-text-field> -->
-        <!-- <div class="d-flex align-center justify-end bottom-send">
-          <div class="color-ccc font-size-14 mr-5">按Enter键发送</div>
-          <v-btn color="primary" size="small" :disabled="!inputVal" @click="handleSend">发送</v-btn>
-        </div> -->
       </div>
     </div>
   </div>
@@ -211,14 +203,16 @@
 
 <script setup>
 defineOptions({ name: 'message-chatting'})
-import { ref, nextTick, onMounted } from 'vue'
+import { ref, nextTick, onMounted, inject } from 'vue'
 import { timesTampChange } from '@/utils/date'
 import { useIMStore } from '@/store/im'
+import { useRouter } from 'vue-router';
+import { getDict } from '@/hooks/web/useDictionaries'
 
 import { useUserStore } from '@/store/user'
-// const isEnterprise = inject('isEnterprise')
+const isEnterprise = inject('isEnterprise')
 
-const emits = defineEmits(['handleMore', 'handleSend'])
+const emits = defineEmits(['handleMore', 'handleSend', 'handleAgree', 'handleRefuse'])
 
 defineProps({
   items: {
@@ -236,9 +230,14 @@ defineProps({
   hasMore: {
     type: Boolean,
     default: false
+  },
+  interview: {
+    type: Array,
+    default: () => []
   }
 })
 
+const router = useRouter()
 const overlay = ref(false)
 const IM = useIMStore()
 const userStore = useUserStore()
@@ -251,27 +250,6 @@ const inputVal = ref('')
 const pullDowning = ref(false) // 下拉中
 const pulldownFinished = ref(false) // 下拉完成
 
-// const enterpriseTools = [
-//   { name: '查看面试', icon: 'mdi-email-newsletter', handle: handleInquire },
-//   { name: '面试邀约', icon: 'mdi-email', handle: handleInvite }
-// ]
-
-// const userTools = [
-//   { name: '查看面试', icon: 'mdi-email-newsletter', handle: handleInquire }
-// ]
-
-// const tools = isEnterprise ? enterpriseTools : userTools
-
-// 查看
-// function handleInquire () {
-//   console.log(props.info)
-//   emits('handleInquire', props.info.userId, props.info.enterpriseId || undefined)
-// }
-// // 邀请
-// function handleInvite () {
-//   emits('handleInvite', props.info.userId, props.info.enterpriseId || undefined)
-// }
-
 // 滚动到底部
 const scrollBottom = () => {
   const chat = chatRef.value
@@ -282,6 +260,15 @@ const scrollBottom = () => {
   }
 }
 
+// 求职端-获取求职者与当前邀请人的面试记录
+const statusList = ref([])
+const getStatusList = () => {
+  getDict('menduner_interview_invite_status').then(({data}) => {
+    if (data.length) statusList.value = data
+  })
+}
+if (!isEnterprise) getStatusList()
+
 onMounted(() => {
   nextTick(() => {
     scrollBottom()
@@ -296,39 +283,6 @@ const handleSend = () => {
   emits('handleSend', inputVal)
 }
 
-// const pullDown = async () => {
-//   if (messages.value.length == 0) {
-//     return
-//   }
-//   const firstMsg = messages.value[0]
-//   if (firstMsg.messageSeq == 1) {
-//     pulldownFinished.value = true
-//     return
-//   }
-//   const limit = 15
-//   const msgs = await WKSDK.shared().chatManager.syncMessages(to.value, {
-//     limit: limit,
-//     startMessageSeq: firstMsg.messageSeq - 1,
-//     endMessageSeq: 0,
-//     pullMode: PullMode.Down,
-//   })
-//   if (msgs.length < limit) {
-//     pulldownFinished.value = true;
-//   }
-//   if (msgs && msgs.length > 0) {
-//     msgs.reverse().forEach((m) => {
-//       messages.value.unshift(m)
-//     })
-//   }
-//   nextTick(function () {
-//     const chat = chatRef.value
-//     const firstMsgEl = document.getElementById(firstMsg.clientMsgNo)
-//     if (firstMsgEl) {
-//       chat.scrollTop = firstMsgEl.offsetTop
-//     }
-//   })
-// }
-
 const handleScroll = (e) => {
   const targetScrollTop = e.target.scrollTop;
   if (targetScrollTop <= 250) {
@@ -336,13 +290,7 @@ const handleScroll = (e) => {
     if (pullDowning.value || pulldownFinished.value) {
       return
     }
-    console.log('下拉')
     pullDowning.value = true
-    // pullDown().then(() => {
-    //   pullDowning.value = false
-    // }).catch(() => {
-    //   pullDowning.value = false
-    // })
   }
 }
 
@@ -369,12 +317,23 @@ const reset = () => {
   inputVal.value = ''
 }
 
+// 同意面试邀请
 const handleAgree = (val) => {
-  // const { positionInfo, ...obj } = val
-  // console.log(positionInfo)
+  if (!val.id) return
   emits('handleAgree', val)
 }
 
+// 拒绝面试邀请
+const handleRefuse = (val) => {
+  if (!val.id) return
+  emits('handleRefuse', val)
+}
+
+// 跳转个人中心-面试
+const handleToCenter = () => {
+  router.push({ path: '/recruit/personal/personalCenter', query: { showInterviewScheduleMore: true } })
+}
+
 defineExpose({
   reset,
   changeLoading,
@@ -489,6 +448,14 @@ input {
     border: none;
   }
 }
+.tipsText {
+  color: var(--color-999);
+  font-size: 12px;
+  cursor: pointer;
+  &:hover {
+    color: var(--v-primary-base);
+  }
+}
 
 /* 滚动条样式 */
 ::-webkit-scrollbar {

+ 47 - 42
src/views/recruit/components/message/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="default-width message pa-3" :style="`height: calc(100vh - ${isEnterprise ? '130px' : '50px'});`">
+  <div class="default-width message" :style="`height: calc(100vh - ${isEnterprise ? '130px' : '50px'});`">
     <div class="message-left">
       <div class="message-left-search d-flex align-center px-3 justify-space-between" >
         <div>
@@ -17,7 +17,6 @@
           </v-btn>
         </div>
         <!-- {{ connected ? '连接成功': '连接失败' }} -->
-        <!-- <TextInput v-model="searchInputVal" :item="textItem" @appendInnerClick="handleSearch" @enter="handleSearch"></TextInput> -->
       </div>
       <div class="message-chat-box mt-5">
         <v-overlay
@@ -76,10 +75,12 @@
         ref="chatRef"
         :items="messageItems"
         :info="info"
+        :interview="interview"
         :has-more="hasMore"
         @handleSend="handleUpdate"
         @handleMore="handleGetMore"
         @handleAgree="handleAgree"
+        @handleRefuse="handleRefuse"
       >
         <template #tools>
           <v-btn
@@ -97,7 +98,8 @@
       </Chatting>
     </div>
   </div>
-  <CtDialog :visible="showInvite" :widthType="2" titleClass="text-h6" title="面试信息" @close="showInvite = false" @submit="handleSubmit">
+
+  <CtDialog :visible="showInvite" :widthType="2" titleClass="text-h6" title="邀请面试" @close="showInvite = false" @submit="handleSubmit">
     <InvitePage v-if="showInvite" ref="inviteRef" :item-data="itemData" :position="positionList"></InvitePage>
   </CtDialog>
 </template>
@@ -111,18 +113,19 @@ import Chatting from './components/chatting.vue'
 import { initConnect, send, initChart, getMoreMessages, checkConversation } from '@/hooks/web/useIM'
 import { useRoute } from 'vue-router'
 import { getPositionDetails } from '@/api/position'
+import { getInterviewInviteListByInviteUserId } from '@/api/common'
 import { getUserInfo } from '@/api/personal/user'
 import { useIMStore } from '@/store/im'
 import { useUserStore } from '@/store/user'
-// import { getJobAdvertisedSearch } from '@/api/position'
 import Snackbar from '@/plugins/snackbar'
 
 import { getJobAdvertised } from '@/api/enterprise'
 import { dealDictArrayData } from '@/utils/position'
 import { saveInterviewInvite } from '@/api/recruit/enterprise/interview'
 import { useI18n } from '@/hooks/web/useI18n'
-import { userInterviewInviteConsent } from '@/api/recruit/personal/personalCenter'
-// import Confirm from '@/plugins/confirm'
+import { userInterviewInviteReject, userInterviewInviteConsent } from '@/api/recruit/personal/personalCenter'
+import Confirm from '@/plugins/confirm'
+
 const { t } = useI18n()
 const chatRef = ref()
 
@@ -190,6 +193,13 @@ const {
   chatRef.value.scrollBottom()
 })
 
+// 求职者面试列表
+const interview = ref([])
+const getInterviewInviteList = async () => {
+  if (!info.value.userId) return
+  const data = await getInterviewInviteListByInviteUserId(info.value.userId)
+  interview.value = data.slice(0, 1)
+}
 
 watch(
   () => conversationList.value,
@@ -200,6 +210,7 @@ watch(
       // 更新
       const { list } = await getMoreMessages(1, channelItem.value)
       messageItems.value = list.value
+      if (!isEnterprise) getInterviewInviteList()
       chatRef.value.scrollBottom()
     }
   },
@@ -211,16 +222,6 @@ watch(
 
 
 const showRightNoData = ref(false)
-// const searchInputVal = ref()
-// const textItem = ref({
-//   type: 'text',
-//   value: null,
-//   width: 336,
-//   clearable: true,
-//   hideDetails: true,
-//   appendInnerIcon: 'mdi-magnify',
-//   label: '搜索30天内的联系人'
-// })
 
 const info = ref({})
 
@@ -228,17 +229,12 @@ const handleUpdate = (val) => {
   send(val.value, channelItem.value)
 }
 
-// const handleSearch = () => {
-//   console.log(searchInputVal.value, 'search')
-// }
 
 const enterpriseTools = ref([
-  { name: '查看面试', icon: 'mdi-email-newsletter', color:"teal", handle: handleInquire },
-  { name: '面试邀约', icon: 'mdi-email', color:"warning", loading: false, handle: handleInvite }
+  { name: '面试邀约', icon: 'mdi-email', color:"primary", loading: false, handle: handleInvite }
 ])
 
 const userTools = ref([
-  { name: '查看面试', icon: 'mdi-email-newsletter', color:"teal", handle: handleInquire }
 ])
 
 const tools = isEnterprise ? enterpriseTools.value : userTools.value
@@ -263,6 +259,7 @@ async function handleChange (items) {
     await resetUnread(channel.value, baseInfo?.enterpriseId)
     await updateConversation()
     updateUnreadCount()
+    if (!isEnterprise) getInterviewInviteList()
   } catch (error) {
     messageItems.value = []
   } finally {
@@ -289,12 +286,6 @@ const handleDelete = async ({ channel }) => {
   updateUnreadCount()
 }
 
-// const handleSendInvite  = (item) => {
-//   Confirm('是否发送该职位邀请').then(() => {
-//     send(JSON.stringify(item), channelItem.value, 101)
-//   })
-// }
-
 // 没有企业ID则enterpriseId为undefined
 // 发送消息体 { text, type: 2 }
 // 面试邀约
@@ -324,16 +315,9 @@ async function handleInvite (item) {
   } finally {
     item.loading = false
   }
-  
-}
-// 查看面试
-function handleInquire (userId, enterpriseId) {
-  const query = { userId, enterpriseId }
-  console.log(query)
 }
 
 const handleSubmit = async () => {
-  // console.log(inviteRef.value.CtFormRef.validate)
   const { valid } = await inviteRef.value.CtFormRef.formRef.validate()
   if (!valid) {
     return
@@ -351,17 +335,38 @@ const handleSubmit = async () => {
   query.id = data.id
   Snackbar.success(t('common.operationSuccessful'))
   send(JSON.stringify(query), channelItem.value, 101)
-  // console.log(query)
-  // await saveInterviewInvite(query)
-  // Snackbar.success(t('common.operationSuccessful'))
   showInvite.value = false
 
 }
 
-// 通过上次保留ID
-const handleAgree = async ({ id }) => {
-  await userInterviewInviteConsent({ id, phone: baseInfo.phone})
-  Snackbar.success(t('common.operationSuccessful'))
+const handleAgree = (val) => {
+  if (!val.id) return
+  const query = {
+    id: val.id
+  }
+  const baseInfo = localStorage.getItem('baseInfo')
+  if (baseInfo) {
+    const { phone } = JSON.parse(baseInfo)
+    query.phone = phone
+  }
+
+  Confirm(t('common.confirmTitle'), '是否确定接收此面试邀请?').then(async () => {
+    await userInterviewInviteConsent(query)
+    Snackbar.success(t('common.operationSuccessful'))
+    getInterviewInviteList()
+    send(JSON.stringify({ id: val.id }), channelItem.value, 104)
+  })
+}
+
+// 拒绝面试邀请
+const handleRefuse = (val) => {
+  if (!val.id) return
+  Confirm(t('common.confirmTitle'), '您是否确定要拒绝此面试邀请?').then(async () => {
+    await userInterviewInviteReject(val.id)
+    Snackbar.success(t('common.operationSuccessful'))
+    getInterviewInviteList()
+    send(JSON.stringify({ id: val.id }), channelItem.value, 103)
+  })
 }
 
 </script>

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

@@ -74,7 +74,7 @@ const badgeIcon = computed(() => (item) => {
 
 const userStore = useUserStore()
 const inviteRef = ref()
-const publicRef = ref()
+// const publicRef = ref()
 const showInvite = ref(false)
 const headers = ref([
   { title: '姓名', value: 'name', sortable: false },

+ 1 - 1
src/views/recruit/personal/PersonalCenter/dynamic/left.vue

@@ -96,7 +96,7 @@ watch(() => route.query, (newQuery) => { // newQuery, oldQuery
     const path = route.path
     router.replace({ path, query })
   }
-})
+}, { deep: true, immediate: true })
 
 const selectVal = ref('0')
 const items = ref([])