Xiao_123 10 hónapja
szülő
commit
2b37caeb2b

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

@@ -205,4 +205,12 @@ export const getUserAccount = async () => {
   return await request.get({
     url: '/app-api/menduner/system/mde-user/get/account'
   })
+}
+
+// 获取聊天秘钥信息
+export const getChatKey = async (data) => {
+  return await request.post({
+    url: 'admin-api/im/user/get',
+    data
+  })
 }

+ 8 - 0
src/api/recruit/enterprise/position/index.js

@@ -0,0 +1,8 @@
+import request from '@/config/axios'
+
+// 众聘比例信息
+export const getPublicRatio = async () => {
+  return await request.get({
+    url: '/admin-api/menduner/system/hire-commission-ratio/get'
+  })
+}

+ 1 - 0
src/components/CtForm/index.vue

@@ -14,6 +14,7 @@
                 v-if="['text', 'password', 'number'].includes(item.type)"
                 v-model="item.value"
                 :item="item"
+                @blur="item.blur"
                 @change="handleChange(item)"
               ></textUI>
               <autocompleteUI

+ 6 - 1
src/components/FormUI/TextInput/index.vue

@@ -29,6 +29,7 @@
       @click:append-inner="appendInnerClick"
       @keyup.enter="handleKeyup"
       @click:clear="handleClear"
+      @blur="handleBlur"
     ></v-text-field>
   </div>
 </template>
@@ -38,7 +39,7 @@ import { defineEmits, ref, watch } from 'vue';
 defineOptions({ name:'FormUI-v-text-field'})
 
 const props = defineProps({item: Object, modelValue: [String, Number]})
-const emit = defineEmits(['update:modelValue', 'change', 'appendClick', 'appendInnerClick', 'enter'])
+const emit = defineEmits(['update:modelValue', 'change', 'appendClick', 'appendInnerClick', 'enter', 'blur'])
 const item = props.item
 const value = ref(props.modelValue)
 const searchDebouncedTime = item?.searchDebouncedTime === 0 ? ref(0) : ref(500)
@@ -72,6 +73,10 @@ const handleKeyup = () => {
   emit('enter', value.value)
 }
 
+const handleBlur = () => {
+  emit('blur', props.item, value.value)
+}
+
 const handleWheel = (event, item) => {
   if (item.type !== 'number') return
   event.preventDefault()

+ 2 - 1
src/layout/index.vue

@@ -4,7 +4,7 @@
     <div class="content">
       <router-view></router-view>
     </div>
-    <Footers class="mt-10"></Footers>
+    <Footers v-if="footerWhiteList.indexOf(router.currentRoute.value.path) === -1" class="mt-10"></Footers>
     <Slider v-if="whiteList.indexOf(router.currentRoute.value.path) === -1" class="slider"></Slider>
   </div>
 </template>
@@ -19,6 +19,7 @@ defineOptions({ name: 'personal-layout-index' })
 
 // 不展示侧边栏名单
 const whiteList = ['/login', '/privacyPolicy', '/userAgreement', '/register']
+const footerWhiteList = ['/recruit/personal/message']
 const router = useRouter()
 
 const sharedState = useSharedState()

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

@@ -84,7 +84,7 @@
           </v-menu>
           <div class="d-flex align-center" v-if="getToken()">
             <span class="cursor-pointer mx-5" @click="router.push({ path: '/recruit/personal/TaskCenter' })">{{ $t('sys.signIn') }}</span>
-            <span class="cursor-pointer">{{ $t('sys.news') }}</span>
+            <span class="cursor-pointer" @click="router.push({ path: '/recruit/personal/message' })">{{ $t('sys.news') }}</span>
           </div>
         </div>
         

+ 17 - 0
src/router/modules/components/recruit/personal.js

@@ -187,5 +187,22 @@ const personal = [
       }
     ]
   },
+  {
+    path: '/recruit/personal/message',
+    component: Layout,
+    name: 'message',
+    meta: {
+      title: '消息'
+    },
+    children: [
+      {
+        path: '/recruit/personal/message',
+        component: () => import('@/views/recruit/personal/message/index'),
+        meta: {
+          title: '消息'
+        }
+      }
+    ]
+  },
 ]
 export default personal

