index.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819
  1. <template>
  2. <div class="default-width message" :style="`height: calc(100vh - ${isEnterprise ? '130px' : '50px'});`">
  3. <div class="message-left d-flex flex-column">
  4. <div class="message-left-search d-flex align-center px-3 justify-space-between" >
  5. <div>
  6. <v-icon class="mr-3">mdi-history</v-icon>
  7. 最近联系人
  8. </div>
  9. <div>
  10. <v-btn
  11. density="compact"
  12. :color="showDelete ? '' : 'red'"
  13. :icon="showDelete ? 'mdi-close' : 'mdi-trash-can-outline'"
  14. variant="text"
  15. @click="showDelete = !showDelete"
  16. >
  17. </v-btn>
  18. </div>
  19. <!-- {{ connected ? '连接成功': '连接失败' }} -->
  20. </div>
  21. <div class="message-chat-box mt-5">
  22. <v-overlay
  23. :model-value="!IM.connected"
  24. contained
  25. class="align-center justify-center"
  26. >
  27. <v-progress-circular
  28. color="primary"
  29. size="64"
  30. indeterminate
  31. ></v-progress-circular>
  32. </v-overlay>
  33. <div v-if="conversationList.length">
  34. <v-list density="compact" mandatory @update:selected="handleChange">
  35. <v-list-item
  36. v-for="(val, i) in conversationList"
  37. :key="i"
  38. :value="val"
  39. color="primary"
  40. class="mb-2"
  41. :active="val.channel.channelID === info?.channel?.channelID"
  42. :title="val.userInfoVo ? (val.userInfoVo.userInfoResp?.name ? val.userInfoVo.userInfoResp.name : val.userInfoVo.userInfoResp?.phone) : '系统消息'"
  43. :subtitle="timesTampChange(+val.timestamp.padEnd(13, '0'))"
  44. >
  45. <template v-slot:title="{ title }">
  46. <div v-if="!isEnterprise" class="mt-2 d-flex align-center">
  47. {{ title }}
  48. <div class="ml-3 color-666 font-size-14 enterprise-name ellipsis" :style="{'color': val.channel.channelID === info?.channel?.channelID ? '#00897B' : '#666'}">
  49. {{ val.userInfoVo?.userInfoResp?.enterpriseAnotherName }}
  50. <span class="line" v-if="val.userInfoVo?.userInfoResp?.postNameCn && val.userInfoVo?.userInfoResp?.enterpriseAnotherName"></span>
  51. {{ val.userInfoVo?.userInfoResp?.postNameCn }}
  52. </div>
  53. </div>
  54. <div v-else class="mt-2">{{ title }}</div>
  55. </template>
  56. <template v-slot:subtitle="{ subtitle }">
  57. <div class="mt-2">{{ subtitle }}</div>
  58. </template>
  59. <template v-slot:prepend>
  60. <v-avatar :image="getUserAvatar(val?.userInfoVo?.userInfoResp?.avatar, val?.userInfoVo?.userInfoResp?.sex)"></v-avatar>
  61. </template>
  62. <template v-slot:append>
  63. <v-badge
  64. v-if="val.unread > 0"
  65. color="error"
  66. :content="val.unread"
  67. inline
  68. ></v-badge>
  69. <v-btn v-show="showDelete" density="compact" icon="mdi-trash-can-outline" variant="text" color="red" @click.stop="handleDelete(val)"></v-btn>
  70. </template>
  71. </v-list-item>
  72. </v-list>
  73. <div class="message-no-more-text mt-3">没有更多了</div>
  74. </div>
  75. <div v-else class="left-noData">
  76. <Empty :elevation="false" message="暂无30天内联系人" width="300" height="150"></Empty>
  77. </div>
  78. </div>
  79. </div>
  80. <div class="message-right">
  81. <div v-if="showRightNoData" class="right-noData">
  82. <Empty :elevation="false" message="与您进行过沟通的 Boss 都会在左侧列表中显示"></Empty>
  83. </div>
  84. <Chatting
  85. ref="chatRef"
  86. :items="messageItems"
  87. :info="info"
  88. :interview="interview"
  89. :has-more="hasMore"
  90. :updateConversation="updateConversation"
  91. :updateUnreadCount="updateUnreadCount"
  92. :resetUnread="resetUnread"
  93. @handleSend="handleUpdate"
  94. @handleMore="handleGetMore"
  95. @handleAgree="handleAgree"
  96. @handleRefuse="handleRefuse"
  97. @handlePreview="handlePreview"
  98. @handleSendResume="handleSendResume"
  99. >
  100. <template #tools>
  101. <v-btn
  102. v-for="tool in tools"
  103. :key="tool.name"
  104. size="small"
  105. class="mr-3"
  106. :disabled="tool.disabled"
  107. :color="tool.color"
  108. @click="tool.handle(tool)"
  109. >
  110. <v-progress-circular
  111. v-if="tool.loading"
  112. :width="2"
  113. :size="16"
  114. color="white"
  115. class="mr-2"
  116. indeterminate
  117. ></v-progress-circular>
  118. <v-icon v-else class="mr-2">{{ tool.icon }}</v-icon>
  119. {{ tool.disabled ? tool.disabledText : tool.name }}
  120. </v-btn>
  121. </template>
  122. </Chatting>
  123. </div>
  124. </div>
  125. <!-- 附件上传 -->
  126. <CtDialog
  127. :visible="showUploadDialog"
  128. :widthType="2"
  129. :footer="true"
  130. title="附件简历上传"
  131. titleClass="text-h6"
  132. @close="showUploadDialog = false"
  133. @submit="handleSubmitAttachment"
  134. >
  135. <CtForm ref="CtFormRef" :items="formItems">
  136. <template #uploadFile="{ item }">
  137. <TextInput v-model="item.value" :item="item" @click="openFileInput"></TextInput>
  138. <File ref="uploadFile" @success="handleUploadResume"></File>
  139. </template>
  140. </CtForm>
  141. <div class="color-666" style="font-size: 13px;">* 仅支持.doc, .docx, .pdf文件</div>
  142. </CtDialog>
  143. <!-- 面试邀请 -->
  144. <CtDialog :visible="showInvite" :widthType="4" titleClass="text-h6" title="邀请面试" @close="showInvite = false" @submit="handleSubmit">
  145. <InvitePage v-if="showInvite" ref="inviteRef" :item-data="itemData" :position="positionList"></InvitePage>
  146. </CtDialog>
  147. <TipDialog :visible="showTip" icon="mdi-check-circle-outline" message="面试邀请发送成功" @close="showTip = false">
  148. <div class="color-primary text-decoration-underline cursor-pointer" @click="handleToInterviewManagement">点击到面试中查看。</div>
  149. </TipDialog>
  150. <!-- 求简历-选择求简历的职位 -->
  151. <CtDialog :visible="showSelectPosition" :widthType="2" titleClass="text-h6" title="选择要求简历的职位" @close="showSelectPosition = false" @submit="handleRequestResumeSubmit">
  152. <CtForm v-if="showSelectPosition" ref="requestFromRef" :items="requestFormItems"></CtForm>
  153. </CtDialog>
  154. <!-- 选择附件简历投递 -->
  155. <CtDialog :visible="showResume" :widthType="2" titleClass="text-h6" title="发送简历" @close="showResume = false; selectResume = null; enRequestPositionInfo = {}" @submit="handleSubmitResume">
  156. <div style="position: relative; min-height: 200px">
  157. <v-radio-group v-model="selectResume">
  158. <div v-for="val in resumeList" :key="val.id" class="d-flex align-center radioBox">
  159. <v-radio :label="val.title" :value="val.id" color="primary"></v-radio>
  160. <span class="defaultLink mx-3" style="font-size: 14px;" @click.stop="previewFile(val.url)">预览</span>
  161. </div>
  162. </v-radio-group>
  163. </div>
  164. </CtDialog>
  165. </template>
  166. <script setup>
  167. defineOptions({ name: 'personal-message-index'})
  168. import InvitePage from '@/views/recruit/enterprise/interviewManagement/components/invite'
  169. import { ref, inject, watch, nextTick, computed } from 'vue'
  170. import { useRoute } from 'vue-router'
  171. import Chatting from './components/chatting.vue'
  172. import { initConnect, send, initChart, getMoreMessages, checkConversation } from '@/hooks/web/useIM'
  173. import { useI18n } from '@/hooks/web/useI18n'
  174. import { getPositionDetails, jobCvRelCheckSend, jobCvRelSend, jobCvRelHireSend } from '@/api/position'
  175. import { getInterviewInviteListByInviteUserId, getMessageType } from '@/api/common'
  176. // import { getUserInfo } from '@/api/personal/user'
  177. import { getBaseInfo } from '@/api/common'
  178. import { getJobAdvertised } from '@/api/enterprise'
  179. import { saveInterviewInvite } from '@/api/recruit/enterprise/interview'
  180. import { savePersonResumeCv } from '@/api/recruit/personal/resume'
  181. import { userInterviewInviteReject, userInterviewInviteConsent } from '@/api/recruit/personal/personalCenter'
  182. import { getPersonResumeCv } from '@/api/recruit/personal/resume'
  183. import { useIMStore } from '@/store/im'
  184. import { useUserStore } from '@/store/user'
  185. import Snackbar from '@/plugins/snackbar'
  186. import Confirm from '@/plugins/confirm'
  187. import { getUserAvatar } from '@/utils/avatar'
  188. import { dealDictArrayData } from '@/utils/position'
  189. import { previewFile } from '@/utils'
  190. import { timesTampChange } from '@/utils/date'
  191. import { useRouter } from 'vue-router'
  192. const { t } = useI18n()
  193. const chatRef = ref()
  194. const IM = useIMStore()
  195. // 自己的信息
  196. const { entBaseInfo } = useUserStore()
  197. const isEnterprise = inject('isEnterprise')
  198. // 实例
  199. const route = useRoute()
  200. const channelItem = ref(null)
  201. const messageItems = ref([])
  202. const pageSize = ref(1)
  203. const hasMore = ref(false)
  204. const positionList = ref([])
  205. const showTip = ref(false)
  206. const showInvite = ref(false)
  207. // 企业-求简历
  208. const showSelectPosition = ref(false)
  209. const requestFromRef = ref()
  210. const requestFormItems = ref({
  211. options: [
  212. {
  213. type: 'autocomplete',
  214. key: 'jobId',
  215. value: null,
  216. label: '招聘职位 *',
  217. outlined: true,
  218. clearable: false,
  219. itemText: 'label',
  220. itemValue: 'value',
  221. rules: [v => !!v || '请选择招聘职位'],
  222. items: positionList
  223. }
  224. ]
  225. })
  226. const showDelete = ref(false)
  227. const itemData = ref({})
  228. const inviteRef = ref()
  229. // 发送简历
  230. const showResume = ref(false)
  231. const resumeList = ref([])
  232. const selectResume = ref(null)
  233. // 众聘 介绍人个人id
  234. const isEmployment = ref('-1')
  235. // 上传附件简历
  236. const CtFormRef = ref()
  237. const formItems = ref({
  238. options: [
  239. {
  240. type: 'text',
  241. key: 'title',
  242. value: '',
  243. label: '附件简历名称 *',
  244. rules: [v => !!v || '请输入附件简历名称']
  245. },
  246. {
  247. slotName: 'uploadFile',
  248. key: 'url',
  249. value: '',
  250. truthValue: '',
  251. label: '点击上传附件简历 *',
  252. outline: true,
  253. rules: [v => !!v || '请上传您的附件简历']
  254. }
  255. ]
  256. })
  257. // 求职者面试列表
  258. const interview = ref([])
  259. const showRightNoData = ref(false)
  260. const info = ref({})
  261. const enterpriseTools = ref([
  262. { name: '求简历', key: 'requestResume', icon: 'mdi-email', color:"primary", loading: false, handle: handleInvite, disabled: false, disabledText: '简历已接收' },
  263. { name: '面试邀约', key: 'interviewInvite', icon: 'mdi-email', color:"primary", loading: false, handle: handleInvite, disabled: false, disabledText: '面试邀约' }
  264. ])
  265. const userTools = ref([
  266. {
  267. name: '发送简历',
  268. key: 'sendResume',
  269. icon: 'mdi-email',
  270. color:"primary",
  271. loading: false,
  272. handle: handleSendResume,
  273. disabled: false,
  274. disabledText: '简历已投递'
  275. }
  276. ])
  277. // const tools = isEnterprise ? enterpriseTools.value : userTools.value
  278. const tools = computed(() => {
  279. return isEnterprise ? enterpriseTools.value : userTools.value
  280. })
  281. const positionInfo = ref({})
  282. // const isSendResume = ref(false)
  283. if (!IM) {
  284. console.log('IM is disconnected')
  285. }
  286. // 职位进入
  287. if (route.query.id) {
  288. const api = route.query.enterprise ? getPositionDetails : getBaseInfo // getBaseInfo getUserInfo
  289. // const res = await api({ id: route.query.id })
  290. const res = await api(route.query.enterprise ? { id: route.query.id } : { userId: route.query.id })
  291. if (!res) {
  292. Snackbar.error('个人资料为空')
  293. } else {
  294. const query = route.query.enterprise ? [res.contact?.userId, res.contact?.enterpriseId] : [res?.userId]
  295. nextTick(async () => {
  296. const { channel } = await checkConversation(...query)
  297. const items = [
  298. {
  299. channel,
  300. userInfoVo: {
  301. userInfoResp: route.query.enterprise ? res.contact : { ...res, userId: res?.userId}
  302. }
  303. }
  304. ]
  305. handleChange(items)
  306. })
  307. }
  308. }
  309. const {
  310. conversationList,
  311. updateConversation,
  312. updateUnreadCount,
  313. deleteConversations,
  314. resetUnread
  315. } = initConnect(async (successful) => {
  316. if (!successful) {
  317. Snackbar.error('发送失败')
  318. return
  319. }
  320. chatRef.value.reset()
  321. // 发送成功
  322. const { list } = await getMoreMessages(1, channelItem.value)
  323. // updateConversation()
  324. messageItems.value = list.value
  325. chatRef.value.scrollBottom()
  326. })
  327. const getInterviewInviteList = async () => {
  328. if (!info.value.userId) return
  329. const data = await getInterviewInviteListByInviteUserId(info.value.userId)
  330. interview.value = data.slice(0, 1)
  331. }
  332. // 在当前频道中有新消息时更新未读消息数量
  333. const updateUnreadMessageCount = (val) => {
  334. const obj = val.find(e => e.userInfoVo.userId === info.value.userId)
  335. if (!obj?.unread || obj.unread === 0) return
  336. delete info.value.unread
  337. Object.assign(info.value, { unread: obj.unread, enterpriseId: entBaseInfo?.enterpriseId })
  338. }
  339. watch(
  340. () => conversationList.value,
  341. async (val) => {
  342. // 数据发生变化
  343. if (channelItem.value && IM.fromChannel === channelItem.value.channelID) {
  344. // 更新
  345. const { list } = await getMoreMessages(1, channelItem.value)
  346. messageItems.value = list.value
  347. if (Object.keys(info.value).length) updateUnreadMessageCount(val)
  348. chatRef.value.scrollBottom()
  349. }
  350. },
  351. {
  352. deep: true,
  353. immediate: true
  354. }
  355. )
  356. // 获取职位信息
  357. async function getMessageTypeSync () {
  358. const data = await getMessageType({
  359. fromUid: IM.uid,
  360. channelId: channelItem.value?.channelID,
  361. type: 102,
  362. page: {
  363. current: 1,
  364. size: 1,
  365. orders: [
  366. { column: 'message_seq', asc: false }
  367. ]
  368. }
  369. })
  370. if (!data.records || !data.records.length) {
  371. return
  372. }
  373. const _item = data.records.pop()
  374. const _itemJSON = JSON.parse(_item.payload)
  375. const _content = JSON.parse(_itemJSON.content)
  376. positionInfo.value = _content.positionInfo
  377. const check = await jobCvRelCheckSend({ jobId: _content.positionInfo.id })
  378. handleChangeSendResumeStatus(check)
  379. }
  380. function handleChangeSendResumeStatus (status) {
  381. if (!isEnterprise) {
  382. const item = userTools.value.find(e => e.key === 'sendResume')
  383. item.disabled = status
  384. }
  385. }
  386. async function handleChange (items) {
  387. try {
  388. chatRef.value.changeOverlay(true)
  389. const { userInfoVo, channel: myChannel, unread } = items.pop()
  390. info.value = userInfoVo?.userInfoResp ?? { name: '系统消息' }
  391. Object.assign(info.value, {
  392. channel: myChannel,
  393. unread
  394. })
  395. // 个人端获取面试信息
  396. if (!isEnterprise) getInterviewInviteList()
  397. const userId = userInfoVo.userInfoResp.userId
  398. const enterpriseId = userInfoVo.userInfoResp.enterpriseId || undefined
  399. const { channel, list, more } = await initChart(userId, enterpriseId)
  400. // console.log('--------',list)
  401. channelItem.value = channel.value
  402. // 获取最近职位记录
  403. getMessageTypeSync()
  404. messageItems.value = list.value
  405. hasMore.value = more
  406. chatRef.value.scrollBottom()
  407. // 点开窗口消除未读数量
  408. await resetUnread(channel.value, entBaseInfo?.enterpriseId)
  409. await updateConversation()
  410. updateUnreadCount()
  411. } catch (error) {
  412. messageItems.value = []
  413. } finally {
  414. chatRef.value.changeOverlay(false)
  415. }
  416. }
  417. // 普通消息
  418. const handleUpdate = (val) => {
  419. send(val.value, channelItem.value)
  420. }
  421. // 选择文件
  422. const uploadFile = ref()
  423. const openFileInput = () => {
  424. uploadFile.value.trigger()
  425. }
  426. // 上传附件
  427. const handleUploadResume = async (url, title, filename) => {
  428. const obj = formItems.value.options.find(e => e.key === 'url')
  429. obj.value = filename
  430. obj.truthValue = url
  431. }
  432. // 获取简历
  433. const showUploadDialog = ref(false)
  434. const enRequestPositionInfo = ref({}) // 企业求简历时选中的职位信息
  435. async function handleSendResume (item) {
  436. try {
  437. item.loading = true
  438. // 获取简历列表
  439. const result = await getPersonResumeCv()
  440. if (result.length === 0) {
  441. Snackbar.warning(t('resume.resumeYetSubmit'))
  442. showUploadDialog.value = true
  443. return
  444. }
  445. resumeList.value = result
  446. if (item?.content?.query?.positionInfo?.data && Object.keys(item?.content?.query?.positionInfo?.data).length) enRequestPositionInfo.value = item?.content?.query?.positionInfo?.data
  447. showResume.value = true
  448. } finally {
  449. item.loading = false
  450. }
  451. }
  452. /**
  453. * 发送简历
  454. * text param
  455. * {
  456. * remark: 备注
  457. * query: {} 自定义参数 access -1 未确定 0 拒绝 1 同意
  458. * type: 1 => 发送简历
  459. * 2 => 索要简历
  460. * 3 => 信息描述
  461. * }
  462. */
  463. // 没有上传过简历的弹窗上传并发送给对方
  464. const handleSubmitAttachment = async () => {
  465. const { valid } = await CtFormRef.value.formRef.validate()
  466. if (!valid) return
  467. const obj = {}
  468. formItems.value.options.forEach(e => {
  469. obj[e.key] = e.truthValue || e.value
  470. })
  471. if (!obj.title || !obj.url) return
  472. await savePersonResumeCv(obj)
  473. const text = {
  474. remark: '发送简历',
  475. query: {
  476. src: obj.url,
  477. title: obj.title
  478. },
  479. type: 1
  480. }
  481. if (enRequestPositionInfo.value) text.query.positionInfo = enRequestPositionInfo.value
  482. send (JSON.stringify(text), channelItem.value, 105)
  483. // 简历投递至简历库
  484. if (isEmployment.value !== '-1') {
  485. await jobCvRelHireSend({
  486. jobId: positionInfo.value.id,
  487. url: obj.url,
  488. recommendUserId: isEmployment.value
  489. })
  490. } else {
  491. const jobId = enRequestPositionInfo.value && enRequestPositionInfo.value?.id ? enRequestPositionInfo.value?.id : positionInfo.value.id
  492. const type = (enRequestPositionInfo.value && Object.keys(enRequestPositionInfo.value).length ? enRequestPositionInfo.value.hire : positionInfo.value.hire) ? 1 : 0
  493. await jobCvRelSend({
  494. jobId,
  495. title: obj.title,
  496. url: obj.url,
  497. type
  498. })
  499. }
  500. handleChangeSendResumeStatus(true)
  501. showUploadDialog.value = false
  502. enRequestPositionInfo.value = {}
  503. }
  504. async function handleSubmitResume () {
  505. if (!selectResume.value) {
  506. Snackbar.error(t('resume.selectResumeToSubmit'))
  507. return
  508. }
  509. const _info = resumeList.value.find((item) => item.id === selectResume.value)
  510. const text = {
  511. remark: '发送简历',
  512. query: {
  513. src: _info.url,
  514. title: _info.title,
  515. id: _info.id,
  516. },
  517. type: 1
  518. }
  519. if (enRequestPositionInfo.value) text.query.positionInfo = enRequestPositionInfo.value
  520. send (JSON.stringify(text), channelItem.value, 105)
  521. // 简历投递至简历库
  522. if (isEmployment.value !== '-1') {
  523. await jobCvRelHireSend({
  524. jobId: positionInfo.value.id,
  525. url: _info.url,
  526. recommendUserId: isEmployment.value
  527. })
  528. } else {
  529. const jobId = enRequestPositionInfo.value && enRequestPositionInfo.value?.id ? enRequestPositionInfo.value?.id : positionInfo.value.id
  530. const type = (enRequestPositionInfo.value && Object.keys(enRequestPositionInfo.value).length ? enRequestPositionInfo.value.hire : positionInfo.value.hire) ? 1 : 0
  531. await jobCvRelSend({
  532. jobId,
  533. title: _info.title,
  534. url: _info.url,
  535. type
  536. })
  537. }
  538. handleChangeSendResumeStatus(true)
  539. showResume.value = false
  540. enRequestPositionInfo.value = {}
  541. }
  542. // 简历预览
  543. const handlePreview = (val) => {
  544. previewFile(val.content.query.src)
  545. }
  546. const handleGetMore = async () => {
  547. try {
  548. chatRef.value.changeLoading(true)
  549. pageSize.value++
  550. const { list, more } = await getMoreMessages(pageSize.value, channelItem.value)
  551. messageItems.value.unshift(...list.value)
  552. hasMore.value = more
  553. // chatRef.value.scrollBottom()
  554. } finally {
  555. chatRef.value.changeLoading(false)
  556. }
  557. }
  558. const handleDelete = async ({ channel }) => {
  559. await deleteConversations(channel, entBaseInfo?.enterpriseId)
  560. await updateConversation()
  561. updateUnreadCount()
  562. }
  563. // 没有企业ID则enterpriseId为undefined
  564. // 发送消息体 { text, type: 2 }
  565. // 面试邀约
  566. const getPositionList = async () => {
  567. const data = await getJobAdvertised({})
  568. if (!data.length) return
  569. const list = dealDictArrayData([], data)
  570. positionList.value = list.map(e => {
  571. const salary = e.payFrom && e.payTo ? `${e.payFrom ? e.payFrom + '-' : ''}${e.payTo}${e.payName ? '/' + e.payName : ''}` : '面议'
  572. return {
  573. label: `${e.name}${e.areaName ? '_' + e.areaName : ''} ${salary}`,
  574. value: e.id,
  575. data: e
  576. }
  577. })
  578. }
  579. async function handleInvite (item) {
  580. item.loading = true
  581. positionList.value = []
  582. try {
  583. await getPositionList()
  584. if (!positionList.value.length) return Snackbar.warning('请先发布职位')
  585. if (item.key === 'requestResume') return showSelectPosition.value = true
  586. showInvite.value = true
  587. } catch (error) {
  588. console.log(error)
  589. } finally {
  590. item.loading = false
  591. }
  592. }
  593. // 企业-发送面试邀请
  594. const handleSubmit = async () => {
  595. const { valid } = await inviteRef.value.CtFormRef.formRef.validate()
  596. if (!valid) {
  597. return
  598. }
  599. const query = inviteRef.value.getQuery()
  600. if (!query.time) {
  601. Snackbar.warning('时间不能为空')
  602. return
  603. }
  604. query.userId = info.value.userId
  605. query.positionInfo = positionList.value.find(e => e.value === query.jobId)
  606. // 需要id
  607. const data = await saveInterviewInvite(query)
  608. // 保留邀请id
  609. query.id = data.id
  610. showTip.value = true
  611. send(JSON.stringify(query), channelItem.value, 101)
  612. showInvite.value = false
  613. }
  614. const router = useRouter()
  615. const handleToInterviewManagement = () => {
  616. router.push('/recruit/enterprise/interviewManagement')
  617. }
  618. // 企业-求简历
  619. const handleRequestResumeSubmit = async () => {
  620. const { valid } = await requestFromRef.value.formRef.validate()
  621. if (!valid) return
  622. const jobId = requestFormItems.value.options.find(e => e.key === 'jobId').value
  623. const positionInfo = positionList.value.find(e => e.value === jobId)
  624. const text = {
  625. remark: '求简历',
  626. query: {
  627. src: '',
  628. title: '',
  629. id: '',
  630. positionInfo
  631. },
  632. type: 2
  633. }
  634. send (JSON.stringify(text), channelItem.value, 105)
  635. showSelectPosition.value = false
  636. }
  637. const handleAgree = (val) => {
  638. if (!val.id) return
  639. const query = {
  640. id: val.id
  641. }
  642. const type = route?.meta?.loginType === 'enterprise' ? 'entBaseInfo' : 'baseInfo'
  643. const baseInfo = localStorage.getItem(type)
  644. if (baseInfo) {
  645. const { phone } = JSON.parse(baseInfo)
  646. query.phone = phone
  647. }
  648. Confirm(t('common.confirmTitle'), '是否确定接收此面试邀请?').then(async () => {
  649. await userInterviewInviteConsent(query)
  650. Snackbar.success(t('common.operationSuccessful'))
  651. getInterviewInviteList()
  652. send(JSON.stringify({ id: val.id }), channelItem.value, 104)
  653. })
  654. }
  655. // 拒绝面试邀请
  656. const handleRefuse = (val) => {
  657. if (!val.id) return
  658. Confirm(t('common.confirmTitle'), '您是否确定要拒绝此面试邀请?').then(async () => {
  659. await userInterviewInviteReject(val.id)
  660. Snackbar.success(t('common.operationSuccessful'))
  661. getInterviewInviteList()
  662. send(JSON.stringify({ id: val.id }), channelItem.value, 103)
  663. })
  664. }
  665. </script>
  666. <style scoped lang="scss">
  667. .message {
  668. display: flex;
  669. &-left {
  670. position: relative;
  671. flex-shrink: 0;
  672. height: 100%;;
  673. width: 360px;
  674. background-color: #fff;
  675. border-radius: 8px;
  676. margin-right: 12px;
  677. .message-left-search {
  678. width: 100%;
  679. height: 60px;
  680. background: linear-gradient(90deg, #f5fcfc, #fcfbfa);
  681. border-radius: 8px 8px 0 0;
  682. }
  683. .message-chat-box {
  684. height: 0;
  685. flex: 1;
  686. overflow: auto;
  687. padding-bottom: 20px;
  688. .chat-item {
  689. position: relative;
  690. width: 100%;
  691. height: 78px;
  692. padding: 14px 12px;
  693. cursor: pointer;
  694. &:hover {
  695. background-color: #f8f8f8;
  696. }
  697. .chat-item-time {
  698. position: absolute;
  699. right: 12px;
  700. top: 50%;
  701. transform: translateY(-50%);
  702. }
  703. .title-box {
  704. max-width: 114px;
  705. overflow: hidden;
  706. white-space: nowrap;
  707. text-overflow: ellipsis;
  708. display: inline-block;
  709. }
  710. }
  711. }
  712. .message-no-more-text {
  713. color: var(--color-999);
  714. font-size: 14px;
  715. text-align: center
  716. }
  717. .left-noData {
  718. position: absolute;
  719. top: 50%;
  720. left: 50%;
  721. transform: translate(-50%, -50%);
  722. }
  723. }
  724. &-right {
  725. height: 100%;
  726. flex: 1;
  727. width: 0;
  728. position: relative;
  729. background-color: #fff;
  730. border-radius: 8px;
  731. .right-noData {
  732. position: absolute;
  733. top: 50%;
  734. left: 50%;
  735. transform: translate(-50%, -50%);
  736. }
  737. }
  738. }
  739. .enterprise-name {
  740. max-width: 150px;
  741. .line {
  742. display: inline-block;
  743. width: 1px;
  744. height: 12px;
  745. vertical-align: middle;
  746. background-color: #e0e0e0;
  747. margin: 0 3px;
  748. }
  749. }
  750. .radioBox {
  751. &:hover {
  752. border-radius: 2px;
  753. background-color: var(--color-f8);
  754. }
  755. }
  756. </style>