Procházet zdrojové kódy

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

lifanagju_citu před 9 měsíci
rodič
revize
d83d9a42d0
43 změnil soubory, kde provedl 668 přidání a 301 odebrání
  1. 7 0
      src/api/common/index.js
  2. 8 1
      src/api/position.js
  3. 2 2
      src/api/publicRecruitment/index.js
  4. 1 1
      src/api/recruit/personal/personalCenter/index.js
  5. 5 1
      src/components/CtDialog/index.vue
  6. 1 1
      src/components/DatePicker/index.vue
  7. 3 2
      src/components/Enterprise/components/positions.vue
  8. 26 3
      src/components/Enterprise/details.vue
  9. 20 3
      src/components/Enterprise/info.vue
  10. 2 1
      src/components/Position/longStrip.vue
  11. 2 2
      src/components/Position/similarPositions.vue
  12. 24 18
      src/components/PositionLongStrip/item.vue
  13. 80 40
      src/hooks/web/useIM.js
  14. 2 1
      src/layout/company/navBar.vue
  15. 7 3
      src/layout/company/slider.vue
  16. 2 1
      src/layout/personal/navBar.vue
  17. 1 1
      src/layout/personal/slider.vue
  18. 1 1
      src/router/modules/components/recruit/enterprise.js
  19. 10 0
      src/utils/avatar.js
  20. 1 1
      src/utils/eventList.js
  21. 13 0
      src/utils/position.js
  22. 1 1
      src/views/integral/pointsManagement/index.vue
  23. 28 25
      src/views/publicRecruitment/myRecommendation.vue
  24. 156 118
      src/views/recruit/components/message/components/chatting.vue
  25. 139 35
      src/views/recruit/components/message/index.vue
  26. 4 3
      src/views/recruit/enterprise/elite/components/table.vue
  27. 2 1
      src/views/recruit/enterprise/informationSetting/index.vue
  28. 2 1
      src/views/recruit/enterprise/interview/components/item.vue
  29. 7 0
      src/views/recruit/enterprise/memberCenter/myPoints/index.vue
  30. 5 1
      src/views/recruit/enterprise/positionManagement/index.vue
  31. 11 6
      src/views/recruit/personal/PersonalCenter/components/interview/index.vue
  32. 6 1
      src/views/recruit/personal/PersonalCenter/components/interview/item.vue
  33. 2 1
      src/views/recruit/personal/PersonalCenter/components/interviewSchedule.vue
  34. 2 1
      src/views/recruit/personal/PersonalCenter/components/seenMe.vue
  35. 5 4
      src/views/recruit/personal/PersonalCenter/dynamic/left.vue
  36. 1 0
      src/views/recruit/personal/PersonalCenter/dynamic/right.vue
  37. 7 0
      src/views/recruit/personal/PersonalCenter/index.vue
  38. 1 0
      src/views/recruit/personal/company/index.vue
  39. 8 1
      src/views/recruit/personal/home/index.vue
  40. 7 0
      src/views/recruit/personal/myWallet/index.vue
  41. 48 17
      src/views/recruit/personal/position/components/details.vue
  42. 2 1
      src/views/recruit/personal/remuse/components/basicInfo.vue
  43. 6 1
      src/views/recruit/personal/shareJob/sendResume/simple.vue