+ 25 - 1
src/views/recruit/enterprise/positionManagement/components/baseInfo.vue

@@ -7,6 +7,11 @@
           众聘岗位规则说明
         </span>
       </template>
+      <template #ratio>
+        <div class="color-666 mb-3">
+          众聘岗位分配比例:平台占比{{ ratio.headhuntRate }}%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 推荐人占比{{ ratio.recommendRate }}%&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 投递人占比{{ ratio.cvRate }}%
+        </div>
+      </template>
       <template #positionId="{ item }">
         <v-menu :close-delay="1" :open-delay="0" v-bind="$attrs">
           <template v-slot:activator="{  props }">
@@ -35,12 +40,14 @@ import { reactive, ref, defineExpose, watch } from 'vue'
 import textUI from '@/components/FormUI/TextInput'
 import jobTypeCard from '@/components/jobTypeCard'
 import RulePage from './rule.vue'
+import { getPublicRatio } from '@/api/recruit/enterprise/position'
 
 const props = defineProps({
   itemData: Object
 })
 
 const show = ref(false)
+const ratio = ref({})
 
 const getValue = (key) => {
   return items.value.options.find(e => e.key === key)
@@ -84,6 +91,10 @@ const handleChangePublic = (val) => {
   })
 }
 
+const handleBlur = (item, val) => {
+  console.log(item, val, 'blur')
+}
+
 const formPageRef = ref()
 let query = reactive({})
 
@@ -143,7 +154,8 @@ const items = ref({
           if (value >= 1) return true
           return '赏金金额不得小于1'
         }
-      ]
+      ],
+      blur: handleBlur
     },
     {
       type: 'number',
@@ -165,6 +177,11 @@ const items = ref({
         }
       ]
     },
+    {
+      slotName: 'ratio',
+      noParam: true,
+      hide: false
+    },
     {
       type: 'text',
       key: 'name',
@@ -233,6 +250,13 @@ const items = ref({
   ]
 })
 
