123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555 |
- <template>
- <div class="chatting d-flex flex-column">
- <v-overlay
- :model-value="overlay"
- contained
- class="align-center justify-center"
- >
- <v-progress-circular
- color="primary"
- size="64"
- indeterminate
- ></v-progress-circular>
- </v-overlay>
- <div class="top-info">
- <div class="user-info d-flex align-center">
- <p class="d-flex align-center float-left">
- <span class="name">{{ info.name || info?.phone }}</span>
- <template v-if="info.enterpriseId">
- <span>{{ info.postNameCn }}</span>
- <span v-if="info.postNameCn && info.enterpriseAnotherName" class="septal-line"></span>
- <span>{{ info.enterpriseAnotherName }}</span>
- </template>
- </p>
- </div>
- </div>
- <v-divider></v-divider>
- <div class="py-3 px-7" 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 color-primary">
- <span>{{ val.job.name }}</span>
- <span v-if="!val.job.payFrom && !val.job.payTo" class="ml-3">面议</span>
- <span v-else class="ml-3">{{ val.job.payFrom ? val.job.payFrom + '-' : '' }}{{ val.job.payTo }}</span>
- </div>
- <div :style="{'color': ['5', '98', '99'].includes(val.status) ? 'var(--v-error-base)' : 'var(--v-primary-base)'}">{{ statusList.find(e => e.value === val.status)?.label }}</div>
- </div>
- <div class="mt-1 font-size-14 ellipsis" style="max-width: 100%;">
- <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 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>
- <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">
- <v-btn :loading="loading" variant="text" color="primary" size="small" @click="handleMore">查看更多</v-btn>
- </div>
- <div v-for="(val, i) in items" :key="i" :id="val.id">
- <div class="time-box">{{ timesTampChange(+(val.timestamp.padEnd(13, '0'))) }}</div>
-
-
- <template v-if="val.payload?.type === 102">
- <v-card
- color="teal"
- variant="tonal"
- class="mx-auto mb-5"
- width="400"
- min-height="150"
- :elevation="3"
- >
- <div class="pa-3">
- <div class="text-h6"> {{ val.payload?.content?.positionInfo?.name }}</div>
- <div v-if="!val.payload?.content?.positionInfo?.payFrom && !val.payload?.content?.positionInfo?.payTo" class="text-subtitle-2">薪酬待遇: 面议</div>
- <div v-else class="text-subtitle-2">薪酬待遇: {{ val.payload?.content?.positionInfo?.payFrom ? val.payload?.content?.positionInfo?.payFrom + ' - ' : '' }}{{ val.payload?.content?.positionInfo?.payTo }}</div>
- <div>
- <v-chip
- color="secondary"
- v-for="(v, i) in val.payload?.content?.positionInfo?.enterprise?.welfareList"
- :key="val.message_id + v + i"
- x-small
- class="mt-1 mr-1"
- >
- {{ v }}
- </v-chip>
- </div>
- <v-divider class="my-3"></v-divider>
- <div class="text-subtitle-2 text-right">
- <v-avatar size="24">
- <v-img :src="val.payload?.content?.positionInfo?.contact?.avatar"></v-img>
- </v-avatar>
- {{ val.payload?.content?.positionInfo?.contact?.name }}
- {{ val.payload?.content?.positionInfo?.contact?.postNameCn }}
- {{ val.payload?.content?.positionInfo?.enterprise?.name }}
- </div>
- <div class="text-subtitle-2 text-right">
- 地址:{{ val.payload?.content?.positionInfo?.address }}
- </div>
- </div>
- </v-card>
- </template>
- <template v-if="val.payload?.type === 1006">
- <div class="text-subtitle-2 text-center text-grey">{{ val.from_uid === IM.uid ? '' : '对方' }}撤回了一份简历</div>
- </template>
- <div v-if="val.payload?.type !== 1006" :class="['message-view_item', val.from_uid === IM.uid ? 'is-self' : 'is-other']">
- <div style="width: 40px; height: 40px;">
- <v-avatar>
- <v-img
- :src="(val.from_uid === IM.uid ? mAvatar() : getUserAvatar(info.avatar, info.sex)) || 'https://minio.citupro.com/dev/menduner/7.png'"
- :width="40"
- height="40"
- rounded
- ></v-img>
- </v-avatar>
- </div>
- <!-- 显示沟通职位 -->
- <template v-if="val.payload?.type === 102">
- <div class="message-text" :class="{ active: val.from_uid === IM.uid}">
- {{ 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" 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-if="val.payload.type === 105" class="text-end">
- <v-chip class="ma-2" label color="primary" v-if="val.from_uid === IM.uid">
- <v-icon icon="mdi-check" start></v-icon>
- {{ val.payload.content?.type === 1 ? '附件简历已发送' : '简历请求已发送' }}
- </v-chip>
- <v-card v-if="val.payload.content?.type !== 2 || val.from_uid !== IM.uid" width="300" class="pa-3 ma-2" color="teal" variant="tonal" :elevation="3">
- <v-card-text class="d-flex">
- <p v-if="val.payload.content?.type === 1" style="width: 100%; text-align: left;">{{ val.payload.content?.query?.title || t('resume.attachmentResume') }}</p>
- <p v-if="val.payload.content?.type === 2">{{ t('resume.requestResume') }}</p>
- </v-card-text>
- <v-card-actions class="justify-center">
- <!-- <v-btn variant="tonal" flat size="small" color="error" @click="handleRejectReceive(val.payload)">拒绝</v-btn> -->
- <template v-if="val.payload.content?.type === 1">
- <v-btn block variant="tonal" flat size="small" color="success" @click="handlePreview(val.payload)">点击预览附件简历</v-btn>
- <!-- <v-btn variant="tonal" flat size="small" color="error" @click="handleBack(val)">撤回简历</v-btn> -->
- </template>
- <v-btn v-if="val.payload.content?.type === 2" block variant="tonal" flat size="small" color="success" @click="handleSendResume(val.payload)">点击发送附件简历</v-btn>
- </v-card-actions>
- </v-card>
- </div>
- <div v-else class="message-text" :class="{ active: val.from_uid === IM.uid}">
- {{ val.payload?.content }}
- </div>
- </div>
- <!-- 插入个人-面试职位邀请:同意、拒绝 -->
- <div v-if="isEnterprise && val.payload?.type === 101" class="d-flex justify-center">
- <v-card
- color="teal"
- variant="tonal"
- class="mx-auto"
- min-width="400"
- min-height="150"
- :elevation="3"
- >
- <v-card-item>
- <div>
- <div class="text-overline mb-1">
- 面试邀请
- </div>
- <div class=" d-flex justify-space-between">
- <div class="text-h6 mb-1">
- {{ val.payload?.content?.positionInfo?.data?.name }}
- </div>
- <div v-if="!val.payload?.content?.positionInfo?.data?.payFrom && !val.payload?.content?.positionInfo?.data?.payTo">面议</div>
- <div v-else>
- {{ val.payload?.content?.positionInfo?.data?.payFrom ? val.payload?.content?.positionInfo?.data?.payFrom + ' - ' : '' }}
- {{ val.payload?.content?.positionInfo?.data?.payTo }}
- </div>
- </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>
- </div>
- <!-- <div class="d-flex justify-center" v-if="val.payload.type === 105">
- <v-chip>已成功发送简历</v-chip>
- </div> -->
- </div>
- </div>
- </div>
- <div class="tools pa-3" v-if="Object.keys(info).length > 0">
- <slot name="tools"></slot>
- </div>
- <div class="bottom-info">
- <v-divider></v-divider>
- <div class="pa-3">
- <v-textarea
- v-model="inputVal"
- label="请输入消息"
- placeholder="请输入消息 按Ctrl+Enter换行"
- hide-details
- no-resize
- color="primary"
- bg-color="white"
- variant="plain"
- :disabled="Object.keys(info).length === 0"
- @keydown="handleKeyDown"
- >
- <template #append-inner>
- <v-btn color="primary" :disabled="!inputVal" style="align-self: center;" @click="handleSend">发送</v-btn>
- </template>
- </v-textarea>
- </div>
- </div>
- </div>
- </template>
- <script setup>
- defineOptions({ name: 'message-chatting'})
- import { ref, nextTick, onMounted, inject, watch } from 'vue'
- import { timesTampChange } from '@/utils/date'
- import { useIMStore } from '@/store/im'
- import { useI18n } from '@/hooks/web/useI18n'
- import { useRouter } from 'vue-router';
- import { getDict } from '@/hooks/web/useDictionaries'
- import { getUserAvatar } from '@/utils/avatar'
- import { useUserStore } from '@/store/user'
- const isEnterprise = inject('isEnterprise')
- const { t } = useI18n()
- const emits = defineEmits(['handleMore', 'handleSend', 'handleAgree', 'handleRefuse'])
- const props = defineProps({
- items: {
- type: Array,
- default: () => []
- },
- info: {
- type: Object,
- default: () => ({})
- },
- // uid: {
- // type: String,
- // default: ''
- // },
- hasMore: {
- type: Boolean,
- default: false
- },
- interview: {
- type: Array,
- default: () => []
- },
- updateConversation: {
- type: Function,
- default: () => {}
- },
- updateUnreadCount: {
- type: Function,
- default: () => {}
- },
- resetUnread: {
- type: Function,
- default: () => {}
- }
- })
- watch(() => props.info, async (val) => {
- if (!Object.keys(val).length || val.unread === 0) return
- await props.resetUnread(val.channel, isEnterprise ? val.enterpriseId : null)
- await props.updateConversation()
- props.updateUnreadCount()
- }, { deep: true, immediate: true })
- const router = useRouter()
- const overlay = ref(false)
- const IM = useIMStore()
- const userStore = useUserStore()
- const loading = ref(false)
- const mAvatar = () => {
- if (isEnterprise) {
- return getUserAvatar(userStore.entBaseInfo?.avatar, userStore.entBaseInfo?.sex)
- }
- return getUserAvatar(userStore.baseInfo?.avatar, userStore.baseInfo?.sex)
- }
- const chatRef = ref()
- const inputVal = ref('')
- const pullDowning = ref(false) // 下拉中
- const pulldownFinished = ref(false) // 下拉完成
- // 滚动到底部
- const scrollBottom = () => {
- const chat = chatRef.value
- if (chat) {
- nextTick(function () {
- chat.scrollTop = chat.scrollHeight
- })
- }
- }
- // 求职端-获取求职者与当前邀请人的面试记录
- const statusList = ref([])
- const getStatusList = () => {
- getDict('menduner_interview_invite_status').then(({data}) => {
- if (data.length) statusList.value = data
- })
- }
- if (!isEnterprise) getStatusList()
- onMounted(() => {
- nextTick(() => {
- scrollBottom()
- })
- })
- const handleMore = () => {
- emits('handleMore')
- }
- const handleSend = () => {
- emits('handleSend', inputVal)
- }
- const handleScroll = (e) => {
- const targetScrollTop = e.target.scrollTop;
- if (targetScrollTop <= 250) {
- // 下拉
- if (pullDowning.value || pulldownFinished.value) {
- return
- }
- pullDowning.value = true
- }
- }
- const changeOverlay = (val) => {
- overlay.value = val
- }
- const changeLoading = (val) => {
- loading.value = val
- }
- const handleKeyDown = (event) => {
- if (event.keyCode === 13) {
- event.preventDefault()
- if(event.ctrlKey) {
- // 换行
- inputVal.value += '\n'
- return
- }
- // 发送
- emits('handleSend', inputVal)
- }
- }
- const reset = () => {
- inputVal.value = ''
- }
- // 同意面试邀请
- const handleAgree = (val) => {
- 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 } })
- }
- // 简历预览
- const handlePreview = (val) => {
- emits('handlePreview', val)
- }
- // const handleBack = (val) => {
- // emits('handleBack', val)
- // }
- // 发送简历
- const handleSendResume = (val) => {
- emits('handleSendResume', val)
- }
- // 拒绝接收简历
- // const handleRejectReceive = (val) => {
- // emits('handleRejectReceive', val)
- // }
- // // 同意接收简历
- // const handleAccessReceive = (val) => {
- // emits('handleAccessReceive', val)
- // }
- defineExpose({
- chatRef,
- reset,
- changeLoading,
- scrollBottom,
- changeOverlay
- })
- </script>
- <style scoped lang="scss">
- .chatting {
- position: relative;
- height: 100%;
- }
- .top-info {
- // height: 104px;
- padding: 0 30px;
- .user-info {
- position: relative;
- height: 48px;
- justify-content: space-between;
- // padding: 0 30px;
- span {
- font-size: 14px;
- font-weight: 400;
- // color: var(--color-666);
- line-height: 22px;
- }
- .name {
- line-height: 25px;
- margin-right: 20px;
- }
- }
- .position-content {
- position: relative;
- padding: 16px;
- border-radius: 12px;
- // margin: auto;
- width: 60%;
- max-width: 800px;
- // width: 760px;
- background: linear-gradient(90deg, #d5ffff, #f0fff4);
- .salary {
- font-size: 20px;
- font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif;
- color: var(--v-error-base);
- line-height: 24px;
- }
- &-emolument {
- color: #f30;
- font-weight: 600;
- }
- }
- }
- .message-box {
- flex: 1;
- padding: 0 30px 30px;
- overflow-y: auto;
- .time-box {
- user-select: none;
- position: relative;
- top: 8px;
- margin: 20px 0;
- max-height: 20px;
- text-align: center;
- font-weight: 400;
- font-size: 12px;
- color: var(--color-time-divider);
- }
- .message-view_item {
- display: flex;
- flex-direction: row;
- align-items: flex-start;
- margin: 8px 0;
- position: relative;
- .message-text {
- overflow-wrap: break-word;
- background-color: #f0f2f5;
- border-radius: 6px;
- max-width: 85%;
- padding: 10px;
- &.active {
- background: #d5e6e8;
- }
- }
- }
- .is-self {
- flex-direction: row-reverse;
- display: flex;
- .message-text {
- margin-right: 10px;
- }
- }
- .is-other {
- .message-text {
- margin-left: 10px;
- }
- }
- }
- .bottom-info {
- width: 100%;
- height: 160px;
- background-color: #fff;
- }
- input {
- outline: none;
- width: 748px;
- height: 60px;
- max-width: 748px;
- overflow: auto;
- white-space: pre-wrap;
- &:focus {
- border: none;
- }
- }
- .tipsText {
- color: var(--color-999);
- font-size: 12px;
- cursor: pointer;
- &:hover {
- color: var(--v-primary-base);
- }
- }
- /* 滚动条样式 */
- ::-webkit-scrollbar {
- -webkit-appearance: none;
- width: 10px;
- height: 0px;
- }
- /* 滚动条内的轨道 */
- ::-webkit-scrollbar-track {
- background: rgba(0, 0, 0, 0.1);
- border-radius: 0;
- }
- /* 滚动条内的滑块 */
- ::-webkit-scrollbar-thumb {
- cursor: pointer;
- border-radius: 5px;
- background: rgba(0, 0, 0, 0.15);
- transition: color 0.2s ease;
- }
- </style>
|