| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986 | <template>  <div class="message" :class="{'default-width': !isEnterprise}" :style="`height: calc(100vh - ${isEnterprise ? '130px' : '50px'});`">    <div class="message-left d-flex flex-column">      <div class="message-left-search d-flex align-center px-3 justify-space-between" >        <div>          <v-icon class="mr-3">mdi-history</v-icon>          最近联系人        </div>        <div>          <v-btn            density="compact"            :color="showDelete ? '' : 'red'"            :icon="showDelete ? 'mdi-close' : 'mdi-trash-can-outline'"            variant="text"            @click="showDelete = !showDelete"          >          </v-btn>        </div>        <!-- {{ connected ? '连接成功': '连接失败' }} -->      </div>      <div class="message-chat-box mt-5">        <v-overlay          :model-value="!IM.connected"          contained          class="align-center justify-center"        >          <v-progress-circular            color="primary"            size="64"            indeterminate          ></v-progress-circular>        </v-overlay>        <div v-if="conversationList.length">          <v-list density="compact" mandatory @update:selected="handleChange">            <v-list-item              v-for="(val, i) in conversationList"              :key="i"              :value="val"              color="primary"              class="mb-2"              :active="val.channel.channelID === info?.channel?.channelID"              :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 }">                <div v-if="!isEnterprise" class="mt-2 d-flex align-center">                  {{ title }}                  <div class="ml-3 color-666 font-size-14 enterprise-name ellipsis" v-ellipse-tooltip :style="{'color': val.channel.channelID === info?.channel?.channelID ? '#00B760' : '#666'}">                    {{ formatName(val.userInfoVo?.userInfoResp?.enterpriseAnotherName) }}                    <span class="line" v-if="val.userInfoVo?.userInfoResp?.postNameCn && val.userInfoVo?.userInfoResp?.enterpriseAnotherName"></span>                    {{ val.userInfoVo?.userInfoResp?.postNameCn }}                  </div>                </div>                <div v-else class="mt-2" v-ellipse-tooltip>{{ title }}</div>              </template>              <template v-slot:subtitle="{ subtitle }">                <div class="mt-2">{{ subtitle }}</div>              </template>              <template v-slot:prepend>                <v-avatar :image="getUserAvatar(val?.userInfoVo?.userInfoResp?.avatar, val?.userInfoVo?.userInfoResp?.sex, !val.userInfoVo ? true : false)"></v-avatar>              </template>              <template v-slot:append>                <v-badge                  v-if="val.unread > 0"                  color="error"                  :content="val.unread"                  inline                ></v-badge>                <v-btn v-show="showDelete" density="compact" icon="mdi-trash-can-outline" variant="text" color="red" @click.stop="handleDelete(val)"></v-btn>              </template>            </v-list-item>          </v-list>          <div class="message-no-more-text mt-3">没有更多了</div>        </div>        <div v-else class="left-noData">          <Empty :elevation="false" message="暂无30天内联系人" width="300" height="150"></Empty>        </div>      </div>    </div>    <div class="message-right">      <div v-if="showRightNoData" class="right-noData">        <Empty :elevation="false" message="与您进行过沟通的 Boss 都会在左侧列表中显示"></Empty>      </div>      <Chatting        ref="chatRef"        :items="messageItems"        :info="info"        :interview="interview"        :has-more="hasMore"        :updateConversation="updateConversation"        :updateUnreadCount="updateUnreadCount"        :resetUnread="resetUnread"        @handleSend="handleUpdate"        @handleMore="handleGetMore"        @handleAgree="handleAgree"        @handleRefuse="handleRefuse"        @handlePreview="handlePreview"        @handleSendResume="handleSendResume"        @handleBack="handleBack"      >        <template #tools>          <v-btn            v-for="tool in tools"            :key="tool.name"            size="small"            class="mr-3"            :disabled="tool.disabled"            :color="tool.color"            @click="tool.handle(tool)"          >            <v-progress-circular              v-if="tool.loading"              :width="2"              :size="16"              color="white"              class="mr-2"              indeterminate            ></v-progress-circular>            <v-icon v-else class="mr-2">{{ tool.icon }}</v-icon>            {{ tool.disabled ? tool.disabledText : tool.name }}          </v-btn>        </template>      </Chatting>    </div>  </div>  <!-- 附件上传 -->  <CtDialog    :visible="showUploadDialog"    :widthType="2"    :footer="true"    title="附件简历上传"    titleClass="text-h6"    @close="showUploadDialog = false"    @submit="handleSubmitAttachment"  >    <div class="color-warning mb-3" style="font-size: 13px;">* 仅支持.doc, .docx, .pdf文件且大小不能超过20MB</div>    <CtForm ref="CtFormRef" :items="formItems">      <template #uploadFile="{ item }">        <TextInput v-model="item.value" :item="item" @click="openFileInput"></TextInput>        <File ref="uploadFile" @success="handleUploadResume"></File>      </template>    </CtForm>    <!-- 学生-实习到岗信息 -->    <studentDeliveryForm v-if="baseInfo?.type && Number(baseInfo.type) === 1" ref="studentDeliveryFormRef" />  </CtDialog>  <!-- 面试邀请 -->  <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">    <CtForm v-if="showSelectPosition" ref="requestFromRef" :items="requestFormItems"></CtForm>  </CtDialog>  <!-- 发送简历-选择要发送的职位 -->  <CtDialog :visible="openPositionSelectDialog" :widthType="2" titleClass="text-h6" title="请选择要投递的职位" @close="openPositionSelectDialog = false" @submit="selectPositionSubmit">    <div style="position: relative; min-height: 200px">      <v-radio-group v-model="selectJobId">        <div v-for="val in entPositionList" :key="val.value" class="d-flex align-center radioBox" >          <v-radio :label="val.label" :value="val.value"  color="primary"></v-radio>          <span class="defaultLink mx-3" style="font-size: 14px;" @click.stop="positionDetail(val)">预览</span>        </div>      </v-radio-group>    </div>    <v-btn      v-if="entPositionTotal > 5"      variant="text"      color="primary"      @click="changePositionData"    >      {{ entPositionListLastData ? '没有更多职位了~ 再选一遍' : '换一批'}} <v-icon size="16">mdi-refresh</v-icon>    </v-btn>  </CtDialog>  <!-- 选择附件简历投递 -->  <CtDialog :visible="showResume" :widthType="2" titleClass="text-h6" title="发送简历" @close="showResume = false; selectResume = null; enRequestPositionInfo = {}" @submit="handleSubmitResume">    <div style="position: relative;">      <v-radio-group v-model="selectResume">        <div v-for="val in resumeList" :key="val.id" class="d-flex align-center radioBox">          <v-radio :label="val.title" :value="val.id"  color="primary"></v-radio>          <span class="defaultLink mx-3" style="font-size: 14px;" @click.stop="previewFile(val.url)">预览</span>        </div>      </v-radio-group>    </div>    <studentDeliveryForm v-if="isStudent" ref="studentDeliveryFormRef" />  </CtDialog>	<Loading :visible="pageLoading" /></template><script setup>defineOptions({ name: 'personal-message-index'})import InvitePage from '@/views/recruit/enterprise/interviewManagement/components/invite'import { ref, inject, watch, nextTick, computed } from 'vue'import { useRoute } from 'vue-router'import Chatting from './components/chatting.vue'import { initConnect, send, initChart, getMoreMessages, checkConversation } from '@/hooks/web/useIM'import { useI18n } from '@/hooks/web/useI18n'import { getPositionDetails, jobCvRelCheckSend, jobCvRelSend, jobCvRelHireSend, getJobAdvertisedSearch } from '@/api/position'import { getInterviewInviteListByInviteUserId, getMessageType } from '@/api/common'// import { getUserInfo } from '@/api/personal/user'import { getBaseInfo } from '@/api/common'import { getJobAdvertised } from '@/api/enterprise'import { saveInterviewInvite } from '@/api/recruit/enterprise/interview'import { savePersonResumeCv } from '@/api/recruit/personal/resume'import { userInterviewInviteReject, userInterviewInviteConsent } from '@/api/recruit/personal/personalCenter'import { getPersonResumeCv } from '@/api/recruit/personal/resume'import { formatName } from '@/utils/getText'import { useIMStore } from '@/store/im'import { useUserStore } from '@/store/user'import Snackbar from '@/plugins/snackbar'import Confirm from '@/plugins/confirm'import { getUserAvatar } from '@/utils/avatar'import { dealDictArrayData } from '@/utils/position'import { previewFile } from '@/utils'import { timesTampChange } from '@/utils/date'import { useRouter } from 'vue-router'import studentDeliveryForm from '@/views/recruit/personal/components/studentDeliveryForm.vue'const { t } = useI18n()const chatRef = ref()const IM = useIMStore()// 自己的信息const { entBaseInfo } = useUserStore()const isEnterprise = inject('isEnterprise')// 实例const route = useRoute()const channelItem = ref(null)const messageItems = ref([])const pageSize = ref(1)const hasMore = ref(false)const studentDeliveryFormRef = ref()const baseInfo = ref(localStorage.getItem('baseInfo') ? JSON.parse(localStorage.getItem('baseInfo')) : {})const isStudent = ref(baseInfo.value?.type && Number(baseInfo.value.type) === 1)const positionList = ref([])const showTip = ref(false)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 showDelete = ref(false)const itemData = ref({})const inviteRef = ref()// 发送简历const showResume = ref(false)const resumeList = ref([])const selectResume = ref(null)// 众聘 介绍人个人idconst isEmployment = ref('-1')// 上传附件简历const CtFormRef = ref()const formItems = ref({  options: [    {      type: 'text',      key: 'title',      value: '',      label: '附件简历名称 *',      rules: [v => !!v || '请输入附件简历名称']    },    {      slotName: 'uploadFile',      key: 'url',      value: '',      truthValue: '',      label: '点击上传附件简历 *',      outline: true,      accept: '.doc, .docx, .pdf',      prependInnerIcon: 'mdi-file-document-outline',      rules: [v => !!v || '请上传您的附件简历']    }  ]})// 求职者面试列表const interview = ref([])const showRightNoData = ref(false)const info = ref({})const enterpriseTools = ref([  { 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([  {    name: '发送简历',    key: 'sendResume',    icon: 'mdi-email',    color:"primary",    loading: false,    handle: handleSendResume,    disabled: false,    disabledText: '简历已投递'  }])// const tools = isEnterprise ? enterpriseTools.value : userTools.valueconst tools = computed(() => {  return isEnterprise ? enterpriseTools.value : userTools.value})const positionInfo = ref({})// const isSendResume = ref(false)if (!IM) {  console.log('IM is disconnected')}// 参与招聘会的职位进入需传递招聘会id// const jobFairId = ref('')// if (route.query?.jobFairId) jobFairId.value = route.query.jobFairId// 职位进入if (route.query.id) {  const api = route.query.enterprise ? getPositionDetails : getBaseInfo // getBaseInfo  getUserInfo  // const res = await api({ id: route.query.id })  const res = await api(route.query.enterprise ? { id: route.query.id } : { userId: route.query.id })  if (!res) {    Snackbar.error('个人资料为空')  } else {    const query = route.query.enterprise ? [res.contact?.userId, res.contact?.enterpriseId] : [res?.userId]    nextTick(async () => {      const { channel } = await checkConversation(...query)      const items = [        {          channel,          userInfoVo: {            userInfoResp: route.query.enterprise ? res.contact : { ...res, userId: res?.userId}          }        }      ]      handleChange(items)    })  }}const {  conversationList,  updateConversation,  updateUnreadCount,  deleteConversations,  resetUnread} = initConnect(async (successful) => {  if (!successful) {    Snackbar.error('发送失败')    return  }  chatRef.value.reset()  // 发送成功  const { list } = await getMoreMessages(1, channelItem.value)  // updateConversation()  messageItems.value = list.value  chatRef.value.scrollBottom()})const getInterviewInviteList = async () => {  if (!info.value.userId) return  const data = await getInterviewInviteListByInviteUserId(info.value.userId)  interview.value = data.slice(0, 1)}// 在当前频道中有新消息时更新未读消息数量const updateUnreadMessageCount = (val) => {  const obj = val.find(e => e.userInfoVo.userId === info.value.userId)  if (!obj?.unread || obj.unread === 0) return  delete info.value.unread  Object.assign(info.value, { unread: obj.unread, enterpriseId: entBaseInfo?.enterpriseId })}watch(  () => conversationList.value,  async (val) => {    // 数据发生变化    if (channelItem.value && IM.fromChannel === channelItem.value.channelID) {      // 更新      const { list } = await getMoreMessages(1, channelItem.value)      messageItems.value = list.value      if (Object.keys(info.value).length) updateUnreadMessageCount(val)      chatRef.value.scrollBottom()    }  },  {    deep: true,    immediate: true  })// 获取职位信息async function getMessageTypeSync () {  const data = await getMessageType({    fromUid: IM.uid,    channelId: channelItem.value?.channelID,    type: 102,    page: {      current: 1,      size: 1,      orders: [        { column: 'message_seq', asc: false }      ]    }  })  if (!data.records || !data.records.length) {    positionInfo.value = {}    handleChangeSendResumeStatus(false)    return  }  const _item = data.records.pop()  const _itemJSON = JSON.parse(_item.payload)  const _content = JSON.parse(_itemJSON.content)  positionInfo.value = _content.positionInfo  const check = await jobCvRelCheckSend({ jobId: _content.positionInfo.id })  handleChangeSendResumeStatus(check)}// 修改发送状态function handleChangeSendResumeStatus (status) {  if (!isEnterprise) {    const item = userTools.value.find(e => e.key === 'sendResume')    item.disabled = status  }}async function handleChange (items) {  try {    chatRef.value.changeOverlay(true)    const { userInfoVo, channel: myChannel, unread } = items.pop()    info.value = userInfoVo?.userInfoResp ?? { name: '系统消息' }    Object.assign(info.value, {      channel: myChannel,      unread    })    if (myChannel.channelID === 'system') {      channelItem.value = myChannel      const { list, more } = await getMoreMessages(1, channelItem.value)      messageItems.value = list.value      hasMore.value = more      chatRef.value.scrollBottom()      // 点开窗口消除未读数量      await resetUnread(channelItem.value, entBaseInfo?.enterpriseId)      await updateConversation()      updateUnreadCount()      return    }    // 个人端获取面试信息    if (!isEnterprise) getInterviewInviteList()    const userId = userInfoVo.userInfoResp.userId    const enterpriseId = userInfoVo.userInfoResp.enterpriseId || undefined    const { channel, list, more } = await initChart(userId, enterpriseId)    // console.log('--------',list)    channelItem.value = channel.value    messageItems.value = list.value    hasMore.value = more    chatRef.value.scrollBottom()    // 点开窗口消除未读数量    await resetUnread(channel.value, entBaseInfo?.enterpriseId)    await updateConversation()    updateUnreadCount()    // 获取最近职位记录    getMessageTypeSync()  } catch (error) {    messageItems.value = []  } finally {    chatRef.value.changeOverlay(false)  }}// 普通消息const handleUpdate = (val) => {  send(val.value, channelItem.value)}// 选择文件const uploadFile = ref()const openFileInput = () => {  uploadFile.value.trigger()}// 上传附件const handleUploadResume = async (url, title, filename) => {  const obj = formItems.value.options.find(e => e.key === 'url')  obj.value = filename  obj.truthValue = url}const changePositionData = () => {  entPositionListParams.value.pageNo = entPositionListLastData.value ? 1 : entPositionListParams.value.pageNo + 1  selectJobId.value = ''  getRecruitPositionList()}const positionDetail = (val) => {  const id = val.value  if (!id) return  window.open(`/recruit/personal/position/details/${id}`)}// 选中职位并投递const selectJobId = ref('')const selectPositionSubmit = async () => {  // 投递  openPositionSelectDialog.value = false  handleSendResume(handleSendResumeItem)}const pageLoading = ref(false)const entPositionTotal = ref(0)const entPositionList = ref([])const entPositionListParams = ref({ pageNo: 1, pageSize: 5 })const openPositionSelectDialog = ref(false)const entPositionListLastData = computed(() => entPositionListParams.value.pageNo * entPositionListParams.value.pageSize >=  entPositionTotal.value)// 职位列表const getRecruitPositionList = async () => {  const enterpriseId = info.value?.enterpriseId || null  if (!enterpriseId) return Snackbar.warning('访问企业错误!')  pageLoading.value = true  const { list, total: number } = await getJobAdvertisedSearch({ ...entPositionListParams.value, enterpriseId })  if (!list.length) return Snackbar.warning('企业暂无招聘中的职位,无法进行投递!')  entPositionTotal.value = number  entPositionList.value = list.map(j => {    const e = j?.job || null    if (!e) return e    const salary = e.payFrom && e.payTo ? `${e.payFrom ? e.payFrom + '-' : ''}${e.payTo}${e.payName ? '/' + e.payName : ''}` : '面议'    return {      label: `${formatName(e.name)}_${e.areaName ? e.area?.str : '全国'} ${salary}`,      value: e.id,      data: e    }  }).filter(Boolean)  setTimeout(() => { pageLoading.value = false }, 300)}// 获取简历const showUploadDialog = ref(false)const enRequestPositionInfo = ref({}) // 企业求简历时选中的职位信息let handleSendResumeItem = nullasync function handleSendResume (item) {  const jobId = enRequestPositionInfo.value && enRequestPositionInfo.value?.id ? enRequestPositionInfo.value?.id : positionInfo.value.id  if (!jobId && !selectJobId.value && import.meta.env.VITE_NODE_ENV !== 'production') {    // 没有基于职位接收到的沟通,弹出职位列表让求职者选择。否则无法投递简历。    handleSendResumeItem = item    await getRecruitPositionList()    if (entPositionTotal.value) openPositionSelectDialog.value = true    return  }  try {    item.loading = true    // 获取简历列表    const result = await getPersonResumeCv()    if (result.length === 0) {      Snackbar.warning(t('resume.resumeYetSubmit'))      showUploadDialog.value = true      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  }}// 撤回简历async function handleBack (val) {  console.log(val)  try {    // 撤回简历    // 撤回聊天    // await messageBack({    //   channelId: val.channel_id,    //   messageId: val.message_id,    //   nickName: baseInfo.name    //   // enterpriseId: ''    // })  } catch (error) {    console.log(error)  }}/** * 发送简历 * text param * { *  remark: 备注 *  query: {} 自定义参数 access -1 未确定 0 拒绝 1 同意 *  type: 1 => 发送简历 *        2 => 索要简历 *        3 => 信息描述 * } */// 没有上传过简历的弹窗上传并发送给对方const handleSubmitAttachment = async () => {  const { valid } = await CtFormRef.value.formRef.validate()  if (!valid) return  const obj = {}  formItems.value.options.forEach(e => {    obj[e.key] = e.truthValue || e.value  })  if (!obj.title || !obj.url) return  // 学生实习到岗信息  let practice = {}  if (isStudent.value) {    practice = studentDeliveryFormRef.value.getQueryParams()    console.log(practice, '上传简历-到岗信息')  }  if (isStudent.value && (!practice?.practiceStartTime || !practice?.practiceEndTime)) return Snackbar.warning('请完善实习到岗信息')  // 保存附件  await savePersonResumeCv(obj)  // 简历投递至简历库  if (isEmployment.value !== '-1') {    let params = {      jobId: positionInfo.value.id || selectJobId.value,      url: obj.url,      recommendUserId: isEmployment.value    }    // 如果是学生则需要带上实习信息    if (practice && Object.keys(practice).length > 0) params = Object.assign(params, practice)    await jobCvRelHireSend(params)  } else {    const jobId = enRequestPositionInfo.value && enRequestPositionInfo.value?.id ? enRequestPositionInfo.value?.id : positionInfo.value.id || selectJobId.value    const type = (enRequestPositionInfo.value && Object.keys(enRequestPositionInfo.value).length ? enRequestPositionInfo.value.hire : positionInfo.value.hire) ? 1 : 0    let params = {      jobId,      title: obj.title,      url: obj.url,      type    }    // 如果是学生则需要带上实习信息    if (practice && Object.keys(practice).length > 0) params = Object.assign(params, practice)    await jobCvRelSend(params)  }  handleChangeSendResumeStatus(true)  showUploadDialog.value = false  const text = {    remark: '发送简历',    query: {      src: obj.url,      title: obj.title    },    type: 1  }  if (enRequestPositionInfo.value) text.query.positionInfo = enRequestPositionInfo.value  send (JSON.stringify(text), channelItem.value, 105)  enRequestPositionInfo.value = {}}async function handleSubmitResume () {  if (!selectResume.value) {    Snackbar.warning(t('resume.selectResumeToSubmit'))    return  }  const _info = resumeList.value.find((item) => item.id === selectResume.value)  // 学生实习到岗信息  let practice = {}  if (isStudent.value) {    practice = studentDeliveryFormRef.value.getQueryParams()    console.log(practice, '选择简历-到岗信息')  }  if (isStudent.value && (!practice?.practiceStartTime || !practice?.practiceEndTime)) return Snackbar.warning('请完善实习到岗信息')  // 简历投递至简历库  if (isEmployment.value !== '-1') {    let params = {      jobId: positionInfo.value.id || selectJobId.value,      url: _info.url,      recommendUserId: isEmployment.value    }    // 如果是学生则需要带上实习信息    if (practice && Object.keys(practice).length > 0) params = Object.assign(params, practice)    await jobCvRelHireSend(params)  } else {    const jobId = enRequestPositionInfo.value && enRequestPositionInfo.value?.id ? enRequestPositionInfo.value?.id : positionInfo.value.id || selectJobId.value    const type = (enRequestPositionInfo.value && Object.keys(enRequestPositionInfo.value).length ? enRequestPositionInfo.value.hire : positionInfo.value.hire) ? 1 : 0    let params = {      jobId,      title: _info.title,      url: _info.url,      type    }    // 如果是学生则需要带上实习信息    if (practice && Object.keys(practice).length > 0) params = Object.assign(params, practice)    await jobCvRelSend(params)  }  handleChangeSendResumeStatus(true)  showResume.value = false  const text = {    remark: '发送简历',    query: {      src: _info.url,      title: _info.title,      id: _info.id,    },    type: 1  }  if (enRequestPositionInfo.value) text.query.positionInfo = enRequestPositionInfo.value  send (JSON.stringify(text), channelItem.value, 105)  enRequestPositionInfo.value = {}}// 简历预览const handlePreview = (val) => {  previewFile(val.content.query.src)}const handleGetMore = async () => {  // 当前滚动条高度  const scrollHeight = chatRef.value.chatRef.scrollHeight  // 当前滚动条距离  const scrollTop = chatRef.value.chatRef.scrollTop  try {    chatRef.value.changeLoading(true)    pageSize.value++    const { list, more } = await getMoreMessages(pageSize.value, channelItem.value)    messageItems.value.unshift(...list.value)    hasMore.value = more    nextTick(() => {      // 渲染后高度      const _scrollHeight = chatRef.value.chatRef.scrollHeight      chatRef.value.chatRef.scrollTop = _scrollHeight - scrollHeight - scrollTop    })  } finally {    chatRef.value.changeLoading(false)  }}const handleDelete = async ({ channel }) => {  await deleteConversations(channel, entBaseInfo?.enterpriseId)  await updateConversation()  updateUnreadCount()}// 没有企业ID则enterpriseId为undefined// 发送消息体 { text, type: 2 }// 面试邀约const getPositionList = async () => {  const data = await getJobAdvertised({ status: 0 }) // 0开启 1关闭 不带则全部  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: `${formatName(e.name)}_${e.areaName ? e.area?.str : '全国'} ${salary}`,      value: e.id,      data: e    }  })}async function handleInvite (item) {  item.loading = true  positionList.value = []  try {    await getPositionList()    if (!positionList.value.length) return Snackbar.warning('请先发布职位')    if (item.key === 'requestResume') return showSelectPosition.value = true    showInvite.value = true  } catch (error) {    console.log(error)  } finally {    item.loading = false  }}// 企业-发送面试邀请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  }  query.userId = info.value.userId  query.positionInfo = positionList.value.find(e => e.value === query.jobId)  // 需要id  const data = await saveInterviewInvite(query)  // 保留邀请id  query.id = data.id  showTip.value = true  send(JSON.stringify(query), channelItem.value, 101)  showInvite.value = false}const router = useRouter()const handleToInterviewManagement = () => {  router.push('/recruit/enterprise/invite/interviewManagement')}// 企业-求简历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) => {  if (!val.id) return  const query = {    id: val.id  }  const type = route?.meta?.loginType === 'enterprise' ? 'entBaseInfo' : 'baseInfo'  const baseInfo = localStorage.getItem(type)  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><style scoped lang="scss">.message {  display: flex;  &-left {    position: relative;    flex-shrink: 0;    height: 100%;;    width: 360px;    background-color: #fff;    border-radius: 8px;    margin-right: 12px;    .message-left-search {      width: 100%;      height: 60px;      background: linear-gradient(90deg, #f5fcfc, #fcfbfa);      border-radius: 8px 8px 0 0;    }    .message-chat-box {      height: 0;      flex: 1;      overflow: auto;      padding-bottom: 20px;      .chat-item {        position: relative;        width: 100%;        height: 78px;        padding: 14px 12px;        cursor: pointer;        &:hover {          background-color: #f8f8f8;        }        .chat-item-time {          position: absolute;          right: 12px;          top: 50%;          transform: translateY(-50%);        }        .title-box {          max-width: 114px;          overflow: hidden;          white-space: nowrap;          text-overflow: ellipsis;          display: inline-block;        }      }    }    .message-no-more-text {      color: var(--color-999);      font-size: 14px;      text-align: center    }    .left-noData {      position: absolute;      top: 50%;      left: 50%;      transform: translate(-50%, -50%);    }  }  &-right {    height: 100%;    flex: 1;    width: 0;    position: relative;    background-color: #fff;    border-radius: 8px;    .right-noData {      position: absolute;      top: 50%;      left: 50%;      transform: translate(-50%, -50%);    }  }}.enterprise-name {  max-width: 150px;  .line {    display: inline-block;    width: 1px;    height: 12px;    vertical-align: middle;    background-color: #e0e0e0;    margin: 0 3px;  }}.radioBox {  &:hover {    border-radius: 2px;    background-color: var(--color-f8);  }}</style>
 |