+// 获取众聘分配比例
+const getRatio = async () => {
+  const data = await getPublicRatio()
+  ratio.value = data
+}
+getRatio()
+
 // 编辑回显
 watch(
   () => props.itemData,

+ 252 - 0
src/views/recruit/personal/message/components/chatting.vue

@@ -0,0 +1,252 @@
+<template>
+  <div class="chatting">
+    <div class="top-info">
+      <div class="user-info d-flex align-center">
+        <p class="d-flex align-center float-left">
+          <span class="name">钟女士</span>
+          <span>广州市番禺区洛浦街道12号</span>
+          <span class="septal-line"></span>
+          <span>负责人</span>
+        </p>
+      </div>
+      <div class="position-content">
+        <span class="color-222">无责4k电话客服+朝九晚六+不加班</span>
+        <span class="salary mx-4">4-8k</span>
+        <span class="color-333">广州</span>
+      </div>
+    </div>
+    <div class="chat-record mt-3">
+      <div class="message-box" @scroll="handleScroll" ref="chatRef">
+        <div v-for="(val, i) in items" :key="i" :id="val.id">
+          <div class="time-box">{{ val.time }}</div>
+          <div :class="['message-view_item', val.userId === myUserId ? 'is-self' : 'is-other']">
+            <div style="width: 40px; height: 40px;"><v-img :src="val.avatar" :width="40" height="40" rounded></v-img></div>
+            <div class="message-text">{{ val.text }}</div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="bottom-info">
+      <v-divider></v-divider>
+      <div class="px-5 py-3">
+        <v-text-field  v-model="inputVal" label="请输入消息" color="primary" density="comfortable" variant="plain"></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">发送</v-btn>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+defineOptions({ name: 'message-chatting'})
+import { ref, nextTick, onMounted } from 'vue'
+
+const chatRef = ref()
+const inputVal = ref()
+const pullDowning = ref(false) // 下拉中
+const pulldownFinished = ref(false) // 下拉完成
+const myUserId = 1
+
+const items = ref([
+  { userId: 1, text: '你好', avatar: 'https://minio.citupro.com/dev/menduner/tx1.jpg', time: '11:15', id: 1 },
+  { userId: 2, text: '你好', avatar: 'https://minio.citupro.com/dev/menduner/tx1.jpg', time: '11:15', id: 2 },
+  { userId: 2, text: '你好', avatar: 'https://minio.citupro.com/dev/menduner/tx1.jpg', time: '11:15', id: 3 },
+  { userId: 1, text: '你好', avatar: 'https://minio.citupro.com/dev/menduner/tx1.jpg', time: '11:15', id: 4 },
+  { userId: 1, text: '你好', avatar: 'https://minio.citupro.com/dev/menduner/tx1.jpg', time: '11:15', id: 5 },
+  { userId: 2, text: '你好', avatar: 'https://minio.citupro.com/dev/menduner/tx1.jpg', time: '11:15', id: 6 },
+  { userId: 1, text: '你好', avatar: 'https://minio.citupro.com/dev/menduner/tx1.jpg', time: '11:15', id: 7 },
+  { userId: 1, text: '你好', avatar: 'https://minio.citupro.com/dev/menduner/tx1.jpg', time: '11:15', id: 8 },
+  { userId: 2, text: '你好', avatar: 'https://minio.citupro.com/dev/menduner/tx1.jpg', time: '11:15', id: 9 },
+  { userId: 1, text: '你好', avatar: 'https://minio.citupro.com/dev/menduner/tx1.jpg', time: '11:15', id: 10 },
+  { userId: 2, text: '你好', avatar: 'https://minio.citupro.com/dev/menduner/tx1.jpg', time: '11:15', id: 11 }
+])
+
+// 滚动到底部
+const scrollBottom = () => {
+  const chat = chatRef.value
+  if (chat) {
+    nextTick(function () {
+      chat.scrollTop = chat.scrollHeight
+    })
+  }
+}
+
+onMounted(() => {
+  nextTick(() => {
+    scrollBottom()
+  })
+})
+
+// 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) {
+    // 下拉
+    if (pullDowning.value || pulldownFinished.value) {
+      return
+    }
+    console.log('下拉')
+    pullDowning.value = true
+    // pullDown().then(() => {
+    //   pullDowning.value = false
+    // }).catch(() => {
+    //   pullDowning.value = false
+    // })
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.chatting {
+  position: relative;
+  height: 100%;
+}
+.top-info {
+  height: 104px;
+  .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: 760px;
+    background: linear-gradient(90deg, #f5fcfc, #fcfbfa);
+    .salary {
+      font-size: 20px;
+      font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif;
+      color: var(--v-error-base);
+      line-height: 24px;
+    }
+  }
+}
+.chat-record {
+  
+  padding-bottom: 30px;
+  .message-box {
+    height: 525px;
+    padding: 0 30px 20px;
+    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: center;
+      margin: 8px 0;
+      position: relative;
+      .message-text {
+        background-color: #f0f2f5;
+        border-radius: 6px;
+        max-width: 85%;
+        padding: 10px;
+      }
+    }
+    .is-self {
+      flex-direction: row-reverse;
+      display: flex;
+      .message-text {
+        margin-right: 10px;
+      }
+    }
+    .is-other {
+      .message-text {
+        margin-left: 10px;
+      }
+    }
+  }
+}
+.bottom-info {
+  position: absolute;
+  bottom: 0;
+  width: 100%;
+  height: 160px;
+}
+input {
+  outline: none;
+  width: 748px;
+  height: 60px; 
+  max-width: 748px; 
+  overflow: auto;
+  white-space: pre-wrap;
+  &:focus {
+    border: none;
+  }
+}
+
+/* 滚动条样式 */
+::-webkit-scrollbar {
+  -webkit-appearance: none;
+  width: 0px;
+  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>

+ 157 - 0
src/views/recruit/personal/message/index.vue

@@ -0,0 +1,157 @@
+<template>
+  <div class="default-width message pa-3">
+    <div class="message-left">
+      <div class="message-left-search d-flex align-center justify-center">
+        <TextInput v-model="searchInputVal" :item="textItem" @appendInnerClick="handleSearch" @enter="handleSearch"></TextInput>
+      </div>
+      <div class="message-chat-box mt-5">
+        <div v-if="chatList.length">
+          <div class="chat-item d-flex align-center" v-for="(val, i) in chatList" :key="i">
+            <v-avatar :image="val.avatar || 'https://minio.citupro.com/dev/menduner/7.png'"></v-avatar>
+            <div class="ml-3 font-size-13">
+              <div class="chat-item-time color-999">17.25</div>
+              <div class="d-flex align-center">
+                <span class="font-size-15">{{ val.name }}</span>
+                <span class="ml-3 title-box">{{ val.enterpriseName }}</span>
+                <span class="septal-line"></span>
+                <span>{{ val.postName }}</span>
+              </div>
+              <div class="color-999 mt-1">{{ val.tip }}</div>
+            </div>
+          </div>
+          <div class="message-no-more-text mt-3">没有更多了</div>
+        </div>
+        <div v-else class="left-noData">
+          <Empty :elevation="false" message="暂无30天内联系人" width="300" height="150"></Empty>
+        </div>
+      </div>
+    </div>
+
+    <div class="message-right">
+      <div v-if="showRightNoData" class="right-noData">
+        <Empty :elevation="false" message="与您进行过沟通的 Boss 都会在左侧列表中显示"></Empty>
+      </div>
+      <Chatting></Chatting>
+    </div>
+  </div>
+</template>
+
+<script setup>
+defineOptions({ name: 'personal-message-index'})
+import { ref } from 'vue'
+import Chatting from './components/chatting.vue'
+// import { getChatKey } from '@/api/common'
+
+// const getKey = async () => {
+//   const data = await getChatKey({ userId: '1' })
+//   console.log(data, 'key')
+// }
+// getKey()
+
+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 chatList = ref([
+  {
+    id: 1,
+    name: '钟女士',
+    avatar: 'https://minio.citupro.com/dev/menduner/tx1.jpg',
+    enterpriseName: '泰康泰康泰康泰康泰康泰康',
+    postName: '人事HR',
+    updateTime: 1715337950000,
+    tip: '你好'
+  },
+  {
+    id: 2,
+    name: '林先生',
+    avatar: 'https://minio.citupro.com/dev/menduner/tx1.jpg',
+    enterpriseName: '众宝联合',
+    postName: '人力资源主管',
+    updateTime: 1715337950000,
+    tip: '你好'
+  }
+])
+
+const handleSearch = () => {
+  console.log(searchInputVal.value, 'search')
+}
+</script>
+
+<style scoped lang="scss">
+.message {
+  display: flex;
+  &-left {
+    position: relative;
+    width: 360px;
+    background-color: #fff;
+    border-radius: 8px;
+    height: calc(100vh - 74px);
+    margin-right: 12px;
+    .message-left-search {
+      width: 100%;
+      height: 60px;
+      background: linear-gradient(90deg, #f5fcfc, #fcfbfa);
+      border-radius: 8px 8px 0 0;
+    }
+    .message-chat-box {
+      .chat-item {
+        position: relative;
+        width: 100%;
+        height: 78px;
+        padding: 14px 12px;
+        cursor: pointer;
+        &:hover {
+          background-color: #f8f8f8;
+        }
+        .chat-item-time {
+          position: absolute;
+          right: 12px;
+          top: 50%;
+          transform: translateY(-50%);
+        }
+        .title-box {
+          max-width: 114px;
+          overflow: hidden;
+          white-space: nowrap;
+          text-overflow: ellipsis;
+          display: inline-block;
+        }
+      }
+    }
+    .message-no-more-text {
+      color: var(--color-999);
+      font-size: 14px;
+      text-align: center
+    }
+    .left-noData {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+    }
+  }
+  &-right {
+    flex: 1;
+    position: relative;
+    background-color: #fff;
+    border-radius: 8px;
+    height: calc(100vh - 74px);
+    .right-noData {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+    }
+  }
+}
+</style>