瀏覽代碼

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

lifanagju_citu 6 月之前
父節點
當前提交
94c6efba86

+ 8 - 9
src/config/axios/service.js

@@ -10,8 +10,6 @@ import { enterpriseRefreshToken, userRefreshToken } from '@/api/common'
 import { getToken, getRefreshToken, setToken, setRefreshToken, getIsEnterprise } from '@/utils/auth'
 import { rewardEventTrackClick } from '@/api/integral'
 import errorCode from './errorCode'
-import router from '@/router'
-
 import { useI18n } from '@/hooks/web/useI18n'
 
 // import { resetRouter } from '@/router'
@@ -158,7 +156,7 @@ service.interceptors.response.use(
         isRefreshToken = true
         // 1. 如果获取不到刷新令牌,则只能执行登出操作
         if (!getRefreshToken(tokenIndex)) {
-          return handleAuthorized()
+          return handleAuthorized(response)
         }
         // 2. 进行刷新访问令牌
         try {
@@ -184,7 +182,7 @@ service.interceptors.response.use(
           //   cb()
           // })
           // 提示是否要登出。即不回放当前请求!不然会形成递归
-          return handleAuthorized()
+          return handleAuthorized(response)
         } finally {
           requestList = []
           isRefreshToken = false
@@ -241,18 +239,19 @@ service.interceptors.response.use(
   }
 )
 
-const handleAuthorized = () => {
+const handleAuthorized = (response) => {
   const { t } = useI18n()
   const user = useUserStore()
   user.handleClearStorage() // 清除缓存
 
-  const path = router.currentRoute.value.fullPath
-  const hasRecommendedPath = path.includes('/recruit/enterprise/talentRecommendation')
+   // 公众号-人才推荐
+  const hasRecommendedPath = window.location.href.includes('/recruit/enterprise/talentRecommendation') || window.location.href.includes('/recruit/enterprise/talentRecommendation/details')
+  if (hasRecommendedPath) {
+    return Promise.reject(response.data)
+  }
 
   if (!isReLogin.show) {
     // 人才推荐页面不需要弹窗提示
-    if (hasRecommendedPath) return
-    // 如果已经到重新登录页面则不进行弹窗提示
     if (window.location.href.includes('login?redirect=')) {
       return
     }

+ 6 - 3
src/layout/company/side.vue

@@ -70,9 +70,10 @@ const getList = (arr, obj = []) => {
     if (element?.meta?.isAdmin) {
       data.isAdmin = true
     }
-    if (element?.meta?.isPersonMap) {
-      data.isPersonMap = true
-    }
+    // 人才地图
+    if (element?.meta?.isPersonMap) data.isPersonMap = true
+    // 全员猎聘
+    if (element?.meta?.hireJob) data.hireJob = true
     if (element?.children) {
       getList(element.children, data.children)
     }
@@ -86,6 +87,8 @@ const getList = (arr, obj = []) => {
   }
   // 人才地图是否可看
   if (info && Object.keys(info).length && !info?.entitlement?.personMap) obj = obj.filter(e => !e.isPersonMap)
+  // 全员猎聘是否可看
+  if (info && Object.keys(info).length && !info?.entitlement?.hireJob) obj = obj.filter(e => !e.hireJob)
   return obj
 }
 

+ 37 - 36
src/router/modules/components/recruit/enterprise.js

@@ -129,42 +129,43 @@ const enterprise = [
       },
     ]
   },
-  // {
-  //   path: '/recruit/enterprise/hirePosition',
-  //   component: Layout,
-  //   name: 'crowdSourcing',
-  //   meta: {
-  //     title: '全员猎聘',
-  //     enName: 'Crowd Sourcing',
-  //     icon: 'mdi-account-star-outline'
-  //   },
-  //   children: [
-  //     {
-  //       path: '/recruit/enterprise/hirePosition',
-  //       show: true,
-  //       meta: {
-  //         title: '全员猎聘'
-  //       },
-  //       component: () => import('@/views/recruit/enterprise/hirePosition/index.vue')
-  //     },
-  //     {
-  //       path: '/recruit/enterprise/hirePosition/add',
-  //       show: true,
-  //       meta: {
-  //         title: '新增职位'
-  //       },
-  //       component: () => import('@/views/recruit/enterprise/hirePosition/components/add.vue')
-  //     },
-  //     {
-  //       path: '/recruit/enterprise/hirePosition/edit',
-  //       show: true,
-  //       meta: {
-  //         title: '职位编辑'
-  //       },
-  //       component: () => import('@/views/recruit/enterprise/hirePosition/components/add.vue')
-  //     }
-  //   ]
-  // },
+  {
+    path: '/recruit/enterprise/hirePosition',
+    component: Layout,
+    name: 'crowdSourcing',
+    meta: {
+      title: '全员猎聘',
+      enName: 'Crowd Sourcing',
+      icon: 'mdi-account-star-outline',
+      hireJob: true
+    },
+    children: [
+      {
+        path: '/recruit/enterprise/hirePosition',
+        show: true,
+        meta: {
+          title: '全员猎聘'
+        },
+        component: () => import('@/views/recruit/enterprise/hirePosition/index.vue')
+      },
+      {
+        path: '/recruit/enterprise/hirePosition/add',
+        show: true,
+        meta: {
+          title: '新增职位'
+        },
+        component: () => import('@/views/recruit/enterprise/hirePosition/components/add.vue')
+      },
+      {
+        path: '/recruit/enterprise/hirePosition/edit',
+        show: true,
+        meta: {
+          title: '职位编辑'
+        },
+        component: () => import('@/views/recruit/enterprise/hirePosition/components/add.vue')
+      }
+    ]
+  },
   {
     path: '/recruit/enterprise/talentMap',
     component: Layout,

+ 1 - 1
src/views/mall/exchange.vue

@@ -77,7 +77,7 @@ const formItems = ref({
 // 数据
 const dataList = ref([
   { name: '房券-高端酒店房券', point: 12000, url: 'https://minio.menduner.com/dev/menduner/hotalRoomVoucher.png', type: 1 },
-  { name: '门墩儿酒店英语学习年卡', point: 8000, url: 'https://minio.menduner.com/dev/menduner/englishCourses.png', type: 0 },
+  { name: '门墩儿酒店英语学习年卡', point: 8000, url: 'https://minio.citupro.com/dev/menduner/englishCourses.jpg', type: 0 },
   { name: '红酒-经典年份葡萄酒', point: 5000, url: 'https://minio.menduner.com/dev/menduner/redWine.png', type: 1 },
   { name: '瑞幸咖啡券-瑞幸咖啡精致享受券', point: 2000, url: 'https://minio.menduner.com/dev/menduner/coffee.png', type: 0 },
   { name: '减压捏捏乐', point: 500, url: 'https://minio.menduner.com/dev/menduner/pinchMusic.png', type: 1 }

+ 21 - 30
src/views/recruit/enterprise/talentRecommendation/components/filter.vue

@@ -8,21 +8,11 @@
     <!-- 地区 -->
     <!-- <div>
       <div class="mb-2 font-size-20">地区</div>
-      <div class="color-666">
-        <v-menu :close-delay="1" :open-delay="0" v-bind="$attrs" :close-on-content-click="false">
-          <template v-slot:activator="{  props }">
-            <div>
-              <v-chip v-for="k in areaSelect" :key="k.id" class="mr-3 mb-1" closable label size="small" @click:close="handleAreaClear(k)">{{ k.name }}</v-chip>
-              <v-btn icon="mdi-plus" v-bind="props" variant="outlined" size="x-small"></v-btn>
-            </div>
-          </template>
-          <AreaSelect :isMobile="true" :select="areaSelectData" :currentData="areaSelect" :limit="false" showSelect @handleClick="handleArea"></AreaSelect>
-        </v-menu>
-      </div>
+      <Cascade :item="areaItem" v-model="query.areaIds"></Cascade>
     </div> -->
-    <!-- 职位 -->
+    <!-- 职位类型 -->
     <div class="my-3">
-      <div class="mb-2 font-size-20">职位</div>
+      <div class="mb-2 font-size-20">职位类型</div>
       <div class="d-flex font-size-14 flex-wrap">
         <div v-for="val in items" :key="val.id" class="mr-2 tag mb-2" :class="{'active': val.active}" @click="handleFirst(val)">{{ val.nameCn }}</div>
       </div>
@@ -75,6 +65,14 @@ const query = ref({
   expType: '',
   eduType: ''
 })
+// const areaItem = ref({
+//   itemText: 'name',
+//   itemValue: 'id',
+//   placeholder: '请选择工作城市',
+//   clearable: true,
+//   multiple: true,
+//   items: []
+// })
 const list = ref([
   {
     title: '最高学历',
@@ -96,6 +94,16 @@ list.value.forEach(e => {
     e.items = data || []
   })
 })
+// getDict('areaTreeData', null, 'areaTreeData').then(({ data }) => {
+//   data = data?.length && data || []
+//   console.log(data, 'data')
+//   areaItem.value.items = data.map(e => {
+//     e.children.forEach(k => {
+//       if (k?.children?.length) k.children = []
+//     })
+//     return e
+//   })
+// })
 
 // 职位树状
 const items = ref([])
@@ -128,22 +136,8 @@ const handleSecond = (val) => {
   if (index !== -1) {
     select.value.splice(index, 1)
   } else select.value.push(val)
-  // emit('select', select.value)
 }
 
-// 地区
-// const areaSelect = ref([])
-// const areaSelectData = ref([])
-// const handleArea = (list, arr) => {
-//   areaSelectData.value = list
-//   areaSelect.value = arr
-// }
-// const handleAreaClear = (k) => {
-//   areaSelectData.value = areaSelectData.value.filter(item => item !== k.id)
-//   const index = areaSelect.value.findIndex(item => item.id === k.id)
-//   if (index !== -1) areaSelect.value.splice(index, 1)
-// }
-
 // 清空
 const handleReset = () => {
   query.value = {
@@ -153,8 +147,6 @@ const handleReset = () => {
     expType: '',
     eduType: ''
   }
-  // areaSelectData.value = []
-  // areaSelect.value = []
   select.value = []
   list.value.forEach(e => e.value = -1)
   items.value.forEach(e => e.active = false)
@@ -168,7 +160,6 @@ const checkValue = (obj) => {
 
 const handleConfirm = () => {
   query.value.content = textItem.value.value
-  // query.value.areaIds = areaSelectData.value
   query.value.positionIds = select.value.map(e => e.id)
   list.value.forEach(e => query.value[e.key] = e.value === -1 ? '' : e.value)
 

+ 8 - 8
src/views/recruit/enterprise/talentRecommendation/details.vue

@@ -24,11 +24,6 @@
         <span class="mr-6">{{ $t('resume.workExperience') }}</span>
         <workExperience style="flex: 1;" :data="cvData.workList"></workExperience>
       </div>
-      <!-- 项目经历 -->
-      <!-- <div class="d-flex mt-8" v-if="cvData?.projectList?.length">
-        <span class="mr-6">{{ $t('resume.projectExperience') }}</span>
-        <projectExperience style="flex: 1;" :data="cvData.projectList"></projectExperience>
-      </div> -->
       <!-- 培训经历 -->
       <div class="d-flex mt-8" v-if="cvData?.trainList?.length">
         <span class="mr-6">{{ $t('resume.trainingExperience') }}</span>
@@ -49,7 +44,6 @@ import baseInfo from './details/baseInfo.vue'
 import vocationalSkills from '@/views/recruit/enterprise/talentPool/components/details/vocationalSkills.vue'
 import jobIntention from '@/views/recruit/enterprise/talentPool/components/details/jobIntention.vue'
 import workExperience from '@/views/recruit/enterprise/talentPool/components/details/workExperience.vue'
-// import projectExperience from '@/views/recruit/enterprise/talentPool/components/details/projectExperience.vue'
 import trainingExperience from '@/views/recruit/enterprise/talentPool/components/details/trainingExperience.vue'
 import educationExp from '@/views/recruit/enterprise/talentPool/components/details/educationExp.vue'
 import { getPersonCvDetail } from '@/api/enterprise'
@@ -65,8 +59,14 @@ const getCvDetail = async () => {
   const { id } = route.query
   const { id: userId } = router.currentRoute.value.params
   if (!id || !userId) return
-  const data = await getPersonCvDetail(userId, id)
-  cvData.value = data
+  try {
+    const data = await getPersonCvDetail(userId, id)
+    cvData.value = data
+  } catch (err) {
+    if (err.code === 401) {
+      router.push('/recruit/enterprise/talentRecommendation')
+    }
+  }
 }
 getCvDetail()
 

+ 86 - 20
src/views/recruit/enterprise/talentRecommendation/index.vue

@@ -1,6 +1,6 @@
 <template>
   <div>
-    <div v-if="!getToken(1)" class="login-content">
+    <div v-if="showLogin" class="login-content">
       <div class="login-content-box pa-10">
         <div class="login-content-box-title text-center mt-4">请先登录您的企业账号</div>
         <passwordFrom class="mt-10" ref="passRef" placeholder="请输入企业邮箱" :validEmail="true"></passwordFrom>
@@ -10,9 +10,24 @@
     <div v-else class="content py-3" @scroll="handleScroll">
       <v-card class="py-3 px-5" :style="{'width': isMobile ? '100%' : '750px'}" style="min-height: calc(100vh - 24px); box-sizing: border-box; margin: 0 auto;">
         <div class="d-flex align-center" style="width: 340px; margin: auto;">
-          <!-- <Autocomplete v-model="query.jobId" :item="selectItems" @change="handleChange"></Autocomplete> -->
           <TextInput v-model="textItem.value" :item="textItem" @click="openDrawer" @appendInnerClick="openDrawer"></TextInput>
         </div>
+        <!-- 已发布职位 -->
+        <div class="text-end mb-1">
+          <v-menu transition="slide-y-transition">
+            <template v-slot:activator="{ props, isActive }">
+              <v-btn color="primary" v-bind="props" variant="tonal" :append-icon="isActive ? 'mdi mdi-menu-up' : 'mdi mdi-menu-down'">
+                {{ query?.jobId ? select : '选择已发布职位推荐' }}
+              </v-btn>
+            </template>
+            <v-list>
+              <v-list-item v-for="k in selectItems.items" :key="k.value" color="primary" :active="k.value === query?.jobId">
+                <v-list-item-title @click="handleActive(k)">{{ k.label }}</v-list-item-title>
+              </v-list-item>
+            </v-list>
+          </v-menu>
+          <v-icon v-if="query?.jobId" color="primary" size="30" class="ml-3" @click="handleClearJob">mdi-close-circle-outline</v-icon>
+        </div>
         <v-divider></v-divider>
         <div v-if="items.length">
           <div v-for="(val, index) in items" :key="val.id" @click="handleDetail(val)">
@@ -49,12 +64,11 @@
 <script setup>
 defineOptions({ name: 'talentRecommendation'})
 import { ref, onMounted } from 'vue'
-import { getToken } from '@/utils/auth'
 import passwordFrom from '@/views/login/components/passwordPage.vue'
 import Snackbar from '@/plugins/snackbar'
 import { useUserStore } from '@/store/user'
 import { passwordLogin } from '@/api/common'
-import { getJobAdvertised, getPersonSearchPage } from '@/api/enterprise'
+import { getJobAdvertised, getPersonSearchPage, getPersonRecommendPage } from '@/api/enterprise'
 import { dealDictArrayData } from '@/utils/position'
 import { getUserAvatar } from '@/utils/avatar'
 import { useRouter } from 'vue-router'
@@ -67,9 +81,12 @@ const items = ref([])
 const total = ref(0)
 const query = ref({
   pageNo: 1,
-  pageSize: 20,
-  // jobId: null
+  pageSize: 20
 })
+const select = ref('')
+const screen1 = ref(false)
+const token = ref(localStorage.getItem('ENT_ACCESS_TOKEN'))
+const showLogin = ref(token.value ? false : true)
 const selectItems = ref({
   label: '已发布职位',
   placeholder: '请选择要进行推荐的职位',
@@ -98,10 +115,30 @@ const getJobList = async () => {
   }
 }
 
-// 组件挂载后添加事件监听器  
+const handleActive = (k) => {
+  select.value = k.label
+  query.value.jobId = k.value
+  query.value.pageNo = 1
+  screen1.value = false
+  getData(true)
+}
+
+const handleClearJob = () => {
+  select.value = ''
+  delete query.value.jobId
+  query.value.pageNo = 1
+  items.value = []
+  total.value = 0
+  screen1.value = false
+}
+
+// 组件挂载后添加事件监听器
 const isMobile = ref(false)
 onMounted(() => {
-  if (!getToken(1)) Snackbar.warning('请先登录')
+  if (!token.value) {
+    Snackbar.warning('请先登录')
+    showLogin.value = true
+  }
   else getJobList()
   const userAgent = navigator.userAgent
   isMobile.value = /(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i.test(userAgent)
@@ -112,20 +149,35 @@ const getData = async (isEmpty) => {
   // isEmpty:是否清空列表
   loading.value = true
   try {
-    const res = await getPersonSearchPage(query.value)
+    const res = query.value?.jobId ? await getPersonRecommendPage(query.value) : await getPersonSearchPage(query.value)
     const list = res.list || []
     items.value = list.length ? !isEmpty ? [...items.value, ...dealDictArrayData([], list)] : dealDictArrayData([], list) : []
     total.value = res.total
   } catch (err) {
     loading.value = false
+    if (err.code === 401) {
+      items.value = []
+      total.value = 0
+      token.value = null
+      query.value = {
+        pageNo: 1,
+        pageSize: 20
+      }
+      Snackbar.warning('请先登录')
+      showLogin.value = true
+    }
   } finally {
     loading.value = false
   }
 }
-// if (getToken(1)) getData()
 
 // 底部加载
 const handleScroll = (e) => {
+  if (!token.value) {
+    showLogin.value = true
+    Snackbar.warning('请先登录')
+    return
+  }
   if (e.srcElement.scrollTop + e.srcElement.clientHeight > e.srcElement.scrollHeight - 10) {
     if (items.value.length < total.value) {
       query.value.pageNo++
@@ -134,12 +186,6 @@ const handleScroll = (e) => {
   }
 }
 
-// 选择发布职位
-// const handleChange = () => {
-//   query.value.pageNo = 1
-//   getData(true)
-// }
-
 // 登录
 const handleLogin = async () => {
   const { valid } = await passRef.value.passwordForm.validate()
@@ -149,9 +195,12 @@ const handleLogin = async () => {
     const data = await passwordLogin({ ...passRef.value.loginData, account: passRef.value.loginData.phone })
     await useUserStore().changeRole({ ...data, type: 'emailLogin', noJump: true })
     await getJobList()
-    // await getData()
+    if (data?.accessToken) {
+      token.value = data?.accessToken
+      showLogin.value = false
+    }
   } catch (err) {
-    Snackbar.warning(err.msg)
+    Snackbar.warning(err)
   } finally {
     loading.value = false
   }
@@ -159,6 +208,11 @@ const handleLogin = async () => {
 
 // 人才详情
 const handleDetail = ({ userId, id }) => {
+  if (!token.value) {
+    showLogin.value = true
+    Snackbar.warning('请先登录')
+    return
+  }
   if (!userId || !id) return
   router.push(`/recruit/enterprise/talentRecommendation/details/${userId}?id=${id}`)
 }
@@ -167,12 +221,20 @@ const handleDetail = ({ userId, id }) => {
 const filterRef = ref()
 const screen = ref(false)
 const openDrawer = () => {
-  if (!getToken(1)) return
+  if (!token.value) {
+    showLogin.value = true
+    Snackbar.warning('请先登录')
+    return
+  }
   screen.value = true
 }
 
 const handleSearch = (val) => {
-  // console.log(val, 'search')
+  if (!token.value) {
+    showLogin.value = true
+    Snackbar.warning('请先登录')
+    return
+  }
   screen.value = false
   textItem.value.value = val.content
   query.value.pageNo = 1
@@ -212,4 +274,8 @@ const handleSearch = (val) => {
   height: 10px;
   background-color: #f2f4f7;
 }
+.active {
+  color: var(--v-primary-base);
+  border: 2px solid var(--v-primary-base);
+}
 </style>