+ 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}`
+  })
 }

+ 8 - 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
   })
 }
@@ -194,4 +194,11 @@ export const topJobAdvertised = async (id) => {
   return await request.post({
     url: `/app-admin-api/menduner/system/job-advertised/top?ids=${id}`
   })
+}
+
+// 招聘端-职位详情-认证情况
+export const getEnterpriseAuthDetails = async (enterpriseId) => {
+  return await request.get({
+    url: `/app-api/menduner/system/enterprise/get/auth?enterpriseId=${enterpriseId}`
+  })
 }

+ 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
   })
 }

+ 5 - 1
src/components/CtDialog/index.vue

@@ -40,7 +40,7 @@
             text
             @click="handleClose"
           >
-            取消
+            {{ closeText }}
           </v-btn>
           <v-btn
             color="primary"
@@ -104,6 +104,10 @@ const props = defineProps({
     type: Boolean,
     default: true
   },
+  closeText: {
+    type: String,
+    default: '取消'
+  }
 })
 
 const show = ref(false)

+ 1 - 1
src/components/DatePicker/index.vue

@@ -52,7 +52,7 @@ const time = computed(() => {
 :deep(.dp__input_icon_pad) {
   font-size: 14px;
 }
-::v-deep .dp__input {
+:deep(.dp__input) {
   border: 1px solid #ababab;
   padding: 7px 30px;
   &:hover {

+ 3 - 2
src/components/Enterprise/components/positions.vue

@@ -58,7 +58,7 @@
           <div class="update-time">{{ timesTampChange(val.job.updateTime) }} 刷新</div>
         </div>
         <div v-else class="account-info">
-          <v-avatar :image="val.contact.avatar || 'https://minio.citupro.com/dev/menduner/7.png'"></v-avatar>
+          <v-avatar :image="getUserAvatar(val.contact.avatar, val.contact.sex)"></v-avatar>
           <span class="account-label">{{ val.contact.name }} · {{ val.contact.postNameCn }}</span>
           <span>
             <v-btn class="half-button" color="primary" size="small" @click="toDetails(val)">立即沟通</v-btn>
@@ -84,6 +84,7 @@ import { timesTampChange } from '@/utils/date'
 import { getDict } from '@/hooks/web/useDictionaries'
 import { dealDictObjData } from '@/utils/position'
 import { prologue } from '@/hooks/web/useIM'
+import { getUserAvatar } from '@/utils/avatar'
 import { getJobAdvertisedPositionCount, getJobAreaByEnterpriseId, getJobAdvertisedSearch } from '@/api/position'
 import MPagination from '@/components/CtPagination'
 import expType from '@/views/recruit/personal/position/components/conditionFilter/expType.vue'
@@ -210,7 +211,7 @@ const desc = [
 const toDetails = async (info) => {
   const userId = info.contact.userId
   const enterpriseId = info.contact.enterpriseId
-  const text = '您好,我对该职位很感兴趣,希望能有机会与您进一步沟通。'
+  const text = '您好,可以进一步了解一下这个职位的详情吗?'
   await prologue({userId, enterpriseId, text})
   let url = `/recruit/personal/message?id=${info.job.id}`
   if (info.contact.enterpriseId) {

+ 26 - 3
src/components/Enterprise/details.vue

@@ -7,9 +7,16 @@
           <div class="ml-4">
             <div class="contact-name">
               {{ info.enterprise.name }}
-              <v-icon color="primary" size="24">mdi-shield-check</v-icon>
+              <v-icon :color="statusInfo.color" size="20">{{ statusInfo.mdi }}</v-icon>
+              <span :style="{'color': statusInfo.color,'font-size': '14px'}">{{ statusInfo.label }}</span>
+            </div>
+            <div class="contact-info">
+              {{ info.business.type }}
+              <span v-if="info.business.type && info.scaleName">·</span> 
+              {{ info.scaleName }}
+              <span v-if="info.industryName && info.scaleName">·</span> 
+              {{ info.industryName }}
             </div>
-            <div class="contact-info">{{ info.business.type }} · {{ info.scaleName }} · {{ info.industryName }}</div>
           </div>
         </div>
         <div class="float-right d-flex">
@@ -67,9 +74,10 @@
 
 <script setup>
 defineOptions({ name: 'enterprise-details'})
-import { ref } from 'vue'
+import { ref, computed } from 'vue'
 import EnterpriseIntroduction from './components/introduction.vue'
 import recruitmentPositions from './components/positions.vue'
+import { getEnterpriseAuthDetails } from '@/api/position'
 import { getEnterpriseDetails, getEnterpriseSubscribeCheck, getEnterpriseSubscribe, getEnterpriseUnsubscribe, enterpriseClick } from '@/api/enterprise'
 import { timesTampChange } from '@/utils/date'
 import { dealDictObjData } from '@/utils/position'
@@ -101,8 +109,16 @@ const handleEnterpriseClick = async () => {
 }
 handleEnterpriseClick()
 
+const statusList = [
+  { label: '未认证', color: '#fb8c00', value: null, mdi: 'mdi-shield-remove' },
+  { label: '审核中', color: '#fb8c00', value: '0', mdi: 'mdi-shield-half-full' },
+  { label: '已认证', color: 'var(--v-primary-base)', value: '1', mdi: 'mdi-shield-check' },
+  { label: '已驳回', color: '#fe574a', value: '2', mdi: 'mdi-shield-off' }
+]
+
 // 企业详情
 const info = ref({})
+const authInfo = ref({})
 const getDetails = async () => {
   if (!props.id) return
   const data = await getEnterpriseDetails({ id: props.id })
@@ -111,9 +127,16 @@ const getDetails = async () => {
 
   info.value = { ...data, ...dealDictObjData({}, data.enterprise) }
   getCollectionStatus(props.id)
+  // 企业实名认证信息
+  authInfo.value = await getEnterpriseAuthDetails(props.id)
 }
 getDetails()
 
+const statusInfo = computed(() => {
+  const obj = (authInfo.value && Object.keys(authInfo.value).length) ? statusList.find(e => e.value === authInfo.value.status) : statusList[0]
+  return obj
+})
+
 // 效验求职者是否关注该企业
 const isCollection = ref(false)
 const getCollectionStatus = async (id) => {

+ 20 - 3
src/components/Enterprise/info.vue

@@ -5,8 +5,8 @@
       <v-img class="float-left" :src="props.info.enterprise.logoUrl || 'https://minio.citupro.com/dev/menduner/company-avatar.png'" :width="45" height="45"></v-img>
       <div class="ml-3 float-left">
         <p class="enterprise-name cursor-pointer" @click="handleEnterprise(0)">{{ props.info.enterprise.anotherName }}</p>
-        <v-icon color="primary" size="20">mdi-shield-check</v-icon> <!-- mdi-shield-remove -->
-        <span style="color: var(--v-primary-base);font-size: 14px;">已认证</span>
+        <v-icon :color="statusInfo.color" size="20">{{ statusInfo.mdi }}</v-icon>
+        <span :style="{'color': statusInfo.color,'font-size': '14px'}">{{ statusInfo.label }}</span>
       </div>
     </div>
     <div class="mt-3 border-bottom-dashed" style="font-size: 14px;">
@@ -23,8 +23,10 @@
 
 <script setup>
 defineOptions({ name: 'enterprise-info' })
-import { ref } from 'vue'
+import { ref, computed } from 'vue'
 import { dealDictObjData } from '@/utils/position'
+import { getEnterpriseAuthDetails } from '@/api/position'
+
 const props = defineProps({
   info: {
     type: Object,
@@ -37,13 +39,28 @@ const list = [
   { icon: 'mdi-account-multiple', label: 'scaleName' },
   { icon: 'mdi-family-tree', label: 'industryName' }
 ]
+const statusList = [
+  { label: '未认证', color: '#fb8c00', value: null, mdi: 'mdi-shield-remove' },
+  { label: '审核中', color: '#fb8c00', value: '0', mdi: 'mdi-shield-half-full' },
+  { label: '已认证', color: 'var(--v-primary-base)', value: '1', mdi: 'mdi-shield-check' },
+  { label: '已驳回', color: '#fe574a', value: '2', mdi: 'mdi-shield-off' }
+]
+
 const obj = ref({})
+const authInfo = ref({})
 const getData = async () => {
   const prise = props.info.enterprise
   obj.value = dealDictObjData({}, prise)
+  // 企业实名认证信息
+  authInfo.value = await getEnterpriseAuthDetails(props.info.enterprise.id)
 }
 getData()
 
+const statusInfo = computed(() => {
+  const obj = (authInfo.value && Object.keys(authInfo.value).length) ? statusList.find(e => e.value === authInfo.value.status) : statusList[0]
+  return obj
+})
+
 const handleEnterprise = (val) => {
   const key = val ? 'recruitmentPositions' : 'briefIntroduction'
   window.open(`/recruit/personal/company/details/${props.info.enterprise.id}?key=${key}`)

+ 2 - 1
src/components/Position/longStrip.vue

@@ -7,7 +7,7 @@
           <v-btn v-if="props.showCancelBtn" class="half-button ml-3" color="primary" size="small" @click="handleCancel(val)">取消感兴趣</v-btn>
         </div>
         <div class="img-box">
-          <v-avatar :image="val.contact.avatar || 'https://minio.citupro.com/dev/menduner/7.png'" size="x-small"></v-avatar>
+          <v-avatar :image="getUserAvatar(val.contact.avatar, val.contact.sex)" size="x-small"></v-avatar>
           <span class="name">
             <span class="mx-3">{{ val.contact.name }}</span>
             <span class="gray">{{ val.contact.postNameCn }}</span>
@@ -45,6 +45,7 @@ defineOptions({ name: 'longStrip'})
 import { getPersonJobUnfavorite } from '@/api/position'
 import { useI18n } from '@/hooks/web/useI18n'
 import Snackbar from '@/plugins/snackbar'
+import { getUserAvatar } from '@/utils/avatar'
 
 const emits = defineEmits(['refresh'])
 const { t } = useI18n()

+ 2 - 2
src/components/Position/similarPositions.vue

@@ -10,9 +10,9 @@
         <span class="float-right enterprise-address">{{ item.areaName }}</span>
       </div>
     </div>
-    <div class="text-center more-btn">
+    <!-- <div class="text-center more-btn">
       <v-btn color="primary" variant="outlined" class="buttons" :to="`/recruit/personal/company/details/${props.info.enterpriseId}?key=recruitmentPositions`">{{ $t('position.allBtn') }}</v-btn>
-    </div>
+    </div> -->
   </div>
 </template>
 

+ 24 - 18
src/components/PositionLongStrip/item.vue

@@ -3,23 +3,28 @@
   <div class="positionItem" v-for="(item, index) in list" :key="index" @mouseenter="item.active = true" @mouseleave="item.active = false">
     <div class="position-and-company">
       <!-- 职位 -->
-      <div v-if="item?.job?.hire" class="mr-3">
-        <PublicRecruitment width="45" height="45"></PublicRecruitment>
-      </div>
+      
       <div class="position" @mouseenter="item.positionActive = true" @mouseleave="item.positionActive = false" @click="handlePosition(item)">
         <div class="d-flex">
-          <p v-if="item.job.name.indexOf('style')" v-html="item.job.name" :class="['title1', {'default-active': item.positionActive }]"></p>
-          <p v-else :class="['title1', {'default-active': item.positionActive }]">{{ item.job.name }}{{ item.job.pos ? ' [' + item.job.pos + '] ' : '' }}</p>
-          <p class="salary ml-1">{{ item.job.payFrom }}-{{ item.job.payTo }}/{{ item.job.payName }}</p>
-          <div v-if="item?.job?.hire">
-            <v-chip v-if="item?.job?.hirePrice && item?.job?.hirePrice > 0" class="ml-3" label color="primary" size="small">赏金:{{ commissionCalculation(item.job.hirePrice, 1) }}元</v-chip>
-            <v-chip v-if="item?.job?.hirePoint && item?.job?.hirePoint > 0" class="ml-3" label color="primary" size="small">积分:{{ commissionCalculation(item.job.hirePoint, 1) }}点</v-chip>
+          <div v-if="item?.job?.hire" class="mr-3">
+            <PublicRecruitment width="45" height="45"></PublicRecruitment>
+          </div>
+          <div>
+            <div class="d-flex">
+              <p v-if="item.job.name.indexOf('style')" v-html="item.job.name" :class="['title1', {'default-active': item.positionActive }]"></p>
+              <p v-else :class="['title1', {'default-active': item.positionActive }]">{{ item.job.name }}{{ item.job.pos ? ' [' + item.job.pos + '] ' : '' }}</p>
+              <p class="salary ml-1">{{ item.job.payFrom }}-{{ item.job.payTo }}/{{ item.job.payName }}</p>
+              <div v-if="item?.job?.hire">
+                <v-chip v-if="item?.job?.hirePrice && item?.job?.hirePrice > 0" class="ml-3" label color="primary" size="small">赏金:{{ commissionCalculation(item.job.hirePrice, 1) }}元</v-chip>
+                <v-chip v-if="item?.job?.hirePoint && item?.job?.hirePoint > 0" class="ml-3" label color="primary" size="small">积分:{{ commissionCalculation(item.job.hirePoint, 1) }}点</v-chip>
+              </div>
+            </div>
+            <div class="mt-2">
+              <span v-for="(j, i) in desc" :key="i">
+                <v-chip v-if="item.job[j.value]" size="x-small" label class="mr-1" color="var(--color-666)" :prepend-icon="j.mdi">{{ item.job[j.value] }}</v-chip>
+              </span>
+            </div>
           </div>
-        </div>
-        <div class="mt-2">
-          <span v-for="(j, i) in desc" :key="i">
-            <v-chip v-if="item.job[j.value]" size="x-small" label class="mr-1" color="var(--color-666)" :prepend-icon="j.mdi">{{ item.job[j.value] }}</v-chip>
-          </span>
         </div>
       </div>
       <!-- 公司 -->
@@ -52,7 +57,7 @@
         </template>
       </div>
       <div class="footer-right">
-        <v-avatar size="x-small" :image="item.contact.avatar || 'https://minio.citupro.com/dev/menduner/7.png'"></v-avatar>
+        <v-avatar size="x-small" :image="getUserAvatar(item.contact.avatar, item.contact.sex)"></v-avatar>
         <span class="mx-2 textColor666">
           {{ item.contact.name }}
           <span v-if="item?.contact?.postNameCn">|</span>
@@ -67,6 +72,7 @@
 import { commissionCalculation } from '@/utils/position'
 defineOptions({ name: 'long-strip-position-card-item' })
 import { ref, watch } from 'vue'
+import { getUserAvatar } from '@/utils/avatar'
 const props = defineProps({
   items: {
     type: Object,
@@ -118,7 +124,7 @@ const handleEnterprise = (item) => {
 .company {
   flex: 1;
   display: flex;
-  justify-content: end;
+  justify-content: start;
 }
 .company-info {
   float: left;
@@ -149,8 +155,8 @@ const handleEnterprise = (item) => {
     padding: 16px 20px;
     width: 100%;
     .position {
-      width: 484px;
-      padding-right: 12px;
+      width: 500px;
+      padding-right: 30px;
     }
   }
   .footer {

+ 80 - 40
src/hooks/web/useIM.js

@@ -21,35 +21,44 @@ import {
   // Message, StreamItem, ChannelTypeGroup, MessageStatus, SyncOptions, MessageExtra, MessageContent
 } from "wukongimjssdk"
 
-// 注册消息体
-class ObjectContent extends MessageContent {
-  constructor(text) {
-    super();
-    this.content = text;
-  }
-
-  get conversationDigest() {
-      // 这里需要实现具体的逻辑
-      return this.content
-  }
 
-  get contentType() {
-      // 这里需要实现具体的逻辑
-      return 101; // 示例实现
-  }
+const ObjType = [ 101, 102, 103, 104 ]
 
-  decodeJSON(content) {
-      this.content = content.text;
+const { ObjectContent } = initRegister(101)
+const { ObjectContent: ObjectContent2 } = initRegister(102)
+const { ObjectContent: ObjectContent3 } = initRegister(103)
+const { ObjectContent: ObjectContent4 } = initRegister(104)
+const { ObjectContent: ObjectContent5 } = initRegister(105) // 发送简历
+// 注册消息体
+function initRegister (contentType) {
+  class ObjectContent extends MessageContent {
+    constructor(text) {
+      super();
+      this.content = text
+    }
+    get conversationDigest() {
+        // 这里需要实现具体的逻辑
+        return this.content
+    }
+    get contentType() {
+        // 这里需要实现具体的逻辑
+        return contentType; // 示例实现
+    }
+    decodeJSON(content) {
+        this.content = content.text;
+    }
+    encodeJSON() {
+        return {
+          content: this.content
+        };
+    }
   }
-
-  encodeJSON() {
-      return {
-        content: this.content
-      };
+  // 注册101类型为面试
+  WKSDK.shared().register(contentType, () => new ObjectContent())
+  return {
+    ObjectContent
   }
 }
-// 注册101类型为面试
-WKSDK.shared().register(101, () => new ObjectContent())
 
 
 const HISTORY_QUERY = {
@@ -73,7 +82,7 @@ export function useDataSource () {
   // 最近会话数据源
   WKSDK.shared().config.provider.syncConversationsCallback  = async () => {
     const query = {
-      msg_count: 100
+      msg_count: 1
     }
     if (loginType.loginType === 'enterprise') {
       Object.assign(query, { enterpriseId: userStore.baseInfo.enterpriseId })
@@ -115,17 +124,17 @@ 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 (msg.payload.type === 101) {
-              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)
+    // console.log(resultMessages)
     const more = resp.more === 1
     return {
       more,
@@ -344,23 +353,54 @@ export async function getMoreMessages (pageSize, channel) {
  * @param { Number } type : 101 面试主体 
  * @returns 
  */
+  // 发送职位使用101
+const contentType = {
+  101: ObjectContent,
+  102: ObjectContent2,
+  103: ObjectContent3,
+  104: ObjectContent4,
+  105: ObjectContent5, // 发送简历
+}
 export function send (text, _channel, type) {
   let _text
-  if (type === 101) {
-    _text = new ObjectContent(text)
+  if (contentType[type]) {
+    _text = new contentType[type](text)
     WKSDK.shared().chatManager.send(_text, _channel)
-    console.log(_text)
     return
   }
+  // if (type === 101) {
+  //   _text = new ObjectContent(text)
+  //   WKSDK.shared().chatManager.send(_text, _channel)
+  //   // console.log(_text)
+  //   return
+  // }
+  // if (type === 102) {
+  //   _text = new ObjectContent2(text)
+  //   WKSDK.shared().chatManager.send(_text, _channel)
+  //   // 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)
+  // console.log(_text)
   WKSDK.shared().chatManager.send(_text, _channel)
 }
 
 // 对话开场白
 export async function prologue ({userId, enterpriseId, text}) {
   const { channel } = await checkConversation(userId, enterpriseId)
-  send(text, channel)
+  send(text, channel, 102)
 }
 
 // 检测是否存在频道

+ 2 - 1
src/layout/company/navBar.vue

@@ -32,7 +32,7 @@
               <template v-slot:activator="{ props }">
                 <div class="d-flex ml-5 pl-2 align-center cursor-pointer" v-bind="props">
                   <v-avatar>
-                    <v-img alt="" :src="baseInfo?.avatar || 'https://minio.citupro.com/dev/menduner/7.png'"></v-img>
+                    <v-img alt="" :src="getUserAvatar(baseInfo?.avatar, baseInfo?.sex)"></v-img>
                   </v-avatar>
                   <div class="ml-2">{{ baseInfo?.name ?? $t('sys.tourist') }}</div>
                 </div>
@@ -99,6 +99,7 @@ import { useUserStore } from '@/store/user'; const userStore = useUserStore()
 import { useRouter } from 'vue-router'; const router = useRouter()
 import { useI18n } from '@/hooks/web/useI18n'; const { t } = useI18n()
 import MessageNotification from '../message.vue'
+import { getUserAvatar } from '@/utils/avatar'
 defineOptions({ name: 'personal-navbar' })
 
 defineProps({

+ 7 - 3
src/layout/company/slider.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="slider-box">
-    <div v-for="(item, index) in list" :key="index" class="slider-box-item" @click="handleClick(index)">
+    <div v-for="(item, index) in list" :key="index" class="slider-box-item" @click="handleClick(item, index)">
       <v-btn size="30" class="icons" icon variant="text">
         <v-icon class="icons" size="30">{{ item.mdi }}</v-icon>
         <v-tooltip :text="item.tips" location="start" activator="parent">
@@ -17,15 +17,19 @@
 
 <script setup>
 defineOptions({ name: 'personalSlider' })
+import { useRouter } from 'vue-router'
+
+const router = useRouter()
 const list = [
   { mdi: 'mdi-arrow-up-bold', tips: '返回顶部' },
   { mdi: 'mdi-qrcode', tips: '微信公众号', showImg: 'https://minio.citupro.com/dev/static/mendunerCode.jpg' },
-  { mdi: 'mdi-face-agent', tips: '客服' }
+  { mdi: 'mdi-bell-outline', tips: '消息', path: '/recruit/enterprise/communication' }
 ]
 
-const handleClick = (index) => {
+const handleClick = (item, index) => {
   // 回到顶部
   if (index === 0) window.scrollTo({ top: 0, behavior: 'smooth' })
+  if (item.path) router.push(item.path)
 }
 </script>
 

+ 2 - 1
src/layout/personal/navBar.vue

@@ -42,7 +42,7 @@
               <template v-slot:activator="{ props }">
                 <div class="d-flex ml-3 align-center cursor-pointer" v-bind="props" @click="handleToPersonalCenter">
                   <v-avatar>
-                    <v-img alt="John" :src="baseInfo?.avatar || 'https://minio.citupro.com/dev/menduner/7.png'"></v-img>
+                    <v-img alt="John" :src="getUserAvatar(baseInfo?.avatar, baseInfo?.sex)"></v-img>
                   </v-avatar>
                   <div class="ml-3">{{ baseInfo?.name || $t('sys.tourist') }}</div>
                 </div>
@@ -119,6 +119,7 @@ import CtDialog from '@/components/CtDialog'
 import { useRouter } from 'vue-router'; const router = useRouter()
 import { getUserBindEnterpriseList, getUserRegisterEnterpriseApply } from '@/api/personal/user'
 import MessageNotification from '../message.vue'
+import { getUserAvatar } from '@/utils/avatar'
 
 // import { useIMStore } from '@/store/im'
 defineOptions({ name: 'personal-navbar' })

+ 1 - 1
src/layout/personal/slider.vue

@@ -23,7 +23,7 @@ const router = useRouter()
 const list = [
   { mdi: 'mdi-arrow-up-bold', tips: '返回顶部' },
   { mdi: 'mdi-qrcode', tips: '微信公众号', showImg: 'https://minio.citupro.com/dev/static/mendunerCode.jpg' },
-  { mdi: 'mdi-face-agent', tips: '客服' },
+  { mdi: 'mdi-bell-outline', tips: '消息', path: '/recruit/personal/message' },
   { mdi: 'mdi-list-box-outline', tips: '在线简历', path: '/recruit/personal/resume' }
 ]
 

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

@@ -127,7 +127,7 @@ const enterprise = [
     component: Layout,
     name: 'interview',
     meta: {
-      title: '面试',
+      title: '面试管理',
       enName: 'interview',
       icon: 'mdi-account-multiple-check'
     },

+ 10 - 0
src/utils/avatar.js

@@ -0,0 +1,10 @@
+const male = 'https://minio.citupro.com/dev/menduner/11.png'
+const female = 'https://minio.citupro.com/dev/menduner/7.png'
+
+// 根据性别返回默认头像
+export const getUserAvatar = (avatar, sex) => {
+  if (avatar) return avatar
+  if (!sex) return female
+  if (sex === '1') return male
+  else return female
+}

+ 1 - 1
src/utils/eventList.js

@@ -29,7 +29,7 @@ export const updateEventList = (type) => {
       // 更新账户信息
       if (type) await store.getUserAccountInfo()
       else await store.getEnterpriseUserAccountInfo()
-    }, 300000)
+    }, 200000)
 
   } else {
     clearInterval(timer.value)

+ 13 - 0
src/utils/position.js

@@ -70,6 +70,19 @@ export const dealDictObjData = (res, obj) => {
   return res
 }
 
+// 获取单个字典对应的数值
+export const getDictValueWithLabel = (dict, value, valueKey = 'value', labelKey = 'label') => {
+  let result = ''
+  getDict(dict).then(({ data }) => {
+    if (!data || !data.length) return
+    const obj = data.find(e => e[valueKey] === value)
+    console.log(obj, 'obj')
+    if (!obj) return
+    result = obj[labelKey]
+  })
+  return result
+}
+
 // 计算众聘佣金
 let data
 const list = ['headhuntRate', 'recommendRate', 'cvRate'] // 平台、推荐人、投递人

+ 1 - 1
src/views/integral/pointsManagement/index.vue

@@ -1,7 +1,7 @@
 <template>
   <div :class="{'default-width': !type}">
     <div class="pa-3 mb-2 white-bgc">
-      <integralShow :showMall="true" :taskCenter="!type" :isEnterprise="type"></integralShow>
+      <integralShow :showMall="true" :taskCenter="!type" :isEnterprise="type === 0 ? false : true"></integralShow>
     </div>
     <div class="mt-3 white-bgc pa-3 pt-3">
       <v-tabs v-model="tab" align-tabs="start" color="primary" bg-color="#f7f8fa" @update:model-value="handleChangeTab">

+ 28 - 25
src/views/publicRecruitment/myRecommendation.vue

@@ -12,14 +12,14 @@
         </div>
         <div class="topTip">推荐好友入职得赏金</div>
         <!-- 数据 -->
-        <TablePage :items="items"></TablePage>
+        <!-- <TablePage :items="items"></TablePage>
         <CtPagination
           v-if="total > 0"
           :total="total"
           :page="query.pageNo"
           :limit="query.pageSize"
           @handleChange="handleChangePage"
-        ></CtPagination>
+        ></CtPagination> -->
       </div>
     </div>
     <!-- 滚动区域 -->
@@ -34,8 +34,11 @@ defineOptions({name: 'defineOptions-name'})
 import { ref } from 'vue'
 import { getDict } from '@/hooks/web/useDictionaries'
 import { useUserStore } from '@/store/user'
-import { getHireJobCvRelCount, getHireJobCvRelPage } from '@/api/publicRecruitment'
-import TablePage from './components/table.vue'
+import { 
+  getHireJobCvRelCount, 
+  // getHireJobCvRelPage
+} from '@/api/publicRecruitment'
+// import TablePage from './components/table.vue'
 import bountyDisplay from './components/bountyDisplay.vue'
 
 const active = ref(0)
@@ -60,18 +63,18 @@ const getData = async () => {
 }
 
 // 列表
-const items = ref([])
-const total = ref(0)
-const query = ref({
-  pageSize: 10,
-  pageNo: 1,
-  status: null
-})
-const getTableList = async () => {
-  const res = await getHireJobCvRelPage(query.value)
-  items.value = res.list
-  total.value = res.total
-}
+// const items = ref([])
+// const total = ref(0)
+// const query = ref({
+//   pageSize: 10,
+//   pageNo: 1,
+//   status: null
+// })
+// const getTableList = async () => {
+//   const res = await getHireJobCvRelPage(query.value)
+//   items.value = res.list
+//   total.value = res.total
+// }
 
 // 状态
 const getStatusData = () => {
@@ -80,25 +83,25 @@ const getStatusData = () => {
     statisticsList.value = data.map(e => {
       return { ...e, count: 0 }
     })
-    query.value.status = data[0].value
+    // query.value.status = data[0].value
     getData()
-    getTableList()
+    // getTableList()
   })
 }
 getStatusData()
 
 // 分页
-const handleChangePage = (e) => {
-  query.value.pageNo = e
-  getTableList()
-}
+// const handleChangePage = (e) => {
+//   query.value.pageNo = e
+//   getTableList()
+// }
 
 // 钻取
 const handleStatisticsItem = (item, index) => {
   active.value = index
-  query.value.pageNo = 1
-  query.value.status = item.value
-  getTableList()
+  // query.value.pageNo = 1
+  // query.value.status = item.value
+  // getTableList()
 }
 </script>
 

+ 156 - 118
src/views/recruit/components/message/components/chatting.vue

@@ -22,26 +22,34 @@
           </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="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 class="ml-3">{{ 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="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="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> -->
+        <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-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">
@@ -49,73 +57,123 @@
         </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 && val.from_uid !== IM.uid"> -->
+          <template v-if="val.payload.type === 102">
+            <v-card
+              color="teal"
+              variant="tonal"
+              class="mx-auto"
+              width="400"
+              min-height="150"
+              :elevation="3"
+            >
+              <div class="pa-3">
+                <div class="text-h6"> {{ val.payload.content.positionInfo.name }}</div>
+                <div class="text-subtitle-2">薪酬待遇: {{ 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>
           <div :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 : info.avatar"
+                  :src="val.from_uid === IM.uid ? mAvatar : getUserAvatar(info.avatar, info.sex)"
                   :width="40"
                   height="40"
                   rounded
                 ></v-img>
               </v-avatar>
             </div>
-            <div v-if="val.payload.type === 101">
-              <v-chip
-                class="ma-2"
-                color="teal"
-                label
-              >
-              发起了面试邀请
-              <v-icon icon="mdi-email-newsletter" end></v-icon>
+            <!-- 显示沟通职位 -->
+            <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 class="message-text" :class="{ active: val.from_uid === IM.uid}">
               {{ val.payload.content }}
             </div>
           </div>
-          <div v-if="val.payload.type === 101 && val.from_uid !== IM.uid" class="d-flex justify-center">
+          <!-- 插入个人-面试职位邀请:同意、拒绝 -->
+          <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="text-h6 mb-1">
-                    {{ val.payload.content.jobName }}
+                  <div class=" d-flex justify-space-between">
+                    <div class="text-h6 mb-1">
+                      {{ val.payload.content.positionInfo.data.name }}
+                    </div>
+                    <div>
+                      {{ val.payload.content.positionInfo.data.payFrom }} - 
+                      {{ val.payload.content.positionInfo.data.payTo }}
+                    </div>
                   </div>
-                  
-                  <!-- <div class="text-caption"></div> -->
-                  <div class="text-caption">联系电话: {{ val.payload.content.invitePhone }} 面试时间: {{ val.payload.content.time }}</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>
-                <v-btn>
-                  同意
-                </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">
-      <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>
+      <slot name="tools"></slot>
     </div>
     <div class="bottom-info">
       <v-divider></v-divider>
@@ -126,21 +184,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>
@@ -151,13 +204,17 @@ defineOptions({ name: 'message-chatting'})
 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 { getDictValueWithLabel } from '@/utils/position'
+import { getUserAvatar } from '@/utils/avatar'
 
 import { useUserStore } from '@/store/user'
 const isEnterprise = inject('isEnterprise')
 
-const emits = defineEmits(['handleInquire', 'handleInvite', 'handleMore', 'handleSend'])
+const emits = defineEmits(['handleMore', 'handleSend', 'handleAgree', 'handleRefuse'])
 
-const props = defineProps({
+defineProps({
   items: {
     type: Array,
     default: () => []
@@ -173,42 +230,26 @@ const props = defineProps({
   hasMore: {
     type: Boolean,
     default: false
+  },
+  interview: {
+    type: Array,
+    default: () => []
   }
 })
 
+const router = useRouter()
 const overlay = ref(false)
 const IM = useIMStore()
 const userStore = useUserStore()
 const loading = ref(false)
 
-const mAvatar = userStore.baseInfo?.avatar || 'https://minio.citupro.com/dev/menduner/7.png'
+const mAvatar = getUserAvatar(userStore.baseInfo?.avatar, userStore.baseInfo?.sex)
 
 const chatRef = ref()
 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
@@ -219,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()
@@ -233,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) {
@@ -273,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
-    // })
   }
 }
 
@@ -305,6 +316,24 @@ const handleKeyDown = (event) => {
 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 } })
+}
+
 defineExpose({
   reset,
   changeLoading,
@@ -380,6 +409,7 @@ defineExpose({
     margin: 8px 0;
     position: relative;
     .message-text {
+      overflow-wrap: break-word;
       background-color: #f0f2f5;
       border-radius: 6px;
       max-width: 85%;
@@ -418,6 +448,14 @@ input {
     border: none;
   }
 }
+.tipsText {
+  color: var(--color-999);
+  font-size: 12px;
+  cursor: pointer;
+  &:hover {
+    color: var(--v-primary-base);
+  }
+}
 
 /* 滚动条样式 */
 ::-webkit-scrollbar {

+ 139 - 35
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
@@ -40,14 +39,14 @@
               color="primary"
               class="mb-2"
               :active="val.channel.channelID === info?.channel?.channelID"
-              :title="val.userInfoVo ? val.userInfoVo.userInfoResp.name : '系统消息'"
+              :title="val.userInfoVo ? (val.userInfoVo.userInfoResp.name ? val.userInfoVo.userInfoResp.name : '游客') : '系统消息'"
               :subtitle="timesTampChange(+val.timestamp.padEnd(13, '0'))"
             >
               <template v-slot:subtitle="{ subtitle }">
                 <div class="mt-2">{{ subtitle }}</div>
               </template>
               <template v-slot:prepend>
-                <v-avatar :image="val.userInfoVo ? val.userInfoVo.userInfoResp.avatar : 'https://minio.citupro.com/dev/menduner/7.png'"></v-avatar>
+                <v-avatar :image="getUserAvatar(val?.userInfoVo?.userInfoResp?.avatar, val?.userInfoVo?.userInfoResp?.sex)"></v-avatar>
               </template>
               <template v-slot:append>
                 <v-badge
@@ -76,32 +75,63 @@
         ref="chatRef"
         :items="messageItems"
         :info="info"
+        :interview="interview"
         :has-more="hasMore"
         @handleSend="handleUpdate"
         @handleMore="handleGetMore"
-        @handleInquire="handleInquire"
-        @handleInvite="handleInvite"
-      ></Chatting>
+        @handleAgree="handleAgree"
+        @handleRefuse="handleRefuse"
+      >
+        <template #tools>
+          <v-btn
+            v-for="tool in tools"
+            :key="tool.name"
+            size="small"
+            :prepend-icon="tool.icon"
+            class="mr-3"
+            :color="tool.color"
+            @click="tool.handle(tool)"
+          >
+            {{ tool.name }}
+          </v-btn>
+        </template>
+      </Chatting>
     </div>
   </div>
+
+  <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>
 
 <script setup>
 defineOptions({ name: 'personal-message-index'})
+import InvitePage from '@/views/recruit/enterprise/interview/components/invite'
 import { timesTampChange } from '@/utils/date'
 import { ref, inject, watch,onMounted, nextTick } from 'vue'
 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 Snackbar from '@/plugins/snackbar'
+import { getUserAvatar } from '@/utils/avatar'
 
+import { getJobAdvertised } from '@/api/enterprise'
+import { dealDictArrayData } from '@/utils/position'
+import { saveInterviewInvite } from '@/api/recruit/enterprise/interview'
+import { useI18n } from '@/hooks/web/useI18n'
+import { userInterviewInviteReject, userInterviewInviteConsent } from '@/api/recruit/personal/personalCenter'
+import Confirm from '@/plugins/confirm'
+
+const { t } = useI18n()
 const chatRef = ref()
 
 const IM = useIMStore()
+// 自己的信息
 const { baseInfo } = useUserStore()
 
 const isEnterprise = inject('isEnterprise')
@@ -113,8 +143,14 @@ const messageItems = ref([])
 const pageSize = ref(1)
 const hasMore = ref(false)
 
+const showInvite = ref(false)
+
+const positionList = ref([])
 
 const showDelete = ref(false)
+const itemData = ref({})
+
+const inviteRef = ref()
 
 if (!IM) {
   console.log('IM is disconnected')
@@ -158,6 +194,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,
@@ -168,6 +211,7 @@ watch(
       // 更新
       const { list } = await getMoreMessages(1, channelItem.value)
       messageItems.value = list.value
+      if (!isEnterprise) getInterviewInviteList()
       chatRef.value.scrollBottom()
     }
   },
@@ -179,16 +223,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({})
 
@@ -196,9 +230,15 @@ const handleUpdate = (val) => {
   send(val.value, channelItem.value)
 }
 
-// const handleSearch = () => {
-//   console.log(searchInputVal.value, 'search')
-// }
+
+const enterpriseTools = ref([
+  { name: '面试邀约', icon: 'mdi-email', color:"primary", loading: false, handle: handleInvite }
+])
+
+const userTools = ref([
+])
+
+const tools = isEnterprise ? enterpriseTools.value : userTools.value
 
 async function handleChange (items) {
   // console.log([...items])
@@ -220,6 +260,7 @@ async function handleChange (items) {
     await resetUnread(channel.value, baseInfo?.enterpriseId)
     await updateConversation()
     updateUnreadCount()
+    if (!isEnterprise) getInterviewInviteList()
   } catch (error) {
     messageItems.value = []
   } finally {
@@ -249,23 +290,84 @@ const handleDelete = async ({ channel }) => {
 // 没有企业ID则enterpriseId为undefined
 // 发送消息体 { text, type: 2 }
 // 面试邀约
-const handleInvite = (userId, enterpriseId) => {
-  const query = { userId, enterpriseId }
-  // IM 信息发送
-  const msg = {
-    jobName: '可控核聚变工程师',
-    time: '2024-07-06 15:00:00',
-    invitePhone: '138001380000',
-    address: '北京市海淀区中关村',
-    remark: '备注'
+async function handleInvite (item) {
+  item.loading = true
+  positionList.value = []
+  try {
+    const data = await getJobAdvertised({ hire: false })
+    if (!data.length) return
+    const list = dealDictArrayData([], data)
+    positionList.value = list.map(e => {
+      return {
+        label: `${e.name}${e.areaName ? '_' + e.areaName : ''} ${e.payFrom}-${e.payTo}/${e.payName}`,
+        value: e.id,
+        data: e
+      }
+    })
+    // itemData.value = {
+    //   userId: '',
+    //   jobId: ''
+    // }
+    showInvite.value = true
+    // send(JSON.stringify(msg), channelItem.value, 101)
+    // console.log(query)
+  } catch (error) {
+    console.log(error)
+  } finally {
+    item.loading = false
   }
-  send(JSON.stringify(msg), channelItem.value, 101)
-  console.log(query)
 }
-// 查看面试
-const handleInquire = (userId, enterpriseId) => {
-  const query = { userId, enterpriseId }
-  console.log(query)
+
+const handleSubmit = async () => {
+  const { valid } = await inviteRef.value.CtFormRef.formRef.validate()
+  if (!valid) {
+    return
+  }
+  const query = inviteRef.value.getQuery()
+  if (!query.time) {
+    Snackbar.error('时间不能为空')
+    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
+  Snackbar.success(t('common.operationSuccessful'))
+  send(JSON.stringify(query), channelItem.value, 101)
+  showInvite.value = false
+
+}
+
+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>
@@ -275,6 +377,7 @@ const handleInquire = (userId, enterpriseId) => {
   display: flex;
   &-left {
     position: relative;
+    flex-shrink: 0;
     height: 100%;;
     width: 360px;
     background-color: #fff;
@@ -326,6 +429,7 @@ const handleInquire = (userId, enterpriseId) => {
   &-right {
     height: 100%;
     flex: 1;
+    width: 0;
     position: relative;
     background-color: #fff;
     border-radius: 8px;

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

@@ -17,7 +17,7 @@
             offset-y="6"
             :color="badgeColor(item)"
             :icon="badgeIcon(item)">
-            <v-avatar size="40" :image="item.person.avatar || 'https://minio.citupro.com/dev/menduner/7.png'"></v-avatar>
+            <v-avatar size="40" :image="getUserAvatar(item.person.avatar, item.person.sex)"></v-avatar>
           </v-badge>
           <span class="defaultLink ml-3">{{ item?.person?.name }}</span>
         </div>
@@ -55,6 +55,7 @@ import { useI18n } from '@/hooks/web/useI18n'
 import { useUserStore } from '@/store/user'
 import Snackbar from '@/plugins/snackbar'
 import InvitePage from './invite.vue'
+import { getUserAvatar } from '@/utils/avatar'
 // import PublicPage from './public.vue'
 
 const { t } = useI18n()
@@ -74,7 +75,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 },
@@ -86,7 +87,7 @@ const headers = ref([
   { title: '状态', key: 'status', sortable: false },
   { title: '操作', value: 'actions', sortable: false }
 ])
-const unfit = { title: '类型', key: 'unfitType', sortable: false, value: item => item.type === '0' ? '简历不适' : '面试不适' }
+const unfit = { title: '类型', key: 'unfitType', sortable: false, value: item => item.type === '0' ? '简历不适' : '面试不适' }
 const delivery = { title: '类型', key: 'deliveryType', sortable: false, value: item => item.type === '0' ? '普通职位' : '赏金职位' }
 
 const list = [0, 4]

+ 2 - 1
src/views/recruit/enterprise/informationSetting/index.vue

@@ -4,7 +4,7 @@
       <template #avatar="{ item }">
         <div style="color: #7a7a7a;">头像</div>
         <div class="avatarsBox" @mouseover="showIcon = true" @mouseleave="showIcon = false">
-          <v-avatar class="elevation-5" size=80 :image="item.value || 'https://minio.citupro.com/dev/menduner/7.png'"></v-avatar>
+          <v-avatar class="elevation-5" size=80 :image="getUserAvatar(item.value, baseInfo?.sex)"></v-avatar>
           <div v-show="showIcon" @click="openFileInput" v-bind="$attrs" class="mdi mdi-camera-outline">
             <input
               type="file"
@@ -31,6 +31,7 @@ import { useI18n } from '@/hooks/web/useI18n'
 import { getDict } from '@/hooks/web/useDictionaries'
 import { useUserStore } from '@/store/user'
 import Snackbar from '@/plugins/snackbar'
+import { getUserAvatar } from '@/utils/avatar'
 
 const { t } = useI18n()
 const userStore = useUserStore()

+ 2 - 1
src/views/recruit/enterprise/interview/components/item.vue

@@ -2,7 +2,7 @@
   <div class="listItem d-flex align-center pa-3 mb-3" v-for="(item, index) in items" :key="'item_' + index">
     <div class="d-flex align-center">
       <div class="mr-5 font-size-16" style="color: orange; width: 96px;">{{ timesTampChange(item.time, 'Y-M-D h:m') }}</div>
-      <v-avatar class="mr-2" size=40 :image="item?.person?.avatar || 'https://minio.citupro.com/dev/menduner/7.png'"></v-avatar>
+      <v-avatar class="mr-2" size=40 :image="getUserAvatar(item?.person?.avatar, item?.person?.sex)"></v-avatar>
       <div class="d-flex flex-column mr-3" style="width: 110px;">
         <span class="ellipsis mb-1">{{ item?.person?.name }}</span>
         <span class="ellipsis" style="color: var(--color-999);">{{ item?.job?.name }}</span>
@@ -85,6 +85,7 @@ import { completedInterviewInvite, cancelInterviewInvite, saveInterviewInvite, n
 import InvitePage from './invite.vue'
 import Snackbar from '@/plugins/snackbar'
 import Confirm from '@/plugins/confirm'
+import { getUserAvatar } from '@/utils/avatar'
 
 defineProps({
   items: Array,

+ 7 - 0
src/views/recruit/enterprise/memberCenter/myPoints/index.vue

@@ -5,6 +5,13 @@
 <script setup>
 defineOptions({ name: 'myWallet'})
 import IntegralPage from '@/views/integral/pointsManagement'
+import { useUserStore } from '@/store/user'
+
+const store = useUserStore()
+const updateAccountInfo = async () => {
+  await store.getEnterpriseUserAccountInfo()
+}
+updateAccountInfo()
 
 </script>
 

+ 5 - 1
src/views/recruit/enterprise/positionManagement/index.vue

@@ -40,6 +40,9 @@ import { useRouter } from 'vue-router'; const router = useRouter()
 import { getJobAdvertisedList } from '@/api/position'
 import { dealDictArrayData } from '@/utils/position'
 import { useI18n } from '@/hooks/web/useI18n'
+import { useUserStore } from '@/store/user'
+
+const store = useUserStore()
 
 const { t } = useI18n()
 const total = ref(0)
@@ -72,8 +75,9 @@ const textItem = ref({
   appendInnerIcon: 'mdi-magnify'
 })
 
-const handleAdd = () => {
+const handleAdd = async () => {
   router.push('/recruit/enterprise/position/add')
+  await store.getEnterpriseUserAccountInfo()
 }
 
 

+ 11 - 6
src/views/recruit/personal/PersonalCenter/components/interview/index.vue

@@ -2,13 +2,10 @@
   <div>
     <v-divider></v-divider>
       <v-tabs class="mb-3" v-model="query.status" align-tabs="start" color="primary" bg-color="#fff" @update:model-value="handleUpdate">
-        <v-tab :value="0">待接受</v-tab>
-        <v-tab :value="1">待面试</v-tab>
-        <v-tab :value="3">已完成</v-tab>
-        <v-tab :value="98">已拒绝</v-tab>
+        <v-tab v-for="val in tabList" :key="val.value" :value="val.value">{{ val.label }}</v-tab>
       </v-tabs>
       <div v-if="items.length">
-        <ItemPage :items="items" @refresh="getData"></ItemPage>
+        <ItemPage :items="items" @refresh="getData" :tab="query.status"></ItemPage>
         <CtPagination
           v-if="total > 0"
           :total="total"
@@ -27,15 +24,23 @@ defineOptions({ name: 'interview-index'})
 import { ref } from 'vue'
 import { getUserInterviewInvitePage } from '@/api/recruit/personal/personalCenter'
 import { dealDictObjData } from '@/utils/position'
+import { getDict } from '@/hooks/web/useDictionaries'
 import ItemPage from './item.vue'
 
 const query = ref({
   pageNo: 1,
   pageSize: 10,
-  status: 0
+  status: '0'
 })
 const total = ref(0)
 const items = ref([])
+const tabList = ref([])
+
+const getTabList = async () => {
+  const { data } = await getDict('menduner_interview_invite_status')
+  tabList.value = data
+}
+getTabList()
 
 const getData = async () => {
   const { list, total: number } = await getUserInterviewInvitePage(query.value)

+ 6 - 1
src/views/recruit/personal/PersonalCenter/components/interview/item.vue

@@ -6,7 +6,7 @@
           <v-btn class="ml-3" color="error" size="small" @click="handleRefuse(val)">拒绝</v-btn>
         </div>
         <div class="img-box">
-          <v-avatar :image="val.contact.avatar || 'https://minio.citupro.com/dev/menduner/7.png'" size="x-small"></v-avatar>
+          <v-avatar :image="getUserAvatar(val.contact.avatar, val.contact.sex)" size="x-small"></v-avatar>
           <span class="name">
             <span class="mx-3">{{ val.contact.name }}</span>
             <span class="gray">{{ val.contact.postNameCn }}</span>
@@ -50,6 +50,7 @@ import { timesTampChange } from '@/utils/date'
 import { userInterviewInviteReject, userInterviewInviteConsent } from '@/api/recruit/personal/personalCenter'
 import Snackbar from '@/plugins/snackbar'
 import Confirm from '@/plugins/confirm'
+import { getUserAvatar } from '@/utils/avatar'
 
 const { t } = useI18n()
 const emits = defineEmits(['refresh'])
@@ -57,6 +58,10 @@ const props = defineProps({
   items: {
     type: Array,
     default: () => []
+  },
+  tab: {
+    type: String,
+    default: '0'
   }
 })
 

+ 2 - 1
src/views/recruit/personal/PersonalCenter/components/interviewSchedule.vue

@@ -40,7 +40,7 @@
             <span class="c-base">{{ val?.job?.payFrom || '--' }}-{{ val?.job?.payTo || '--' }}/{{ val?.job?.payName || '--' }}</span>
           </div>
           <div class="img-box">
-            <v-avatar size="small" :image="val.contact.avatar || 'https://minio.citupro.com/dev/menduner/7.png'"></v-avatar>
+            <v-avatar size="small" :image="getUserAvatar(val.contact.avatar, val.contact.sex)"></v-avatar>
             <span class="name">
               <span class="mx-3">{{ val.contact.name }}</span>
               <span class="septal-line"></span>
@@ -59,6 +59,7 @@ import { getDict } from '@/hooks/web/useDictionaries'
 import { getText } from '@/utils/getText'
 import { timesTampChange } from '@/utils/date'
 import { ref } from 'vue'
+import { getUserAvatar } from '@/utils/avatar'
 defineOptions({name: 'PersonalCenter-interviewSchedule'})
 const emit = defineEmits(['handleMore'])
 const props = defineProps({

+ 2 - 1
src/views/recruit/personal/PersonalCenter/components/seenMe.vue

@@ -5,7 +5,7 @@
         <div class="position-and-company">
           <div class="position">
             <div class="float-left">
-              <v-img :src="item?.contact.avatar || 'https://minio.citupro.com/dev/menduner/company-avatar.png'" :width="40" style="height: 40px;border-radius: 4px;"/>
+              <v-img :src="getUserAvatar(item?.contact.avatar, item?.contact.sex)" :width="40" style="height: 40px;border-radius: 4px;"/>
             </div>
             <div class="company-info">
               <h3 :class="{'default-active': item.active }" class="title1">{{ item.contact.name }}</h3>
@@ -45,6 +45,7 @@ import { getInterestedMePage } from '@/api/recruit/personal/personalCenter'
 import { dealDictObjData } from '@/utils/position'
 import { timesTampChange } from '@/utils/date'
 import Empty from '@/components/Empty'
+import { getUserAvatar } from '@/utils/avatar'
 
 const total = ref(0)
 const items = ref([])

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

@@ -7,7 +7,7 @@
         offset-y="50" 
         :color="baseInfo?.sex ? (baseInfo?.sex === '1' ? '#1867c0' : 'error') : 'error'" 
         :icon="baseInfo?.sex ? (baseInfo?.sex === '1' ? 'mdi-gender-male' : 'mdi-gender-female') : 'mdi-gender-female'">
-        <v-avatar size="x-large" :image="baseInfo?.avatar || 'https://minio.citupro.com/dev/menduner/7.png'"></v-avatar>
+        <v-avatar size="x-large" :image="getUserAvatar(baseInfo?.avatar, baseInfo?.sex)"></v-avatar>
       </v-badge>
       <div class="ml-5 content">
         <div class="username">
@@ -69,15 +69,16 @@ import { useUserStore } from '@/store/user'
 import { updateJobStatus } from '@/api/recruit/personal/resume'
 import { useI18n } from '@/hooks/web/useI18n'
 import Snackbar from '@/plugins/snackbar'
-import communication from '../components/communication.vue'
+// import communication from '../components/communication.vue'
 import delivery from '../components/delivery.vue'
 import interview from '../components/interview/index.vue'
 import interested from '../components/interested.vue'
 import seenMe from '../components/seenMe.vue'
+import { getUserAvatar } from '@/utils/avatar'
 
 const { t } = useI18n()
 const list = [
-  { title: t('position.throughCommunication'), path: communication },
+  // { title: t('position.throughCommunication'), path: communication },
   { title: t('position.delivered'), path: delivery },
   { title: t('position.interview'), path: interview },
   { title: t('position.interested'), path: interested },
@@ -96,7 +97,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([])

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

@@ -129,6 +129,7 @@ const openFileInput = () => {
 // 上传附件
 const typeList = ['pdf', 'doc', 'docx']
 const handleUploadFile = async (e) => {
+  if (!e.target.files.length) return
   const file = e.target.files[0]
   const size = file.size
   if (size / (1024*1024) > 10) {

+ 7 - 0
src/views/recruit/personal/PersonalCenter/index.vue

@@ -14,6 +14,13 @@ defineOptions({ name: 'personal-center'})
 
 import LeftPage from './dynamic/left.vue'
 import RightPage from './dynamic/right.vue'
+import { useUserStore } from '@/store/user'
+
+const store = useUserStore()
+const updateAccountInfo = async () => {
+  await store.getUserAccountInfo()
+}
+updateAccountInfo()
 </script>
 
 <style scoped lang="scss">

+ 1 - 0
src/views/recruit/personal/company/index.vue

@@ -61,6 +61,7 @@ const dealRouteQuery = (data) => {
 
 // 搜索
 const handleSearch = async (val, key) => {
+  if (!val) return
   query.value.pageNo = 1
   // val为-1时选择的是不限或者全国, 此时选中的字段不传
   if (val === -1 || val[0] === -1) delete query.value[key]

+ 8 - 1
src/views/recruit/personal/home/index.vue

@@ -14,7 +14,7 @@
     </div>
   </div>
   <!-- 快速填写简易人才信息-弹窗 -->
-  <simplePage v-if="showSimplePage" :closeable="true"></simplePage>
+  <simplePage v-if="showSimplePage" :closeable="true" closeText="跳过"></simplePage>
 </template>
 
 <script setup>
@@ -30,6 +30,13 @@ import PopularEnterprises from './components/popularEnterprises.vue'
 import advertisementPage from './components/advertisement.vue'
 import { useRouter } from 'vue-router'
 import { nextTick, ref } from 'vue'
+import { useUserStore } from '@/store/user'
+
+const store = useUserStore()
+const updateBaseInfo = async () => {
+  await store.getUserBaseInfos()
+}
+updateBaseInfo()
 
 const router = useRouter()
 const simple = localStorage.getItem('simpleCompleteDialogHaveBeenShow')

+ 7 - 0
src/views/recruit/personal/myWallet/index.vue

@@ -5,6 +5,13 @@
 <script setup>
 defineOptions({ name: 'myWallet'})
 import IntegralPage from '@/views/integral/pointsManagement'
+import { useUserStore } from '@/store/user'
+
+const store = useUserStore()
+const updateAccountInfo = async () => {
+  await store.getUserAccountInfo()
+}
+updateAccountInfo()
 
 </script>
 

+ 48 - 17
src/views/recruit/personal/position/components/details.vue

@@ -10,8 +10,8 @@
         <span class="refresh-time">{{ timesTampChange(info.updateTime) }} {{ $t('common.refresh') }} <v-icon color="warning" size="20">mdi-alert-outline</v-icon></span>
       </div>
       <div class="banner-tags mt-4">
-        <span v-for="k in desc" :key="k.mdi" class="mr-10">
-          <span v-if="positionInfo[k.value]">
+        <span v-for="k in desc" :key="k.mdi">
+          <span v-if="positionInfo[k.value]" class="mr-10">
             <v-icon color="var(--color-666)" size="20">{{ k.mdi }}</v-icon>
             <span class="ml-1">{{ positionInfo[k.value] }}</span>
           </span>
@@ -70,7 +70,7 @@
             <v-divider class="my-3"></v-divider>
             <div class="contact" v-if="Object.keys(info).length">
               <div class="float-left d-flex align-center">
-                <v-img :src="info.contact.avatar || 'https://minio.citupro.com/dev/menduner/7.png'" :width="45" style="height: 45px;"></v-img>
+                <v-img :src="getUserAvatar(info.contact.avatar, info.contact.sex)" :width="45" style="height: 45px;"></v-img>
                 <div class="ml-2">
                   <div class="contact-name">{{ info.contact.name }}</div>
                   <div class="contact-info">
@@ -112,8 +112,9 @@
       </div>
     </div>
 
-    <!-- 弹窗提示去上传简历 -->
-    <promptToUpload v-model="dialog" @handleToUpload="handleToUpload"></promptToUpload>
+    <!-- 简历上传 -->
+    <input type="file" ref="fileInput" accept=".pdf, .doc, .docx" style="display: none;" @change="handleUploadFile"/>
+
     <!-- 选择简历 -->
     <selectResumeDialog v-model="showResume" :list="resumeList" @submit="handleSubmit" @close="handleClose"></selectResumeDialog>
     <!-- 复制分享链接 -->
@@ -149,6 +150,7 @@ defineOptions({ name: 'position-details' })
 import { computed, ref } from 'vue'
 import { useRouter } from 'vue-router'
 import { timesTampChange } from '@/utils/date'
+import { uploadFile } from '@/api/common'
 import { getPersonResumeCv } from '@/api/recruit/personal/resume'
 import { useI18n } from '@/hooks/web/useI18n'
 import { getPositionDetails, getSimilarPosition, getJobFavoriteCheck, getPersonJobFavorite, getPersonJobUnfavorite, jobCvRelCheckSend, jobCvRelSend } from '@/api/position'
@@ -157,10 +159,10 @@ import similarPositions from '@/components/Position/similarPositions.vue'
 import EnterpriseInfo from '@/components/Enterprise/info.vue'
 import Snackbar from '@/plugins/snackbar'
 import Dialog from '@/components/CtDialog'
-import promptToUpload from './jobDetails/promptToUpload'
 import selectResumeDialog from './jobDetails/selectResumeDialog'
 import { getToken } from '@/utils/auth'
 import { prologue } from '@/hooks/web/useIM'
+import { getUserAvatar } from '@/utils/avatar'
 
 const { t } = useI18n()
 const router = useRouter()
@@ -181,7 +183,6 @@ const info = ref({})
 const positionInfo = ref({})
 const getPositionDetail = async () => {
   const data = await getPositionDetails({ id })
-  console.log('daaa', data)
   info.value = data
   positionInfo.value = { ...dealDictObjData({}, info.value), ...info.value }
   getSimilarPositionList()
@@ -207,7 +208,6 @@ const getCollectionStatus = async () => {
   if (!getToken()) return isCollection.value = false
   const data = await getJobFavoriteCheck({ jobId: id })
   isCollection.value = data
-  console.log('isCollection', data)
 }
 getCollectionStatus()
 
@@ -246,14 +246,37 @@ const handleCollection = async () => {
   await getCollectionStatus()
 }
 
-const dialog = ref(false)
-const showResume = ref(false)
-// 去上传附件
-const handleToUpload = () => {
-  dialog.value = false
-  window.open('/recruit/personal/personalCenter')
+// 投递简历时,若当前用户没有简历列表则弹窗上传简历以及投递
+const typeList = ['pdf', 'doc', 'docx']
+const handleUploadFile = async (e) => {
+  if (!e.target.files.length) return
+  const file = e.target.files[0]
+  const size = file.size
+  if (size / (1024*1024) > 10) {
+    Snackbar.warning(t('common.fileSizeExceed'))
+    return
+  }
+  const arr = file.name.split('.')
+  if (typeList.indexOf(arr[arr.length - 1]) < 0) {
+    Snackbar.warning(t('common.fileFormatIncorrect'))
+    return
+  }
+  const formData = new FormData()
+  formData.append('file', file)
+  const { data } = await uploadFile(formData)
+  if (!data) return
+
+  await jobCvRelSend({ jobId: id, title: arr[0], url: data, type: info.value.hire ? 1 : 0 })
+  setTimeout(() => {
+    Snackbar.success(t('resume.deliverySuccess'))
+    deliveryCheck()
+  }, 3000)
 }
 
+const showResume = ref(false)
+const clicked = ref(false)
+const fileInput = ref()
+
 // 效验是否有投递简历
 const resumeList = ref([])
 const selectResume = ref()
@@ -263,7 +286,11 @@ const handleDelivery = async () => {
   resumeList.value = result
   // 没有上传过简历的先去上传
   if (!result.length) {
-    dialog.value = true
+    Snackbar.warning('您还未上传过简历,请先上传简历')
+    if (clicked.value) return
+    clicked.value = true
+    fileInput.value.click()
+    clicked.value = false
     return
   }
   showResume.value = true
@@ -290,8 +317,12 @@ const handleSubmit = async (val) =>{
 const toDetails = async (info) => {
   const userId = info.contact.userId
   const enterpriseId = info.contact.enterpriseId
-  const text = '您好,我对该职位很感兴趣,希望能有机会与您进一步沟通。'
-  await prologue({userId, enterpriseId, text})
+  const text = '您好,可以进一步了解一下这个职位的详情吗?'
+  const textObj = {
+    text: text,
+    positionInfo: positionInfo.value
+  }
+  await prologue({userId, enterpriseId, text: JSON.stringify(textObj)})
   let url = `/recruit/personal/message?id=${info.id}`
   if (info.contact.enterpriseId) {
     url += `&enterprise=${info.contact.enterpriseId}`

+ 2 - 1
src/views/recruit/personal/remuse/components/basicInfo.vue

@@ -13,7 +13,7 @@
           offset-y="33" 
           :color="baseInfo?.sex ? (baseInfo?.sex === '1' ? '#1867c0' : 'error') : 'error'" 
           :icon="baseInfo?.sex ? (baseInfo?.sex === '1' ? 'mdi-gender-male' : 'mdi-gender-female') : 'mdi-gender-female'">
-          <v-avatar size=80 :image="baseInfo?.avatar || 'https://minio.citupro.com/dev/menduner/7.png'">
+          <v-avatar size=80 :image="getUserAvatar(baseInfo?.avatar, baseInfo?.sex)">
           </v-avatar>
           <div v-show="showIcon" @click="openFileInput" v-bind="$attrs" class="mdi mdi-camera-outline">
             <input
@@ -123,6 +123,7 @@ import { getTimeStamp, timesTampChange } from '@/utils/date'
 import { updatePersonAvatar, saveResumeBasicInfo } from '@/api/recruit/personal/resume'
 import { useUserStore } from '@/store/user'
 import { uploadFile } from '@/api/common'
+import { getUserAvatar } from '@/utils/avatar'
 import { useI18n } from '@/hooks/web/useI18n'
 import { ref } from 'vue';
 defineOptions({name: 'resume-components-basicInfo'})

+ 6 - 1
src/views/recruit/personal/shareJob/sendResume/simple.vue

@@ -3,6 +3,7 @@
   <CtDialog
     :visible="openDialog"
     :widthType="2"
+    :closeText="closeText"
     titleClass="text-h6"
     title="补充基本信息"
     :closeable="props.closeable"
@@ -26,6 +27,10 @@ const props = defineProps({
   closeable: {
     type: Boolean,
     default: true
+  },
+  closeText: {
+    type: String,
+    default: '取消'
   }
 })
 
@@ -62,7 +67,7 @@ const getUserInfoVerify = () => {
 // 查询用户基本信息-失败 
 const getUserInfoFail = () => {
   if (timer.value) clearInterval(timer.value); timer.value = null
-  Snackbar.success(t('login.getUserInfoFailed')+','+t('login.loginAgain'))
+  Snackbar.error(t('login.getUserInfoFailed')+','+t('login.loginAgain'))
 }
 
 const formRef = ref()